diff options
Diffstat (limited to 'node_modules/markdown-it/lib')
51 files changed, 6185 insertions, 0 deletions
diff --git a/node_modules/markdown-it/lib/common/entities.js b/node_modules/markdown-it/lib/common/entities.js new file mode 100644 index 0000000..c2e23e9 --- /dev/null +++ b/node_modules/markdown-it/lib/common/entities.js @@ -0,0 +1,6 @@ +// HTML5 entities map: { name -> utf16string } +// +'use strict'; + +/*eslint quotes:0*/ +module.exports = require('entities/lib/maps/entities.json'); diff --git a/node_modules/markdown-it/lib/common/html_blocks.js b/node_modules/markdown-it/lib/common/html_blocks.js new file mode 100644 index 0000000..daa6b46 --- /dev/null +++ b/node_modules/markdown-it/lib/common/html_blocks.js @@ -0,0 +1,70 @@ +// List of valid html blocks names, accorting to commonmark spec +// http://jgm.github.io/CommonMark/spec.html#html-blocks + +'use strict'; + + +module.exports = [ + 'address', + 'article', + 'aside', + 'base', + 'basefont', + 'blockquote', + 'body', + 'caption', + 'center', + 'col', + 'colgroup', + 'dd', + 'details', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hr', + 'html', + 'iframe', + 'legend', + 'li', + 'link', + 'main', + 'menu', + 'menuitem', + 'nav', + 'noframes', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'section', + 'source', + 'summary', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'title', + 'tr', + 'track', + 'ul' +]; diff --git a/node_modules/markdown-it/lib/common/html_re.js b/node_modules/markdown-it/lib/common/html_re.js new file mode 100644 index 0000000..df81906 --- /dev/null +++ b/node_modules/markdown-it/lib/common/html_re.js @@ -0,0 +1,28 @@ +// Regexps to match html elements + +'use strict'; + +var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; + +var unquoted = '[^"\'=<>`\\x00-\\x20]+'; +var single_quoted = "'[^']*'"; +var double_quoted = '"[^"]*"'; + +var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'; + +var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'; + +var open_tag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>'; + +var close_tag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>'; +var comment = '<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->'; +var processing = '<[?][\\s\\S]*?[?]>'; +var declaration = '<![A-Z]+\\s+[^>]*>'; +var cdata = '<!\\[CDATA\\[[\\s\\S]*?\\]\\]>'; + +var HTML_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + '|' + comment + + '|' + processing + '|' + declaration + '|' + cdata + ')'); +var HTML_OPEN_CLOSE_TAG_RE = new RegExp('^(?:' + open_tag + '|' + close_tag + ')'); + +module.exports.HTML_TAG_RE = HTML_TAG_RE; +module.exports.HTML_OPEN_CLOSE_TAG_RE = HTML_OPEN_CLOSE_TAG_RE; diff --git a/node_modules/markdown-it/lib/common/utils.js b/node_modules/markdown-it/lib/common/utils.js new file mode 100644 index 0000000..712cd29 --- /dev/null +++ b/node_modules/markdown-it/lib/common/utils.js @@ -0,0 +1,317 @@ +// Utilities +// +'use strict'; + + +function _class(obj) { return Object.prototype.toString.call(obj); } + +function isString(obj) { return _class(obj) === '[object String]'; } + +var _hasOwnProperty = Object.prototype.hasOwnProperty; + +function has(object, key) { + return _hasOwnProperty.call(object, key); +} + +// Merge objects +// +function assign(obj /*from1, from2, from3, ...*/) { + var sources = Array.prototype.slice.call(arguments, 1); + + sources.forEach(function (source) { + if (!source) { return; } + + if (typeof source !== 'object') { + throw new TypeError(source + 'must be object'); + } + + Object.keys(source).forEach(function (key) { + obj[key] = source[key]; + }); + }); + + return obj; +} + +// Remove element from array and put another array at those position. +// Useful for some operations with tokens +function arrayReplaceAt(src, pos, newElements) { + return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isValidEntityCode(c) { + /*eslint no-bitwise:0*/ + // broken sequence + if (c >= 0xD800 && c <= 0xDFFF) { return false; } + // never used + if (c >= 0xFDD0 && c <= 0xFDEF) { return false; } + if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; } + // control codes + if (c >= 0x00 && c <= 0x08) { return false; } + if (c === 0x0B) { return false; } + if (c >= 0x0E && c <= 0x1F) { return false; } + if (c >= 0x7F && c <= 0x9F) { return false; } + // out of range + if (c > 0x10FFFF) { return false; } + return true; +} + +function fromCodePoint(c) { + /*eslint no-bitwise:0*/ + if (c > 0xffff) { + c -= 0x10000; + var surrogate1 = 0xd800 + (c >> 10), + surrogate2 = 0xdc00 + (c & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } + return String.fromCharCode(c); +} + + +var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])/g; +var ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi; +var UNESCAPE_ALL_RE = new RegExp(UNESCAPE_MD_RE.source + '|' + ENTITY_RE.source, 'gi'); + +var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i; + +var entities = require('./entities'); + +function replaceEntityPattern(match, name) { + var code = 0; + + if (has(entities, name)) { + return entities[name]; + } + + if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) { + code = name[1].toLowerCase() === 'x' ? + parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); + + if (isValidEntityCode(code)) { + return fromCodePoint(code); + } + } + + return match; +} + +/*function replaceEntities(str) { + if (str.indexOf('&') < 0) { return str; } + + return str.replace(ENTITY_RE, replaceEntityPattern); +}*/ + +function unescapeMd(str) { + if (str.indexOf('\\') < 0) { return str; } + return str.replace(UNESCAPE_MD_RE, '$1'); +} + +function unescapeAll(str) { + if (str.indexOf('\\') < 0 && str.indexOf('&') < 0) { return str; } + + return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity) { + if (escaped) { return escaped; } + return replaceEntityPattern(match, entity); + }); +} + +//////////////////////////////////////////////////////////////////////////////// + +var HTML_ESCAPE_TEST_RE = /[&<>"]/; +var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; +var HTML_REPLACEMENTS = { + '&': '&', + '<': '<', + '>': '>', + '"': '"' +}; + +function replaceUnsafeChar(ch) { + return HTML_REPLACEMENTS[ch]; +} + +function escapeHtml(str) { + if (HTML_ESCAPE_TEST_RE.test(str)) { + return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); + } + return str; +} + +//////////////////////////////////////////////////////////////////////////////// + +var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; + +function escapeRE(str) { + return str.replace(REGEXP_ESCAPE_RE, '\\$&'); +} + +//////////////////////////////////////////////////////////////////////////////// + +function isSpace(code) { + switch (code) { + case 0x09: + case 0x20: + return true; + } + return false; +} + +// Zs (unicode class) || [\t\f\v\r\n] +function isWhiteSpace(code) { + if (code >= 0x2000 && code <= 0x200A) { return true; } + switch (code) { + case 0x09: // \t + case 0x0A: // \n + case 0x0B: // \v + case 0x0C: // \f + case 0x0D: // \r + case 0x20: + case 0xA0: + case 0x1680: + case 0x202F: + case 0x205F: + case 0x3000: + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +/*eslint-disable max-len*/ +var UNICODE_PUNCT_RE = require('uc.micro/categories/P/regex'); + +// Currently without astral characters support. +function isPunctChar(ch) { + return UNICODE_PUNCT_RE.test(ch); +} + + +// Markdown ASCII punctuation characters. +// +// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ +// http://spec.commonmark.org/0.15/#ascii-punctuation-character +// +// Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. +// +function isMdAsciiPunct(ch) { + switch (ch) { + case 0x21/* ! */: + case 0x22/* " */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x27/* ' */: + case 0x28/* ( */: + case 0x29/* ) */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2C/* , */: + case 0x2D/* - */: + case 0x2E/* . */: + case 0x2F/* / */: + case 0x3A/* : */: + case 0x3B/* ; */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x3F/* ? */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7C/* | */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } +} + +// Hepler to unify [reference labels]. +// +function normalizeReference(str) { + // Trim and collapse whitespace + // + str = str.trim().replace(/\s+/g, ' '); + + // In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug + // fixed in v12 (couldn't find any details). + // + // So treat this one as a special case + // (remove this when node v10 is no longer supported). + // + if ('ẞ'.toLowerCase() === 'Ṿ') { + str = str.replace(/ẞ/g, 'ß'); + } + + // .toLowerCase().toUpperCase() should get rid of all differences + // between letter variants. + // + // Simple .toLowerCase() doesn't normalize 125 code points correctly, + // and .toUpperCase doesn't normalize 6 of them (list of exceptions: + // İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently + // uppercased versions). + // + // Here's an example showing how it happens. Lets take greek letter omega: + // uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) + // + // Unicode entries: + // 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8; + // 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 + // 03D1;GREEK THETA SYMBOL;Ll;0;L;<compat> 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 + // 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L;<compat> 0398;;;;N;;;;03B8; + // + // Case-insensitive comparison should treat all of them as equivalent. + // + // But .toLowerCase() doesn't change ϑ (it's already lowercase), + // and .toUpperCase() doesn't change ϴ (already uppercase). + // + // Applying first lower then upper case normalizes any character: + // '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' + // + // Note: this is equivalent to unicode case folding; unicode normalization + // is a different step that is not required here. + // + // Final result should be uppercased, because it's later stored in an object + // (this avoid a conflict with Object.prototype members, + // most notably, `__proto__`) + // + return str.toLowerCase().toUpperCase(); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Re-export libraries commonly used in both markdown-it and its plugins, +// so plugins won't have to depend on them explicitly, which reduces their +// bundled size (e.g. a browser build). +// +exports.lib = {}; +exports.lib.mdurl = require('mdurl'); +exports.lib.ucmicro = require('uc.micro'); + +exports.assign = assign; +exports.isString = isString; +exports.has = has; +exports.unescapeMd = unescapeMd; +exports.unescapeAll = unescapeAll; +exports.isValidEntityCode = isValidEntityCode; +exports.fromCodePoint = fromCodePoint; +// exports.replaceEntities = replaceEntities; +exports.escapeHtml = escapeHtml; +exports.arrayReplaceAt = arrayReplaceAt; +exports.isSpace = isSpace; +exports.isWhiteSpace = isWhiteSpace; +exports.isMdAsciiPunct = isMdAsciiPunct; +exports.isPunctChar = isPunctChar; +exports.escapeRE = escapeRE; +exports.normalizeReference = normalizeReference; diff --git a/node_modules/markdown-it/lib/helpers/index.js b/node_modules/markdown-it/lib/helpers/index.js new file mode 100644 index 0000000..bfdbfa2 --- /dev/null +++ b/node_modules/markdown-it/lib/helpers/index.js @@ -0,0 +1,7 @@ +// Just a shortcut for bulk export +'use strict'; + + +exports.parseLinkLabel = require('./parse_link_label'); +exports.parseLinkDestination = require('./parse_link_destination'); +exports.parseLinkTitle = require('./parse_link_title'); diff --git a/node_modules/markdown-it/lib/helpers/parse_link_destination.js b/node_modules/markdown-it/lib/helpers/parse_link_destination.js new file mode 100644 index 0000000..637f1f4 --- /dev/null +++ b/node_modules/markdown-it/lib/helpers/parse_link_destination.js @@ -0,0 +1,82 @@ +// Parse link destination +// +'use strict'; + + +var unescapeAll = require('../common/utils').unescapeAll; + + +module.exports = function parseLinkDestination(str, pos, max) { + var code, level, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (str.charCodeAt(pos) === 0x3C /* < */) { + pos++; + while (pos < max) { + code = str.charCodeAt(pos); + if (code === 0x0A /* \n */) { return result; } + if (code === 0x3C /* < */) { return result; } + if (code === 0x3E /* > */) { + result.pos = pos + 1; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } + if (code === 0x5C /* \ */ && pos + 1 < max) { + pos += 2; + continue; + } + + pos++; + } + + // no closing '>' + return result; + } + + // this should be ... } else { ... branch + + level = 0; + while (pos < max) { + code = str.charCodeAt(pos); + + if (code === 0x20) { break; } + + // ascii control characters + if (code < 0x20 || code === 0x7F) { break; } + + if (code === 0x5C /* \ */ && pos + 1 < max) { + if (str.charCodeAt(pos + 1) === 0x20) { break; } + pos += 2; + continue; + } + + if (code === 0x28 /* ( */) { + level++; + if (level > 32) { return result; } + } + + if (code === 0x29 /* ) */) { + if (level === 0) { break; } + level--; + } + + pos++; + } + + if (start === pos) { return result; } + if (level !== 0) { return result; } + + result.str = unescapeAll(str.slice(start, pos)); + result.lines = lines; + result.pos = pos; + result.ok = true; + return result; +}; diff --git a/node_modules/markdown-it/lib/helpers/parse_link_label.js b/node_modules/markdown-it/lib/helpers/parse_link_label.js new file mode 100644 index 0000000..5a450fd --- /dev/null +++ b/node_modules/markdown-it/lib/helpers/parse_link_label.js @@ -0,0 +1,48 @@ +// Parse link label +// +// this function assumes that first character ("[") already matches; +// returns the end of the label +// +'use strict'; + +module.exports = function parseLinkLabel(state, start, disableNested) { + var level, found, marker, prevPos, + labelEnd = -1, + max = state.posMax, + oldPos = state.pos; + + state.pos = start + 1; + level = 1; + + while (state.pos < max) { + marker = state.src.charCodeAt(state.pos); + if (marker === 0x5D /* ] */) { + level--; + if (level === 0) { + found = true; + break; + } + } + + prevPos = state.pos; + state.md.inline.skipToken(state); + if (marker === 0x5B /* [ */) { + if (prevPos === state.pos - 1) { + // increase level if we find text `[`, which is not a part of any token + level++; + } else if (disableNested) { + state.pos = oldPos; + return -1; + } + } + } + + if (found) { + labelEnd = state.pos; + } + + // restore old state + state.pos = oldPos; + + return labelEnd; +}; diff --git a/node_modules/markdown-it/lib/helpers/parse_link_title.js b/node_modules/markdown-it/lib/helpers/parse_link_title.js new file mode 100644 index 0000000..051d6f4 --- /dev/null +++ b/node_modules/markdown-it/lib/helpers/parse_link_title.js @@ -0,0 +1,55 @@ +// Parse link title +// +'use strict'; + + +var unescapeAll = require('../common/utils').unescapeAll; + + +module.exports = function parseLinkTitle(str, pos, max) { + var code, + marker, + lines = 0, + start = pos, + result = { + ok: false, + pos: 0, + lines: 0, + str: '' + }; + + if (pos >= max) { return result; } + + marker = str.charCodeAt(pos); + + if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return result; } + + pos++; + + // if opening marker is "(", switch it to closing marker ")" + if (marker === 0x28) { marker = 0x29; } + + while (pos < max) { + code = str.charCodeAt(pos); + if (code === marker) { + result.pos = pos + 1; + result.lines = lines; + result.str = unescapeAll(str.slice(start + 1, pos)); + result.ok = true; + return result; + } else if (code === 0x28 /* ( */ && marker === 0x29 /* ) */) { + return result; + } else if (code === 0x0A) { + lines++; + } else if (code === 0x5C /* \ */ && pos + 1 < max) { + pos++; + if (str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + + pos++; + } + + return result; +}; diff --git a/node_modules/markdown-it/lib/index.js b/node_modules/markdown-it/lib/index.js new file mode 100644 index 0000000..afec8d8 --- /dev/null +++ b/node_modules/markdown-it/lib/index.js @@ -0,0 +1,582 @@ +// Main parser class + +'use strict'; + + +var utils = require('./common/utils'); +var helpers = require('./helpers'); +var Renderer = require('./renderer'); +var ParserCore = require('./parser_core'); +var ParserBlock = require('./parser_block'); +var ParserInline = require('./parser_inline'); +var LinkifyIt = require('linkify-it'); +var mdurl = require('mdurl'); +var punycode = require('punycode'); + + +var config = { + default: require('./presets/default'), + zero: require('./presets/zero'), + commonmark: require('./presets/commonmark') +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// This validator can prohibit more than really needed to prevent XSS. It's a +// tradeoff to keep code simple and to be secure by default. +// +// If you need different setup - override validator method as you wish. Or +// replace it with dummy function and use external sanitizer. +// + +var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/; +var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/; + +function validateLink(url) { + // url should be normalized at this point, and existing entities are decoded + var str = url.trim().toLowerCase(); + + return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true; +} + +//////////////////////////////////////////////////////////////////////////////// + + +var RECODE_HOSTNAME_FOR = [ 'http:', 'https:', 'mailto:' ]; + +function normalizeLink(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toASCII(parsed.hostname); + } catch (er) { /**/ } + } + } + + return mdurl.encode(mdurl.format(parsed)); +} + +function normalizeLinkText(url) { + var parsed = mdurl.parse(url, true); + + if (parsed.hostname) { + // Encode hostnames in urls like: + // `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + // + // We don't encode unknown schemas, because it's likely that we encode + // something we shouldn't (e.g. `skype:name` treated as `skype:host`) + // + if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) { + try { + parsed.hostname = punycode.toUnicode(parsed.hostname); + } catch (er) { /**/ } + } + } + + // add '%' to exclude list because of https://github.com/markdown-it/markdown-it/issues/720 + return mdurl.decode(mdurl.format(parsed), mdurl.decode.defaultChars + '%'); +} + + +/** + * class MarkdownIt + * + * Main parser/renderer class. + * + * ##### Usage + * + * ```javascript + * // node.js, "classic" way: + * var MarkdownIt = require('markdown-it'), + * md = new MarkdownIt(); + * var result = md.render('# markdown-it rulezz!'); + * + * // node.js, the same, but with sugar: + * var md = require('markdown-it')(); + * var result = md.render('# markdown-it rulezz!'); + * + * // browser without AMD, added to "window" on script load + * // Note, there are no dash. + * var md = window.markdownit(); + * var result = md.render('# markdown-it rulezz!'); + * ``` + * + * Single line rendering, without paragraph wrap: + * + * ```javascript + * var md = require('markdown-it')(); + * var result = md.renderInline('__markdown-it__ rulezz!'); + * ``` + **/ + +/** + * new MarkdownIt([presetName, options]) + * - presetName (String): optional, `commonmark` / `zero` + * - options (Object) + * + * Creates parser instanse with given config. Can be called without `new`. + * + * ##### presetName + * + * MarkdownIt provides named presets as a convenience to quickly + * enable/disable active syntax rules and options for common use cases. + * + * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) - + * configures parser to strict [CommonMark](http://commonmark.org/) mode. + * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) - + * similar to GFM, used when no preset name given. Enables all available rules, + * but still without html, typographer & autolinker. + * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) - + * all rules disabled. Useful to quickly setup your config via `.enable()`. + * For example, when you need only `bold` and `italic` markup and nothing else. + * + * ##### options: + * + * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful! + * That's not safe! You may need external sanitizer to protect output from XSS. + * It's better to extend features via plugins, instead of enabling HTML. + * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags + * (`<br />`). This is needed only for full CommonMark compatibility. In real + * world you will need HTML output. + * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `<br>`. + * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks. + * Can be useful for external highlighters. + * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links. + * - __typographer__ - `false`. Set `true` to enable [some language-neutral + * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) + + * quotes beautification (smartquotes). + * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement + * pairs, when typographer enabled and smartquotes on. For example, you can + * use `'«»„“'` for Russian, `'„“‚‘'` for German, and + * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp). + * - __highlight__ - `null`. Highlighter function for fenced code blocks. + * Highlighter `function (str, lang)` should return escaped HTML. It can also + * return empty string if the source was not changed and should be escaped + * externaly. If result starts with <pre... internal wrapper is skipped. + * + * ##### Example + * + * ```javascript + * // commonmark mode + * var md = require('markdown-it')('commonmark'); + * + * // default mode + * var md = require('markdown-it')(); + * + * // enable everything + * var md = require('markdown-it')({ + * html: true, + * linkify: true, + * typographer: true + * }); + * ``` + * + * ##### Syntax highlighting + * + * ```js + * var hljs = require('highlight.js') // https://highlightjs.org/ + * + * var md = require('markdown-it')({ + * highlight: function (str, lang) { + * if (lang && hljs.getLanguage(lang)) { + * try { + * return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value; + * } catch (__) {} + * } + * + * return ''; // use external default escaping + * } + * }); + * ``` + * + * Or with full wrapper override (if you need assign class to `<pre>`): + * + * ```javascript + * var hljs = require('highlight.js') // https://highlightjs.org/ + * + * // Actual default values + * var md = require('markdown-it')({ + * highlight: function (str, lang) { + * if (lang && hljs.getLanguage(lang)) { + * try { + * return '<pre class="hljs"><code>' + + * hljs.highlight(str, { language: lang, ignoreIllegals: true }).value + + * '</code></pre>'; + * } catch (__) {} + * } + * + * return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>'; + * } + * }); + * ``` + * + **/ +function MarkdownIt(presetName, options) { + if (!(this instanceof MarkdownIt)) { + return new MarkdownIt(presetName, options); + } + + if (!options) { + if (!utils.isString(presetName)) { + options = presetName || {}; + presetName = 'default'; + } + } + + /** + * MarkdownIt#inline -> ParserInline + * + * Instance of [[ParserInline]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.inline = new ParserInline(); + + /** + * MarkdownIt#block -> ParserBlock + * + * Instance of [[ParserBlock]]. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.block = new ParserBlock(); + + /** + * MarkdownIt#core -> Core + * + * Instance of [[Core]] chain executor. You may need it to add new rules when + * writing plugins. For simple rules control use [[MarkdownIt.disable]] and + * [[MarkdownIt.enable]]. + **/ + this.core = new ParserCore(); + + /** + * MarkdownIt#renderer -> Renderer + * + * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering + * rules for new token types, generated by plugins. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * function myToken(tokens, idx, options, env, self) { + * //... + * return result; + * }; + * + * md.renderer.rules['my_token'] = myToken + * ``` + * + * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js). + **/ + this.renderer = new Renderer(); + + /** + * MarkdownIt#linkify -> LinkifyIt + * + * [linkify-it](https://github.com/markdown-it/linkify-it) instance. + * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js) + * rule. + **/ + this.linkify = new LinkifyIt(); + + /** + * MarkdownIt#validateLink(url) -> Boolean + * + * Link validation function. CommonMark allows too much in links. By default + * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas + * except some embedded image types. + * + * You can change this behaviour: + * + * ```javascript + * var md = require('markdown-it')(); + * // enable everything + * md.validateLink = function () { return true; } + * ``` + **/ + this.validateLink = validateLink; + + /** + * MarkdownIt#normalizeLink(url) -> String + * + * Function used to encode link url to a machine-readable format, + * which includes url-encoding, punycode, etc. + **/ + this.normalizeLink = normalizeLink; + + /** + * MarkdownIt#normalizeLinkText(url) -> String + * + * Function used to decode link url to a human-readable format` + **/ + this.normalizeLinkText = normalizeLinkText; + + + // Expose utils & helpers for easy acces from plugins + + /** + * MarkdownIt#utils -> utils + * + * Assorted utility functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js). + **/ + this.utils = utils; + + /** + * MarkdownIt#helpers -> helpers + * + * Link components parser functions, useful to write plugins. See details + * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers). + **/ + this.helpers = utils.assign({}, helpers); + + + this.options = {}; + this.configure(presetName); + + if (options) { this.set(options); } +} + + +/** chainable + * MarkdownIt.set(options) + * + * Set parser options (in the same format as in constructor). Probably, you + * will never need it, but you can change options after constructor call. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .set({ html: true, breaks: true }) + * .set({ typographer, true }); + * ``` + * + * __Note:__ To achieve the best possible performance, don't modify a + * `markdown-it` instance options on the fly. If you need multiple configurations + * it's best to create multiple instances and initialize each with separate + * config. + **/ +MarkdownIt.prototype.set = function (options) { + utils.assign(this.options, options); + return this; +}; + + +/** chainable, internal + * MarkdownIt.configure(presets) + * + * Batch load of all options and compenent settings. This is internal method, + * and you probably will not need it. But if you will - see available presets + * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) + * + * We strongly recommend to use presets instead of direct config loads. That + * will give better compatibility with next versions. + **/ +MarkdownIt.prototype.configure = function (presets) { + var self = this, presetName; + + if (utils.isString(presets)) { + presetName = presets; + presets = config[presetName]; + if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); } + } + + if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); } + + if (presets.options) { self.set(presets.options); } + + if (presets.components) { + Object.keys(presets.components).forEach(function (name) { + if (presets.components[name].rules) { + self[name].ruler.enableOnly(presets.components[name].rules); + } + if (presets.components[name].rules2) { + self[name].ruler2.enableOnly(presets.components[name].rules2); + } + }); + } + return this; +}; + + +/** chainable + * MarkdownIt.enable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to enable + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable list or rules. It will automatically find appropriate components, + * containing rules with given names. If rule not found, and `ignoreInvalid` + * not set - throws exception. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')() + * .enable(['sub', 'sup']) + * .disable('smartquotes'); + * ``` + **/ +MarkdownIt.prototype.enable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.enable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.enable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed); + } + + return this; +}; + + +/** chainable + * MarkdownIt.disable(list, ignoreInvalid) + * - list (String|Array): rule name or list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * The same as [[MarkdownIt.enable]], but turn specified rules off. + **/ +MarkdownIt.prototype.disable = function (list, ignoreInvalid) { + var result = []; + + if (!Array.isArray(list)) { list = [ list ]; } + + [ 'core', 'block', 'inline' ].forEach(function (chain) { + result = result.concat(this[chain].ruler.disable(list, true)); + }, this); + + result = result.concat(this.inline.ruler2.disable(list, true)); + + var missed = list.filter(function (name) { return result.indexOf(name) < 0; }); + + if (missed.length && !ignoreInvalid) { + throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed); + } + return this; +}; + + +/** chainable + * MarkdownIt.use(plugin, params) + * + * Load specified plugin with given params into current parser instance. + * It's just a sugar to call `plugin(md, params)` with curring. + * + * ##### Example + * + * ```javascript + * var iterator = require('markdown-it-for-inline'); + * var md = require('markdown-it')() + * .use(iterator, 'foo_replace', 'text', function (tokens, idx) { + * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar'); + * }); + * ``` + **/ +MarkdownIt.prototype.use = function (plugin /*, params, ... */) { + var args = [ this ].concat(Array.prototype.slice.call(arguments, 1)); + plugin.apply(plugin, args); + return this; +}; + + +/** internal + * MarkdownIt.parse(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * Parse input string and return list of block tokens (special token type + * "inline" will contain list of inline tokens). You should not call this + * method directly, until you write custom renderer (for example, to produce + * AST). + * + * `env` is used to pass data between "distributed" rules and return additional + * metadata like reference info, needed for the renderer. It also can be used to + * inject data in specific cases. Usually, you will be ok to pass `{}`, + * and then pass updated object to renderer. + **/ +MarkdownIt.prototype.parse = function (src, env) { + if (typeof src !== 'string') { + throw new Error('Input data should be a String'); + } + + var state = new this.core.State(src, this, env); + + this.core.process(state); + + return state.tokens; +}; + + +/** + * MarkdownIt.render(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Render markdown string into html. It does all magic for you :). + * + * `env` can be used to inject additional metadata (`{}` by default). + * But you will not need it with high probability. See also comment + * in [[MarkdownIt.parse]]. + **/ +MarkdownIt.prototype.render = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parse(src, env), this.options, env); +}; + + +/** internal + * MarkdownIt.parseInline(src, env) -> Array + * - src (String): source string + * - env (Object): environment sandbox + * + * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the + * block tokens list with the single `inline` element, containing parsed inline + * tokens in `children` property. Also updates `env` object. + **/ +MarkdownIt.prototype.parseInline = function (src, env) { + var state = new this.core.State(src, this, env); + + state.inlineMode = true; + this.core.process(state); + + return state.tokens; +}; + + +/** + * MarkdownIt.renderInline(src [, env]) -> String + * - src (String): source string + * - env (Object): environment sandbox + * + * Similar to [[MarkdownIt.render]] but for single paragraph content. Result + * will NOT be wrapped into `<p>` tags. + **/ +MarkdownIt.prototype.renderInline = function (src, env) { + env = env || {}; + + return this.renderer.render(this.parseInline(src, env), this.options, env); +}; + + +module.exports = MarkdownIt; diff --git a/node_modules/markdown-it/lib/parser_block.js b/node_modules/markdown-it/lib/parser_block.js new file mode 100644 index 0000000..5450c2a --- /dev/null +++ b/node_modules/markdown-it/lib/parser_block.js @@ -0,0 +1,122 @@ +/** internal + * class ParserBlock + * + * Block-level tokenizer. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + // First 2 params - rule name & source. Secondary array - list of rules, + // which can be terminated by this one. + [ 'table', require('./rules_block/table'), [ 'paragraph', 'reference' ] ], + [ 'code', require('./rules_block/code') ], + [ 'fence', require('./rules_block/fence'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'hr', require('./rules_block/hr'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ], + [ 'list', require('./rules_block/list'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'reference', require('./rules_block/reference') ], + [ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'heading', require('./rules_block/heading'), [ 'paragraph', 'reference', 'blockquote' ] ], + [ 'lheading', require('./rules_block/lheading') ], + [ 'paragraph', require('./rules_block/paragraph') ] +]; + + +/** + * new ParserBlock() + **/ +function ParserBlock() { + /** + * ParserBlock#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of block rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); + } +} + + +// Generate tokens for input range +// +ParserBlock.prototype.tokenize = function (state, startLine, endLine) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + line = startLine, + hasEmptyLines = false, + maxNesting = state.md.options.maxNesting; + + while (line < endLine) { + state.line = line = state.skipEmptyLines(line); + if (line >= endLine) { break; } + + // Termination condition for nested calls. + // Nested calls currently used for blockquotes & lists + if (state.sCount[line] < state.blkIndent) { break; } + + // If nesting level exceeded - skip tail to the end. That's not ordinary + // situation and we should not care about content. + if (state.level >= maxNesting) { + state.line = endLine; + break; + } + + // Try all possible rules. + // On success, rule should: + // + // - update `state.line` + // - update `state.tokens` + // - return true + + for (i = 0; i < len; i++) { + ok = rules[i](state, line, endLine, false); + if (ok) { break; } + } + + // set state.tight if we had an empty line before current tag + // i.e. latest empty line should not count + state.tight = !hasEmptyLines; + + // paragraph might "eat" one newline after it in nested lists + if (state.isEmpty(state.line - 1)) { + hasEmptyLines = true; + } + + line = state.line; + + if (line < endLine && state.isEmpty(line)) { + hasEmptyLines = true; + line++; + state.line = line; + } + } +}; + + +/** + * ParserBlock.parse(str, md, env, outTokens) + * + * Process input string and push block tokens into `outTokens` + **/ +ParserBlock.prototype.parse = function (src, md, env, outTokens) { + var state; + + if (!src) { return; } + + state = new this.State(src, md, env, outTokens); + + this.tokenize(state, state.line, state.lineMax); +}; + + +ParserBlock.prototype.State = require('./rules_block/state_block'); + + +module.exports = ParserBlock; diff --git a/node_modules/markdown-it/lib/parser_core.js b/node_modules/markdown-it/lib/parser_core.js new file mode 100644 index 0000000..1eaa2b0 --- /dev/null +++ b/node_modules/markdown-it/lib/parser_core.js @@ -0,0 +1,58 @@ +/** internal + * class Core + * + * Top-level rules executor. Glues block/inline parsers and does intermediate + * transformations. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +var _rules = [ + [ 'normalize', require('./rules_core/normalize') ], + [ 'block', require('./rules_core/block') ], + [ 'inline', require('./rules_core/inline') ], + [ 'linkify', require('./rules_core/linkify') ], + [ 'replacements', require('./rules_core/replacements') ], + [ 'smartquotes', require('./rules_core/smartquotes') ] +]; + + +/** + * new Core() + **/ +function Core() { + /** + * Core#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of core rules. + **/ + this.ruler = new Ruler(); + + for (var i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } +} + + +/** + * Core.process(state) + * + * Executes core chain rules. + **/ +Core.prototype.process = function (state) { + var i, l, rules; + + rules = this.ruler.getRules(''); + + for (i = 0, l = rules.length; i < l; i++) { + rules[i](state); + } +}; + +Core.prototype.State = require('./rules_core/state_core'); + + +module.exports = Core; diff --git a/node_modules/markdown-it/lib/parser_inline.js b/node_modules/markdown-it/lib/parser_inline.js new file mode 100644 index 0000000..c8e66d3 --- /dev/null +++ b/node_modules/markdown-it/lib/parser_inline.js @@ -0,0 +1,177 @@ +/** internal + * class ParserInline + * + * Tokenizes paragraph content. + **/ +'use strict'; + + +var Ruler = require('./ruler'); + + +//////////////////////////////////////////////////////////////////////////////// +// Parser rules + +var _rules = [ + [ 'text', require('./rules_inline/text') ], + [ 'newline', require('./rules_inline/newline') ], + [ 'escape', require('./rules_inline/escape') ], + [ 'backticks', require('./rules_inline/backticks') ], + [ 'strikethrough', require('./rules_inline/strikethrough').tokenize ], + [ 'emphasis', require('./rules_inline/emphasis').tokenize ], + [ 'link', require('./rules_inline/link') ], + [ 'image', require('./rules_inline/image') ], + [ 'autolink', require('./rules_inline/autolink') ], + [ 'html_inline', require('./rules_inline/html_inline') ], + [ 'entity', require('./rules_inline/entity') ] +]; + +var _rules2 = [ + [ 'balance_pairs', require('./rules_inline/balance_pairs') ], + [ 'strikethrough', require('./rules_inline/strikethrough').postProcess ], + [ 'emphasis', require('./rules_inline/emphasis').postProcess ], + [ 'text_collapse', require('./rules_inline/text_collapse') ] +]; + + +/** + * new ParserInline() + **/ +function ParserInline() { + var i; + + /** + * ParserInline#ruler -> Ruler + * + * [[Ruler]] instance. Keep configuration of inline rules. + **/ + this.ruler = new Ruler(); + + for (i = 0; i < _rules.length; i++) { + this.ruler.push(_rules[i][0], _rules[i][1]); + } + + /** + * ParserInline#ruler2 -> Ruler + * + * [[Ruler]] instance. Second ruler used for post-processing + * (e.g. in emphasis-like rules). + **/ + this.ruler2 = new Ruler(); + + for (i = 0; i < _rules2.length; i++) { + this.ruler2.push(_rules2[i][0], _rules2[i][1]); + } +} + + +// Skip single token by running all rules in validation mode; +// returns `true` if any rule reported success +// +ParserInline.prototype.skipToken = function (state) { + var ok, i, pos = state.pos, + rules = this.ruler.getRules(''), + len = rules.length, + maxNesting = state.md.options.maxNesting, + cache = state.cache; + + + if (typeof cache[pos] !== 'undefined') { + state.pos = cache[pos]; + return; + } + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + // Increment state.level and decrement it later to limit recursion. + // It's harmless to do here, because no tokens are created. But ideally, + // we'd need a separate private state variable for this purpose. + // + state.level++; + ok = rules[i](state, true); + state.level--; + + if (ok) { break; } + } + } else { + // Too much nesting, just skip until the end of the paragraph. + // + // NOTE: this will cause links to behave incorrectly in the following case, + // when an amount of `[` is exactly equal to `maxNesting + 1`: + // + // [[[[[[[[[[[[[[[[[[[[[foo]() + // + // TODO: remove this workaround when CM standard will allow nested links + // (we can replace it by preventing links from being parsed in + // validation mode) + // + state.pos = state.posMax; + } + + if (!ok) { state.pos++; } + cache[pos] = state.pos; +}; + + +// Generate tokens for input range +// +ParserInline.prototype.tokenize = function (state) { + var ok, i, + rules = this.ruler.getRules(''), + len = rules.length, + end = state.posMax, + maxNesting = state.md.options.maxNesting; + + while (state.pos < end) { + // Try all possible rules. + // On success, rule should: + // + // - update `state.pos` + // - update `state.tokens` + // - return true + + if (state.level < maxNesting) { + for (i = 0; i < len; i++) { + ok = rules[i](state, false); + if (ok) { break; } + } + } + + if (ok) { + if (state.pos >= end) { break; } + continue; + } + + state.pending += state.src[state.pos++]; + } + + if (state.pending) { + state.pushPending(); + } +}; + + +/** + * ParserInline.parse(str, md, env, outTokens) + * + * Process input string and push inline tokens into `outTokens` + **/ +ParserInline.prototype.parse = function (str, md, env, outTokens) { + var i, rules, len; + var state = new this.State(str, md, env, outTokens); + + this.tokenize(state); + + rules = this.ruler2.getRules(''); + len = rules.length; + + for (i = 0; i < len; i++) { + rules[i](state); + } +}; + + +ParserInline.prototype.State = require('./rules_inline/state_inline'); + + +module.exports = ParserInline; diff --git a/node_modules/markdown-it/lib/presets/commonmark.js b/node_modules/markdown-it/lib/presets/commonmark.js new file mode 100644 index 0000000..7066553 --- /dev/null +++ b/node_modules/markdown-it/lib/presets/commonmark.js @@ -0,0 +1,80 @@ +// Commonmark default options + +'use strict'; + + +module.exports = { + options: { + html: true, // Enable HTML tags in source + xhtmlOut: true, // Use '/' to close single tags (<br />) + breaks: false, // Convert '\n' in paragraphs into <br> + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with <pre... internal wrapper is skipped. + // + // function (/*str, lang*/) { return ''; } + // + highlight: null, + + maxNesting: 20 // Internal protection, recursion limit + }, + + components: { + + core: { + rules: [ + 'normalize', + 'block', + 'inline' + ] + }, + + block: { + rules: [ + 'blockquote', + 'code', + 'fence', + 'heading', + 'hr', + 'html_block', + 'lheading', + 'list', + 'reference', + 'paragraph' + ] + }, + + inline: { + rules: [ + 'autolink', + 'backticks', + 'emphasis', + 'entity', + 'escape', + 'html_inline', + 'image', + 'link', + 'newline', + 'text' + ], + rules2: [ + 'balance_pairs', + 'emphasis', + 'text_collapse' + ] + } + } +}; diff --git a/node_modules/markdown-it/lib/presets/default.js b/node_modules/markdown-it/lib/presets/default.js new file mode 100644 index 0000000..17ecef2 --- /dev/null +++ b/node_modules/markdown-it/lib/presets/default.js @@ -0,0 +1,41 @@ +// markdown-it default options + +'use strict'; + + +module.exports = { + options: { + html: false, // Enable HTML tags in source + xhtmlOut: false, // Use '/' to close single tags (<br />) + breaks: false, // Convert '\n' in paragraphs into <br> + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with <pre... internal wrapper is skipped. + // + // function (/*str, lang*/) { return ''; } + // + highlight: null, + + maxNesting: 100 // Internal protection, recursion limit + }, + + components: { + + core: {}, + block: {}, + inline: {} + } +}; diff --git a/node_modules/markdown-it/lib/presets/zero.js b/node_modules/markdown-it/lib/presets/zero.js new file mode 100644 index 0000000..5da413c --- /dev/null +++ b/node_modules/markdown-it/lib/presets/zero.js @@ -0,0 +1,62 @@ +// "Zero" preset, with nothing enabled. Useful for manual configuring of simple +// modes. For example, to parse bold/italic only. + +'use strict'; + + +module.exports = { + options: { + html: false, // Enable HTML tags in source + xhtmlOut: false, // Use '/' to close single tags (<br />) + breaks: false, // Convert '\n' in paragraphs into <br> + langPrefix: 'language-', // CSS language prefix for fenced blocks + linkify: false, // autoconvert URL-like texts to links + + // Enable some language-neutral replacements + quotes beautification + typographer: false, + + // Double + single quotes replacement pairs, when typographer enabled, + // and smartquotes on. Could be either a String or an Array. + // + // For example, you can use '«»„“' for Russian, '„“‚‘' for German, + // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + quotes: '\u201c\u201d\u2018\u2019', /* “”‘’ */ + + // Highlighter function. Should return escaped HTML, + // or '' if the source string is not changed and should be escaped externaly. + // If result starts with <pre... internal wrapper is skipped. + // + // function (/*str, lang*/) { return ''; } + // + highlight: null, + + maxNesting: 20 // Internal protection, recursion limit + }, + + components: { + + core: { + rules: [ + 'normalize', + 'block', + 'inline' + ] + }, + + block: { + rules: [ + 'paragraph' + ] + }, + + inline: { + rules: [ + 'text' + ], + rules2: [ + 'balance_pairs', + 'text_collapse' + ] + } + } +}; diff --git a/node_modules/markdown-it/lib/renderer.js b/node_modules/markdown-it/lib/renderer.js new file mode 100644 index 0000000..08eacf3 --- /dev/null +++ b/node_modules/markdown-it/lib/renderer.js @@ -0,0 +1,341 @@ +/** + * class Renderer + * + * Generates HTML from parsed token stream. Each instance has independent + * copy of rules. Those can be rewritten with ease. Also, you can add new + * rules if you create plugin and adds new token types. + **/ +'use strict'; + + +var assign = require('./common/utils').assign; +var unescapeAll = require('./common/utils').unescapeAll; +var escapeHtml = require('./common/utils').escapeHtml; + + +//////////////////////////////////////////////////////////////////////////////// + +var default_rules = {}; + + +default_rules.code_inline = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + return '<code' + slf.renderAttrs(token) + '>' + + escapeHtml(tokens[idx].content) + + '</code>'; +}; + + +default_rules.code_block = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + return '<pre' + slf.renderAttrs(token) + '><code>' + + escapeHtml(tokens[idx].content) + + '</code></pre>\n'; +}; + + +default_rules.fence = function (tokens, idx, options, env, slf) { + var token = tokens[idx], + info = token.info ? unescapeAll(token.info).trim() : '', + langName = '', + langAttrs = '', + highlighted, i, arr, tmpAttrs, tmpToken; + + if (info) { + arr = info.split(/(\s+)/g); + langName = arr[0]; + langAttrs = arr.slice(2).join(''); + } + + if (options.highlight) { + highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content); + } else { + highlighted = escapeHtml(token.content); + } + + if (highlighted.indexOf('<pre') === 0) { + return highlighted + '\n'; + } + + // If language exists, inject class gently, without modifying original token. + // May be, one day we will add .deepClone() for token and simplify this part, but + // now we prefer to keep things local. + if (info) { + i = token.attrIndex('class'); + tmpAttrs = token.attrs ? token.attrs.slice() : []; + + if (i < 0) { + tmpAttrs.push([ 'class', options.langPrefix + langName ]); + } else { + tmpAttrs[i] = tmpAttrs[i].slice(); + tmpAttrs[i][1] += ' ' + options.langPrefix + langName; + } + + // Fake token just to render attributes + tmpToken = { + attrs: tmpAttrs + }; + + return '<pre><code' + slf.renderAttrs(tmpToken) + '>' + + highlighted + + '</code></pre>\n'; + } + + + return '<pre><code' + slf.renderAttrs(token) + '>' + + highlighted + + '</code></pre>\n'; +}; + + +default_rules.image = function (tokens, idx, options, env, slf) { + var token = tokens[idx]; + + // "alt" attr MUST be set, even if empty. Because it's mandatory and + // should be placed on proper position for tests. + // + // Replace content with actual value + + token.attrs[token.attrIndex('alt')][1] = + slf.renderInlineAsText(token.children, options, env); + + return slf.renderToken(tokens, idx, options); +}; + + +default_rules.hardbreak = function (tokens, idx, options /*, env */) { + return options.xhtmlOut ? '<br />\n' : '<br>\n'; +}; +default_rules.softbreak = function (tokens, idx, options /*, env */) { + return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\n') : '\n'; +}; + + +default_rules.text = function (tokens, idx /*, options, env */) { + return escapeHtml(tokens[idx].content); +}; + + +default_rules.html_block = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; +default_rules.html_inline = function (tokens, idx /*, options, env */) { + return tokens[idx].content; +}; + + +/** + * new Renderer() + * + * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults. + **/ +function Renderer() { + + /** + * Renderer#rules -> Object + * + * Contains render rules for tokens. Can be updated and extended. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.renderer.rules.strong_open = function () { return '<b>'; }; + * md.renderer.rules.strong_close = function () { return '</b>'; }; + * + * var result = md.renderInline(...); + * ``` + * + * Each rule is called as independent static function with fixed signature: + * + * ```javascript + * function my_token_render(tokens, idx, options, env, renderer) { + * // ... + * return renderedHTML; + * } + * ``` + * + * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js) + * for more details and examples. + **/ + this.rules = assign({}, default_rules); +} + + +/** + * Renderer.renderAttrs(token) -> String + * + * Render token attributes to string. + **/ +Renderer.prototype.renderAttrs = function renderAttrs(token) { + var i, l, result; + + if (!token.attrs) { return ''; } + + result = ''; + + for (i = 0, l = token.attrs.length; i < l; i++) { + result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"'; + } + + return result; +}; + + +/** + * Renderer.renderToken(tokens, idx, options) -> String + * - tokens (Array): list of tokens + * - idx (Numbed): token index to render + * - options (Object): params of parser instance + * + * Default token renderer. Can be overriden by custom function + * in [[Renderer#rules]]. + **/ +Renderer.prototype.renderToken = function renderToken(tokens, idx, options) { + var nextToken, + result = '', + needLf = false, + token = tokens[idx]; + + // Tight list paragraphs + if (token.hidden) { + return ''; + } + + // Insert a newline between hidden paragraph and subsequent opening + // block-level tag. + // + // For example, here we should insert a newline before blockquote: + // - a + // > + // + if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) { + result += '\n'; + } + + // Add token name, e.g. `<img` + result += (token.nesting === -1 ? '</' : '<') + token.tag; + + // Encode attributes, e.g. `<img src="foo"` + result += this.renderAttrs(token); + + // Add a slash for self-closing tags, e.g. `<img src="foo" /` + if (token.nesting === 0 && options.xhtmlOut) { + result += ' /'; + } + + // Check if we need to add a newline after this tag + if (token.block) { + needLf = true; + + if (token.nesting === 1) { + if (idx + 1 < tokens.length) { + nextToken = tokens[idx + 1]; + + if (nextToken.type === 'inline' || nextToken.hidden) { + // Block-level tag containing an inline tag. + // + needLf = false; + + } else if (nextToken.nesting === -1 && nextToken.tag === token.tag) { + // Opening tag + closing tag of the same type. E.g. `<li></li>`. + // + needLf = false; + } + } + } + } + + result += needLf ? '>\n' : '>'; + + return result; +}; + + +/** + * Renderer.renderInline(tokens, options, env) -> String + * - tokens (Array): list on block tokens to render + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * The same as [[Renderer.render]], but for single token of `inline` type. + **/ +Renderer.prototype.renderInline = function (tokens, options, env) { + var type, + result = '', + rules = this.rules; + + for (var i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (typeof rules[type] !== 'undefined') { + result += rules[type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options); + } + } + + return result; +}; + + +/** internal + * Renderer.renderInlineAsText(tokens, options, env) -> String + * - tokens (Array): list on block tokens to render + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Special kludge for image `alt` attributes to conform CommonMark spec. + * Don't try to use it! Spec requires to show `alt` content with stripped markup, + * instead of simple escaping. + **/ +Renderer.prototype.renderInlineAsText = function (tokens, options, env) { + var result = ''; + + for (var i = 0, len = tokens.length; i < len; i++) { + if (tokens[i].type === 'text') { + result += tokens[i].content; + } else if (tokens[i].type === 'image') { + result += this.renderInlineAsText(tokens[i].children, options, env); + } else if (tokens[i].type === 'softbreak') { + result += '\n'; + } + } + + return result; +}; + + +/** + * Renderer.render(tokens, options, env) -> String + * - tokens (Array): list on block tokens to render + * - options (Object): params of parser instance + * - env (Object): additional data from parsed input (references, for example) + * + * Takes token stream and generates HTML. Probably, you will never need to call + * this method directly. + **/ +Renderer.prototype.render = function (tokens, options, env) { + var i, len, type, + result = '', + rules = this.rules; + + for (i = 0, len = tokens.length; i < len; i++) { + type = tokens[i].type; + + if (type === 'inline') { + result += this.renderInline(tokens[i].children, options, env); + } else if (typeof rules[type] !== 'undefined') { + result += rules[tokens[i].type](tokens, i, options, env, this); + } else { + result += this.renderToken(tokens, i, options, env); + } + } + + return result; +}; + +module.exports = Renderer; diff --git a/node_modules/markdown-it/lib/ruler.js b/node_modules/markdown-it/lib/ruler.js new file mode 100644 index 0000000..9ad5da4 --- /dev/null +++ b/node_modules/markdown-it/lib/ruler.js @@ -0,0 +1,352 @@ +/** + * class Ruler + * + * Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and + * [[MarkdownIt#inline]] to manage sequences of functions (rules): + * + * - keep rules in defined order + * - assign the name to each rule + * - enable/disable rules + * - add/replace rules + * - allow assign rules to additional named chains (in the same) + * - cacheing lists of active rules + * + * You will not need use this class directly until write plugins. For simple + * rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and + * [[MarkdownIt.use]]. + **/ +'use strict'; + + +/** + * new Ruler() + **/ +function Ruler() { + // List of added rules. Each element is: + // + // { + // name: XXX, + // enabled: Boolean, + // fn: Function(), + // alt: [ name2, name3 ] + // } + // + this.__rules__ = []; + + // Cached rule chains. + // + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + // + this.__cache__ = null; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper methods, should not be used directly + + +// Find rule index by name +// +Ruler.prototype.__find__ = function (name) { + for (var i = 0; i < this.__rules__.length; i++) { + if (this.__rules__[i].name === name) { + return i; + } + } + return -1; +}; + + +// Build rules lookup cache +// +Ruler.prototype.__compile__ = function () { + var self = this; + var chains = [ '' ]; + + // collect unique names + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + rule.alt.forEach(function (altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName); + } + }); + }); + + self.__cache__ = {}; + + chains.forEach(function (chain) { + self.__cache__[chain] = []; + self.__rules__.forEach(function (rule) { + if (!rule.enabled) { return; } + + if (chain && rule.alt.indexOf(chain) < 0) { return; } + + self.__cache__[chain].push(rule.fn); + }); + }); +}; + + +/** + * Ruler.at(name, fn [, options]) + * - name (String): rule name to replace. + * - fn (Function): new rule function. + * - options (Object): new rule options (not mandatory). + * + * Replace rule by name with new function & options. Throws error if name not + * found. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * Replace existing typographer replacement rule with new one: + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.at('replacements', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.at = function (name, fn, options) { + var index = this.__find__(name); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + name); } + + this.__rules__[index].fn = fn; + this.__rules__[index].alt = opt.alt || []; + this.__cache__ = null; +}; + + +/** + * Ruler.before(beforeName, ruleName, fn [, options]) + * - beforeName (String): new rule will be added before this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain before one with given name. See also + * [[Ruler.after]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.before = function (beforeName, ruleName, fn, options) { + var index = this.__find__(beforeName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + beforeName); } + + this.__rules__.splice(index, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +/** + * Ruler.after(afterName, ruleName, fn [, options]) + * - afterName (String): new rule will be added after this one. + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Add new rule to chain after one with given name. See also + * [[Ruler.before]], [[Ruler.push]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.inline.ruler.after('text', 'my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.after = function (afterName, ruleName, fn, options) { + var index = this.__find__(afterName); + var opt = options || {}; + + if (index === -1) { throw new Error('Parser rule not found: ' + afterName); } + + this.__rules__.splice(index + 1, 0, { + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + +/** + * Ruler.push(ruleName, fn [, options]) + * - ruleName (String): name of added rule. + * - fn (Function): rule function. + * - options (Object): rule options (not mandatory). + * + * Push new rule to the end of chain. See also + * [[Ruler.before]], [[Ruler.after]]. + * + * ##### Options: + * + * - __alt__ - array with names of "alternate" chains. + * + * ##### Example + * + * ```javascript + * var md = require('markdown-it')(); + * + * md.core.ruler.push('my_rule', function replace(state) { + * //... + * }); + * ``` + **/ +Ruler.prototype.push = function (ruleName, fn, options) { + var opt = options || {}; + + this.__rules__.push({ + name: ruleName, + enabled: true, + fn: fn, + alt: opt.alt || [] + }); + + this.__cache__ = null; +}; + + +/** + * Ruler.enable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to enable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.disable]], [[Ruler.enableOnly]]. + **/ +Ruler.prototype.enable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and enable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = true; + result.push(name); + }, this); + + this.__cache__ = null; + return result; +}; + + +/** + * Ruler.enableOnly(list [, ignoreInvalid]) + * - list (String|Array): list of rule names to enable (whitelist). + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Enable rules with given names, and disable everything else. If any rule name + * not found - throw Error. Errors can be disabled by second param. + * + * See also [[Ruler.disable]], [[Ruler.enable]]. + **/ +Ruler.prototype.enableOnly = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + this.__rules__.forEach(function (rule) { rule.enabled = false; }); + + this.enable(list, ignoreInvalid); +}; + + +/** + * Ruler.disable(list [, ignoreInvalid]) -> Array + * - list (String|Array): list of rule names to disable. + * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found. + * + * Disable rules with given names. If any rule name not found - throw Error. + * Errors can be disabled by second param. + * + * Returns list of found rule names (if no exception happened). + * + * See also [[Ruler.enable]], [[Ruler.enableOnly]]. + **/ +Ruler.prototype.disable = function (list, ignoreInvalid) { + if (!Array.isArray(list)) { list = [ list ]; } + + var result = []; + + // Search by name and disable + list.forEach(function (name) { + var idx = this.__find__(name); + + if (idx < 0) { + if (ignoreInvalid) { return; } + throw new Error('Rules manager: invalid rule name ' + name); + } + this.__rules__[idx].enabled = false; + result.push(name); + }, this); + + this.__cache__ = null; + return result; +}; + + +/** + * Ruler.getRules(chainName) -> Array + * + * Return array of active functions (rules) for given chain name. It analyzes + * rules configuration, compiles caches if not exists and returns result. + * + * Default chain name is `''` (empty string). It can't be skipped. That's + * done intentionally, to keep signature monomorphic for high speed. + **/ +Ruler.prototype.getRules = function (chainName) { + if (this.__cache__ === null) { + this.__compile__(); + } + + // Chain can be empty, if rules disabled. But we still have to return Array. + return this.__cache__[chainName] || []; +}; + +module.exports = Ruler; diff --git a/node_modules/markdown-it/lib/rules_block/blockquote.js b/node_modules/markdown-it/lib/rules_block/blockquote.js new file mode 100644 index 0000000..a02699a --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/blockquote.js @@ -0,0 +1,284 @@ +// Block quotes + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function blockquote(state, startLine, endLine, silent) { + var adjustTab, + ch, + i, + initial, + l, + lastLineEmpty, + lines, + nextLine, + offset, + oldBMarks, + oldBSCount, + oldIndent, + oldParentType, + oldSCount, + oldTShift, + spaceAfterMarker, + terminate, + terminatorRules, + token, + isOutdented, + oldLineMax = state.lineMax, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // check the block quote marker + if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; } + + // we know that it's going to be a valid blockquote, + // so no point trying to find the end of it in silent mode + if (silent) { return true; } + + // set offset past spaces and ">" + initial = offset = state.sCount[startLine] + 1; + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[startLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks = [ state.bMarks[startLine] ]; + state.bMarks[startLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[startLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + oldBSCount = [ state.bsCount[startLine] ]; + state.bsCount[startLine] = state.sCount[startLine] + 1 + (spaceAfterMarker ? 1 : 0); + + lastLineEmpty = pos >= max; + + oldSCount = [ state.sCount[startLine] ]; + state.sCount[startLine] = offset - initial; + + oldTShift = [ state.tShift[startLine] ]; + state.tShift[startLine] = pos - state.bMarks[startLine]; + + terminatorRules = state.md.block.ruler.getRules('blockquote'); + + oldParentType = state.parentType; + state.parentType = 'blockquote'; + + // Search the end of the block + // + // Block ends with either: + // 1. an empty line outside: + // ``` + // > test + // + // ``` + // 2. an empty line inside: + // ``` + // > + // test + // ``` + // 3. another tag: + // ``` + // > test + // - - - + // ``` + for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + // check if it's outdented, i.e. it's inside list item and indented + // less than said list item: + // + // ``` + // 1. anything + // > current blockquote + // 2. checking this line + // ``` + isOutdented = state.sCount[nextLine] < state.blkIndent; + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos >= max) { + // Case 1: line is not inside the blockquote, and this line is empty. + break; + } + + if (state.src.charCodeAt(pos++) === 0x3E/* > */ && !isOutdented) { + // This line is inside the blockquote. + + // set offset past spaces and ">" + initial = offset = state.sCount[nextLine] + 1; + + // skip one optional space after '>' + if (state.src.charCodeAt(pos) === 0x20 /* space */) { + // ' > test ' + // ^ -- position start of line here: + pos++; + initial++; + offset++; + adjustTab = false; + spaceAfterMarker = true; + } else if (state.src.charCodeAt(pos) === 0x09 /* tab */) { + spaceAfterMarker = true; + + if ((state.bsCount[nextLine] + offset) % 4 === 3) { + // ' >\t test ' + // ^ -- position start of line here (tab has width===1) + pos++; + initial++; + offset++; + adjustTab = false; + } else { + // ' >\t test ' + // ^ -- position start of line here + shift bsCount slightly + // to make extra space appear + adjustTab = true; + } + } else { + spaceAfterMarker = false; + } + + oldBMarks.push(state.bMarks[nextLine]); + state.bMarks[nextLine] = pos; + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (isSpace(ch)) { + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine] + (adjustTab ? 1 : 0)) % 4; + } else { + offset++; + } + } else { + break; + } + + pos++; + } + + lastLineEmpty = pos >= max; + + oldBSCount.push(state.bsCount[nextLine]); + state.bsCount[nextLine] = state.sCount[nextLine] + 1 + (spaceAfterMarker ? 1 : 0); + + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] = offset - initial; + + oldTShift.push(state.tShift[nextLine]); + state.tShift[nextLine] = pos - state.bMarks[nextLine]; + continue; + } + + // Case 2: line is not inside the blockquote, and the last line was empty. + if (lastLineEmpty) { break; } + + // Case 3: another tag found. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + + if (terminate) { + // Quirk to enforce "hard termination mode" for paragraphs; + // normally if you call `tokenize(state, startLine, nextLine)`, + // paragraphs will look below nextLine for paragraph continuation, + // but if blockquote is terminated by another tag, they shouldn't + state.lineMax = nextLine; + + if (state.blkIndent !== 0) { + // state.blkIndent was non-zero, we now set it to zero, + // so we need to re-calculate all offsets to appear as + // if indent wasn't changed + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + state.sCount[nextLine] -= state.blkIndent; + } + + break; + } + + oldBMarks.push(state.bMarks[nextLine]); + oldBSCount.push(state.bsCount[nextLine]); + oldTShift.push(state.tShift[nextLine]); + oldSCount.push(state.sCount[nextLine]); + + // A negative indentation means that this is a paragraph continuation + // + state.sCount[nextLine] = -1; + } + + oldIndent = state.blkIndent; + state.blkIndent = 0; + + token = state.push('blockquote_open', 'blockquote', 1); + token.markup = '>'; + token.map = lines = [ startLine, 0 ]; + + state.md.block.tokenize(state, startLine, nextLine); + + token = state.push('blockquote_close', 'blockquote', -1); + token.markup = '>'; + + state.lineMax = oldLineMax; + state.parentType = oldParentType; + lines[1] = state.line; + + // Restore original tShift; this might not be necessary since the parser + // has already been here, but just to make sure we can do that. + for (i = 0; i < oldTShift.length; i++) { + state.bMarks[i + startLine] = oldBMarks[i]; + state.tShift[i + startLine] = oldTShift[i]; + state.sCount[i + startLine] = oldSCount[i]; + state.bsCount[i + startLine] = oldBSCount[i]; + } + state.blkIndent = oldIndent; + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/code.js b/node_modules/markdown-it/lib/rules_block/code.js new file mode 100644 index 0000000..018e019 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/code.js @@ -0,0 +1,34 @@ +// Code block (4 spaces padded) + +'use strict'; + + +module.exports = function code(state, startLine, endLine/*, silent*/) { + var nextLine, last, token; + + if (state.sCount[startLine] - state.blkIndent < 4) { return false; } + + last = nextLine = startLine + 1; + + while (nextLine < endLine) { + if (state.isEmpty(nextLine)) { + nextLine++; + continue; + } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + nextLine++; + last = nextLine; + continue; + } + break; + } + + state.line = last; + + token = state.push('code_block', 'code', 0); + token.content = state.getLines(startLine, last, 4 + state.blkIndent, false) + '\n'; + token.map = [ startLine, state.line ]; + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/fence.js b/node_modules/markdown-it/lib/rules_block/fence.js new file mode 100644 index 0000000..44f1538 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/fence.js @@ -0,0 +1,98 @@ +// fences (``` lang, ~~~ lang) + +'use strict'; + + +module.exports = function fence(state, startLine, endLine, silent) { + var marker, len, params, nextLine, mem, token, markup, + haveEndMarker = false, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (pos + 3 > max) { return false; } + + marker = state.src.charCodeAt(pos); + + if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) { + return false; + } + + // scan marker length + mem = pos; + pos = state.skipChars(pos, marker); + + len = pos - mem; + + if (len < 3) { return false; } + + markup = state.src.slice(mem, pos); + params = state.src.slice(pos, max); + + if (marker === 0x60 /* ` */) { + if (params.indexOf(String.fromCharCode(marker)) >= 0) { + return false; + } + } + + // Since start is found, we can report success here in validation mode + if (silent) { return true; } + + // search end of block + nextLine = startLine; + + for (;;) { + nextLine++; + if (nextLine >= endLine) { + // unclosed block should be autoclosed by end of document. + // also block seems to be autoclosed by end of parent + break; + } + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max && state.sCount[nextLine] < state.blkIndent) { + // non-empty line with negative indent should stop the list: + // - ``` + // test + break; + } + + if (state.src.charCodeAt(pos) !== marker) { continue; } + + if (state.sCount[nextLine] - state.blkIndent >= 4) { + // closing fence should be indented less than 4 spaces + continue; + } + + pos = state.skipChars(pos, marker); + + // closing code fence must be at least as long as the opening one + if (pos - mem < len) { continue; } + + // make sure tail has spaces only + pos = state.skipSpaces(pos); + + if (pos < max) { continue; } + + haveEndMarker = true; + // found! + break; + } + + // If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine]; + + state.line = nextLine + (haveEndMarker ? 1 : 0); + + token = state.push('fence', 'code', 0); + token.info = params; + token.content = state.getLines(startLine + 1, nextLine, len, true); + token.markup = markup; + token.map = [ startLine, state.line ]; + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/heading.js b/node_modules/markdown-it/lib/rules_block/heading.js new file mode 100644 index 0000000..9863f48 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/heading.js @@ -0,0 +1,55 @@ +// heading (#, ##, ...) + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function heading(state, startLine, endLine, silent) { + var ch, level, tmp, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + ch = state.src.charCodeAt(pos); + + if (ch !== 0x23/* # */ || pos >= max) { return false; } + + // count heading level + level = 1; + ch = state.src.charCodeAt(++pos); + while (ch === 0x23/* # */ && pos < max && level <= 6) { + level++; + ch = state.src.charCodeAt(++pos); + } + + if (level > 6 || (pos < max && !isSpace(ch))) { return false; } + + if (silent) { return true; } + + // Let's cut tails like ' ### ' from the end of string + + max = state.skipSpacesBack(max, pos); + tmp = state.skipCharsBack(max, 0x23, pos); // # + if (tmp > pos && isSpace(state.src.charCodeAt(tmp - 1))) { + max = tmp; + } + + state.line = startLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = '########'.slice(0, level); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = state.src.slice(pos, max).trim(); + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = '########'.slice(0, level); + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/hr.js b/node_modules/markdown-it/lib/rules_block/hr.js new file mode 100644 index 0000000..a3bb14e --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/hr.js @@ -0,0 +1,45 @@ +// Horizontal rule + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function hr(state, startLine, endLine, silent) { + var marker, cnt, ch, token, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + marker = state.src.charCodeAt(pos++); + + // Check hr marker + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x5F/* _ */) { + return false; + } + + // markers can be mixed with spaces, but there should be at least 3 of them + + cnt = 1; + while (pos < max) { + ch = state.src.charCodeAt(pos++); + if (ch !== marker && !isSpace(ch)) { return false; } + if (ch === marker) { cnt++; } + } + + if (cnt < 3) { return false; } + + if (silent) { return true; } + + state.line = startLine + 1; + + token = state.push('hr', 'hr', 0); + token.map = [ startLine, state.line ]; + token.markup = Array(cnt + 1).join(String.fromCharCode(marker)); + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/html_block.js b/node_modules/markdown-it/lib/rules_block/html_block.js new file mode 100644 index 0000000..2f17675 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/html_block.js @@ -0,0 +1,74 @@ +// HTML block + +'use strict'; + + +var block_names = require('../common/html_blocks'); +var HTML_OPEN_CLOSE_TAG_RE = require('../common/html_re').HTML_OPEN_CLOSE_TAG_RE; + +// An array of opening and corresponding closing sequences for html tags, +// last argument defines whether it can terminate a paragraph or not +// +var HTML_SEQUENCES = [ + [ /^<(script|pre|style|textarea)(?=(\s|>|$))/i, /<\/(script|pre|style|textarea)>/i, true ], + [ /^<!--/, /-->/, true ], + [ /^<\?/, /\?>/, true ], + [ /^<![A-Z]/, />/, true ], + [ /^<!\[CDATA\[/, /\]\]>/, true ], + [ new RegExp('^</?(' + block_names.join('|') + ')(?=(\\s|/?>|$))', 'i'), /^$/, true ], + [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false ] +]; + + +module.exports = function html_block(state, startLine, endLine, silent) { + var i, nextLine, token, lineText, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (!state.md.options.html) { return false; } + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + lineText = state.src.slice(pos, max); + + for (i = 0; i < HTML_SEQUENCES.length; i++) { + if (HTML_SEQUENCES[i][0].test(lineText)) { break; } + } + + if (i === HTML_SEQUENCES.length) { return false; } + + if (silent) { + // true if this sequence can be a terminator, false otherwise + return HTML_SEQUENCES[i][2]; + } + + nextLine = startLine + 1; + + // If we are here - we detected HTML block. + // Let's roll down till block end. + if (!HTML_SEQUENCES[i][1].test(lineText)) { + for (; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + lineText = state.src.slice(pos, max); + + if (HTML_SEQUENCES[i][1].test(lineText)) { + if (lineText.length !== 0) { nextLine++; } + break; + } + } + } + + state.line = nextLine; + + token = state.push('html_block', '', 0); + token.map = [ startLine, nextLine ]; + token.content = state.getLines(startLine, nextLine, state.blkIndent, true); + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/lheading.js b/node_modules/markdown-it/lib/rules_block/lheading.js new file mode 100644 index 0000000..19bdc39 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/lheading.js @@ -0,0 +1,83 @@ +// lheading (---, ===) + +'use strict'; + + +module.exports = function lheading(state, startLine, endLine/*, silent*/) { + var content, terminate, i, l, token, pos, max, level, marker, + nextLine = startLine + 1, oldParentType, + terminatorRules = state.md.block.ruler.getRules('paragraph'); + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + oldParentType = state.parentType; + state.parentType = 'paragraph'; // use paragraph to match terminatorRules + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // + // Check for underline in setext header + // + if (state.sCount[nextLine] >= state.blkIndent) { + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + max = state.eMarks[nextLine]; + + if (pos < max) { + marker = state.src.charCodeAt(pos); + + if (marker === 0x2D/* - */ || marker === 0x3D/* = */) { + pos = state.skipChars(pos, marker); + pos = state.skipSpaces(pos); + + if (pos >= max) { + level = (marker === 0x3D/* = */ ? 1 : 2); + break; + } + } + } + } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + if (!level) { + // Didn't find valid underline + return false; + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine + 1; + + token = state.push('heading_open', 'h' + String(level), 1); + token.markup = String.fromCharCode(marker); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line - 1 ]; + token.children = []; + + token = state.push('heading_close', 'h' + String(level), -1); + token.markup = String.fromCharCode(marker); + + state.parentType = oldParentType; + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/list.js b/node_modules/markdown-it/lib/rules_block/list.js new file mode 100644 index 0000000..1e5e87b --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/list.js @@ -0,0 +1,364 @@ +// Lists + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +// Search `[-+*][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipBulletListMarker(state, startLine) { + var marker, pos, max, ch; + + pos = state.bMarks[startLine] + state.tShift[startLine]; + max = state.eMarks[startLine]; + + marker = state.src.charCodeAt(pos++); + // Check bullet + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x2B/* + */) { + return -1; + } + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " -test " - is not a list item + return -1; + } + } + + return pos; +} + +// Search `\d+[.)][\n ]`, returns next pos after marker on success +// or -1 on fail. +function skipOrderedListMarker(state, startLine) { + var ch, + start = state.bMarks[startLine] + state.tShift[startLine], + pos = start, + max = state.eMarks[startLine]; + + // List marker should have at least 2 chars (digit + dot) + if (pos + 1 >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch < 0x30/* 0 */ || ch > 0x39/* 9 */) { return -1; } + + for (;;) { + // EOL -> fail + if (pos >= max) { return -1; } + + ch = state.src.charCodeAt(pos++); + + if (ch >= 0x30/* 0 */ && ch <= 0x39/* 9 */) { + + // List marker should have no more than 9 digits + // (prevents integer overflow in browsers) + if (pos - start >= 10) { return -1; } + + continue; + } + + // found valid marker + if (ch === 0x29/* ) */ || ch === 0x2e/* . */) { + break; + } + + return -1; + } + + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (!isSpace(ch)) { + // " 1.test " - is not a list item + return -1; + } + } + return pos; +} + +function markTightParagraphs(state, idx) { + var i, l, + level = state.level + 2; + + for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { + if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { + state.tokens[i + 2].hidden = true; + state.tokens[i].hidden = true; + i += 2; + } + } +} + + +module.exports = function list(state, startLine, endLine, silent) { + var ch, + contentStart, + i, + indent, + indentAfterMarker, + initial, + isOrdered, + itemLines, + l, + listLines, + listTokIdx, + markerCharCode, + markerValue, + max, + nextLine, + offset, + oldListIndent, + oldParentType, + oldSCount, + oldTShift, + oldTight, + pos, + posAfterMarker, + prevEmptyEnd, + start, + terminate, + terminatorRules, + token, + isTerminatingParagraph = false, + tight = true; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + // Special case: + // - item 1 + // - item 2 + // - item 3 + // - item 4 + // - this one is a paragraph continuation + if (state.listIndent >= 0 && + state.sCount[startLine] - state.listIndent >= 4 && + state.sCount[startLine] < state.blkIndent) { + return false; + } + + // limit conditions when list can interrupt + // a paragraph (validation mode only) + if (silent && state.parentType === 'paragraph') { + // Next list item should still terminate previous list item; + // + // This code can fail if plugins use blkIndent as well as lists, + // but I hope the spec gets fixed long before that happens. + // + if (state.sCount[startLine] >= state.blkIndent) { + isTerminatingParagraph = true; + } + } + + // Detect list type and position after marker + if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { + isOrdered = true; + start = state.bMarks[startLine] + state.tShift[startLine]; + markerValue = Number(state.src.slice(start, posAfterMarker - 1)); + + // If we're starting a new ordered list right after + // a paragraph, it should start with 1. + if (isTerminatingParagraph && markerValue !== 1) return false; + + } else if ((posAfterMarker = skipBulletListMarker(state, startLine)) >= 0) { + isOrdered = false; + + } else { + return false; + } + + // If we're starting a new unordered list right after + // a paragraph, first line should not be empty. + if (isTerminatingParagraph) { + if (state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]) return false; + } + + // We should terminate list on style change. Remember first one to compare. + markerCharCode = state.src.charCodeAt(posAfterMarker - 1); + + // For validation mode we can terminate immediately + if (silent) { return true; } + + // Start list + listTokIdx = state.tokens.length; + + if (isOrdered) { + token = state.push('ordered_list_open', 'ol', 1); + if (markerValue !== 1) { + token.attrs = [ [ 'start', markerValue ] ]; + } + + } else { + token = state.push('bullet_list_open', 'ul', 1); + } + + token.map = listLines = [ startLine, 0 ]; + token.markup = String.fromCharCode(markerCharCode); + + // + // Iterate list items + // + + nextLine = startLine; + prevEmptyEnd = false; + terminatorRules = state.md.block.ruler.getRules('list'); + + oldParentType = state.parentType; + state.parentType = 'list'; + + while (nextLine < endLine) { + pos = posAfterMarker; + max = state.eMarks[nextLine]; + + initial = offset = state.sCount[nextLine] + posAfterMarker - (state.bMarks[startLine] + state.tShift[startLine]); + + while (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch === 0x09) { + offset += 4 - (offset + state.bsCount[nextLine]) % 4; + } else if (ch === 0x20) { + offset++; + } else { + break; + } + + pos++; + } + + contentStart = pos; + + if (contentStart >= max) { + // trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1; + } else { + indentAfterMarker = offset - initial; + } + + // If we have more than 4 spaces, the indent is 1 + // (the rest is just indented code block) + if (indentAfterMarker > 4) { indentAfterMarker = 1; } + + // " - test" + // ^^^^^ - calculating total length of this thing + indent = initial + indentAfterMarker; + + // Run subparser & write tokens + token = state.push('list_item_open', 'li', 1); + token.markup = String.fromCharCode(markerCharCode); + token.map = itemLines = [ startLine, 0 ]; + if (isOrdered) { + token.info = state.src.slice(start, posAfterMarker - 1); + } + + // change current state, then restore it after parser subcall + oldTight = state.tight; + oldTShift = state.tShift[startLine]; + oldSCount = state.sCount[startLine]; + + // - example list + // ^ listIndent position will be here + // ^ blkIndent position will be here + // + oldListIndent = state.listIndent; + state.listIndent = state.blkIndent; + state.blkIndent = indent; + + state.tight = true; + state.tShift[startLine] = contentStart - state.bMarks[startLine]; + state.sCount[startLine] = offset; + + if (contentStart >= max && state.isEmpty(startLine + 1)) { + // workaround for this case + // (list item is empty, list terminates before "foo"): + // ~~~~~~~~ + // - + // + // foo + // ~~~~~~~~ + state.line = Math.min(state.line + 2, endLine); + } else { + state.md.block.tokenize(state, startLine, endLine, true); + } + + // If any of list item is tight, mark list as tight + if (!state.tight || prevEmptyEnd) { + tight = false; + } + // Item become loose if finish with empty line, + // but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - startLine) > 1 && state.isEmpty(state.line - 1); + + state.blkIndent = state.listIndent; + state.listIndent = oldListIndent; + state.tShift[startLine] = oldTShift; + state.sCount[startLine] = oldSCount; + state.tight = oldTight; + + token = state.push('list_item_close', 'li', -1); + token.markup = String.fromCharCode(markerCharCode); + + nextLine = startLine = state.line; + itemLines[1] = nextLine; + contentStart = state.bMarks[startLine]; + + if (nextLine >= endLine) { break; } + + // + // Try to check if list is terminated or continued. + // + if (state.sCount[nextLine] < state.blkIndent) { break; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { break; } + + // fail if terminating block found + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + + // fail if list has another type + if (isOrdered) { + posAfterMarker = skipOrderedListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + start = state.bMarks[nextLine] + state.tShift[nextLine]; + } else { + posAfterMarker = skipBulletListMarker(state, nextLine); + if (posAfterMarker < 0) { break; } + } + + if (markerCharCode !== state.src.charCodeAt(posAfterMarker - 1)) { break; } + } + + // Finalize list + if (isOrdered) { + token = state.push('ordered_list_close', 'ol', -1); + } else { + token = state.push('bullet_list_close', 'ul', -1); + } + token.markup = String.fromCharCode(markerCharCode); + + listLines[1] = nextLine; + state.line = nextLine; + + state.parentType = oldParentType; + + // mark paragraphs tight if needed + if (tight) { + markTightParagraphs(state, listTokIdx); + } + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/paragraph.js b/node_modules/markdown-it/lib/rules_block/paragraph.js new file mode 100644 index 0000000..f0c6872 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/paragraph.js @@ -0,0 +1,52 @@ +// Paragraph + +'use strict'; + + +module.exports = function paragraph(state, startLine/*, endLine*/) { + var content, terminate, i, l, token, oldParentType, + nextLine = startLine + 1, + terminatorRules = state.md.block.ruler.getRules('paragraph'), + endLine = state.lineMax; + + oldParentType = state.parentType; + state.parentType = 'paragraph'; + + // jump line-by-line until empty one or EOF + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + + state.line = nextLine; + + token = state.push('paragraph_open', 'p', 1); + token.map = [ startLine, state.line ]; + + token = state.push('inline', '', 0); + token.content = content; + token.map = [ startLine, state.line ]; + token.children = []; + + token = state.push('paragraph_close', 'p', -1); + + state.parentType = oldParentType; + + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/reference.js b/node_modules/markdown-it/lib/rules_block/reference.js new file mode 100644 index 0000000..78daa26 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/reference.js @@ -0,0 +1,198 @@ +'use strict'; + + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function reference(state, startLine, _endLine, silent) { + var ch, + destEndPos, + destEndLineNo, + endLine, + href, + i, + l, + label, + labelEnd, + oldParentType, + res, + start, + str, + terminate, + terminatorRules, + title, + lines = 0, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine], + nextLine = startLine + 1; + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + + if (state.src.charCodeAt(pos) !== 0x5B/* [ */) { return false; } + + // Simple check to quickly interrupt scan on [link](url) at the start of line. + // Can be useful on practice: https://github.com/markdown-it/markdown-it/issues/54 + while (++pos < max) { + if (state.src.charCodeAt(pos) === 0x5D /* ] */ && + state.src.charCodeAt(pos - 1) !== 0x5C/* \ */) { + if (pos + 1 === max) { return false; } + if (state.src.charCodeAt(pos + 1) !== 0x3A/* : */) { return false; } + break; + } + } + + endLine = state.lineMax; + + // jump line-by-line until empty one or EOF + terminatorRules = state.md.block.ruler.getRules('reference'); + + oldParentType = state.parentType; + state.parentType = 'reference'; + + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.sCount[nextLine] - state.blkIndent > 3) { continue; } + + // quirk for blockquotes, this line should already be checked by that rule + if (state.sCount[nextLine] < 0) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + if (terminate) { break; } + } + + str = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); + max = str.length; + + for (pos = 1; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x5B /* [ */) { + return false; + } else if (ch === 0x5D /* ] */) { + labelEnd = pos; + break; + } else if (ch === 0x0A /* \n */) { + lines++; + } else if (ch === 0x5C /* \ */) { + pos++; + if (pos < max && str.charCodeAt(pos) === 0x0A) { + lines++; + } + } + } + + if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return false; } + + // [label]: destination 'title' + // ^^^ skip optional whitespace here + for (pos = labelEnd + 2; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^^^^^ parse this + res = state.md.helpers.parseLinkDestination(str, pos, max); + if (!res.ok) { return false; } + + href = state.md.normalizeLink(res.str); + if (!state.md.validateLink(href)) { return false; } + + pos = res.pos; + lines += res.lines; + + // save cursor state, we could require to rollback later + destEndPos = pos; + destEndLineNo = lines; + + // [label]: destination 'title' + // ^^^ skipping those spaces + start = pos; + for (; pos < max; pos++) { + ch = str.charCodeAt(pos); + if (ch === 0x0A) { + lines++; + } else if (isSpace(ch)) { + /*eslint no-empty:0*/ + } else { + break; + } + } + + // [label]: destination 'title' + // ^^^^^^^ parse this + res = state.md.helpers.parseLinkTitle(str, pos, max); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + lines += res.lines; + } else { + title = ''; + pos = destEndPos; + lines = destEndLineNo; + } + + // skip trailing spaces until the rest of the line + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + if (title) { + // garbage at the end of the line after title, + // but it could still be a valid reference if we roll back + title = ''; + pos = destEndPos; + lines = destEndLineNo; + while (pos < max) { + ch = str.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + } + } + + if (pos < max && str.charCodeAt(pos) !== 0x0A) { + // garbage at the end of the line + return false; + } + + label = normalizeReference(str.slice(1, labelEnd)); + if (!label) { + // CommonMark 0.20 disallows empty labels + return false; + } + + // Reference can not terminate anything. This check is for safety only. + /*istanbul ignore if*/ + if (silent) { return true; } + + if (typeof state.env.references === 'undefined') { + state.env.references = {}; + } + if (typeof state.env.references[label] === 'undefined') { + state.env.references[label] = { title: title, href: href }; + } + + state.parentType = oldParentType; + + state.line = startLine + lines + 1; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_block/state_block.js b/node_modules/markdown-it/lib/rules_block/state_block.js new file mode 100644 index 0000000..e42cb4b --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/state_block.js @@ -0,0 +1,231 @@ +// Parser state class + +'use strict'; + +var Token = require('../token'); +var isSpace = require('../common/utils').isSpace; + + +function StateBlock(src, md, env, tokens) { + var ch, s, start, pos, len, indent, offset, indent_found; + + this.src = src; + + // link to parser instance + this.md = md; + + this.env = env; + + // + // Internal state vartiables + // + + this.tokens = tokens; + + this.bMarks = []; // line begin offsets for fast jumps + this.eMarks = []; // line end offsets for fast jumps + this.tShift = []; // offsets of the first non-space characters (tabs not expanded) + this.sCount = []; // indents for each line (tabs expanded) + + // An amount of virtual spaces (tabs expanded) between beginning + // of each line (bMarks) and real beginning of that line. + // + // It exists only as a hack because blockquotes override bMarks + // losing information in the process. + // + // It's used only when expanding tabs, you can think about it as + // an initial tab length, e.g. bsCount=21 applied to string `\t123` + // means first tab should be expanded to 4-21%4 === 3 spaces. + // + this.bsCount = []; + + // block parser variables + this.blkIndent = 0; // required block content indent (for example, if we are + // inside a list, it would be positioned after list marker) + this.line = 0; // line index in src + this.lineMax = 0; // lines count + this.tight = false; // loose/tight mode for lists + this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any) + this.listIndent = -1; // indent of the current list block (-1 if there isn't any) + + // can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' + // used in lists to determine if they interrupt a paragraph + this.parentType = 'root'; + + this.level = 0; + + // renderer + this.result = ''; + + // Create caches + // Generate markers. + s = this.src; + indent_found = false; + + for (start = pos = indent = offset = 0, len = s.length; pos < len; pos++) { + ch = s.charCodeAt(pos); + + if (!indent_found) { + if (isSpace(ch)) { + indent++; + + if (ch === 0x09) { + offset += 4 - offset % 4; + } else { + offset++; + } + continue; + } else { + indent_found = true; + } + } + + if (ch === 0x0A || pos === len - 1) { + if (ch !== 0x0A) { pos++; } + this.bMarks.push(start); + this.eMarks.push(pos); + this.tShift.push(indent); + this.sCount.push(offset); + this.bsCount.push(0); + + indent_found = false; + indent = 0; + offset = 0; + start = pos + 1; + } + } + + // Push fake entry to simplify cache bounds checks + this.bMarks.push(s.length); + this.eMarks.push(s.length); + this.tShift.push(0); + this.sCount.push(0); + this.bsCount.push(0); + + this.lineMax = this.bMarks.length - 1; // don't count last fake line +} + +// Push new token to "stream". +// +StateBlock.prototype.push = function (type, tag, nesting) { + var token = new Token(type, tag, nesting); + token.block = true; + + if (nesting < 0) this.level--; // closing tag + token.level = this.level; + if (nesting > 0) this.level++; // opening tag + + this.tokens.push(token); + return token; +}; + +StateBlock.prototype.isEmpty = function isEmpty(line) { + return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; +}; + +StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) { + for (var max = this.lineMax; from < max; from++) { + if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) { + break; + } + } + return from; +}; + +// Skip spaces from given position. +StateBlock.prototype.skipSpaces = function skipSpaces(pos) { + var ch; + + for (var max = this.src.length; pos < max; pos++) { + ch = this.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + } + return pos; +}; + +// Skip spaces from given position in reverse. +StateBlock.prototype.skipSpacesBack = function skipSpacesBack(pos, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (!isSpace(this.src.charCodeAt(--pos))) { return pos + 1; } + } + return pos; +}; + +// Skip char codes from given position +StateBlock.prototype.skipChars = function skipChars(pos, code) { + for (var max = this.src.length; pos < max; pos++) { + if (this.src.charCodeAt(pos) !== code) { break; } + } + return pos; +}; + +// Skip char codes reverse from given position - 1 +StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) { + if (pos <= min) { return pos; } + + while (pos > min) { + if (code !== this.src.charCodeAt(--pos)) { return pos + 1; } + } + return pos; +}; + +// cut lines range from source. +StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) { + var i, lineIndent, ch, first, last, queue, lineStart, + line = begin; + + if (begin >= end) { + return ''; + } + + queue = new Array(end - begin); + + for (i = 0; line < end; line++, i++) { + lineIndent = 0; + lineStart = first = this.bMarks[line]; + + if (line + 1 < end || keepLastLF) { + // No need for bounds check because we have fake entry on tail. + last = this.eMarks[line] + 1; + } else { + last = this.eMarks[line]; + } + + while (first < last && lineIndent < indent) { + ch = this.src.charCodeAt(first); + + if (isSpace(ch)) { + if (ch === 0x09) { + lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; + } else { + lineIndent++; + } + } else if (first - lineStart < this.tShift[line]) { + // patched tShift masked characters to look like spaces (blockquotes, list markers) + lineIndent++; + } else { + break; + } + + first++; + } + + if (lineIndent > indent) { + // partially expanding tabs in code blocks, e.g '\t\tfoobar' + // with indent=2 becomes ' \tfoobar' + queue[i] = new Array(lineIndent - indent + 1).join(' ') + this.src.slice(first, last); + } else { + queue[i] = this.src.slice(first, last); + } + } + + return queue.join(''); +}; + +// re-export Token class to use in block rules +StateBlock.prototype.Token = Token; + + +module.exports = StateBlock; diff --git a/node_modules/markdown-it/lib/rules_block/table.js b/node_modules/markdown-it/lib/rules_block/table.js new file mode 100644 index 0000000..7d9208f --- /dev/null +++ b/node_modules/markdown-it/lib/rules_block/table.js @@ -0,0 +1,221 @@ +// GFM table, https://github.github.com/gfm/#tables-extension- + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +function getLine(state, line) { + var pos = state.bMarks[line] + state.tShift[line], + max = state.eMarks[line]; + + return state.src.substr(pos, max - pos); +} + +function escapedSplit(str) { + var result = [], + pos = 0, + max = str.length, + ch, + isEscaped = false, + lastPos = 0, + current = ''; + + ch = str.charCodeAt(pos); + + while (pos < max) { + if (ch === 0x7c/* | */) { + if (!isEscaped) { + // pipe separating cells, '|' + result.push(current + str.substring(lastPos, pos)); + current = ''; + lastPos = pos + 1; + } else { + // escaped pipe, '\|' + current += str.substring(lastPos, pos - 1); + lastPos = pos; + } + } + + isEscaped = (ch === 0x5c/* \ */); + pos++; + + ch = str.charCodeAt(pos); + } + + result.push(current + str.substring(lastPos)); + + return result; +} + + +module.exports = function table(state, startLine, endLine, silent) { + var ch, lineText, pos, i, l, nextLine, columns, columnCount, token, + aligns, t, tableLines, tbodyLines, oldParentType, terminate, + terminatorRules, firstCh, secondCh; + + // should have at least two lines + if (startLine + 2 > endLine) { return false; } + + nextLine = startLine + 1; + + if (state.sCount[nextLine] < state.blkIndent) { return false; } + + // if it's indented more than 3 spaces, it should be a code block + if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; } + + // first character of the second line should be '|', '-', ':', + // and no other characters are allowed but spaces; + // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp + + pos = state.bMarks[nextLine] + state.tShift[nextLine]; + if (pos >= state.eMarks[nextLine]) { return false; } + + firstCh = state.src.charCodeAt(pos++); + if (firstCh !== 0x7C/* | */ && firstCh !== 0x2D/* - */ && firstCh !== 0x3A/* : */) { return false; } + + if (pos >= state.eMarks[nextLine]) { return false; } + + secondCh = state.src.charCodeAt(pos++); + if (secondCh !== 0x7C/* | */ && secondCh !== 0x2D/* - */ && secondCh !== 0x3A/* : */ && !isSpace(secondCh)) { + return false; + } + + // if first character is '-', then second character must not be a space + // (due to parsing ambiguity with list) + if (firstCh === 0x2D/* - */ && isSpace(secondCh)) { return false; } + + while (pos < state.eMarks[nextLine]) { + ch = state.src.charCodeAt(pos); + + if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; } + + pos++; + } + + lineText = getLine(state, startLine + 1); + + columns = lineText.split('|'); + aligns = []; + for (i = 0; i < columns.length; i++) { + t = columns[i].trim(); + if (!t) { + // allow empty columns before and after table, but not in between columns; + // e.g. allow ` |---| `, disallow ` ---||--- ` + if (i === 0 || i === columns.length - 1) { + continue; + } else { + return false; + } + } + + if (!/^:?-+:?$/.test(t)) { return false; } + if (t.charCodeAt(t.length - 1) === 0x3A/* : */) { + aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right'); + } else if (t.charCodeAt(0) === 0x3A/* : */) { + aligns.push('left'); + } else { + aligns.push(''); + } + } + + lineText = getLine(state, startLine).trim(); + if (lineText.indexOf('|') === -1) { return false; } + if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } + columns = escapedSplit(lineText); + if (columns.length && columns[0] === '') columns.shift(); + if (columns.length && columns[columns.length - 1] === '') columns.pop(); + + // header row will define an amount of columns in the entire table, + // and align row should be exactly the same (the rest of the rows can differ) + columnCount = columns.length; + if (columnCount === 0 || columnCount !== aligns.length) { return false; } + + if (silent) { return true; } + + oldParentType = state.parentType; + state.parentType = 'table'; + + // use 'blockquote' lists for termination because it's + // the most similar to tables + terminatorRules = state.md.block.ruler.getRules('blockquote'); + + token = state.push('table_open', 'table', 1); + token.map = tableLines = [ startLine, 0 ]; + + token = state.push('thead_open', 'thead', 1); + token.map = [ startLine, startLine + 1 ]; + + token = state.push('tr_open', 'tr', 1); + token.map = [ startLine, startLine + 1 ]; + + for (i = 0; i < columns.length; i++) { + token = state.push('th_open', 'th', 1); + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i].trim(); + token.children = []; + + token = state.push('th_close', 'th', -1); + } + + token = state.push('tr_close', 'tr', -1); + token = state.push('thead_close', 'thead', -1); + + for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { + if (state.sCount[nextLine] < state.blkIndent) { break; } + + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } + } + + if (terminate) { break; } + lineText = getLine(state, nextLine).trim(); + if (!lineText) { break; } + if (state.sCount[nextLine] - state.blkIndent >= 4) { break; } + columns = escapedSplit(lineText); + if (columns.length && columns[0] === '') columns.shift(); + if (columns.length && columns[columns.length - 1] === '') columns.pop(); + + if (nextLine === startLine + 2) { + token = state.push('tbody_open', 'tbody', 1); + token.map = tbodyLines = [ startLine + 2, 0 ]; + } + + token = state.push('tr_open', 'tr', 1); + token.map = [ nextLine, nextLine + 1 ]; + + for (i = 0; i < columnCount; i++) { + token = state.push('td_open', 'td', 1); + if (aligns[i]) { + token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ]; + } + + token = state.push('inline', '', 0); + token.content = columns[i] ? columns[i].trim() : ''; + token.children = []; + + token = state.push('td_close', 'td', -1); + } + token = state.push('tr_close', 'tr', -1); + } + + if (tbodyLines) { + token = state.push('tbody_close', 'tbody', -1); + tbodyLines[1] = nextLine; + } + + token = state.push('table_close', 'table', -1); + tableLines[1] = nextLine; + + state.parentType = oldParentType; + state.line = nextLine; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_core/block.js b/node_modules/markdown-it/lib/rules_core/block.js new file mode 100644 index 0000000..2a365fa --- /dev/null +++ b/node_modules/markdown-it/lib/rules_core/block.js @@ -0,0 +1,16 @@ +'use strict'; + + +module.exports = function block(state) { + var token; + + if (state.inlineMode) { + token = new state.Token('inline', '', 0); + token.content = state.src; + token.map = [ 0, 1 ]; + token.children = []; + state.tokens.push(token); + } else { + state.md.block.parse(state.src, state.md, state.env, state.tokens); + } +}; diff --git a/node_modules/markdown-it/lib/rules_core/inline.js b/node_modules/markdown-it/lib/rules_core/inline.js new file mode 100644 index 0000000..4c33d0d --- /dev/null +++ b/node_modules/markdown-it/lib/rules_core/inline.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = function inline(state) { + var tokens = state.tokens, tok, i, l; + + // Parse inlines + for (i = 0, l = tokens.length; i < l; i++) { + tok = tokens[i]; + if (tok.type === 'inline') { + state.md.inline.parse(tok.content, state.md, state.env, tok.children); + } + } +}; diff --git a/node_modules/markdown-it/lib/rules_core/linkify.js b/node_modules/markdown-it/lib/rules_core/linkify.js new file mode 100644 index 0000000..7c3ffc8 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_core/linkify.js @@ -0,0 +1,133 @@ +// Replace link-like texts with link nodes. +// +// Currently restricted by `md.validateLink()` to http/https/ftp +// +'use strict'; + + +var arrayReplaceAt = require('../common/utils').arrayReplaceAt; + + +function isLinkOpen(str) { + return /^<a[>\s]/i.test(str); +} +function isLinkClose(str) { + return /^<\/a\s*>/i.test(str); +} + + +module.exports = function linkify(state) { + var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, + level, htmlLinkLevel, url, fullUrl, urlText, + blockTokens = state.tokens, + links; + + if (!state.md.options.linkify) { return; } + + for (j = 0, l = blockTokens.length; j < l; j++) { + if (blockTokens[j].type !== 'inline' || + !state.md.linkify.pretest(blockTokens[j].content)) { + continue; + } + + tokens = blockTokens[j].children; + + htmlLinkLevel = 0; + + // We scan from the end, to keep position when new tags added. + // Use reversed logic in links start/end match + for (i = tokens.length - 1; i >= 0; i--) { + currentToken = tokens[i]; + + // Skip content of markdown links + if (currentToken.type === 'link_close') { + i--; + while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') { + i--; + } + continue; + } + + // Skip content of html tag links + if (currentToken.type === 'html_inline') { + if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) { + htmlLinkLevel--; + } + if (isLinkClose(currentToken.content)) { + htmlLinkLevel++; + } + } + if (htmlLinkLevel > 0) { continue; } + + if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) { + + text = currentToken.content; + links = state.md.linkify.match(text); + + // Now split string to nodes + nodes = []; + level = currentToken.level; + lastPos = 0; + + for (ln = 0; ln < links.length; ln++) { + + url = links[ln].url; + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { continue; } + + urlText = links[ln].text; + + // Linkifier might send raw hostnames like "example.com", where url + // starts with domain name. So we prepend http:// in those cases, + // and remove it afterwards. + // + if (!links[ln].schema) { + urlText = state.md.normalizeLinkText('http://' + urlText).replace(/^http:\/\//, ''); + } else if (links[ln].schema === 'mailto:' && !/^mailto:/i.test(urlText)) { + urlText = state.md.normalizeLinkText('mailto:' + urlText).replace(/^mailto:/, ''); + } else { + urlText = state.md.normalizeLinkText(urlText); + } + + pos = links[ln].index; + + if (pos > lastPos) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos, pos); + token.level = level; + nodes.push(token); + } + + token = new state.Token('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.level = level++; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + token = new state.Token('text', '', 0); + token.content = urlText; + token.level = level; + nodes.push(token); + + token = new state.Token('link_close', 'a', -1); + token.level = --level; + token.markup = 'linkify'; + token.info = 'auto'; + nodes.push(token); + + lastPos = links[ln].lastIndex; + } + if (lastPos < text.length) { + token = new state.Token('text', '', 0); + token.content = text.slice(lastPos); + token.level = level; + nodes.push(token); + } + + // replace current node + blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); + } + } + } +}; diff --git a/node_modules/markdown-it/lib/rules_core/normalize.js b/node_modules/markdown-it/lib/rules_core/normalize.js new file mode 100644 index 0000000..ad196cd --- /dev/null +++ b/node_modules/markdown-it/lib/rules_core/normalize.js @@ -0,0 +1,21 @@ +// Normalize input string + +'use strict'; + + +// https://spec.commonmark.org/0.29/#line-ending +var NEWLINES_RE = /\r\n?|\n/g; +var NULL_RE = /\0/g; + + +module.exports = function normalize(state) { + var str; + + // Normalize newlines + str = state.src.replace(NEWLINES_RE, '\n'); + + // Replace NULL characters + str = str.replace(NULL_RE, '\uFFFD'); + + state.src = str; +}; diff --git a/node_modules/markdown-it/lib/rules_core/replacements.js b/node_modules/markdown-it/lib/rules_core/replacements.js new file mode 100644 index 0000000..533496f --- /dev/null +++ b/node_modules/markdown-it/lib/rules_core/replacements.js @@ -0,0 +1,107 @@ +// Simple typographic replacements +// +// (c) (C) → © +// (tm) (TM) → ™ +// (r) (R) → ® +// +- → ± +// (p) (P) -> § +// ... → … (also ?.... → ?.., !.... → !..) +// ???????? → ???, !!!!! → !!!, `,,` → `,` +// -- → –, --- → — +// +'use strict'; + +// TODO: +// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ +// - miltiplication 2 x 4 -> 2 × 4 + +var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; + +// Workaround for phantomjs - need regex without /g flag, +// or root check will fail every second time +var SCOPED_ABBR_TEST_RE = /\((c|tm|r|p)\)/i; + +var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig; +var SCOPED_ABBR = { + c: '©', + r: '®', + p: '§', + tm: '™' +}; + +function replaceFn(match, name) { + return SCOPED_ABBR[name.toLowerCase()]; +} + +function replace_scoped(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } +} + +function replace_rare(inlineTokens) { + var i, token, inside_autolink = 0; + + for (i = inlineTokens.length - 1; i >= 0; i--) { + token = inlineTokens[i]; + + if (token.type === 'text' && !inside_autolink) { + if (RARE_RE.test(token.content)) { + token.content = token.content + .replace(/\+-/g, '±') + // .., ..., ....... -> … + // but ?..... & !..... -> ?.. & !.. + .replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..') + .replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',') + // em-dash + .replace(/(^|[^-])---(?=[^-]|$)/mg, '$1\u2014') + // en-dash + .replace(/(^|\s)--(?=\s|$)/mg, '$1\u2013') + .replace(/(^|[^-\s])--(?=[^-\s]|$)/mg, '$1\u2013'); + } + } + + if (token.type === 'link_open' && token.info === 'auto') { + inside_autolink--; + } + + if (token.type === 'link_close' && token.info === 'auto') { + inside_autolink++; + } + } +} + + +module.exports = function replace(state) { + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline') { continue; } + + if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) { + replace_scoped(state.tokens[blkIdx].children); + } + + if (RARE_RE.test(state.tokens[blkIdx].content)) { + replace_rare(state.tokens[blkIdx].children); + } + + } +}; diff --git a/node_modules/markdown-it/lib/rules_core/smartquotes.js b/node_modules/markdown-it/lib/rules_core/smartquotes.js new file mode 100644 index 0000000..e96fc71 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_core/smartquotes.js @@ -0,0 +1,201 @@ +// Convert straight quotation marks to typographic ones +// +'use strict'; + + +var isWhiteSpace = require('../common/utils').isWhiteSpace; +var isPunctChar = require('../common/utils').isPunctChar; +var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + +var QUOTE_TEST_RE = /['"]/; +var QUOTE_RE = /['"]/g; +var APOSTROPHE = '\u2019'; /* ’ */ + + +function replaceAt(str, index, ch) { + return str.substr(0, index) + ch + str.substr(index + 1); +} + +function process_inlines(tokens, state) { + var i, token, text, t, pos, max, thisLevel, item, lastChar, nextChar, + isLastPunctChar, isNextPunctChar, isLastWhiteSpace, isNextWhiteSpace, + canOpen, canClose, j, isSingle, stack, openQuote, closeQuote; + + stack = []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + + thisLevel = tokens[i].level; + + for (j = stack.length - 1; j >= 0; j--) { + if (stack[j].level <= thisLevel) { break; } + } + stack.length = j + 1; + + if (token.type !== 'text') { continue; } + + text = token.content; + pos = 0; + max = text.length; + + /*eslint no-labels:0,block-scoped-var:0*/ + OUTER: + while (pos < max) { + QUOTE_RE.lastIndex = pos; + t = QUOTE_RE.exec(text); + if (!t) { break; } + + canOpen = canClose = true; + pos = t.index + 1; + isSingle = (t[0] === "'"); + + // Find previous character, + // default to space if it's the beginning of the line + // + lastChar = 0x20; + + if (t.index - 1 >= 0) { + lastChar = text.charCodeAt(t.index - 1); + } else { + for (j = i - 1; j >= 0; j--) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // lastChar defaults to 0x20 + if (!tokens[j].content) continue; // should skip all tokens except 'text', 'html_inline' or 'code_inline' + + lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); + break; + } + } + + // Find next character, + // default to space if it's the end of the line + // + nextChar = 0x20; + + if (pos < max) { + nextChar = text.charCodeAt(pos); + } else { + for (j = i + 1; j < tokens.length; j++) { + if (tokens[j].type === 'softbreak' || tokens[j].type === 'hardbreak') break; // nextChar defaults to 0x20 + if (!tokens[j].content) continue; // should skip all tokens except 'text', 'html_inline' or 'code_inline' + + nextChar = tokens[j].content.charCodeAt(0); + break; + } + } + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + canOpen = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + canOpen = false; + } + } + + if (isLastWhiteSpace) { + canClose = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + canClose = false; + } + } + + if (nextChar === 0x22 /* " */ && t[0] === '"') { + if (lastChar >= 0x30 /* 0 */ && lastChar <= 0x39 /* 9 */) { + // special case: 1"" - count first quote as an inch + canClose = canOpen = false; + } + } + + if (canOpen && canClose) { + // Replace quotes in the middle of punctuation sequence, but not + // in the middle of the words, i.e.: + // + // 1. foo " bar " baz - not replaced + // 2. foo-"-bar-"-baz - replaced + // 3. foo"bar"baz - not replaced + // + canOpen = isLastPunctChar; + canClose = isNextPunctChar; + } + + if (!canOpen && !canClose) { + // middle of word + if (isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + continue; + } + + if (canClose) { + // this could be a closing quote, rewind the stack to get a match + for (j = stack.length - 1; j >= 0; j--) { + item = stack[j]; + if (stack[j].level < thisLevel) { break; } + if (item.single === isSingle && stack[j].level === thisLevel) { + item = stack[j]; + + if (isSingle) { + openQuote = state.md.options.quotes[2]; + closeQuote = state.md.options.quotes[3]; + } else { + openQuote = state.md.options.quotes[0]; + closeQuote = state.md.options.quotes[1]; + } + + // replace token.content *before* tokens[item.token].content, + // because, if they are pointing at the same token, replaceAt + // could mess up indices when quote length != 1 + token.content = replaceAt(token.content, t.index, closeQuote); + tokens[item.token].content = replaceAt( + tokens[item.token].content, item.pos, openQuote); + + pos += closeQuote.length - 1; + if (item.token === i) { pos += openQuote.length - 1; } + + text = token.content; + max = text.length; + + stack.length = j; + continue OUTER; + } + } + } + + if (canOpen) { + stack.push({ + token: i, + pos: t.index, + single: isSingle, + level: thisLevel + }); + } else if (canClose && isSingle) { + token.content = replaceAt(token.content, t.index, APOSTROPHE); + } + } + } +} + + +module.exports = function smartquotes(state) { + /*eslint max-depth:0*/ + var blkIdx; + + if (!state.md.options.typographer) { return; } + + for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { + + if (state.tokens[blkIdx].type !== 'inline' || + !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) { + continue; + } + + process_inlines(state.tokens[blkIdx].children, state); + } +}; diff --git a/node_modules/markdown-it/lib/rules_core/state_core.js b/node_modules/markdown-it/lib/rules_core/state_core.js new file mode 100644 index 0000000..87cfd85 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_core/state_core.js @@ -0,0 +1,20 @@ +// Core state object +// +'use strict'; + +var Token = require('../token'); + + +function StateCore(src, md, env) { + this.src = src; + this.env = env; + this.tokens = []; + this.inlineMode = false; + this.md = md; // link to parser instance +} + +// re-export Token class to use in core rules +StateCore.prototype.Token = Token; + + +module.exports = StateCore; diff --git a/node_modules/markdown-it/lib/rules_inline/autolink.js b/node_modules/markdown-it/lib/rules_inline/autolink.js new file mode 100644 index 0000000..66deb90 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/autolink.js @@ -0,0 +1,76 @@ +// Process autolinks '<protocol:...>' + +'use strict'; + + +/*eslint max-len:0*/ +var EMAIL_RE = /^([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/; +var AUTOLINK_RE = /^([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)$/; + + +module.exports = function autolink(state, silent) { + var url, fullUrl, token, ch, start, max, + pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } + + start = state.pos; + max = state.posMax; + + for (;;) { + if (++pos >= max) return false; + + ch = state.src.charCodeAt(pos); + + if (ch === 0x3C /* < */) return false; + if (ch === 0x3E /* > */) break; + } + + url = state.src.slice(start + 1, pos); + + if (AUTOLINK_RE.test(url)) { + fullUrl = state.md.normalizeLink(url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += url.length + 2; + return true; + } + + if (EMAIL_RE.test(url)) { + fullUrl = state.md.normalizeLink('mailto:' + url); + if (!state.md.validateLink(fullUrl)) { return false; } + + if (!silent) { + token = state.push('link_open', 'a', 1); + token.attrs = [ [ 'href', fullUrl ] ]; + token.markup = 'autolink'; + token.info = 'auto'; + + token = state.push('text', '', 0); + token.content = state.md.normalizeLinkText(url); + + token = state.push('link_close', 'a', -1); + token.markup = 'autolink'; + token.info = 'auto'; + } + + state.pos += url.length + 2; + return true; + } + + return false; +}; diff --git a/node_modules/markdown-it/lib/rules_inline/backticks.js b/node_modules/markdown-it/lib/rules_inline/backticks.js new file mode 100644 index 0000000..b9c9ddb --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/backticks.js @@ -0,0 +1,63 @@ +// Parse backticks + +'use strict'; + + +module.exports = function backtick(state, silent) { + var start, max, marker, token, matchStart, matchEnd, openerLength, closerLength, + pos = state.pos, + ch = state.src.charCodeAt(pos); + + if (ch !== 0x60/* ` */) { return false; } + + start = pos; + pos++; + max = state.posMax; + + // scan marker length + while (pos < max && state.src.charCodeAt(pos) === 0x60/* ` */) { pos++; } + + marker = state.src.slice(start, pos); + openerLength = marker.length; + + if (state.backticksScanned && (state.backticks[openerLength] || 0) <= start) { + if (!silent) state.pending += marker; + state.pos += openerLength; + return true; + } + + matchStart = matchEnd = pos; + + // Nothing found in the cache, scan until the end of the line (or until marker is found) + while ((matchStart = state.src.indexOf('`', matchEnd)) !== -1) { + matchEnd = matchStart + 1; + + // scan marker length + while (matchEnd < max && state.src.charCodeAt(matchEnd) === 0x60/* ` */) { matchEnd++; } + + closerLength = matchEnd - matchStart; + + if (closerLength === openerLength) { + // Found matching closer length. + if (!silent) { + token = state.push('code_inline', 'code', 0); + token.markup = marker; + token.content = state.src.slice(pos, matchStart) + .replace(/\n/g, ' ') + .replace(/^ (.+) $/, '$1'); + } + state.pos = matchEnd; + return true; + } + + // Some different length found, put it in cache as upper limit of where closer can be found + state.backticks[closerLength] = matchStart; + } + + // Scanned through the end, didn't find anything + state.backticksScanned = true; + + if (!silent) state.pending += marker; + state.pos += openerLength; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_inline/balance_pairs.js b/node_modules/markdown-it/lib/rules_inline/balance_pairs.js new file mode 100644 index 0000000..4faad90 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/balance_pairs.js @@ -0,0 +1,130 @@ +// For each opening emphasis-like marker find a matching closing one +// +'use strict'; + + +function processDelimiters(state, delimiters) { + var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx, + isOddMatch, lastJump, + openersBottom = {}, + max = delimiters.length; + + if (!max) return; + + // headerIdx is the first delimiter of the current (where closer is) delimiter run + var headerIdx = 0; + var lastTokenIdx = -2; // needs any value lower than -1 + var jumps = []; + + for (closerIdx = 0; closerIdx < max; closerIdx++) { + closer = delimiters[closerIdx]; + + jumps.push(0); + + // markers belong to same delimiter run if: + // - they have adjacent tokens + // - AND markers are the same + // + if (delimiters[headerIdx].marker !== closer.marker || lastTokenIdx !== closer.token - 1) { + headerIdx = closerIdx; + } + + lastTokenIdx = closer.token; + + // Length is only used for emphasis-specific "rule of 3", + // if it's not defined (in strikethrough or 3rd party plugins), + // we can default it to 0 to disable those checks. + // + closer.length = closer.length || 0; + + if (!closer.close) continue; + + // Previously calculated lower bounds (previous fails) + // for each marker, each delimiter length modulo 3, + // and for whether this closer can be an opener; + // https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460 + if (!openersBottom.hasOwnProperty(closer.marker)) { + openersBottom[closer.marker] = [ -1, -1, -1, -1, -1, -1 ]; + } + + minOpenerIdx = openersBottom[closer.marker][(closer.open ? 3 : 0) + (closer.length % 3)]; + + openerIdx = headerIdx - jumps[headerIdx] - 1; + + newMinOpenerIdx = openerIdx; + + for (; openerIdx > minOpenerIdx; openerIdx -= jumps[openerIdx] + 1) { + opener = delimiters[openerIdx]; + + if (opener.marker !== closer.marker) continue; + + if (opener.open && opener.end < 0) { + + isOddMatch = false; + + // from spec: + // + // If one of the delimiters can both open and close emphasis, then the + // sum of the lengths of the delimiter runs containing the opening and + // closing delimiters must not be a multiple of 3 unless both lengths + // are multiples of 3. + // + if (opener.close || closer.open) { + if ((opener.length + closer.length) % 3 === 0) { + if (opener.length % 3 !== 0 || closer.length % 3 !== 0) { + isOddMatch = true; + } + } + } + + if (!isOddMatch) { + // If previous delimiter cannot be an opener, we can safely skip + // the entire sequence in future checks. This is required to make + // sure algorithm has linear complexity (see *_*_*_*_*_... case). + // + lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ? + jumps[openerIdx - 1] + 1 : + 0; + + jumps[closerIdx] = closerIdx - openerIdx + lastJump; + jumps[openerIdx] = lastJump; + + closer.open = false; + opener.end = closerIdx; + opener.close = false; + newMinOpenerIdx = -1; + // treat next token as start of run, + // it optimizes skips in **<...>**a**<...>** pathological case + lastTokenIdx = -2; + break; + } + } + } + + if (newMinOpenerIdx !== -1) { + // If match for this delimiter run failed, we want to set lower bound for + // future lookups. This is required to make sure algorithm has linear + // complexity. + // + // See details here: + // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442 + // + openersBottom[closer.marker][(closer.open ? 3 : 0) + ((closer.length || 0) % 3)] = newMinOpenerIdx; + } + } +} + + +module.exports = function link_pairs(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + processDelimiters(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + processDelimiters(state, tokens_meta[curr].delimiters); + } + } +}; diff --git a/node_modules/markdown-it/lib/rules_inline/emphasis.js b/node_modules/markdown-it/lib/rules_inline/emphasis.js new file mode 100644 index 0000000..7e8ab4c --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/emphasis.js @@ -0,0 +1,130 @@ +// Process *this* and _that_ +// +'use strict'; + + +// Insert each marker as a separate text token, and add it to delimiter list +// +module.exports.tokenize = function emphasis(state, silent) { + var i, scanned, token, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x5F /* _ */ && marker !== 0x2A /* * */) { return false; } + + scanned = state.scanDelims(state.pos, marker === 0x2A); + + for (i = 0; i < scanned.length; i++) { + token = state.push('text', '', 0); + token.content = String.fromCharCode(marker); + + state.delimiters.push({ + // Char code of the starting marker (number). + // + marker: marker, + + // Total length of these series of delimiters. + // + length: scanned.length, + + // A position of the token this delimiter corresponds to. + // + token: state.tokens.length - 1, + + // If this delimiter is matched as a valid opener, `end` will be + // equal to its position, otherwise it's `-1`. + // + end: -1, + + // Boolean flags that determine if this delimiter could open or close + // an emphasis. + // + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; +}; + + +function postProcess(state, delimiters) { + var i, + startDelim, + endDelim, + token, + ch, + isStrong, + max = delimiters.length; + + for (i = max - 1; i >= 0; i--) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x5F/* _ */ && startDelim.marker !== 0x2A/* * */) { + continue; + } + + // Process only opening markers + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + // If the previous delimiter has the same marker and is adjacent to this one, + // merge those into one strong delimiter. + // + // `<em><em>whatever</em></em>` -> `<strong>whatever</strong>` + // + isStrong = i > 0 && + delimiters[i - 1].end === startDelim.end + 1 && + // check that first two markers match and adjacent + delimiters[i - 1].marker === startDelim.marker && + delimiters[i - 1].token === startDelim.token - 1 && + // check that last two markers are adjacent (we can safely assume they match) + delimiters[startDelim.end + 1].token === endDelim.token + 1; + + ch = String.fromCharCode(startDelim.marker); + + token = state.tokens[startDelim.token]; + token.type = isStrong ? 'strong_open' : 'em_open'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = 1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = isStrong ? 'strong_close' : 'em_close'; + token.tag = isStrong ? 'strong' : 'em'; + token.nesting = -1; + token.markup = isStrong ? ch + ch : ch; + token.content = ''; + + if (isStrong) { + state.tokens[delimiters[i - 1].token].content = ''; + state.tokens[delimiters[startDelim.end + 1].token].content = ''; + i--; + } + } +} + + +// Walk through delimiter list and replace text tokens with tags +// +module.exports.postProcess = function emphasis(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } +}; diff --git a/node_modules/markdown-it/lib/rules_inline/entity.js b/node_modules/markdown-it/lib/rules_inline/entity.js new file mode 100644 index 0000000..6fcc889 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/entity.js @@ -0,0 +1,48 @@ +// Process html entity - {, ¯, ", ... + +'use strict'; + +var entities = require('../common/entities'); +var has = require('../common/utils').has; +var isValidEntityCode = require('../common/utils').isValidEntityCode; +var fromCodePoint = require('../common/utils').fromCodePoint; + + +var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i; +var NAMED_RE = /^&([a-z][a-z0-9]{1,31});/i; + + +module.exports = function entity(state, silent) { + var ch, code, match, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x26/* & */) { return false; } + + if (pos + 1 < max) { + ch = state.src.charCodeAt(pos + 1); + + if (ch === 0x23 /* # */) { + match = state.src.slice(pos).match(DIGITAL_RE); + if (match) { + if (!silent) { + code = match[1][0].toLowerCase() === 'x' ? parseInt(match[1].slice(1), 16) : parseInt(match[1], 10); + state.pending += isValidEntityCode(code) ? fromCodePoint(code) : fromCodePoint(0xFFFD); + } + state.pos += match[0].length; + return true; + } + } else { + match = state.src.slice(pos).match(NAMED_RE); + if (match) { + if (has(entities, match[1])) { + if (!silent) { state.pending += entities[match[1]]; } + state.pos += match[0].length; + return true; + } + } + } + } + + if (!silent) { state.pending += '&'; } + state.pos++; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_inline/escape.js b/node_modules/markdown-it/lib/rules_inline/escape.js new file mode 100644 index 0000000..229ead0 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/escape.js @@ -0,0 +1,52 @@ +// Process escaped chars and hardbreaks + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + +var ESCAPED = []; + +for (var i = 0; i < 256; i++) { ESCAPED.push(0); } + +'\\!"#$%&\'()*+,./:;<=>?@[]^_`{|}~-' + .split('').forEach(function (ch) { ESCAPED[ch.charCodeAt(0)] = 1; }); + + +module.exports = function escape(state, silent) { + var ch, pos = state.pos, max = state.posMax; + + if (state.src.charCodeAt(pos) !== 0x5C/* \ */) { return false; } + + pos++; + + if (pos < max) { + ch = state.src.charCodeAt(pos); + + if (ch < 256 && ESCAPED[ch] !== 0) { + if (!silent) { state.pending += state.src[pos]; } + state.pos += 2; + return true; + } + + if (ch === 0x0A) { + if (!silent) { + state.push('hardbreak', 'br', 0); + } + + pos++; + // skip leading whitespaces from next line + while (pos < max) { + ch = state.src.charCodeAt(pos); + if (!isSpace(ch)) { break; } + pos++; + } + + state.pos = pos; + return true; + } + } + + if (!silent) { state.pending += '\\'; } + state.pos++; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_inline/html_inline.js b/node_modules/markdown-it/lib/rules_inline/html_inline.js new file mode 100644 index 0000000..28c7980 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/html_inline.js @@ -0,0 +1,47 @@ +// Process html tags + +'use strict'; + + +var HTML_TAG_RE = require('../common/html_re').HTML_TAG_RE; + + +function isLetter(ch) { + /*eslint no-bitwise:0*/ + var lc = ch | 0x20; // to lower case + return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); +} + + +module.exports = function html_inline(state, silent) { + var ch, match, max, token, + pos = state.pos; + + if (!state.md.options.html) { return false; } + + // Check start + max = state.posMax; + if (state.src.charCodeAt(pos) !== 0x3C/* < */ || + pos + 2 >= max) { + return false; + } + + // Quick fail on second char + ch = state.src.charCodeAt(pos + 1); + if (ch !== 0x21/* ! */ && + ch !== 0x3F/* ? */ && + ch !== 0x2F/* / */ && + !isLetter(ch)) { + return false; + } + + match = state.src.slice(pos).match(HTML_TAG_RE); + if (!match) { return false; } + + if (!silent) { + token = state.push('html_inline', '', 0); + token.content = state.src.slice(pos, pos + match[0].length); + } + state.pos += match[0].length; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_inline/image.js b/node_modules/markdown-it/lib/rules_inline/image.js new file mode 100644 index 0000000..53edd32 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/image.js @@ -0,0 +1,152 @@ +// Process ![image](<src> "title") + +'use strict'; + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function image(state, silent) { + var attrs, + code, + content, + label, + labelEnd, + labelStart, + pos, + ref, + res, + title, + token, + tokens, + start, + href = '', + oldPos = state.pos, + max = state.posMax; + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 2; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( <href> "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( <href> "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( <href> "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( <href> "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( <href> "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos; + return false; + } + pos++; + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + content = state.src.slice(labelStart, labelEnd); + + state.md.inline.parse( + content, + state.md, + state.env, + tokens = [] + ); + + token = state.push('image', 'img', 0); + token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ]; + token.children = tokens; + token.content = content; + + if (title) { + attrs.push([ 'title', title ]); + } + } + + state.pos = pos; + state.posMax = max; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_inline/link.js b/node_modules/markdown-it/lib/rules_inline/link.js new file mode 100644 index 0000000..1d242bf --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/link.js @@ -0,0 +1,148 @@ +// Process [link](<to> "stuff") + +'use strict'; + +var normalizeReference = require('../common/utils').normalizeReference; +var isSpace = require('../common/utils').isSpace; + + +module.exports = function link(state, silent) { + var attrs, + code, + label, + labelEnd, + labelStart, + pos, + res, + ref, + token, + href = '', + title = '', + oldPos = state.pos, + max = state.posMax, + start = state.pos, + parseReference = true; + + if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 1; + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // might have found a valid shortcut link, disable reference parsing + parseReference = false; + + // [link]( <href> "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( <href> "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + + // [link]( <href> "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + + // [link]( <href> "title" ) + // ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( <href> "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (!isSpace(code) && code !== 0x0A) { break; } + } + } + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + // parsing a valid shortcut link failed, fallback to reference + parseReference = true; + } + pos++; + } + + if (parseReference) { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = state.md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + + token = state.push('link_open', 'a', 1); + token.attrs = attrs = [ [ 'href', href ] ]; + if (title) { + attrs.push([ 'title', title ]); + } + + state.md.inline.tokenize(state); + + token = state.push('link_close', 'a', -1); + } + + state.pos = pos; + state.posMax = max; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_inline/newline.js b/node_modules/markdown-it/lib/rules_inline/newline.js new file mode 100644 index 0000000..9eeead4 --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/newline.js @@ -0,0 +1,46 @@ +// Proceess '\n' + +'use strict'; + +var isSpace = require('../common/utils').isSpace; + + +module.exports = function newline(state, silent) { + var pmax, max, ws, pos = state.pos; + + if (state.src.charCodeAt(pos) !== 0x0A/* \n */) { return false; } + + pmax = state.pending.length - 1; + max = state.posMax; + + // ' \n' -> hardbreak + // Lookup in pending chars is bad practice! Don't copy to other rules! + // Pending string is stored in concat mode, indexed lookups will cause + // convertion to flat mode. + if (!silent) { + if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) { + if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) { + // Find whitespaces tail of pending chars. + ws = pmax - 1; + while (ws >= 1 && state.pending.charCodeAt(ws - 1) === 0x20) ws--; + + state.pending = state.pending.slice(0, ws); + state.push('hardbreak', 'br', 0); + } else { + state.pending = state.pending.slice(0, -1); + state.push('softbreak', 'br', 0); + } + + } else { + state.push('softbreak', 'br', 0); + } + } + + pos++; + + // skip heading spaces for next line + while (pos < max && isSpace(state.src.charCodeAt(pos))) { pos++; } + + state.pos = pos; + return true; +}; diff --git a/node_modules/markdown-it/lib/rules_inline/state_inline.js b/node_modules/markdown-it/lib/rules_inline/state_inline.js new file mode 100644 index 0000000..efbf9bd --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/state_inline.js @@ -0,0 +1,154 @@ +// Inline parser state + +'use strict'; + + +var Token = require('../token'); +var isWhiteSpace = require('../common/utils').isWhiteSpace; +var isPunctChar = require('../common/utils').isPunctChar; +var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct; + + +function StateInline(src, md, env, outTokens) { + this.src = src; + this.env = env; + this.md = md; + this.tokens = outTokens; + this.tokens_meta = Array(outTokens.length); + + this.pos = 0; + this.posMax = this.src.length; + this.level = 0; + this.pending = ''; + this.pendingLevel = 0; + + // Stores { start: end } pairs. Useful for backtrack + // optimization of pairs parse (emphasis, strikes). + this.cache = {}; + + // List of emphasis-like delimiters for current tag + this.delimiters = []; + + // Stack of delimiter lists for upper level tags + this._prev_delimiters = []; + + // backtick length => last seen position + this.backticks = {}; + this.backticksScanned = false; +} + + +// Flush pending text +// +StateInline.prototype.pushPending = function () { + var token = new Token('text', '', 0); + token.content = this.pending; + token.level = this.pendingLevel; + this.tokens.push(token); + this.pending = ''; + return token; +}; + + +// Push new token to "stream". +// If pending text exists - flush it as text token +// +StateInline.prototype.push = function (type, tag, nesting) { + if (this.pending) { + this.pushPending(); + } + + var token = new Token(type, tag, nesting); + var token_meta = null; + + if (nesting < 0) { + // closing tag + this.level--; + this.delimiters = this._prev_delimiters.pop(); + } + + token.level = this.level; + + if (nesting > 0) { + // opening tag + this.level++; + this._prev_delimiters.push(this.delimiters); + this.delimiters = []; + token_meta = { delimiters: this.delimiters }; + } + + this.pendingLevel = this.level; + this.tokens.push(token); + this.tokens_meta.push(token_meta); + return token; +}; + + +// Scan a sequence of emphasis-like markers, and determine whether +// it can start an emphasis sequence or end an emphasis sequence. +// +// - start - position to scan from (it should point at a valid marker); +// - canSplitWord - determine if these markers can be found inside a word +// +StateInline.prototype.scanDelims = function (start, canSplitWord) { + var pos = start, lastChar, nextChar, count, can_open, can_close, + isLastWhiteSpace, isLastPunctChar, + isNextWhiteSpace, isNextPunctChar, + left_flanking = true, + right_flanking = true, + max = this.posMax, + marker = this.src.charCodeAt(start); + + // treat beginning of the line as a whitespace + lastChar = start > 0 ? this.src.charCodeAt(start - 1) : 0x20; + + while (pos < max && this.src.charCodeAt(pos) === marker) { pos++; } + + count = pos - start; + + // treat end of the line as a whitespace + nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20; + + isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); + isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); + + isLastWhiteSpace = isWhiteSpace(lastChar); + isNextWhiteSpace = isWhiteSpace(nextChar); + + if (isNextWhiteSpace) { + left_flanking = false; + } else if (isNextPunctChar) { + if (!(isLastWhiteSpace || isLastPunctChar)) { + left_flanking = false; + } + } + + if (isLastWhiteSpace) { + right_flanking = false; + } else if (isLastPunctChar) { + if (!(isNextWhiteSpace || isNextPunctChar)) { + right_flanking = false; + } + } + + if (!canSplitWord) { + can_open = left_flanking && (!right_flanking || isLastPunctChar); + can_close = right_flanking && (!left_flanking || isNextPunctChar); + } else { + can_open = left_flanking; + can_close = right_flanking; + } + + return { + can_open: can_open, + can_close: can_close, + length: count + }; +}; + + +// re-export Token class to use in block rules +StateInline.prototype.Token = Token; + + +module.exports = StateInline; diff --git a/node_modules/markdown-it/lib/rules_inline/strikethrough.js b/node_modules/markdown-it/lib/rules_inline/strikethrough.js new file mode 100644 index 0000000..3c35adf --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/strikethrough.js @@ -0,0 +1,130 @@ +// ~~strike through~~ +// +'use strict'; + + +// Insert each marker as a separate text token, and add it to delimiter list +// +module.exports.tokenize = function strikethrough(state, silent) { + var i, scanned, token, len, ch, + start = state.pos, + marker = state.src.charCodeAt(start); + + if (silent) { return false; } + + if (marker !== 0x7E/* ~ */) { return false; } + + scanned = state.scanDelims(state.pos, true); + len = scanned.length; + ch = String.fromCharCode(marker); + + if (len < 2) { return false; } + + if (len % 2) { + token = state.push('text', '', 0); + token.content = ch; + len--; + } + + for (i = 0; i < len; i += 2) { + token = state.push('text', '', 0); + token.content = ch + ch; + + state.delimiters.push({ + marker: marker, + length: 0, // disable "rule of 3" length checks meant for emphasis + token: state.tokens.length - 1, + end: -1, + open: scanned.can_open, + close: scanned.can_close + }); + } + + state.pos += scanned.length; + + return true; +}; + + +function postProcess(state, delimiters) { + var i, j, + startDelim, + endDelim, + token, + loneMarkers = [], + max = delimiters.length; + + for (i = 0; i < max; i++) { + startDelim = delimiters[i]; + + if (startDelim.marker !== 0x7E/* ~ */) { + continue; + } + + if (startDelim.end === -1) { + continue; + } + + endDelim = delimiters[startDelim.end]; + + token = state.tokens[startDelim.token]; + token.type = 's_open'; + token.tag = 's'; + token.nesting = 1; + token.markup = '~~'; + token.content = ''; + + token = state.tokens[endDelim.token]; + token.type = 's_close'; + token.tag = 's'; + token.nesting = -1; + token.markup = '~~'; + token.content = ''; + + if (state.tokens[endDelim.token - 1].type === 'text' && + state.tokens[endDelim.token - 1].content === '~') { + + loneMarkers.push(endDelim.token - 1); + } + } + + // If a marker sequence has an odd number of characters, it's splitted + // like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the + // start of the sequence. + // + // So, we have to move all those markers after subsequent s_close tags. + // + while (loneMarkers.length) { + i = loneMarkers.pop(); + j = i + 1; + + while (j < state.tokens.length && state.tokens[j].type === 's_close') { + j++; + } + + j--; + + if (i !== j) { + token = state.tokens[j]; + state.tokens[j] = state.tokens[i]; + state.tokens[i] = token; + } + } +} + + +// Walk through delimiter list and replace text tokens with tags +// +module.exports.postProcess = function strikethrough(state) { + var curr, + tokens_meta = state.tokens_meta, + max = state.tokens_meta.length; + + postProcess(state, state.delimiters); + + for (curr = 0; curr < max; curr++) { + if (tokens_meta[curr] && tokens_meta[curr].delimiters) { + postProcess(state, tokens_meta[curr].delimiters); + } + } +}; diff --git a/node_modules/markdown-it/lib/rules_inline/text.js b/node_modules/markdown-it/lib/rules_inline/text.js new file mode 100644 index 0000000..b19591e --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/text.js @@ -0,0 +1,89 @@ +// Skip text characters for text token, place those to pending buffer +// and increment current pos + +'use strict'; + + +// Rule to skip pure text +// '{}$%@~+=:' reserved for extentions + +// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~ + +// !!!! Don't confuse with "Markdown ASCII Punctuation" chars +// http://spec.commonmark.org/0.15/#ascii-punctuation-character +function isTerminatorChar(ch) { + switch (ch) { + case 0x0A/* \n */: + case 0x21/* ! */: + case 0x23/* # */: + case 0x24/* $ */: + case 0x25/* % */: + case 0x26/* & */: + case 0x2A/* * */: + case 0x2B/* + */: + case 0x2D/* - */: + case 0x3A/* : */: + case 0x3C/* < */: + case 0x3D/* = */: + case 0x3E/* > */: + case 0x40/* @ */: + case 0x5B/* [ */: + case 0x5C/* \ */: + case 0x5D/* ] */: + case 0x5E/* ^ */: + case 0x5F/* _ */: + case 0x60/* ` */: + case 0x7B/* { */: + case 0x7D/* } */: + case 0x7E/* ~ */: + return true; + default: + return false; + } +} + +module.exports = function text(state, silent) { + var pos = state.pos; + + while (pos < state.posMax && !isTerminatorChar(state.src.charCodeAt(pos))) { + pos++; + } + + if (pos === state.pos) { return false; } + + if (!silent) { state.pending += state.src.slice(state.pos, pos); } + + state.pos = pos; + + return true; +}; + +// Alternative implementation, for memory. +// +// It costs 10% of performance, but allows extend terminators list, if place it +// to `ParcerInline` property. Probably, will switch to it sometime, such +// flexibility required. + +/* +var TERMINATOR_RE = /[\n!#$%&*+\-:<=>@[\\\]^_`{}~]/; + +module.exports = function text(state, silent) { + var pos = state.pos, + idx = state.src.slice(pos).search(TERMINATOR_RE); + + // first char is terminator -> empty text + if (idx === 0) { return false; } + + // no terminator -> text till end of string + if (idx < 0) { + if (!silent) { state.pending += state.src.slice(pos); } + state.pos = state.src.length; + return true; + } + + if (!silent) { state.pending += state.src.slice(pos, pos + idx); } + + state.pos += idx; + + return true; +};*/ diff --git a/node_modules/markdown-it/lib/rules_inline/text_collapse.js b/node_modules/markdown-it/lib/rules_inline/text_collapse.js new file mode 100644 index 0000000..390b0fe --- /dev/null +++ b/node_modules/markdown-it/lib/rules_inline/text_collapse.js @@ -0,0 +1,41 @@ +// Clean up tokens after emphasis and strikethrough postprocessing: +// merge adjacent text nodes into one and re-calculate all token levels +// +// This is necessary because initially emphasis delimiter markers (*, _, ~) +// are treated as their own separate text tokens. Then emphasis rule either +// leaves them as text (needed to merge with adjacent text) or turns them +// into opening/closing tags (which messes up levels inside). +// +'use strict'; + + +module.exports = function text_collapse(state) { + var curr, last, + level = 0, + tokens = state.tokens, + max = state.tokens.length; + + for (curr = last = 0; curr < max; curr++) { + // re-calculate levels after emphasis/strikethrough turns some text nodes + // into opening/closing tags + if (tokens[curr].nesting < 0) level--; // closing tag + tokens[curr].level = level; + if (tokens[curr].nesting > 0) level++; // opening tag + + if (tokens[curr].type === 'text' && + curr + 1 < max && + tokens[curr + 1].type === 'text') { + + // collapse two adjacent text nodes + tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; + } else { + if (curr !== last) { tokens[last] = tokens[curr]; } + + last++; + } + } + + if (curr !== last) { + tokens.length = last; + } +}; diff --git a/node_modules/markdown-it/lib/token.js b/node_modules/markdown-it/lib/token.js new file mode 100644 index 0000000..c5fd271 --- /dev/null +++ b/node_modules/markdown-it/lib/token.js @@ -0,0 +1,201 @@ +// Token class + +'use strict'; + + +/** + * class Token + **/ + +/** + * new Token(type, tag, nesting) + * + * Create new token and fill passed properties. + **/ +function Token(type, tag, nesting) { + /** + * Token#type -> String + * + * Type of the token (string, e.g. "paragraph_open") + **/ + this.type = type; + + /** + * Token#tag -> String + * + * html tag name, e.g. "p" + **/ + this.tag = tag; + + /** + * Token#attrs -> Array + * + * Html attributes. Format: `[ [ name1, value1 ], [ name2, value2 ] ]` + **/ + this.attrs = null; + + /** + * Token#map -> Array + * + * Source map info. Format: `[ line_begin, line_end ]` + **/ + this.map = null; + + /** + * Token#nesting -> Number + * + * Level change (number in {-1, 0, 1} set), where: + * + * - `1` means the tag is opening + * - `0` means the tag is self-closing + * - `-1` means the tag is closing + **/ + this.nesting = nesting; + + /** + * Token#level -> Number + * + * nesting level, the same as `state.level` + **/ + this.level = 0; + + /** + * Token#children -> Array + * + * An array of child nodes (inline and img tokens) + **/ + this.children = null; + + /** + * Token#content -> String + * + * In a case of self-closing tag (code, html, fence, etc.), + * it has contents of this tag. + **/ + this.content = ''; + + /** + * Token#markup -> String + * + * '*' or '_' for emphasis, fence string for fence, etc. + **/ + this.markup = ''; + + /** + * Token#info -> String + * + * Additional information: + * + * - Info string for "fence" tokens + * - The value "auto" for autolink "link_open" and "link_close" tokens + * - The string value of the item marker for ordered-list "list_item_open" tokens + **/ + this.info = ''; + + /** + * Token#meta -> Object + * + * A place for plugins to store an arbitrary data + **/ + this.meta = null; + + /** + * Token#block -> Boolean + * + * True for block-level tokens, false for inline tokens. + * Used in renderer to calculate line breaks + **/ + this.block = false; + + /** + * Token#hidden -> Boolean + * + * If it's true, ignore this element when rendering. Used for tight lists + * to hide paragraphs. + **/ + this.hidden = false; +} + + +/** + * Token.attrIndex(name) -> Number + * + * Search attribute index by name. + **/ +Token.prototype.attrIndex = function attrIndex(name) { + var attrs, i, len; + + if (!this.attrs) { return -1; } + + attrs = this.attrs; + + for (i = 0, len = attrs.length; i < len; i++) { + if (attrs[i][0] === name) { return i; } + } + return -1; +}; + + +/** + * Token.attrPush(attrData) + * + * Add `[ name, value ]` attribute to list. Init attrs if necessary + **/ +Token.prototype.attrPush = function attrPush(attrData) { + if (this.attrs) { + this.attrs.push(attrData); + } else { + this.attrs = [ attrData ]; + } +}; + + +/** + * Token.attrSet(name, value) + * + * Set `name` attribute to `value`. Override old value if exists. + **/ +Token.prototype.attrSet = function attrSet(name, value) { + var idx = this.attrIndex(name), + attrData = [ name, value ]; + + if (idx < 0) { + this.attrPush(attrData); + } else { + this.attrs[idx] = attrData; + } +}; + + +/** + * Token.attrGet(name) + * + * Get the value of attribute `name`, or null if it does not exist. + **/ +Token.prototype.attrGet = function attrGet(name) { + var idx = this.attrIndex(name), value = null; + if (idx >= 0) { + value = this.attrs[idx][1]; + } + return value; +}; + + +/** + * Token.attrJoin(name, value) + * + * Join value to existing attribute via space. Or create new attribute if not + * exists. Useful to operate with token classes. + **/ +Token.prototype.attrJoin = function attrJoin(name, value) { + var idx = this.attrIndex(name); + + if (idx < 0) { + this.attrPush([ name, value ]); + } else { + this.attrs[idx][1] = this.attrs[idx][1] + ' ' + value; + } +}; + + +module.exports = Token; |