'
- + (escaped ? code : escape$1(code, true))
+ + (escaped ? code : escape$2(code, true))
+ '
\n';
}
return ''
- + (escaped ? code : escape$1(code, true))
+ + (escaped ? code : escape$2(code, true))
+ '
\n';
}
@@ -2037,11 +1992,11 @@ var Renderer_1 = class Renderer {
}
link(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+ href = cleanUrl$1(this.options.sanitize, this.options.baseUrl, href);
if (href === null) {
return text;
}
- let out = ' 0 && item.tokens[0].type === 'text') {
+ if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') {
item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
@@ -2387,7 +2337,7 @@ var Parser_1 = class Parser {
// Run any renderer extensions
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
- ret = this.options.extensions.renderers[token.type].call(this, token);
+ ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
if (ret !== false || !['escape', 'html', 'link', 'image', 'strong', 'em', 'codespan', 'br', 'del', 'text'].includes(token.type)) {
out += ret || '';
continue;
@@ -2450,22 +2400,16 @@ var Parser_1 = class Parser {
}
};
-const Lexer = Lexer_1;
-const Parser = Parser_1;
-const Tokenizer = Tokenizer_1;
-const Renderer = Renderer_1;
-const TextRenderer = TextRenderer_1;
-const Slugger = Slugger_1;
const {
- merge,
- checkSanitizeDeprecation,
- escape
+ merge: merge$2,
+ checkSanitizeDeprecation: checkSanitizeDeprecation$1,
+ escape: escape$3
} = helpers;
const {
getDefaults,
changeDefaults,
- defaults
-} = defaults$5.exports;
+ defaults: defaults$5
+} = defaults;
/**
* Marked
@@ -2485,15 +2429,15 @@ function marked(src, opt, callback) {
opt = null;
}
- opt = merge({}, marked.defaults, opt || {});
- checkSanitizeDeprecation(opt);
+ opt = merge$2({}, marked.defaults, opt || {});
+ checkSanitizeDeprecation$1(opt);
if (callback) {
const highlight = opt.highlight;
let tokens;
try {
- tokens = Lexer.lex(src, opt);
+ tokens = Lexer_1.lex(src, opt);
} catch (e) {
return callback(e);
}
@@ -2506,7 +2450,7 @@ function marked(src, opt, callback) {
if (opt.walkTokens) {
marked.walkTokens(tokens, opt.walkTokens);
}
- out = Parser.parse(tokens, opt);
+ out = Parser_1.parse(tokens, opt);
} catch (e) {
err = e;
}
@@ -2558,16 +2502,16 @@ function marked(src, opt, callback) {
}
try {
- const tokens = Lexer.lex(src, opt);
+ const tokens = Lexer_1.lex(src, opt);
if (opt.walkTokens) {
marked.walkTokens(tokens, opt.walkTokens);
}
- return Parser.parse(tokens, opt);
+ return Parser_1.parse(tokens, opt);
} catch (e) {
e.message += '\nPlease report this to https://github.com/markedjs/marked.';
if (opt.silent) {
return 'An error occurred:
' - + escape(e.message + '', true) + + escape$3(e.message + '', true) + ''; } throw e; @@ -2580,21 +2524,21 @@ function marked(src, opt, callback) { marked.options = marked.setOptions = function(opt) { - merge(marked.defaults, opt); + merge$2(marked.defaults, opt); changeDefaults(marked.defaults); return marked; }; marked.getDefaults = getDefaults; -marked.defaults = defaults; +marked.defaults = defaults$5; /** * Use Extension */ marked.use = function(...args) { - const opts = merge({}, ...args); + const opts = merge$2({}, ...args); const extensions = marked.defaults.extensions || { renderers: {}, childTokens: {} }; let hasExtensions; @@ -2654,7 +2598,7 @@ marked.use = function(...args) { // ==-- Parse "overwrite" extensions --== // if (pack.renderer) { - const renderer = marked.defaults.renderer || new Renderer(); + const renderer = marked.defaults.renderer || new Renderer_1(); for (const prop in pack.renderer) { const prevRenderer = renderer[prop]; // Replace renderer with func to run extension, but fall back if false @@ -2669,7 +2613,7 @@ marked.use = function(...args) { opts.renderer = renderer; } if (pack.tokenizer) { - const tokenizer = marked.defaults.tokenizer || new Tokenizer(); + const tokenizer = marked.defaults.tokenizer || new Tokenizer_1(); for (const prop in pack.tokenizer) { const prevTokenizer = tokenizer[prop]; // Replace tokenizer with func to run extension, but fall back if false @@ -2752,20 +2696,20 @@ marked.parseInline = function(src, opt) { + Object.prototype.toString.call(src) + ', string expected'); } - opt = merge({}, marked.defaults, opt || {}); - checkSanitizeDeprecation(opt); + opt = merge$2({}, marked.defaults, opt || {}); + checkSanitizeDeprecation$1(opt); try { - const tokens = Lexer.lexInline(src, opt); + const tokens = Lexer_1.lexInline(src, opt); if (opt.walkTokens) { marked.walkTokens(tokens, opt.walkTokens); } - return Parser.parseInline(tokens, opt); + return Parser_1.parseInline(tokens, opt); } catch (e) { e.message += '\nPlease report this to https://github.com/markedjs/marked.'; if (opt.silent) { return '
An error occurred:
' - + escape(e.message + '', true) + + escape$3(e.message + '', true) + ''; } throw e; @@ -2776,18 +2720,18 @@ marked.parseInline = function(src, opt) { * Expose */ -marked.Parser = Parser; -marked.parser = Parser.parse; +marked.Parser = Parser_1; +marked.parser = Parser_1.parse; -marked.Renderer = Renderer; -marked.TextRenderer = TextRenderer; +marked.Renderer = Renderer_1; +marked.TextRenderer = TextRenderer_1; -marked.Lexer = Lexer; -marked.lexer = Lexer.lex; +marked.Lexer = Lexer_1; +marked.lexer = Lexer_1.lex; -marked.Tokenizer = Tokenizer; +marked.Tokenizer = Tokenizer_1; -marked.Slugger = Slugger; +marked.Slugger = Slugger_1; marked.parse = marked; diff --git a/lib/marked.js b/lib/marked.js index 10fe5d2815..8f43b0b880 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -49,61 +49,70 @@ } function _createForOfIteratorHelperLoose(o, allowArrayLike) { - var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; - if (it) return (it = it.call(o)).next.bind(it); - - if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { - if (it) o = it; - var i = 0; - return function () { - if (i >= o.length) return { - done: true - }; - return { - done: false, - value: o[i++] + var it; + + if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { + if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { + if (it) o = it; + var i = 0; + return function () { + if (i >= o.length) return { + done: true + }; + return { + done: false, + value: o[i++] + }; }; - }; + } + + throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } - throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + it = o[Symbol.iterator](); + return it.next.bind(it); } - var defaults$5 = {exports: {}}; - - function getDefaults$1() { - return { - baseUrl: null, - breaks: false, - extensions: null, - gfm: true, - headerIds: true, - headerPrefix: '', - highlight: null, - langPrefix: 'language-', - mangle: true, - pedantic: false, - renderer: null, - sanitize: false, - sanitizer: null, - silent: false, - smartLists: false, - smartypants: false, - tokenizer: null, - walkTokens: null, - xhtml: false - }; + function createCommonjsModule(fn) { + var module = { exports: {} }; + return fn(module, module.exports), module.exports; } - function changeDefaults$1(newDefaults) { - defaults$5.exports.defaults = newDefaults; - } + var defaults = createCommonjsModule(function (module) { + function getDefaults() { + return { + baseUrl: null, + breaks: false, + extensions: null, + gfm: true, + headerIds: true, + headerPrefix: '', + highlight: null, + langPrefix: 'language-', + mangle: true, + pedantic: false, + renderer: null, + sanitize: false, + sanitizer: null, + silent: false, + smartLists: false, + smartypants: false, + tokenizer: null, + walkTokens: null, + xhtml: false + }; + } - defaults$5.exports = { - defaults: getDefaults$1(), - getDefaults: getDefaults$1, - changeDefaults: changeDefaults$1 - }; + function changeDefaults(newDefaults) { + module.exports.defaults = newDefaults; + } + + module.exports = { + defaults: getDefaults(), + getDefaults: getDefaults, + changeDefaults: changeDefaults + }; + }); /** * Helpers @@ -124,7 +133,7 @@ return escapeReplacements[ch]; }; - function escape$2(html, encode) { + function escape(html, encode) { if (encode) { if (escapeTest.test(html)) { return html.replace(escapeReplace, getEscapeReplacement); @@ -140,7 +149,7 @@ var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig; - function unescape$1(html) { + function unescape(html) { // explicitly match decimal, hex, and named HTML entities return html.replace(unescapeTest, function (_, n) { n = n.toLowerCase(); @@ -156,7 +165,7 @@ var caret = /(^|[^\[])\^/g; - function edit$1(regex, opt) { + function edit(regex, opt) { regex = regex.source || regex; opt = opt || ''; var obj = { @@ -176,12 +185,12 @@ var nonWordAndColonTest = /[^\w:]/g; var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; - function cleanUrl$1(sanitize, base, href) { + function cleanUrl(sanitize, base, href) { if (sanitize) { var prot; try { - prot = decodeURIComponent(unescape$1(href)).replace(nonWordAndColonTest, '').toLowerCase(); + prot = decodeURIComponent(unescape(href)).replace(nonWordAndColonTest, '').toLowerCase(); } catch (e) { return null; } @@ -217,7 +226,7 @@ if (justDomain.test(base)) { baseUrls[' ' + base] = base + '/'; } else { - baseUrls[' ' + base] = rtrim$1(base, '/', true); + baseUrls[' ' + base] = rtrim(base, '/', true); } } @@ -241,11 +250,11 @@ } } - var noopTest$1 = { + var noopTest = { exec: function noopTest() {} }; - function merge$2(obj) { + function merge(obj) { var i = 1, target, key; @@ -263,7 +272,7 @@ return obj; } - function splitCells$1(tableRow, count) { + function splitCells(tableRow, count) { // ensure that every cell-delimiting pipe has a space // before it to distinguish it from an escaped pipe var row = tableRow.replace(/\|/g, function (match, offset, str) { @@ -284,7 +293,15 @@ } }), cells = row.split(/ \|/); - var i = 0; + var i = 0; // First/last cell in a row cannot be empty if it has no leading/trailing pipe + + if (!cells[0].trim()) { + cells.shift(); + } + + if (!cells[cells.length - 1].trim()) { + cells.pop(); + } if (cells.length > count) { cells.splice(count); @@ -305,7 +322,7 @@ // invert: Remove suffix of non-c chars instead. Default falsey. - function rtrim$1(str, c, invert) { + function rtrim(str, c, invert) { var l = str.length; if (l === 0) { @@ -330,7 +347,7 @@ return str.substr(0, l - suffLen); } - function findClosingBracket$1(str, b) { + function findClosingBracket(str, b) { if (str.indexOf(b[1]) === -1) { return -1; } @@ -356,14 +373,14 @@ return -1; } - function checkSanitizeDeprecation$1(opt) { + function checkSanitizeDeprecation(opt) { if (opt && opt.sanitize && !opt.silent) { console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options'); } } // copied from https://stackoverflow.com/a/5450113/806777 - function repeatString$1(pattern, count) { + function repeatString(pattern, count) { if (count < 1) { return ''; } @@ -383,38 +400,40 @@ } var helpers = { - escape: escape$2, - unescape: unescape$1, - edit: edit$1, - cleanUrl: cleanUrl$1, + escape: escape, + unescape: unescape, + edit: edit, + cleanUrl: cleanUrl, resolveUrl: resolveUrl, - noopTest: noopTest$1, - merge: merge$2, - splitCells: splitCells$1, - rtrim: rtrim$1, - findClosingBracket: findClosingBracket$1, - checkSanitizeDeprecation: checkSanitizeDeprecation$1, - repeatString: repeatString$1 + noopTest: noopTest, + merge: merge, + splitCells: splitCells, + rtrim: rtrim, + findClosingBracket: findClosingBracket, + checkSanitizeDeprecation: checkSanitizeDeprecation, + repeatString: repeatString }; - var defaults$4 = defaults$5.exports.defaults; - var rtrim = helpers.rtrim, - splitCells = helpers.splitCells, + var defaults$1 = defaults.defaults; + var rtrim$1 = helpers.rtrim, + splitCells$1 = helpers.splitCells, _escape = helpers.escape, - findClosingBracket = helpers.findClosingBracket; + findClosingBracket$1 = helpers.findClosingBracket; - function outputLink(cap, link, raw) { + function outputLink(cap, link, raw, lexer) { var href = link.href; var title = link.title ? _escape(link.title) : null; var text = cap[1].replace(/\\([\[\]])/g, '$1'); if (cap[0].charAt(0) !== '!') { + lexer.state.inLink = true; return { type: 'link', raw: raw, href: href, title: title, - text: text + text: text, + tokens: lexer.inlineTokens(text, []) }; } else { return { @@ -458,7 +477,7 @@ var Tokenizer_1 = /*#__PURE__*/function () { function Tokenizer(options) { - this.options = options || defaults$4; + this.options = options || defaults$1; } var _proto = Tokenizer.prototype; @@ -489,7 +508,7 @@ type: 'code', raw: cap[0], codeBlockStyle: 'indented', - text: !this.options.pedantic ? rtrim(text, '\n') : text + text: !this.options.pedantic ? rtrim$1(text, '\n') : text }; } }; @@ -516,7 +535,7 @@ var text = cap[2].trim(); // remove trailing #s if (/#$/.test(text)) { - var trimmed = rtrim(text, '#'); + var trimmed = rtrim$1(text, '#'); if (this.options.pedantic) { text = trimmed.trim(); @@ -526,51 +545,15 @@ } } - return { + var token = { type: 'heading', raw: cap[0], depth: cap[1].length, - text: text + text: text, + tokens: [] }; - } - }; - - _proto.nptable = function nptable(src) { - var cap = this.rules.block.nptable.exec(src); - - if (cap) { - var item = { - type: 'table', - header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [], - raw: cap[0] - }; - - if (item.header.length === item.align.length) { - var l = item.align.length; - var i; - - for (i = 0; i < l; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - l = item.cells.length; - - for (i = 0; i < l; i++) { - item.cells[i] = splitCells(item.cells[i], item.header.length); - } - - return item; - } + this.lexer.inline(token.text, token.tokens); + return token; } }; @@ -593,6 +576,7 @@ return { type: 'blockquote', raw: cap[0], + tokens: this.lexer.blockTokens(text, []), text: text }; } @@ -602,121 +586,150 @@ var cap = this.rules.block.list.exec(src); if (cap) { - var raw = cap[0]; - var bull = cap[2]; + var raw, istask, ischecked, indent, i, blankLine, endsWithBlankLine, line, lines, itemContents; + var bull = cap[1].trim(); var isordered = bull.length > 1; var list = { type: 'list', - raw: raw, + raw: '', ordered: isordered, start: isordered ? +bull.slice(0, -1) : '', loose: false, items: [] - }; // Get each top-level item. - - var itemMatch = cap[0].match(this.rules.block.item); - var next = false, - item, - space, - bcurr, - bnext, - addBack, - loose, - istask, - ischecked, - endMatch; - var l = itemMatch.length; - bcurr = this.rules.block.listItemStart.exec(itemMatch[0]); - - for (var i = 0; i < l; i++) { - item = itemMatch[i]; - raw = item; - - if (!this.options.pedantic) { - // Determine if current item contains the end of the list - endMatch = item.match(new RegExp('\\n\\s*\\n {0,' + (bcurr[0].length - 1) + '}\\S')); - - if (endMatch) { - addBack = item.length - endMatch.index + itemMatch.slice(i + 1).join('\n').length; - list.raw = list.raw.substring(0, list.raw.length - addBack); - item = item.substring(0, endMatch.index); - raw = item; - l = i + 1; - } - } // Determine whether the next list item belongs here. - // Backpedal if it does not belong in this list. + }; + bull = isordered ? "\\d{1,9}\\" + bull.slice(-1) : "\\" + bull; + if (this.options.pedantic) { + bull = isordered ? bull : '[*+-]'; + } // Get next list item - if (i !== l - 1) { - bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]); - if (!this.options.pedantic ? bnext[1].length >= bcurr[0].length || bnext[1].length > 3 : bnext[1].length > bcurr[1].length) { - // nested list or continuation - itemMatch.splice(i, 2, itemMatch[i] + (!this.options.pedantic && bnext[1].length < bcurr[0].length && !itemMatch[i].match(/\n$/) ? '' : '\n') + itemMatch[i + 1]); - i--; - l--; - continue; - } else if ( // different bullet style - !this.options.pedantic || this.options.smartLists ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1] : isordered === (bnext[2].length === 1)) { - addBack = itemMatch.slice(i + 1).join('\n').length; - list.raw = list.raw.substring(0, list.raw.length - addBack); - i = l - 1; - } + var itemRegex = new RegExp("^( {0,3}" + bull + ")((?: [^\\n]*| *)(?:\\n[^\\n]*)*(?:\\n|$))"); // Get each top-level item + + while (src) { + if (this.rules.block.hr.test(src)) { + // End list if we encounter an HR (possibly move into itemRegex?) + break; + } + + if (!(cap = itemRegex.exec(src))) { + break; + } + + lines = cap[2].split('\n'); + + if (this.options.pedantic) { + indent = 2; + itemContents = lines[0].trimLeft(); + } else { + indent = cap[2].search(/[^ ]/); // Find first non-space char + + indent = cap[1].length + (indent > 4 ? 1 : indent); // intented code blocks after 4 spaces; indent is always 1 + + itemContents = lines[0].slice(indent - cap[1].length); + } - bcurr = bnext; - } // Remove the list item's bullet - // so it is seen as the next token. + blankLine = false; + raw = cap[0]; + if (!lines[0] && /^ *$/.test(lines[1])) { + // items begin with at most one blank line + raw = cap[1] + lines.slice(0, 2).join('\n') + '\n'; + list.loose = true; + lines = []; + } - space = item.length; - item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); // Outdent whatever the - // list item contains. Hacky. + var nextBulletRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:[*+-]|\\d{1,9}[.)])"); - if (~item.indexOf('\n ')) { - space -= item.length; - item = !this.options.pedantic ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') : item.replace(/^ {1,4}/gm, ''); - } // trim item newlines at end + for (i = 1; i < lines.length; i++) { + line = lines[i]; + if (this.options.pedantic) { + // Re-align to follow commonmark nesting rules + line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); + } // End list item if found start of new bullet - item = rtrim(item, '\n'); - if (i !== l - 1) { - raw = raw + '\n'; - } // Determine whether item is loose or not. - // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ - // for discount behavior. + if (nextBulletRegex.test(line)) { + raw = cap[1] + lines.slice(0, i).join('\n') + '\n'; + break; + } // Until we encounter a blank line, item contents do not need indentation - loose = next || /\n\n(?!\s*$)/.test(raw); + if (!blankLine) { + if (!line.trim()) { + // Check if current line is empty + blankLine = true; + } // Dedent if possible - if (i !== l - 1) { - next = raw.slice(-2) === '\n\n'; - if (!loose) loose = next; + + if (line.search(/[^ ]/) >= indent) { + itemContents += '\n' + line.slice(indent); + } else { + itemContents += '\n' + line; + } + + continue; + } // Dedent this line + + + if (line.search(/[^ ]/) >= indent || !line.trim()) { + itemContents += '\n' + line.slice(indent); + continue; + } else { + // Line was not properly indented; end of this item + raw = cap[1] + lines.slice(0, i).join('\n') + '\n'; + break; + } } - if (loose) { - list.loose = true; + if (!list.loose) { + // If the previous item ended with a blank line, the list is loose + if (endsWithBlankLine) { + list.loose = true; + } else if (/\n *\n *$/.test(raw)) { + endsWithBlankLine = true; + } } // Check for task list items if (this.options.gfm) { - istask = /^\[[ xX]\] /.test(item); - ischecked = undefined; + istask = /^\[[ xX]\] /.exec(itemContents); if (istask) { - ischecked = item[1] !== ' '; - item = item.replace(/^\[[ xX]\] +/, ''); + ischecked = istask[0] !== '[ ] '; + itemContents = itemContents.replace(/^\[[ xX]\] +/, ''); } } list.items.push({ type: 'list_item', raw: raw, - task: istask, + task: !!istask, checked: ischecked, - loose: loose, - text: item + loose: false, + text: itemContents }); + list.raw += raw; + src = src.slice(raw.length); + } // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic + + + list.items[list.items.length - 1].raw = raw.trimRight(); + list.items[list.items.length - 1].text = itemContents.trimRight(); + list.raw = list.raw.trimRight(); + var l = list.items.length; // Item child tokens handled here at end because we needed to have the final item to trim it first + + for (i = 0; i < l; i++) { + this.lexer.state.top = false; + list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []); + + if (list.items[i].tokens.some(function (t) { + return t.type === 'space'; + })) { + list.loose = true; + list.items[i].loose = true; + } } return list; @@ -727,12 +740,21 @@ var cap = this.rules.block.html.exec(src); if (cap) { - return { - type: this.options.sanitize ? 'paragraph' : 'html', + var token = { + type: 'html', raw: cap[0], pre: !this.options.sanitizer && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), - text: this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0] + text: cap[0] }; + + if (this.options.sanitize) { + token.type = 'paragraph'; + token.text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]); + token.tokens = []; + this.lexer.inline(token.text, token.tokens); + } + + return token; } }; @@ -758,7 +780,7 @@ if (cap) { var item = { type: 'table', - header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), + header: splitCells$1(cap[1].replace(/^ *| *\| *$/g, '')), align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] }; @@ -766,7 +788,7 @@ if (item.header.length === item.align.length) { item.raw = cap[0]; var l = item.align.length; - var i; + var i, j, k, row; for (i = 0; i < l; i++) { if (/^ *-+: *$/.test(item.align[i])) { @@ -783,7 +805,33 @@ l = item.cells.length; for (i = 0; i < l; i++) { - item.cells[i] = splitCells(item.cells[i].replace(/^ *\| *| *\| *$/g, ''), item.header.length); + item.cells[i] = splitCells$1(item.cells[i], item.header.length); + } // parse child tokens inside headers and cells + + + item.tokens = { + header: [], + cells: [] + }; // header child tokens + + l = item.header.length; + + for (j = 0; j < l; j++) { + item.tokens.header[j] = []; + this.lexer.inlineTokens(item.header[j], item.tokens.header[j]); + } // cell child tokens + + + l = item.cells.length; + + for (j = 0; j < l; j++) { + row = item.cells[j]; + item.tokens.cells[j] = []; + + for (k = 0; k < row.length; k++) { + item.tokens.cells[j][k] = []; + this.lexer.inlineTokens(row[k], item.tokens.cells[j][k]); + } } return item; @@ -795,12 +843,15 @@ var cap = this.rules.block.lheading.exec(src); if (cap) { - return { + var token = { type: 'heading', raw: cap[0], depth: cap[2].charAt(0) === '=' ? 1 : 2, - text: cap[1] + text: cap[1], + tokens: [] }; + this.lexer.inline(token.text, token.tokens); + return token; } }; @@ -808,11 +859,14 @@ var cap = this.rules.block.paragraph.exec(src); if (cap) { - return { + var token = { type: 'paragraph', raw: cap[0], - text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1] + text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1], + tokens: [] }; + this.lexer.inline(token.text, token.tokens); + return token; } }; @@ -820,11 +874,14 @@ var cap = this.rules.block.text.exec(src); if (cap) { - return { + var token = { type: 'text', raw: cap[0], - text: cap[0] + text: cap[0], + tokens: [] }; + this.lexer.inline(token.text, token.tokens); + return token; } }; @@ -840,27 +897,27 @@ } }; - _proto.tag = function tag(src, inLink, inRawBlock) { + _proto.tag = function tag(src) { var cap = this.rules.inline.tag.exec(src); if (cap) { - if (!inLink && /^/i.test(cap[0])) { - inLink = false; + if (!this.lexer.state.inLink && /^/i.test(cap[0])) { + this.lexer.state.inLink = false; } - if (!inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - inRawBlock = true; - } else if (inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { - inRawBlock = false; + if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { + this.lexer.state.inRawBlock = true; + } else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { + this.lexer.state.inRawBlock = false; } return { type: this.options.sanitize ? 'text' : 'html', raw: cap[0], - inLink: inLink, - inRawBlock: inRawBlock, + inLink: this.lexer.state.inLink, + inRawBlock: this.lexer.state.inRawBlock, text: this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0] }; } @@ -879,14 +936,14 @@ } // ending angle bracket cannot be escaped - var rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\'); + var rtrimSlash = rtrim$1(trimmedUrl.slice(0, -1), '\\'); if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { return; } } else { // find closing parenthesis - var lastParenIndex = findClosingBracket(cap[2], '()'); + var lastParenIndex = findClosingBracket$1(cap[2], '()'); if (lastParenIndex > -1) { var start = cap[0].indexOf('!') === 0 ? 5 : 4; @@ -926,7 +983,7 @@ return outputLink(cap, { href: href ? href.replace(this.rules.inline._escapes, '$1') : href, title: title ? title.replace(this.rules.inline._escapes, '$1') : title - }, cap[0]); + }, cap[0], this.lexer); } }; @@ -946,7 +1003,7 @@ }; } - return outputLink(cap, link, cap[0]); + return outputLink(cap, link, cap[0], this.lexer); } }; @@ -997,18 +1054,23 @@ rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); // Create `em` if smallest delimiter has odd char count. *a*** if (Math.min(lLength, rLength) % 2) { + var _text = src.slice(1, lLength + match.index + rLength); + return { type: 'em', raw: src.slice(0, lLength + match.index + rLength + 1), - text: src.slice(1, lLength + match.index + rLength) + text: _text, + tokens: this.lexer.inlineTokens(_text, []) }; } // Create 'strong' if smallest delimiter has even char count. **a*** + var text = src.slice(2, lLength + match.index + rLength - 1); return { type: 'strong', raw: src.slice(0, lLength + match.index + rLength + 1), - text: src.slice(2, lLength + match.index + rLength - 1) + text: text, + tokens: this.lexer.inlineTokens(text, []) }; } } @@ -1053,7 +1115,8 @@ return { type: 'del', raw: cap[0], - text: cap[2] + text: cap[2], + tokens: this.lexer.inlineTokens(cap[2], []) }; } }; @@ -1127,13 +1190,13 @@ } }; - _proto.inlineText = function inlineText(src, inRawBlock, smartypants) { + _proto.inlineText = function inlineText(src, smartypants) { var cap = this.rules.inline.text.exec(src); if (cap) { var text; - if (inRawBlock) { + if (this.lexer.state.inRawBlock) { text = this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0]; } else { text = _escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]); @@ -1150,21 +1213,21 @@ return Tokenizer; }(); - var noopTest = helpers.noopTest, - edit = helpers.edit, + var noopTest$1 = helpers.noopTest, + edit$1 = helpers.edit, merge$1 = helpers.merge; /** * Block-Level Grammar */ - var block$1 = { + var block = { newline: /^(?: *(?:\n|$))+/, code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/, - fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/, + fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/, hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/, + list: /^( {0,3}bull)( [^\n]+?)?(?:\n|$)/, html: '^ {0,3}(?:' // optional indentation + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)' // (1) + '|comment[^\\n]*(\\n+|$)' // (2) @@ -1176,76 +1239,66 @@ + '|(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) closing tag + ')', def: /^ {0,3}\[(label)\]: *\n? *([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, - nptable: noopTest, - table: noopTest, + table: noopTest$1, lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/, // regex template, placeholders will be replaced according to different paragraph // interruption rules of commonmark and the original markdown spec: _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/, text: /^[^\n]+/ }; - block$1._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; - block$1._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; - block$1.def = edit(block$1.def).replace('label', block$1._label).replace('title', block$1._title).getRegex(); - block$1.bullet = /(?:[*+-]|\d{1,9}[.)])/; - block$1.item = /^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/; - block$1.item = edit(block$1.item, 'gm').replace(/bull/g, block$1.bullet).getRegex(); - block$1.listItemStart = edit(/^( *)(bull) */).replace('bull', block$1.bullet).getRegex(); - block$1.list = edit(block$1.list).replace(/bull/g, block$1.bullet).replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))').replace('def', '\\n+(?=' + block$1.def.source + ')').getRegex(); - block$1._tag = '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|h[1-6]|head|header|hr|html|iframe' + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + '|track|ul'; - block$1._comment = /|$)/; - block$1.html = edit(block$1.html, 'i').replace('comment', block$1._comment).replace('tag', block$1._tag).replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); - block$1.paragraph = edit(block$1._paragraph).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs + block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; + block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; + block.def = edit$1(block.def).replace('label', block._label).replace('title', block._title).getRegex(); + block.bullet = /(?:[*+-]|\d{1,9}[.)])/; + block.listItemStart = edit$1(/^( *)(bull) */).replace('bull', block.bullet).getRegex(); + block.list = edit$1(block.list).replace(/bull/g, block.bullet).replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))').replace('def', '\\n+(?=' + block.def.source + ')').getRegex(); + block._tag = '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|h[1-6]|head|header|hr|html|iframe' + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + '|track|ul'; + block._comment = /|$)/; + block.html = edit$1(block.html, 'i').replace('comment', block._comment).replace('tag', block._tag).replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(); + block.paragraph = edit$1(block._paragraph).replace('hr', block.hr).replace('heading', ' {0,3}#{1,6} ').replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs .replace('blockquote', ' {0,3}>').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', '?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)').replace('tag', block$1._tag) // pars can be interrupted by type (6) html blocks + .replace('html', '?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)').replace('tag', block._tag) // pars can be interrupted by type (6) html blocks .getRegex(); - block$1.blockquote = edit(block$1.blockquote).replace('paragraph', block$1.paragraph).getRegex(); + block.blockquote = edit$1(block.blockquote).replace('paragraph', block.paragraph).getRegex(); /** * Normal Block Grammar */ - block$1.normal = merge$1({}, block$1); + block.normal = merge$1({}, block); /** * GFM Block Grammar */ - block$1.gfm = merge$1({}, block$1.normal, { - nptable: '^ *([^|\\n ].*\\|.*)\\n' // Header - + ' {0,3}([-:]+ *\\|[-| :]*)' // Align - + '(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)', - // Cells - table: '^ *\\|(.+)\\n' // Header - + ' {0,3}\\|?( *[-:]+[-| :]*)' // Align + block.gfm = merge$1({}, block.normal, { + table: '^ *([^\\n ].*\\|.*)\\n' // Header + + ' {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)\\|?' // Align + '(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells }); - block$1.gfm.nptable = edit(block$1.gfm.nptable).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', '?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)').replace('tag', block$1._tag) // tables can be interrupted by type (6) html blocks - .getRegex(); - block$1.gfm.table = edit(block$1.gfm.table).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt - .replace('html', '?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)').replace('tag', block$1._tag) // tables can be interrupted by type (6) html blocks + block.gfm.table = edit$1(block.gfm.table).replace('hr', block.hr).replace('heading', ' {0,3}#{1,6} ').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt + .replace('html', '?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)').replace('tag', block._tag) // tables can be interrupted by type (6) html blocks .getRegex(); /** * Pedantic grammar (original John Gruber's loose markdown specification) */ - block$1.pedantic = merge$1({}, block$1.normal, { - html: edit('^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)' // closed tag - + '|
An error occurred:
' + escape(e.message + '', true) + ''; + return '
An error occurred:
' + escape$2(e.message + '', true) + ''; } throw e; @@ -2700,13 +2676,13 @@ marked.options = marked.setOptions = function (opt) { - merge(marked.defaults, opt); + merge$2(marked.defaults, opt); changeDefaults(marked.defaults); return marked; }; marked.getDefaults = getDefaults; - marked.defaults = defaults; + marked.defaults = defaults$5; /** * Use Extension */ @@ -2718,7 +2694,7 @@ args[_key] = arguments[_key]; } - var opts = merge.apply(void 0, [{}].concat(args)); + var opts = merge$2.apply(void 0, [{}].concat(args)); var extensions = marked.defaults.extensions || { renderers: {}, childTokens: {} @@ -2797,7 +2773,7 @@ if (pack.renderer) { (function () { - var renderer = marked.defaults.renderer || new Renderer(); + var renderer = marked.defaults.renderer || new Renderer_1(); var _loop = function _loop(prop) { var prevRenderer = renderer[prop]; // Replace renderer with func to run extension, but fall back if false @@ -2827,7 +2803,7 @@ if (pack.tokenizer) { (function () { - var tokenizer = marked.defaults.tokenizer || new Tokenizer(); + var tokenizer = marked.defaults.tokenizer || new Tokenizer_1(); var _loop2 = function _loop2(prop) { var prevTokenizer = tokenizer[prop]; // Replace tokenizer with func to run extension, but fall back if false @@ -2944,22 +2920,22 @@ throw new Error('marked.parseInline(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected'); } - opt = merge({}, marked.defaults, opt || {}); - checkSanitizeDeprecation(opt); + opt = merge$2({}, marked.defaults, opt || {}); + checkSanitizeDeprecation$1(opt); try { - var tokens = Lexer.lexInline(src, opt); + var tokens = Lexer_1.lexInline(src, opt); if (opt.walkTokens) { marked.walkTokens(tokens, opt.walkTokens); } - return Parser.parseInline(tokens, opt); + return Parser_1.parseInline(tokens, opt); } catch (e) { e.message += '\nPlease report this to https://github.com/markedjs/marked.'; if (opt.silent) { - return '
An error occurred:
' + escape(e.message + '', true) + ''; + return '
An error occurred:
' + escape$2(e.message + '', true) + ''; } throw e; @@ -2970,14 +2946,14 @@ */ - marked.Parser = Parser; - marked.parser = Parser.parse; - marked.Renderer = Renderer; - marked.TextRenderer = TextRenderer; - marked.Lexer = Lexer; - marked.lexer = Lexer.lex; - marked.Tokenizer = Tokenizer; - marked.Slugger = Slugger; + marked.Parser = Parser_1; + marked.parser = Parser_1.parse; + marked.Renderer = Renderer_1; + marked.TextRenderer = TextRenderer_1; + marked.Lexer = Lexer_1; + marked.lexer = Lexer_1.lex; + marked.Tokenizer = Tokenizer_1; + marked.Slugger = Slugger_1; marked.parse = marked; var marked_1 = marked; diff --git a/src/Lexer.js b/src/Lexer.js index c1d8b03cb4..d34aa47bc0 100644 --- a/src/Lexer.js +++ b/src/Lexer.js @@ -163,10 +163,9 @@ module.exports = class Lexer { src = src.substring(token.raw.length); lastToken = tokens[tokens.length - 1]; // An indented code block cannot interrupt a paragraph. - if (lastToken && lastToken.type === 'paragraph') { + if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) { lastToken.raw += '\n' + token.raw; lastToken.text += '\n' + token.text; - this.inlineQueue.pop(); this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; } else { tokens.push(token); @@ -217,9 +216,14 @@ module.exports = class Lexer { } // def - if (this.state.top && (token = this.tokenizer.def(src))) { + if (token = this.tokenizer.def(src)) { src = src.substring(token.raw.length); - if (!this.tokens.links[token.tag]) { + lastToken = tokens[tokens.length - 1]; + if (lastToken && (lastToken.type === 'paragraph' || lastToken.type === 'text')) { + lastToken.raw += '\n' + token.raw; + lastToken.text += '\n' + token.raw; + this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text; + } else if (!this.tokens.links[token.tag]) { this.tokens.links[token.tag] = { href: token.href, title: token.title diff --git a/src/Parser.js b/src/Parser.js index e804c143c4..ef5009aea8 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -149,7 +149,7 @@ module.exports = class Parser { if (item.task) { checkbox = this.renderer.checkbox(checked); if (loose) { - if (item.tokens.length > 0 && item.tokens[0].type === 'text') { + if (item.tokens.length > 0 && item.tokens[0].type === 'paragraph') { item.tokens[0].text = checkbox + ' ' + item.tokens[0].text; if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') { item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text; diff --git a/src/Tokenizer.js b/src/Tokenizer.js index 922452e7be..85bdb33675 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -164,145 +164,149 @@ module.exports = class Tokenizer { } list(src) { - const cap = this.rules.block.list.exec(src); + let cap = this.rules.block.list.exec(src); if (cap) { - let raw = cap[0]; - const bull = cap[2]; + let raw, istask, ischecked, indent, i, blankLine, endsWithBlankLine, + line, lines, itemContents; + + let bull = cap[1].trim(); const isordered = bull.length > 1; const list = { type: 'list', - raw, + raw: '', ordered: isordered, start: isordered ? +bull.slice(0, -1) : '', loose: false, items: [] }; - // Get each top-level item. - const itemMatch = cap[0].match(this.rules.block.item); - - let next = false, - item, - space, - bcurr, - bnext, - addBack, - loose, - istask, - ischecked, - endMatch; - - let l = itemMatch.length; - bcurr = this.rules.block.listItemStart.exec(itemMatch[0]); - for (let i = 0; i < l; i++) { - item = itemMatch[i]; - raw = item; - - if (!this.options.pedantic) { - // Determine if current item contains the end of the list - endMatch = item.match(new RegExp('\\n\\s*\\n {0,' + (bcurr[0].length - 1) + '}\\S')); - if (endMatch) { - addBack = item.length - endMatch.index + itemMatch.slice(i + 1).join('\n').length; - list.raw = list.raw.substring(0, list.raw.length - addBack); - - item = item.substring(0, endMatch.index); - raw = item; - l = i + 1; - } + bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`; + + if (this.options.pedantic) { + bull = isordered ? bull : '[*+-]'; + } + + // Get next list item + const itemRegex = new RegExp(`^( {0,3}${bull})((?: [^\\n]*| *)(?:\\n[^\\n]*)*(?:\\n|$))`); + + // Get each top-level item + while (src) { + if (this.rules.block.hr.test(src)) { // End list if we encounter an HR (possibly move into itemRegex?) + break; } - // Determine whether the next list item belongs here. - // Backpedal if it does not belong in this list. - if (i !== l - 1) { - bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]); - if ( - !this.options.pedantic - ? bnext[1].length >= bcurr[0].length || bnext[1].length > 3 - : bnext[1].length > bcurr[1].length - ) { - // nested list or continuation - itemMatch.splice(i, 2, itemMatch[i] + (!this.options.pedantic && bnext[1].length < bcurr[0].length && !itemMatch[i].match(/\n$/) ? '' : '\n') + itemMatch[i + 1]); - i--; - l--; - continue; - } else if ( - // different bullet style - !this.options.pedantic || this.options.smartLists - ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1] - : isordered === (bnext[2].length === 1) - ) { - addBack = itemMatch.slice(i + 1).join('\n').length; - list.raw = list.raw.substring(0, list.raw.length - addBack); - i = l - 1; - } - bcurr = bnext; + if (!(cap = itemRegex.exec(src))) { + break; } - // Remove the list item's bullet - // so it is seen as the next token. - space = item.length; - item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); - - // Outdent whatever the - // list item contains. Hacky. - if (~item.indexOf('\n ')) { - space -= item.length; - item = !this.options.pedantic - ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') - : item.replace(/^ {1,4}/gm, ''); + lines = cap[2].split('\n'); + + if (this.options.pedantic) { + indent = 2; + itemContents = lines[0].trimLeft(); + } else { + indent = cap[2].search(/[^ ]/); // Find first non-space char + indent = cap[1].length + (indent > 4 ? 1 : indent); // intented code blocks after 4 spaces; indent is always 1 + itemContents = lines[0].slice(indent - cap[1].length); } - // trim item newlines at end - item = rtrim(item, '\n'); - if (i !== l - 1) { - raw = raw + '\n'; + blankLine = false; + raw = cap[0]; + + if (!lines[0] && /^ *$/.test(lines[1])) { // items begin with at most one blank line + raw = cap[1] + lines.slice(0, 2).join('\n') + '\n'; + list.loose = true; + lines = []; } - // Determine whether item is loose or not. - // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ - // for discount behavior. - loose = next || /\n\n(?!\s*$)/.test(raw); - if (i !== l - 1) { - next = raw.slice(-2) === '\n\n'; - if (!loose) loose = next; + const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])`); + + for (i = 1; i < lines.length; i++) { + line = lines[i]; + + if (this.options.pedantic) { // Re-align to follow commonmark nesting rules + line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); + } + + // End list item if found start of new bullet + if (nextBulletRegex.test(line)) { + raw = cap[1] + lines.slice(0, i).join('\n') + '\n'; + break; + } + + // Until we encounter a blank line, item contents do not need indentation + if (!blankLine) { + if (!line.trim()) { // Check if current line is empty + blankLine = true; + } + + // Dedent if possible + if (line.search(/[^ ]/) >= indent) { + itemContents += '\n' + line.slice(indent); + } else { + itemContents += '\n' + line; + } + continue; + } + + // Dedent this line + if (line.search(/[^ ]/) >= indent || !line.trim()) { + itemContents += '\n' + line.slice(indent); + continue; + } else { // Line was not properly indented; end of this item + raw = cap[1] + lines.slice(0, i).join('\n') + '\n'; + break; + } } - if (loose) { - list.loose = true; + if (!list.loose) { + // If the previous item ended with a blank line, the list is loose + if (endsWithBlankLine) { + list.loose = true; + } else if (/\n *\n *$/.test(raw)) { + endsWithBlankLine = true; + } } // Check for task list items if (this.options.gfm) { - istask = /^\[[ xX]\] /.test(item); - ischecked = undefined; + istask = /^\[[ xX]\] /.exec(itemContents); if (istask) { - ischecked = item[1] !== ' '; - item = item.replace(/^\[[ xX]\] +/, ''); + ischecked = istask[0] !== '[ ] '; + itemContents = itemContents.replace(/^\[[ xX]\] +/, ''); } } - this.lexer.state.top = false; - - const token = { + list.items.push({ type: 'list_item', - raw, - task: istask, + raw: raw, + task: !!istask, checked: ischecked, - loose: loose, - text: item, - tokens: this.lexer.blockTokens(item, []) - }; + loose: false, + text: itemContents + }); - // this.lexer.inline(token.text, ) - list.items.push(token); + list.raw += raw; + src = src.slice(raw.length); } - // l2 = token.items.length; - // for (j = 0; j < l2; j++) { - // this.inline(token.items[j].tokens); - // } - // break; + // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic + list.items[list.items.length - 1].raw = raw.trimRight(); + list.items[list.items.length - 1].text = itemContents.trimRight(); + list.raw = list.raw.trimRight(); + + const l = list.items.length; + + // Item child tokens handled here at end because we needed to have the final item to trim it first + for (i = 0; i < l; i++) { + this.lexer.state.top = false; + list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, []); + if (list.items[i].tokens.some(t => t.type === 'space')) { + list.loose = true; + list.items[i].loose = true; + } + } return list; } diff --git a/src/rules.js b/src/rules.js index 13961f2251..39fa8e56c4 100644 --- a/src/rules.js +++ b/src/rules.js @@ -10,11 +10,11 @@ const { const block = { newline: /^(?: *(?:\n|$))+/, code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/, - fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/, + fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/, hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/, + list: /^( {0,3}bull)( [^\n]+?)?(?:\n|$)/, html: '^ {0,3}(?:' // optional indentation + '<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)' // (1) + '|comment[^\\n]*(\\n+|$)' // (2) @@ -42,11 +42,6 @@ block.def = edit(block.def) .getRegex(); block.bullet = /(?:[*+-]|\d{1,9}[.)])/; -block.item = /^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/; -block.item = edit(block.item, 'gm') - .replace(/bull/g, block.bullet) - .getRegex(); - block.listItemStart = edit(/^( *)(bull) */) .replace('bull', block.bullet) .getRegex(); diff --git a/test/specs/commonmark/commonmark.0.30.json b/test/specs/commonmark/commonmark.0.30.json index 18a49682bd..62a0dfe076 100644 --- a/test/specs/commonmark/commonmark.0.30.json +++ b/test/specs/commonmark/commonmark.0.30.json @@ -2107,8 +2107,7 @@ "example": 262, "start_line": 4314, "end_line": 4326, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n", @@ -2124,8 +2123,7 @@ "example": 264, "start_line": 4359, "end_line": 4377, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "123456789. ok\n", @@ -2197,8 +2195,7 @@ "example": 273, "start_line": 4493, "end_line": 4509, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "1. indented code\n\n paragraph\n\n more code\n", @@ -2206,8 +2203,7 @@ "example": 274, "start_line": 4515, "end_line": 4531, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": " foo\n\nbar\n", @@ -2239,8 +2235,7 @@ "example": 278, "start_line": 4596, "end_line": 4617, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "- \n foo\n", @@ -2256,8 +2251,7 @@ "example": 280, "start_line": 4636, "end_line": 4645, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "- foo\n-\n- bar\n", @@ -2289,8 +2283,7 @@ "example": 284, "start_line": 4695, "end_line": 4701, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "foo\n*\n\nfoo\n1.\n", @@ -2466,8 +2459,7 @@ "example": 306, "start_line": 5388, "end_line": 5407, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "- foo\n - bar\n - baz\n\n\n bim\n", @@ -2475,8 +2467,7 @@ "example": 307, "start_line": 5409, "end_line": 5431, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "- foo\n- bar\n\n\n\n- baz\n- bim\n", @@ -2556,8 +2547,7 @@ "example": 317, "start_line": 5645, "end_line": 5663, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n", @@ -2565,8 +2555,7 @@ "example": 318, "start_line": 5668, "end_line": 5687, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "- a\n - b\n\n c\n- d\n", @@ -2574,8 +2563,7 @@ "example": 319, "start_line": 5694, "end_line": 5712, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "* a\n > b\n >\n* c\n", diff --git a/test/specs/gfm/commonmark.0.30.json b/test/specs/gfm/commonmark.0.30.json index 1b01311866..e32558a3a3 100644 --- a/test/specs/gfm/commonmark.0.30.json +++ b/test/specs/gfm/commonmark.0.30.json @@ -2107,8 +2107,7 @@ "example": 262, "start_line": 4314, "end_line": 4326, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n", @@ -2124,8 +2123,7 @@ "example": 264, "start_line": 4359, "end_line": 4377, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "123456789. ok\n", @@ -2197,8 +2195,7 @@ "example": 273, "start_line": 4493, "end_line": 4509, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "1. indented code\n\n paragraph\n\n more code\n", @@ -2206,8 +2203,7 @@ "example": 274, "start_line": 4515, "end_line": 4531, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": " foo\n\nbar\n", @@ -2239,8 +2235,7 @@ "example": 278, "start_line": 4596, "end_line": 4617, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "- \n foo\n", @@ -2256,8 +2251,7 @@ "example": 280, "start_line": 4636, "end_line": 4645, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "- foo\n-\n- bar\n", @@ -2289,8 +2283,7 @@ "example": 284, "start_line": 4695, "end_line": 4701, - "section": "List items", - "shouldFail": true + "section": "List items" }, { "markdown": "foo\n*\n\nfoo\n1.\n", @@ -2466,8 +2459,7 @@ "example": 306, "start_line": 5388, "end_line": 5407, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "- foo\n - bar\n - baz\n\n\n bim\n", @@ -2475,8 +2467,7 @@ "example": 307, "start_line": 5409, "end_line": 5431, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "- foo\n- bar\n\n\n\n- baz\n- bim\n", @@ -2556,8 +2547,7 @@ "example": 317, "start_line": 5645, "end_line": 5663, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n", @@ -2565,8 +2555,7 @@ "example": 318, "start_line": 5668, "end_line": 5687, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "- a\n - b\n\n c\n- d\n", @@ -2574,8 +2563,7 @@ "example": 319, "start_line": 5694, "end_line": 5712, - "section": "Lists", - "shouldFail": true + "section": "Lists" }, { "markdown": "* a\n > b\n >\n* c\n", diff --git a/test/specs/new/def_blocks.html b/test/specs/new/def_blocks.html index 5d8de49c04..23207da91e 100644 --- a/test/specs/new/def_blocks.html +++ b/test/specs/new/def_blocks.html @@ -12,13 +12,14 @@
hello
+hello +[4]: hello
+Just a note, I've found that I can't test my markdown parser vs others. For example, both markdown.js and showdown code blocks in lists wrong. They're also completely inconsistent with regards to paragraphs in list items.
A link. Not anymore.
List Item 1
List Item 2
Code goes here.
-Lots of it...
List Item 3 The final item.
List Item 4 The real final item.
Paragraph.
- bq Item 1
- bq Item 2
- New bq Item 1
- New bq Item 2 Text here
Another blockquote! I really need to get more creative with mockup text.. markdown.js breaks here again
Hello world. Here is a link. And an image .
Code goes here.
-Lots of it...
diff --git a/test/specs/new/main.md b/test/specs/new/main.md
deleted file mode 100644
index 58e17a6a76..0000000000
--- a/test/specs/new/main.md
+++ /dev/null
@@ -1,55 +0,0 @@
-[test]: http://google.com/ "Google"
-
-# A heading
-
-Just a note, I've found that I can't test my markdown parser vs others.
-For example, both markdown.js and showdown code blocks in lists wrong. They're
-also completely [inconsistent][test] with regards to paragraphs in list items.
-
-A link. Not anymore.
-
-
-
-* List Item 1
-
-* List Item 2
- * New List Item 1
- Hi, this is a list item.
- * New List Item 2
- Another item
- Code goes here.
- Lots of it...
- * New List Item 3
- The last item
-
-* List Item 3
-The final item.
-
-* List Item 4
-The real final item.
-
-Paragraph.
-
-> * bq Item 1
-> * bq Item 2
-> * New bq Item 1
-> * New bq Item 2
-> Text here
-
-* * *
-
-> Another blockquote!
-> I really need to get
-> more creative with
-> mockup text..
-> markdown.js breaks here again
-
-Another Heading
--------------
-
-Hello *world*. Here is a [link](//hello).
-And an image ![alt](src).
-
- Code goes here.
- Lots of it...
diff --git a/test/unit/Lexer-spec.js b/test/unit/Lexer-spec.js
index f94cd6e70d..af3f5781b1 100644
--- a/test/unit/Lexer-spec.js
+++ b/test/unit/Lexer-spec.js
@@ -295,7 +295,7 @@ a | b
tokens: [
{
type: 'list',
- raw: '- item 1\n- item 2\n',
+ raw: '- item 1\n- item 2',
ordered: false,
start: '',
loose: false,
@@ -316,7 +316,7 @@ a | b
},
{
type: 'list_item',
- raw: '- item 2\n',
+ raw: '- item 2',
task: false,
checked: undefined,
loose: false,
@@ -343,7 +343,7 @@ a | b
tokens: jasmine.arrayContaining([
jasmine.objectContaining({
type: 'list',
- raw: '1. item 1\n2. item 2\n',
+ raw: '1. item 1\n2. item 2',
ordered: true,
start: 1,
items: [
@@ -351,7 +351,7 @@ a | b
raw: '1. item 1\n'
}),
jasmine.objectContaining({
- raw: '2. item 2\n'
+ raw: '2. item 2'
})
]
})
@@ -368,7 +368,7 @@ a | b
tokens: jasmine.arrayContaining([
jasmine.objectContaining({
type: 'list',
- raw: '1) item 1\n2) item 2\n',
+ raw: '1) item 1\n2) item 2',
ordered: true,
start: 1,
items: [
@@ -376,7 +376,7 @@ a | b
raw: '1) item 1\n'
}),
jasmine.objectContaining({
- raw: '2) item 2\n'
+ raw: '2) item 2'
})
]
})
@@ -395,7 +395,7 @@ paragraph
tokens: [
{
type: 'list',
- raw: '- item 1\n- item 2\n\n',
+ raw: '- item 1\n- item 2',
ordered: false,
start: '',
loose: false,
@@ -416,7 +416,7 @@ paragraph
},
{
type: 'list_item',
- raw: '- item 2\n\n',
+ raw: '- item 2',
task: false,
checked: undefined,
loose: false,
@@ -430,6 +430,7 @@ paragraph
}
]
},
+ { type: 'space', raw: '\n\n' },
{
type: 'paragraph',
raw: 'paragraph',
@@ -453,7 +454,7 @@ paragraph
tokens: jasmine.arrayContaining([
jasmine.objectContaining({
type: 'list',
- raw: '2. item 1\n3. item 2\n',
+ raw: '2. item 1\n3. item 2',
ordered: true,
start: 2,
items: [
@@ -461,7 +462,7 @@ paragraph
raw: '2. item 1\n'
}),
jasmine.objectContaining({
- raw: '3. item 2\n'
+ raw: '3. item 2'
})
]
})
@@ -479,14 +480,14 @@ paragraph
tokens: jasmine.arrayContaining([
jasmine.objectContaining({
type: 'list',
- raw: '- item 1\n\n- item 2\n',
+ raw: '- item 1\n\n- item 2',
loose: true,
items: [
jasmine.objectContaining({
raw: '- item 1\n\n'
}),
jasmine.objectContaining({
- raw: '- item 2\n'
+ raw: '- item 2'
})
]
})
@@ -503,7 +504,7 @@ paragraph
tokens: jasmine.arrayContaining([
jasmine.objectContaining({
type: 'list',
- raw: '- [ ] item 1\n- [x] item 2\n',
+ raw: '- [ ] item 1\n- [x] item 2',
items: [
jasmine.objectContaining({
raw: '- [ ] item 1\n',
@@ -511,7 +512,7 @@ paragraph
checked: false
}),
jasmine.objectContaining({
- raw: '- [x] item 2\n',
+ raw: '- [x] item 2',
task: true,
checked: true
})
diff --git a/test/unit/marked-spec.js b/test/unit/marked-spec.js
index 6b67a8fe61..79fa151520 100644
--- a/test/unit/marked-spec.js
+++ b/test/unit/marked-spec.js
@@ -1001,6 +1001,7 @@ br
['heading', '# heading'],
['text', 'heading'],
['code', '```code```'],
+ ['space', ''],
['table', '| a | b ||---|---|| 1 | 2 || 3 | 4 |'],
['text', 'a'],
['text', 'b'],
@@ -1015,6 +1016,7 @@ br
['list_item', '- list'],
['text', 'list'],
['text', 'list'],
+ ['space', ''],
['html', '