123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866 |
- import { version } from './version';
- import { defaults, isBoolean, removeWithPredicate } from './utils';
- import { AnchorTagBuilder } from './anchor-tag-builder';
- import { HtmlTag } from './html-tag';
- import { parseMatches } from './parser/parse-matches';
- import { parseHtml } from './htmlParser/parse-html';
- import { mentionServices } from './parser/mention-utils';
- import { hashtagServices } from './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 <a href="http://www.yahoo.com">yahoo.com</a>'
- *
- *
- * 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 <a href="http://www.yahoo.com">yahoo.com</a>'
- *
- *
- * ## 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 '<a href="http://newplace.to.link.phone.numbers.to/">' + phoneNumber + '</a>';
- *
- * case 'hashtag' :
- * var hashtag = match.getHashtag();
- * console.log( hashtag );
- *
- * return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
- *
- * case 'mention' :
- * var mention = match.getMention();
- * console.log( mention );
- *
- * return '<a href="http://newplace.to.link.mention.to/">' + mention + '</a>';
- * }
- * }
- * } );
- *
- *
- * 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 = isBoolean(cfg.email) ? cfg.email : this.email;
- this.phone = isBoolean(cfg.phone) ? cfg.phone : this.phone;
- this.hashtag = cfg.hashtag || this.hashtag;
- this.mention = cfg.mention || this.mention;
- this.newWindow = isBoolean(cfg.newWindow) ? cfg.newWindow : this.newWindow;
- this.stripPrefix = normalizeStripPrefixCfg(cfg.stripPrefix);
- this.stripTrailingSlash = isBoolean(cfg.stripTrailingSlash)
- ? cfg.stripTrailingSlash
- : this.stripTrailingSlash;
- this.decodePercentEncoding = 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 && 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 && 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 <a href="http://google.com">google.com</a>"
- *
- * @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 <a> tag, for instance
- matches = [];
- // Find all matches within the `textOrHtml` (but not matches that are
- // already nested within <a>, <style> and <script> tags)
- parseHtml(textOrHtml, {
- onOpenTag: function (tagName) {
- if (skipTagNames.indexOf(tagName) >= 0) {
- skipTagsStackCount++;
- }
- },
- onText: function (text, offset) {
- // Only process text nodes that are not within an <a>, <style> or <script> tag
- if (skipTagsStackCount === 0) {
- // "Walk around" common HTML entities. An ' ' (for example)
- // could be at the end of a URL, but we don't want to
- // include the trailing '&' in the URL. See issue #76
- // TODO: Handle HTML entities separately in parseHtml() and
- // don't emit them as "text" except for & entities
- var htmlCharacterEntitiesRegex = /( | |<|<|>|>|"|"|')/gi; // NOTE: capturing group is significant to include the split characters in the .split() call below
- var textSplit = text.split(htmlCharacterEntitiesRegex);
- var currentOffset_1 = offset;
- textSplit.forEach(function (splitText, i) {
- // even number matches are text, odd numbers are html entities
- if (i % 2 === 0) {
- var textNodeMatches = _this.parseText(splitText, currentOffset_1);
- matches.push.apply(matches, textNodeMatches);
- }
- currentOffset_1 += splitText.length;
- });
- }
- },
- onCloseTag: function (tagName) {
- if (skipTagNames.indexOf(tagName) >= 0) {
- skipTagsStackCount = Math.max(skipTagsStackCount - 1, 0); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
- }
- },
- onComment: function (_offset) { },
- onDoctype: function (_offset) { }, // no need to process doctype nodes
- });
- // After we have found all matches, remove subsequent matches that
- // overlap with a previous match. This can happen for instance with URLs,
- // where the url 'google.com/#link' would match '#link' as a hashtag.
- matches = this.compactMatches(matches);
- // And finally, remove matches for match types that have been turned
- // off. We needed to have all match types turned on initially so that
- // things like hashtags could be filtered out if they were really just
- // part of a URL match (for instance, as a named anchor).
- matches = this.removeUnwantedMatches(matches);
- return matches;
- };
- /**
- * After we have found all matches, we need to remove matches that overlap
- * with a previous match. This can happen for instance with URLs, where the
- * url 'google.com/#link' would match '#link' as a hashtag. Because the
- * '#link' part is contained in a larger match that comes before the HashTag
- * match, we'll remove the HashTag match.
- *
- * @private
- * @param {Autolinker.match.Match[]} matches
- * @return {Autolinker.match.Match[]}
- */
- Autolinker.prototype.compactMatches = function (matches) {
- // First, the matches need to be sorted in order of offset
- matches.sort(function (a, b) {
- return a.getOffset() - b.getOffset();
- });
- var i = 0;
- while (i < matches.length - 1) {
- var match = matches[i], offset = match.getOffset(), matchedTextLength = match.getMatchedText().length, endIdx = offset + matchedTextLength;
- if (i + 1 < matches.length) {
- // Remove subsequent matches that equal offset with current match
- if (matches[i + 1].getOffset() === offset) {
- var removeIdx = matches[i + 1].getMatchedText().length > matchedTextLength ? i : i + 1;
- matches.splice(removeIdx, 1);
- continue;
- }
- // Remove subsequent matches that overlap with the current match
- if (matches[i + 1].getOffset() < endIdx) {
- matches.splice(i + 1, 1);
- continue;
- }
- }
- i++;
- }
- return matches;
- };
- /**
- * Removes matches for matchers that were turned off in the options. For
- * example, if {@link #hashtag hashtags} were not to be matched, we'll
- * remove them from the `matches` array here.
- *
- * Note: we *must* use all Matchers on the input string, and then filter
- * them out later. For example, if the options were `{ url: false, hashtag: true }`,
- * we wouldn't want to match the text '#link' as a HashTag inside of the text
- * 'google.com/#link'. The way the algorithm works is that we match the full
- * URL first (which prevents the accidental HashTag match), and then we'll
- * simply throw away the URL match.
- *
- * @private
- * @param {Autolinker.match.Match[]} matches The array of matches to remove
- * the unwanted matches from. Note: this array is mutated for the
- * removals.
- * @return {Autolinker.match.Match[]} The mutated input `matches` array.
- */
- Autolinker.prototype.removeUnwantedMatches = function (matches) {
- if (!this.hashtag)
- removeWithPredicate(matches, function (match) {
- return match.getType() === 'hashtag';
- });
- if (!this.email)
- removeWithPredicate(matches, function (match) {
- return match.getType() === 'email';
- });
- if (!this.phone)
- removeWithPredicate(matches, function (match) {
- return match.getType() === 'phone';
- });
- if (!this.mention)
- removeWithPredicate(matches, function (match) {
- return match.getType() === 'mention';
- });
- if (!this.urls.schemeMatches) {
- removeWithPredicate(matches, function (m) {
- return m.getType() === 'url' && m.getUrlMatchType() === 'scheme';
- });
- }
- if (!this.urls.tldMatches) {
- removeWithPredicate(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'tld'; });
- }
- if (!this.urls.ipV4Matches) {
- removeWithPredicate(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'ipV4'; });
- }
- return matches;
- };
- /**
- * Parses the input `text` 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.
- *
- * This method processes a **non-HTML string**, and is used to parse and
- * match within the text nodes of an HTML string. This method is used
- * internally by {@link #parse}.
- *
- * @private
- * @param {String} text The text to find matches within (depending on if the
- * {@link #urls}, {@link #email}, {@link #phone},
- * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string.
- * @param {Number} [offset=0] The offset of the text node within the
- * original string. This is used when parsing with the {@link #parse}
- * method to generate correct offsets within the {@link Autolinker.match.Match}
- * instances, but may be omitted if calling this method publicly.
- * @return {Autolinker.match.Match[]} The array of Matches found in the
- * given input `text`.
- */
- Autolinker.prototype.parseText = function (text, offset) {
- if (offset === void 0) { offset = 0; }
- offset = offset || 0;
- var matches = parseMatches(text, {
- tagBuilder: this.getTagBuilder(),
- stripPrefix: this.stripPrefix,
- stripTrailingSlash: this.stripTrailingSlash,
- decodePercentEncoding: this.decodePercentEncoding,
- hashtagServiceName: this.hashtag,
- mentionServiceName: this.mention || 'twitter',
- });
- // Correct the offset of each of the matches. They are originally
- // the offset of the match within the provided text node, but we
- // need to correct them to be relative to the original HTML input
- // string (i.e. the one provided to #parse).
- for (var i = 0, numTextMatches = matches.length; i < numTextMatches; i++) {
- matches[i].setOffset(offset + matches[i].getOffset());
- }
- return matches;
- };
- /**
- * Automatically links URLs, Email addresses, Phone numbers, Hashtags,
- * and Mentions (Twitter, Instagram, Soundcloud) 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>`
- *
- * This method finds the text around any HTML elements in the input
- * `textOrHtml`, which will be the text that is processed. Any original HTML
- * elements will be left as-is, as well as the text that is already wrapped
- * in anchor (<a>) tags.
- *
- * @param {String} textOrHtml The HTML or text to autolink matches within
- * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled).
- * @return {String} The HTML, with matches automatically linked.
- */
- Autolinker.prototype.link = function (textOrHtml) {
- if (!textOrHtml) {
- return '';
- } // handle `null` and `undefined` (for JavaScript users that don't have TypeScript support)
- /* We would want to sanitize the start and end characters of a tag
- * before processing the string in order to avoid an XSS scenario.
- * This behaviour can be changed by toggling the sanitizeHtml option.
- */
- if (this.sanitizeHtml) {
- textOrHtml = textOrHtml.replace(/</g, '<').replace(/>/g, '>');
- }
- var matches = this.parse(textOrHtml), newHtml = [], lastIndex = 0;
- for (var i = 0, len = matches.length; i < len; i++) {
- var match = matches[i];
- newHtml.push(textOrHtml.substring(lastIndex, match.getOffset()));
- newHtml.push(this.createMatchReturnVal(match));
- lastIndex = match.getOffset() + match.getMatchedText().length;
- }
- newHtml.push(textOrHtml.substring(lastIndex)); // handle the text after the last match
- return newHtml.join('');
- };
- /**
- * Creates the return string value for a given match in the input string.
- *
- * This method handles the {@link #replaceFn}, if one was provided.
- *
- * @private
- * @param {Autolinker.match.Match} match The Match object that represents
- * the match.
- * @return {String} The string that the `match` should be replaced with.
- * This is usually the anchor tag string, but may be the `matchStr` itself
- * if the match is not to be replaced.
- */
- Autolinker.prototype.createMatchReturnVal = function (match) {
- // Handle a custom `replaceFn` being provided
- var replaceFnResult;
- if (this.replaceFn) {
- replaceFnResult = this.replaceFn.call(this.context, match); // Autolinker instance is the context
- }
- if (typeof replaceFnResult === 'string') {
- return replaceFnResult; // `replaceFn` returned a string, use that
- }
- else if (replaceFnResult === false) {
- return match.getMatchedText(); // no replacement for the match
- }
- else if (replaceFnResult instanceof HtmlTag) {
- return replaceFnResult.toAnchorString();
- }
- else {
- // replaceFnResult === true, or no/unknown return value from function
- // Perform Autolinker's default anchor tag generation
- var anchorTag = match.buildTag(); // returns an Autolinker.HtmlTag instance
- return anchorTag.toAnchorString();
- }
- };
- /**
- * Returns the {@link #tagBuilder} instance for this Autolinker instance,
- * lazily instantiating it if it does not yet exist.
- *
- * @private
- * @return {Autolinker.AnchorTagBuilder}
- */
- Autolinker.prototype.getTagBuilder = function () {
- var tagBuilder = this.tagBuilder;
- if (!tagBuilder) {
- tagBuilder = this.tagBuilder = new AnchorTagBuilder({
- newWindow: this.newWindow,
- truncate: this.truncate,
- className: this.className,
- });
- }
- return tagBuilder;
- };
- // NOTE: must be 'export default' here for UMD module
- /**
- * @static
- * @property {String} version
- *
- * The Autolinker version number in the form major.minor.patch
- *
- * Ex: 3.15.0
- */
- Autolinker.version = version;
- return Autolinker;
- }());
- export default Autolinker;
- /**
- * Normalizes the {@link #urls} config into an Object with its 2 properties:
- * `schemeMatches` and `tldMatches`, both booleans.
- *
- * See {@link #urls} config for details.
- *
- * @private
- * @param {Boolean/Object} urls
- * @return {Object}
- */
- function normalizeUrlsCfg(urls) {
- if (urls == null)
- urls = true; // default to `true`
- if (isBoolean(urls)) {
- return { schemeMatches: urls, tldMatches: urls, ipV4Matches: urls };
- }
- else {
- // object form
- return {
- schemeMatches: isBoolean(urls.schemeMatches) ? urls.schemeMatches : true,
- tldMatches: isBoolean(urls.tldMatches) ? urls.tldMatches : true,
- ipV4Matches: isBoolean(urls.ipV4Matches) ? urls.ipV4Matches : true,
- };
- }
- }
- /**
- * Normalizes the {@link #stripPrefix} config into an Object with 2
- * properties: `scheme`, and `www` - both Booleans.
- *
- * See {@link #stripPrefix} config for details.
- *
- * @private
- * @param {Boolean/Object} stripPrefix
- * @return {Object}
- */
- function normalizeStripPrefixCfg(stripPrefix) {
- if (stripPrefix == null)
- stripPrefix = true; // default to `true`
- if (isBoolean(stripPrefix)) {
- return { scheme: stripPrefix, www: stripPrefix };
- }
- else {
- // object form
- return {
- scheme: isBoolean(stripPrefix.scheme) ? stripPrefix.scheme : true,
- www: isBoolean(stripPrefix.www) ? stripPrefix.www : true,
- };
- }
- }
- /**
- * Normalizes the {@link #truncate} config into an Object with 2 properties:
- * `length` (Number), and `location` (String).
- *
- * See {@link #truncate} config for details.
- *
- * @private
- * @param {Number/Object} truncate
- * @return {Object}
- */
- function normalizeTruncateCfg(truncate) {
- if (typeof truncate === 'number') {
- return { length: truncate, location: 'end' };
- }
- else {
- // object, or undefined/null
- return defaults(truncate || {}, {
- length: Number.POSITIVE_INFINITY,
- location: 'end',
- });
- }
- }
- //# sourceMappingURL=autolinker.js.map
|