UNPKG

interweave-autolink

Version:

URL, IP, email, and hashtag autolinking support for Interweave.

654 lines (594 loc) 16 kB
// Bundled with Packemon: https://packemon.dev // Platform: browser, Support: stable, Format: lib 'use strict'; Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } }); const React = require('react'); const interweave = require('interweave'); const _interopDefault = e => e && e.__esModule ? e : { default: e }; const React__default = /*#__PURE__*/_interopDefault(React); /* eslint-disable unicorn/better-regex, unicorn/no-unsafe-regex */ function combinePatterns(patterns, options = {}) { var _options$join, _options$flags; let regex = patterns.map(pattern => pattern.source).join((_options$join = options.join) !== null && _options$join !== void 0 ? _options$join : ''); if (options.capture) { regex = `(${regex})`; } else if (options.nonCapture) { regex = `(?:${regex})`; } if (options.match) { regex += options.match; } return new RegExp(regex, (_options$flags = options.flags) !== null && _options$flags !== void 0 ? _options$flags : ''); } // https://www.ietf.org/rfc/rfc3986.txt // https://blog.codinghorror.com/the-problem-with-urls/ // http://www.regular-expressions.info/email.html const VALID_ALNUM_CHARS = /[a-z0-9]/; const VALID_PATH_CHARS = /(?:[a-zA-Z\u0400-\u04FF0-9\-_~!$&'()[\]\\/*+,;=.%]*)/; const URL_SCHEME = /(https?:\/\/)?/; const URL_AUTH = combinePatterns([/[a-z\u0400-\u04FF0-9\-_~!$&'()*+,;=.:]+/, // Includes colon /@/], { capture: true, match: '?' }); const URL_HOST = combinePatterns([/(?:(?:[a-z0-9](?:[-a-z0-9_]*[a-z0-9])?)\.)*/, // Subdomain /(?:(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)\.)/, // Domain /(?:[a-z](?:[-a-z0-9]*[a-z0-9])?)/ // TLD ], { capture: true }); const URL_PORT = /(?::(\d{1,5}))?/; const URL_PATH = combinePatterns([/\//, combinePatterns([/[-+a-z0-9!*';:=,.$/%[\]_~@|&]*/, /[-+a-z0-9/]/ // Valid ending chars ], { match: '*', nonCapture: true })], { capture: true, match: '?' }); const URL_QUERY = combinePatterns([/\?/, combinePatterns([VALID_PATH_CHARS, /[a-z0-9_&=]/ // Valid ending chars ], { match: '?', nonCapture: true })], { capture: true, match: '?' }); const URL_FRAGMENT = combinePatterns([/#/, combinePatterns([VALID_PATH_CHARS, /[a-z0-9]/ // Valid ending chars ], { match: '?', nonCapture: true })], { capture: true, match: '?' }); const URL_PATTERN = combinePatterns([URL_SCHEME, URL_AUTH, URL_HOST, URL_PORT, URL_PATH, URL_QUERY, URL_FRAGMENT], { flags: 'i' }); const IP_V4_PART = /(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/; const IP_V4 = combinePatterns([IP_V4_PART, IP_V4_PART, IP_V4_PART, IP_V4_PART], { capture: true, join: '\\.' }); const IP_PATTERN = combinePatterns([URL_SCHEME, URL_AUTH, IP_V4, URL_PORT, URL_PATH, URL_QUERY, URL_FRAGMENT], { flags: 'i' }); const NON_LATIN = [// Chinese /[\d_\u4E00-\u9FFF-]+/, // Japanese /[\d_\u3000-\u30FF-]+/, // Korean /[\d_\u1100-\u11FF\u3130-\u318F\uA960-\uA97F\uAC00-\uD7FF-]+/, // Thai /[\d_\u0E00-\u0E7F-]+/, // Russian, Ukrainian // eslint-disable-next-line no-misleading-character-class /[\d_a-z\u0400-\u052F\u1C80-\u1C8F\u2DE0-\u2DFF\uA640-\uA69F-]+/, // Latin based /[\d_a-z\u0080-\u00FF\u0100-\u017F\u0180-\u024F-]+/]; const HASHTAG_PATTERN = combinePatterns([/#/, combinePatterns(NON_LATIN, { capture: true, join: '|' })], { flags: 'i' }); const MENTION_PATTERN = /@([\dA-z-_]+)/; const EMAIL_USERNAME_PART = /[.a-z0-9!#$%&?*+=_{|}~-]*/; const EMAIL_USERNAME = combinePatterns([VALID_ALNUM_CHARS, EMAIL_USERNAME_PART, VALID_ALNUM_CHARS], { capture: true }); const EMAIL_PATTERN = combinePatterns([EMAIL_USERNAME, URL_HOST], { flags: 'i', join: '@' }); const EMAIL_DISTINCT_PATTERN = new RegExp(`^${EMAIL_PATTERN.source}$`, EMAIL_PATTERN.flags); // Properly and efficiently detecting URLs + all TLDs is nigh impossible, // instead we will only support the most common top-level TLDs. // https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains const TOP_LEVEL_TLDS = [// Original 'com', 'org', 'net', 'int', 'edu', 'gov', 'mil', // Sponsored 'aero', 'asia', 'biz', 'cat', 'coop', 'jobs', 'mobi', 'museum', 'post', 'tel', 'travel', 'xxx', // Misc 'app', 'arpa', 'test', // Countries 'ac', // Ascension Island 'ad', // Andorra 'ae', // United Arab Emirates 'af', // Afghanistan 'ag', // Antigua and Barbuda 'ai', // Anguilla 'al', // Albania 'am', // Armenia 'an', // Netherlands Antilles 'ao', // Angola 'aq', // Antarctica 'ar', // Argentina 'as', // American Samoa 'at', // Austria 'au', // Australia 'aw', // Aruba 'ax', // Aland Islands 'az', // Azerbaijan 'ba', // Bosnia and Herzegovina 'bb', // Barbados 'bd', // Bangladesh 'be', // Belgium 'bf', // Burkina Faso 'bg', // Bulgaria 'bh', // Bahrain 'bi', // Burundi 'bj', // Benin 'bl', // Saint Barthelemy 'bm', // Bermuda 'bn', // Brunei Darussalam 'bo', // Bolivia 'bq', // Bonaire, Sint Eustatius and Saba 'br', // Brazil 'bs', // Bahamas 'bt', // Bhutan 'bv', // Bouvet Island 'bw', // Botswana 'by', // Belarus 'bz', // Belize 'ca', // Canada 'cc', // Cocos (Keeling) Islands 'cd', // Congo, The Democratic Republic of the 'cf', // Central African Republic 'cg', // Congo 'ch', // Switzerland 'ci', // Cote d'Ivoire 'ck', // Cook Islands 'cl', // Chile 'cm', // Cameroon 'cn', // China 'co', // Colombia 'cr', // Costa Rica 'cu', // Cuba 'cv', // Cape Verde 'cw', // Curaçao 'cx', // Christmas Island 'cy', // Cyprus 'cz', // Czech Republic 'de', // Germany 'dj', // Djibouti 'dk', // Denmark 'dm', // Dominica 'do', // Dominican Republic 'dz', // Algeria 'ec', // Ecuador 'ee', // Estonia 'eg', // Egypt 'eh', // Western Sahara 'er', // Eritrea 'es', // Spain 'et', // Ethiopia 'eu', // European Union 'fi', // Finland 'fj', // Fiji 'fk', // Falkland Islands (Malvinas) 'fm', // Micronesia, Federated States of 'fo', // Faroe Islands 'fr', // France 'ga', // Gabon 'gb', // United Kingdom 'gd', // Grenada 'ge', // Georgia 'gf', // French Guiana 'gg', // Guernsey 'gh', // Ghana 'gi', // Gibraltar 'gl', // Greenland 'gm', // Gambia 'gn', // Guinea 'gp', // Guadeloupe 'gq', // Equatorial Guinea 'gr', // Greece 'gs', // South Georgia and the South Sandwich Islands 'gt', // Guatemala 'gu', // Guam 'gw', // Guinea-Bissau 'gy', // Guyana 'hk', // Hong Kong 'hm', // Heard Island and McDonald Islands 'hn', // Honduras 'hr', // Croatia 'ht', // Haiti 'hu', // Hungary 'id', // Indonesia 'ie', // Ireland 'il', // Israel 'im', // Isle of Man 'in', // India 'io', // British Indian Ocean Territory 'iq', // Iraq 'ir', // Iran, Islamic Republic of 'is', // Iceland 'it', // Italy 'je', // Jersey 'jm', // Jamaica 'jo', // Jordan 'jp', // Japan 'ke', // Kenya 'kg', // Kyrgyzstan 'kh', // Cambodia 'ki', // Kiribati 'km', // Comoros 'kn', // Saint Kitts and Nevis 'kp', // Korea, Democratic People's Republic of 'kr', // Korea, Republic of 'kw', // Kuwait 'ky', // Cayman Islands 'kz', // Kazakhstan 'la', // Lao People's Democratic Republic 'lb', // Lebanon 'lc', // Saint Lucia 'li', // Liechtenstein 'lk', // Sri Lanka 'lr', // Liberia 'ls', // Lesotho 'lt', // Lithuania 'lu', // Luxembourg 'lv', // Latvia 'ly', // Libyan Arab Jamahiriya 'ma', // Morocco 'mc', // Monaco 'md', // Moldova, Republic of 'me', // Montenegro 'mf', // Saint Martin (French part) 'mg', // Madagascar 'mh', // Marshall Islands 'mk', // Macedonia, The Former Yugoslav Republic of 'ml', // Mali 'mm', // Myanmar 'mn', // Mongolia 'mo', // Macao 'mp', // Northern Mariana Islands 'mq', // Martinique 'mr', // Mauritania 'ms', // Montserrat 'mt', // Malta 'mu', // Mauritius 'mv', // Maldives 'mw', // Malawi 'mx', // Mexico 'my', // Malaysia 'mz', // Mozambique 'na', // Namibia 'nc', // New Caledonia 'ne', // Niger 'nf', // Norfolk Island 'ng', // Nigeria 'ni', // Nicaragua 'nl', // Netherlands 'no', // Norway 'np', // Nepal 'nr', // Nauru 'nu', // Niue 'nz', // New Zealand 'om', // Oman 'pa', // Panama 'pe', // Peru 'pf', // French Polynesia 'pg', // Papua New Guinea 'ph', // Philippines 'pk', // Pakistan 'pl', // Poland 'pm', // Saint Pierre and Miquelon 'pn', // Pitcairn 'pr', // Puerto Rico 'ps', // Palestinian Territory, Occupied 'pt', // Portugal 'pw', // Palau 'py', // Paraguay 'qa', // Qatar 're', // Reunion 'ro', // Romania 'rs', // Serbia 'ru', // Russian Federation 'rw', // Rwanda 'sa', // Saudi Arabia 'sb', // Solomon Islands 'sc', // Seychelles 'sd', // Sudan 'se', // Sweden 'sg', // Singapore 'sh', // Saint Helena 'si', // Slovenia 'sj', // Svalbard and Jan Mayen 'sk', // Slovakia 'sl', // Sierra Leone 'sm', // San Marino 'sn', // Senegal 'so', // Somalia 'sr', // Suriname 'st', // Sao Tome and Principe 'su', // Soviet Union (being phased out) 'sv', // El Salvador 'sx', // Sint Maarten (Dutch part) 'sy', // Syrian Arab Republic 'sz', // Swaziland 'tc', // Turks and Caicos Islands 'td', // Chad 'tf', // French Southern Territories 'tg', // Togo 'th', // Thailand 'tj', // Tajikistan 'tk', // Tokelau 'tl', // Timor-Leste 'tm', // Turkmenistan 'tn', // Tunisia 'to', // Tonga 'tp', // Portuguese Timor (being phased out) 'tr', // Turkey 'tt', // Trinidad and Tobago 'tv', // Tuvalu 'tw', // Taiwan, Province of China 'tz', // Tanzania, United Republic of 'ua', // Ukraine 'ug', // Uganda 'uk', // United Kingdom 'um', // United States Minor Outlying Islands 'us', // United States 'uy', // Uruguay 'uz', // Uzbekistan 'va', // Holy See (Vatican City State) 'vc', // Saint Vincent and the Grenadines 've', // Venezuela, Bolivarian Republic of 'vg', // Virgin Islands, British 'vi', // Virgin Islands, U.S. 'vn', // Viet Nam 'vu', // Vanuatu 'wf', // Wallis and Futuna 'ws', // Samoa 'ye', // Yemen 'yt', // Mayotte 'za', // South Africa 'zm', // Zambia 'zw' // Zimbabwe ]; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function Link({ children, href, onClick, newWindow }) { return /*#__PURE__*/React__default.default.createElement("a", { href: href, rel: "noopener noreferrer", target: newWindow ? '_blank' : undefined, onClick: onClick }, children); } function Email({ children, email, emailParts, ...props }) { return /*#__PURE__*/React__default.default.createElement(Link, _extends({}, props, { href: `mailto:${email}` }), children); } class EmailMatcher extends interweave.Matcher { replaceWith(children, props) { return /*#__PURE__*/React__default.default.createElement(Email, props, children); } asTag() { return 'a'; } match(string) { return this.doMatch(string, EMAIL_PATTERN, matches => ({ email: matches[0], emailParts: { host: matches[2], username: matches[1] } })); } } function Hashtag({ children, encodeHashtag = false, hashtag, hashtagUrl = '{{hashtag}}', preserveHash = false, ...props }) { let tag = hashtag; // Prepare the hashtag if (!preserveHash && tag.startsWith('#')) { tag = tag.slice(1); } if (encodeHashtag) { tag = encodeURIComponent(tag); } // Determine the URL let url = hashtagUrl || '{{hashtag}}'; url = typeof url === 'function' ? url(tag) : url.replace('{{hashtag}}', tag); return /*#__PURE__*/React__default.default.createElement(Link, _extends({}, props, { href: url }), children); } class HashtagMatcher extends interweave.Matcher { replaceWith(children, props) { return /*#__PURE__*/React__default.default.createElement(Hashtag, props, children); } asTag() { return 'a'; } match(string) { return this.doMatch(string, HASHTAG_PATTERN, matches => ({ hashtag: matches[0] })); } } function Url({ children, url, urlParts, ...props }) { let href = url; if (!href.match(/^https?:\/\//)) { href = `http://${href}`; } return /*#__PURE__*/React__default.default.createElement(Link, _extends({}, props, { href: href }), children); } class UrlMatcher extends interweave.Matcher { constructor(name, options, factory) { super(name, { customTLDs: [], validateTLD: true, ...options }, factory); } replaceWith(children, props) { return /*#__PURE__*/React__default.default.createElement(Url, props, children); } asTag() { return 'a'; } match(string) { const response = this.doMatch(string, URL_PATTERN, this.handleMatches); // False positives with URL auth scheme if (response !== null && response !== void 0 && response.match.match(EMAIL_DISTINCT_PATTERN)) { response.valid = false; } if (response !== null && response !== void 0 && response.valid && this.options.validateTLD) { var _this$options$customT; const { host } = response.urlParts; const validList = [...TOP_LEVEL_TLDS, ...((_this$options$customT = this.options.customTLDs) !== null && _this$options$customT !== void 0 ? _this$options$customT : [])]; const tld = host.slice(host.lastIndexOf('.') + 1).toLowerCase(); if (!validList.includes(tld)) { return null; } } return response; } /** * Package the matched response. */ handleMatches(matches) { return { url: matches[0], urlParts: { auth: matches[2] ? matches[2].slice(0, -1) : '', fragment: matches[7] || '', host: matches[3], path: matches[5] || '', port: matches[4] ? matches[4] : '', query: matches[6] || '', scheme: matches[1] ? matches[1].replace('://', '') : 'http' } }; } } class IpMatcher extends UrlMatcher { constructor(name, options, factory) { super(name, { ...options, // IPs dont have TLDs validateTLD: false }, factory); } match(string) { return this.doMatch(string, IP_PATTERN, this.handleMatches); } } function Mention({ children, mention, mentionUrl, ...props }) { if (!mentionUrl) { // eslint-disable-next-line react/jsx-no-useless-fragment return /*#__PURE__*/React__default.default.createElement(React__default.default.Fragment, null, children); } const url = typeof mentionUrl === 'function' ? mentionUrl(mention) : mentionUrl.replace('{{mention}}', mention); return /*#__PURE__*/React__default.default.createElement(Link, _extends({}, props, { href: url }), children); } class MentionMatcher extends interweave.Matcher { replaceWith(children, props) { return /*#__PURE__*/React__default.default.createElement(Mention, props, children); } asTag() { return 'a'; } match(string) { return this.doMatch(string, MENTION_PATTERN, this.handleMatches); } handleMatches(matches) { return { mention: matches[1] }; } } exports.EMAIL_DISTINCT_PATTERN = EMAIL_DISTINCT_PATTERN; exports.EMAIL_PATTERN = EMAIL_PATTERN; exports.EMAIL_USERNAME = EMAIL_USERNAME; exports.EMAIL_USERNAME_PART = EMAIL_USERNAME_PART; exports.Email = Email; exports.EmailMatcher = EmailMatcher; exports.HASHTAG_PATTERN = HASHTAG_PATTERN; exports.Hashtag = Hashtag; exports.HashtagMatcher = HashtagMatcher; exports.IP_PATTERN = IP_PATTERN; exports.IP_V4 = IP_V4; exports.IP_V4_PART = IP_V4_PART; exports.IpMatcher = IpMatcher; exports.Link = Link; exports.MENTION_PATTERN = MENTION_PATTERN; exports.Mention = Mention; exports.MentionMatcher = MentionMatcher; exports.TOP_LEVEL_TLDS = TOP_LEVEL_TLDS; exports.URL_AUTH = URL_AUTH; exports.URL_FRAGMENT = URL_FRAGMENT; exports.URL_HOST = URL_HOST; exports.URL_PATH = URL_PATH; exports.URL_PATTERN = URL_PATTERN; exports.URL_PORT = URL_PORT; exports.URL_QUERY = URL_QUERY; exports.URL_SCHEME = URL_SCHEME; exports.Url = Url; exports.UrlMatcher = UrlMatcher; exports.VALID_ALNUM_CHARS = VALID_ALNUM_CHARS; exports.VALID_PATH_CHARS = VALID_PATH_CHARS; exports.combinePatterns = combinePatterns; //# sourceMappingURL=index.js.map