shelving
Version:
Toolkit for using data in JavaScript.
45 lines (44 loc) • 3.22 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { createMarkupRule } from "../MarkupRule.js";
import { BLOCK_CONTENT_REGEXP, BLOCK_SPACE_REGEXP, BLOCK_START_REGEXP, createBlockRegExp, LINE_SPACE_REGEXP } from "../util/regexp.js";
const _INDENT = /^\t/gm; // Nesting is recognised with tabs only.
const _BULLET = "[-*•+]"; // Allowed bullet symbol.
const _ITEM = new RegExp(`(?:^|\n)${_BULLET}(?:${LINE_SPACE_REGEXP}+(${BLOCK_CONTENT_REGEXP}))?${BLOCK_SPACE_REGEXP}*(?=\n${_BULLET}(?:\\s|$)|$)`, "g");
// A GitHub-style todo checkbox at the start of an item: `[ ]` (unchecked) or `[x]` (checked,
// case-insensitive), which must be followed by whitespace or the end of the item.
const _CHECKBOX = /^\[([ xX])\](?!\S)[^\n\S]*/;
// End of a list block: the end of the string, or a blank line that is *not* followed by another
// item or an indented continuation line. A blank line before a new item or a continuation
// paragraph keeps the list going (a "loose" list); anything else ends the list.
const _END = `${BLOCK_SPACE_REGEXP}*(?:$|\\n${LINE_SPACE_REGEXP}*\\n(?!${LINE_SPACE_REGEXP}|${_BULLET}))`;
// A list is "loose" when it contains a blank line. Loose items are parsed as blocks so their
// content is wrapped in `<p>` tags instead of rendered inline.
const _LOOSE = new RegExp(`\\n${LINE_SPACE_REGEXP}*\\n`);
/**
* Unordered list.
* - Line starting with `-`, `*`, `•`, or `+` character followed by one or more space characters.
* - No spaces can appear before the bullet character.
* - Second-level list can be created by indenting with `\t` one tab.
* - List block runs until a blank line that is not followed by another item or an indented continuation line.
* - A list with blank lines between its items (or before a continuation paragraph) is "loose": its items are wrapped in `<p>` tags.
* - An item starting with `[ ]` or `[x]` (case-insensitive) is a todo item: a checkbox `<input>` plus the content wrapped in a `<label>` so clicking it toggles the checkbox.
* - Sparse lists are not supported.
*/
export const UNORDERED_RULE = createMarkupRule(createBlockRegExp(`(?<list>${_BULLET}(?:${LINE_SPACE_REGEXP}+${BLOCK_CONTENT_REGEXP})?)`, BLOCK_START_REGEXP, _END), (key, { list }, parser) => _jsx("ul", { children: Array.from(_getItems(list, parser)) }, key), ["block", "list"]);
/** Parse a markdown list into a set of items elements. */
export function* _getItems(list, parser) {
// Items of a loose list are parsed as blocks so `PARAGRAPH_RULE` wraps their content in `<p>`.
const context = _LOOSE.test(list) ? "block" : "list";
let key = 0;
for (const [_unused, raw = ""] of list.matchAll(_ITEM)) {
const item = raw.replace(_INDENT, "");
// A todo item renders a checkbox; the `<label>` wrapping the content toggles it on click.
const checkbox = _CHECKBOX.exec(item);
if (checkbox) {
yield (_jsx("li", { children: _jsxs("label", { children: [_jsx("input", { type: "checkbox", defaultChecked: checkbox[1] !== " " }), parser.parse(item.slice(checkbox[0].length), context)] }) }, key++));
}
else {
yield _jsx("li", { children: parser.parse(item, context) }, key++);
}
}
}