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