"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var version_1 = require("./version"); var utils_1 = require("./utils"); var anchor_tag_builder_1 = require("./anchor-tag-builder"); var html_tag_1 = require("./html-tag"); var parse_matches_1 = require("./parser/parse-matches"); var parse_html_1 = require("./htmlParser/parse-html"); var mention_utils_1 = require("./parser/mention-utils"); var hashtag_utils_1 = require("./parser/hashtag-utils"); /** * @class Autolinker * @extends Object * * Utility class used to process a given string of text, and wrap the matches in * the appropriate anchor (<a>) tags to turn them into links. * * Any of the configuration options may be provided in an Object provided * to the Autolinker constructor, which will configure how the {@link #link link()} * method will process the links. * * For example: * * var autolinker = new Autolinker( { * newWindow : false, * truncate : 30 * } ); * * var html = autolinker.link( "Joe went to www.yahoo.com" ); * // produces: 'Joe went to yahoo.com' * * * The {@link #static-link static link()} method may also be used to inline * options into a single call, which may be more convenient for one-off uses. * For example: * * var html = Autolinker.link( "Joe went to www.yahoo.com", { * newWindow : false, * truncate : 30 * } ); * // produces: 'Joe went to yahoo.com' * * * ## Custom Replacements of Links * * If the configuration options do not provide enough flexibility, a {@link #replaceFn} * may be provided to fully customize the output of Autolinker. This function is * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram, Soundcloud) * match that is encountered. * * For example: * * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud) * * var linkedText = Autolinker.link( input, { * replaceFn : function( match ) { * console.log( "href = ", match.getAnchorHref() ); * console.log( "text = ", match.getAnchorText() ); * * switch( match.getType() ) { * case 'url' : * console.log( "url: ", match.getUrl() ); * * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes * tag.setAttr( 'rel', 'nofollow' ); * tag.addClass( 'external-link' ); * * return tag; * * } else { * return true; // let Autolinker perform its normal anchor tag replacement * } * * case 'email' : * var email = match.getEmail(); * console.log( "email: ", email ); * * if( email === "my@own.address" ) { * return false; // don't auto-link this particular email address; leave as-is * } else { * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`) * } * * case 'phone' : * var phoneNumber = match.getPhoneNumber(); * console.log( phoneNumber ); * * return '' + phoneNumber + ''; * * case 'hashtag' : * var hashtag = match.getHashtag(); * console.log( hashtag ); * * return '' + hashtag + ''; * * case 'mention' : * var mention = match.getMention(); * console.log( mention ); * * return '' + mention + ''; * } * } * } ); * * * The function may return the following values: * * - `true` (Boolean): Allow Autolinker to replace the match as it normally * would. * - `false` (Boolean): Do not replace the current match at all - leave as-is. * - Any String: If a string is returned from the function, the string will be * used directly as the replacement HTML for the match. * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify * an HTML tag before writing out its HTML text. */ var Autolinker = /** @class */ (function () { /** * @method constructor * @param {Object} [cfg] The configuration options for the Autolinker instance, * specified in an Object (map). */ function Autolinker(cfg) { if (cfg === void 0) { cfg = {}; } /** * The Autolinker version number exposed on the instance itself. * * Ex: 0.25.1 * * @property {String} version */ this.version = Autolinker.version; /** * @cfg {Boolean/Object} [urls] * * `true` if URLs should be automatically linked, `false` if they should not * be. Defaults to `true`. * * Examples: * * urls: true * * // or * * urls: { * schemeMatches : true, * tldMatches : true, * ipV4Matches : true * } * * As shown above, this option also accepts an Object form with 3 properties * to allow for more customization of what exactly gets linked. All default * to `true`: * * @cfg {Boolean} [urls.schemeMatches] `true` to match URLs found prefixed * with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`, * `false` to prevent these types of matches. * @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top * level domains (.com, .net, etc.) that are not prefixed with a scheme * (such as 'http://'). This option attempts to match anything that looks * like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc. * `false` to prevent these types of matches. * @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text * that are not prefixed with a scheme (such as 'http://'). This option * attempts to match anything that looks like an IPv4 address in text. Ex: * `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types * of matches. */ this.urls = {}; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Boolean} [email=true] * * `true` if email addresses should be automatically linked, `false` if they * should not be. */ this.email = true; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Boolean} [phone=true] * * `true` if Phone numbers ("(555)555-5555") should be automatically linked, * `false` if they should not be. */ this.phone = true; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Boolean/String} [hashtag=false] * * A string for the service name to have hashtags (ex: "#myHashtag") * auto-linked to. The currently-supported values are: * * - 'twitter' * - 'facebook' * - 'instagram' * * Pass `false` to skip auto-linking of hashtags. */ this.hashtag = false; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {String/Boolean} [mention=false] * * A string for the service name to have mentions (ex: "@myuser") * auto-linked to. The currently supported values are: * * - 'twitter' * - 'instagram' * - 'soundcloud' * - 'tiktok' * * Defaults to `false` to skip auto-linking of mentions. */ this.mention = false; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Boolean} [newWindow=true] * * `true` if the links should open in a new window, `false` otherwise. */ this.newWindow = true; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Boolean/Object} [stripPrefix=true] * * `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped * from the beginning of URL links' text, `false` otherwise. Defaults to * `true`. * * Examples: * * stripPrefix: true * * // or * * stripPrefix: { * scheme : true, * www : true * } * * As shown above, this option also accepts an Object form with 2 properties * to allow for more customization of what exactly is prevented from being * displayed. Both default to `true`: * * @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of * a URL match from being displayed to the user. Example: * `'http://google.com'` will be displayed as `'google.com'`. `false` to * not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme * will be removed, so as not to remove a potentially dangerous scheme * (such as `'file://'` or `'javascript:'`) * @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the * `'www.'` part of a URL match from being displayed to the user. Ex: * `'www.google.com'` will be displayed as `'google.com'`. `false` to not * strip the `'www'`. */ this.stripPrefix = { scheme: true, www: true, }; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Boolean} [stripTrailingSlash=true] * * `true` to remove the trailing slash from URL matches, `false` to keep * the trailing slash. * * Example when `true`: `http://google.com/` will be displayed as * `http://google.com`. */ this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Boolean} [decodePercentEncoding=true] * * `true` to decode percent-encoded characters in URL matches, `false` to keep * the percent-encoded characters. * * Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will * be displayed as `https://en.wikipedia.org/wiki/San_José`. */ this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Number/Object} [truncate=0] * * ## Number Form * * A number for how many characters matched text should be truncated to * inside the text of a link. If the matched text is over this number of * characters, it will be truncated to this length by adding a two period * ellipsis ('..') to the end of the string. * * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' * truncated to 25 characters might look something like this: * 'yahoo.com/some/long/pat..' * * Example Usage: * * truncate: 25 * * * Defaults to `0` for "no truncation." * * * ## Object Form * * An Object may also be provided with two properties: `length` (Number) and * `location` (String). `location` may be one of the following: 'end' * (default), 'middle', or 'smart'. * * Example Usage: * * truncate: { length: 25, location: 'middle' } * * @cfg {Number} [truncate.length=0] How many characters to allow before * truncation will occur. Defaults to `0` for "no truncation." * @cfg {"end"/"middle"/"smart"} [truncate.location="end"] * * - 'end' (default): will truncate up to the number of characters, and then * add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..' * - 'middle': will truncate and add the ellipsis in the middle. Ex: * 'yahoo.com/s..th/to/a/file' * - 'smart': for URLs where the algorithm attempts to strip out unnecessary * parts first (such as the 'www.', then URL scheme, hash, etc.), * attempting to make the URL human-readable before looking for a good * point to insert the ellipsis if it is still too long. Ex: * 'yahoo.com/some..to/a/file'. For more details, see * {@link Autolinker.truncate.TruncateSmart}. */ this.truncate = { length: 0, location: 'end', }; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {String} className * * A CSS class name to add to the generated links. This class will be added * to all links, as well as this class plus match suffixes for styling * url/email/phone/hashtag/mention links differently. * * For example, if this config is provided as "myLink", then: * * - URL links will have the CSS classes: "myLink myLink-url" * - Email links will have the CSS classes: "myLink myLink-email", and * - Phone links will have the CSS classes: "myLink myLink-phone" * - Hashtag links will have the CSS classes: "myLink myLink-hashtag" * - Mention links will have the CSS classes: "myLink myLink-mention myLink-[type]" * where [type] is either "instagram", "twitter" or "soundcloud" */ this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Function} replaceFn * * A function to individually process each match found in the input string. * * See the class's description for usage. * * The `replaceFn` can be called with a different context object (`this` * reference) using the {@link #context} cfg. * * This function is called with the following parameter: * * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which * can be used to retrieve information about the match that the `replaceFn` * is currently processing. See {@link Autolinker.match.Match} subclasses * for details. */ this.replaceFn = null; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Object} context * * The context object (`this` reference) to call the `replaceFn` with. * * Defaults to this Autolinker instance. */ this.context = undefined; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @cfg {Boolean} [sanitizeHtml=false] * * `true` to HTML-encode the start and end brackets of existing HTML tags found * in the input string. This will escape `<` and `>` characters to `<` and * `>`, respectively. * * Setting this to `true` will prevent XSS (Cross-site Scripting) attacks, * but will remove the significance of existing HTML tags in the input string. If * you would like to maintain the significance of existing HTML tags while also * making the output HTML string safe, leave this option as `false` and use a * tool like https://github.com/cure53/DOMPurify (or others) on the input string * before running Autolinker. */ this.sanitizeHtml = false; // default value just to get the above doc comment in the ES5 output and documentation generator /** * @private * @property {Autolinker.AnchorTagBuilder} tagBuilder * * The AnchorTagBuilder instance used to build match replacement anchor tags. * Note: this is lazily instantiated in the {@link #getTagBuilder} method. */ this.tagBuilder = null; // Note: when `this.something` is used in the rhs of these assignments, // it refers to the default values set above the constructor this.urls = normalizeUrlsCfg(cfg.urls); this.email = (0, utils_1.isBoolean)(cfg.email) ? cfg.email : this.email; this.phone = (0, utils_1.isBoolean)(cfg.phone) ? cfg.phone : this.phone; this.hashtag = cfg.hashtag || this.hashtag; this.mention = cfg.mention || this.mention; this.newWindow = (0, utils_1.isBoolean)(cfg.newWindow) ? cfg.newWindow : this.newWindow; this.stripPrefix = normalizeStripPrefixCfg(cfg.stripPrefix); this.stripTrailingSlash = (0, utils_1.isBoolean)(cfg.stripTrailingSlash) ? cfg.stripTrailingSlash : this.stripTrailingSlash; this.decodePercentEncoding = (0, utils_1.isBoolean)(cfg.decodePercentEncoding) ? cfg.decodePercentEncoding : this.decodePercentEncoding; this.sanitizeHtml = cfg.sanitizeHtml || false; // Validate the value of the `mention` cfg var mention = this.mention; if (mention !== false && mention_utils_1.mentionServices.indexOf(mention) === -1) { throw new Error("invalid `mention` cfg '".concat(mention, "' - see docs")); } // Validate the value of the `hashtag` cfg var hashtag = this.hashtag; if (hashtag !== false && hashtag_utils_1.hashtagServices.indexOf(hashtag) === -1) { throw new Error("invalid `hashtag` cfg '".concat(hashtag, "' - see docs")); } this.truncate = normalizeTruncateCfg(cfg.truncate); this.className = cfg.className || this.className; this.replaceFn = cfg.replaceFn || this.replaceFn; this.context = cfg.context || this; } /** * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles, * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs * found within HTML tags. * * For instance, if given the text: `You should go to http://www.yahoo.com`, * then the result will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` * * Example: * * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } ); * // Produces: "Go to google.com" * * @static * @param {String} textOrHtml The HTML or text to find matches within (depending * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #mention}, * {@link #hashtag}, and {@link #mention} options are enabled). * @param {Object} [options] Any of the configuration options for the Autolinker * class, specified in an Object (map). See the class description for an * example call. * @return {String} The HTML text, with matches automatically linked. */ Autolinker.link = function (textOrHtml, options) { var autolinker = new Autolinker(options); return autolinker.link(textOrHtml); }; /** * Parses the input `textOrHtml` looking for URLs, email addresses, phone * numbers, username handles, and hashtags (depending on the configuration * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match} * objects describing those matches (without making any replacements). * * Note that if parsing multiple pieces of text, it is slightly more efficient * to create an Autolinker instance, and use the instance-level {@link #parse} * method. * * Example: * * var matches = Autolinker.parse( "Hello google.com, I am asdf@asdf.com", { * urls: true, * email: true * } ); * * console.log( matches.length ); // 2 * console.log( matches[ 0 ].getType() ); // 'url' * console.log( matches[ 0 ].getUrl() ); // 'google.com' * console.log( matches[ 1 ].getType() ); // 'email' * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com' * * @static * @param {String} textOrHtml The HTML or text to find matches within * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, * {@link #hashtag}, and {@link #mention} options are enabled). * @param {Object} [options] Any of the configuration options for the Autolinker * class, specified in an Object (map). See the class description for an * example call. * @return {Autolinker.match.Match[]} The array of Matches found in the * given input `textOrHtml`. */ Autolinker.parse = function (textOrHtml, options) { var autolinker = new Autolinker(options); return autolinker.parse(textOrHtml); }; /** * Parses the input `textOrHtml` looking for URLs, email addresses, phone * numbers, username handles, and hashtags (depending on the configuration * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match} * objects describing those matches (without making any replacements). * * This method is used by the {@link #link} method, but can also be used to * simply do parsing of the input in order to discover what kinds of links * there are and how many. * * Example usage: * * var autolinker = new Autolinker( { * urls: true, * email: true * } ); * * var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" ); * * console.log( matches.length ); // 2 * console.log( matches[ 0 ].getType() ); // 'url' * console.log( matches[ 0 ].getUrl() ); // 'google.com' * console.log( matches[ 1 ].getType() ); // 'email' * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com' * * @param {String} textOrHtml The HTML or text to find matches within * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, * {@link #hashtag}, and {@link #mention} options are enabled). * @return {Autolinker.match.Match[]} The array of Matches found in the * given input `textOrHtml`. */ Autolinker.prototype.parse = function (textOrHtml) { var _this = this; 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 tag, for instance matches = []; // Find all matches within the `textOrHtml` (but not matches that are // already nested within ,