postext 1.33.11 → 1.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/__tests__/bundle/designOverflowPin.test.js +1 -1
- package/dist/__tests__/bundle/gridBreakRulesPin.test.js +2 -2
- package/dist/__tests__/bundle/shapingLanguagePin.test.js +3 -3
- package/dist/__tests__/bundle/taskBoxPin.test.js +1 -1
- package/dist/__tests__/bundle.test.js +1 -1
- package/dist/__tests__/codeBlocks.test.js +1 -1
- package/dist/__tests__/composed/corpus.test.d.ts +2 -0
- package/dist/__tests__/composed/corpus.test.d.ts.map +1 -0
- package/dist/__tests__/composed/corpus.test.js +33 -0
- package/dist/__tests__/composed/corpus.test.js.map +1 -0
- package/dist/__tests__/composed/slotChecks.test.d.ts +2 -0
- package/dist/__tests__/composed/slotChecks.test.d.ts.map +1 -0
- package/dist/__tests__/composed/slotChecks.test.js +148 -0
- package/dist/__tests__/composed/slotChecks.test.js.map +1 -0
- package/dist/__tests__/composedTemplates.test.d.ts +2 -0
- package/dist/__tests__/composedTemplates.test.d.ts.map +1 -0
- package/dist/__tests__/composedTemplates.test.js +349 -0
- package/dist/__tests__/composedTemplates.test.js.map +1 -0
- package/dist/__tests__/contentInConfig.test.d.ts +2 -0
- package/dist/__tests__/contentInConfig.test.d.ts.map +1 -0
- package/dist/__tests__/contentInConfig.test.js +201 -0
- package/dist/__tests__/contentInConfig.test.js.map +1 -0
- package/dist/__tests__/defaults/stripRoundTrip.test.js +3 -1
- package/dist/__tests__/defaults/stripRoundTrip.test.js.map +1 -1
- package/dist/__tests__/exports.test.js +22 -0
- package/dist/__tests__/exports.test.js.map +1 -1
- package/dist/__tests__/hardBreaks.test.js +1 -1
- package/dist/__tests__/headingInlineMarks.test.js +1 -1
- package/dist/__tests__/mathSizeMigration.test.js +2 -2
- package/dist/__tests__/parse/composedPages.test.d.ts +2 -0
- package/dist/__tests__/parse/composedPages.test.d.ts.map +1 -0
- package/dist/__tests__/parse/composedPages.test.js +323 -0
- package/dist/__tests__/parse/composedPages.test.js.map +1 -0
- package/dist/__tests__/parse/containers.test.js +2 -2
- package/dist/__tests__/parse/containers.test.js.map +1 -1
- package/dist/__tests__/pipeline/calloutInlineResourceGap.test.js +1 -1
- package/dist/__tests__/pipeline/calloutSplitMinLinesChild.test.js +1 -1
- package/dist/__tests__/pipeline/colonListRoom.test.js +1 -1
- package/dist/__tests__/pipeline/compoundBreaks.test.js +1 -1
- package/dist/__tests__/pipeline/dashBreaks.test.js +1 -1
- package/dist/__tests__/pipeline/headingSplitOrphans.test.js +1 -1
- package/dist/__tests__/pipeline/inlineFigureSpace.test.js +1 -1
- package/dist/__tests__/pipeline/paragraphContainerTail.test.js +1 -1
- package/dist/__tests__/pipeline/raggedOptimal.test.js +1 -1
- package/dist/__tests__/tocGroups.test.d.ts +2 -0
- package/dist/__tests__/tocGroups.test.d.ts.map +1 -0
- package/dist/__tests__/tocGroups.test.js +213 -0
- package/dist/__tests__/tocGroups.test.js.map +1 -0
- package/dist/__tests__/verseLines.test.js +1 -1
- package/dist/bundle/configVersion.d.ts +16 -5
- package/dist/bundle/configVersion.d.ts.map +1 -1
- package/dist/bundle/configVersion.js +17 -4
- package/dist/bundle/configVersion.js.map +1 -1
- package/dist/bundle/types.d.ts +4 -3
- package/dist/bundle/types.d.ts.map +1 -1
- package/dist/composed/document.d.ts +43 -0
- package/dist/composed/document.d.ts.map +1 -0
- package/dist/composed/document.js +88 -0
- package/dist/composed/document.js.map +1 -0
- package/dist/composed/slotChecks.d.ts +29 -0
- package/dist/composed/slotChecks.d.ts.map +1 -0
- package/dist/composed/slotChecks.js +109 -0
- package/dist/composed/slotChecks.js.map +1 -0
- package/dist/composed/templates.d.ts +133 -0
- package/dist/composed/templates.d.ts.map +1 -0
- package/dist/composed/templates.js +702 -0
- package/dist/composed/templates.js.map +1 -0
- package/dist/composed/types.d.ts +404 -0
- package/dist/composed/types.d.ts.map +1 -0
- package/dist/composed/types.js +2 -0
- package/dist/composed/types.js.map +1 -0
- package/dist/composed/validate.d.ts +26 -0
- package/dist/composed/validate.d.ts.map +1 -0
- package/dist/composed/validate.js +276 -0
- package/dist/composed/validate.js.map +1 -0
- package/dist/configWarnings.d.ts +19 -3
- package/dist/configWarnings.d.ts.map +1 -1
- package/dist/configWarnings.js +170 -40
- package/dist/configWarnings.js.map +1 -1
- package/dist/defaults/checks.d.ts +7 -0
- package/dist/defaults/checks.d.ts.map +1 -0
- package/dist/defaults/checks.js +49 -0
- package/dist/defaults/checks.js.map +1 -0
- package/dist/defaults/index.d.ts +1 -0
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +26 -0
- package/dist/defaults/index.js.map +1 -1
- package/dist/defaults/toc.d.ts +17 -2
- package/dist/defaults/toc.d.ts.map +1 -1
- package/dist/defaults/toc.js +53 -1
- package/dist/defaults/toc.js.map +1 -1
- package/dist/design/slots.d.ts +29 -0
- package/dist/design/slots.d.ts.map +1 -0
- package/dist/design/slots.js +48 -0
- package/dist/design/slots.js.map +1 -0
- package/dist/index.d.ts +17 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/parse/blockParser.d.ts.map +1 -1
- package/dist/parse/blockParser.js +295 -36
- package/dist/parse/blockParser.js.map +1 -1
- package/dist/parse/index.d.ts +1 -1
- package/dist/parse/index.d.ts.map +1 -1
- package/dist/parse/index.js.map +1 -1
- package/dist/parse/indexMarks.d.ts.map +1 -1
- package/dist/parse/indexMarks.js +24 -0
- package/dist/parse/indexMarks.js.map +1 -1
- package/dist/parse/types.d.ts +95 -2
- package/dist/parse/types.d.ts.map +1 -1
- package/dist/pipeline/build.d.ts.map +1 -1
- package/dist/pipeline/build.js +178 -3
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js +17 -2
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/contentWarnings.d.ts.map +1 -1
- package/dist/pipeline/contentWarnings.js +73 -0
- package/dist/pipeline/contentWarnings.js.map +1 -1
- package/dist/pipeline/placeholders.d.ts +7 -0
- package/dist/pipeline/placeholders.d.ts.map +1 -1
- package/dist/pipeline/placeholders.js +41 -0
- package/dist/pipeline/placeholders.js.map +1 -1
- package/dist/pipeline/textWrap.js +1 -1
- package/dist/pipeline/textWrap.js.map +1 -1
- package/dist/pipeline/toc.d.ts +11 -1
- package/dist/pipeline/toc.d.ts.map +1 -1
- package/dist/pipeline/toc.js +147 -8
- package/dist/pipeline/toc.js.map +1 -1
- package/dist/types.d.ts +99 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/util/closestKey.d.ts +4 -0
- package/dist/util/closestKey.d.ts.map +1 -0
- package/dist/util/closestKey.js +40 -0
- package/dist/util/closestKey.js.map +1 -0
- package/dist/vdt.d.ts +125 -7
- package/dist/vdt.d.ts.map +1 -1
- package/dist/vdt.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* positions back into the original markdown.
|
|
6
6
|
*/
|
|
7
7
|
import { attachEquationAnchors } from './equationLabels';
|
|
8
|
-
import { parseAttrBlobStrict, parseDirectiveAttrs } from './attrs';
|
|
8
|
+
import { parseAttrBlobStrict, parseDirectiveAttrs, scanDirectiveAttrs } from './attrs';
|
|
9
9
|
import { extractInlineMath, fixMathSourceMap, injectMathSpans } from './inlineMath';
|
|
10
10
|
import { BREAK_PLACEHOLDER, TITLE_BREAK_RE, extractInlineChips, extractInlineFootnotes, extractInlineRefs, injectFootnoteSpans, extractInlineSwatches, injectChipSpans, injectRefSpans, injectSwatchSpans, parseInlineFormatting, protectCodeSpans, replaceBodyBreaks, BODY_BREAK_MARK, bodyBreakSpans, titleBreakIndices, trimSpans, extractInlineTabs, injectTabSpans } from './inlineFormatting';
|
|
11
11
|
import { buildBlockMapping } from './sourceMapping';
|
|
@@ -24,12 +24,23 @@ const HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
|
24
24
|
const DIRECTIVE_RE = /^:::\s*([a-z][a-z0-9-]*)\s*(?:\{([^}]*)\})?\s*$/;
|
|
25
25
|
/** A bare `:::` line: closes the innermost open container. */
|
|
26
26
|
const CONTAINER_CLOSE_RE = /^:::\s*$/;
|
|
27
|
+
/** A composed page's opening fence (#854): four or more colons, `page`,
|
|
28
|
+
* its attributes (`::::page{template="…"}`). Three colons stay a comic
|
|
29
|
+
* page (`:::page{split}`). */
|
|
30
|
+
const PAGE_FENCE_RE = /^(:{4,})\s*page\s*(?:\{([^}]*)\})?\s*$/;
|
|
31
|
+
/** A bare fence of four or more colons: it closes the composed page opened
|
|
32
|
+
* with as many (#854). */
|
|
33
|
+
const LONG_CLOSE_RE = /^(:{4,})\s*$/;
|
|
34
|
+
/** A `::label[TEXT]{…}` line of a `:::figure` (#854). */
|
|
35
|
+
const LABEL_LINE_RE = /^::label\[/;
|
|
36
|
+
/** `at="62% 48%"`: a label's place in percents of the picture. */
|
|
37
|
+
const LABEL_AT_PERCENT_RE = /^\s*(-?\d+(?:\.\d+)?)\s*%\s+(-?\d+(?:\.\d+)?)\s*%\s*$/;
|
|
27
38
|
/** A bayt's hemistichs cut at a `\\` with a space on each side (the
|
|
28
39
|
* Wikisource markup, #378). */
|
|
29
40
|
const HEMISTICH_WIKI_RE = /\s\\\\\s/;
|
|
30
41
|
/** Set of directive names recognized today. Unknown names fall through to
|
|
31
42
|
* paragraph-parsing and downstream warnings flag them. */
|
|
32
|
-
export const KNOWN_DIRECTIVES = new Set(['pagebreak', 'numbering', 'columnbreak', 'space', 'toc', 'index', 'bibliography', 'references', 'verse', 'page', 'strip']);
|
|
43
|
+
export const KNOWN_DIRECTIVES = new Set(['pagebreak', 'numbering', 'columnbreak', 'space', 'toc', 'index', 'bibliography', 'references', 'verse', 'page', 'strip', 'figure']);
|
|
33
44
|
/** Trailing `{key="value" …}` attribute block on a heading line, e.g.
|
|
34
45
|
* `# Title {author="I. Zango"}`. The braces must be balanced (no nested
|
|
35
46
|
* braces) and be the last thing on the line, after a space — or right
|
|
@@ -55,7 +66,45 @@ function headingAttrTokens(blob, spaced) {
|
|
|
55
66
|
}
|
|
56
67
|
/** Set of fenced-container names recognized today. A `:::name` line whose
|
|
57
68
|
* name is a known container opens a block that runs until a bare `:::`. */
|
|
58
|
-
export const KNOWN_CONTAINERS = new Set(['callout', 'paragraphs', 'part', 'columns', 'paper']);
|
|
69
|
+
export const KNOWN_CONTAINERS = new Set(['callout', 'paragraphs', 'part', 'columns', 'paper', 'frame']);
|
|
70
|
+
/** Absolute source range of each attribute value of a blob that starts at
|
|
71
|
+
* `blobStart` (a flag: its key). A repeated key keeps the last, as
|
|
72
|
+
* `parseDirectiveAttrs` does. */
|
|
73
|
+
function attrSourcesOf(raw, blobStart) {
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const t of scanDirectiveAttrs(raw)) {
|
|
76
|
+
out[t.key] = t.valueStart !== undefined && t.valueEnd !== undefined
|
|
77
|
+
? { sourceStart: blobStart + t.valueStart, sourceEnd: blobStart + t.valueEnd }
|
|
78
|
+
: { sourceStart: blobStart + t.start, sourceEnd: blobStart + t.end };
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/** The slot a blob's `slot="…"` names (#854), or undefined when it names
|
|
83
|
+
* none (or an empty one). */
|
|
84
|
+
function slotOf(attrs, raw, blobStart) {
|
|
85
|
+
const name = attrs.slot?.trim();
|
|
86
|
+
if (!name)
|
|
87
|
+
return undefined;
|
|
88
|
+
const at = attrSourcesOf(raw, blobStart).slot;
|
|
89
|
+
return { name, sourceStart: at.sourceStart, sourceEnd: at.sourceEnd };
|
|
90
|
+
}
|
|
91
|
+
/** The offset just past the `]` closing the bracket opened right before
|
|
92
|
+
* `from` (nested brackets counted, a backslash escapes), or -1. */
|
|
93
|
+
function closingBracket(text, from) {
|
|
94
|
+
let depth = 1;
|
|
95
|
+
for (let k = from; k < text.length; k++) {
|
|
96
|
+
const c = text[k];
|
|
97
|
+
if (c === '\\') {
|
|
98
|
+
k++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (c === '[')
|
|
102
|
+
depth++;
|
|
103
|
+
else if (c === ']' && --depth === 0)
|
|
104
|
+
return k;
|
|
105
|
+
}
|
|
106
|
+
return -1;
|
|
107
|
+
}
|
|
59
108
|
/** True for lines that end a paragraph run even without a blank line: any
|
|
60
109
|
* `:::name` directive/container fence and the bare `:::` closing fence. */
|
|
61
110
|
function isFenceLine(trimmed) {
|
|
@@ -599,8 +648,149 @@ function parseBlocks(markdown, options) {
|
|
|
599
648
|
// Open fenced containers, innermost last. Each entry remembers what it
|
|
600
649
|
// needs to emit the matching `containerEnd` (or an `unclosedContainer`
|
|
601
650
|
// issue pointing at the opening line when EOF arrives first).
|
|
651
|
+
// A composed page's entry (#854) records how many colons opened it: only
|
|
652
|
+
// a bare fence of as many closes it, never a bare `:::`.
|
|
602
653
|
const containerStack = [];
|
|
603
654
|
let nextContainerId = 1;
|
|
655
|
+
/** Composed page fences read so far (`ComposedPageFence.fenceIndex`). */
|
|
656
|
+
let pageFences = 0;
|
|
657
|
+
/** Emit the `containerEnd` of the innermost open container (at
|
|
658
|
+
* `srcStart`…`srcEnd`), reporting it as unclosed when `unclosed`. */
|
|
659
|
+
const popContainer = (srcStart, srcEnd, unclosed) => {
|
|
660
|
+
const open = containerStack.pop();
|
|
661
|
+
blocks.push({
|
|
662
|
+
type: 'containerEnd',
|
|
663
|
+
text: '',
|
|
664
|
+
spans: [],
|
|
665
|
+
containerName: open.name,
|
|
666
|
+
containerId: open.id,
|
|
667
|
+
sourceStart: srcStart,
|
|
668
|
+
sourceEnd: srcEnd,
|
|
669
|
+
sourceMap: [],
|
|
670
|
+
});
|
|
671
|
+
if (unclosed) {
|
|
672
|
+
issues.push({
|
|
673
|
+
kind: 'unclosedContainer',
|
|
674
|
+
delimiter: ':::',
|
|
675
|
+
containerName: open.name,
|
|
676
|
+
containerId: open.id,
|
|
677
|
+
sourceStart: open.sourceStart,
|
|
678
|
+
sourceEnd: open.sourceEnd,
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
/** Index in `containerStack` of the open composed page, or -1. */
|
|
683
|
+
const openPageAt = () => {
|
|
684
|
+
for (let k = containerStack.length - 1; k >= 0; k--)
|
|
685
|
+
if (containerStack[k].colons !== undefined)
|
|
686
|
+
return k;
|
|
687
|
+
return -1;
|
|
688
|
+
};
|
|
689
|
+
/** Whether line `k` (trimmed `pl`) is a composed page fence: an opening
|
|
690
|
+
* one, or the bare fence that closes the open page. */
|
|
691
|
+
const isPageFenceLine = (pl) => {
|
|
692
|
+
if (PAGE_FENCE_RE.test(pl))
|
|
693
|
+
return true;
|
|
694
|
+
const close = LONG_CLOSE_RE.exec(pl);
|
|
695
|
+
const at = close ? openPageAt() : -1;
|
|
696
|
+
return at >= 0 && containerStack[at].colons === close[1].length;
|
|
697
|
+
};
|
|
698
|
+
/**
|
|
699
|
+
* A `:::figure{resource="…" slot="…"}` whose fence is line `start`
|
|
700
|
+
* (#854): its `::label[…]{…}` lines up to the closing `:::`, as one
|
|
701
|
+
* `resourceBlock`. A line that is neither a label nor blank ends a figure
|
|
702
|
+
* left open (`unclosedContainer`), and is read as usual.
|
|
703
|
+
*/
|
|
704
|
+
const parseFigure = (start, attrsRaw) => {
|
|
705
|
+
const attrs = parseDirectiveAttrs(attrsRaw);
|
|
706
|
+
const fenceLine = rawLines[start];
|
|
707
|
+
const blobStart = lineOffsets[start] + fenceLine.indexOf('{') + 1;
|
|
708
|
+
const attrSources = attrsRaw ? attrSourcesOf(attrsRaw, blobStart) : {};
|
|
709
|
+
const labels = [];
|
|
710
|
+
let k = start + 1;
|
|
711
|
+
let last = start;
|
|
712
|
+
let closed = false;
|
|
713
|
+
while (k < rawLines.length) {
|
|
714
|
+
const t = rawLines[k].trim();
|
|
715
|
+
if (CONTAINER_CLOSE_RE.test(t)) {
|
|
716
|
+
closed = true;
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
if (t === '') {
|
|
720
|
+
k++;
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
const label = LABEL_LINE_RE.test(t) ? parseLabel(k) : undefined;
|
|
724
|
+
if (!label)
|
|
725
|
+
break;
|
|
726
|
+
labels.push(label);
|
|
727
|
+
last = k;
|
|
728
|
+
k++;
|
|
729
|
+
}
|
|
730
|
+
const slot = attrsRaw ? slotOf(attrs, attrsRaw, blobStart) : undefined;
|
|
731
|
+
blocks.push({
|
|
732
|
+
type: 'resourceBlock',
|
|
733
|
+
text: '',
|
|
734
|
+
spans: [],
|
|
735
|
+
resourceId: attrs.resource?.trim() ?? '',
|
|
736
|
+
figure: { attrs, attrSources, labels },
|
|
737
|
+
...(slot ? { slot } : {}),
|
|
738
|
+
sourceStart: lineOffsets[start],
|
|
739
|
+
sourceEnd: closed ? lineEndOffset(k) : lineEndOffset(last),
|
|
740
|
+
sourceMap: [],
|
|
741
|
+
});
|
|
742
|
+
if (!closed) {
|
|
743
|
+
issues.push({
|
|
744
|
+
kind: 'unclosedContainer',
|
|
745
|
+
delimiter: ':::',
|
|
746
|
+
containerName: 'figure',
|
|
747
|
+
containerId: nextContainerId++,
|
|
748
|
+
sourceStart: lineOffsets[start],
|
|
749
|
+
sourceEnd: lineEndOffset(start),
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
return closed ? k + 1 : last + 1;
|
|
753
|
+
};
|
|
754
|
+
/** The `::label[TEXT]{at=… style=…}` on line `k` (#854), or undefined
|
|
755
|
+
* when the line is not one (text after the attributes, an unclosed
|
|
756
|
+
* bracket). */
|
|
757
|
+
const parseLabel = (k) => {
|
|
758
|
+
const raw = rawLines[k];
|
|
759
|
+
const lead = raw.length - raw.trimStart().length;
|
|
760
|
+
const body = raw.trimEnd();
|
|
761
|
+
const open = lead + '::label['.length;
|
|
762
|
+
const close = closingBracket(body, open);
|
|
763
|
+
if (close < 0)
|
|
764
|
+
return undefined;
|
|
765
|
+
const rest = body.slice(close + 1);
|
|
766
|
+
const attrsMatch = /^\s*(?:\{([^}]*)\})?\s*$/.exec(rest);
|
|
767
|
+
if (!attrsMatch)
|
|
768
|
+
return undefined;
|
|
769
|
+
const lineStart = lineOffsets[k];
|
|
770
|
+
const blob = attrsMatch[1] ?? '';
|
|
771
|
+
const blobStart = lineStart + close + 1 + rest.indexOf('{') + 1;
|
|
772
|
+
const attrs = parseDirectiveAttrs(blob);
|
|
773
|
+
const attrSources = blob ? attrSourcesOf(blob, blobStart) : {};
|
|
774
|
+
const textStart = lineStart + open;
|
|
775
|
+
const textEnd = lineStart + close;
|
|
776
|
+
const run = inlineRun(raw.slice(open, close), textStart, textEnd);
|
|
777
|
+
const atRaw = attrs.at ?? '';
|
|
778
|
+
const pct = LABEL_AT_PERCENT_RE.exec(atRaw);
|
|
779
|
+
const at = pct ? { x: Number(pct[1]) / 100, y: Number(pct[2]) / 100 } : { anchor: atRaw.trim() };
|
|
780
|
+
const style = attrs.style?.trim();
|
|
781
|
+
return {
|
|
782
|
+
text: run.text,
|
|
783
|
+
spans: run.spans,
|
|
784
|
+
sourceMap: run.sourceMap,
|
|
785
|
+
at,
|
|
786
|
+
...(style ? { style } : {}),
|
|
787
|
+
textStart,
|
|
788
|
+
textEnd,
|
|
789
|
+
attrSources,
|
|
790
|
+
sourceStart: lineStart + lead,
|
|
791
|
+
sourceEnd: lineStart + body.length,
|
|
792
|
+
};
|
|
793
|
+
};
|
|
604
794
|
/** Index of the last source line of the latest display formula that
|
|
605
795
|
* interrupted a paragraph: a paragraph that starts on the next line
|
|
606
796
|
* continues the one the formula interrupted (see
|
|
@@ -689,7 +879,15 @@ function parseBlocks(markdown, options) {
|
|
|
689
879
|
const info = readCodeInfo(open.info);
|
|
690
880
|
const srcStart = lineOffsets[start];
|
|
691
881
|
const srcEnd = closed ? lineEndOffset(end) : lineEndOffset(rawLines.length - 1);
|
|
692
|
-
|
|
882
|
+
const code = codeBlock(start + 1, end, cuts, srcStart, srcEnd, { ...info, info: open.info, fence });
|
|
883
|
+
// `slot="…"` among the fence's attributes (#854).
|
|
884
|
+
if (info.attrs.slot !== undefined) {
|
|
885
|
+
const infoAt = rawLines[start].indexOf(open.info, open.indent + open.marker.length);
|
|
886
|
+
const slot = slotOf(info.attrs, open.info, srcStart + Math.max(0, infoAt));
|
|
887
|
+
if (slot)
|
|
888
|
+
code.slot = slot;
|
|
889
|
+
}
|
|
890
|
+
blocks.push(code);
|
|
693
891
|
if (!closed) {
|
|
694
892
|
issues.push({
|
|
695
893
|
kind: 'unclosedCodeBlock',
|
|
@@ -827,20 +1025,57 @@ function parseBlocks(markdown, options) {
|
|
|
827
1025
|
i++;
|
|
828
1026
|
continue;
|
|
829
1027
|
}
|
|
830
|
-
//
|
|
831
|
-
//
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
const
|
|
1028
|
+
// `::resource{id="…" slot="…"}` (#854): the embed sent to a slot of a
|
|
1029
|
+
// composed page. Only the two keys; anything else stays text as before.
|
|
1030
|
+
if (trimmed.startsWith('::resource') && trimmed.includes('slot')) {
|
|
1031
|
+
const m = /^::resource\s*\{([^}]*)\}\s*$/.exec(trimmed);
|
|
1032
|
+
const tokens = m ? parseAttrBlobStrict(m[1]) : undefined;
|
|
1033
|
+
const id = tokens?.find((t) => t.key === 'id');
|
|
1034
|
+
if (tokens && id && id.quoted && id.value !== '' && tokens.some((t) => t.key === 'slot') && tokens.every((t) => t.key === 'id' || t.key === 'slot')) {
|
|
1035
|
+
const blobStart = lineOffsets[i] + line.indexOf('{') + 1;
|
|
1036
|
+
const slot = slotOf(parseDirectiveAttrs(m[1]), m[1], blobStart);
|
|
1037
|
+
blocks.push({
|
|
1038
|
+
type: 'resourceBlock',
|
|
1039
|
+
text: '',
|
|
1040
|
+
spans: [],
|
|
1041
|
+
resourceId: id.value,
|
|
1042
|
+
...(slot ? { slot } : {}),
|
|
1043
|
+
sourceStart: lineOffsets[i],
|
|
1044
|
+
sourceEnd: lineEndOffset(i),
|
|
1045
|
+
sourceMap: [],
|
|
1046
|
+
});
|
|
1047
|
+
i++;
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
// A composed page (#854): `::::page{template=…}` opens it, a bare fence
|
|
1052
|
+
// of as many colons closes it. A page opened while one is open closes
|
|
1053
|
+
// that one first (reported unclosed), and so does its closing fence any
|
|
1054
|
+
// `:::` container still open inside it.
|
|
1055
|
+
const pageOpen = PAGE_FENCE_RE.exec(trimmed);
|
|
1056
|
+
if (pageOpen) {
|
|
836
1057
|
const srcStart = lineOffsets[i];
|
|
837
1058
|
const srcEnd = lineEndOffset(i);
|
|
1059
|
+
const at = openPageAt();
|
|
1060
|
+
if (at >= 0)
|
|
1061
|
+
while (containerStack.length > at)
|
|
1062
|
+
popContainer(srcStart, srcStart, true);
|
|
1063
|
+
const attrsRaw = pageOpen[2] ?? '';
|
|
1064
|
+
const attrs = parseDirectiveAttrs(attrsRaw);
|
|
1065
|
+
const blobStart = srcStart + line.indexOf('{') + 1;
|
|
1066
|
+
const attrSources = attrsRaw ? attrSourcesOf(attrsRaw, blobStart) : {};
|
|
1067
|
+
const id = nextContainerId++;
|
|
1068
|
+
const colons = pageOpen[1].length;
|
|
1069
|
+
containerStack.push({ id, name: 'composedPage', sourceStart: srcStart, sourceEnd: srcEnd, colons });
|
|
1070
|
+
const spread = attrs.spread !== undefined && attrs.spread.trim().toLowerCase() !== 'false';
|
|
838
1071
|
blocks.push({
|
|
839
|
-
type: '
|
|
1072
|
+
type: 'containerStart',
|
|
840
1073
|
text: '',
|
|
841
1074
|
spans: [],
|
|
842
|
-
containerName:
|
|
843
|
-
|
|
1075
|
+
containerName: 'composedPage',
|
|
1076
|
+
containerAttrs: attrs,
|
|
1077
|
+
containerId: id,
|
|
1078
|
+
composedPage: { template: attrs.template?.trim() ?? '', spread, attrs, attrSources, fenceIndex: pageFences++, colons },
|
|
844
1079
|
sourceStart: srcStart,
|
|
845
1080
|
sourceEnd: srcEnd,
|
|
846
1081
|
sourceMap: [],
|
|
@@ -848,6 +1083,29 @@ function parseBlocks(markdown, options) {
|
|
|
848
1083
|
i++;
|
|
849
1084
|
continue;
|
|
850
1085
|
}
|
|
1086
|
+
const longClose = LONG_CLOSE_RE.exec(trimmed);
|
|
1087
|
+
if (longClose) {
|
|
1088
|
+
const at = openPageAt();
|
|
1089
|
+
if (at >= 0 && containerStack[at].colons === longClose[1].length) {
|
|
1090
|
+
const srcStart = lineOffsets[i];
|
|
1091
|
+
while (containerStack.length > at + 1)
|
|
1092
|
+
popContainer(srcStart, srcStart, true);
|
|
1093
|
+
popContainer(srcStart, lineEndOffset(i), false);
|
|
1094
|
+
i++;
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
// Container closing fence — a bare `:::` while a container is open pops
|
|
1099
|
+
// it and emits the matching `containerEnd` marker. With nothing open the
|
|
1100
|
+
// line is not special and falls through to paragraph parsing so the
|
|
1101
|
+
// stray fence stays visible instead of vanishing silently.
|
|
1102
|
+
// A bare `:::` never closes a composed page (#854): right inside one it
|
|
1103
|
+
// is a stray fence, set as text.
|
|
1104
|
+
if (containerStack.length > 0 && containerStack[containerStack.length - 1].colons === undefined && CONTAINER_CLOSE_RE.test(trimmed)) {
|
|
1105
|
+
popContainer(lineOffsets[i], lineEndOffset(i), false);
|
|
1106
|
+
i++;
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
851
1109
|
// A comic page (#555) or strip (#566): `:::page{split=…}` … `:::`, its
|
|
852
1110
|
// panels and script read whole (`parseComicFence`).
|
|
853
1111
|
const comicMatch = trimmed.match(DIRECTIVE_RE);
|
|
@@ -906,6 +1164,11 @@ function parseBlocks(markdown, options) {
|
|
|
906
1164
|
i = verse.next;
|
|
907
1165
|
continue;
|
|
908
1166
|
}
|
|
1167
|
+
// A figure with labels (#854): `:::figure{resource=…}` … `:::`.
|
|
1168
|
+
if (refsMatch && refsMatch[1] === 'figure') {
|
|
1169
|
+
i = parseFigure(i, refsMatch[2] ?? '');
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
909
1172
|
// Container opening fence — `:::name` / `:::name{attrs}` whose name is a
|
|
910
1173
|
// known container. Emits a `containerStart` marker; the blocks that
|
|
911
1174
|
// follow are parsed as usual until the matching closing fence.
|
|
@@ -917,13 +1180,17 @@ function parseBlocks(markdown, options) {
|
|
|
917
1180
|
const srcEnd = lineEndOffset(i);
|
|
918
1181
|
const id = nextContainerId++;
|
|
919
1182
|
containerStack.push({ id, name, sourceStart: srcStart, sourceEnd: srcEnd });
|
|
1183
|
+
const containerAttrs = parseDirectiveAttrs(attrsRaw);
|
|
1184
|
+
// `:::frame{slot="…"}` (#854), or any container sent to a slot.
|
|
1185
|
+
const slot = containerAttrs.slot !== undefined ? slotOf(containerAttrs, attrsRaw, srcStart + line.indexOf('{') + 1) : undefined;
|
|
920
1186
|
blocks.push({
|
|
921
1187
|
type: 'containerStart',
|
|
922
1188
|
text: '',
|
|
923
1189
|
spans: [],
|
|
924
1190
|
containerName: name,
|
|
925
|
-
containerAttrs
|
|
1191
|
+
containerAttrs,
|
|
926
1192
|
containerId: id,
|
|
1193
|
+
...(slot ? { slot } : {}),
|
|
927
1194
|
sourceStart: srcStart,
|
|
928
1195
|
sourceEnd: srcEnd,
|
|
929
1196
|
sourceMap: [],
|
|
@@ -968,6 +1235,7 @@ function parseBlocks(markdown, options) {
|
|
|
968
1235
|
// (the map is a greedy plain-char → source walk bounded by `srcEnd`).
|
|
969
1236
|
let attrs;
|
|
970
1237
|
let attrSources;
|
|
1238
|
+
let headingSlot;
|
|
971
1239
|
const attrsMatch = headingRawContent.match(HEADING_ATTRS_RE);
|
|
972
1240
|
const tokens = attrsMatch && attrsMatch.index !== undefined
|
|
973
1241
|
? headingAttrTokens(attrsMatch[1], attrsMatch[0][0] !== '{')
|
|
@@ -984,6 +1252,15 @@ function parseBlocks(markdown, options) {
|
|
|
984
1252
|
attrSources ??= {};
|
|
985
1253
|
attrSources[t.key] = { start: attrsAbsStart + t.valueStart, end: attrsAbsStart + t.valueEnd };
|
|
986
1254
|
}
|
|
1255
|
+
// `# Title {slot="titulo"}` (#854).
|
|
1256
|
+
const slotToken = read.filter((t) => t.key === 'slot').pop();
|
|
1257
|
+
if (slotToken && slotToken.value.trim() !== '') {
|
|
1258
|
+
headingSlot = {
|
|
1259
|
+
name: slotToken.value.trim(),
|
|
1260
|
+
sourceStart: attrsAbsStart + (slotToken.valueStart ?? slotToken.start),
|
|
1261
|
+
sourceEnd: attrsAbsStart + (slotToken.valueEnd ?? slotToken.end),
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
987
1264
|
headingRawContent = headingRawContent.slice(0, attrsMatch.index);
|
|
988
1265
|
srcEnd = contentAbsStart + headingRawContent.length;
|
|
989
1266
|
}
|
|
@@ -1011,6 +1288,7 @@ function parseBlocks(markdown, options) {
|
|
|
1011
1288
|
...(attrs ? { attrs } : {}),
|
|
1012
1289
|
...(attrSources ? { attrSources } : {}),
|
|
1013
1290
|
...(titleBreaks.length > 0 ? { titleBreaks } : {}),
|
|
1291
|
+
...(headingSlot ? { slot: headingSlot } : {}),
|
|
1014
1292
|
sourceStart: srcStart,
|
|
1015
1293
|
sourceEnd: srcEnd,
|
|
1016
1294
|
sourceMap: mapping.sourceMap,
|
|
@@ -1141,7 +1419,7 @@ function parseBlocks(markdown, options) {
|
|
|
1141
1419
|
const pl = rawLine.trim();
|
|
1142
1420
|
if (pl === '' || pl.match(HEADING_RE) || pl.startsWith('>') || rawLine.match(LIST_ITEM_RE))
|
|
1143
1421
|
break;
|
|
1144
|
-
if (i > startIdx && isFenceLine(pl))
|
|
1422
|
+
if (i > startIdx && (isFenceLine(pl) || isPageFenceLine(pl)))
|
|
1145
1423
|
break;
|
|
1146
1424
|
// A code fence interrupts the paragraph (#624).
|
|
1147
1425
|
if (i > startIdx && fences && codeFenceOpen(rawLine))
|
|
@@ -1190,27 +1468,8 @@ function parseBlocks(markdown, options) {
|
|
|
1190
1468
|
// EOF with containers still open: auto-close innermost-first with
|
|
1191
1469
|
// zero-length end markers at the end of input, and report each one so the
|
|
1192
1470
|
// sandbox can point at the fence that never got its `:::`.
|
|
1193
|
-
while (containerStack.length > 0)
|
|
1194
|
-
|
|
1195
|
-
blocks.push({
|
|
1196
|
-
type: 'containerEnd',
|
|
1197
|
-
text: '',
|
|
1198
|
-
spans: [],
|
|
1199
|
-
containerName: open.name,
|
|
1200
|
-
containerId: open.id,
|
|
1201
|
-
sourceStart: markdown.length,
|
|
1202
|
-
sourceEnd: markdown.length,
|
|
1203
|
-
sourceMap: [],
|
|
1204
|
-
});
|
|
1205
|
-
issues.push({
|
|
1206
|
-
kind: 'unclosedContainer',
|
|
1207
|
-
delimiter: ':::',
|
|
1208
|
-
containerName: open.name,
|
|
1209
|
-
containerId: open.id,
|
|
1210
|
-
sourceStart: open.sourceStart,
|
|
1211
|
-
sourceEnd: open.sourceEnd,
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1471
|
+
while (containerStack.length > 0)
|
|
1472
|
+
popContainer(markdown.length, markdown.length, true);
|
|
1214
1473
|
return { blocks, issues };
|
|
1215
1474
|
}
|
|
1216
1475
|
//# sourceMappingURL=blockParser.js.map
|