autolinker
Version:
Utility to automatically link the URLs, email addresses, phone numbers, hashtags, and mentions (Twitter, Instagram) in a given block of text/HTML
1 lines • 15.3 kB
Source Map (JSON)
{"version":3,"file":"url-match.js","sourceRoot":"","sources":["../../../src/match/url-match.ts"],"names":[],"mappings":"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","sourcesContent":["import { AbstractMatch, AbstractMatchConfig } from './abstract-match';\nimport { httpSchemePrefixRe } from '../parser/uri-utils';\nimport type { StripPrefixConfigObj } from '../autolinker';\n\n/**\n * A regular expression used to remove the 'www.' from URLs.\n */\nconst wwwPrefixRegex = /^(https?:\\/\\/)?(?:www\\.)?/i;\n\n/**\n * The regular expression used to remove the protocol-relative '//' from a URL\n * string, for purposes of formatting the anchor text. A protocol-relative URL\n * is, for example, \"//yahoo.com\"\n */\nconst protocolRelativeRegex = /^\\/\\//;\n\n/**\n * @class Autolinker.match.Url\n * @extends Autolinker.match.AbstractMatch\n *\n * Represents a Url match found in an input string which should be Autolinked.\n *\n * See this class's superclass ({@link Autolinker.match.Match}) for more details.\n */\nexport class UrlMatch extends AbstractMatch {\n /**\n * @public\n * @property {'url'} type\n *\n * A string name for the type of match that this class represents. Can be\n * used in a TypeScript discriminating union to type-narrow from the\n * `Match` type.\n */\n public readonly type = 'url' as const;\n\n /**\n * @cfg {String} url (required)\n *\n * The url that was matched.\n */\n private url: string = ''; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {\"scheme\"/\"www\"/\"tld\"} urlMatchType (required)\n *\n * The type of URL match that this class represents. This helps to determine\n * if the match was made in the original text with a prefixed scheme (ex:\n * 'http://www.google.com'), a prefixed 'www' (ex: 'www.google.com'), or\n * was matched by a known top-level domain (ex: 'google.com').\n */\n private readonly urlMatchType: UrlMatchType = 'scheme'; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Boolean} protocolRelativeMatch (required)\n *\n * `true` if the URL is a protocol-relative match. A protocol-relative match\n * is a URL that starts with '//', and will be either http:// or https://\n * based on the protocol that the site is loaded under.\n */\n private readonly protocolRelativeMatch: boolean = false; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Object} stripPrefix (required)\n *\n * The Object form of {@link Autolinker#cfg-stripPrefix}.\n */\n private readonly stripPrefix: Required<StripPrefixConfigObj> = {\n scheme: true,\n www: true,\n }; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Boolean} stripTrailingSlash (required)\n * @inheritdoc Autolinker#cfg-stripTrailingSlash\n */\n private readonly stripTrailingSlash: boolean = true; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @cfg {Boolean} decodePercentEncoding (required)\n * @inheritdoc Autolinker#cfg-decodePercentEncoding\n */\n private readonly decodePercentEncoding: boolean = true; // default value just to get the above doc comment in the ES5 output and documentation generator\n\n /**\n * @private\n * @property {Boolean} protocolPrepended\n *\n * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the\n * {@link #url} did not have a protocol)\n */\n private protocolPrepended: boolean = false;\n\n /**\n * @method constructor\n * @param {Object} cfg The configuration properties for the Match\n * instance, specified in an Object (map).\n */\n constructor(cfg: UrlMatchConfig) {\n super(cfg);\n\n this.urlMatchType = cfg.urlMatchType;\n this.url = cfg.url;\n this.protocolRelativeMatch = cfg.protocolRelativeMatch;\n this.stripPrefix = cfg.stripPrefix;\n this.stripTrailingSlash = cfg.stripTrailingSlash;\n this.decodePercentEncoding = cfg.decodePercentEncoding;\n }\n\n /**\n * Returns a string name for the type of match that this class represents.\n * For the case of UrlMatch, returns 'url'.\n *\n * @return {String}\n */\n public getType(): 'url' {\n return 'url';\n }\n\n /**\n * Returns a string name for the type of URL match that this class\n * represents.\n *\n * This helps to determine if the match was made in the original text with a\n * prefixed scheme (ex: 'http://www.google.com'), a prefixed 'www' (ex:\n * 'www.google.com'), or was matched by a known top-level domain (ex:\n * 'google.com').\n *\n * @return {\"scheme\"/\"www\"/\"tld\"}\n */\n public getUrlMatchType(): UrlMatchType {\n return this.urlMatchType;\n }\n\n /**\n * Returns the url that was matched, assuming the protocol to be 'http://' if the original\n * match was missing a protocol.\n *\n * @return {String}\n */\n public getUrl(): string {\n let url = this.url;\n\n // if the url string doesn't begin with a scheme, assume 'http://'\n if (\n !this.protocolRelativeMatch &&\n this.urlMatchType !== 'scheme' &&\n !this.protocolPrepended\n ) {\n url = this.url = 'http://' + url;\n\n this.protocolPrepended = true;\n }\n\n return url;\n }\n\n /**\n * Returns the anchor href that should be generated for the match.\n *\n * @return {String}\n */\n public getAnchorHref(): string {\n const url = this.getUrl();\n\n return url.replace(/&/g, '&'); // any &'s in the URL should be converted back to '&' if they were displayed as & in the source html\n }\n\n /**\n * Returns the anchor text that should be generated for the match.\n *\n * @return {String}\n */\n getAnchorText(): string {\n let anchorText = this.getMatchedText();\n\n if (this.protocolRelativeMatch) {\n // Strip off any protocol-relative '//' from the anchor text\n anchorText = stripProtocolRelativePrefix(anchorText);\n }\n if (this.stripPrefix.scheme) {\n anchorText = stripSchemePrefix(anchorText);\n }\n if (this.stripPrefix.www) {\n anchorText = stripWwwPrefix(anchorText);\n }\n if (this.stripTrailingSlash) {\n anchorText = removeTrailingSlash(anchorText); // remove trailing slash, if there is one\n }\n if (this.decodePercentEncoding) {\n anchorText = removePercentEncoding(anchorText);\n }\n return anchorText;\n }\n}\n\nexport interface UrlMatchConfig extends AbstractMatchConfig {\n url: string;\n urlMatchType: UrlMatchType;\n protocolRelativeMatch: boolean;\n stripPrefix: Required<StripPrefixConfigObj>;\n stripTrailingSlash: boolean;\n decodePercentEncoding: boolean;\n}\n\nexport type UrlMatchType = 'scheme' | 'tld' | 'ipV4';\n\n// Utility Functionality\n\n/**\n * Strips the scheme prefix (such as \"http://\" or \"https://\") from the given\n * `url`.\n *\n * @private\n * @param {String} url The text of the anchor that is being generated, for\n * which to strip off the url scheme.\n * @return {String} The `url`, with the scheme stripped.\n */\nfunction stripSchemePrefix(url: string): string {\n return url.replace(httpSchemePrefixRe, '');\n}\n\n/**\n * Strips the 'www' prefix from the given `url`.\n *\n * @private\n * @param {String} url The text of the anchor that is being generated, for\n * which to strip off the 'www' if it exists.\n * @return {String} The `url`, with the 'www' stripped.\n */\nfunction stripWwwPrefix(url: string): string {\n // If the URL doesn't actually include 'www.' in it, skip running the\n // .replace() regexp on it, which is fairly slow even just to check the\n // string for the 'www.'s existence. Most URLs these days do not have 'www.'\n // in it, so most of the time we skip running the .replace(). One other\n // option in the future is to run a state machine on the `url` string\n if (!url.includes('www.')) {\n return url;\n } else {\n return url.replace(wwwPrefixRegex, '$1'); // leave any scheme ($1), it one exists\n }\n}\n\n/**\n * Strips any protocol-relative '//' from the anchor text.\n *\n * @private\n * @param {String} text The text of the anchor that is being generated, for which to strip off the\n * protocol-relative prefix (such as stripping off \"//\")\n * @return {String} The `anchorText`, with the protocol-relative prefix stripped.\n */\nfunction stripProtocolRelativePrefix(text: string): string {\n return text.replace(protocolRelativeRegex, '');\n}\n\n/**\n * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed.\n *\n * @private\n * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing\n * slash ('/') that may exist.\n * @return {String} The `anchorText`, with the trailing slash removed.\n */\nfunction removeTrailingSlash(anchorText: string): string {\n if (anchorText.charAt(anchorText.length - 1) === '/') {\n anchorText = anchorText.slice(0, -1);\n }\n return anchorText;\n}\n\n/**\n * Decodes percent-encoded characters from the given `anchorText`, in\n * preparation for the text to be displayed.\n *\n * @private\n * @param {String} anchorText The text of the anchor that is being\n * generated, for which to decode any percent-encoded characters.\n * @return {String} The `anchorText`, with the percent-encoded characters\n * decoded.\n */\nfunction removePercentEncoding(anchorText: string): string {\n // First, convert a few of the known % encodings to the corresponding\n // HTML entities that could accidentally be interpretted as special\n // HTML characters\n // NOTE: This used to be written as 5 separate .replace() calls, but that\n // was 25% slower than the current form below according to jsperf\n const preProcessedEntityAnchorText = anchorText.replace(/%(?:22|26|27|3C|3E)/gi, match => {\n if (match === '%22') return '"'; // %22: '\"' char\n if (match === '%26') return '&'; // %26: '&' char\n if (match === '%27') return '''; // %27: \"'\" char\n if (match === '%3C' || match === '%3c') return '<'; // %3C: '<' char\n /*if (match === '%3E' || match === '%3e')*/ return '>'; // %3E: '>' char\n });\n\n // Now attempt to URL-decode the rest of the anchor text. However,\n // decodeURIComponent() is a slow function. Only call it if we have\n // remaining %-encoded entities. Adding this check added ~300 ops/sec to\n // benchmark\n if (preProcessedEntityAnchorText.includes('%')) {\n try {\n return decodeURIComponent(preProcessedEntityAnchorText);\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (error: unknown) {\n // Invalid % escape sequence in the anchor text, we'll simply return\n // the preProcessedEntityAnchorText below\n }\n }\n return preProcessedEntityAnchorText;\n}\n"]}