@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
package/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import { MarkdownEdit, MarkdownRange } from "./MarkdownEdit.js";
2
+ import { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterFormat, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, MarkdownNode, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar } from "./MarkdownNode.js";
3
+ import { FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterValidationError, MarkdownFrontmatter } from "./Frontmatter.js";
4
+ import { SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError } from "./FrontmatterResolver.js";
5
+ import { JsonFrontmatter } from "./JsonFrontmatter.js";
6
+ import { MarkdownDiagnostic, MarkdownParseErrorCode } from "./MarkdownDiagnostic.js";
7
+ import { Markdown, MarkdownDialect, MarkdownParseError, MarkdownParseOptions, MarkdownStringifyError } from "./Markdown.js";
8
+ import { MarkdownDocument } from "./MarkdownDocument.js";
9
+ import { MarkdownFormat, MarkdownFormattingOptions, MarkdownModificationError, MarkdownModificationErrorCode } from "./MarkdownFormat.js";
10
+ import { MarkdownVisitor, MarkdownVisitorEvent } from "./MarkdownVisitor.js";
11
+ import { Mdast, MdastDecodeError } from "./Mdast.js";
12
+ import { TomlFrontmatter } from "./TomlFrontmatter.js";
13
+ import { YamlFrontmatter } from "./YamlFrontmatter.js";
14
+
15
+ export { Blockquote, Break, BreakStyle, BulletChar, Code, Definition, Delete, Emphasis, EmphasisChar, FenceChar, FlowContent, FootnoteDefinition, FootnoteReference, Frontmatter, FrontmatterContent, FrontmatterDecodeError, FrontmatterEncodeError, FrontmatterFormat, FrontmatterFormatMismatchError, FrontmatterMissingError, FrontmatterValidationError, Heading, HeadingDepth, HeadingStyle, Html, Image, ImageReference, InlineCode, JsonFrontmatter, Link, LinkReference, List, ListContent, ListDelimiter, ListItem, Markdown, MarkdownDiagnostic, MarkdownDialect, MarkdownDocument, MarkdownEdit, MarkdownFormat, MarkdownFormattingOptions, MarkdownFrontmatter, MarkdownModificationError, MarkdownModificationErrorCode, MarkdownNode, MarkdownParseError, MarkdownParseErrorCode, MarkdownParseOptions, MarkdownRange, MarkdownStringifyError, MarkdownVisitor, MarkdownVisitorEvent, Mdast, MdastDecodeError, Paragraph, PhrasingContent, Point, Position, ReferenceType, Root, RowContent, SchemaDeclaration, SchemaDeclarationByName, SchemaDeclarationByPath, SchemaDeclarationByUrl, SchemaDeclarationInline, SchemaDeclarationInvalidError, SchemaDeclarationMissingError, SchemaNameUnknownError, SchemaResolver, SchemaVersionUnresolvableError, Strong, Table, TableAlign, TableCell, TableContent, TableRow, Text, ThematicBreak, ThematicBreakChar, TomlFrontmatter, YamlFrontmatter };
@@ -0,0 +1,419 @@
1
+ import { Frontmatter, Point, Position, Root } from "../MarkdownNode.js";
2
+ import { makeBlockNode } from "./blockTypes.js";
3
+ import { columnsToNextTabStop, preprocessLines } from "./preprocess.js";
4
+ import { isHtmlBlockEnd } from "./blocks/htmlBlock.js";
5
+ import { blockDialect } from "./blockRegistry.js";
6
+ import { scanFrontmatter } from "./blocks/frontmatter.js";
7
+ import { GuardExceeded } from "./carriers.js";
8
+ import { LineIndex } from "./lineIndex.js";
9
+ import { prepareInline } from "./rawInline.js";
10
+
11
+ //#region src/internal/blockParser.ts
12
+ /** Upstream's cheap pre-filter: a line that cannot start any block. */
13
+ const reMaybeSpecial = /^[#`~*+_=<>0-9-]/;
14
+ var BlockParser = class {
15
+ lines;
16
+ lineIndex;
17
+ dialect;
18
+ /**
19
+ * The dialect's NAME, kept alongside its block tables because the inline
20
+ * pass keys its own registry off the same string.
21
+ */
22
+ dialectName;
23
+ sourceLength;
24
+ doc;
25
+ tipNode;
26
+ oldtip;
27
+ lastMatchedContainer;
28
+ currentLine = "";
29
+ lineStart = 0;
30
+ lineNumber = 0;
31
+ offset = 0;
32
+ column = 0;
33
+ nextNonspace = 0;
34
+ nextNonspaceColumn = 0;
35
+ indent = 0;
36
+ indented = false;
37
+ blank = false;
38
+ allClosed = true;
39
+ partiallyConsumedTab = false;
40
+ lastLineLength = 0;
41
+ frontmatterCapture;
42
+ constructor(text, dialect, dialectName, frontmatter = false) {
43
+ this.lines = preprocessLines(text);
44
+ this.frontmatterCapture = frontmatter ? scanFrontmatter(this.lines, text) : null;
45
+ const starts = this.lines.map((line) => line.start);
46
+ if (text.endsWith("\n")) starts.push(text.length);
47
+ this.lineIndex = LineIndex.fromLineStarts(text, starts);
48
+ this.sourceLength = text.length;
49
+ this.dialect = dialect;
50
+ this.dialectName = dialectName;
51
+ this.doc = makeBlockNode("document", 0, 1);
52
+ this.tipNode = this.doc;
53
+ this.oldtip = this.doc;
54
+ this.lastMatchedContainer = this.doc;
55
+ }
56
+ /**
57
+ * The deepest open block. Only reachable while the document is open — a
58
+ * read after the final finalize is a wiring bug and dies as a defect.
59
+ */
60
+ get tip() {
61
+ const tip = this.tipNode;
62
+ if (tip === void 0) throw new TypeError("block parser: the tip was read after the document was finalized");
63
+ return tip;
64
+ }
65
+ constructOf(type) {
66
+ const construct = this.dialect.constructs.get(type);
67
+ if (construct === void 0) throw new TypeError(`block parser: no construct registered for block type ${type}`);
68
+ return construct;
69
+ }
70
+ advanceOffset(count, columns = false) {
71
+ const line = this.currentLine;
72
+ let remaining = count;
73
+ let char = line.charAt(this.offset);
74
+ while (remaining > 0 && char !== "") {
75
+ if (char === " ") {
76
+ const charsToTab = columnsToNextTabStop(this.column);
77
+ if (columns) {
78
+ this.partiallyConsumedTab = charsToTab > remaining;
79
+ const charsToAdvance = charsToTab > remaining ? remaining : charsToTab;
80
+ this.column += charsToAdvance;
81
+ this.offset += this.partiallyConsumedTab ? 0 : 1;
82
+ remaining -= charsToAdvance;
83
+ } else {
84
+ this.partiallyConsumedTab = false;
85
+ this.column += charsToTab;
86
+ this.offset += 1;
87
+ remaining -= 1;
88
+ }
89
+ } else {
90
+ this.partiallyConsumedTab = false;
91
+ this.offset += 1;
92
+ this.column += 1;
93
+ remaining -= 1;
94
+ }
95
+ char = line.charAt(this.offset);
96
+ }
97
+ }
98
+ advanceNextNonspace() {
99
+ this.offset = this.nextNonspace;
100
+ this.column = this.nextNonspaceColumn;
101
+ this.partiallyConsumedTab = false;
102
+ }
103
+ findNextNonspace() {
104
+ const line = this.currentLine;
105
+ let index = this.offset;
106
+ let cols = this.column;
107
+ let char = line.charAt(index);
108
+ while (char !== "") {
109
+ if (char === " ") {
110
+ index += 1;
111
+ cols += 1;
112
+ } else if (char === " ") {
113
+ index += 1;
114
+ cols += columnsToNextTabStop(cols);
115
+ } else break;
116
+ char = line.charAt(index);
117
+ }
118
+ this.blank = char === "\n" || char === "\r" || char === "";
119
+ this.nextNonspace = index;
120
+ this.nextNonspaceColumn = cols;
121
+ this.indent = this.nextNonspaceColumn - this.column;
122
+ this.indented = this.indent >= 4;
123
+ }
124
+ addLine() {
125
+ const tip = this.tip;
126
+ if (this.partiallyConsumedTab) {
127
+ this.offset += 1;
128
+ tip.stringContent += " ".repeat(columnsToNextTabStop(this.column));
129
+ }
130
+ const content = this.currentLine.slice(this.offset);
131
+ tip.segments.push({
132
+ textOffset: tip.stringContent.length,
133
+ sourceOffset: this.lineStart + this.offset,
134
+ length: content.length
135
+ });
136
+ tip.stringContent += `${content}\n`;
137
+ }
138
+ addChild(type, offsetInLine) {
139
+ while (!this.constructOf(this.tip.type).canContain(type)) this.finalizeBlock(this.tip, this.lineNumber - 1);
140
+ const parent = this.tip;
141
+ const depth = parent.depth + 1;
142
+ if (depth > 256) throw new GuardExceeded("NestingDepthExceeded", 256, depth, this.lineStart + offsetInLine);
143
+ const child = makeBlockNode(type, this.lineStart + offsetInLine, this.lineNumber, depth);
144
+ child.parent = parent;
145
+ parent.children.push(child);
146
+ this.tipNode = child;
147
+ return child;
148
+ }
149
+ setScanPosition(offset, column) {
150
+ this.offset = offset;
151
+ this.column = column;
152
+ }
153
+ setLastLineLength(length) {
154
+ this.lastLineLength = length;
155
+ }
156
+ replaceBlock(block, type) {
157
+ const parent = block.parent;
158
+ const replacement = makeBlockNode(type, block.startOffset, block.startLine, block.depth);
159
+ replacement.parent = parent;
160
+ replacement.stringContent = block.stringContent;
161
+ replacement.segments.push(...block.segments);
162
+ replacement.endOffset = block.endOffset;
163
+ replacement.endLine = block.endLine;
164
+ if (parent !== void 0) {
165
+ const at = parent.children.indexOf(block);
166
+ if (at === -1) parent.children.push(replacement);
167
+ else parent.children[at] = replacement;
168
+ }
169
+ block.open = false;
170
+ this.tipNode = replacement;
171
+ return replacement;
172
+ }
173
+ insertBefore(block, type) {
174
+ const parent = block.parent;
175
+ const sibling = makeBlockNode(type, block.startOffset, block.startLine, block.depth);
176
+ sibling.parent = parent;
177
+ sibling.open = false;
178
+ if (parent !== void 0) {
179
+ const at = parent.children.indexOf(block);
180
+ if (at === -1) parent.children.push(sibling);
181
+ else parent.children.splice(at, 0, sibling);
182
+ }
183
+ return sibling;
184
+ }
185
+ closeUnmatchedBlocks() {
186
+ if (this.allClosed) return;
187
+ while (this.oldtip !== this.lastMatchedContainer) {
188
+ const parent = this.oldtip.parent;
189
+ this.finalizeBlock(this.oldtip, this.lineNumber - 1);
190
+ this.oldtip = parent ?? this.doc;
191
+ }
192
+ this.allClosed = true;
193
+ }
194
+ finalizeBlock(block, lineNumber) {
195
+ const above = block.parent;
196
+ block.open = false;
197
+ block.endLine = lineNumber;
198
+ block.endOffset = this.endOfLine(lineNumber, this.lastLineLength);
199
+ this.constructOf(block.type).finalize?.(this, block);
200
+ this.tipNode = above;
201
+ }
202
+ /**
203
+ * The absolute offset `length` characters into line `lineNumber` — the
204
+ * offset form of upstream's `sourcepos[1] = [lineNumber, lastLineLength]`.
205
+ * Out-of-range line numbers clamp rather than throw.
206
+ */
207
+ endOfLine(lineNumber, length) {
208
+ const index = Math.min(Math.max(lineNumber - 1, 0), this.lines.length - 1);
209
+ const line = this.lines[index];
210
+ if (line === void 0) return 0;
211
+ return line.start + Math.min(Math.max(length, 0), line.text.length);
212
+ }
213
+ incorporateLine(line) {
214
+ let container = this.doc;
215
+ this.oldtip = this.tip;
216
+ this.offset = 0;
217
+ this.column = 0;
218
+ this.blank = false;
219
+ this.partiallyConsumedTab = false;
220
+ this.lineNumber += 1;
221
+ this.currentLine = line.text;
222
+ this.lineStart = line.start;
223
+ let lastChild = container.children[container.children.length - 1];
224
+ while (lastChild?.open) {
225
+ container = lastChild;
226
+ this.findNextNonspace();
227
+ const verdict = this.constructOf(container.type).continue(this, container);
228
+ if (verdict === 2) return;
229
+ if (verdict === 1) {
230
+ container = container.parent ?? this.doc;
231
+ break;
232
+ }
233
+ lastChild = container.children[container.children.length - 1];
234
+ }
235
+ this.allClosed = container === this.oldtip;
236
+ this.lastMatchedContainer = container;
237
+ let matchedLeaf = container.type !== "paragraph" && this.constructOf(container.type).acceptsLines;
238
+ const starts = this.dialect.starts;
239
+ while (!matchedLeaf) {
240
+ this.findNextNonspace();
241
+ if (!this.indented) {
242
+ const rest = this.currentLine.slice(this.nextNonspace);
243
+ if (!reMaybeSpecial.test(rest) && !(this.dialect.mayStartBlock?.(rest, container) ?? false)) {
244
+ this.advanceNextNonspace();
245
+ break;
246
+ }
247
+ }
248
+ let index = 0;
249
+ while (index < starts.length) {
250
+ const start = starts[index];
251
+ const result = start === void 0 ? 0 : start.trigger(this, container);
252
+ if (result === 1) {
253
+ container = this.tip;
254
+ break;
255
+ }
256
+ if (result === 2) {
257
+ container = this.tip;
258
+ matchedLeaf = true;
259
+ break;
260
+ }
261
+ index += 1;
262
+ }
263
+ if (index === starts.length) {
264
+ this.advanceNextNonspace();
265
+ break;
266
+ }
267
+ }
268
+ if (!this.allClosed && !this.blank && this.tip.type === "paragraph") this.addLine();
269
+ else {
270
+ this.closeUnmatchedBlocks();
271
+ if (this.constructOf(container.type).acceptsLines) {
272
+ this.addLine();
273
+ if (container.type === "html" && isHtmlBlockEnd(container, this.currentLine.slice(this.offset))) {
274
+ this.lastLineLength = this.currentLine.length;
275
+ this.finalizeBlock(container, this.lineNumber);
276
+ }
277
+ } else if (this.offset < this.currentLine.length && !this.blank) {
278
+ container = this.addChild("paragraph", this.offset);
279
+ this.advanceNextNonspace();
280
+ this.addLine();
281
+ }
282
+ }
283
+ this.lastLineLength = this.currentLine.length;
284
+ }
285
+ /**
286
+ * Prepend the captured frontmatter node, when there is one. The root's
287
+ * own span widens to cover the block if the body ended before it — the
288
+ * frontmatter-only document, where the block pass saw no lines at all.
289
+ */
290
+ withFrontmatter(root) {
291
+ const capture = this.frontmatterCapture;
292
+ if (capture === null) return root;
293
+ const node = Frontmatter.make({
294
+ type: "frontmatter",
295
+ format: capture.format,
296
+ value: capture.value,
297
+ position: this.position(0, capture.endOffset)
298
+ });
299
+ const position = root.position.end.offset >= capture.endOffset ? root.position : this.position(0, capture.endOffset);
300
+ return Root.make({
301
+ type: "root",
302
+ children: [node, ...root.children],
303
+ position
304
+ });
305
+ }
306
+ position(startOffset, endOffset) {
307
+ const start = Math.min(Math.max(startOffset, 0), this.sourceLength);
308
+ const end = Math.min(Math.max(endOffset, start), this.sourceLength);
309
+ const startPoint = this.lineIndex.positionAt(start);
310
+ const endPoint = this.lineIndex.positionAt(end);
311
+ return Position.make({
312
+ start: Point.make({
313
+ line: startPoint.line,
314
+ column: startPoint.column,
315
+ offset: start
316
+ }),
317
+ end: Point.make({
318
+ line: endPoint.line,
319
+ column: endPoint.column,
320
+ offset: end
321
+ })
322
+ });
323
+ }
324
+ /**
325
+ * Depth-first materialization. Recursion depth is container nesting depth,
326
+ * which `addChild`'s `MAX_NESTING_DEPTH` guard caps at parse time — this
327
+ * walk can never see a tree the line loop refused to build.
328
+ */
329
+ materializeBlock(block, context, definitions) {
330
+ const children = [];
331
+ for (const child of block.children) {
332
+ const node = this.materializeBlock(child, context, definitions);
333
+ if (node !== void 0 && node.type !== "root") children.push(node);
334
+ }
335
+ const definition = definitions.get(block);
336
+ if (definition !== void 0) return definition;
337
+ return this.constructOf(block.type).materialize(block, children, context);
338
+ }
339
+ /**
340
+ * Index every reference target in the tree — link reference definitions and
341
+ * GFM footnote definition labels — before anything else is built.
342
+ *
343
+ * The inline pass resolves references against these, and a definition can
344
+ * appear anywhere, including after the paragraph that references it, so
345
+ * both maps have to exist before the first leaf is parsed. Walking twice is
346
+ * the price; the alternative is a mutable tree the inline pass patches
347
+ * afterwards.
348
+ *
349
+ * Link reference definitions are MATERIALIZED here, so the tree and the
350
+ * refmap point at one object. Footnote definitions deliberately are not:
351
+ * a footnote definition is a container whose children are flow blocks, and
352
+ * materializing those runs the inline pass over them — which consults this
353
+ * very map. Only the labels are collected, which is all formation needs,
354
+ * and the nodes are built in document order by the ordinary walk. That is
355
+ * why `footnoteLabels` is a set of labels rather than a map to nodes; a
356
+ * consumer wanting the node walks the tree, where it still sits.
357
+ */
358
+ collectReferences(block, context, nodes, refmap, footnoteLabels) {
359
+ if (block.type === "definition") {
360
+ const materialized = this.constructOf(block.type).materialize(block, [], context);
361
+ const key = block.data.definition?.key;
362
+ if (materialized?.type === "definition" && key !== void 0) {
363
+ nodes.set(block, materialized);
364
+ if (!refmap.has(key)) refmap.set(key, materialized);
365
+ }
366
+ return;
367
+ }
368
+ const footnoteKey = block.data.footnote?.key;
369
+ if (footnoteKey !== void 0) footnoteLabels.add(footnoteKey);
370
+ for (const child of block.children) this.collectReferences(child, context, nodes, refmap, footnoteLabels);
371
+ }
372
+ parse() {
373
+ const skippedLines = this.frontmatterCapture?.lineCount ?? 0;
374
+ this.lineNumber = skippedLines;
375
+ for (let index = skippedLines; index < this.lines.length; index += 1) {
376
+ const line = this.lines[index];
377
+ if (line !== void 0) this.incorporateLine(line);
378
+ }
379
+ while (this.tipNode !== void 0) this.finalizeBlock(this.tipNode, this.lines.length);
380
+ this.doc.endOffset = this.sourceLength;
381
+ const refmap = /* @__PURE__ */ new Map();
382
+ const footnoteLabels = /* @__PURE__ */ new Set();
383
+ const definitionNodes = /* @__PURE__ */ new Map();
384
+ const rawInlines = [];
385
+ const context = {
386
+ position: (start, end) => this.position(start, end),
387
+ inlineSlice: (block) => prepareInline(block, (start, end) => this.position(start, end), refmap, this.dialectName, footnoteLabels),
388
+ registerInline: (parent, prepared) => {
389
+ rawInlines.push({
390
+ parent,
391
+ text: prepared.text,
392
+ startOffset: prepared.startOffset,
393
+ segments: prepared.segments
394
+ });
395
+ }
396
+ };
397
+ this.collectReferences(this.doc, context, definitionNodes, refmap, footnoteLabels);
398
+ const materialized = this.materializeBlock(this.doc, context, definitionNodes);
399
+ if (materialized === void 0 || materialized.type !== "root") throw new TypeError("block parser: the document construct did not materialize a root");
400
+ return {
401
+ root: this.withFrontmatter(materialized),
402
+ rawInlines,
403
+ carriers: [],
404
+ refmap,
405
+ footnoteLabels
406
+ };
407
+ }
408
+ };
409
+ /**
410
+ * Run the block pass over `text`.
411
+ *
412
+ * Every node carries a complete {@link Position}, leaf blocks have their
413
+ * inline content parsed, and `rawInlines` reports the raw text each leaf was
414
+ * built from.
415
+ */
416
+ const parseBlocks = (text, dialect = "commonmark", frontmatter = false) => new BlockParser(text, blockDialect(dialect), dialect, frontmatter).parse();
417
+
418
+ //#endregion
419
+ export { parseBlocks };
@@ -0,0 +1,90 @@
1
+ import { atxHeadingStart, headingConstruct } from "./blocks/atxHeading.js";
2
+ import { blockquoteConstruct, blockquoteStart } from "./blocks/blockquote.js";
3
+ import { codeConstruct } from "./blocks/code.js";
4
+ import { documentConstruct } from "./blocks/document.js";
5
+ import { fencedCodeStart } from "./blocks/fencedCode.js";
6
+ import { footnoteDefinitionConstruct, footnoteDefinitionStart } from "./blocks/footnoteDefinition.js";
7
+ import { htmlBlockConstruct, htmlBlockStart } from "./blocks/htmlBlock.js";
8
+ import { indentedCodeStart } from "./blocks/indentedCode.js";
9
+ import { definitionConstruct } from "./blocks/linkReferenceDefinition.js";
10
+ import { listConstruct, listItemConstruct, listItemStart } from "./blocks/list.js";
11
+ import { paragraphConstruct } from "./blocks/paragraph.js";
12
+ import { setextHeadingStart } from "./blocks/setextHeading.js";
13
+ import { tableCellConstruct, tableConstruct, tableHeaderStart, tableRowConstruct, tableRowStart } from "./blocks/table.js";
14
+ import { taskListItemStart } from "./blocks/taskListItem.js";
15
+ import { thematicBreakConstruct, thematicBreakStart } from "./blocks/thematicBreak.js";
16
+
17
+ //#region src/internal/blockRegistry.ts
18
+ const constructTable = (constructs) => new Map(constructs.map((construct) => [construct.type, construct]));
19
+ const commonmarkDialect = {
20
+ constructs: constructTable([
21
+ documentConstruct,
22
+ blockquoteConstruct,
23
+ listConstruct,
24
+ listItemConstruct,
25
+ paragraphConstruct,
26
+ headingConstruct,
27
+ thematicBreakConstruct,
28
+ codeConstruct,
29
+ htmlBlockConstruct,
30
+ definitionConstruct
31
+ ]),
32
+ starts: [
33
+ blockquoteStart,
34
+ atxHeadingStart,
35
+ fencedCodeStart,
36
+ htmlBlockStart,
37
+ setextHeadingStart,
38
+ thematicBreakStart,
39
+ listItemStart,
40
+ indentedCodeStart
41
+ ]
42
+ };
43
+ const gfmDialect = {
44
+ constructs: constructTable([
45
+ documentConstruct,
46
+ blockquoteConstruct,
47
+ listConstruct,
48
+ listItemConstruct,
49
+ paragraphConstruct,
50
+ headingConstruct,
51
+ thematicBreakConstruct,
52
+ codeConstruct,
53
+ htmlBlockConstruct,
54
+ definitionConstruct,
55
+ footnoteDefinitionConstruct,
56
+ tableConstruct,
57
+ tableRowConstruct,
58
+ tableCellConstruct
59
+ ]),
60
+ starts: [
61
+ blockquoteStart,
62
+ atxHeadingStart,
63
+ fencedCodeStart,
64
+ htmlBlockStart,
65
+ setextHeadingStart,
66
+ thematicBreakStart,
67
+ footnoteDefinitionStart,
68
+ listItemStart,
69
+ indentedCodeStart,
70
+ taskListItemStart,
71
+ tableHeaderStart,
72
+ tableRowStart
73
+ ],
74
+ mayStartBlock: (rest, container) => container.type === "table" || rest.startsWith("[") || rest.startsWith("|") || rest.startsWith(":")
75
+ };
76
+ const dialects = /* @__PURE__ */ new Map([["commonmark", commonmarkDialect], ["gfm", gfmDialect]]);
77
+ /**
78
+ * The block tables for `dialect`.
79
+ *
80
+ * An unknown dialect cannot arrive through the schema-typed public surface,
81
+ * so it is programmer error and dies as a defect.
82
+ */
83
+ const blockDialect = (dialect) => {
84
+ const found = dialects.get(dialect);
85
+ if (found === void 0) throw new TypeError(`unknown markdown dialect: ${String(dialect)}`);
86
+ return found;
87
+ };
88
+
89
+ //#endregion
90
+ export { blockDialect };
@@ -0,0 +1,27 @@
1
+ //#region src/internal/blockTypes.ts
2
+ /** Narrow materialized children to the flow content most constructs contain. */
3
+ const flowChildren = (children) => children.filter((child) => child.type !== "root" && child.type !== "listItem" && child.type !== "tableRow" && child.type !== "tableCell");
4
+ /** Narrow materialized children to the list items a list contains. */
5
+ const listItemChildren = (children) => children.filter((child) => child.type === "listItem");
6
+ /** Narrow materialized children to the rows a table contains. */
7
+ const tableRowChildren = (children) => children.filter((child) => child.type === "tableRow");
8
+ /** Narrow materialized children to the cells a table row contains. */
9
+ const tableCellChildren = (children) => children.filter((child) => child.type === "tableCell");
10
+ /** Open a fresh {@link BlockNode}. */
11
+ const makeBlockNode = (type, startOffset, startLine, depth = 0) => ({
12
+ type,
13
+ depth,
14
+ parent: void 0,
15
+ children: [],
16
+ open: true,
17
+ stringContent: "",
18
+ segments: [],
19
+ startOffset,
20
+ endOffset: startOffset,
21
+ startLine,
22
+ endLine: startLine,
23
+ data: {}
24
+ });
25
+
26
+ //#endregion
27
+ export { flowChildren, listItemChildren, makeBlockNode, tableCellChildren, tableRowChildren };
@@ -0,0 +1,54 @@
1
+ import { Heading } from "../../MarkdownNode.js";
2
+
3
+ //#region src/internal/blocks/atxHeading.ts
4
+ const reATXHeadingMarker = /^#{1,6}(?:[ \t]+|$)/;
5
+ const reOnlyTrailingHashes = /^[ \t]*#+[ \t]*$/;
6
+ const reClosingHashes = /[ \t]+#+[ \t]*$/;
7
+ /** Narrow a `#`-run length to the schema's depth literal; the regex caps it at 6. */
8
+ const headingDepth = (hashes) => hashes < 1 ? 1 : hashes > 6 ? 6 : hashes;
9
+ /** Heading: never spans more than one line, and contains no blocks. */
10
+ const headingConstruct = {
11
+ type: "heading",
12
+ acceptsLines: false,
13
+ canContain: () => false,
14
+ continue: () => 1,
15
+ materialize: (block, _children, context) => {
16
+ const inline = context.inlineSlice(block);
17
+ const style = block.data.headingStyle ?? "atx";
18
+ const node = Heading.make({
19
+ depth: block.data.level ?? 1,
20
+ children: inline.children,
21
+ position: context.position(block.startOffset, block.endOffset),
22
+ headingStyle: style
23
+ });
24
+ context.registerInline(node, inline);
25
+ return node;
26
+ }
27
+ };
28
+ /** The ATX heading block start: `#` through `######`, optionally closed. */
29
+ const atxHeadingStart = {
30
+ name: "atxHeading",
31
+ trigger: (scanner) => {
32
+ if (scanner.indented) return 0;
33
+ const match = reATXHeadingMarker.exec(scanner.currentLine.slice(scanner.nextNonspace));
34
+ if (match === null) return 0;
35
+ scanner.advanceNextNonspace();
36
+ scanner.advanceOffset(match[0].length, false);
37
+ scanner.closeUnmatchedBlocks();
38
+ const container = scanner.addChild("heading", scanner.nextNonspace);
39
+ container.data.level = headingDepth(match[0].trim().length);
40
+ container.data.headingStyle = "atx";
41
+ const content = scanner.currentLine.slice(scanner.offset).replace(reOnlyTrailingHashes, "").replace(reClosingHashes, "");
42
+ container.stringContent = content;
43
+ container.segments.push({
44
+ textOffset: 0,
45
+ sourceOffset: scanner.lineStart + scanner.offset,
46
+ length: content.length
47
+ });
48
+ scanner.advanceOffset(scanner.currentLine.length - scanner.offset, false);
49
+ return 2;
50
+ }
51
+ };
52
+
53
+ //#endregion
54
+ export { atxHeadingStart, headingConstruct };
@@ -0,0 +1,40 @@
1
+ import { Blockquote } from "../../MarkdownNode.js";
2
+ import { flowChildren } from "../blockTypes.js";
3
+ import { isSpaceOrTab, peekCode } from "../preprocess.js";
4
+
5
+ //#region src/internal/blocks/blockquote.ts
6
+ const C_GREATERTHAN = 62;
7
+ /** Blockquote: continues while each line carries its `>` marker. */
8
+ const blockquoteConstruct = {
9
+ type: "blockquote",
10
+ acceptsLines: false,
11
+ canContain: (child) => child !== "listItem",
12
+ continue: (scanner) => {
13
+ const line = scanner.currentLine;
14
+ if (scanner.indented || peekCode(line, scanner.nextNonspace) !== C_GREATERTHAN) return 1;
15
+ scanner.advanceNextNonspace();
16
+ scanner.advanceOffset(1, false);
17
+ if (isSpaceOrTab(peekCode(line, scanner.offset))) scanner.advanceOffset(1, true);
18
+ return 0;
19
+ },
20
+ materialize: (block, children, context) => Blockquote.make({
21
+ children: flowChildren(children),
22
+ position: context.position(block.startOffset, block.endOffset)
23
+ })
24
+ };
25
+ /** The blockquote block start: an unindented `>`. */
26
+ const blockquoteStart = {
27
+ name: "blockquote",
28
+ trigger: (scanner) => {
29
+ if (scanner.indented || peekCode(scanner.currentLine, scanner.nextNonspace) !== C_GREATERTHAN) return 0;
30
+ scanner.advanceNextNonspace();
31
+ scanner.advanceOffset(1, false);
32
+ if (isSpaceOrTab(peekCode(scanner.currentLine, scanner.offset))) scanner.advanceOffset(1, true);
33
+ scanner.closeUnmatchedBlocks();
34
+ scanner.addChild("blockquote", scanner.nextNonspace);
35
+ return 1;
36
+ }
37
+ };
38
+
39
+ //#endregion
40
+ export { blockquoteConstruct, blockquoteStart };