@mapbox/jsxtreme-markdown
Version:
Transform Markdown into JSX or React component modules
448 lines (361 loc) • 9.97 kB
JavaScript
;
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;
}
}