UNPKG

24.8 kBTypeScriptView Raw
1import { Match } from './match/match';
2import { HtmlTag } from './html-tag';
3import { MentionService } from './parser/mention-utils';
4import { HashtagService } from './parser/hashtag-utils';
5/**
6 * @class Autolinker
7 * @extends Object
8 *
9 * Utility class used to process a given string of text, and wrap the matches in
10 * the appropriate anchor (<a>) tags to turn them into links.
11 *
12 * Any of the configuration options may be provided in an Object provided
13 * to the Autolinker constructor, which will configure how the {@link #link link()}
14 * method will process the links.
15 *
16 * For example:
17 *
18 * var autolinker = new Autolinker( {
19 * newWindow : false,
20 * truncate : 30
21 * } );
22 *
23 * var html = autolinker.link( "Joe went to www.yahoo.com" );
24 * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
25 *
26 *
27 * The {@link #static-link static link()} method may also be used to inline
28 * options into a single call, which may be more convenient for one-off uses.
29 * For example:
30 *
31 * var html = Autolinker.link( "Joe went to www.yahoo.com", {
32 * newWindow : false,
33 * truncate : 30
34 * } );
35 * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
36 *
37 *
38 * ## Custom Replacements of Links
39 *
40 * If the configuration options do not provide enough flexibility, a {@link #replaceFn}
41 * may be provided to fully customize the output of Autolinker. This function is
42 * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
43 * match that is encountered.
44 *
45 * For example:
46 *
47 * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
48 *
49 * var linkedText = Autolinker.link( input, {
50 * replaceFn : function( match ) {
51 * console.log( "href = ", match.getAnchorHref() );
52 * console.log( "text = ", match.getAnchorText() );
53 *
54 * switch( match.getType() ) {
55 * case 'url' :
56 * console.log( "url: ", match.getUrl() );
57 *
58 * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
59 * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
60 * tag.setAttr( 'rel', 'nofollow' );
61 * tag.addClass( 'external-link' );
62 *
63 * return tag;
64 *
65 * } else {
66 * return true; // let Autolinker perform its normal anchor tag replacement
67 * }
68 *
69 * case 'email' :
70 * var email = match.getEmail();
71 * console.log( "email: ", email );
72 *
73 * if( email === "my@own.address" ) {
74 * return false; // don't auto-link this particular email address; leave as-is
75 * } else {
76 * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
77 * }
78 *
79 * case 'phone' :
80 * var phoneNumber = match.getPhoneNumber();
81 * console.log( phoneNumber );
82 *
83 * return '<a href="http://newplace.to.link.phone.numbers.to/">' + phoneNumber + '</a>';
84 *
85 * case 'hashtag' :
86 * var hashtag = match.getHashtag();
87 * console.log( hashtag );
88 *
89 * return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
90 *
91 * case 'mention' :
92 * var mention = match.getMention();
93 * console.log( mention );
94 *
95 * return '<a href="http://newplace.to.link.mention.to/">' + mention + '</a>';
96 * }
97 * }
98 * } );
99 *
100 *
101 * The function may return the following values:
102 *
103 * - `true` (Boolean): Allow Autolinker to replace the match as it normally
104 * would.
105 * - `false` (Boolean): Do not replace the current match at all - leave as-is.
106 * - Any String: If a string is returned from the function, the string will be
107 * used directly as the replacement HTML for the match.
108 * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify
109 * an HTML tag before writing out its HTML text.
110 */
111export default class Autolinker {
112 /**
113 * @static
114 * @property {String} version
115 *
116 * The Autolinker version number in the form major.minor.patch
117 *
118 * Ex: 3.15.0
119 */
120 static readonly version = "4.1.0";
121 /**
122 * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
123 * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs
124 * found within HTML tags.
125 *
126 * For instance, if given the text: `You should go to http://www.yahoo.com`,
127 * then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
128 *
129 * Example:
130 *
131 * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
132 * // Produces: "Go to <a href="http://google.com">google.com</a>"
133 *
134 * @static
135 * @param {String} textOrHtml The HTML or text to find matches within (depending
136 * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #mention},
137 * {@link #hashtag}, and {@link #mention} options are enabled).
138 * @param {Object} [options] Any of the configuration options for the Autolinker
139 * class, specified in an Object (map). See the class description for an
140 * example call.
141 * @return {String} The HTML text, with matches automatically linked.
142 */
143 static link(textOrHtml: string, options?: AutolinkerConfig): string;
144 /**
145 * Parses the input `textOrHtml` looking for URLs, email addresses, phone
146 * numbers, username handles, and hashtags (depending on the configuration
147 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
148 * objects describing those matches (without making any replacements).
149 *
150 * Note that if parsing multiple pieces of text, it is slightly more efficient
151 * to create an Autolinker instance, and use the instance-level {@link #parse}
152 * method.
153 *
154 * Example:
155 *
156 * var matches = Autolinker.parse( "Hello google.com, I am asdf@asdf.com", {
157 * urls: true,
158 * email: true
159 * } );
160 *
161 * console.log( matches.length ); // 2
162 * console.log( matches[ 0 ].getType() ); // 'url'
163 * console.log( matches[ 0 ].getUrl() ); // 'google.com'
164 * console.log( matches[ 1 ].getType() ); // 'email'
165 * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
166 *
167 * @static
168 * @param {String} textOrHtml The HTML or text to find matches within
169 * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
170 * {@link #hashtag}, and {@link #mention} options are enabled).
171 * @param {Object} [options] Any of the configuration options for the Autolinker
172 * class, specified in an Object (map). See the class description for an
173 * example call.
174 * @return {Autolinker.match.Match[]} The array of Matches found in the
175 * given input `textOrHtml`.
176 */
177 static parse(textOrHtml: string, options: AutolinkerConfig): Match[];
178 /**
179 * The Autolinker version number exposed on the instance itself.
180 *
181 * Ex: 0.25.1
182 *
183 * @property {String} version
184 */
185 readonly version = "4.1.0";
186 /**
187 * @cfg {Boolean/Object} [urls]
188 *
189 * `true` if URLs should be automatically linked, `false` if they should not
190 * be. Defaults to `true`.
191 *
192 * Examples:
193 *
194 * urls: true
195 *
196 * // or
197 *
198 * urls: {
199 * schemeMatches : true,
200 * tldMatches : true,
201 * ipV4Matches : true
202 * }
203 *
204 * As shown above, this option also accepts an Object form with 3 properties
205 * to allow for more customization of what exactly gets linked. All default
206 * to `true`:
207 *
208 * @cfg {Boolean} [urls.schemeMatches] `true` to match URLs found prefixed
209 * with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`,
210 * `false` to prevent these types of matches.
211 * @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
212 * level domains (.com, .net, etc.) that are not prefixed with a scheme
213 * (such as 'http://'). This option attempts to match anything that looks
214 * like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
215 * `false` to prevent these types of matches.
216 * @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
217 * that are not prefixed with a scheme (such as 'http://'). This option
218 * attempts to match anything that looks like an IPv4 address in text. Ex:
219 * `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
220 * of matches.
221 */
222 private readonly urls;
223 /**
224 * @cfg {Boolean} [email=true]
225 *
226 * `true` if email addresses should be automatically linked, `false` if they
227 * should not be.
228 */
229 private readonly email;
230 /**
231 * @cfg {Boolean} [phone=true]
232 *
233 * `true` if Phone numbers ("(555)555-5555") should be automatically linked,
234 * `false` if they should not be.
235 */
236 private readonly phone;
237 /**
238 * @cfg {Boolean/String} [hashtag=false]
239 *
240 * A string for the service name to have hashtags (ex: "#myHashtag")
241 * auto-linked to. The currently-supported values are:
242 *
243 * - 'twitter'
244 * - 'facebook'
245 * - 'instagram'
246 * - 'tiktok'
247 * - 'youtube'
248 *
249 * Pass `false` to skip auto-linking of hashtags.
250 */
251 private readonly hashtag;
252 /**
253 * @cfg {String/Boolean} [mention=false]
254 *
255 * A string for the service name to have mentions (ex: "@myuser")
256 * auto-linked to. The currently supported values are:
257 *
258 * - 'twitter'
259 * - 'instagram'
260 * - 'soundcloud'
261 * - 'tiktok'
262 * - 'youtube'
263 *
264 * Defaults to `false` to skip auto-linking of mentions.
265 */
266 private readonly mention;
267 /**
268 * @cfg {Boolean} [newWindow=true]
269 *
270 * `true` if the links should open in a new window, `false` otherwise.
271 */
272 private readonly newWindow;
273 /**
274 * @cfg {Boolean/Object} [stripPrefix=true]
275 *
276 * `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
277 * from the beginning of URL links' text, `false` otherwise. Defaults to
278 * `true`.
279 *
280 * Examples:
281 *
282 * stripPrefix: true
283 *
284 * // or
285 *
286 * stripPrefix: {
287 * scheme : true,
288 * www : true
289 * }
290 *
291 * As shown above, this option also accepts an Object form with 2 properties
292 * to allow for more customization of what exactly is prevented from being
293 * displayed. Both default to `true`:
294 *
295 * @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
296 * a URL match from being displayed to the user. Example:
297 * `'http://google.com'` will be displayed as `'google.com'`. `false` to
298 * not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
299 * will be removed, so as not to remove a potentially dangerous scheme
300 * (such as `'file://'` or `'javascript:'`)
301 * @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
302 * `'www.'` part of a URL match from being displayed to the user. Ex:
303 * `'www.google.com'` will be displayed as `'google.com'`. `false` to not
304 * strip the `'www'`.
305 */
306 private readonly stripPrefix;
307 /**
308 * @cfg {Boolean} [stripTrailingSlash=true]
309 *
310 * `true` to remove the trailing slash from URL matches, `false` to keep
311 * the trailing slash.
312 *
313 * Example when `true`: `http://google.com/` will be displayed as
314 * `http://google.com`.
315 */
316 private readonly stripTrailingSlash;
317 /**
318 * @cfg {Boolean} [decodePercentEncoding=true]
319 *
320 * `true` to decode percent-encoded characters in URL matches, `false` to keep
321 * the percent-encoded characters.
322 *
323 * Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
324 * be displayed as `https://en.wikipedia.org/wiki/San_José`.
325 */
326 private readonly decodePercentEncoding;
327 /**
328 * @cfg {Number/Object} [truncate=0]
329 *
330 * ## Number Form
331 *
332 * A number for how many characters matched text should be truncated to
333 * inside the text of a link. If the matched text is over this number of
334 * characters, it will be truncated to this length by adding a two period
335 * ellipsis ('..') to the end of the string.
336 *
337 * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file'
338 * truncated to 25 characters might look something like this:
339 * 'yahoo.com/some/long/pat..'
340 *
341 * Example Usage:
342 *
343 * truncate: 25
344 *
345 *
346 * Defaults to `0` for "no truncation."
347 *
348 *
349 * ## Object Form
350 *
351 * An Object may also be provided with two properties: `length` (Number) and
352 * `location` (String). `location` may be one of the following: 'end'
353 * (default), 'middle', or 'smart'.
354 *
355 * Example Usage:
356 *
357 * truncate: { length: 25, location: 'middle' }
358 *
359 * @cfg {Number} [truncate.length=0] How many characters to allow before
360 * truncation will occur. Defaults to `0` for "no truncation."
361 * @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
362 *
363 * - 'end' (default): will truncate up to the number of characters, and then
364 * add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
365 * - 'middle': will truncate and add the ellipsis in the middle. Ex:
366 * 'yahoo.com/s..th/to/a/file'
367 * - 'smart': for URLs where the algorithm attempts to strip out unnecessary
368 * parts first (such as the 'www.', then URL scheme, hash, etc.),
369 * attempting to make the URL human-readable before looking for a good
370 * point to insert the ellipsis if it is still too long. Ex:
371 * 'yahoo.com/some..to/a/file'. For more details, see
372 * {@link Autolinker.truncate.TruncateSmart}.
373 */
374 private readonly truncate;
375 /**
376 * @cfg {String} className
377 *
378 * A CSS class name to add to the generated links. This class will be added
379 * to all links, as well as this class plus match suffixes for styling
380 * url/email/phone/hashtag/mention links differently.
381 *
382 * For example, if this config is provided as "myLink", then:
383 *
384 * - URL links will have the CSS classes: "myLink myLink-url"
385 * - Email links will have the CSS classes: "myLink myLink-email", and
386 * - Phone links will have the CSS classes: "myLink myLink-phone"
387 * - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
388 * - Mention links will have the CSS classes: "myLink myLink-mention myLink-[type]"
389 * where [type] is either "instagram", "twitter" or "soundcloud"
390 */
391 private readonly className;
392 /**
393 * @cfg {Function} replaceFn
394 *
395 * A function to individually process each match found in the input string.
396 *
397 * See the class's description for usage.
398 *
399 * The `replaceFn` can be called with a different context object (`this`
400 * reference) using the {@link #context} cfg.
401 *
402 * This function is called with the following parameter:
403 *
404 * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which
405 * can be used to retrieve information about the match that the `replaceFn`
406 * is currently processing. See {@link Autolinker.match.Match} subclasses
407 * for details.
408 */
409 private readonly replaceFn;
410 /**
411 * @cfg {Object} context
412 *
413 * The context object (`this` reference) to call the `replaceFn` with.
414 *
415 * Defaults to this Autolinker instance.
416 */
417 private readonly context;
418 /**
419 * @cfg {Boolean} [sanitizeHtml=false]
420 *
421 * `true` to HTML-encode the start and end brackets of existing HTML tags found
422 * in the input string. This will escape `<` and `>` characters to `&lt;` and
423 * `&gt;`, respectively.
424 *
425 * Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
426 * but will remove the significance of existing HTML tags in the input string. If
427 * you would like to maintain the significance of existing HTML tags while also
428 * making the output HTML string safe, leave this option as `false` and use a
429 * tool like https://github.com/cure53/DOMPurify (or others) on the input string
430 * before running Autolinker.
431 */
432 private readonly sanitizeHtml;
433 /**
434 * @private
435 * @property {Autolinker.AnchorTagBuilder} tagBuilder
436 *
437 * The AnchorTagBuilder instance used to build match replacement anchor tags.
438 * Note: this is lazily instantiated in the {@link #getTagBuilder} method.
439 */
440 private tagBuilder;
441 /**
442 * @method constructor
443 * @param {Object} [cfg] The configuration options for the Autolinker instance,
444 * specified in an Object (map).
445 */
446 constructor(cfg?: AutolinkerConfig);
447 /**
448 * Parses the input `textOrHtml` looking for URLs, email addresses, phone
449 * numbers, username handles, and hashtags (depending on the configuration
450 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
451 * objects describing those matches (without making any replacements).
452 *
453 * This method is used by the {@link #link} method, but can also be used to
454 * simply do parsing of the input in order to discover what kinds of links
455 * there are and how many.
456 *
457 * Example usage:
458 *
459 * var autolinker = new Autolinker( {
460 * urls: true,
461 * email: true
462 * } );
463 *
464 * var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
465 *
466 * console.log( matches.length ); // 2
467 * console.log( matches[ 0 ].getType() ); // 'url'
468 * console.log( matches[ 0 ].getUrl() ); // 'google.com'
469 * console.log( matches[ 1 ].getType() ); // 'email'
470 * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
471 *
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 * @return {Autolinker.match.Match[]} The array of Matches found in the
476 * given input `textOrHtml`.
477 */
478 parse(textOrHtml: string): Match[];
479 /**
480 * After we have found all matches, we need to remove matches that overlap
481 * with a previous match. This can happen for instance with URLs, where the
482 * url 'google.com/#link' would match '#link' as a hashtag. Because the
483 * '#link' part is contained in a larger match that comes before the HashTag
484 * match, we'll remove the HashTag match.
485 *
486 * @private
487 * @param {Autolinker.match.Match[]} matches
488 * @return {Autolinker.match.Match[]}
489 */
490 private compactMatches;
491 /**
492 * Removes matches for matchers that were turned off in the options. For
493 * example, if {@link #hashtag hashtags} were not to be matched, we'll
494 * remove them from the `matches` array here.
495 *
496 * Note: we *must* use all Matchers on the input string, and then filter
497 * them out later. For example, if the options were `{ url: false, hashtag: true }`,
498 * we wouldn't want to match the text '#link' as a HashTag inside of the text
499 * 'google.com/#link'. The way the algorithm works is that we match the full
500 * URL first (which prevents the accidental HashTag match), and then we'll
501 * simply throw away the URL match.
502 *
503 * @private
504 * @param {Autolinker.match.Match[]} matches The array of matches to remove
505 * the unwanted matches from. Note: this array is mutated for the
506 * removals.
507 * @return {Autolinker.match.Match[]} The mutated input `matches` array.
508 */
509 private removeUnwantedMatches;
510 /**
511 * Parses the input `text` looking for URLs, email addresses, phone
512 * numbers, username handles, and hashtags (depending on the configuration
513 * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
514 * objects describing those matches.
515 *
516 * This method processes a **non-HTML string**, and is used to parse and
517 * match within the text nodes of an HTML string. This method is used
518 * internally by {@link #parse}.
519 *
520 * @private
521 * @param {String} text The text to find matches within (depending on if the
522 * {@link #urls}, {@link #email}, {@link #phone},
523 * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string.
524 * @param {Number} [offset=0] The offset of the text node within the
525 * original string. This is used when parsing with the {@link #parse}
526 * method to generate correct offsets within the {@link Autolinker.match.Match}
527 * instances, but may be omitted if calling this method publicly.
528 * @return {Autolinker.match.Match[]} The array of Matches found in the
529 * given input `text`.
530 */
531 private parseText;
532 /**
533 * Automatically links URLs, Email addresses, Phone numbers, Hashtags,
534 * and Mentions (Twitter, Instagram, Soundcloud) found in the given chunk of HTML. Does not link
535 * URLs found within HTML tags.
536 *
537 * For instance, if given the text: `You should go to http://www.yahoo.com`,
538 * then the result will be `You should go to
539 * &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
540 *
541 * This method finds the text around any HTML elements in the input
542 * `textOrHtml`, which will be the text that is processed. Any original HTML
543 * elements will be left as-is, as well as the text that is already wrapped
544 * in anchor (&lt;a&gt;) tags.
545 *
546 * @param {String} textOrHtml The HTML or text to autolink matches within
547 * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled).
548 * @return {String} The HTML, with matches automatically linked.
549 */
550 link(textOrHtml: string): string;
551 /**
552 * Creates the return string value for a given match in the input string.
553 *
554 * This method handles the {@link #replaceFn}, if one was provided.
555 *
556 * @private
557 * @param {Autolinker.match.Match} match The Match object that represents
558 * the match.
559 * @return {String} The string that the `match` should be replaced with.
560 * This is usually the anchor tag string, but may be the `matchStr` itself
561 * if the match is not to be replaced.
562 */
563 private createMatchReturnVal;
564 /**
565 * Returns the {@link #tagBuilder} instance for this Autolinker instance,
566 * lazily instantiating it if it does not yet exist.
567 *
568 * @private
569 * @return {Autolinker.AnchorTagBuilder}
570 */
571 private getTagBuilder;
572}
573export interface AutolinkerConfig {
574 urls?: UrlsConfig;
575 email?: boolean;
576 phone?: boolean;
577 hashtag?: HashtagConfig;
578 mention?: MentionConfig;
579 newWindow?: boolean;
580 stripPrefix?: StripPrefixConfig;
581 stripTrailingSlash?: boolean;
582 truncate?: TruncateConfig;
583 className?: string;
584 replaceFn?: ReplaceFn | null;
585 context?: any;
586 sanitizeHtml?: boolean;
587 decodePercentEncoding?: boolean;
588}
589export type UrlsConfig = boolean | UrlsConfigObj;
590export interface UrlsConfigObj {
591 schemeMatches?: boolean;
592 tldMatches?: boolean;
593 ipV4Matches?: boolean;
594}
595export type StripPrefixConfig = boolean | StripPrefixConfigObj;
596export interface StripPrefixConfigObj {
597 scheme?: boolean;
598 www?: boolean;
599}
600export type TruncateConfig = number | TruncateConfigObj;
601export interface TruncateConfigObj {
602 length?: number;
603 location?: 'end' | 'middle' | 'smart';
604}
605export type HashtagConfig = false | HashtagService;
606export type MentionConfig = false | MentionService;
607export type ReplaceFn = (match: Match) => ReplaceFnReturn;
608export type ReplaceFnReturn = boolean | string | HtmlTag | null | undefined | void;