1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | var version_1 = require("./version");
|
4 | var utils_1 = require("./utils");
|
5 | var anchor_tag_builder_1 = require("./anchor-tag-builder");
|
6 | var html_tag_1 = require("./html-tag");
|
7 | var parse_matches_1 = require("./parser/parse-matches");
|
8 | var parse_html_1 = require("./htmlParser/parse-html");
|
9 | var mention_utils_1 = require("./parser/mention-utils");
|
10 | var 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 | */
|
117 | var 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 `<` and
|
373 | * `>`, 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 <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
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 ' ' (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 & entities
|
536 | var htmlCharacterEntitiesRegex = /( | |<|<|>|>|"|"|')/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 | * <a href="http://www.yahoo.com">http://www.yahoo.com</a>`
|
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 (<a>) 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, '<').replace(/>/g, '>');
|
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 | }());
|
796 | exports.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 | */
|
807 | function 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 | */
|
832 | function 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 | */
|
856 | function 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 |