UNPKG

@mapbox/jsxtreme-markdown

Version:
216 lines (178 loc) 4.69 kB
'use strict'; var ccount = require('ccount'); var decode = require('parse-entities'); var decimal = require('is-decimal'); var alphabetical = require('is-alphabetical'); var whitespace = require('is-whitespace-character'); var locate = require('../locate/url'); module.exports = url; url.locator = locate; url.notInLink = true; var exclamationMark = 33; // '!' var ampersand = 38; // '&' var rightParenthesis = 41; // ')' var asterisk = 42; // '*' var comma = 44; // ',' var dash = 45; // '-' var dot = 46; // '.' var colon = 58; // ':' var semicolon = 59; // ';' var questionMark = 63; // '?' var lessThan = 60; // '<' var underscore = 95; // '_' var tilde = 126; // '~' var leftParenthesisCharacter = '('; var rightParenthesisCharacter = ')'; function url(eat, value, silent) { var self = this; var gfm = self.options.gfm; var tokenizers = self.inlineTokenizers; var length = value.length; var previousDot = -1; var protocolless = false; var dots; var lastTwoPartsStart; var start; var index; var pathStart; var path; var code; var end; var leftCount; var rightCount; var content; var children; var url; var exit; if (!gfm) { return; } // `WWW.` doesn’t work. if (value.slice(0, 4) === 'www.') { protocolless = true; index = 4; } else if (value.slice(0, 7).toLowerCase() === 'http://') { index = 7; } else if (value.slice(0, 8).toLowerCase() === 'https://') { index = 8; } else { return; } // Act as if the starting boundary is a dot. previousDot = index - 1; // Parse a valid domain. start = index; dots = []; while (index < length) { code = value.charCodeAt(index); if (code === dot) { // Dots may not appear after each other. if (previousDot === index - 1) { break; } dots.push(index); previousDot = index; index++; continue; } if ( decimal(code) || alphabetical(code) || code === dash || code === underscore ) { index++; continue; } break; } // Ignore a final dot: if (code === dot) { dots.pop(); index--; } // If there are not dots, exit. if (dots[0] === undefined) { return; } // If there is an underscore in the last two domain parts, exit: // `www.example.c_m` and `www.ex_ample.com` are not OK, but // `www.sub_domain.example.com` is. lastTwoPartsStart = dots.length < 2 ? start : dots[dots.length - 2] + 1; if (value.slice(lastTwoPartsStart, index).indexOf('_') !== -1) { return; } /* istanbul ignore if - never used (yet) */ if (silent) { return true; } end = index; pathStart = index; // Parse a path. while (index < length) { code = value.charCodeAt(index); if (whitespace(code) || code === lessThan) { break; } index++; if ( code === exclamationMark || code === asterisk || code === comma || code === dot || code === colon || code === questionMark || code === underscore || code === tilde ) { // Empty } else { end = index; } } index = end; // If the path ends in a closing paren, and the count of closing parens is // higher than the opening count, then remove the supefluous closing parens. if (value.charCodeAt(index - 1) === rightParenthesis) { path = value.slice(pathStart, index); leftCount = ccount(path, leftParenthesisCharacter); rightCount = ccount(path, rightParenthesisCharacter); while (rightCount > leftCount) { index = pathStart + path.lastIndexOf(rightParenthesisCharacter); path = value.slice(pathStart, index); rightCount--; } } if (value.charCodeAt(index - 1) === semicolon) { // GitHub doesn’t document this, but final semicolons aren’t paret of the // URL either. index--; // // If the path ends in what looks like an entity, it’s not part of the path. if (alphabetical(value.charCodeAt(index - 1))) { end = index - 2; while (alphabetical(value.charCodeAt(end))) { end--; } if (value.charCodeAt(end) === ampersand) { index = end; } } } content = value.slice(0, index); url = decode(content, { nonTerminated: false }); if (protocolless) { url = 'http://' + url; } exit = self.enterLink(); // Temporarily remove all tokenizers except text in url. self.inlineTokenizers = { text: tokenizers.text }; children = self.tokenizeInline(content, eat.now()); self.inlineTokenizers = tokenizers; exit(); return eat(content)({ type: 'link', title: null, url: url, children: children, }); }