UNPKG

@mapbox/jsxtreme-markdown

Version:
448 lines (361 loc) 9.97 kB
'use strict'; var trim = require('string.prototype.trim'); var repeat = require('repeat-string'); var decimal = require('is-decimal'); var getIndent = require('../util/get-indentation'); var removeIndent = require('../util/remove-indentation'); var interrupt = require('../util/interrupt'); module.exports = list; var asterisk = '*'; var underscore = '_'; var plusSign = '+'; var dash = '-'; var dot = '.'; var space = ' '; var lineFeed = '\n'; var tab = '\t'; var rightParenthesis = ')'; var lowercaseX = 'x'; var tabSize = 4; var looseListItemExpression = /\n\n(?!\s*$)/; var taskItemExpression = /^\[([ X\tx])][ \t]/; var bulletExpression = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t|$|(?=\n))([^\n]*)/; var pedanticBulletExpression = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/; var initialIndentExpression = /^( {1,4}|\t)?/gm; function list(eat, value, silent) { var self = this; var commonmark = self.options.commonmark; var pedantic = self.options.pedantic; var tokenizers = self.blockTokenizers; var interuptors = self.interruptList; var index = 0; var length = value.length; var start = null; var size; var queue; var ordered; var character; var marker; var nextIndex; var startIndex; var prefixed; var currentMarker; var content; var line; var previousEmpty; var empty; var items; var allLines; var emptyLines; var item; var enterTop; var exitBlockquote; var spread = false; var node; var now; var end; var indented; while (index < length) { character = value.charAt(index); if (character !== tab && character !== space) { break; } index++; } character = value.charAt(index); if (character === asterisk || character === plusSign || character === dash) { marker = character; ordered = false; } else { ordered = true; queue = ''; while (index < length) { character = value.charAt(index); if (!decimal(character)) { break; } queue += character; index++; } character = value.charAt(index); if ( !queue || !(character === dot || (commonmark && character === rightParenthesis)) ) { return; } /* Slightly abusing `silent` mode, whose goal is to make interrupting * paragraphs work. * Well, that’s exactly what we want to do here: don’t interrupt: * 2. here, because the “list” doesn’t start with `1`. */ if (silent && queue !== '1') { return; } start = parseInt(queue, 10); marker = character; } character = value.charAt(++index); if ( character !== space && character !== tab && (pedantic || (character !== lineFeed && character !== '')) ) { return; } if (silent) { return true; } index = 0; items = []; allLines = []; emptyLines = []; while (index < length) { nextIndex = value.indexOf(lineFeed, index); startIndex = index; prefixed = false; indented = false; if (nextIndex === -1) { nextIndex = length; } size = 0; while (index < length) { character = value.charAt(index); if (character === tab) { size += tabSize - (size % tabSize); } else if (character === space) { size++; } else { break; } index++; } if (item && size >= item.indent) { indented = true; } character = value.charAt(index); currentMarker = null; if (!indented) { if ( character === asterisk || character === plusSign || character === dash ) { currentMarker = character; index++; size++; } else { queue = ''; while (index < length) { character = value.charAt(index); if (!decimal(character)) { break; } queue += character; index++; } character = value.charAt(index); index++; if ( queue && (character === dot || (commonmark && character === rightParenthesis)) ) { currentMarker = character; size += queue.length + 1; } } if (currentMarker) { character = value.charAt(index); if (character === tab) { size += tabSize - (size % tabSize); index++; } else if (character === space) { end = index + tabSize; while (index < end) { if (value.charAt(index) !== space) { break; } index++; size++; } if (index === end && value.charAt(index) === space) { index -= tabSize - 1; size -= tabSize - 1; } } else if (character !== lineFeed && character !== '') { currentMarker = null; } } } if (currentMarker) { if (!pedantic && marker !== currentMarker) { break; } prefixed = true; } else { if (!commonmark && !indented && value.charAt(startIndex) === space) { indented = true; } else if (commonmark && item) { indented = size >= item.indent || size > tabSize; } prefixed = false; index = startIndex; } line = value.slice(startIndex, nextIndex); content = startIndex === index ? line : value.slice(index, nextIndex); if ( currentMarker === asterisk || currentMarker === underscore || currentMarker === dash ) { if (tokenizers.thematicBreak.call(self, eat, line, true)) { break; } } previousEmpty = empty; empty = !prefixed && !trim(content).length; if (indented && item) { item.value = item.value.concat(emptyLines, line); allLines = allLines.concat(emptyLines, line); emptyLines = []; } else if (prefixed) { if (emptyLines.length !== 0) { spread = true; item.value.push(''); item.trail = emptyLines.concat(); } item = { value: [line], indent: size, trail: [], }; items.push(item); allLines = allLines.concat(emptyLines, line); emptyLines = []; } else if (empty) { if (previousEmpty && !commonmark) { break; } emptyLines.push(line); } else { if (previousEmpty) { break; } if (interrupt(interuptors, tokenizers, self, [eat, line, true])) { break; } item.value = item.value.concat(emptyLines, line); allLines = allLines.concat(emptyLines, line); emptyLines = []; } index = nextIndex + 1; } node = eat(allLines.join(lineFeed)).reset({ type: 'list', ordered: ordered, start: start, spread: spread, children: [], }); enterTop = self.enterList(); exitBlockquote = self.enterBlock(); index = -1; length = items.length; while (++index < length) { item = items[index].value.join(lineFeed); now = eat.now(); eat(item)(listItem(self, item, now), node); item = items[index].trail.join(lineFeed); if (index !== length - 1) { item += lineFeed; } eat(item); } enterTop(); exitBlockquote(); return node; } function listItem(ctx, value, position) { var offsets = ctx.offset; var fn = ctx.options.pedantic ? pedanticListItem : normalListItem; var checked = null; var task; var indent; value = fn.apply(null, arguments); if (ctx.options.gfm) { task = value.match(taskItemExpression); if (task) { indent = task[0].length; checked = task[1].toLowerCase() === lowercaseX; offsets[position.line] += indent; value = value.slice(indent); } } return { type: 'listItem', spread: looseListItemExpression.test(value), checked: checked, children: ctx.tokenizeBlock(value, position), }; } // Create a list-item using overly simple mechanics. function pedanticListItem(ctx, value, position) { var offsets = ctx.offset; var line = position.line; // Remove the list-item’s bullet. value = value.replace(pedanticBulletExpression, replacer); // The initial line was also matched by the below, so we reset the `line`. line = position.line; return value.replace(initialIndentExpression, replacer); // A simple replacer which removed all matches, and adds their length to // `offset`. function replacer($0) { offsets[line] = (offsets[line] || 0) + $0.length; line++; return ''; } } // Create a list-item using sane mechanics. function normalListItem(ctx, value, position) { var offsets = ctx.offset; var line = position.line; var max; var bullet; var rest; var lines; var trimmedLines; var index; var length; // Remove the list-item’s bullet. value = value.replace(bulletExpression, replacer); lines = value.split(lineFeed); trimmedLines = removeIndent(value, getIndent(max).indent).split(lineFeed); // We replaced the initial bullet with something else above, which was used // to trick `removeIndentation` into removing some more characters when // possible. However, that could result in the initial line to be stripped // more than it should be. trimmedLines[0] = rest; offsets[line] = (offsets[line] || 0) + bullet.length; line++; index = 0; length = lines.length; while (++index < length) { offsets[line] = (offsets[line] || 0) + lines[index].length - trimmedLines[index].length; line++; } return trimmedLines.join(lineFeed); /* eslint-disable-next-line max-params */ function replacer($0, $1, $2, $3, $4) { bullet = $1 + $2 + $3; rest = $4; // Make sure that the first nine numbered list items can indent with an // extra space. That is, when the bullet did not receive an extra final // space. if (Number($2) < 10 && bullet.length % 2 === 1) { $2 = space + $2; } max = $1 + repeat(space, $2.length) + $3; return max + rest; } }