diff --git a/resources/misc/previewInterop.js b/resources/misc/previewInterop.js
index b77d2af2f79eaaf6f0ff9052c657b6cca622c496..aadaf9fac5547d9d4d5049bf9b42d400d7c6c570 100644
--- a/resources/misc/previewInterop.js
+++ b/resources/misc/previewInterop.js
@@ -10,8 +10,9 @@ function getPreviewInfo(messageId, url) {
     var title = null
     var description = null
     var image = null
-    if (!url.includes("http://") && !url.includes("https://")) {
-        url = "http://".concat(url)
+    var u = new URL(url)
+    if (u.protocol === '') {
+        url = "https://".concat(url)
     }
     var domain = (new URL(url))
     fetch(url, {
diff --git a/src/libclient/web-chatview/linkify-string.js b/src/libclient/web-chatview/linkify-string.js
index 78c83e524902bab8e049f6d0d72eeab1612c7cdb..b7fb03f8991333cda04175c308b5d94cbbb1e0e0 100644
--- a/src/libclient/web-chatview/linkify-string.js
+++ b/src/libclient/web-chatview/linkify-string.js
@@ -1,118 +1,100 @@
 /*
- * Copyright (c) 2016 SoapBox Innovations Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+* Copyright (c) 2021 SoapBox Innovations Inc.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
 */
-
-'use strict';
-
-;(function (window, linkify) {
-	var linkifyString = function (linkify) {
-		'use strict';
-
-		/**
-  	Convert strings of text into linkable HTML text
-  */
-
-		var tokenize = linkify.tokenize;
-		var options = linkify.options;
-		var Options = options.Options;
-
-
-		function escapeText(text) {
-			return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
-		}
-
-		function escapeAttr(href) {
-			return href.replace(/"/g, '&quot;');
-		}
-
-		function attributesToString(attributes) {
-			if (!attributes) {
-				return '';
-			}
-			var result = [];
-
-			for (var attr in attributes) {
-				var val = attributes[attr] + '';
-				result.push(attr + '="' + escapeAttr(val) + '"');
-			}
-			return result.join(' ');
-		}
-
-        function linkifyStr(str, color='#0645AD') {
-			var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
-
-			opts = new Options(opts);
-
-			var tokens = tokenize(str);
-			var result = [];
-
-			for (var i = 0; i < tokens.length; i++) {
-				var token = tokens[i];
-
-				if (token.type === 'nl' && opts.nl2br) {
-					result.push('<br>\n');
-					continue;
-				} else if (!token.isLink || !opts.check(token)) {
-					result.push(escapeText(token.toString()));
-					continue;
-				}
-
-				var _opts$resolve = opts.resolve(token);
-
-				var formatted = _opts$resolve.formatted;
-				var formattedHref = _opts$resolve.formattedHref;
-				var tagName = _opts$resolve.tagName;
-				var className = _opts$resolve.className;
-				var target = _opts$resolve.target;
-				var attributes = _opts$resolve.attributes;
-
-
-				var link = '<' + tagName + ' href="' + escapeAttr(formattedHref) + '"';
-
-				if (className) {
-					link += ' class="' + escapeAttr(className) + '"';
-				}
-
-				if (target) {
-					link += ' target="' + escapeAttr(target) + '"';
-				}
-
-				if (attributes) {
-					link += ' ' + attributesToString(attributes);
-				}
-
-                link += ' style="color: '+color+';">' + escapeText(formatted) + '</' + tagName + '>';
-				result.push(link);
-			}
-
-			return result.join('');
-		}
-
-		if (!String.prototype.linkify) {
-			String.prototype.linkify = function (opts) {
-				return linkifyStr(this, opts);
-			};
-		}
-
-		return linkifyStr;
-	}(linkify);
-	window.linkifyStr = linkifyString;
-})(window, linkify);
+var linkifyStr = (function (linkifyjs) {
+	'use strict';
+
+	/**
+		Convert strings of text into linkable HTML text
+	*/
+
+	function escapeText(text) {
+	  return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+	}
+
+	function escapeAttr(href) {
+	  return href.replace(/"/g, '&quot;');
+	}
+
+	function attributesToString(attributes) {
+	  var result = [];
+
+	  for (var attr in attributes) {
+	    var val = attributes[attr] + '';
+	    result.push(attr + "=\"" + escapeAttr(val) + "\"");
+	  }
+
+	  return result.join(' ');
+	}
+
+	function defaultRender(_ref) {
+	  var tagName = _ref.tagName,
+	      attributes = _ref.attributes,
+	      content = _ref.content;
+	  return "<" + tagName + " " + attributesToString(attributes) + ">" + escapeText(content) + "</" + tagName + ">";
+	}
+	/**
+	 * Convert a plan text string to an HTML string with links. Expects that the
+	 * given strings does not contain any HTML entities. Use the linkify-html
+	 * interface if you need to parse HTML entities.
+	 *
+	 * @param {string} str string to linkify
+	 * @param {import('linkifyjs').Opts} [opts] overridable options
+	 * @returns {string}
+	 */
+
+
+	function linkifyStr(str, opts) {
+	  if (opts === void 0) {
+	    opts = {};
+	  }
+
+	  opts = new linkifyjs.Options(opts, defaultRender);
+	  var tokens = linkifyjs.tokenize(str);
+	  var result = [];
+
+	  for (var i = 0; i < tokens.length; i++) {
+	    var token = tokens[i];
+
+	    if (token.t === 'nl' && opts.get('nl2br')) {
+	      result.push('<br>\n');
+	    } else if (!token.isLink || !opts.check(token)) {
+	      result.push(escapeText(token.toString()));
+	    } else {
+	      result.push(opts.render(token));
+	    }
+	  }
+
+	  return result.join('');
+	}
+
+	if (!String.prototype.linkify) {
+	  Object.defineProperty(String.prototype, 'linkify', {
+	    writable: false,
+	    value: function linkify(options) {
+	      return linkifyStr(this, options);
+	    }
+	  });
+	}
+
+	return linkifyStr;
+
+})(linkify);
\ No newline at end of file
diff --git a/src/libclient/web-chatview/linkify.js b/src/libclient/web-chatview/linkify.js
index 7666ff8aeb8b2e1960467601bc1a157b98b80f7b..7363cbdb42424d37879257ae390deeb917deeff7 100644
--- a/src/libclient/web-chatview/linkify.js
+++ b/src/libclient/web-chatview/linkify.js
@@ -1,584 +1,2023 @@
 /*
- * Copyright (c) 2016 SoapBox Innovations Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+* Copyright (c) 2021 SoapBox Innovations Inc.
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
 */
+var linkify = (function (exports) {
+	'use strict';
 
-;(function () {
-'use strict';
-
-var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+	// THIS FILE IS AUTOMATICALLY GENERATED DO NOT EDIT DIRECTLY
+	// https://data.iana.org/TLD/tlds-alpha-by-domain.txt
+	var tlds = 'aaa \
+aarp \
+abarth \
+abb \
+abbott \
+abbvie \
+abc \
+able \
+abogado \
+abudhabi \
+ac \
+academy \
+accenture \
+accountant \
+accountants \
+aco \
+actor \
+ad \
+adac \
+ads \
+adult \
+ae \
+aeg \
+aero \
+aetna \
+af \
+afl \
+africa \
+ag \
+agakhan \
+agency \
+ai \
+aig \
+airbus \
+airforce \
+airtel \
+akdn \
+al \
+alfaromeo \
+alibaba \
+alipay \
+allfinanz \
+allstate \
+ally \
+alsace \
+alstom \
+am \
+amazon \
+americanexpress \
+americanfamily \
+amex \
+amfam \
+amica \
+amsterdam \
+analytics \
+android \
+anquan \
+anz \
+ao \
+aol \
+apartments \
+app \
+apple \
+aq \
+aquarelle \
+ar \
+arab \
+aramco \
+archi \
+army \
+arpa \
+art \
+arte \
+as \
+asda \
+asia \
+associates \
+at \
+athleta \
+attorney \
+au \
+auction \
+audi \
+audible \
+audio \
+auspost \
+author \
+auto \
+autos \
+avianca \
+aw \
+aws \
+ax \
+axa \
+az \
+azure \
+ba \
+baby \
+baidu \
+banamex \
+bananarepublic \
+band \
+bank \
+bar \
+barcelona \
+barclaycard \
+barclays \
+barefoot \
+bargains \
+baseball \
+basketball \
+bauhaus \
+bayern \
+bb \
+bbc \
+bbt \
+bbva \
+bcg \
+bcn \
+bd \
+be \
+beats \
+beauty \
+beer \
+bentley \
+berlin \
+best \
+bestbuy \
+bet \
+bf \
+bg \
+bh \
+bharti \
+bi \
+bible \
+bid \
+bike \
+bing \
+bingo \
+bio \
+biz \
+bj \
+black \
+blackfriday \
+blockbuster \
+blog \
+bloomberg \
+blue \
+bm \
+bms \
+bmw \
+bn \
+bnpparibas \
+bo \
+boats \
+boehringer \
+bofa \
+bom \
+bond \
+boo \
+book \
+booking \
+bosch \
+bostik \
+boston \
+bot \
+boutique \
+box \
+br \
+bradesco \
+bridgestone \
+broadway \
+broker \
+brother \
+brussels \
+bs \
+bt \
+bugatti \
+build \
+builders \
+business \
+buy \
+buzz \
+bv \
+bw \
+by \
+bz \
+bzh \
+ca \
+cab \
+cafe \
+cal \
+call \
+calvinklein \
+cam \
+camera \
+camp \
+cancerresearch \
+canon \
+capetown \
+capital \
+capitalone \
+car \
+caravan \
+cards \
+care \
+career \
+careers \
+cars \
+casa \
+case \
+cash \
+casino \
+cat \
+catering \
+catholic \
+cba \
+cbn \
+cbre \
+cbs \
+cc \
+cd \
+center \
+ceo \
+cern \
+cf \
+cfa \
+cfd \
+cg \
+ch \
+chanel \
+channel \
+charity \
+chase \
+chat \
+cheap \
+chintai \
+christmas \
+chrome \
+church \
+ci \
+cipriani \
+circle \
+cisco \
+citadel \
+citi \
+citic \
+city \
+cityeats \
+ck \
+cl \
+claims \
+cleaning \
+click \
+clinic \
+clinique \
+clothing \
+cloud \
+club \
+clubmed \
+cm \
+cn \
+co \
+coach \
+codes \
+coffee \
+college \
+cologne \
+com \
+comcast \
+commbank \
+community \
+company \
+compare \
+computer \
+comsec \
+condos \
+construction \
+consulting \
+contact \
+contractors \
+cooking \
+cookingchannel \
+cool \
+coop \
+corsica \
+country \
+coupon \
+coupons \
+courses \
+cpa \
+cr \
+credit \
+creditcard \
+creditunion \
+cricket \
+crown \
+crs \
+cruise \
+cruises \
+cu \
+cuisinella \
+cv \
+cw \
+cx \
+cy \
+cymru \
+cyou \
+cz \
+dabur \
+dad \
+dance \
+data \
+date \
+dating \
+datsun \
+day \
+dclk \
+dds \
+de \
+deal \
+dealer \
+deals \
+degree \
+delivery \
+dell \
+deloitte \
+delta \
+democrat \
+dental \
+dentist \
+desi \
+design \
+dev \
+dhl \
+diamonds \
+diet \
+digital \
+direct \
+directory \
+discount \
+discover \
+dish \
+diy \
+dj \
+dk \
+dm \
+dnp \
+do \
+docs \
+doctor \
+dog \
+domains \
+dot \
+download \
+drive \
+dtv \
+dubai \
+dunlop \
+dupont \
+durban \
+dvag \
+dvr \
+dz \
+earth \
+eat \
+ec \
+eco \
+edeka \
+edu \
+education \
+ee \
+eg \
+email \
+emerck \
+energy \
+engineer \
+engineering \
+enterprises \
+epson \
+equipment \
+er \
+ericsson \
+erni \
+es \
+esq \
+estate \
+et \
+etisalat \
+eu \
+eurovision \
+eus \
+events \
+exchange \
+expert \
+exposed \
+express \
+extraspace \
+fage \
+fail \
+fairwinds \
+faith \
+family \
+fan \
+fans \
+farm \
+farmers \
+fashion \
+fast \
+fedex \
+feedback \
+ferrari \
+ferrero \
+fi \
+fiat \
+fidelity \
+fido \
+film \
+final \
+finance \
+financial \
+fire \
+firestone \
+firmdale \
+fish \
+fishing \
+fit \
+fitness \
+fj \
+fk \
+flickr \
+flights \
+flir \
+florist \
+flowers \
+fly \
+fm \
+fo \
+foo \
+food \
+foodnetwork \
+football \
+ford \
+forex \
+forsale \
+forum \
+foundation \
+fox \
+fr \
+free \
+fresenius \
+frl \
+frogans \
+frontdoor \
+frontier \
+ftr \
+fujitsu \
+fun \
+fund \
+furniture \
+futbol \
+fyi \
+ga \
+gal \
+gallery \
+gallo \
+gallup \
+game \
+games \
+gap \
+garden \
+gay \
+gb \
+gbiz \
+gd \
+gdn \
+ge \
+gea \
+gent \
+genting \
+george \
+gf \
+gg \
+ggee \
+gh \
+gi \
+gift \
+gifts \
+gives \
+giving \
+gl \
+glass \
+gle \
+global \
+globo \
+gm \
+gmail \
+gmbh \
+gmo \
+gmx \
+gn \
+godaddy \
+gold \
+goldpoint \
+golf \
+goo \
+goodyear \
+goog \
+google \
+gop \
+got \
+gov \
+gp \
+gq \
+gr \
+grainger \
+graphics \
+gratis \
+green \
+gripe \
+grocery \
+group \
+gs \
+gt \
+gu \
+guardian \
+gucci \
+guge \
+guide \
+guitars \
+guru \
+gw \
+gy \
+hair \
+hamburg \
+hangout \
+haus \
+hbo \
+hdfc \
+hdfcbank \
+health \
+healthcare \
+help \
+helsinki \
+here \
+hermes \
+hgtv \
+hiphop \
+hisamitsu \
+hitachi \
+hiv \
+hk \
+hkt \
+hm \
+hn \
+hockey \
+holdings \
+holiday \
+homedepot \
+homegoods \
+homes \
+homesense \
+honda \
+horse \
+hospital \
+host \
+hosting \
+hot \
+hoteles \
+hotels \
+hotmail \
+house \
+how \
+hr \
+hsbc \
+ht \
+hu \
+hughes \
+hyatt \
+hyundai \
+ibm \
+icbc \
+ice \
+icu \
+id \
+ie \
+ieee \
+ifm \
+ikano \
+il \
+im \
+imamat \
+imdb \
+immo \
+immobilien \
+in \
+inc \
+industries \
+infiniti \
+info \
+ing \
+ink \
+institute \
+insurance \
+insure \
+int \
+international \
+intuit \
+investments \
+io \
+ipiranga \
+iq \
+ir \
+irish \
+is \
+ismaili \
+ist \
+istanbul \
+it \
+itau \
+itv \
+jaguar \
+java \
+jcb \
+je \
+jeep \
+jetzt \
+jewelry \
+jio \
+jll \
+jm \
+jmp \
+jnj \
+jo \
+jobs \
+joburg \
+jot \
+joy \
+jp \
+jpmorgan \
+jprs \
+juegos \
+juniper \
+kaufen \
+kddi \
+ke \
+kerryhotels \
+kerrylogistics \
+kerryproperties \
+kfh \
+kg \
+kh \
+ki \
+kia \
+kids \
+kim \
+kinder \
+kindle \
+kitchen \
+kiwi \
+km \
+kn \
+koeln \
+komatsu \
+kosher \
+kp \
+kpmg \
+kpn \
+kr \
+krd \
+kred \
+kuokgroup \
+kw \
+ky \
+kyoto \
+kz \
+la \
+lacaixa \
+lamborghini \
+lamer \
+lancaster \
+lancia \
+land \
+landrover \
+lanxess \
+lasalle \
+lat \
+latino \
+latrobe \
+law \
+lawyer \
+lb \
+lc \
+lds \
+lease \
+leclerc \
+lefrak \
+legal \
+lego \
+lexus \
+lgbt \
+li \
+lidl \
+life \
+lifeinsurance \
+lifestyle \
+lighting \
+like \
+lilly \
+limited \
+limo \
+lincoln \
+linde \
+link \
+lipsy \
+live \
+living \
+lk \
+llc \
+llp \
+loan \
+loans \
+locker \
+locus \
+loft \
+lol \
+london \
+lotte \
+lotto \
+love \
+lpl \
+lplfinancial \
+lr \
+ls \
+lt \
+ltd \
+ltda \
+lu \
+lundbeck \
+luxe \
+luxury \
+lv \
+ly \
+ma \
+macys \
+madrid \
+maif \
+maison \
+makeup \
+man \
+management \
+mango \
+map \
+market \
+marketing \
+markets \
+marriott \
+marshalls \
+maserati \
+mattel \
+mba \
+mc \
+mckinsey \
+md \
+me \
+med \
+media \
+meet \
+melbourne \
+meme \
+memorial \
+men \
+menu \
+merckmsd \
+mg \
+mh \
+miami \
+microsoft \
+mil \
+mini \
+mint \
+mit \
+mitsubishi \
+mk \
+ml \
+mlb \
+mls \
+mm \
+mma \
+mn \
+mo \
+mobi \
+mobile \
+moda \
+moe \
+moi \
+mom \
+monash \
+money \
+monster \
+mormon \
+mortgage \
+moscow \
+moto \
+motorcycles \
+mov \
+movie \
+mp \
+mq \
+mr \
+ms \
+msd \
+mt \
+mtn \
+mtr \
+mu \
+museum \
+music \
+mutual \
+mv \
+mw \
+mx \
+my \
+mz \
+na \
+nab \
+nagoya \
+name \
+natura \
+navy \
+nba \
+nc \
+ne \
+nec \
+net \
+netbank \
+netflix \
+network \
+neustar \
+new \
+news \
+next \
+nextdirect \
+nexus \
+nf \
+nfl \
+ng \
+ngo \
+nhk \
+ni \
+nico \
+nike \
+nikon \
+ninja \
+nissan \
+nissay \
+nl \
+no \
+nokia \
+northwesternmutual \
+norton \
+now \
+nowruz \
+nowtv \
+np \
+nr \
+nra \
+nrw \
+ntt \
+nu \
+nyc \
+nz \
+obi \
+observer \
+office \
+okinawa \
+olayan \
+olayangroup \
+oldnavy \
+ollo \
+om \
+omega \
+one \
+ong \
+onl \
+online \
+ooo \
+open \
+oracle \
+orange \
+org \
+organic \
+origins \
+osaka \
+otsuka \
+ott \
+ovh \
+pa \
+page \
+panasonic \
+paris \
+pars \
+partners \
+parts \
+party \
+passagens \
+pay \
+pccw \
+pe \
+pet \
+pf \
+pfizer \
+pg \
+ph \
+pharmacy \
+phd \
+philips \
+phone \
+photo \
+photography \
+photos \
+physio \
+pics \
+pictet \
+pictures \
+pid \
+pin \
+ping \
+pink \
+pioneer \
+pizza \
+pk \
+pl \
+place \
+play \
+playstation \
+plumbing \
+plus \
+pm \
+pn \
+pnc \
+pohl \
+poker \
+politie \
+porn \
+post \
+pr \
+pramerica \
+praxi \
+press \
+prime \
+pro \
+prod \
+productions \
+prof \
+progressive \
+promo \
+properties \
+property \
+protection \
+pru \
+prudential \
+ps \
+pt \
+pub \
+pw \
+pwc \
+py \
+qa \
+qpon \
+quebec \
+quest \
+racing \
+radio \
+re \
+read \
+realestate \
+realtor \
+realty \
+recipes \
+red \
+redstone \
+redumbrella \
+rehab \
+reise \
+reisen \
+reit \
+reliance \
+ren \
+rent \
+rentals \
+repair \
+report \
+republican \
+rest \
+restaurant \
+review \
+reviews \
+rexroth \
+rich \
+richardli \
+ricoh \
+ril \
+rio \
+rip \
+ro \
+rocher \
+rocks \
+rodeo \
+rogers \
+room \
+rs \
+rsvp \
+ru \
+rugby \
+ruhr \
+run \
+rw \
+rwe \
+ryukyu \
+sa \
+saarland \
+safe \
+safety \
+sakura \
+sale \
+salon \
+samsclub \
+samsung \
+sandvik \
+sandvikcoromant \
+sanofi \
+sap \
+sarl \
+sas \
+save \
+saxo \
+sb \
+sbi \
+sbs \
+sc \
+sca \
+scb \
+schaeffler \
+schmidt \
+scholarships \
+school \
+schule \
+schwarz \
+science \
+scot \
+sd \
+se \
+search \
+seat \
+secure \
+security \
+seek \
+select \
+sener \
+services \
+ses \
+seven \
+sew \
+sex \
+sexy \
+sfr \
+sg \
+sh \
+shangrila \
+sharp \
+shaw \
+shell \
+shia \
+shiksha \
+shoes \
+shop \
+shopping \
+shouji \
+show \
+showtime \
+si \
+silk \
+sina \
+singles \
+site \
+sj \
+sk \
+ski \
+skin \
+sky \
+skype \
+sl \
+sling \
+sm \
+smart \
+smile \
+sn \
+sncf \
+so \
+soccer \
+social \
+softbank \
+software \
+sohu \
+solar \
+solutions \
+song \
+sony \
+soy \
+spa \
+space \
+sport \
+spot \
+sr \
+srl \
+ss \
+st \
+stada \
+staples \
+star \
+statebank \
+statefarm \
+stc \
+stcgroup \
+stockholm \
+storage \
+store \
+stream \
+studio \
+study \
+style \
+su \
+sucks \
+supplies \
+supply \
+support \
+surf \
+surgery \
+suzuki \
+sv \
+swatch \
+swiss \
+sx \
+sy \
+sydney \
+systems \
+sz \
+tab \
+taipei \
+talk \
+taobao \
+target \
+tatamotors \
+tatar \
+tattoo \
+tax \
+taxi \
+tc \
+tci \
+td \
+tdk \
+team \
+tech \
+technology \
+tel \
+temasek \
+tennis \
+teva \
+tf \
+tg \
+th \
+thd \
+theater \
+theatre \
+tiaa \
+tickets \
+tienda \
+tiffany \
+tips \
+tires \
+tirol \
+tj \
+tjmaxx \
+tjx \
+tk \
+tkmaxx \
+tl \
+tm \
+tmall \
+tn \
+to \
+today \
+tokyo \
+tools \
+top \
+toray \
+toshiba \
+total \
+tours \
+town \
+toyota \
+toys \
+tr \
+trade \
+trading \
+training \
+travel \
+travelchannel \
+travelers \
+travelersinsurance \
+trust \
+trv \
+tt \
+tube \
+tui \
+tunes \
+tushu \
+tv \
+tvs \
+tw \
+tz \
+ua \
+ubank \
+ubs \
+ug \
+uk \
+unicom \
+university \
+uno \
+uol \
+ups \
+us \
+uy \
+uz \
+va \
+vacations \
+vana \
+vanguard \
+vc \
+ve \
+vegas \
+ventures \
+verisign \
+vermögensberater \
+vermögensberatung \
+versicherung \
+vet \
+vg \
+vi \
+viajes \
+video \
+vig \
+viking \
+villas \
+vin \
+vip \
+virgin \
+visa \
+vision \
+viva \
+vivo \
+vlaanderen \
+vn \
+vodka \
+volkswagen \
+volvo \
+vote \
+voting \
+voto \
+voyage \
+vu \
+vuelos \
+wales \
+walmart \
+walter \
+wang \
+wanggou \
+watch \
+watches \
+weather \
+weatherchannel \
+webcam \
+weber \
+website \
+wed \
+wedding \
+weibo \
+weir \
+wf \
+whoswho \
+wien \
+wiki \
+williamhill \
+win \
+windows \
+wine \
+winners \
+wme \
+wolterskluwer \
+woodside \
+work \
+works \
+world \
+wow \
+ws \
+wtc \
+wtf \
+xbox \
+xerox \
+xfinity \
+xihuan \
+xin \
+xxx \
+xyz \
+yachts \
+yahoo \
+yamaxun \
+yandex \
+ye \
+yodobashi \
+yoga \
+yokohama \
+you \
+youtube \
+yt \
+yun \
+za \
+zappos \
+zara \
+zero \
+zip \
+zm \
+zone \
+zuerich \
+zw'.split(' '); // Internationalized domain names containing non-ASCII
+
+	var utlds = 'ελ \
+ευ \
+бг \
+бел \
+дети \
+ею \
+католик \
+ком \
+мкд \
+мон \
+москва \
+онлайн \
+орг \
+рус \
+рф \
+сайт \
+срб \
+укр \
+қаз \
+Õ°Õ¡Õµ \
+ישראל \
+קום \
+ابوظبي \
+اتصالات \
+ارامكو \
+الاردن \
+البحرين \
+الجزائر \
+السعودية \
+العليان \
+المغرب \
+امارات \
+ایران \
+بارت \
+بازار \
+بيتك \
+بھارت \
+تونس \
+سودان \
+سورية \
+شبكة \
+عراق \
+عرب \
+عمان \
+فلسطين \
+قطر \
+كاثوليك \
+كوم \
+مصر \
+مليسيا \
+موريتانيا \
+موقع \
+همراه \
+پاکستان \
+ڀارت \
+कॉम \
+नेट \
+भारत \
+भारतम् \
+भारोत \
+संगठन \
+বাংলা \
+ভারত \
+ভাৰত \
+ਭਾਰਤ \
+ભારત \
+ଭାରତ \
+இந்தியா \
+இலங்கை \
+சிங்கப்பூர் \
+భారత్ \
+ಭಾರತ \
+ഭാരതം \
+ලංකා \
+คอม \
+ไทย \
+ລາວ \
+გე \
+みんな \
+アマゾン \
+クラウド \
+グーグル \
+コム \
+ストア \
+セール \
+ファッション \
+ポイント \
+世界 \
+中信 \
+中国 \
+中國 \
+中文网 \
+亚马逊 \
+企业 \
+佛山 \
+信息 \
+健康 \
+八卦 \
+公司 \
+公益 \
+台湾 \
+台灣 \
+商城 \
+商店 \
+商标 \
+嘉里 \
+嘉里大酒店 \
+在线 \
+大拿 \
+天主教 \
+娱乐 \
+家電 \
+广东 \
+微博 \
+慈善 \
+我爱你 \
+手机 \
+招聘 \
+政务 \
+政府 \
+新加坡 \
+æ–°é—» \
+时尚 \
+書籍 \
+机构 \
+淡马锡 \
+游戏 \
+澳門 \
+点看 \
+移动 \
+组织机构 \
+网址 \
+网店 \
+网站 \
+网络 \
+联通 \
+诺基亚 \
+谷歌 \
+购物 \
+通販 \
+集团 \
+電訊盈科 \
+飞利浦 \
+食品 \
+餐厅 \
+香格里拉 \
+香港 \
+ë‹·ë„· \
+ë‹·ì»´ \
+삼성 \
+한국'.split(' ');
 
-(function (exports) {
-	'use strict';
+	/**
+	 * @template A
+	 * @template B
+	 * @param {A} target
+	 * @param {B} properties
+	 * @return {A & B}
+	 */
+	var assign = function assign(target, properties) {
+	  for (var key in properties) {
+	    target[key] = properties[key];
+	  }
+
+	  return target;
+	};
 
-	function inherits(parent, child) {
-		var props = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
+	/**
+	 * Finite State Machine generation utilities
+	 */
+	/**
+	 * @template T
+	 * @typedef {{ [group: string]: T[] }} Collections
+	 */
 
-		var extended = Object.create(parent.prototype);
-		for (var p in props) {
-			extended[p] = props[p];
-		}
-		extended.constructor = child;
-		child.prototype = extended;
-		return child;
+	/**
+	 * @typedef {{ [group: string]: true }} Flags
+	 */
+	// Keys in scanner Collections instances
+
+	var numeric = 'numeric';
+	var ascii = 'ascii';
+	var alpha = 'alpha';
+	var asciinumeric = 'asciinumeric';
+	var alphanumeric = 'alphanumeric';
+	var domain = 'domain';
+	var emoji = 'emoji';
+	var scheme = 'scheme';
+	var slashscheme = 'slashscheme';
+	var whitespace = 'whitespace';
+	/**
+	 * @template T
+	 * @param {string} name
+	 * @param {Collections<T>} groups to register in
+	 * @returns {T[]} Current list of tokens in the given collection
+	 */
+
+	function registerGroup(name, groups) {
+	  if (!(name in groups)) {
+	    groups[name] = [];
+	  }
+
+	  return groups[name];
 	}
-
-	var defaults = {
-		defaultProtocol: 'http',
-		events: null,
-		format: noop,
-		formatHref: noop,
-		nl2br: false,
-		tagName: 'a',
-		target: typeToTarget,
-		validate: true,
-		ignoreTags: [],
-		attributes: null,
-		className: 'linkified' };
-
-	function Options(opts) {
-		opts = opts || {};
-
-		this.defaultProtocol = opts.defaultProtocol || defaults.defaultProtocol;
-		this.events = opts.events || defaults.events;
-		this.format = opts.format || defaults.format;
-		this.formatHref = opts.formatHref || defaults.formatHref;
-		this.nl2br = opts.nl2br || defaults.nl2br;
-		this.tagName = opts.tagName || defaults.tagName;
-		this.target = opts.target || defaults.target;
-		this.validate = opts.validate || defaults.validate;
-		this.ignoreTags = [];
-
-		// linkAttributes and linkClass is deprecated
-		this.attributes = opts.attributes || opts.linkAttributes || defaults.attributes;
-		this.className = opts.className || opts.linkClass || defaults.className;
-
-		// Make all tags names upper case
-
-		var ignoredTags = opts.ignoreTags || defaults.ignoreTags;
-		for (var i = 0; i < ignoredTags.length; i++) {
-			this.ignoreTags.push(ignoredTags[i].toUpperCase());
-		}
+	/**
+	 * @template T
+	 * @param {T} t token to add
+	 * @param {Collections<T>} groups
+	 * @param {Flags} flags
+	 */
+
+
+	function addToGroups(t, flags, groups) {
+	  if (flags[numeric]) {
+	    flags[asciinumeric] = true;
+	    flags[alphanumeric] = true;
+	  }
+
+	  if (flags[ascii]) {
+	    flags[asciinumeric] = true;
+	    flags[alpha] = true;
+	  }
+
+	  if (flags[asciinumeric]) {
+	    flags[alphanumeric] = true;
+	  }
+
+	  if (flags[alpha]) {
+	    flags[alphanumeric] = true;
+	  }
+
+	  if (flags[alphanumeric]) {
+	    flags[domain] = true;
+	  }
+
+	  if (flags[emoji]) {
+	    flags[domain] = true;
+	  }
+
+	  for (var k in flags) {
+	    var group = registerGroup(k, groups);
+
+	    if (group.indexOf(t) < 0) {
+	      group.push(t);
+	    }
+	  }
 	}
-
-	Options.prototype = {
-		/**
-   * Given the token, return all options for how it should be displayed
-   */
-		resolve: function resolve(token) {
-			var href = token.toHref(this.defaultProtocol);
-			return {
-				formatted: this.get('format', token.toString(), token),
-				formattedHref: this.get('formatHref', href, token),
-				tagName: this.get('tagName', href, token),
-				className: this.get('className', href, token),
-				target: this.get('target', href, token),
-				events: this.getObject('events', href, token),
-				attributes: this.getObject('attributes', href, token)
-			};
-		},
-
-
-		/**
-   * Returns true or false based on whether a token should be displayed as a
-   * link based on the user options. By default,
-   */
-		check: function check(token) {
-			return this.get('validate', token.toString(), token);
-		},
-
-
-		// Private methods
-
-		/**
-   * Resolve an option's value based on the value of the option and the given
-   * params.
-   * @param [String] key Name of option to use
-   * @param operator will be passed to the target option if it's method
-   * @param [MultiToken] token The token from linkify.tokenize
-   */
-		get: function get(key, operator, token) {
-			var option = this[key];
-
-			if (!option) {
-				return option;
-			}
-
-			switch (typeof option === 'undefined' ? 'undefined' : _typeof(option)) {
-				case 'function':
-					return option(operator, token.type);
-				case 'object':
-					var optionValue = option[token.type] || defaults[key];
-					return typeof optionValue === 'function' ? optionValue(operator, token.type) : optionValue;
-			}
-
-			return option;
-		},
-		getObject: function getObject(key, operator, token) {
-			var option = this[key];
-			return typeof option === 'function' ? option(operator, token.type) : option;
-		}
-	};
-
 	/**
-  * Quick indexOf replacement for checking the ignoreTags option
-  */
-	function contains(arr, value) {
-		for (var i = 0; i < arr.length; i++) {
-			if (arr[i] === value) {
-				return true;
-			}
-		}
-		return false;
+	 * @template T
+	 * @param {T} t token to check
+	 * @param {Collections<T>} groups
+	 * @returns {Flags} group flags that contain this token
+	 */
+
+	function flagsForToken(t, groups) {
+	  var result = {};
+
+	  for (var c in groups) {
+	    if (groups[c].indexOf(t) >= 0) {
+	      result[c] = true;
+	    }
+	  }
+
+	  return result;
 	}
+	/**
+	 * @template T
+	 * @typedef {null | T } Transition
+	 */
 
-	function noop(val) {
-		return val;
-	}
+	/**
+	 * Define a basic state machine state. j is the list of character transitions,
+	 * jr is the list of regex-match transitions, jd is the default state to
+	 * transition to t is the accepting token type, if any. If this is the terminal
+	 * state, then it does not emit a token.
+	 *
+	 * The template type T represents the type of the token this state accepts. This
+	 * should be a string (such as of the token exports in `text.js`) or a
+	 * MultiToken subclass (from `multi.js`)
+	 *
+	 * @template T
+	 * @param {T} [token] Token that this state emits
+	 */
 
-	function typeToTarget(href, type) {
-		return type === 'url' ? '_blank' : null;
-	}
 
-	var options = Object.freeze({
-		defaults: defaults,
-		Options: Options,
-		contains: contains
-	});
+	function State(token) {
+	  if (token === void 0) {
+	    token = null;
+	  }
+
+	  // this.n = null; // DEBUG: State name
+
+	  /** @type {{ [input: string]: State<T> }} j */
+	  this.j = {}; // IMPLEMENTATION 1
+	  // this.j = []; // IMPLEMENTATION 2
+
+	  /** @type {[RegExp, State<T>][]} jr */
 
-	function createStateClass() {
-		return function (tClass) {
-			this.j = [];
-			this.T = tClass || null;
-		};
+	  this.jr = [];
+	  /** @type {?State<T>} jd */
+
+	  this.jd = null;
+	  /** @type {?T} t */
+
+	  this.t = token;
 	}
+	/**
+	 * Scanner token groups
+	 * @type Collections<string>
+	 */
+
+	State.groups = {};
+	State.prototype = {
+	  accepts: function accepts() {
+	    return !!this.t;
+	  },
+
+	  /**
+	   * Follow an existing transition from the given input to the next state.
+	   * Does not mutate.
+	   * @param {string} input character or token type to transition on
+	   * @returns {?State<T>} the next state, if any
+	   */
+	  go: function go(input) {
+	    var state = this;
+	    var nextState = state.j[input];
+
+	    if (nextState) {
+	      return nextState;
+	    }
+
+	    for (var i = 0; i < state.jr.length; i++) {
+	      var regex = state.jr[i][0];
+	      var _nextState = state.jr[i][1]; // note: might be empty to prevent default jump
+
+	      if (_nextState && regex.test(input)) {
+	        return _nextState;
+	      }
+	    } // Nowhere left to jump! Return default, if any
+
+
+	    return state.jd;
+	  },
+
+	  /**
+	   * Whether the state has a transition for the given input. Set the second
+	   * argument to true to only look for an exact match (and not a default or
+	   * regular-expression-based transition)
+	   * @param {string} input
+	   * @param {boolean} exactOnly
+	   */
+	  has: function has(input, exactOnly) {
+	    if (exactOnly === void 0) {
+	      exactOnly = false;
+	    }
+
+	    return exactOnly ? input in this.j : !!this.go(input);
+	  },
+
+	  /**
+	   * Short for "transition all"; create a transition from the array of items
+	   * in the given list to the same final resulting state.
+	   * @param {string | string[]} inputs Group of inputs to transition on
+	   * @param {Transition<T> | State<T>} [next] Transition options
+	   * @param {Flags} [flags] Collections flags to add token to
+	   * @param {Collections<T>} [groups] Master list of token groups
+	   */
+	  ta: function ta(inputs, next, flags, groups) {
+	    for (var i = 0; i < inputs.length; i++) {
+	      this.tt(inputs[i], next, flags, groups);
+	    }
+	  },
+
+	  /**
+	   * Short for "take regexp transition"; defines a transition for this state
+	   * when it encounters a token which matches the given regular expression
+	   * @param {RegExp} regexp Regular expression transition (populate first)
+	   * @param {T | State<T>} [next] Transition options
+	   * @param {Flags} [flags] Collections flags to add token to
+	   * @param {Collections<T>} [groups] Master list of token groups
+	   * @returns {State<T>} taken after the given input
+	   */
+	  tr: function tr(regexp, next, flags, groups) {
+	    groups = groups || State.groups;
+	    var nextState;
+
+	    if (next && next.j) {
+	      nextState = next;
+	    } else {
+	      // Token with maybe token groups
+	      nextState = new State(next);
+
+	      if (flags && groups) {
+	        addToGroups(next, flags, groups);
+	      }
+	    }
+
+	    this.jr.push([regexp, nextState]);
+	    return nextState;
+	  },
+
+	  /**
+	   * Short for "take transitions", will take as many sequential transitions as
+	   * the length of the given input and returns the
+	   * resulting final state.
+	   * @param {string | string[]} input
+	   * @param {T | State<T>} [next] Transition options
+	   * @param {Flags} [flags] Collections flags to add token to
+	   * @param {Collections<T>} [groups] Master list of token groups
+	   * @returns {State<T>} taken after the given input
+	   */
+	  ts: function ts(input, next, flags, groups) {
+	    var state = this;
+	    var len = input.length;
+
+	    if (!len) {
+	      return state;
+	    }
+
+	    for (var i = 0; i < len - 1; i++) {
+	      state = state.tt(input[i]);
+	    }
+
+	    return state.tt(input[len - 1], next, flags, groups);
+	  },
+
+	  /**
+	   * Short for "take transition", this is a method for building/working with
+	   * state machines.
+	   *
+	   * If a state already exists for the given input, returns it.
+	   *
+	   * If a token is specified, that state will emit that token when reached by
+	   * the linkify engine.
+	   *
+	   * If no state exists, it will be initialized with some default transitions
+	   * that resemble existing default transitions.
+	   *
+	   * If a state is given for the second argument, that state will be
+	   * transitioned to on the given input regardless of what that input
+	   * previously did.
+	   *
+	   * Specify a token group flags to define groups that this token belongs to.
+	   * The token will be added to corresponding entires in the given groups
+	   * object.
+	   *
+	   * @param {string} input character, token type to transition on
+	   * @param {T | State<T>} [next] Transition options
+	   * @param {Flags} [flags] Collections flags to add token to
+	   * @param {Collections<T>} [groups] Master list of groups
+	   * @returns {State<T>} taken after the given input
+	   */
+	  tt: function tt(input, next, flags, groups) {
+	    groups = groups || State.groups;
+	    var state = this; // Check if existing state given, just a basic transition
+
+	    if (next && next.j) {
+	      state.j[input] = next;
+	      return next;
+	    }
+
+	    var t = next; // Take the transition with the usual default mechanisms and use that as
+	    // a template for creating the next state
+
+	    var nextState,
+	        templateState = state.go(input);
+
+	    if (templateState) {
+	      nextState = new State();
+	      assign(nextState.j, templateState.j);
+	      nextState.jr.push.apply(nextState.jr, templateState.jr);
+	      nextState.jd = templateState.jd;
+	      nextState.t = templateState.t;
+	    } else {
+	      nextState = new State();
+	    }
+
+	    if (t) {
+	      // Ensure newly token is in the same groups as the old token
+	      if (groups) {
+	        if (nextState.t && typeof nextState.t === 'string') {
+	          var allFlags = assign(flagsForToken(nextState.t, groups), flags);
+	          addToGroups(t, allFlags, groups);
+	        } else if (flags) {
+	          addToGroups(t, flags, groups);
+	        }
+	      }
+
+	      nextState.t = t; // overwrite anything that was previously there
+	    }
+
+	    state.j[input] = nextState;
+	    return nextState;
+	  }
+	}; // Helper functions to improve minification (not exported outside linkifyjs module)
 
 	/**
- 	A simple state machine that can emit token classes
-
- 	The `j` property in this class refers to state jumps. It's a
- 	multidimensional array where for each element:
-
- 	* index [0] is a symbol or class of symbols to transition to.
- 	* index [1] is a State instance which matches
-
- 	The type of symbol will depend on the target implementation for this class.
- 	In Linkify, we have a two-stage scanner. Each stage uses this state machine
- 	but with a slighly different (polymorphic) implementation.
-
- 	The `T` property refers to the token class.
-
- 	TODO: Can the `on` and `next` methods be combined?
-
- 	@class BaseState
- */
-	var BaseState = createStateClass();
-	BaseState.prototype = {
-		defaultTransition: false,
-
-		/**
-  	@method constructor
-  	@param {Class} tClass Pass in the kind of token to emit if there are
-  		no jumps after this state and the state is accepting.
-  */
-
-		/**
-  	On the given symbol(s), this machine should go to the given state
-  		@method on
-  	@param {Array|Mixed} symbol
-  	@param {BaseState} state Note that the type of this state should be the
-  		same as the current instance (i.e., don't pass in a different
-  		subclass)
-  */
-		on: function on(symbol, state) {
-			if (symbol instanceof Array) {
-				for (var i = 0; i < symbol.length; i++) {
-					this.j.push([symbol[i], state]);
-				}
-				return this;
-			}
-			this.j.push([symbol, state]);
-			return this;
-		},
-
-
-		/**
-  	Given the next item, returns next state for that item
-  	@method next
-  	@param {Mixed} item Should be an instance of the symbols handled by
-  		this particular machine.
-  	@return {State} state Returns false if no jumps are available
-  */
-		next: function next(item) {
-			for (var i = 0; i < this.j.length; i++) {
-				var jump = this.j[i];
-				var symbol = jump[0]; // Next item to check for
-				var state = jump[1]; // State to jump to if items match
-
-				// compare item with symbol
-				if (this.test(item, symbol)) {
-					return state;
-				}
-			}
-
-			// Nowhere left to jump!
-			return this.defaultTransition;
-		},
-
-
-		/**
-  	Does this state accept?
-  	`true` only of `this.T` exists
-  		@method accepts
-  	@return {Boolean}
-  */
-		accepts: function accepts() {
-			return !!this.T;
-		},
-
-
-		/**
-  	Determine whether a given item "symbolizes" the symbol, where symbol is
-  	a class of items handled by this state machine.
-  		This method should be overridden in extended classes.
-  		@method test
-  	@param {Mixed} item Does this item match the given symbol?
-  	@param {Mixed} symbol
-  	@return {Boolean}
-  */
-		test: function test(item, symbol) {
-			return item === symbol;
-		},
-
-
-		/**
-  	Emit the token for this State (just return it in this case)
-  	If this emits a token, this instance is an accepting state
-  	@method emit
-  	@return {Class} T
-  */
-		emit: function emit() {
-			return this.T;
-		}
+	 * @template T
+	 * @param {State<T>} state
+	 * @param {string | string[]} input
+	 * @param {Flags} [flags]
+	 * @param {Collections<T>} [groups]
+	 */
+
+	var ta = function ta(state, input, next, flags, groups) {
+	  return state.ta(input, next, flags, groups);
 	};
-
 	/**
- 	State machine for string-based input
-
- 	@class CharacterState
- 	@extends BaseState
- */
-	var CharacterState = inherits(BaseState, createStateClass(), {
-		/**
-  	Does the given character match the given character or regular
-  	expression?
-  		@method test
-  	@param {String} char
-  	@param {String|RegExp} charOrRegExp
-  	@return {Boolean}
-  */
-		test: function test(character, charOrRegExp) {
-			return character === charOrRegExp || charOrRegExp instanceof RegExp && charOrRegExp.test(character);
-		}
-	});
-
+	 * @template T
+	 * @param {State<T>} state
+	 * @param {RegExp} regexp
+	 * @param {T | State<T>} [next]
+	 * @param {Flags} [flags]
+	 * @param {Collections<T>} [groups]
+	 */
+
+	var tr = function tr(state, regexp, next, flags, groups) {
+	  return state.tr(regexp, next, flags, groups);
+	};
 	/**
- 	State machine for input in the form of TextTokens
-
- 	@class TokenState
- 	@extends BaseState
- */
-	var State = inherits(BaseState, createStateClass(), {
-
-		/**
-   * Similar to `on`, but returns the state the results in the transition from
-   * the given item
-   * @method jump
-   * @param {Mixed} item
-   * @param {Token} [token]
-   * @return state
-   */
-		jump: function jump(token) {
-			var tClass = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
-
-			var state = this.next(new token('')); // dummy temp token
-			if (state === this.defaultTransition) {
-				// Make a new state!
-				state = new this.constructor(tClass);
-				this.on(token, state);
-			} else if (tClass) {
-				state.T = tClass;
-			}
-			return state;
-		},
-
-
-		/**
-  	Is the given token an instance of the given token class?
-  		@method test
-  	@param {TextToken} token
-  	@param {Class} tokenClass
-  	@return {Boolean}
-  */
-		test: function test(token, tokenClass) {
-			return token instanceof tokenClass;
-		}
-	});
-
+	 * @template T
+	 * @param {State<T>} state
+	 * @param {string | string[]} input
+	 * @param {T | State<T>} [next]
+	 * @param {Flags} [flags]
+	 * @param {Collections<T>} [groups]
+	 */
+
+	var ts = function ts(state, input, next, flags, groups) {
+	  return state.ts(input, next, flags, groups);
+	};
 	/**
- 	Given a non-empty target string, generates states (if required) for each
- 	consecutive substring of characters in str starting from the beginning of
- 	the string. The final state will have a special value, as specified in
- 	options. All other "in between" substrings will have a default end state.
-
- 	This turns the state machine into a Trie-like data structure (rather than a
- 	intelligently-designed DFA).
-
- 	Note that I haven't really tried these with any strings other than
- 	DOMAIN.
-
- 	@param {String} str
- 	@param {CharacterState} start State to jump from the first character
- 	@param {Class} endToken Token class to emit when the given string has been
- 		matched and no more jumps exist.
- 	@param {Class} defaultToken "Filler token", or which token type to emit when
- 		we don't have a full match
- 	@return {Array} list of newly-created states
- */
-	function stateify(str, start, endToken, defaultToken) {
-		var i = 0,
-		    len = str.length,
-		    state = start,
-		    newStates = [],
-		    nextState = void 0;
-
-		// Find the next state without a jump to the next character
-		while (i < len && (nextState = state.next(str[i]))) {
-			state = nextState;
-			i++;
-		}
-
-		if (i >= len) {
-			return [];
-		} // no new tokens were added
-
-		while (i < len - 1) {
-			nextState = new CharacterState(defaultToken);
-			newStates.push(nextState);
-			state.on(str[i], nextState);
-			state = nextState;
-			i++;
-		}
-
-		nextState = new CharacterState(endToken);
-		newStates.push(nextState);
-		state.on(str[len - 1], nextState);
-
-		return newStates;
-	}
-
-	function createTokenClass() {
-		return function (value) {
-			if (value) {
-				this.v = value;
-			}
-		};
-	}
+	 * @template T
+	 * @param {State<T>} state
+	 * @param {string} input
+	 * @param {T | State<T>} [next]
+	 * @param {Collections<T>} [groups]
+	 * @param {Flags} [flags]
+	 */
+
+	var tt = function tt(state, input, next, flags, groups) {
+	  return state.tt(input, next, flags, groups);
+	};
 
 	/******************************************************************************
- 	Text Tokens
- 	Tokens composed of strings
- ******************************************************************************/
+	Text Tokens
+	Identifiers for token outputs from the regexp scanner
+	******************************************************************************/
+	// A valid web domain token
+	var WORD = 'WORD'; // only contains a-z
 
-	/**
- 	Abstract class used for manufacturing text tokens.
- 	Pass in the value this token represents
-
- 	@class TextToken
- 	@abstract
- */
-	var TextToken = createTokenClass();
-	TextToken.prototype = {
-		toString: function toString() {
-			return this.v + '';
-		}
-	};
+	var UWORD = 'UWORD'; // contains letters other than a-z, used for IDN
+	// Special case of word
 
-	function inheritsToken(value) {
-		var props = value ? { v: value } : {};
-		return inherits(TextToken, createTokenClass(), props);
-	}
+	var LOCALHOST = 'LOCALHOST'; // Valid top-level domain, special case of WORD (see tlds.js)
 
-	/**
- 	A valid domain token
- 	@class DOMAIN
- 	@extends TextToken
- */
-	var DOMAIN = inheritsToken();
+	var TLD = 'TLD'; // Valid IDN TLD, special case of UWORD (see tlds.js)
 
-	/**
- 	@class AT
- 	@extends TextToken
- */
-	var AT = inheritsToken('@');
+	var UTLD = 'UTLD'; // The scheme portion of a web URI protocol. Supported types include: `mailto`,
+	// `file`, and user-defined custom protocols. Limited to schemes that contain
+	// only letters
 
-	/**
- 	Represents a single colon `:` character
+	var SCHEME = 'SCHEME'; // Similar to SCHEME, except makes distinction for schemes that must always be
+	// followed by `://`, not just `:`. Supported types include `http`, `https`,
+	// `ftp`, `ftps`
 
- 	@class COLON
- 	@extends TextToken
- */
-	var COLON = inheritsToken(':');
+	var SLASH_SCHEME = 'SLASH_SCHEME'; // Any sequence of digits 0-9
 
-	/**
- 	@class DOT
- 	@extends TextToken
- */
-	var DOT = inheritsToken('.');
+	var NUM = 'NUM'; // Any number of consecutive whitespace characters that are not newline
 
-	/**
- 	A character class that can surround the URL, but which the URL cannot begin
- 	or end with. Does not include certain English punctuation like parentheses.
+	var WS = 'WS'; // New line (unix style)
 
- 	@class PUNCTUATION
- 	@extends TextToken
- */
-	var PUNCTUATION = inheritsToken();
+	var NL$1 = 'NL'; // \n
+	// Opening/closing bracket classes
 
-	/**
- 	The word localhost (by itself)
- 	@class LOCALHOST
- 	@extends TextToken
- */
-	var LOCALHOST = inheritsToken();
+	var OPENBRACE = 'OPENBRACE'; // {
 
-	/**
- 	Newline token
- 	@class NL
- 	@extends TextToken
- */
-	var TNL = inheritsToken('\n');
+	var OPENBRACKET = 'OPENBRACKET'; // [
 
-	/**
- 	@class NUM
- 	@extends TextToken
- */
-	var NUM = inheritsToken();
+	var OPENANGLEBRACKET = 'OPENANGLEBRACKET'; // <
 
-	/**
- 	@class PLUS
- 	@extends TextToken
- */
-	var PLUS = inheritsToken('+');
+	var OPENPAREN = 'OPENPAREN'; // (
 
-	/**
- 	@class POUND
- 	@extends TextToken
- */
-	var POUND = inheritsToken('#');
+	var CLOSEBRACE = 'CLOSEBRACE'; // }
 
-	/**
- 	Represents a web URL protocol. Supported types include
+	var CLOSEBRACKET = 'CLOSEBRACKET'; // ]
 
- 	* `http:`
- 	* `https:`
- 	* `ftp:`
- 	* `ftps:`
- 	* There's Another super weird one
+	var CLOSEANGLEBRACKET = 'CLOSEANGLEBRACKET'; // >
 
- 	@class PROTOCOL
- 	@extends TextToken
- */
-	var PROTOCOL = inheritsToken();
+	var CLOSEPAREN = 'CLOSEPAREN'; // )
+	// Various symbols
 
-	/**
- 	@class QUERY
- 	@extends TextToken
- */
-	var QUERY = inheritsToken('?');
+	var AMPERSAND = 'AMPERSAND'; // &
 
-	/**
- 	@class SLASH
- 	@extends TextToken
- */
-	var SLASH = inheritsToken('/');
+	var APOSTROPHE = 'APOSTROPHE'; // '
 
-	/**
- 	@class UNDERSCORE
- 	@extends TextToken
- */
-	var UNDERSCORE = inheritsToken('_');
+	var ASTERISK = 'ASTERISK'; // *
 
-	/**
- 	One ore more non-whitespace symbol.
- 	@class SYM
- 	@extends TextToken
- */
-	var SYM = inheritsToken();
+	var AT = 'AT'; // @
 
-	/**
- 	@class TLD
- 	@extends TextToken
- */
-	var TLD = inheritsToken();
+	var BACKSLASH = 'BACKSLASH'; // \
 
-	/**
- 	Represents a string of consecutive whitespace characters
+	var BACKTICK = 'BACKTICK'; // `
 
- 	@class WS
- 	@extends TextToken
- */
-	var WS = inheritsToken();
+	var CARET = 'CARET'; // ^
 
-	/**
- 	Opening/closing bracket classes
- */
-
-	var OPENBRACE = inheritsToken('{');
-	var OPENBRACKET = inheritsToken('[');
-	var OPENANGLEBRACKET = inheritsToken('<');
-	var OPENPAREN = inheritsToken('(');
-	var CLOSEBRACE = inheritsToken('}');
-	var CLOSEBRACKET = inheritsToken(']');
-	var CLOSEANGLEBRACKET = inheritsToken('>');
-	var CLOSEPAREN = inheritsToken(')');
-
-	var TOKENS = Object.freeze({
-		Base: TextToken,
-		DOMAIN: DOMAIN,
-		AT: AT,
-		COLON: COLON,
-		DOT: DOT,
-		PUNCTUATION: PUNCTUATION,
+	var COLON = 'COLON'; // :
+
+	var COMMA = 'COMMA'; // ,
+
+	var DOLLAR = 'DOLLAR'; // $
+
+	var DOT = 'DOT'; // .
+
+	var EQUALS = 'EQUALS'; // =
+
+	var EXCLAMATION = 'EXCLAMATION'; // !
+
+	var HYPHEN = 'HYPHEN'; // -
+
+	var PERCENT = 'PERCENT'; // %
+
+	var PIPE = 'PIPE'; // |
+
+	var PLUS = 'PLUS'; // +
+
+	var POUND = 'POUND'; // #
+
+	var QUERY = 'QUERY'; // ?
+
+	var QUOTE = 'QUOTE'; // "
+
+	var SEMI = 'SEMI'; // ;
+
+	var SLASH = 'SLASH'; // /
+
+	var TILDE = 'TILDE'; // ~
+
+	var UNDERSCORE = 'UNDERSCORE'; // _
+	// Emoji symbol
+
+	var EMOJI$1 = 'EMOJI'; // Default token - anything that is not one of the above
+
+	var SYM = 'SYM';
+
+	var tk = /*#__PURE__*/Object.freeze({
+		__proto__: null,
+		WORD: WORD,
+		UWORD: UWORD,
 		LOCALHOST: LOCALHOST,
-		NL: TNL,
-		NUM: NUM,
-		PLUS: PLUS,
-		POUND: POUND,
-		QUERY: QUERY,
-		PROTOCOL: PROTOCOL,
-		SLASH: SLASH,
-		UNDERSCORE: UNDERSCORE,
-		SYM: SYM,
 		TLD: TLD,
+		UTLD: UTLD,
+		SCHEME: SCHEME,
+		SLASH_SCHEME: SLASH_SCHEME,
+		NUM: NUM,
 		WS: WS,
+		NL: NL$1,
 		OPENBRACE: OPENBRACE,
 		OPENBRACKET: OPENBRACKET,
 		OPENANGLEBRACKET: OPENANGLEBRACKET,
@@ -586,686 +2025,1477 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
 		CLOSEBRACE: CLOSEBRACE,
 		CLOSEBRACKET: CLOSEBRACKET,
 		CLOSEANGLEBRACKET: CLOSEANGLEBRACKET,
-		CLOSEPAREN: CLOSEPAREN
+		CLOSEPAREN: CLOSEPAREN,
+		AMPERSAND: AMPERSAND,
+		APOSTROPHE: APOSTROPHE,
+		ASTERISK: ASTERISK,
+		AT: AT,
+		BACKSLASH: BACKSLASH,
+		BACKTICK: BACKTICK,
+		CARET: CARET,
+		COLON: COLON,
+		COMMA: COMMA,
+		DOLLAR: DOLLAR,
+		DOT: DOT,
+		EQUALS: EQUALS,
+		EXCLAMATION: EXCLAMATION,
+		HYPHEN: HYPHEN,
+		PERCENT: PERCENT,
+		PIPE: PIPE,
+		PLUS: PLUS,
+		POUND: POUND,
+		QUERY: QUERY,
+		QUOTE: QUOTE,
+		SEMI: SEMI,
+		SLASH: SLASH,
+		TILDE: TILDE,
+		UNDERSCORE: UNDERSCORE,
+		EMOJI: EMOJI$1,
+		SYM: SYM
+	});
+
+	// Note that these two Unicode ones expand into a really big one with Babel
+	var ASCII_LETTER = /[a-z]/;
+	var LETTER = /(?:[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u0870-\u0887\u0889-\u088E\u08A0-\u08C9\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C5D\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u1711\u171F-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4C\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BF\u31F0-\u31FF\u3400-\u4DBF\u4E00-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7CA\uA7D0\uA7D1\uA7D3\uA7D5-\uA7D9\uA7F2-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDD70-\uDD7A\uDD7C-\uDD8A\uDD8C-\uDD92\uDD94\uDD95\uDD97-\uDDA1\uDDA3-\uDDB1\uDDB3-\uDDB9\uDDBB\uDDBC\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67\uDF80-\uDF85\uDF87-\uDFB0\uDFB2-\uDFBA]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDD00-\uDD23\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF1C\uDF27\uDF30-\uDF45\uDF70-\uDF81\uDFB0-\uDFC4\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC71\uDC72\uDC75\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDEB8\uDF00-\uDF1A\uDF40-\uDF46]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCDF\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEB0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDEE0-\uDEF2\uDFB0]|\uD808[\uDC00-\uDF99]|\uD809[\uDC80-\uDD43]|\uD80B[\uDF90-\uDFF0]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE70-\uDEBE\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE7F\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82B[\uDFF0-\uDFF3\uDFF5-\uDFFB\uDFFD\uDFFE]|\uD82C[\uDC00-\uDD22\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD837[\uDF00-\uDF1E]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD4E\uDE90-\uDEAD\uDEC0-\uDEEB]|\uD839[\uDFE0-\uDFE6\uDFE8-\uDFEB\uDFED\uDFEE\uDFF0-\uDFFE]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43\uDD4B]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDEDF\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF38\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])/; // Any Unicode character with letter data type
+
+	var EMOJI = /(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEDD-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDDFF\uDE70-\uDE74\uDE78-\uDE7C\uDE80-\uDE86\uDE90-\uDEAC\uDEB0-\uDEBA\uDEC0-\uDEC5\uDED0-\uDED9\uDEE0-\uDEE7\uDEF0-\uDEF6])/; // Any Unicode emoji character
+
+	var EMOJI_VARIATION$1 = /\ufe0f/;
+	var DIGIT = /\d/;
+	var SPACE = /\s/;
+
+	var regexp = /*#__PURE__*/Object.freeze({
+		__proto__: null,
+		ASCII_LETTER: ASCII_LETTER,
+		LETTER: LETTER,
+		EMOJI: EMOJI,
+		EMOJI_VARIATION: EMOJI_VARIATION$1,
+		DIGIT: DIGIT,
+		SPACE: SPACE
 	});
 
 	/**
- 	The scanner provides an interface that takes a string of text as input, and
- 	outputs an array of tokens instances that can be used for easy URL parsing.
+		The scanner provides an interface that takes a string of text as input, and
+		outputs an array of tokens instances that can be used for easy URL parsing.
+	*/
+	var NL = '\n'; // New line character
 
- 	@module linkify
- 	@submodule scanner
- 	@main scanner
- */
+	var EMOJI_VARIATION = "\uFE0F"; // Variation selector, follows heart and others
 
-	var tlds = 'aaa|aarp|abb|abbott|abogado|ac|academy|accenture|accountant|accountants|aco|active|actor|ad|adac|ads|adult|ae|aeg|aero|af|afl|ag|agency|ai|aig|airforce|airtel|al|alibaba|alipay|allfinanz|alsace|am|amica|amsterdam|an|analytics|android|ao|apartments|app|apple|aq|aquarelle|ar|aramco|archi|army|arpa|arte|as|asia|associates|at|attorney|au|auction|audi|audio|author|auto|autos|avianca|aw|ax|axa|az|azure|ba|baidu|band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bb|bbc|bbva|bcg|bcn|bd|be|beats|beer|bentley|berlin|best|bet|bf|bg|bh|bharti|bi|bible|bid|bike|bing|bingo|bio|biz|bj|black|blackfriday|bloomberg|blue|bm|bms|bmw|bn|bnl|bnpparibas|bo|boats|boehringer|bom|bond|boo|book|boots|bosch|bostik|bot|boutique|br|bradesco|bridgestone|broadway|broker|brother|brussels|bs|bt|budapest|bugatti|build|builders|business|buy|buzz|bv|bw|by|bz|bzh|ca|cab|cafe|cal|call|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|cc|cd|ceb|center|ceo|cern|cf|cfa|cfd|cg|ch|chanel|channel|chase|chat|cheap|chloe|christmas|chrome|church|ci|cipriani|circle|cisco|citic|city|cityeats|ck|cl|claims|cleaning|click|clinic|clinique|clothing|cloud|club|clubmed|cm|cn|co|coach|codes|coffee|college|cologne|com|commbank|community|company|compare|computer|comsec|condos|construction|consulting|contact|contractors|cooking|cool|coop|corsica|country|coupon|coupons|courses|cr|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cu|cuisinella|cv|cw|cx|cy|cymru|cyou|cz|dabur|dad|dance|date|dating|datsun|day|dclk|de|dealer|deals|degree|delivery|dell|deloitte|delta|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount|dj|dk|dm|dnp|do|docs|dog|doha|domains|download|drive|dubai|durban|dvag|dz|earth|eat|ec|edeka|edu|education|ee|eg|email|emerck|energy|engineer|engineering|enterprises|epson|equipment|er|erni|es|esq|estate|et|eu|eurovision|eus|events|everbank|exchange|expert|exposed|express|fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|fast|feedback|ferrero|fi|film|final|finance|financial|firestone|firmdale|fish|fishing|fit|fitness|fj|fk|flickr|flights|florist|flowers|flsmidth|fly|fm|fo|foo|football|ford|forex|forsale|forum|foundation|fox|fr|fresenius|frl|frogans|frontier|fund|furniture|futbol|fyi|ga|gal|gallery|gallup|game|garden|gb|gbiz|gd|gdn|ge|gea|gent|genting|gf|gg|ggee|gh|gi|gift|gifts|gives|giving|gl|glass|gle|global|globo|gm|gmail|gmbh|gmo|gmx|gn|gold|goldpoint|golf|goo|goog|google|gop|got|gov|gp|gq|gr|grainger|graphics|gratis|green|gripe|group|gs|gt|gu|gucci|guge|guide|guitars|guru|gw|gy|hamburg|hangout|haus|hdfcbank|health|healthcare|help|helsinki|here|hermes|hiphop|hitachi|hiv|hk|hm|hn|hockey|holdings|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hr|hsbc|ht|hu|hyundai|ibm|icbc|ice|icu|id|ie|ifm|iinet|il|im|immo|immobilien|in|industries|infiniti|info|ing|ink|institute|insurance|insure|int|international|investments|io|ipiranga|iq|ir|irish|is|iselect|ist|istanbul|it|itau|iwc|jaguar|java|jcb|je|jetzt|jewelry|jlc|jll|jm|jmp|jo|jobs|joburg|jot|joy|jp|jpmorgan|jprs|juegos|kaufen|kddi|ke|kerryhotels|kerrylogistics|kerryproperties|kfh|kg|kh|ki|kia|kim|kinder|kitchen|kiwi|km|kn|koeln|komatsu|kp|kpn|kr|krd|kred|kuokgroup|kw|ky|kyoto|kz|la|lacaixa|lamborghini|lamer|lancaster|land|landrover|lanxess|lasalle|lat|latrobe|law|lawyer|lb|lc|lds|lease|leclerc|legal|lexus|lgbt|li|liaison|lidl|life|lifeinsurance|lifestyle|lighting|like|limited|limo|lincoln|linde|link|live|living|lixil|lk|loan|loans|local|locus|lol|london|lotte|lotto|love|lr|ls|lt|ltd|ltda|lu|lupin|luxe|luxury|lv|ly|ma|madrid|maif|maison|makeup|man|management|mango|market|marketing|markets|marriott|mba|mc|md|me|med|media|meet|melbourne|meme|memorial|men|menu|meo|mg|mh|miami|microsoft|mil|mini|mk|ml|mm|mma|mn|mo|mobi|mobily|moda|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar|mp|mq|mr|ms|mt|mtn|mtpc|mtr|mu|museum|mutuelle|mv|mw|mx|my|mz|na|nadex|nagoya|name|natura|navy|nc|ne|nec|net|netbank|network|neustar|new|news|nexus|nf|ng|ngo|nhk|ni|nico|nikon|ninja|nissan|nl|no|nokia|norton|nowruz|np|nr|nra|nrw|ntt|nu|nyc|nz|obi|office|okinawa|om|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|origins|osaka|otsuka|ovh|pa|page|pamperedchef|panerai|paris|pars|partners|parts|party|passagens|pe|pet|pf|pg|ph|pharmacy|philips|photo|photography|photos|physio|piaget|pics|pictet|pictures|pid|pin|ping|pink|pizza|pk|pl|place|play|playstation|plumbing|plus|pm|pn|pohl|poker|porn|post|pr|praxi|press|pro|prod|productions|prof|promo|properties|property|protection|ps|pt|pub|pw|pwc|py|qa|qpon|quebec|quest|racing|re|read|realtor|realty|recipes|red|redstone|redumbrella|rehab|reise|reisen|reit|ren|rent|rentals|repair|report|republican|rest|restaurant|review|reviews|rexroth|rich|ricoh|rio|rip|ro|rocher|rocks|rodeo|room|rs|rsvp|ru|ruhr|run|rw|rwe|ryukyu|sa|saarland|safe|safety|sakura|sale|salon|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|sas|saxo|sb|sbs|sc|sca|scb|schaeffler|schmidt|scholarships|school|schule|schwarz|science|scor|scot|sd|se|seat|security|seek|select|sener|services|seven|sew|sex|sexy|sfr|sg|sh|sharp|shell|shia|shiksha|shoes|show|shriram|si|singles|site|sj|sk|ski|skin|sky|skype|sl|sm|smile|sn|sncf|so|soccer|social|softbank|software|sohu|solar|solutions|song|sony|soy|space|spiegel|spot|spreadbetting|sr|srl|st|stada|star|starhub|statefarm|statoil|stc|stcgroup|stockholm|storage|store|studio|study|style|su|sucks|supplies|supply|support|surf|surgery|suzuki|sv|swatch|swiss|sx|sy|sydney|symantec|systems|sz|tab|taipei|taobao|tatamotors|tatar|tattoo|tax|taxi|tc|tci|td|team|tech|technology|tel|telecity|telefonica|temasek|tennis|tf|tg|th|thd|theater|theatre|tickets|tienda|tiffany|tips|tires|tirol|tj|tk|tl|tm|tmall|tn|to|today|tokyo|tools|top|toray|toshiba|total|tours|town|toyota|toys|tp|tr|trade|trading|training|travel|travelers|travelersinsurance|trust|trv|tt|tube|tui|tunes|tushu|tv|tvs|tw|tz|ua|ubs|ug|uk|unicom|university|uno|uol|us|uy|uz|va|vacations|vana|vc|ve|vegas|ventures|verisign|versicherung|vet|vg|vi|viajes|video|viking|villas|vin|vip|virgin|vision|vista|vistaprint|viva|vlaanderen|vn|vodka|volkswagen|vote|voting|voto|voyage|vu|vuelos|wales|walter|wang|wanggou|watch|watches|weather|weatherchannel|webcam|weber|website|wed|wedding|weir|wf|whoswho|wien|wiki|williamhill|win|windows|wine|wme|wolterskluwer|work|works|world|ws|wtc|wtf|xbox|xerox|xin|xperia|xxx|xyz|yachts|yahoo|yamaxun|yandex|ye|yodobashi|yoga|yokohama|youtube|yt|za|zara|zero|zip|zm|zone|zuerich|zw'.split('|'); // macro, see gulpfile.js
+	var EMOJI_JOINER = "\u200D"; // zero-width joiner
 
-	var NUMBERS = '0123456789'.split('');
-	var ALPHANUM = '0123456789abcdefghijklmnopqrstuvwxyz'.split('');
-	var WHITESPACE = [' ', '\f', '\r', '\t', '\v', ' ', ' ', '᠎']; // excluding line breaks
+	/**
+	 * Scanner output token:
+	 * - `t` is the token name (e.g., 'NUM', 'EMOJI', 'TLD')
+	 * - `v` is the value of the token (e.g., '123', '❤️', 'com')
+	 * - `s` is the start index of the token in the original string
+	 * - `e` is the end index of the token in the original string
+	 * @typedef {{t: string, v: string, s: number, e: number}} Token
+	 */
 
-	var domainStates = []; // states that jump to DOMAIN on /[a-z0-9]/
-	var makeState = function makeState(tokenClass) {
-		return new CharacterState(tokenClass);
-	};
+	/**
+	 * @template T
+	 * @typedef {{ [collection: string]: T[] }} Collections
+	 */
 
-	// Frequently used states
-	var S_START = makeState();
-	var S_NUM = makeState(NUM);
-	var S_DOMAIN = makeState(DOMAIN);
-	var S_DOMAIN_HYPHEN = makeState(); // domain followed by 1 or more hyphen characters
-	var S_WS = makeState(WS);
+	/**
+	 * Initialize the scanner character-based state machine for the given start
+	 * state
+	 * @param {[string, boolean][]} customSchemes List of custom schemes, where each
+	 * item is a length-2 tuple with the first element set to the string scheme, and
+	 * the second element set to `true` if the `://` after the scheme is optional
+	 */
+
+	function init$2(customSchemes) {
+	  var _tr, _tr2, _tr3, _tr4, _tt, _tr5;
+
+	  if (customSchemes === void 0) {
+	    customSchemes = [];
+	  }
+
+	  // Frequently used states (name argument removed during minification)
+
+	  /** @type Collections<string> */
+	  var groups = {}; // of tokens
+
+	  State.groups = groups;
+	  /** @type State<string> */
+
+	  var Start = new State(); // States for special URL symbols that accept immediately after start
+
+	  tt(Start, "'", APOSTROPHE);
+	  tt(Start, '{', OPENBRACE);
+	  tt(Start, '[', OPENBRACKET);
+	  tt(Start, '<', OPENANGLEBRACKET);
+	  tt(Start, '(', OPENPAREN);
+	  tt(Start, '}', CLOSEBRACE);
+	  tt(Start, ']', CLOSEBRACKET);
+	  tt(Start, '>', CLOSEANGLEBRACKET);
+	  tt(Start, ')', CLOSEPAREN);
+	  tt(Start, '&', AMPERSAND);
+	  tt(Start, '*', ASTERISK);
+	  tt(Start, '@', AT);
+	  tt(Start, '`', BACKTICK);
+	  tt(Start, '^', CARET);
+	  tt(Start, ':', COLON);
+	  tt(Start, ',', COMMA);
+	  tt(Start, '$', DOLLAR);
+	  tt(Start, '.', DOT);
+	  tt(Start, '=', EQUALS);
+	  tt(Start, '!', EXCLAMATION);
+	  tt(Start, '-', HYPHEN);
+	  tt(Start, '%', PERCENT);
+	  tt(Start, '|', PIPE);
+	  tt(Start, '+', PLUS);
+	  tt(Start, '#', POUND);
+	  tt(Start, '?', QUERY);
+	  tt(Start, '"', QUOTE);
+	  tt(Start, '/', SLASH);
+	  tt(Start, ';', SEMI);
+	  tt(Start, '~', TILDE);
+	  tt(Start, '_', UNDERSCORE);
+	  tt(Start, '\\', BACKSLASH);
+	  var Num = tr(Start, DIGIT, NUM, (_tr = {}, _tr[numeric] = true, _tr));
+	  tr(Num, DIGIT, Num); // State which emits a word token
+
+	  var Word = tr(Start, ASCII_LETTER, WORD, (_tr2 = {}, _tr2[ascii] = true, _tr2));
+	  tr(Word, ASCII_LETTER, Word); // Same as previous, but specific to non-fsm.ascii alphabet words
+
+	  var UWord = tr(Start, LETTER, UWORD, (_tr3 = {}, _tr3[alpha] = true, _tr3));
+	  tr(UWord, ASCII_LETTER); // Non-accepting
+
+	  tr(UWord, LETTER, UWord); // Whitespace jumps
+	  // Tokens of only non-newline whitespace are arbitrarily long
+	  // If any whitespace except newline, more whitespace!
+
+	  var Ws = tr(Start, SPACE, WS, (_tr4 = {}, _tr4[whitespace] = true, _tr4));
+	  tt(Start, NL, NL$1, (_tt = {}, _tt[whitespace] = true, _tt));
+	  tt(Ws, NL); // non-accepting state to avoid mixing whitespaces
+
+	  tr(Ws, SPACE, Ws); // Emoji tokens. They are not grouped by the scanner except in cases where a
+	  // zero-width joiner is present
+
+	  var Emoji = tr(Start, EMOJI, EMOJI$1, (_tr5 = {}, _tr5[emoji] = true, _tr5));
+	  tr(Emoji, EMOJI, Emoji);
+	  tt(Emoji, EMOJI_VARIATION, Emoji); // tt(Start, EMOJI_VARIATION, Emoji); // This one is sketchy
+
+	  var EmojiJoiner = tt(Emoji, EMOJI_JOINER);
+	  tr(EmojiJoiner, EMOJI, Emoji); // tt(EmojiJoiner, EMOJI_VARIATION, Emoji); // also sketchy
+	  // Generates states for top-level domains
+	  // Note that this is most accurate when tlds are in alphabetical order
+
+	  var wordjr = [[ASCII_LETTER, Word]];
+	  var uwordjr = [[ASCII_LETTER, null], [LETTER, UWord]];
+
+	  for (var i = 0; i < tlds.length; i++) {
+	    fastts(Start, tlds[i], TLD, WORD, wordjr);
+	  }
+
+	  for (var _i = 0; _i < utlds.length; _i++) {
+	    fastts(Start, utlds[_i], UTLD, UWORD, uwordjr);
+	  }
+
+	  addToGroups(TLD, {
+	    tld: true,
+	    ascii: true
+	  }, groups);
+	  addToGroups(UTLD, {
+	    utld: true,
+	    alpha: true
+	  }, groups); // Collect the states generated by different protocols. NOTE: If any new TLDs
+	  // get added that are also protocols, set the token to be the same as the
+	  // protocol to ensure parsing works as expected.
+
+	  fastts(Start, 'file', SCHEME, WORD, wordjr);
+	  fastts(Start, 'mailto', SCHEME, WORD, wordjr);
+	  fastts(Start, 'http', SLASH_SCHEME, WORD, wordjr);
+	  fastts(Start, 'https', SLASH_SCHEME, WORD, wordjr);
+	  fastts(Start, 'ftp', SLASH_SCHEME, WORD, wordjr);
+	  fastts(Start, 'ftps', SLASH_SCHEME, WORD, wordjr);
+	  addToGroups(SCHEME, {
+	    scheme: true,
+	    ascii: true
+	  }, groups);
+	  addToGroups(SLASH_SCHEME, {
+	    slashscheme: true,
+	    ascii: true
+	  }, groups); // Register custom schemes. Assumes each scheme is asciinumeric with hyphens
+
+	  customSchemes = customSchemes.sort(function (a, b) {
+	    return a[0] > b[0] ? 1 : -1;
+	  });
+
+	  for (var _i2 = 0; _i2 < customSchemes.length; _i2++) {
+	    var _ref, _ref2;
+
+	    var sch = customSchemes[_i2][0];
+	    var optionalSlashSlash = customSchemes[_i2][1];
+	    var flags = optionalSlashSlash ? (_ref = {}, _ref[scheme] = true, _ref) : (_ref2 = {}, _ref2[slashscheme] = true, _ref2);
+
+	    if (sch.indexOf('-') >= 0) {
+	      flags[domain] = true;
+	    } else if (!ASCII_LETTER.test(sch)) {
+	      flags[numeric] = true; // numbers only
+	    } else if (DIGIT.test(sch)) {
+	      flags[asciinumeric] = true;
+	    } else {
+	      flags[ascii] = true;
+	    }
+
+	    ts(Start, sch, sch, flags);
+	  } // Localhost token
+
+
+	  ts(Start, 'localhost', LOCALHOST, {
+	    ascii: true
+	  }); // Set default transition for start state (some symbol)
+
+	  Start.jd = new State(SYM);
+	  return {
+	    start: Start,
+	    tokens: assign({
+	      groups: groups
+	    }, tk)
+	  };
+	}
+	/**
+		Given a string, returns an array of TOKEN instances representing the
+		composition of that string.
+
+		@method run
+		@param {State<string>} start scanner starting state
+		@param {string} str input string to scan
+		@return {Token[]} list of tokens, each with a type and value
+	*/
+
+	function run$1(start, str) {
+	  // State machine is not case sensitive, so input is tokenized in lowercased
+	  // form (still returns regular case). Uses selective `toLowerCase` because
+	  // lowercasing the entire string causes the length and character position to
+	  // vary in some non-English strings with V8-based runtimes.
+	  var iterable = stringToArray(str.replace(/[A-Z]/g, function (c) {
+	    return c.toLowerCase();
+	  }));
+	  var charCount = iterable.length; // <= len if there are emojis, etc
+
+	  var tokens = []; // return value
+	  // cursor through the string itself, accounting for characters that have
+	  // width with length 2 such as emojis
+
+	  var cursor = 0; // Cursor through the array-representation of the string
+
+	  var charCursor = 0; // Tokenize the string
+
+	  while (charCursor < charCount) {
+	    var state = start;
+	    var nextState = null;
+	    var tokenLength = 0;
+	    var latestAccepting = null;
+	    var sinceAccepts = -1;
+	    var charsSinceAccepts = -1;
+
+	    while (charCursor < charCount && (nextState = state.go(iterable[charCursor]))) {
+	      state = nextState; // Keep track of the latest accepting state
+
+	      if (state.accepts()) {
+	        sinceAccepts = 0;
+	        charsSinceAccepts = 0;
+	        latestAccepting = state;
+	      } else if (sinceAccepts >= 0) {
+	        sinceAccepts += iterable[charCursor].length;
+	        charsSinceAccepts++;
+	      }
+
+	      tokenLength += iterable[charCursor].length;
+	      cursor += iterable[charCursor].length;
+	      charCursor++;
+	    } // Roll back to the latest accepting state
+
+
+	    cursor -= sinceAccepts;
+	    charCursor -= charsSinceAccepts;
+	    tokenLength -= sinceAccepts; // No more jumps, just make a new token from the last accepting one
+
+	    tokens.push({
+	      t: latestAccepting.t,
+	      // token type/name
+	      v: str.slice(cursor - tokenLength, cursor),
+	      // string value
+	      s: cursor - tokenLength,
+	      // start index
+	      e: cursor // end index (excluding)
+
+	    });
+	  }
+
+	  return tokens;
+	}
+	/**
+	 * Convert a String to an Array of characters, taking into account that some
+	 * characters like emojis take up two string indexes.
+	 *
+	 * Adapted from core-js (MIT license)
+	 * https://github.com/zloirock/core-js/blob/2d69cf5f99ab3ea3463c395df81e5a15b68f49d9/packages/core-js/internals/string-multibyte.js
+	 *
+	 * @function stringToArray
+	 * @param {string} str
+	 * @returns {string[]}
+	 */
+
+	function stringToArray(str) {
+	  var result = [];
+	  var len = str.length;
+	  var index = 0;
+
+	  while (index < len) {
+	    var first = str.charCodeAt(index);
+	    var second = void 0;
+	    var char = first < 0xd800 || first > 0xdbff || index + 1 === len || (second = str.charCodeAt(index + 1)) < 0xdc00 || second > 0xdfff ? str[index] // single character
+	    : str.slice(index, index + 2); // two-index characters
+
+	    result.push(char);
+	    index += char.length;
+	  }
+
+	  return result;
+	}
+	/**
+	 * Fast version of ts function for when transition defaults are well known
+	 * @param {State<string>} state
+	 * @param {string} input
+	 * @param {string} t
+	 * @param {string} defaultt
+	 * @param {[RegExp, State<string>][]} jr
+	 * @returns {State<string>}
+	 */
+
+	function fastts(state, input, t, defaultt, jr) {
+	  var next;
+	  var len = input.length;
+
+	  for (var i = 0; i < len - 1; i++) {
+	    var char = input[i];
+
+	    if (state.j[char]) {
+	      next = state.j[char];
+	    } else {
+	      next = new State(defaultt);
+	      next.jr = jr.slice();
+	      state.j[char] = next;
+	    }
+
+	    state = next;
+	  }
+
+	  next = new State(t);
+	  next.jr = jr.slice();
+	  state.j[input[len - 1]] = next;
+	  return next;
+	}
 
-	// States for special URL symbols
-	S_START.on('@', makeState(AT)).on('.', makeState(DOT)).on('+', makeState(PLUS)).on('#', makeState(POUND)).on('?', makeState(QUERY)).on('/', makeState(SLASH)).on('_', makeState(UNDERSCORE)).on(':', makeState(COLON)).on('{', makeState(OPENBRACE)).on('[', makeState(OPENBRACKET)).on('<', makeState(OPENANGLEBRACKET)).on('(', makeState(OPENPAREN)).on('}', makeState(CLOSEBRACE)).on(']', makeState(CLOSEBRACKET)).on('>', makeState(CLOSEANGLEBRACKET)).on(')', makeState(CLOSEPAREN)).on([',', ';', '!', '"', '\''], makeState(PUNCTUATION));
+	function _inheritsLoose(subClass, superClass) {
+	  subClass.prototype = Object.create(superClass.prototype);
+	  subClass.prototype.constructor = subClass;
 
-	// Whitespace jumps
-	// Tokens of only non-newline whitespace are arbitrarily long
-	S_START.on('\n', makeState(TNL)).on(WHITESPACE, S_WS);
+	  _setPrototypeOf(subClass, superClass);
+	}
 
-	// If any whitespace except newline, more whitespace!
-	S_WS.on(WHITESPACE, S_WS);
+	function _setPrototypeOf(o, p) {
+	  _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
+	    o.__proto__ = p;
+	    return o;
+	  };
 
-	// Generates states for top-level domains
-	// Note that this is most accurate when tlds are in alphabetical order
-	for (var i = 0; i < tlds.length; i++) {
-		var newStates = stateify(tlds[i], S_START, TLD, DOMAIN);
-		domainStates.push.apply(domainStates, newStates);
+	  return _setPrototypeOf(o, p);
 	}
 
-	// Collect the states generated by different protocls
-	var partialProtocolFileStates = stateify('file', S_START, DOMAIN, DOMAIN);
-	var partialProtocolFtpStates = stateify('ftp', S_START, DOMAIN, DOMAIN);
-	var partialProtocolHttpStates = stateify('http', S_START, DOMAIN, DOMAIN);
+	/**
+	 * An object where each key is a valid DOM Event Name such as `click` or `focus`
+	 * and each value is an event handler function.
+	 *
+	 * https://developer.mozilla.org/en-US/docs/Web/API/Element#events
+	 * @typedef {?{ [event: string]: Function }} EventListeners
+	 */
 
-	// Add the states to the array of DOMAINeric states
-	domainStates.push.apply(domainStates, partialProtocolFileStates);
-	domainStates.push.apply(domainStates, partialProtocolFtpStates);
-	domainStates.push.apply(domainStates, partialProtocolHttpStates);
+	/**
+	 * All formatted properties required to render a link, including `tagName`,
+	 * `attributes`, `content` and `eventListeners`.
+	 * @typedef {{ tagName: any, attributes: {[attr: string]: any}, content: string,
+	 * eventListeners: EventListeners }} IntermediateRepresentation
+	 */
 
-	// Protocol states
-	var S_PROTOCOL_FILE = partialProtocolFileStates.pop();
-	var S_PROTOCOL_FTP = partialProtocolFtpStates.pop();
-	var S_PROTOCOL_HTTP = partialProtocolHttpStates.pop();
-	var S_PROTOCOL_SECURE = makeState(DOMAIN);
-	var S_FULL_PROTOCOL = makeState(PROTOCOL); // Full protocol ends with COLON
+	/**
+	 * Specify either an object described by the template type `O` or a function.
+	 *
+	 * The function takes a string value (usually the link's href attribute), the
+	 * link type (`'url'`, `'hashtag`', etc.) and an internal token representation
+	 * of the link. It should return an object of the template type `O`
+	 * @template O
+	 * @typedef {O | ((value: string, type: string, token: MultiToken) => O)} OptObj
+	 */
 
-	// Secure protocols (end with 's')
-	S_PROTOCOL_FTP.on('s', S_PROTOCOL_SECURE).on(':', S_FULL_PROTOCOL);
+	/**
+	 * Specify either a function described by template type `F` or an object.
+	 *
+	 * Each key in the object should be a link type (`'url'`, `'hashtag`', etc.). Each
+	 * value should be a function with template type `F` that is called when the
+	 * corresponding link type is encountered.
+	 * @template F
+	 * @typedef {F | { [type: string]: F}} OptFn
+	 */
 
-	S_PROTOCOL_HTTP.on('s', S_PROTOCOL_SECURE).on(':', S_FULL_PROTOCOL);
+	/**
+	 * Specify either a value with template type `V`, a function that returns `V` or
+	 * an object where each value resolves to `V`.
+	 *
+	 * The function takes a string value (usually the link's href attribute), the
+	 * link type (`'url'`, `'hashtag`', etc.) and an internal token representation
+	 * of the link. It should return an object of the template type `V`
+	 *
+	 * For the object, each key should be a link type (`'url'`, `'hashtag`', etc.).
+	 * Each value should either have type `V` or a function that returns V. This
+	 * function similarly takes a string value and a token.
+	 *
+	 * Example valid types for `Opt<string>`:
+	 *
+	 * ```js
+	 * 'hello'
+	 * (value, type, token) => 'world'
+	 * { url: 'hello', email: (value, token) => 'world'}
+	 * ```
+	 * @template V
+	 * @typedef {V | ((value: string, type: string, token: MultiToken) => V) | { [type: string]: V | ((value: string, token: MultiToken) => V) }} Opt
+	 */
 
-	domainStates.push(S_PROTOCOL_SECURE);
+	/**
+	 * See available options: https://linkify.js.org/docs/options.html
+	 * @typedef {{
+	 * 	defaultProtocol?: string,
+	 *  events?: OptObj<EventListeners>,
+	 * 	format?: Opt<string>,
+	 * 	formatHref?: Opt<string>,
+	 * 	nl2br?: boolean,
+	 * 	tagName?: Opt<any>,
+	 * 	target?: Opt<string>,
+	 * 	rel?: Opt<string>,
+	 * 	validate?: Opt<boolean>,
+	 * 	truncate?: Opt<number>,
+	 * 	className?: Opt<string>,
+	 * 	attributes?: OptObj<({ [attr: string]: any })>,
+	 *  ignoreTags?: string[],
+	 * 	render?: OptFn<((ir: IntermediateRepresentation) => any)>
+	 * }} Opts
+	 */
 
-	// Become protocol tokens after a COLON
-	S_PROTOCOL_FILE.on(':', S_FULL_PROTOCOL);
-	S_PROTOCOL_SECURE.on(':', S_FULL_PROTOCOL);
+	/**
+	 * @type Required<Opts>
+	 */
 
-	// Localhost
-	var partialLocalhostStates = stateify('localhost', S_START, LOCALHOST, DOMAIN);
-	domainStates.push.apply(domainStates, partialLocalhostStates);
+	var defaults = {
+	  defaultProtocol: 'http',
+	  events: null,
+	  format: noop,
+	  formatHref: noop,
+	  nl2br: false,
+	  tagName: 'a',
+	  target: null,
+	  rel: null,
+	  validate: true,
+	  truncate: Infinity,
+	  className: null,
+	  attributes: null,
+	  ignoreTags: [],
+	  render: null
+	};
+	/**
+	 * Utility class for linkify interfaces to apply specified
+	 * {@link Opts formatting and rendering options}.
+	 *
+	 * @param {Opts | Options} [opts] Option value overrides.
+	 * @param {(ir: IntermediateRepresentation) => any} [defaultRender] (For
+	 *   internal use) default render function that determines how to generate an
+	 *   HTML element based on a link token's derived tagName, attributes and HTML.
+	 *   Similar to render option
+	 */
 
-	// Everything else
-	// DOMAINs make more DOMAINs
-	// Number and character transitions
-	S_START.on(NUMBERS, S_NUM);
-	S_NUM.on('-', S_DOMAIN_HYPHEN).on(NUMBERS, S_NUM).on(ALPHANUM, S_DOMAIN); // number becomes DOMAIN
+	function Options(opts, defaultRender) {
+	  if (defaultRender === void 0) {
+	    defaultRender = null;
+	  }
 
-	S_DOMAIN.on('-', S_DOMAIN_HYPHEN).on(ALPHANUM, S_DOMAIN);
+	  var o = assign({}, defaults);
 
-	// All the generated states should have a jump to DOMAIN
-	for (var _i = 0; _i < domainStates.length; _i++) {
-		domainStates[_i].on('-', S_DOMAIN_HYPHEN).on(ALPHANUM, S_DOMAIN);
-	}
+	  if (opts) {
+	    o = assign(o, opts instanceof Options ? opts.o : opts);
+	  } // Ensure all ignored tags are uppercase
 
-	S_DOMAIN_HYPHEN.on('-', S_DOMAIN_HYPHEN).on(NUMBERS, S_DOMAIN).on(ALPHANUM, S_DOMAIN);
 
-	// Set default transition
-	S_START.defaultTransition = makeState(SYM);
+	  var ignoredTags = o.ignoreTags;
+	  var uppercaseIgnoredTags = [];
 
-	/**
- 	Given a string, returns an array of TOKEN instances representing the
- 	composition of that string.
-
- 	@method run
- 	@param {String} str Input string to scan
- 	@return {Array} Array of TOKEN instances
- */
-	var run = function run(str) {
-
-		// The state machine only looks at lowercase strings.
-		// This selective `toLowerCase` is used because lowercasing the entire
-		// string causes the length and character position to vary in some in some
-		// non-English strings. This happens only on V8-based runtimes.
-		var lowerStr = str.replace(/[A-Z]/g, function (c) {
-			return c.toLowerCase();
-		});
-		var len = str.length;
-		var tokens = []; // return value
-
-		var cursor = 0;
-
-		// Tokenize the string
-		while (cursor < len) {
-			var state = S_START;
-			var secondState = null;
-			var nextState = null;
-			var tokenLength = 0;
-			var latestAccepting = null;
-			var sinceAccepts = -1;
-
-			while (cursor < len && (nextState = state.next(lowerStr[cursor]))) {
-				secondState = null;
-				state = nextState;
-
-				// Keep track of the latest accepting state
-				if (state.accepts()) {
-					sinceAccepts = 0;
-					latestAccepting = state;
-				} else if (sinceAccepts >= 0) {
-					sinceAccepts++;
-				}
-
-				tokenLength++;
-				cursor++;
-			}
-
-			if (sinceAccepts < 0) {
-				continue;
-			} // Should never happen
-
-			// Roll back to the latest accepting state
-			cursor -= sinceAccepts;
-			tokenLength -= sinceAccepts;
-
-			// Get the class for the new token
-			var TOKEN = latestAccepting.emit(); // Current token class
-
-			// No more jumps, just make a new token
-			tokens.push(new TOKEN(str.substr(cursor - tokenLength, tokenLength)));
-		}
-
-		return tokens;
+	  for (var i = 0; i < ignoredTags.length; i++) {
+	    uppercaseIgnoredTags.push(ignoredTags[i].toUpperCase());
+	  }
+	  /** @protected */
+
+
+	  this.o = o;
+
+	  if (defaultRender) {
+	    this.defaultRender = defaultRender;
+	  }
+
+	  this.ignoreTags = uppercaseIgnoredTags;
+	}
+	Options.prototype = {
+	  o: defaults,
+
+	  /**
+	   * @type string[]
+	   */
+	  ignoreTags: [],
+
+	  /**
+	   * @param {IntermediateRepresentation} ir
+	   * @returns {any}
+	   */
+	  defaultRender: function defaultRender(ir) {
+	    return ir;
+	  },
+
+	  /**
+	   * Returns true or false based on whether a token should be displayed as a
+	   * link based on the user options.
+	   * @param {MultiToken} token
+	   * @returns {boolean}
+	   */
+	  check: function check(token) {
+	    return this.get('validate', token.toString(), token);
+	  },
+	  // Private methods
+
+	  /**
+	   * Resolve an option's value based on the value of the option and the given
+	   * params. If operator and token are specified and the target option is
+	   * callable, automatically calls the function with the given argument.
+	   * @template {keyof Opts} K
+	   * @param {K} key Name of option to use
+	   * @param {string} [operator] will be passed to the target option if it's a
+	   * function. If not specified, RAW function value gets returned
+	   * @param {MultiToken} [token] The token from linkify.tokenize
+	   * @returns {Opts[K] | any}
+	   */
+	  get: function get(key, operator, token) {
+	    var isCallable = operator != null;
+	    var option = this.o[key];
+
+	    if (!option) {
+	      return option;
+	    }
+
+	    if (typeof option === 'object') {
+	      option = token.t in option ? option[token.t] : defaults[key];
+
+	      if (typeof option === 'function' && isCallable) {
+	        option = option(operator, token);
+	      }
+	    } else if (typeof option === 'function' && isCallable) {
+	      option = option(operator, token.t, token);
+	    }
+
+	    return option;
+	  },
+
+	  /**
+	   * @template {keyof Opts} L
+	   * @param {L} key Name of options object to use
+	   * @param {string} [operator]
+	   * @param {MultiToken} [token]
+	   * @returns {Opts[L] | any}
+	   */
+	  getObj: function getObj(key, operator, token) {
+	    var obj = this.o[key];
+
+	    if (typeof obj === 'function' && operator != null) {
+	      obj = obj(operator, token.t, token);
+	    }
+
+	    return obj;
+	  },
+
+	  /**
+	   * Convert the given token to a rendered element that may be added to the
+	   * calling-interface's DOM
+	   * @param {MultiToken} token Token to render to an HTML element
+	   * @returns {any} Render result; e.g., HTML string, DOM element, React
+	   *   Component, etc.
+	   */
+	  render: function render(token) {
+	    var ir = token.render(this); // intermediate representation
+
+	    var renderFn = this.get('render', null, token) || this.defaultRender;
+	    return renderFn(ir, token.t, token);
+	  }
 	};
 
-	var start = S_START;
+	function noop(val) {
+	  return val;
+	}
 
-	var scanner = Object.freeze({
-		State: CharacterState,
-		TOKENS: TOKENS,
-		run: run,
-		start: start
+	var options = /*#__PURE__*/Object.freeze({
+		__proto__: null,
+		defaults: defaults,
+		Options: Options,
+		assign: assign
 	});
 
 	/******************************************************************************
- 	Multi-Tokens
- 	Tokens composed of arrays of TextTokens
- ******************************************************************************/
-
-	// Is the given token a valid domain token?
-	// Should nums be included here?
-	function isDomainToken(token) {
-		return token instanceof DOMAIN || token instanceof TLD;
+		Multi-Tokens
+		Tokens composed of arrays of TextTokens
+	******************************************************************************/
+
+	/**
+	 * @param {string} value
+	 * @param {Token[]} tokens
+	 */
+
+	function MultiToken(value, tokens) {
+	  this.t = 'token';
+	  this.v = value;
+	  this.tk = tokens;
 	}
+	/**
+	 * Abstract class used for manufacturing tokens of text tokens. That is rather
+	 * than the value for a token being a small string of text, it's value an array
+	 * of text tokens.
+	 *
+	 * Used for grouping together URLs, emails, hashtags, and other potential
+	 * creations.
+	 * @class MultiToken
+	 * @property {string} t
+	 * @property {string} v
+	 * @property {Token[]} tk
+	 * @abstract
+	 */
 
+	MultiToken.prototype = {
+	  isLink: false,
+
+	  /**
+	   * Return the string this token represents.
+	   * @return {string}
+	   */
+	  toString: function toString() {
+	    return this.v;
+	  },
+
+	  /**
+	   * What should the value for this token be in the `href` HTML attribute?
+	   * Returns the `.toString` value by default.
+	   * @param {string} [scheme]
+	   * @return {string}
+	  */
+	  toHref: function toHref(scheme) {
+	    return this.toString();
+	  },
+
+	  /**
+	   * @param {Options} options Formatting options
+	   * @returns {string}
+	   */
+	  toFormattedString: function toFormattedString(options) {
+	    var val = this.toString();
+	    var truncate = options.get('truncate', val, this);
+	    var formatted = options.get('format', val, this);
+	    return truncate && formatted.length > truncate ? formatted.substring(0, truncate) + '…' : formatted;
+	  },
+
+	  /**
+	   *
+	   * @param {Options} options
+	   * @returns {string}
+	   */
+	  toFormattedHref: function toFormattedHref(options) {
+	    return options.get('formatHref', this.toHref(options.get('defaultProtocol')), this);
+	  },
+
+	  /**
+	   * The start index of this token in the original input string
+	   * @returns {number}
+	   */
+	  startIndex: function startIndex() {
+	    return this.tk[0].s;
+	  },
+
+	  /**
+	   * The end index of this token in the original input string (up to this
+	   * index but not including it)
+	   * @returns {number}
+	   */
+	  endIndex: function endIndex() {
+	    return this.tk[this.tk.length - 1].e;
+	  },
+
+	  /**
+	  	Returns an object  of relevant values for this token, which includes keys
+	  	* type - Kind of token ('url', 'email', etc.)
+	  	* value - Original text
+	  	* href - The value that should be added to the anchor tag's href
+	  		attribute
+	  		@method toObject
+	  	@param {string} [protocol] `'http'` by default
+	  */
+	  toObject: function toObject(protocol) {
+	    if (protocol === void 0) {
+	      protocol = defaults.defaultProtocol;
+	    }
+
+	    return {
+	      type: this.t,
+	      value: this.toString(),
+	      isLink: this.isLink,
+	      href: this.toHref(protocol),
+	      start: this.startIndex(),
+	      end: this.endIndex()
+	    };
+	  },
+
+	  /**
+	   *
+	   * @param {Options} options Formatting option
+	   */
+	  toFormattedObject: function toFormattedObject(options) {
+	    return {
+	      type: this.t,
+	      value: this.toFormattedString(options),
+	      isLink: this.isLink,
+	      href: this.toFormattedHref(options),
+	      start: this.startIndex(),
+	      end: this.endIndex()
+	    };
+	  },
+
+	  /**
+	   * Whether this token should be rendered as a link according to the given options
+	   * @param {Options} options
+	   * @returns {boolean}
+	   */
+	  validate: function validate(options) {
+	    return options.get('validate', this.toString(), this);
+	  },
+
+	  /**
+	   * Return an object that represents how this link should be rendered.
+	   * @param {Options} options Formattinng options
+	   */
+	  render: function render(options) {
+	    var token = this;
+	    var href = this.toFormattedHref(options);
+	    var tagName = options.get('tagName', href, token);
+	    var content = this.toFormattedString(options);
+	    var attributes = {};
+	    var className = options.get('className', href, token);
+	    var target = options.get('target', href, token);
+	    var rel = options.get('rel', href, token);
+	    var attrs = options.getObj('attributes', href, token);
+	    var eventListeners = options.getObj('events', href, token);
+	    attributes.href = href;
+
+	    if (className) {
+	      attributes.class = className;
+	    }
+
+	    if (target) {
+	      attributes.target = target;
+	    }
+
+	    if (rel) {
+	      attributes.rel = rel;
+	    }
+
+	    if (attrs) {
+	      assign(attributes, attrs);
+	    }
+
+	    return {
+	      tagName: tagName,
+	      attributes: attributes,
+	      content: content,
+	      eventListeners: eventListeners
+	    };
+	  }
+	}; // Base token
 	/**
- 	Abstract class used for manufacturing tokens of text tokens. That is rather
- 	than the value for a token being a small string of text, it's value an array
- 	of text tokens.
+	 * Create a new token that can be emitted by the parser state machine
+	 * @param {string} type readable type of the token
+	 * @param {object} props properties to assign or override, including isLink = true or false
+	 * @returns {new (value: string, tokens: Token[]) => MultiToken} new token class
+	 */
 
- 	Used for grouping together URLs, emails, hashtags, and other potential
- 	creations.
+	function createTokenClass(type, props) {
+	  var Token = /*#__PURE__*/function (_MultiToken) {
+	    _inheritsLoose(Token, _MultiToken);
 
- 	@class MultiToken
- 	@abstract
- */
-	var MultiToken = createTokenClass();
+	    function Token(value, tokens) {
+	      var _this;
 
-	MultiToken.prototype = {
-		/**
-  	String representing the type for this token
-  	@property type
-  	@default 'TOKEN'
-  */
-		type: 'token',
-
-		/**
-  	Is this multitoken a link?
-  	@property isLink
-  	@default false
-  */
-		isLink: false,
-
-		/**
-  	Return the string this token represents.
-  	@method toString
-  	@return {String}
-  */
-		toString: function toString() {
-			var result = [];
-			for (var _i2 = 0; _i2 < this.v.length; _i2++) {
-				result.push(this.v[_i2].toString());
-			}
-			return result.join('');
-		},
-
-
-		/**
-  	What should the value for this token be in the `href` HTML attribute?
-  	Returns the `.toString` value by default.
-  		@method toHref
-  	@return {String}
-  */
-		toHref: function toHref() {
-			return this.toString();
-		},
-
-
-		/**
-  	Returns a hash of relevant values for this token, which includes keys
-  	* type - Kind of token ('url', 'email', etc.)
-  	* value - Original text
-  	* href - The value that should be added to the anchor tag's href
-  		attribute
-  		@method toObject
-  	@param {String} [protocol] `'http'` by default
-  	@return {Object}
-  */
-		toObject: function toObject() {
-			var protocol = arguments.length <= 0 || arguments[0] === undefined ? 'http' : arguments[0];
-
-			return {
-				type: this.type,
-				value: this.toString(),
-				href: this.toHref(protocol)
-			};
-		}
-	};
+	      _this = _MultiToken.call(this, value, tokens) || this;
+	      _this.t = type;
+	      return _this;
+	    }
+
+	    return Token;
+	  }(MultiToken);
 
+	  for (var p in props) {
+	    Token.prototype[p] = props[p];
+	  }
+
+	  Token.t = type;
+	  return Token;
+	}
 	/**
- 	Represents a list of tokens making up a valid email address
- 	@class EMAIL
- 	@extends MultiToken
- */
-	var EMAIL = inherits(MultiToken, createTokenClass(), {
-		type: 'email',
-		isLink: true,
-		toHref: function toHref() {
-			return 'mailto:' + this.toString();
-		}
+		Represents a list of tokens making up a valid email address
+	*/
+
+	var Email = createTokenClass('email', {
+	  isLink: true,
+	  toHref: function toHref() {
+	    return 'mailto:' + this.toString();
+	  }
 	});
-
 	/**
- 	Represents some plain text
- 	@class TEXT
- 	@extends MultiToken
- */
-	var TEXT = inherits(MultiToken, createTokenClass(), { type: 'text' });
+		Represents some plain text
+	*/
 
+	var Text = createTokenClass('text');
 	/**
- 	Multi-linebreak token - represents a line break
- 	@class NL
- 	@extends MultiToken
- */
-	var NL = inherits(MultiToken, createTokenClass(), { type: 'nl' });
+		Multi-linebreak token - represents a line break
+		@class Nl
+	*/
 
+	var Nl = createTokenClass('nl');
 	/**
- 	Represents a list of tokens making up a valid URL
- 	@class URL
- 	@extends MultiToken
- */
-	var URL = inherits(MultiToken, createTokenClass(), {
-		type: 'url',
-		isLink: true,
-
-		/**
-  	Lowercases relevant parts of the domain and adds the protocol if
-  	required. Note that this will not escape unsafe HTML characters in the
-  	URL.
-  		@method href
-  	@param {String} protocol
-  	@return {String}
-  */
-		toHref: function toHref() {
-			var protocol = arguments.length <= 0 || arguments[0] === undefined ? 'http' : arguments[0];
-
-			var hasProtocol = false;
-			var hasSlashSlash = false;
-			var tokens = this.v;
-			var result = [];
-			var i = 0;
-
-			// Make the first part of the domain lowercase
-			// Lowercase protocol
-			while (tokens[i] instanceof PROTOCOL) {
-				hasProtocol = true;
-				result.push(tokens[i].toString().toLowerCase());
-				i++;
-			}
-
-			// Skip slash-slash
-			while (tokens[i] instanceof SLASH) {
-				hasSlashSlash = true;
-				result.push(tokens[i].toString());
-				i++;
-			}
-
-			// Lowercase all other characters in the domain
-			while (isDomainToken(tokens[i])) {
-				result.push(tokens[i].toString().toLowerCase());
-				i++;
-			}
-
-			// Leave all other characters as they were written
-			for (; i < tokens.length; i++) {
-				result.push(tokens[i].toString());
-			}
-
-			result = result.join('');
-
-			if (!(hasProtocol || hasSlashSlash)) {
-				result = protocol + '://' + result;
-			}
-
-			return result;
-		},
-		hasProtocol: function hasProtocol() {
-			return this.v[0] instanceof PROTOCOL;
-		}
+		Represents a list of text tokens making up a valid URL
+		@class Url
+	*/
+
+	var Url = createTokenClass('url', {
+	  isLink: true,
+
+	  /**
+	  	Lowercases relevant parts of the domain and adds the protocol if
+	  	required. Note that this will not escape unsafe HTML characters in the
+	  	URL.
+	  		@param {string} [scheme] default scheme (e.g., 'https')
+	  	@return {string} the full href
+	  */
+	  toHref: function toHref(scheme) {
+	    if (scheme === void 0) {
+	      scheme = defaults.defaultProtocol;
+	    }
+
+	    // Check if already has a prefix scheme
+	    return this.hasProtocol() ? this.v : scheme + "://" + this.v;
+	  },
+
+	  /**
+	   * Check whether this URL token has a protocol
+	   * @return {boolean}
+	   */
+	  hasProtocol: function hasProtocol() {
+	    var tokens = this.tk;
+	    return tokens.length >= 2 && tokens[0].t !== LOCALHOST && tokens[1].t === COLON;
+	  }
 	});
 
-	var TOKENS$1 = Object.freeze({
+	var multi = /*#__PURE__*/Object.freeze({
+		__proto__: null,
+		MultiToken: MultiToken,
 		Base: MultiToken,
-		EMAIL: EMAIL,
-		NL: NL,
-		TEXT: TEXT,
-		URL: URL
+		createTokenClass: createTokenClass,
+		Email: Email,
+		Text: Text,
+		Nl: Nl,
+		Url: Url
 	});
 
 	/**
- 	Not exactly parser, more like the second-stage scanner (although we can
- 	theoretically hotswap the code here with a real parser in the future... but
- 	for a little URL-finding utility abstract syntax trees may be a little
- 	overkill).
-
- 	URL format: http://en.wikipedia.org/wiki/URI_scheme
- 	Email format: http://en.wikipedia.org/wiki/Email_address (links to RFC in
- 	reference)
-
- 	@module linkify
- 	@submodule parser
- 	@main parser
- */
-
-	var makeState$1 = function makeState$1(tokenClass) {
-		return new State(tokenClass);
+		Not exactly parser, more like the second-stage scanner (although we can
+		theoretically hotswap the code here with a real parser in the future... but
+		for a little URL-finding utility abstract syntax trees may be a little
+		overkill).
+
+		URL format: http://en.wikipedia.org/wiki/URI_scheme
+		Email format: http://en.wikipedia.org/wiki/EmailAddress (links to RFC in
+		reference)
+
+		@module linkify
+		@submodule parser
+		@main run
+	*/
+
+	var makeState = function makeState(arg) {
+	  return new State(arg);
 	};
+	/**
+	 * Generate the parser multi token-based state machine
+	 * @param {{ groups: Collections<string> }} tokens
+	 */
 
-	// The universal starting state.
-	var S_START$1 = makeState$1();
-
-	// Intermediate states for URLs. Note that domains that begin with a protocol
-	// are treated slighly differently from those that don't.
-	var S_PROTOCOL = makeState$1(); // e.g., 'http:'
-	var S_PROTOCOL_SLASH = makeState$1(); // e.g., '/', 'http:/''
-	var S_PROTOCOL_SLASH_SLASH = makeState$1(); // e.g., '//', 'http://'
-	var S_DOMAIN$1 = makeState$1(); // parsed string ends with a potential domain name (A)
-	var S_DOMAIN_DOT = makeState$1(); // (A) domain followed by DOT
-	var S_TLD = makeState$1(URL); // (A) Simplest possible URL with no query string
-	var S_TLD_COLON = makeState$1(); // (A) URL followed by colon (potential port number here)
-	var S_TLD_PORT = makeState$1(URL); // TLD followed by a port number
-	var S_URL = makeState$1(URL); // Long URL with optional port and maybe query string
-	var S_URL_NON_ACCEPTING = makeState$1(); // URL followed by some symbols (will not be part of the final URL)
-	var S_URL_OPENBRACE = makeState$1(); // URL followed by {
-	var S_URL_OPENBRACKET = makeState$1(); // URL followed by [
-	var S_URL_OPENANGLEBRACKET = makeState$1(); // URL followed by <
-	var S_URL_OPENPAREN = makeState$1(); // URL followed by (
-	var S_URL_OPENBRACE_Q = makeState$1(URL); // URL followed by { and some symbols that the URL can end it
-	var S_URL_OPENBRACKET_Q = makeState$1(URL); // URL followed by [ and some symbols that the URL can end it
-	var S_URL_OPENANGLEBRACKET_Q = makeState$1(URL); // URL followed by < and some symbols that the URL can end it
-	var S_URL_OPENPAREN_Q = makeState$1(URL); // URL followed by ( and some symbols that the URL can end it
-	var S_URL_OPENBRACE_SYMS = makeState$1(); // S_URL_OPENBRACE_Q followed by some symbols it cannot end it
-	var S_URL_OPENBRACKET_SYMS = makeState$1(); // S_URL_OPENBRACKET_Q followed by some symbols it cannot end it
-	var S_URL_OPENANGLEBRACKET_SYMS = makeState$1(); // S_URL_OPENANGLEBRACKET_Q followed by some symbols it cannot end it
-	var S_URL_OPENPAREN_SYMS = makeState$1(); // S_URL_OPENPAREN_Q followed by some symbols it cannot end it
-	var S_EMAIL_DOMAIN = makeState$1(); // parsed string starts with local email info + @ with a potential domain name (C)
-	var S_EMAIL_DOMAIN_DOT = makeState$1(); // (C) domain followed by DOT
-	var S_EMAIL = makeState$1(EMAIL); // (C) Possible email address (could have more tlds)
-	var S_EMAIL_COLON = makeState$1(); // (C) URL followed by colon (potential port number here)
-	var S_EMAIL_PORT = makeState$1(EMAIL); // (C) Email address with a port
-	var S_LOCALPART = makeState$1(); // Local part of the email address
-	var S_LOCALPART_AT = makeState$1(); // Local part of the email address plus @
-	var S_LOCALPART_DOT = makeState$1(); // Local part of the email address plus '.' (localpart cannot end in .)
-	var S_NL = makeState$1(NL); // single new line
-
-	// Make path from start to protocol (with '//')
-	S_START$1.on(TNL, S_NL).on(PROTOCOL, S_PROTOCOL).on(SLASH, S_PROTOCOL_SLASH);
-
-	S_PROTOCOL.on(SLASH, S_PROTOCOL_SLASH);
-	S_PROTOCOL_SLASH.on(SLASH, S_PROTOCOL_SLASH_SLASH);
-
-	// The very first potential domain name
-	S_START$1.on(TLD, S_DOMAIN$1).on(DOMAIN, S_DOMAIN$1).on(LOCALHOST, S_TLD).on(NUM, S_DOMAIN$1);
-
-	// Force URL for anything sane followed by protocol
-	S_PROTOCOL_SLASH_SLASH.on(TLD, S_URL).on(DOMAIN, S_URL).on(NUM, S_URL).on(LOCALHOST, S_URL);
-
-	// Account for dots and hyphens
-	// hyphens are usually parts of domain names
-	S_DOMAIN$1.on(DOT, S_DOMAIN_DOT);
-	S_EMAIL_DOMAIN.on(DOT, S_EMAIL_DOMAIN_DOT);
-
-	// Hyphen can jump back to a domain name
-
-	// After the first domain and a dot, we can find either a URL or another domain
-	S_DOMAIN_DOT.on(TLD, S_TLD).on(DOMAIN, S_DOMAIN$1).on(NUM, S_DOMAIN$1).on(LOCALHOST, S_DOMAIN$1);
-
-	S_EMAIL_DOMAIN_DOT.on(TLD, S_EMAIL).on(DOMAIN, S_EMAIL_DOMAIN).on(NUM, S_EMAIL_DOMAIN).on(LOCALHOST, S_EMAIL_DOMAIN);
-
-	// S_TLD accepts! But the URL could be longer, try to find a match greedily
-	// The `run` function should be able to "rollback" to the accepting state
-	S_TLD.on(DOT, S_DOMAIN_DOT);
-	S_EMAIL.on(DOT, S_EMAIL_DOMAIN_DOT);
-
-	// Become real URLs after `SLASH` or `COLON NUM SLASH`
-	// Here PSS and non-PSS converge
-	S_TLD.on(COLON, S_TLD_COLON).on(SLASH, S_URL);
-	S_TLD_COLON.on(NUM, S_TLD_PORT);
-	S_TLD_PORT.on(SLASH, S_URL);
-	S_EMAIL.on(COLON, S_EMAIL_COLON);
-	S_EMAIL_COLON.on(NUM, S_EMAIL_PORT);
-
-	// Types of characters the URL can definitely end in
-	var qsAccepting = [DOMAIN, AT, LOCALHOST, NUM, PLUS, POUND, PROTOCOL, SLASH, TLD, UNDERSCORE, SYM];
-
-	// Types of tokens that can follow a URL and be part of the query string
-	// but cannot be the very last characters
-	// Characters that cannot appear in the URL at all should be excluded
-	var qsNonAccepting = [COLON, DOT, QUERY, PUNCTUATION, CLOSEBRACE, CLOSEBRACKET, CLOSEANGLEBRACKET, CLOSEPAREN, OPENBRACE, OPENBRACKET, OPENANGLEBRACKET, OPENPAREN];
-
-	// These states are responsible primarily for determining whether or not to
-	// include the final round bracket.
-
-	// URL, followed by an opening bracket
-	S_URL.on(OPENBRACE, S_URL_OPENBRACE).on(OPENBRACKET, S_URL_OPENBRACKET).on(OPENANGLEBRACKET, S_URL_OPENANGLEBRACKET).on(OPENPAREN, S_URL_OPENPAREN);
-
-	// URL with extra symbols at the end, followed by an opening bracket
-	S_URL_NON_ACCEPTING.on(OPENBRACE, S_URL_OPENBRACE).on(OPENBRACKET, S_URL_OPENBRACKET).on(OPENANGLEBRACKET, S_URL_OPENANGLEBRACKET).on(OPENPAREN, S_URL_OPENPAREN);
-
-	// Closing bracket component. This character WILL be included in the URL
-	S_URL_OPENBRACE.on(CLOSEBRACE, S_URL);
-	S_URL_OPENBRACKET.on(CLOSEBRACKET, S_URL);
-	S_URL_OPENANGLEBRACKET.on(CLOSEANGLEBRACKET, S_URL);
-	S_URL_OPENPAREN.on(CLOSEPAREN, S_URL);
-	S_URL_OPENBRACE_Q.on(CLOSEBRACE, S_URL);
-	S_URL_OPENBRACKET_Q.on(CLOSEBRACKET, S_URL);
-	S_URL_OPENANGLEBRACKET_Q.on(CLOSEANGLEBRACKET, S_URL);
-	S_URL_OPENPAREN_Q.on(CLOSEPAREN, S_URL);
-	S_URL_OPENBRACE_SYMS.on(CLOSEBRACE, S_URL);
-	S_URL_OPENBRACKET_SYMS.on(CLOSEBRACKET, S_URL);
-	S_URL_OPENANGLEBRACKET_SYMS.on(CLOSEANGLEBRACKET, S_URL);
-	S_URL_OPENPAREN_SYMS.on(CLOSEPAREN, S_URL);
-
-	// URL that beings with an opening bracket, followed by a symbols.
-	// Note that the final state can still be `S_URL_OPENBRACE_Q` (if the URL only
-	// has a single opening bracket for some reason).
-	S_URL_OPENBRACE.on(qsAccepting, S_URL_OPENBRACE_Q);
-	S_URL_OPENBRACKET.on(qsAccepting, S_URL_OPENBRACKET_Q);
-	S_URL_OPENANGLEBRACKET.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
-	S_URL_OPENPAREN.on(qsAccepting, S_URL_OPENPAREN_Q);
-	S_URL_OPENBRACE.on(qsNonAccepting, S_URL_OPENBRACE_SYMS);
-	S_URL_OPENBRACKET.on(qsNonAccepting, S_URL_OPENBRACKET_SYMS);
-	S_URL_OPENANGLEBRACKET.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_SYMS);
-	S_URL_OPENPAREN.on(qsNonAccepting, S_URL_OPENPAREN_SYMS);
-
-	// URL that begins with an opening bracket, followed by some symbols
-	S_URL_OPENBRACE_Q.on(qsAccepting, S_URL_OPENBRACE_Q);
-	S_URL_OPENBRACKET_Q.on(qsAccepting, S_URL_OPENBRACKET_Q);
-	S_URL_OPENANGLEBRACKET_Q.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
-	S_URL_OPENPAREN_Q.on(qsAccepting, S_URL_OPENPAREN_Q);
-	S_URL_OPENBRACE_Q.on(qsNonAccepting, S_URL_OPENBRACE_Q);
-	S_URL_OPENBRACKET_Q.on(qsNonAccepting, S_URL_OPENBRACKET_Q);
-	S_URL_OPENANGLEBRACKET_Q.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_Q);
-	S_URL_OPENPAREN_Q.on(qsNonAccepting, S_URL_OPENPAREN_Q);
-
-	S_URL_OPENBRACE_SYMS.on(qsAccepting, S_URL_OPENBRACE_Q);
-	S_URL_OPENBRACKET_SYMS.on(qsAccepting, S_URL_OPENBRACKET_Q);
-	S_URL_OPENANGLEBRACKET_SYMS.on(qsAccepting, S_URL_OPENANGLEBRACKET_Q);
-	S_URL_OPENPAREN_SYMS.on(qsAccepting, S_URL_OPENPAREN_Q);
-	S_URL_OPENBRACE_SYMS.on(qsNonAccepting, S_URL_OPENBRACE_SYMS);
-	S_URL_OPENBRACKET_SYMS.on(qsNonAccepting, S_URL_OPENBRACKET_SYMS);
-	S_URL_OPENANGLEBRACKET_SYMS.on(qsNonAccepting, S_URL_OPENANGLEBRACKET_SYMS);
-	S_URL_OPENPAREN_SYMS.on(qsNonAccepting, S_URL_OPENPAREN_SYMS);
-
-	// Account for the query string
-	S_URL.on(qsAccepting, S_URL);
-	S_URL_NON_ACCEPTING.on(qsAccepting, S_URL);
-
-	S_URL.on(qsNonAccepting, S_URL_NON_ACCEPTING);
-	S_URL_NON_ACCEPTING.on(qsNonAccepting, S_URL_NON_ACCEPTING);
-
-	// Email address-specific state definitions
-	// Note: We are not allowing '/' in email addresses since this would interfere
-	// with real URLs
-
-	// Tokens allowed in the localpart of the email
-	var localpartAccepting = [DOMAIN, NUM, PLUS, POUND, QUERY, UNDERSCORE, SYM, TLD];
-
-	// Some of the tokens in `localpartAccepting` are already accounted for here and
-	// will not be overwritten (don't worry)
-	S_DOMAIN$1.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT);
-	S_TLD.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT);
-	S_DOMAIN_DOT.on(localpartAccepting, S_LOCALPART);
-
-	// Okay we're on a localpart. Now what?
-	// TODO: IP addresses and what if the email starts with numbers?
-	S_LOCALPART.on(localpartAccepting, S_LOCALPART).on(AT, S_LOCALPART_AT) // close to an email address now
-	.on(DOT, S_LOCALPART_DOT);
-	S_LOCALPART_DOT.on(localpartAccepting, S_LOCALPART);
-	S_LOCALPART_AT.on(TLD, S_EMAIL_DOMAIN).on(DOMAIN, S_EMAIL_DOMAIN).on(LOCALHOST, S_EMAIL);
-	// States following `@` defined above
-
-	var run$1 = function run$1(tokens) {
-		var len = tokens.length;
-		var cursor = 0;
-		var multis = [];
-		var textTokens = [];
-
-		while (cursor < len) {
-			var state = S_START$1;
-			var secondState = null;
-			var nextState = null;
-			var multiLength = 0;
-			var latestAccepting = null;
-			var sinceAccepts = -1;
-
-			while (cursor < len && !(secondState = state.next(tokens[cursor]))) {
-				// Starting tokens with nowhere to jump to.
-				// Consider these to be just plain text
-				textTokens.push(tokens[cursor++]);
-			}
-
-			while (cursor < len && (nextState = secondState || state.next(tokens[cursor]))) {
-
-				// Get the next state
-				secondState = null;
-				state = nextState;
-
-				// Keep track of the latest accepting state
-				if (state.accepts()) {
-					sinceAccepts = 0;
-					latestAccepting = state;
-				} else if (sinceAccepts >= 0) {
-					sinceAccepts++;
-				}
-
-				cursor++;
-				multiLength++;
-			}
-
-			if (sinceAccepts < 0) {
-
-				// No accepting state was found, part of a regular text token
-				// Add all the tokens we looked at to the text tokens array
-				for (var _i3 = cursor - multiLength; _i3 < cursor; _i3++) {
-					textTokens.push(tokens[_i3]);
-				}
-			} else {
-
-				// Accepting state!
-
-				// First close off the textTokens (if available)
-				if (textTokens.length > 0) {
-					multis.push(new TEXT(textTokens));
-					textTokens = [];
-				}
-
-				// Roll back to the latest accepting state
-				cursor -= sinceAccepts;
-				multiLength -= sinceAccepts;
-
-				// Create a new multitoken
-				var MULTI = latestAccepting.emit();
-				multis.push(new MULTI(tokens.slice(cursor - multiLength, cursor)));
-			}
-		}
-
-		// Finally close off the textTokens (if available)
-		if (textTokens.length > 0) {
-			multis.push(new TEXT(textTokens));
-		}
-
-		return multis;
-	};
 
-	var parser = Object.freeze({
-		State: State,
-		TOKENS: TOKENS$1,
-		run: run$1,
-		start: S_START$1
-	});
+	function init$1(_ref) {
+	  var groups = _ref.groups;
+	  // Types of characters the URL can definitely end in
+	  var qsAccepting = groups.domain.concat([AMPERSAND, ASTERISK, AT, BACKSLASH, BACKTICK, CARET, DOLLAR, EQUALS, HYPHEN, NUM, PERCENT, PIPE, PLUS, POUND, SLASH, SYM, TILDE, UNDERSCORE]); // Types of tokens that can follow a URL and be part of the query string
+	  // but cannot be the very last characters
+	  // Characters that cannot appear in the URL at all should be excluded
 
-	if (!Array.isArray) {
-		Array.isArray = function (arg) {
-			return Object.prototype.toString.call(arg) === '[object Array]';
-		};
-	}
+	  var qsNonAccepting = [APOSTROPHE, CLOSEANGLEBRACKET, CLOSEBRACE, CLOSEBRACKET, CLOSEPAREN, COLON, COMMA, DOT, EXCLAMATION, OPENANGLEBRACKET, OPENBRACE, OPENBRACKET, OPENPAREN, QUERY, QUOTE, SEMI]; // For addresses without the mailto prefix
+	  // Tokens allowed in the localpart of the email
+
+	  var localpartAccepting = [AMPERSAND, APOSTROPHE, ASTERISK, BACKSLASH, BACKTICK, CARET, CLOSEBRACE, DOLLAR, EQUALS, HYPHEN, NUM, OPENBRACE, PERCENT, PIPE, PLUS, POUND, QUERY, SLASH, SYM, TILDE, UNDERSCORE]; // The universal starting state.
+
+	  /**
+	   * @type State<Token>
+	   */
+
+	  var Start = makeState();
+	  var Localpart = tt(Start, TILDE); // Local part of the email address
+
+	  ta(Localpart, localpartAccepting, Localpart);
+	  ta(Localpart, groups.domain, Localpart);
+	  var Domain = makeState(),
+	      Scheme = makeState(),
+	      SlashScheme = makeState();
+	  ta(Start, groups.domain, Domain); // parsed string ends with a potential domain name (A)
+
+	  ta(Start, groups.scheme, Scheme); // e.g., 'mailto'
+
+	  ta(Start, groups.slashscheme, SlashScheme); // e.g., 'http'
+
+	  ta(Domain, localpartAccepting, Localpart);
+	  ta(Domain, groups.domain, Domain);
+	  var LocalpartAt = tt(Domain, AT); // Local part of the email address plus @
+
+	  tt(Localpart, AT, LocalpartAt); // close to an email address now
+
+	  var LocalpartDot = tt(Localpart, DOT); // Local part of the email address plus '.' (localpart cannot end in .)
+
+	  ta(LocalpartDot, localpartAccepting, Localpart);
+	  ta(LocalpartDot, groups.domain, Localpart);
+	  var EmailDomain = makeState();
+	  ta(LocalpartAt, groups.domain, EmailDomain); // parsed string starts with local email info + @ with a potential domain name
+
+	  ta(EmailDomain, groups.domain, EmailDomain);
+	  var EmailDomainDot = tt(EmailDomain, DOT); // domain followed by DOT
+
+	  ta(EmailDomainDot, groups.domain, EmailDomain);
+	  var Email$1 = makeState(Email); // Possible email address (could have more tlds)
+
+	  ta(EmailDomainDot, groups.tld, Email$1);
+	  ta(EmailDomainDot, groups.utld, Email$1);
+	  tt(LocalpartAt, LOCALHOST, Email$1); // Hyphen can jump back to a domain name
+
+	  var EmailDomainHyphen = tt(EmailDomain, HYPHEN); // parsed string starts with local email info + @ with a potential domain name
+
+	  ta(EmailDomainHyphen, groups.domain, EmailDomain);
+	  ta(Email$1, groups.domain, EmailDomain);
+	  tt(Email$1, DOT, EmailDomainDot);
+	  tt(Email$1, HYPHEN, EmailDomainHyphen); // Final possible email states
+
+	  var EmailColon = tt(Email$1, COLON); // URL followed by colon (potential port number here)
+
+	  /*const EmailColonPort = */
+
+	  ta(EmailColon, groups.numeric, Email); // URL followed by colon and port numner
+	  // Account for dots and hyphens. Hyphens are usually parts of domain names
+	  // (but not TLDs)
+
+	  var DomainHyphen = tt(Domain, HYPHEN); // domain followed by hyphen
+
+	  var DomainDot = tt(Domain, DOT); // domain followed by DOT
+
+	  ta(DomainHyphen, groups.domain, Domain);
+	  ta(DomainDot, localpartAccepting, Localpart);
+	  ta(DomainDot, groups.domain, Domain);
+	  var DomainDotTld = makeState(Url); // Simplest possible URL with no query string
+
+	  ta(DomainDot, groups.tld, DomainDotTld);
+	  ta(DomainDot, groups.utld, DomainDotTld);
+	  ta(DomainDotTld, groups.domain, Domain);
+	  ta(DomainDotTld, localpartAccepting, Localpart);
+	  tt(DomainDotTld, DOT, DomainDot);
+	  tt(DomainDotTld, HYPHEN, DomainHyphen);
+	  tt(DomainDotTld, AT, LocalpartAt);
+	  var DomainDotTldColon = tt(DomainDotTld, COLON); // URL followed by colon (potential port number here)
+
+	  var DomainDotTldColonPort = makeState(Url); // TLD followed by a port number
+
+	  ta(DomainDotTldColon, groups.numeric, DomainDotTldColonPort); // Long URL with optional port and maybe query string
+
+	  var Url$1 = makeState(Url); // URL with extra symbols at the end, followed by an opening bracket
+
+	  var UrlNonaccept = makeState(); // URL followed by some symbols (will not be part of the final URL)
+	  // Query strings
+
+	  ta(Url$1, qsAccepting, Url$1);
+	  ta(Url$1, qsNonAccepting, UrlNonaccept);
+	  ta(UrlNonaccept, qsAccepting, Url$1);
+	  ta(UrlNonaccept, qsNonAccepting, UrlNonaccept); // Become real URLs after `SLASH` or `COLON NUM SLASH`
+	  // Here works with or without scheme:// prefix
+
+	  tt(DomainDotTld, SLASH, Url$1);
+	  tt(DomainDotTldColonPort, SLASH, Url$1); // Note that domains that begin with schemes are treated slighly differently
 
+	  var UriPrefix = tt(Scheme, COLON); // e.g., 'mailto:' or 'http://'
+
+	  var SlashSchemeColon = tt(SlashScheme, COLON); // e.g., 'http:'
+
+	  var SlashSchemeColonSlash = tt(SlashSchemeColon, SLASH); // e.g., 'http:/'
+
+	  tt(SlashSchemeColonSlash, SLASH, UriPrefix); // Scheme states can transition to domain states
+
+	  ta(Scheme, groups.domain, Domain);
+	  tt(Scheme, DOT, DomainDot);
+	  tt(Scheme, HYPHEN, DomainHyphen);
+	  ta(SlashScheme, groups.domain, Domain);
+	  tt(SlashScheme, DOT, DomainDot);
+	  tt(SlashScheme, HYPHEN, DomainHyphen); // Force URL with scheme prefix followed by anything sane
+
+	  ta(UriPrefix, groups.domain, Url$1);
+	  tt(UriPrefix, SLASH, Url$1); // URL, followed by an opening bracket
+
+	  var UrlOpenbrace = tt(Url$1, OPENBRACE); // URL followed by {
+
+	  var UrlOpenbracket = tt(Url$1, OPENBRACKET); // URL followed by [
+
+	  var UrlOpenanglebracket = tt(Url$1, OPENANGLEBRACKET); // URL followed by <
+
+	  var UrlOpenparen = tt(Url$1, OPENPAREN); // URL followed by (
+
+	  tt(UrlNonaccept, OPENBRACE, UrlOpenbrace);
+	  tt(UrlNonaccept, OPENBRACKET, UrlOpenbracket);
+	  tt(UrlNonaccept, OPENANGLEBRACKET, UrlOpenanglebracket);
+	  tt(UrlNonaccept, OPENPAREN, UrlOpenparen); // Closing bracket component. This character WILL be included in the URL
+
+	  tt(UrlOpenbrace, CLOSEBRACE, Url$1);
+	  tt(UrlOpenbracket, CLOSEBRACKET, Url$1);
+	  tt(UrlOpenanglebracket, CLOSEANGLEBRACKET, Url$1);
+	  tt(UrlOpenparen, CLOSEPAREN, Url$1);
+	  tt(UrlOpenbrace, CLOSEBRACE, Url$1); // URL that beings with an opening bracket, followed by a symbols.
+	  // Note that the final state can still be `UrlOpenbrace` (if the URL only
+	  // has a single opening bracket for some reason).
+
+	  var UrlOpenbraceQ = makeState(Url); // URL followed by { and some symbols that the URL can end it
+
+	  var UrlOpenbracketQ = makeState(Url); // URL followed by [ and some symbols that the URL can end it
+
+	  var UrlOpenanglebracketQ = makeState(Url); // URL followed by < and some symbols that the URL can end it
+
+	  var UrlOpenparenQ = makeState(Url); // URL followed by ( and some symbols that the URL can end it
+
+	  ta(UrlOpenbrace, qsAccepting, UrlOpenbraceQ);
+	  ta(UrlOpenbracket, qsAccepting, UrlOpenbracketQ);
+	  ta(UrlOpenanglebracket, qsAccepting, UrlOpenanglebracketQ);
+	  ta(UrlOpenparen, qsAccepting, UrlOpenparenQ);
+	  var UrlOpenbraceSyms = makeState(); // UrlOpenbrace followed by some symbols it cannot end it
+
+	  var UrlOpenbracketSyms = makeState(); // UrlOpenbracketQ followed by some symbols it cannot end it
+
+	  var UrlOpenanglebracketSyms = makeState(); // UrlOpenanglebracketQ followed by some symbols it cannot end it
+
+	  var UrlOpenparenSyms = makeState(); // UrlOpenparenQ followed by some symbols it cannot end it
+
+	  ta(UrlOpenbrace, qsNonAccepting);
+	  ta(UrlOpenbracket, qsNonAccepting);
+	  ta(UrlOpenanglebracket, qsNonAccepting);
+	  ta(UrlOpenparen, qsNonAccepting); // URL that begins with an opening bracket, followed by some symbols
+
+	  ta(UrlOpenbraceQ, qsAccepting, UrlOpenbraceQ);
+	  ta(UrlOpenbracketQ, qsAccepting, UrlOpenbracketQ);
+	  ta(UrlOpenanglebracketQ, qsAccepting, UrlOpenanglebracketQ);
+	  ta(UrlOpenparenQ, qsAccepting, UrlOpenparenQ);
+	  ta(UrlOpenbraceQ, qsNonAccepting, UrlOpenbraceQ);
+	  ta(UrlOpenbracketQ, qsNonAccepting, UrlOpenbracketQ);
+	  ta(UrlOpenanglebracketQ, qsNonAccepting, UrlOpenanglebracketQ);
+	  ta(UrlOpenparenQ, qsNonAccepting, UrlOpenparenQ);
+	  ta(UrlOpenbraceSyms, qsAccepting, UrlOpenbraceSyms);
+	  ta(UrlOpenbracketSyms, qsAccepting, UrlOpenbracketQ);
+	  ta(UrlOpenanglebracketSyms, qsAccepting, UrlOpenanglebracketQ);
+	  ta(UrlOpenparenSyms, qsAccepting, UrlOpenparenQ);
+	  ta(UrlOpenbraceSyms, qsNonAccepting, UrlOpenbraceSyms);
+	  ta(UrlOpenbracketSyms, qsNonAccepting, UrlOpenbracketSyms);
+	  ta(UrlOpenanglebracketSyms, qsNonAccepting, UrlOpenanglebracketSyms);
+	  ta(UrlOpenparenSyms, qsNonAccepting, UrlOpenparenSyms); // Close brace/bracket to become regular URL
+
+	  tt(UrlOpenbracketQ, CLOSEBRACKET, Url$1);
+	  tt(UrlOpenanglebracketQ, CLOSEANGLEBRACKET, Url$1);
+	  tt(UrlOpenparenQ, CLOSEPAREN, Url$1);
+	  tt(UrlOpenbraceQ, CLOSEBRACE, Url$1);
+	  tt(UrlOpenbracketSyms, CLOSEBRACKET, Url$1);
+	  tt(UrlOpenanglebracketSyms, CLOSEANGLEBRACKET, Url$1);
+	  tt(UrlOpenparenSyms, CLOSEPAREN, Url$1);
+	  tt(UrlOpenbraceSyms, CLOSEPAREN, Url$1);
+	  tt(Start, LOCALHOST, DomainDotTld); // localhost is a valid URL state
+
+	  tt(Start, NL$1, Nl); // single new line
+
+	  return {
+	    start: Start,
+	    tokens: tk
+	  };
+	}
+	/**
+	 * Run the parser state machine on a list of scanned string-based tokens to
+	 * create a list of multi tokens, each of which represents a URL, email address,
+	 * plain text, etc.
+	 *
+	 * @param {State<MultiToken>} start parser start state
+	 * @param {string} input the original input used to generate the given tokens
+	 * @param {Token[]} tokens list of scanned tokens
+	 * @returns {MultiToken[]}
+	 */
+
+	function run(start, input, tokens) {
+	  var len = tokens.length;
+	  var cursor = 0;
+	  var multis = [];
+	  var textTokens = [];
+
+	  while (cursor < len) {
+	    var state = start;
+	    var secondState = null;
+	    var nextState = null;
+	    var multiLength = 0;
+	    var latestAccepting = null;
+	    var sinceAccepts = -1;
+
+	    while (cursor < len && !(secondState = state.go(tokens[cursor].t))) {
+	      // Starting tokens with nowhere to jump to.
+	      // Consider these to be just plain text
+	      textTokens.push(tokens[cursor++]);
+	    }
+
+	    while (cursor < len && (nextState = secondState || state.go(tokens[cursor].t))) {
+	      // Get the next state
+	      secondState = null;
+	      state = nextState; // Keep track of the latest accepting state
+
+	      if (state.accepts()) {
+	        sinceAccepts = 0;
+	        latestAccepting = state;
+	      } else if (sinceAccepts >= 0) {
+	        sinceAccepts++;
+	      }
+
+	      cursor++;
+	      multiLength++;
+	    }
+
+	    if (sinceAccepts < 0) {
+	      // No accepting state was found, part of a regular text token add
+	      // the first text token to the text tokens array and try again from
+	      // the next
+	      cursor -= multiLength;
+
+	      if (cursor < len) {
+	        textTokens.push(tokens[cursor]);
+	        cursor++;
+	      }
+	    } else {
+	      // Accepting state!
+	      // First close off the textTokens (if available)
+	      if (textTokens.length > 0) {
+	        multis.push(initMultiToken(Text, input, textTokens));
+	        textTokens = [];
+	      } // Roll back to the latest accepting state
+
+
+	      cursor -= sinceAccepts;
+	      multiLength -= sinceAccepts; // Create a new multitoken
+
+	      var Multi = latestAccepting.t;
+	      var subtokens = tokens.slice(cursor - multiLength, cursor);
+	      multis.push(initMultiToken(Multi, input, subtokens));
+	    }
+	  } // Finally close off the textTokens (if available)
+
+
+	  if (textTokens.length > 0) {
+	    multis.push(initMultiToken(Text, input, textTokens));
+	  }
+
+	  return multis;
+	}
 	/**
- 	Converts a string into tokens that represent linkable and non-linkable bits
- 	@method tokenize
- 	@param {String} str
- 	@return {Array} tokens
- */
-	var tokenize = function tokenize(str) {
-		return run$1(run(str));
+	 * Utility function for instantiating a new multitoken with all the relevant
+	 * fields during parsing.
+	 * @param {new (value: string, tokens: Token[]) => MultiToken} Multi class to instantiate
+	 * @param {string} input original input string
+	 * @param {Token[]} tokens consecutive tokens scanned from input string
+	 * @returns {MultiToken}
+	 */
+
+	function initMultiToken(Multi, input, tokens) {
+	  var startIdx = tokens[0].s;
+	  var endIdx = tokens[tokens.length - 1].e;
+	  var value = input.slice(startIdx, endIdx);
+	  return new Multi(value, tokens);
+	}
+
+	var warn = typeof console !== 'undefined' && console && console.warn || function () {};
+
+	var warnAdvice = 'To avoid this warning, please register all custom schemes before invoking linkify the first time.'; // Side-effect initialization state
+
+	var INIT = {
+	  scanner: null,
+	  parser: null,
+	  tokenQueue: [],
+	  pluginQueue: [],
+	  customSchemes: [],
+	  initialized: false
 	};
+	/**
+	 * @typedef {{
+	 * 	start: State<string>,
+	 * 	tokens: { groups: Collections<string> } & typeof tk
+	 * }} ScannerInit
+	 */
 
 	/**
- 	Returns a list of linkable items in the given string.
- */
-	var find = function find(str) {
-		var type = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
-
-		var tokens = tokenize(str);
-		var filtered = [];
-
-		for (var i = 0; i < tokens.length; i++) {
-			var token = tokens[i];
-			if (token.isLink && (!type || token.type === type)) {
-				filtered.push(token.toObject());
-			}
-		}
-
-		return filtered;
-	};
+	 * @typedef {{
+	 * 	start: State<MultiToken>,
+	 * 	tokens: typeof multi
+	 * }} ParserInit
+	 */
 
 	/**
- 	Is the given string valid linkable text of some sort
- 	Note that this does not trim the text for you.
+	 * @typedef {(arg: { scanner: ScannerInit }) => void} TokenPlugin
+	 */
 
- 	Optionally pass in a second `type` param, which is the type of link to test
- 	for.
+	/**
+	 * @typedef {(arg: { scanner: ScannerInit, parser: ParserInit }) => void} Plugin
+	 */
 
- 	For example,
+	/**
+	 * De-register all plugins and reset the internal state-machine. Used for
+	 * testing; not required in practice.
+	 * @private
+	 */
+
+	function reset() {
+	  State.groups = {};
+	  INIT.scanner = null;
+	  INIT.parser = null;
+	  INIT.tokenQueue = [];
+	  INIT.pluginQueue = [];
+	  INIT.customSchemes = [];
+	  INIT.initialized = false;
+	}
+	/**
+	 * Register a token plugin to allow the scanner to recognize additional token
+	 * types before the parser state machine is constructed from the results.
+	 * @param {string} name of plugin to register
+	 * @param {TokenPlugin} plugin function that accepts the scanner state machine
+	 * and available scanner tokens and collections and extends the state machine to
+	 * recognize additional tokens or groups.
+	 */
+
+	function registerTokenPlugin(name, plugin) {
+	  if (typeof plugin !== 'function') {
+	    throw new Error("linkifyjs: Invalid token plugin " + plugin + " (expects function)");
+	  }
+
+	  for (var i = 0; i < INIT.tokenQueue.length; i++) {
+	    if (name === INIT.tokenQueue[i][0]) {
+	      warn("linkifyjs: token plugin \"" + name + "\" already registered - will be overwritten");
+	      INIT.tokenQueue[i] = [name, plugin];
+	      return;
+	    }
+	  }
+
+	  INIT.tokenQueue.push([name, plugin]);
+
+	  if (INIT.initialized) {
+	    warn("linkifyjs: already initialized - will not register token plugin \"" + name + "\" until you manually call linkify.init(). " + warnAdvice);
+	  }
+	}
+	/**
+	 * Register a linkify plugin
+	 * @param {string} name of plugin to register
+	 * @param {Plugin} plugin function that accepts the parser state machine and
+	 * extends the parser to recognize additional link types
+	 */
+
+	function registerPlugin(name, plugin) {
+	  if (typeof plugin !== 'function') {
+	    throw new Error("linkifyjs: Invalid plugin " + plugin + " (expects function)");
+	  }
+
+	  for (var i = 0; i < INIT.pluginQueue.length; i++) {
+	    if (name === INIT.pluginQueue[i][0]) {
+	      warn("linkifyjs: plugin \"" + name + "\" already registered - will be overwritten");
+	      INIT.pluginQueue[i] = [name, plugin];
+	      return;
+	    }
+	  }
+
+	  INIT.pluginQueue.push([name, plugin]);
+
+	  if (INIT.initialized) {
+	    warn("linkifyjs: already initialized - will not register plugin \"" + name + "\" until you manually call linkify.init(). " + warnAdvice);
+	  }
+	}
+	/**
+	 * Detect URLs with the following additional protocol. Anything with format
+	 * "protocol://..." will be considered a link. If `optionalSlashSlash` is set to
+	 * `true`, anything with format "protocol:..." will be considered a link.
+	 * @param {string} protocol
+	 * @param {boolean} [optionalSlashSlash]
+	 */
+
+	function registerCustomProtocol(scheme, optionalSlashSlash) {
+	  if (optionalSlashSlash === void 0) {
+	    optionalSlashSlash = false;
+	  }
+
+	  if (INIT.initialized) {
+	    warn("linkifyjs: already initialized - will not register custom scheme \"" + scheme + "\" until you manually call linkify.init(). " + warnAdvice);
+	  }
+
+	  if (!/^[0-9a-z]+(-[0-9a-z]+)*$/.test(scheme)) {
+	    throw new Error('linkifyjs: incorrect scheme format.\n 1. Must only contain digits, lowercase ASCII letters or "-"\n 2. Cannot start or end with "-"\n 3. "-" cannot repeat');
+	  }
+
+	  INIT.customSchemes.push([scheme, optionalSlashSlash]);
+	}
+	/**
+	 * Initialize the linkify state machine. Called automatically the first time
+	 * linkify is called on a string, but may be called manually as well.
+	 */
 
- 		test(str, 'email');
+	function init() {
+	  // Initialize scanner state machine and plugins
+	  INIT.scanner = init$2(INIT.customSchemes);
 
- 	Will return `true` if str is a valid email.
- */
-	var test = function test(str) {
-		var type = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
+	  for (var i = 0; i < INIT.tokenQueue.length; i++) {
+	    INIT.tokenQueue[i][1]({
+	      scanner: INIT.scanner
+	    });
+	  } // Initialize parser state machine and plugins
 
-		var tokens = tokenize(str);
-		return tokens.length === 1 && tokens[0].isLink && (!type || tokens[0].type === type);
-	};
 
+	  INIT.parser = init$1(INIT.scanner.tokens);
+
+	  for (var _i = 0; _i < INIT.pluginQueue.length; _i++) {
+	    INIT.pluginQueue[_i][1]({
+	      scanner: INIT.scanner,
+	      parser: INIT.parser
+	    });
+	  }
+
+	  INIT.initialized = true;
+	}
+	/**
+	 * Parse a string into tokens that represent linkable and non-linkable sub-components
+	 * @param {string} str
+	 * @return {MultiToken[]} tokens
+	 */
+
+	function tokenize(str) {
+	  if (!INIT.initialized) {
+	    init();
+	  }
+
+	  return run(INIT.parser.start, str, run$1(INIT.scanner.start, str));
+	}
+	/**
+	 * Find a list of linkable items in the given string.
+	 * @param {string} str string to find links in
+	 * @param {string | Opts} [type] either formatting options or specific type of
+	 * links to find, e.g., 'url' or 'email'
+	 * @param {Opts} [opts] formatting options for final output. Cannot be specified
+	 * if opts already provided in `type` argument
+	*/
+
+	function find(str, type, opts) {
+	  if (type === void 0) {
+	    type = null;
+	  }
+
+	  if (opts === void 0) {
+	    opts = null;
+	  }
+
+	  if (type && typeof type === 'object') {
+	    if (opts) {
+	      throw Error("linkifyjs: Invalid link type " + type + "; must be a string");
+	    }
+
+	    opts = type;
+	    type = null;
+	  }
+
+	  var options = new Options(opts);
+	  var tokens = tokenize(str);
+	  var filtered = [];
+
+	  for (var i = 0; i < tokens.length; i++) {
+	    var token = tokens[i];
+
+	    if (token.isLink && (!type || token.t === type)) {
+	      filtered.push(token.toFormattedObject(options));
+	    }
+	  }
+
+	  return filtered;
+	}
+	/**
+	 * Is the given string valid linkable text of some sort. Note that this does not
+	 * trim the text for you.
+	 *
+	 * Optionally pass in a second `type` param, which is the type of link to test
+	 * for.
+	 *
+	 * For example,
+	 *
+	 *     linkify.test(str, 'email');
+	 *
+	 * Returns `true` if str is a valid email.
+	 * @param {string} str string to test for links
+	 * @param {string} [type] optional specific link type to look for
+	 * @returns boolean true/false
+	 */
+
+	function test(str, type) {
+	  if (type === void 0) {
+	    type = null;
+	  }
+
+	  var tokens = tokenize(str);
+	  return tokens.length === 1 && tokens[0].isLink && (!type || tokens[0].t === type);
+	}
+
+	exports.MultiToken = MultiToken;
+	exports.Options = Options;
+	exports.State = State;
+	exports.createTokenClass = createTokenClass;
 	exports.find = find;
-	exports.inherits = inherits;
+	exports.init = init;
+	exports.multi = multi;
 	exports.options = options;
-	exports.parser = parser;
-	exports.scanner = scanner;
+	exports.regexp = regexp;
+	exports.registerCustomProtocol = registerCustomProtocol;
+	exports.registerPlugin = registerPlugin;
+	exports.registerTokenPlugin = registerTokenPlugin;
+	exports.reset = reset;
+	exports.stringToArray = stringToArray;
 	exports.test = test;
 	exports.tokenize = tokenize;
-})(window.linkify = window.linkify || {});
-})();
+
+	Object.defineProperty(exports, '__esModule', { value: true });
+
+	return exports;
+
+})({});
\ No newline at end of file