diff --git a/dist/Autolinker.js b/dist/Autolinker.js index fac02d4..a043d9b 100644 --- a/dist/Autolinker.js +++ b/dist/Autolinker.js @@ -1,6 +1,6 @@ /*! * Autolinker.js - * v4.0.1 + * v4.0.2 * * Copyright(c) 2024 Gregory Jacobs * MIT License @@ -15,7 +15,7 @@ // Important: this file is generated from the 'build' script and should not be // edited directly - var version = '4.0.1'; + var version = '4.0.2'; /** * Simpler helper method to check for undefined simply for the benefit of diff --git a/dist/Autolinker.js.map b/dist/Autolinker.js.map index 85ca181..974f346 100644 --- a/dist/Autolinker.js.map +++ b/dist/Autolinker.js.map @@ -1 +1 @@ -{"version":3,"file":"autolinker.js","sources":["../src/version.ts","../src/utils.ts","../src/regex-lib.ts","../src/html-tag.ts","../src/truncate/truncate-smart.ts","../src/truncate/truncate-middle.ts","../src/truncate/truncate-end.ts","../src/anchor-tag-builder.ts","../node_modules/tslib/tslib.es6.js","../src/match/abstract-match.ts","../src/parser/tld-regex.ts","../src/parser/uri-utils.ts","../src/match/url-match.ts","../src/parser/email-utils.ts","../src/match/email-match.ts","../src/parser/hashtag-utils.ts","../src/match/hashtag-match.ts","../src/parser/mention-utils.ts","../src/match/mention-match.ts","../src/parser/phone-number-utils.ts","../src/match/phone-match.ts","../src/parser/parse-matches.ts","../src/htmlParser/parse-html.ts","../src/autolinker.ts"],"sourcesContent":["// Important: this file is generated from the 'build' script and should not be\n// edited directly\nexport const version = '4.0.1';\n","/**\n * Simpler helper method to check for undefined simply for the benefit of\n * gaining better compression when minified by not needing to have multiple\n * comparisons to the `undefined` keyword in the codebase.\n */\nexport function isUndefined(value: any): value is undefined {\n return value === undefined;\n}\n\n/**\n * Simpler helper method to check for a boolean type simply for the benefit of\n * gaining better compression when minified by not needing to have multiple\n * `typeof` comparisons in the codebase.\n */\nexport function isBoolean(value: any): value is boolean {\n return typeof value === 'boolean';\n}\n\n/**\n * Assigns (shallow copies) the properties of `src` onto `dest`, if the\n * corresponding property on `dest` === `undefined`.\n *\n * @param {Object} dest The destination object.\n * @param {Object} src The source object.\n * @return {Object} The destination object (`dest`)\n */\nexport function defaults(dest: any, src: any) {\n for (let prop in src) {\n if (src.hasOwnProperty(prop) && isUndefined(dest[prop])) {\n dest[prop] = src[prop];\n }\n }\n\n return dest;\n}\n\n/**\n * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the\n * end of the string (by default, two periods: '..'). If the `str` length does not exceed\n * `len`, the string will be returned unchanged.\n *\n * @param {String} str The string to truncate and add an ellipsis to.\n * @param {Number} truncateLen The length to truncate the string at.\n * @param {String} [ellipsisChars=...] The ellipsis character(s) to add to the end of `str`\n * when truncated. Defaults to '...'\n */\nexport function ellipsis(str: string, truncateLen: number, ellipsisChars?: string) {\n let ellipsisLength: number;\n\n if (str.length > truncateLen) {\n if (ellipsisChars == null) {\n ellipsisChars = '…';\n ellipsisLength = 3;\n } else {\n ellipsisLength = ellipsisChars.length;\n }\n\n str = str.substring(0, truncateLen - ellipsisLength) + ellipsisChars;\n }\n return str;\n}\n\n/**\n * Removes array elements by value. Mutates the input array.\n *\n * Using this instead of the ES5 Array.prototype.filter() function to prevent\n * creating many new arrays in memory for removing an element.\n *\n * @param arr The array to remove elements from. This array is mutated.\n * @param fn The element to remove.\n */\nexport function remove(arr: T[], item: T) {\n for (let i = arr.length - 1; i >= 0; i--) {\n if (arr[i] === item) {\n arr.splice(i, 1);\n }\n }\n}\n\n/**\n * Removes array elements based on a filtering function. Mutates the input\n * array.\n *\n * Using this instead of the ES5 Array.prototype.filter() function to prevent\n * creating many new arrays in memory for filtering.\n *\n * @param arr The array to remove elements from. This array is mutated.\n * @param fn The predicate function which should return `true` to remove an\n * element.\n */\nexport function removeWithPredicate(arr: T[], fn: (item: T) => boolean) {\n for (let i = arr.length - 1; i >= 0; i--) {\n if (fn(arr[i]) === true) {\n arr.splice(i, 1);\n }\n }\n}\n\n/**\n * Function that should never be called but is used to check that every\n * enum value is handled using TypeScript's 'never' type.\n */\nexport function assertNever(theValue: never) {\n throw new Error(`Unhandled case for value: '${theValue}'`);\n}\n","/*\n * This file builds and stores a library of the common regular expressions used\n * by the Autolinker utility.\n *\n * Other regular expressions may exist ad-hoc, but these are generally the\n * regular expressions that are shared between source files.\n */\n\n/**\n * Regular expression to match upper and lowercase ASCII letters\n */\nexport const letterRe = /[A-Za-z]/;\n\n/**\n * Regular expression to match ASCII digits\n */\nexport const digitRe = /[\\d]/;\n\n/**\n * Regular expression to match everything *except* ASCII digits\n */\nexport const nonDigitRe = /[\\D]/;\n\n/**\n * Regular expression to match whitespace\n */\nexport const whitespaceRe = /\\s/;\n\n/**\n * Regular expression to match quote characters\n */\nexport const quoteRe = /['\"]/;\n\n/**\n * Regular expression to match the range of ASCII control characters (0-31), and\n * the backspace char (127)\n */\nexport const controlCharsRe = /[\\x00-\\x1F\\x7F]/;\n\n/**\n * The string form of a regular expression that would match all of the\n * alphabetic (\"letter\") chars in the unicode character set when placed in a\n * RegExp character class (`[]`). This includes all international alphabetic\n * characters.\n *\n * These would be the characters matched by unicode regex engines `\\p{L}`\n * escape (\"all letters\").\n *\n * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)\n * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Letter'\n * regex's bmp\n *\n * VERY IMPORTANT: This set of characters is defined inside of a Regular\n * Expression literal rather than a string literal to prevent UglifyJS from\n * compressing the unicode escape sequences into their actual unicode\n * characters. If Uglify compresses these into the unicode characters\n * themselves, this results in the error \"Range out of order in character\n * class\" when these characters are used inside of a Regular Expression\n * character class (`[]`). See usages of this const. Alternatively, we can set\n * the UglifyJS option `ascii_only` to true for the build, but that doesn't\n * help others who are pulling in Autolinker into their own build and running\n * UglifyJS themselves.\n */\n// prettier-ignore\nexport const alphaCharsStr = /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\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\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\\u08A0-\\u08B4\\u08B6-\\u08BD\\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\\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\\u0C60\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\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\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\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-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\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-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C88\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\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-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\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-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FD5\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7AE\\uA7B0-\\uA7B7\\uA7F7-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD\\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-\\uAB65\\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/\n .source; // see note in above variable description\n\n/**\n * The string form of a regular expression that would match all emoji characters\n * Based on the emoji regex defined in this article: https://thekevinscott.com/emojis-in-javascript/\n */\nexport const emojiStr =\n /\\u2700-\\u27bf\\udde6-\\uddff\\ud800-\\udbff\\udc00-\\udfff\\ufe0e\\ufe0f\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0\\ud83c\\udffb-\\udfff\\u200d\\u3299\\u3297\\u303d\\u3030\\u24c2\\ud83c\\udd70-\\udd71\\udd7e-\\udd7f\\udd8e\\udd91-\\udd9a\\udde6-\\uddff\\ude01-\\ude02\\ude1a\\ude2f\\ude32-\\ude3a\\ude50-\\ude51\\u203c\\u2049\\u25aa-\\u25ab\\u25b6\\u25c0\\u25fb-\\u25fe\\u00a9\\u00ae\\u2122\\u2139\\udc04\\u2600-\\u26FF\\u2b05\\u2b06\\u2b07\\u2b1b\\u2b1c\\u2b50\\u2b55\\u231a\\u231b\\u2328\\u23cf\\u23e9-\\u23f3\\u23f8-\\u23fa\\udccf\\u2935\\u2934\\u2190-\\u21ff/\n .source;\n// ^ high surrogate\n// ^ low surrogate\n\n/**\n * The string form of a regular expression that would match all of the\n * combining mark characters in the unicode character set when placed in a\n * RegExp character class (`[]`).\n *\n * These would be the characters matched by unicode regex engines `\\p{M}`\n * escape (\"all marks\").\n *\n * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)\n * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Mark'\n * regex's bmp\n *\n * VERY IMPORTANT: This set of characters is defined inside of a Regular\n * Expression literal rather than a string literal to prevent UglifyJS from\n * compressing the unicode escape sequences into their actual unicode\n * characters. If Uglify compresses these into the unicode characters\n * themselves, this results in the error \"Range out of order in character\n * class\" when these characters are used inside of a Regular Expression\n * character class (`[]`). See usages of this const. Alternatively, we can set\n * the UglifyJS option `ascii_only` to true for the build, but that doesn't\n * help others who are pulling in Autolinker into their own build and running\n * UglifyJS themselves.\n */\n// prettier-ignore\nexport const marksStr = /\\u0300-\\u036F\\u0483-\\u0489\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065F\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0859-\\u085B\\u08D4-\\u08E1\\u08E3-\\u0903\\u093A-\\u093C\\u093E-\\u094F\\u0951-\\u0957\\u0962\\u0963\\u0981-\\u0983\\u09BC\\u09BE-\\u09C4\\u09C7\\u09C8\\u09CB-\\u09CD\\u09D7\\u09E2\\u09E3\\u0A01-\\u0A03\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81-\\u0A83\\u0ABC\\u0ABE-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0AE2\\u0AE3\\u0B01-\\u0B03\\u0B3C\\u0B3E-\\u0B44\\u0B47\\u0B48\\u0B4B-\\u0B4D\\u0B56\\u0B57\\u0B62\\u0B63\\u0B82\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD7\\u0C00-\\u0C03\\u0C3E-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0C81-\\u0C83\\u0CBC\\u0CBE-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6\\u0CE2\\u0CE3\\u0D01-\\u0D03\\u0D3E-\\u0D44\\u0D46-\\u0D48\\u0D4A-\\u0D4D\\u0D57\\u0D62\\u0D63\\u0D82\\u0D83\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F3E\\u0F3F\\u0F71-\\u0F84\\u0F86\\u0F87\\u0F8D-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102B-\\u103E\\u1056-\\u1059\\u105E-\\u1060\\u1062-\\u1064\\u1067-\\u106D\\u1071-\\u1074\\u1082-\\u108D\\u108F\\u109A-\\u109D\\u135D-\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B4-\\u17D3\\u17DD\\u180B-\\u180D\\u1885\\u1886\\u18A9\\u1920-\\u192B\\u1930-\\u193B\\u1A17-\\u1A1B\\u1A55-\\u1A5E\\u1A60-\\u1A7C\\u1A7F\\u1AB0-\\u1ABE\\u1B00-\\u1B04\\u1B34-\\u1B44\\u1B6B-\\u1B73\\u1B80-\\u1B82\\u1BA1-\\u1BAD\\u1BE6-\\u1BF3\\u1C24-\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE8\\u1CED\\u1CF2-\\u1CF4\\u1CF8\\u1CF9\\u1DC0-\\u1DF5\\u1DFB-\\u1DFF\\u20D0-\\u20F0\\u2CEF-\\u2CF1\\u2D7F\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F-\\uA672\\uA674-\\uA67D\\uA69E\\uA69F\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA823-\\uA827\\uA880\\uA881\\uA8B4-\\uA8C5\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA953\\uA980-\\uA983\\uA9B3-\\uA9C0\\uA9E5\\uAA29-\\uAA36\\uAA43\\uAA4C\\uAA4D\\uAA7B-\\uAA7D\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uAAEB-\\uAAEF\\uAAF5\\uAAF6\\uABE3-\\uABEA\\uABEC\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE2F/\n .source; // see note in above variable description\n\n/**\n * The string form of a regular expression that would match all of the\n * alphabetic (\"letter\") chars, emoji, and combining marks in the unicode character set\n * when placed in a RegExp character class (`[]`). This includes all\n * international alphabetic characters.\n *\n * These would be the characters matched by unicode regex engines `\\p{L}\\p{M}`\n * escapes and emoji characters.\n */\nexport const alphaCharsAndMarksStr = alphaCharsStr + emojiStr + marksStr;\n\n/**\n * The string form of a regular expression that would match all of the\n * decimal number chars in the unicode character set when placed in a RegExp\n * character class (`[]`).\n *\n * These would be the characters matched by unicode regex engines `\\p{Nd}`\n * escape (\"all decimal numbers\")\n *\n * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)\n * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Decimal_Number'\n * regex's bmp\n *\n * VERY IMPORTANT: This set of characters is defined inside of a Regular\n * Expression literal rather than a string literal to prevent UglifyJS from\n * compressing the unicode escape sequences into their actual unicode\n * characters. If Uglify compresses these into the unicode characters\n * themselves, this results in the error \"Range out of order in character\n * class\" when these characters are used inside of a Regular Expression\n * character class (`[]`). See usages of this const. Alternatively, we can set\n * the UglifyJS option `ascii_only` to true for the build, but that doesn't\n * help others who are pulling in Autolinker into their own build and running\n * UglifyJS themselves.\n */\n// prettier-ignore\nexport const decimalNumbersStr = /0-9\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0BE6-\\u0BEF\\u0C66-\\u0C6F\\u0CE6-\\u0CEF\\u0D66-\\u0D6F\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F29\\u1040-\\u1049\\u1090-\\u1099\\u17E0-\\u17E9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19D9\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\uA620-\\uA629\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19/\n .source; // see note in above variable description\n\n/**\n * The regular expression that will match all of the letters and decimal number\n * chars in the unicode character set when placed in a RegExp character class\n * (`[]`).\n *\n * These would be the characters matched by unicode regex engines\n * `[\\p{L}\\p{Nd}]` escape (\"all letters and decimal numbers\")\n */\nexport const alphaNumericCharsRe = new RegExp(`[${alphaCharsStr + decimalNumbersStr}]`);\n\n/**\n * The string form of a regular expression that would match all of the\n * letters, combining marks, and decimal number chars in the unicode character\n * set when placed in a RegExp character class (`[]`).\n *\n * These would be the characters matched by unicode regex engines\n * `[\\p{L}\\p{M}\\p{Nd}]` escape (\"all letters, combining marks, and decimal\n * numbers\")\n */\nexport const alphaNumericAndMarksCharsStr = alphaCharsAndMarksStr + decimalNumbersStr;\n\n/**\n * The regular expression that will match a single letter of the\n * {@link #alphaNumericAndMarksCharsStr}.\n */\nexport const alphaNumericAndMarksRe = new RegExp(`[${alphaNumericAndMarksCharsStr}]`);\n","import { whitespaceRe } from './regex-lib';\n\n/**\n * @class Autolinker.HtmlTag\n * @extends Object\n *\n * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically.\n *\n * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use\n * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}.\n *\n * ## Examples\n *\n * Example instantiation:\n *\n * var tag = new Autolinker.HtmlTag( {\n * tagName : 'a',\n * attrs : { 'href': 'http://google.com', 'class': 'external-link' },\n * innerHtml : 'Google'\n * } );\n *\n * tag.toAnchorString(); // Google\n *\n * // Individual accessor methods\n * tag.getTagName(); // 'a'\n * tag.getAttr( 'href' ); // 'http://google.com'\n * tag.hasClass( 'external-link' ); // true\n *\n *\n * Using mutator methods (which may be used in combination with instantiation config properties):\n *\n * var tag = new Autolinker.HtmlTag();\n * tag.setTagName( 'a' );\n * tag.setAttr( 'href', 'http://google.com' );\n * tag.addClass( 'external-link' );\n * tag.setInnerHtml( 'Google' );\n *\n * tag.getTagName(); // 'a'\n * tag.getAttr( 'href' ); // 'http://google.com'\n * tag.hasClass( 'external-link' ); // true\n *\n * tag.toAnchorString(); // Google\n *\n *\n * ## Example use within a {@link Autolinker#replaceFn replaceFn}\n *\n * var html = Autolinker.link( \"Test google.com\", {\n * replaceFn : function( match ) {\n * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text\n * tag.setAttr( 'rel', 'nofollow' );\n *\n * return tag;\n * }\n * } );\n *\n * // generated html:\n * // Test google.com\n *\n *\n * ## Example use with a new tag for the replacement\n *\n * var html = Autolinker.link( \"Test google.com\", {\n * replaceFn : function( match ) {\n * var tag = new Autolinker.HtmlTag( {\n * tagName : 'button',\n * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() },\n * innerHtml : 'Load URL: ' + match.getAnchorText()\n * } );\n *\n * return tag;\n * }\n * } );\n *\n * // generated html:\n * // Test \n */\nexport class HtmlTag {\n /**\n * @cfg {String} tagName\n *\n * The tag name. Ex: 'a', 'button', etc.\n *\n * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString}\n * is executed.\n */\n private tagName: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Object.} attrs\n *\n * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the\n * values are the attribute values.\n */\n private attrs: { [key: string]: string } = {}; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {String} innerHTML\n *\n * The inner HTML for the tag.\n */\n private innerHTML: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @method constructor\n * @param {Object} [cfg] The configuration properties for this class, in an Object (map)\n */\n constructor(cfg: HtmlTagCfg = {}) {\n this.tagName = cfg.tagName || '';\n this.attrs = cfg.attrs || {};\n this.innerHTML = cfg.innerHtml || cfg.innerHTML || ''; // accept either the camelCased form or the fully capitalized acronym as in the DOM\n }\n\n /**\n * Sets the tag name that will be used to generate the tag with.\n *\n * @param {String} tagName\n * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n */\n setTagName(tagName: string): this {\n this.tagName = tagName;\n return this;\n }\n\n /**\n * Retrieves the tag name.\n *\n * @return {String}\n */\n getTagName(): string {\n return this.tagName || '';\n }\n\n /**\n * Sets an attribute on the HtmlTag.\n *\n * @param {String} attrName The attribute name to set.\n * @param {String} attrValue The attribute value to set.\n * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n */\n setAttr(attrName: string, attrValue: string): this {\n let tagAttrs = this.getAttrs();\n tagAttrs[attrName] = attrValue;\n\n return this;\n }\n\n /**\n * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`.\n *\n * @param {String} attrName The attribute name to retrieve.\n * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.\n */\n getAttr(attrName: string): string {\n return this.getAttrs()[attrName];\n }\n\n /**\n * Sets one or more attributes on the HtmlTag.\n *\n * @param {Object.} attrs A key/value Object (map) of the attributes to set.\n * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n */\n setAttrs(attrs: { [attr: string]: string }): this {\n Object.assign(this.getAttrs(), attrs);\n\n return this;\n }\n\n /**\n * Retrieves the attributes Object (map) for the HtmlTag.\n *\n * @return {Object.} A key/value object of the attributes for the HtmlTag.\n */\n getAttrs(): { [key: string]: string } {\n return this.attrs || (this.attrs = {});\n }\n\n /**\n * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag.\n *\n * @param {String} cssClass One or more space-separated CSS classes to set (overwrite).\n * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n */\n setClass(cssClass: string): this {\n return this.setAttr('class', cssClass);\n }\n\n /**\n * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes.\n *\n * @param {String} cssClass One or more space-separated CSS classes to add.\n * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n */\n addClass(cssClass: string): this {\n let classAttr = this.getClass(),\n classes = !classAttr ? [] : classAttr.split(whitespaceRe),\n newClasses = cssClass.split(whitespaceRe),\n newClass: string | undefined;\n\n while ((newClass = newClasses.shift())) {\n if (classes.indexOf(newClass) === -1) {\n classes.push(newClass);\n }\n }\n\n this.getAttrs()['class'] = classes.join(' ');\n return this;\n }\n\n /**\n * Convenience method to remove one or more CSS classes from the HtmlTag.\n *\n * @param {String} cssClass One or more space-separated CSS classes to remove.\n * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n */\n removeClass(cssClass: string): this {\n let classAttr = this.getClass(),\n classes = !classAttr ? [] : classAttr.split(whitespaceRe),\n removeClasses = cssClass.split(whitespaceRe),\n removeClass: string | undefined;\n\n while (classes.length && (removeClass = removeClasses.shift())) {\n let idx = classes.indexOf(removeClass);\n if (idx !== -1) {\n classes.splice(idx, 1);\n }\n }\n\n this.getAttrs()['class'] = classes.join(' ');\n return this;\n }\n\n /**\n * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when\n * there are multiple.\n *\n * @return {String}\n */\n getClass(): string {\n return this.getAttrs()['class'] || '';\n }\n\n /**\n * Convenience method to check if the tag has a CSS class or not.\n *\n * @param {String} cssClass The CSS class to check for.\n * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise.\n */\n hasClass(cssClass: string): boolean {\n return (' ' + this.getClass() + ' ').indexOf(' ' + cssClass + ' ') !== -1;\n }\n\n /**\n * Sets the inner HTML for the tag.\n *\n * @param {String} html The inner HTML to set.\n * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n */\n setInnerHTML(html: string): this {\n this.innerHTML = html;\n\n return this;\n }\n\n /**\n * Backwards compatibility method name.\n *\n * @param {String} html The inner HTML to set.\n * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.\n */\n setInnerHtml(html: string): this {\n return this.setInnerHTML(html);\n }\n\n /**\n * Retrieves the inner HTML for the tag.\n *\n * @return {String}\n */\n getInnerHTML(): string {\n return this.innerHTML || '';\n }\n\n /**\n * Backward compatibility method name.\n *\n * @return {String}\n */\n getInnerHtml(): string {\n return this.getInnerHTML();\n }\n\n /**\n * Generates the HTML string for the tag.\n *\n * @return {String}\n */\n toAnchorString(): string {\n let tagName = this.getTagName(),\n attrsStr = this.buildAttrsStr();\n\n attrsStr = attrsStr ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes\n\n return ['<', tagName, attrsStr, '>', this.getInnerHtml(), ''].join('');\n }\n\n /**\n * Support method for {@link #toAnchorString}, returns the string space-separated key=\"value\" pairs, used to populate\n * the stringified HtmlTag.\n *\n * @protected\n * @return {String} Example return: `attr1=\"value1\" attr2=\"value2\"`\n */\n protected buildAttrsStr(): string {\n if (!this.attrs) return ''; // no `attrs` Object (map) has been set, return empty string\n\n let attrs = this.getAttrs(),\n attrsArr: string[] = [];\n\n for (let prop in attrs) {\n if (attrs.hasOwnProperty(prop)) {\n attrsArr.push(prop + '=\"' + attrs[prop] + '\"');\n }\n }\n return attrsArr.join(' ');\n }\n}\n\nexport interface HtmlTagCfg {\n tagName?: string;\n attrs?: { [key: string]: string };\n innerHtml?: string;\n innerHTML?: string;\n}\n","/**\n * Date: 2015-10-05\n * Author: Kasper Søfren (https://github.com/kafoso)\n *\n * A truncation feature, where the ellipsis will be placed at a section within\n * the URL making it still somewhat human readable.\n *\n * @param {String} url\t\t\t\t\t\t A URL.\n * @param {Number} truncateLen\t\t The maximum length of the truncated output URL string.\n * @param {String} ellipsisChars\t The characters to place within the url, e.g. \"...\".\n * @return {String} The truncated URL.\n */\nexport function truncateSmart(url: string, truncateLen: number, ellipsisChars?: string) {\n let ellipsisLengthBeforeParsing: number;\n let ellipsisLength: number;\n\n if (ellipsisChars == null) {\n ellipsisChars = '…';\n ellipsisLength = 3;\n ellipsisLengthBeforeParsing = 8;\n } else {\n ellipsisLength = ellipsisChars.length;\n ellipsisLengthBeforeParsing = ellipsisChars.length;\n }\n\n let parse_url = function (url: string) {\n // Functionality inspired by PHP function of same name\n let urlObj: UrlObject = {};\n let urlSub = url;\n let match = urlSub.match(/^([a-z]+):\\/\\//i);\n if (match) {\n urlObj.scheme = match[1];\n urlSub = urlSub.substr(match[0].length);\n }\n match = urlSub.match(/^(.*?)(?=(\\?|#|\\/|$))/i);\n if (match) {\n urlObj.host = match[1];\n urlSub = urlSub.substr(match[0].length);\n }\n match = urlSub.match(/^\\/(.*?)(?=(\\?|#|$))/i);\n if (match) {\n urlObj.path = match[1];\n urlSub = urlSub.substr(match[0].length);\n }\n match = urlSub.match(/^\\?(.*?)(?=(#|$))/i);\n if (match) {\n urlObj.query = match[1];\n urlSub = urlSub.substr(match[0].length);\n }\n match = urlSub.match(/^#(.*?)$/i);\n if (match) {\n urlObj.fragment = match[1];\n //urlSub = urlSub.substr(match[0].length); -- not used. Uncomment if adding another block.\n }\n return urlObj;\n };\n\n let buildUrl = function (urlObj: UrlObject) {\n let url = '';\n if (urlObj.scheme && urlObj.host) {\n url += urlObj.scheme + '://';\n }\n if (urlObj.host) {\n url += urlObj.host;\n }\n if (urlObj.path) {\n url += '/' + urlObj.path;\n }\n if (urlObj.query) {\n url += '?' + urlObj.query;\n }\n if (urlObj.fragment) {\n url += '#' + urlObj.fragment;\n }\n return url;\n };\n\n let buildSegment = function (segment: string, remainingAvailableLength: number) {\n let remainingAvailableLengthHalf = remainingAvailableLength / 2,\n startOffset = Math.ceil(remainingAvailableLengthHalf),\n endOffset = -1 * Math.floor(remainingAvailableLengthHalf),\n end = '';\n if (endOffset < 0) {\n end = segment.substr(endOffset);\n }\n return segment.substr(0, startOffset) + ellipsisChars + end;\n };\n if (url.length <= truncateLen) {\n return url;\n }\n let availableLength = truncateLen - ellipsisLength;\n let urlObj = parse_url(url);\n // Clean up the URL\n if (urlObj.query) {\n let matchQuery = urlObj.query.match(/^(.*?)(?=(\\?|\\#))(.*?)$/i);\n if (matchQuery) {\n // Malformed URL; two or more \"?\". Removed any content behind the 2nd.\n urlObj.query = urlObj.query.substr(0, matchQuery[1].length);\n url = buildUrl(urlObj);\n }\n }\n if (url.length <= truncateLen) {\n return url;\n }\n if (urlObj.host) {\n urlObj.host = urlObj.host.replace(/^www\\./, '');\n url = buildUrl(urlObj);\n }\n if (url.length <= truncateLen) {\n return url;\n }\n // Process and build the URL\n let str = '';\n if (urlObj.host) {\n str += urlObj.host;\n }\n if (str.length >= availableLength) {\n if ((urlObj.host as string).length == truncateLen) {\n return (\n (urlObj.host as string).substr(0, truncateLen - ellipsisLength) + ellipsisChars\n ).substr(0, availableLength + ellipsisLengthBeforeParsing);\n }\n return buildSegment(str, availableLength).substr(\n 0,\n availableLength + ellipsisLengthBeforeParsing\n );\n }\n let pathAndQuery = '';\n if (urlObj.path) {\n pathAndQuery += '/' + urlObj.path;\n }\n if (urlObj.query) {\n pathAndQuery += '?' + urlObj.query;\n }\n if (pathAndQuery) {\n if ((str + pathAndQuery).length >= availableLength) {\n if ((str + pathAndQuery).length == truncateLen) {\n return (str + pathAndQuery).substr(0, truncateLen);\n }\n let remainingAvailableLength = availableLength - str.length;\n return (str + buildSegment(pathAndQuery, remainingAvailableLength)).substr(\n 0,\n availableLength + ellipsisLengthBeforeParsing\n );\n } else {\n str += pathAndQuery;\n }\n }\n if (urlObj.fragment) {\n let fragment = '#' + urlObj.fragment;\n if ((str + fragment).length >= availableLength) {\n if ((str + fragment).length == truncateLen) {\n return (str + fragment).substr(0, truncateLen);\n }\n let remainingAvailableLength2 = availableLength - str.length;\n return (str + buildSegment(fragment, remainingAvailableLength2)).substr(\n 0,\n availableLength + ellipsisLengthBeforeParsing\n );\n } else {\n str += fragment;\n }\n }\n if (urlObj.scheme && urlObj.host) {\n let scheme = urlObj.scheme + '://';\n if ((str + scheme).length < availableLength) {\n return (scheme + str).substr(0, truncateLen);\n }\n }\n if (str.length <= truncateLen) {\n return str;\n }\n let end = '';\n if (availableLength > 0) {\n end = str.substr(-1 * Math.floor(availableLength / 2));\n }\n return (str.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(\n 0,\n availableLength + ellipsisLengthBeforeParsing\n );\n}\n\ninterface UrlObject {\n scheme?: string;\n host?: string;\n path?: string;\n query?: string;\n fragment?: string;\n}\n","/**\n * Date: 2015-10-05\n * Author: Kasper Søfren (https://github.com/kafoso)\n *\n * A truncation feature, where the ellipsis will be placed in the dead-center of the URL.\n *\n * @param {String} url A URL.\n * @param {Number} truncateLen The maximum length of the truncated output URL string.\n * @param {String} ellipsisChars The characters to place within the url, e.g. \"..\".\n * @return {String} The truncated URL.\n */\nexport function truncateMiddle(url: string, truncateLen: number, ellipsisChars?: string) {\n if (url.length <= truncateLen) {\n return url;\n }\n\n let ellipsisLengthBeforeParsing: number;\n let ellipsisLength: number;\n\n if (ellipsisChars == null) {\n ellipsisChars = '…';\n ellipsisLengthBeforeParsing = 8;\n ellipsisLength = 3;\n } else {\n ellipsisLengthBeforeParsing = ellipsisChars.length;\n ellipsisLength = ellipsisChars.length;\n }\n\n let availableLength = truncateLen - ellipsisLength;\n let end = '';\n if (availableLength > 0) {\n end = url.substr(-1 * Math.floor(availableLength / 2));\n }\n return (url.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(\n 0,\n availableLength + ellipsisLengthBeforeParsing\n );\n}\n","import { ellipsis } from '../utils';\n\n/**\n * A truncation feature where the ellipsis will be placed at the end of the URL.\n *\n * @param {String} anchorText\n * @param {Number} truncateLen The maximum length of the truncated output URL string.\n * @param {String} ellipsisChars The characters to place within the url, e.g. \"..\".\n * @return {String} The truncated URL.\n */\nexport function truncateEnd(anchorText: string, truncateLen: number, ellipsisChars?: string) {\n return ellipsis(anchorText, truncateLen, ellipsisChars);\n}\n","import { HtmlTag } from './html-tag';\nimport { TruncateConfigObj } from './autolinker';\nimport { truncateSmart } from './truncate/truncate-smart';\nimport { truncateMiddle } from './truncate/truncate-middle';\nimport { truncateEnd } from './truncate/truncate-end';\nimport { AbstractMatch } from './match/abstract-match';\n\n/**\n * @protected\n * @class Autolinker.AnchorTagBuilder\n * @extends Object\n *\n * Builds anchor (<a>) tags for the Autolinker utility when a match is\n * found.\n *\n * Normally this class is instantiated, configured, and used internally by an\n * {@link Autolinker} instance, but may actually be used indirectly in a\n * {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag}\n * instances which may be modified before returning from the\n * {@link Autolinker#replaceFn replaceFn}. For example:\n *\n * var html = Autolinker.link( \"Test google.com\", {\n * replaceFn : function( match ) {\n * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance\n * tag.setAttr( 'rel', 'nofollow' );\n *\n * return tag;\n * }\n * } );\n *\n * // generated html:\n * // Test google.com\n */\nexport class AnchorTagBuilder {\n /**\n * @cfg {Boolean} newWindow\n * @inheritdoc Autolinker#newWindow\n */\n private readonly newWindow: boolean = false; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Object} truncate\n * @inheritdoc Autolinker#truncate\n */\n private readonly truncate: TruncateConfigObj = {}; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {String} className\n * @inheritdoc Autolinker#className\n */\n private readonly className: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @method constructor\n * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).\n */\n constructor(cfg: AnchorTagBuilderCfg = {}) {\n this.newWindow = cfg.newWindow || false;\n this.truncate = cfg.truncate || {};\n this.className = cfg.className || '';\n }\n\n /**\n * Generates the actual anchor (<a>) tag to use in place of the\n * matched text, via its `match` object.\n *\n * @param match The Match instance to generate an anchor tag from.\n * @return The HtmlTag instance for the anchor tag.\n */\n public build(match: AbstractMatch) {\n return new HtmlTag({\n tagName: 'a',\n attrs: this.createAttrs(match),\n innerHtml: this.processAnchorText(match.getAnchorText()),\n });\n }\n\n /**\n * Creates the Object (map) of the HTML attributes for the anchor (<a>)\n * tag being generated.\n *\n * @protected\n * @param match The Match instance to generate an anchor tag from.\n * @return A key/value Object (map) of the anchor tag's attributes.\n */\n protected createAttrs(match: AbstractMatch) {\n let attrs: { [attrName: string]: string } = {\n href: match.getAnchorHref(), // we'll always have the `href` attribute\n };\n\n let cssClass = this.createCssClass(match);\n if (cssClass) {\n attrs['class'] = cssClass;\n }\n if (this.newWindow) {\n attrs['target'] = '_blank';\n attrs['rel'] = 'noopener noreferrer'; // Issue #149. See https://mathiasbynens.github.io/rel-noopener/\n }\n\n if (this.truncate) {\n if (this.truncate.length && this.truncate.length < match.getAnchorText().length) {\n attrs['title'] = match.getAnchorHref();\n }\n }\n\n return attrs;\n }\n\n /**\n * Creates the CSS class that will be used for a given anchor tag, based on\n * the `matchType` and the {@link #className} config.\n *\n * Example returns:\n *\n * - \"\" // no {@link #className}\n * - \"myLink myLink-url\" // url match\n * - \"myLink myLink-email\" // email match\n * - \"myLink myLink-phone\" // phone match\n * - \"myLink myLink-hashtag\" // hashtag match\n * - \"myLink myLink-mention myLink-twitter\" // mention match with Twitter service\n *\n * @protected\n * @param match The Match instance to generate an\n * anchor tag from.\n * @return The CSS class string for the link. Example return:\n * \"myLink myLink-url\". If no {@link #className} was configured, returns\n * an empty string.\n */\n protected createCssClass(match: AbstractMatch): string {\n let className = this.className;\n\n if (!className) {\n return '';\n } else {\n let returnClasses = [className],\n cssClassSuffixes = match.getCssClassSuffixes();\n\n for (let i = 0, len = cssClassSuffixes.length; i < len; i++) {\n returnClasses.push(className + '-' + cssClassSuffixes[i]);\n }\n return returnClasses.join(' ');\n }\n }\n\n /**\n * Processes the `anchorText` by truncating the text according to the\n * {@link #truncate} config.\n *\n * @private\n * @param anchorText The anchor tag's text (i.e. what will be\n * displayed).\n * @return The processed `anchorText`.\n */\n private processAnchorText(anchorText: string): string {\n anchorText = this.doTruncate(anchorText);\n\n return anchorText;\n }\n\n /**\n * Performs the truncation of the `anchorText` based on the {@link #truncate}\n * option. If the `anchorText` is longer than the length specified by the\n * {@link #truncate} option, the truncation is performed based on the\n * `location` property. See {@link #truncate} for details.\n *\n * @private\n * @param anchorText The anchor tag's text (i.e. what will be\n * displayed).\n * @return The truncated anchor text.\n */\n private doTruncate(anchorText: string): string {\n let truncate = this.truncate;\n if (!truncate || !truncate.length) return anchorText;\n\n let truncateLength = truncate.length,\n truncateLocation = truncate.location;\n\n if (truncateLocation === 'smart') {\n return truncateSmart(anchorText, truncateLength);\n } else if (truncateLocation === 'middle') {\n return truncateMiddle(anchorText, truncateLength);\n } else {\n return truncateEnd(anchorText, truncateLength);\n }\n }\n}\n\nexport interface AnchorTagBuilderCfg {\n newWindow?: boolean;\n truncate?: TruncateConfigObj;\n className?: string;\n}\n","/******************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\r\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\r\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\r\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\r\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\r\n var _, done = false;\r\n for (var i = decorators.length - 1; i >= 0; i--) {\r\n var context = {};\r\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\r\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\r\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\r\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\r\n if (kind === \"accessor\") {\r\n if (result === void 0) continue;\r\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\r\n if (_ = accept(result.get)) descriptor.get = _;\r\n if (_ = accept(result.set)) descriptor.set = _;\r\n if (_ = accept(result.init)) initializers.unshift(_);\r\n }\r\n else if (_ = accept(result)) {\r\n if (kind === \"field\") initializers.unshift(_);\r\n else descriptor[key] = _;\r\n }\r\n }\r\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\r\n done = true;\r\n};\r\n\r\nexport function __runInitializers(thisArg, initializers, value) {\r\n var useValue = arguments.length > 2;\r\n for (var i = 0; i < initializers.length; i++) {\r\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\r\n }\r\n return useValue ? value : void 0;\r\n};\r\n\r\nexport function __propKey(x) {\r\n return typeof x === \"symbol\" ? x : \"\".concat(x);\r\n};\r\n\r\nexport function __setFunctionName(f, name, prefix) {\r\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\r\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\r\n};\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\r\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n var desc = Object.getOwnPropertyDescriptor(m, k);\r\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\r\n desc = { enumerable: true, get: function() { return m[k]; } };\r\n }\r\n Object.defineProperty(o, k2, desc);\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\r\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nvar ownKeys = function(o) {\r\n ownKeys = Object.getOwnPropertyNames || function (o) {\r\n var ar = [];\r\n for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;\r\n return ar;\r\n };\r\n return ownKeys(o);\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== \"default\") __createBinding(result, mod, k[i]);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n\r\nexport function __classPrivateFieldIn(state, receiver) {\r\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\r\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\r\n}\r\n\r\nexport function __addDisposableResource(env, value, async) {\r\n if (value !== null && value !== void 0) {\r\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\r\n var dispose, inner;\r\n if (async) {\r\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\r\n dispose = value[Symbol.asyncDispose];\r\n }\r\n if (dispose === void 0) {\r\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\r\n dispose = value[Symbol.dispose];\r\n if (async) inner = dispose;\r\n }\r\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\r\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\r\n env.stack.push({ value: value, dispose: dispose, async: async });\r\n }\r\n else if (async) {\r\n env.stack.push({ async: true });\r\n }\r\n return value;\r\n\r\n}\r\n\r\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\r\n var e = new Error(message);\r\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\r\n};\r\n\r\nexport function __disposeResources(env) {\r\n function fail(e) {\r\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\r\n env.hasError = true;\r\n }\r\n var r, s = 0;\r\n function next() {\r\n while (r = env.stack.pop()) {\r\n try {\r\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\r\n if (r.dispose) {\r\n var result = r.dispose.call(r.value);\r\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\r\n }\r\n else s |= 1;\r\n }\r\n catch (e) {\r\n fail(e);\r\n }\r\n }\r\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\r\n if (env.hasError) throw env.error;\r\n }\r\n return next();\r\n}\r\n\r\nexport function __rewriteRelativeImportExtension(path, preserveJsx) {\r\n if (typeof path === \"string\" && /^\\.\\.?\\//.test(path)) {\r\n return path.replace(/\\.(tsx)$|((?:\\.d)?)((?:\\.[^./]+?)?)\\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {\r\n return tsx ? preserveJsx ? \".jsx\" : \".js\" : d && (!ext || !cm) ? m : (d + ext + \".\" + cm.toLowerCase() + \"js\");\r\n });\r\n }\r\n return path;\r\n}\r\n\r\nexport default {\r\n __extends: __extends,\r\n __assign: __assign,\r\n __rest: __rest,\r\n __decorate: __decorate,\r\n __param: __param,\r\n __esDecorate: __esDecorate,\r\n __runInitializers: __runInitializers,\r\n __propKey: __propKey,\r\n __setFunctionName: __setFunctionName,\r\n __metadata: __metadata,\r\n __awaiter: __awaiter,\r\n __generator: __generator,\r\n __createBinding: __createBinding,\r\n __exportStar: __exportStar,\r\n __values: __values,\r\n __read: __read,\r\n __spread: __spread,\r\n __spreadArrays: __spreadArrays,\r\n __spreadArray: __spreadArray,\r\n __await: __await,\r\n __asyncGenerator: __asyncGenerator,\r\n __asyncDelegator: __asyncDelegator,\r\n __asyncValues: __asyncValues,\r\n __makeTemplateObject: __makeTemplateObject,\r\n __importStar: __importStar,\r\n __importDefault: __importDefault,\r\n __classPrivateFieldGet: __classPrivateFieldGet,\r\n __classPrivateFieldSet: __classPrivateFieldSet,\r\n __classPrivateFieldIn: __classPrivateFieldIn,\r\n __addDisposableResource: __addDisposableResource,\r\n __disposeResources: __disposeResources,\r\n __rewriteRelativeImportExtension: __rewriteRelativeImportExtension,\r\n};\r\n","import { AnchorTagBuilder } from '../anchor-tag-builder';\nimport { HtmlTag } from '../html-tag';\nimport { MatchType } from './match';\n\n/**\n * @abstract\n * @class Autolinker.match.AbstractMatch\n *\n * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a\n * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match.\n *\n * For example:\n *\n * var input = \"...\"; // string with URLs, Email Addresses, and Mentions (Twitter, Instagram, Soundcloud)\n *\n * var linkedText = Autolinker.link( input, {\n * replaceFn : function( match ) {\n * console.log( \"href = \", match.getAnchorHref() );\n * console.log( \"text = \", match.getAnchorText() );\n *\n * switch( match.getType() ) {\n * case 'url' :\n * console.log( \"url: \", match.getUrl() );\n *\n * case 'email' :\n * console.log( \"email: \", match.getEmail() );\n *\n * case 'mention' :\n * console.log( \"mention: \", match.getMention() );\n * }\n * }\n * } );\n *\n * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}.\n */\nexport abstract class AbstractMatch {\n /**\n * @public\n * @property {'url'/'email'/'hashtag'/'mention'/'phone'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public abstract readonly type: MatchType;\n\n /**\n * @cfg {Autolinker.AnchorTagBuilder} tagBuilder (required)\n *\n * Reference to the AnchorTagBuilder instance to use to generate an anchor\n * tag for the Match.\n */\n // @ts-ignore\n private _ = null; // property used just to get the above doc comment into the ES5 output and documentation generator\n\n // Actual property for the above jsdoc comment\n private readonly tagBuilder: AnchorTagBuilder;\n\n /**\n * @cfg {String} matchedText (required)\n *\n * The original text that was matched by the {@link Autolinker.matcher.Matcher}.\n */\n protected readonly matchedText: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Number} offset (required)\n *\n * The offset of where the match was made in the input string.\n */\n private offset: number = 0; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @member Autolinker.match.Match\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: AbstractMatchConfig) {\n this.tagBuilder = cfg.tagBuilder;\n this.matchedText = cfg.matchedText;\n this.offset = cfg.offset;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n *\n * @deprecated Use {@link #type} instead which can assist in type-narrowing\n * for TypeScript.\n * @abstract\n * @return {String}\n */\n public abstract getType(): MatchType;\n\n /**\n * Returns the original text that was matched.\n *\n * @return {String}\n */\n public getMatchedText(): string {\n return this.matchedText;\n }\n\n /**\n * Sets the {@link #offset} of where the match was made in the input string.\n *\n * A {@link Autolinker.matcher.Matcher} will be fed only HTML text nodes,\n * and will therefore set an original offset that is relative to the HTML\n * text node itself. However, we want this offset to be relative to the full\n * HTML input string, and thus if using {@link Autolinker#parse} (rather\n * than calling a {@link Autolinker.matcher.Matcher} directly), then this\n * offset is corrected after the Matcher itself has done its job.\n *\n * @private\n * @param {Number} offset\n */\n setOffset(offset: number): void {\n this.offset = offset;\n }\n\n /**\n * Returns the offset of where the match was made in the input string. This\n * is the 0-based index of the match.\n *\n * @return {Number}\n */\n public getOffset(): number {\n return this.offset;\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @abstract\n * @return {String}\n */\n public abstract getAnchorHref(): string;\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @abstract\n * @return {String}\n */\n public abstract getAnchorText(): string;\n\n /**\n * Returns the CSS class suffix(es) for this match.\n *\n * A CSS class suffix is appended to the {@link Autolinker#className} in\n * the {@link Autolinker.AnchorTagBuilder} when a match is translated into\n * an anchor tag.\n *\n * For example, if {@link Autolinker#className} was configured as 'myLink',\n * and this method returns `[ 'url' ]`, the final class name of the element\n * will become: 'myLink myLink-url'.\n *\n * The match may provide multiple CSS class suffixes to be appended to the\n * {@link Autolinker#className} in order to facilitate better styling\n * options for different match criteria. See {@link Autolinker.match.Mention}\n * for an example.\n *\n * By default, this method returns a single array with the match's\n * {@link #getType type} name, but may be overridden by subclasses.\n *\n * @return {String[]}\n */\n public getCssClassSuffixes(): string[] {\n return [this.type];\n }\n\n /**\n * Builds and returns an {@link Autolinker.HtmlTag} instance based on the\n * Match.\n *\n * This can be used to easily generate anchor tags from matches, and either\n * return their HTML string, or modify them before doing so.\n *\n * Example Usage:\n *\n * var tag = match.buildTag();\n * tag.addClass( 'cordova-link' );\n * tag.setAttr( 'target', '_system' );\n *\n * tag.toAnchorString(); // Google\n *\n * Example Usage in {@link Autolinker#replaceFn}:\n *\n * var html = Autolinker.link( \"Test google.com\", {\n * replaceFn : function( match ) {\n * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance\n * tag.setAttr( 'rel', 'nofollow' );\n *\n * return tag;\n * }\n * } );\n *\n * // generated html:\n * // Test google.com\n */\n public buildTag(): HtmlTag {\n return this.tagBuilder.build(this);\n }\n}\n\nexport interface AbstractMatchConfig {\n tagBuilder: AnchorTagBuilder;\n matchedText: string;\n offset: number;\n}\n","// NOTE: THIS IS A GENERATED FILE\n// To update with the latest TLD list, run `npm run update-tld-regex`\n\nexport const tldRegexStr = '(?:xn--vermgensberatung-pwb|xn--vermgensberater-ctb|xn--clchc0ea0b2g2a9gcd|xn--w4r85el8fhu5dnra|travelersinsurance|vermögensberatung|xn--5su34j936bgsg|xn--bck1b9a5dre4c|xn--mgbah1a3hjkrd|xn--mgbai9azgqp6j|xn--mgberp4a5d4ar|xn--xkc2dl3a5ee0h|vermögensberater|xn--fzys8d69uvgm|xn--mgba7c0bbn0a|xn--mgbcpq6gpa1a|xn--xkc2al3hye2a|americanexpress|kerryproperties|sandvikcoromant|xn--i1b6b1a6a2e|xn--kcrx77d1x4a|xn--lgbbat1ad8j|xn--mgba3a4f16a|xn--mgbc0a9azcg|xn--nqv7fs00ema|americanfamily|kerrylogistics|weatherchannel|xn--54b7fta0cc|xn--6qq986b3xl|xn--80aqecdr1a|xn--b4w605ferd|xn--fiq228c5hs|xn--h2breg3eve|xn--jlq480n2rg|xn--mgba3a3ejt|xn--mgbaam7a8h|xn--mgbayh7gpa|xn--mgbbh1a71e|xn--mgbca7dzdo|xn--mgbi4ecexp|xn--mgbx4cd0ab|xn--rvc1e0am3e|international|lifeinsurance|wolterskluwer|xn--cckwcxetd|xn--eckvdtc9d|xn--fpcrj9c3d|xn--fzc2c9e2c|xn--h2brj9c8c|xn--tiq49xqyj|xn--yfro4i67o|xn--ygbi2ammx|construction|lplfinancial|scholarships|versicherung|xn--3e0b707e|xn--45br5cyl|xn--4dbrk0ce|xn--80adxhks|xn--80asehdb|xn--8y0a063a|xn--gckr3f0f|xn--mgb9awbf|xn--mgbab2bd|xn--mgbgu82a|xn--mgbpl2fh|xn--mgbt3dhd|xn--mk1bu44c|xn--ngbc5azd|xn--ngbe9e0a|xn--ogbpf8fl|xn--qcka1pmc|accountants|barclaycard|blackfriday|blockbuster|bridgestone|calvinklein|contractors|creditunion|engineering|enterprises|investments|kerryhotels|lamborghini|motorcycles|olayangroup|photography|playstation|productions|progressive|redumbrella|williamhill|xn--11b4c3d|xn--1ck2e1b|xn--1qqw23a|xn--2scrj9c|xn--3bst00m|xn--3ds443g|xn--3hcrj9c|xn--42c2d9a|xn--45brj9c|xn--55qw42g|xn--6frz82g|xn--80ao21a|xn--9krt00a|xn--cck2b3b|xn--czr694b|xn--d1acj3b|xn--efvy88h|xn--fct429k|xn--fjq720a|xn--flw351e|xn--g2xx48c|xn--gecrj9c|xn--gk3at1e|xn--h2brj9c|xn--hxt814e|xn--imr513n|xn--j6w193g|xn--jvr189m|xn--kprw13d|xn--kpry57d|xn--mgbbh1a|xn--mgbtx2b|xn--mix891f|xn--nyqy26a|xn--otu796d|xn--pgbs0dh|xn--q9jyb4c|xn--rhqv96g|xn--rovu88b|xn--s9brj9c|xn--ses554g|xn--t60b56a|xn--vuq861b|xn--w4rs40l|xn--xhq521b|xn--zfr164b|சிங்கப்பூர்|accountant|apartments|associates|basketball|bnpparibas|boehringer|capitalone|consulting|creditcard|cuisinella|eurovision|extraspace|foundation|healthcare|immobilien|industries|management|mitsubishi|nextdirect|properties|protection|prudential|realestate|republican|restaurant|schaeffler|tatamotors|technology|university|vlaanderen|xn--30rr7y|xn--3pxu8k|xn--45q11c|xn--4gbrim|xn--55qx5d|xn--5tzm5g|xn--80aswg|xn--90a3ac|xn--9dbq2a|xn--9et52u|xn--c2br7g|xn--cg4bki|xn--czrs0t|xn--czru2d|xn--fiq64b|xn--fiqs8s|xn--fiqz9s|xn--io0a7i|xn--kput3i|xn--mxtq1m|xn--o3cw4h|xn--pssy2u|xn--q7ce6a|xn--unup4y|xn--wgbh1c|xn--wgbl6a|xn--y9a3aq|accenture|allfinanz|amsterdam|analytics|aquarelle|barcelona|bloomberg|christmas|community|directory|education|equipment|fairwinds|financial|firestone|fresenius|furniture|goldpoint|hisamitsu|homedepot|homegoods|homesense|institute|insurance|kuokgroup|lancaster|landrover|lifestyle|marketing|marshalls|melbourne|microsoft|panasonic|pramerica|richardli|shangrila|solutions|statebank|statefarm|stockholm|travelers|vacations|xn--90ais|xn--c1avg|xn--d1alf|xn--e1a4c|xn--fhbei|xn--j1aef|xn--j1amh|xn--l1acc|xn--ngbrx|xn--nqv7f|xn--p1acf|xn--qxa6a|xn--tckwe|xn--vhquv|yodobashi|موريتانيا|abudhabi|airforce|allstate|attorney|barclays|barefoot|bargains|baseball|boutique|bradesco|broadway|brussels|builders|business|capetown|catering|catholic|cipriani|cleaning|clinique|clothing|commbank|computer|delivery|deloitte|democrat|diamonds|discount|discover|download|engineer|ericsson|exchange|feedback|fidelity|firmdale|football|frontier|goodyear|grainger|graphics|hdfcbank|helsinki|holdings|hospital|infiniti|ipiranga|istanbul|jpmorgan|lighting|lundbeck|marriott|mckinsey|memorial|merckmsd|mortgage|observer|partners|pharmacy|pictures|plumbing|property|redstone|reliance|saarland|samsclub|security|services|shopping|softbank|software|stcgroup|supplies|training|vanguard|ventures|verisign|woodside|xn--90ae|xn--node|xn--p1ai|xn--qxam|yokohama|السعودية|abogado|academy|agakhan|alibaba|android|athleta|auction|audible|auspost|banamex|bauhaus|bentley|bestbuy|booking|brother|capital|caravan|careers|channel|charity|chintai|citadel|clubmed|college|cologne|company|compare|contact|cooking|corsica|country|coupons|courses|cricket|cruises|dentist|digital|domains|exposed|express|farmers|fashion|ferrari|ferrero|finance|fishing|fitness|flights|florist|flowers|forsale|frogans|fujitsu|gallery|genting|godaddy|grocery|guitars|hamburg|hangout|hitachi|holiday|hosting|hotmail|hyundai|ismaili|jewelry|juniper|kitchen|komatsu|lacaixa|lanxess|lasalle|latrobe|leclerc|limited|lincoln|markets|monster|netbank|netflix|network|neustar|okinawa|organic|origins|philips|pioneer|politie|realtor|recipes|rentals|reviews|rexroth|samsung|sandvik|schmidt|schwarz|science|shiksha|singles|staples|storage|support|surgery|systems|temasek|theater|theatre|tickets|toshiba|trading|walmart|wanggou|watches|weather|website|wedding|whoswho|windows|winners|yamaxun|youtube|zuerich|католик|البحرين|الجزائر|العليان|پاکستان|كاثوليك|இந்தியா|abbott|abbvie|africa|agency|airbus|airtel|alipay|alsace|alstom|amazon|anquan|aramco|author|bayern|beauty|berlin|bharti|bostik|boston|broker|camera|career|casino|center|chanel|chrome|church|circle|claims|clinic|coffee|comsec|condos|coupon|credit|cruise|dating|datsun|dealer|degree|dental|design|direct|doctor|dunlop|dupont|durban|emerck|energy|estate|events|expert|family|flickr|futbol|gallup|garden|george|giving|global|google|gratis|health|hermes|hiphop|hockey|hotels|hughes|imamat|insure|intuit|jaguar|joburg|juegos|kaufen|kindle|kosher|latino|lawyer|lefrak|living|locker|london|luxury|madrid|maison|makeup|market|mattel|mobile|monash|mormon|moscow|museum|nagoya|nissan|nissay|norton|nowruz|office|olayan|online|oracle|orange|otsuka|pfizer|photos|physio|pictet|quebec|racing|realty|reisen|repair|report|review|rogers|ryukyu|safety|sakura|sanofi|school|schule|search|secure|select|shouji|soccer|social|stream|studio|supply|suzuki|swatch|sydney|taipei|taobao|target|tattoo|tennis|tienda|tjmaxx|tkmaxx|toyota|travel|unicom|viajes|viking|villas|virgin|vision|voting|voyage|walter|webcam|xihuan|yachts|yandex|zappos|москва|онлайн|ابوظبي|ارامكو|الاردن|المغرب|امارات|فلسطين|مليسيا|भारतम्|இலங்கை|ファッション|actor|adult|aetna|amfam|amica|apple|archi|audio|autos|azure|baidu|beats|bible|bingo|black|boats|bosch|build|canon|cards|chase|cheap|cisco|citic|click|cloud|coach|codes|crown|cymru|dance|deals|delta|drive|dubai|earth|edeka|email|epson|faith|fedex|final|forex|forum|gallo|games|gifts|gives|glass|globo|gmail|green|gripe|group|gucci|guide|homes|honda|horse|house|hyatt|ikano|irish|jetzt|koeln|kyoto|lamer|lease|legal|lexus|lilly|lipsy|loans|locus|lotte|lotto|mango|media|miami|money|movie|music|nexus|nikon|ninja|nokia|nowtv|omega|osaka|paris|parts|party|phone|photo|pizza|place|poker|praxi|press|prime|promo|quest|radio|rehab|reise|ricoh|rocks|rodeo|rugby|salon|sener|seven|sharp|shell|shoes|skype|sling|smart|smile|solar|space|sport|stada|store|study|style|sucks|swiss|tatar|tires|tirol|tmall|today|tokyo|tools|toray|total|tours|trade|trust|tunes|tushu|ubank|vegas|video|vodka|volvo|wales|watch|weber|weibo|works|world|xerox|yahoo|ישראל|ایران|بازار|بھارت|سودان|سورية|همراه|भारोत|संगठन|বাংলা|భారత్|ഭാരതം|嘉里大酒店|aarp|able|aero|akdn|ally|amex|arab|army|arpa|arte|asda|asia|audi|auto|baby|band|bank|bbva|beer|best|bike|bing|blog|blue|bofa|bond|book|buzz|cafe|call|camp|care|cars|casa|case|cash|cbre|cern|chat|citi|city|club|cool|coop|cyou|data|date|dclk|deal|dell|desi|diet|dish|docs|dvag|erni|fage|fail|fans|farm|fast|fido|film|fire|fish|flir|food|ford|free|fund|game|gbiz|gent|ggee|gift|gmbh|gold|golf|goog|guge|guru|hair|haus|hdfc|help|here|host|hsbc|icbc|ieee|imdb|immo|info|itau|java|jeep|jobs|jprs|kddi|kids|kiwi|kpmg|kred|land|lego|lgbt|lidl|life|like|limo|link|live|loan|love|ltda|luxe|maif|meet|meme|menu|mini|mint|mobi|moda|moto|name|navy|news|next|nico|nike|ollo|open|page|pars|pccw|pics|ping|pink|play|plus|pohl|porn|post|prod|prof|qpon|read|reit|rent|rest|rich|room|rsvp|ruhr|safe|sale|sarl|save|saxo|scot|seat|seek|sexy|shia|shop|show|silk|sina|site|skin|sncf|sohu|song|sony|spot|star|surf|talk|taxi|team|tech|teva|tiaa|tips|town|toys|tube|vana|visa|viva|vivo|vote|voto|wang|weir|wien|wiki|wine|work|xbox|yoga|zara|zero|zone|дети|сайт|بارت|بيتك|ڀارت|تونس|شبكة|عراق|عمان|موقع|भारत|ভারত|ভাৰত|ਭਾਰਤ|ભારત|ଭାରତ|ಭಾರತ|ලංකා|アマゾン|グーグル|クラウド|ポイント|组织机构|電訊盈科|香格里拉|aaa|abb|abc|aco|ads|aeg|afl|aig|anz|aol|app|art|aws|axa|bar|bbc|bbt|bcg|bcn|bet|bid|bio|biz|bms|bmw|bom|boo|bot|box|buy|bzh|cab|cal|cam|car|cat|cba|cbn|ceo|cfa|cfd|com|cpa|crs|dad|day|dds|dev|dhl|diy|dnp|dog|dot|dtv|dvr|eat|eco|edu|esq|eus|fan|fit|fly|foo|fox|frl|ftr|fun|fyi|gal|gap|gay|gdn|gea|gle|gmo|gmx|goo|gop|got|gov|hbo|hiv|hkt|hot|how|ibm|ice|icu|ifm|inc|ing|ink|int|ist|itv|jcb|jio|jll|jmp|jnj|jot|joy|kfh|kia|kim|kpn|krd|lat|law|lds|llc|llp|lol|lpl|ltd|man|map|mba|med|men|mil|mit|mlb|mls|mma|moe|moi|mom|mov|msd|mtn|mtr|nab|nba|nec|net|new|nfl|ngo|nhk|now|nra|nrw|ntt|nyc|obi|one|ong|onl|ooo|org|ott|ovh|pay|pet|phd|pid|pin|pnc|pro|pru|pub|pwc|red|ren|ril|rio|rip|run|rwe|sap|sas|sbi|sbs|scb|sew|sex|sfr|ski|sky|soy|spa|srl|stc|tab|tax|tci|tdk|tel|thd|tjx|top|trv|tui|tvs|ubs|uno|uol|ups|vet|vig|vin|vip|wed|win|wme|wow|wtc|wtf|xin|xxx|xyz|you|yun|zip|бел|ком|қаз|мкд|мон|орг|рус|срб|укр|հայ|קום|عرب|قطر|كوم|مصر|कॉम|नेट|คอม|ไทย|ລາວ|ストア|セール|みんな|中文网|亚马逊|天主教|我爱你|新加坡|淡马锡|飞利浦|ac|ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw|ελ|ευ|бг|ею|рф|გე|닷넷|닷컴|삼성|한국|コム|世界|中信|中国|中國|企业|佛山|信息|健康|八卦|公司|公益|台湾|台灣|商城|商店|商标|嘉里|在线|大拿|娱乐|家電|广东|微博|慈善|手机|招聘|政务|政府|新闻|时尚|書籍|机构|游戏|澳門|点看|移动|网址|网店|网站|网络|联通|谷歌|购物|通販|集团|食品|餐厅|香港)';\n\nexport const tldRegex = new RegExp('^' + tldRegexStr + '$');\n","import { alphaNumericAndMarksRe, letterRe, digitRe } from '../regex-lib';\nimport { tldRegex } from './tld-regex';\n\n/**\n * A regular expression that is simply the character class of the characters\n * that may be used in a domain name, minus the '-' or '.'\n */\nexport const domainNameCharRegex = alphaNumericAndMarksRe;\n\n/**\n * The set of characters that will start a URL suffix (i.e. the path, query, and\n * hash part of the URL)\n */\nexport const urlSuffixStartCharsRe = /[\\/?#]/;\n\n/**\n * The set of characters that are allowed in the URL suffix (i.e. the path,\n * query, and hash part of the URL) which may also form the ending character of\n * the URL.\n *\n * The {@link #urlSuffixNotAllowedAsLastCharRe} are additional allowed URL\n * suffix characters, but (generally) should not be the last character of a URL.\n */\nexport const urlSuffixAllowedSpecialCharsRe = /[-+&@#/%=~_()|'$*\\[\\]{}\\u2713]/;\n\n/**\n * URL suffix characters (i.e. path, query, and has part of the URL) that are\n * not allowed as the *last character* in the URL suffix as they would normally\n * form the end of a sentence.\n *\n * The {@link #urlSuffixAllowedSpecialCharsRe} contains additional allowed URL\n * suffix characters which are allowed as the last character.\n */\nexport const urlSuffixNotAllowedAsLastCharRe = /[?!:,.;^]/;\n\n/**\n * Regular expression to match an http:// or https:// scheme.\n */\nexport const httpSchemeRe = /https?:\\/\\//i;\n\n/**\n * Regular expression to match an http:// or https:// scheme as the prefix of\n * a string.\n */\nexport const httpSchemePrefixRe = new RegExp('^' + httpSchemeRe.source, 'i');\n\nexport const urlSuffixedCharsNotAllowedAtEndRe = new RegExp(\n urlSuffixNotAllowedAsLastCharRe.source + '$'\n);\n\n/**\n * A regular expression used to determine the schemes we should not autolink\n */\nexport const invalidSchemeRe = /^(javascript|vbscript):/i;\n\n// A regular expression used to determine if the URL is a scheme match (such as\n// 'http://google.com', and as opposed to a \"TLD match\"). This regular\n// expression is used to parse out the host along with if the URL has an\n// authority component (i.e. '//')\n//\n// Capturing groups:\n// 1. '//' if the URL has an authority component, empty string otherwise\n// 2. The host (if one exists). Ex: 'google.com'\n//\n// See https://www.rfc-editor.org/rfc/rfc3986#appendix-A for terminology\nexport const schemeUrlRe = /^[A-Za-z][-.+A-Za-z0-9]*:(\\/\\/)?([^:/]*)/;\n\n// A regular expression used to determine if the URL is a TLD match (such as\n// 'google.com', and as opposed to a \"scheme match\"). This regular\n// expression is used to help parse out the TLD (top-level domain) of the host.\n//\n// See https://www.rfc-editor.org/rfc/rfc3986#appendix-A for terminology\nexport const tldUrlHostRe = /^(?:\\/\\/)?([^/#?:]+)/; // optionally prefixed with protocol-relative '//' chars\n\n/**\n * Determines if the given character may start a scheme (ex: 'http').\n */\nexport function isSchemeStartChar(char: string): boolean {\n return letterRe.test(char);\n}\n\n/**\n * Determines if the given character is a valid character in a scheme (such as\n * 'http' or 'ssh+git'), but only after the start char (which is handled by\n * {@link isSchemeStartChar}.\n */\nexport function isSchemeChar(char: string): boolean {\n return (\n letterRe.test(char) || digitRe.test(char) || char === '+' || char === '-' || char === '.'\n );\n}\n\n/**\n * Determines if the character can begin a domain label, which must be an\n * alphanumeric character and not an underscore or dash.\n *\n * A domain label is a segment of a hostname such as subdomain.google.com.\n */\nexport function isDomainLabelStartChar(char: string): boolean {\n return alphaNumericAndMarksRe.test(char);\n}\n\n/**\n * Determines if the character is part of a domain label (but not a domain label\n * start character).\n *\n * A domain label is a segment of a hostname such as subdomain.google.com.\n */\nexport function isDomainLabelChar(char: string): boolean {\n return char === '_' || isDomainLabelStartChar(char);\n}\n\n/**\n * Determines if the character is a path character (\"pchar\") as defined by\n * https://tools.ietf.org/html/rfc3986#appendix-A\n *\n * pchar = unreserved / pct-encoded / sub-delims / \":\" / \"@\"\n *\n * unreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n * pct-encoded = \"%\" HEXDIG HEXDIG\n * sub-delims = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\"\n * / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n *\n * Note that this implementation doesn't follow the spec exactly, but rather\n * follows URL path characters found out in the wild (spec might be out of date?)\n */\nexport function isPathChar(char: string): boolean {\n return (\n alphaNumericAndMarksRe.test(char) ||\n urlSuffixAllowedSpecialCharsRe.test(char) ||\n urlSuffixNotAllowedAsLastCharRe.test(char)\n );\n}\n\n/**\n * Determines if the character given may begin the \"URL Suffix\" section of a\n * URI (i.e. the path, query, or hash section). These are the '/', '?' and '#'\n * characters.\n *\n * See https://tools.ietf.org/html/rfc3986#appendix-A\n */\nexport function isUrlSuffixStartChar(char: string) {\n return urlSuffixStartCharsRe.test(char);\n}\n\n/**\n * Determines if the TLD read in the host is a known TLD (Top-Level Domain).\n *\n * Example: 'com' would be a known TLD (for a host of 'google.com'), but\n * 'local' would not (for a domain name of 'my-computer.local').\n */\nexport function isKnownTld(tld: string) {\n return tldRegex.test(tld.toLowerCase()); // make sure the tld is lowercase for the regex\n}\n\n/**\n * Determines if the given `url` is a valid scheme-prefixed URL.\n */\nexport function isValidSchemeUrl(url: string): boolean {\n // If the scheme is 'javascript:' or 'vbscript:', these link\n // types can be dangerous. Don't link them.\n if (invalidSchemeRe.test(url)) {\n return false;\n }\n\n const schemeMatch = url.match(schemeUrlRe);\n if (!schemeMatch) {\n return false;\n }\n\n const isAuthorityMatch = !!schemeMatch![1];\n const host = schemeMatch![2];\n if (isAuthorityMatch) {\n // Any match that has an authority ('//' chars) after the scheme is\n // valid, such as 'http://anything'\n return true;\n }\n\n // If there's no authority ('//' chars), check that we have a hostname\n // that looks valid.\n //\n // The host must contain at least one '.' char and have a domain label\n // with at least one letter to be considered valid.\n //\n // Accept:\n // - git:domain.com (scheme followed by a host\n // Do not accept:\n // - git:something ('something' doesn't look like a host)\n // - version:1.0 ('1.0' doesn't look like a host)\n if (host.indexOf('.') === -1 || !letterRe.test(host)) {\n return false;\n }\n return true;\n}\n\n/**\n * Determines if the given `url` is a match with a valid TLD.\n */\nexport function isValidTldMatch(url: string): boolean {\n // TLD URL such as 'google.com', we need to confirm that we have a valid\n // top-level domain\n const tldUrlHostMatch = url.match(tldUrlHostRe);\n if (!tldUrlHostMatch) {\n // At this point, if the URL didn't match our TLD re, it must be invalid\n // (highly unlikely to happen, but just in case)\n return false;\n }\n\n const host = tldUrlHostMatch[0];\n const hostLabels = host.split('.');\n if (hostLabels.length < 2) {\n // 0 or 1 host label, there's no TLD. Ex: 'localhost'\n return false;\n }\n\n const tld = hostLabels[hostLabels.length - 1];\n if (!isKnownTld(tld)) {\n return false;\n }\n\n // TODO: Implement these conditions for TLD matcher:\n // (\n // this.longestDomainLabelLength <= 63 &&\n // this.domainNameLength <= 255\n // );\n\n return true;\n}\n\n// Regular expression to confirm a valid IPv4 address (ex: '192.168.0.1')\nconst ipV4Re =\n /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;\n\n// Regular expression used to split the IPv4 address itself from any port/path/query/hash\nconst ipV4PartRe = /[:/?#]/;\n\n/**\n * Determines if the given URL is a valid IPv4-prefixed URL.\n */\nexport function isValidIpV4Address(url: string): boolean {\n // Grab just the IP address\n const ipV4Part = url.split(ipV4PartRe, 1)[0]; // only 1 result needed\n\n return ipV4Re.test(ipV4Part);\n}\n","import { AbstractMatch, AbstractMatchConfig } from './abstract-match';\nimport { httpSchemePrefixRe } from '../parser/uri-utils';\nimport type { StripPrefixConfigObj } from '../autolinker';\n\n/**\n * A regular expression used to remove the 'www.' from URLs.\n */\nconst wwwPrefixRegex = /^(https?:\\/\\/)?(www\\.)?/i;\n\n/**\n * The regular expression used to remove the protocol-relative '//' from a URL\n * string, for purposes of formatting the anchor text. A protocol-relative URL\n * is, for example, \"//yahoo.com\"\n */\nconst protocolRelativeRegex = /^\\/\\//;\n\n/**\n * @class Autolinker.match.Url\n * @extends Autolinker.match.AbstractMatch\n *\n * Represents a Url match found in an input string which should be Autolinked.\n *\n * See this class's superclass ({@link Autolinker.match.Match}) for more details.\n */\nexport class UrlMatch extends AbstractMatch {\n /**\n * @public\n * @property {'url'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public readonly type: 'url' = 'url';\n\n /**\n * @cfg {String} url (required)\n *\n * The url that was matched.\n */\n private url: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {\"scheme\"/\"www\"/\"tld\"} urlMatchType (required)\n *\n * The type of URL match that this class represents. This helps to determine\n * if the match was made in the original text with a prefixed scheme (ex:\n * 'http://www.google.com'), a prefixed 'www' (ex: 'www.google.com'), or\n * was matched by a known top-level domain (ex: 'google.com').\n */\n private readonly urlMatchType: UrlMatchType = 'scheme'; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Boolean} protocolRelativeMatch (required)\n *\n * `true` if the URL is a protocol-relative match. A protocol-relative match\n * is a URL that starts with '//', and will be either http:// or https://\n * based on the protocol that the site is loaded under.\n */\n private readonly protocolRelativeMatch: boolean = false; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Object} stripPrefix (required)\n *\n * The Object form of {@link Autolinker#cfg-stripPrefix}.\n */\n private readonly stripPrefix: Required = {\n scheme: true,\n www: true,\n }; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Boolean} stripTrailingSlash (required)\n * @inheritdoc Autolinker#cfg-stripTrailingSlash\n */\n private readonly stripTrailingSlash: boolean = true; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Boolean} decodePercentEncoding (required)\n * @inheritdoc Autolinker#cfg-decodePercentEncoding\n */\n private readonly decodePercentEncoding: boolean = true; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @private\n * @property {Boolean} protocolPrepended\n *\n * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the\n * {@link #url} did not have a protocol)\n */\n private protocolPrepended: boolean = false;\n\n /**\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: UrlMatchConfig) {\n super(cfg);\n\n this.urlMatchType = cfg.urlMatchType;\n this.url = cfg.url;\n this.protocolRelativeMatch = cfg.protocolRelativeMatch;\n this.stripPrefix = cfg.stripPrefix;\n this.stripTrailingSlash = cfg.stripTrailingSlash;\n this.decodePercentEncoding = cfg.decodePercentEncoding;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n * For the case of UrlMatch, returns 'url'.\n *\n * @return {String}\n */\n public getType(): 'url' {\n return 'url';\n }\n\n /**\n * Returns a string name for the type of URL match that this class\n * represents.\n *\n * This helps to determine if the match was made in the original text with a\n * prefixed scheme (ex: 'http://www.google.com'), a prefixed 'www' (ex:\n * 'www.google.com'), or was matched by a known top-level domain (ex:\n * 'google.com').\n *\n * @return {\"scheme\"/\"www\"/\"tld\"}\n */\n public getUrlMatchType(): UrlMatchType {\n return this.urlMatchType;\n }\n\n /**\n * Returns the url that was matched, assuming the protocol to be 'http://' if the original\n * match was missing a protocol.\n *\n * @return {String}\n */\n public getUrl(): string {\n let url = this.url;\n\n // if the url string doesn't begin with a scheme, assume 'http://'\n if (\n !this.protocolRelativeMatch &&\n this.urlMatchType !== 'scheme' &&\n !this.protocolPrepended\n ) {\n url = this.url = 'http://' + url;\n\n this.protocolPrepended = true;\n }\n\n return url;\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @return {String}\n */\n public getAnchorHref(): string {\n let url = this.getUrl();\n\n return url.replace(/&/g, '&'); // any &'s in the URL should be converted back to '&' if they were displayed as & in the source html\n }\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorText(): string {\n let anchorText = this.getMatchedText();\n\n if (this.protocolRelativeMatch) {\n // Strip off any protocol-relative '//' from the anchor text\n anchorText = stripProtocolRelativePrefix(anchorText);\n }\n if (this.stripPrefix.scheme) {\n anchorText = stripSchemePrefix(anchorText);\n }\n if (this.stripPrefix.www) {\n anchorText = stripWwwPrefix(anchorText);\n }\n if (this.stripTrailingSlash) {\n anchorText = removeTrailingSlash(anchorText); // remove trailing slash, if there is one\n }\n if (this.decodePercentEncoding) {\n anchorText = removePercentEncoding(anchorText);\n }\n return anchorText;\n }\n}\n\nexport interface UrlMatchConfig extends AbstractMatchConfig {\n url: string;\n urlMatchType: UrlMatchType;\n protocolRelativeMatch: boolean;\n stripPrefix: Required;\n stripTrailingSlash: boolean;\n decodePercentEncoding: boolean;\n}\n\nexport type UrlMatchType = 'scheme' | 'tld' | 'ipV4';\n\n// Utility Functionality\n\n/**\n * Strips the scheme prefix (such as \"http://\" or \"https://\") from the given\n * `url`.\n *\n * @private\n * @param {String} url The text of the anchor that is being generated, for\n * which to strip off the url scheme.\n * @return {String} The `url`, with the scheme stripped.\n */\nfunction stripSchemePrefix(url: string): string {\n return url.replace(httpSchemePrefixRe, '');\n}\n\n/**\n * Strips the 'www' prefix from the given `url`.\n *\n * @private\n * @param {String} url The text of the anchor that is being generated, for\n * which to strip off the 'www' if it exists.\n * @return {String} The `url`, with the 'www' stripped.\n */\nfunction stripWwwPrefix(url: string): string {\n return url.replace(wwwPrefixRegex, '$1'); // leave any scheme ($1), it one exists\n}\n\n/**\n * Strips any protocol-relative '//' from the anchor text.\n *\n * @private\n * @param {String} text The text of the anchor that is being generated, for which to strip off the\n * protocol-relative prefix (such as stripping off \"//\")\n * @return {String} The `anchorText`, with the protocol-relative prefix stripped.\n */\nfunction stripProtocolRelativePrefix(text: string): string {\n return text.replace(protocolRelativeRegex, '');\n}\n\n/**\n * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed.\n *\n * @private\n * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing\n * slash ('/') that may exist.\n * @return {String} The `anchorText`, with the trailing slash removed.\n */\nfunction removeTrailingSlash(anchorText: string): string {\n if (anchorText.charAt(anchorText.length - 1) === '/') {\n anchorText = anchorText.slice(0, -1);\n }\n return anchorText;\n}\n\n/**\n * Decodes percent-encoded characters from the given `anchorText`, in\n * preparation for the text to be displayed.\n *\n * @private\n * @param {String} anchorText The text of the anchor that is being\n * generated, for which to decode any percent-encoded characters.\n * @return {String} The `anchorText`, with the percent-encoded characters\n * decoded.\n */\nfunction removePercentEncoding(anchorText: string): string {\n // First, convert a few of the known % encodings to the corresponding\n // HTML entities that could accidentally be interpretted as special\n // HTML characters\n const preProcessedEntityAnchorText = anchorText\n .replace(/%22/gi, '"') // \" char\n .replace(/%26/gi, '&') // & char\n .replace(/%27/gi, ''') // ' char\n .replace(/%3C/gi, '<') // < char\n .replace(/%3E/gi, '>'); // > char\n\n try {\n // Now attempt to decode the rest of the anchor text\n return decodeURIComponent(preProcessedEntityAnchorText);\n } catch (e) {\n // Invalid % escape sequence in the anchor text\n return preProcessedEntityAnchorText;\n }\n}\n","import { alphaNumericAndMarksCharsStr, alphaNumericAndMarksRe } from '../regex-lib';\nimport { isKnownTld } from './uri-utils';\n\n/**\n * A regular expression to match a 'mailto:' prefix on an email address.\n */\nexport const mailtoSchemePrefixRe = /^mailto:/i;\n\n/**\n * Regular expression for all of the valid characters of the local part of an\n * email address.\n */\nconst emailLocalPartCharRegex = new RegExp(`[${alphaNumericAndMarksCharsStr}!#$%&'*+/=?^_\\`{|}~-]`);\n\n/**\n * Determines if the given character may start the \"local part\" of an email\n * address. The local part is the part to the left of the '@' sign.\n *\n * Technically according to the email spec, any of the characters in the\n * {@link emailLocalPartCharRegex} can start an email address (including any of\n * the special characters), but this is so rare in the wild and the\n * implementation is much simpler by only starting an email address with a word\n * character. This is especially important when matching the '{' character which\n * generally starts a brace that isn't part of the email address.\n */\nexport function isEmailLocalPartStartChar(char: string): boolean {\n return alphaNumericAndMarksRe.test(char);\n}\n\n/**\n * Determines if the given character can be part of the \"local part\" of an email\n * address. The local part is the part to the left of the '@' sign.\n */\nexport function isEmailLocalPartChar(char: string): boolean {\n return emailLocalPartCharRegex.test(char);\n}\n\n/**\n * Determines if the given email address is valid. We consider it valid if it\n * has a valid TLD in its host.\n *\n * @param emailAddress email address\n * @return true is email have valid TLD, false otherwise\n */\nexport function isValidEmail(emailAddress: string): boolean {\n const emailAddressTld: string = emailAddress.split('.').pop() || '';\n\n return isKnownTld(emailAddressTld);\n}\n","import { AbstractMatchConfig, AbstractMatch } from './abstract-match';\n\n/**\n * @class Autolinker.match.Email\n * @extends Autolinker.match.AbstractMatch\n *\n * Represents a Email match found in an input string which should be Autolinked.\n *\n * See this class's superclass ({@link Autolinker.match.Match}) for more details.\n */\nexport class EmailMatch extends AbstractMatch {\n /**\n * @public\n * @property {'email'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public readonly type: 'email' = 'email';\n\n /**\n * @cfg {String} email (required)\n *\n * The email address that was matched.\n */\n private readonly email: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: EmailMatchConfig) {\n super(cfg);\n\n this.email = cfg.email;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n * For the case of EmailMatch, returns 'email'.\n *\n * @return {String}\n */\n getType(): 'email' {\n return 'email';\n }\n\n /**\n * Returns the email address that was matched.\n *\n * @return {String}\n */\n getEmail() {\n return this.email;\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorHref() {\n return 'mailto:' + this.email;\n }\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorText() {\n return this.email;\n }\n}\n\nexport interface EmailMatchConfig extends AbstractMatchConfig {\n email: string;\n}\n","import { alphaNumericAndMarksRe } from '../regex-lib';\n\n/**\n * Determines if the given `char` is a an allowed character in a hashtag. These\n * are underscores or any alphanumeric char.\n */\nexport function isHashtagTextChar(char: string): boolean {\n return char === '_' || alphaNumericAndMarksRe.test(char);\n}\n\n/**\n * Determines if a hashtag match is valid.\n */\nexport function isValidHashtag(hashtag: string): boolean {\n // Max length of 140 for a hashtag ('#' char + 139 word chars)\n return hashtag.length <= 140;\n}\n\nexport type HashtagService = 'twitter' | 'facebook' | 'instagram' | 'tiktok';\nexport const hashtagServices: HashtagService[] = ['twitter', 'facebook', 'instagram', 'tiktok'];\n","import { HashtagService } from '../parser/hashtag-utils';\nimport { assertNever } from '../utils';\nimport { AbstractMatch, AbstractMatchConfig } from './abstract-match';\n\n/**\n * @class Autolinker.match.Hashtag\n * @extends Autolinker.match.AbstractMatch\n *\n * Represents a Hashtag match found in an input string which should be\n * Autolinked.\n *\n * See this class's superclass ({@link Autolinker.match.Match}) for more\n * details.\n */\nexport class HashtagMatch extends AbstractMatch {\n /**\n * @public\n * @property {'hashtag'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public readonly type: 'hashtag' = 'hashtag';\n\n /**\n * @cfg {String} serviceName\n *\n * The service to point hashtag matches to. See {@link Autolinker#hashtag}\n * for available values.\n */\n private readonly serviceName: HashtagService = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {String} hashtag (required)\n *\n * The HashtagMatch that was matched, without the '#'.\n */\n private readonly hashtag: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: HashtagMatchConfig) {\n super(cfg);\n\n this.serviceName = cfg.serviceName;\n this.hashtag = cfg.hashtag;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n * For the case of HashtagMatch, returns 'hashtag'.\n *\n * @return {String}\n */\n getType(): 'hashtag' {\n return 'hashtag';\n }\n\n /**\n * Returns the configured {@link #serviceName} to point the HashtagMatch to.\n * Ex: 'facebook', 'twitter'.\n *\n * @return {String}\n */\n getServiceName(): HashtagService {\n return this.serviceName;\n }\n\n /**\n * Returns the matched hashtag, without the '#' character.\n *\n * @return {String}\n */\n getHashtag(): string {\n return this.hashtag;\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorHref(): string {\n let serviceName = this.serviceName,\n hashtag = this.hashtag;\n\n switch (serviceName) {\n case 'twitter':\n return 'https://twitter.com/hashtag/' + hashtag;\n case 'facebook':\n return 'https://www.facebook.com/hashtag/' + hashtag;\n case 'instagram':\n return 'https://instagram.com/explore/tags/' + hashtag;\n case 'tiktok':\n return 'https://www.tiktok.com/tag/' + hashtag;\n\n default:\n // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case\n assertNever(serviceName);\n throw new Error(`Invalid hashtag service: ${serviceName}`);\n }\n }\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorText(): string {\n return '#' + this.hashtag;\n }\n\n /**\n * Returns the CSS class suffixes that should be used on a tag built with\n * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for\n * details.\n *\n * @return {String[]}\n */\n getCssClassSuffixes(): string[] {\n let cssClassSuffixes = super.getCssClassSuffixes(),\n serviceName = this.getServiceName();\n\n if (serviceName) {\n cssClassSuffixes.push(serviceName);\n }\n return cssClassSuffixes;\n }\n}\n\nexport interface HashtagMatchConfig extends AbstractMatchConfig {\n serviceName: HashtagService;\n hashtag: string;\n}\n","const mentionRegexes: { [serviceName in MentionService]: RegExp } = {\n twitter: /^@\\w{1,15}$/,\n instagram: /^@[_\\w]{1,30}$/,\n soundcloud: /^@[-a-z0-9_]{3,25}$/,\n\n // TikTok usernames are 1-24 characters containing letters, numbers, underscores\n // and periods, but cannot end in a period: https://support.tiktok.com/en/getting-started/setting-up-your-profile/changing-your-username\n tiktok: /^@[.\\w]{1,23}[\\w]$/,\n};\n\n// Regex that allows for all possible mention characters for any service. We'll\n// confirm the match based on the user-configured service name after a match is\n// found.\nconst mentionTextCharRe = /[-\\w.]/;\n\n/**\n * Determines if the given character can be part of a mention's text characters.\n */\nexport function isMentionTextChar(char: string): boolean {\n return mentionTextCharRe.test(char);\n}\n\n/**\n * Determines if the given `mention` text is valid.\n */\nexport function isValidMention(mention: string, serviceName: MentionService): boolean {\n const re = mentionRegexes[serviceName];\n\n return re.test(mention);\n}\n\nexport type MentionService = 'twitter' | 'instagram' | 'soundcloud' | 'tiktok';\nexport const mentionServices: MentionService[] = ['twitter', 'instagram', 'soundcloud', 'tiktok'];\n","import { MentionService } from '../parser/mention-utils';\nimport { AbstractMatch, AbstractMatchConfig } from './abstract-match';\n\n/**\n * @class Autolinker.match.Mention\n * @extends Autolinker.match.AbstractMatch\n *\n * Represents a Mention match found in an input string which should be Autolinked.\n *\n * See this class's superclass ({@link Autolinker.match.Match}) for more details.\n */\nexport class MentionMatch extends AbstractMatch {\n /**\n * @public\n * @property {'mention'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public readonly type: 'mention' = 'mention';\n\n /**\n * @cfg {String} serviceName\n *\n * The service to point mention matches to. See {@link Autolinker#mention}\n * for available values.\n */\n private readonly serviceName: MentionService = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {String} mention (required)\n *\n * The Mention that was matched, without the '@' character.\n */\n private readonly mention: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: MentionMatchConfig) {\n super(cfg);\n\n this.mention = cfg.mention;\n this.serviceName = cfg.serviceName;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n * For the case of MentionMatch, returns 'mention'.\n *\n * @return {String}\n */\n getType(): 'mention' {\n return 'mention';\n }\n\n /**\n * Returns the mention, without the '@' character.\n *\n * @return {String}\n */\n getMention(): string {\n return this.mention;\n }\n\n /**\n * Returns the configured {@link #serviceName} to point the mention to.\n * Ex: 'instagram', 'twitter', 'soundcloud'.\n *\n * @return {String}\n */\n getServiceName(): MentionService {\n return this.serviceName;\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorHref(): string {\n switch (this.serviceName) {\n case 'twitter':\n return 'https://twitter.com/' + this.mention;\n case 'instagram':\n return 'https://instagram.com/' + this.mention;\n case 'soundcloud':\n return 'https://soundcloud.com/' + this.mention;\n case 'tiktok':\n return 'https://www.tiktok.com/@' + this.mention;\n\n default:\n // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case.\n throw new Error('Unknown service name to point mention to: ' + this.serviceName);\n }\n }\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorText(): string {\n return '@' + this.mention;\n }\n\n /**\n * Returns the CSS class suffixes that should be used on a tag built with\n * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for\n * details.\n *\n * @return {String[]}\n */\n getCssClassSuffixes(): string[] {\n let cssClassSuffixes = super.getCssClassSuffixes(),\n serviceName = this.getServiceName();\n\n if (serviceName) {\n cssClassSuffixes.push(serviceName);\n }\n return cssClassSuffixes;\n }\n}\n\nexport interface MentionMatchConfig extends AbstractMatchConfig {\n serviceName: MentionService;\n mention: string;\n}\n","// Regex that holds the characters used to separate segments of a phone number\nconst separatorCharRe = /[-. ]/;\n\n// Regex that specifies any delimiter char that allows us to treat the number as\n// a phone number rather than just any other number that could appear in text.\nconst hasDelimCharsRe = /[-. ()]/;\n\n// \"Pause\" and \"Wait\" control chars\nconst controlCharRe = /[,;]/;\n\n// Over the years, many people have added to this regex, but it should have been\n// split up by country. Maybe one day we can break this down.\nconst mostPhoneNumbers =\n /(?:(?:(?:(\\+)?\\d{1,3}[-. ]?)?\\(?\\d{3}\\)?[-. ]?\\d{3}[-. ]?\\d{4})|(?:(\\+)(?:9[976]\\d|8[987530]\\d|6[987]\\d|5[90]\\d|42\\d|3[875]\\d|2[98654321]\\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)[-. ]?(?:\\d[-. ]?){6,12}\\d+))([,;]+[0-9]+#?)*/;\n\n// Regex for Japanese phone numbers\nconst japanesePhoneRe =\n /(0([1-9]-?[1-9]\\d{3}|[1-9]{2}-?\\d{3}|[1-9]{2}\\d{1}-?\\d{2}|[1-9]{2}\\d{2}-?\\d{1})-?\\d{4}|0[789]0-?\\d{4}-?\\d{4}|050-?\\d{4}-?\\d{4})/;\n\n// Combined regex\nconst validPhoneNumberRe = new RegExp(`^${mostPhoneNumbers.source}|${japanesePhoneRe.source}$`);\n\n/**\n * Determines if the character is a phone number separator character (i.e.\n * '-', '.', or ' ' (space))\n */\nexport function isPhoneNumberSeparatorChar(char: string): boolean {\n return separatorCharRe.test(char);\n}\n\n/**\n * Determines if the character is a control character in a phone number. Control\n * characters are as follows:\n *\n * - ',': A 1 second pause. Useful for dialing extensions once the main phone number has been reached\n * - ';': A \"wait\" that waits for the user to take action (tap something, for instance on a smart phone)\n */\nexport function isPhoneNumberControlChar(char: string): boolean {\n return controlCharRe.test(char);\n}\n\n/**\n * Determines if the given phone number text found in a string is a valid phone\n * number.\n *\n * Our state machine parser is simplified to grab anything that looks like a\n * phone number, and this function confirms the match.\n */\nexport function isValidPhoneNumber(phoneNumberText: string): boolean {\n // We'll only consider the match as a phone number if there is some kind of\n // delimiter character (a prefixed '+' sign, or separator chars).\n //\n // Accepts:\n // (123) 456-7890\n // +38755233976\n // Does not accept:\n // 1234567890 (no delimiter chars - may just be a random number that's not a phone number)\n const hasDelimiters =\n phoneNumberText.charAt(0) === '+' || hasDelimCharsRe.test(phoneNumberText);\n\n return hasDelimiters && validPhoneNumberRe.test(phoneNumberText);\n}\n","import { AbstractMatch, AbstractMatchConfig } from './abstract-match';\n\n/**\n * @class Autolinker.match.Phone\n * @extends Autolinker.match.AbstractMatch\n *\n * Represents a Phone number match found in an input string which should be\n * Autolinked.\n *\n * See this class's superclass ({@link Autolinker.match.Match}) for more\n * details.\n */\nexport class PhoneMatch extends AbstractMatch {\n /**\n * @public\n * @property {'phone'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public readonly type: 'phone' = 'phone';\n\n /**\n * @protected\n * @property {String} number (required)\n *\n * The phone number that was matched, without any delimiter characters.\n *\n * Note: This is a string to allow for prefixed 0's.\n */\n private readonly number: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @protected\n * @property {Boolean} plusSign (required)\n *\n * `true` if the matched phone number started with a '+' sign. We'll include\n * it in the `tel:` URL if so, as this is needed for international numbers.\n *\n * Ex: '+1 (123) 456 7879'\n */\n private readonly plusSign: boolean = false; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: PhoneMatchConfig) {\n super(cfg);\n\n this.number = cfg.number;\n this.plusSign = cfg.plusSign;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n * For the case of PhoneMatch, returns 'phone'.\n *\n * @return {String}\n */\n getType(): 'phone' {\n return 'phone';\n }\n\n /**\n * Returns the phone number that was matched as a string, without any\n * delimiter characters.\n *\n * Note: This is a string to allow for prefixed 0's.\n *\n * @return {String}\n */\n getPhoneNumber(): string {\n return this.number;\n }\n\n /**\n * Alias of {@link #getPhoneNumber}, returns the phone number that was\n * matched as a string, without any delimiter characters.\n *\n * Note: This is a string to allow for prefixed 0's.\n *\n * @return {String}\n */\n getNumber(): string {\n return this.getPhoneNumber();\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorHref(): string {\n return 'tel:' + (this.plusSign ? '+' : '') + this.number;\n }\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorText(): string {\n return this.matchedText;\n }\n}\n\nexport interface PhoneMatchConfig extends AbstractMatchConfig {\n number: string;\n plusSign: boolean;\n}\n","import { alphaNumericAndMarksRe, digitRe } from '../regex-lib';\nimport { UrlMatch, UrlMatchType } from '../match/url-match';\nimport { Match } from '../match/match';\nimport { remove, assertNever } from '../utils';\nimport {\n httpSchemeRe,\n isDomainLabelChar,\n isDomainLabelStartChar,\n isPathChar,\n isSchemeChar,\n isSchemeStartChar,\n isUrlSuffixStartChar,\n isValidIpV4Address,\n isValidSchemeUrl,\n isValidTldMatch,\n urlSuffixedCharsNotAllowedAtEndRe,\n} from './uri-utils';\nimport {\n isEmailLocalPartChar,\n isEmailLocalPartStartChar,\n isValidEmail,\n mailtoSchemePrefixRe,\n} from './email-utils';\nimport { EmailMatch } from '../match/email-match';\nimport { HashtagService, isHashtagTextChar, isValidHashtag } from './hashtag-utils';\nimport { HashtagMatch } from '../match/hashtag-match';\nimport { isMentionTextChar, isValidMention, MentionService } from './mention-utils';\nimport { MentionMatch } from '../match/mention-match';\nimport {\n isPhoneNumberSeparatorChar,\n isPhoneNumberControlChar,\n isValidPhoneNumber,\n} from './phone-number-utils';\nimport { PhoneMatch } from '../match/phone-match';\nimport { AnchorTagBuilder } from '../anchor-tag-builder';\nimport type { StripPrefixConfigObj } from '../autolinker';\n\n// For debugging: search for and uncomment other \"For debugging\" lines\n// import CliTable from 'cli-table';\n\n/**\n * Parses URL, email, twitter, mention, and hashtag matches from the given\n * `text`.\n */\nexport function parseMatches(text: string, args: ParseMatchesArgs): Match[] {\n const tagBuilder = args.tagBuilder;\n const stripPrefix = args.stripPrefix;\n const stripTrailingSlash = args.stripTrailingSlash;\n const decodePercentEncoding = args.decodePercentEncoding;\n const hashtagServiceName = args.hashtagServiceName;\n const mentionServiceName = args.mentionServiceName;\n\n const matches: Match[] = [];\n const textLen = text.length;\n\n // An array of all active state machines. Empty array means we're in the\n // \"no url\" state\n const stateMachines: StateMachine[] = [];\n\n // For debugging: search for and uncomment other \"For debugging\" lines\n // const table = new CliTable({\n // head: ['charIdx', 'char', 'code', 'type', 'states', 'charIdx', 'startIdx', 'reached accept state'],\n // });\n\n let charIdx = 0;\n for (; charIdx < textLen; charIdx++) {\n const char = text.charAt(charIdx);\n\n if (stateMachines.length === 0) {\n stateNoMatch(char);\n } else {\n // Must loop through the state machines backwards for when one\n // is removed\n for (let stateIdx = stateMachines.length - 1; stateIdx >= 0; stateIdx--) {\n const stateMachine = stateMachines[stateIdx];\n\n switch (stateMachine.state) {\n // Protocol-relative URL states\n case State.ProtocolRelativeSlash1:\n stateProtocolRelativeSlash1(stateMachine, char);\n break;\n case State.ProtocolRelativeSlash2:\n stateProtocolRelativeSlash2(stateMachine, char);\n break;\n\n case State.SchemeChar:\n stateSchemeChar(stateMachine, char);\n break;\n case State.SchemeHyphen:\n stateSchemeHyphen(stateMachine, char);\n break;\n case State.SchemeColon:\n stateSchemeColon(stateMachine, char);\n break;\n case State.SchemeSlash1:\n stateSchemeSlash1(stateMachine, char);\n break;\n case State.SchemeSlash2:\n stateSchemeSlash2(stateMachine, char);\n break;\n\n case State.DomainLabelChar:\n stateDomainLabelChar(stateMachine, char);\n break;\n case State.DomainHyphen:\n stateDomainHyphen(stateMachine, char);\n break;\n case State.DomainDot:\n stateDomainDot(stateMachine, char);\n break;\n\n case State.IpV4Digit:\n stateIpV4Digit(stateMachine as IpV4UrlStateMachine, char);\n break;\n case State.IpV4Dot:\n stateIPv4Dot(stateMachine as IpV4UrlStateMachine, char);\n break;\n\n case State.PortColon:\n statePortColon(stateMachine, char);\n break;\n case State.PortNumber:\n statePortNumber(stateMachine, char);\n break;\n case State.Path:\n statePath(stateMachine, char);\n break;\n\n // Email States\n case State.EmailMailto_M:\n stateEmailMailto_M(stateMachine, char);\n break;\n case State.EmailMailto_A:\n stateEmailMailto_A(stateMachine, char);\n break;\n case State.EmailMailto_I:\n stateEmailMailto_I(stateMachine, char);\n break;\n case State.EmailMailto_L:\n stateEmailMailto_L(stateMachine, char);\n break;\n case State.EmailMailto_T:\n stateEmailMailto_T(stateMachine, char);\n break;\n case State.EmailMailto_O:\n stateEmailMailto_O(stateMachine, char);\n break;\n case State.EmailMailto_Colon:\n stateEmailMailtoColon(stateMachine, char);\n break;\n case State.EmailLocalPart:\n stateEmailLocalPart(stateMachine, char);\n break;\n case State.EmailLocalPartDot:\n stateEmailLocalPartDot(stateMachine, char);\n break;\n case State.EmailAtSign:\n stateEmailAtSign(stateMachine, char);\n break;\n case State.EmailDomainChar:\n stateEmailDomainChar(stateMachine, char);\n break;\n case State.EmailDomainHyphen:\n stateEmailDomainHyphen(stateMachine, char);\n break;\n case State.EmailDomainDot:\n stateEmailDomainDot(stateMachine, char);\n break;\n\n // Hashtag states\n case State.HashtagHashChar:\n stateHashtagHashChar(stateMachine, char);\n break;\n case State.HashtagTextChar:\n stateHashtagTextChar(stateMachine, char);\n break;\n\n // Mention states\n case State.MentionAtChar:\n stateMentionAtChar(stateMachine, char);\n break;\n case State.MentionTextChar:\n stateMentionTextChar(stateMachine, char);\n break;\n\n // Phone number states\n case State.PhoneNumberOpenParen:\n statePhoneNumberOpenParen(stateMachine, char);\n break;\n case State.PhoneNumberAreaCodeDigit1:\n statePhoneNumberAreaCodeDigit1(stateMachine, char);\n break;\n case State.PhoneNumberAreaCodeDigit2:\n statePhoneNumberAreaCodeDigit2(stateMachine, char);\n break;\n case State.PhoneNumberAreaCodeDigit3:\n statePhoneNumberAreaCodeDigit3(stateMachine, char);\n break;\n case State.PhoneNumberCloseParen:\n statePhoneNumberCloseParen(stateMachine, char);\n break;\n case State.PhoneNumberPlus:\n statePhoneNumberPlus(stateMachine, char);\n break;\n case State.PhoneNumberDigit:\n statePhoneNumberDigit(stateMachine, char);\n break;\n case State.PhoneNumberSeparator:\n statePhoneNumberSeparator(stateMachine, char);\n break;\n case State.PhoneNumberControlChar:\n statePhoneNumberControlChar(stateMachine, char);\n break;\n case State.PhoneNumberPoundChar:\n statePhoneNumberPoundChar(stateMachine, char);\n break;\n\n default:\n assertNever(stateMachine.state);\n }\n }\n\n // Special case for handling a colon (or other non-alphanumeric)\n // when preceded by another character, such as in the text:\n // Link 1:http://google.com\n // In this case, the 'h' character after the colon wouldn't start a\n // new scheme url because we'd be in a ipv4 or tld url and the colon\n // would be interpreted as a port ':' char. Also, only start a new\n // scheme url machine if there isn't currently one so we don't start\n // new ones for colons inside a url\n if (charIdx > 0 && isSchemeStartChar(char)) {\n const prevChar = text.charAt(charIdx - 1);\n if (!isSchemeStartChar(prevChar) && !stateMachines.some(isSchemeUrlStateMachine)) {\n stateMachines.push(createSchemeUrlStateMachine(charIdx, State.SchemeChar));\n }\n }\n }\n\n // For debugging: search for and uncomment other \"For debugging\" lines\n // table.push([\n // charIdx,\n // char,\n // `10: ${char.charCodeAt(0)}\\n0x: ${char.charCodeAt(0).toString(16)}\\nU+${char.codePointAt(0)}`,\n // stateMachines.map(machine => `${machine.type}${'matchType' in machine ? ` (${machine.matchType})` : ''}`).join('\\n') || '(none)',\n // stateMachines.map(machine => State[machine.state]).join('\\n') || '(none)',\n // charIdx,\n // stateMachines.map(m => m.startIdx).join('\\n'),\n // stateMachines.map(m => m.acceptStateReached).join('\\n'),\n // ]);\n }\n\n // Capture any valid match at the end of the string\n // Note: this loop must happen in reverse because\n // captureMatchIfValidAndRemove() removes state machines from the array\n // and we'll end up skipping every other one if we remove while looping\n // forward\n for (let i = stateMachines.length - 1; i >= 0; i--) {\n stateMachines.forEach(stateMachine => captureMatchIfValidAndRemove(stateMachine));\n }\n\n // For debugging: search for and uncomment other \"For debugging\" lines\n // console.log(`\\nRead string:\\n ${text}`);\n // console.log(table.toString());\n\n return matches;\n\n // Handles the state when we're not in a URL/email/etc. (i.e. when no state machines exist)\n function stateNoMatch(char: string) {\n if (char === '#') {\n // Hash char, start a Hashtag match\n stateMachines.push(createHashtagStateMachine(charIdx, State.HashtagHashChar));\n } else if (char === '@') {\n // '@' char, start a Mention match\n stateMachines.push(createMentionStateMachine(charIdx, State.MentionAtChar));\n } else if (char === '/') {\n // A slash could begin a protocol-relative URL\n stateMachines.push(createTldUrlStateMachine(charIdx, State.ProtocolRelativeSlash1));\n } else if (char === '+') {\n // A '+' char can start a Phone number\n stateMachines.push(createPhoneNumberStateMachine(charIdx, State.PhoneNumberPlus));\n } else if (char === '(') {\n stateMachines.push(createPhoneNumberStateMachine(charIdx, State.PhoneNumberOpenParen));\n } else {\n if (digitRe.test(char)) {\n // A digit could start a phone number\n stateMachines.push(createPhoneNumberStateMachine(charIdx, State.PhoneNumberDigit));\n\n // A digit could start an IP address\n stateMachines.push(createIpV4UrlStateMachine(charIdx, State.IpV4Digit));\n }\n\n if (isEmailLocalPartStartChar(char)) {\n // Any email local part. An 'm' character in particular could\n // start a 'mailto:' match\n const startState =\n char.toLowerCase() === 'm' ? State.EmailMailto_M : State.EmailLocalPart;\n stateMachines.push(createEmailStateMachine(charIdx, startState));\n }\n\n if (isSchemeStartChar(char)) {\n // An uppercase or lowercase letter may start a scheme match\n stateMachines.push(createSchemeUrlStateMachine(charIdx, State.SchemeChar));\n }\n\n if (alphaNumericAndMarksRe.test(char)) {\n // A unicode alpha character or digit could start a domain name\n // label for a TLD match\n stateMachines.push(createTldUrlStateMachine(charIdx, State.DomainLabelChar));\n }\n }\n\n // Anything else, remain in the \"non-url\" state by not creating any\n // state machines\n }\n\n // Implements ABNF: ALPHA *( ALPHA / DIGIT / \"+\" / \"-\" / \".\" )\n function stateSchemeChar(stateMachine: StateMachine, char: string) {\n if (char === ':') {\n stateMachine.state = State.SchemeColon;\n } else if (char === '-') {\n stateMachine.state = State.SchemeHyphen;\n } else if (isSchemeChar(char)) {\n // Stay in SchemeChar state\n } else {\n // Any other character, not a scheme\n remove(stateMachines, stateMachine);\n }\n }\n\n function stateSchemeHyphen(stateMachine: StateMachine, char: string) {\n if (char === '-') {\n // Stay in SchemeHyphen state\n // TODO: Should a colon following a dash be counted as the end of the scheme?\n // } else if (char === ':') {\n // stateMachine.state = State.SchemeColon;\n } else if (char === '/') {\n // Not a valid scheme match, but may be the start of a\n // protocol-relative match (such as //google.com)\n remove(stateMachines, stateMachine);\n stateMachines.push(createTldUrlStateMachine(charIdx, State.ProtocolRelativeSlash1));\n } else if (isSchemeChar(char)) {\n stateMachine.state = State.SchemeChar;\n } else {\n // Any other character, not a scheme\n remove(stateMachines, stateMachine);\n }\n }\n\n function stateSchemeColon(stateMachine: StateMachine, char: string) {\n if (char === '/') {\n stateMachine.state = State.SchemeSlash1;\n } else if (char === '.') {\n // We've read something like 'hello:.' - don't capture\n remove(stateMachines, stateMachine);\n } else if (isDomainLabelStartChar(char)) {\n stateMachine.state = State.DomainLabelChar;\n\n // It's possible that we read an \"introduction\" piece of text,\n // and the character after the current colon actually starts an\n // actual scheme. An example of this is:\n // \"The link:http://google.com\"\n // Hence, start a new machine to capture this match if so\n if (isSchemeStartChar(char)) {\n stateMachines.push(createSchemeUrlStateMachine(charIdx, State.SchemeChar));\n }\n } else {\n remove(stateMachines, stateMachine);\n }\n }\n\n function stateSchemeSlash1(stateMachine: StateMachine, char: string) {\n if (char === '/') {\n stateMachine.state = State.SchemeSlash2;\n } else if (isPathChar(char)) {\n stateMachine.state = State.Path;\n stateMachine.acceptStateReached = true;\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function stateSchemeSlash2(stateMachine: StateMachine, char: string) {\n if (char === '/') {\n // 3rd slash, must be an absolute path (path-absolute in the\n // ABNF), such as in a file:///c:/windows/etc. See\n // https://tools.ietf.org/html/rfc3986#appendix-A\n stateMachine.state = State.Path;\n } else if (isDomainLabelStartChar(char)) {\n // start of \"authority\" section - see https://tools.ietf.org/html/rfc3986#appendix-A\n stateMachine.state = State.DomainLabelChar;\n stateMachine.acceptStateReached = true;\n } else {\n // not valid\n remove(stateMachines, stateMachine);\n }\n }\n\n // Handles reading a '/' from the NonUrl state\n function stateProtocolRelativeSlash1(stateMachine: StateMachine, char: string) {\n if (char === '/') {\n stateMachine.state = State.ProtocolRelativeSlash2;\n } else {\n // Anything else, cannot be the start of a protocol-relative\n // URL.\n remove(stateMachines, stateMachine);\n }\n }\n\n // Handles reading a second '/', which could start a protocol-relative URL\n function stateProtocolRelativeSlash2(stateMachine: StateMachine, char: string) {\n if (isDomainLabelStartChar(char)) {\n stateMachine.state = State.DomainLabelChar;\n } else {\n // Anything else, not a URL\n remove(stateMachines, stateMachine);\n }\n }\n\n // Handles when we have read a domain label character\n function stateDomainLabelChar(stateMachine: StateMachine, char: string) {\n if (char === '.') {\n stateMachine.state = State.DomainDot;\n } else if (char === '-') {\n stateMachine.state = State.DomainHyphen;\n } else if (char === ':') {\n // Beginning of a port number, end the domain name\n stateMachine.state = State.PortColon;\n } else if (isUrlSuffixStartChar(char)) {\n // '/', '?', or '#'\n stateMachine.state = State.Path;\n } else if (isDomainLabelChar(char)) {\n // Stay in the DomainLabelChar state\n } else {\n // Anything else, end the domain name\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function stateDomainHyphen(stateMachine: StateMachine, char: string) {\n if (char === '-') {\n // Remain in the DomainHyphen state\n } else if (char === '.') {\n // Not valid to have a '-.' in a domain label\n captureMatchIfValidAndRemove(stateMachine);\n } else if (isDomainLabelStartChar(char)) {\n stateMachine.state = State.DomainLabelChar;\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function stateDomainDot(stateMachine: StateMachine, char: string) {\n if (char === '.') {\n // domain names cannot have multiple '.'s next to each other.\n // It's possible we've already read a valid domain name though,\n // and that the '..' sequence just forms an ellipsis at the end\n // of a sentence\n captureMatchIfValidAndRemove(stateMachine);\n } else if (isDomainLabelStartChar(char)) {\n stateMachine.state = State.DomainLabelChar;\n stateMachine.acceptStateReached = true; // after hitting a dot, and then another domain label, we've reached an accept state\n } else {\n // Anything else, end the domain name\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function stateIpV4Digit(stateMachine: IpV4UrlStateMachine, char: string) {\n if (char === '.') {\n stateMachine.state = State.IpV4Dot;\n } else if (char === ':') {\n // Beginning of a port number\n stateMachine.state = State.PortColon;\n } else if (digitRe.test(char)) {\n // stay in the IPv4 digit state\n } else if (isUrlSuffixStartChar(char)) {\n stateMachine.state = State.Path;\n } else if (alphaNumericAndMarksRe.test(char)) {\n // If we hit an alpha character, must not be an IPv4\n // Example of this: 1.2.3.4abc\n remove(stateMachines, stateMachine);\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function stateIPv4Dot(stateMachine: IpV4UrlStateMachine, char: string) {\n if (digitRe.test(char)) {\n stateMachine.octetsEncountered++;\n\n // Once we have encountered 4 octets, it's *potentially* a valid\n // IPv4 address. Our IPv4 regex will confirm the match later\n // though to make sure each octet is in the 0-255 range, and\n // there's exactly 4 octets (not 5 or more)\n if (stateMachine.octetsEncountered === 4) {\n stateMachine.acceptStateReached = true;\n }\n\n stateMachine.state = State.IpV4Digit;\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function statePortColon(stateMachine: StateMachine, char: string) {\n if (digitRe.test(char)) {\n stateMachine.state = State.PortNumber;\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function statePortNumber(stateMachine: StateMachine, char: string) {\n if (digitRe.test(char)) {\n // Stay in port number state\n } else if (isUrlSuffixStartChar(char)) {\n // '/', '?', or '#'\n stateMachine.state = State.Path;\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function statePath(stateMachine: StateMachine, char: string) {\n if (isPathChar(char)) {\n // Stay in the path state\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n // Handles if we're reading a 'mailto:' prefix on the string\n function stateEmailMailto_M(stateMachine: StateMachine, char: string) {\n if (char.toLowerCase() === 'a') {\n stateMachine.state = State.EmailMailto_A;\n } else {\n stateEmailLocalPart(stateMachine, char);\n }\n }\n\n function stateEmailMailto_A(stateMachine: StateMachine, char: string) {\n if (char.toLowerCase() === 'i') {\n stateMachine.state = State.EmailMailto_I;\n } else {\n stateEmailLocalPart(stateMachine, char);\n }\n }\n\n function stateEmailMailto_I(stateMachine: StateMachine, char: string) {\n if (char.toLowerCase() === 'l') {\n stateMachine.state = State.EmailMailto_L;\n } else {\n stateEmailLocalPart(stateMachine, char);\n }\n }\n\n function stateEmailMailto_L(stateMachine: StateMachine, char: string) {\n if (char.toLowerCase() === 't') {\n stateMachine.state = State.EmailMailto_T;\n } else {\n stateEmailLocalPart(stateMachine, char);\n }\n }\n\n function stateEmailMailto_T(stateMachine: StateMachine, char: string) {\n if (char.toLowerCase() === 'o') {\n stateMachine.state = State.EmailMailto_O;\n } else {\n stateEmailLocalPart(stateMachine, char);\n }\n }\n\n function stateEmailMailto_O(stateMachine: StateMachine, char: string) {\n if (char.toLowerCase() === ':') {\n stateMachine.state = State.EmailMailto_Colon;\n } else {\n stateEmailLocalPart(stateMachine, char);\n }\n }\n\n function stateEmailMailtoColon(stateMachine: StateMachine, char: string) {\n if (isEmailLocalPartChar(char)) {\n stateMachine.state = State.EmailLocalPart;\n } else {\n remove(stateMachines, stateMachine);\n }\n }\n\n // Handles the state when we're currently in the \"local part\" of an\n // email address (as opposed to the \"domain part\")\n function stateEmailLocalPart(stateMachine: StateMachine, char: string) {\n if (char === '.') {\n stateMachine.state = State.EmailLocalPartDot;\n } else if (char === '@') {\n stateMachine.state = State.EmailAtSign;\n } else if (isEmailLocalPartChar(char)) {\n // stay in the \"local part\" of the email address\n // Note: because stateEmailLocalPart() is called from the\n // 'mailto' states (when the 'mailto' prefix itself has been\n // broken), make sure to set the state to EmailLocalPart\n stateMachine.state = State.EmailLocalPart;\n } else {\n // not an email address character\n remove(stateMachines, stateMachine);\n }\n }\n\n // Handles the state where we've read\n function stateEmailLocalPartDot(stateMachine: StateMachine, char: string) {\n if (char === '.') {\n // We read a second '.' in a row, not a valid email address\n // local part\n remove(stateMachines, stateMachine);\n } else if (char === '@') {\n // We read the '@' character immediately after a dot ('.'), not\n // an email address\n remove(stateMachines, stateMachine);\n } else if (isEmailLocalPartChar(char)) {\n stateMachine.state = State.EmailLocalPart;\n } else {\n // Anything else, not an email address\n remove(stateMachines, stateMachine);\n }\n }\n\n function stateEmailAtSign(stateMachine: StateMachine, char: string) {\n if (isDomainLabelStartChar(char)) {\n stateMachine.state = State.EmailDomainChar;\n } else {\n // Anything else, not an email address\n remove(stateMachines, stateMachine);\n }\n }\n\n function stateEmailDomainChar(stateMachine: StateMachine, char: string) {\n if (char === '.') {\n stateMachine.state = State.EmailDomainDot;\n } else if (char === '-') {\n stateMachine.state = State.EmailDomainHyphen;\n } else if (isDomainLabelChar(char)) {\n // Stay in the DomainChar state\n } else {\n // Anything else, we potentially matched if the criteria has\n // been met\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function stateEmailDomainHyphen(stateMachine: StateMachine, char: string) {\n if (char === '-' || char === '.') {\n // Not valid to have two hyphens (\"--\") or hypen+dot (\"-.\")\n captureMatchIfValidAndRemove(stateMachine);\n } else if (isDomainLabelChar(char)) {\n stateMachine.state = State.EmailDomainChar;\n } else {\n // Anything else\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function stateEmailDomainDot(stateMachine: StateMachine, char: string) {\n if (char === '.' || char === '-') {\n // not valid to have two dots (\"..\") or dot+hypen (\".-\")\n captureMatchIfValidAndRemove(stateMachine);\n } else if (isDomainLabelStartChar(char)) {\n stateMachine.state = State.EmailDomainChar;\n\n // After having read a '.' and then a valid domain character,\n // we now know that the domain part of the email is valid, and\n // we have found at least a partial EmailMatch (however, the\n // email address may have additional characters from this point)\n stateMachine.acceptStateReached = true;\n } else {\n // Anything else\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n // Handles the state when we've just encountered a '#' character\n function stateHashtagHashChar(stateMachine: StateMachine, char: string) {\n if (isHashtagTextChar(char)) {\n // '#' char with valid hash text char following\n stateMachine.state = State.HashtagTextChar;\n stateMachine.acceptStateReached = true;\n } else {\n remove(stateMachines, stateMachine);\n }\n }\n\n // Handles the state when we're currently in the hash tag's text chars\n function stateHashtagTextChar(stateMachine: StateMachine, char: string) {\n if (isHashtagTextChar(char)) {\n // Continue reading characters in the HashtagText state\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n // Handles the state when we've just encountered a '@' character\n function stateMentionAtChar(stateMachine: StateMachine, char: string) {\n if (isMentionTextChar(char)) {\n // '@' char with valid mention text char following\n stateMachine.state = State.MentionTextChar;\n stateMachine.acceptStateReached = true;\n } else {\n remove(stateMachines, stateMachine);\n }\n }\n\n // Handles the state when we're currently in the mention's text chars\n function stateMentionTextChar(stateMachine: StateMachine, char: string) {\n if (isMentionTextChar(char)) {\n // Continue reading characters in the HashtagText state\n } else if (alphaNumericAndMarksRe.test(char)) {\n // Char is invalid for a mention text char, not a valid match.\n // Note that ascii alphanumeric chars are okay (which are tested\n // in the previous 'if' statement, but others are not)\n remove(stateMachines, stateMachine);\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n function statePhoneNumberPlus(stateMachine: StateMachine, char: string) {\n if (digitRe.test(char)) {\n stateMachine.state = State.PhoneNumberDigit;\n } else {\n remove(stateMachines, stateMachine);\n\n // This character may start a new match. Add states for it\n stateNoMatch(char);\n }\n }\n\n function statePhoneNumberOpenParen(stateMachine: StateMachine, char: string) {\n if (digitRe.test(char)) {\n stateMachine.state = State.PhoneNumberAreaCodeDigit1;\n } else {\n remove(stateMachines, stateMachine);\n }\n\n // It's also possible that the paren was just an open brace for\n // a piece of text. Start other machines\n stateNoMatch(char);\n }\n\n function statePhoneNumberAreaCodeDigit1(stateMachine: StateMachine, char: string) {\n if (digitRe.test(char)) {\n stateMachine.state = State.PhoneNumberAreaCodeDigit2;\n } else {\n remove(stateMachines, stateMachine);\n }\n }\n\n function statePhoneNumberAreaCodeDigit2(stateMachine: StateMachine, char: string) {\n if (digitRe.test(char)) {\n stateMachine.state = State.PhoneNumberAreaCodeDigit3;\n } else {\n remove(stateMachines, stateMachine);\n }\n }\n\n function statePhoneNumberAreaCodeDigit3(stateMachine: StateMachine, char: string) {\n if (char === ')') {\n stateMachine.state = State.PhoneNumberCloseParen;\n } else {\n remove(stateMachines, stateMachine);\n }\n }\n\n function statePhoneNumberCloseParen(stateMachine: StateMachine, char: string) {\n if (digitRe.test(char)) {\n stateMachine.state = State.PhoneNumberDigit;\n } else if (isPhoneNumberSeparatorChar(char)) {\n stateMachine.state = State.PhoneNumberSeparator;\n } else {\n remove(stateMachines, stateMachine);\n }\n }\n\n function statePhoneNumberDigit(stateMachine: StateMachine, char: string) {\n // For now, if we've reached any digits, we'll say that the machine\n // has reached its accept state. The phone regex will confirm the\n // match later.\n // Alternatively, we could count the number of digits to avoid\n // invoking the phone number regex\n stateMachine.acceptStateReached = true;\n\n if (isPhoneNumberControlChar(char)) {\n stateMachine.state = State.PhoneNumberControlChar;\n } else if (char === '#') {\n stateMachine.state = State.PhoneNumberPoundChar;\n } else if (digitRe.test(char)) {\n // Stay in the phone number digit state\n } else if (char === '(') {\n stateMachine.state = State.PhoneNumberOpenParen;\n } else if (isPhoneNumberSeparatorChar(char)) {\n stateMachine.state = State.PhoneNumberSeparator;\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n\n // The transition from a digit character to a letter can be the\n // start of a new scheme URL match\n if (isSchemeStartChar(char)) {\n stateMachines.push(createSchemeUrlStateMachine(charIdx, State.SchemeChar));\n }\n }\n }\n\n function statePhoneNumberSeparator(stateMachine: StateMachine, char: string) {\n if (digitRe.test(char)) {\n stateMachine.state = State.PhoneNumberDigit;\n } else if (char === '(') {\n stateMachine.state = State.PhoneNumberOpenParen;\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n\n // This character may start a new match. Add states for it\n stateNoMatch(char);\n }\n }\n\n // The \";\" characters is \"wait\" in a phone number\n // The \",\" characters is \"pause\" in a phone number\n function statePhoneNumberControlChar(stateMachine: StateMachine, char: string) {\n if (isPhoneNumberControlChar(char)) {\n // Stay in the \"control char\" state\n } else if (char === '#') {\n stateMachine.state = State.PhoneNumberPoundChar;\n } else if (digitRe.test(char)) {\n stateMachine.state = State.PhoneNumberDigit;\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n // The \"#\" characters is \"pound\" in a phone number\n function statePhoneNumberPoundChar(stateMachine: StateMachine, char: string) {\n if (isPhoneNumberControlChar(char)) {\n stateMachine.state = State.PhoneNumberControlChar;\n } else if (digitRe.test(char)) {\n // According to some of the older tests, if there's a digit\n // after a '#' sign, the match is invalid. TODO: Revisit if this is true\n remove(stateMachines, stateMachine);\n } else {\n captureMatchIfValidAndRemove(stateMachine);\n }\n }\n\n /*\n * Captures a match if it is valid (i.e. has a full domain name for a\n * TLD match). If a match is not valid, it is possible that we want to\n * keep reading characters in order to make a full match.\n */\n function captureMatchIfValidAndRemove(stateMachine: StateMachine) {\n // Remove the state machine first. There are a number of code paths\n // which return out of this function early, so make sure we have\n // this done\n remove(stateMachines, stateMachine);\n\n // Make sure the state machine being checked has actually reached an\n // \"accept\" state. If it hasn't reach one, it can't be a match\n if (!stateMachine.acceptStateReached) {\n return;\n }\n\n let startIdx = stateMachine.startIdx;\n let matchedText = text.slice(stateMachine.startIdx, charIdx);\n\n // Handle any unbalanced braces (parens, square brackets, or curly\n // brackets) inside the URL. This handles situations like:\n // The link (google.com)\n // and\n // Check out this link here (en.wikipedia.org/wiki/IANA_(disambiguation))\n //\n // And also remove any punctuation chars at the end such as:\n // '?', ',', ':', '.', etc.\n matchedText = excludeUnbalancedTrailingBracesAndPunctuation(matchedText);\n\n if (stateMachine.type === 'url') {\n // We don't want to accidentally match a URL that is preceded by an\n // '@' character, which would be an email address\n const charBeforeUrlMatch = text.charAt(stateMachine.startIdx - 1);\n if (charBeforeUrlMatch === '@') {\n return;\n }\n\n // For the purpose of this parser, we've generalized 'www'\n // matches as part of 'tld' matches. However, for backward\n // compatibility, we distinguish beween TLD matches and matches\n // that begin with 'www.' so that users may turn off 'www'\n // matches. As such, we need to correct for that now if the\n // URL begins with 'www.'\n const urlMatchType: UrlMatchType = stateMachine.matchType;\n\n if (urlMatchType === 'scheme') {\n // Autolinker accepts many characters in a url's scheme (like `fake://test.com`).\n // However, in cases where a URL is missing whitespace before an obvious link,\n // (for example: `nowhitespacehttp://www.test.com`), we only want the match to start\n // at the http:// part. We will check if the match contains a common scheme and then\n // shift the match to start from there.\n const httpSchemeMatch = httpSchemeRe.exec(matchedText);\n if (httpSchemeMatch) {\n // If we found an overmatched URL, we want to find the index\n // of where the match should start and shift the match to\n // start from the beginning of the common scheme\n startIdx = startIdx + httpSchemeMatch.index;\n matchedText = matchedText.slice(httpSchemeMatch.index);\n }\n\n if (!isValidSchemeUrl(matchedText)) {\n return; // not a valid match\n }\n } else if (urlMatchType === 'tld') {\n if (!isValidTldMatch(matchedText)) {\n return; // not a valid match\n }\n } else if (urlMatchType === 'ipV4') {\n if (!isValidIpV4Address(matchedText)) {\n return; // not a valid match\n }\n } else {\n assertNever(urlMatchType);\n }\n\n matches.push(\n new UrlMatch({\n tagBuilder: tagBuilder,\n matchedText: matchedText,\n offset: startIdx,\n urlMatchType: urlMatchType,\n url: matchedText,\n protocolRelativeMatch: matchedText.slice(0, 2) === '//',\n\n // TODO: Do these settings need to be passed to the match,\n // or should we handle them here in UrlMatcher?\n stripPrefix: stripPrefix,\n stripTrailingSlash: stripTrailingSlash,\n decodePercentEncoding: decodePercentEncoding,\n })\n );\n } else if (stateMachine.type === 'email') {\n // if the email address has a valid TLD, add it to the list of matches\n if (isValidEmail(matchedText)) {\n matches.push(\n new EmailMatch({\n tagBuilder: tagBuilder,\n matchedText: matchedText,\n offset: startIdx,\n email: matchedText.replace(mailtoSchemePrefixRe, ''),\n })\n );\n }\n } else if (stateMachine.type === 'hashtag') {\n if (isValidHashtag(matchedText)) {\n matches.push(\n new HashtagMatch({\n tagBuilder,\n matchedText: matchedText,\n offset: startIdx,\n serviceName: hashtagServiceName,\n hashtag: matchedText.slice(1),\n })\n );\n }\n } else if (stateMachine.type === 'mention') {\n if (isValidMention(matchedText, mentionServiceName)) {\n matches.push(\n new MentionMatch({\n tagBuilder: tagBuilder,\n matchedText: matchedText,\n offset: startIdx,\n serviceName: mentionServiceName,\n mention: matchedText.slice(1), // strip off the '@' character at the beginning\n })\n );\n }\n } else if (stateMachine.type === 'phone') {\n // remove any trailing spaces that were considered as \"separator\"\n // chars by the state machine\n matchedText = matchedText.replace(/ +$/g, '');\n\n if (isValidPhoneNumber(matchedText)) {\n const cleanNumber = matchedText.replace(/[^0-9,;#]/g, ''); // strip out non-digit characters exclude comma semicolon and #\n\n matches.push(\n new PhoneMatch({\n tagBuilder: tagBuilder,\n matchedText: matchedText,\n offset: startIdx,\n number: cleanNumber,\n plusSign: matchedText.charAt(0) === '+',\n })\n );\n }\n } else {\n assertNever(stateMachine);\n }\n }\n}\n\nexport interface ParseMatchesArgs {\n tagBuilder: AnchorTagBuilder;\n stripPrefix: Required;\n stripTrailingSlash: boolean;\n decodePercentEncoding: boolean;\n hashtagServiceName: HashtagService;\n mentionServiceName: MentionService;\n}\n\nconst openBraceRe = /[\\(\\{\\[]/;\nconst closeBraceRe = /[\\)\\}\\]]/;\nconst oppositeBrace: { [char: string]: string } = {\n ')': '(',\n '}': '{',\n ']': '[',\n};\n\n/**\n * Determines if a match found has unmatched closing parenthesis,\n * square brackets or curly brackets. If so, these unbalanced symbol(s) will be\n * removed from the URL match itself.\n *\n * A match may have an extra closing parenthesis/square brackets/curly brackets\n * at the end of the match because these are valid URL path characters. For\n * example, \"wikipedia.com/something_(disambiguation)\" should be auto-linked.\n *\n * However, an extra parenthesis *will* be included when the URL itself is\n * wrapped in parenthesis, such as in the case of:\n *\n * \"(wikipedia.com/something_(disambiguation))\"\n *\n * In this case, the last closing parenthesis should *not* be part of the\n * URL itself, and this method will exclude it from the returned URL.\n *\n * For square brackets in URLs such as in PHP arrays, the same behavior as\n * parenthesis discussed above should happen:\n *\n * \"[http://www.example.com/foo.php?bar[]=1&bar[]=2&bar[]=3]\"\n *\n * The very last closing square bracket should not be part of the URL itself,\n * and therefore this method will remove it.\n *\n * @param matchedText The full matched URL/email/hashtag/etc. from the state\n * machine parser.\n * @return The updated matched text with extraneous suffix characters removed.\n */\nexport function excludeUnbalancedTrailingBracesAndPunctuation(matchedText: string): string {\n const braceCounts: { [char: string]: number } = {\n '(': 0,\n '{': 0,\n '[': 0,\n };\n\n for (let i = 0; i < matchedText.length; i++) {\n const char = matchedText.charAt(i);\n\n if (openBraceRe.test(char)) {\n braceCounts[char]++;\n } else if (closeBraceRe.test(char)) {\n braceCounts[oppositeBrace[char]]--;\n }\n }\n\n let endIdx = matchedText.length - 1;\n let char: string;\n while (endIdx >= 0) {\n char = matchedText.charAt(endIdx);\n\n if (closeBraceRe.test(char)) {\n const oppositeBraceChar = oppositeBrace[char];\n\n if (braceCounts[oppositeBraceChar] < 0) {\n braceCounts[oppositeBraceChar]++;\n endIdx--;\n } else {\n break;\n }\n } else if (urlSuffixedCharsNotAllowedAtEndRe.test(char)) {\n // Walk back a punctuation char like '?', ',', ':', '.', etc.\n endIdx--;\n } else {\n break;\n }\n }\n\n return matchedText.slice(0, endIdx + 1);\n}\n\n// States for the parser\n// For debugging: temporarily remove 'const'\nconst enum State {\n // Scheme states\n SchemeChar = 0, // First char must be an ASCII letter. Subsequent characters can be: ALPHA / DIGIT / \"+\" / \"-\" / \".\"\n SchemeHyphen, // Extra state used to figure out when we can start a new match after (such as if we have '-//' which starts a protocol-relative match)\n SchemeColon, // Once we've reached the colon character after a scheme name\n SchemeSlash1,\n SchemeSlash2,\n\n DomainLabelChar, // Note: Domain labels must begin with a letter or number (no hyphens), and can include unicode letters\n DomainHyphen,\n DomainDot,\n PortColon,\n PortNumber,\n Path,\n\n // Protocol-relative URL states\n ProtocolRelativeSlash1,\n ProtocolRelativeSlash2,\n\n // IPv4 States\n IpV4Digit,\n IpV4Dot,\n\n // Email Address States\n EmailMailto_M, // if matching a 'mailto:' prefix\n EmailMailto_A, // if matching a 'mailto:' prefix\n EmailMailto_I, // if matching a 'mailto:' prefix\n EmailMailto_L, // if matching a 'mailto:' prefix\n EmailMailto_T, // if matching a 'mailto:' prefix\n EmailMailto_O, // if matching a 'mailto:' prefix\n EmailMailto_Colon,\n EmailLocalPart,\n EmailLocalPartDot,\n EmailAtSign,\n EmailDomainChar,\n EmailDomainHyphen,\n EmailDomainDot,\n\n // Hashtag States\n HashtagHashChar, // When we've encountered the '#' char\n HashtagTextChar, // Inside a hashtag char\n\n // Mention State\n MentionAtChar,\n MentionTextChar,\n\n // Phone Number States\n PhoneNumberOpenParen,\n PhoneNumberAreaCodeDigit1, // a digit inside area code parens, such as the '1' in '(123)456-7890'\n PhoneNumberAreaCodeDigit2, // a digit inside area code parens, such as the '2' in '(123)456-7890'\n PhoneNumberAreaCodeDigit3, // a digit inside area code parens, such as the '3' in '(123)456-7890'\n PhoneNumberCloseParen,\n PhoneNumberPlus,\n PhoneNumberDigit, // a digit outside of area code parens\n PhoneNumberSeparator, // '-', '.' or ' '\n PhoneNumberControlChar, // ',' for 1 second pause, ';' for \"wait\" for user to take action\n PhoneNumberPoundChar, // '#' for pound character\n}\n\ntype StateMachine =\n | UrlStateMachine\n | EmailStateMachine\n | MentionStateMachine\n | HashtagStateMachine\n | PhoneNumberStateMachine;\n\ninterface AbstractStateMachine {\n startIdx: number; // the index of the first character in the match\n state: State;\n acceptStateReached: boolean;\n}\n\ninterface AbstractUrlStateMachine extends AbstractStateMachine {\n readonly type: 'url';\n}\n\ntype UrlStateMachine = SchemeUrlStateMachine | TldUrlStateMachine | IpV4UrlStateMachine;\n\n/**\n * State machine with metadata for capturing TLD (top-level domain) URLs.\n */\ninterface SchemeUrlStateMachine extends AbstractUrlStateMachine {\n readonly matchType: 'scheme';\n}\n\n/**\n * State machine with metadata for capturing TLD (top-level domain) URLs.\n */\ninterface TldUrlStateMachine extends AbstractUrlStateMachine {\n readonly matchType: 'tld';\n}\n\n/**\n * State machine for capturing IPv4 addresses that are not prefixed with a\n * scheme (such as 'http://').\n */\ninterface IpV4UrlStateMachine extends AbstractUrlStateMachine {\n readonly matchType: 'ipV4';\n octetsEncountered: number; // if we encounter a number of octets other than 4, it's not an IPv4 address\n}\n\n/**\n * State machine for capturing email addresses.\n */\ninterface EmailStateMachine extends AbstractStateMachine {\n readonly type: 'email';\n}\n\n/**\n * State machine for capturing hashtags.\n */\ninterface HashtagStateMachine extends AbstractStateMachine {\n readonly type: 'hashtag';\n}\n\n/**\n * State machine for capturing hashtags.\n */\ninterface MentionStateMachine extends AbstractStateMachine {\n readonly type: 'mention';\n}\n\n/**\n * State machine for capturing phone numbers.\n *\n * Note: this doesn't actually capture phone numbers at the moment, but is used\n * to exclude phone number matches from URLs where the URL matcher would\n * otherwise potentially think a phone number is part of a domain label.\n */\ninterface PhoneNumberStateMachine extends AbstractStateMachine {\n readonly type: 'phone';\n}\n\nfunction createSchemeUrlStateMachine(startIdx: number, state: State): SchemeUrlStateMachine {\n return {\n type: 'url',\n startIdx,\n state,\n acceptStateReached: false,\n matchType: 'scheme',\n };\n}\n\nfunction createTldUrlStateMachine(startIdx: number, state: State): TldUrlStateMachine {\n return {\n type: 'url',\n startIdx,\n state,\n acceptStateReached: false,\n matchType: 'tld',\n };\n}\n\nfunction createIpV4UrlStateMachine(startIdx: number, state: State): IpV4UrlStateMachine {\n return {\n type: 'url',\n startIdx,\n state,\n acceptStateReached: false,\n matchType: 'ipV4',\n octetsEncountered: 1, // starts at 1 because we create this machine when encountering the first octet\n };\n}\n\nfunction createEmailStateMachine(startIdx: number, state: State): EmailStateMachine {\n return {\n type: 'email',\n startIdx,\n state,\n acceptStateReached: false,\n };\n}\n\nfunction createHashtagStateMachine(startIdx: number, state: State): HashtagStateMachine {\n return {\n type: 'hashtag',\n startIdx,\n state,\n acceptStateReached: false,\n };\n}\n\nfunction createMentionStateMachine(startIdx: number, state: State): MentionStateMachine {\n return {\n type: 'mention',\n startIdx,\n state,\n acceptStateReached: false,\n };\n}\n\nfunction createPhoneNumberStateMachine(startIdx: number, state: State): PhoneNumberStateMachine {\n return {\n type: 'phone',\n startIdx,\n state,\n acceptStateReached: false,\n };\n}\n\nfunction isSchemeUrlStateMachine(machine: StateMachine): machine is SchemeUrlStateMachine {\n return machine.type === 'url' && machine.matchType === 'scheme';\n}\n","import { State } from './state';\nimport { letterRe, digitRe, whitespaceRe, quoteRe, controlCharsRe } from '../regex-lib';\nimport { assertNever } from '../utils';\n\n// For debugging: search for other \"For debugging\" lines\n// import CliTable from 'cli-table';\n\n/**\n * Parses an HTML string, calling the callbacks to notify of tags and text.\n *\n * ## History\n *\n * This file previously used a regular expression to find html tags in the input\n * text. Unfortunately, we ran into a bunch of catastrophic backtracking issues\n * with certain input text, causing Autolinker to either hang or just take a\n * really long time to parse the string.\n *\n * The current code is intended to be a O(n) algorithm that walks through\n * the string in one pass, and tries to be as cheap as possible. We don't need\n * to implement the full HTML spec, but rather simply determine where the string\n * looks like an HTML tag, and where it looks like text (so that we can autolink\n * that).\n *\n * This state machine parser is intended just to be a simple but performant\n * parser of HTML for the subset of requirements we have. We simply need to:\n *\n * 1. Determine where HTML tags are\n * 2. Determine the tag name (Autolinker specifically only cares about ,\n *