智慧水务管理系统 - 精河县供水工程综合管理平台

autolinker.js 40KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. var tslib_1 = require("tslib");
  4. var version_1 = require("./version");
  5. var utils_1 = require("./utils");
  6. var anchor_tag_builder_1 = require("./anchor-tag-builder");
  7. var html_tag_1 = require("./html-tag");
  8. var parse_matches_1 = require("./parser/parse-matches");
  9. var parse_html_1 = require("./htmlParser/parse-html");
  10. var mention_utils_1 = require("./parser/mention-utils");
  11. var hashtag_utils_1 = require("./parser/hashtag-utils");
  12. /**
  13. * @class Autolinker
  14. * @extends Object
  15. *
  16. * Utility class used to process a given string of text, and wrap the matches in
  17. * the appropriate anchor (<a>) tags to turn them into links.
  18. *
  19. * Any of the configuration options may be provided in an Object provided
  20. * to the Autolinker constructor, which will configure how the {@link #link link()}
  21. * method will process the links.
  22. *
  23. * For example:
  24. *
  25. * var autolinker = new Autolinker( {
  26. * newWindow : false,
  27. * truncate : 30
  28. * } );
  29. *
  30. * var html = autolinker.link( "Joe went to www.yahoo.com" );
  31. * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
  32. *
  33. *
  34. * The {@link #static-link static link()} method may also be used to inline
  35. * options into a single call, which may be more convenient for one-off uses.
  36. * For example:
  37. *
  38. * var html = Autolinker.link( "Joe went to www.yahoo.com", {
  39. * newWindow : false,
  40. * truncate : 30
  41. * } );
  42. * // produces: 'Joe went to <a href="http://www.yahoo.com">yahoo.com</a>'
  43. *
  44. *
  45. * ## Custom Replacements of Links
  46. *
  47. * If the configuration options do not provide enough flexibility, a {@link #replaceFn}
  48. * may be provided to fully customize the output of Autolinker. This function is
  49. * called once for each URL/Email/Phone#/Hashtag/Mention (Twitter, Instagram, Soundcloud)
  50. * match that is encountered.
  51. *
  52. * For example:
  53. *
  54. * var input = "..."; // string with URLs, Email Addresses, Phone #s, Hashtags, and Mentions (Twitter, Instagram, Soundcloud)
  55. *
  56. * var linkedText = Autolinker.link( input, {
  57. * replaceFn : function( match ) {
  58. * console.log( "href = ", match.getAnchorHref() );
  59. * console.log( "text = ", match.getAnchorText() );
  60. *
  61. * switch( match.getType() ) {
  62. * case 'url' :
  63. * console.log( "url: ", match.getUrl() );
  64. *
  65. * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) {
  66. * var tag = match.buildTag(); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes
  67. * tag.setAttr( 'rel', 'nofollow' );
  68. * tag.addClass( 'external-link' );
  69. *
  70. * return tag;
  71. *
  72. * } else {
  73. * return true; // let Autolinker perform its normal anchor tag replacement
  74. * }
  75. *
  76. * case 'email' :
  77. * var email = match.getEmail();
  78. * console.log( "email: ", email );
  79. *
  80. * if( email === "my@own.address" ) {
  81. * return false; // don't auto-link this particular email address; leave as-is
  82. * } else {
  83. * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`)
  84. * }
  85. *
  86. * case 'phone' :
  87. * var phoneNumber = match.getPhoneNumber();
  88. * console.log( phoneNumber );
  89. *
  90. * return '<a href="http://newplace.to.link.phone.numbers.to/">' + phoneNumber + '</a>';
  91. *
  92. * case 'hashtag' :
  93. * var hashtag = match.getHashtag();
  94. * console.log( hashtag );
  95. *
  96. * return '<a href="http://newplace.to.link.hashtag.handles.to/">' + hashtag + '</a>';
  97. *
  98. * case 'mention' :
  99. * var mention = match.getMention();
  100. * console.log( mention );
  101. *
  102. * return '<a href="http://newplace.to.link.mention.to/">' + mention + '</a>';
  103. * }
  104. * }
  105. * } );
  106. *
  107. *
  108. * The function may return the following values:
  109. *
  110. * - `true` (Boolean): Allow Autolinker to replace the match as it normally
  111. * would.
  112. * - `false` (Boolean): Do not replace the current match at all - leave as-is.
  113. * - Any String: If a string is returned from the function, the string will be
  114. * used directly as the replacement HTML for the match.
  115. * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify
  116. * an HTML tag before writing out its HTML text.
  117. */
  118. var Autolinker = /** @class */ (function () {
  119. /**
  120. * @method constructor
  121. * @param {Object} [cfg] The configuration options for the Autolinker instance,
  122. * specified in an Object (map).
  123. */
  124. function Autolinker(cfg) {
  125. if (cfg === void 0) { cfg = {}; }
  126. /**
  127. * The Autolinker version number exposed on the instance itself.
  128. *
  129. * Ex: 0.25.1
  130. *
  131. * @property {String} version
  132. */
  133. this.version = Autolinker.version;
  134. /**
  135. * @cfg {Boolean/Object} [urls]
  136. *
  137. * `true` if URLs should be automatically linked, `false` if they should not
  138. * be. Defaults to `true`.
  139. *
  140. * Examples:
  141. *
  142. * urls: true
  143. *
  144. * // or
  145. *
  146. * urls: {
  147. * schemeMatches : true,
  148. * tldMatches : true,
  149. * ipV4Matches : true
  150. * }
  151. *
  152. * As shown above, this option also accepts an Object form with 3 properties
  153. * to allow for more customization of what exactly gets linked. All default
  154. * to `true`:
  155. *
  156. * @cfg {Boolean} [urls.schemeMatches] `true` to match URLs found prefixed
  157. * with a scheme, i.e. `http://google.com`, or `other+scheme://google.com`,
  158. * `false` to prevent these types of matches.
  159. * @cfg {Boolean} [urls.tldMatches] `true` to match URLs with known top
  160. * level domains (.com, .net, etc.) that are not prefixed with a scheme
  161. * (such as 'http://'). This option attempts to match anything that looks
  162. * like a URL in the given text. Ex: `google.com`, `asdf.org/?page=1`, etc.
  163. * `false` to prevent these types of matches.
  164. * @cfg {Boolean} [urls.ipV4Matches] `true` to match IPv4 addresses in text
  165. * that are not prefixed with a scheme (such as 'http://'). This option
  166. * attempts to match anything that looks like an IPv4 address in text. Ex:
  167. * `192.168.0.1`, `10.0.0.1/?page=1`, etc. `false` to prevent these types
  168. * of matches.
  169. */
  170. this.urls = {}; // default value just to get the above doc comment in the ES5 output and documentation generator
  171. /**
  172. * @cfg {Boolean} [email=true]
  173. *
  174. * `true` if email addresses should be automatically linked, `false` if they
  175. * should not be.
  176. */
  177. this.email = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  178. /**
  179. * @cfg {Boolean} [phone=true]
  180. *
  181. * `true` if Phone numbers ("(555)555-5555") should be automatically linked,
  182. * `false` if they should not be.
  183. */
  184. this.phone = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  185. /**
  186. * @cfg {Boolean/String} [hashtag=false]
  187. *
  188. * A string for the service name to have hashtags (ex: "#myHashtag")
  189. * auto-linked to. The currently-supported values are:
  190. *
  191. * - 'twitter'
  192. * - 'facebook'
  193. * - 'instagram'
  194. * - 'tiktok'
  195. * - 'youtube'
  196. *
  197. * Pass `false` to skip auto-linking of hashtags.
  198. */
  199. this.hashtag = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  200. /**
  201. * @cfg {String/Boolean} [mention=false]
  202. *
  203. * A string for the service name to have mentions (ex: "@myuser")
  204. * auto-linked to. The currently supported values are:
  205. *
  206. * - 'twitter'
  207. * - 'instagram'
  208. * - 'soundcloud'
  209. * - 'tiktok'
  210. * - 'youtube'
  211. *
  212. * Defaults to `false` to skip auto-linking of mentions.
  213. */
  214. this.mention = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  215. /**
  216. * @cfg {Boolean} [newWindow=true]
  217. *
  218. * `true` if the links should open in a new window, `false` otherwise.
  219. */
  220. this.newWindow = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  221. /**
  222. * @cfg {Boolean/Object} [stripPrefix=true]
  223. *
  224. * `true` if 'http://' (or 'https://') and/or the 'www.' should be stripped
  225. * from the beginning of URL links' text, `false` otherwise. Defaults to
  226. * `true`.
  227. *
  228. * Examples:
  229. *
  230. * stripPrefix: true
  231. *
  232. * // or
  233. *
  234. * stripPrefix: {
  235. * scheme : true,
  236. * www : true
  237. * }
  238. *
  239. * As shown above, this option also accepts an Object form with 2 properties
  240. * to allow for more customization of what exactly is prevented from being
  241. * displayed. Both default to `true`:
  242. *
  243. * @cfg {Boolean} [stripPrefix.scheme] `true` to prevent the scheme part of
  244. * a URL match from being displayed to the user. Example:
  245. * `'http://google.com'` will be displayed as `'google.com'`. `false` to
  246. * not strip the scheme. NOTE: Only an `'http://'` or `'https://'` scheme
  247. * will be removed, so as not to remove a potentially dangerous scheme
  248. * (such as `'file://'` or `'javascript:'`)
  249. * @cfg {Boolean} [stripPrefix.www] www (Boolean): `true` to prevent the
  250. * `'www.'` part of a URL match from being displayed to the user. Ex:
  251. * `'www.google.com'` will be displayed as `'google.com'`. `false` to not
  252. * strip the `'www'`.
  253. */
  254. this.stripPrefix = {
  255. scheme: true,
  256. www: true,
  257. }; // default value just to get the above doc comment in the ES5 output and documentation generator
  258. /**
  259. * @cfg {Boolean} [stripTrailingSlash=true]
  260. *
  261. * `true` to remove the trailing slash from URL matches, `false` to keep
  262. * the trailing slash.
  263. *
  264. * Example when `true`: `http://google.com/` will be displayed as
  265. * `http://google.com`.
  266. */
  267. this.stripTrailingSlash = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  268. /**
  269. * @cfg {Boolean} [decodePercentEncoding=true]
  270. *
  271. * `true` to decode percent-encoded characters in URL matches, `false` to keep
  272. * the percent-encoded characters.
  273. *
  274. * Example when `true`: `https://en.wikipedia.org/wiki/San_Jos%C3%A9` will
  275. * be displayed as `https://en.wikipedia.org/wiki/San_José`.
  276. */
  277. this.decodePercentEncoding = true; // default value just to get the above doc comment in the ES5 output and documentation generator
  278. /**
  279. * @cfg {Number/Object} [truncate=0]
  280. *
  281. * ## Number Form
  282. *
  283. * A number for how many characters matched text should be truncated to
  284. * inside the text of a link. If the matched text is over this number of
  285. * characters, it will be truncated to this length by adding a two period
  286. * ellipsis ('..') to the end of the string.
  287. *
  288. * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file'
  289. * truncated to 25 characters might look something like this:
  290. * 'yahoo.com/some/long/pat..'
  291. *
  292. * Example Usage:
  293. *
  294. * truncate: 25
  295. *
  296. *
  297. * Defaults to `0` for "no truncation."
  298. *
  299. *
  300. * ## Object Form
  301. *
  302. * An Object may also be provided with two properties: `length` (Number) and
  303. * `location` (String). `location` may be one of the following: 'end'
  304. * (default), 'middle', or 'smart'.
  305. *
  306. * Example Usage:
  307. *
  308. * truncate: { length: 25, location: 'middle' }
  309. *
  310. * @cfg {Number} [truncate.length=0] How many characters to allow before
  311. * truncation will occur. Defaults to `0` for "no truncation."
  312. * @cfg {"end"/"middle"/"smart"} [truncate.location="end"]
  313. *
  314. * - 'end' (default): will truncate up to the number of characters, and then
  315. * add an ellipsis at the end. Ex: 'yahoo.com/some/long/pat..'
  316. * - 'middle': will truncate and add the ellipsis in the middle. Ex:
  317. * 'yahoo.com/s..th/to/a/file'
  318. * - 'smart': for URLs where the algorithm attempts to strip out unnecessary
  319. * parts first (such as the 'www.', then URL scheme, hash, etc.),
  320. * attempting to make the URL human-readable before looking for a good
  321. * point to insert the ellipsis if it is still too long. Ex:
  322. * 'yahoo.com/some..to/a/file'. For more details, see
  323. * {@link Autolinker.truncate.TruncateSmart}.
  324. */
  325. this.truncate = {
  326. length: 0,
  327. location: 'end',
  328. }; // default value just to get the above doc comment in the ES5 output and documentation generator
  329. /**
  330. * @cfg {String} className
  331. *
  332. * A CSS class name to add to the generated links. This class will be added
  333. * to all links, as well as this class plus match suffixes for styling
  334. * url/email/phone/hashtag/mention links differently.
  335. *
  336. * For example, if this config is provided as "myLink", then:
  337. *
  338. * - URL links will have the CSS classes: "myLink myLink-url"
  339. * - Email links will have the CSS classes: "myLink myLink-email", and
  340. * - Phone links will have the CSS classes: "myLink myLink-phone"
  341. * - Hashtag links will have the CSS classes: "myLink myLink-hashtag"
  342. * - Mention links will have the CSS classes: "myLink myLink-mention myLink-[type]"
  343. * where [type] is either "instagram", "twitter" or "soundcloud"
  344. */
  345. this.className = ''; // default value just to get the above doc comment in the ES5 output and documentation generator
  346. /**
  347. * @cfg {Function} replaceFn
  348. *
  349. * A function to individually process each match found in the input string.
  350. *
  351. * See the class's description for usage.
  352. *
  353. * The `replaceFn` can be called with a different context object (`this`
  354. * reference) using the {@link #context} cfg.
  355. *
  356. * This function is called with the following parameter:
  357. *
  358. * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which
  359. * can be used to retrieve information about the match that the `replaceFn`
  360. * is currently processing. See {@link Autolinker.match.Match} subclasses
  361. * for details.
  362. */
  363. this.replaceFn = null; // default value just to get the above doc comment in the ES5 output and documentation generator
  364. /**
  365. * @cfg {Object} context
  366. *
  367. * The context object (`this` reference) to call the `replaceFn` with.
  368. *
  369. * Defaults to this Autolinker instance.
  370. */
  371. this.context = undefined; // default value just to get the above doc comment in the ES5 output and documentation generator
  372. /**
  373. * @cfg {Boolean} [sanitizeHtml=false]
  374. *
  375. * `true` to HTML-encode the start and end brackets of existing HTML tags found
  376. * in the input string. This will escape `<` and `>` characters to `&lt;` and
  377. * `&gt;`, respectively.
  378. *
  379. * Setting this to `true` will prevent XSS (Cross-site Scripting) attacks,
  380. * but will remove the significance of existing HTML tags in the input string. If
  381. * you would like to maintain the significance of existing HTML tags while also
  382. * making the output HTML string safe, leave this option as `false` and use a
  383. * tool like https://github.com/cure53/DOMPurify (or others) on the input string
  384. * before running Autolinker.
  385. */
  386. this.sanitizeHtml = false; // default value just to get the above doc comment in the ES5 output and documentation generator
  387. /**
  388. * @private
  389. * @property {Autolinker.AnchorTagBuilder} tagBuilder
  390. *
  391. * The AnchorTagBuilder instance used to build match replacement anchor tags.
  392. * Note: this is lazily instantiated in the {@link #getTagBuilder} method.
  393. */
  394. this.tagBuilder = null;
  395. // Note: when `this.something` is used in the rhs of these assignments,
  396. // it refers to the default values set above the constructor
  397. this.urls = normalizeUrlsCfg(cfg.urls);
  398. this.email = (0, utils_1.isBoolean)(cfg.email) ? cfg.email : this.email;
  399. this.phone = (0, utils_1.isBoolean)(cfg.phone) ? cfg.phone : this.phone;
  400. this.hashtag = cfg.hashtag || this.hashtag;
  401. this.mention = cfg.mention || this.mention;
  402. this.newWindow = (0, utils_1.isBoolean)(cfg.newWindow) ? cfg.newWindow : this.newWindow;
  403. this.stripPrefix = normalizeStripPrefixCfg(cfg.stripPrefix);
  404. this.stripTrailingSlash = (0, utils_1.isBoolean)(cfg.stripTrailingSlash)
  405. ? cfg.stripTrailingSlash
  406. : this.stripTrailingSlash;
  407. this.decodePercentEncoding = (0, utils_1.isBoolean)(cfg.decodePercentEncoding)
  408. ? cfg.decodePercentEncoding
  409. : this.decodePercentEncoding;
  410. this.sanitizeHtml = cfg.sanitizeHtml || false;
  411. // Validate the value of the `mention` cfg
  412. var mention = this.mention;
  413. if (mention !== false && mention_utils_1.mentionServices.indexOf(mention) === -1) {
  414. throw new Error("invalid `mention` cfg '".concat(mention, "' - see docs"));
  415. }
  416. // Validate the value of the `hashtag` cfg
  417. var hashtag = this.hashtag;
  418. if (hashtag !== false && hashtag_utils_1.hashtagServices.indexOf(hashtag) === -1) {
  419. throw new Error("invalid `hashtag` cfg '".concat(hashtag, "' - see docs"));
  420. }
  421. this.truncate = normalizeTruncateCfg(cfg.truncate);
  422. this.className = cfg.className || this.className;
  423. this.replaceFn = cfg.replaceFn || this.replaceFn;
  424. this.context = cfg.context || this;
  425. }
  426. /**
  427. * Automatically links URLs, Email addresses, Phone Numbers, Twitter handles,
  428. * Hashtags, and Mentions found in the given chunk of HTML. Does not link URLs
  429. * found within HTML tags.
  430. *
  431. * For instance, if given the text: `You should go to http://www.yahoo.com`,
  432. * then the result will be `You should go to &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
  433. *
  434. * Example:
  435. *
  436. * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } );
  437. * // Produces: "Go to <a href="http://google.com">google.com</a>"
  438. *
  439. * @static
  440. * @param {String} textOrHtml The HTML or text to find matches within (depending
  441. * on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #mention},
  442. * {@link #hashtag}, and {@link #mention} options are enabled).
  443. * @param {Object} [options] Any of the configuration options for the Autolinker
  444. * class, specified in an Object (map). See the class description for an
  445. * example call.
  446. * @return {String} The HTML text, with matches automatically linked.
  447. */
  448. Autolinker.link = function (textOrHtml, options) {
  449. var autolinker = new Autolinker(options);
  450. return autolinker.link(textOrHtml);
  451. };
  452. /**
  453. * Parses the input `textOrHtml` looking for URLs, email addresses, phone
  454. * numbers, username handles, and hashtags (depending on the configuration
  455. * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
  456. * objects describing those matches (without making any replacements).
  457. *
  458. * Note that if parsing multiple pieces of text, it is slightly more efficient
  459. * to create an Autolinker instance, and use the instance-level {@link #parse}
  460. * method.
  461. *
  462. * Example:
  463. *
  464. * var matches = Autolinker.parse("Hello google.com, I am asdf@asdf.com", {
  465. * urls: true,
  466. * email: true
  467. * });
  468. *
  469. * console.log(matches.length); // 2
  470. * console.log(matches[0].getType()); // 'url'
  471. * console.log(matches[0].getUrl()); // 'google.com'
  472. * console.log(matches[1].getType()); // 'email'
  473. * console.log(matches[1].getEmail()); // 'asdf@asdf.com'
  474. *
  475. * @static
  476. * @param {String} textOrHtml The HTML or text to find matches within
  477. * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
  478. * {@link #hashtag}, and {@link #mention} options are enabled).
  479. * @param {Object} [options] Any of the configuration options for the Autolinker
  480. * class, specified in an Object (map). See the class description for an
  481. * example call.
  482. * @return {Autolinker.match.Match[]} The array of Matches found in the
  483. * given input `textOrHtml`.
  484. */
  485. Autolinker.parse = function (textOrHtml, options) {
  486. var autolinker = new Autolinker(options);
  487. return autolinker.parse(textOrHtml);
  488. };
  489. /**
  490. * Parses the input `textOrHtml` looking for URLs, email addresses, phone
  491. * numbers, username handles, and hashtags (depending on the configuration
  492. * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
  493. * objects describing those matches (without making any replacements).
  494. *
  495. * This method is used by the {@link #link} method, but can also be used to
  496. * simply do parsing of the input in order to discover what kinds of links
  497. * there are and how many.
  498. *
  499. * Example usage:
  500. *
  501. * var autolinker = new Autolinker( {
  502. * urls: true,
  503. * email: true
  504. * } );
  505. *
  506. * var matches = autolinker.parse( "Hello google.com, I am asdf@asdf.com" );
  507. *
  508. * console.log( matches.length ); // 2
  509. * console.log( matches[ 0 ].getType() ); // 'url'
  510. * console.log( matches[ 0 ].getUrl() ); // 'google.com'
  511. * console.log( matches[ 1 ].getType() ); // 'email'
  512. * console.log( matches[ 1 ].getEmail() ); // 'asdf@asdf.com'
  513. *
  514. * @param {String} textOrHtml The HTML or text to find matches within
  515. * (depending on if the {@link #urls}, {@link #email}, {@link #phone},
  516. * {@link #hashtag}, and {@link #mention} options are enabled).
  517. * @return {Autolinker.match.Match[]} The array of Matches found in the
  518. * given input `textOrHtml`.
  519. */
  520. Autolinker.prototype.parse = function (textOrHtml) {
  521. var _this = this;
  522. var skipTagNames = ['a', 'style', 'script'];
  523. var 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
  524. var matches = [];
  525. // Find all matches within the `textOrHtml` (but not matches that are
  526. // already nested within <a>, <style> and <script> tags)
  527. (0, parse_html_1.parseHtml)(textOrHtml, {
  528. onOpenTag: function (tagName) {
  529. if (skipTagNames.indexOf(tagName) >= 0) {
  530. skipTagsStackCount++;
  531. }
  532. },
  533. onText: function (text, offset) {
  534. // Only process text nodes that are not within an <a>, <style> or <script> tag
  535. if (skipTagsStackCount === 0) {
  536. // "Walk around" common HTML entities. An '&nbsp;' (for example)
  537. // could be at the end of a URL, but we don't want to
  538. // include the trailing '&' in the URL. See issue #76
  539. // TODO: Handle HTML entities separately in parseHtml() and
  540. // don't emit them as "text" except for &amp; entities
  541. var htmlCharacterEntitiesRegex = /(&nbsp;|&#160;|&lt;|&#60;|&gt;|&#62;|&quot;|&#34;|&#39;)/gi; // NOTE: capturing group is significant to include the split characters in the .split() call below
  542. var textSplit = text.split(htmlCharacterEntitiesRegex);
  543. var currentOffset_1 = offset;
  544. textSplit.forEach(function (splitText, i) {
  545. // even number matches are text, odd numbers are html entities
  546. if (i % 2 === 0) {
  547. var textNodeMatches = _this.parseText(splitText, currentOffset_1);
  548. matches.push.apply(matches, tslib_1.__spreadArray([], tslib_1.__read(textNodeMatches), false));
  549. }
  550. currentOffset_1 += splitText.length;
  551. });
  552. }
  553. },
  554. onCloseTag: function (tagName) {
  555. if (skipTagNames.indexOf(tagName) >= 0) {
  556. skipTagsStackCount = Math.max(skipTagsStackCount - 1, 0); // attempt to handle extraneous </a> tags by making sure the stack count never goes below 0
  557. }
  558. },
  559. onComment: function ( /*_offset: number*/) { }, // no need to process comment nodes
  560. onDoctype: function ( /*_offset: number*/) { }, // no need to process doctype nodes
  561. });
  562. // After we have found all matches, remove subsequent matches that
  563. // overlap with a previous match. This can happen for instance with an
  564. // email address where the local-part of the email is also a top-level
  565. // domain, such as in "google.com@aaa.com". In this case, the entire
  566. // email address should be linked rather than just the 'google.com'
  567. // part.
  568. matches = this.compactMatches(matches);
  569. // And finally, remove matches for match types that have been turned
  570. // off. We needed to have all match types turned on initially so that
  571. // things like hashtags could be filtered out if they were really just
  572. // part of a URL match (for instance, as a named anchor).
  573. matches = this.removeUnwantedMatches(matches);
  574. return matches;
  575. };
  576. /**
  577. * After we have found all matches, we need to remove matches that overlap
  578. * with a previous match. This can happen for instance with an
  579. * email address where the local-part of the email is also a top-level
  580. * domain, such as in "google.com@aaa.com". In this case, the entire email
  581. * address should be linked rather than just the 'google.com' part.
  582. *
  583. * @private
  584. * @param {Autolinker.match.Match[]} matches
  585. * @return {Autolinker.match.Match[]}
  586. */
  587. Autolinker.prototype.compactMatches = function (matches) {
  588. // First, the matches need to be sorted in order of offset in the input
  589. // string
  590. matches.sort(byMatchOffset);
  591. var i = 0;
  592. while (i < matches.length - 1) {
  593. var match = matches[i];
  594. var offset = match.getOffset();
  595. var matchedTextLength = match.getMatchedText().length;
  596. if (i + 1 < matches.length) {
  597. // Remove subsequent matches that equal offset with current match
  598. // This can happen when matching the text "google.com@aaa.com"
  599. // where we have both a URL ('google.com') and an email. We
  600. // should only keep the email match in this case.
  601. if (matches[i + 1].getOffset() === offset) {
  602. // Remove the shorter match
  603. var removeIdx = matches[i + 1].getMatchedText().length > matchedTextLength ? i : i + 1;
  604. matches.splice(removeIdx, 1);
  605. continue;
  606. }
  607. // Remove subsequent matches that overlap with the current match
  608. //
  609. // NOTE: This was a fundamental snippet of the Autolinker.js v3
  610. // algorithm where we had multiple regular expressions searching
  611. // the input string for matches. The regexes would sometimes
  612. // overlap such as in the case of "google.com/#link", where we
  613. // would have both a URL match and a hashtag match.
  614. //
  615. // However, the Autolinker.js v4 algorithm uses a state machine
  616. // parser and knows that the '#link' part of 'google.com/#link'
  617. // is part of the URL that precedes it, so we don't need this
  618. // piece of code any more. Keeping it here commented for now in
  619. // case we need to put it back at some point, but none of the
  620. // test cases are currently able to trigger the need for it.
  621. // const endIdx = offset + matchedTextLength;
  622. // if (matches[i + 1].getOffset() < endIdx) {
  623. // matches.splice(i + 1, 1);
  624. // continue;
  625. // }
  626. }
  627. i++;
  628. }
  629. return matches;
  630. };
  631. /**
  632. * Removes matches for matchers that were turned off in the options. For
  633. * example, if {@link #hashtag hashtags} were not to be matched, we'll
  634. * remove them from the `matches` array here.
  635. *
  636. * Note: we *must* use all Matchers on the input string, and then filter
  637. * them out later. For example, if the options were `{ url: false, hashtag: true }`,
  638. * we wouldn't want to match the text '#link' as a HashTag inside of the text
  639. * 'google.com/#link'. The way the algorithm works is that we match the full
  640. * URL first (which prevents the accidental HashTag match), and then we'll
  641. * simply throw away the URL match.
  642. *
  643. * @private
  644. * @param {Autolinker.match.Match[]} matches The array of matches to remove
  645. * the unwanted matches from. Note: this array is mutated for the
  646. * removals.
  647. * @return {Autolinker.match.Match[]} The mutated input `matches` array.
  648. */
  649. Autolinker.prototype.removeUnwantedMatches = function (matches) {
  650. if (!this.hashtag)
  651. (0, utils_1.removeWithPredicate)(matches, function (match) {
  652. return match.getType() === 'hashtag';
  653. });
  654. if (!this.email)
  655. (0, utils_1.removeWithPredicate)(matches, function (match) {
  656. return match.getType() === 'email';
  657. });
  658. if (!this.phone)
  659. (0, utils_1.removeWithPredicate)(matches, function (match) {
  660. return match.getType() === 'phone';
  661. });
  662. if (!this.mention)
  663. (0, utils_1.removeWithPredicate)(matches, function (match) {
  664. return match.getType() === 'mention';
  665. });
  666. if (!this.urls.schemeMatches) {
  667. (0, utils_1.removeWithPredicate)(matches, function (m) {
  668. return m.getType() === 'url' && m.getUrlMatchType() === 'scheme';
  669. });
  670. }
  671. if (!this.urls.tldMatches) {
  672. (0, utils_1.removeWithPredicate)(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'tld'; });
  673. }
  674. if (!this.urls.ipV4Matches) {
  675. (0, utils_1.removeWithPredicate)(matches, function (m) { return m.getType() === 'url' && m.getUrlMatchType() === 'ipV4'; });
  676. }
  677. return matches;
  678. };
  679. /**
  680. * Parses the input `text` looking for URLs, email addresses, phone
  681. * numbers, username handles, and hashtags (depending on the configuration
  682. * of the Autolinker instance), and returns an array of {@link Autolinker.match.Match}
  683. * objects describing those matches.
  684. *
  685. * This method processes a **non-HTML string**, and is used to parse and
  686. * match within the text nodes of an HTML string. This method is used
  687. * internally by {@link #parse}.
  688. *
  689. * @private
  690. * @param {String} text The text to find matches within (depending on if the
  691. * {@link #urls}, {@link #email}, {@link #phone},
  692. * {@link #hashtag}, and {@link #mention} options are enabled). This must be a non-HTML string.
  693. * @param {Number} [offset=0] The offset of the text node within the
  694. * original string. This is used when parsing with the {@link #parse}
  695. * method to generate correct offsets within the {@link Autolinker.match.Match}
  696. * instances, but may be omitted if calling this method publicly.
  697. * @return {Autolinker.match.Match[]} The array of Matches found in the
  698. * given input `text`.
  699. */
  700. Autolinker.prototype.parseText = function (text, offset) {
  701. offset = offset || 0;
  702. var matches = (0, parse_matches_1.parseMatches)(text, {
  703. tagBuilder: this.getTagBuilder(),
  704. stripPrefix: this.stripPrefix,
  705. stripTrailingSlash: this.stripTrailingSlash,
  706. decodePercentEncoding: this.decodePercentEncoding,
  707. hashtagServiceName: this.hashtag,
  708. mentionServiceName: this.mention || 'twitter',
  709. });
  710. // Correct the offset of each of the matches. They are originally
  711. // the offset of the match within the provided text node, but we
  712. // need to correct them to be relative to the original HTML input
  713. // string (i.e. the one provided to #parse).
  714. for (var i = 0, numTextMatches = matches.length; i < numTextMatches; i++) {
  715. matches[i].setOffset(offset + matches[i].getOffset());
  716. }
  717. return matches;
  718. };
  719. /**
  720. * Automatically links URLs, Email addresses, Phone numbers, Hashtags,
  721. * and Mentions (Twitter, Instagram, Soundcloud) found in the given chunk of HTML. Does not link
  722. * URLs found within HTML tags.
  723. *
  724. * For instance, if given the text: `You should go to http://www.yahoo.com`,
  725. * then the result will be `You should go to
  726. * &lt;a href="http://www.yahoo.com"&gt;http://www.yahoo.com&lt;/a&gt;`
  727. *
  728. * This method finds the text around any HTML elements in the input
  729. * `textOrHtml`, which will be the text that is processed. Any original HTML
  730. * elements will be left as-is, as well as the text that is already wrapped
  731. * in anchor (&lt;a&gt;) tags.
  732. *
  733. * @param {String} textOrHtml The HTML or text to autolink matches within
  734. * (depending on if the {@link #urls}, {@link #email}, {@link #phone}, {@link #hashtag}, and {@link #mention} options are enabled).
  735. * @return {String} The HTML, with matches automatically linked.
  736. */
  737. Autolinker.prototype.link = function (textOrHtml) {
  738. if (!textOrHtml) {
  739. return '';
  740. } // handle `null` and `undefined` (for JavaScript users that don't have TypeScript support), and nothing to do with an empty string too
  741. /* We would want to sanitize the start and end characters of a tag
  742. * before processing the string in order to avoid an XSS scenario.
  743. * This behaviour can be changed by toggling the sanitizeHtml option.
  744. */
  745. if (this.sanitizeHtml) {
  746. textOrHtml = textOrHtml.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  747. }
  748. var matches = this.parse(textOrHtml);
  749. var newHtml = new Array(matches.length * 2 + 1);
  750. var lastIndex = 0;
  751. for (var i = 0, len = matches.length; i < len; i++) {
  752. var match = matches[i];
  753. newHtml.push(textOrHtml.substring(lastIndex, match.getOffset()));
  754. newHtml.push(this.createMatchReturnVal(match));
  755. lastIndex = match.getOffset() + match.getMatchedText().length;
  756. }
  757. newHtml.push(textOrHtml.substring(lastIndex)); // handle the text after the last match
  758. return newHtml.join('');
  759. };
  760. /**
  761. * Creates the return string value for a given match in the input string.
  762. *
  763. * This method handles the {@link #replaceFn}, if one was provided.
  764. *
  765. * @private
  766. * @param {Autolinker.match.Match} match The Match object that represents
  767. * the match.
  768. * @return {String} The string that the `match` should be replaced with.
  769. * This is usually the anchor tag string, but may be the `matchStr` itself
  770. * if the match is not to be replaced.
  771. */
  772. Autolinker.prototype.createMatchReturnVal = function (match) {
  773. // Handle a custom `replaceFn` being provided
  774. var replaceFnResult;
  775. if (this.replaceFn) {
  776. replaceFnResult = this.replaceFn.call(this.context, match); // Autolinker instance is the context
  777. }
  778. if (typeof replaceFnResult === 'string') {
  779. return replaceFnResult; // `replaceFn` returned a string, use that
  780. }
  781. else if (replaceFnResult === false) {
  782. return match.getMatchedText(); // no replacement for the match
  783. }
  784. else if (replaceFnResult instanceof html_tag_1.HtmlTag) {
  785. return replaceFnResult.toAnchorString();
  786. }
  787. else {
  788. // replaceFnResult === true, or no/unknown return value from function
  789. // Perform Autolinker's default anchor tag generation
  790. var anchorTag = match.buildTag(); // returns an Autolinker.HtmlTag instance
  791. return anchorTag.toAnchorString();
  792. }
  793. };
  794. /**
  795. * Returns the {@link #tagBuilder} instance for this Autolinker instance,
  796. * lazily instantiating it if it does not yet exist.
  797. *
  798. * @private
  799. * @return {Autolinker.AnchorTagBuilder}
  800. */
  801. Autolinker.prototype.getTagBuilder = function () {
  802. var tagBuilder = this.tagBuilder;
  803. if (!tagBuilder) {
  804. tagBuilder = this.tagBuilder = new anchor_tag_builder_1.AnchorTagBuilder({
  805. newWindow: this.newWindow,
  806. truncate: this.truncate,
  807. className: this.className,
  808. });
  809. }
  810. return tagBuilder;
  811. };
  812. // NOTE: must be 'export default' here for UMD module
  813. /**
  814. * @static
  815. * @property {String} version
  816. *
  817. * The Autolinker version number in the form major.minor.patch
  818. *
  819. * Ex: 3.15.0
  820. */
  821. Autolinker.version = version_1.version;
  822. return Autolinker;
  823. }());
  824. exports.default = Autolinker;
  825. /**
  826. * Normalizes the {@link #urls} config into an Object with its 2 properties:
  827. * `schemeMatches` and `tldMatches`, both booleans.
  828. *
  829. * See {@link #urls} config for details.
  830. *
  831. * @private
  832. * @param {Boolean/Object} urls
  833. * @return {Object}
  834. */
  835. function normalizeUrlsCfg(urls) {
  836. if (urls == null)
  837. urls = true; // default to `true`
  838. if ((0, utils_1.isBoolean)(urls)) {
  839. return { schemeMatches: urls, tldMatches: urls, ipV4Matches: urls };
  840. }
  841. else {
  842. // object form
  843. return {
  844. schemeMatches: (0, utils_1.isBoolean)(urls.schemeMatches) ? urls.schemeMatches : true,
  845. tldMatches: (0, utils_1.isBoolean)(urls.tldMatches) ? urls.tldMatches : true,
  846. ipV4Matches: (0, utils_1.isBoolean)(urls.ipV4Matches) ? urls.ipV4Matches : true,
  847. };
  848. }
  849. }
  850. /**
  851. * Normalizes the {@link #stripPrefix} config into an Object with 2
  852. * properties: `scheme`, and `www` - both Booleans.
  853. *
  854. * See {@link #stripPrefix} config for details.
  855. *
  856. * @private
  857. * @param {Boolean/Object} stripPrefix
  858. * @return {Object}
  859. */
  860. function normalizeStripPrefixCfg(stripPrefix) {
  861. if (stripPrefix == null)
  862. stripPrefix = true; // default to `true`
  863. if ((0, utils_1.isBoolean)(stripPrefix)) {
  864. return { scheme: stripPrefix, www: stripPrefix };
  865. }
  866. else {
  867. // object form
  868. return {
  869. scheme: (0, utils_1.isBoolean)(stripPrefix.scheme) ? stripPrefix.scheme : true,
  870. www: (0, utils_1.isBoolean)(stripPrefix.www) ? stripPrefix.www : true,
  871. };
  872. }
  873. }
  874. /**
  875. * Normalizes the {@link #truncate} config into an Object with 2 properties:
  876. * `length` (Number), and `location` (String).
  877. *
  878. * See {@link #truncate} config for details.
  879. *
  880. * @private
  881. * @param {Number/Object} truncate
  882. * @return {Object}
  883. */
  884. function normalizeTruncateCfg(truncate) {
  885. if (typeof truncate === 'number') {
  886. return { length: truncate, location: 'end' };
  887. }
  888. else {
  889. // object, or undefined/null
  890. return tslib_1.__assign({ length: Number.POSITIVE_INFINITY, location: 'end' }, truncate);
  891. }
  892. }
  893. /**
  894. * Helper function for Array.prototype.sort() to sort the Matches by
  895. * their offset in the input string.
  896. */
  897. function byMatchOffset(a, b) {
  898. return a.getOffset() - b.getOffset();
  899. }
  900. //# sourceMappingURL=autolinker.js.map