UNPKG

39.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var version_1 = require("./version");
4var utils_1 = require("./utils");
5var anchor_tag_builder_1 = require("./anchor-tag-builder");
6var html_tag_1 = require("./html-tag");
7var parse_matches_1 = require("./parser/parse-matches");
8var parse_html_1 = require("./htmlParser/parse-html");
9var mention_utils_1 = require("./parser/mention-utils");
10var hashtag_utils_1 = require("./parser/hashtag-utils");
11/**
12 * @class Autolinker
13 * @extends Object
14 *
15 * Utility class used to process a given string of text, and wrap the matches in
16 * the appropriate anchor (<a>) tags to turn them into links.
17 *
18 * Any of the configuration options may be provided in an Object provided
19 * to the Autolinker constructor, which will configure how the {@link #link link()}
20 * method will process the links.
21 *
22 * For example:
23 *
24 * var autolinker = new Autolinker( {
25 * newWindow : false,
26 * truncate : 30
27 * } );
28 *
29 * var html = autolinker.link( "Joe went to www.yahoo.com" );
30 * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
31 *
32 *
33 * The {@link #static-link static link()} method may also be used to inline
34 * options into a single call, which may be more convenient for one-off uses.
35 * For example:
36 *
37 * var html = Autolinker.link( "Joe went to www.yahoo.com", {
38 * newWindow : false,
39 * truncate : 30
40 * } );
41 * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
42 *
43 *
44 * ## Custom Replacements of Links
45 *
46 * If the configuration options do not provide enough flexibility, a {@link #replaceFn}
47 * may be provided to fully customize the output of Autolinker. This function is
48 * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
49 * match that is encountered.
50 *
51 * For example:
52 *
53 * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
54 *
55 * var linkedText = Autolinker.link( input, {
56 * replaceFn : function( match ) {
57 * console.log( "href = ", match.getAnchorHref() );
58 * console.log( "text = ", match.getAnchorText() );
59 *
60 * switch( match.getType() ) {
61 * case 'url' :
62 * console.log( "url: ", match.getUrl() );
63 *
64 * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
65 * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
66 * tag.setAttr( 'rel', 'nofollow' );
67 * tag.addClass( 'external-link' );
68 *
69 * return tag;
70 *
71 * } else {
72 * return true; // let Autolinker perform its normal anchor tag replacement
73 * }
74 *
75 * case 'email' :
76 * var email = match.getEmail();
77 * console.log( "email: ", email );
78 *
79 * if( email === "my@own.address" ) {
80 * return false; // don't auto-link this particular email address; leave as-is
81 * } else {
82 * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
83 * }
84 *
85 * case 'phone' :
86 * var phoneNumber = match.getPhoneNumber();
87 * console.log( phoneNumber );
88 *
89 * return '<a href="http://newplace.to.link.phone.numbers.to/">' + phoneNumber + '</a>';
90 *
91 * case 'hashtag' :
92 * var hashtag = match.getHashtag();
93 * console.log( hashtag );
94 *
95 * return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
96 *
97 * case 'mention' :
98 * var mention = match.getMention();
99 * console.log( mention );
100 *
101 * return '<a href="http://newplace.to.link.mention.to/">' + mention + '</a>';
102 * }
103 * }
104 * } );
105 *
106 *
107 * The function may return the following values:
108 *
109 * - `true` (Boolean): Allow Autolinker to replace the match as it normally
110 * would.
111 * - `false` (Boolean): Do not replace the current match at all - leave as-is.
112 * - Any String: If a string is returned from the function, the string will be
113 * used directly as the replacement HTML for the match.
114 * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify
115 * an HTML tag before writing out its HTML text.
116 */
117var Autolinker = /** @class */ (function () {
118 /**
119 * @method constructor
120 * @param {Object} [cfg] The configuration options for the Autolinker instance,
121 * specified in an Object (map).
122 */
123 function Autolinker(cfg) {
124 if (cfg === void 0) { cfg = {}; }
125 /**
126 * The Autolinker version number exposed on the instance itself.
127 *
128 * Ex: 0.25.1
129 *
130 * @property {String} version
131 */
132 this.version = Autolinker.version;
133 /**
134 * @cfg {Boolean/Object} [urls]
135 *
136 * `true` if URLs should be automatically linked, `false` if they should not
137 * be. Defaults to `true`.
138 *
139 * Examples:
140 *
141 * urls: true
142 *
143 * // or
144 *
145 * urls: {
146 * schemeMatches : true,
147 * tldMatches : true,
148 * ipV4Matches : true
149 * }
150 *
151 * As shown above, this option also accepts an Object form with 3 properties
152 * to allow for more customization of what exactly gets linked. All default
153 * to `true`:
154 *
155 * @cfg {Boolean} [urls.schemeMatches] `true` to match URLs found prefixed
156 * with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`,
157 * `false` to prevent these types of matches.
158 * @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
159 * level domains (.com, .net, etc.) that are not prefixed with a scheme
160 * (such as 'http://'). This option attempts to match anything that looks
161 * like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
162 * `false` to prevent these types of matches.
163 * @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
164 * that are not prefixed with a scheme (such as 'http://'). This option
165 * attempts to match anything that looks like an IPv4 address in text. Ex:
166 * `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
167 * of matches.
168 */
169 this.urls = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
170 /**
171 * @cfg {Boolean} [email=true]
172 *
173 * `true` if email addresses should be automatically linked, `false` if they
174 * should not be.
175 */
176 this.email = true; // default value just to get the above doc comment in the ES5 output and documentation generator
177 /**
178 * @cfg {Boolean} [phone=true]
179 *
180 * `true` if Phone numbers ("(555)555-5555") should be automatically linked,
181 * `false` if they should not be.
182 */
183 this.phone = true; // default value just to get the above doc comment in the ES5 output and documentation generator
184 /**
185 * @cfg {Boolean/String} [hashtag=false]
186 *
187 * A string for the service name to have hashtags (ex: "#myHashtag")
188 * auto-linked to. The currently-supported values are:
189 *
190 * - 'twitter'
191 * - 'facebook'
192 * - 'instagram'
193 *
194 * Pass `false` to skip auto-linking of hashtags.
195 */
196 this.hashtag = false; // default value just to get the above doc comment in the ES5 output and documentation generator
197 /**
198 * @cfg {String/Boolean} [mention=false]
199 *
200 * A string for the service name to have mentions (ex: "@myuser")
201 * auto-linked to. The currently supported values are:
202 *
203 * - 'twitter'
204 * - 'instagram'
205 * - 'soundcloud'
206 * - 'tiktok'
207 *
208 * Defaults to `false` to skip auto-linking of mentions.
209 */
210 this.mention = false; // default value just to get the above doc comment in the ES5 output and documentation generator
211 /**
212 * @cfg {Boolean} [newWindow=true]
213 *
214 * `true` if the links should open in a new window, `false` otherwise.
215 */
216 this.newWindow = true; // default value just to get the above doc comment in the ES5 output and documentation generator
217 /**
218 * @cfg {Boolean/Object} [stripPrefix=true]
219 *
220 * `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
221 * from the beginning of URL links' text, `false` otherwise. Defaults to
222 * `true`.
223 *
224 * Examples:
225 *
226 * stripPrefix: true
227 *
228 * // or
229 *
230 * stripPrefix: {
231 * scheme : true,
232 * www : true
233 * }
234 *
235 * As shown above, this option also accepts an Object form with 2 properties
236 * to allow for more customization of what exactly is prevented from being
237 * displayed. Both default to `true`:
238 *
239 * @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
240 * a URL match from being displayed to the user. Example:
241 * `'http://google.com'` will be displayed as `'google.com'`. `false` to
242 * not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
243 * will be removed, so as not to remove a potentially dangerous scheme
244 * (such as `'file://'` or `'javascript:'`)
245 * @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
246 * `'www.'` part of a URL match from being displayed to the user. Ex:
247 * `'www.google.com'` will be displayed as `'google.com'`. `false` to not
248 * strip the `'www'`.
249 */
250 this.stripPrefix = {
251 scheme: true,
252 www: true,
253 }; // default value just to get the above doc comment in the ES5 output and documentation generator
254 /**
255 * @cfg {Boolean} [stripTrailingSlash=true]
256 *
257 * `true` to remove the trailing slash from URL matches, `false` to keep
258 * the trailing slash.
259 *
260 * Example when `true`: `http://google.com/` will be displayed as
261 * `http://google.com`.
262 */
263 this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
264 /**
265 * @cfg {Boolean} [decodePercentEncoding=true]
266 *
267 * `true` to decode percent-encoded characters in URL matches, `false` to keep
268 * the percent-encoded characters.
269 *
270 * Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
271 * be displayed as `https://en.wikipedia.org/wiki/San_José`.
272 */
273 this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
274 /**
275 * @cfg {Number/Object} [truncate=0]
276 *
277 * ## Number Form
278 *
279 * A number for how many characters matched text should be truncated to
280 * inside the text of a link. If the matched text is over this number of
281 * characters, it will be truncated to this length by adding a two period
282 * ellipsis ('..') to the end of the string.
283 *
284 * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file'
285 * truncated to 25 characters might look something like this:
286 * 'yahoo.com/some/long/pat..'
287 *
288 * Example Usage:
289 *
290 * truncate: 25
291 *
292 *
293 * Defaults to `0` for "no truncation."
294 *
295 *
296 * ## Object Form
297 *
298 * An Object may also be provided with two properties: `length` (Number) and
299 * `location` (String). `location` may be one of the following: 'end'
300 * (default), 'middle', or 'smart'.
301 *
302 * Example Usage:
303 *
304 * truncate: { length: 25, location: 'middle' }
305 *
306 * @cfg {Number} [truncate.length=0] How many characters to allow before
307 * truncation will occur. Defaults to `0` for "no truncation."
308 * @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
309 *
310 * - 'end' (default): will truncate up to the number of characters, and then
311 * add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
312 * - 'middle': will truncate and add the ellipsis in the middle. Ex:
313 * 'yahoo.com/s..th/to/a/file'
314 * - 'smart': for URLs where the algorithm attempts to strip out unnecessary
315 * parts first (such as the 'www.', then URL scheme, hash, etc.),
316 * attempting to make the URL human-readable before looking for a good
317 * point to insert the ellipsis if it is still too long. Ex:
318 * 'yahoo.com/some..to/a/file'. For more details, see
319 * {@link Autolinker.truncate.TruncateSmart}.
320 */
321 this.truncate = {
322 length: 0,
323 location: 'end',
324 }; // default value just to get the above doc comment in the ES5 output and documentation generator
325 /**
326 * @cfg {String} className
327 *
328 * A CSS class name to add to the generated links. This class will be added
329 * to all links, as well as this class plus match suffixes for styling
330 * url/email/phone/hashtag/mention links differently.
331 *
332 * For example, if this config is provided as "myLink", then:
333 *
334 * - URL links will have the CSS classes: "myLink myLink-url"
335 * - Email links will have the CSS classes: "myLink myLink-email", and
336 * - Phone links will have the CSS classes: "myLink myLink-phone"
337 * - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
338 * - Mention links will have the CSS classes: "myLink myLink-mention myLink-[type]"
339 * where [type] is either "instagram", "twitter" or "soundcloud"
340 */
341 this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
342 /**
343 * @cfg {Function} replaceFn
344 *
345 * A function to individually process each match found in the input string.
346 *
347 * See the class's description for usage.
348 *
349 * The `replaceFn` can be called with a different context object (`this`
350 * reference) using the {@link #context} cfg.
351 *
352 * This function is called with the following parameter:
353 *
354 * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which
355 * can be used to retrieve information about the match that the `replaceFn`
356 * is currently processing. See {@link Autolinker.match.Match} subclasses
357 * for details.
358 */
359 this.replaceFn = null; // default value just to get the above doc comment in the ES5 output and documentation generator
360 /**
361 * @cfg {Object} context
362 *
363 * The context object (`this` reference) to call the `replaceFn` with.
364 *
365 * Defaults to this Autolinker instance.
366 */
367 this.context = undefined; // default value just to get the above doc comment in the ES5 output and documentation generator
368 /**
369 * @cfg {Boolean} [sanitizeHtml=false]
370 *
371 * `true` to HTML-encode the start and end brackets of existing HTML tags found
372 * in the input string. This will escape `<` and `>` characters to `&lt;` and
373 * `&gt;`, respectively.
374 *
375 * Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
376 * but will remove the significance of existing HTML tags in the input string. If
377 * you would like to maintain the significance of existing HTML tags while also
378 * making the output HTML string safe, leave this option as `false` and use a
379 * tool like https://github.com/cure53/DOMPurify (or others) on the input string
380 * before running Autolinker.
381 */
382 this.sanitizeHtml = false; // default value just to get the above doc comment in the ES5 output and documentation generator
383 /**
384 * @private
385 * @property {Autolinker.AnchorTagBuilder} tagBuilder
386 *
387 * The AnchorTagBuilder instance used to build match replacement anchor tags.
388 * Note: this is lazily instantiated in the {@link #getTagBuilder} method.
389 */
390 this.tagBuilder = null;
391 // Note: when `this.something` is used in the rhs of these assignments,
392 // it refers to the default values set above the constructor
393 this.urls = normalizeUrlsCfg(cfg.urls);
394 this.email = (0, utils_1.isBoolean)(cfg.email) ? cfg.email : this.email;
395 this.phone = (0, utils_1.isBoolean)(cfg.phone) ? cfg.phone : this.phone;
396 this.hashtag = cfg.hashtag || this.hashtag;
397 this.mention = cfg.mention || this.mention;
398 this.newWindow = (0, utils_1.isBoolean)(cfg.newWindow) ? cfg.newWindow : this.newWindow;
399 this.stripPrefix = normalizeStripPrefixCfg(cfg.stripPrefix);
400 this.stripTrailingSlash = (0, utils_1.isBoolean)(cfg.stripTrailingSlash)
401 ? cfg.stripTrailingSlash
402 : this.stripTrailingSlash;
403 this.decodePercentEncoding = (0, utils_1.isBoolean)(cfg.decodePercentEncoding)
404 ? cfg.decodePercentEncoding
405 : this.decodePercentEncoding;
406 this.sanitizeHtml = cfg.sanitizeHtml || false;
407 // Validate the value of the `mention` cfg
408 var mention = this.mention;
409 if (mention !== false && mention_utils_1.mentionServices.indexOf(mention) === -1) {
410 throw new Error("invalid `mention` cfg '".concat(mention, "' - see docs"));
411 }
412 // Validate the value of the `hashtag` cfg
413 var hashtag = this.hashtag;
414 if (hashtag !== false && hashtag_utils_1.hashtagServices.indexOf(hashtag) === -1) {
415 throw new Error("invalid `hashtag` cfg '".concat(hashtag, "' - see docs"));
416 }
417 this.truncate = normalizeTruncateCfg(cfg.truncate);
418 this.className = cfg.className || this.className;
419 this.replaceFn = cfg.replaceFn || this.replaceFn;
420 this.context = cfg.context || this;
421 }
422 /**
423 * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
424 * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs
425 * found within HTML tags.
426 *
427 * For instance, if given the text: `You should go to http://www.yahoo.com`,
428 * then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
429 *
430 * Example:
431 *
432 * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
433 * // Produces: "Go to <a href="http://google.com">google.com</a>"
434 *
435 * @static
436 * @param {String} textOrHtml The HTML or text to find matches within (depending
437 * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #mention},
438 * {@link #hashtag}, and {@link #mention} options are enabled).
439 * @param {Object} [options] Any of the configuration options for the Autolinker
440 * class, specified in an Object (map). See the class description for an
441 * example call.
442 * @return {String} The HTML text, with matches automatically linked.
443 */
444 Autolinker.link = function (textOrHtml, options) {
445 var autolinker = new Autolinker(options);
446 return autolinker.link(textOrHtml);
447 };
448 /**
449 * Parses the input `textOrHtml` looking for URLs, email addresses, phone
450 * numbers, username handles, and hashtags (depending on the configuration
451 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
452 * objects describing those matches (without making any replacements).
453 *
454 * Note that if parsing multiple pieces of text, it is slightly more efficient
455 * to create an Autolinker instance, and use the instance-level {@link #parse}
456 * method.
457 *
458 * Example:
459 *
460 * var matches = Autolinker.parse( "Hello google.com, I am asdf@asdf.com", {
461 * urls: true,
462 * email: true
463 * } );
464 *
465 * console.log( matches.length ); // 2
466 * console.log( matches[ 0 ].getType() ); // 'url'
467 * console.log( matches[ 0 ].getUrl() ); // 'google.com'
468 * console.log( matches[ 1 ].getType() ); // 'email'
469 * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
470 *
471 * @static
472 * @param {String} textOrHtml The HTML or text to find matches within
473 * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
474 * {@link #hashtag}, and {@link #mention} options are enabled).
475 * @param {Object} [options] Any of the configuration options for the Autolinker
476 * class, specified in an Object (map). See the class description for an
477 * example call.
478 * @return {Autolinker.match.Match[]} The array of Matches found in the
479 * given input `textOrHtml`.
480 */
481 Autolinker.parse = function (textOrHtml, options) {
482 var autolinker = new Autolinker(options);
483 return autolinker.parse(textOrHtml);
484 };
485 /**
486 * Parses the input `textOrHtml` looking for URLs, email addresses, phone
487 * numbers, username handles, and hashtags (depending on the configuration
488 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
489 * objects describing those matches (without making any replacements).
490 *
491 * This method is used by the {@link #link} method, but can also be used to
492 * simply do parsing of the input in order to discover what kinds of links
493 * there are and how many.
494 *
495 * Example usage:
496 *
497 * var autolinker = new Autolinker( {
498 * urls: true,
499 * email: true
500 * } );
501 *
502 * var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
503 *
504 * console.log( matches.length ); // 2
505 * console.log( matches[ 0 ].getType() ); // 'url'
506 * console.log( matches[ 0 ].getUrl() ); // 'google.com'
507 * console.log( matches[ 1 ].getType() ); // 'email'
508 * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
509 *
510 * @param {String} textOrHtml The HTML or text to find matches within
511 * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
512 * {@link #hashtag}, and {@link #mention} options are enabled).
513 * @return {Autolinker.match.Match[]} The array of Matches found in the
514 * given input `textOrHtml`.
515 */
516 Autolinker.prototype.parse = function (textOrHtml) {
517 var _this = this;
518 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
519 matches = [];
520 // Find all matches within the `textOrHtml` (but not matches that are
521 // already nested within <a>, <style> and <script> tags)
522 (0, parse_html_1.parseHtml)(textOrHtml, {
523 onOpenTag: function (tagName) {
524 if (skipTagNames.indexOf(tagName) >= 0) {
525 skipTagsStackCount++;
526 }
527 },
528 onText: function (text, offset) {
529 // Only process text nodes that are not within an <a>, <style> or <script> tag
530 if (skipTagsStackCount === 0) {
531 // "Walk around" common HTML entities. An '&nbsp;' (for example)
532 // could be at the end of a URL, but we don't want to
533 // include the trailing '&' in the URL. See issue #76
534 // TODO: Handle HTML entities separately in parseHtml() and
535 // don't emit them as "text" except for &amp; entities
536 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
537 var textSplit = text.split(htmlCharacterEntitiesRegex);
538 var currentOffset_1 = offset;
539 textSplit.forEach(function (splitText, i) {
540 // even number matches are text, odd numbers are html entities
541 if (i % 2 === 0) {
542 var textNodeMatches = _this.parseText(splitText, currentOffset_1);
543 matches.push.apply(matches, textNodeMatches);
544 }
545 currentOffset_1 += splitText.length;
546 });
547 }
548 },
549 onCloseTag: function (tagName) {
550 if (skipTagNames.indexOf(tagName) >= 0) {
551 skipTagsStackCount = Math.max(skipTagsStackCount - 1, 0); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
552 }
553 },
554 onComment: function (_offset) { },
555 onDoctype: function (_offset) { }, // no need to process doctype nodes
556 });
557 // After we have found all matches, remove subsequent matches that
558 // overlap with a previous match. This can happen for instance with URLs,
559 // where the url 'google.com/#link' would match '#link' as a hashtag.
560 matches = this.compactMatches(matches);
561 // And finally, remove matches for match types that have been turned
562 // off. We needed to have all match types turned on initially so that
563 // things like hashtags could be filtered out if they were really just
564 // part of a URL match (for instance, as a named anchor).
565 matches = this.removeUnwantedMatches(matches);
566 return matches;
567 };
568 /**
569 * After we have found all matches, we need to remove matches that overlap
570 * with a previous match. This can happen for instance with URLs, where the
571 * url 'google.com/#link' would match '#link' as a hashtag. Because the
572 * '#link' part is contained in a larger match that comes before the HashTag
573 * match, we'll remove the HashTag match.
574 *
575 * @private
576 * @param {Autolinker.match.Match[]} matches
577 * @return {Autolinker.match.Match[]}
578 */
579 Autolinker.prototype.compactMatches = function (matches) {
580 // First, the matches need to be sorted in order of offset
581 matches.sort(function (a, b) {
582 return a.getOffset() - b.getOffset();
583 });
584 var i = 0;
585 while (i < matches.length - 1) {
586 var match = matches[i], offset = match.getOffset(), matchedTextLength = match.getMatchedText().length, endIdx = offset + matchedTextLength;
587 if (i + 1 < matches.length) {
588 // Remove subsequent matches that equal offset with current match
589 if (matches[i + 1].getOffset() === offset) {
590 var removeIdx = matches[i + 1].getMatchedText().length > matchedTextLength ? i : i + 1;
591 matches.splice(removeIdx, 1);
592 continue;
593 }
594 // Remove subsequent matches that overlap with the current match
595 if (matches[i + 1].getOffset() < endIdx) {
596 matches.splice(i + 1, 1);
597 continue;
598 }
599 }
600 i++;
601 }
602 return matches;
603 };
604 /**
605 * Removes matches for matchers that were turned off in the options. For
606 * example, if {@link #hashtag hashtags} were not to be matched, we'll
607 * remove them from the `matches` array here.
608 *
609 * Note: we *must* use all Matchers on the input string, and then filter
610 * them out later. For example, if the options were `{ url: false, hashtag: true }`,
611 * we wouldn't want to match the text '#link' as a HashTag inside of the text
612 * 'google.com/#link'. The way the algorithm works is that we match the full
613 * URL first (which prevents the accidental HashTag match), and then we'll
614 * simply throw away the URL match.
615 *
616 * @private
617 * @param {Autolinker.match.Match[]} matches The array of matches to remove
618 * the unwanted matches from. Note: this array is mutated for the
619 * removals.
620 * @return {Autolinker.match.Match[]} The mutated input `matches` array.
621 */
622 Autolinker.prototype.removeUnwantedMatches = function (matches) {
623 if (!this.hashtag)
624 (0, utils_1.removeWithPredicate)(matches, function (match) {
625 return match.getType() === 'hashtag';
626 });
627 if (!this.email)
628 (0, utils_1.removeWithPredicate)(matches, function (match) {
629 return match.getType() === 'email';
630 });
631 if (!this.phone)
632 (0, utils_1.removeWithPredicate)(matches, function (match) {
633 return match.getType() === 'phone';
634 });
635 if (!this.mention)
636 (0, utils_1.removeWithPredicate)(matches, function (match) {
637 return match.getType() === 'mention';
638 });
639 if (!this.urls.schemeMatches) {
640 (0, utils_1.removeWithPredicate)(matches, function (m) {
641 return m.getType() === 'url' && m.getUrlMatchType() === 'scheme';
642 });
643 }
644 if (!this.urls.tldMatches) {
645 (0, utils_1.removeWithPredicate)(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'tld'; });
646 }
647 if (!this.urls.ipV4Matches) {
648 (0, utils_1.removeWithPredicate)(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'ipV4'; });
649 }
650 return matches;
651 };
652 /**
653 * Parses the input `text` looking for URLs, email addresses, phone
654 * numbers, username handles, and hashtags (depending on the configuration
655 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
656 * objects describing those matches.
657 *
658 * This method processes a **non-HTML string**, and is used to parse and
659 * match within the text nodes of an HTML string. This method is used
660 * internally by {@link #parse}.
661 *
662 * @private
663 * @param {String} text The text to find matches within (depending on if the
664 * {@link #urls}, {@link #email}, {@link #phone},
665 * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string.
666 * @param {Number} [offset=0] The offset of the text node within the
667 * original string. This is used when parsing with the {@link #parse}
668 * method to generate correct offsets within the {@link Autolinker.match.Match}
669 * instances, but may be omitted if calling this method publicly.
670 * @return {Autolinker.match.Match[]} The array of Matches found in the
671 * given input `text`.
672 */
673 Autolinker.prototype.parseText = function (text, offset) {
674 if (offset === void 0) { offset = 0; }
675 offset = offset || 0;
676 var matches = (0, parse_matches_1.parseMatches)(text, {
677 tagBuilder: this.getTagBuilder(),
678 stripPrefix: this.stripPrefix,
679 stripTrailingSlash: this.stripTrailingSlash,
680 decodePercentEncoding: this.decodePercentEncoding,
681 hashtagServiceName: this.hashtag,
682 mentionServiceName: this.mention || 'twitter',
683 });
684 // Correct the offset of each of the matches. They are originally
685 // the offset of the match within the provided text node, but we
686 // need to correct them to be relative to the original HTML input
687 // string (i.e. the one provided to #parse).
688 for (var i = 0, numTextMatches = matches.length; i < numTextMatches; i++) {
689 matches[i].setOffset(offset + matches[i].getOffset());
690 }
691 return matches;
692 };
693 /**
694 * Automatically links URLs, Email addresses, Phone numbers, Hashtags,
695 * and Mentions (Twitter, Instagram, Soundcloud) found in the given chunk of HTML. Does not link
696 * URLs found within HTML tags.
697 *
698 * For instance, if given the text: `You should go to http://www.yahoo.com`,
699 * then the result will be `You should go to
700 * &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
701 *
702 * This method finds the text around any HTML elements in the input
703 * `textOrHtml`, which will be the text that is processed. Any original HTML
704 * elements will be left as-is, as well as the text that is already wrapped
705 * in anchor (&lt;a&gt;) tags.
706 *
707 * @param {String} textOrHtml The HTML or text to autolink matches within
708 * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled).
709 * @return {String} The HTML, with matches automatically linked.
710 */
711 Autolinker.prototype.link = function (textOrHtml) {
712 if (!textOrHtml) {
713 return '';
714 } // handle `null` and `undefined` (for JavaScript users that don't have TypeScript support)
715 /* We would want to sanitize the start and end characters of a tag
716 * before processing the string in order to avoid an XSS scenario.
717 * This behaviour can be changed by toggling the sanitizeHtml option.
718 */
719 if (this.sanitizeHtml) {
720 textOrHtml = textOrHtml.replace(/</g, '&lt;').replace(/>/g, '&gt;');
721 }
722 var matches = this.parse(textOrHtml), newHtml = [], lastIndex = 0;
723 for (var i = 0, len = matches.length; i < len; i++) {
724 var match = matches[i];
725 newHtml.push(textOrHtml.substring(lastIndex, match.getOffset()));
726 newHtml.push(this.createMatchReturnVal(match));
727 lastIndex = match.getOffset() + match.getMatchedText().length;
728 }
729 newHtml.push(textOrHtml.substring(lastIndex)); // handle the text after the last match
730 return newHtml.join('');
731 };
732 /**
733 * Creates the return string value for a given match in the input string.
734 *
735 * This method handles the {@link #replaceFn}, if one was provided.
736 *
737 * @private
738 * @param {Autolinker.match.Match} match The Match object that represents
739 * the match.
740 * @return {String} The string that the `match` should be replaced with.
741 * This is usually the anchor tag string, but may be the `matchStr` itself
742 * if the match is not to be replaced.
743 */
744 Autolinker.prototype.createMatchReturnVal = function (match) {
745 // Handle a custom `replaceFn` being provided
746 var replaceFnResult;
747 if (this.replaceFn) {
748 replaceFnResult = this.replaceFn.call(this.context, match); // Autolinker instance is the context
749 }
750 if (typeof replaceFnResult === 'string') {
751 return replaceFnResult; // `replaceFn` returned a string, use that
752 }
753 else if (replaceFnResult === false) {
754 return match.getMatchedText(); // no replacement for the match
755 }
756 else if (replaceFnResult instanceof html_tag_1.HtmlTag) {
757 return replaceFnResult.toAnchorString();
758 }
759 else {
760 // replaceFnResult === true, or no/unknown return value from function
761 // Perform Autolinker's default anchor tag generation
762 var anchorTag = match.buildTag(); // returns an Autolinker.HtmlTag instance
763 return anchorTag.toAnchorString();
764 }
765 };
766 /**
767 * Returns the {@link #tagBuilder} instance for this Autolinker instance,
768 * lazily instantiating it if it does not yet exist.
769 *
770 * @private
771 * @return {Autolinker.AnchorTagBuilder}
772 */
773 Autolinker.prototype.getTagBuilder = function () {
774 var tagBuilder = this.tagBuilder;
775 if (!tagBuilder) {
776 tagBuilder = this.tagBuilder = new anchor_tag_builder_1.AnchorTagBuilder({
777 newWindow: this.newWindow,
778 truncate: this.truncate,
779 className: this.className,
780 });
781 }
782 return tagBuilder;
783 };
784 // NOTE: must be 'export default' here for UMD module
785 /**
786 * @static
787 * @property {String} version
788 *
789 * The Autolinker version number in the form major.minor.patch
790 *
791 * Ex: 3.15.0
792 */
793 Autolinker.version = version_1.version;
794 return Autolinker;
795}());
796exports.default = Autolinker;
797/**
798 * Normalizes the {@link #urls} config into an Object with its 2 properties:
799 * `schemeMatches` and `tldMatches`, both booleans.
800 *
801 * See {@link #urls} config for details.
802 *
803 * @private
804 * @param {Boolean/Object} urls
805 * @return {Object}
806 */
807function normalizeUrlsCfg(urls) {
808 if (urls == null)
809 urls = true; // default to `true`
810 if ((0, utils_1.isBoolean)(urls)) {
811 return { schemeMatches: urls, tldMatches: urls, ipV4Matches: urls };
812 }
813 else {
814 // object form
815 return {
816 schemeMatches: (0, utils_1.isBoolean)(urls.schemeMatches) ? urls.schemeMatches : true,
817 tldMatches: (0, utils_1.isBoolean)(urls.tldMatches) ? urls.tldMatches : true,
818 ipV4Matches: (0, utils_1.isBoolean)(urls.ipV4Matches) ? urls.ipV4Matches : true,
819 };
820 }
821}
822/**
823 * Normalizes the {@link #stripPrefix} config into an Object with 2
824 * properties: `scheme`, and `www` - both Booleans.
825 *
826 * See {@link #stripPrefix} config for details.
827 *
828 * @private
829 * @param {Boolean/Object} stripPrefix
830 * @return {Object}
831 */
832function normalizeStripPrefixCfg(stripPrefix) {
833 if (stripPrefix == null)
834 stripPrefix = true; // default to `true`
835 if ((0, utils_1.isBoolean)(stripPrefix)) {
836 return { scheme: stripPrefix, www: stripPrefix };
837 }
838 else {
839 // object form
840 return {
841 scheme: (0, utils_1.isBoolean)(stripPrefix.scheme) ? stripPrefix.scheme : true,
842 www: (0, utils_1.isBoolean)(stripPrefix.www) ? stripPrefix.www : true,
843 };
844 }
845}
846/**
847 * Normalizes the {@link #truncate} config into an Object with 2 properties:
848 * `length` (Number), and `location` (String).
849 *
850 * See {@link #truncate} config for details.
851 *
852 * @private
853 * @param {Number/Object} truncate
854 * @return {Object}
855 */
856function normalizeTruncateCfg(truncate) {
857 if (typeof truncate === 'number') {
858 return { length: truncate, location: 'end' };
859 }
860 else {
861 // object, or undefined/null
862 return (0, utils_1.defaults)(truncate || {}, {
863 length: Number.POSITIVE_INFINITY,
864 location: 'end',
865 });
866 }
867}
868//# sourceMappingURL=autolinker.js.map
\No newline at end of file