source: node_modules/autolinker/dist/es2015/autolinker.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

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