UNPKG

tent-markdown

Version:
213 lines (181 loc) 9.52 kB
/* Adapted from https://github.com/twitter/twitter-text-js Copyright 2011 Twitter, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License below, or at: http://www.apache.org/licenses/LICENSE-2.0 */ (function(expose) { twttr = { txt: { regexen: {} } } // Builds a RegExp function regexSupplant(regex, flags) { flags = flags || ""; if (typeof regex !== "string") { if (regex.global && flags.indexOf("g") < 0) { flags += "g"; } if (regex.ignoreCase && flags.indexOf("i") < 0) { flags += "i"; } if (regex.multiline && flags.indexOf("m") < 0) { flags += "m"; } regex = regex.source; } return new RegExp(regex.replace(/#\{(\w+)\}/g, function(match, name) { var newRegex = twttr.txt.regexen[name] || ""; if (typeof newRegex !== "string") { newRegex = newRegex.source; } return newRegex; }), flags); } twttr.txt.regexSupplant = regexSupplant; // simple string interpolation function stringSupplant(str, values) { return str.replace(/#\{(\w+)\}/g, function(match, name) { return values[name] || ""; }); } var fromCode = String.fromCharCode; var INVALID_CHARS = [ fromCode(0xFFFE), fromCode(0xFEFF), // BOM fromCode(0xFFFF) // Special ]; twttr.txt.regexen.invalid_chars_group = regexSupplant(INVALID_CHARS.join("")); twttr.txt.stringSupplant = stringSupplant; twttr.txt.stringSupplant = stringSupplant var UNICODE_SPACES = [ fromCode(0x0020), // White_Space # Zs SPACE fromCode(0x0085), // White_Space # Cc <control-0085> fromCode(0x00A0), // White_Space # Zs NO-BREAK SPACE fromCode(0x1680), // White_Space # Zs OGHAM SPACE MARK fromCode(0x180E), // White_Space # Zs MONGOLIAN VOWEL SEPARATOR fromCode(0x2028), // White_Space # Zl LINE SEPARATOR fromCode(0x2029), // White_Space # Zp PARAGRAPH SEPARATOR fromCode(0x202F), // White_Space # Zs NARROW NO-BREAK SPACE fromCode(0x205F), // White_Space # Zs MEDIUM MATHEMATICAL SPACE fromCode(0x3000) // White_Space # Zs IDEOGRAPHIC SPACE ]; twttr.txt.regexen.spaces_group = regexSupplant(UNICODE_SPACES.join("")); twttr.txt.regexen.spaces = regexSupplant("[" + UNICODE_SPACES.join("") + "]"); twttr.txt.regexen.invalid_chars_group = regexSupplant(INVALID_CHARS.join("")); twttr.txt.regexen.punct = /\!'#%&'\(\)*\+,\\\-\.\/:;<=>\?@\[\]\^_{|}~\$/; // URL related regex collection twttr.txt.regexen.validUrlPrecedingChars = regexSupplant(/(?:[^A-Za-z0-9@@$###{invalid_chars_group}]|^)/); twttr.txt.regexen.invalidUrlWithoutProtocolPrecedingChars = /[-_.\/]$/; twttr.txt.regexen.invalidDomainChars = stringSupplant("#{punct}#{spaces_group}#{invalid_chars_group}", twttr.txt.regexen); twttr.txt.regexen.validDomainChars = regexSupplant(/[^#{invalidDomainChars}]/); twttr.txt.regexen.validSubdomain = regexSupplant(/(?:(?:#{validDomainChars}(?:[_-]|#{validDomainChars})*)?#{validDomainChars}\.)/); twttr.txt.regexen.validDomainName = regexSupplant(/(?:(?:#{validDomainChars}(?:-|#{validDomainChars})*)?#{validDomainChars}\.)/); twttr.txt.regexen.validGTLD = regexSupplant(/(?:(?:aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx|local)(?=[^0-9a-zA-Z]|$))/); twttr.txt.regexen.validCCTLD = regexSupplant(/(?:(?:ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)(?=[^0-9a-zA-Z]|$))/); twttr.txt.regexen.validPunycode = regexSupplant(/(?:xn--[0-9a-z]+)/); twttr.txt.regexen.validDomain = regexSupplant(/(?:#{validSubdomain}*#{validDomainName}(?:#{validGTLD}|#{validCCTLD}|#{validPunycode}))/); twttr.txt.regexen.validAsciiDomain = regexSupplant(/(?:(?:[-a-z0-9#{latinAccentChars}]+)\.)+(?:#{validGTLD}|#{validCCTLD}|#{validPunycode})/gi); twttr.txt.regexen.invalidShortDomain = regexSupplant(/^#{validDomainName}#{validCCTLD}$/); twttr.txt.regexen.validPortNumber = regexSupplant(/[0-9]+/); twttr.txt.regexen.validGeneralUrlPathChars = regexSupplant(/[a-z0-9!\*';:=\+,\.\$\/%#\[\]\-_~|&#{latinAccentChars}]/i); // Allow URL paths to contain balanced parens // 1. Used in Wikipedia URLs like /Primer_(film) // 2. Used in IIS sessions like /S(dfd346)/ twttr.txt.regexen.validUrlBalancedParens = regexSupplant(/\(#{validGeneralUrlPathChars}+\)/i); // Valid end-of-path chracters (so /foo. does not gobble the period). // 1. Allow =&# for empty URL parameters and other URL-join artifacts twttr.txt.regexen.validUrlPathEndingChars = regexSupplant(/[\+\-a-z0-9=_#\/#{latinAccentChars}]|(?:#{validUrlBalancedParens})/i); // Allow @ in a url, but only in the middle. Catch things like http://example.com/@user/ twttr.txt.regexen.validUrlPath = regexSupplant('(?:' + '(?:' + '#{validGeneralUrlPathChars}*' + '(?:#{validUrlBalancedParens}#{validGeneralUrlPathChars}*)*' + '#{validUrlPathEndingChars}'+ ')|(?:@#{validGeneralUrlPathChars}+\/)'+ ')', 'i'); twttr.txt.regexen.validUrlQueryChars = /[a-z0-9!?\*'\(\);:&=\+\$\/%#\[\]\-_\.,~|]/i; twttr.txt.regexen.validUrlQueryEndingChars = /[a-z0-9_&=#\/]/i; twttr.txt.regexen.extractUrl = regexSupplant( '(' + // $1 total match '(#{validUrlPrecedingChars})' + // $2 Preceeding chracter '(' + // $3 URL '(https?:\\/\\/)?' + // $4 Protocol (optional) '(#{validDomain})' + // $5 Domain(s) '(?::(#{validPortNumber}))?' + // $6 Port number (optional) '(\\/#{validUrlPath}*)?' + // $7 URL Path '(\\?#{validUrlQueryChars}*#{validUrlQueryEndingChars})?' + // $8 Query String ')' + ')' , 'gi'); twttr.txt.regexen.validTcoUrl = /^https?:\/\/t\.co\/[a-z0-9]+/i; twttr.extractUrlsWithIndices = function(text, options) { if (!options) { options = {extractUrlsWithoutProtocol: true}; } if (!text || (options.extractUrlsWithoutProtocol ? !text.match(/\./) : !text.match(/:/))) { return []; } var urls = []; while (twttr.txt.regexen.extractUrl.exec(text)) { var before = RegExp.$2, url = RegExp.$3, protocol = RegExp.$4, domain = RegExp.$5, path = RegExp.$7; var endPosition = twttr.txt.regexen.extractUrl.lastIndex, startPosition = endPosition - url.length; // if protocol is missing and domain contains non-ASCII characters, // extract ASCII-only domains. if (!protocol) { if (!options.extractUrlsWithoutProtocol || before.match(twttr.txt.regexen.invalidUrlWithoutProtocolPrecedingChars)) { continue; } var lastUrl = null, lastUrlInvalidMatch = false, asciiEndPosition = 0; domain.replace(twttr.txt.regexen.validAsciiDomain, function(asciiDomain) { var asciiStartPosition = domain.indexOf(asciiDomain, asciiEndPosition); asciiEndPosition = asciiStartPosition + asciiDomain.length; lastUrl = { url: asciiDomain, indices: [startPosition + asciiStartPosition, startPosition + asciiEndPosition] }; if (!before.match(/^[\^]$/)) { lastUrlInvalidMatch = asciiDomain.match(twttr.txt.regexen.invalidShortDomain); } if (!lastUrlInvalidMatch) { urls.push(lastUrl); } }); // no ASCII-only domain found. Skip the entire URL. if (lastUrl == null) { continue; } // lastUrl only contains domain. Need to add path and query if they exist. if (path) { if (lastUrlInvalidMatch) { urls.push(lastUrl); } lastUrl.url = url.replace(domain, lastUrl.url); lastUrl.indices[1] = endPosition; } } else { // In the case of t.co URLs, don't allow additional path characters. if (url.match(twttr.txt.regexen.validTcoUrl)) { url = RegExp.lastMatch; endPosition = startPosition + url.length; } urls.push({ url: url, indices: [startPosition, endPosition] }); } } return urls; }; expose.extractUrlsWithIndices = twttr.extractUrlsWithIndices; })((function() { if (typeof exports === "undefined") { window.twttr = {}; return window.twttr; } else { return exports; } })());