UNPKG

221 kBJavaScriptView Raw
1/*!
2 * Autolinker.js
3 * v4.0.0
4 *
5 * Copyright(c) 2022 Gregory Jacobs <greg@greg-jacobs.com>
6 * MIT License
7 *
8 * https://github.com/gregjacobs/Autolinker.js
9 */
10(function (global, factory) {
11 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
12 typeof define === 'function' && define.amd ? define(factory) :
13 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Autolinker = factory());
14})(this, (function () { 'use strict';
15
16 // Important: this file is generated from the 'build' script and should not be
17 // edited directly
18 var version = '4.0.0';
19
20 /**
21 * Simpler helper method to check for undefined simply for the benefit of
22 * gaining better compression when minified by not needing to have multiple
23 * comparisons to the `undefined` keyword in the codebase.
24 */
25 function isUndefined(value) {
26 return value === undefined;
27 }
28 /**
29 * Simpler helper method to check for a boolean type simply for the benefit of
30 * gaining better compression when minified by not needing to have multiple
31 * `typeof` comparisons in the codebase.
32 */
33 function isBoolean(value) {
34 return typeof value === 'boolean';
35 }
36 /**
37 * Assigns (shallow copies) the properties of `src` onto `dest`, if the
38 * corresponding property on `dest` === `undefined`.
39 *
40 * @param {Object} dest The destination object.
41 * @param {Object} src The source object.
42 * @return {Object} The destination object (`dest`)
43 */
44 function defaults(dest, src) {
45 for (var prop in src) {
46 if (src.hasOwnProperty(prop) && isUndefined(dest[prop])) {
47 dest[prop] = src[prop];
48 }
49 }
50 return dest;
51 }
52 /**
53 * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the
54 * end of the string (by default, two periods: '..'). If the `str` length does not exceed
55 * `len`, the string will be returned unchanged.
56 *
57 * @param {String} str The string to truncate and add an ellipsis to.
58 * @param {Number} truncateLen The length to truncate the string at.
59 * @param {String} [ellipsisChars=...] The ellipsis character(s) to add to the end of `str`
60 * when truncated. Defaults to '...'
61 */
62 function ellipsis(str, truncateLen, ellipsisChars) {
63 var ellipsisLength;
64 if (str.length > truncateLen) {
65 if (ellipsisChars == null) {
66 ellipsisChars = '&hellip;';
67 ellipsisLength = 3;
68 }
69 else {
70 ellipsisLength = ellipsisChars.length;
71 }
72 str = str.substring(0, truncateLen - ellipsisLength) + ellipsisChars;
73 }
74 return str;
75 }
76 /**
77 * Removes array elements by value. Mutates the input array.
78 *
79 * Using this instead of the ES5 Array.prototype.filter() function to prevent
80 * creating many new arrays in memory for removing an element.
81 *
82 * @param arr The array to remove elements from. This array is mutated.
83 * @param fn The element to remove.
84 */
85 function remove(arr, item) {
86 for (var i = arr.length - 1; i >= 0; i--) {
87 if (arr[i] === item) {
88 arr.splice(i, 1);
89 }
90 }
91 }
92 /**
93 * Removes array elements based on a filtering function. Mutates the input
94 * array.
95 *
96 * Using this instead of the ES5 Array.prototype.filter() function to prevent
97 * creating many new arrays in memory for filtering.
98 *
99 * @param arr The array to remove elements from. This array is mutated.
100 * @param fn The predicate function which should return `true` to remove an
101 * element.
102 */
103 function removeWithPredicate(arr, fn) {
104 for (var i = arr.length - 1; i >= 0; i--) {
105 if (fn(arr[i]) === true) {
106 arr.splice(i, 1);
107 }
108 }
109 }
110 /**
111 * Function that should never be called but is used to check that every
112 * enum value is handled using TypeScript's 'never' type.
113 */
114 function assertNever(theValue) {
115 throw new Error("Unhandled case for value: '".concat(theValue, "'"));
116 }
117
118 /*
119 * This file builds and stores a library of the common regular expressions used
120 * by the Autolinker utility.
121 *
122 * Other regular expressions may exist ad-hoc, but these are generally the
123 * regular expressions that are shared between source files.
124 */
125 /**
126 * Regular expression to match upper and lowercase ASCII letters
127 */
128 var letterRe = /[A-Za-z]/;
129 /**
130 * Regular expression to match ASCII digits
131 */
132 var digitRe = /[\d]/;
133 /**
134 * Regular expression to match whitespace
135 */
136 var whitespaceRe = /\s/;
137 /**
138 * Regular expression to match quote characters
139 */
140 var quoteRe = /['"]/;
141 /**
142 * Regular expression to match the range of ASCII control characters (0-31), and
143 * the backspace char (127)
144 */
145 var controlCharsRe = /[\x00-\x1F\x7F]/;
146 /**
147 * The string form of a regular expression that would match all of the
148 * alphabetic ("letter") chars in the unicode character set when placed in a
149 * RegExp character class (`[]`). This includes all international alphabetic
150 * characters.
151 *
152 * These would be the characters matched by unicode regex engines `\p{L}`
153 * escape ("all letters").
154 *
155 * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)
156 * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Letter'
157 * regex's bmp
158 *
159 * VERY IMPORTANT: This set of characters is defined inside of a Regular
160 * Expression literal rather than a string literal to prevent UglifyJS from
161 * compressing the unicode escape sequences into their actual unicode
162 * characters. If Uglify compresses these into the unicode characters
163 * themselves, this results in the error "Range out of order in character
164 * class" when these characters are used inside of a Regular Expression
165 * character class (`[]`). See usages of this const. Alternatively, we can set
166 * the UglifyJS option `ascii_only` to true for the build, but that doesn't
167 * help others who are pulling in Autolinker into their own build and running
168 * UglifyJS themselves.
169 */
170 // prettier-ignore
171 var 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/
172 .source; // see note in above variable description
173 /**
174 * The string form of a regular expression that would match all emoji characters
175 * Based on the emoji regex defined in this article: https://thekevinscott.com/emojis-in-javascript/
176 */
177 var emojiStr = /\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/
178 .source;
179 /**
180 * The string form of a regular expression that would match all of the
181 * combining mark characters in the unicode character set when placed in a
182 * RegExp character class (`[]`).
183 *
184 * These would be the characters matched by unicode regex engines `\p{M}`
185 * escape ("all marks").
186 *
187 * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)
188 * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Mark'
189 * regex's bmp
190 *
191 * VERY IMPORTANT: This set of characters is defined inside of a Regular
192 * Expression literal rather than a string literal to prevent UglifyJS from
193 * compressing the unicode escape sequences into their actual unicode
194 * characters. If Uglify compresses these into the unicode characters
195 * themselves, this results in the error "Range out of order in character
196 * class" when these characters are used inside of a Regular Expression
197 * character class (`[]`). See usages of this const. Alternatively, we can set
198 * the UglifyJS option `ascii_only` to true for the build, but that doesn't
199 * help others who are pulling in Autolinker into their own build and running
200 * UglifyJS themselves.
201 */
202 // prettier-ignore
203 var 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/
204 .source; // see note in above variable description
205 /**
206 * The string form of a regular expression that would match all of the
207 * alphabetic ("letter") chars, emoji, and combining marks in the unicode character set
208 * when placed in a RegExp character class (`[]`). This includes all
209 * international alphabetic characters.
210 *
211 * These would be the characters matched by unicode regex engines `\p{L}\p{M}`
212 * escapes and emoji characters.
213 */
214 var alphaCharsAndMarksStr = alphaCharsStr + emojiStr + marksStr;
215 /**
216 * The string form of a regular expression that would match all of the
217 * decimal number chars in the unicode character set when placed in a RegExp
218 * character class (`[]`).
219 *
220 * These would be the characters matched by unicode regex engines `\p{Nd}`
221 * escape ("all decimal numbers")
222 *
223 * Taken from the XRegExp library: http://xregexp.com/ (thanks @https://github.com/slevithan)
224 * Specifically: http://xregexp.com/v/3.2.0/xregexp-all.js, the 'Decimal_Number'
225 * regex's bmp
226 *
227 * VERY IMPORTANT: This set of characters is defined inside of a Regular
228 * Expression literal rather than a string literal to prevent UglifyJS from
229 * compressing the unicode escape sequences into their actual unicode
230 * characters. If Uglify compresses these into the unicode characters
231 * themselves, this results in the error "Range out of order in character
232 * class" when these characters are used inside of a Regular Expression
233 * character class (`[]`). See usages of this const. Alternatively, we can set
234 * the UglifyJS option `ascii_only` to true for the build, but that doesn't
235 * help others who are pulling in Autolinker into their own build and running
236 * UglifyJS themselves.
237 */
238 // prettier-ignore
239 var 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/
240 .source; // see note in above variable description
241 /**
242 * The string form of a regular expression that would match all of the
243 * letters, combining marks, and decimal number chars in the unicode character
244 * set when placed in a RegExp character class (`[]`).
245 *
246 * These would be the characters matched by unicode regex engines
247 * `[\p{L}\p{M}\p{Nd}]` escape ("all letters, combining marks, and decimal
248 * numbers")
249 */
250 var alphaNumericAndMarksCharsStr = alphaCharsAndMarksStr + decimalNumbersStr;
251 /**
252 * The regular expression that will match a single letter of the
253 * {@link #alphaNumericAndMarksCharsStr}.
254 */
255 var alphaNumericAndMarksRe = new RegExp("[".concat(alphaNumericAndMarksCharsStr, "]"));
256
257 /**
258 * @class Autolinker.HtmlTag
259 * @extends Object
260 *
261 * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically.
262 *
263 * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use
264 * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}.
265 *
266 * ## Examples
267 *
268 * Example instantiation:
269 *
270 * var tag = new Autolinker.HtmlTag( {
271 * tagName : 'a',
272 * attrs : { 'href': 'http://google.com', 'class': 'external-link' },
273 * innerHtml : 'Google'
274 * } );
275 *
276 * tag.toAnchorString(); // <a href="http://google.com" class="external-link">Google</a>
277 *
278 * // Individual accessor methods
279 * tag.getTagName(); // 'a'
280 * tag.getAttr( 'href' ); // 'http://google.com'
281 * tag.hasClass( 'external-link' ); // true
282 *
283 *
284 * Using mutator methods (which may be used in combination with instantiation config properties):
285 *
286 * var tag = new Autolinker.HtmlTag();
287 * tag.setTagName( 'a' );
288 * tag.setAttr( 'href', 'http://google.com' );
289 * tag.addClass( 'external-link' );
290 * tag.setInnerHtml( 'Google' );
291 *
292 * tag.getTagName(); // 'a'
293 * tag.getAttr( 'href' ); // 'http://google.com'
294 * tag.hasClass( 'external-link' ); // true
295 *
296 * tag.toAnchorString(); // <a href="http://google.com" class="external-link">Google</a>
297 *
298 *
299 * ## Example use within a {@link Autolinker#replaceFn replaceFn}
300 *
301 * var html = Autolinker.link( "Test google.com", {
302 * replaceFn : function( match ) {
303 * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text
304 * tag.setAttr( 'rel', 'nofollow' );
305 *
306 * return tag;
307 * }
308 * } );
309 *
310 * // generated html:
311 * // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
312 *
313 *
314 * ## Example use with a new tag for the replacement
315 *
316 * var html = Autolinker.link( "Test google.com", {
317 * replaceFn : function( match ) {
318 * var tag = new Autolinker.HtmlTag( {
319 * tagName : 'button',
320 * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() },
321 * innerHtml : 'Load URL: ' + match.getAnchorText()
322 * } );
323 *
324 * return tag;
325 * }
326 * } );
327 *
328 * // generated html:
329 * // Test <button title="Load URL: http://google.com">Load URL: google.com</button>
330 */
331 var HtmlTag = /** @class */ (function () {
332 /**
333 * @method constructor
334 * @param {Object} [cfg] The configuration properties for this class, in an Object (map)
335 */
336 function HtmlTag(cfg) {
337 if (cfg === void 0) { cfg = {}; }
338 /**
339 * @cfg {String} tagName
340 *
341 * The tag name. Ex: 'a', 'button', etc.
342 *
343 * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toAnchorString}
344 * is executed.
345 */
346 this.tagName = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
347 /**
348 * @cfg {Object.<String, String>} attrs
349 *
350 * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the
351 * values are the attribute values.
352 */
353 this.attrs = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
354 /**
355 * @cfg {String} innerHTML
356 *
357 * The inner HTML for the tag.
358 */
359 this.innerHTML = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
360 this.tagName = cfg.tagName || '';
361 this.attrs = cfg.attrs || {};
362 this.innerHTML = cfg.innerHtml || cfg.innerHTML || ''; // accept either the camelCased form or the fully capitalized acronym as in the DOM
363 }
364 /**
365 * Sets the tag name that will be used to generate the tag with.
366 *
367 * @param {String} tagName
368 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
369 */
370 HtmlTag.prototype.setTagName = function (tagName) {
371 this.tagName = tagName;
372 return this;
373 };
374 /**
375 * Retrieves the tag name.
376 *
377 * @return {String}
378 */
379 HtmlTag.prototype.getTagName = function () {
380 return this.tagName || '';
381 };
382 /**
383 * Sets an attribute on the HtmlTag.
384 *
385 * @param {String} attrName The attribute name to set.
386 * @param {String} attrValue The attribute value to set.
387 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
388 */
389 HtmlTag.prototype.setAttr = function (attrName, attrValue) {
390 var tagAttrs = this.getAttrs();
391 tagAttrs[attrName] = attrValue;
392 return this;
393 };
394 /**
395 * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`.
396 *
397 * @param {String} attrName The attribute name to retrieve.
398 * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag.
399 */
400 HtmlTag.prototype.getAttr = function (attrName) {
401 return this.getAttrs()[attrName];
402 };
403 /**
404 * Sets one or more attributes on the HtmlTag.
405 *
406 * @param {Object.<String, String>} attrs A key/value Object (map) of the attributes to set.
407 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
408 */
409 HtmlTag.prototype.setAttrs = function (attrs) {
410 Object.assign(this.getAttrs(), attrs);
411 return this;
412 };
413 /**
414 * Retrieves the attributes Object (map) for the HtmlTag.
415 *
416 * @return {Object.<String, String>} A key/value object of the attributes for the HtmlTag.
417 */
418 HtmlTag.prototype.getAttrs = function () {
419 return this.attrs || (this.attrs = {});
420 };
421 /**
422 * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag.
423 *
424 * @param {String} cssClass One or more space-separated CSS classes to set (overwrite).
425 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
426 */
427 HtmlTag.prototype.setClass = function (cssClass) {
428 return this.setAttr('class', cssClass);
429 };
430 /**
431 * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes.
432 *
433 * @param {String} cssClass One or more space-separated CSS classes to add.
434 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
435 */
436 HtmlTag.prototype.addClass = function (cssClass) {
437 var classAttr = this.getClass(), classes = !classAttr ? [] : classAttr.split(whitespaceRe), newClasses = cssClass.split(whitespaceRe), newClass;
438 while ((newClass = newClasses.shift())) {
439 if (classes.indexOf(newClass) === -1) {
440 classes.push(newClass);
441 }
442 }
443 this.getAttrs()['class'] = classes.join(' ');
444 return this;
445 };
446 /**
447 * Convenience method to remove one or more CSS classes from the HtmlTag.
448 *
449 * @param {String} cssClass One or more space-separated CSS classes to remove.
450 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
451 */
452 HtmlTag.prototype.removeClass = function (cssClass) {
453 var classAttr = this.getClass(), classes = !classAttr ? [] : classAttr.split(whitespaceRe), removeClasses = cssClass.split(whitespaceRe), removeClass;
454 while (classes.length && (removeClass = removeClasses.shift())) {
455 var idx = classes.indexOf(removeClass);
456 if (idx !== -1) {
457 classes.splice(idx, 1);
458 }
459 }
460 this.getAttrs()['class'] = classes.join(' ');
461 return this;
462 };
463 /**
464 * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when
465 * there are multiple.
466 *
467 * @return {String}
468 */
469 HtmlTag.prototype.getClass = function () {
470 return this.getAttrs()['class'] || '';
471 };
472 /**
473 * Convenience method to check if the tag has a CSS class or not.
474 *
475 * @param {String} cssClass The CSS class to check for.
476 * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise.
477 */
478 HtmlTag.prototype.hasClass = function (cssClass) {
479 return (' ' + this.getClass() + ' ').indexOf(' ' + cssClass + ' ') !== -1;
480 };
481 /**
482 * Sets the inner HTML for the tag.
483 *
484 * @param {String} html The inner HTML to set.
485 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
486 */
487 HtmlTag.prototype.setInnerHTML = function (html) {
488 this.innerHTML = html;
489 return this;
490 };
491 /**
492 * Backwards compatibility method name.
493 *
494 * @param {String} html The inner HTML to set.
495 * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained.
496 */
497 HtmlTag.prototype.setInnerHtml = function (html) {
498 return this.setInnerHTML(html);
499 };
500 /**
501 * Retrieves the inner HTML for the tag.
502 *
503 * @return {String}
504 */
505 HtmlTag.prototype.getInnerHTML = function () {
506 return this.innerHTML || '';
507 };
508 /**
509 * Backward compatibility method name.
510 *
511 * @return {String}
512 */
513 HtmlTag.prototype.getInnerHtml = function () {
514 return this.getInnerHTML();
515 };
516 /**
517 * Generates the HTML string for the tag.
518 *
519 * @return {String}
520 */
521 HtmlTag.prototype.toAnchorString = function () {
522 var tagName = this.getTagName(), attrsStr = this.buildAttrsStr();
523 attrsStr = attrsStr ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes
524 return ['<', tagName, attrsStr, '>', this.getInnerHtml(), '</', tagName, '>'].join('');
525 };
526 /**
527 * Support method for {@link #toAnchorString}, returns the string space-separated key="value" pairs, used to populate
528 * the stringified HtmlTag.
529 *
530 * @protected
531 * @return {String} Example return: `attr1="value1" attr2="value2"`
532 */
533 HtmlTag.prototype.buildAttrsStr = function () {
534 if (!this.attrs)
535 return ''; // no `attrs` Object (map) has been set, return empty string
536 var attrs = this.getAttrs(), attrsArr = [];
537 for (var prop in attrs) {
538 if (attrs.hasOwnProperty(prop)) {
539 attrsArr.push(prop + '="' + attrs[prop] + '"');
540 }
541 }
542 return attrsArr.join(' ');
543 };
544 return HtmlTag;
545 }());
546
547 /**
548 * Date: 2015-10-05
549 * Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)
550 *
551 * A truncation feature, where the ellipsis will be placed at a section within
552 * the URL making it still somewhat human readable.
553 *
554 * @param {String} url A URL.
555 * @param {Number} truncateLen The maximum length of the truncated output URL string.
556 * @param {String} ellipsisChars The characters to place within the url, e.g. "...".
557 * @return {String} The truncated URL.
558 */
559 function truncateSmart(url, truncateLen, ellipsisChars) {
560 var ellipsisLengthBeforeParsing;
561 var ellipsisLength;
562 if (ellipsisChars == null) {
563 ellipsisChars = '&hellip;';
564 ellipsisLength = 3;
565 ellipsisLengthBeforeParsing = 8;
566 }
567 else {
568 ellipsisLength = ellipsisChars.length;
569 ellipsisLengthBeforeParsing = ellipsisChars.length;
570 }
571 var parse_url = function (url) {
572 // Functionality inspired by PHP function of same name
573 var urlObj = {};
574 var urlSub = url;
575 var match = urlSub.match(/^([a-z]+):\/\//i);
576 if (match) {
577 urlObj.scheme = match[1];
578 urlSub = urlSub.substr(match[0].length);
579 }
580 match = urlSub.match(/^(.*?)(?=(\?|#|\/|$))/i);
581 if (match) {
582 urlObj.host = match[1];
583 urlSub = urlSub.substr(match[0].length);
584 }
585 match = urlSub.match(/^\/(.*?)(?=(\?|#|$))/i);
586 if (match) {
587 urlObj.path = match[1];
588 urlSub = urlSub.substr(match[0].length);
589 }
590 match = urlSub.match(/^\?(.*?)(?=(#|$))/i);
591 if (match) {
592 urlObj.query = match[1];
593 urlSub = urlSub.substr(match[0].length);
594 }
595 match = urlSub.match(/^#(.*?)$/i);
596 if (match) {
597 urlObj.fragment = match[1];
598 //urlSub = urlSub.substr(match[0].length); -- not used. Uncomment if adding another block.
599 }
600 return urlObj;
601 };
602 var buildUrl = function (urlObj) {
603 var url = '';
604 if (urlObj.scheme && urlObj.host) {
605 url += urlObj.scheme + '://';
606 }
607 if (urlObj.host) {
608 url += urlObj.host;
609 }
610 if (urlObj.path) {
611 url += '/' + urlObj.path;
612 }
613 if (urlObj.query) {
614 url += '?' + urlObj.query;
615 }
616 if (urlObj.fragment) {
617 url += '#' + urlObj.fragment;
618 }
619 return url;
620 };
621 var buildSegment = function (segment, remainingAvailableLength) {
622 var remainingAvailableLengthHalf = remainingAvailableLength / 2, startOffset = Math.ceil(remainingAvailableLengthHalf), endOffset = -1 * Math.floor(remainingAvailableLengthHalf), end = '';
623 if (endOffset < 0) {
624 end = segment.substr(endOffset);
625 }
626 return segment.substr(0, startOffset) + ellipsisChars + end;
627 };
628 if (url.length <= truncateLen) {
629 return url;
630 }
631 var availableLength = truncateLen - ellipsisLength;
632 var urlObj = parse_url(url);
633 // Clean up the URL
634 if (urlObj.query) {
635 var matchQuery = urlObj.query.match(/^(.*?)(?=(\?|\#))(.*?)$/i);
636 if (matchQuery) {
637 // Malformed URL; two or more "?". Removed any content behind the 2nd.
638 urlObj.query = urlObj.query.substr(0, matchQuery[1].length);
639 url = buildUrl(urlObj);
640 }
641 }
642 if (url.length <= truncateLen) {
643 return url;
644 }
645 if (urlObj.host) {
646 urlObj.host = urlObj.host.replace(/^www\./, '');
647 url = buildUrl(urlObj);
648 }
649 if (url.length <= truncateLen) {
650 return url;
651 }
652 // Process and build the URL
653 var str = '';
654 if (urlObj.host) {
655 str += urlObj.host;
656 }
657 if (str.length >= availableLength) {
658 if (urlObj.host.length == truncateLen) {
659 return (urlObj.host.substr(0, truncateLen - ellipsisLength) + ellipsisChars).substr(0, availableLength + ellipsisLengthBeforeParsing);
660 }
661 return buildSegment(str, availableLength).substr(0, availableLength + ellipsisLengthBeforeParsing);
662 }
663 var pathAndQuery = '';
664 if (urlObj.path) {
665 pathAndQuery += '/' + urlObj.path;
666 }
667 if (urlObj.query) {
668 pathAndQuery += '?' + urlObj.query;
669 }
670 if (pathAndQuery) {
671 if ((str + pathAndQuery).length >= availableLength) {
672 if ((str + pathAndQuery).length == truncateLen) {
673 return (str + pathAndQuery).substr(0, truncateLen);
674 }
675 var remainingAvailableLength = availableLength - str.length;
676 return (str + buildSegment(pathAndQuery, remainingAvailableLength)).substr(0, availableLength + ellipsisLengthBeforeParsing);
677 }
678 else {
679 str += pathAndQuery;
680 }
681 }
682 if (urlObj.fragment) {
683 var fragment = '#' + urlObj.fragment;
684 if ((str + fragment).length >= availableLength) {
685 if ((str + fragment).length == truncateLen) {
686 return (str + fragment).substr(0, truncateLen);
687 }
688 var remainingAvailableLength2 = availableLength - str.length;
689 return (str + buildSegment(fragment, remainingAvailableLength2)).substr(0, availableLength + ellipsisLengthBeforeParsing);
690 }
691 else {
692 str += fragment;
693 }
694 }
695 if (urlObj.scheme && urlObj.host) {
696 var scheme = urlObj.scheme + '://';
697 if ((str + scheme).length < availableLength) {
698 return (scheme + str).substr(0, truncateLen);
699 }
700 }
701 if (str.length <= truncateLen) {
702 return str;
703 }
704 var end = '';
705 if (availableLength > 0) {
706 end = str.substr(-1 * Math.floor(availableLength / 2));
707 }
708 return (str.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(0, availableLength + ellipsisLengthBeforeParsing);
709 }
710
711 /**
712 * Date: 2015-10-05
713 * Author: Kasper Søfren <soefritz@gmail.com> (https://github.com/kafoso)
714 *
715 * A truncation feature, where the ellipsis will be placed in the dead-center of the URL.
716 *
717 * @param {String} url A URL.
718 * @param {Number} truncateLen The maximum length of the truncated output URL string.
719 * @param {String} ellipsisChars The characters to place within the url, e.g. "..".
720 * @return {String} The truncated URL.
721 */
722 function truncateMiddle(url, truncateLen, ellipsisChars) {
723 if (url.length <= truncateLen) {
724 return url;
725 }
726 var ellipsisLengthBeforeParsing;
727 var ellipsisLength;
728 if (ellipsisChars == null) {
729 ellipsisChars = '&hellip;';
730 ellipsisLengthBeforeParsing = 8;
731 ellipsisLength = 3;
732 }
733 else {
734 ellipsisLengthBeforeParsing = ellipsisChars.length;
735 ellipsisLength = ellipsisChars.length;
736 }
737 var availableLength = truncateLen - ellipsisLength;
738 var end = '';
739 if (availableLength > 0) {
740 end = url.substr(-1 * Math.floor(availableLength / 2));
741 }
742 return (url.substr(0, Math.ceil(availableLength / 2)) + ellipsisChars + end).substr(0, availableLength + ellipsisLengthBeforeParsing);
743 }
744
745 /**
746 * A truncation feature where the ellipsis will be placed at the end of the URL.
747 *
748 * @param {String} anchorText
749 * @param {Number} truncateLen The maximum length of the truncated output URL string.
750 * @param {String} ellipsisChars The characters to place within the url, e.g. "..".
751 * @return {String} The truncated URL.
752 */
753 function truncateEnd(anchorText, truncateLen, ellipsisChars) {
754 return ellipsis(anchorText, truncateLen, ellipsisChars);
755 }
756
757 /**
758 * @protected
759 * @class Autolinker.AnchorTagBuilder
760 * @extends Object
761 *
762 * Builds anchor (&lt;a&gt;) tags for the Autolinker utility when a match is
763 * found.
764 *
765 * Normally this class is instantiated, configured, and used internally by an
766 * {@link Autolinker} instance, but may actually be used indirectly in a
767 * {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag}
768 * instances which may be modified before returning from the
769 * {@link Autolinker#replaceFn replaceFn}. For example:
770 *
771 * var html = Autolinker.link( "Test google.com", {
772 * replaceFn : function( match ) {
773 * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance
774 * tag.setAttr( 'rel', 'nofollow' );
775 *
776 * return tag;
777 * }
778 * } );
779 *
780 * // generated html:
781 * // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
782 */
783 var AnchorTagBuilder = /** @class */ (function () {
784 /**
785 * @method constructor
786 * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map).
787 */
788 function AnchorTagBuilder(cfg) {
789 if (cfg === void 0) { cfg = {}; }
790 /**
791 * @cfg {Boolean} newWindow
792 * @inheritdoc Autolinker#newWindow
793 */
794 this.newWindow = false; // default value just to get the above doc comment in the ES5 output and documentation generator
795 /**
796 * @cfg {Object} truncate
797 * @inheritdoc Autolinker#truncate
798 */
799 this.truncate = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
800 /**
801 * @cfg {String} className
802 * @inheritdoc Autolinker#className
803 */
804 this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
805 this.newWindow = cfg.newWindow || false;
806 this.truncate = cfg.truncate || {};
807 this.className = cfg.className || '';
808 }
809 /**
810 * Generates the actual anchor (&lt;a&gt;) tag to use in place of the
811 * matched text, via its `match` object.
812 *
813 * @param match The Match instance to generate an anchor tag from.
814 * @return The HtmlTag instance for the anchor tag.
815 */
816 AnchorTagBuilder.prototype.build = function (match) {
817 return new HtmlTag({
818 tagName: 'a',
819 attrs: this.createAttrs(match),
820 innerHtml: this.processAnchorText(match.getAnchorText()),
821 });
822 };
823 /**
824 * Creates the Object (map) of the HTML attributes for the anchor (&lt;a&gt;)
825 * tag being generated.
826 *
827 * @protected
828 * @param match The Match instance to generate an anchor tag from.
829 * @return A key/value Object (map) of the anchor tag's attributes.
830 */
831 AnchorTagBuilder.prototype.createAttrs = function (match) {
832 var attrs = {
833 href: match.getAnchorHref(), // we'll always have the `href` attribute
834 };
835 var cssClass = this.createCssClass(match);
836 if (cssClass) {
837 attrs['class'] = cssClass;
838 }
839 if (this.newWindow) {
840 attrs['target'] = '_blank';
841 attrs['rel'] = 'noopener noreferrer'; // Issue #149. See https://mathiasbynens.github.io/rel-noopener/
842 }
843 if (this.truncate) {
844 if (this.truncate.length && this.truncate.length < match.getAnchorText().length) {
845 attrs['title'] = match.getAnchorHref();
846 }
847 }
848 return attrs;
849 };
850 /**
851 * Creates the CSS class that will be used for a given anchor tag, based on
852 * the `matchType` and the {@link #className} config.
853 *
854 * Example returns:
855 *
856 * - "" // no {@link #className}
857 * - "myLink myLink-url" // url match
858 * - "myLink myLink-email" // email match
859 * - "myLink myLink-phone" // phone match
860 * - "myLink myLink-hashtag" // hashtag match
861 * - "myLink myLink-mention myLink-twitter" // mention match with Twitter service
862 *
863 * @protected
864 * @param match The Match instance to generate an
865 * anchor tag from.
866 * @return The CSS class string for the link. Example return:
867 * "myLink myLink-url". If no {@link #className} was configured, returns
868 * an empty string.
869 */
870 AnchorTagBuilder.prototype.createCssClass = function (match) {
871 var className = this.className;
872 if (!className) {
873 return '';
874 }
875 else {
876 var returnClasses = [className], cssClassSuffixes = match.getCssClassSuffixes();
877 for (var i = 0, len = cssClassSuffixes.length; i < len; i++) {
878 returnClasses.push(className + '-' + cssClassSuffixes[i]);
879 }
880 return returnClasses.join(' ');
881 }
882 };
883 /**
884 * Processes the `anchorText` by truncating the text according to the
885 * {@link #truncate} config.
886 *
887 * @private
888 * @param anchorText The anchor tag's text (i.e. what will be
889 * displayed).
890 * @return The processed `anchorText`.
891 */
892 AnchorTagBuilder.prototype.processAnchorText = function (anchorText) {
893 anchorText = this.doTruncate(anchorText);
894 return anchorText;
895 };
896 /**
897 * Performs the truncation of the `anchorText` based on the {@link #truncate}
898 * option. If the `anchorText` is longer than the length specified by the
899 * {@link #truncate} option, the truncation is performed based on the
900 * `location` property. See {@link #truncate} for details.
901 *
902 * @private
903 * @param anchorText The anchor tag's text (i.e. what will be
904 * displayed).
905 * @return The truncated anchor text.
906 */
907 AnchorTagBuilder.prototype.doTruncate = function (anchorText) {
908 var truncate = this.truncate;
909 if (!truncate || !truncate.length)
910 return anchorText;
911 var truncateLength = truncate.length, truncateLocation = truncate.location;
912 if (truncateLocation === 'smart') {
913 return truncateSmart(anchorText, truncateLength);
914 }
915 else if (truncateLocation === 'middle') {
916 return truncateMiddle(anchorText, truncateLength);
917 }
918 else {
919 return truncateEnd(anchorText, truncateLength);
920 }
921 };
922 return AnchorTagBuilder;
923 }());
924
925 /*! *****************************************************************************
926 Copyright (c) Microsoft Corporation.
927
928 Permission to use, copy, modify, and/or distribute this software for any
929 purpose with or without fee is hereby granted.
930
931 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
932 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
933 AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
934 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
935 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
936 OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
937 PERFORMANCE OF THIS SOFTWARE.
938 ***************************************************************************** */
939 /* global Reflect, Promise */
940
941 var extendStatics = function(d, b) {
942 extendStatics = Object.setPrototypeOf ||
943 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
944 function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
945 return extendStatics(d, b);
946 };
947
948 function __extends(d, b) {
949 if (typeof b !== "function" && b !== null)
950 throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
951 extendStatics(d, b);
952 function __() { this.constructor = d; }
953 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
954 }
955
956 var __assign = function() {
957 __assign = Object.assign || function __assign(t) {
958 for (var s, i = 1, n = arguments.length; i < n; i++) {
959 s = arguments[i];
960 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
961 }
962 return t;
963 };
964 return __assign.apply(this, arguments);
965 };
966
967 /**
968 * @abstract
969 * @class Autolinker.match.AbstractMatch
970 *
971 * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a
972 * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match.
973 *
974 * For example:
975 *
976 * var input = "..."; // string with URLs, Email Addresses, and Mentions (Twitter, Instagram, Soundcloud)
977 *
978 * var linkedText = Autolinker.link( input, {
979 * replaceFn : function( match ) {
980 * console.log( "href = ", match.getAnchorHref() );
981 * console.log( "text = ", match.getAnchorText() );
982 *
983 * switch( match.getType() ) {
984 * case 'url' :
985 * console.log( "url: ", match.getUrl() );
986 *
987 * case 'email' :
988 * console.log( "email: ", match.getEmail() );
989 *
990 * case 'mention' :
991 * console.log( "mention: ", match.getMention() );
992 * }
993 * }
994 * } );
995 *
996 * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}.
997 */
998 var AbstractMatch = /** @class */ (function () {
999 /**
1000 * @member Autolinker.match.Match
1001 * @method constructor
1002 * @param {Object} cfg The configuration properties for the Match
1003 * instance, specified in an Object (map).
1004 */
1005 function AbstractMatch(cfg) {
1006 /**
1007 * @cfg {Autolinker.AnchorTagBuilder} tagBuilder (required)
1008 *
1009 * Reference to the AnchorTagBuilder instance to use to generate an anchor
1010 * tag for the Match.
1011 */
1012 // @ts-ignore
1013 this._ = null; // property used just to get the above doc comment into the ES5 output and documentation generator
1014 /**
1015 * @cfg {String} matchedText (required)
1016 *
1017 * The original text that was matched by the {@link Autolinker.matcher.Matcher}.
1018 */
1019 this.matchedText = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
1020 /**
1021 * @cfg {Number} offset (required)
1022 *
1023 * The offset of where the match was made in the input string.
1024 */
1025 this.offset = 0; // default value just to get the above doc comment in the ES5 output and documentation generator
1026 this.tagBuilder = cfg.tagBuilder;
1027 this.matchedText = cfg.matchedText;
1028 this.offset = cfg.offset;
1029 }
1030 /**
1031 * Returns the original text that was matched.
1032 *
1033 * @return {String}
1034 */
1035 AbstractMatch.prototype.getMatchedText = function () {
1036 return this.matchedText;
1037 };
1038 /**
1039 * Sets the {@link #offset} of where the match was made in the input string.
1040 *
1041 * A {@link Autolinker.matcher.Matcher} will be fed only HTML text nodes,
1042 * and will therefore set an original offset that is relative to the HTML
1043 * text node itself. However, we want this offset to be relative to the full
1044 * HTML input string, and thus if using {@link Autolinker#parse} (rather
1045 * than calling a {@link Autolinker.matcher.Matcher} directly), then this
1046 * offset is corrected after the Matcher itself has done its job.
1047 *
1048 * @private
1049 * @param {Number} offset
1050 */
1051 AbstractMatch.prototype.setOffset = function (offset) {
1052 this.offset = offset;
1053 };
1054 /**
1055 * Returns the offset of where the match was made in the input string. This
1056 * is the 0-based index of the match.
1057 *
1058 * @return {Number}
1059 */
1060 AbstractMatch.prototype.getOffset = function () {
1061 return this.offset;
1062 };
1063 /**
1064 * Returns the CSS class suffix(es) for this match.
1065 *
1066 * A CSS class suffix is appended to the {@link Autolinker#className} in
1067 * the {@link Autolinker.AnchorTagBuilder} when a match is translated into
1068 * an anchor tag.
1069 *
1070 * For example, if {@link Autolinker#className} was configured as 'myLink',
1071 * and this method returns `[ 'url' ]`, the final class name of the element
1072 * will become: 'myLink myLink-url'.
1073 *
1074 * The match may provide multiple CSS class suffixes to be appended to the
1075 * {@link Autolinker#className} in order to facilitate better styling
1076 * options for different match criteria. See {@link Autolinker.match.Mention}
1077 * for an example.
1078 *
1079 * By default, this method returns a single array with the match's
1080 * {@link #getType type} name, but may be overridden by subclasses.
1081 *
1082 * @return {String[]}
1083 */
1084 AbstractMatch.prototype.getCssClassSuffixes = function () {
1085 return [this.type];
1086 };
1087 /**
1088 * Builds and returns an {@link Autolinker.HtmlTag} instance based on the
1089 * Match.
1090 *
1091 * This can be used to easily generate anchor tags from matches, and either
1092 * return their HTML string, or modify them before doing so.
1093 *
1094 * Example Usage:
1095 *
1096 * var tag = match.buildTag();
1097 * tag.addClass( 'cordova-link' );
1098 * tag.setAttr( 'target', '_system' );
1099 *
1100 * tag.toAnchorString(); // <a href="http://google.com" class="cordova-link" target="_system">Google</a>
1101 *
1102 * Example Usage in {@link Autolinker#replaceFn}:
1103 *
1104 * var html = Autolinker.link( "Test google.com", {
1105 * replaceFn : function( match ) {
1106 * var tag = match.buildTag(); // returns an {@link Autolinker.HtmlTag} instance
1107 * tag.setAttr( 'rel', 'nofollow' );
1108 *
1109 * return tag;
1110 * }
1111 * } );
1112 *
1113 * // generated html:
1114 * // Test <a href="http://google.com" target="_blank" rel="nofollow">google.com</a>
1115 */
1116 AbstractMatch.prototype.buildTag = function () {
1117 return this.tagBuilder.build(this);
1118 };
1119 return AbstractMatch;
1120 }());
1121
1122 // NOTE: THIS IS A GENERATED FILE
1123 // To update with the latest TLD list, run `npm run update-tld-regex`
1124 var tldRegexStr = '(?:xn--vermgensberatung-pwb|xn--vermgensberater-ctb|xn--clchc0ea0b2g2a9gcd|xn--w4r85el8fhu5dnra|northwesternmutual|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--mgbaakc7dvf|xn--mgbc0a9azcg|xn--nqv7fs00ema|americanfamily|bananarepublic|cancerresearch|cookingchannel|kerrylogistics|weatherchannel|xn--54b7fta0cc|xn--6qq986b3xl|xn--80aqecdr1a|xn--b4w605ferd|xn--fiq228c5hs|xn--h2breg3eve|xn--jlq480n2rg|xn--jlq61u9w7b|xn--mgba3a3ejt|xn--mgbaam7a8h|xn--mgbayh7gpa|xn--mgbbh1a71e|xn--mgbca7dzdo|xn--mgbi4ecexp|xn--mgbx4cd0ab|xn--rvc1e0am3e|international|lifeinsurance|travelchannel|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|foodnetwork|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|volkswagen|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|alfaromeo|allfinanz|amsterdam|analytics|aquarelle|barcelona|bloomberg|christmas|community|directory|education|equipment|fairwinds|financial|firestone|fresenius|frontdoor|furniture|goldpoint|hisamitsu|homedepot|homegoods|homesense|institute|insurance|kuokgroup|lancaster|landrover|lifestyle|marketing|marshalls|melbourne|microsoft|panasonic|passagens|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|cityeats|cleaning|clinique|clothing|commbank|computer|delivery|deloitte|democrat|diamonds|discount|discover|download|engineer|ericsson|etisalat|exchange|feedback|fidelity|firmdale|football|frontier|goodyear|grainger|graphics|guardian|hdfcbank|helsinki|holdings|hospital|infiniti|ipiranga|istanbul|jpmorgan|lighting|lundbeck|marriott|maserati|mckinsey|memorial|merckmsd|mortgage|observer|partners|pharmacy|pictures|plumbing|property|redstone|reliance|saarland|samsclub|security|services|shopping|showtime|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|avianca|banamex|bauhaus|bentley|bestbuy|booking|brother|bugatti|capital|caravan|careers|channel|charity|chintai|citadel|clubmed|college|cologne|comcast|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|hoteles|hotmail|hyundai|ismaili|jewelry|juniper|kitchen|komatsu|lacaixa|lanxess|lasalle|latrobe|leclerc|limited|lincoln|markets|monster|netbank|netflix|network|neustar|okinawa|oldnavy|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|tiffany|toshiba|trading|walmart|wanggou|watches|weather|website|wedding|whoswho|windows|winners|xfinity|yamaxun|youtube|zuerich|католик|اتصالات|البحرين|الجزائر|العليان|پاکستان|كاثوليك|இந்தியா|abarth|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|kinder|kindle|kosher|lancia|latino|lawyer|lefrak|living|locker|london|luxury|madrid|maison|makeup|market|mattel|mobile|monash|mormon|moscow|museum|mutual|nagoya|natura|nissan|nissay|norton|nowruz|office|olayan|online|oracle|orange|otsuka|pfizer|photos|physio|pictet|quebec|racing|realty|reisen|repair|report|review|rocher|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|vuelos|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|dabur|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|linde|lipsy|loans|locus|lotte|lotto|macys|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|adac|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|fiat|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|hgtv|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|loft|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|shaw|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|cbs|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|sca|scb|ses|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|ελ|ευ|бг|ею|рф|გე|닷넷|닷컴|삼성|한국|コム|世界|中信|中国|中國|企业|佛山|信息|健康|八卦|公司|公益|台湾|台灣|商城|商店|商标|嘉里|在线|大拿|娱乐|家電|广东|微博|慈善|手机|招聘|政务|政府|新闻|时尚|書籍|机构|游戏|澳門|点看|移动|网址|网店|网站|网络|联通|谷歌|购物|通販|集团|食品|餐厅|香港)';
1125 var tldRegex = new RegExp('^' + tldRegexStr + '$');
1126
1127 /**
1128 * The set of characters that will start a URL suffix (i.e. the path, query, and
1129 * hash part of the URL)
1130 */
1131 var urlSuffixStartCharsRe = /[\/?#]/;
1132 /**
1133 * The set of characters that are allowed in the URL suffix (i.e. the path,
1134 * query, and hash part of the URL) which may also form the ending character of
1135 * the URL.
1136 *
1137 * The {@link #urlSuffixNotAllowedAsLastCharRe} are additional allowed URL
1138 * suffix characters, but (generally) should not be the last character of a URL.
1139 */
1140 var urlSuffixAllowedSpecialCharsRe = /[-+&@#/%=~_()|'$*\[\]{}\u2713]/;
1141 /**
1142 * URL suffix characters (i.e. path, query, and has part of the URL) that are
1143 * not allowed as the *last character* in the URL suffix as they would normally
1144 * form the end of a sentence.
1145 *
1146 * The {@link #urlSuffixAllowedSpecialCharsRe} contains additional allowed URL
1147 * suffix characters which are allowed as the last character.
1148 */
1149 var urlSuffixNotAllowedAsLastCharRe = /[?!:,.;^]/;
1150 /**
1151 * Regular expression to match an http:// or https:// scheme.
1152 */
1153 var httpSchemeRe = /https?:\/\//i;
1154 /**
1155 * Regular expression to match an http:// or https:// scheme as the prefix of
1156 * a string.
1157 */
1158 var httpSchemePrefixRe = new RegExp('^' + httpSchemeRe.source, 'i');
1159 var urlSuffixedCharsNotAllowedAtEndRe = new RegExp(urlSuffixNotAllowedAsLastCharRe.source + '$');
1160 /**
1161 * A regular expression used to determine the schemes we should not autolink
1162 */
1163 var invalidSchemeRe = /^(javascript|vbscript):/i;
1164 // A regular expression used to determine if the URL is a scheme match (such as
1165 // 'http://google.com', and as opposed to a "TLD match"). This regular
1166 // expression is used to parse out the host along with if the URL has an
1167 // authority component (i.e. '//')
1168 //
1169 // Capturing groups:
1170 // 1. '//' if the URL has an authority component, empty string otherwise
1171 // 2. The host (if one exists). Ex: 'google.com'
1172 //
1173 // See https://www.rfc-editor.org/rfc/rfc3986#appendix-A for terminology
1174 var schemeUrlRe = /^[A-Za-z][-.+A-Za-z0-9]*:(\/\/)?([^:/]*)/;
1175 // A regular expression used to determine if the URL is a TLD match (such as
1176 // 'google.com', and as opposed to a "scheme match"). This regular
1177 // expression is used to help parse out the TLD (top-level domain) of the host.
1178 //
1179 // See https://www.rfc-editor.org/rfc/rfc3986#appendix-A for terminology
1180 var tldUrlHostRe = /^(?:\/\/)?([^/#?:]+)/; // optionally prefixed with protocol-relative '//' chars
1181 /**
1182 * Determines if the given character may start a scheme (ex: 'http').
1183 */
1184 function isSchemeStartChar(char) {
1185 return letterRe.test(char);
1186 }
1187 /**
1188 * Determines if the given character is a valid character in a scheme (such as
1189 * 'http' or 'ssh+git'), but only after the start char (which is handled by
1190 * {@link isSchemeStartChar}.
1191 */
1192 function isSchemeChar(char) {
1193 return (letterRe.test(char) || digitRe.test(char) || char === '+' || char === '-' || char === '.');
1194 }
1195 /**
1196 * Determines if the character can begin a domain label, which must be an
1197 * alphanumeric character and not an underscore or dash.
1198 *
1199 * A domain label is a segment of a hostname such as subdomain.google.com.
1200 */
1201 function isDomainLabelStartChar(char) {
1202 return alphaNumericAndMarksRe.test(char);
1203 }
1204 /**
1205 * Determines if the character is part of a domain label (but not a domain label
1206 * start character).
1207 *
1208 * A domain label is a segment of a hostname such as subdomain.google.com.
1209 */
1210 function isDomainLabelChar(char) {
1211 return char === '_' || isDomainLabelStartChar(char);
1212 }
1213 /**
1214 * Determines if the character is a path character ("pchar") as defined by
1215 * https://tools.ietf.org/html/rfc3986#appendix-A
1216 *
1217 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
1218 *
1219 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1220 * pct-encoded = "%" HEXDIG HEXDIG
1221 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1222 * / "*" / "+" / "," / ";" / "="
1223 *
1224 * Note that this implementation doesn't follow the spec exactly, but rather
1225 * follows URL path characters found out in the wild (spec might be out of date?)
1226 */
1227 function isPathChar(char) {
1228 return (alphaNumericAndMarksRe.test(char) ||
1229 urlSuffixAllowedSpecialCharsRe.test(char) ||
1230 urlSuffixNotAllowedAsLastCharRe.test(char));
1231 }
1232 /**
1233 * Determines if the character given may begin the "URL Suffix" section of a
1234 * URI (i.e. the path, query, or hash section). These are the '/', '?' and '#'
1235 * characters.
1236 *
1237 * See https://tools.ietf.org/html/rfc3986#appendix-A
1238 */
1239 function isUrlSuffixStartChar(char) {
1240 return urlSuffixStartCharsRe.test(char);
1241 }
1242 /**
1243 * Determines if the TLD read in the host is a known TLD (Top-Level Domain).
1244 *
1245 * Example: 'com' would be a known TLD (for a host of 'google.com'), but
1246 * 'local' would not (for a domain name of 'my-computer.local').
1247 */
1248 function isKnownTld(tld) {
1249 return tldRegex.test(tld.toLowerCase()); // make sure the tld is lowercase for the regex
1250 }
1251 /**
1252 * Determines if the given `url` is a valid scheme-prefixed URL.
1253 */
1254 function isValidSchemeUrl(url) {
1255 // If the scheme is 'javascript:' or 'vbscript:', these link
1256 // types can be dangerous. Don't link them.
1257 if (invalidSchemeRe.test(url)) {
1258 return false;
1259 }
1260 var schemeMatch = url.match(schemeUrlRe);
1261 if (!schemeMatch) {
1262 return false;
1263 }
1264 var isAuthorityMatch = !!schemeMatch[1];
1265 var host = schemeMatch[2];
1266 if (isAuthorityMatch) {
1267 // Any match that has an authority ('//' chars) after the scheme is
1268 // valid, such as 'http://anything'
1269 return true;
1270 }
1271 // If there's no authority ('//' chars), check that we have a hostname
1272 // that looks valid.
1273 //
1274 // The host must contain at least one '.' char and have a domain label
1275 // with at least one letter to be considered valid.
1276 //
1277 // Accept:
1278 // - git:domain.com (scheme followed by a host
1279 // Do not accept:
1280 // - git:something ('something' doesn't look like a host)
1281 // - version:1.0 ('1.0' doesn't look like a host)
1282 if (host.indexOf('.') === -1 || !letterRe.test(host)) {
1283 return false;
1284 }
1285 return true;
1286 }
1287 /**
1288 * Determines if the given `url` is a match with a valid TLD.
1289 */
1290 function isValidTldMatch(url) {
1291 // TLD URL such as 'google.com', we need to confirm that we have a valid
1292 // top-level domain
1293 var tldUrlHostMatch = url.match(tldUrlHostRe);
1294 if (!tldUrlHostMatch) {
1295 // At this point, if the URL didn't match our TLD re, it must be invalid
1296 // (highly unlikely to happen, but just in case)
1297 return false;
1298 }
1299 var host = tldUrlHostMatch[0];
1300 var hostLabels = host.split('.');
1301 if (hostLabels.length < 2) {
1302 // 0 or 1 host label, there's no TLD. Ex: 'localhost'
1303 return false;
1304 }
1305 var tld = hostLabels[hostLabels.length - 1];
1306 if (!isKnownTld(tld)) {
1307 return false;
1308 }
1309 // TODO: Implement these conditions for TLD matcher:
1310 // (
1311 // this.longestDomainLabelLength <= 63 &&
1312 // this.domainNameLength <= 255
1313 // );
1314 return true;
1315 }
1316 // Regular expression to confirm a valid IPv4 address (ex: '192.168.0.1')
1317 var ipV4Re = /^(?:(?: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]?)$/;
1318 // Regular expression used to split the IPv4 address itself from any port/path/query/hash
1319 var ipV4PartRe = /[:/?#]/;
1320 /**
1321 * Determines if the given URL is a valid IPv4-prefixed URL.
1322 */
1323 function isValidIpV4Address(url) {
1324 // Grab just the IP address
1325 var ipV4Part = url.split(ipV4PartRe, 1)[0]; // only 1 result needed
1326 return ipV4Re.test(ipV4Part);
1327 }
1328
1329 /**
1330 * A regular expression used to remove the 'www.' from URLs.
1331 */
1332 var wwwPrefixRegex = /^(https?:\/\/)?(www\.)?/i;
1333 /**
1334 * The regular expression used to remove the protocol-relative '//' from a URL
1335 * string, for purposes of formatting the anchor text. A protocol-relative URL
1336 * is, for example, "//yahoo.com"
1337 */
1338 var protocolRelativeRegex = /^\/\//;
1339 /**
1340 * @class Autolinker.match.Url
1341 * @extends Autolinker.match.AbstractMatch
1342 *
1343 * Represents a Url match found in an input string which should be Autolinked.
1344 *
1345 * See this class's superclass ({@link Autolinker.match.Match}) for more details.
1346 */
1347 var UrlMatch = /** @class */ (function (_super) {
1348 __extends(UrlMatch, _super);
1349 /**
1350 * @method constructor
1351 * @param {Object} cfg The configuration properties for the Match
1352 * instance, specified in an Object (map).
1353 */
1354 function UrlMatch(cfg) {
1355 var _this = _super.call(this, cfg) || this;
1356 /**
1357 * @public
1358 * @property {'url'} type
1359 *
1360 * A string name for the type of match that this class represents. Can be
1361 * used in a TypeScript discriminating union to type-narrow from the
1362 * `Match` type.
1363 */
1364 _this.type = 'url';
1365 /**
1366 * @cfg {String} url (required)
1367 *
1368 * The url that was matched.
1369 */
1370 _this.url = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
1371 /**
1372 * @cfg {"scheme"/"www"/"tld"} urlMatchType (required)
1373 *
1374 * The type of URL match that this class represents. This helps to determine
1375 * if the match was made in the original text with a prefixed scheme (ex:
1376 * 'http://www.google.com'), a prefixed 'www' (ex: 'www.google.com'), or
1377 * was matched by a known top-level domain (ex: 'google.com').
1378 */
1379 _this.urlMatchType = 'scheme'; // default value just to get the above doc comment in the ES5 output and documentation generator
1380 /**
1381 * @cfg {Boolean} protocolRelativeMatch (required)
1382 *
1383 * `true` if the URL is a protocol-relative match. A protocol-relative match
1384 * is a URL that starts with '//', and will be either http:// or https://
1385 * based on the protocol that the site is loaded under.
1386 */
1387 _this.protocolRelativeMatch = false; // default value just to get the above doc comment in the ES5 output and documentation generator
1388 /**
1389 * @cfg {Object} stripPrefix (required)
1390 *
1391 * The Object form of {@link Autolinker#cfg-stripPrefix}.
1392 */
1393 _this.stripPrefix = {
1394 scheme: true,
1395 www: true,
1396 }; // default value just to get the above doc comment in the ES5 output and documentation generator
1397 /**
1398 * @cfg {Boolean} stripTrailingSlash (required)
1399 * @inheritdoc Autolinker#cfg-stripTrailingSlash
1400 */
1401 _this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
1402 /**
1403 * @cfg {Boolean} decodePercentEncoding (required)
1404 * @inheritdoc Autolinker#cfg-decodePercentEncoding
1405 */
1406 _this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
1407 /**
1408 * @private
1409 * @property {Boolean} protocolPrepended
1410 *
1411 * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the
1412 * {@link #url} did not have a protocol)
1413 */
1414 _this.protocolPrepended = false;
1415 _this.urlMatchType = cfg.urlMatchType;
1416 _this.url = cfg.url;
1417 _this.protocolRelativeMatch = cfg.protocolRelativeMatch;
1418 _this.stripPrefix = cfg.stripPrefix;
1419 _this.stripTrailingSlash = cfg.stripTrailingSlash;
1420 _this.decodePercentEncoding = cfg.decodePercentEncoding;
1421 return _this;
1422 }
1423 /**
1424 * Returns a string name for the type of match that this class represents.
1425 * For the case of UrlMatch, returns 'url'.
1426 *
1427 * @return {String}
1428 */
1429 UrlMatch.prototype.getType = function () {
1430 return 'url';
1431 };
1432 /**
1433 * Returns a string name for the type of URL match that this class
1434 * represents.
1435 *
1436 * This helps to determine if the match was made in the original text with a
1437 * prefixed scheme (ex: 'http://www.google.com'), a prefixed 'www' (ex:
1438 * 'www.google.com'), or was matched by a known top-level domain (ex:
1439 * 'google.com').
1440 *
1441 * @return {"scheme"/"www"/"tld"}
1442 */
1443 UrlMatch.prototype.getUrlMatchType = function () {
1444 return this.urlMatchType;
1445 };
1446 /**
1447 * Returns the url that was matched, assuming the protocol to be 'http://' if the original
1448 * match was missing a protocol.
1449 *
1450 * @return {String}
1451 */
1452 UrlMatch.prototype.getUrl = function () {
1453 var url = this.url;
1454 // if the url string doesn't begin with a scheme, assume 'http://'
1455 if (!this.protocolRelativeMatch &&
1456 this.urlMatchType !== 'scheme' &&
1457 !this.protocolPrepended) {
1458 url = this.url = 'http://' + url;
1459 this.protocolPrepended = true;
1460 }
1461 return url;
1462 };
1463 /**
1464 * Returns the anchor href that should be generated for the match.
1465 *
1466 * @return {String}
1467 */
1468 UrlMatch.prototype.getAnchorHref = function () {
1469 var url = this.getUrl();
1470 return url.replace(/&amp;/g, '&'); // any &amp;'s in the URL should be converted back to '&' if they were displayed as &amp; in the source html
1471 };
1472 /**
1473 * Returns the anchor text that should be generated for the match.
1474 *
1475 * @return {String}
1476 */
1477 UrlMatch.prototype.getAnchorText = function () {
1478 var anchorText = this.getMatchedText();
1479 if (this.protocolRelativeMatch) {
1480 // Strip off any protocol-relative '//' from the anchor text
1481 anchorText = stripProtocolRelativePrefix(anchorText);
1482 }
1483 if (this.stripPrefix.scheme) {
1484 anchorText = stripSchemePrefix(anchorText);
1485 }
1486 if (this.stripPrefix.www) {
1487 anchorText = stripWwwPrefix(anchorText);
1488 }
1489 if (this.stripTrailingSlash) {
1490 anchorText = removeTrailingSlash(anchorText); // remove trailing slash, if there is one
1491 }
1492 if (this.decodePercentEncoding) {
1493 anchorText = removePercentEncoding(anchorText);
1494 }
1495 return anchorText;
1496 };
1497 return UrlMatch;
1498 }(AbstractMatch));
1499 // Utility Functionality
1500 /**
1501 * Strips the scheme prefix (such as "http://" or "https://") from the given
1502 * `url`.
1503 *
1504 * @private
1505 * @param {String} url The text of the anchor that is being generated, for
1506 * which to strip off the url scheme.
1507 * @return {String} The `url`, with the scheme stripped.
1508 */
1509 function stripSchemePrefix(url) {
1510 return url.replace(httpSchemePrefixRe, '');
1511 }
1512 /**
1513 * Strips the 'www' prefix from the given `url`.
1514 *
1515 * @private
1516 * @param {String} url The text of the anchor that is being generated, for
1517 * which to strip off the 'www' if it exists.
1518 * @return {String} The `url`, with the 'www' stripped.
1519 */
1520 function stripWwwPrefix(url) {
1521 return url.replace(wwwPrefixRegex, '$1'); // leave any scheme ($1), it one exists
1522 }
1523 /**
1524 * Strips any protocol-relative '//' from the anchor text.
1525 *
1526 * @private
1527 * @param {String} text The text of the anchor that is being generated, for which to strip off the
1528 * protocol-relative prefix (such as stripping off "//")
1529 * @return {String} The `anchorText`, with the protocol-relative prefix stripped.
1530 */
1531 function stripProtocolRelativePrefix(text) {
1532 return text.replace(protocolRelativeRegex, '');
1533 }
1534 /**
1535 * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed.
1536 *
1537 * @private
1538 * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing
1539 * slash ('/') that may exist.
1540 * @return {String} The `anchorText`, with the trailing slash removed.
1541 */
1542 function removeTrailingSlash(anchorText) {
1543 if (anchorText.charAt(anchorText.length - 1) === '/') {
1544 anchorText = anchorText.slice(0, -1);
1545 }
1546 return anchorText;
1547 }
1548 /**
1549 * Decodes percent-encoded characters from the given `anchorText`, in
1550 * preparation for the text to be displayed.
1551 *
1552 * @private
1553 * @param {String} anchorText The text of the anchor that is being
1554 * generated, for which to decode any percent-encoded characters.
1555 * @return {String} The `anchorText`, with the percent-encoded characters
1556 * decoded.
1557 */
1558 function removePercentEncoding(anchorText) {
1559 // First, convert a few of the known % encodings to the corresponding
1560 // HTML entities that could accidentally be interpretted as special
1561 // HTML characters
1562 var preProcessedEntityAnchorText = anchorText
1563 .replace(/%22/gi, '&quot;') // " char
1564 .replace(/%26/gi, '&amp;') // & char
1565 .replace(/%27/gi, '&#39;') // ' char
1566 .replace(/%3C/gi, '&lt;') // < char
1567 .replace(/%3E/gi, '&gt;'); // > char
1568 try {
1569 // Now attempt to decode the rest of the anchor text
1570 return decodeURIComponent(preProcessedEntityAnchorText);
1571 }
1572 catch (e) {
1573 // Invalid % escape sequence in the anchor text
1574 return preProcessedEntityAnchorText;
1575 }
1576 }
1577
1578 /**
1579 * A regular expression to match a 'mailto:' prefix on an email address.
1580 */
1581 var mailtoSchemePrefixRe = /^mailto:/i;
1582 /**
1583 * Regular expression for all of the valid characters of the local part of an
1584 * email address.
1585 */
1586 var emailLocalPartCharRegex = new RegExp("[".concat(alphaNumericAndMarksCharsStr, "!#$%&'*+/=?^_`{|}~-]"));
1587 /**
1588 * Determines if the given character may start the "local part" of an email
1589 * address. The local part is the part to the left of the '@' sign.
1590 *
1591 * Technically according to the email spec, any of the characters in the
1592 * {@link emailLocalPartCharRegex} can start an email address (including any of
1593 * the special characters), but this is so rare in the wild and the
1594 * implementation is much simpler by only starting an email address with a word
1595 * character. This is especially important when matching the '{' character which
1596 * generally starts a brace that isn't part of the email address.
1597 */
1598 function isEmailLocalPartStartChar(char) {
1599 return alphaNumericAndMarksRe.test(char);
1600 }
1601 /**
1602 * Determines if the given character can be part of the "local part" of an email
1603 * address. The local part is the part to the left of the '@' sign.
1604 */
1605 function isEmailLocalPartChar(char) {
1606 return emailLocalPartCharRegex.test(char);
1607 }
1608 /**
1609 * Determines if the given email address is valid. We consider it valid if it
1610 * has a valid TLD in its host.
1611 *
1612 * @param emailAddress email address
1613 * @return true is email have valid TLD, false otherwise
1614 */
1615 function isValidEmail(emailAddress) {
1616 var emailAddressTld = emailAddress.split('.').pop() || '';
1617 return isKnownTld(emailAddressTld);
1618 }
1619
1620 /**
1621 * @class Autolinker.match.Email
1622 * @extends Autolinker.match.AbstractMatch
1623 *
1624 * Represents a Email match found in an input string which should be Autolinked.
1625 *
1626 * See this class's superclass ({@link Autolinker.match.Match}) for more details.
1627 */
1628 var EmailMatch = /** @class */ (function (_super) {
1629 __extends(EmailMatch, _super);
1630 /**
1631 * @method constructor
1632 * @param {Object} cfg The configuration properties for the Match
1633 * instance, specified in an Object (map).
1634 */
1635 function EmailMatch(cfg) {
1636 var _this = _super.call(this, cfg) || this;
1637 /**
1638 * @public
1639 * @property {'email'} type
1640 *
1641 * A string name for the type of match that this class represents. Can be
1642 * used in a TypeScript discriminating union to type-narrow from the
1643 * `Match` type.
1644 */
1645 _this.type = 'email';
1646 /**
1647 * @cfg {String} email (required)
1648 *
1649 * The email address that was matched.
1650 */
1651 _this.email = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
1652 _this.email = cfg.email;
1653 return _this;
1654 }
1655 /**
1656 * Returns a string name for the type of match that this class represents.
1657 * For the case of EmailMatch, returns 'email'.
1658 *
1659 * @return {String}
1660 */
1661 EmailMatch.prototype.getType = function () {
1662 return 'email';
1663 };
1664 /**
1665 * Returns the email address that was matched.
1666 *
1667 * @return {String}
1668 */
1669 EmailMatch.prototype.getEmail = function () {
1670 return this.email;
1671 };
1672 /**
1673 * Returns the anchor href that should be generated for the match.
1674 *
1675 * @return {String}
1676 */
1677 EmailMatch.prototype.getAnchorHref = function () {
1678 return 'mailto:' + this.email;
1679 };
1680 /**
1681 * Returns the anchor text that should be generated for the match.
1682 *
1683 * @return {String}
1684 */
1685 EmailMatch.prototype.getAnchorText = function () {
1686 return this.email;
1687 };
1688 return EmailMatch;
1689 }(AbstractMatch));
1690
1691 /**
1692 * Determines if the given `char` is a an allowed character in a hashtag. These
1693 * are underscores or any alphanumeric char.
1694 */
1695 function isHashtagTextChar(char) {
1696 return char === '_' || alphaNumericAndMarksRe.test(char);
1697 }
1698 /**
1699 * Determines if a hashtag match is valid.
1700 */
1701 function isValidHashtag(hashtag) {
1702 // Max length of 140 for a hashtag ('#' char + 139 word chars)
1703 return hashtag.length <= 140;
1704 }
1705 var hashtagServices = ['twitter', 'facebook', 'instagram', 'tiktok'];
1706
1707 /**
1708 * @class Autolinker.match.Hashtag
1709 * @extends Autolinker.match.AbstractMatch
1710 *
1711 * Represents a Hashtag match found in an input string which should be
1712 * Autolinked.
1713 *
1714 * See this class's superclass ({@link Autolinker.match.Match}) for more
1715 * details.
1716 */
1717 var HashtagMatch = /** @class */ (function (_super) {
1718 __extends(HashtagMatch, _super);
1719 /**
1720 * @method constructor
1721 * @param {Object} cfg The configuration properties for the Match
1722 * instance, specified in an Object (map).
1723 */
1724 function HashtagMatch(cfg) {
1725 var _this = _super.call(this, cfg) || this;
1726 /**
1727 * @public
1728 * @property {'hashtag'} type
1729 *
1730 * A string name for the type of match that this class represents. Can be
1731 * used in a TypeScript discriminating union to type-narrow from the
1732 * `Match` type.
1733 */
1734 _this.type = 'hashtag';
1735 /**
1736 * @cfg {String} serviceName
1737 *
1738 * The service to point hashtag matches to. See {@link Autolinker#hashtag}
1739 * for available values.
1740 */
1741 _this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator
1742 /**
1743 * @cfg {String} hashtag (required)
1744 *
1745 * The HashtagMatch that was matched, without the '#'.
1746 */
1747 _this.hashtag = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
1748 _this.serviceName = cfg.serviceName;
1749 _this.hashtag = cfg.hashtag;
1750 return _this;
1751 }
1752 /**
1753 * Returns a string name for the type of match that this class represents.
1754 * For the case of HashtagMatch, returns 'hashtag'.
1755 *
1756 * @return {String}
1757 */
1758 HashtagMatch.prototype.getType = function () {
1759 return 'hashtag';
1760 };
1761 /**
1762 * Returns the configured {@link #serviceName} to point the HashtagMatch to.
1763 * Ex: 'facebook', 'twitter'.
1764 *
1765 * @return {String}
1766 */
1767 HashtagMatch.prototype.getServiceName = function () {
1768 return this.serviceName;
1769 };
1770 /**
1771 * Returns the matched hashtag, without the '#' character.
1772 *
1773 * @return {String}
1774 */
1775 HashtagMatch.prototype.getHashtag = function () {
1776 return this.hashtag;
1777 };
1778 /**
1779 * Returns the anchor href that should be generated for the match.
1780 *
1781 * @return {String}
1782 */
1783 HashtagMatch.prototype.getAnchorHref = function () {
1784 var serviceName = this.serviceName, hashtag = this.hashtag;
1785 switch (serviceName) {
1786 case 'twitter':
1787 return 'https://twitter.com/hashtag/' + hashtag;
1788 case 'facebook':
1789 return 'https://www.facebook.com/hashtag/' + hashtag;
1790 case 'instagram':
1791 return 'https://instagram.com/explore/tags/' + hashtag;
1792 case 'tiktok':
1793 return 'https://www.tiktok.com/tag/' + hashtag;
1794 default:
1795 // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case
1796 assertNever(serviceName);
1797 throw new Error("Invalid hashtag service: ".concat(serviceName));
1798 }
1799 };
1800 /**
1801 * Returns the anchor text that should be generated for the match.
1802 *
1803 * @return {String}
1804 */
1805 HashtagMatch.prototype.getAnchorText = function () {
1806 return '#' + this.hashtag;
1807 };
1808 /**
1809 * Returns the CSS class suffixes that should be used on a tag built with
1810 * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for
1811 * details.
1812 *
1813 * @return {String[]}
1814 */
1815 HashtagMatch.prototype.getCssClassSuffixes = function () {
1816 var cssClassSuffixes = _super.prototype.getCssClassSuffixes.call(this), serviceName = this.getServiceName();
1817 if (serviceName) {
1818 cssClassSuffixes.push(serviceName);
1819 }
1820 return cssClassSuffixes;
1821 };
1822 return HashtagMatch;
1823 }(AbstractMatch));
1824
1825 var mentionRegexes = {
1826 twitter: /^@\w{1,15}$/,
1827 instagram: /^@[_\w]{1,30}$/,
1828 soundcloud: /^@[-a-z0-9_]{3,25}$/,
1829 // TikTok usernames are 1-24 characters containing letters, numbers, underscores
1830 // and periods, but cannot end in a period: https://support.tiktok.com/en/getting-started/setting-up-your-profile/changing-your-username
1831 tiktok: /^@[.\w]{1,23}[\w]$/,
1832 };
1833 // Regex that allows for all possible mention characters for any service. We'll
1834 // confirm the match based on the user-configured service name after a match is
1835 // found.
1836 var mentionTextCharRe = /[-\w.]/;
1837 /**
1838 * Determines if the given character can be part of a mention's text characters.
1839 */
1840 function isMentionTextChar(char) {
1841 return mentionTextCharRe.test(char);
1842 }
1843 /**
1844 * Determines if the given `mention` text is valid.
1845 */
1846 function isValidMention(mention, serviceName) {
1847 var re = mentionRegexes[serviceName];
1848 return re.test(mention);
1849 }
1850 var mentionServices = ['twitter', 'instagram', 'soundcloud', 'tiktok'];
1851
1852 /**
1853 * @class Autolinker.match.Mention
1854 * @extends Autolinker.match.AbstractMatch
1855 *
1856 * Represents a Mention match found in an input string which should be Autolinked.
1857 *
1858 * See this class's superclass ({@link Autolinker.match.Match}) for more details.
1859 */
1860 var MentionMatch = /** @class */ (function (_super) {
1861 __extends(MentionMatch, _super);
1862 /**
1863 * @method constructor
1864 * @param {Object} cfg The configuration properties for the Match
1865 * instance, specified in an Object (map).
1866 */
1867 function MentionMatch(cfg) {
1868 var _this = _super.call(this, cfg) || this;
1869 /**
1870 * @public
1871 * @property {'mention'} type
1872 *
1873 * A string name for the type of match that this class represents. Can be
1874 * used in a TypeScript discriminating union to type-narrow from the
1875 * `Match` type.
1876 */
1877 _this.type = 'mention';
1878 /**
1879 * @cfg {String} serviceName
1880 *
1881 * The service to point mention matches to. See {@link Autolinker#mention}
1882 * for available values.
1883 */
1884 _this.serviceName = 'twitter'; // default value just to get the above doc comment in the ES5 output and documentation generator
1885 /**
1886 * @cfg {String} mention (required)
1887 *
1888 * The Mention that was matched, without the '@' character.
1889 */
1890 _this.mention = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
1891 _this.mention = cfg.mention;
1892 _this.serviceName = cfg.serviceName;
1893 return _this;
1894 }
1895 /**
1896 * Returns a string name for the type of match that this class represents.
1897 * For the case of MentionMatch, returns 'mention'.
1898 *
1899 * @return {String}
1900 */
1901 MentionMatch.prototype.getType = function () {
1902 return 'mention';
1903 };
1904 /**
1905 * Returns the mention, without the '@' character.
1906 *
1907 * @return {String}
1908 */
1909 MentionMatch.prototype.getMention = function () {
1910 return this.mention;
1911 };
1912 /**
1913 * Returns the configured {@link #serviceName} to point the mention to.
1914 * Ex: 'instagram', 'twitter', 'soundcloud'.
1915 *
1916 * @return {String}
1917 */
1918 MentionMatch.prototype.getServiceName = function () {
1919 return this.serviceName;
1920 };
1921 /**
1922 * Returns the anchor href that should be generated for the match.
1923 *
1924 * @return {String}
1925 */
1926 MentionMatch.prototype.getAnchorHref = function () {
1927 switch (this.serviceName) {
1928 case 'twitter':
1929 return 'https://twitter.com/' + this.mention;
1930 case 'instagram':
1931 return 'https://instagram.com/' + this.mention;
1932 case 'soundcloud':
1933 return 'https://soundcloud.com/' + this.mention;
1934 case 'tiktok':
1935 return 'https://www.tiktok.com/@' + this.mention;
1936 default:
1937 // Shouldn't happen because Autolinker's constructor should block any invalid values, but just in case.
1938 throw new Error('Unknown service name to point mention to: ' + this.serviceName);
1939 }
1940 };
1941 /**
1942 * Returns the anchor text that should be generated for the match.
1943 *
1944 * @return {String}
1945 */
1946 MentionMatch.prototype.getAnchorText = function () {
1947 return '@' + this.mention;
1948 };
1949 /**
1950 * Returns the CSS class suffixes that should be used on a tag built with
1951 * the match. See {@link Autolinker.match.Match#getCssClassSuffixes} for
1952 * details.
1953 *
1954 * @return {String[]}
1955 */
1956 MentionMatch.prototype.getCssClassSuffixes = function () {
1957 var cssClassSuffixes = _super.prototype.getCssClassSuffixes.call(this), serviceName = this.getServiceName();
1958 if (serviceName) {
1959 cssClassSuffixes.push(serviceName);
1960 }
1961 return cssClassSuffixes;
1962 };
1963 return MentionMatch;
1964 }(AbstractMatch));
1965
1966 // Regex that holds the characters used to separate segments of a phone number
1967 var separatorCharRe = /[-. ]/;
1968 // Regex that specifies any delimiter char that allows us to treat the number as
1969 // a phone number rather than just any other number that could appear in text.
1970 var hasDelimCharsRe = /[-. ()]/;
1971 // "Pause" and "Wait" control chars
1972 var controlCharRe = /[,;]/;
1973 // Over the years, many people have added to this regex, but it should have been
1974 // split up by country. Maybe one day we can break this down.
1975 var mostPhoneNumbers = /(?:(?:(?:(\+)?\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]+#?)*/;
1976 // Regex for Japanese phone numbers
1977 var japanesePhoneRe = /(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})/;
1978 // Combined regex
1979 var validPhoneNumberRe = new RegExp("^".concat(mostPhoneNumbers.source, "|").concat(japanesePhoneRe.source, "$"));
1980 /**
1981 * Determines if the character is a phone number separator character (i.e.
1982 * '-', '.', or ' ' (space))
1983 */
1984 function isPhoneNumberSeparatorChar(char) {
1985 return separatorCharRe.test(char);
1986 }
1987 /**
1988 * Determines if the character is a control character in a phone number. Control
1989 * characters are as follows:
1990 *
1991 * - ',': A 1 second pause. Useful for dialing extensions once the main phone number has been reached
1992 * - ';': A "wait" that waits for the user to take action (tap something, for instance on a smart phone)
1993 */
1994 function isPhoneNumberControlChar(char) {
1995 return controlCharRe.test(char);
1996 }
1997 /**
1998 * Determines if the given phone number text found in a string is a valid phone
1999 * number.
2000 *
2001 * Our state machine parser is simplified to grab anything that looks like a
2002 * phone number, and this function confirms the match.
2003 */
2004 function isValidPhoneNumber(phoneNumberText) {
2005 // We'll only consider the match as a phone number if there is some kind of
2006 // delimiter character (a prefixed '+' sign, or separator chars).
2007 //
2008 // Accepts:
2009 // (123) 456-7890
2010 // +38755233976
2011 // Does not accept:
2012 // 1234567890 (no delimiter chars - may just be a random number that's not a phone number)
2013 var hasDelimiters = phoneNumberText.charAt(0) === '+' || hasDelimCharsRe.test(phoneNumberText);
2014 return hasDelimiters && validPhoneNumberRe.test(phoneNumberText);
2015 }
2016
2017 /**
2018 * @class Autolinker.match.Phone
2019 * @extends Autolinker.match.AbstractMatch
2020 *
2021 * Represents a Phone number match found in an input string which should be
2022 * Autolinked.
2023 *
2024 * See this class's superclass ({@link Autolinker.match.Match}) for more
2025 * details.
2026 */
2027 var PhoneMatch = /** @class */ (function (_super) {
2028 __extends(PhoneMatch, _super);
2029 /**
2030 * @method constructor
2031 * @param {Object} cfg The configuration properties for the Match
2032 * instance, specified in an Object (map).
2033 */
2034 function PhoneMatch(cfg) {
2035 var _this = _super.call(this, cfg) || this;
2036 /**
2037 * @public
2038 * @property {'phone'} type
2039 *
2040 * A string name for the type of match that this class represents. Can be
2041 * used in a TypeScript discriminating union to type-narrow from the
2042 * `Match` type.
2043 */
2044 _this.type = 'phone';
2045 /**
2046 * @protected
2047 * @property {String} number (required)
2048 *
2049 * The phone number that was matched, without any delimiter characters.
2050 *
2051 * Note: This is a string to allow for prefixed 0's.
2052 */
2053 _this.number = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
2054 /**
2055 * @protected
2056 * @property {Boolean} plusSign (required)
2057 *
2058 * `true` if the matched phone number started with a '+' sign. We'll include
2059 * it in the `tel:` URL if so, as this is needed for international numbers.
2060 *
2061 * Ex: '+1 (123) 456 7879'
2062 */
2063 _this.plusSign = false; // default value just to get the above doc comment in the ES5 output and documentation generator
2064 _this.number = cfg.number;
2065 _this.plusSign = cfg.plusSign;
2066 return _this;
2067 }
2068 /**
2069 * Returns a string name for the type of match that this class represents.
2070 * For the case of PhoneMatch, returns 'phone'.
2071 *
2072 * @return {String}
2073 */
2074 PhoneMatch.prototype.getType = function () {
2075 return 'phone';
2076 };
2077 /**
2078 * Returns the phone number that was matched as a string, without any
2079 * delimiter characters.
2080 *
2081 * Note: This is a string to allow for prefixed 0's.
2082 *
2083 * @return {String}
2084 */
2085 PhoneMatch.prototype.getPhoneNumber = function () {
2086 return this.number;
2087 };
2088 /**
2089 * Alias of {@link #getPhoneNumber}, returns the phone number that was
2090 * matched as a string, without any delimiter characters.
2091 *
2092 * Note: This is a string to allow for prefixed 0's.
2093 *
2094 * @return {String}
2095 */
2096 PhoneMatch.prototype.getNumber = function () {
2097 return this.getPhoneNumber();
2098 };
2099 /**
2100 * Returns the anchor href that should be generated for the match.
2101 *
2102 * @return {String}
2103 */
2104 PhoneMatch.prototype.getAnchorHref = function () {
2105 return 'tel:' + (this.plusSign ? '+' : '') + this.number;
2106 };
2107 /**
2108 * Returns the anchor text that should be generated for the match.
2109 *
2110 * @return {String}
2111 */
2112 PhoneMatch.prototype.getAnchorText = function () {
2113 return this.matchedText;
2114 };
2115 return PhoneMatch;
2116 }(AbstractMatch));
2117
2118 // For debugging: search for and uncomment other "For debugging" lines
2119 // import CliTable from 'cli-table';
2120 /**
2121 * Parses URL, email, twitter, mention, and hashtag matches from the given
2122 * `text`.
2123 */
2124 function parseMatches(text, args) {
2125 var tagBuilder = args.tagBuilder;
2126 var stripPrefix = args.stripPrefix;
2127 var stripTrailingSlash = args.stripTrailingSlash;
2128 var decodePercentEncoding = args.decodePercentEncoding;
2129 var hashtagServiceName = args.hashtagServiceName;
2130 var mentionServiceName = args.mentionServiceName;
2131 var matches = [];
2132 var textLen = text.length;
2133 // An array of all active state machines. Empty array means we're in the
2134 // "no url" state
2135 var stateMachines = [];
2136 // For debugging: search for and uncomment other "For debugging" lines
2137 // const table = new CliTable({
2138 // head: ['charIdx', 'char', 'states', 'charIdx', 'startIdx', 'reached accept state'],
2139 // });
2140 var charIdx = 0;
2141 for (; charIdx < textLen; charIdx++) {
2142 var char = text.charAt(charIdx);
2143 if (stateMachines.length === 0) {
2144 stateNoMatch(char);
2145 }
2146 else {
2147 // Must loop through the state machines backwards for when one
2148 // is removed
2149 for (var stateIdx = stateMachines.length - 1; stateIdx >= 0; stateIdx--) {
2150 var stateMachine = stateMachines[stateIdx];
2151 switch (stateMachine.state) {
2152 // Protocol-relative URL states
2153 case 11 /* ProtocolRelativeSlash1 */:
2154 stateProtocolRelativeSlash1(stateMachine, char);
2155 break;
2156 case 12 /* ProtocolRelativeSlash2 */:
2157 stateProtocolRelativeSlash2(stateMachine, char);
2158 break;
2159 case 0 /* SchemeChar */:
2160 stateSchemeChar(stateMachine, char);
2161 break;
2162 case 1 /* SchemeHyphen */:
2163 stateSchemeHyphen(stateMachine, char);
2164 break;
2165 case 2 /* SchemeColon */:
2166 stateSchemeColon(stateMachine, char);
2167 break;
2168 case 3 /* SchemeSlash1 */:
2169 stateSchemeSlash1(stateMachine, char);
2170 break;
2171 case 4 /* SchemeSlash2 */:
2172 stateSchemeSlash2(stateMachine, char);
2173 break;
2174 case 5 /* DomainLabelChar */:
2175 stateDomainLabelChar(stateMachine, char);
2176 break;
2177 case 6 /* DomainHyphen */:
2178 stateDomainHyphen(stateMachine, char);
2179 break;
2180 case 7 /* DomainDot */:
2181 stateDomainDot(stateMachine, char);
2182 break;
2183 case 13 /* IpV4Digit */:
2184 stateIpV4Digit(stateMachine, char);
2185 break;
2186 case 14 /* IpV4Dot */:
2187 stateIPv4Dot(stateMachine, char);
2188 break;
2189 case 8 /* PortColon */:
2190 statePortColon(stateMachine, char);
2191 break;
2192 case 9 /* PortNumber */:
2193 statePortNumber(stateMachine, char);
2194 break;
2195 case 10 /* Path */:
2196 statePath(stateMachine, char);
2197 break;
2198 // Email States
2199 case 15 /* EmailMailto_M */:
2200 stateEmailMailto_M(stateMachine, char);
2201 break;
2202 case 16 /* EmailMailto_A */:
2203 stateEmailMailto_A(stateMachine, char);
2204 break;
2205 case 17 /* EmailMailto_I */:
2206 stateEmailMailto_I(stateMachine, char);
2207 break;
2208 case 18 /* EmailMailto_L */:
2209 stateEmailMailto_L(stateMachine, char);
2210 break;
2211 case 19 /* EmailMailto_T */:
2212 stateEmailMailto_T(stateMachine, char);
2213 break;
2214 case 20 /* EmailMailto_O */:
2215 stateEmailMailto_O(stateMachine, char);
2216 break;
2217 case 21 /* EmailMailto_Colon */:
2218 stateEmailMailtoColon(stateMachine, char);
2219 break;
2220 case 22 /* EmailLocalPart */:
2221 stateEmailLocalPart(stateMachine, char);
2222 break;
2223 case 23 /* EmailLocalPartDot */:
2224 stateEmailLocalPartDot(stateMachine, char);
2225 break;
2226 case 24 /* EmailAtSign */:
2227 stateEmailAtSign(stateMachine, char);
2228 break;
2229 case 25 /* EmailDomainChar */:
2230 stateEmailDomainChar(stateMachine, char);
2231 break;
2232 case 26 /* EmailDomainHyphen */:
2233 stateEmailDomainHyphen(stateMachine, char);
2234 break;
2235 case 27 /* EmailDomainDot */:
2236 stateEmailDomainDot(stateMachine, char);
2237 break;
2238 // Hashtag states
2239 case 28 /* HashtagHashChar */:
2240 stateHashtagHashChar(stateMachine, char);
2241 break;
2242 case 29 /* HashtagTextChar */:
2243 stateHashtagTextChar(stateMachine, char);
2244 break;
2245 // Mention states
2246 case 30 /* MentionAtChar */:
2247 stateMentionAtChar(stateMachine, char);
2248 break;
2249 case 31 /* MentionTextChar */:
2250 stateMentionTextChar(stateMachine, char);
2251 break;
2252 // Phone number states
2253 case 32 /* PhoneNumberOpenParen */:
2254 statePhoneNumberOpenParen(stateMachine, char);
2255 break;
2256 case 33 /* PhoneNumberAreaCodeDigit1 */:
2257 statePhoneNumberAreaCodeDigit1(stateMachine, char);
2258 break;
2259 case 34 /* PhoneNumberAreaCodeDigit2 */:
2260 statePhoneNumberAreaCodeDigit2(stateMachine, char);
2261 break;
2262 case 35 /* PhoneNumberAreaCodeDigit3 */:
2263 statePhoneNumberAreaCodeDigit3(stateMachine, char);
2264 break;
2265 case 36 /* PhoneNumberCloseParen */:
2266 statePhoneNumberCloseParen(stateMachine, char);
2267 break;
2268 case 37 /* PhoneNumberPlus */:
2269 statePhoneNumberPlus(stateMachine, char);
2270 break;
2271 case 38 /* PhoneNumberDigit */:
2272 statePhoneNumberDigit(stateMachine, char);
2273 break;
2274 case 39 /* PhoneNumberSeparator */:
2275 statePhoneNumberSeparator(stateMachine, char);
2276 break;
2277 case 40 /* PhoneNumberControlChar */:
2278 statePhoneNumberControlChar(stateMachine, char);
2279 break;
2280 case 41 /* PhoneNumberPoundChar */:
2281 statePhoneNumberPoundChar(stateMachine, char);
2282 break;
2283 default:
2284 assertNever(stateMachine.state);
2285 }
2286 }
2287 }
2288 // For debugging: search for and uncomment other "For debugging" lines
2289 // table.push([
2290 // charIdx,
2291 // char,
2292 // stateMachines.map(machine => State[machine.state]).join('\n') || '(none)',
2293 // charIdx,
2294 // stateMachines.map(m => m.startIdx).join('\n'),
2295 // stateMachines.map(m => m.acceptStateReached).join('\n'),
2296 // ]);
2297 }
2298 // Capture any valid match at the end of the string
2299 // Note: this loop must happen in reverse because
2300 // captureMatchIfValidAndRemove() removes state machines from the array
2301 // and we'll end up skipping every other one if we remove while looping
2302 // forward
2303 for (var i = stateMachines.length - 1; i >= 0; i--) {
2304 stateMachines.forEach(function (stateMachine) { return captureMatchIfValidAndRemove(stateMachine); });
2305 }
2306 // For debugging: search for and uncomment other "For debugging" lines
2307 // console.log(`\nRead string:\n ${text}`);
2308 // console.log(table.toString());
2309 return matches;
2310 // Handles the state when we're not in a URL/email/etc. (i.e. when no state machines exist)
2311 function stateNoMatch(char) {
2312 if (char === '#') {
2313 // Hash char, start a Hashtag match
2314 stateMachines.push(createHashtagStateMachine(charIdx, 28 /* HashtagHashChar */));
2315 }
2316 else if (char === '@') {
2317 // '@' char, start a Mention match
2318 stateMachines.push(createMentionStateMachine(charIdx, 30 /* MentionAtChar */));
2319 }
2320 else if (char === '/') {
2321 // A slash could begin a protocol-relative URL
2322 stateMachines.push(createTldUrlStateMachine(charIdx, 11 /* ProtocolRelativeSlash1 */));
2323 }
2324 else if (char === '+') {
2325 // A '+' char can start a Phone number
2326 stateMachines.push(createPhoneNumberStateMachine(charIdx, 37 /* PhoneNumberPlus */));
2327 }
2328 else if (char === '(') {
2329 stateMachines.push(createPhoneNumberStateMachine(charIdx, 32 /* PhoneNumberOpenParen */));
2330 }
2331 else {
2332 if (digitRe.test(char)) {
2333 // A digit could start a phone number
2334 stateMachines.push(createPhoneNumberStateMachine(charIdx, 38 /* PhoneNumberDigit */));
2335 // A digit could start an IP address
2336 stateMachines.push(createIpV4UrlStateMachine(charIdx, 13 /* IpV4Digit */));
2337 }
2338 if (isEmailLocalPartStartChar(char)) {
2339 // Any email local part. An 'm' character in particular could
2340 // start a 'mailto:' match
2341 var startState = char.toLowerCase() === 'm' ? 15 /* EmailMailto_M */ : 22 /* EmailLocalPart */;
2342 stateMachines.push(createEmailStateMachine(charIdx, startState));
2343 }
2344 if (isSchemeStartChar(char)) {
2345 // An uppercase or lowercase letter may start a scheme match
2346 stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
2347 }
2348 if (alphaNumericAndMarksRe.test(char)) {
2349 // A unicode alpha character or digit could start a domain name
2350 // label for a TLD match
2351 stateMachines.push(createTldUrlStateMachine(charIdx, 5 /* DomainLabelChar */));
2352 }
2353 }
2354 // Anything else, remain in the "non-url" state by not creating any
2355 // state machines
2356 }
2357 // Implements ABNF: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
2358 function stateSchemeChar(stateMachine, char) {
2359 if (char === ':') {
2360 stateMachine.state = 2 /* SchemeColon */;
2361 }
2362 else if (char === '-') {
2363 stateMachine.state = 1 /* SchemeHyphen */;
2364 }
2365 else if (isSchemeChar(char)) ;
2366 else {
2367 // Any other character, not a scheme
2368 remove(stateMachines, stateMachine);
2369 }
2370 }
2371 function stateSchemeHyphen(stateMachine, char) {
2372 if (char === '-') ;
2373 else if (char === '/') {
2374 // Not a valid scheme match, but may be the start of a
2375 // protocol-relative match (such as //google.com)
2376 remove(stateMachines, stateMachine);
2377 stateMachines.push(createTldUrlStateMachine(charIdx, 11 /* ProtocolRelativeSlash1 */));
2378 }
2379 else if (isSchemeChar(char)) {
2380 stateMachine.state = 0 /* SchemeChar */;
2381 }
2382 else {
2383 // Any other character, not a scheme
2384 remove(stateMachines, stateMachine);
2385 }
2386 }
2387 function stateSchemeColon(stateMachine, char) {
2388 if (char === '/') {
2389 stateMachine.state = 3 /* SchemeSlash1 */;
2390 }
2391 else if (char === '.') {
2392 // We've read something like 'hello:.' - don't capture
2393 remove(stateMachines, stateMachine);
2394 }
2395 else if (isDomainLabelStartChar(char)) {
2396 stateMachine.state = 5 /* DomainLabelChar */;
2397 // It's possible that we read an "introduction" piece of text,
2398 // and the character after the current colon actually starts an
2399 // actual scheme. An example of this is:
2400 // "The link:http://google.com"
2401 // Hence, start a new machine to capture this match if so
2402 if (isSchemeStartChar(char)) {
2403 stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
2404 }
2405 }
2406 else {
2407 remove(stateMachines, stateMachine);
2408 }
2409 }
2410 function stateSchemeSlash1(stateMachine, char) {
2411 if (char === '/') {
2412 stateMachine.state = 4 /* SchemeSlash2 */;
2413 }
2414 else if (isPathChar(char)) {
2415 stateMachine.state = 10 /* Path */;
2416 stateMachine.acceptStateReached = true;
2417 }
2418 else {
2419 captureMatchIfValidAndRemove(stateMachine);
2420 }
2421 }
2422 function stateSchemeSlash2(stateMachine, char) {
2423 if (char === '/') {
2424 // 3rd slash, must be an absolute path (path-absolute in the
2425 // ABNF), such as in a file:///c:/windows/etc. See
2426 // https://tools.ietf.org/html/rfc3986#appendix-A
2427 stateMachine.state = 10 /* Path */;
2428 }
2429 else if (isDomainLabelStartChar(char)) {
2430 // start of "authority" section - see https://tools.ietf.org/html/rfc3986#appendix-A
2431 stateMachine.state = 5 /* DomainLabelChar */;
2432 stateMachine.acceptStateReached = true;
2433 }
2434 else {
2435 // not valid
2436 remove(stateMachines, stateMachine);
2437 }
2438 }
2439 // Handles reading a '/' from the NonUrl state
2440 function stateProtocolRelativeSlash1(stateMachine, char) {
2441 if (char === '/') {
2442 stateMachine.state = 12 /* ProtocolRelativeSlash2 */;
2443 }
2444 else {
2445 // Anything else, cannot be the start of a protocol-relative
2446 // URL.
2447 remove(stateMachines, stateMachine);
2448 }
2449 }
2450 // Handles reading a second '/', which could start a protocol-relative URL
2451 function stateProtocolRelativeSlash2(stateMachine, char) {
2452 if (isDomainLabelStartChar(char)) {
2453 stateMachine.state = 5 /* DomainLabelChar */;
2454 }
2455 else {
2456 // Anything else, not a URL
2457 remove(stateMachines, stateMachine);
2458 }
2459 }
2460 // Handles when we have read a domain label character
2461 function stateDomainLabelChar(stateMachine, char) {
2462 if (char === '.') {
2463 stateMachine.state = 7 /* DomainDot */;
2464 }
2465 else if (char === '-') {
2466 stateMachine.state = 6 /* DomainHyphen */;
2467 }
2468 else if (char === ':') {
2469 // Beginning of a port number, end the domain name
2470 stateMachine.state = 8 /* PortColon */;
2471 }
2472 else if (isUrlSuffixStartChar(char)) {
2473 // '/', '?', or '#'
2474 stateMachine.state = 10 /* Path */;
2475 }
2476 else if (isDomainLabelChar(char)) ;
2477 else {
2478 // Anything else, end the domain name
2479 captureMatchIfValidAndRemove(stateMachine);
2480 }
2481 }
2482 function stateDomainHyphen(stateMachine, char) {
2483 if (char === '-') ;
2484 else if (char === '.') {
2485 // Not valid to have a '-.' in a domain label
2486 captureMatchIfValidAndRemove(stateMachine);
2487 }
2488 else if (isDomainLabelStartChar(char)) {
2489 stateMachine.state = 5 /* DomainLabelChar */;
2490 }
2491 else {
2492 captureMatchIfValidAndRemove(stateMachine);
2493 }
2494 }
2495 function stateDomainDot(stateMachine, char) {
2496 if (char === '.') {
2497 // domain names cannot have multiple '.'s next to each other.
2498 // It's possible we've already read a valid domain name though,
2499 // and that the '..' sequence just forms an ellipsis at the end
2500 // of a sentence
2501 captureMatchIfValidAndRemove(stateMachine);
2502 }
2503 else if (isDomainLabelStartChar(char)) {
2504 stateMachine.state = 5 /* DomainLabelChar */;
2505 stateMachine.acceptStateReached = true; // after hitting a dot, and then another domain label, we've reached an accept state
2506 }
2507 else {
2508 // Anything else, end the domain name
2509 captureMatchIfValidAndRemove(stateMachine);
2510 }
2511 }
2512 function stateIpV4Digit(stateMachine, char) {
2513 if (char === '.') {
2514 stateMachine.state = 14 /* IpV4Dot */;
2515 }
2516 else if (char === ':') {
2517 // Beginning of a port number
2518 stateMachine.state = 8 /* PortColon */;
2519 }
2520 else if (digitRe.test(char)) ;
2521 else if (isUrlSuffixStartChar(char)) {
2522 stateMachine.state = 10 /* Path */;
2523 }
2524 else if (alphaNumericAndMarksRe.test(char)) {
2525 // If we hit an alpha character, must not be an IPv4
2526 // Example of this: 1.2.3.4abc
2527 remove(stateMachines, stateMachine);
2528 }
2529 else {
2530 captureMatchIfValidAndRemove(stateMachine);
2531 }
2532 }
2533 function stateIPv4Dot(stateMachine, char) {
2534 if (digitRe.test(char)) {
2535 stateMachine.octetsEncountered++;
2536 // Once we have encountered 4 octets, it's *potentially* a valid
2537 // IPv4 address. Our IPv4 regex will confirm the match later
2538 // though to make sure each octet is in the 0-255 range, and
2539 // there's exactly 4 octets (not 5 or more)
2540 if (stateMachine.octetsEncountered === 4) {
2541 stateMachine.acceptStateReached = true;
2542 }
2543 stateMachine.state = 13 /* IpV4Digit */;
2544 }
2545 else {
2546 captureMatchIfValidAndRemove(stateMachine);
2547 }
2548 }
2549 function statePortColon(stateMachine, char) {
2550 if (digitRe.test(char)) {
2551 stateMachine.state = 9 /* PortNumber */;
2552 }
2553 else {
2554 captureMatchIfValidAndRemove(stateMachine);
2555 }
2556 }
2557 function statePortNumber(stateMachine, char) {
2558 if (digitRe.test(char)) ;
2559 else if (isUrlSuffixStartChar(char)) {
2560 // '/', '?', or '#'
2561 stateMachine.state = 10 /* Path */;
2562 }
2563 else {
2564 captureMatchIfValidAndRemove(stateMachine);
2565 }
2566 }
2567 function statePath(stateMachine, char) {
2568 if (isPathChar(char)) ;
2569 else {
2570 captureMatchIfValidAndRemove(stateMachine);
2571 }
2572 }
2573 // Handles if we're reading a 'mailto:' prefix on the string
2574 function stateEmailMailto_M(stateMachine, char) {
2575 if (char.toLowerCase() === 'a') {
2576 stateMachine.state = 16 /* EmailMailto_A */;
2577 }
2578 else {
2579 stateEmailLocalPart(stateMachine, char);
2580 }
2581 }
2582 function stateEmailMailto_A(stateMachine, char) {
2583 if (char.toLowerCase() === 'i') {
2584 stateMachine.state = 17 /* EmailMailto_I */;
2585 }
2586 else {
2587 stateEmailLocalPart(stateMachine, char);
2588 }
2589 }
2590 function stateEmailMailto_I(stateMachine, char) {
2591 if (char.toLowerCase() === 'l') {
2592 stateMachine.state = 18 /* EmailMailto_L */;
2593 }
2594 else {
2595 stateEmailLocalPart(stateMachine, char);
2596 }
2597 }
2598 function stateEmailMailto_L(stateMachine, char) {
2599 if (char.toLowerCase() === 't') {
2600 stateMachine.state = 19 /* EmailMailto_T */;
2601 }
2602 else {
2603 stateEmailLocalPart(stateMachine, char);
2604 }
2605 }
2606 function stateEmailMailto_T(stateMachine, char) {
2607 if (char.toLowerCase() === 'o') {
2608 stateMachine.state = 20 /* EmailMailto_O */;
2609 }
2610 else {
2611 stateEmailLocalPart(stateMachine, char);
2612 }
2613 }
2614 function stateEmailMailto_O(stateMachine, char) {
2615 if (char.toLowerCase() === ':') {
2616 stateMachine.state = 21 /* EmailMailto_Colon */;
2617 }
2618 else {
2619 stateEmailLocalPart(stateMachine, char);
2620 }
2621 }
2622 function stateEmailMailtoColon(stateMachine, char) {
2623 if (isEmailLocalPartChar(char)) {
2624 stateMachine.state = 22 /* EmailLocalPart */;
2625 }
2626 else {
2627 remove(stateMachines, stateMachine);
2628 }
2629 }
2630 // Handles the state when we're currently in the "local part" of an
2631 // email address (as opposed to the "domain part")
2632 function stateEmailLocalPart(stateMachine, char) {
2633 if (char === '.') {
2634 stateMachine.state = 23 /* EmailLocalPartDot */;
2635 }
2636 else if (char === '@') {
2637 stateMachine.state = 24 /* EmailAtSign */;
2638 }
2639 else if (isEmailLocalPartChar(char)) {
2640 // stay in the "local part" of the email address
2641 // Note: because stateEmailLocalPart() is called from the
2642 // 'mailto' states (when the 'mailto' prefix itself has been
2643 // broken), make sure to set the state to EmailLocalPart
2644 stateMachine.state = 22 /* EmailLocalPart */;
2645 }
2646 else {
2647 // not an email address character
2648 remove(stateMachines, stateMachine);
2649 }
2650 }
2651 // Handles the state where we've read
2652 function stateEmailLocalPartDot(stateMachine, char) {
2653 if (char === '.') {
2654 // We read a second '.' in a row, not a valid email address
2655 // local part
2656 remove(stateMachines, stateMachine);
2657 }
2658 else if (char === '@') {
2659 // We read the '@' character immediately after a dot ('.'), not
2660 // an email address
2661 remove(stateMachines, stateMachine);
2662 }
2663 else if (isEmailLocalPartChar(char)) {
2664 stateMachine.state = 22 /* EmailLocalPart */;
2665 }
2666 else {
2667 // Anything else, not an email address
2668 remove(stateMachines, stateMachine);
2669 }
2670 }
2671 function stateEmailAtSign(stateMachine, char) {
2672 if (isDomainLabelStartChar(char)) {
2673 stateMachine.state = 25 /* EmailDomainChar */;
2674 }
2675 else {
2676 // Anything else, not an email address
2677 remove(stateMachines, stateMachine);
2678 }
2679 }
2680 function stateEmailDomainChar(stateMachine, char) {
2681 if (char === '.') {
2682 stateMachine.state = 27 /* EmailDomainDot */;
2683 }
2684 else if (char === '-') {
2685 stateMachine.state = 26 /* EmailDomainHyphen */;
2686 }
2687 else if (isDomainLabelChar(char)) ;
2688 else {
2689 // Anything else, we potentially matched if the criteria has
2690 // been met
2691 captureMatchIfValidAndRemove(stateMachine);
2692 }
2693 }
2694 function stateEmailDomainHyphen(stateMachine, char) {
2695 if (char === '-' || char === '.') {
2696 // Not valid to have two hyphens ("--") or hypen+dot ("-.")
2697 captureMatchIfValidAndRemove(stateMachine);
2698 }
2699 else if (isDomainLabelChar(char)) {
2700 stateMachine.state = 25 /* EmailDomainChar */;
2701 }
2702 else {
2703 // Anything else
2704 captureMatchIfValidAndRemove(stateMachine);
2705 }
2706 }
2707 function stateEmailDomainDot(stateMachine, char) {
2708 if (char === '.' || char === '-') {
2709 // not valid to have two dots ("..") or dot+hypen (".-")
2710 captureMatchIfValidAndRemove(stateMachine);
2711 }
2712 else if (isDomainLabelStartChar(char)) {
2713 stateMachine.state = 25 /* EmailDomainChar */;
2714 // After having read a '.' and then a valid domain character,
2715 // we now know that the domain part of the email is valid, and
2716 // we have found at least a partial EmailMatch (however, the
2717 // email address may have additional characters from this point)
2718 stateMachine.acceptStateReached = true;
2719 }
2720 else {
2721 // Anything else
2722 captureMatchIfValidAndRemove(stateMachine);
2723 }
2724 }
2725 // Handles the state when we've just encountered a '#' character
2726 function stateHashtagHashChar(stateMachine, char) {
2727 if (isHashtagTextChar(char)) {
2728 // '#' char with valid hash text char following
2729 stateMachine.state = 29 /* HashtagTextChar */;
2730 stateMachine.acceptStateReached = true;
2731 }
2732 else {
2733 remove(stateMachines, stateMachine);
2734 }
2735 }
2736 // Handles the state when we're currently in the hash tag's text chars
2737 function stateHashtagTextChar(stateMachine, char) {
2738 if (isHashtagTextChar(char)) ;
2739 else {
2740 captureMatchIfValidAndRemove(stateMachine);
2741 }
2742 }
2743 // Handles the state when we've just encountered a '@' character
2744 function stateMentionAtChar(stateMachine, char) {
2745 if (isMentionTextChar(char)) {
2746 // '@' char with valid mention text char following
2747 stateMachine.state = 31 /* MentionTextChar */;
2748 stateMachine.acceptStateReached = true;
2749 }
2750 else {
2751 remove(stateMachines, stateMachine);
2752 }
2753 }
2754 // Handles the state when we're currently in the mention's text chars
2755 function stateMentionTextChar(stateMachine, char) {
2756 if (isMentionTextChar(char)) ;
2757 else if (alphaNumericAndMarksRe.test(char)) {
2758 // Char is invalid for a mention text char, not a valid match.
2759 // Note that ascii alphanumeric chars are okay (which are tested
2760 // in the previous 'if' statement, but others are not)
2761 remove(stateMachines, stateMachine);
2762 }
2763 else {
2764 captureMatchIfValidAndRemove(stateMachine);
2765 }
2766 }
2767 function statePhoneNumberPlus(stateMachine, char) {
2768 if (digitRe.test(char)) {
2769 stateMachine.state = 38 /* PhoneNumberDigit */;
2770 }
2771 else {
2772 remove(stateMachines, stateMachine);
2773 // This character may start a new match. Add states for it
2774 stateNoMatch(char);
2775 }
2776 }
2777 function statePhoneNumberOpenParen(stateMachine, char) {
2778 if (digitRe.test(char)) {
2779 stateMachine.state = 33 /* PhoneNumberAreaCodeDigit1 */;
2780 }
2781 else {
2782 remove(stateMachines, stateMachine);
2783 }
2784 // It's also possible that the paren was just an open brace for
2785 // a piece of text. Start other machines
2786 stateNoMatch(char);
2787 }
2788 function statePhoneNumberAreaCodeDigit1(stateMachine, char) {
2789 if (digitRe.test(char)) {
2790 stateMachine.state = 34 /* PhoneNumberAreaCodeDigit2 */;
2791 }
2792 else {
2793 remove(stateMachines, stateMachine);
2794 }
2795 }
2796 function statePhoneNumberAreaCodeDigit2(stateMachine, char) {
2797 if (digitRe.test(char)) {
2798 stateMachine.state = 35 /* PhoneNumberAreaCodeDigit3 */;
2799 }
2800 else {
2801 remove(stateMachines, stateMachine);
2802 }
2803 }
2804 function statePhoneNumberAreaCodeDigit3(stateMachine, char) {
2805 if (char === ')') {
2806 stateMachine.state = 36 /* PhoneNumberCloseParen */;
2807 }
2808 else {
2809 remove(stateMachines, stateMachine);
2810 }
2811 }
2812 function statePhoneNumberCloseParen(stateMachine, char) {
2813 if (digitRe.test(char)) {
2814 stateMachine.state = 38 /* PhoneNumberDigit */;
2815 }
2816 else if (isPhoneNumberSeparatorChar(char)) {
2817 stateMachine.state = 39 /* PhoneNumberSeparator */;
2818 }
2819 else {
2820 remove(stateMachines, stateMachine);
2821 }
2822 }
2823 function statePhoneNumberDigit(stateMachine, char) {
2824 // For now, if we've reached any digits, we'll say that the machine
2825 // has reached its accept state. The phone regex will confirm the
2826 // match later.
2827 // Alternatively, we could count the number of digits to avoid
2828 // invoking the phone number regex
2829 stateMachine.acceptStateReached = true;
2830 if (isPhoneNumberControlChar(char)) {
2831 stateMachine.state = 40 /* PhoneNumberControlChar */;
2832 }
2833 else if (char === '#') {
2834 stateMachine.state = 41 /* PhoneNumberPoundChar */;
2835 }
2836 else if (digitRe.test(char)) ;
2837 else if (char === '(') {
2838 stateMachine.state = 32 /* PhoneNumberOpenParen */;
2839 }
2840 else if (isPhoneNumberSeparatorChar(char)) {
2841 stateMachine.state = 39 /* PhoneNumberSeparator */;
2842 }
2843 else {
2844 captureMatchIfValidAndRemove(stateMachine);
2845 // The transition from a digit character to a letter can be the
2846 // start of a new scheme URL match
2847 if (isSchemeStartChar(char)) {
2848 stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
2849 }
2850 }
2851 }
2852 function statePhoneNumberSeparator(stateMachine, char) {
2853 if (digitRe.test(char)) {
2854 stateMachine.state = 38 /* PhoneNumberDigit */;
2855 }
2856 else if (char === '(') {
2857 stateMachine.state = 32 /* PhoneNumberOpenParen */;
2858 }
2859 else {
2860 captureMatchIfValidAndRemove(stateMachine);
2861 // This character may start a new match. Add states for it
2862 stateNoMatch(char);
2863 }
2864 }
2865 // The ";" characters is "wait" in a phone number
2866 // The "," characters is "pause" in a phone number
2867 function statePhoneNumberControlChar(stateMachine, char) {
2868 if (isPhoneNumberControlChar(char)) ;
2869 else if (char === '#') {
2870 stateMachine.state = 41 /* PhoneNumberPoundChar */;
2871 }
2872 else if (digitRe.test(char)) {
2873 stateMachine.state = 38 /* PhoneNumberDigit */;
2874 }
2875 else {
2876 captureMatchIfValidAndRemove(stateMachine);
2877 }
2878 }
2879 // The "#" characters is "pound" in a phone number
2880 function statePhoneNumberPoundChar(stateMachine, char) {
2881 if (isPhoneNumberControlChar(char)) {
2882 stateMachine.state = 40 /* PhoneNumberControlChar */;
2883 }
2884 else if (digitRe.test(char)) {
2885 // According to some of the older tests, if there's a digit
2886 // after a '#' sign, the match is invalid. TODO: Revisit if this is true
2887 remove(stateMachines, stateMachine);
2888 }
2889 else {
2890 captureMatchIfValidAndRemove(stateMachine);
2891 }
2892 }
2893 /*
2894 * Captures a match if it is valid (i.e. has a full domain name for a
2895 * TLD match). If a match is not valid, it is possible that we want to
2896 * keep reading characters in order to make a full match.
2897 */
2898 function captureMatchIfValidAndRemove(stateMachine) {
2899 // Remove the state machine first. There are a number of code paths
2900 // which return out of this function early, so make sure we have
2901 // this done
2902 remove(stateMachines, stateMachine);
2903 // Make sure the state machine being checked has actually reached an
2904 // "accept" state. If it hasn't reach one, it can't be a match
2905 if (!stateMachine.acceptStateReached) {
2906 return;
2907 }
2908 var startIdx = stateMachine.startIdx;
2909 var matchedText = text.slice(stateMachine.startIdx, charIdx);
2910 // Handle any unbalanced braces (parens, square brackets, or curly
2911 // brackets) inside the URL. This handles situations like:
2912 // The link (google.com)
2913 // and
2914 // Check out this link here (en.wikipedia.org/wiki/IANA_(disambiguation))
2915 //
2916 // And also remove any punctuation chars at the end such as:
2917 // '?', ',', ':', '.', etc.
2918 matchedText = excludeUnbalancedTrailingBracesAndPunctuation(matchedText);
2919 if (stateMachine.type === 'url') {
2920 // We don't want to accidentally match a URL that is preceded by an
2921 // '@' character, which would be an email address
2922 var charBeforeUrlMatch = text.charAt(stateMachine.startIdx - 1);
2923 if (charBeforeUrlMatch === '@') {
2924 return;
2925 }
2926 // For the purpose of this parser, we've generalized 'www'
2927 // matches as part of 'tld' matches. However, for backward
2928 // compatibility, we distinguish beween TLD matches and matches
2929 // that begin with 'www.' so that users may turn off 'www'
2930 // matches. As such, we need to correct for that now if the
2931 // URL begins with 'www.'
2932 var urlMatchType = stateMachine.matchType;
2933 if (urlMatchType === 'scheme') {
2934 // Autolinker accepts many characters in a url's scheme (like `fake://test.com`).
2935 // However, in cases where a URL is missing whitespace before an obvious link,
2936 // (for example: `nowhitespacehttp://www.test.com`), we only want the match to start
2937 // at the http:// part. We will check if the match contains a common scheme and then
2938 // shift the match to start from there.
2939 var httpSchemeMatch = httpSchemeRe.exec(matchedText);
2940 if (httpSchemeMatch) {
2941 // If we found an overmatched URL, we want to find the index
2942 // of where the match should start and shift the match to
2943 // start from the beginning of the common scheme
2944 startIdx = startIdx + httpSchemeMatch.index;
2945 matchedText = matchedText.slice(httpSchemeMatch.index);
2946 }
2947 if (!isValidSchemeUrl(matchedText)) {
2948 return; // not a valid match
2949 }
2950 }
2951 else if (urlMatchType === 'tld') {
2952 if (!isValidTldMatch(matchedText)) {
2953 return; // not a valid match
2954 }
2955 }
2956 else if (urlMatchType === 'ipV4') {
2957 if (!isValidIpV4Address(matchedText)) {
2958 return; // not a valid match
2959 }
2960 }
2961 else {
2962 assertNever(urlMatchType);
2963 }
2964 matches.push(new UrlMatch({
2965 tagBuilder: tagBuilder,
2966 matchedText: matchedText,
2967 offset: startIdx,
2968 urlMatchType: urlMatchType,
2969 url: matchedText,
2970 protocolRelativeMatch: matchedText.slice(0, 2) === '//',
2971 // TODO: Do these settings need to be passed to the match,
2972 // or should we handle them here in UrlMatcher?
2973 stripPrefix: stripPrefix,
2974 stripTrailingSlash: stripTrailingSlash,
2975 decodePercentEncoding: decodePercentEncoding,
2976 }));
2977 }
2978 else if (stateMachine.type === 'email') {
2979 // if the email address has a valid TLD, add it to the list of matches
2980 if (isValidEmail(matchedText)) {
2981 matches.push(new EmailMatch({
2982 tagBuilder: tagBuilder,
2983 matchedText: matchedText,
2984 offset: startIdx,
2985 email: matchedText.replace(mailtoSchemePrefixRe, ''),
2986 }));
2987 }
2988 }
2989 else if (stateMachine.type === 'hashtag') {
2990 if (isValidHashtag(matchedText)) {
2991 matches.push(new HashtagMatch({
2992 tagBuilder: tagBuilder,
2993 matchedText: matchedText,
2994 offset: startIdx,
2995 serviceName: hashtagServiceName,
2996 hashtag: matchedText.slice(1),
2997 }));
2998 }
2999 }
3000 else if (stateMachine.type === 'mention') {
3001 if (isValidMention(matchedText, mentionServiceName)) {
3002 matches.push(new MentionMatch({
3003 tagBuilder: tagBuilder,
3004 matchedText: matchedText,
3005 offset: startIdx,
3006 serviceName: mentionServiceName,
3007 mention: matchedText.slice(1), // strip off the '@' character at the beginning
3008 }));
3009 }
3010 }
3011 else if (stateMachine.type === 'phone') {
3012 // remove any trailing spaces that were considered as "separator"
3013 // chars by the state machine
3014 matchedText = matchedText.replace(/ +$/g, '');
3015 if (isValidPhoneNumber(matchedText)) {
3016 var cleanNumber = matchedText.replace(/[^0-9,;#]/g, ''); // strip out non-digit characters exclude comma semicolon and #
3017 matches.push(new PhoneMatch({
3018 tagBuilder: tagBuilder,
3019 matchedText: matchedText,
3020 offset: startIdx,
3021 number: cleanNumber,
3022 plusSign: matchedText.charAt(0) === '+',
3023 }));
3024 }
3025 }
3026 else {
3027 assertNever(stateMachine);
3028 }
3029 }
3030 }
3031 var openBraceRe = /[\(\{\[]/;
3032 var closeBraceRe = /[\)\}\]]/;
3033 var oppositeBrace = {
3034 ')': '(',
3035 '}': '{',
3036 ']': '[',
3037 };
3038 /**
3039 * Determines if a match found has unmatched closing parenthesis,
3040 * square brackets or curly brackets. If so, these unbalanced symbol(s) will be
3041 * removed from the URL match itself.
3042 *
3043 * A match may have an extra closing parenthesis/square brackets/curly brackets
3044 * at the end of the match because these are valid URL path characters. For
3045 * example, "wikipedia.com/something_(disambiguation)" should be auto-linked.
3046 *
3047 * However, an extra parenthesis *will* be included when the URL itself is
3048 * wrapped in parenthesis, such as in the case of:
3049 *
3050 * "(wikipedia.com/something_(disambiguation))"
3051 *
3052 * In this case, the last closing parenthesis should *not* be part of the
3053 * URL itself, and this method will exclude it from the returned URL.
3054 *
3055 * For square brackets in URLs such as in PHP arrays, the same behavior as
3056 * parenthesis discussed above should happen:
3057 *
3058 * "[http://www.example.com/foo.php?bar[]=1&bar[]=2&bar[]=3]"
3059 *
3060 * The very last closing square bracket should not be part of the URL itself,
3061 * and therefore this method will remove it.
3062 *
3063 * @param matchedText The full matched URL/email/hashtag/etc. from the state
3064 * machine parser.
3065 * @return The updated matched text with extraneous suffix characters removed.
3066 */
3067 function excludeUnbalancedTrailingBracesAndPunctuation(matchedText) {
3068 var braceCounts = {
3069 '(': 0,
3070 '{': 0,
3071 '[': 0,
3072 };
3073 for (var i = 0; i < matchedText.length; i++) {
3074 var char_1 = matchedText.charAt(i);
3075 if (openBraceRe.test(char_1)) {
3076 braceCounts[char_1]++;
3077 }
3078 else if (closeBraceRe.test(char_1)) {
3079 braceCounts[oppositeBrace[char_1]]--;
3080 }
3081 }
3082 var endIdx = matchedText.length - 1;
3083 var char;
3084 while (endIdx >= 0) {
3085 char = matchedText.charAt(endIdx);
3086 if (closeBraceRe.test(char)) {
3087 var oppositeBraceChar = oppositeBrace[char];
3088 if (braceCounts[oppositeBraceChar] < 0) {
3089 braceCounts[oppositeBraceChar]++;
3090 endIdx--;
3091 }
3092 else {
3093 break;
3094 }
3095 }
3096 else if (urlSuffixedCharsNotAllowedAtEndRe.test(char)) {
3097 // Walk back a punctuation char like '?', ',', ':', '.', etc.
3098 endIdx--;
3099 }
3100 else {
3101 break;
3102 }
3103 }
3104 return matchedText.slice(0, endIdx + 1);
3105 }
3106 function createSchemeUrlStateMachine(startIdx, state) {
3107 return {
3108 type: 'url',
3109 startIdx: startIdx,
3110 state: state,
3111 acceptStateReached: false,
3112 matchType: 'scheme',
3113 };
3114 }
3115 function createTldUrlStateMachine(startIdx, state) {
3116 return {
3117 type: 'url',
3118 startIdx: startIdx,
3119 state: state,
3120 acceptStateReached: false,
3121 matchType: 'tld',
3122 };
3123 }
3124 function createIpV4UrlStateMachine(startIdx, state) {
3125 return {
3126 type: 'url',
3127 startIdx: startIdx,
3128 state: state,
3129 acceptStateReached: false,
3130 matchType: 'ipV4',
3131 octetsEncountered: 1, // starts at 1 because we create this machine when encountering the first octet
3132 };
3133 }
3134 function createEmailStateMachine(startIdx, state) {
3135 return {
3136 type: 'email',
3137 startIdx: startIdx,
3138 state: state,
3139 acceptStateReached: false,
3140 };
3141 }
3142 function createHashtagStateMachine(startIdx, state) {
3143 return {
3144 type: 'hashtag',
3145 startIdx: startIdx,
3146 state: state,
3147 acceptStateReached: false,
3148 };
3149 }
3150 function createMentionStateMachine(startIdx, state) {
3151 return {
3152 type: 'mention',
3153 startIdx: startIdx,
3154 state: state,
3155 acceptStateReached: false,
3156 };
3157 }
3158 function createPhoneNumberStateMachine(startIdx, state) {
3159 return {
3160 type: 'phone',
3161 startIdx: startIdx,
3162 state: state,
3163 acceptStateReached: false,
3164 };
3165 }
3166
3167 // For debugging: search for other "For debugging" lines
3168 // import CliTable from 'cli-table';
3169 /**
3170 * Parses an HTML string, calling the callbacks to notify of tags and text.
3171 *
3172 * ## History
3173 *
3174 * This file previously used a regular expression to find html tags in the input
3175 * text. Unfortunately, we ran into a bunch of catastrophic backtracking issues
3176 * with certain input text, causing Autolinker to either hang or just take a
3177 * really long time to parse the string.
3178 *
3179 * The current code is intended to be a O(n) algorithm that walks through
3180 * the string in one pass, and tries to be as cheap as possible. We don't need
3181 * to implement the full HTML spec, but rather simply determine where the string
3182 * looks like an HTML tag, and where it looks like text (so that we can autolink
3183 * that).
3184 *
3185 * This state machine parser is intended just to be a simple but performant
3186 * parser of HTML for the subset of requirements we have. We simply need to:
3187 *
3188 * 1. Determine where HTML tags are
3189 * 2. Determine the tag name (Autolinker specifically only cares about <a>,
3190 * <script>, and <style> tags, so as not to link any text within them)
3191 *
3192 * We don't need to:
3193 *
3194 * 1. Create a parse tree
3195 * 2. Auto-close tags with invalid markup
3196 * 3. etc.
3197 *
3198 * The other intention behind this is that we didn't want to add external
3199 * dependencies on the Autolinker utility which would increase its size. For
3200 * instance, adding htmlparser2 adds 125kb to the minified output file,
3201 * increasing its final size from 47kb to 172kb (at the time of writing). It
3202 * also doesn't work exactly correctly, treating the string "<3 blah blah blah"
3203 * as an HTML tag.
3204 *
3205 * Reference for HTML spec:
3206 *
3207 * https://www.w3.org/TR/html51/syntax.html#sec-tokenization
3208 *
3209 * @param {String} html The HTML to parse
3210 * @param {Object} callbacks
3211 * @param {Function} callbacks.onOpenTag Callback function to call when an open
3212 * tag is parsed. Called with the tagName as its argument.
3213 * @param {Function} callbacks.onCloseTag Callback function to call when a close
3214 * tag is parsed. Called with the tagName as its argument. If a self-closing
3215 * tag is found, `onCloseTag` is called immediately after `onOpenTag`.
3216 * @param {Function} callbacks.onText Callback function to call when text (i.e
3217 * not an HTML tag) is parsed. Called with the text (string) as its first
3218 * argument, and offset (number) into the string as its second.
3219 */
3220 function parseHtml(html, _a) {
3221 var onOpenTag = _a.onOpenTag, onCloseTag = _a.onCloseTag, onText = _a.onText, onComment = _a.onComment, onDoctype = _a.onDoctype;
3222 var noCurrentTag = new CurrentTag();
3223 var charIdx = 0, len = html.length, state = 0 /* Data */, currentDataIdx = 0, // where the current data start index is
3224 currentTag = noCurrentTag; // describes the current tag that is being read
3225 // For debugging: search for other "For debugging" lines
3226 // const table = new CliTable( {
3227 // head: [ 'charIdx', 'char', 'state', 'currentDataIdx', 'currentOpenTagIdx', 'tag.type' ]
3228 // } );
3229 while (charIdx < len) {
3230 var char = html.charAt(charIdx);
3231 // For debugging: search for other "For debugging" lines
3232 // ALSO: Temporarily remove the 'const' keyword on the State enum
3233 // table.push(
3234 // [ charIdx, char, State[ state ], currentDataIdx, currentTag.idx, currentTag.idx === -1 ? '' : currentTag.type ]
3235 // );
3236 switch (state) {
3237 case 0 /* Data */:
3238 stateData(char);
3239 break;
3240 case 1 /* TagOpen */:
3241 stateTagOpen(char);
3242 break;
3243 case 2 /* EndTagOpen */:
3244 stateEndTagOpen(char);
3245 break;
3246 case 3 /* TagName */:
3247 stateTagName(char);
3248 break;
3249 case 4 /* BeforeAttributeName */:
3250 stateBeforeAttributeName(char);
3251 break;
3252 case 5 /* AttributeName */:
3253 stateAttributeName(char);
3254 break;
3255 case 6 /* AfterAttributeName */:
3256 stateAfterAttributeName(char);
3257 break;
3258 case 7 /* BeforeAttributeValue */:
3259 stateBeforeAttributeValue(char);
3260 break;
3261 case 8 /* AttributeValueDoubleQuoted */:
3262 stateAttributeValueDoubleQuoted(char);
3263 break;
3264 case 9 /* AttributeValueSingleQuoted */:
3265 stateAttributeValueSingleQuoted(char);
3266 break;
3267 case 10 /* AttributeValueUnquoted */:
3268 stateAttributeValueUnquoted(char);
3269 break;
3270 case 11 /* AfterAttributeValueQuoted */:
3271 stateAfterAttributeValueQuoted(char);
3272 break;
3273 case 12 /* SelfClosingStartTag */:
3274 stateSelfClosingStartTag(char);
3275 break;
3276 case 13 /* MarkupDeclarationOpenState */:
3277 stateMarkupDeclarationOpen();
3278 break;
3279 case 14 /* CommentStart */:
3280 stateCommentStart(char);
3281 break;
3282 case 15 /* CommentStartDash */:
3283 stateCommentStartDash(char);
3284 break;
3285 case 16 /* Comment */:
3286 stateComment(char);
3287 break;
3288 case 17 /* CommentEndDash */:
3289 stateCommentEndDash(char);
3290 break;
3291 case 18 /* CommentEnd */:
3292 stateCommentEnd(char);
3293 break;
3294 case 19 /* CommentEndBang */:
3295 stateCommentEndBang(char);
3296 break;
3297 case 20 /* Doctype */:
3298 stateDoctype(char);
3299 break;
3300 default:
3301 assertNever(state);
3302 }
3303 // For debugging: search for other "For debugging" lines
3304 // ALSO: Temporarily remove the 'const' keyword on the State enum
3305 // table.push(
3306 // [ charIdx, char, State[ state ], currentDataIdx, currentTag.idx, currentTag.idx === -1 ? '' : currentTag.type ]
3307 // );
3308 charIdx++;
3309 }
3310 if (currentDataIdx < charIdx) {
3311 emitText();
3312 }
3313 // For debugging: search for other "For debugging" lines
3314 // console.log( '\n' + table.toString() );
3315 // Called when non-tags are being read (i.e. the text around HTML †ags)
3316 // https://www.w3.org/TR/html51/syntax.html#data-state
3317 function stateData(char) {
3318 if (char === '<') {
3319 startNewTag();
3320 }
3321 }
3322 // Called after a '<' is read from the Data state
3323 // https://www.w3.org/TR/html51/syntax.html#tag-open-state
3324 function stateTagOpen(char) {
3325 if (char === '!') {
3326 state = 13 /* MarkupDeclarationOpenState */;
3327 }
3328 else if (char === '/') {
3329 state = 2 /* EndTagOpen */;
3330 currentTag = new CurrentTag(__assign(__assign({}, currentTag), { isClosing: true }));
3331 }
3332 else if (char === '<') {
3333 // start of another tag (ignore the previous, incomplete one)
3334 startNewTag();
3335 }
3336 else if (letterRe.test(char)) {
3337 // tag name start (and no '/' read)
3338 state = 3 /* TagName */;
3339 currentTag = new CurrentTag(__assign(__assign({}, currentTag), { isOpening: true }));
3340 }
3341 else {
3342 // Any other
3343 state = 0 /* Data */;
3344 currentTag = noCurrentTag;
3345 }
3346 }
3347 // After a '<x', '</x' sequence is read (where 'x' is a letter character),
3348 // this is to continue reading the tag name
3349 // https://www.w3.org/TR/html51/syntax.html#tag-name-state
3350 function stateTagName(char) {
3351 if (whitespaceRe.test(char)) {
3352 currentTag = new CurrentTag(__assign(__assign({}, currentTag), { name: captureTagName() }));
3353 state = 4 /* BeforeAttributeName */;
3354 }
3355 else if (char === '<') {
3356 // start of another tag (ignore the previous, incomplete one)
3357 startNewTag();
3358 }
3359 else if (char === '/') {
3360 currentTag = new CurrentTag(__assign(__assign({}, currentTag), { name: captureTagName() }));
3361 state = 12 /* SelfClosingStartTag */;
3362 }
3363 else if (char === '>') {
3364 currentTag = new CurrentTag(__assign(__assign({}, currentTag), { name: captureTagName() }));
3365 emitTagAndPreviousTextNode(); // resets to Data state as well
3366 }
3367 else if (!letterRe.test(char) && !digitRe.test(char) && char !== ':') {
3368 // Anything else that does not form an html tag. Note: the colon
3369 // character is accepted for XML namespaced tags
3370 resetToDataState();
3371 }
3372 else ;
3373 }
3374 // Called after the '/' is read from a '</' sequence
3375 // https://www.w3.org/TR/html51/syntax.html#end-tag-open-state
3376 function stateEndTagOpen(char) {
3377 if (char === '>') {
3378 // parse error. Encountered "</>". Skip it without treating as a tag
3379 resetToDataState();
3380 }
3381 else if (letterRe.test(char)) {
3382 state = 3 /* TagName */;
3383 }
3384 else {
3385 // some other non-tag-like character, don't treat this as a tag
3386 resetToDataState();
3387 }
3388 }
3389 // https://www.w3.org/TR/html51/syntax.html#before-attribute-name-state
3390 function stateBeforeAttributeName(char) {
3391 if (whitespaceRe.test(char)) ;
3392 else if (char === '/') {
3393 state = 12 /* SelfClosingStartTag */;
3394 }
3395 else if (char === '>') {
3396 emitTagAndPreviousTextNode(); // resets to Data state as well
3397 }
3398 else if (char === '<') {
3399 // start of another tag (ignore the previous, incomplete one)
3400 startNewTag();
3401 }
3402 else if (char === "=" || quoteRe.test(char) || controlCharsRe.test(char)) {
3403 // "Parse error" characters that, according to the spec, should be
3404 // appended to the attribute name, but we'll treat these characters
3405 // as not forming a real HTML tag
3406 resetToDataState();
3407 }
3408 else {
3409 // Any other char, start of a new attribute name
3410 state = 5 /* AttributeName */;
3411 }
3412 }
3413 // https://www.w3.org/TR/html51/syntax.html#attribute-name-state
3414 function stateAttributeName(char) {
3415 if (whitespaceRe.test(char)) {
3416 state = 6 /* AfterAttributeName */;
3417 }
3418 else if (char === '/') {
3419 state = 12 /* SelfClosingStartTag */;
3420 }
3421 else if (char === '=') {
3422 state = 7 /* BeforeAttributeValue */;
3423 }
3424 else if (char === '>') {
3425 emitTagAndPreviousTextNode(); // resets to Data state as well
3426 }
3427 else if (char === '<') {
3428 // start of another tag (ignore the previous, incomplete one)
3429 startNewTag();
3430 }
3431 else if (quoteRe.test(char)) {
3432 // "Parse error" characters that, according to the spec, should be
3433 // appended to the attribute name, but we'll treat these characters
3434 // as not forming a real HTML tag
3435 resetToDataState();
3436 }
3437 else ;
3438 }
3439 // https://www.w3.org/TR/html51/syntax.html#after-attribute-name-state
3440 function stateAfterAttributeName(char) {
3441 if (whitespaceRe.test(char)) ;
3442 else if (char === '/') {
3443 state = 12 /* SelfClosingStartTag */;
3444 }
3445 else if (char === '=') {
3446 state = 7 /* BeforeAttributeValue */;
3447 }
3448 else if (char === '>') {
3449 emitTagAndPreviousTextNode();
3450 }
3451 else if (char === '<') {
3452 // start of another tag (ignore the previous, incomplete one)
3453 startNewTag();
3454 }
3455 else if (quoteRe.test(char)) {
3456 // "Parse error" characters that, according to the spec, should be
3457 // appended to the attribute name, but we'll treat these characters
3458 // as not forming a real HTML tag
3459 resetToDataState();
3460 }
3461 else {
3462 // Any other character, start a new attribute in the current tag
3463 state = 5 /* AttributeName */;
3464 }
3465 }
3466 // https://www.w3.org/TR/html51/syntax.html#before-attribute-value-state
3467 function stateBeforeAttributeValue(char) {
3468 if (whitespaceRe.test(char)) ;
3469 else if (char === "\"") {
3470 state = 8 /* AttributeValueDoubleQuoted */;
3471 }
3472 else if (char === "'") {
3473 state = 9 /* AttributeValueSingleQuoted */;
3474 }
3475 else if (/[>=`]/.test(char)) {
3476 // Invalid chars after an '=' for an attribute value, don't count
3477 // the current tag as an HTML tag
3478 resetToDataState();
3479 }
3480 else if (char === '<') {
3481 // start of another tag (ignore the previous, incomplete one)
3482 startNewTag();
3483 }
3484 else {
3485 // Any other character, consider it an unquoted attribute value
3486 state = 10 /* AttributeValueUnquoted */;
3487 }
3488 }
3489 // https://www.w3.org/TR/html51/syntax.html#attribute-value-double-quoted-state
3490 function stateAttributeValueDoubleQuoted(char) {
3491 if (char === "\"") {
3492 // end the current double-quoted attribute
3493 state = 11 /* AfterAttributeValueQuoted */;
3494 }
3495 }
3496 // https://www.w3.org/TR/html51/syntax.html#attribute-value-single-quoted-state
3497 function stateAttributeValueSingleQuoted(char) {
3498 if (char === "'") {
3499 // end the current single-quoted attribute
3500 state = 11 /* AfterAttributeValueQuoted */;
3501 }
3502 }
3503 // https://www.w3.org/TR/html51/syntax.html#attribute-value-unquoted-state
3504 function stateAttributeValueUnquoted(char) {
3505 if (whitespaceRe.test(char)) {
3506 state = 4 /* BeforeAttributeName */;
3507 }
3508 else if (char === '>') {
3509 emitTagAndPreviousTextNode();
3510 }
3511 else if (char === '<') {
3512 // start of another tag (ignore the previous, incomplete one)
3513 startNewTag();
3514 }
3515 else ;
3516 }
3517 // https://www.w3.org/TR/html51/syntax.html#after-attribute-value-quoted-state
3518 function stateAfterAttributeValueQuoted(char) {
3519 if (whitespaceRe.test(char)) {
3520 state = 4 /* BeforeAttributeName */;
3521 }
3522 else if (char === '/') {
3523 state = 12 /* SelfClosingStartTag */;
3524 }
3525 else if (char === '>') {
3526 emitTagAndPreviousTextNode();
3527 }
3528 else if (char === '<') {
3529 // start of another tag (ignore the previous, incomplete one)
3530 startNewTag();
3531 }
3532 else {
3533 // Any other character, "parse error". Spec says to switch to the
3534 // BeforeAttributeState and re-consume the character, as it may be
3535 // the start of a new attribute name
3536 state = 4 /* BeforeAttributeName */;
3537 reconsumeCurrentCharacter();
3538 }
3539 }
3540 // A '/' has just been read in the current tag (presumably for '/>'), and
3541 // this handles the next character
3542 // https://www.w3.org/TR/html51/syntax.html#self-closing-start-tag-state
3543 function stateSelfClosingStartTag(char) {
3544 if (char === '>') {
3545 currentTag = new CurrentTag(__assign(__assign({}, currentTag), { isClosing: true }));
3546 emitTagAndPreviousTextNode(); // resets to Data state as well
3547 }
3548 else {
3549 state = 4 /* BeforeAttributeName */;
3550 }
3551 }
3552 // https://www.w3.org/TR/html51/syntax.html#markup-declaration-open-state
3553 // (HTML Comments or !DOCTYPE)
3554 function stateMarkupDeclarationOpen(char) {
3555 if (html.substr(charIdx, 2) === '--') {
3556 // html comment
3557 charIdx += 2; // "consume" characters
3558 currentTag = new CurrentTag(__assign(__assign({}, currentTag), { type: 'comment' }));
3559 state = 14 /* CommentStart */;
3560 }
3561 else if (html.substr(charIdx, 7).toUpperCase() === 'DOCTYPE') {
3562 charIdx += 7; // "consume" characters
3563 currentTag = new CurrentTag(__assign(__assign({}, currentTag), { type: 'doctype' }));
3564 state = 20 /* Doctype */;
3565 }
3566 else {
3567 // At this point, the spec specifies that the state machine should
3568 // enter the "bogus comment" state, in which case any character(s)
3569 // after the '<!' that were read should become an HTML comment up
3570 // until the first '>' that is read (or EOF). Instead, we'll assume
3571 // that a user just typed '<!' as part of text data
3572 resetToDataState();
3573 }
3574 }
3575 // Handles after the sequence '<!--' has been read
3576 // https://www.w3.org/TR/html51/syntax.html#comment-start-state
3577 function stateCommentStart(char) {
3578 if (char === '-') {
3579 // We've read the sequence '<!---' at this point (3 dashes)
3580 state = 15 /* CommentStartDash */;
3581 }
3582 else if (char === '>') {
3583 // At this point, we'll assume the comment wasn't a real comment
3584 // so we'll just emit it as data. We basically read the sequence
3585 // '<!-->'
3586 resetToDataState();
3587 }
3588 else {
3589 // Any other char, take it as part of the comment
3590 state = 16 /* Comment */;
3591 }
3592 }
3593 // We've read the sequence '<!---' at this point (3 dashes)
3594 // https://www.w3.org/TR/html51/syntax.html#comment-start-dash-state
3595 function stateCommentStartDash(char) {
3596 if (char === '-') {
3597 // We've read '<!----' (4 dashes) at this point
3598 state = 18 /* CommentEnd */;
3599 }
3600 else if (char === '>') {
3601 // At this point, we'll assume the comment wasn't a real comment
3602 // so we'll just emit it as data. We basically read the sequence
3603 // '<!--->'
3604 resetToDataState();
3605 }
3606 else {
3607 // Anything else, take it as a valid comment
3608 state = 16 /* Comment */;
3609 }
3610 }
3611 // Currently reading the comment's text (data)
3612 // https://www.w3.org/TR/html51/syntax.html#comment-state
3613 function stateComment(char) {
3614 if (char === '-') {
3615 state = 17 /* CommentEndDash */;
3616 }
3617 }
3618 // When we we've read the first dash inside a comment, it may signal the
3619 // end of the comment if we read another dash
3620 // https://www.w3.org/TR/html51/syntax.html#comment-end-dash-state
3621 function stateCommentEndDash(char) {
3622 if (char === '-') {
3623 state = 18 /* CommentEnd */;
3624 }
3625 else {
3626 // Wasn't a dash, must still be part of the comment
3627 state = 16 /* Comment */;
3628 }
3629 }
3630 // After we've read two dashes inside a comment, it may signal the end of
3631 // the comment if we then read a '>' char
3632 // https://www.w3.org/TR/html51/syntax.html#comment-end-state
3633 function stateCommentEnd(char) {
3634 if (char === '>') {
3635 emitTagAndPreviousTextNode();
3636 }
3637 else if (char === '!') {
3638 state = 19 /* CommentEndBang */;
3639 }
3640 else if (char === '-') ;
3641 else {
3642 // Anything else, switch back to the comment state since we didn't
3643 // read the full "end comment" sequence (i.e. '-->')
3644 state = 16 /* Comment */;
3645 }
3646 }
3647 // We've read the sequence '--!' inside of a comment
3648 // https://www.w3.org/TR/html51/syntax.html#comment-end-bang-state
3649 function stateCommentEndBang(char) {
3650 if (char === '-') {
3651 // We read the sequence '--!-' inside of a comment. The last dash
3652 // could signify that the comment is going to close
3653 state = 17 /* CommentEndDash */;
3654 }
3655 else if (char === '>') {
3656 // End of comment with the sequence '--!>'
3657 emitTagAndPreviousTextNode();
3658 }
3659 else {
3660 // The '--!' was not followed by a '>', continue reading the
3661 // comment's text
3662 state = 16 /* Comment */;
3663 }
3664 }
3665 /**
3666 * For DOCTYPES in particular, we don't care about the attributes. Just
3667 * advance to the '>' character and emit the tag, unless we find a '<'
3668 * character in which case we'll start a new tag.
3669 *
3670 * Example doctype tag:
3671 * <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
3672 *
3673 * Actual spec: https://www.w3.org/TR/html51/syntax.html#doctype-state
3674 */
3675 function stateDoctype(char) {
3676 if (char === '>') {
3677 emitTagAndPreviousTextNode();
3678 }
3679 else if (char === '<') {
3680 startNewTag();
3681 }
3682 else ;
3683 }
3684 /**
3685 * Resets the state back to the Data state, and removes the current tag.
3686 *
3687 * We'll generally run this function whenever a "parse error" is
3688 * encountered, where the current tag that is being read no longer looks
3689 * like a real HTML tag.
3690 */
3691 function resetToDataState() {
3692 state = 0 /* Data */;
3693 currentTag = noCurrentTag;
3694 }
3695 /**
3696 * Starts a new HTML tag at the current index, ignoring any previous HTML
3697 * tag that was being read.
3698 *
3699 * We'll generally run this function whenever we read a new '<' character,
3700 * including when we read a '<' character inside of an HTML tag that we were
3701 * previously reading.
3702 */
3703 function startNewTag() {
3704 state = 1 /* TagOpen */;
3705 currentTag = new CurrentTag({ idx: charIdx });
3706 }
3707 /**
3708 * Once we've decided to emit an open tag, that means we can also emit the
3709 * text node before it.
3710 */
3711 function emitTagAndPreviousTextNode() {
3712 var textBeforeTag = html.slice(currentDataIdx, currentTag.idx);
3713 if (textBeforeTag) {
3714 // the html tag was the first element in the html string, or two
3715 // tags next to each other, in which case we should not emit a text
3716 // node
3717 onText(textBeforeTag, currentDataIdx);
3718 }
3719 if (currentTag.type === 'comment') {
3720 onComment(currentTag.idx);
3721 }
3722 else if (currentTag.type === 'doctype') {
3723 onDoctype(currentTag.idx);
3724 }
3725 else {
3726 if (currentTag.isOpening) {
3727 onOpenTag(currentTag.name, currentTag.idx);
3728 }
3729 if (currentTag.isClosing) {
3730 // note: self-closing tags will emit both opening and closing
3731 onCloseTag(currentTag.name, currentTag.idx);
3732 }
3733 }
3734 // Since we just emitted a tag, reset to the data state for the next char
3735 resetToDataState();
3736 currentDataIdx = charIdx + 1;
3737 }
3738 function emitText() {
3739 var text = html.slice(currentDataIdx, charIdx);
3740 onText(text, currentDataIdx);
3741 currentDataIdx = charIdx + 1;
3742 }
3743 /**
3744 * Captures the tag name from the start of the tag to the current character
3745 * index, and converts it to lower case
3746 */
3747 function captureTagName() {
3748 var startIdx = currentTag.idx + (currentTag.isClosing ? 2 : 1);
3749 return html.slice(startIdx, charIdx).toLowerCase();
3750 }
3751 /**
3752 * Causes the main loop to re-consume the current character, such as after
3753 * encountering a "parse error" that changed state and needs to reconsume
3754 * the same character in that new state.
3755 */
3756 function reconsumeCurrentCharacter() {
3757 charIdx--;
3758 }
3759 }
3760 var CurrentTag = /** @class */ (function () {
3761 function CurrentTag(cfg) {
3762 if (cfg === void 0) { cfg = {}; }
3763 this.idx = cfg.idx !== undefined ? cfg.idx : -1;
3764 this.type = cfg.type || 'tag';
3765 this.name = cfg.name || '';
3766 this.isOpening = !!cfg.isOpening;
3767 this.isClosing = !!cfg.isClosing;
3768 }
3769 return CurrentTag;
3770 }());
3771
3772 /**
3773 * @class Autolinker
3774 * @extends Object
3775 *
3776 * Utility class used to process a given string of text, and wrap the matches in
3777 * the appropriate anchor (&lt;a&gt;) tags to turn them into links.
3778 *
3779 * Any of the configuration options may be provided in an Object provided
3780 * to the Autolinker constructor, which will configure how the {@link #link link()}
3781 * method will process the links.
3782 *
3783 * For example:
3784 *
3785 * var autolinker = new Autolinker( {
3786 * newWindow : false,
3787 * truncate : 30
3788 * } );
3789 *
3790 * var html = autolinker.link( "Joe went to www.yahoo.com" );
3791 * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
3792 *
3793 *
3794 * The {@link #static-link static link()} method may also be used to inline
3795 * options into a single call, which may be more convenient for one-off uses.
3796 * For example:
3797 *
3798 * var html = Autolinker.link( "Joe went to www.yahoo.com", {
3799 * newWindow : false,
3800 * truncate : 30
3801 * } );
3802 * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
3803 *
3804 *
3805 * ## Custom Replacements of Links
3806 *
3807 * If the configuration options do not provide enough flexibility, a {@link #replaceFn}
3808 * may be provided to fully customize the output of Autolinker. This function is
3809 * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
3810 * match that is encountered.
3811 *
3812 * For example:
3813 *
3814 * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
3815 *
3816 * var linkedText = Autolinker.link( input, {
3817 * replaceFn : function( match ) {
3818 * console.log( "href = ", match.getAnchorHref() );
3819 * console.log( "text = ", match.getAnchorText() );
3820 *
3821 * switch( match.getType() ) {
3822 * case 'url' :
3823 * console.log( "url: ", match.getUrl() );
3824 *
3825 * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
3826 * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
3827 * tag.setAttr( 'rel', 'nofollow' );
3828 * tag.addClass( 'external-link' );
3829 *
3830 * return tag;
3831 *
3832 * } else {
3833 * return true; // let Autolinker perform its normal anchor tag replacement
3834 * }
3835 *
3836 * case 'email' :
3837 * var email = match.getEmail();
3838 * console.log( "email: ", email );
3839 *
3840 * if( email === "my@own.address" ) {
3841 * return false; // don't auto-link this particular email address; leave as-is
3842 * } else {
3843 * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
3844 * }
3845 *
3846 * case 'phone' :
3847 * var phoneNumber = match.getPhoneNumber();
3848 * console.log( phoneNumber );
3849 *
3850 * return '<a href="http://newplace.to.link.phone.numbers.to/">' + phoneNumber + '</a>';
3851 *
3852 * case 'hashtag' :
3853 * var hashtag = match.getHashtag();
3854 * console.log( hashtag );
3855 *
3856 * return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
3857 *
3858 * case 'mention' :
3859 * var mention = match.getMention();
3860 * console.log( mention );
3861 *
3862 * return '<a href="http://newplace.to.link.mention.to/">' + mention + '</a>';
3863 * }
3864 * }
3865 * } );
3866 *
3867 *
3868 * The function may return the following values:
3869 *
3870 * - `true` (Boolean): Allow Autolinker to replace the match as it normally
3871 * would.
3872 * - `false` (Boolean): Do not replace the current match at all - leave as-is.
3873 * - Any String: If a string is returned from the function, the string will be
3874 * used directly as the replacement HTML for the match.
3875 * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify
3876 * an HTML tag before writing out its HTML text.
3877 */
3878 var Autolinker = /** @class */ (function () {
3879 /**
3880 * @method constructor
3881 * @param {Object} [cfg] The configuration options for the Autolinker instance,
3882 * specified in an Object (map).
3883 */
3884 function Autolinker(cfg) {
3885 if (cfg === void 0) { cfg = {}; }
3886 /**
3887 * The Autolinker version number exposed on the instance itself.
3888 *
3889 * Ex: 0.25.1
3890 *
3891 * @property {String} version
3892 */
3893 this.version = Autolinker.version;
3894 /**
3895 * @cfg {Boolean/Object} [urls]
3896 *
3897 * `true` if URLs should be automatically linked, `false` if they should not
3898 * be. Defaults to `true`.
3899 *
3900 * Examples:
3901 *
3902 * urls: true
3903 *
3904 * // or
3905 *
3906 * urls: {
3907 * schemeMatches : true,
3908 * tldMatches : true,
3909 * ipV4Matches : true
3910 * }
3911 *
3912 * As shown above, this option also accepts an Object form with 3 properties
3913 * to allow for more customization of what exactly gets linked. All default
3914 * to `true`:
3915 *
3916 * @cfg {Boolean} [urls.schemeMatches] `true` to match URLs found prefixed
3917 * with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`,
3918 * `false` to prevent these types of matches.
3919 * @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
3920 * level domains (.com, .net, etc.) that are not prefixed with a scheme
3921 * (such as 'http://'). This option attempts to match anything that looks
3922 * like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
3923 * `false` to prevent these types of matches.
3924 * @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
3925 * that are not prefixed with a scheme (such as 'http://'). This option
3926 * attempts to match anything that looks like an IPv4 address in text. Ex:
3927 * `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
3928 * of matches.
3929 */
3930 this.urls = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
3931 /**
3932 * @cfg {Boolean} [email=true]
3933 *
3934 * `true` if email addresses should be automatically linked, `false` if they
3935 * should not be.
3936 */
3937 this.email = true; // default value just to get the above doc comment in the ES5 output and documentation generator
3938 /**
3939 * @cfg {Boolean} [phone=true]
3940 *
3941 * `true` if Phone numbers ("(555)555-5555") should be automatically linked,
3942 * `false` if they should not be.
3943 */
3944 this.phone = true; // default value just to get the above doc comment in the ES5 output and documentation generator
3945 /**
3946 * @cfg {Boolean/String} [hashtag=false]
3947 *
3948 * A string for the service name to have hashtags (ex: "#myHashtag")
3949 * auto-linked to. The currently-supported values are:
3950 *
3951 * - 'twitter'
3952 * - 'facebook'
3953 * - 'instagram'
3954 *
3955 * Pass `false` to skip auto-linking of hashtags.
3956 */
3957 this.hashtag = false; // default value just to get the above doc comment in the ES5 output and documentation generator
3958 /**
3959 * @cfg {String/Boolean} [mention=false]
3960 *
3961 * A string for the service name to have mentions (ex: "@myuser")
3962 * auto-linked to. The currently supported values are:
3963 *
3964 * - 'twitter'
3965 * - 'instagram'
3966 * - 'soundcloud'
3967 * - 'tiktok'
3968 *
3969 * Defaults to `false` to skip auto-linking of mentions.
3970 */
3971 this.mention = false; // default value just to get the above doc comment in the ES5 output and documentation generator
3972 /**
3973 * @cfg {Boolean} [newWindow=true]
3974 *
3975 * `true` if the links should open in a new window, `false` otherwise.
3976 */
3977 this.newWindow = true; // default value just to get the above doc comment in the ES5 output and documentation generator
3978 /**
3979 * @cfg {Boolean/Object} [stripPrefix=true]
3980 *
3981 * `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
3982 * from the beginning of URL links' text, `false` otherwise. Defaults to
3983 * `true`.
3984 *
3985 * Examples:
3986 *
3987 * stripPrefix: true
3988 *
3989 * // or
3990 *
3991 * stripPrefix: {
3992 * scheme : true,
3993 * www : true
3994 * }
3995 *
3996 * As shown above, this option also accepts an Object form with 2 properties
3997 * to allow for more customization of what exactly is prevented from being
3998 * displayed. Both default to `true`:
3999 *
4000 * @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
4001 * a URL match from being displayed to the user. Example:
4002 * `'http://google.com'` will be displayed as `'google.com'`. `false` to
4003 * not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
4004 * will be removed, so as not to remove a potentially dangerous scheme
4005 * (such as `'file://'` or `'javascript:'`)
4006 * @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
4007 * `'www.'` part of a URL match from being displayed to the user. Ex:
4008 * `'www.google.com'` will be displayed as `'google.com'`. `false` to not
4009 * strip the `'www'`.
4010 */
4011 this.stripPrefix = {
4012 scheme: true,
4013 www: true,
4014 }; // default value just to get the above doc comment in the ES5 output and documentation generator
4015 /**
4016 * @cfg {Boolean} [stripTrailingSlash=true]
4017 *
4018 * `true` to remove the trailing slash from URL matches, `false` to keep
4019 * the trailing slash.
4020 *
4021 * Example when `true`: `http://google.com/` will be displayed as
4022 * `http://google.com`.
4023 */
4024 this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
4025 /**
4026 * @cfg {Boolean} [decodePercentEncoding=true]
4027 *
4028 * `true` to decode percent-encoded characters in URL matches, `false` to keep
4029 * the percent-encoded characters.
4030 *
4031 * Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
4032 * be displayed as `https://en.wikipedia.org/wiki/San_José`.
4033 */
4034 this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
4035 /**
4036 * @cfg {Number/Object} [truncate=0]
4037 *
4038 * ## Number Form
4039 *
4040 * A number for how many characters matched text should be truncated to
4041 * inside the text of a link. If the matched text is over this number of
4042 * characters, it will be truncated to this length by adding a two period
4043 * ellipsis ('..') to the end of the string.
4044 *
4045 * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file'
4046 * truncated to 25 characters might look something like this:
4047 * 'yahoo.com/some/long/pat..'
4048 *
4049 * Example Usage:
4050 *
4051 * truncate: 25
4052 *
4053 *
4054 * Defaults to `0` for "no truncation."
4055 *
4056 *
4057 * ## Object Form
4058 *
4059 * An Object may also be provided with two properties: `length` (Number) and
4060 * `location` (String). `location` may be one of the following: 'end'
4061 * (default), 'middle', or 'smart'.
4062 *
4063 * Example Usage:
4064 *
4065 * truncate: { length: 25, location: 'middle' }
4066 *
4067 * @cfg {Number} [truncate.length=0] How many characters to allow before
4068 * truncation will occur. Defaults to `0` for "no truncation."
4069 * @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
4070 *
4071 * - 'end' (default): will truncate up to the number of characters, and then
4072 * add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
4073 * - 'middle': will truncate and add the ellipsis in the middle. Ex:
4074 * 'yahoo.com/s..th/to/a/file'
4075 * - 'smart': for URLs where the algorithm attempts to strip out unnecessary
4076 * parts first (such as the 'www.', then URL scheme, hash, etc.),
4077 * attempting to make the URL human-readable before looking for a good
4078 * point to insert the ellipsis if it is still too long. Ex:
4079 * 'yahoo.com/some..to/a/file'. For more details, see
4080 * {@link Autolinker.truncate.TruncateSmart}.
4081 */
4082 this.truncate = {
4083 length: 0,
4084 location: 'end',
4085 }; // default value just to get the above doc comment in the ES5 output and documentation generator
4086 /**
4087 * @cfg {String} className
4088 *
4089 * A CSS class name to add to the generated links. This class will be added
4090 * to all links, as well as this class plus match suffixes for styling
4091 * url/email/phone/hashtag/mention links differently.
4092 *
4093 * For example, if this config is provided as "myLink", then:
4094 *
4095 * - URL links will have the CSS classes: "myLink myLink-url"
4096 * - Email links will have the CSS classes: "myLink myLink-email", and
4097 * - Phone links will have the CSS classes: "myLink myLink-phone"
4098 * - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
4099 * - Mention links will have the CSS classes: "myLink myLink-mention myLink-[type]"
4100 * where [type] is either "instagram", "twitter" or "soundcloud"
4101 */
4102 this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
4103 /**
4104 * @cfg {Function} replaceFn
4105 *
4106 * A function to individually process each match found in the input string.
4107 *
4108 * See the class's description for usage.
4109 *
4110 * The `replaceFn` can be called with a different context object (`this`
4111 * reference) using the {@link #context} cfg.
4112 *
4113 * This function is called with the following parameter:
4114 *
4115 * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which
4116 * can be used to retrieve information about the match that the `replaceFn`
4117 * is currently processing. See {@link Autolinker.match.Match} subclasses
4118 * for details.
4119 */
4120 this.replaceFn = null; // default value just to get the above doc comment in the ES5 output and documentation generator
4121 /**
4122 * @cfg {Object} context
4123 *
4124 * The context object (`this` reference) to call the `replaceFn` with.
4125 *
4126 * Defaults to this Autolinker instance.
4127 */
4128 this.context = undefined; // default value just to get the above doc comment in the ES5 output and documentation generator
4129 /**
4130 * @cfg {Boolean} [sanitizeHtml=false]
4131 *
4132 * `true` to HTML-encode the start and end brackets of existing HTML tags found
4133 * in the input string. This will escape `<` and `>` characters to `&lt;` and
4134 * `&gt;`, respectively.
4135 *
4136 * Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
4137 * but will remove the significance of existing HTML tags in the input string. If
4138 * you would like to maintain the significance of existing HTML tags while also
4139 * making the output HTML string safe, leave this option as `false` and use a
4140 * tool like https://github.com/cure53/DOMPurify (or others) on the input string
4141 * before running Autolinker.
4142 */
4143 this.sanitizeHtml = false; // default value just to get the above doc comment in the ES5 output and documentation generator
4144 /**
4145 * @private
4146 * @property {Autolinker.AnchorTagBuilder} tagBuilder
4147 *
4148 * The AnchorTagBuilder instance used to build match replacement anchor tags.
4149 * Note: this is lazily instantiated in the {@link #getTagBuilder} method.
4150 */
4151 this.tagBuilder = null;
4152 // Note: when `this.something` is used in the rhs of these assignments,
4153 // it refers to the default values set above the constructor
4154 this.urls = normalizeUrlsCfg(cfg.urls);
4155 this.email = isBoolean(cfg.email) ? cfg.email : this.email;
4156 this.phone = isBoolean(cfg.phone) ? cfg.phone : this.phone;
4157 this.hashtag = cfg.hashtag || this.hashtag;
4158 this.mention = cfg.mention || this.mention;
4159 this.newWindow = isBoolean(cfg.newWindow) ? cfg.newWindow : this.newWindow;
4160 this.stripPrefix = normalizeStripPrefixCfg(cfg.stripPrefix);
4161 this.stripTrailingSlash = isBoolean(cfg.stripTrailingSlash)
4162 ? cfg.stripTrailingSlash
4163 : this.stripTrailingSlash;
4164 this.decodePercentEncoding = isBoolean(cfg.decodePercentEncoding)
4165 ? cfg.decodePercentEncoding
4166 : this.decodePercentEncoding;
4167 this.sanitizeHtml = cfg.sanitizeHtml || false;
4168 // Validate the value of the `mention` cfg
4169 var mention = this.mention;
4170 if (mention !== false && mentionServices.indexOf(mention) === -1) {
4171 throw new Error("invalid `mention` cfg '".concat(mention, "' - see docs"));
4172 }
4173 // Validate the value of the `hashtag` cfg
4174 var hashtag = this.hashtag;
4175 if (hashtag !== false && hashtagServices.indexOf(hashtag) === -1) {
4176 throw new Error("invalid `hashtag` cfg '".concat(hashtag, "' - see docs"));
4177 }
4178 this.truncate = normalizeTruncateCfg(cfg.truncate);
4179 this.className = cfg.className || this.className;
4180 this.replaceFn = cfg.replaceFn || this.replaceFn;
4181 this.context = cfg.context || this;
4182 }
4183 /**
4184 * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
4185 * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs
4186 * found within HTML tags.
4187 *
4188 * For instance, if given the text: `You should go to http://www.yahoo.com`,
4189 * then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
4190 *
4191 * Example:
4192 *
4193 * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
4194 * // Produces: "Go to <a href="http://google.com">google.com</a>"
4195 *
4196 * @static
4197 * @param {String} textOrHtml The HTML or text to find matches within (depending
4198 * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #mention},
4199 * {@link #hashtag}, and {@link #mention} options are enabled).
4200 * @param {Object} [options] Any of the configuration options for the Autolinker
4201 * class, specified in an Object (map). See the class description for an
4202 * example call.
4203 * @return {String} The HTML text, with matches automatically linked.
4204 */
4205 Autolinker.link = function (textOrHtml, options) {
4206 var autolinker = new Autolinker(options);
4207 return autolinker.link(textOrHtml);
4208 };
4209 /**
4210 * Parses the input `textOrHtml` looking for URLs, email addresses, phone
4211 * numbers, username handles, and hashtags (depending on the configuration
4212 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
4213 * objects describing those matches (without making any replacements).
4214 *
4215 * Note that if parsing multiple pieces of text, it is slightly more efficient
4216 * to create an Autolinker instance, and use the instance-level {@link #parse}
4217 * method.
4218 *
4219 * Example:
4220 *
4221 * var matches = Autolinker.parse( "Hello google.com, I am asdf@asdf.com", {
4222 * urls: true,
4223 * email: true
4224 * } );
4225 *
4226 * console.log( matches.length ); // 2
4227 * console.log( matches[ 0 ].getType() ); // 'url'
4228 * console.log( matches[ 0 ].getUrl() ); // 'google.com'
4229 * console.log( matches[ 1 ].getType() ); // 'email'
4230 * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
4231 *
4232 * @static
4233 * @param {String} textOrHtml The HTML or text to find matches within
4234 * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
4235 * {@link #hashtag}, and {@link #mention} options are enabled).
4236 * @param {Object} [options] Any of the configuration options for the Autolinker
4237 * class, specified in an Object (map). See the class description for an
4238 * example call.
4239 * @return {Autolinker.match.Match[]} The array of Matches found in the
4240 * given input `textOrHtml`.
4241 */
4242 Autolinker.parse = function (textOrHtml, options) {
4243 var autolinker = new Autolinker(options);
4244 return autolinker.parse(textOrHtml);
4245 };
4246 /**
4247 * Parses the input `textOrHtml` looking for URLs, email addresses, phone
4248 * numbers, username handles, and hashtags (depending on the configuration
4249 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
4250 * objects describing those matches (without making any replacements).
4251 *
4252 * This method is used by the {@link #link} method, but can also be used to
4253 * simply do parsing of the input in order to discover what kinds of links
4254 * there are and how many.
4255 *
4256 * Example usage:
4257 *
4258 * var autolinker = new Autolinker( {
4259 * urls: true,
4260 * email: true
4261 * } );
4262 *
4263 * var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
4264 *
4265 * console.log( matches.length ); // 2
4266 * console.log( matches[ 0 ].getType() ); // 'url'
4267 * console.log( matches[ 0 ].getUrl() ); // 'google.com'
4268 * console.log( matches[ 1 ].getType() ); // 'email'
4269 * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
4270 *
4271 * @param {String} textOrHtml The HTML or text to find matches within
4272 * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
4273 * {@link #hashtag}, and {@link #mention} options are enabled).
4274 * @return {Autolinker.match.Match[]} The array of Matches found in the
4275 * given input `textOrHtml`.
4276 */
4277 Autolinker.prototype.parse = function (textOrHtml) {
4278 var _this = this;
4279 var skipTagNames = ['a', 'style', 'script'], skipTagsStackCount = 0, // used to only Autolink text outside of anchor/script/style tags. We don't want to autolink something that is already linked inside of an <a> tag, for instance
4280 matches = [];
4281 // Find all matches within the `textOrHtml` (but not matches that are
4282 // already nested within <a>, <style> and <script> tags)
4283 parseHtml(textOrHtml, {
4284 onOpenTag: function (tagName) {
4285 if (skipTagNames.indexOf(tagName) >= 0) {
4286 skipTagsStackCount++;
4287 }
4288 },
4289 onText: function (text, offset) {
4290 // Only process text nodes that are not within an <a>, <style> or <script> tag
4291 if (skipTagsStackCount === 0) {
4292 // "Walk around" common HTML entities. An '&nbsp;' (for example)
4293 // could be at the end of a URL, but we don't want to
4294 // include the trailing '&' in the URL. See issue #76
4295 // TODO: Handle HTML entities separately in parseHtml() and
4296 // don't emit them as "text" except for &amp; entities
4297 var htmlCharacterEntitiesRegex = /(&nbsp;|&#160;|&lt;|&#60;|&gt;|&#62;|&quot;|&#34;|&#39;)/gi; // NOTE: capturing group is significant to include the split characters in the .split() call below
4298 var textSplit = text.split(htmlCharacterEntitiesRegex);
4299 var currentOffset_1 = offset;
4300 textSplit.forEach(function (splitText, i) {
4301 // even number matches are text, odd numbers are html entities
4302 if (i % 2 === 0) {
4303 var textNodeMatches = _this.parseText(splitText, currentOffset_1);
4304 matches.push.apply(matches, textNodeMatches);
4305 }
4306 currentOffset_1 += splitText.length;
4307 });
4308 }
4309 },
4310 onCloseTag: function (tagName) {
4311 if (skipTagNames.indexOf(tagName) >= 0) {
4312 skipTagsStackCount = Math.max(skipTagsStackCount - 1, 0); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
4313 }
4314 },
4315 onComment: function (_offset) { },
4316 onDoctype: function (_offset) { }, // no need to process doctype nodes
4317 });
4318 // After we have found all matches, remove subsequent matches that
4319 // overlap with a previous match. This can happen for instance with URLs,
4320 // where the url 'google.com/#link' would match '#link' as a hashtag.
4321 matches = this.compactMatches(matches);
4322 // And finally, remove matches for match types that have been turned
4323 // off. We needed to have all match types turned on initially so that
4324 // things like hashtags could be filtered out if they were really just
4325 // part of a URL match (for instance, as a named anchor).
4326 matches = this.removeUnwantedMatches(matches);
4327 return matches;
4328 };
4329 /**
4330 * After we have found all matches, we need to remove matches that overlap
4331 * with a previous match. This can happen for instance with URLs, where the
4332 * url 'google.com/#link' would match '#link' as a hashtag. Because the
4333 * '#link' part is contained in a larger match that comes before the HashTag
4334 * match, we'll remove the HashTag match.
4335 *
4336 * @private
4337 * @param {Autolinker.match.Match[]} matches
4338 * @return {Autolinker.match.Match[]}
4339 */
4340 Autolinker.prototype.compactMatches = function (matches) {
4341 // First, the matches need to be sorted in order of offset
4342 matches.sort(function (a, b) {
4343 return a.getOffset() - b.getOffset();
4344 });
4345 var i = 0;
4346 while (i < matches.length - 1) {
4347 var match = matches[i], offset = match.getOffset(), matchedTextLength = match.getMatchedText().length, endIdx = offset + matchedTextLength;
4348 if (i + 1 < matches.length) {
4349 // Remove subsequent matches that equal offset with current match
4350 if (matches[i + 1].getOffset() === offset) {
4351 var removeIdx = matches[i + 1].getMatchedText().length > matchedTextLength ? i : i + 1;
4352 matches.splice(removeIdx, 1);
4353 continue;
4354 }
4355 // Remove subsequent matches that overlap with the current match
4356 if (matches[i + 1].getOffset() < endIdx) {
4357 matches.splice(i + 1, 1);
4358 continue;
4359 }
4360 }
4361 i++;
4362 }
4363 return matches;
4364 };
4365 /**
4366 * Removes matches for matchers that were turned off in the options. For
4367 * example, if {@link #hashtag hashtags} were not to be matched, we'll
4368 * remove them from the `matches` array here.
4369 *
4370 * Note: we *must* use all Matchers on the input string, and then filter
4371 * them out later. For example, if the options were `{ url: false, hashtag: true }`,
4372 * we wouldn't want to match the text '#link' as a HashTag inside of the text
4373 * 'google.com/#link'. The way the algorithm works is that we match the full
4374 * URL first (which prevents the accidental HashTag match), and then we'll
4375 * simply throw away the URL match.
4376 *
4377 * @private
4378 * @param {Autolinker.match.Match[]} matches The array of matches to remove
4379 * the unwanted matches from. Note: this array is mutated for the
4380 * removals.
4381 * @return {Autolinker.match.Match[]} The mutated input `matches` array.
4382 */
4383 Autolinker.prototype.removeUnwantedMatches = function (matches) {
4384 if (!this.hashtag)
4385 removeWithPredicate(matches, function (match) {
4386 return match.getType() === 'hashtag';
4387 });
4388 if (!this.email)
4389 removeWithPredicate(matches, function (match) {
4390 return match.getType() === 'email';
4391 });
4392 if (!this.phone)
4393 removeWithPredicate(matches, function (match) {
4394 return match.getType() === 'phone';
4395 });
4396 if (!this.mention)
4397 removeWithPredicate(matches, function (match) {
4398 return match.getType() === 'mention';
4399 });
4400 if (!this.urls.schemeMatches) {
4401 removeWithPredicate(matches, function (m) {
4402 return m.getType() === 'url' && m.getUrlMatchType() === 'scheme';
4403 });
4404 }
4405 if (!this.urls.tldMatches) {
4406 removeWithPredicate(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'tld'; });
4407 }
4408 if (!this.urls.ipV4Matches) {
4409 removeWithPredicate(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'ipV4'; });
4410 }
4411 return matches;
4412 };
4413 /**
4414 * Parses the input `text` looking for URLs, email addresses, phone
4415 * numbers, username handles, and hashtags (depending on the configuration
4416 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
4417 * objects describing those matches.
4418 *
4419 * This method processes a **non-HTML string**, and is used to parse and
4420 * match within the text nodes of an HTML string. This method is used
4421 * internally by {@link #parse}.
4422 *
4423 * @private
4424 * @param {String} text The text to find matches within (depending on if the
4425 * {@link #urls}, {@link #email}, {@link #phone},
4426 * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string.
4427 * @param {Number} [offset=0] The offset of the text node within the
4428 * original string. This is used when parsing with the {@link #parse}
4429 * method to generate correct offsets within the {@link Autolinker.match.Match}
4430 * instances, but may be omitted if calling this method publicly.
4431 * @return {Autolinker.match.Match[]} The array of Matches found in the
4432 * given input `text`.
4433 */
4434 Autolinker.prototype.parseText = function (text, offset) {
4435 if (offset === void 0) { offset = 0; }
4436 offset = offset || 0;
4437 var matches = parseMatches(text, {
4438 tagBuilder: this.getTagBuilder(),
4439 stripPrefix: this.stripPrefix,
4440 stripTrailingSlash: this.stripTrailingSlash,
4441 decodePercentEncoding: this.decodePercentEncoding,
4442 hashtagServiceName: this.hashtag,
4443 mentionServiceName: this.mention || 'twitter',
4444 });
4445 // Correct the offset of each of the matches. They are originally
4446 // the offset of the match within the provided text node, but we
4447 // need to correct them to be relative to the original HTML input
4448 // string (i.e. the one provided to #parse).
4449 for (var i = 0, numTextMatches = matches.length; i < numTextMatches; i++) {
4450 matches[i].setOffset(offset + matches[i].getOffset());
4451 }
4452 return matches;
4453 };
4454 /**
4455 * Automatically links URLs, Email addresses, Phone numbers, Hashtags,
4456 * and Mentions (Twitter, Instagram, Soundcloud) found in the given chunk of HTML. Does not link
4457 * URLs found within HTML tags.
4458 *
4459 * For instance, if given the text: `You should go to http://www.yahoo.com`,
4460 * then the result will be `You should go to
4461 * &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
4462 *
4463 * This method finds the text around any HTML elements in the input
4464 * `textOrHtml`, which will be the text that is processed. Any original HTML
4465 * elements will be left as-is, as well as the text that is already wrapped
4466 * in anchor (&lt;a&gt;) tags.
4467 *
4468 * @param {String} textOrHtml The HTML or text to autolink matches within
4469 * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled).
4470 * @return {String} The HTML, with matches automatically linked.
4471 */
4472 Autolinker.prototype.link = function (textOrHtml) {
4473 if (!textOrHtml) {
4474 return '';
4475 } // handle `null` and `undefined` (for JavaScript users that don't have TypeScript support)
4476 /* We would want to sanitize the start and end characters of a tag
4477 * before processing the string in order to avoid an XSS scenario.
4478 * This behaviour can be changed by toggling the sanitizeHtml option.
4479 */
4480 if (this.sanitizeHtml) {
4481 textOrHtml = textOrHtml.replace(/</g, '&lt;').replace(/>/g, '&gt;');
4482 }
4483 var matches = this.parse(textOrHtml), newHtml = [], lastIndex = 0;
4484 for (var i = 0, len = matches.length; i < len; i++) {
4485 var match = matches[i];
4486 newHtml.push(textOrHtml.substring(lastIndex, match.getOffset()));
4487 newHtml.push(this.createMatchReturnVal(match));
4488 lastIndex = match.getOffset() + match.getMatchedText().length;
4489 }
4490 newHtml.push(textOrHtml.substring(lastIndex)); // handle the text after the last match
4491 return newHtml.join('');
4492 };
4493 /**
4494 * Creates the return string value for a given match in the input string.
4495 *
4496 * This method handles the {@link #replaceFn}, if one was provided.
4497 *
4498 * @private
4499 * @param {Autolinker.match.Match} match The Match object that represents
4500 * the match.
4501 * @return {String} The string that the `match` should be replaced with.
4502 * This is usually the anchor tag string, but may be the `matchStr` itself
4503 * if the match is not to be replaced.
4504 */
4505 Autolinker.prototype.createMatchReturnVal = function (match) {
4506 // Handle a custom `replaceFn` being provided
4507 var replaceFnResult;
4508 if (this.replaceFn) {
4509 replaceFnResult = this.replaceFn.call(this.context, match); // Autolinker instance is the context
4510 }
4511 if (typeof replaceFnResult === 'string') {
4512 return replaceFnResult; // `replaceFn` returned a string, use that
4513 }
4514 else if (replaceFnResult === false) {
4515 return match.getMatchedText(); // no replacement for the match
4516 }
4517 else if (replaceFnResult instanceof HtmlTag) {
4518 return replaceFnResult.toAnchorString();
4519 }
4520 else {
4521 // replaceFnResult === true, or no/unknown return value from function
4522 // Perform Autolinker's default anchor tag generation
4523 var anchorTag = match.buildTag(); // returns an Autolinker.HtmlTag instance
4524 return anchorTag.toAnchorString();
4525 }
4526 };
4527 /**
4528 * Returns the {@link #tagBuilder} instance for this Autolinker instance,
4529 * lazily instantiating it if it does not yet exist.
4530 *
4531 * @private
4532 * @return {Autolinker.AnchorTagBuilder}
4533 */
4534 Autolinker.prototype.getTagBuilder = function () {
4535 var tagBuilder = this.tagBuilder;
4536 if (!tagBuilder) {
4537 tagBuilder = this.tagBuilder = new AnchorTagBuilder({
4538 newWindow: this.newWindow,
4539 truncate: this.truncate,
4540 className: this.className,
4541 });
4542 }
4543 return tagBuilder;
4544 };
4545 // NOTE: must be 'export default' here for UMD module
4546 /**
4547 * @static
4548 * @property {String} version
4549 *
4550 * The Autolinker version number in the form major.minor.patch
4551 *
4552 * Ex: 3.15.0
4553 */
4554 Autolinker.version = version;
4555 return Autolinker;
4556 }());
4557 /**
4558 * Normalizes the {@link #urls} config into an Object with its 2 properties:
4559 * `schemeMatches` and `tldMatches`, both booleans.
4560 *
4561 * See {@link #urls} config for details.
4562 *
4563 * @private
4564 * @param {Boolean/Object} urls
4565 * @return {Object}
4566 */
4567 function normalizeUrlsCfg(urls) {
4568 if (urls == null)
4569 urls = true; // default to `true`
4570 if (isBoolean(urls)) {
4571 return { schemeMatches: urls, tldMatches: urls, ipV4Matches: urls };
4572 }
4573 else {
4574 // object form
4575 return {
4576 schemeMatches: isBoolean(urls.schemeMatches) ? urls.schemeMatches : true,
4577 tldMatches: isBoolean(urls.tldMatches) ? urls.tldMatches : true,
4578 ipV4Matches: isBoolean(urls.ipV4Matches) ? urls.ipV4Matches : true,
4579 };
4580 }
4581 }
4582 /**
4583 * Normalizes the {@link #stripPrefix} config into an Object with 2
4584 * properties: `scheme`, and `www` - both Booleans.
4585 *
4586 * See {@link #stripPrefix} config for details.
4587 *
4588 * @private
4589 * @param {Boolean/Object} stripPrefix
4590 * @return {Object}
4591 */
4592 function normalizeStripPrefixCfg(stripPrefix) {
4593 if (stripPrefix == null)
4594 stripPrefix = true; // default to `true`
4595 if (isBoolean(stripPrefix)) {
4596 return { scheme: stripPrefix, www: stripPrefix };
4597 }
4598 else {
4599 // object form
4600 return {
4601 scheme: isBoolean(stripPrefix.scheme) ? stripPrefix.scheme : true,
4602 www: isBoolean(stripPrefix.www) ? stripPrefix.www : true,
4603 };
4604 }
4605 }
4606 /**
4607 * Normalizes the {@link #truncate} config into an Object with 2 properties:
4608 * `length` (Number), and `location` (String).
4609 *
4610 * See {@link #truncate} config for details.
4611 *
4612 * @private
4613 * @param {Number/Object} truncate
4614 * @return {Object}
4615 */
4616 function normalizeTruncateCfg(truncate) {
4617 if (typeof truncate === 'number') {
4618 return { length: truncate, location: 'end' };
4619 }
4620 else {
4621 // object, or undefined/null
4622 return defaults(truncate || {}, {
4623 length: Number.POSITIVE_INFINITY,
4624 location: 'end',
4625 });
4626 }
4627 }
4628
4629 return Autolinker;
4630
4631}));
4632//# sourceMappingURL=autolinker.js.map