@astryxdesign/core 0.6.2-canary.bfee8a4 → 0.6.2-canary.cb7a565
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/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +12 -2
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +6 -1
- package/dist/Kbd/Kbd.d.ts +5 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +36 -42
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -2
- package/dist/Markdown/Markdown.d.ts +8 -2
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.bench.js +248 -0
- package/dist/Markdown/Markdown.js +262 -109
- package/dist/Markdown/ast.d.ts +162 -0
- package/dist/Markdown/ast.d.ts.map +1 -0
- package/dist/Markdown/ast.js +74 -0
- package/dist/Markdown/index.d.ts +3 -1
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/index.js +1 -0
- package/dist/Markdown/parser.d.ts +104 -58
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +693 -167
- package/dist/Markdown/plugins/index.d.ts +14 -0
- package/dist/Markdown/plugins/index.d.ts.map +1 -0
- package/dist/Markdown/plugins/index.js +12 -0
- package/dist/Markdown/plugins/protocol.d.ts +191 -0
- package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
- package/dist/Markdown/plugins/protocol.js +957 -0
- package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
- package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
- package/dist/Markdown/plugins/semanticFence.js +213 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
- package/dist/Markdown/plugins/sourceDecoration.js +393 -0
- package/dist/Markdown/plugins/textTransform.d.ts +30 -0
- package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
- package/dist/Markdown/plugins/textTransform.js +374 -0
- package/dist/Markdown/url.d.ts +4 -0
- package/dist/Markdown/url.d.ts.map +1 -0
- package/dist/Markdown/url.js +25 -0
- package/dist/Markdown/utils.d.ts +4 -2
- package/dist/Markdown/utils.d.ts.map +1 -1
- package/dist/Markdown/utils.js +2 -1
- package/dist/Outline/index.d.ts +1 -0
- package/dist/Outline/index.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
- package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
- package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/useOutlineFromMarkdown.js +8 -3
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
- package/dist/SideNav/SideNav.d.ts +2 -1
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +9 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +14 -8
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +11 -0
- package/dist/TabList/Tab.d.ts +1 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +20 -7
- package/dist/ToggleButton/ToggleButton.d.ts +2 -1
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +7 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +15 -6
- package/dist/astryx.css +8 -0
- package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +9 -0
- package/package.json +8 -3
- package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
- package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
- package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
- package/src/Button/__tests__/Button.a11y.states.ts +9 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -1
- package/src/ClickableCard/ClickableCard.test.tsx +102 -5
- package/src/Collapsible/Collapsible.doc.mjs +11 -0
- package/src/Collapsible/Collapsible.test.tsx +21 -0
- package/src/Collapsible/Collapsible.tsx +5 -0
- package/src/Kbd/Kbd.doc.mjs +3 -3
- package/src/Kbd/Kbd.test.tsx +57 -1
- package/src/Kbd/Kbd.tsx +57 -37
- package/src/Link/Link.doc.mjs +11 -0
- package/src/Link/Link.test.tsx +24 -0
- package/src/Link/Link.tsx +5 -0
- package/src/Markdown/Markdown.doc.mjs +175 -0
- package/src/Markdown/Markdown.fr23.bench.ts +288 -0
- package/src/Markdown/Markdown.fr23.perf.test.ts +211 -0
- package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
- package/src/Markdown/Markdown.public.test.ts +126 -3
- package/src/Markdown/Markdown.spec.md +159 -43
- package/src/Markdown/Markdown.test.tsx +11 -0
- package/src/Markdown/Markdown.tsx +414 -135
- package/src/Markdown/ast.test.ts +307 -0
- package/src/Markdown/ast.ts +337 -0
- package/src/Markdown/incremental.test.ts +12 -0
- package/src/Markdown/index.ts +34 -0
- package/src/Markdown/parser.ts +1117 -235
- package/src/Markdown/plugins/index.ts +47 -0
- package/src/Markdown/plugins/protocol.test.tsx +955 -0
- package/src/Markdown/plugins/protocol.ts +1619 -0
- package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
- package/src/Markdown/plugins/semanticFence.ts +424 -0
- package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
- package/src/Markdown/plugins/sourceDecoration.ts +539 -0
- package/src/Markdown/plugins/textTransform.test.ts +467 -0
- package/src/Markdown/plugins/textTransform.ts +554 -0
- package/src/Markdown/url.ts +30 -0
- package/src/Markdown/utils.ts +13 -1
- package/src/Outline/Outline.doc.mjs +7 -5
- package/src/Outline/Outline.spec.md +1 -1
- package/src/Outline/index.ts +1 -0
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +39 -13
- package/src/Outline/useOutlineFromMarkdown.ts +15 -3
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- package/src/RadioList/RadioList.doc.mjs +11 -0
- package/src/RadioList/RadioList.test.tsx +32 -0
- package/src/RadioList/RadioListItem.tsx +25 -1
- package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
- package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
- package/src/SideNav/SideNav.doc.mjs +1 -1
- package/src/SideNav/SideNav.test.tsx +10 -0
- package/src/SideNav/SideNav.tsx +14 -2
- package/src/Slider/Slider.doc.mjs +11 -0
- package/src/Slider/Slider.test.tsx +128 -0
- package/src/Slider/Slider.tsx +25 -7
- package/src/Switch/Switch.doc.mjs +11 -0
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +28 -0
- package/src/TabList/Tab.tsx +35 -7
- package/src/TabList/TabList.doc.mjs +11 -0
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
- package/src/ToggleButton/ToggleButton.test.tsx +133 -0
- package/src/ToggleButton/ToggleButton.tsx +9 -2
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
- package/src/Tokenizer/Tokenizer.spec.md +142 -75
- package/src/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +15 -6
- package/src/Typeahead/Typeahead.test.tsx +53 -0
- package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
- package/src/__tests__/pressState.ts +93 -0
- package/src/utils/interactionOverlay.stylex.ts +20 -0
package/dist/Markdown/parser.js
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file parser.ts
|
|
5
|
-
* @input Markdown string
|
|
6
|
-
* @output
|
|
7
|
-
* helpers
|
|
8
|
-
*
|
|
9
|
-
* Outline's parseOutlineFromMarkdown
|
|
10
|
-
* @position Core parser; consumed by Markdown.tsx and Outline
|
|
5
|
+
* @input Markdown string, released parse options, and optional ordered plugins
|
|
6
|
+
* @output Canonical MDAST-aligned nodes for internal consumers plus unchanged
|
|
7
|
+
* released parser-node projections; shared heading slug helpers
|
|
8
|
+
* @position Core parser and compatibility boundary; consumed by Markdown and Outline
|
|
11
9
|
*/
|
|
12
10
|
|
|
11
|
+
import { getMarkdownAstLegacyCodeLanguage, markMarkdownAstLegacyCodeLanguage } from "./ast.js";
|
|
12
|
+
import { applyMarkdownTransforms, freezeMarkdownPluginData, isMarkdownPluginData, prepareMarkdownPlugins, reportMarkdownPluginFailure } from "./plugins/protocol.js";
|
|
13
|
+
import { isSafeMarkdownParserUrl } from "./url.js";
|
|
14
|
+
|
|
13
15
|
// ---------------------------------------------------------------------------
|
|
14
16
|
// Types
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
@@ -36,6 +38,240 @@
|
|
|
36
38
|
* optional fields without breaking anyone.
|
|
37
39
|
*/
|
|
38
40
|
|
|
41
|
+
function projectRange(position) {
|
|
42
|
+
const start = position?.start.offset;
|
|
43
|
+
const end = position?.end.offset;
|
|
44
|
+
return start == null || end == null ? {} : {
|
|
45
|
+
range: {
|
|
46
|
+
start,
|
|
47
|
+
end
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Canonical nodes always carry Core-authored positions; the released block
|
|
54
|
+
* shape exposes them as `range` only when the caller passed `sourceRanges`,
|
|
55
|
+
* so this projection stays byte-for-byte what it has always returned.
|
|
56
|
+
*
|
|
57
|
+
* An extension node is the one kind the projection passes through instead of
|
|
58
|
+
* rebuilding field by field, so it is the one kind that could carry a
|
|
59
|
+
* canonical `position` out into released output. Strip it unless the caller
|
|
60
|
+
* asked for provenance, in which case the node keeps exactly the `position`
|
|
61
|
+
* it has always exposed.
|
|
62
|
+
*/
|
|
63
|
+
function projectExtensionNode(node, withRanges) {
|
|
64
|
+
if (withRanges || node.position === undefined) {
|
|
65
|
+
return node;
|
|
66
|
+
}
|
|
67
|
+
const {
|
|
68
|
+
position: _canonicalPosition,
|
|
69
|
+
...released
|
|
70
|
+
} = node;
|
|
71
|
+
return released;
|
|
72
|
+
}
|
|
73
|
+
function projectInlineNode(node, cache, withRanges) {
|
|
74
|
+
const cached = cache.get(node);
|
|
75
|
+
if (cached != null) {
|
|
76
|
+
return cached;
|
|
77
|
+
}
|
|
78
|
+
let projected;
|
|
79
|
+
switch (node.type) {
|
|
80
|
+
case 'text':
|
|
81
|
+
projected = {
|
|
82
|
+
type: 'text',
|
|
83
|
+
content: node.value
|
|
84
|
+
};
|
|
85
|
+
break;
|
|
86
|
+
case 'strong':
|
|
87
|
+
projected = {
|
|
88
|
+
type: 'bold',
|
|
89
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
90
|
+
};
|
|
91
|
+
break;
|
|
92
|
+
case 'emphasis':
|
|
93
|
+
projected = {
|
|
94
|
+
type: 'italic',
|
|
95
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
96
|
+
};
|
|
97
|
+
break;
|
|
98
|
+
case 'delete':
|
|
99
|
+
projected = {
|
|
100
|
+
type: 'strikethrough',
|
|
101
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
102
|
+
};
|
|
103
|
+
break;
|
|
104
|
+
case 'inlineCode':
|
|
105
|
+
projected = {
|
|
106
|
+
type: 'code',
|
|
107
|
+
content: node.value
|
|
108
|
+
};
|
|
109
|
+
break;
|
|
110
|
+
case 'inlineMath':
|
|
111
|
+
projected = {
|
|
112
|
+
type: 'math',
|
|
113
|
+
value: node.value
|
|
114
|
+
};
|
|
115
|
+
break;
|
|
116
|
+
case 'link':
|
|
117
|
+
projected = {
|
|
118
|
+
type: 'link',
|
|
119
|
+
href: node.url,
|
|
120
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
121
|
+
};
|
|
122
|
+
break;
|
|
123
|
+
case 'image':
|
|
124
|
+
projected = {
|
|
125
|
+
type: 'image',
|
|
126
|
+
src: node.url,
|
|
127
|
+
alt: node.alt
|
|
128
|
+
};
|
|
129
|
+
break;
|
|
130
|
+
case 'citation':
|
|
131
|
+
projected = {
|
|
132
|
+
type: 'citation',
|
|
133
|
+
sourceId: node.sourceId
|
|
134
|
+
};
|
|
135
|
+
break;
|
|
136
|
+
case 'break':
|
|
137
|
+
projected = {
|
|
138
|
+
type: 'break'
|
|
139
|
+
};
|
|
140
|
+
break;
|
|
141
|
+
case 'extension':
|
|
142
|
+
projected = projectExtensionNode(node, withRanges);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
cache.set(node, projected);
|
|
146
|
+
return projected;
|
|
147
|
+
}
|
|
148
|
+
function projectTableCell(node, cache, withRanges) {
|
|
149
|
+
const cached = cache.get(node);
|
|
150
|
+
if (cached != null) {
|
|
151
|
+
return cached;
|
|
152
|
+
}
|
|
153
|
+
const projected = {
|
|
154
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
155
|
+
};
|
|
156
|
+
cache.set(node, projected);
|
|
157
|
+
return projected;
|
|
158
|
+
}
|
|
159
|
+
function projectListItem(node, cache, withRanges) {
|
|
160
|
+
const cached = cache.get(node);
|
|
161
|
+
if (cached != null) {
|
|
162
|
+
return cached;
|
|
163
|
+
}
|
|
164
|
+
const projected = {
|
|
165
|
+
checked: node.checked,
|
|
166
|
+
children: node.children.map(child => projectBlockNode(child, cache, withRanges))
|
|
167
|
+
};
|
|
168
|
+
cache.set(node, projected);
|
|
169
|
+
return projected;
|
|
170
|
+
}
|
|
171
|
+
function projectBlockNode(node, cache, withRanges) {
|
|
172
|
+
const cached = cache.get(node);
|
|
173
|
+
if (cached != null) {
|
|
174
|
+
return cached;
|
|
175
|
+
}
|
|
176
|
+
const metadata = withRanges ? projectRange(node.position) : {};
|
|
177
|
+
let projected;
|
|
178
|
+
switch (node.type) {
|
|
179
|
+
case 'heading':
|
|
180
|
+
projected = {
|
|
181
|
+
type: 'heading',
|
|
182
|
+
level: node.depth,
|
|
183
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges)),
|
|
184
|
+
...metadata
|
|
185
|
+
};
|
|
186
|
+
break;
|
|
187
|
+
case 'paragraph':
|
|
188
|
+
projected = {
|
|
189
|
+
type: 'paragraph',
|
|
190
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges)),
|
|
191
|
+
...metadata
|
|
192
|
+
};
|
|
193
|
+
break;
|
|
194
|
+
case 'code':
|
|
195
|
+
projected = {
|
|
196
|
+
type: 'codeblock',
|
|
197
|
+
language: getMarkdownAstLegacyCodeLanguage(node) ?? 'plaintext',
|
|
198
|
+
content: node.value,
|
|
199
|
+
...metadata
|
|
200
|
+
};
|
|
201
|
+
break;
|
|
202
|
+
case 'math':
|
|
203
|
+
projected = {
|
|
204
|
+
type: 'math',
|
|
205
|
+
value: node.value,
|
|
206
|
+
...metadata
|
|
207
|
+
};
|
|
208
|
+
break;
|
|
209
|
+
case 'blockquote':
|
|
210
|
+
projected = {
|
|
211
|
+
type: 'blockquote',
|
|
212
|
+
children: node.children.map(child => projectBlockNode(child, cache, withRanges)),
|
|
213
|
+
...metadata
|
|
214
|
+
};
|
|
215
|
+
break;
|
|
216
|
+
case 'list':
|
|
217
|
+
projected = {
|
|
218
|
+
type: 'list',
|
|
219
|
+
ordered: node.ordered,
|
|
220
|
+
start: node.start,
|
|
221
|
+
delimiter: node.delimiter,
|
|
222
|
+
loose: node.spread,
|
|
223
|
+
items: node.children.map(item => projectListItem(item, cache, withRanges)),
|
|
224
|
+
...metadata
|
|
225
|
+
};
|
|
226
|
+
break;
|
|
227
|
+
case 'table':
|
|
228
|
+
{
|
|
229
|
+
const [header = {
|
|
230
|
+
type: 'tableRow',
|
|
231
|
+
children: []
|
|
232
|
+
}, ...rows] = node.children;
|
|
233
|
+
projected = {
|
|
234
|
+
type: 'table',
|
|
235
|
+
headers: header.children.map(cell => projectTableCell(cell, cache, withRanges)),
|
|
236
|
+
alignments: [...node.align],
|
|
237
|
+
rows: rows.map(row => row.children.map(cell => projectTableCell(cell, cache, withRanges))),
|
|
238
|
+
...metadata
|
|
239
|
+
};
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
case 'thematicBreak':
|
|
243
|
+
projected = {
|
|
244
|
+
type: 'hr',
|
|
245
|
+
...metadata
|
|
246
|
+
};
|
|
247
|
+
break;
|
|
248
|
+
case 'image':
|
|
249
|
+
projected = {
|
|
250
|
+
type: 'image',
|
|
251
|
+
alt: node.alt,
|
|
252
|
+
src: node.url,
|
|
253
|
+
...metadata
|
|
254
|
+
};
|
|
255
|
+
break;
|
|
256
|
+
case 'extension':
|
|
257
|
+
projected = projectExtensionNode(node, withRanges);
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
cache.set(node, projected);
|
|
261
|
+
return projected;
|
|
262
|
+
}
|
|
263
|
+
function projectInlineNodes(nodes, withRanges, cache = new WeakMap()) {
|
|
264
|
+
return nodes.map(node => projectInlineNode(node, cache, withRanges));
|
|
265
|
+
}
|
|
266
|
+
function projectMarkdownRoot(root, withRanges, cache = new WeakMap()) {
|
|
267
|
+
return root.children.map(node => projectBlockNode(node, cache, withRanges));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Whether a caller asked for the released `range` field on its blocks. */
|
|
271
|
+
function wantsLegacyRanges(arg) {
|
|
272
|
+
return arg != null && typeof arg.has !== 'function' && arg.sourceRanges === true;
|
|
273
|
+
}
|
|
274
|
+
|
|
39
275
|
// ---------------------------------------------------------------------------
|
|
40
276
|
// Parse options
|
|
41
277
|
// ---------------------------------------------------------------------------
|
|
@@ -56,13 +292,37 @@
|
|
|
56
292
|
* from silently widening an exhaustive node switch.
|
|
57
293
|
*/
|
|
58
294
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Every resolved options object is built here, so all of them share one key
|
|
297
|
+
* set in one order — and therefore one hidden class. The parser reads these
|
|
298
|
+
* fields in its hottest loops; letting an omitted-options parse and an
|
|
299
|
+
* options-bag parse produce differently-shaped objects makes those reads
|
|
300
|
+
* polymorphic in any process that does both, which is exactly what a caller
|
|
301
|
+
* comparing an empty pipeline against a configured one does.
|
|
302
|
+
*/
|
|
303
|
+
function makeResolvedOptions(fields) {
|
|
304
|
+
return {
|
|
305
|
+
sourceIds: fields.sourceIds,
|
|
306
|
+
autolink: fields.autolink,
|
|
307
|
+
math: fields.math,
|
|
308
|
+
sourceRanges: fields.sourceRanges,
|
|
309
|
+
astPositions: fields.astPositions ?? true,
|
|
310
|
+
plugins: fields.plugins,
|
|
311
|
+
isFinal: fields.isFinal,
|
|
312
|
+
allowBlockSyntax: fields.allowBlockSyntax ?? true,
|
|
313
|
+
baseOffset: fields.baseOffset,
|
|
314
|
+
linkDefs: fields.linkDefs
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const EMPTY_OPTS = makeResolvedOptions({
|
|
318
|
+
isFinal: true
|
|
319
|
+
});
|
|
320
|
+
const EMPTY_INCREMENTAL_OPTS = makeResolvedOptions({
|
|
321
|
+
isFinal: false
|
|
322
|
+
});
|
|
323
|
+
function resolveOptions(arg, incremental = false) {
|
|
64
324
|
if (arg == null) {
|
|
65
|
-
return EMPTY_OPTS;
|
|
325
|
+
return incremental ? EMPTY_INCREMENTAL_OPTS : EMPTY_OPTS;
|
|
66
326
|
}
|
|
67
327
|
// Duck-type the legacy `ReadonlySet<string>` form: any object whose
|
|
68
328
|
// `.has` is callable is treated as the legacy sourceIds set. This is
|
|
@@ -70,18 +330,20 @@ function resolveOptions(arg) {
|
|
|
70
330
|
// or polyfilled `ReadonlySet` implementations as a `ParseOptions` bag
|
|
71
331
|
// and silently lose citation resolution.
|
|
72
332
|
if (typeof arg.has === 'function') {
|
|
73
|
-
return {
|
|
333
|
+
return makeResolvedOptions({
|
|
74
334
|
sourceIds: arg,
|
|
75
|
-
|
|
76
|
-
};
|
|
335
|
+
isFinal: !incremental
|
|
336
|
+
});
|
|
77
337
|
}
|
|
78
338
|
const opts = arg;
|
|
79
|
-
return {
|
|
339
|
+
return makeResolvedOptions({
|
|
80
340
|
sourceIds: opts.sourceIds,
|
|
81
341
|
autolink: opts.autolink,
|
|
82
342
|
math: opts.math === true ? true : undefined,
|
|
83
|
-
sourceRanges: opts.sourceRanges
|
|
84
|
-
|
|
343
|
+
sourceRanges: opts.sourceRanges,
|
|
344
|
+
plugins: opts.plugins != null && opts.plugins.length > 0 ? prepareMarkdownPlugins(opts.plugins) : undefined,
|
|
345
|
+
isFinal: incremental ? opts.isFinal === true : true
|
|
346
|
+
});
|
|
85
347
|
}
|
|
86
348
|
|
|
87
349
|
// ---------------------------------------------------------------------------
|
|
@@ -325,6 +587,11 @@ function extractLinkDefinitions(input, math = false) {
|
|
|
325
587
|
};
|
|
326
588
|
}
|
|
327
589
|
|
|
590
|
+
/** Order-independent signature of a citation-source set, for cache checks. */
|
|
591
|
+
function sourceIdsSignature(sourceIds) {
|
|
592
|
+
return sourceIds == null || sourceIds.size === 0 ? '' : [...sourceIds].sort().join('\u0000');
|
|
593
|
+
}
|
|
594
|
+
|
|
328
595
|
/** Order-independent signature of a link-definition set, for cache checks. */
|
|
329
596
|
function linkDefsSignature(defs) {
|
|
330
597
|
if (defs.size === 0) {
|
|
@@ -339,6 +606,12 @@ function linkDefsSignature(defs) {
|
|
|
339
606
|
* Returns the node plus the index just past the reference, or null when it is
|
|
340
607
|
* not a resolvable reference (caller falls through to literal handling).
|
|
341
608
|
*/
|
|
609
|
+
function protectedInlineOptions(opts) {
|
|
610
|
+
return opts.plugins == null ? opts : {
|
|
611
|
+
...opts,
|
|
612
|
+
plugins: undefined
|
|
613
|
+
};
|
|
614
|
+
}
|
|
342
615
|
function matchReferenceLink(text, start, linkDefs, opts) {
|
|
343
616
|
const textClose = text.indexOf(']', start + 1);
|
|
344
617
|
if (textClose === -1) {
|
|
@@ -355,12 +628,12 @@ function matchReferenceLink(text, start, linkDefs, opts) {
|
|
|
355
628
|
// matches nothing.
|
|
356
629
|
const label = rawLabel === '' ? linkText : rawLabel;
|
|
357
630
|
const href = linkDefs.get(normalizeLinkLabel(label));
|
|
358
|
-
if (href != null &&
|
|
631
|
+
if (href != null && isSafeMarkdownParserUrl(href)) {
|
|
359
632
|
return {
|
|
360
633
|
node: {
|
|
361
634
|
type: 'link',
|
|
362
|
-
href,
|
|
363
|
-
children: parseInlineImpl(linkText, opts)
|
|
635
|
+
url: href,
|
|
636
|
+
children: parseInlineImpl(linkText, protectedInlineOptions(opts))
|
|
364
637
|
},
|
|
365
638
|
end: labelClose + 1
|
|
366
639
|
};
|
|
@@ -374,14 +647,14 @@ function matchReferenceLink(text, start, linkDefs, opts) {
|
|
|
374
647
|
return null;
|
|
375
648
|
}
|
|
376
649
|
const href = linkDefs.get(normalizeLinkLabel(linkText));
|
|
377
|
-
if (href == null || !
|
|
650
|
+
if (href == null || !isSafeMarkdownParserUrl(href)) {
|
|
378
651
|
return null;
|
|
379
652
|
}
|
|
380
653
|
return {
|
|
381
654
|
node: {
|
|
382
655
|
type: 'link',
|
|
383
|
-
href,
|
|
384
|
-
children: parseInlineImpl(linkText, opts)
|
|
656
|
+
url: href,
|
|
657
|
+
children: parseInlineImpl(linkText, protectedInlineOptions(opts))
|
|
385
658
|
},
|
|
386
659
|
end: textClose + 1
|
|
387
660
|
};
|
|
@@ -400,11 +673,11 @@ function matchReferenceImage(text, start, linkDefs) {
|
|
|
400
673
|
const rawLabel = text.slice(altClose + 2, labelClose);
|
|
401
674
|
const label = rawLabel === '' ? alt : rawLabel;
|
|
402
675
|
const src = linkDefs.get(normalizeLinkLabel(label));
|
|
403
|
-
if (src != null &&
|
|
676
|
+
if (src != null && isSafeMarkdownParserUrl(src)) {
|
|
404
677
|
return {
|
|
405
678
|
node: {
|
|
406
679
|
type: 'image',
|
|
407
|
-
src,
|
|
680
|
+
url: src,
|
|
408
681
|
alt
|
|
409
682
|
},
|
|
410
683
|
end: labelClose + 1
|
|
@@ -417,13 +690,13 @@ function matchReferenceImage(text, start, linkDefs) {
|
|
|
417
690
|
return null;
|
|
418
691
|
}
|
|
419
692
|
const src = linkDefs.get(normalizeLinkLabel(alt));
|
|
420
|
-
if (src == null || !
|
|
693
|
+
if (src == null || !isSafeMarkdownParserUrl(src)) {
|
|
421
694
|
return null;
|
|
422
695
|
}
|
|
423
696
|
return {
|
|
424
697
|
node: {
|
|
425
698
|
type: 'image',
|
|
426
|
-
src,
|
|
699
|
+
url: src,
|
|
427
700
|
alt
|
|
428
701
|
},
|
|
429
702
|
end: altClose + 1
|
|
@@ -500,26 +773,6 @@ function findInlineMathEnd(text, start) {
|
|
|
500
773
|
}
|
|
501
774
|
|
|
502
775
|
// ---------------------------------------------------------------------------
|
|
503
|
-
// URL scheme sanitization
|
|
504
|
-
// ---------------------------------------------------------------------------
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Reject URLs with dangerous schemes (javascript:, vbscript:, data:) that
|
|
508
|
-
* could execute arbitrary code when rendered as link hrefs or image srcs.
|
|
509
|
-
* Returns true if the URL is safe to use, false otherwise.
|
|
510
|
-
*/
|
|
511
|
-
function isSafeUrl(url) {
|
|
512
|
-
// Trim and collapse whitespace/control chars that browsers tolerate but
|
|
513
|
-
// could bypass a naive prefix check (e.g. "java\nscript:alert(1)").
|
|
514
|
-
// eslint-disable-next-line no-control-regex -- control chars are the bypass
|
|
515
|
-
const normalized = url.replace(/[\x00-\x1f\x7f]/g, '').trim();
|
|
516
|
-
const lower = normalized.toLowerCase();
|
|
517
|
-
if (lower.startsWith('javascript:') || lower.startsWith('vbscript:') || lower.startsWith('data:text/html')) {
|
|
518
|
-
return false;
|
|
519
|
-
}
|
|
520
|
-
return true;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
776
|
// ---------------------------------------------------------------------------
|
|
524
777
|
// Inline parser
|
|
525
778
|
// ---------------------------------------------------------------------------
|
|
@@ -570,8 +823,127 @@ function matchBracketCitation(text, i, opts) {
|
|
|
570
823
|
end: closeIndex + 1
|
|
571
824
|
};
|
|
572
825
|
}
|
|
826
|
+
function currentLineStart(source, offset) {
|
|
827
|
+
return source.lastIndexOf('\n', offset - 1) + 1;
|
|
828
|
+
}
|
|
829
|
+
function matchExtensionSyntax(source, offset, context, opts) {
|
|
830
|
+
const byFirstCharacter = context === 'inline' ? opts.plugins?.inlineByFirstCharacter : opts.allowBlockSyntax === false ? undefined : opts.plugins?.blockByFirstCharacter;
|
|
831
|
+
const candidates = byFirstCharacter?.get(source[offset]);
|
|
832
|
+
if (candidates == null) {
|
|
833
|
+
return {
|
|
834
|
+
status: 'none'
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const seen = new Set();
|
|
838
|
+
for (const candidate of candidates) {
|
|
839
|
+
if (seen.has(candidate)) {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
seen.add(candidate);
|
|
843
|
+
let fullPrefix = false;
|
|
844
|
+
for (const prefix of candidate.contribution.startsWith) {
|
|
845
|
+
if (source.startsWith(prefix, offset)) {
|
|
846
|
+
fullPrefix = true;
|
|
847
|
+
break;
|
|
848
|
+
}
|
|
849
|
+
if (!opts.isFinal) {
|
|
850
|
+
const remaining = source.slice(offset);
|
|
851
|
+
if (remaining.length < prefix.length && prefix.startsWith(remaining)) {
|
|
852
|
+
return {
|
|
853
|
+
status: 'defer'
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (!fullPrefix) {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
const end = Math.min(source.length, offset + candidate.contribution.maxSpan);
|
|
862
|
+
let result;
|
|
863
|
+
try {
|
|
864
|
+
result = candidate.contribution.tokenize({
|
|
865
|
+
source: end === source.length ? source : source.slice(0, end),
|
|
866
|
+
offset,
|
|
867
|
+
end,
|
|
868
|
+
isFinal: opts.isFinal,
|
|
869
|
+
context,
|
|
870
|
+
lineStart: currentLineStart(source, offset),
|
|
871
|
+
column: offset - currentLineStart(source, offset)
|
|
872
|
+
});
|
|
873
|
+
} catch (error) {
|
|
874
|
+
reportMarkdownPluginFailure(candidate.pluginName, 'syntax', error);
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (result == null || typeof result !== 'object' || typeof result.then === 'function') {
|
|
878
|
+
reportMarkdownPluginFailure(candidate.pluginName, 'syntax', new TypeError('Tokenizer returned an invalid result'));
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
if (result.status === 'no-match') {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if (result.status === 'defer') {
|
|
885
|
+
return !opts.isFinal && end === source.length && source.length - offset < candidate.contribution.maxSpan ? {
|
|
886
|
+
status: 'defer'
|
|
887
|
+
} : {
|
|
888
|
+
status: 'none'
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
if (result.status !== 'match' || !Number.isInteger(result.end) || result.end <= offset || result.end > end || result.node.type !== 'extension' || result.node.plugin !== candidate.pluginName || result.node.display !== context || result.node.name.trim() === '' || !isMarkdownPluginData(result.node.data) || !opts.plugins?.renderers.has(`${candidate.pluginName}\0${result.node.name}`)) {
|
|
892
|
+
reportMarkdownPluginFailure(candidate.pluginName, 'syntax', new TypeError('Tokenizer returned an invalid extension node'));
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
const rawNode = result.node;
|
|
896
|
+
const {
|
|
897
|
+
source: _ignoredSource,
|
|
898
|
+
position: _ignoredPosition,
|
|
899
|
+
...safeNode
|
|
900
|
+
} = rawNode;
|
|
901
|
+
const absoluteStart = (opts.baseOffset ?? 0) + offset;
|
|
902
|
+
const absoluteEnd = (opts.baseOffset ?? 0) + result.end;
|
|
903
|
+
return {
|
|
904
|
+
status: 'match',
|
|
905
|
+
end: result.end,
|
|
906
|
+
node: Object.freeze({
|
|
907
|
+
...safeNode,
|
|
908
|
+
data: freezeMarkdownPluginData(result.node.data),
|
|
909
|
+
source: source.slice(offset, result.end),
|
|
910
|
+
...(opts.sourceRanges && context === 'block' ? {
|
|
911
|
+
position: Object.freeze({
|
|
912
|
+
start: Object.freeze({
|
|
913
|
+
offset: absoluteStart
|
|
914
|
+
}),
|
|
915
|
+
end: Object.freeze({
|
|
916
|
+
offset: absoluteEnd
|
|
917
|
+
})
|
|
918
|
+
})
|
|
919
|
+
} : null)
|
|
920
|
+
})
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
return {
|
|
924
|
+
status: 'none'
|
|
925
|
+
};
|
|
926
|
+
}
|
|
573
927
|
export function parseInline(text, arg) {
|
|
574
|
-
return
|
|
928
|
+
return projectInlineNodes(parseInlineAst(text, arg), wantsLegacyRanges(arg));
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/** @internal Canonical inline parse used by Markdown rendering. */
|
|
932
|
+
export function parseInlineAst(text, arg) {
|
|
933
|
+
const opts = resolveOptions(arg);
|
|
934
|
+
const nodes = parseInlineEntry(text, opts);
|
|
935
|
+
if ((opts.plugins?.transforms.length ?? 0) === 0) {
|
|
936
|
+
return nodes;
|
|
937
|
+
}
|
|
938
|
+
const transformed = applyMarkdownTransforms({
|
|
939
|
+
type: 'root',
|
|
940
|
+
children: [{
|
|
941
|
+
type: 'paragraph',
|
|
942
|
+
children: nodes
|
|
943
|
+
}]
|
|
944
|
+
}, opts.plugins, text, opts.isFinal, 'inline');
|
|
945
|
+
const paragraph = transformed.children[0];
|
|
946
|
+
return paragraph?.type === 'paragraph' ? [...paragraph.children] : nodes;
|
|
575
947
|
}
|
|
576
948
|
|
|
577
949
|
/**
|
|
@@ -589,13 +961,18 @@ function parseInlineEntry(text, opts) {
|
|
|
589
961
|
}
|
|
590
962
|
function parseInlineImpl(text, opts) {
|
|
591
963
|
const nodes = [];
|
|
964
|
+
// Only a plugin that actually contributes INLINE syntax may cost anything
|
|
965
|
+
// per source position. A transform-only list contributes none, so it takes
|
|
966
|
+
// the same path as an omitted or empty one: no candidate probe per
|
|
967
|
+
// position, and no map lookup per character in the plain-text scan below.
|
|
968
|
+
const inlineExtensionStarts = opts.plugins != null && opts.plugins.inlineByFirstCharacter.size > 0 ? opts.plugins.inlineByFirstCharacter : undefined;
|
|
592
969
|
let i = 0;
|
|
593
970
|
while (i < text.length) {
|
|
594
971
|
// --- Escape ---
|
|
595
972
|
if (text[i] === '\\' && i + 1 < text.length) {
|
|
596
973
|
nodes.push({
|
|
597
974
|
type: 'text',
|
|
598
|
-
|
|
975
|
+
value: text[i + 1]
|
|
599
976
|
});
|
|
600
977
|
i += 2;
|
|
601
978
|
continue;
|
|
@@ -608,8 +985,8 @@ function parseInlineImpl(text, opts) {
|
|
|
608
985
|
const closeIndex = text.indexOf('`'.repeat(tickCount), openIndex);
|
|
609
986
|
if (closeIndex !== -1) {
|
|
610
987
|
nodes.push({
|
|
611
|
-
type: '
|
|
612
|
-
|
|
988
|
+
type: 'inlineCode',
|
|
989
|
+
value: text.slice(openIndex, closeIndex)
|
|
613
990
|
});
|
|
614
991
|
i = closeIndex + tickCount;
|
|
615
992
|
continue;
|
|
@@ -621,7 +998,7 @@ function parseInlineImpl(text, opts) {
|
|
|
621
998
|
const closeIndex = findInlineMathEnd(text, i);
|
|
622
999
|
if (closeIndex !== -1) {
|
|
623
1000
|
nodes.push({
|
|
624
|
-
type: '
|
|
1001
|
+
type: 'inlineMath',
|
|
625
1002
|
value: text.slice(i + 1, closeIndex)
|
|
626
1003
|
});
|
|
627
1004
|
i = closeIndex + 1;
|
|
@@ -649,16 +1026,16 @@ function parseInlineImpl(text, opts) {
|
|
|
649
1026
|
const srcClose = findClosingParen(text, altClose + 2);
|
|
650
1027
|
if (srcClose !== -1) {
|
|
651
1028
|
const src = text.slice(altClose + 2, srcClose);
|
|
652
|
-
if (!
|
|
1029
|
+
if (!isSafeMarkdownParserUrl(src)) {
|
|
653
1030
|
// Dangerous scheme — emit as plain text.
|
|
654
1031
|
nodes.push({
|
|
655
1032
|
type: 'text',
|
|
656
|
-
|
|
1033
|
+
value: text.slice(i, srcClose + 1)
|
|
657
1034
|
});
|
|
658
1035
|
} else {
|
|
659
1036
|
nodes.push({
|
|
660
1037
|
type: 'image',
|
|
661
|
-
src,
|
|
1038
|
+
url: src,
|
|
662
1039
|
alt: text.slice(i + 2, altClose)
|
|
663
1040
|
});
|
|
664
1041
|
}
|
|
@@ -698,17 +1075,17 @@ function parseInlineImpl(text, opts) {
|
|
|
698
1075
|
const urlClose = findClosingParen(text, textClose + 2);
|
|
699
1076
|
if (urlClose !== -1) {
|
|
700
1077
|
const href = text.slice(textClose + 2, urlClose);
|
|
701
|
-
if (!
|
|
1078
|
+
if (!isSafeMarkdownParserUrl(href)) {
|
|
702
1079
|
// Dangerous scheme — emit as plain text instead of a link.
|
|
703
1080
|
nodes.push({
|
|
704
1081
|
type: 'text',
|
|
705
|
-
|
|
1082
|
+
value: text.slice(i, urlClose + 1)
|
|
706
1083
|
});
|
|
707
1084
|
} else {
|
|
708
1085
|
nodes.push({
|
|
709
1086
|
type: 'link',
|
|
710
|
-
href,
|
|
711
|
-
children: parseInlineImpl(text.slice(i + 1, textClose), opts)
|
|
1087
|
+
url: href,
|
|
1088
|
+
children: parseInlineImpl(text.slice(i + 1, textClose), protectedInlineOptions(opts))
|
|
712
1089
|
});
|
|
713
1090
|
}
|
|
714
1091
|
i = urlClose + 1;
|
|
@@ -737,9 +1114,9 @@ function parseInlineImpl(text, opts) {
|
|
|
737
1114
|
const closeIndex = text.indexOf(marker, i + 3);
|
|
738
1115
|
if (closeIndex !== -1 && (!isUnderscore || !isWordChar(text[closeIndex + 3]))) {
|
|
739
1116
|
nodes.push({
|
|
740
|
-
type: '
|
|
1117
|
+
type: 'strong',
|
|
741
1118
|
children: [{
|
|
742
|
-
type: '
|
|
1119
|
+
type: 'emphasis',
|
|
743
1120
|
children: parseInlineImpl(text.slice(i + 3, closeIndex), opts)
|
|
744
1121
|
}]
|
|
745
1122
|
});
|
|
@@ -759,7 +1136,7 @@ function parseInlineImpl(text, opts) {
|
|
|
759
1136
|
const closeIndex = text.indexOf(marker, i + 2);
|
|
760
1137
|
if (closeIndex !== -1 && (!isUnderscore || !isWordChar(text[closeIndex + 2]))) {
|
|
761
1138
|
nodes.push({
|
|
762
|
-
type: '
|
|
1139
|
+
type: 'strong',
|
|
763
1140
|
children: parseInlineImpl(text.slice(i + 2, closeIndex), opts)
|
|
764
1141
|
});
|
|
765
1142
|
i = closeIndex + 2;
|
|
@@ -773,7 +1150,7 @@ function parseInlineImpl(text, opts) {
|
|
|
773
1150
|
const closeIndex = text.indexOf('~~', i + 2);
|
|
774
1151
|
if (closeIndex !== -1) {
|
|
775
1152
|
nodes.push({
|
|
776
|
-
type: '
|
|
1153
|
+
type: 'delete',
|
|
777
1154
|
children: parseInlineImpl(text.slice(i + 2, closeIndex), opts)
|
|
778
1155
|
});
|
|
779
1156
|
i = closeIndex + 2;
|
|
@@ -790,7 +1167,7 @@ function parseInlineImpl(text, opts) {
|
|
|
790
1167
|
const closeIndex = text.indexOf(text[i], i + 1);
|
|
791
1168
|
if (closeIndex !== -1 && closeIndex > i + 1 && (!isUnderscore || !isWordChar(text[closeIndex + 1]))) {
|
|
792
1169
|
nodes.push({
|
|
793
|
-
type: '
|
|
1170
|
+
type: 'emphasis',
|
|
794
1171
|
children: parseInlineImpl(text.slice(i + 1, closeIndex), opts)
|
|
795
1172
|
});
|
|
796
1173
|
i = closeIndex + 1;
|
|
@@ -799,9 +1176,22 @@ function parseInlineImpl(text, opts) {
|
|
|
799
1176
|
}
|
|
800
1177
|
}
|
|
801
1178
|
|
|
1179
|
+
// --- Extension syntax (built-ins and protected contexts win) ---
|
|
1180
|
+
if (inlineExtensionStarts !== undefined) {
|
|
1181
|
+
const extension = matchExtensionSyntax(text, i, 'inline', opts);
|
|
1182
|
+
if (extension.status === 'match') {
|
|
1183
|
+
nodes.push(extension.node);
|
|
1184
|
+
i = extension.end;
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
if (extension.status === 'defer') {
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
802
1192
|
// --- Plain text (with line-break detection) ---
|
|
803
1193
|
let end = i + 1;
|
|
804
|
-
while (end < text.length && !'*_~`[!\\\n\u3010'.includes(text[end]) && !(opts.math && text[end] === '$')) {
|
|
1194
|
+
while (end < text.length && !'*_~`[!\\\n\u3010'.includes(text[end]) && !(opts.math && text[end] === '$') && (inlineExtensionStarts === undefined || !inlineExtensionStarts.has(text[end]))) {
|
|
805
1195
|
end++;
|
|
806
1196
|
}
|
|
807
1197
|
const content = text.slice(i, end);
|
|
@@ -813,11 +1203,14 @@ function parseInlineImpl(text, opts) {
|
|
|
813
1203
|
if (trimmed.length > 0) {
|
|
814
1204
|
const last = nodes[nodes.length - 1];
|
|
815
1205
|
if (last?.type === 'text') {
|
|
816
|
-
|
|
1206
|
+
nodes[nodes.length - 1] = {
|
|
1207
|
+
...last,
|
|
1208
|
+
value: last.value + trimmed
|
|
1209
|
+
};
|
|
817
1210
|
} else {
|
|
818
1211
|
nodes.push({
|
|
819
1212
|
type: 'text',
|
|
820
|
-
|
|
1213
|
+
value: trimmed
|
|
821
1214
|
});
|
|
822
1215
|
}
|
|
823
1216
|
}
|
|
@@ -830,11 +1223,14 @@ function parseInlineImpl(text, opts) {
|
|
|
830
1223
|
}
|
|
831
1224
|
const last = nodes[nodes.length - 1];
|
|
832
1225
|
if (last?.type === 'text') {
|
|
833
|
-
|
|
1226
|
+
nodes[nodes.length - 1] = {
|
|
1227
|
+
...last,
|
|
1228
|
+
value: last.value + content
|
|
1229
|
+
};
|
|
834
1230
|
} else {
|
|
835
1231
|
nodes.push({
|
|
836
1232
|
type: 'text',
|
|
837
|
-
content
|
|
1233
|
+
value: content
|
|
838
1234
|
});
|
|
839
1235
|
}
|
|
840
1236
|
i = end;
|
|
@@ -938,7 +1334,7 @@ function scanAutolinksInText(text) {
|
|
|
938
1334
|
while ((m = re.exec(text)) !== null) {
|
|
939
1335
|
const url = m[1];
|
|
940
1336
|
// Skip dangerous URL schemes (javascript:, vbscript:, data:text/html)
|
|
941
|
-
if (!
|
|
1337
|
+
if (!isSafeMarkdownParserUrl(url)) {
|
|
942
1338
|
continue;
|
|
943
1339
|
}
|
|
944
1340
|
matches.push({
|
|
@@ -1063,7 +1459,7 @@ function splitTextOnAutolinks(content) {
|
|
|
1063
1459
|
if (matches.length === 0) {
|
|
1064
1460
|
return [{
|
|
1065
1461
|
type: 'text',
|
|
1066
|
-
content
|
|
1462
|
+
value: content
|
|
1067
1463
|
}];
|
|
1068
1464
|
}
|
|
1069
1465
|
const out = [];
|
|
@@ -1072,15 +1468,15 @@ function splitTextOnAutolinks(content) {
|
|
|
1072
1468
|
if (m.start > cursor) {
|
|
1073
1469
|
out.push({
|
|
1074
1470
|
type: 'text',
|
|
1075
|
-
|
|
1471
|
+
value: content.slice(cursor, m.start)
|
|
1076
1472
|
});
|
|
1077
1473
|
}
|
|
1078
1474
|
out.push({
|
|
1079
1475
|
type: 'link',
|
|
1080
|
-
|
|
1476
|
+
url: m.href,
|
|
1081
1477
|
children: [{
|
|
1082
1478
|
type: 'text',
|
|
1083
|
-
|
|
1479
|
+
value: m.display
|
|
1084
1480
|
}]
|
|
1085
1481
|
});
|
|
1086
1482
|
cursor = m.end;
|
|
@@ -1088,7 +1484,7 @@ function splitTextOnAutolinks(content) {
|
|
|
1088
1484
|
if (cursor < content.length) {
|
|
1089
1485
|
out.push({
|
|
1090
1486
|
type: 'text',
|
|
1091
|
-
|
|
1487
|
+
value: content.slice(cursor)
|
|
1092
1488
|
});
|
|
1093
1489
|
}
|
|
1094
1490
|
return out;
|
|
@@ -1106,16 +1502,19 @@ function transformAutolinks(nodes) {
|
|
|
1106
1502
|
const out = [];
|
|
1107
1503
|
for (const node of nodes) {
|
|
1108
1504
|
if (node.type === 'text') {
|
|
1109
|
-
const split = splitTextOnAutolinks(node.
|
|
1505
|
+
const split = splitTextOnAutolinks(node.value);
|
|
1110
1506
|
for (const seg of split) {
|
|
1111
1507
|
const last = out[out.length - 1];
|
|
1112
1508
|
if (seg.type === 'text' && last?.type === 'text') {
|
|
1113
|
-
|
|
1509
|
+
out[out.length - 1] = {
|
|
1510
|
+
...last,
|
|
1511
|
+
value: last.value + seg.value
|
|
1512
|
+
};
|
|
1114
1513
|
} else {
|
|
1115
1514
|
out.push(seg);
|
|
1116
1515
|
}
|
|
1117
1516
|
}
|
|
1118
|
-
} else if (node.type === '
|
|
1517
|
+
} else if (node.type === 'strong' || node.type === 'emphasis' || node.type === 'delete') {
|
|
1119
1518
|
out.push({
|
|
1120
1519
|
...node,
|
|
1121
1520
|
children: transformAutolinks(node.children)
|
|
@@ -1200,10 +1599,19 @@ function isTableSeparator(line) {
|
|
|
1200
1599
|
* offset into it would not address the document.
|
|
1201
1600
|
*/
|
|
1202
1601
|
function nested(opts) {
|
|
1203
|
-
|
|
1602
|
+
const nestedOptions = opts.allowBlockSyntax === false ? opts : {
|
|
1204
1603
|
...opts,
|
|
1205
|
-
|
|
1206
|
-
}
|
|
1604
|
+
allowBlockSyntax: false
|
|
1605
|
+
};
|
|
1606
|
+
return nestedOptions.sourceRanges || nestedOptions.astPositions ? {
|
|
1607
|
+
...nestedOptions,
|
|
1608
|
+
sourceRanges: false,
|
|
1609
|
+
astPositions: false
|
|
1610
|
+
} : nestedOptions;
|
|
1611
|
+
}
|
|
1612
|
+
function blockExtensionColumn(line) {
|
|
1613
|
+
const indentation = line.length - line.trimStart().length;
|
|
1614
|
+
return indentation <= 3 ? indentation : null;
|
|
1207
1615
|
}
|
|
1208
1616
|
|
|
1209
1617
|
/**
|
|
@@ -1271,6 +1679,7 @@ function splitTableRow(line) {
|
|
|
1271
1679
|
}
|
|
1272
1680
|
function parseTable(lines, lineIndex, opts) {
|
|
1273
1681
|
const headers = splitTableRow(lines[lineIndex]).map(cell => ({
|
|
1682
|
+
type: 'tableCell',
|
|
1274
1683
|
children: parseInlineEntry(cell, opts)
|
|
1275
1684
|
}));
|
|
1276
1685
|
const alignments = splitTableRow(lines[lineIndex + 1]).map(cell => {
|
|
@@ -1282,17 +1691,24 @@ function parseTable(lines, lineIndex, opts) {
|
|
|
1282
1691
|
const rows = [];
|
|
1283
1692
|
let rowIndex = lineIndex + 2;
|
|
1284
1693
|
while (rowIndex < lines.length && lines[rowIndex].includes('|') && lines[rowIndex].trim() !== '') {
|
|
1285
|
-
rows.push(
|
|
1286
|
-
|
|
1287
|
-
|
|
1694
|
+
rows.push({
|
|
1695
|
+
type: 'tableRow',
|
|
1696
|
+
children: splitTableRow(lines[rowIndex]).map(cell => ({
|
|
1697
|
+
type: 'tableCell',
|
|
1698
|
+
children: parseInlineEntry(cell, opts)
|
|
1699
|
+
}))
|
|
1700
|
+
});
|
|
1288
1701
|
rowIndex++;
|
|
1289
1702
|
}
|
|
1703
|
+
const header = {
|
|
1704
|
+
type: 'tableRow',
|
|
1705
|
+
children: headers
|
|
1706
|
+
};
|
|
1290
1707
|
return {
|
|
1291
1708
|
node: {
|
|
1292
1709
|
type: 'table',
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
rows
|
|
1710
|
+
align: alignments,
|
|
1711
|
+
children: [header, ...rows]
|
|
1296
1712
|
},
|
|
1297
1713
|
nextIndex: rowIndex
|
|
1298
1714
|
};
|
|
@@ -1335,6 +1751,7 @@ function parseList(lines, startIndex, ordered, opts) {
|
|
|
1335
1751
|
itemText += '\n' + deindented.join('\n');
|
|
1336
1752
|
}
|
|
1337
1753
|
items.push({
|
|
1754
|
+
type: 'listItem',
|
|
1338
1755
|
checked,
|
|
1339
1756
|
children: parseMarkdownImpl(itemText, nested(opts))
|
|
1340
1757
|
});
|
|
@@ -1351,15 +1768,16 @@ function parseList(lines, startIndex, ordered, opts) {
|
|
|
1351
1768
|
index = lookahead;
|
|
1352
1769
|
}
|
|
1353
1770
|
}
|
|
1771
|
+
const node = {
|
|
1772
|
+
type: 'list',
|
|
1773
|
+
ordered,
|
|
1774
|
+
start,
|
|
1775
|
+
delimiter: ordered ? delim : undefined,
|
|
1776
|
+
spread: loose || undefined,
|
|
1777
|
+
children: items
|
|
1778
|
+
};
|
|
1354
1779
|
return {
|
|
1355
|
-
node
|
|
1356
|
-
type: 'list',
|
|
1357
|
-
ordered,
|
|
1358
|
-
start,
|
|
1359
|
-
delimiter: ordered ? delim : undefined,
|
|
1360
|
-
loose: loose || undefined,
|
|
1361
|
-
items
|
|
1362
|
-
},
|
|
1780
|
+
node,
|
|
1363
1781
|
nextIndex: index
|
|
1364
1782
|
};
|
|
1365
1783
|
}
|
|
@@ -1369,7 +1787,21 @@ function parseList(lines, startIndex, ordered, opts) {
|
|
|
1369
1787
|
// ---------------------------------------------------------------------------
|
|
1370
1788
|
|
|
1371
1789
|
export function parseMarkdown(input, arg) {
|
|
1372
|
-
return
|
|
1790
|
+
return projectMarkdownRoot(parseMarkdownAst(input, arg), wantsLegacyRanges(arg));
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
/** @internal Canonical block parse used by Markdown and Outline rendering. */
|
|
1794
|
+
export function parseMarkdownAst(input, arg, isFinal = true) {
|
|
1795
|
+
const resolved = resolveOptions(arg);
|
|
1796
|
+
const opts = isFinal ? resolved : {
|
|
1797
|
+
...resolved,
|
|
1798
|
+
isFinal: false
|
|
1799
|
+
};
|
|
1800
|
+
const root = {
|
|
1801
|
+
type: 'root',
|
|
1802
|
+
children: parseMarkdownImpl(input, opts)
|
|
1803
|
+
};
|
|
1804
|
+
return applyMarkdownTransforms(root, opts.plugins, input, opts.isFinal, 'block');
|
|
1373
1805
|
}
|
|
1374
1806
|
function parseMarkdownImpl(input, baseOpts) {
|
|
1375
1807
|
// Collect this input's link reference definitions and strip their lines,
|
|
@@ -1398,14 +1830,26 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1398
1830
|
linkDefs
|
|
1399
1831
|
} : baseOpts;
|
|
1400
1832
|
const lines = cleaned.split('\n');
|
|
1833
|
+
const hasBlockExtensionSyntax = opts.allowBlockSyntax !== false && (opts.plugins?.blockByFirstCharacter.size ?? 0) > 0;
|
|
1834
|
+
// Derived from the already-split lines rather than a second character
|
|
1835
|
+
// scan: every line contributes its own length plus the newline it ended on.
|
|
1836
|
+
const lineOffsets = [0];
|
|
1837
|
+
if (opts.astPositions === true || hasBlockExtensionSyntax) {
|
|
1838
|
+
let offset = 0;
|
|
1839
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1840
|
+
offset += lines[index].length + 1;
|
|
1841
|
+
lineOffsets.push(offset);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
const blockExtensionMatches = new Map();
|
|
1401
1845
|
const blocks = [];
|
|
1402
1846
|
// The line each block started on, parallel to `blocks`. Only collected when
|
|
1403
1847
|
// ranges were asked for; a block's end is resolved after the loop, since the
|
|
1404
1848
|
// branch that produced it has already moved `index` past whatever it read.
|
|
1405
|
-
const blockStartLines = opts.
|
|
1849
|
+
const blockStartLines = opts.astPositions === true ? [] : null;
|
|
1406
1850
|
// Set only by a block that consumes blank lines as content, where the
|
|
1407
1851
|
// positional end derivation would trim them away.
|
|
1408
|
-
const blockEndLines = opts.
|
|
1852
|
+
const blockEndLines = opts.astPositions === true ? [] : null;
|
|
1409
1853
|
let blockStartLine = 0;
|
|
1410
1854
|
const pushBlock = (node, endLine) => {
|
|
1411
1855
|
blocks.push(node);
|
|
@@ -1422,10 +1866,13 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1422
1866
|
}
|
|
1423
1867
|
|
|
1424
1868
|
// --- Fenced code block ---
|
|
1425
|
-
const fenceMatch = line.match(/^(`{3,}|~{3,})
|
|
1869
|
+
const fenceMatch = line.match(/^(`{3,}|~{3,})/);
|
|
1426
1870
|
if (fenceMatch) {
|
|
1427
1871
|
const fence = fenceMatch[1];
|
|
1428
|
-
const
|
|
1872
|
+
const info = line.slice(fence.length);
|
|
1873
|
+
const language = info.match(/^(\S+)/)?.[1] ?? null;
|
|
1874
|
+
const legacyLanguage = info.match(/^(\w*)/)?.[1] || null;
|
|
1875
|
+
const meta = language == null ? undefined : info.slice(language.length).trim() || undefined;
|
|
1429
1876
|
const codeLines = [];
|
|
1430
1877
|
index++;
|
|
1431
1878
|
while (index < lines.length && !lines[index].startsWith(fence)) {
|
|
@@ -1436,11 +1883,14 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1436
1883
|
// A fence owns its blank lines, and an unterminated one (mid-stream)
|
|
1437
1884
|
// can end on them, so it states its own end rather than letting the
|
|
1438
1885
|
// positional derivation trim them off.
|
|
1439
|
-
pushBlock({
|
|
1440
|
-
type: '
|
|
1441
|
-
language,
|
|
1442
|
-
|
|
1443
|
-
|
|
1886
|
+
pushBlock(markMarkdownAstLegacyCodeLanguage({
|
|
1887
|
+
type: 'code',
|
|
1888
|
+
lang: language,
|
|
1889
|
+
...(meta == null ? {} : {
|
|
1890
|
+
meta
|
|
1891
|
+
}),
|
|
1892
|
+
value: codeLines.join('\n')
|
|
1893
|
+
}, legacyLanguage), Math.min(index, lines.length) - 1);
|
|
1444
1894
|
continue;
|
|
1445
1895
|
}
|
|
1446
1896
|
|
|
@@ -1462,7 +1912,7 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1462
1912
|
if (headingMatch) {
|
|
1463
1913
|
pushBlock({
|
|
1464
1914
|
type: 'heading',
|
|
1465
|
-
|
|
1915
|
+
depth: headingMatch[1].length,
|
|
1466
1916
|
children: parseInlineEntry(headingMatch[2], opts)
|
|
1467
1917
|
});
|
|
1468
1918
|
index++;
|
|
@@ -1472,7 +1922,7 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1472
1922
|
// --- HR (must precede list check to handle `- - -`, `* * *`, `_ _ _`) ---
|
|
1473
1923
|
if (isHorizontalRule(line)) {
|
|
1474
1924
|
pushBlock({
|
|
1475
|
-
type: '
|
|
1925
|
+
type: 'thematicBreak'
|
|
1476
1926
|
});
|
|
1477
1927
|
index++;
|
|
1478
1928
|
continue;
|
|
@@ -1482,11 +1932,11 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1482
1932
|
// An unsafe src falls through to the paragraph path and renders as
|
|
1483
1933
|
// literal text, the same rule the inline image path applies.
|
|
1484
1934
|
const imageMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)/);
|
|
1485
|
-
if (imageMatch && line.trim() === imageMatch[0] &&
|
|
1935
|
+
if (imageMatch && line.trim() === imageMatch[0] && isSafeMarkdownParserUrl(imageMatch[2])) {
|
|
1486
1936
|
pushBlock({
|
|
1487
1937
|
type: 'image',
|
|
1488
1938
|
alt: imageMatch[1],
|
|
1489
|
-
|
|
1939
|
+
url: imageMatch[2]
|
|
1490
1940
|
});
|
|
1491
1941
|
index++;
|
|
1492
1942
|
continue;
|
|
@@ -1530,11 +1980,52 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1530
1980
|
continue;
|
|
1531
1981
|
}
|
|
1532
1982
|
|
|
1983
|
+
// --- Extension block syntax (built-in blocks take precedence) ---
|
|
1984
|
+
if (hasBlockExtensionSyntax) {
|
|
1985
|
+
const extensionColumn = blockExtensionColumn(line);
|
|
1986
|
+
const extensionOffset = extensionColumn == null ? lineOffsets[index] : lineOffsets[index] + extensionColumn;
|
|
1987
|
+
const extension = blockExtensionMatches.get(extensionOffset) ?? (extensionColumn == null ? {
|
|
1988
|
+
status: 'none'
|
|
1989
|
+
} : matchExtensionSyntax(cleaned, extensionOffset, 'block', opts));
|
|
1990
|
+
blockExtensionMatches.delete(extensionOffset);
|
|
1991
|
+
if (extension.status === 'match') {
|
|
1992
|
+
const consumedEnd = extension.end;
|
|
1993
|
+
const consumed = cleaned.slice(lineOffsets[index], consumedEnd);
|
|
1994
|
+
if (consumedEnd < cleaned.length && cleaned[consumedEnd] !== '\n' && cleaned[consumedEnd - 1] !== '\n') {
|
|
1995
|
+
reportMarkdownPluginFailure(extension.node.plugin, 'syntax', new TypeError('Block syntax must consume complete lines'));
|
|
1996
|
+
} else {
|
|
1997
|
+
const newlineCount = consumed.split('\n').length - 1;
|
|
1998
|
+
const consumedLines = Math.max(1, newlineCount + (consumed.endsWith('\n') ? 0 : 1));
|
|
1999
|
+
pushBlock(extension.node, index + consumedLines - 1);
|
|
2000
|
+
index += consumedLines;
|
|
2001
|
+
continue;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
if (extension.status === 'defer') {
|
|
2005
|
+
break;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1533
2009
|
// --- Paragraph ---
|
|
1534
2010
|
const paraLines = [line];
|
|
1535
2011
|
index++;
|
|
1536
|
-
while (index < lines.length
|
|
1537
|
-
|
|
2012
|
+
while (index < lines.length) {
|
|
2013
|
+
const nextLine = lines[index];
|
|
2014
|
+
if (isBlockStart(nextLine) || opts.math && matchDisplayMathBlock(lines, index) != null || nextLine.trim() === '') {
|
|
2015
|
+
break;
|
|
2016
|
+
}
|
|
2017
|
+
if (hasBlockExtensionSyntax) {
|
|
2018
|
+
const extensionColumn = blockExtensionColumn(nextLine);
|
|
2019
|
+
if (extensionColumn != null) {
|
|
2020
|
+
const extensionOffset = lineOffsets[index] + extensionColumn;
|
|
2021
|
+
const extension = matchExtensionSyntax(cleaned, extensionOffset, 'block', opts);
|
|
2022
|
+
if (extension.status !== 'none') {
|
|
2023
|
+
blockExtensionMatches.set(extensionOffset, extension);
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
paraLines.push(nextLine);
|
|
1538
2029
|
index++;
|
|
1539
2030
|
}
|
|
1540
2031
|
pushBlock({
|
|
@@ -1586,9 +2077,13 @@ function stampSourceRanges(blocks, blockStartLines, blockEndLines, lines, lineMa
|
|
|
1586
2077
|
const end = lineStart(endLine) + lines[endLine].length;
|
|
1587
2078
|
blocks[i] = {
|
|
1588
2079
|
...blocks[i],
|
|
1589
|
-
|
|
1590
|
-
start:
|
|
1591
|
-
|
|
2080
|
+
position: {
|
|
2081
|
+
start: {
|
|
2082
|
+
offset: lineStart(startLine)
|
|
2083
|
+
},
|
|
2084
|
+
end: {
|
|
2085
|
+
offset: end
|
|
2086
|
+
}
|
|
1592
2087
|
}
|
|
1593
2088
|
};
|
|
1594
2089
|
}
|
|
@@ -1609,6 +2104,11 @@ function makeIncrementalCache(state) {
|
|
|
1609
2104
|
tailLinkDefs: new Map(),
|
|
1610
2105
|
linkDefs: defs,
|
|
1611
2106
|
linkDefsKey: linkDefsSignature(defs),
|
|
2107
|
+
settledAstBlocks: [],
|
|
2108
|
+
settledRevision: 0,
|
|
2109
|
+
projectedRevision: -1,
|
|
2110
|
+
projectedSettledBlocks: [],
|
|
2111
|
+
projectionCache: new WeakMap(),
|
|
1612
2112
|
work: {
|
|
1613
2113
|
splitCharacters: 0,
|
|
1614
2114
|
boundaryLines: 0,
|
|
@@ -2041,7 +2541,7 @@ function trimUnsettledStructural(text) {
|
|
|
2041
2541
|
* rather than into the slice.
|
|
2042
2542
|
*/
|
|
2043
2543
|
function atOffset(opts, offset) {
|
|
2044
|
-
return opts.
|
|
2544
|
+
return opts.astPositions === true ? {
|
|
2045
2545
|
...opts,
|
|
2046
2546
|
baseOffset: offset
|
|
2047
2547
|
} : opts;
|
|
@@ -2066,14 +2566,13 @@ function mergeSettledBlocks(prev, delta) {
|
|
|
2066
2566
|
ordered: prevLast.ordered,
|
|
2067
2567
|
start: prevLast.start,
|
|
2068
2568
|
delimiter: prevLast.delimiter,
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
// One list now, so one
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
end: deltaFirst.range.end
|
|
2569
|
+
spread: true,
|
|
2570
|
+
children: [...prevLast.children, ...deltaFirst.children],
|
|
2571
|
+
// One list now, so one position spans both halves.
|
|
2572
|
+
...(prevLast.position != null && deltaFirst.position != null ? {
|
|
2573
|
+
position: {
|
|
2574
|
+
start: prevLast.position.start,
|
|
2575
|
+
end: deltaFirst.position.end
|
|
2077
2576
|
}
|
|
2078
2577
|
} : null)
|
|
2079
2578
|
};
|
|
@@ -2085,11 +2584,11 @@ function mergeSettledBlocks(prev, delta) {
|
|
|
2085
2584
|
/** Append a newly-settled slice without copying the already-settled prefix. */
|
|
2086
2585
|
function appendSettledBlocks(prev, delta) {
|
|
2087
2586
|
if (delta.length === 0) {
|
|
2088
|
-
return;
|
|
2587
|
+
return false;
|
|
2089
2588
|
}
|
|
2090
2589
|
if (prev.length === 0) {
|
|
2091
2590
|
prev.push(...delta);
|
|
2092
|
-
return;
|
|
2591
|
+
return false;
|
|
2093
2592
|
}
|
|
2094
2593
|
const prevLast = prev[prev.length - 1];
|
|
2095
2594
|
const deltaFirst = delta[0];
|
|
@@ -2099,19 +2598,20 @@ function appendSettledBlocks(prev, delta) {
|
|
|
2099
2598
|
ordered: prevLast.ordered,
|
|
2100
2599
|
start: prevLast.start,
|
|
2101
2600
|
delimiter: prevLast.delimiter,
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
...(prevLast.
|
|
2105
|
-
|
|
2106
|
-
start: prevLast.
|
|
2107
|
-
end: deltaFirst.
|
|
2601
|
+
spread: true,
|
|
2602
|
+
children: [...prevLast.children, ...deltaFirst.children],
|
|
2603
|
+
...(prevLast.position != null && deltaFirst.position != null ? {
|
|
2604
|
+
position: {
|
|
2605
|
+
start: prevLast.position.start,
|
|
2606
|
+
end: deltaFirst.position.end
|
|
2108
2607
|
}
|
|
2109
2608
|
} : null)
|
|
2110
2609
|
};
|
|
2111
2610
|
prev.push(...delta.slice(1));
|
|
2112
|
-
return;
|
|
2611
|
+
return true;
|
|
2113
2612
|
}
|
|
2114
2613
|
prev.push(...delta);
|
|
2614
|
+
return false;
|
|
2115
2615
|
}
|
|
2116
2616
|
function sameUnsettledDefinitions(previous, next, settled) {
|
|
2117
2617
|
for (const [label, destination] of previous) {
|
|
@@ -2132,12 +2632,19 @@ function resetIncrementalCache(state, cache) {
|
|
|
2132
2632
|
state.settledBlocks = [];
|
|
2133
2633
|
state.settledUpTo = 0;
|
|
2134
2634
|
state.linkDefsKey = undefined;
|
|
2635
|
+
state.sourceIdsKey = undefined;
|
|
2636
|
+
state.pluginSyntaxIdentity = undefined;
|
|
2135
2637
|
state.math = undefined;
|
|
2136
2638
|
cache.settledEnd = 0;
|
|
2137
2639
|
cache.settledLinkDefs.clear();
|
|
2138
2640
|
cache.tailLinkDefs = new Map();
|
|
2139
2641
|
cache.linkDefs = new Map();
|
|
2140
2642
|
cache.linkDefsKey = '';
|
|
2643
|
+
cache.settledAstBlocks = [];
|
|
2644
|
+
cache.settledRevision++;
|
|
2645
|
+
cache.projectedRevision = -1;
|
|
2646
|
+
cache.projectedSettledBlocks = [];
|
|
2647
|
+
cache.projectionCache = new WeakMap();
|
|
2141
2648
|
cache.work = {
|
|
2142
2649
|
splitCharacters: 0,
|
|
2143
2650
|
boundaryLines: 0,
|
|
@@ -2159,24 +2666,47 @@ function resetIncrementalCache(state, cache) {
|
|
|
2159
2666
|
*/
|
|
2160
2667
|
|
|
2161
2668
|
export function parseMarkdownIncremental(input, state, arg) {
|
|
2162
|
-
const
|
|
2669
|
+
const withRanges = wantsLegacyRanges(arg);
|
|
2670
|
+
const root = parseMarkdownAstIncremental(input, state, arg);
|
|
2163
2671
|
const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
|
|
2164
|
-
|
|
2672
|
+
const projected = projectMarkdownRoot(root, withRanges, cache.projectionCache);
|
|
2673
|
+
if (cache.projectedRevision !== cache.settledRevision) {
|
|
2674
|
+
cache.projectedSettledBlocks = cache.settledAstBlocks.map(block => projectBlockNode(block, cache.projectionCache, withRanges));
|
|
2675
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2676
|
+
}
|
|
2677
|
+
state.settledBlocks = cache.projectedSettledBlocks;
|
|
2678
|
+
return projected;
|
|
2679
|
+
}
|
|
2165
2680
|
|
|
2681
|
+
/** @internal Canonical incremental parse used by Markdown rendering. */
|
|
2682
|
+
export function parseMarkdownAstIncremental(input, state, arg) {
|
|
2683
|
+
const opts = resolveOptions(arg, true);
|
|
2684
|
+
const root = {
|
|
2685
|
+
type: 'root',
|
|
2686
|
+
children: parseMarkdownIncrementalAstBlocks(input, state, opts)
|
|
2687
|
+
};
|
|
2688
|
+
return applyMarkdownTransforms(root, opts.plugins, input, opts.isFinal, 'block');
|
|
2689
|
+
}
|
|
2690
|
+
function parseMarkdownIncrementalAstBlocks(input, state, opts) {
|
|
2691
|
+
const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
|
|
2692
|
+
let reparseSettled = false;
|
|
2693
|
+
const nextSourceIdsKey = sourceIdsSignature(opts.sourceIds);
|
|
2694
|
+
const nextPluginSyntaxIdentity = opts.plugins?.syntaxIdentity ?? '';
|
|
2166
2695
|
// Invalidate cache when an option that changes parsed nodes flips — cached
|
|
2167
2696
|
// settled blocks were parsed with the previous setting and would otherwise
|
|
2168
2697
|
// be reused unchanged.
|
|
2169
|
-
if (state.autolink !== opts.autolink || state.math !== opts.math || Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges)) {
|
|
2698
|
+
if (state.autolink !== opts.autolink || state.math !== opts.math || Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges) || state.sourceIdsKey !== nextSourceIdsKey || state.pluginSyntaxIdentity !== nextPluginSyntaxIdentity) {
|
|
2170
2699
|
reparseSettled = true;
|
|
2171
2700
|
state.autolink = opts.autolink;
|
|
2172
2701
|
state.math = opts.math;
|
|
2173
2702
|
state.sourceRanges = opts.sourceRanges;
|
|
2703
|
+
state.sourceIdsKey = nextSourceIdsKey;
|
|
2704
|
+
state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
|
|
2174
2705
|
}
|
|
2175
2706
|
if (input === '') {
|
|
2176
2707
|
resetIncrementalCache(state, cache);
|
|
2177
2708
|
return [];
|
|
2178
2709
|
}
|
|
2179
|
-
|
|
2180
2710
|
// The settled prefix is only reusable while the input still contains it
|
|
2181
2711
|
// verbatim. Lengths alone cannot tell: a same-length or longer replacement
|
|
2182
2712
|
// (new args after reusing a state) disagrees with the prefix without ever
|
|
@@ -2190,6 +2720,8 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
2190
2720
|
state.autolink = opts.autolink;
|
|
2191
2721
|
state.math = opts.math;
|
|
2192
2722
|
state.sourceRanges = opts.sourceRanges;
|
|
2723
|
+
state.sourceIdsKey = nextSourceIdsKey;
|
|
2724
|
+
state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
|
|
2193
2725
|
}
|
|
2194
2726
|
|
|
2195
2727
|
// The recurring parse costs are confined to the mutable suffix: splitting,
|
|
@@ -2260,22 +2792,38 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
2260
2792
|
// table, which inside a fence is ordinary code: a TypeScript union or a `- `
|
|
2261
2793
|
// would disappear from the code block as it streams.
|
|
2262
2794
|
const unsettledText = openFence ? unsettledRaw : openMath ? trimOpenDisplayMath(unsettledRaw) : trimUnsettledStructural(unsettledRaw);
|
|
2795
|
+
const legacyRanges = opts.sourceRanges === true;
|
|
2263
2796
|
let parsedSettledBlocks = 0;
|
|
2264
2797
|
if (reparseSettled) {
|
|
2265
|
-
|
|
2266
|
-
parsedSettledBlocks =
|
|
2798
|
+
cache.settledAstBlocks = state.settledText ? parseMarkdownImpl(state.settledText, parseOpts) : [];
|
|
2799
|
+
parsedSettledBlocks = cache.settledAstBlocks.length;
|
|
2800
|
+
cache.settledRevision++;
|
|
2801
|
+
cache.projectedSettledBlocks = cache.settledAstBlocks.map(block => projectBlockNode(block, cache.projectionCache, legacyRanges));
|
|
2802
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2267
2803
|
} else if (settledDelta !== '') {
|
|
2268
2804
|
const deltaBlocks = parseMarkdownImpl(settledDelta, atOffset(parseOpts, oldSettledEnd));
|
|
2269
|
-
|
|
2805
|
+
const mergeIndex = cache.settledAstBlocks.length - 1;
|
|
2806
|
+
const mergedList = appendSettledBlocks(cache.settledAstBlocks, deltaBlocks);
|
|
2270
2807
|
parsedSettledBlocks = deltaBlocks.length;
|
|
2808
|
+
cache.settledRevision++;
|
|
2809
|
+
const projectedDelta = deltaBlocks.map(block => projectBlockNode(block, cache.projectionCache, legacyRanges));
|
|
2810
|
+
if (mergedList) {
|
|
2811
|
+
const canonicalMerged = cache.settledAstBlocks[mergeIndex];
|
|
2812
|
+
const projectedMerged = projectBlockNode(canonicalMerged, cache.projectionCache, legacyRanges);
|
|
2813
|
+
cache.projectedSettledBlocks[mergeIndex] = projectedMerged;
|
|
2814
|
+
cache.projectedSettledBlocks.push(...projectedDelta.slice(1));
|
|
2815
|
+
} else {
|
|
2816
|
+
cache.projectedSettledBlocks.push(...projectedDelta);
|
|
2817
|
+
}
|
|
2818
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2271
2819
|
}
|
|
2272
2820
|
|
|
2273
2821
|
// The unsettled tail is trimmed before parsing, so its offset in the
|
|
2274
2822
|
// document is where that trimmed text actually starts — not the boundary,
|
|
2275
|
-
// which is a line index. If it somehow
|
|
2276
|
-
//
|
|
2277
|
-
//
|
|
2278
|
-
const unsettledStart = unsettledText && opts.
|
|
2823
|
+
// which is a line index. If it somehow cannot be located, parse it without
|
|
2824
|
+
// positions rather than report wrong ones. The search starts at the
|
|
2825
|
+
// settled end, so it scans the tail and never the prefix.
|
|
2826
|
+
const unsettledStart = unsettledText && opts.astPositions === true ? input.indexOf(unsettledText, cache.settledEnd) : -1;
|
|
2279
2827
|
const unsettledBlocks = unsettledText ? parseMarkdownImpl(unsettledText, unsettledStart >= 0 ? atOffset(parseOpts, unsettledStart) : nested(parseOpts)) : [];
|
|
2280
2828
|
state.prevInput = input;
|
|
2281
2829
|
|
|
@@ -2290,7 +2838,7 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
2290
2838
|
definitionCharacters: settledDelta.length + unsettledInput.length,
|
|
2291
2839
|
renderedBlocks: parsedSettledBlocks + unsettledBlocks.length
|
|
2292
2840
|
};
|
|
2293
|
-
return mergeSettledBlocks(
|
|
2841
|
+
return mergeSettledBlocks(cache.settledAstBlocks, unsettledBlocks);
|
|
2294
2842
|
}
|
|
2295
2843
|
|
|
2296
2844
|
// ---------------------------------------------------------------------------
|
|
@@ -2301,28 +2849,6 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
2301
2849
|
// derives its item ids from the same functions, so outline hash links always
|
|
2302
2850
|
// resolve to a rendered heading by construction.
|
|
2303
2851
|
|
|
2304
|
-
/** Flatten inline nodes into their plain text content. */
|
|
2305
|
-
export function inlineText(nodes) {
|
|
2306
|
-
return nodes.map(node => {
|
|
2307
|
-
switch (node.type) {
|
|
2308
|
-
case 'text':
|
|
2309
|
-
case 'code':
|
|
2310
|
-
case 'math':
|
|
2311
|
-
return node.type === 'math' ? node.value : node.content;
|
|
2312
|
-
case 'bold':
|
|
2313
|
-
case 'italic':
|
|
2314
|
-
case 'strikethrough':
|
|
2315
|
-
case 'link':
|
|
2316
|
-
return inlineText(node.children);
|
|
2317
|
-
case 'image':
|
|
2318
|
-
return node.alt;
|
|
2319
|
-
case 'citation':
|
|
2320
|
-
case 'break':
|
|
2321
|
-
return '';
|
|
2322
|
-
}
|
|
2323
|
-
}).join('');
|
|
2324
|
-
}
|
|
2325
|
-
|
|
2326
2852
|
/** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
|
|
2327
2853
|
export function slugify(value) {
|
|
2328
2854
|
return value.trim().toLowerCase().replace(/['"]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|