@orkestrel/markdown 0.0.16 → 0.0.18
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.
- package/dist/src/core/index.cjs +1174 -1254
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +211 -2
- package/dist/src/core/index.d.ts +211 -2
- package/dist/src/core/index.js +1157 -1255
- package/dist/src/core/index.js.map +1 -1
- package/package.json +15 -15
package/dist/src/core/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { arrayOf, booleanShape, createContract, integerShape, isBoolean, isEmptyString, isNonEmptyArray, isNonEmptyString, isNumber, isString, lazyOf, literalOf, literalShape, nullableOf, objectShape, optionalShape, parseInteger, recordOf, stringShape, unionOf } from "@orkestrel/contract";
|
|
2
2
|
import { HTML, SAFE_ATTRIBUTES, SAFE_URL_SCHEMES, TABLE_ALIGNMENTS, UNSAFE_ELEMENTS, attributeOf, collapseSpace, foldNode as foldNode$1, renderHTML as renderHTML$1, renderText, sanitizeURL } from "@orkestrel/html";
|
|
3
|
-
|
|
3
|
+
// src/core/constants.ts
|
|
4
4
|
/**
|
|
5
5
|
* Caps the recursion depth the parse pipeline (`parseDocument` and its
|
|
6
6
|
* `parsers.ts` helpers), the `helpers.ts` traversal / projection functions
|
|
@@ -28,8 +28,7 @@ var EMPTY_PROJECTION = Object.freeze({
|
|
|
28
28
|
cells: Object.freeze([]),
|
|
29
29
|
rows: Object.freeze([])
|
|
30
30
|
});
|
|
31
|
-
|
|
32
|
-
//#region src/core/validators.ts
|
|
31
|
+
// src/core/validators.ts
|
|
33
32
|
/**
|
|
34
33
|
* Determines whether a node is a heading block.
|
|
35
34
|
*
|
|
@@ -358,8 +357,26 @@ var isMarkdownDocument = recordOf({
|
|
|
358
357
|
element: literalOf("document"),
|
|
359
358
|
children: arrayOf(isBlockNode)
|
|
360
359
|
});
|
|
361
|
-
|
|
362
|
-
|
|
360
|
+
// src/core/MarkdownLineIterator.ts
|
|
361
|
+
/** Keeps a block parse's line cursor and its unconsumed lookahead. */
|
|
362
|
+
var MarkdownLineIterator = class extends Iterator {
|
|
363
|
+
#indices;
|
|
364
|
+
#lookahead;
|
|
365
|
+
constructor(indices) {
|
|
366
|
+
super();
|
|
367
|
+
this.#indices = indices;
|
|
368
|
+
this.#lookahead = indices.next();
|
|
369
|
+
}
|
|
370
|
+
get value() {
|
|
371
|
+
return this.#lookahead.done ? void 0 : this.#lookahead.value;
|
|
372
|
+
}
|
|
373
|
+
next() {
|
|
374
|
+
const result = this.#lookahead;
|
|
375
|
+
if (!result.done) this.#lookahead = this.#indices.next();
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
// src/core/parsers.ts
|
|
363
380
|
/**
|
|
364
381
|
* Parses a run of markdown lines into a block AST, recursing into nested
|
|
365
382
|
* blockquotes, list items, and depth-capped degrade paragraphs.
|
|
@@ -396,41 +413,39 @@ function parseBlocks(lines, depth, spans = /* @__PURE__ */ new Map(), end) {
|
|
|
396
413
|
return [paragraph];
|
|
397
414
|
}
|
|
398
415
|
const blocks = [];
|
|
399
|
-
|
|
400
|
-
|
|
416
|
+
const indices = new MarkdownLineIterator(lines.keys());
|
|
417
|
+
indices.reduce((index, step) => {
|
|
418
|
+
if (step < index) return index;
|
|
401
419
|
const line = text[index] ?? "";
|
|
402
|
-
if (isBlankLine(line))
|
|
403
|
-
index += 1;
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
420
|
+
if (isBlankLine(line)) return index + 1;
|
|
406
421
|
const fence = extractFence(line);
|
|
407
422
|
if (fence) {
|
|
408
423
|
const start = index;
|
|
409
424
|
const body = [];
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
closed = true;
|
|
419
|
-
index += 1;
|
|
425
|
+
for (const cursor of indices) {
|
|
426
|
+
const closed = isFenceClose(text[cursor] ?? "", fence.marker);
|
|
427
|
+
body.push([
|
|
428
|
+
cursor,
|
|
429
|
+
closed,
|
|
430
|
+
closed ? void 0 : lines[cursor]
|
|
431
|
+
]);
|
|
432
|
+
if (closed) break;
|
|
420
433
|
}
|
|
434
|
+
const closed = body.at(-1)?.[1] ?? false;
|
|
435
|
+
const next = (body.at(-1)?.[0] ?? index) + 1;
|
|
421
436
|
const node = {
|
|
422
437
|
element: "codeBlock",
|
|
423
438
|
...fence.lang === void 0 ? {} : { lang: fence.lang },
|
|
424
|
-
code: joinSources(body, "\n").text
|
|
439
|
+
code: joinSources(body.flatMap(([, , source]) => source === void 0 ? [] : [source]), "\n").text
|
|
425
440
|
};
|
|
426
|
-
const source = joinSources(lines.slice(start,
|
|
441
|
+
const source = joinSources(lines.slice(start, next), "\n");
|
|
427
442
|
const span = projectSpan(source, 0, source.text.length);
|
|
428
443
|
if (span !== void 0) spans.set(node, !closed && end !== void 0 ? {
|
|
429
444
|
start: span.start,
|
|
430
445
|
end
|
|
431
446
|
} : span);
|
|
432
447
|
blocks.push(node);
|
|
433
|
-
|
|
448
|
+
return next;
|
|
434
449
|
}
|
|
435
450
|
if (isThematicBreak(line)) {
|
|
436
451
|
const node = { element: "thematicBreak" };
|
|
@@ -438,8 +453,7 @@ function parseBlocks(lines, depth, spans = /* @__PURE__ */ new Map(), end) {
|
|
|
438
453
|
const span = source === void 0 ? void 0 : projectSpan(source, 0, source.text.length);
|
|
439
454
|
if (span !== void 0) spans.set(node, span);
|
|
440
455
|
blocks.push(node);
|
|
441
|
-
index
|
|
442
|
-
continue;
|
|
456
|
+
return index + 1;
|
|
443
457
|
}
|
|
444
458
|
const heading = extractHeading(line);
|
|
445
459
|
if (heading) {
|
|
@@ -456,57 +470,60 @@ function parseBlocks(lines, depth, spans = /* @__PURE__ */ new Map(), end) {
|
|
|
456
470
|
const span = source === void 0 ? void 0 : projectSpan(source, 0, source.text.length);
|
|
457
471
|
if (span !== void 0) spans.set(node, span);
|
|
458
472
|
blocks.push(node);
|
|
459
|
-
index
|
|
460
|
-
continue;
|
|
473
|
+
return index + 1;
|
|
461
474
|
}
|
|
462
475
|
if (isQuote(line)) {
|
|
463
476
|
const start = index;
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
477
|
+
const first = lines[index];
|
|
478
|
+
const quoted = first === void 0 ? [] : [[index, stripQuote(first)]];
|
|
479
|
+
while (indices.value !== void 0 && isQuote(text[indices.value] ?? "")) {
|
|
480
|
+
const cursor = indices.next().value;
|
|
481
|
+
if (cursor === void 0) break;
|
|
482
|
+
const quotedLine = lines[cursor];
|
|
467
483
|
if (quotedLine === void 0) break;
|
|
468
|
-
quoted.push(stripQuote(quotedLine));
|
|
469
|
-
index += 1;
|
|
484
|
+
quoted.push([cursor, stripQuote(quotedLine)]);
|
|
470
485
|
}
|
|
471
|
-
const
|
|
486
|
+
const next = quoted.length === 0 ? index : (quoted.at(-1)?.[0] ?? index) + 1;
|
|
487
|
+
const source = joinSources(lines.slice(start, next), "\n");
|
|
472
488
|
const span = projectSpan(source, 0, source.text.length);
|
|
473
489
|
const node = {
|
|
474
490
|
element: "blockquote",
|
|
475
|
-
children: parseBlocks(quoted, depth + 1, spans,
|
|
491
|
+
children: parseBlocks(quoted.map(([, quotedLine]) => quotedLine), depth + 1, spans, next === lines.length && end !== void 0 ? end : span?.end)
|
|
476
492
|
};
|
|
477
493
|
if (span !== void 0) spans.set(node, span);
|
|
478
494
|
blocks.push(node);
|
|
479
|
-
|
|
495
|
+
return next;
|
|
480
496
|
}
|
|
481
497
|
if (isTableStart(line, text[index + 1])) {
|
|
482
498
|
const table = collectTable(lines, index, spans);
|
|
483
499
|
blocks.push(table.node);
|
|
484
|
-
|
|
485
|
-
continue;
|
|
500
|
+
return table.next;
|
|
486
501
|
}
|
|
487
502
|
if (extractListItem(line)) {
|
|
488
503
|
const list = collectList(lines, index, depth, spans, end);
|
|
489
504
|
blocks.push(list.node);
|
|
490
|
-
|
|
491
|
-
continue;
|
|
505
|
+
return list.next;
|
|
492
506
|
}
|
|
493
507
|
const start = index;
|
|
494
|
-
const paragraph = [];
|
|
495
|
-
while (
|
|
496
|
-
const
|
|
497
|
-
if (
|
|
498
|
-
|
|
508
|
+
const paragraph = [[index, lines[index]]];
|
|
509
|
+
while (indices.value !== void 0 && !isBlankLine(text[indices.value] ?? "") && !startsBlock(text, indices.value)) {
|
|
510
|
+
const cursor = indices.next().value;
|
|
511
|
+
if (cursor === void 0) break;
|
|
512
|
+
paragraph.push([cursor, lines[cursor]]);
|
|
499
513
|
}
|
|
500
|
-
const
|
|
514
|
+
const next = paragraph.length === 0 ? index : (paragraph.at(-1)?.[0] ?? index) + 1;
|
|
515
|
+
const content = paragraph.flatMap(([, source]) => source === void 0 ? [] : [source]);
|
|
516
|
+
const source = joinSources(content.map((paragraphLine, position) => normalizeParagraphLine(paragraphLine, position < content.length - 1)), "\n");
|
|
501
517
|
const node = {
|
|
502
518
|
element: "paragraph",
|
|
503
519
|
children: coalesceText(scanInlineSource(source, 0, source.text.length, spans), spans)
|
|
504
520
|
};
|
|
505
|
-
const region = joinSources(lines.slice(start,
|
|
521
|
+
const region = joinSources(lines.slice(start, next), "\n");
|
|
506
522
|
const span = projectSpan(region, 0, region.text.length);
|
|
507
523
|
if (span !== void 0) spans.set(node, span);
|
|
508
524
|
blocks.push(node);
|
|
509
|
-
|
|
525
|
+
return next;
|
|
526
|
+
}, 0);
|
|
510
527
|
return blocks;
|
|
511
528
|
}
|
|
512
529
|
/**
|
|
@@ -533,6 +550,10 @@ function parseDocument(markdown) {
|
|
|
533
550
|
* @param markdown - The markdown source to parse.
|
|
534
551
|
* @returns The parsed document and its node-identity span map.
|
|
535
552
|
*
|
|
553
|
+
* @remarks
|
|
554
|
+
* Parsing inserts into a scratch memo. The returned map contains only nodes of the
|
|
555
|
+
* final document, enumerated without the public walk's depth cap.
|
|
556
|
+
*
|
|
536
557
|
* @example
|
|
537
558
|
* ```ts
|
|
538
559
|
* const [document, spans] = parseProvenance('# Hi')
|
|
@@ -540,16 +561,19 @@ function parseDocument(markdown) {
|
|
|
540
561
|
* ```
|
|
541
562
|
*/
|
|
542
563
|
function parseProvenance(markdown) {
|
|
543
|
-
const
|
|
564
|
+
const memo = /* @__PURE__ */ new Map();
|
|
544
565
|
const document = {
|
|
545
566
|
element: "document",
|
|
546
|
-
children: parseBlocks(splitLines(markdown), 0,
|
|
567
|
+
children: parseBlocks(splitLines(markdown), 0, memo, markdown.length)
|
|
547
568
|
};
|
|
548
|
-
|
|
569
|
+
memo.set(document, {
|
|
549
570
|
start: 0,
|
|
550
571
|
end: markdown.length
|
|
551
572
|
});
|
|
552
|
-
|
|
573
|
+
const nodes = [document];
|
|
574
|
+
for (const node of nodes) for (const child of listChildren(node)) nodes.push(child);
|
|
575
|
+
const present = new Set(nodes);
|
|
576
|
+
return [document, new Map([...memo].filter(([node]) => present.has(node)))];
|
|
553
577
|
}
|
|
554
578
|
/**
|
|
555
579
|
* Parses inline markdown text (emphasis, code spans, links, images, and hard
|
|
@@ -567,8 +591,7 @@ function parseProvenance(markdown) {
|
|
|
567
591
|
function parseInline(text) {
|
|
568
592
|
return coalesceText(scanInline(text, 0, text.length));
|
|
569
593
|
}
|
|
570
|
-
|
|
571
|
-
//#region src/core/helpers.ts
|
|
594
|
+
// src/core/helpers.ts
|
|
572
595
|
/**
|
|
573
596
|
* Splits a markdown document into offset-bearing lines while normalizing CRLF and
|
|
574
597
|
* bare CR terminators at the line boundary. A single trailing terminator does not
|
|
@@ -583,36 +606,17 @@ function parseInline(text) {
|
|
|
583
606
|
* ```
|
|
584
607
|
*/
|
|
585
608
|
function splitLines(markdown) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const character = markdown[index];
|
|
591
|
-
if (character !== "\r" && character !== "\n") {
|
|
592
|
-
index += 1;
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
lines.push({
|
|
596
|
-
text: markdown.slice(start, index),
|
|
609
|
+
return Iterator.from(markdown.matchAll(/([^\r\n]*)(?:\r\n|\r|\n|$)/g)).filter((match) => match.index < markdown.length || markdown.length === 0).map((match) => {
|
|
610
|
+
const text = match[1] ?? "";
|
|
611
|
+
return {
|
|
612
|
+
text,
|
|
597
613
|
segments: [{
|
|
598
614
|
offset: 0,
|
|
599
|
-
start,
|
|
600
|
-
end: index
|
|
615
|
+
start: match.index,
|
|
616
|
+
end: match.index + text.length
|
|
601
617
|
}]
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
start = index;
|
|
605
|
-
}
|
|
606
|
-
lines.push({
|
|
607
|
-
text: markdown.slice(start),
|
|
608
|
-
segments: [{
|
|
609
|
-
offset: 0,
|
|
610
|
-
start,
|
|
611
|
-
end: markdown.length
|
|
612
|
-
}]
|
|
613
|
-
});
|
|
614
|
-
if (lines.length > 1 && lines[lines.length - 1]?.text === "") lines.pop();
|
|
615
|
-
return lines;
|
|
618
|
+
};
|
|
619
|
+
}).toArray();
|
|
616
620
|
}
|
|
617
621
|
/**
|
|
618
622
|
* Slices derived markdown text and narrows each intersecting source segment to the
|
|
@@ -632,24 +636,23 @@ function splitLines(markdown) {
|
|
|
632
636
|
function sliceSource(source, from, to) {
|
|
633
637
|
const start = Math.max(0, Math.min(from, source.text.length));
|
|
634
638
|
const end = Math.max(start, Math.min(to, source.text.length));
|
|
635
|
-
const segments =
|
|
636
|
-
for (let index = 0; index < source.segments.length; index += 1) {
|
|
639
|
+
const segments = source.segments.keys().flatMap((index) => {
|
|
637
640
|
const segment = source.segments[index];
|
|
638
|
-
if (segment === void 0)
|
|
641
|
+
if (segment === void 0) return [];
|
|
639
642
|
const next = source.segments[index + 1];
|
|
640
643
|
const limit = Math.min(segment.offset + (segment.end - segment.start), next === void 0 ? source.text.length : next.offset);
|
|
641
644
|
const overlapStart = Math.max(start, segment.offset);
|
|
642
645
|
const overlapEnd = Math.min(end, limit);
|
|
643
646
|
const empty = segment.offset === limit && overlapStart === segment.offset;
|
|
644
|
-
if (overlapStart >= overlapEnd && !empty)
|
|
647
|
+
if (overlapStart >= overlapEnd && !empty) return [];
|
|
645
648
|
const originalStart = overlapStart === limit ? segment.end : Math.min(segment.end, segment.start + overlapStart - segment.offset);
|
|
646
649
|
const originalEnd = overlapEnd === limit ? segment.end : Math.min(segment.end, segment.start + overlapEnd - segment.offset);
|
|
647
|
-
|
|
650
|
+
return [{
|
|
648
651
|
offset: overlapStart - start,
|
|
649
652
|
start: originalStart,
|
|
650
653
|
end: originalEnd
|
|
651
|
-
}
|
|
652
|
-
}
|
|
654
|
+
}];
|
|
655
|
+
}).toArray();
|
|
653
656
|
return {
|
|
654
657
|
text: source.text.slice(start, end),
|
|
655
658
|
segments
|
|
@@ -670,31 +673,34 @@ function sliceSource(source, from, to) {
|
|
|
670
673
|
* ```
|
|
671
674
|
*/
|
|
672
675
|
function joinSources(sources, separator) {
|
|
673
|
-
|
|
676
|
+
const parts = [];
|
|
674
677
|
const segments = [];
|
|
675
|
-
|
|
678
|
+
sources.keys().reduce((offset, index) => {
|
|
676
679
|
const source = sources[index];
|
|
677
|
-
if (source === void 0)
|
|
680
|
+
if (source === void 0) return offset;
|
|
678
681
|
if (index > 0) {
|
|
679
682
|
const previous = sources[index - 1];
|
|
680
683
|
const left = previous?.segments[previous.segments.length - 1];
|
|
681
684
|
const right = source.segments[0];
|
|
682
685
|
if (separator.length > 0 && left !== void 0 && right !== void 0 && left.end < right.start) segments.push({
|
|
683
|
-
offset
|
|
686
|
+
offset,
|
|
684
687
|
start: left.end,
|
|
685
688
|
end: right.start
|
|
686
689
|
});
|
|
687
|
-
|
|
690
|
+
parts.push(separator);
|
|
688
691
|
}
|
|
692
|
+
const start = offset + (index > 0 ? separator.length : 0);
|
|
689
693
|
for (const segment of source.segments) segments.push({
|
|
690
|
-
offset:
|
|
694
|
+
offset: start + segment.offset,
|
|
691
695
|
start: segment.start,
|
|
692
696
|
end: segment.end
|
|
693
697
|
});
|
|
694
|
-
text
|
|
695
|
-
|
|
698
|
+
const text = source.text;
|
|
699
|
+
parts.push(text);
|
|
700
|
+
return start + text.length;
|
|
701
|
+
}, 0);
|
|
696
702
|
return {
|
|
697
|
-
text,
|
|
703
|
+
text: parts.join(""),
|
|
698
704
|
segments
|
|
699
705
|
};
|
|
700
706
|
}
|
|
@@ -715,27 +721,46 @@ function joinSources(sources, separator) {
|
|
|
715
721
|
*/
|
|
716
722
|
function projectSpan(source, from, to) {
|
|
717
723
|
if (from < 0 || to < from || to > source.text.length) return void 0;
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
724
|
+
const [spanStart, spanEnd] = source.segments.keys().reduce(([start, end, found], index) => {
|
|
725
|
+
if (found) return [
|
|
726
|
+
start,
|
|
727
|
+
end,
|
|
728
|
+
found
|
|
729
|
+
];
|
|
721
730
|
const segment = source.segments[index];
|
|
722
|
-
if (segment === void 0)
|
|
731
|
+
if (segment === void 0) return [
|
|
732
|
+
start,
|
|
733
|
+
end,
|
|
734
|
+
false
|
|
735
|
+
];
|
|
723
736
|
const next = source.segments[index + 1];
|
|
724
737
|
const limit = Math.min(segment.offset + (segment.end - segment.start), next === void 0 ? source.text.length : next.offset);
|
|
725
738
|
if (from === to && from >= segment.offset && from <= limit) {
|
|
726
|
-
if (next !== void 0 && from === next.offset)
|
|
739
|
+
if (next !== void 0 && from === next.offset) return [
|
|
740
|
+
start,
|
|
741
|
+
end,
|
|
742
|
+
false
|
|
743
|
+
];
|
|
727
744
|
const position = from === limit ? segment.end : Math.min(segment.end, segment.start + from - segment.offset);
|
|
728
|
-
return
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
745
|
+
return [
|
|
746
|
+
position,
|
|
747
|
+
position,
|
|
748
|
+
true
|
|
749
|
+
];
|
|
732
750
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
751
|
+
return [
|
|
752
|
+
start === void 0 && from >= segment.offset && from < limit ? segment.start + from - segment.offset : start,
|
|
753
|
+
to > segment.offset && to <= limit ? to === limit ? segment.end : Math.min(segment.end, segment.start + to - segment.offset) : end,
|
|
754
|
+
false
|
|
755
|
+
];
|
|
756
|
+
}, [
|
|
757
|
+
void 0,
|
|
758
|
+
void 0,
|
|
759
|
+
false
|
|
760
|
+
]);
|
|
761
|
+
return spanStart === void 0 || spanEnd === void 0 ? void 0 : {
|
|
762
|
+
start: spanStart,
|
|
763
|
+
end: spanEnd
|
|
739
764
|
};
|
|
740
765
|
}
|
|
741
766
|
/**
|
|
@@ -796,10 +821,7 @@ function normalizeParagraphLine(source, breaks) {
|
|
|
796
821
|
* ```
|
|
797
822
|
*/
|
|
798
823
|
function countIndent(line) {
|
|
799
|
-
|
|
800
|
-
for (const character of line) if (character === " " || character === " ") count += 1;
|
|
801
|
-
else break;
|
|
802
|
-
return count;
|
|
824
|
+
return line.match(/^[ \t]*/)?.[0].length ?? 0;
|
|
803
825
|
}
|
|
804
826
|
/**
|
|
805
827
|
* Checks whether `character` is whitespace under the emphasis flanking rule — a space, a
|
|
@@ -879,16 +901,8 @@ function isQuote(line) {
|
|
|
879
901
|
*/
|
|
880
902
|
function isFenceClose(line, marker) {
|
|
881
903
|
const character = marker[0] === "~" ? "~" : "`";
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
let run = 0;
|
|
885
|
-
while (index < line.length && line[index] === character) {
|
|
886
|
-
run++;
|
|
887
|
-
index++;
|
|
888
|
-
}
|
|
889
|
-
if (run < marker.length) return false;
|
|
890
|
-
while (index < line.length && isFenceWhitespace(line[index])) index++;
|
|
891
|
-
return index === line.length;
|
|
904
|
+
const trimmed = line.replace(/^[ \t\n\r\f\v]+|[ \t\n\r\f\v]+$/g, "");
|
|
905
|
+
return trimmed.length >= marker.length && Iterator.from(trimmed).every((value) => value === character);
|
|
892
906
|
}
|
|
893
907
|
/**
|
|
894
908
|
* Checks whether `character` is a regex-`\s`-equivalent whitespace character — the
|
|
@@ -1094,12 +1108,11 @@ function splitTableRow(row) {
|
|
|
1094
1108
|
function splitTableSources(row) {
|
|
1095
1109
|
const source = trimSource(row);
|
|
1096
1110
|
const cells = [];
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
if (
|
|
1102
|
-
pieces.push(sliceSource(source, start, index));
|
|
1111
|
+
const pieces = [];
|
|
1112
|
+
const [tailStart, tailOffset] = Iterator.from(source.text.matchAll(/\\\||\|/g)).reduce(([start, offset], match) => {
|
|
1113
|
+
const index = match.index;
|
|
1114
|
+
pieces.push(sliceSource(source, start, index));
|
|
1115
|
+
if (match[0] === "\\|") {
|
|
1103
1116
|
const span = projectSpan(source, index, index + 2);
|
|
1104
1117
|
pieces.push({
|
|
1105
1118
|
text: "|",
|
|
@@ -1109,21 +1122,16 @@ function splitTableSources(row) {
|
|
|
1109
1122
|
end: span.end
|
|
1110
1123
|
}]
|
|
1111
1124
|
});
|
|
1112
|
-
index
|
|
1113
|
-
start = index + 1;
|
|
1114
|
-
continue;
|
|
1125
|
+
return [index + 2, offset];
|
|
1115
1126
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
cells.
|
|
1124
|
-
if (isNonEmptyArray(cells) && isEmptyString((cells[0]?.text ?? "").trim())) cells.shift();
|
|
1125
|
-
if (isNonEmptyArray(cells) && isEmptyString((cells[cells.length - 1]?.text ?? "").trim())) cells.pop();
|
|
1126
|
-
return cells;
|
|
1127
|
+
cells.push(joinSources(pieces.slice(offset), ""));
|
|
1128
|
+
return [index + 1, pieces.length];
|
|
1129
|
+
}, [0, 0]);
|
|
1130
|
+
pieces.push(sliceSource(source, tailStart, source.text.length));
|
|
1131
|
+
cells.push(joinSources(pieces.slice(tailOffset), ""));
|
|
1132
|
+
const first = isEmptyString((cells[0]?.text ?? "").trim()) ? 1 : 0;
|
|
1133
|
+
const end = isEmptyString((cells.at(-1)?.text ?? "").trim()) ? cells.length - 1 : cells.length;
|
|
1134
|
+
return cells.slice(first, end);
|
|
1127
1135
|
}
|
|
1128
1136
|
/**
|
|
1129
1137
|
* Derives the per-column {@link TableAlign} list from a GFM delimiter row — `:---`
|
|
@@ -1182,22 +1190,15 @@ function startsBlock(lines, index) {
|
|
|
1182
1190
|
* ```
|
|
1183
1191
|
*/
|
|
1184
1192
|
function unescapeText(text) {
|
|
1185
|
-
|
|
1186
|
-
for (let index = 0; index < text.length; index += 1) {
|
|
1187
|
-
const character = text[index] ?? "";
|
|
1188
|
-
if (character === "\\" && isEscapable(text[index + 1] ?? "")) {
|
|
1189
|
-
out += text[index + 1] ?? "";
|
|
1190
|
-
index += 1;
|
|
1191
|
-
} else out += character;
|
|
1192
|
-
}
|
|
1193
|
-
return out;
|
|
1193
|
+
return text.replace(/\\([\s\S])/g, (match, character) => isEscapable(character) ? character : match);
|
|
1194
1194
|
}
|
|
1195
1195
|
/**
|
|
1196
1196
|
* Merges adjacent text nodes into one — the inline scanner emits a text node per
|
|
1197
1197
|
* unrecognized character, so coalescing keeps the AST clean and assertion-friendly.
|
|
1198
1198
|
*
|
|
1199
1199
|
* @param nodes - The inline nodes (possibly with adjacent text runs)
|
|
1200
|
-
* @param spans - The optional operation-owned node
|
|
1200
|
+
* @param spans - The optional operation-owned span memo; inserts merged-node entries
|
|
1201
|
+
* without deleting or replacing constituent entries
|
|
1201
1202
|
* @returns The nodes with consecutive text nodes concatenated
|
|
1202
1203
|
*
|
|
1203
1204
|
* @example
|
|
@@ -1207,28 +1208,53 @@ function unescapeText(text) {
|
|
|
1207
1208
|
* ```
|
|
1208
1209
|
*/
|
|
1209
1210
|
function coalesceText(nodes, spans) {
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1211
|
+
const result = Iterator.from(nodes).reduce(({ first, last, value, complete, log }, node) => {
|
|
1212
|
+
if (node.element === "text" && first?.element === "text") return {
|
|
1213
|
+
first,
|
|
1214
|
+
last: node,
|
|
1215
|
+
value: (value ?? first.value) + node.value,
|
|
1216
|
+
complete: complete && (spans?.has(node) ?? false),
|
|
1217
|
+
log
|
|
1218
|
+
};
|
|
1219
|
+
if (first !== void 0) {
|
|
1220
|
+
const merged = value === void 0 ? first : {
|
|
1215
1221
|
element: "text",
|
|
1216
|
-
value
|
|
1222
|
+
value
|
|
1217
1223
|
};
|
|
1218
|
-
const left = spans?.get(
|
|
1219
|
-
const right = spans?.get(
|
|
1220
|
-
if (
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1224
|
+
const left = spans?.get(first);
|
|
1225
|
+
const right = last === void 0 ? void 0 : spans?.get(last);
|
|
1226
|
+
if (value !== void 0 && complete && left !== void 0 && right !== void 0) spans?.set(merged, {
|
|
1227
|
+
start: left.start,
|
|
1228
|
+
end: right.end
|
|
1229
|
+
});
|
|
1230
|
+
log.push(merged);
|
|
1231
|
+
}
|
|
1232
|
+
return {
|
|
1233
|
+
first: node,
|
|
1234
|
+
last: node,
|
|
1235
|
+
value: void 0,
|
|
1236
|
+
complete: spans?.has(node) ?? false,
|
|
1237
|
+
log
|
|
1238
|
+
};
|
|
1239
|
+
}, {
|
|
1240
|
+
first: void 0,
|
|
1241
|
+
last: void 0,
|
|
1242
|
+
value: void 0,
|
|
1243
|
+
complete: false,
|
|
1244
|
+
log: []
|
|
1245
|
+
});
|
|
1246
|
+
if (result.first === void 0) return result.log;
|
|
1247
|
+
const merged = result.value === void 0 ? result.first : {
|
|
1248
|
+
element: "text",
|
|
1249
|
+
value: result.value
|
|
1250
|
+
};
|
|
1251
|
+
const left = spans?.get(result.first);
|
|
1252
|
+
const right = result.last === void 0 ? void 0 : spans?.get(result.last);
|
|
1253
|
+
if (result.value !== void 0 && result.complete && left !== void 0 && right !== void 0) spans?.set(merged, {
|
|
1254
|
+
start: left.start,
|
|
1255
|
+
end: right.end
|
|
1256
|
+
});
|
|
1257
|
+
return [...result.log, merged];
|
|
1232
1258
|
}
|
|
1233
1259
|
/**
|
|
1234
1260
|
* Scans an inline code span at `start` (a `` ` ``-run … a matching `` ` ``-run of the
|
|
@@ -1247,23 +1273,26 @@ function coalesceText(nodes, spans) {
|
|
|
1247
1273
|
* ```
|
|
1248
1274
|
*/
|
|
1249
1275
|
function scanCode(source, start, to) {
|
|
1250
|
-
|
|
1251
|
-
while (start + run < to && source[start + run] === "`") run += 1;
|
|
1276
|
+
const run = source[start] === "`" ? countMarkdownRun(source, start, to) : 0;
|
|
1252
1277
|
const open = "`".repeat(run);
|
|
1253
|
-
|
|
1254
|
-
|
|
1278
|
+
const attempts = walkMarkdownCharacters(source, 0, source.length + 2);
|
|
1279
|
+
const [, result] = attempts.reduce(([search]) => {
|
|
1255
1280
|
const closeAt = source.indexOf(open, search);
|
|
1256
|
-
if (closeAt === -1 || closeAt + run > to)
|
|
1281
|
+
if (closeAt === -1 || closeAt + run > to) {
|
|
1282
|
+
attempts.return(void 0);
|
|
1283
|
+
return [search, void 0];
|
|
1284
|
+
}
|
|
1257
1285
|
if (source[closeAt - 1] !== "`" && source[closeAt + run] !== "`") {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
return {
|
|
1261
|
-
value,
|
|
1286
|
+
attempts.return(void 0);
|
|
1287
|
+
const raw = source.slice(start + run, closeAt);
|
|
1288
|
+
return [search, {
|
|
1289
|
+
value: raw.length > 2 && raw.startsWith(" ") && raw.endsWith(" ") && raw.trim().length > 0 ? raw.slice(1, -1) : raw,
|
|
1262
1290
|
end: closeAt + run
|
|
1263
|
-
};
|
|
1291
|
+
}];
|
|
1264
1292
|
}
|
|
1265
|
-
|
|
1266
|
-
}
|
|
1293
|
+
return [closeAt + 1, void 0];
|
|
1294
|
+
}, [start + run, void 0]);
|
|
1295
|
+
return result;
|
|
1267
1296
|
}
|
|
1268
1297
|
/**
|
|
1269
1298
|
* Locates a link `[text](href)` at `start` — the text runs to a balanced `]`, then `(`
|
|
@@ -1282,45 +1311,12 @@ function scanCode(source, start, to) {
|
|
|
1282
1311
|
* ```
|
|
1283
1312
|
*/
|
|
1284
1313
|
function locateLink(source, start, to) {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
if (character === "\\") {
|
|
1290
|
-
index += 1;
|
|
1291
|
-
continue;
|
|
1292
|
-
}
|
|
1293
|
-
if (character === "[") bracketDepth += 1;
|
|
1294
|
-
else if (character === "]") {
|
|
1295
|
-
bracketDepth -= 1;
|
|
1296
|
-
if (bracketDepth === 0) {
|
|
1297
|
-
close = index;
|
|
1298
|
-
break;
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
if (close === -1 || source[close + 1] !== "(") return void 0;
|
|
1303
|
-
let parenDepth = 0;
|
|
1304
|
-
let parenClose = -1;
|
|
1305
|
-
for (let index = close + 1; index < to; index += 1) {
|
|
1306
|
-
const character = source[index] ?? "";
|
|
1307
|
-
if (character === "\\") {
|
|
1308
|
-
index += 1;
|
|
1309
|
-
continue;
|
|
1310
|
-
}
|
|
1311
|
-
if (character === "(") parenDepth += 1;
|
|
1312
|
-
else if (character === ")") {
|
|
1313
|
-
parenDepth -= 1;
|
|
1314
|
-
if (parenDepth === 0) {
|
|
1315
|
-
parenClose = index;
|
|
1316
|
-
break;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
if (parenClose === -1) return void 0;
|
|
1321
|
-
return {
|
|
1314
|
+
const close = locateMarkdownDelimiter(source, start, to, "[", "]");
|
|
1315
|
+
if (close === void 0 || source[close + 1] !== "(") return void 0;
|
|
1316
|
+
const end = locateMarkdownDelimiter(source, close + 1, to, "(", ")");
|
|
1317
|
+
return end === void 0 ? void 0 : {
|
|
1322
1318
|
close,
|
|
1323
|
-
end:
|
|
1319
|
+
end: end + 1
|
|
1324
1320
|
};
|
|
1325
1321
|
}
|
|
1326
1322
|
/**
|
|
@@ -1376,44 +1372,38 @@ function scanLink(source, start, to, depth = 0) {
|
|
|
1376
1372
|
*/
|
|
1377
1373
|
function locateEmphasis(source, start, to) {
|
|
1378
1374
|
const marker = source[start] ?? "";
|
|
1379
|
-
|
|
1380
|
-
while (start + run < to && source[start + run] === marker && run < 2) run += 1;
|
|
1375
|
+
const run = Math.min(2, countMarkdownRun(source, start, to));
|
|
1381
1376
|
const strong = run === 2;
|
|
1382
1377
|
const openEnd = start + run;
|
|
1383
1378
|
if (openEnd >= to || isFlankingWhitespace(source[openEnd] ?? "")) return void 0;
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
if (character === "\\")
|
|
1388
|
-
index += 2;
|
|
1389
|
-
continue;
|
|
1390
|
-
}
|
|
1379
|
+
const characters = walkMarkdownCharacters(source, openEnd, to);
|
|
1380
|
+
const [, result] = characters.reduce(([next], [index, character]) => {
|
|
1381
|
+
if (index < next) return [next, void 0];
|
|
1382
|
+
if (character === "\\") return [index + 2, void 0];
|
|
1391
1383
|
if (character === "`") {
|
|
1392
1384
|
const span = scanCode(source, index, to);
|
|
1393
|
-
|
|
1394
|
-
continue;
|
|
1385
|
+
return [span ? span.end : index + 1, void 0];
|
|
1395
1386
|
}
|
|
1396
1387
|
if ((character === "*" || character === "_") && character !== marker) {
|
|
1397
1388
|
const nested = locateEmphasis(source, index, to);
|
|
1398
|
-
if (nested !== void 0)
|
|
1399
|
-
index = nested.end;
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1389
|
+
if (nested !== void 0) return [nested.end, void 0];
|
|
1402
1390
|
}
|
|
1403
1391
|
if (character === marker) {
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1392
|
+
const closeRun = countMarkdownRun(source, index, to);
|
|
1393
|
+
if (closeRun >= run && !isFlankingWhitespace(source[index - 1] ?? "")) {
|
|
1394
|
+
characters.return(void 0);
|
|
1395
|
+
return [index + run, {
|
|
1396
|
+
strong,
|
|
1397
|
+
open: openEnd,
|
|
1398
|
+
close: index,
|
|
1399
|
+
end: index + run
|
|
1400
|
+
}];
|
|
1401
|
+
}
|
|
1402
|
+
return [index + closeRun, void 0];
|
|
1414
1403
|
}
|
|
1415
|
-
index
|
|
1416
|
-
}
|
|
1404
|
+
return [index + 1, void 0];
|
|
1405
|
+
}, [openEnd, void 0]);
|
|
1406
|
+
return result;
|
|
1417
1407
|
}
|
|
1418
1408
|
/**
|
|
1419
1409
|
* Scans an emphasis run at `start` (`*` / `_`, doubled for strong) — finds the nearest
|
|
@@ -1517,116 +1507,45 @@ function scanInlineSource(source, from, to, spans, depth = 0) {
|
|
|
1517
1507
|
return [node];
|
|
1518
1508
|
} else return [];
|
|
1519
1509
|
const nodes = [];
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1510
|
+
const parts = [];
|
|
1511
|
+
const [end, pendingStart, partOffset] = walkMarkdownCharacters(source.text, from, to).reduce(([next, start, offset], [index, character]) => {
|
|
1512
|
+
if (index < next) return [
|
|
1513
|
+
next,
|
|
1514
|
+
start,
|
|
1515
|
+
offset
|
|
1516
|
+
];
|
|
1525
1517
|
if (character === "\\" && index + 1 < to && isEscapable(source.text[index + 1] ?? "")) {
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
let spaceEnd = index;
|
|
1533
|
-
while (spaceEnd < to && source.text[spaceEnd] === " ") spaceEnd += 1;
|
|
1534
|
-
if (spaceEnd - index >= 2 && source.text[spaceEnd] === "\n") {
|
|
1535
|
-
if (pending.length > 0) {
|
|
1536
|
-
const node = {
|
|
1537
|
-
element: "text",
|
|
1538
|
-
value: pending
|
|
1539
|
-
};
|
|
1540
|
-
const span = projectSpan(source, pendingStart, index);
|
|
1541
|
-
if (span !== void 0) spans.set(node, span);
|
|
1542
|
-
nodes.push(node);
|
|
1543
|
-
pending = "";
|
|
1544
|
-
}
|
|
1545
|
-
const node = { element: "break" };
|
|
1546
|
-
const span = projectSpan(source, index, spaceEnd + 1);
|
|
1547
|
-
if (span !== void 0) spans.set(node, span);
|
|
1548
|
-
nodes.push(node);
|
|
1549
|
-
index = spaceEnd + 1;
|
|
1550
|
-
pendingStart = index;
|
|
1551
|
-
continue;
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
let scanned;
|
|
1555
|
-
let end = index;
|
|
1556
|
-
if (character === "`") {
|
|
1557
|
-
const span = scanCode(source.text, index, to);
|
|
1558
|
-
if (span) {
|
|
1559
|
-
scanned = {
|
|
1560
|
-
element: "codeSpan",
|
|
1561
|
-
value: span.value
|
|
1562
|
-
};
|
|
1563
|
-
end = span.end;
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
if (character === "!" && source.text[index + 1] === "[") {
|
|
1567
|
-
const link = locateLink(source.text, index + 1, to);
|
|
1568
|
-
if (link !== void 0) {
|
|
1569
|
-
scanned = {
|
|
1570
|
-
element: "image",
|
|
1571
|
-
src: unescapeText(source.text.slice(link.close + 2, link.end - 1).trim()),
|
|
1572
|
-
children: coalesceText(scanInlineSource(source, index + 2, link.close, spans, depth + 1), spans)
|
|
1573
|
-
};
|
|
1574
|
-
end = link.end;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
if (character === "[") {
|
|
1578
|
-
const link = locateLink(source.text, index, to);
|
|
1579
|
-
if (link !== void 0) {
|
|
1580
|
-
scanned = {
|
|
1581
|
-
element: "link",
|
|
1582
|
-
href: unescapeText(source.text.slice(link.close + 2, link.end - 1).trim()),
|
|
1583
|
-
children: coalesceText(scanInlineSource(source, index + 1, link.close, spans, depth + 1), spans)
|
|
1584
|
-
};
|
|
1585
|
-
end = link.end;
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
if (character === "*" || character === "_") {
|
|
1589
|
-
const emphasis = locateEmphasis(source.text, index, to);
|
|
1590
|
-
if (emphasis !== void 0) {
|
|
1591
|
-
scanned = {
|
|
1592
|
-
element: "emphasis",
|
|
1593
|
-
strong: emphasis.strong,
|
|
1594
|
-
children: coalesceText(scanInlineSource(source, emphasis.open, emphasis.close, spans, depth + 1), spans)
|
|
1595
|
-
};
|
|
1596
|
-
end = emphasis.end;
|
|
1597
|
-
}
|
|
1518
|
+
parts.push(source.text[index + 1] ?? "");
|
|
1519
|
+
return [
|
|
1520
|
+
index + 2,
|
|
1521
|
+
start,
|
|
1522
|
+
offset
|
|
1523
|
+
];
|
|
1598
1524
|
}
|
|
1525
|
+
const scanned = scanMarkdownInline(source, index, to, spans, depth);
|
|
1599
1526
|
if (scanned !== void 0) {
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
}
|
|
1610
|
-
const span = projectSpan(source, index, end);
|
|
1611
|
-
if (span !== void 0) spans.set(scanned, span);
|
|
1612
|
-
nodes.push(scanned);
|
|
1613
|
-
index = end;
|
|
1614
|
-
pendingStart = index;
|
|
1615
|
-
continue;
|
|
1527
|
+
recordMarkdownText(source, parts.slice(offset).join(""), start, index, spans, nodes);
|
|
1528
|
+
const span = projectSpan(source, index, scanned.end);
|
|
1529
|
+
if (span !== void 0) spans.set(scanned.node, span);
|
|
1530
|
+
nodes.push(scanned.node);
|
|
1531
|
+
return [
|
|
1532
|
+
scanned.end,
|
|
1533
|
+
scanned.end,
|
|
1534
|
+
parts.length
|
|
1535
|
+
];
|
|
1616
1536
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
}
|
|
1537
|
+
parts.push(character);
|
|
1538
|
+
return [
|
|
1539
|
+
index + 1,
|
|
1540
|
+
start,
|
|
1541
|
+
offset
|
|
1542
|
+
];
|
|
1543
|
+
}, [
|
|
1544
|
+
from,
|
|
1545
|
+
from,
|
|
1546
|
+
0
|
|
1547
|
+
]);
|
|
1548
|
+
recordMarkdownText(source, parts.slice(partOffset).join(""), pendingStart, end, spans, nodes);
|
|
1630
1549
|
return nodes;
|
|
1631
1550
|
}
|
|
1632
1551
|
/**
|
|
@@ -1654,30 +1573,28 @@ function collectTable(lines, start, spans = /* @__PURE__ */ new Map()) {
|
|
|
1654
1573
|
return coalesceText(scanInlineSource(source, 0, source.text.length, spans), spans);
|
|
1655
1574
|
});
|
|
1656
1575
|
const align = delimiterToAlignments(lines[start + 1]?.text ?? "");
|
|
1657
|
-
const padded = [];
|
|
1658
|
-
|
|
1659
|
-
const
|
|
1660
|
-
|
|
1661
|
-
while (index < lines.length && !isBlankLine(lines[index]?.text ?? "") && (lines[index]?.text ?? "").includes("|")) {
|
|
1576
|
+
const padded = Array.from({ length: columns }, (_, column) => align[column] ?? null);
|
|
1577
|
+
const entries = [];
|
|
1578
|
+
for (const index of walkMarkdownLines(lines, start + 2)) {
|
|
1579
|
+
if (isBlankLine(lines[index]?.text ?? "") || !(lines[index]?.text ?? "").includes("|")) break;
|
|
1662
1580
|
const cells = splitTableSources(lines[index] ?? {
|
|
1663
1581
|
text: "",
|
|
1664
1582
|
segments: []
|
|
1665
1583
|
});
|
|
1666
|
-
const row =
|
|
1667
|
-
for (let column = 0; column < columns; column += 1) {
|
|
1584
|
+
const row = Array.from({ length: columns }, (_, column) => {
|
|
1668
1585
|
const source = trimSource(cells[column] ?? {
|
|
1669
1586
|
text: "",
|
|
1670
1587
|
segments: []
|
|
1671
1588
|
});
|
|
1672
|
-
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
index += 1;
|
|
1589
|
+
return coalesceText(scanInlineSource(source, 0, source.text.length, spans), spans);
|
|
1590
|
+
});
|
|
1591
|
+
entries.push([index, row]);
|
|
1676
1592
|
}
|
|
1593
|
+
const index = entries.length === 0 ? start + 2 : (entries.at(-1)?.[0] ?? start + 1) + 1;
|
|
1677
1594
|
const node = {
|
|
1678
1595
|
element: "table",
|
|
1679
1596
|
header,
|
|
1680
|
-
rows,
|
|
1597
|
+
rows: entries.map(([, row]) => row),
|
|
1681
1598
|
align: padded
|
|
1682
1599
|
};
|
|
1683
1600
|
const source = joinSources(lines.slice(start, index), "\n");
|
|
@@ -1712,23 +1629,20 @@ function collectList(lines, start, depth, spans = /* @__PURE__ */ new Map(), end
|
|
|
1712
1629
|
const topIndent = first?.indent ?? 0;
|
|
1713
1630
|
const items = [];
|
|
1714
1631
|
const chain = [];
|
|
1715
|
-
|
|
1716
|
-
for (let cursor = start; cursor < lines.length; cursor += 1) {
|
|
1632
|
+
const nested = walkMarkdownLines(lines, start).every((cursor) => {
|
|
1717
1633
|
const parsed = extractListItem(text[cursor] ?? "");
|
|
1718
1634
|
const previous = chain[chain.length - 1];
|
|
1719
|
-
if (parsed === void 0 || previous !== void 0 && (previous.content.length > 0 || parsed.indent !== previous.marker))
|
|
1720
|
-
nested = false;
|
|
1721
|
-
break;
|
|
1722
|
-
}
|
|
1635
|
+
if (parsed === void 0 || previous !== void 0 && (previous.content.length > 0 || parsed.indent !== previous.marker)) return false;
|
|
1723
1636
|
chain.push(parsed);
|
|
1724
|
-
|
|
1637
|
+
return true;
|
|
1638
|
+
});
|
|
1725
1639
|
const remaining = 64 - depth;
|
|
1726
1640
|
if (nested && remaining > 0 && chain.length > remaining) {
|
|
1727
1641
|
const terminal = chain[remaining - 1];
|
|
1728
1642
|
const terminalLine = lines[start + remaining - 1];
|
|
1729
1643
|
if (terminal !== void 0 && terminalLine !== void 0) {
|
|
1730
1644
|
const sources = [sliceSource(terminalLine, terminal.marker, terminalLine.text.length)];
|
|
1731
|
-
for (
|
|
1645
|
+
for (const cursor of walkMarkdownLines(lines, start + remaining)) {
|
|
1732
1646
|
const line = lines[cursor];
|
|
1733
1647
|
if (line !== void 0) sources.push(sliceSource(line, terminal.marker, line.text.length));
|
|
1734
1648
|
}
|
|
@@ -1746,16 +1660,16 @@ function collectList(lines, start, depth, spans = /* @__PURE__ */ new Map(), end
|
|
|
1746
1660
|
spans.set(textNode, residualSpan);
|
|
1747
1661
|
spans.set(paragraph, residualSpan);
|
|
1748
1662
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1663
|
+
const node = Array.from({ length: Math.ceil(remaining) }, (_, step) => step).reduce((child, step) => {
|
|
1664
|
+
const cursor = remaining - 1 - step;
|
|
1665
|
+
const children = child === void 0 ? [paragraph] : [child];
|
|
1752
1666
|
const parsed = chain[cursor];
|
|
1753
|
-
if (parsed === void 0)
|
|
1667
|
+
if (parsed === void 0) return child;
|
|
1754
1668
|
const item = {
|
|
1755
1669
|
element: "listItem",
|
|
1756
1670
|
children
|
|
1757
1671
|
};
|
|
1758
|
-
|
|
1672
|
+
const parent = {
|
|
1759
1673
|
element: "list",
|
|
1760
1674
|
ordered: parsed.ordered,
|
|
1761
1675
|
start: parsed.start,
|
|
@@ -1765,72 +1679,78 @@ function collectList(lines, start, depth, spans = /* @__PURE__ */ new Map(), end
|
|
|
1765
1679
|
const span = projectSpan(region, 0, region.text.length);
|
|
1766
1680
|
if (span !== void 0) {
|
|
1767
1681
|
spans.set(item, span);
|
|
1768
|
-
spans.set(
|
|
1682
|
+
spans.set(parent, span);
|
|
1769
1683
|
}
|
|
1770
|
-
|
|
1771
|
-
}
|
|
1684
|
+
return parent;
|
|
1685
|
+
}, void 0);
|
|
1772
1686
|
if (node !== void 0) return {
|
|
1773
1687
|
node,
|
|
1774
1688
|
next: lines.length
|
|
1775
1689
|
};
|
|
1776
1690
|
}
|
|
1777
1691
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1692
|
+
const positions = walkMarkdownLines(lines, start);
|
|
1693
|
+
const finish = positions.reduce((index, position) => {
|
|
1694
|
+
if (position < index) return index;
|
|
1780
1695
|
const parsed = extractListItem(text[index] ?? "");
|
|
1781
|
-
if (!parsed || parsed.indent > topIndent || parsed.ordered !== ordered)
|
|
1696
|
+
if (!parsed || parsed.indent > topIndent || parsed.ordered !== ordered) {
|
|
1697
|
+
positions.return(void 0);
|
|
1698
|
+
return index;
|
|
1699
|
+
}
|
|
1782
1700
|
const itemStart = index;
|
|
1783
1701
|
const itemLine = lines[index];
|
|
1784
|
-
if (itemLine === void 0)
|
|
1785
|
-
|
|
1702
|
+
if (itemLine === void 0) {
|
|
1703
|
+
positions.return(void 0);
|
|
1704
|
+
return index;
|
|
1705
|
+
}
|
|
1706
|
+
const entries = [[index, sliceSource(itemLine, parsed.marker, itemLine.text.length)]];
|
|
1786
1707
|
const continuation = parsed.marker;
|
|
1787
|
-
index
|
|
1788
|
-
|
|
1789
|
-
const nextSource = lines[index];
|
|
1708
|
+
for (const cursor of walkMarkdownLines(lines, index + 1)) {
|
|
1709
|
+
const nextSource = lines[cursor];
|
|
1790
1710
|
if (nextSource === void 0) break;
|
|
1791
1711
|
const next = nextSource.text;
|
|
1792
1712
|
if (isBlankLine(next)) {
|
|
1793
|
-
const after = lines[
|
|
1794
|
-
if (
|
|
1795
|
-
|
|
1796
|
-
index += 1;
|
|
1713
|
+
const after = lines[cursor + 1]?.text ?? "";
|
|
1714
|
+
if (cursor + 1 < lines.length && !isBlankLine(after) && countIndent(after) >= continuation) {
|
|
1715
|
+
entries.push([cursor, sliceSource(nextSource, 0, 0)]);
|
|
1797
1716
|
continue;
|
|
1798
1717
|
}
|
|
1799
1718
|
break;
|
|
1800
1719
|
}
|
|
1801
1720
|
if (countIndent(next) >= continuation) {
|
|
1802
|
-
|
|
1803
|
-
index += 1;
|
|
1721
|
+
entries.push([cursor, sliceSource(nextSource, continuation, next.length)]);
|
|
1804
1722
|
continue;
|
|
1805
1723
|
}
|
|
1806
|
-
if (extractListItem(next) || startsBlock(text,
|
|
1807
|
-
|
|
1808
|
-
index += 1;
|
|
1724
|
+
if (extractListItem(next) || startsBlock(text, cursor)) break;
|
|
1725
|
+
entries.push([cursor, trimSource(nextSource)]);
|
|
1809
1726
|
}
|
|
1727
|
+
const next = (entries.at(-1)?.[0] ?? index) + 1;
|
|
1728
|
+
const itemLines = entries.map(([, line]) => line);
|
|
1810
1729
|
const tail = itemLines[itemLines.length - 1];
|
|
1811
1730
|
const segment = tail?.segments[tail.segments.length - 1];
|
|
1812
|
-
const itemEnd =
|
|
1731
|
+
const itemEnd = next === lines.length && end !== void 0 ? end : segment?.end;
|
|
1813
1732
|
const item = {
|
|
1814
1733
|
element: "listItem",
|
|
1815
1734
|
children: parseBlocks(itemLines, depth + 1, spans, itemEnd)
|
|
1816
1735
|
};
|
|
1817
|
-
const source = joinSources(lines.slice(itemStart,
|
|
1736
|
+
const source = joinSources(lines.slice(itemStart, next), "\n");
|
|
1818
1737
|
const span = projectSpan(source, 0, source.text.length);
|
|
1819
1738
|
if (span !== void 0) spans.set(item, span);
|
|
1820
1739
|
items.push(item);
|
|
1821
|
-
|
|
1740
|
+
return next;
|
|
1741
|
+
}, start);
|
|
1822
1742
|
const node = {
|
|
1823
1743
|
element: "list",
|
|
1824
1744
|
ordered,
|
|
1825
1745
|
start: startOrdinal,
|
|
1826
1746
|
items
|
|
1827
1747
|
};
|
|
1828
|
-
const source = joinSources(lines.slice(start,
|
|
1748
|
+
const source = joinSources(lines.slice(start, finish), "\n");
|
|
1829
1749
|
const span = projectSpan(source, 0, source.text.length);
|
|
1830
1750
|
if (span !== void 0) spans.set(node, span);
|
|
1831
1751
|
return {
|
|
1832
1752
|
node,
|
|
1833
|
-
next:
|
|
1753
|
+
next: finish
|
|
1834
1754
|
};
|
|
1835
1755
|
}
|
|
1836
1756
|
/**
|
|
@@ -1873,7 +1793,7 @@ function markdownToHTML(node) {
|
|
|
1873
1793
|
continue;
|
|
1874
1794
|
}
|
|
1875
1795
|
const children = [];
|
|
1876
|
-
|
|
1796
|
+
const depth = frame.depth + (current.element === "document" ? 0 : current.element === "table" ? 4 : 1);
|
|
1877
1797
|
switch (current.element) {
|
|
1878
1798
|
case "document":
|
|
1879
1799
|
for (const child of current.children) if (child !== void 0) children.push(child);
|
|
@@ -1882,24 +1802,20 @@ function markdownToHTML(node) {
|
|
|
1882
1802
|
case "paragraph":
|
|
1883
1803
|
case "blockquote":
|
|
1884
1804
|
for (const child of current.children) if (child !== void 0) children.push(child);
|
|
1885
|
-
depth += 1;
|
|
1886
1805
|
break;
|
|
1887
1806
|
case "listItem": {
|
|
1888
1807
|
const only = current.children[0];
|
|
1889
1808
|
if (current.children.length === 1 && only !== void 0 && only.element === "paragraph") {
|
|
1890
1809
|
for (const child of only.children) if (child !== void 0) children.push(child);
|
|
1891
1810
|
} else for (const child of current.children) if (child !== void 0) children.push(child);
|
|
1892
|
-
depth += 1;
|
|
1893
1811
|
break;
|
|
1894
1812
|
}
|
|
1895
1813
|
case "emphasis":
|
|
1896
1814
|
case "link":
|
|
1897
1815
|
for (const child of current.children) if (child !== void 0) children.push(child);
|
|
1898
|
-
depth += 1;
|
|
1899
1816
|
break;
|
|
1900
1817
|
case "list":
|
|
1901
1818
|
for (const child of current.items) if (child !== void 0) children.push(child);
|
|
1902
|
-
depth += 1;
|
|
1903
1819
|
break;
|
|
1904
1820
|
case "table":
|
|
1905
1821
|
if (frame.depth + 4 > 64) {
|
|
@@ -1914,7 +1830,6 @@ function markdownToHTML(node) {
|
|
|
1914
1830
|
for (const child of cell) if (child !== void 0) children.push(child);
|
|
1915
1831
|
}
|
|
1916
1832
|
}
|
|
1917
|
-
depth += 4;
|
|
1918
1833
|
}
|
|
1919
1834
|
if (current.element === "codeBlock" && frame.depth + 2 > 64) {
|
|
1920
1835
|
values.push(void 0);
|
|
@@ -1925,7 +1840,7 @@ function markdownToHTML(node) {
|
|
|
1925
1840
|
expanded: true,
|
|
1926
1841
|
count: children.length
|
|
1927
1842
|
});
|
|
1928
|
-
for (
|
|
1843
|
+
for (const index of children.keys().toArray().toReversed()) {
|
|
1929
1844
|
const child = children[index];
|
|
1930
1845
|
if (child !== void 0) stack.push({
|
|
1931
1846
|
node: child,
|
|
@@ -1936,231 +1851,207 @@ function markdownToHTML(node) {
|
|
|
1936
1851
|
}
|
|
1937
1852
|
continue;
|
|
1938
1853
|
}
|
|
1939
|
-
const
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
1854
|
+
const value = projectMarkdownNode(current, frame.count === 0 ? [] : values.splice(values.length - frame.count, frame.count));
|
|
1855
|
+
values.push(value);
|
|
1856
|
+
}
|
|
1857
|
+
const projected = values[0];
|
|
1858
|
+
if (projected?.category === "document") return projected;
|
|
1859
|
+
return {
|
|
1860
|
+
category: "document",
|
|
1861
|
+
children: projected === void 0 ? [] : [projected]
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Projects one markdown node from its already projected children.
|
|
1866
|
+
*
|
|
1867
|
+
* @example
|
|
1868
|
+
* ```ts
|
|
1869
|
+
* projectMarkdownNode({ element: 'text', value: 'hi' }, []) // { category: 'text', value: 'hi' }
|
|
1870
|
+
* ```
|
|
1871
|
+
*/
|
|
1872
|
+
function projectMarkdownNode(current, children) {
|
|
1873
|
+
const projected = [];
|
|
1874
|
+
for (const child of children) if (child !== void 0) projected.push(child);
|
|
1875
|
+
switch (current.element) {
|
|
1876
|
+
case "document": return {
|
|
1877
|
+
category: "document",
|
|
1878
|
+
children: projected
|
|
1879
|
+
};
|
|
1880
|
+
case "heading": return {
|
|
1881
|
+
category: "element",
|
|
1882
|
+
name: `h${current.level}`,
|
|
1883
|
+
attributes: [],
|
|
1884
|
+
children: projected
|
|
1885
|
+
};
|
|
1886
|
+
case "paragraph": return {
|
|
1887
|
+
category: "element",
|
|
1888
|
+
name: "p",
|
|
1889
|
+
attributes: [],
|
|
1890
|
+
children: projected
|
|
1891
|
+
};
|
|
1892
|
+
case "thematicBreak": return {
|
|
1893
|
+
category: "element",
|
|
1894
|
+
name: "hr",
|
|
1895
|
+
attributes: [],
|
|
1896
|
+
children: []
|
|
1897
|
+
};
|
|
1898
|
+
case "blockquote": return {
|
|
1899
|
+
category: "element",
|
|
1900
|
+
name: "blockquote",
|
|
1901
|
+
attributes: [],
|
|
1902
|
+
children: projected
|
|
1903
|
+
};
|
|
1904
|
+
case "codeBlock": return {
|
|
1905
|
+
category: "element",
|
|
1906
|
+
name: "pre",
|
|
1907
|
+
attributes: [],
|
|
1908
|
+
children: [{
|
|
1909
|
+
category: "element",
|
|
1910
|
+
name: "code",
|
|
1911
|
+
attributes: current.lang === void 0 ? [] : [{
|
|
1912
|
+
name: "class",
|
|
1913
|
+
value: `language-${current.lang}`
|
|
1914
|
+
}],
|
|
1915
|
+
children: [{
|
|
1916
|
+
category: "text",
|
|
1917
|
+
value: current.code
|
|
1918
|
+
}]
|
|
1919
|
+
}]
|
|
1920
|
+
};
|
|
1921
|
+
case "list": return {
|
|
1922
|
+
category: "element",
|
|
1923
|
+
name: current.ordered ? "ol" : "ul",
|
|
1924
|
+
attributes: current.ordered && current.start !== 1 ? [{
|
|
1925
|
+
name: "start",
|
|
1926
|
+
value: String(current.start)
|
|
1927
|
+
}] : [],
|
|
1928
|
+
children: projected
|
|
1929
|
+
};
|
|
1930
|
+
case "listItem": return {
|
|
1931
|
+
category: "element",
|
|
1932
|
+
name: "li",
|
|
1933
|
+
attributes: [],
|
|
1934
|
+
children: projected
|
|
1935
|
+
};
|
|
1936
|
+
case "table": {
|
|
1937
|
+
const outputs = children.values();
|
|
1938
|
+
const header = [];
|
|
1939
|
+
for (const [column, cell] of current.header.entries()) {
|
|
1940
|
+
if (cell === void 0) continue;
|
|
1941
|
+
const align = current.align[column];
|
|
1942
|
+
const attributes = align === "left" || align === "right" || align === "center" ? [{
|
|
1943
|
+
name: "align",
|
|
1944
|
+
value: align
|
|
1945
|
+
}] : [];
|
|
1946
|
+
const count = Iterator.from(cell).filter((child) => child !== void 0).reduce((sum) => sum + 1, 0);
|
|
1947
|
+
const cellChildren = [];
|
|
1948
|
+
for (const child of outputs.take(count).toArray()) if (child !== void 0) cellChildren.push(child);
|
|
1949
|
+
header.push({
|
|
2014
1950
|
category: "element",
|
|
2015
|
-
name: "
|
|
2016
|
-
attributes
|
|
2017
|
-
children:
|
|
2018
|
-
};
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
const
|
|
2023
|
-
for (const [column, cell] of
|
|
1951
|
+
name: "th",
|
|
1952
|
+
attributes,
|
|
1953
|
+
children: cellChildren
|
|
1954
|
+
});
|
|
1955
|
+
}
|
|
1956
|
+
const rows = [];
|
|
1957
|
+
for (const row of current.rows) {
|
|
1958
|
+
const cells = [];
|
|
1959
|
+
for (const [column, cell] of row.entries()) {
|
|
2024
1960
|
if (cell === void 0) continue;
|
|
2025
1961
|
const align = current.align[column];
|
|
2026
1962
|
const attributes = align === "left" || align === "right" || align === "center" ? [{
|
|
2027
1963
|
name: "align",
|
|
2028
1964
|
value: align
|
|
2029
1965
|
}] : [];
|
|
2030
|
-
|
|
2031
|
-
for (const child of cell) if (child !== void 0) count += 1;
|
|
1966
|
+
const count = Iterator.from(cell).filter((child) => child !== void 0).reduce((sum) => sum + 1, 0);
|
|
2032
1967
|
const cellChildren = [];
|
|
2033
|
-
for (const child of
|
|
2034
|
-
|
|
1968
|
+
for (const child of outputs.take(count).toArray()) if (child !== void 0) cellChildren.push(child);
|
|
1969
|
+
cells.push({
|
|
2035
1970
|
category: "element",
|
|
2036
|
-
name: "
|
|
1971
|
+
name: "td",
|
|
2037
1972
|
attributes,
|
|
2038
1973
|
children: cellChildren
|
|
2039
1974
|
});
|
|
2040
|
-
offset += count;
|
|
2041
|
-
}
|
|
2042
|
-
const rows = [];
|
|
2043
|
-
for (const row of current.rows) {
|
|
2044
|
-
const cells = [];
|
|
2045
|
-
for (const [column, cell] of row.entries()) {
|
|
2046
|
-
if (cell === void 0) continue;
|
|
2047
|
-
const align = current.align[column];
|
|
2048
|
-
const attributes = align === "left" || align === "right" || align === "center" ? [{
|
|
2049
|
-
name: "align",
|
|
2050
|
-
value: align
|
|
2051
|
-
}] : [];
|
|
2052
|
-
let count = 0;
|
|
2053
|
-
for (const child of cell) if (child !== void 0) count += 1;
|
|
2054
|
-
const cellChildren = [];
|
|
2055
|
-
for (const child of children.slice(offset, offset + count)) if (child !== void 0) cellChildren.push(child);
|
|
2056
|
-
cells.push({
|
|
2057
|
-
category: "element",
|
|
2058
|
-
name: "td",
|
|
2059
|
-
attributes,
|
|
2060
|
-
children: cellChildren
|
|
2061
|
-
});
|
|
2062
|
-
offset += count;
|
|
2063
|
-
}
|
|
2064
|
-
rows.push({
|
|
2065
|
-
category: "element",
|
|
2066
|
-
name: "tr",
|
|
2067
|
-
attributes: [],
|
|
2068
|
-
children: cells
|
|
2069
|
-
});
|
|
2070
1975
|
}
|
|
2071
|
-
|
|
2072
|
-
category: "element",
|
|
2073
|
-
name: "thead",
|
|
2074
|
-
attributes: [],
|
|
2075
|
-
children: [{
|
|
2076
|
-
category: "element",
|
|
2077
|
-
name: "tr",
|
|
2078
|
-
attributes: [],
|
|
2079
|
-
children: header
|
|
2080
|
-
}]
|
|
2081
|
-
}];
|
|
2082
|
-
if (isNonEmptyArray(current.rows)) tableChildren.push({
|
|
1976
|
+
rows.push({
|
|
2083
1977
|
category: "element",
|
|
2084
|
-
name: "
|
|
1978
|
+
name: "tr",
|
|
2085
1979
|
attributes: [],
|
|
2086
|
-
children:
|
|
1980
|
+
children: cells
|
|
2087
1981
|
});
|
|
2088
|
-
value = {
|
|
2089
|
-
category: "element",
|
|
2090
|
-
name: "table",
|
|
2091
|
-
attributes: [],
|
|
2092
|
-
children: tableChildren
|
|
2093
|
-
};
|
|
2094
|
-
break;
|
|
2095
1982
|
}
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
break;
|
|
2102
|
-
case "emphasis":
|
|
2103
|
-
value = {
|
|
2104
|
-
category: "element",
|
|
2105
|
-
name: current.strong ? "strong" : "em",
|
|
2106
|
-
attributes: [],
|
|
2107
|
-
children: projected
|
|
2108
|
-
};
|
|
2109
|
-
break;
|
|
2110
|
-
case "codeSpan":
|
|
2111
|
-
value = {
|
|
2112
|
-
category: "element",
|
|
2113
|
-
name: "code",
|
|
2114
|
-
attributes: [],
|
|
2115
|
-
children: [{
|
|
2116
|
-
category: "text",
|
|
2117
|
-
value: current.value
|
|
2118
|
-
}]
|
|
2119
|
-
};
|
|
2120
|
-
break;
|
|
2121
|
-
case "link":
|
|
2122
|
-
value = {
|
|
2123
|
-
category: "element",
|
|
2124
|
-
name: "a",
|
|
2125
|
-
attributes: [{
|
|
2126
|
-
name: "href",
|
|
2127
|
-
value: current.href
|
|
2128
|
-
}],
|
|
2129
|
-
children: projected
|
|
2130
|
-
};
|
|
2131
|
-
break;
|
|
2132
|
-
case "image":
|
|
2133
|
-
value = {
|
|
2134
|
-
category: "element",
|
|
2135
|
-
name: "img",
|
|
2136
|
-
attributes: [{
|
|
2137
|
-
name: "src",
|
|
2138
|
-
value: current.src
|
|
2139
|
-
}, {
|
|
2140
|
-
name: "alt",
|
|
2141
|
-
value: flattenText(current)
|
|
2142
|
-
}],
|
|
2143
|
-
children: []
|
|
2144
|
-
};
|
|
2145
|
-
break;
|
|
2146
|
-
case "break":
|
|
2147
|
-
value = {
|
|
1983
|
+
const tableChildren = [{
|
|
1984
|
+
category: "element",
|
|
1985
|
+
name: "thead",
|
|
1986
|
+
attributes: [],
|
|
1987
|
+
children: [{
|
|
2148
1988
|
category: "element",
|
|
2149
|
-
name: "
|
|
1989
|
+
name: "tr",
|
|
2150
1990
|
attributes: [],
|
|
2151
|
-
children:
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
|
|
1991
|
+
children: header
|
|
1992
|
+
}]
|
|
1993
|
+
}];
|
|
1994
|
+
if (isNonEmptyArray(current.rows)) tableChildren.push({
|
|
1995
|
+
category: "element",
|
|
1996
|
+
name: "tbody",
|
|
1997
|
+
attributes: [],
|
|
1998
|
+
children: rows
|
|
1999
|
+
});
|
|
2000
|
+
return {
|
|
2001
|
+
category: "element",
|
|
2002
|
+
name: "table",
|
|
2003
|
+
attributes: [],
|
|
2004
|
+
children: tableChildren
|
|
2005
|
+
};
|
|
2155
2006
|
}
|
|
2156
|
-
|
|
2007
|
+
case "text": return {
|
|
2008
|
+
category: "text",
|
|
2009
|
+
value: current.value
|
|
2010
|
+
};
|
|
2011
|
+
case "emphasis": return {
|
|
2012
|
+
category: "element",
|
|
2013
|
+
name: current.strong ? "strong" : "em",
|
|
2014
|
+
attributes: [],
|
|
2015
|
+
children: projected
|
|
2016
|
+
};
|
|
2017
|
+
case "codeSpan": return {
|
|
2018
|
+
category: "element",
|
|
2019
|
+
name: "code",
|
|
2020
|
+
attributes: [],
|
|
2021
|
+
children: [{
|
|
2022
|
+
category: "text",
|
|
2023
|
+
value: current.value
|
|
2024
|
+
}]
|
|
2025
|
+
};
|
|
2026
|
+
case "link": return {
|
|
2027
|
+
category: "element",
|
|
2028
|
+
name: "a",
|
|
2029
|
+
attributes: [{
|
|
2030
|
+
name: "href",
|
|
2031
|
+
value: current.href
|
|
2032
|
+
}],
|
|
2033
|
+
children: projected
|
|
2034
|
+
};
|
|
2035
|
+
case "image": return {
|
|
2036
|
+
category: "element",
|
|
2037
|
+
name: "img",
|
|
2038
|
+
attributes: [{
|
|
2039
|
+
name: "src",
|
|
2040
|
+
value: current.src
|
|
2041
|
+
}, {
|
|
2042
|
+
name: "alt",
|
|
2043
|
+
value: flattenText(current)
|
|
2044
|
+
}],
|
|
2045
|
+
children: []
|
|
2046
|
+
};
|
|
2047
|
+
case "break": return {
|
|
2048
|
+
category: "element",
|
|
2049
|
+
name: "br",
|
|
2050
|
+
attributes: [],
|
|
2051
|
+
children: []
|
|
2052
|
+
};
|
|
2053
|
+
default: return;
|
|
2157
2054
|
}
|
|
2158
|
-
const projected = values[0];
|
|
2159
|
-
if (projected?.category === "document") return projected;
|
|
2160
|
-
return {
|
|
2161
|
-
category: "document",
|
|
2162
|
-
children: projected === void 0 ? [] : [projected]
|
|
2163
|
-
};
|
|
2164
2055
|
}
|
|
2165
2056
|
/**
|
|
2166
2057
|
* Renders a {@link MarkdownNode} to its canonical markdown source — the inverse
|
|
@@ -2199,7 +2090,7 @@ function renderMarkdown(node) {
|
|
|
2199
2090
|
count: 0,
|
|
2200
2091
|
escaped: "",
|
|
2201
2092
|
escapeBang: false,
|
|
2202
|
-
|
|
2093
|
+
emphasis: 0
|
|
2203
2094
|
}];
|
|
2204
2095
|
const values = [];
|
|
2205
2096
|
while (stack.length > 0) {
|
|
@@ -2207,51 +2098,14 @@ function renderMarkdown(node) {
|
|
|
2207
2098
|
if (frame === void 0) continue;
|
|
2208
2099
|
const current = frame.node;
|
|
2209
2100
|
if (!frame.expanded) {
|
|
2210
|
-
|
|
2211
|
-
if ((frame.depth >= 64 || current.element === "text") && "value" in current && isString(current.value)) for (let index = 0; index < current.value.length; index += 1) {
|
|
2212
|
-
const character = current.value[index] ?? "";
|
|
2213
|
-
const atLineStart = index === 0 || current.value[index - 1] === "\n";
|
|
2214
|
-
if (current.element === "text" && character === "!" && index === current.value.length - 1 && frame.escapeBang) {
|
|
2215
|
-
escaped += "\\!";
|
|
2216
|
-
continue;
|
|
2217
|
-
}
|
|
2218
|
-
if (character === "\\" || character === "*" || character === "_" || character === "`" || character === "[" || character === "]") {
|
|
2219
|
-
escaped += `\\${character}`;
|
|
2220
|
-
continue;
|
|
2221
|
-
}
|
|
2222
|
-
if (atLineStart) {
|
|
2223
|
-
if (character === "#" || character === ">") {
|
|
2224
|
-
escaped += `\\${character}`;
|
|
2225
|
-
continue;
|
|
2226
|
-
}
|
|
2227
|
-
if ((character === "-" || character === "~") && current.value[index + 1] === character && current.value[index + 2] === character) {
|
|
2228
|
-
escaped += `\\${character}`;
|
|
2229
|
-
continue;
|
|
2230
|
-
}
|
|
2231
|
-
if ((character === "-" || character === "+") && (current.value[index + 1] ?? " ") === " ") {
|
|
2232
|
-
escaped += `\\${character}`;
|
|
2233
|
-
continue;
|
|
2234
|
-
}
|
|
2235
|
-
if (/[0-9]/.test(character)) {
|
|
2236
|
-
let end = index;
|
|
2237
|
-
while (end < current.value.length && /[0-9]/.test(current.value[end] ?? "")) end += 1;
|
|
2238
|
-
const marker = current.value[end];
|
|
2239
|
-
if ((marker === "." || marker === ")") && current.value[end + 1] === " ") {
|
|
2240
|
-
escaped += `${current.value.slice(index, end)}\\${marker}`;
|
|
2241
|
-
index = end;
|
|
2242
|
-
continue;
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
escaped += character;
|
|
2247
|
-
}
|
|
2101
|
+
const escaped = (frame.depth >= 64 || current.element === "text") && "value" in current && isString(current.value) ? escapeMarkdownText(current.value, current.element === "text" && frame.escapeBang) : "";
|
|
2248
2102
|
if (frame.depth >= 64) {
|
|
2249
2103
|
values.push(escaped);
|
|
2250
2104
|
continue;
|
|
2251
2105
|
}
|
|
2252
2106
|
const groups = [];
|
|
2253
2107
|
const adjacent = [];
|
|
2254
|
-
|
|
2108
|
+
const depth = frame.depth + (current.element === "table" ? 2 : 1);
|
|
2255
2109
|
switch (current.element) {
|
|
2256
2110
|
case "document":
|
|
2257
2111
|
case "blockquote":
|
|
@@ -2278,7 +2132,7 @@ function renderMarkdown(node) {
|
|
|
2278
2132
|
}
|
|
2279
2133
|
for (const row of current.rows) {
|
|
2280
2134
|
if (row === void 0) continue;
|
|
2281
|
-
for (
|
|
2135
|
+
for (const column of current.header.keys()) {
|
|
2282
2136
|
const cell = row[column];
|
|
2283
2137
|
if (cell !== void 0) {
|
|
2284
2138
|
groups.push(cell);
|
|
@@ -2286,26 +2140,17 @@ function renderMarkdown(node) {
|
|
|
2286
2140
|
}
|
|
2287
2141
|
}
|
|
2288
2142
|
}
|
|
2289
|
-
depth += 1;
|
|
2290
2143
|
}
|
|
2291
2144
|
const children = [];
|
|
2292
2145
|
const escapeBangs = [];
|
|
2293
|
-
for (
|
|
2146
|
+
for (const groupIndex of groups.keys()) {
|
|
2294
2147
|
const group = groups[groupIndex];
|
|
2295
2148
|
if (group === void 0) continue;
|
|
2296
|
-
|
|
2297
|
-
|
|
2149
|
+
const siblings = group.filter((child) => child !== void 0);
|
|
2150
|
+
for (const position of siblings.keys()) {
|
|
2151
|
+
const child = siblings[position];
|
|
2298
2152
|
if (child === void 0) continue;
|
|
2299
|
-
|
|
2300
|
-
if (adjacent[groupIndex] === true) {
|
|
2301
|
-
let nextPosition = position + 1;
|
|
2302
|
-
let next = group[nextPosition];
|
|
2303
|
-
while (next === void 0 && nextPosition < group.length) {
|
|
2304
|
-
nextPosition += 1;
|
|
2305
|
-
next = group[nextPosition];
|
|
2306
|
-
}
|
|
2307
|
-
escapeBang = next?.element === "link";
|
|
2308
|
-
}
|
|
2153
|
+
const escapeBang = (adjacent[groupIndex] === true ? siblings[position + 1] : void 0)?.element === "link";
|
|
2309
2154
|
children.push(child);
|
|
2310
2155
|
escapeBangs.push(escapeBang);
|
|
2311
2156
|
}
|
|
@@ -2316,8 +2161,8 @@ function renderMarkdown(node) {
|
|
|
2316
2161
|
count: children.length,
|
|
2317
2162
|
escaped
|
|
2318
2163
|
});
|
|
2319
|
-
const
|
|
2320
|
-
for (
|
|
2164
|
+
const emphasis = current.element === "emphasis" ? frame.emphasis + 1 : frame.emphasis;
|
|
2165
|
+
for (const index of children.keys().toArray().toReversed()) {
|
|
2321
2166
|
const child = children[index];
|
|
2322
2167
|
if (child !== void 0) stack.push({
|
|
2323
2168
|
node: child,
|
|
@@ -2326,136 +2171,112 @@ function renderMarkdown(node) {
|
|
|
2326
2171
|
count: 0,
|
|
2327
2172
|
escaped: "",
|
|
2328
2173
|
escapeBang: escapeBangs[index] === true && depth < 64,
|
|
2329
|
-
|
|
2174
|
+
emphasis
|
|
2330
2175
|
});
|
|
2331
2176
|
}
|
|
2332
2177
|
continue;
|
|
2333
2178
|
}
|
|
2334
|
-
const
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2179
|
+
const value = renderMarkdownNode(current, frame.count === 0 ? [] : values.splice(values.length - frame.count, frame.count), frame.escaped, frame.emphasis);
|
|
2180
|
+
if (stack.length === 0) return value;
|
|
2181
|
+
values.push(value);
|
|
2182
|
+
}
|
|
2183
|
+
return "";
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Renders one markdown node from its rendered children and escaped literal text.
|
|
2187
|
+
*
|
|
2188
|
+
* @example
|
|
2189
|
+
* ```ts
|
|
2190
|
+
* renderMarkdownNode({ element: 'thematicBreak' }, [], '', 0) // '---'
|
|
2191
|
+
* ```
|
|
2192
|
+
*/
|
|
2193
|
+
function renderMarkdownNode(current, children, literal, emphasis) {
|
|
2194
|
+
switch (current.element) {
|
|
2195
|
+
case "codeBlock":
|
|
2196
|
+
case "codeSpan": {
|
|
2197
|
+
const body = current.element === "codeBlock" ? current.code : current.value;
|
|
2198
|
+
const longest = Iterator.from(body.matchAll(/`+/g)).reduce((maximum, match) => Math.max(maximum, match[0].length), 0);
|
|
2199
|
+
const fence = "`".repeat(Math.max(current.element === "codeBlock" ? 3 : 1, longest + 1));
|
|
2200
|
+
if (current.element === "codeBlock") return `${fence}${current.lang === void 0 ? "" : current.lang}\n${current.code}\n${fence}`;
|
|
2201
|
+
const pad = current.value.startsWith("`") || current.value.endsWith("`") ? " " : "";
|
|
2202
|
+
return `${fence}${pad}${current.value}${pad}${fence}`;
|
|
2203
|
+
}
|
|
2204
|
+
case "break": return " \n";
|
|
2205
|
+
case "document": return children.join("\n\n");
|
|
2206
|
+
case "heading": {
|
|
2207
|
+
const escaped = children.join("").replace(/(^|[^\\])(#+)$/, (_match, before, hashes) => {
|
|
2208
|
+
return `${before}\\${hashes[0] ?? ""}${hashes.slice(1)}`;
|
|
2209
|
+
});
|
|
2210
|
+
return `${"#".repeat(current.level)} ${escaped}`;
|
|
2211
|
+
}
|
|
2212
|
+
case "paragraph": return children.join("");
|
|
2213
|
+
case "thematicBreak": return "---";
|
|
2214
|
+
case "blockquote": return children.join("\n\n").split("\n").map((line) => line === "" ? ">" : `> ${line}`).join("\n");
|
|
2215
|
+
case "list": {
|
|
2216
|
+
const items = [];
|
|
2217
|
+
children.entries().reduce((ordinal, [position, body]) => {
|
|
2218
|
+
const marker = current.ordered ? `${ordinal}. ` : "- ";
|
|
2219
|
+
const pad = " ".repeat(marker.length);
|
|
2220
|
+
if (current.items[position]?.children[0]?.element === "table") {
|
|
2221
|
+
items.push(`${marker}\n${body.split("\n").map((line) => pad + line).join("\n")}`);
|
|
2222
|
+
return ordinal + 1;
|
|
2350
2223
|
}
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
});
|
|
2365
|
-
value = `${"#".repeat(current.level)} ${escaped}`;
|
|
2366
|
-
break;
|
|
2367
|
-
}
|
|
2368
|
-
case "paragraph":
|
|
2369
|
-
value = children.join("");
|
|
2370
|
-
break;
|
|
2371
|
-
case "thematicBreak":
|
|
2372
|
-
value = "---";
|
|
2373
|
-
break;
|
|
2374
|
-
case "blockquote":
|
|
2375
|
-
value = children.join("\n\n").split("\n").map((line) => line === "" ? ">" : `> ${line}`).join("\n");
|
|
2376
|
-
break;
|
|
2377
|
-
case "list": {
|
|
2378
|
-
const items = [];
|
|
2379
|
-
let ordinal = current.start;
|
|
2380
|
-
for (const [position, body] of children.entries()) {
|
|
2381
|
-
const marker = current.ordered ? `${ordinal}. ` : "- ";
|
|
2382
|
-
ordinal += 1;
|
|
2383
|
-
const pad = " ".repeat(marker.length);
|
|
2384
|
-
if (current.items[position]?.children[0]?.element === "table") {
|
|
2385
|
-
items.push(`${marker}\n${body.split("\n").map((line) => pad + line).join("\n")}`);
|
|
2386
|
-
continue;
|
|
2387
|
-
}
|
|
2388
|
-
items.push(body.split("\n").map((line, index) => index === 0 ? marker + line : line === "" ? "" : pad + line).join("\n"));
|
|
2224
|
+
items.push(body.split("\n").map((line, index) => index === 0 ? marker + line : line === "" ? "" : pad + line).join("\n"));
|
|
2225
|
+
return ordinal + 1;
|
|
2226
|
+
}, current.start);
|
|
2227
|
+
return items.join("\n");
|
|
2228
|
+
}
|
|
2229
|
+
case "listItem": return children.join("\n\n");
|
|
2230
|
+
case "table": {
|
|
2231
|
+
const outputs = children.values();
|
|
2232
|
+
const header = [];
|
|
2233
|
+
for (const cell of current.header) {
|
|
2234
|
+
if (cell === void 0) {
|
|
2235
|
+
header.push("");
|
|
2236
|
+
continue;
|
|
2389
2237
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2238
|
+
const count = Iterator.from(cell).filter((child) => child !== void 0).reduce((sum) => sum + 1, 0);
|
|
2239
|
+
header.push(outputs.take(count).toArray().join("").replace(/\|/g, "\\|"));
|
|
2392
2240
|
}
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2241
|
+
const delimiter = current.align.map((align) => {
|
|
2242
|
+
if (align === null) return "---";
|
|
2243
|
+
if (align === "left") return ":---";
|
|
2244
|
+
if (align === "right") return "---:";
|
|
2245
|
+
if (align === "center") return ":---:";
|
|
2246
|
+
return "---";
|
|
2247
|
+
});
|
|
2248
|
+
const rows = [];
|
|
2249
|
+
for (const row of current.rows) {
|
|
2250
|
+
const cells = [];
|
|
2251
|
+
for (const column of current.header.keys()) {
|
|
2252
|
+
const cell = row[column];
|
|
2400
2253
|
if (cell === void 0) {
|
|
2401
|
-
|
|
2254
|
+
cells.push("");
|
|
2402
2255
|
continue;
|
|
2403
2256
|
}
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
header.push(children.slice(offset, offset + count).join("").replace(/\|/g, "\\|"));
|
|
2407
|
-
offset += count;
|
|
2408
|
-
}
|
|
2409
|
-
const delimiter = current.align.map((align) => {
|
|
2410
|
-
if (align === null) return "---";
|
|
2411
|
-
if (align === "left") return ":---";
|
|
2412
|
-
if (align === "right") return "---:";
|
|
2413
|
-
if (align === "center") return ":---:";
|
|
2414
|
-
return "---";
|
|
2415
|
-
});
|
|
2416
|
-
const rows = [];
|
|
2417
|
-
for (const row of current.rows) {
|
|
2418
|
-
const cells = [];
|
|
2419
|
-
for (let column = 0; column < current.header.length; column += 1) {
|
|
2420
|
-
const cell = row[column];
|
|
2421
|
-
if (cell === void 0) {
|
|
2422
|
-
cells.push("");
|
|
2423
|
-
continue;
|
|
2424
|
-
}
|
|
2425
|
-
let count = 0;
|
|
2426
|
-
for (const child of cell) if (child !== void 0) count += 1;
|
|
2427
|
-
cells.push(children.slice(offset, offset + count).join("").replace(/\|/g, "\\|"));
|
|
2428
|
-
offset += count;
|
|
2429
|
-
}
|
|
2430
|
-
rows.push(`| ${cells.join(" | ")} |`);
|
|
2257
|
+
const count = Iterator.from(cell).filter((child) => child !== void 0).reduce((sum) => sum + 1, 0);
|
|
2258
|
+
cells.push(outputs.take(count).toArray().join("").replace(/\|/g, "\\|"));
|
|
2431
2259
|
}
|
|
2432
|
-
|
|
2433
|
-
`| ${header.join(" | ")} |`,
|
|
2434
|
-
`| ${delimiter.join(" | ")} |`,
|
|
2435
|
-
...rows
|
|
2436
|
-
].join("\n");
|
|
2437
|
-
break;
|
|
2438
|
-
}
|
|
2439
|
-
case "text":
|
|
2440
|
-
value = frame.escaped;
|
|
2441
|
-
break;
|
|
2442
|
-
case "emphasis": {
|
|
2443
|
-
const marker = frame.nesting % 2 === 0 ? current.strong ? "**" : "*" : current.strong ? "__" : "_";
|
|
2444
|
-
value = `${marker}${children.join("")}${marker}`;
|
|
2445
|
-
break;
|
|
2260
|
+
rows.push(`| ${cells.join(" | ")} |`);
|
|
2446
2261
|
}
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
}
|
|
2453
|
-
default: value = "";
|
|
2262
|
+
return [
|
|
2263
|
+
`| ${header.join(" | ")} |`,
|
|
2264
|
+
`| ${delimiter.join(" | ")} |`,
|
|
2265
|
+
...rows
|
|
2266
|
+
].join("\n");
|
|
2454
2267
|
}
|
|
2455
|
-
|
|
2456
|
-
|
|
2268
|
+
case "text": return literal;
|
|
2269
|
+
case "emphasis": {
|
|
2270
|
+
const marker = emphasis % 2 === 0 ? current.strong ? "**" : "*" : current.strong ? "__" : "_";
|
|
2271
|
+
return `${marker}${children.join("")}${marker}`;
|
|
2272
|
+
}
|
|
2273
|
+
case "link":
|
|
2274
|
+
case "image": {
|
|
2275
|
+
const escaped = (current.element === "link" ? current.href : current.src).replace(/[\\()]/g, (character) => `\\${character}`);
|
|
2276
|
+
return `${current.element === "image" ? "!" : ""}[${children.join("")}](${escaped})`;
|
|
2277
|
+
}
|
|
2278
|
+
default: return "";
|
|
2457
2279
|
}
|
|
2458
|
-
return "";
|
|
2459
2280
|
}
|
|
2460
2281
|
/**
|
|
2461
2282
|
* Builds an HTML-to-markdown projection with absent fields defaulted from
|
|
@@ -2506,27 +2327,7 @@ function createProjection(parts = {}) {
|
|
|
2506
2327
|
* ```
|
|
2507
2328
|
*/
|
|
2508
2329
|
function trimInlines(nodes) {
|
|
2509
|
-
|
|
2510
|
-
for (const node of nodes) if (node !== void 0) out.push(node);
|
|
2511
|
-
const first = out[0];
|
|
2512
|
-
if (first !== void 0 && first.element === "text") {
|
|
2513
|
-
const value = first.value.replace(/^\s+/, "");
|
|
2514
|
-
if (isEmptyString(value)) out.shift();
|
|
2515
|
-
else out[0] = {
|
|
2516
|
-
element: "text",
|
|
2517
|
-
value
|
|
2518
|
-
};
|
|
2519
|
-
}
|
|
2520
|
-
const last = out[out.length - 1];
|
|
2521
|
-
if (last !== void 0 && last.element === "text") {
|
|
2522
|
-
const value = last.value.replace(/\s+$/, "");
|
|
2523
|
-
if (isEmptyString(value)) out.pop();
|
|
2524
|
-
else out[out.length - 1] = {
|
|
2525
|
-
element: "text",
|
|
2526
|
-
value
|
|
2527
|
-
};
|
|
2528
|
-
}
|
|
2529
|
-
return out;
|
|
2330
|
+
return trimTrailing(trimLeading(nodes));
|
|
2530
2331
|
}
|
|
2531
2332
|
/**
|
|
2532
2333
|
* Reduces an inline run to the shape markdown can actually write back: adjacent text
|
|
@@ -2553,45 +2354,18 @@ function trimInlines(nodes) {
|
|
|
2553
2354
|
* ```
|
|
2554
2355
|
*/
|
|
2555
2356
|
function normalizeInlines(nodes, breaks) {
|
|
2556
|
-
const spent =
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
const previous = out[out.length - 1];
|
|
2569
|
-
if (node.element === "text") {
|
|
2570
|
-
const value = previous?.element === "break" ? node.value.replace(/^\s+/, "") : node.value;
|
|
2571
|
-
if (!isEmptyString(value)) out.push({
|
|
2572
|
-
element: "text",
|
|
2573
|
-
value
|
|
2574
|
-
});
|
|
2575
|
-
continue;
|
|
2576
|
-
}
|
|
2577
|
-
if (node.element === "break") {
|
|
2578
|
-
if (previous === void 0 || previous.element === "break") continue;
|
|
2579
|
-
if (previous.element === "text") {
|
|
2580
|
-
const value = previous.value.replace(/\s+$/, "");
|
|
2581
|
-
if (isEmptyString(value)) out.pop();
|
|
2582
|
-
else out[out.length - 1] = {
|
|
2583
|
-
element: "text",
|
|
2584
|
-
value
|
|
2585
|
-
};
|
|
2586
|
-
}
|
|
2587
|
-
if (out.length === 0) continue;
|
|
2588
|
-
out.push(node);
|
|
2589
|
-
continue;
|
|
2590
|
-
}
|
|
2591
|
-
out.push(node);
|
|
2592
|
-
}
|
|
2593
|
-
while (out.length > 0 && out[out.length - 1]?.element === "break") out.pop();
|
|
2594
|
-
return coalesceText(out);
|
|
2357
|
+
const spent = coalesceText(Iterator.from(nodes).filter((node) => node !== void 0).map((node) => node.element === "break" && !breaks ? {
|
|
2358
|
+
element: "text",
|
|
2359
|
+
value: " "
|
|
2360
|
+
} : node).toArray());
|
|
2361
|
+
const starts = [0, ...spent.entries().flatMap(([index, node]) => node.element === "break" ? [index + 1] : [])];
|
|
2362
|
+
const segments = starts.map((start, index) => {
|
|
2363
|
+
const end = starts[index + 1];
|
|
2364
|
+
const segment = spent.slice(start, end === void 0 ? spent.length : end - 1).filter((node) => node.element !== "text" || !isEmptyString(node.value));
|
|
2365
|
+
return index < starts.length - 1 ? trimTrailing(segment) : segment;
|
|
2366
|
+
});
|
|
2367
|
+
const first = segments.findIndex((segment) => segment.length > 0);
|
|
2368
|
+
return coalesceText(segments.map((segment, index) => index > first ? trimLeading(segment) : segment).filter((segment) => segment.length > 0).flatMap((segment, index) => index === 0 ? segment : [{ element: "break" }, ...segment]));
|
|
2595
2369
|
}
|
|
2596
2370
|
/**
|
|
2597
2371
|
* Combines the projections of one node's children into the projection of that node —
|
|
@@ -2621,63 +2395,72 @@ function normalizeInlines(nodes, breaks) {
|
|
|
2621
2395
|
*/
|
|
2622
2396
|
function mergeProjections(children) {
|
|
2623
2397
|
const blocks = [];
|
|
2624
|
-
const
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2398
|
+
const text = [];
|
|
2399
|
+
const [tail, remainingCells, remainingRows] = Iterator.from(children).reduce(([pending, cells, rows], child) => {
|
|
2400
|
+
if (child === void 0) return [
|
|
2401
|
+
pending,
|
|
2402
|
+
cells,
|
|
2403
|
+
rows
|
|
2404
|
+
];
|
|
2405
|
+
text.push(child.text);
|
|
2631
2406
|
if (isNonEmptyArray(child.blocks)) {
|
|
2632
2407
|
const flushed = trimInlines(normalizeInlines(pending, true));
|
|
2633
2408
|
if (isNonEmptyArray(flushed)) blocks.push({
|
|
2634
2409
|
element: "paragraph",
|
|
2635
2410
|
children: flushed
|
|
2636
2411
|
});
|
|
2637
|
-
pending = [];
|
|
2638
2412
|
for (const row of rows) for (const cell of row) if (cell !== void 0 && isNonEmptyArray(cell.inlines)) blocks.push({
|
|
2639
2413
|
element: "paragraph",
|
|
2640
2414
|
children: cell.inlines
|
|
2641
2415
|
});
|
|
2642
|
-
rows.length = 0;
|
|
2643
2416
|
for (const cell of cells) if (cell !== void 0 && isNonEmptyArray(cell.inlines)) blocks.push({
|
|
2644
2417
|
element: "paragraph",
|
|
2645
2418
|
children: cell.inlines
|
|
2646
2419
|
});
|
|
2647
|
-
cells.length = 0;
|
|
2648
2420
|
for (const block of projectionToBlocks(child)) blocks.push(block);
|
|
2649
|
-
|
|
2421
|
+
return [
|
|
2422
|
+
[],
|
|
2423
|
+
[],
|
|
2424
|
+
[]
|
|
2425
|
+
];
|
|
2650
2426
|
}
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
cells.length = 0;
|
|
2655
|
-
}
|
|
2427
|
+
const rowed = isNonEmptyArray(child.rows);
|
|
2428
|
+
if (rowed) {
|
|
2429
|
+
if (isNonEmptyArray(cells)) rows.push(cells);
|
|
2656
2430
|
for (const row of child.rows) if (row !== void 0) rows.push(row);
|
|
2657
2431
|
}
|
|
2658
|
-
|
|
2432
|
+
const nextCells = rowed ? [] : cells;
|
|
2433
|
+
for (const cell of child.cells) if (cell !== void 0) nextCells.push(cell);
|
|
2659
2434
|
for (const inline of child.inlines) if (inline !== void 0) pending.push(inline);
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2435
|
+
return [
|
|
2436
|
+
pending,
|
|
2437
|
+
nextCells,
|
|
2438
|
+
rows
|
|
2439
|
+
];
|
|
2440
|
+
}, [
|
|
2441
|
+
[],
|
|
2442
|
+
[],
|
|
2443
|
+
[]
|
|
2444
|
+
]);
|
|
2445
|
+
const rowed = isNonEmptyArray(remainingRows) && isNonEmptyArray(remainingCells);
|
|
2446
|
+
const finalRows = rowed ? [...remainingRows, [...remainingCells]] : remainingRows;
|
|
2447
|
+
const finalCells = rowed ? [] : remainingCells;
|
|
2665
2448
|
if (!isNonEmptyArray(blocks)) return createProjection({
|
|
2666
|
-
inlines: coalesceText(
|
|
2667
|
-
text,
|
|
2668
|
-
cells,
|
|
2669
|
-
rows
|
|
2449
|
+
inlines: coalesceText(tail),
|
|
2450
|
+
text: text.join(""),
|
|
2451
|
+
cells: finalCells,
|
|
2452
|
+
rows: finalRows
|
|
2670
2453
|
});
|
|
2671
|
-
const flushed = trimInlines(normalizeInlines(
|
|
2454
|
+
const flushed = trimInlines(normalizeInlines(tail, true));
|
|
2672
2455
|
if (isNonEmptyArray(flushed)) blocks.push({
|
|
2673
2456
|
element: "paragraph",
|
|
2674
2457
|
children: flushed
|
|
2675
2458
|
});
|
|
2676
2459
|
return createProjection({
|
|
2677
2460
|
blocks,
|
|
2678
|
-
text,
|
|
2679
|
-
cells,
|
|
2680
|
-
rows
|
|
2461
|
+
text: text.join(""),
|
|
2462
|
+
cells: finalCells,
|
|
2463
|
+
rows: finalRows
|
|
2681
2464
|
});
|
|
2682
2465
|
}
|
|
2683
2466
|
/**
|
|
@@ -2892,21 +2675,11 @@ function projectHTMLNode(node, children) {
|
|
|
2892
2675
|
});
|
|
2893
2676
|
}
|
|
2894
2677
|
case "pre": {
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
position = index;
|
|
2899
|
-
break;
|
|
2900
|
-
}
|
|
2901
|
-
const source = position === -1 ? void 0 : node.children[position];
|
|
2902
|
-
const projected = position === -1 ? void 0 : children[position];
|
|
2678
|
+
const position = node.children.entries().find(([, child]) => child?.category === "element")?.[0];
|
|
2679
|
+
const source = position === void 0 ? void 0 : node.children[position];
|
|
2680
|
+
const projected = position === void 0 ? void 0 : children[position];
|
|
2903
2681
|
if (source?.category === "element" && source.name === "code" && projected !== void 0) {
|
|
2904
|
-
|
|
2905
|
-
for (const token of (attributeOf(source, "class") ?? "").split(/\s+/)) {
|
|
2906
|
-
if (!token.startsWith("language-") || token.length <= 9 || token.includes("`")) continue;
|
|
2907
|
-
lang = token.slice(9);
|
|
2908
|
-
break;
|
|
2909
|
-
}
|
|
2682
|
+
const lang = (attributeOf(source, "class") ?? "").split(/\s+/).find((token) => token.startsWith("language-") && token.length > 9 && !token.includes("`"))?.slice(9);
|
|
2910
2683
|
return createProjection({
|
|
2911
2684
|
blocks: [{
|
|
2912
2685
|
element: "codeBlock",
|
|
@@ -3018,24 +2791,21 @@ function projectHTMLNode(node, children) {
|
|
|
3018
2791
|
if (source === void 0) continue;
|
|
3019
2792
|
if (source.index >= source.children.length) continue;
|
|
3020
2793
|
const child = source.children[source.index];
|
|
3021
|
-
|
|
3022
|
-
|
|
2794
|
+
sources.push({
|
|
2795
|
+
...source,
|
|
2796
|
+
index: source.index + 1,
|
|
2797
|
+
direct: child?.category === "element" ? child.name === "th" || child.name === "td" : source.direct
|
|
2798
|
+
});
|
|
3023
2799
|
if (child?.category !== "element") continue;
|
|
3024
2800
|
if (child.name === "th" || child.name === "td") {
|
|
3025
2801
|
if (!source.direct) {
|
|
3026
2802
|
headings.push(false);
|
|
3027
2803
|
rowed.push(false);
|
|
3028
2804
|
}
|
|
3029
|
-
source.direct = true;
|
|
3030
2805
|
continue;
|
|
3031
2806
|
}
|
|
3032
|
-
source.direct = false;
|
|
3033
2807
|
if (child.name === "tr") {
|
|
3034
|
-
|
|
3035
|
-
for (const cell of child.children) if (cell?.category === "element" && cell.name === "th") {
|
|
3036
|
-
heading = true;
|
|
3037
|
-
break;
|
|
3038
|
-
}
|
|
2808
|
+
const heading = Iterator.from(child.children).some((cell) => cell?.category === "element" && cell.name === "th");
|
|
3039
2809
|
headings.push(heading);
|
|
3040
2810
|
rowed.push(true);
|
|
3041
2811
|
continue;
|
|
@@ -3046,35 +2816,21 @@ function projectHTMLNode(node, children) {
|
|
|
3046
2816
|
direct: false
|
|
3047
2817
|
});
|
|
3048
2818
|
}
|
|
3049
|
-
|
|
3050
|
-
for (const [index, heading] of headings.entries()) {
|
|
3051
|
-
if (!heading) continue;
|
|
3052
|
-
position = index;
|
|
3053
|
-
break;
|
|
3054
|
-
}
|
|
3055
|
-
if (position === void 0) for (const [index, structural] of rowed.entries()) {
|
|
3056
|
-
if (!structural) continue;
|
|
3057
|
-
position = index;
|
|
3058
|
-
break;
|
|
3059
|
-
}
|
|
2819
|
+
const position = headings.entries().find(([, heading]) => heading)?.[0] ?? rowed.entries().find(([, structural]) => structural)?.[0];
|
|
3060
2820
|
const headerRow = position === void 0 ? void 0 : rows[position];
|
|
3061
2821
|
const columns = headerRow?.length ?? rows[0]?.length ?? 0;
|
|
3062
2822
|
if (columns === 0) return createProjection({
|
|
3063
2823
|
blocks: projectionToBlocks(merged),
|
|
3064
2824
|
text: merged.text
|
|
3065
2825
|
});
|
|
3066
|
-
const
|
|
3067
|
-
const
|
|
3068
|
-
|
|
3069
|
-
const cell = headerRow?.[column];
|
|
3070
|
-
header.push(cell?.inlines ?? []);
|
|
3071
|
-
align.push(cell?.align ?? null);
|
|
3072
|
-
}
|
|
2826
|
+
const headerCells = Array.from({ length: columns }, (_, column) => headerRow?.[column]);
|
|
2827
|
+
const header = headerCells.map((cell) => cell?.inlines ?? []);
|
|
2828
|
+
const align = headerCells.map((cell) => cell?.align ?? null);
|
|
3073
2829
|
const body = [];
|
|
3074
2830
|
for (const [index, row] of rows.entries()) {
|
|
3075
2831
|
if (row === void 0 || index === position) continue;
|
|
3076
2832
|
const cells = [];
|
|
3077
|
-
for (
|
|
2833
|
+
for (const column of header.keys()) cells.push(row[column]?.inlines ?? []);
|
|
3078
2834
|
body.push(cells);
|
|
3079
2835
|
}
|
|
3080
2836
|
return createProjection({
|
|
@@ -3175,32 +2931,8 @@ function* walkNodes(node) {
|
|
|
3175
2931
|
if (frame === void 0) continue;
|
|
3176
2932
|
yield frame.node;
|
|
3177
2933
|
if (frame.depth >= 64) continue;
|
|
3178
|
-
const children =
|
|
3179
|
-
|
|
3180
|
-
case "document":
|
|
3181
|
-
case "heading":
|
|
3182
|
-
case "paragraph":
|
|
3183
|
-
case "blockquote":
|
|
3184
|
-
case "listItem":
|
|
3185
|
-
case "emphasis":
|
|
3186
|
-
case "link":
|
|
3187
|
-
case "image":
|
|
3188
|
-
for (const child of frame.node.children) if (child !== void 0) children.push(child);
|
|
3189
|
-
break;
|
|
3190
|
-
case "list":
|
|
3191
|
-
for (const child of frame.node.items) if (child !== void 0) children.push(child);
|
|
3192
|
-
break;
|
|
3193
|
-
case "table":
|
|
3194
|
-
for (const cell of frame.node.header) if (cell !== void 0) {
|
|
3195
|
-
for (const child of cell) if (child !== void 0) children.push(child);
|
|
3196
|
-
}
|
|
3197
|
-
for (const row of frame.node.rows) if (row !== void 0) {
|
|
3198
|
-
for (const cell of row) if (cell !== void 0) {
|
|
3199
|
-
for (const child of cell) if (child !== void 0) children.push(child);
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
2934
|
+
const children = listChildren(frame.node);
|
|
2935
|
+
for (const index of children.keys().toArray().toReversed()) {
|
|
3204
2936
|
const child = children[index];
|
|
3205
2937
|
if (child !== void 0) stack.push({
|
|
3206
2938
|
node: child,
|
|
@@ -3282,7 +3014,7 @@ function foldNode(node, handlers, depth) {
|
|
|
3282
3014
|
expanded: true,
|
|
3283
3015
|
count: children.length
|
|
3284
3016
|
});
|
|
3285
|
-
for (
|
|
3017
|
+
for (const index of children.keys().toArray().toReversed()) {
|
|
3286
3018
|
const child = children[index];
|
|
3287
3019
|
if (child !== void 0) stack.push({
|
|
3288
3020
|
node: child,
|
|
@@ -3294,71 +3026,37 @@ function foldNode(node, handlers, depth) {
|
|
|
3294
3026
|
continue;
|
|
3295
3027
|
}
|
|
3296
3028
|
const children = frame.count === 0 ? [] : values.splice(values.length - frame.count, frame.count);
|
|
3297
|
-
|
|
3298
|
-
switch (frame.node.element) {
|
|
3299
|
-
case "document":
|
|
3300
|
-
value = handlers.document(frame.node, children);
|
|
3301
|
-
break;
|
|
3302
|
-
case "heading":
|
|
3303
|
-
value = handlers.heading(frame.node, children);
|
|
3304
|
-
break;
|
|
3305
|
-
case "paragraph":
|
|
3306
|
-
value = handlers.paragraph(frame.node, children);
|
|
3307
|
-
break;
|
|
3308
|
-
case "thematicBreak":
|
|
3309
|
-
value = handlers.thematicBreak(frame.node, children);
|
|
3310
|
-
break;
|
|
3311
|
-
case "blockquote":
|
|
3312
|
-
value = handlers.blockquote(frame.node, children);
|
|
3313
|
-
break;
|
|
3314
|
-
case "codeBlock":
|
|
3315
|
-
value = handlers.codeBlock(frame.node, children);
|
|
3316
|
-
break;
|
|
3317
|
-
case "list":
|
|
3318
|
-
value = handlers.list(frame.node, children);
|
|
3319
|
-
break;
|
|
3320
|
-
case "listItem":
|
|
3321
|
-
value = handlers.listItem(frame.node, children);
|
|
3322
|
-
break;
|
|
3323
|
-
case "table":
|
|
3324
|
-
value = handlers.table(frame.node, children);
|
|
3325
|
-
break;
|
|
3326
|
-
case "text":
|
|
3327
|
-
value = handlers.text(frame.node, children);
|
|
3328
|
-
break;
|
|
3329
|
-
case "emphasis":
|
|
3330
|
-
value = handlers.emphasis(frame.node, children);
|
|
3331
|
-
break;
|
|
3332
|
-
case "codeSpan":
|
|
3333
|
-
value = handlers.codeSpan(frame.node, children);
|
|
3334
|
-
break;
|
|
3335
|
-
case "break":
|
|
3336
|
-
value = handlers.break(frame.node, children);
|
|
3337
|
-
break;
|
|
3338
|
-
case "link":
|
|
3339
|
-
value = handlers.link(frame.node, children);
|
|
3340
|
-
break;
|
|
3341
|
-
case "image": value = handlers.image(frame.node, children);
|
|
3342
|
-
}
|
|
3029
|
+
const value = foldMarkdownNode(frame.node, handlers, children);
|
|
3343
3030
|
if (stack.length === 0) return value;
|
|
3344
3031
|
values.push(value);
|
|
3345
3032
|
}
|
|
3033
|
+
return foldMarkdownNode(node, handlers, []);
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Dispatches one markdown node and its folded children to the matching handler.
|
|
3037
|
+
*
|
|
3038
|
+
* @example
|
|
3039
|
+
* ```ts
|
|
3040
|
+
* foldMarkdownNode({ element: 'text', value: 'hi' }, countHandlers, []) // 1
|
|
3041
|
+
* ```
|
|
3042
|
+
*/
|
|
3043
|
+
function foldMarkdownNode(node, handlers, children) {
|
|
3346
3044
|
switch (node.element) {
|
|
3347
|
-
case "document": return handlers.document(node,
|
|
3348
|
-
case "heading": return handlers.heading(node,
|
|
3349
|
-
case "paragraph": return handlers.paragraph(node,
|
|
3350
|
-
case "thematicBreak": return handlers.thematicBreak(node,
|
|
3351
|
-
case "blockquote": return handlers.blockquote(node,
|
|
3352
|
-
case "codeBlock": return handlers.codeBlock(node,
|
|
3353
|
-
case "list": return handlers.list(node,
|
|
3354
|
-
case "listItem": return handlers.listItem(node,
|
|
3355
|
-
case "table": return handlers.table(node,
|
|
3356
|
-
case "text": return handlers.text(node,
|
|
3357
|
-
case "emphasis": return handlers.emphasis(node,
|
|
3358
|
-
case "codeSpan": return handlers.codeSpan(node,
|
|
3359
|
-
case "break": return handlers.break(node,
|
|
3360
|
-
case "link": return handlers.link(node,
|
|
3361
|
-
case "image": return handlers.image(node,
|
|
3045
|
+
case "document": return handlers.document(node, children);
|
|
3046
|
+
case "heading": return handlers.heading(node, children);
|
|
3047
|
+
case "paragraph": return handlers.paragraph(node, children);
|
|
3048
|
+
case "thematicBreak": return handlers.thematicBreak(node, children);
|
|
3049
|
+
case "blockquote": return handlers.blockquote(node, children);
|
|
3050
|
+
case "codeBlock": return handlers.codeBlock(node, children);
|
|
3051
|
+
case "list": return handlers.list(node, children);
|
|
3052
|
+
case "listItem": return handlers.listItem(node, children);
|
|
3053
|
+
case "table": return handlers.table(node, children);
|
|
3054
|
+
case "text": return handlers.text(node, children);
|
|
3055
|
+
case "emphasis": return handlers.emphasis(node, children);
|
|
3056
|
+
case "codeSpan": return handlers.codeSpan(node, children);
|
|
3057
|
+
case "break": return handlers.break(node, children);
|
|
3058
|
+
case "link": return handlers.link(node, children);
|
|
3059
|
+
case "image": return handlers.image(node, children);
|
|
3362
3060
|
}
|
|
3363
3061
|
}
|
|
3364
3062
|
/**
|
|
@@ -3402,7 +3100,7 @@ function rewriteDocument(document, rewrite) {
|
|
|
3402
3100
|
count: 0
|
|
3403
3101
|
}];
|
|
3404
3102
|
const values = [];
|
|
3405
|
-
const derivations =
|
|
3103
|
+
const derivations = [];
|
|
3406
3104
|
while (stack.length > 0) {
|
|
3407
3105
|
const frame = stack.pop();
|
|
3408
3106
|
if (frame === void 0) continue;
|
|
@@ -3443,7 +3141,7 @@ function rewriteDocument(document, rewrite) {
|
|
|
3443
3141
|
count: children.length
|
|
3444
3142
|
});
|
|
3445
3143
|
const depth = current.element === "document" ? 0 : frame.depth + 1;
|
|
3446
|
-
for (
|
|
3144
|
+
for (const index of children.keys().toArray().toReversed()) {
|
|
3447
3145
|
const child = children[index];
|
|
3448
3146
|
if (child !== void 0) stack.push({
|
|
3449
3147
|
node: child,
|
|
@@ -3454,197 +3152,138 @@ function rewriteDocument(document, rewrite) {
|
|
|
3454
3152
|
}
|
|
3455
3153
|
continue;
|
|
3456
3154
|
}
|
|
3457
|
-
const
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
let offset = 0;
|
|
3464
|
-
for (const block of current.children) {
|
|
3465
|
-
if (block === void 0) continue;
|
|
3466
|
-
const child = children[offset];
|
|
3467
|
-
const accepted = child !== void 0 && isBlockNode(child) ? child : block;
|
|
3468
|
-
blocks.push(accepted);
|
|
3469
|
-
if (accepted !== block) changed = true;
|
|
3470
|
-
offset += 1;
|
|
3471
|
-
}
|
|
3472
|
-
if (changed) rebuilt = {
|
|
3473
|
-
element: "document",
|
|
3474
|
-
children: blocks
|
|
3475
|
-
};
|
|
3476
|
-
break;
|
|
3477
|
-
}
|
|
3478
|
-
case "heading":
|
|
3479
|
-
case "paragraph": {
|
|
3480
|
-
const inlines = [];
|
|
3481
|
-
let offset = 0;
|
|
3482
|
-
for (const inline of current.children) {
|
|
3483
|
-
if (inline === void 0) continue;
|
|
3484
|
-
const child = children[offset];
|
|
3485
|
-
const accepted = child !== void 0 && isInlineNode(child) ? child : inline;
|
|
3486
|
-
inlines.push(accepted);
|
|
3487
|
-
if (accepted !== inline) changed = true;
|
|
3488
|
-
offset += 1;
|
|
3489
|
-
}
|
|
3490
|
-
if (changed) rebuilt = {
|
|
3491
|
-
...current,
|
|
3492
|
-
children: inlines
|
|
3493
|
-
};
|
|
3494
|
-
break;
|
|
3495
|
-
}
|
|
3496
|
-
case "blockquote": {
|
|
3497
|
-
const blocks = [];
|
|
3498
|
-
let offset = 0;
|
|
3499
|
-
for (const block of current.children) {
|
|
3500
|
-
if (block === void 0) continue;
|
|
3501
|
-
const child = children[offset];
|
|
3502
|
-
const accepted = child !== void 0 && isBlockNode(child) ? child : block;
|
|
3503
|
-
blocks.push(accepted);
|
|
3504
|
-
if (accepted !== block) changed = true;
|
|
3505
|
-
offset += 1;
|
|
3506
|
-
}
|
|
3507
|
-
if (changed) rebuilt = {
|
|
3508
|
-
...current,
|
|
3509
|
-
children: blocks
|
|
3510
|
-
};
|
|
3511
|
-
break;
|
|
3512
|
-
}
|
|
3513
|
-
case "listItem": {
|
|
3514
|
-
const blocks = [];
|
|
3515
|
-
let offset = 0;
|
|
3516
|
-
for (const block of current.children) {
|
|
3517
|
-
if (block === void 0) continue;
|
|
3518
|
-
const child = children[offset];
|
|
3519
|
-
const accepted = child !== void 0 && isBlockNode(child) ? child : block;
|
|
3520
|
-
blocks.push(accepted);
|
|
3521
|
-
if (accepted !== block) changed = true;
|
|
3522
|
-
offset += 1;
|
|
3523
|
-
}
|
|
3524
|
-
if (changed) rebuilt = {
|
|
3525
|
-
element: "listItem",
|
|
3526
|
-
children: blocks
|
|
3527
|
-
};
|
|
3528
|
-
break;
|
|
3529
|
-
}
|
|
3530
|
-
case "emphasis":
|
|
3531
|
-
case "link":
|
|
3532
|
-
case "image": {
|
|
3533
|
-
const inlines = [];
|
|
3534
|
-
let offset = 0;
|
|
3535
|
-
for (const inline of current.children) {
|
|
3536
|
-
if (inline === void 0) continue;
|
|
3537
|
-
const child = children[offset];
|
|
3538
|
-
const accepted = child !== void 0 && isInlineNode(child) ? child : inline;
|
|
3539
|
-
inlines.push(accepted);
|
|
3540
|
-
if (accepted !== inline) changed = true;
|
|
3541
|
-
offset += 1;
|
|
3542
|
-
}
|
|
3543
|
-
if (changed) rebuilt = {
|
|
3544
|
-
...current,
|
|
3545
|
-
children: inlines
|
|
3546
|
-
};
|
|
3547
|
-
break;
|
|
3548
|
-
}
|
|
3549
|
-
case "list": {
|
|
3550
|
-
const items = [];
|
|
3551
|
-
let offset = 0;
|
|
3552
|
-
for (const item of current.items) {
|
|
3553
|
-
if (item === void 0) continue;
|
|
3554
|
-
const child = children[offset];
|
|
3555
|
-
const accepted = child?.element === "listItem" ? child : item;
|
|
3556
|
-
items.push(accepted);
|
|
3557
|
-
if (accepted !== item) changed = true;
|
|
3558
|
-
offset += 1;
|
|
3559
|
-
}
|
|
3560
|
-
if (changed) rebuilt = {
|
|
3561
|
-
...current,
|
|
3562
|
-
items
|
|
3563
|
-
};
|
|
3564
|
-
break;
|
|
3565
|
-
}
|
|
3566
|
-
case "table": {
|
|
3567
|
-
let offset = 0;
|
|
3568
|
-
const header = [];
|
|
3569
|
-
for (const cell of current.header) {
|
|
3570
|
-
if (cell === void 0) continue;
|
|
3571
|
-
const inlines = [];
|
|
3572
|
-
for (const inline of cell) {
|
|
3573
|
-
if (inline === void 0) continue;
|
|
3574
|
-
const child = children[offset];
|
|
3575
|
-
const accepted = child !== void 0 && isInlineNode(child) ? child : inline;
|
|
3576
|
-
inlines.push(accepted);
|
|
3577
|
-
if (accepted !== inline) changed = true;
|
|
3578
|
-
offset += 1;
|
|
3579
|
-
}
|
|
3580
|
-
header.push(inlines);
|
|
3581
|
-
}
|
|
3582
|
-
const rows = [];
|
|
3583
|
-
for (const row of current.rows) {
|
|
3584
|
-
if (row === void 0) continue;
|
|
3585
|
-
const cells = [];
|
|
3586
|
-
for (const cell of row) {
|
|
3587
|
-
if (cell === void 0) continue;
|
|
3588
|
-
const inlines = [];
|
|
3589
|
-
for (const inline of cell) {
|
|
3590
|
-
if (inline === void 0) continue;
|
|
3591
|
-
const child = children[offset];
|
|
3592
|
-
const accepted = child !== void 0 && isInlineNode(child) ? child : inline;
|
|
3593
|
-
inlines.push(accepted);
|
|
3594
|
-
if (accepted !== inline) changed = true;
|
|
3595
|
-
offset += 1;
|
|
3596
|
-
}
|
|
3597
|
-
cells.push(inlines);
|
|
3598
|
-
}
|
|
3599
|
-
rows.push(cells);
|
|
3600
|
-
}
|
|
3601
|
-
if (changed) rebuilt = {
|
|
3602
|
-
...current,
|
|
3603
|
-
header,
|
|
3604
|
-
rows
|
|
3605
|
-
};
|
|
3606
|
-
break;
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
|
-
if (rebuilt !== current) derivations.set(rebuilt, current);
|
|
3155
|
+
const rebuilt = rebuildMarkdownNode(current, frame.count === 0 ? [] : values.splice(values.length - frame.count, frame.count));
|
|
3156
|
+
if (rebuilt !== current) derivations.push([
|
|
3157
|
+
rebuilt,
|
|
3158
|
+
current,
|
|
3159
|
+
true
|
|
3160
|
+
]);
|
|
3610
3161
|
if (current.element === "document") {
|
|
3611
3162
|
const result = rebuilt.element === "document" ? rebuilt : current;
|
|
3612
3163
|
const output = new Set(walkNodes(result));
|
|
3613
|
-
|
|
3614
|
-
for (const [node, source] of derivations) if (output.has(node)) retained.set(node, source);
|
|
3615
|
-
return [result, retained];
|
|
3616
|
-
}
|
|
3617
|
-
const result = rewrite(rebuilt);
|
|
3618
|
-
let accepted = rebuilt;
|
|
3619
|
-
switch (current.element) {
|
|
3620
|
-
case "text":
|
|
3621
|
-
case "emphasis":
|
|
3622
|
-
case "codeSpan":
|
|
3623
|
-
case "break":
|
|
3624
|
-
case "link":
|
|
3625
|
-
case "image":
|
|
3626
|
-
if (isInlineNode(result)) accepted = result;
|
|
3627
|
-
break;
|
|
3628
|
-
case "heading":
|
|
3629
|
-
case "paragraph":
|
|
3630
|
-
case "list":
|
|
3631
|
-
case "table":
|
|
3632
|
-
case "codeBlock":
|
|
3633
|
-
case "blockquote":
|
|
3634
|
-
case "thematicBreak":
|
|
3635
|
-
if (isBlockNode(result)) accepted = result;
|
|
3636
|
-
break;
|
|
3637
|
-
case "listItem": if (result.element === "listItem") accepted = result;
|
|
3638
|
-
}
|
|
3639
|
-
if (accepted !== rebuilt && accepted !== current) {
|
|
3640
|
-
if (derivations.has(accepted) && derivations.get(accepted) !== current) derivations.set(accepted, void 0);
|
|
3641
|
-
else derivations.set(accepted, current);
|
|
3164
|
+
return [result, new Map(Map.groupBy(derivations, ([node]) => node).entries().filter(([node]) => output.has(node)).map(([node, steps]) => [node, steps.reduce((source, [, input, direct], index) => index === 0 || direct || source === input ? input : void 0, void 0)]))];
|
|
3642
3165
|
}
|
|
3166
|
+
const accepted = acceptMarkdownNode(current, rebuilt, rewrite(rebuilt));
|
|
3167
|
+
if (accepted !== rebuilt && accepted !== current) derivations.push([
|
|
3168
|
+
accepted,
|
|
3169
|
+
current,
|
|
3170
|
+
false
|
|
3171
|
+
]);
|
|
3643
3172
|
values.push(accepted);
|
|
3644
3173
|
}
|
|
3645
3174
|
return [document, /* @__PURE__ */ new Map()];
|
|
3646
3175
|
}
|
|
3647
3176
|
/**
|
|
3177
|
+
* Rebuilds one markdown node when accepted child replacements change its identity.
|
|
3178
|
+
*
|
|
3179
|
+
* @example
|
|
3180
|
+
* ```ts
|
|
3181
|
+
* const original: ParagraphNode = { element: 'paragraph', children: [] }
|
|
3182
|
+
* rebuildMarkdownNode(original, []) === original // true
|
|
3183
|
+
* ```
|
|
3184
|
+
*/
|
|
3185
|
+
function rebuildMarkdownNode(current, children) {
|
|
3186
|
+
const outputs = children.values();
|
|
3187
|
+
switch (current.element) {
|
|
3188
|
+
case "document":
|
|
3189
|
+
case "blockquote":
|
|
3190
|
+
case "listItem": {
|
|
3191
|
+
const pairs = Iterator.from(current.children).filter((child) => child !== void 0).map((block) => {
|
|
3192
|
+
const child = outputs.next().value;
|
|
3193
|
+
return [block, child !== void 0 && isBlockNode(child) ? child : block];
|
|
3194
|
+
}).toArray();
|
|
3195
|
+
if (!pairs.some(([input, output]) => input !== output)) return current;
|
|
3196
|
+
const blocks = pairs.map(([, output]) => output);
|
|
3197
|
+
return current.element === "blockquote" ? {
|
|
3198
|
+
...current,
|
|
3199
|
+
children: blocks
|
|
3200
|
+
} : {
|
|
3201
|
+
element: current.element,
|
|
3202
|
+
children: blocks
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
case "heading":
|
|
3206
|
+
case "paragraph":
|
|
3207
|
+
case "emphasis":
|
|
3208
|
+
case "link":
|
|
3209
|
+
case "image": {
|
|
3210
|
+
const pairs = Iterator.from(current.children).filter((child) => child !== void 0).map((inline) => {
|
|
3211
|
+
const child = outputs.next().value;
|
|
3212
|
+
return [inline, child !== void 0 && isInlineNode(child) ? child : inline];
|
|
3213
|
+
}).toArray();
|
|
3214
|
+
return pairs.some(([input, output]) => input !== output) ? {
|
|
3215
|
+
...current,
|
|
3216
|
+
children: pairs.map(([, output]) => output)
|
|
3217
|
+
} : current;
|
|
3218
|
+
}
|
|
3219
|
+
case "list": {
|
|
3220
|
+
const pairs = Iterator.from(current.items).filter((item) => item !== void 0).map((item) => {
|
|
3221
|
+
const child = outputs.next().value;
|
|
3222
|
+
return [item, child?.element === "listItem" ? child : item];
|
|
3223
|
+
}).toArray();
|
|
3224
|
+
return pairs.some(([input, output]) => input !== output) ? {
|
|
3225
|
+
...current,
|
|
3226
|
+
items: pairs.map(([, output]) => output)
|
|
3227
|
+
} : current;
|
|
3228
|
+
}
|
|
3229
|
+
case "table": {
|
|
3230
|
+
const pairs = [];
|
|
3231
|
+
const header = Iterator.from(current.header).filter((cell) => cell !== void 0).map((cell) => rebuildMarkdownCell(cell, outputs, pairs)).toArray();
|
|
3232
|
+
const rows = Iterator.from(current.rows).filter((row) => row !== void 0).map((row) => Iterator.from(row).filter((cell) => cell !== void 0).map((cell) => rebuildMarkdownCell(cell, outputs, pairs)).toArray()).toArray();
|
|
3233
|
+
return pairs.some(([input, output]) => input !== output) ? {
|
|
3234
|
+
...current,
|
|
3235
|
+
header,
|
|
3236
|
+
rows
|
|
3237
|
+
} : current;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
return current;
|
|
3241
|
+
}
|
|
3242
|
+
/**
|
|
3243
|
+
* Rebuilds a table cell and records each input and accepted output pair.
|
|
3244
|
+
*
|
|
3245
|
+
* @example
|
|
3246
|
+
* ```ts
|
|
3247
|
+
* rebuildMarkdownCell([], [].values(), []) // []
|
|
3248
|
+
* ```
|
|
3249
|
+
*/
|
|
3250
|
+
function rebuildMarkdownCell(cell, outputs, pairs) {
|
|
3251
|
+
return Iterator.from(cell).filter((inline) => inline !== void 0).map((inline) => {
|
|
3252
|
+
const child = outputs.next().value;
|
|
3253
|
+
const accepted = child !== void 0 && isInlineNode(child) ? child : inline;
|
|
3254
|
+
pairs.push([inline, accepted]);
|
|
3255
|
+
return accepted;
|
|
3256
|
+
}).toArray();
|
|
3257
|
+
}
|
|
3258
|
+
/**
|
|
3259
|
+
* Accepts a rewritten node only when it fits the original node slot.
|
|
3260
|
+
*
|
|
3261
|
+
* @example
|
|
3262
|
+
* ```ts
|
|
3263
|
+
* const original: TextNode = { element: 'text', value: 'hi' }
|
|
3264
|
+
* acceptMarkdownNode(original, original, { element: 'thematicBreak' }) === original // true
|
|
3265
|
+
* ```
|
|
3266
|
+
*/
|
|
3267
|
+
function acceptMarkdownNode(current, rebuilt, result) {
|
|
3268
|
+
switch (current.element) {
|
|
3269
|
+
case "text":
|
|
3270
|
+
case "emphasis":
|
|
3271
|
+
case "codeSpan":
|
|
3272
|
+
case "break":
|
|
3273
|
+
case "link":
|
|
3274
|
+
case "image": return isInlineNode(result) ? result : rebuilt;
|
|
3275
|
+
case "heading":
|
|
3276
|
+
case "paragraph":
|
|
3277
|
+
case "list":
|
|
3278
|
+
case "table":
|
|
3279
|
+
case "codeBlock":
|
|
3280
|
+
case "blockquote":
|
|
3281
|
+
case "thematicBreak": return isBlockNode(result) ? result : rebuilt;
|
|
3282
|
+
case "listItem": return result.element === "listItem" ? result : rebuilt;
|
|
3283
|
+
}
|
|
3284
|
+
return rebuilt;
|
|
3285
|
+
}
|
|
3286
|
+
/**
|
|
3648
3287
|
* Concatenates the `value` / `code` content of every descendant text / code-span /
|
|
3649
3288
|
* code-block node under `node`, including image alternative content, in walk order —
|
|
3650
3289
|
* the plain-text projection of an AST (search indexing, word counts, a text-only
|
|
@@ -3671,43 +3310,19 @@ function flattenText(node) {
|
|
|
3671
3310
|
node,
|
|
3672
3311
|
depth: 0
|
|
3673
3312
|
}];
|
|
3674
|
-
|
|
3313
|
+
const parts = [];
|
|
3675
3314
|
while (stack.length > 0) {
|
|
3676
3315
|
const frame = stack.pop();
|
|
3677
3316
|
if (frame === void 0 || frame.depth >= 64) continue;
|
|
3678
|
-
const children = [];
|
|
3679
3317
|
switch (frame.node.element) {
|
|
3680
3318
|
case "text":
|
|
3681
3319
|
case "codeSpan":
|
|
3682
|
-
|
|
3320
|
+
parts.push(frame.node.value);
|
|
3683
3321
|
break;
|
|
3684
|
-
case "codeBlock":
|
|
3685
|
-
value += frame.node.code;
|
|
3686
|
-
break;
|
|
3687
|
-
case "document":
|
|
3688
|
-
case "heading":
|
|
3689
|
-
case "paragraph":
|
|
3690
|
-
case "blockquote":
|
|
3691
|
-
case "listItem":
|
|
3692
|
-
case "emphasis":
|
|
3693
|
-
case "link":
|
|
3694
|
-
case "image":
|
|
3695
|
-
for (const child of frame.node.children) if (child !== void 0) children.push(child);
|
|
3696
|
-
break;
|
|
3697
|
-
case "list":
|
|
3698
|
-
for (const child of frame.node.items) if (child !== void 0) children.push(child);
|
|
3699
|
-
break;
|
|
3700
|
-
case "table":
|
|
3701
|
-
for (const cell of frame.node.header) if (cell !== void 0) {
|
|
3702
|
-
for (const child of cell) if (child !== void 0) children.push(child);
|
|
3703
|
-
}
|
|
3704
|
-
for (const row of frame.node.rows) if (row !== void 0) {
|
|
3705
|
-
for (const cell of row) if (cell !== void 0) {
|
|
3706
|
-
for (const child of cell) if (child !== void 0) children.push(child);
|
|
3707
|
-
}
|
|
3708
|
-
}
|
|
3322
|
+
case "codeBlock": parts.push(frame.node.code);
|
|
3709
3323
|
}
|
|
3710
|
-
|
|
3324
|
+
const children = listChildren(frame.node);
|
|
3325
|
+
for (const index of children.keys().toArray().toReversed()) {
|
|
3711
3326
|
const child = children[index];
|
|
3712
3327
|
if (child !== void 0) stack.push({
|
|
3713
3328
|
node: child,
|
|
@@ -3715,10 +3330,310 @@ function flattenText(node) {
|
|
|
3715
3330
|
});
|
|
3716
3331
|
}
|
|
3717
3332
|
}
|
|
3718
|
-
return
|
|
3333
|
+
return parts.join("");
|
|
3334
|
+
}
|
|
3335
|
+
/**
|
|
3336
|
+
* Yields UTF-16 offsets and characters within a markdown scan window.
|
|
3337
|
+
*
|
|
3338
|
+
* @example
|
|
3339
|
+
* ```ts
|
|
3340
|
+
* Array.from(walkMarkdownCharacters('ab', 0, 2)) // [[0, 'a'], [1, 'b']]
|
|
3341
|
+
* ```
|
|
3342
|
+
*/
|
|
3343
|
+
function* walkMarkdownCharacters(source, from, to) {
|
|
3344
|
+
const positions = Array.prototype.keys.call({ length: Math.max(0, Math.ceil(to - from)) });
|
|
3345
|
+
for (const position of positions) {
|
|
3346
|
+
const index = from + position;
|
|
3347
|
+
yield [index, source[index] ?? ""];
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3351
|
+
* Counts consecutive copies of the character at the start of a scan window.
|
|
3352
|
+
*
|
|
3353
|
+
* @example
|
|
3354
|
+
* ```ts
|
|
3355
|
+
* countMarkdownRun('***x', 0, 4) // 3
|
|
3356
|
+
* ```
|
|
3357
|
+
*/
|
|
3358
|
+
function countMarkdownRun(source, start, to) {
|
|
3359
|
+
if (start >= to || source[start] === void 0) return 0;
|
|
3360
|
+
return /^([\s\S])\1*/.exec(source.slice(start, Math.ceil(to)))?.[0].length ?? 0;
|
|
3361
|
+
}
|
|
3362
|
+
/**
|
|
3363
|
+
* Finds the next defined markdown node at or after an array offset.
|
|
3364
|
+
*
|
|
3365
|
+
* @example
|
|
3366
|
+
* ```ts
|
|
3367
|
+
* findFollowingNode([], 0) // undefined
|
|
3368
|
+
* ```
|
|
3369
|
+
*/
|
|
3370
|
+
function findFollowingNode(nodes, start) {
|
|
3371
|
+
return nodes.keys().drop(start).map((index) => nodes[index]).find((node) => node !== void 0) ?? nodes[Math.max(start, nodes.length)];
|
|
3372
|
+
}
|
|
3373
|
+
/**
|
|
3374
|
+
* Escapes literal text so markdown syntax remains literal when rendered.
|
|
3375
|
+
*
|
|
3376
|
+
* @example
|
|
3377
|
+
* ```ts
|
|
3378
|
+
* escapeMarkdownText('*hi*', false) // '\\*hi\\*'
|
|
3379
|
+
* ```
|
|
3380
|
+
*/
|
|
3381
|
+
function escapeMarkdownText(value, bang) {
|
|
3382
|
+
const parts = [];
|
|
3383
|
+
walkMarkdownCharacters(value, 0, value.length).reduce((next, [index, character]) => {
|
|
3384
|
+
if (index < next) return next;
|
|
3385
|
+
const atLineStart = index === 0 || value[index - 1] === "\n";
|
|
3386
|
+
if (character === "!" && index === value.length - 1 && bang) {
|
|
3387
|
+
parts.push("\\!");
|
|
3388
|
+
return index + 1;
|
|
3389
|
+
}
|
|
3390
|
+
if ("\\*_`[]".includes(character) || atLineStart && (character === "#" || character === ">") || atLineStart && (character === "-" || character === "~") && value[index + 1] === character && value[index + 2] === character || atLineStart && (character === "-" || character === "+") && (value[index + 1] ?? " ") === " ") {
|
|
3391
|
+
parts.push("\\" + character);
|
|
3392
|
+
return index + 1;
|
|
3393
|
+
}
|
|
3394
|
+
if (atLineStart && /[0-9]/.test(character)) {
|
|
3395
|
+
const end = index + (/^[0-9]+/.exec(value.slice(index))?.[0].length ?? 0);
|
|
3396
|
+
const marker = value[end];
|
|
3397
|
+
if ((marker === "." || marker === ")") && value[end + 1] === " ") {
|
|
3398
|
+
parts.push(value.slice(index, end) + "\\" + marker);
|
|
3399
|
+
return end + 1;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
parts.push(character);
|
|
3403
|
+
return index + 1;
|
|
3404
|
+
}, 0);
|
|
3405
|
+
return parts.join("");
|
|
3406
|
+
}
|
|
3407
|
+
/**
|
|
3408
|
+
* Locates a balanced closing delimiter while skipping backslash escapes.
|
|
3409
|
+
*
|
|
3410
|
+
* @example
|
|
3411
|
+
* ```ts
|
|
3412
|
+
* locateMarkdownDelimiter('[a]', 0, 3, '[', ']') // 2
|
|
3413
|
+
* ```
|
|
3414
|
+
*/
|
|
3415
|
+
function locateMarkdownDelimiter(source, start, to, open, close) {
|
|
3416
|
+
const characters = walkMarkdownCharacters(source, start, to);
|
|
3417
|
+
const [, , result] = characters.reduce(([depth, next], [index, character]) => {
|
|
3418
|
+
if (index < next) return [
|
|
3419
|
+
depth,
|
|
3420
|
+
next,
|
|
3421
|
+
void 0
|
|
3422
|
+
];
|
|
3423
|
+
if (character === "\\") return [
|
|
3424
|
+
depth,
|
|
3425
|
+
index + 2,
|
|
3426
|
+
void 0
|
|
3427
|
+
];
|
|
3428
|
+
if (character === open) return [
|
|
3429
|
+
depth + 1,
|
|
3430
|
+
index + 1,
|
|
3431
|
+
void 0
|
|
3432
|
+
];
|
|
3433
|
+
if (character !== close) return [
|
|
3434
|
+
depth,
|
|
3435
|
+
index + 1,
|
|
3436
|
+
void 0
|
|
3437
|
+
];
|
|
3438
|
+
if (depth === 1) {
|
|
3439
|
+
characters.return(void 0);
|
|
3440
|
+
return [
|
|
3441
|
+
0,
|
|
3442
|
+
index + 1,
|
|
3443
|
+
index
|
|
3444
|
+
];
|
|
3445
|
+
}
|
|
3446
|
+
return [
|
|
3447
|
+
depth - 1,
|
|
3448
|
+
index + 1,
|
|
3449
|
+
void 0
|
|
3450
|
+
];
|
|
3451
|
+
}, [
|
|
3452
|
+
0,
|
|
3453
|
+
start,
|
|
3454
|
+
void 0
|
|
3455
|
+
]);
|
|
3456
|
+
return result;
|
|
3457
|
+
}
|
|
3458
|
+
/**
|
|
3459
|
+
* Appends a nonempty text node and records its original source span.
|
|
3460
|
+
*
|
|
3461
|
+
* @example
|
|
3462
|
+
* ```ts
|
|
3463
|
+
* const nodes: InlineNode[] = []
|
|
3464
|
+
* recordMarkdownText({ text: 'a', segments: [] }, 'a', 0, 1, new Map(), nodes)
|
|
3465
|
+
* nodes // [{ element: 'text', value: 'a' }]
|
|
3466
|
+
* ```
|
|
3467
|
+
*/
|
|
3468
|
+
function recordMarkdownText(source, value, start, end, spans, nodes) {
|
|
3469
|
+
if (value.length === 0) return;
|
|
3470
|
+
const node = {
|
|
3471
|
+
element: "text",
|
|
3472
|
+
value
|
|
3473
|
+
};
|
|
3474
|
+
const span = projectSpan(source, start, end);
|
|
3475
|
+
if (span !== void 0) spans.set(node, span);
|
|
3476
|
+
nodes.push(node);
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* Scans one inline construct and records the spans of its nested children.
|
|
3480
|
+
*
|
|
3481
|
+
* @example
|
|
3482
|
+
* ```ts
|
|
3483
|
+
* scanMarkdownInline({ text: '`x`', segments: [] }, 0, 3, new Map(), 0)?.end // 3
|
|
3484
|
+
* ```
|
|
3485
|
+
*/
|
|
3486
|
+
function scanMarkdownInline(source, index, to, spans, depth) {
|
|
3487
|
+
const character = source.text[index] ?? "";
|
|
3488
|
+
if (character === " ") {
|
|
3489
|
+
const end = index + countMarkdownRun(source.text, index, to);
|
|
3490
|
+
if (end - index >= 2 && source.text[end] === "\n") return {
|
|
3491
|
+
node: { element: "break" },
|
|
3492
|
+
end: end + 1
|
|
3493
|
+
};
|
|
3494
|
+
}
|
|
3495
|
+
if (character === "`") {
|
|
3496
|
+
const span = scanCode(source.text, index, to);
|
|
3497
|
+
if (span) return {
|
|
3498
|
+
node: {
|
|
3499
|
+
element: "codeSpan",
|
|
3500
|
+
value: span.value
|
|
3501
|
+
},
|
|
3502
|
+
end: span.end
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
if (character === "!" && source.text[index + 1] === "[") {
|
|
3506
|
+
const link = locateLink(source.text, index + 1, to);
|
|
3507
|
+
if (link !== void 0) return {
|
|
3508
|
+
node: {
|
|
3509
|
+
element: "image",
|
|
3510
|
+
src: unescapeText(source.text.slice(link.close + 2, link.end - 1).trim()),
|
|
3511
|
+
children: coalesceText(scanInlineSource(source, index + 2, link.close, spans, depth + 1), spans)
|
|
3512
|
+
},
|
|
3513
|
+
end: link.end
|
|
3514
|
+
};
|
|
3515
|
+
}
|
|
3516
|
+
if (character === "[") {
|
|
3517
|
+
const link = locateLink(source.text, index, to);
|
|
3518
|
+
if (link !== void 0) return {
|
|
3519
|
+
node: {
|
|
3520
|
+
element: "link",
|
|
3521
|
+
href: unescapeText(source.text.slice(link.close + 2, link.end - 1).trim()),
|
|
3522
|
+
children: coalesceText(scanInlineSource(source, index + 1, link.close, spans, depth + 1), spans)
|
|
3523
|
+
},
|
|
3524
|
+
end: link.end
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
if (character === "*" || character === "_") {
|
|
3528
|
+
const emphasis = locateEmphasis(source.text, index, to);
|
|
3529
|
+
if (emphasis !== void 0) return {
|
|
3530
|
+
node: {
|
|
3531
|
+
element: "emphasis",
|
|
3532
|
+
strong: emphasis.strong,
|
|
3533
|
+
children: coalesceText(scanInlineSource(source, emphasis.open, emphasis.close, spans, depth + 1), spans)
|
|
3534
|
+
},
|
|
3535
|
+
end: emphasis.end
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
/**
|
|
3540
|
+
* Yields line offsets through a closable markdown scan iterator.
|
|
3541
|
+
*
|
|
3542
|
+
* @example
|
|
3543
|
+
* ```ts
|
|
3544
|
+
* Array.from(walkMarkdownLines(splitLines('a\nb'), 1)) // [1]
|
|
3545
|
+
* ```
|
|
3546
|
+
*/
|
|
3547
|
+
function* walkMarkdownLines(lines, start) {
|
|
3548
|
+
const positions = Array.prototype.keys.call({ get length() {
|
|
3549
|
+
return Math.max(0, Math.ceil(lines.length - start));
|
|
3550
|
+
} });
|
|
3551
|
+
for (const position of positions) yield start + position;
|
|
3552
|
+
}
|
|
3553
|
+
/**
|
|
3554
|
+
* Splits inline nodes into adjacent text runs and singleton non-text runs.
|
|
3555
|
+
*
|
|
3556
|
+
* @param nodes - The inline nodes in source order
|
|
3557
|
+
* @returns The runs with their original node identities
|
|
3558
|
+
*
|
|
3559
|
+
* @example
|
|
3560
|
+
* ```ts
|
|
3561
|
+
* splitTextRuns([{ element: 'text', value: 'a' }]).length // 1
|
|
3562
|
+
* ```
|
|
3563
|
+
*/
|
|
3564
|
+
function splitTextRuns(nodes) {
|
|
3565
|
+
const starts = nodes.entries().flatMap(([index, node]) => index === 0 || node.element !== "text" || nodes[index - 1]?.element !== "text" ? [index] : []).toArray();
|
|
3566
|
+
return starts.map((start, index) => nodes.slice(start, starts[index + 1]));
|
|
3567
|
+
}
|
|
3568
|
+
/**
|
|
3569
|
+
* Lists the direct children of a markdown node in traversal order.
|
|
3570
|
+
*
|
|
3571
|
+
* @param node - The node whose direct children to list
|
|
3572
|
+
* @returns The children, with table cells flattened in header then body order
|
|
3573
|
+
*
|
|
3574
|
+
* @example
|
|
3575
|
+
* ```ts
|
|
3576
|
+
* listChildren({ element: 'document', children: [] }) // []
|
|
3577
|
+
* ```
|
|
3578
|
+
*/
|
|
3579
|
+
function listChildren(node) {
|
|
3580
|
+
switch (node.element) {
|
|
3581
|
+
case "document":
|
|
3582
|
+
case "heading":
|
|
3583
|
+
case "paragraph":
|
|
3584
|
+
case "blockquote":
|
|
3585
|
+
case "listItem":
|
|
3586
|
+
case "emphasis":
|
|
3587
|
+
case "link":
|
|
3588
|
+
case "image": return Iterator.from(node.children).filter((child) => child !== void 0).toArray();
|
|
3589
|
+
case "list": return Iterator.from(node.items).filter((child) => child !== void 0).toArray();
|
|
3590
|
+
case "table": return [...Iterator.from(node.header).filter((cell) => cell !== void 0).flatMap((cell) => Iterator.from(cell).filter((child) => child !== void 0)), ...Iterator.from(node.rows).filter((row) => row !== void 0).flatMap((row) => Iterator.from(row).filter((cell) => cell !== void 0)).flatMap((cell) => Iterator.from(cell).filter((child) => child !== void 0))];
|
|
3591
|
+
default: return [];
|
|
3592
|
+
}
|
|
3719
3593
|
}
|
|
3720
|
-
|
|
3721
|
-
|
|
3594
|
+
/**
|
|
3595
|
+
* Trims leading whitespace when an inline run starts with a text node.
|
|
3596
|
+
*
|
|
3597
|
+
* @param nodes - The inline run to trim
|
|
3598
|
+
* @returns The run with its leading text trimmed or removed when empty
|
|
3599
|
+
*
|
|
3600
|
+
* @example
|
|
3601
|
+
* ```ts
|
|
3602
|
+
* trimLeading([{ element: 'text', value: ' a ' }]) // [{ element: 'text', value: 'a ' }]
|
|
3603
|
+
* ```
|
|
3604
|
+
*/
|
|
3605
|
+
function trimLeading(nodes) {
|
|
3606
|
+
const present = Iterator.from(nodes).filter((node) => node !== void 0).toArray();
|
|
3607
|
+
const first = present[0];
|
|
3608
|
+
if (first?.element !== "text") return present;
|
|
3609
|
+
const value = first.value.replace(/^\s+/, "");
|
|
3610
|
+
return isEmptyString(value) ? present.slice(1) : [{
|
|
3611
|
+
element: "text",
|
|
3612
|
+
value
|
|
3613
|
+
}, ...present.slice(1)];
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* Trims trailing whitespace when an inline run ends with a text node.
|
|
3617
|
+
*
|
|
3618
|
+
* @param nodes - The inline run to trim
|
|
3619
|
+
* @returns The run with its trailing text trimmed or removed when empty
|
|
3620
|
+
*
|
|
3621
|
+
* @example
|
|
3622
|
+
* ```ts
|
|
3623
|
+
* trimTrailing([{ element: 'text', value: ' a ' }]) // [{ element: 'text', value: ' a' }]
|
|
3624
|
+
* ```
|
|
3625
|
+
*/
|
|
3626
|
+
function trimTrailing(nodes) {
|
|
3627
|
+
const present = Iterator.from(nodes).filter((node) => node !== void 0).toArray();
|
|
3628
|
+
const last = present.at(-1);
|
|
3629
|
+
if (last?.element !== "text") return present;
|
|
3630
|
+
const value = last.value.replace(/\s+$/, "");
|
|
3631
|
+
return isEmptyString(value) ? present.slice(0, -1) : [...present.slice(0, -1), {
|
|
3632
|
+
element: "text",
|
|
3633
|
+
value
|
|
3634
|
+
}];
|
|
3635
|
+
}
|
|
3636
|
+
// src/core/compilers.ts
|
|
3722
3637
|
/**
|
|
3723
3638
|
* Renders a {@link MarkdownNode} to sanitized canonical HTML.
|
|
3724
3639
|
*
|
|
@@ -3744,8 +3659,7 @@ function flattenText(node) {
|
|
|
3744
3659
|
function renderHTML(node) {
|
|
3745
3660
|
return renderHTML$1(new HTML(markdownToHTML(node)).sanitize({ attributes: [...SAFE_ATTRIBUTES, "src"] }).document);
|
|
3746
3661
|
}
|
|
3747
|
-
|
|
3748
|
-
//#region src/core/shapers.ts
|
|
3662
|
+
// src/core/shapers.ts
|
|
3749
3663
|
/**
|
|
3750
3664
|
* Describes the shape of a {@link TextNode} — a plain-text leaf inline run.
|
|
3751
3665
|
*
|
|
@@ -3866,8 +3780,7 @@ var listItemMatchShape = objectShape({
|
|
|
3866
3780
|
indent: integerShape(),
|
|
3867
3781
|
marker: integerShape()
|
|
3868
3782
|
});
|
|
3869
|
-
|
|
3870
|
-
//#region src/core/Markdown.ts
|
|
3783
|
+
// src/core/Markdown.ts
|
|
3871
3784
|
/**
|
|
3872
3785
|
* Wraps a typed {@link MarkdownDocument} AST as a stateful, parsed markdown document
|
|
3873
3786
|
* with the query (`find` / `filter` / `reduce` / iteration), rewrite (`map`), fold, and
|
|
@@ -3973,9 +3886,7 @@ var Markdown = class Markdown {
|
|
|
3973
3886
|
for (const node of this.walk()) if (predicate(node)) return node;
|
|
3974
3887
|
}
|
|
3975
3888
|
filter(predicate) {
|
|
3976
|
-
|
|
3977
|
-
for (const node of this.walk()) if (predicate(node)) out.push(node);
|
|
3978
|
-
return out;
|
|
3889
|
+
return this.walk().filter((node) => predicate(node)).toArray();
|
|
3979
3890
|
}
|
|
3980
3891
|
/**
|
|
3981
3892
|
* Rewrites the AST bottom-up (copy-on-write) and returns a new {@link Markdown},
|
|
@@ -3992,9 +3903,7 @@ var Markdown = class Markdown {
|
|
|
3992
3903
|
}
|
|
3993
3904
|
/** Folds the AST depth-first, pre-order into an accumulator. */
|
|
3994
3905
|
reduce(callback, initial) {
|
|
3995
|
-
|
|
3996
|
-
for (const node of this.walk()) accumulator = callback(accumulator, node);
|
|
3997
|
-
return accumulator;
|
|
3906
|
+
return this.walk().reduce((accumulator, node) => callback(accumulator, node), initial);
|
|
3998
3907
|
}
|
|
3999
3908
|
/** Runs a total catamorphism over the document using a {@link MarkdownHandlerMap} table. */
|
|
4000
3909
|
fold(handlers) {
|
|
@@ -4011,7 +3920,9 @@ var Markdown = class Markdown {
|
|
|
4011
3920
|
* ```ts
|
|
4012
3921
|
* // universal - works in every ReadableStream-supporting environment
|
|
4013
3922
|
* const reader = markdown.stream().getReader()
|
|
4014
|
-
* for (
|
|
3923
|
+
* for (;;) {
|
|
3924
|
+
* const result = await reader.read()
|
|
3925
|
+
* if (result.done) break
|
|
4015
3926
|
* console.log(result.value) // one BlockNode
|
|
4016
3927
|
* }
|
|
4017
3928
|
*
|
|
@@ -4023,18 +3934,11 @@ var Markdown = class Markdown {
|
|
|
4023
3934
|
* ```
|
|
4024
3935
|
*/
|
|
4025
3936
|
stream() {
|
|
4026
|
-
const blocks = this.#document.children;
|
|
4027
|
-
let index = 0;
|
|
3937
|
+
const blocks = this.#document.children.values();
|
|
4028
3938
|
return new ReadableStream({ pull(controller) {
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
controller.close();
|
|
4033
|
-
return;
|
|
4034
|
-
}
|
|
4035
|
-
controller.enqueue(block);
|
|
4036
|
-
index += 1;
|
|
4037
|
-
} else controller.close();
|
|
3939
|
+
const block = blocks.next();
|
|
3940
|
+
if (!block.done && block.value !== void 0) controller.enqueue(block.value);
|
|
3941
|
+
else controller.close();
|
|
4038
3942
|
} });
|
|
4039
3943
|
}
|
|
4040
3944
|
#derive(document, derivations) {
|
|
@@ -4053,8 +3957,7 @@ var Markdown = class Markdown {
|
|
|
4053
3957
|
return derived;
|
|
4054
3958
|
}
|
|
4055
3959
|
};
|
|
4056
|
-
|
|
4057
|
-
//#region src/core/factories.ts
|
|
3960
|
+
// src/core/factories.ts
|
|
4058
3961
|
/**
|
|
4059
3962
|
* Creates a stateful markdown handle from a markdown string or an already-parsed
|
|
4060
3963
|
* {@link MarkdownDocument} — a typed AST plus the query, rewrite, and fold operations
|
|
@@ -4172,7 +4075,6 @@ function createCodeBlockContract() {
|
|
|
4172
4075
|
function createThematicBreakContract() {
|
|
4173
4076
|
return createContract(thematicBreakShape);
|
|
4174
4077
|
}
|
|
4175
|
-
|
|
4176
|
-
export { EMPTY_PROJECTION, MAX_DEPTH, Markdown, coalesceText, codeBlockShape, codeSpanShape, collectList, collectTable, countIndent, createCodeBlockContract, createCodeSpanContract, createLineBreakContract, createMarkdown, createProjection, createTextContract, createThematicBreakContract, delimiterToAlignments, extractFence, extractHeading, extractListItem, flattenText, foldNode, htmlToMarkdown, isBlankLine, isBlockNode, isBlockquoteNode, isCodeBlockNode, isCodeSpanNode, isEmphasisNode, isEscapable, isFenceClose, isFenceWhitespace, isFlankingWhitespace, isHeadingNode, isImageNode, isInlineNode, isLineBreakNode, isLinkNode, isListNode, isMarkdownDocument, isMarkdownNode, isParagraphNode, isQuote, isTableNode, isTableStart, isTextNode, isThematicBreak, isThematicBreakNode, joinSources, lineBreakShape, listItemMatchShape, locateEmphasis, locateLink, markdownToHTML, mergeProjections, normalizeInlines, normalizeParagraphLine, parseBlocks, parseDocument, parseInline, parseProvenance, projectHTMLLeaf, projectHTMLNode, projectSpan, projectionToBlocks, projectionToInlines, renderHTML, renderMarkdown, rewriteDocument, scanCode, scanEmphasis, scanInline, scanInlineSource, scanLink, sliceSource, splitLines, splitTableRow, splitTableSources, startsBlock, stripQuote, tableAlignShape, textShape, thematicBreakShape, trimInlines, trimSource, unescapeText, walkNodes };
|
|
4078
|
+
export { EMPTY_PROJECTION, MAX_DEPTH, Markdown, acceptMarkdownNode, coalesceText, codeBlockShape, codeSpanShape, collectList, collectTable, countIndent, countMarkdownRun, createCodeBlockContract, createCodeSpanContract, createLineBreakContract, createMarkdown, createProjection, createTextContract, createThematicBreakContract, delimiterToAlignments, escapeMarkdownText, extractFence, extractHeading, extractListItem, findFollowingNode, flattenText, foldMarkdownNode, foldNode, htmlToMarkdown, isBlankLine, isBlockNode, isBlockquoteNode, isCodeBlockNode, isCodeSpanNode, isEmphasisNode, isEscapable, isFenceClose, isFenceWhitespace, isFlankingWhitespace, isHeadingNode, isImageNode, isInlineNode, isLineBreakNode, isLinkNode, isListNode, isMarkdownDocument, isMarkdownNode, isParagraphNode, isQuote, isTableNode, isTableStart, isTextNode, isThematicBreak, isThematicBreakNode, joinSources, lineBreakShape, listChildren, listItemMatchShape, locateEmphasis, locateLink, locateMarkdownDelimiter, markdownToHTML, mergeProjections, normalizeInlines, normalizeParagraphLine, parseBlocks, parseDocument, parseInline, parseProvenance, projectHTMLLeaf, projectHTMLNode, projectMarkdownNode, projectSpan, projectionToBlocks, projectionToInlines, rebuildMarkdownCell, rebuildMarkdownNode, recordMarkdownText, renderHTML, renderMarkdown, renderMarkdownNode, rewriteDocument, scanCode, scanEmphasis, scanInline, scanInlineSource, scanLink, scanMarkdownInline, sliceSource, splitLines, splitTableRow, splitTableSources, splitTextRuns, startsBlock, stripQuote, tableAlignShape, textShape, thematicBreakShape, trimInlines, trimLeading, trimSource, trimTrailing, unescapeText, walkMarkdownCharacters, walkMarkdownLines, walkNodes };
|
|
4177
4079
|
|
|
4178
4080
|
//# sourceMappingURL=index.js.map
|