@effected/markdown 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/Frontmatter.js +272 -0
  2. package/FrontmatterResolver.js +273 -0
  3. package/JsonFrontmatter.js +46 -0
  4. package/LICENSE +21 -0
  5. package/Markdown.js +251 -0
  6. package/MarkdownDiagnostic.js +85 -0
  7. package/MarkdownDocument.js +248 -0
  8. package/MarkdownEdit.js +52 -0
  9. package/MarkdownFormat.js +380 -0
  10. package/MarkdownNode.js +641 -0
  11. package/MarkdownVisitor.js +88 -0
  12. package/Mdast.js +384 -0
  13. package/README.md +255 -0
  14. package/TomlFrontmatter.js +44 -0
  15. package/YamlFrontmatter.js +45 -0
  16. package/index.d.ts +2190 -0
  17. package/index.js +15 -0
  18. package/internal/blockParser.js +419 -0
  19. package/internal/blockRegistry.js +90 -0
  20. package/internal/blockTypes.js +27 -0
  21. package/internal/blocks/atxHeading.js +54 -0
  22. package/internal/blocks/blockquote.js +40 -0
  23. package/internal/blocks/code.js +90 -0
  24. package/internal/blocks/document.js +17 -0
  25. package/internal/blocks/fencedCode.js +26 -0
  26. package/internal/blocks/footnoteDefinition.js +84 -0
  27. package/internal/blocks/frontmatter.js +56 -0
  28. package/internal/blocks/htmlBlock.js +72 -0
  29. package/internal/blocks/indentedCode.js +16 -0
  30. package/internal/blocks/linkReferenceDefinition.js +72 -0
  31. package/internal/blocks/list.js +159 -0
  32. package/internal/blocks/paragraph.js +27 -0
  33. package/internal/blocks/setextHeading.js +24 -0
  34. package/internal/blocks/table.js +378 -0
  35. package/internal/blocks/taskListItem.js +36 -0
  36. package/internal/blocks/thematicBreak.js +35 -0
  37. package/internal/carriers.js +42 -0
  38. package/internal/entities.js +26 -0
  39. package/internal/entityMap.js +7 -0
  40. package/internal/htmlTags.js +18 -0
  41. package/internal/inlineNode.js +54 -0
  42. package/internal/inlineParser.js +403 -0
  43. package/internal/inlineRegistry.js +68 -0
  44. package/internal/inlines/autolink.js +36 -0
  45. package/internal/inlines/autolinkLiteral.js +374 -0
  46. package/internal/inlines/codeSpan.js +32 -0
  47. package/internal/inlines/emphasis.js +79 -0
  48. package/internal/inlines/entity.js +21 -0
  49. package/internal/inlines/escape.js +34 -0
  50. package/internal/inlines/footnoteReference.js +63 -0
  51. package/internal/inlines/lineBreak.js +25 -0
  52. package/internal/inlines/link.js +212 -0
  53. package/internal/inlines/rawHtml.js +27 -0
  54. package/internal/inlines/strikethrough.js +81 -0
  55. package/internal/inlines/text.js +22 -0
  56. package/internal/lineIndex.js +76 -0
  57. package/internal/patterns.js +26 -0
  58. package/internal/preprocess.js +58 -0
  59. package/internal/rawInline.js +57 -0
  60. package/internal/references.js +160 -0
  61. package/internal/segments.js +36 -0
  62. package/internal/stringify.js +519 -0
  63. package/internal/unescape.js +18 -0
  64. package/package.json +61 -0
  65. package/tsdoc-metadata.json +11 -0
@@ -0,0 +1,90 @@
1
+ import { Code } from "../../MarkdownNode.js";
2
+ import { isSpaceOrTab, peekCode } from "../preprocess.js";
3
+ import { unescapeString } from "../unescape.js";
4
+
5
+ //#region src/internal/blocks/code.ts
6
+ const reBlankLine = /^[ \t]*$/;
7
+ const reClosingCodeFence = /^(?:`{3,}|~{3,})(?=[ \t]*$)/;
8
+ /**
9
+ * Drop the trailing blank lines an indented code block accumulates, and pull
10
+ * the block's end back to the last line that survived.
11
+ */
12
+ const finalizeIndented = (block) => {
13
+ const lines = block.stringContent.split("\n");
14
+ while (lines.length > 0 && reBlankLine.test(lines[lines.length - 1] ?? "")) lines.pop();
15
+ block.stringContent = lines.length === 0 ? "" : `${lines.join("\n")}\n`;
16
+ const lastSegment = block.segments[lines.length - 1];
17
+ if (lastSegment !== void 0) {
18
+ block.endOffset = lastSegment.sourceOffset + lastSegment.length;
19
+ block.endLine = block.startLine + lines.length - 1;
20
+ }
21
+ };
22
+ /**
23
+ * Split a fenced block's first line off as the info string, and the rest as
24
+ * the literal content.
25
+ *
26
+ * mdast splits the info string into `lang` (the first word) and `meta` (the
27
+ * rest); upstream keeps it whole as `info`. Each half is unescaped
28
+ * separately, so a backslash-escaped space inside the language word does not
29
+ * become a split point.
30
+ */
31
+ const finalizeFenced = (block) => {
32
+ const content = block.stringContent;
33
+ const newlineAt = content.indexOf("\n");
34
+ const firstLine = (newlineAt === -1 ? content : content.slice(0, newlineAt)).trim();
35
+ block.stringContent = newlineAt === -1 ? "" : content.slice(newlineAt + 1);
36
+ if (firstLine.length === 0) return;
37
+ const wordEnd = firstLine.search(/[ \t]/);
38
+ if (wordEnd === -1) {
39
+ block.data.lang = unescapeString(firstLine);
40
+ return;
41
+ }
42
+ block.data.lang = unescapeString(firstLine.slice(0, wordEnd));
43
+ const meta = firstLine.slice(wordEnd).trim();
44
+ if (meta.length > 0) block.data.meta = unescapeString(meta);
45
+ };
46
+ /** Code: absorbs lines verbatim, contains nothing. */
47
+ const codeConstruct = {
48
+ type: "code",
49
+ acceptsLines: true,
50
+ canContain: () => false,
51
+ continue: (scanner, block) => {
52
+ if (block.data.isFenced === true) {
53
+ const line = scanner.currentLine;
54
+ const closing = scanner.indent <= 3 && line.charAt(scanner.nextNonspace) === block.data.fenceChar ? reClosingCodeFence.exec(line.slice(scanner.nextNonspace)) : null;
55
+ if (closing !== null && closing[0].length >= (block.data.fenceLength ?? 3)) {
56
+ scanner.setLastLineLength(scanner.offset + scanner.indent + closing[0].length);
57
+ scanner.finalizeBlock(block, scanner.lineNumber);
58
+ return 2;
59
+ }
60
+ let remaining = block.data.fenceOffset ?? 0;
61
+ while (remaining > 0 && isSpaceOrTab(peekCode(line, scanner.offset))) {
62
+ scanner.advanceOffset(1, true);
63
+ remaining -= 1;
64
+ }
65
+ return 0;
66
+ }
67
+ if (scanner.indent >= 4) scanner.advanceOffset(4, true);
68
+ else if (scanner.blank) scanner.advanceNextNonspace();
69
+ else return 1;
70
+ return 0;
71
+ },
72
+ finalize: (_scanner, block) => {
73
+ if (block.data.isFenced === true) finalizeFenced(block);
74
+ else finalizeIndented(block);
75
+ },
76
+ materialize: (block, _children, context) => {
77
+ const { isFenced, fenceChar, fenceLength, lang, meta } = block.data;
78
+ return Code.make({
79
+ value: block.stringContent,
80
+ position: context.position(block.startOffset, block.endOffset),
81
+ ...lang === void 0 ? {} : { lang },
82
+ ...meta === void 0 ? {} : { meta },
83
+ ...isFenced === true && fenceChar !== void 0 ? { fenceChar } : {},
84
+ ...isFenced === true && fenceLength !== void 0 ? { fenceLength } : {}
85
+ });
86
+ }
87
+ };
88
+
89
+ //#endregion
90
+ export { codeConstruct };
@@ -0,0 +1,17 @@
1
+ import { Root } from "../../MarkdownNode.js";
2
+
3
+ //#region src/internal/blocks/document.ts
4
+ /** The document root: contains everything except a bare list item. */
5
+ const documentConstruct = {
6
+ type: "document",
7
+ acceptsLines: false,
8
+ canContain: (child) => child !== "listItem",
9
+ continue: () => 0,
10
+ materialize: (block, children, context) => Root.make({
11
+ children,
12
+ position: context.position(block.startOffset, block.endOffset)
13
+ })
14
+ };
15
+
16
+ //#endregion
17
+ export { documentConstruct };
@@ -0,0 +1,26 @@
1
+ //#region src/internal/blocks/fencedCode.ts
2
+ const reCodeFence = /^`{3,}(?!.*`)|^~{3,}/;
3
+ const fenceCharOf = (char) => char === "`" || char === "~" ? char : void 0;
4
+ /** The fenced-code block start: three or more backticks or tildes. */
5
+ const fencedCodeStart = {
6
+ name: "fencedCode",
7
+ trigger: (scanner) => {
8
+ if (scanner.indented) return 0;
9
+ const match = reCodeFence.exec(scanner.currentLine.slice(scanner.nextNonspace));
10
+ if (match === null) return 0;
11
+ const fenceChar = fenceCharOf(match[0].charAt(0));
12
+ if (fenceChar === void 0) return 0;
13
+ scanner.closeUnmatchedBlocks();
14
+ const container = scanner.addChild("code", scanner.nextNonspace);
15
+ container.data.isFenced = true;
16
+ container.data.fenceLength = match[0].length;
17
+ container.data.fenceChar = fenceChar;
18
+ container.data.fenceOffset = scanner.indent;
19
+ scanner.advanceNextNonspace();
20
+ scanner.advanceOffset(match[0].length, false);
21
+ return 2;
22
+ }
23
+ };
24
+
25
+ //#endregion
26
+ export { fencedCodeStart };
@@ -0,0 +1,84 @@
1
+ import { FootnoteDefinition } from "../../MarkdownNode.js";
2
+ import { flowChildren } from "../blockTypes.js";
3
+ import { stickyOf } from "../patterns.js";
4
+ import { normalizeLabelText } from "../references.js";
5
+
6
+ //#region src/internal/blocks/footnoteDefinition.ts
7
+ /**
8
+ * `_scan_footnote_definition` from `src/scanners.re`:
9
+ *
10
+ * ```re2c
11
+ * '[^' ([^\] \r\n\x00\t]+) ']:' [ \t]*
12
+ * ```
13
+ *
14
+ * The label class is the whole grammar and the whole surprise: a footnote
15
+ * label may hold no whitespace at all, which is where it parts company with a
16
+ * link reference label. The line terminators in the class are unreachable here
17
+ * (the block pass hands over one line at a time, terminator stripped) but stay
18
+ * so the class reads as upstream's.
19
+ */
20
+ const reFootnoteDefinition = /\[\^([^\]\0\t\n\r ]+)\]:[ \t]*/;
21
+ /** Columns of indentation a continuation line must carry. */
22
+ const CONTINUATION_INDENT = 4;
23
+ /**
24
+ * FootnoteDefinition: a container whose children are flow blocks, continued by
25
+ * four columns of indentation.
26
+ */
27
+ const footnoteDefinitionConstruct = {
28
+ type: "footnoteDefinition",
29
+ acceptsLines: false,
30
+ canContain: (child) => child !== "listItem",
31
+ continue: (scanner) => {
32
+ if (scanner.indent >= CONTINUATION_INDENT) {
33
+ scanner.advanceOffset(CONTINUATION_INDENT, true);
34
+ return 0;
35
+ }
36
+ return scanner.currentLine.length === 0 ? 0 : 1;
37
+ },
38
+ materialize: (block, children, context) => {
39
+ const footnote = block.data.footnote;
40
+ if (footnote === void 0) return;
41
+ return FootnoteDefinition.make({
42
+ identifier: footnote.identifier,
43
+ label: footnote.label,
44
+ children: flowChildren(children),
45
+ position: context.position(block.startOffset, block.endOffset)
46
+ });
47
+ }
48
+ };
49
+ /**
50
+ * The footnote-definition block start: an unindented `[^label]:`.
51
+ *
52
+ * Registered at the CORE position cmark-gfm runs it from — after the thematic
53
+ * break, before the list marker — rather than with the GFM extensions at the
54
+ * end of the table, because that is literally where the branch sits in
55
+ * `open_new_blocks`. It carries no `cont_type == PARAGRAPH` guard, so unlike
56
+ * the thematic break above it, a footnote definition interrupts a paragraph.
57
+ */
58
+ const footnoteDefinitionStart = {
59
+ name: "footnoteDefinition",
60
+ trigger: (scanner) => {
61
+ if (scanner.indented) return 0;
62
+ const sticky = stickyOf(reFootnoteDefinition);
63
+ sticky.lastIndex = scanner.nextNonspace;
64
+ const found = sticky.exec(scanner.currentLine);
65
+ const rawLabel = found?.[1];
66
+ if (found === null || rawLabel === void 0) return 0;
67
+ const key = normalizeLabelText(rawLabel);
68
+ if (key === "") return 0;
69
+ scanner.closeUnmatchedBlocks();
70
+ const start = scanner.nextNonspace;
71
+ scanner.advanceNextNonspace();
72
+ scanner.advanceOffset(found[0].length, false);
73
+ const block = scanner.addChild("footnoteDefinition", start);
74
+ block.data.footnote = {
75
+ key,
76
+ identifier: key.toLowerCase(),
77
+ label: rawLabel
78
+ };
79
+ return 1;
80
+ }
81
+ };
82
+
83
+ //#endregion
84
+ export { footnoteDefinitionConstruct, footnoteDefinitionStart };
@@ -0,0 +1,56 @@
1
+ //#region src/internal/blocks/frontmatter.ts
2
+ const FENCES = /* @__PURE__ */ new Map([
3
+ ["---", {
4
+ format: "yaml",
5
+ close: "---"
6
+ }],
7
+ ["+++", {
8
+ format: "toml",
9
+ close: "+++"
10
+ }],
11
+ ["---json", {
12
+ format: "json",
13
+ close: "---"
14
+ }]
15
+ ]);
16
+ /**
17
+ * Scan the head of a preprocessed document for a frontmatter block.
18
+ *
19
+ * `lines` is the preprocessor's line table (U+0000 already replaced,
20
+ * terminators stripped, absolute `start` offsets); `text` is the original
21
+ * source, consulted only for the terminators between value lines — a
22
+ * terminator can never contain U+0000, so slicing it from the source is
23
+ * exact, and the value keeps CRLF interiors verbatim while the line content
24
+ * keeps the preprocessor's U+FFFD replacement.
25
+ *
26
+ * Returns `null` when the document has no frontmatter — which is the common
27
+ * case and never an error.
28
+ */
29
+ const scanFrontmatter = (lines, text) => {
30
+ const opening = lines[0];
31
+ if (opening === void 0 || opening.start !== 0) return null;
32
+ const rule = FENCES.get(opening.text);
33
+ if (rule === void 0) return null;
34
+ for (let index = 1; index < lines.length; index += 1) {
35
+ const line = lines[index];
36
+ if (line === void 0 || line.text !== rule.close) continue;
37
+ const parts = [];
38
+ for (let inner = 1; inner < index; inner += 1) {
39
+ const current = lines[inner];
40
+ const next = lines[inner + 1];
41
+ if (current === void 0 || next === void 0) break;
42
+ parts.push(current.text);
43
+ if (inner + 1 < index) parts.push(text.slice(current.start + current.text.length, next.start));
44
+ }
45
+ return {
46
+ format: rule.format,
47
+ value: parts.join(""),
48
+ lineCount: index + 1,
49
+ endOffset: line.start + line.text.length
50
+ };
51
+ }
52
+ return null;
53
+ };
54
+
55
+ //#endregion
56
+ export { scanFrontmatter };
@@ -0,0 +1,72 @@
1
+ import { Html } from "../../MarkdownNode.js";
2
+ import { peekCode } from "../preprocess.js";
3
+ import { CLOSETAG, OPENTAG } from "../htmlTags.js";
4
+
5
+ //#region src/internal/blocks/htmlBlock.ts
6
+ const C_LESSTHAN = 60;
7
+ const reHtmlBlockOpen = [
8
+ /./,
9
+ /^<(?:script|pre|textarea|style)(?:\s|>|$)/i,
10
+ /^<!--/,
11
+ /^<[?]/,
12
+ /^<![A-Za-z]/,
13
+ /^<!\[CDATA\[/,
14
+ /^<[/]?(?: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[123456]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|search|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:\s|[/]?[>]|$)/i,
15
+ new RegExp(`^(?:${OPENTAG}|${CLOSETAG})\\s*$`, "i")
16
+ ];
17
+ const reHtmlBlockClose = [
18
+ /./,
19
+ /<\/(?:script|pre|textarea|style)>/i,
20
+ /-->/,
21
+ /\?>/,
22
+ />/,
23
+ /\]\]>/
24
+ ];
25
+ /**
26
+ * Whether `rest` closes `block` — only types 1 through 5 have a closing
27
+ * pattern, and the line loop calls this after the line has been appended.
28
+ */
29
+ const isHtmlBlockEnd = (block, rest) => {
30
+ const type = block.data.htmlBlockType;
31
+ if (type === void 0 || type < 1 || type > 5) return false;
32
+ return reHtmlBlockClose[type]?.test(rest) ?? false;
33
+ };
34
+ /** HTML block: absorbs lines verbatim, contains nothing. */
35
+ const htmlBlockConstruct = {
36
+ type: "html",
37
+ acceptsLines: true,
38
+ canContain: () => false,
39
+ continue: (scanner, block) => {
40
+ const type = block.data.htmlBlockType;
41
+ return scanner.blank && (type === 6 || type === 7) ? 1 : 0;
42
+ },
43
+ finalize: (_scanner, block) => {
44
+ block.stringContent = block.stringContent.replace(/\n$/, "");
45
+ },
46
+ materialize: (block, _children, context) => Html.make({
47
+ value: block.stringContent,
48
+ position: context.position(block.startOffset, block.endOffset)
49
+ })
50
+ };
51
+ /** The HTML block start: one of seven open conditions after an unindented `<`. */
52
+ const htmlBlockStart = {
53
+ name: "htmlBlock",
54
+ trigger: (scanner, container) => {
55
+ if (scanner.indented || peekCode(scanner.currentLine, scanner.nextNonspace) !== C_LESSTHAN) return 0;
56
+ const rest = scanner.currentLine.slice(scanner.nextNonspace);
57
+ for (let blockType = 1; blockType <= 7; blockType += 1) {
58
+ const opens = reHtmlBlockOpen[blockType]?.test(rest) ?? false;
59
+ const mayOpen = blockType < 7 || container.type !== "paragraph" && !(!scanner.allClosed && !scanner.blank && scanner.tip.type === "paragraph");
60
+ if (opens && mayOpen) {
61
+ scanner.closeUnmatchedBlocks();
62
+ const block = scanner.addChild("html", scanner.offset);
63
+ block.data.htmlBlockType = blockType;
64
+ return 2;
65
+ }
66
+ }
67
+ return 0;
68
+ }
69
+ };
70
+
71
+ //#endregion
72
+ export { htmlBlockConstruct, htmlBlockStart, isHtmlBlockEnd };
@@ -0,0 +1,16 @@
1
+ //#region src/internal/blocks/indentedCode.ts
2
+ /** The indented-code block start: four columns of indentation. */
3
+ const indentedCodeStart = {
4
+ name: "indentedCode",
5
+ trigger: (scanner) => {
6
+ if (!scanner.indented || scanner.tip.type === "paragraph" || scanner.blank) return 0;
7
+ const start = scanner.offset;
8
+ scanner.advanceOffset(4, true);
9
+ scanner.closeUnmatchedBlocks();
10
+ scanner.addChild("code", start);
11
+ return 2;
12
+ }
13
+ };
14
+
15
+ //#endregion
16
+ export { indentedCodeStart };
@@ -0,0 +1,72 @@
1
+ import { Definition } from "../../MarkdownNode.js";
2
+ import { makeBlockNode } from "../blockTypes.js";
3
+ import { parseReference } from "../references.js";
4
+ import { sliceWithSegments, sourceOffsetAt } from "../segments.js";
5
+
6
+ //#region src/internal/blocks/linkReferenceDefinition.ts
7
+ const C_OPEN_BRACKET = 91;
8
+ /**
9
+ * Split every leading link reference definition out of `block`, inserting one
10
+ * `definition` node per definition into `block`'s parent, immediately before
11
+ * `block` itself.
12
+ *
13
+ * Definitions can only ever start a paragraph — they cannot interrupt one —
14
+ * so a single leading pass is exhaustive.
15
+ */
16
+ const extractDefinitions = (block) => {
17
+ const parent = block.parent;
18
+ if (parent === void 0) return;
19
+ while (block.stringContent.charCodeAt(0) === C_OPEN_BRACKET) {
20
+ const reference = parseReference(block.stringContent);
21
+ if (reference === void 0) return;
22
+ const consumed = block.stringContent.slice(0, reference.length);
23
+ const startOffset = sourceOffsetAt(block.segments, 0, block.startOffset);
24
+ const linesConsumed = (consumed.match(/\n/g) ?? []).length;
25
+ const contentLength = consumed.replace(/[ \t\r\n]+$/, "").length;
26
+ const endOffset = sourceOffsetAt(block.segments, contentLength, startOffset);
27
+ const contentLines = (consumed.slice(0, contentLength).match(/\n/g) ?? []).length;
28
+ const node = makeBlockNode("definition", startOffset, block.startLine, block.depth);
29
+ node.parent = parent;
30
+ node.open = false;
31
+ node.endOffset = endOffset;
32
+ node.endLine = block.startLine + contentLines;
33
+ node.data.definition = {
34
+ key: reference.key,
35
+ identifier: reference.identifier,
36
+ label: reference.label,
37
+ url: reference.url,
38
+ ...reference.title === void 0 ? {} : { title: reference.title }
39
+ };
40
+ const at = parent.children.indexOf(block);
41
+ parent.children.splice(at === -1 ? parent.children.length : at, 0, node);
42
+ const remaining = sliceWithSegments(block.segments, reference.length, block.stringContent.length);
43
+ block.stringContent = block.stringContent.slice(reference.length);
44
+ block.segments.splice(0, block.segments.length, ...remaining);
45
+ block.startLine += linesConsumed;
46
+ block.startOffset = remaining[0]?.sourceOffset ?? endOffset;
47
+ }
48
+ };
49
+ /**
50
+ * Definition: a node with no block start of its own — {@link extractDefinitions}
51
+ * is the only thing that ever creates one, already closed.
52
+ */
53
+ const definitionConstruct = {
54
+ type: "definition",
55
+ acceptsLines: false,
56
+ canContain: () => false,
57
+ continue: () => 1,
58
+ materialize: (block, _children, context) => {
59
+ const definition = block.data.definition;
60
+ if (definition === void 0) return;
61
+ return Definition.make({
62
+ identifier: definition.identifier,
63
+ label: definition.label,
64
+ url: definition.url,
65
+ position: context.position(block.startOffset, block.endOffset),
66
+ ...definition.title === void 0 ? {} : { title: definition.title }
67
+ });
68
+ }
69
+ };
70
+
71
+ //#endregion
72
+ export { definitionConstruct, extractDefinitions };
@@ -0,0 +1,159 @@
1
+ import { List, ListItem } from "../../MarkdownNode.js";
2
+ import { flowChildren, listItemChildren } from "../blockTypes.js";
3
+ import { isSpaceOrTab, peekCode } from "../preprocess.js";
4
+
5
+ //#region src/internal/blocks/list.ts
6
+ const reBulletListMarker = /^[*+-]/;
7
+ const reOrderedListMarker = /^(\d{1,9})([.)])/;
8
+ const reNonSpace = /[^ \t\f\v\r\n]/;
9
+ const bulletCharOf = (char) => char === "-" || char === "*" || char === "+" ? char : void 0;
10
+ const delimiterOf = (char) => char === "." || char === ")" ? char : void 0;
11
+ /**
12
+ * Parse a list marker at the scanner's next non-whitespace position,
13
+ * advancing past it on success.
14
+ */
15
+ const parseListMarker = (scanner, container) => {
16
+ if (scanner.indent >= 4) return;
17
+ const rest = scanner.currentLine.slice(scanner.nextNonspace);
18
+ const markerOffset = scanner.indent;
19
+ let data;
20
+ let markerLength;
21
+ const bullet = reBulletListMarker.exec(rest);
22
+ const ordered = bullet === null ? reOrderedListMarker.exec(rest) : null;
23
+ if (bullet !== null) {
24
+ const bulletChar = bulletCharOf(bullet[0].charAt(0));
25
+ data = {
26
+ type: "bullet",
27
+ tight: true,
28
+ padding: 0,
29
+ markerOffset,
30
+ ...bulletChar === void 0 ? {} : { bulletChar }
31
+ };
32
+ markerLength = bullet[0].length;
33
+ } else if (ordered !== null && (container.type !== "paragraph" || Number.parseInt(ordered[1] ?? "", 10) === 1)) {
34
+ const delimiter = delimiterOf(ordered[2] ?? "");
35
+ data = {
36
+ type: "ordered",
37
+ tight: true,
38
+ padding: 0,
39
+ markerOffset,
40
+ start: Number.parseInt(ordered[1] ?? "1", 10),
41
+ ...delimiter === void 0 ? {} : { delimiter }
42
+ };
43
+ markerLength = ordered[0].length;
44
+ } else return;
45
+ const afterMarker = peekCode(scanner.currentLine, scanner.nextNonspace + markerLength);
46
+ if (!(afterMarker === -1 || isSpaceOrTab(afterMarker))) return;
47
+ if (container.type === "paragraph" && !reNonSpace.test(scanner.currentLine.slice(scanner.nextNonspace + markerLength))) return;
48
+ scanner.advanceNextNonspace();
49
+ scanner.advanceOffset(markerLength, true);
50
+ const spacesStartColumn = scanner.column;
51
+ const spacesStartOffset = scanner.offset;
52
+ do
53
+ scanner.advanceOffset(1, true);
54
+ while (scanner.column - spacesStartColumn < 5 && isSpaceOrTab(peekCode(scanner.currentLine, scanner.offset)));
55
+ const blankItem = peekCode(scanner.currentLine, scanner.offset) === -1;
56
+ const spacesAfterMarker = scanner.column - spacesStartColumn;
57
+ if (spacesAfterMarker >= 5 || spacesAfterMarker < 1 || blankItem) {
58
+ data.padding = markerLength + 1;
59
+ scanner.setScanPosition(spacesStartOffset, spacesStartColumn);
60
+ if (isSpaceOrTab(peekCode(scanner.currentLine, scanner.offset))) scanner.advanceOffset(1, true);
61
+ } else data.padding = markerLength + spacesAfterMarker;
62
+ return data;
63
+ };
64
+ /** Two markers belong to the same list when type, delimiter and bullet agree. */
65
+ const listsMatch = (listData, itemData) => listData.type === itemData.type && listData.delimiter === itemData.delimiter && listData.bulletChar === itemData.bulletChar;
66
+ /** Whether `block` is followed by a sibling that a blank line separates from it. */
67
+ const endsWithBlankLine = (block, next) => next !== void 0 && block.endLine !== next.startLine - 1;
68
+ /** Whether any of `block`'s children are separated from the next by a blank line. */
69
+ const hasBlankLineBetweenChildren = (block) => block.children.some((child, index) => endsWithBlankLine(child, block.children[index + 1]));
70
+ /** List: a container of list items and nothing else. */
71
+ const listConstruct = {
72
+ type: "list",
73
+ acceptsLines: false,
74
+ canContain: (child) => child === "listItem",
75
+ continue: () => 0,
76
+ finalize: (_scanner, block) => {
77
+ let tight = true;
78
+ for (const [index, item] of block.children.entries()) {
79
+ const itemSpread = hasBlankLineBetweenChildren(item);
80
+ item.data.spread = itemSpread;
81
+ if (endsWithBlankLine(item, block.children[index + 1]) || itemSpread) tight = false;
82
+ }
83
+ const listData = block.data.listData;
84
+ if (listData !== void 0) listData.tight = tight;
85
+ block.data.spread = !tight;
86
+ const last = block.children[block.children.length - 1];
87
+ if (last !== void 0) {
88
+ block.endOffset = last.endOffset;
89
+ block.endLine = last.endLine;
90
+ }
91
+ },
92
+ materialize: (block, children, context) => {
93
+ const listData = block.data.listData;
94
+ const ordered = listData?.type === "ordered";
95
+ return List.make({
96
+ ordered,
97
+ spread: block.data.spread ?? false,
98
+ children: listItemChildren(children),
99
+ position: context.position(block.startOffset, block.endOffset),
100
+ ...ordered && listData?.start !== void 0 ? { start: listData.start } : {},
101
+ ...listData?.bulletChar === void 0 ? {} : { bulletChar: listData.bulletChar },
102
+ ...listData?.delimiter === void 0 ? {} : { delimiter: listData.delimiter }
103
+ });
104
+ }
105
+ };
106
+ /** List item: a container of anything but a bare list item. */
107
+ const listItemConstruct = {
108
+ type: "listItem",
109
+ acceptsLines: false,
110
+ canContain: (child) => child !== "listItem",
111
+ continue: (scanner, block) => {
112
+ const listData = block.data.listData;
113
+ if (listData === void 0) return 1;
114
+ if (scanner.blank) {
115
+ if (block.children.length === 0) return 1;
116
+ scanner.advanceNextNonspace();
117
+ } else if (scanner.indent >= listData.markerOffset + listData.padding) scanner.advanceOffset(listData.markerOffset + listData.padding, true);
118
+ else return 1;
119
+ return 0;
120
+ },
121
+ finalize: (_scanner, block) => {
122
+ const last = block.children[block.children.length - 1];
123
+ if (last !== void 0) {
124
+ block.endOffset = last.endOffset;
125
+ block.endLine = last.endLine;
126
+ return;
127
+ }
128
+ block.endLine = block.startLine;
129
+ block.endOffset = block.startOffset + (block.data.listData?.padding ?? 0);
130
+ },
131
+ materialize: (block, children, context) => ListItem.make({
132
+ spread: block.data.spread ?? false,
133
+ children: flowChildren(children),
134
+ position: context.position(block.startOffset, block.endOffset),
135
+ ...block.data.checked === void 0 ? {} : { checked: block.data.checked }
136
+ })
137
+ };
138
+ /** The list-item block start: a bullet or ordered marker. */
139
+ const listItemStart = {
140
+ name: "listItem",
141
+ trigger: (scanner, container) => {
142
+ if (scanner.indented && container.type !== "list") return 0;
143
+ const data = parseListMarker(scanner, container);
144
+ if (data === void 0) return 0;
145
+ scanner.closeUnmatchedBlocks();
146
+ const openList = scanner.tip;
147
+ const openListData = openList.data.listData;
148
+ if (openList.type !== "list" || openListData === void 0 || !listsMatch(openListData, data)) {
149
+ const list = scanner.addChild("list", scanner.nextNonspace);
150
+ list.data.listData = data;
151
+ }
152
+ const item = scanner.addChild("listItem", scanner.nextNonspace);
153
+ item.data.listData = data;
154
+ return 1;
155
+ }
156
+ };
157
+
158
+ //#endregion
159
+ export { listConstruct, listItemConstruct, listItemStart };
@@ -0,0 +1,27 @@
1
+ import { Paragraph } from "../../MarkdownNode.js";
2
+ import { extractDefinitions } from "./linkReferenceDefinition.js";
3
+
4
+ //#region src/internal/blocks/paragraph.ts
5
+ /** Paragraph: absorbs lines until a blank one, and contains nothing. */
6
+ const paragraphConstruct = {
7
+ type: "paragraph",
8
+ acceptsLines: true,
9
+ canContain: () => false,
10
+ continue: (scanner) => scanner.blank ? 1 : 0,
11
+ finalize: (_scanner, block) => {
12
+ extractDefinitions(block);
13
+ },
14
+ materialize: (block, _children, context) => {
15
+ const inline = context.inlineSlice(block);
16
+ if (inline.text.length === 0) return;
17
+ const node = Paragraph.make({
18
+ children: inline.children,
19
+ position: context.position(block.startOffset, block.endOffset)
20
+ });
21
+ context.registerInline(node, inline);
22
+ return node;
23
+ }
24
+ };
25
+
26
+ //#endregion
27
+ export { paragraphConstruct };
@@ -0,0 +1,24 @@
1
+ import { extractDefinitions } from "./linkReferenceDefinition.js";
2
+
3
+ //#region src/internal/blocks/setextHeading.ts
4
+ const reSetextHeadingLine = /^(?:=+|-+)[ \t]*$/;
5
+ /** The setext heading block start: a run of `=` or `-` under a paragraph. */
6
+ const setextHeadingStart = {
7
+ name: "setextHeading",
8
+ trigger: (scanner, container) => {
9
+ if (scanner.indented || container.type !== "paragraph") return 0;
10
+ const match = reSetextHeadingLine.exec(scanner.currentLine.slice(scanner.nextNonspace));
11
+ if (match === null) return 0;
12
+ scanner.closeUnmatchedBlocks();
13
+ extractDefinitions(container);
14
+ if (container.stringContent.length === 0) return 0;
15
+ const heading = scanner.replaceBlock(container, "heading");
16
+ heading.data.level = match[0].charAt(0) === "=" ? 1 : 2;
17
+ heading.data.headingStyle = "setext";
18
+ scanner.advanceOffset(scanner.currentLine.length - scanner.offset, false);
19
+ return 2;
20
+ }
21
+ };
22
+
23
+ //#endregion
24
+ export { setextHeadingStart };