@astryxdesign/core 0.6.2-canary.bfee8a4 → 0.6.2-canary.d4cc232
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.map +1 -1
- package/dist/Markdown/Markdown.js +63 -59
- package/dist/Markdown/ast.d.ts +124 -0
- package/dist/Markdown/ast.d.ts.map +1 -0
- package/dist/Markdown/ast.js +38 -0
- package/dist/Markdown/parser.d.ts +14 -8
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +352 -97
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +7 -7
- 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 +3 -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.spec.md +159 -43
- package/src/Markdown/Markdown.test.tsx +11 -0
- package/src/Markdown/Markdown.tsx +106 -96
- package/src/Markdown/ast.test.ts +169 -0
- package/src/Markdown/ast.ts +231 -0
- package/src/Markdown/incremental.test.ts +12 -0
- package/src/Markdown/parser.ts +427 -118
- package/src/Outline/Outline.spec.md +1 -1
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +8 -13
- 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/src/Markdown/parser.ts
CHANGED
|
@@ -2,14 +2,22 @@
|
|
|
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 and released parse options
|
|
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 type {
|
|
12
|
+
MarkdownAstBlockContent,
|
|
13
|
+
MarkdownAstListItem,
|
|
14
|
+
MarkdownAstPhrasingContent,
|
|
15
|
+
MarkdownAstPosition,
|
|
16
|
+
MarkdownAstRoot,
|
|
17
|
+
MarkdownAstTableCell,
|
|
18
|
+
MarkdownAstTableRow,
|
|
19
|
+
} from './ast';
|
|
20
|
+
|
|
13
21
|
// ---------------------------------------------------------------------------
|
|
14
22
|
// Types
|
|
15
23
|
// ---------------------------------------------------------------------------
|
|
@@ -130,6 +138,199 @@ export type TableCellNode = {children: InlineNode[]};
|
|
|
130
138
|
type TableCellNodeWithMath = {children: InlineNodeWithMath[]};
|
|
131
139
|
export type TableAlignment = 'left' | 'center' | 'right' | null;
|
|
132
140
|
|
|
141
|
+
type LegacyProjectionCache = WeakMap<object, object>;
|
|
142
|
+
|
|
143
|
+
function projectRange(
|
|
144
|
+
position: MarkdownAstPosition | undefined,
|
|
145
|
+
): BlockMetadata {
|
|
146
|
+
const start = position?.start.offset;
|
|
147
|
+
const end = position?.end.offset;
|
|
148
|
+
return start == null || end == null ? {} : {range: {start, end}};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function projectInlineNode(
|
|
152
|
+
node: MarkdownAstPhrasingContent,
|
|
153
|
+
cache: LegacyProjectionCache,
|
|
154
|
+
): InlineNodeWithMath {
|
|
155
|
+
const cached = cache.get(node);
|
|
156
|
+
if (cached != null) {
|
|
157
|
+
return cached as InlineNodeWithMath;
|
|
158
|
+
}
|
|
159
|
+
let projected: InlineNodeWithMath;
|
|
160
|
+
switch (node.type) {
|
|
161
|
+
case 'text':
|
|
162
|
+
projected = {type: 'text', content: node.value};
|
|
163
|
+
break;
|
|
164
|
+
case 'strong':
|
|
165
|
+
projected = {
|
|
166
|
+
type: 'bold',
|
|
167
|
+
children: node.children.map(child => projectInlineNode(child, cache)),
|
|
168
|
+
};
|
|
169
|
+
break;
|
|
170
|
+
case 'emphasis':
|
|
171
|
+
projected = {
|
|
172
|
+
type: 'italic',
|
|
173
|
+
children: node.children.map(child => projectInlineNode(child, cache)),
|
|
174
|
+
};
|
|
175
|
+
break;
|
|
176
|
+
case 'delete':
|
|
177
|
+
projected = {
|
|
178
|
+
type: 'strikethrough',
|
|
179
|
+
children: node.children.map(child => projectInlineNode(child, cache)),
|
|
180
|
+
};
|
|
181
|
+
break;
|
|
182
|
+
case 'inlineCode':
|
|
183
|
+
projected = {type: 'code', content: node.value};
|
|
184
|
+
break;
|
|
185
|
+
case 'inlineMath':
|
|
186
|
+
projected = {type: 'math', value: node.value};
|
|
187
|
+
break;
|
|
188
|
+
case 'link':
|
|
189
|
+
projected = {
|
|
190
|
+
type: 'link',
|
|
191
|
+
href: node.url,
|
|
192
|
+
children: node.children.map(child => projectInlineNode(child, cache)),
|
|
193
|
+
};
|
|
194
|
+
break;
|
|
195
|
+
case 'image':
|
|
196
|
+
projected = {type: 'image', src: node.url, alt: node.alt};
|
|
197
|
+
break;
|
|
198
|
+
case 'citation':
|
|
199
|
+
projected = {type: 'citation', sourceId: node.sourceId};
|
|
200
|
+
break;
|
|
201
|
+
case 'break':
|
|
202
|
+
projected = {type: 'break'};
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
cache.set(node, projected);
|
|
206
|
+
return projected;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function projectTableCell(
|
|
210
|
+
node: MarkdownAstTableCell,
|
|
211
|
+
cache: LegacyProjectionCache,
|
|
212
|
+
): TableCellNodeWithMath {
|
|
213
|
+
const cached = cache.get(node);
|
|
214
|
+
if (cached != null) {
|
|
215
|
+
return cached as TableCellNodeWithMath;
|
|
216
|
+
}
|
|
217
|
+
const projected = {
|
|
218
|
+
children: node.children.map(child => projectInlineNode(child, cache)),
|
|
219
|
+
};
|
|
220
|
+
cache.set(node, projected);
|
|
221
|
+
return projected;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function projectListItem(
|
|
225
|
+
node: MarkdownAstListItem,
|
|
226
|
+
cache: LegacyProjectionCache,
|
|
227
|
+
): ListItemNodeWithMath {
|
|
228
|
+
const cached = cache.get(node);
|
|
229
|
+
if (cached != null) {
|
|
230
|
+
return cached as ListItemNodeWithMath;
|
|
231
|
+
}
|
|
232
|
+
const projected = {
|
|
233
|
+
checked: node.checked,
|
|
234
|
+
children: node.children.map(child => projectBlockNode(child, cache)),
|
|
235
|
+
};
|
|
236
|
+
cache.set(node, projected);
|
|
237
|
+
return projected;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function projectBlockNode(
|
|
241
|
+
node: MarkdownAstBlockContent,
|
|
242
|
+
cache: LegacyProjectionCache,
|
|
243
|
+
): BlockNodeWithMath {
|
|
244
|
+
const cached = cache.get(node);
|
|
245
|
+
if (cached != null) {
|
|
246
|
+
return cached as BlockNodeWithMath;
|
|
247
|
+
}
|
|
248
|
+
const metadata = projectRange(node.position);
|
|
249
|
+
let projected: BlockNodeWithMath;
|
|
250
|
+
switch (node.type) {
|
|
251
|
+
case 'heading':
|
|
252
|
+
projected = {
|
|
253
|
+
type: 'heading',
|
|
254
|
+
level: node.depth,
|
|
255
|
+
children: node.children.map(child => projectInlineNode(child, cache)),
|
|
256
|
+
...metadata,
|
|
257
|
+
};
|
|
258
|
+
break;
|
|
259
|
+
case 'paragraph':
|
|
260
|
+
projected = {
|
|
261
|
+
type: 'paragraph',
|
|
262
|
+
children: node.children.map(child => projectInlineNode(child, cache)),
|
|
263
|
+
...metadata,
|
|
264
|
+
};
|
|
265
|
+
break;
|
|
266
|
+
case 'code':
|
|
267
|
+
projected = {
|
|
268
|
+
type: 'codeblock',
|
|
269
|
+
language: node.lang ?? 'plaintext',
|
|
270
|
+
content: node.value,
|
|
271
|
+
...metadata,
|
|
272
|
+
};
|
|
273
|
+
break;
|
|
274
|
+
case 'math':
|
|
275
|
+
projected = {type: 'math', value: node.value, ...metadata};
|
|
276
|
+
break;
|
|
277
|
+
case 'blockquote':
|
|
278
|
+
projected = {
|
|
279
|
+
type: 'blockquote',
|
|
280
|
+
children: node.children.map(child => projectBlockNode(child, cache)),
|
|
281
|
+
...metadata,
|
|
282
|
+
};
|
|
283
|
+
break;
|
|
284
|
+
case 'list':
|
|
285
|
+
projected = {
|
|
286
|
+
type: 'list',
|
|
287
|
+
ordered: node.ordered,
|
|
288
|
+
start: node.start,
|
|
289
|
+
delimiter: node.delimiter,
|
|
290
|
+
loose: node.spread,
|
|
291
|
+
items: node.children.map(item => projectListItem(item, cache)),
|
|
292
|
+
...metadata,
|
|
293
|
+
};
|
|
294
|
+
break;
|
|
295
|
+
case 'table': {
|
|
296
|
+
const [header = {type: 'tableRow' as const, children: []}, ...rows] =
|
|
297
|
+
node.children;
|
|
298
|
+
projected = {
|
|
299
|
+
type: 'table',
|
|
300
|
+
headers: header.children.map(cell => projectTableCell(cell, cache)),
|
|
301
|
+
alignments: [...node.align],
|
|
302
|
+
rows: rows.map(row =>
|
|
303
|
+
row.children.map(cell => projectTableCell(cell, cache)),
|
|
304
|
+
),
|
|
305
|
+
...metadata,
|
|
306
|
+
};
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case 'thematicBreak':
|
|
310
|
+
projected = {type: 'hr', ...metadata};
|
|
311
|
+
break;
|
|
312
|
+
case 'image':
|
|
313
|
+
projected = {type: 'image', alt: node.alt, src: node.url, ...metadata};
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
cache.set(node, projected);
|
|
317
|
+
return projected;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function projectInlineNodes(
|
|
321
|
+
nodes: ReadonlyArray<MarkdownAstPhrasingContent>,
|
|
322
|
+
cache: LegacyProjectionCache = new WeakMap(),
|
|
323
|
+
): InlineNodeWithMath[] {
|
|
324
|
+
return nodes.map(node => projectInlineNode(node, cache));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function projectMarkdownRoot(
|
|
328
|
+
root: MarkdownAstRoot,
|
|
329
|
+
cache: LegacyProjectionCache = new WeakMap(),
|
|
330
|
+
): BlockNodeWithMath[] {
|
|
331
|
+
return root.children.map(node => projectBlockNode(node, cache));
|
|
332
|
+
}
|
|
333
|
+
|
|
133
334
|
// ---------------------------------------------------------------------------
|
|
134
335
|
// Parse options
|
|
135
336
|
// ---------------------------------------------------------------------------
|
|
@@ -201,7 +402,10 @@ type ResolvedOptions = {
|
|
|
201
402
|
readonly linkDefs?: ReadonlyMap<string, string>;
|
|
202
403
|
};
|
|
203
404
|
|
|
204
|
-
const EMPTY_OPTS: ResolvedOptions = {
|
|
405
|
+
const EMPTY_OPTS: ResolvedOptions = {
|
|
406
|
+
sourceIds: undefined,
|
|
407
|
+
autolink: undefined,
|
|
408
|
+
};
|
|
205
409
|
|
|
206
410
|
function resolveOptions(
|
|
207
411
|
arg: ReadonlySet<string> | RuntimeParseOptions | undefined,
|
|
@@ -542,7 +746,7 @@ function matchReferenceLink(
|
|
|
542
746
|
start: number,
|
|
543
747
|
linkDefs: ReadonlyMap<string, string>,
|
|
544
748
|
opts: ResolvedOptions,
|
|
545
|
-
): {node:
|
|
749
|
+
): {node: MarkdownAstPhrasingContent; end: number} | null {
|
|
546
750
|
const textClose = text.indexOf(']', start + 1);
|
|
547
751
|
if (textClose === -1) {
|
|
548
752
|
return null;
|
|
@@ -560,7 +764,11 @@ function matchReferenceLink(
|
|
|
560
764
|
const href = linkDefs.get(normalizeLinkLabel(label));
|
|
561
765
|
if (href != null && isSafeUrl(href)) {
|
|
562
766
|
return {
|
|
563
|
-
node: {
|
|
767
|
+
node: {
|
|
768
|
+
type: 'link',
|
|
769
|
+
url: href,
|
|
770
|
+
children: parseInlineImpl(linkText, opts),
|
|
771
|
+
},
|
|
564
772
|
end: labelClose + 1,
|
|
565
773
|
};
|
|
566
774
|
}
|
|
@@ -577,7 +785,7 @@ function matchReferenceLink(
|
|
|
577
785
|
return null;
|
|
578
786
|
}
|
|
579
787
|
return {
|
|
580
|
-
node: {type: 'link', href, children: parseInlineImpl(linkText, opts)},
|
|
788
|
+
node: {type: 'link', url: href, children: parseInlineImpl(linkText, opts)},
|
|
581
789
|
end: textClose + 1,
|
|
582
790
|
};
|
|
583
791
|
}
|
|
@@ -587,7 +795,7 @@ function matchReferenceImage(
|
|
|
587
795
|
text: string,
|
|
588
796
|
start: number,
|
|
589
797
|
linkDefs: ReadonlyMap<string, string>,
|
|
590
|
-
): {node:
|
|
798
|
+
): {node: MarkdownAstPhrasingContent; end: number} | null {
|
|
591
799
|
const altClose = text.indexOf(']', start + 2);
|
|
592
800
|
if (altClose === -1) {
|
|
593
801
|
return null;
|
|
@@ -600,7 +808,7 @@ function matchReferenceImage(
|
|
|
600
808
|
const label = rawLabel === '' ? alt : rawLabel;
|
|
601
809
|
const src = linkDefs.get(normalizeLinkLabel(label));
|
|
602
810
|
if (src != null && isSafeUrl(src)) {
|
|
603
|
-
return {node: {type: 'image', src, alt}, end: labelClose + 1};
|
|
811
|
+
return {node: {type: 'image', url: src, alt}, end: labelClose + 1};
|
|
604
812
|
}
|
|
605
813
|
// No match — fall back to a shortcut `![alt]`.
|
|
606
814
|
}
|
|
@@ -612,7 +820,7 @@ function matchReferenceImage(
|
|
|
612
820
|
if (src == null || !isSafeUrl(src)) {
|
|
613
821
|
return null;
|
|
614
822
|
}
|
|
615
|
-
return {node: {type: 'image', src, alt}, end: altClose + 1};
|
|
823
|
+
return {node: {type: 'image', url: src, alt}, end: altClose + 1};
|
|
616
824
|
}
|
|
617
825
|
|
|
618
826
|
// ---------------------------------------------------------------------------
|
|
@@ -791,6 +999,14 @@ export function parseInline(
|
|
|
791
999
|
text: string,
|
|
792
1000
|
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
793
1001
|
): InlineNodeWithMath[] {
|
|
1002
|
+
return projectInlineNodes(parseInlineAst(text, arg));
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/** @internal Canonical inline parse used by Markdown rendering. */
|
|
1006
|
+
export function parseInlineAst(
|
|
1007
|
+
text: string,
|
|
1008
|
+
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
1009
|
+
): MarkdownAstPhrasingContent[] {
|
|
794
1010
|
return parseInlineEntry(text, resolveOptions(arg));
|
|
795
1011
|
}
|
|
796
1012
|
|
|
@@ -806,7 +1022,7 @@ export function parseInline(
|
|
|
806
1022
|
function parseInlineEntry(
|
|
807
1023
|
text: string,
|
|
808
1024
|
opts: ResolvedOptions,
|
|
809
|
-
):
|
|
1025
|
+
): MarkdownAstPhrasingContent[] {
|
|
810
1026
|
const nodes = parseInlineImpl(text, opts);
|
|
811
1027
|
return opts.autolink === 'gfm' ? transformAutolinks(nodes) : nodes;
|
|
812
1028
|
}
|
|
@@ -814,14 +1030,14 @@ function parseInlineEntry(
|
|
|
814
1030
|
function parseInlineImpl(
|
|
815
1031
|
text: string,
|
|
816
1032
|
opts: ResolvedOptions,
|
|
817
|
-
):
|
|
818
|
-
const nodes:
|
|
1033
|
+
): MarkdownAstPhrasingContent[] {
|
|
1034
|
+
const nodes: MarkdownAstPhrasingContent[] = [];
|
|
819
1035
|
let i = 0;
|
|
820
1036
|
|
|
821
1037
|
while (i < text.length) {
|
|
822
1038
|
// --- Escape ---
|
|
823
1039
|
if (text[i] === '\\' && i + 1 < text.length) {
|
|
824
|
-
nodes.push({type: 'text',
|
|
1040
|
+
nodes.push({type: 'text', value: text[i + 1]});
|
|
825
1041
|
i += 2;
|
|
826
1042
|
continue;
|
|
827
1043
|
}
|
|
@@ -832,7 +1048,10 @@ function parseInlineImpl(
|
|
|
832
1048
|
const openIndex = i + tickCount;
|
|
833
1049
|
const closeIndex = text.indexOf('`'.repeat(tickCount), openIndex);
|
|
834
1050
|
if (closeIndex !== -1) {
|
|
835
|
-
nodes.push({
|
|
1051
|
+
nodes.push({
|
|
1052
|
+
type: 'inlineCode',
|
|
1053
|
+
value: text.slice(openIndex, closeIndex),
|
|
1054
|
+
});
|
|
836
1055
|
i = closeIndex + tickCount;
|
|
837
1056
|
continue;
|
|
838
1057
|
}
|
|
@@ -842,7 +1061,10 @@ function parseInlineImpl(
|
|
|
842
1061
|
if (opts.math && text[i] === '$') {
|
|
843
1062
|
const closeIndex = findInlineMathEnd(text, i);
|
|
844
1063
|
if (closeIndex !== -1) {
|
|
845
|
-
nodes.push({
|
|
1064
|
+
nodes.push({
|
|
1065
|
+
type: 'inlineMath',
|
|
1066
|
+
value: text.slice(i + 1, closeIndex),
|
|
1067
|
+
});
|
|
846
1068
|
i = closeIndex + 1;
|
|
847
1069
|
continue;
|
|
848
1070
|
}
|
|
@@ -867,11 +1089,11 @@ function parseInlineImpl(
|
|
|
867
1089
|
const src = text.slice(altClose + 2, srcClose);
|
|
868
1090
|
if (!isSafeUrl(src)) {
|
|
869
1091
|
// Dangerous scheme — emit as plain text.
|
|
870
|
-
nodes.push({type: 'text',
|
|
1092
|
+
nodes.push({type: 'text', value: text.slice(i, srcClose + 1)});
|
|
871
1093
|
} else {
|
|
872
1094
|
nodes.push({
|
|
873
1095
|
type: 'image',
|
|
874
|
-
src,
|
|
1096
|
+
url: src,
|
|
875
1097
|
alt: text.slice(i + 2, altClose),
|
|
876
1098
|
});
|
|
877
1099
|
}
|
|
@@ -910,11 +1132,11 @@ function parseInlineImpl(
|
|
|
910
1132
|
const href = text.slice(textClose + 2, urlClose);
|
|
911
1133
|
if (!isSafeUrl(href)) {
|
|
912
1134
|
// Dangerous scheme — emit as plain text instead of a link.
|
|
913
|
-
nodes.push({type: 'text',
|
|
1135
|
+
nodes.push({type: 'text', value: text.slice(i, urlClose + 1)});
|
|
914
1136
|
} else {
|
|
915
1137
|
nodes.push({
|
|
916
1138
|
type: 'link',
|
|
917
|
-
href,
|
|
1139
|
+
url: href,
|
|
918
1140
|
children: parseInlineImpl(text.slice(i + 1, textClose), opts),
|
|
919
1141
|
});
|
|
920
1142
|
}
|
|
@@ -950,10 +1172,10 @@ function parseInlineImpl(
|
|
|
950
1172
|
(!isUnderscore || !isWordChar(text[closeIndex + 3]))
|
|
951
1173
|
) {
|
|
952
1174
|
nodes.push({
|
|
953
|
-
type: '
|
|
1175
|
+
type: 'strong',
|
|
954
1176
|
children: [
|
|
955
1177
|
{
|
|
956
|
-
type: '
|
|
1178
|
+
type: 'emphasis',
|
|
957
1179
|
children: parseInlineImpl(text.slice(i + 3, closeIndex), opts),
|
|
958
1180
|
},
|
|
959
1181
|
],
|
|
@@ -980,7 +1202,7 @@ function parseInlineImpl(
|
|
|
980
1202
|
(!isUnderscore || !isWordChar(text[closeIndex + 2]))
|
|
981
1203
|
) {
|
|
982
1204
|
nodes.push({
|
|
983
|
-
type: '
|
|
1205
|
+
type: 'strong',
|
|
984
1206
|
children: parseInlineImpl(text.slice(i + 2, closeIndex), opts),
|
|
985
1207
|
});
|
|
986
1208
|
i = closeIndex + 2;
|
|
@@ -994,7 +1216,7 @@ function parseInlineImpl(
|
|
|
994
1216
|
const closeIndex = text.indexOf('~~', i + 2);
|
|
995
1217
|
if (closeIndex !== -1) {
|
|
996
1218
|
nodes.push({
|
|
997
|
-
type: '
|
|
1219
|
+
type: 'delete',
|
|
998
1220
|
children: parseInlineImpl(text.slice(i + 2, closeIndex), opts),
|
|
999
1221
|
});
|
|
1000
1222
|
i = closeIndex + 2;
|
|
@@ -1015,7 +1237,7 @@ function parseInlineImpl(
|
|
|
1015
1237
|
(!isUnderscore || !isWordChar(text[closeIndex + 1]))
|
|
1016
1238
|
) {
|
|
1017
1239
|
nodes.push({
|
|
1018
|
-
type: '
|
|
1240
|
+
type: 'emphasis',
|
|
1019
1241
|
children: parseInlineImpl(text.slice(i + 1, closeIndex), opts),
|
|
1020
1242
|
});
|
|
1021
1243
|
i = closeIndex + 1;
|
|
@@ -1043,9 +1265,12 @@ function parseInlineImpl(
|
|
|
1043
1265
|
if (trimmed.length > 0) {
|
|
1044
1266
|
const last = nodes[nodes.length - 1];
|
|
1045
1267
|
if (last?.type === 'text') {
|
|
1046
|
-
|
|
1268
|
+
nodes[nodes.length - 1] = {
|
|
1269
|
+
...last,
|
|
1270
|
+
value: last.value + trimmed,
|
|
1271
|
+
};
|
|
1047
1272
|
} else {
|
|
1048
|
-
nodes.push({type: 'text',
|
|
1273
|
+
nodes.push({type: 'text', value: trimmed});
|
|
1049
1274
|
}
|
|
1050
1275
|
}
|
|
1051
1276
|
nodes.push({type: 'break'});
|
|
@@ -1056,9 +1281,9 @@ function parseInlineImpl(
|
|
|
1056
1281
|
|
|
1057
1282
|
const last = nodes[nodes.length - 1];
|
|
1058
1283
|
if (last?.type === 'text') {
|
|
1059
|
-
last.
|
|
1284
|
+
nodes[nodes.length - 1] = {...last, value: last.value + content};
|
|
1060
1285
|
} else {
|
|
1061
|
-
nodes.push({type: 'text', content});
|
|
1286
|
+
nodes.push({type: 'text', value: content});
|
|
1062
1287
|
}
|
|
1063
1288
|
i = end;
|
|
1064
1289
|
}
|
|
@@ -1303,26 +1528,26 @@ function scanAutolinksInText(text: string): AutolinkMatch[] {
|
|
|
1303
1528
|
* Split a text-node `content` string into a sequence of text + link nodes
|
|
1304
1529
|
* based on autolink matches.
|
|
1305
1530
|
*/
|
|
1306
|
-
function splitTextOnAutolinks(content: string):
|
|
1531
|
+
function splitTextOnAutolinks(content: string): MarkdownAstPhrasingContent[] {
|
|
1307
1532
|
const matches = scanAutolinksInText(content);
|
|
1308
1533
|
if (matches.length === 0) {
|
|
1309
|
-
return [{type: 'text', content}];
|
|
1534
|
+
return [{type: 'text', value: content}];
|
|
1310
1535
|
}
|
|
1311
|
-
const out:
|
|
1536
|
+
const out: MarkdownAstPhrasingContent[] = [];
|
|
1312
1537
|
let cursor = 0;
|
|
1313
1538
|
for (const m of matches) {
|
|
1314
1539
|
if (m.start > cursor) {
|
|
1315
|
-
out.push({type: 'text',
|
|
1540
|
+
out.push({type: 'text', value: content.slice(cursor, m.start)});
|
|
1316
1541
|
}
|
|
1317
1542
|
out.push({
|
|
1318
1543
|
type: 'link',
|
|
1319
|
-
|
|
1320
|
-
children: [{type: 'text',
|
|
1544
|
+
url: m.href,
|
|
1545
|
+
children: [{type: 'text', value: m.display}],
|
|
1321
1546
|
});
|
|
1322
1547
|
cursor = m.end;
|
|
1323
1548
|
}
|
|
1324
1549
|
if (cursor < content.length) {
|
|
1325
|
-
out.push({type: 'text',
|
|
1550
|
+
out.push({type: 'text', value: content.slice(cursor)});
|
|
1326
1551
|
}
|
|
1327
1552
|
return out;
|
|
1328
1553
|
}
|
|
@@ -1335,23 +1560,25 @@ function splitTextOnAutolinks(content: string): InlineNodeWithMath[] {
|
|
|
1335
1560
|
* `image` alt text, `citation`, or `break`. Runs only on the outermost
|
|
1336
1561
|
* block's inline tree (see `parseInlineEntry`).
|
|
1337
1562
|
*/
|
|
1338
|
-
function transformAutolinks(
|
|
1339
|
-
|
|
1563
|
+
function transformAutolinks(
|
|
1564
|
+
nodes: ReadonlyArray<MarkdownAstPhrasingContent>,
|
|
1565
|
+
): MarkdownAstPhrasingContent[] {
|
|
1566
|
+
const out: MarkdownAstPhrasingContent[] = [];
|
|
1340
1567
|
for (const node of nodes) {
|
|
1341
1568
|
if (node.type === 'text') {
|
|
1342
|
-
const split = splitTextOnAutolinks(node.
|
|
1569
|
+
const split = splitTextOnAutolinks(node.value);
|
|
1343
1570
|
for (const seg of split) {
|
|
1344
1571
|
const last = out[out.length - 1];
|
|
1345
1572
|
if (seg.type === 'text' && last?.type === 'text') {
|
|
1346
|
-
last.
|
|
1573
|
+
out[out.length - 1] = {...last, value: last.value + seg.value};
|
|
1347
1574
|
} else {
|
|
1348
1575
|
out.push(seg);
|
|
1349
1576
|
}
|
|
1350
1577
|
}
|
|
1351
1578
|
} else if (
|
|
1352
|
-
node.type === '
|
|
1353
|
-
node.type === '
|
|
1354
|
-
node.type === '
|
|
1579
|
+
node.type === 'strong' ||
|
|
1580
|
+
node.type === 'emphasis' ||
|
|
1581
|
+
node.type === 'delete'
|
|
1355
1582
|
) {
|
|
1356
1583
|
out.push({...node, children: transformAutolinks(node.children)});
|
|
1357
1584
|
} else {
|
|
@@ -1510,9 +1737,9 @@ function parseTable(
|
|
|
1510
1737
|
lines: string[],
|
|
1511
1738
|
lineIndex: number,
|
|
1512
1739
|
opts: ResolvedOptions,
|
|
1513
|
-
): {node:
|
|
1514
|
-
const headers:
|
|
1515
|
-
cell => ({children: parseInlineEntry(cell, opts)}),
|
|
1740
|
+
): {node: MarkdownAstBlockContent; nextIndex: number} {
|
|
1741
|
+
const headers: MarkdownAstTableCell[] = splitTableRow(lines[lineIndex]).map(
|
|
1742
|
+
cell => ({type: 'tableCell', children: parseInlineEntry(cell, opts)}),
|
|
1516
1743
|
);
|
|
1517
1744
|
const alignments: TableAlignment[] = splitTableRow(lines[lineIndex + 1]).map(
|
|
1518
1745
|
cell => {
|
|
@@ -1528,22 +1755,28 @@ function parseTable(
|
|
|
1528
1755
|
: null;
|
|
1529
1756
|
},
|
|
1530
1757
|
);
|
|
1531
|
-
const rows:
|
|
1758
|
+
const rows: MarkdownAstTableRow[] = [];
|
|
1532
1759
|
let rowIndex = lineIndex + 2;
|
|
1533
1760
|
while (
|
|
1534
1761
|
rowIndex < lines.length &&
|
|
1535
1762
|
lines[rowIndex].includes('|') &&
|
|
1536
1763
|
lines[rowIndex].trim() !== ''
|
|
1537
1764
|
) {
|
|
1538
|
-
rows.push(
|
|
1539
|
-
|
|
1765
|
+
rows.push({
|
|
1766
|
+
type: 'tableRow',
|
|
1767
|
+
children: splitTableRow(lines[rowIndex]).map(cell => ({
|
|
1768
|
+
type: 'tableCell',
|
|
1540
1769
|
children: parseInlineEntry(cell, opts),
|
|
1541
1770
|
})),
|
|
1542
|
-
);
|
|
1771
|
+
});
|
|
1543
1772
|
rowIndex++;
|
|
1544
1773
|
}
|
|
1545
1774
|
return {
|
|
1546
|
-
node: {
|
|
1775
|
+
node: {
|
|
1776
|
+
type: 'table',
|
|
1777
|
+
align: alignments,
|
|
1778
|
+
children: [{type: 'tableRow', children: headers}, ...rows],
|
|
1779
|
+
},
|
|
1547
1780
|
nextIndex: rowIndex,
|
|
1548
1781
|
};
|
|
1549
1782
|
}
|
|
@@ -1553,8 +1786,8 @@ function parseList(
|
|
|
1553
1786
|
startIndex: number,
|
|
1554
1787
|
ordered: boolean,
|
|
1555
1788
|
opts: ResolvedOptions,
|
|
1556
|
-
): {node:
|
|
1557
|
-
const items:
|
|
1789
|
+
): {node: MarkdownAstBlockContent; nextIndex: number} {
|
|
1790
|
+
const items: MarkdownAstListItem[] = [];
|
|
1558
1791
|
const baseIndent = getIndent(lines[startIndex]);
|
|
1559
1792
|
// Ordered lists may use either '.' or ')' as the marker delimiter
|
|
1560
1793
|
// (CommonMark 5.2). Capture which one this list starts with so its items
|
|
@@ -1608,7 +1841,11 @@ function parseList(
|
|
|
1608
1841
|
itemText += '\n' + deindented.join('\n');
|
|
1609
1842
|
}
|
|
1610
1843
|
|
|
1611
|
-
items.push({
|
|
1844
|
+
items.push({
|
|
1845
|
+
type: 'listItem',
|
|
1846
|
+
checked,
|
|
1847
|
+
children: parseMarkdownImpl(itemText, nested(opts)),
|
|
1848
|
+
});
|
|
1612
1849
|
|
|
1613
1850
|
// CommonMark loose list: blank line(s) between items of the same style
|
|
1614
1851
|
// and indent still form one list. Skip the blanks and continue if the
|
|
@@ -1632,8 +1869,8 @@ function parseList(
|
|
|
1632
1869
|
ordered,
|
|
1633
1870
|
start,
|
|
1634
1871
|
delimiter: ordered ? (delim as '.' | ')') : undefined,
|
|
1635
|
-
|
|
1636
|
-
items,
|
|
1872
|
+
spread: loose || undefined,
|
|
1873
|
+
children: items,
|
|
1637
1874
|
},
|
|
1638
1875
|
nextIndex: index,
|
|
1639
1876
|
};
|
|
@@ -1659,13 +1896,24 @@ export function parseMarkdown(
|
|
|
1659
1896
|
input: string,
|
|
1660
1897
|
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
1661
1898
|
): BlockNodeWithMath[] {
|
|
1662
|
-
return
|
|
1899
|
+
return projectMarkdownRoot(parseMarkdownAst(input, arg));
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
/** @internal Canonical block parse used by Markdown and Outline rendering. */
|
|
1903
|
+
export function parseMarkdownAst(
|
|
1904
|
+
input: string,
|
|
1905
|
+
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
1906
|
+
): MarkdownAstRoot {
|
|
1907
|
+
return {
|
|
1908
|
+
type: 'root',
|
|
1909
|
+
children: parseMarkdownImpl(input, resolveOptions(arg)),
|
|
1910
|
+
};
|
|
1663
1911
|
}
|
|
1664
1912
|
|
|
1665
1913
|
function parseMarkdownImpl(
|
|
1666
1914
|
input: string,
|
|
1667
1915
|
baseOpts: ResolvedOptions,
|
|
1668
|
-
):
|
|
1916
|
+
): MarkdownAstBlockContent[] {
|
|
1669
1917
|
// Collect this input's link reference definitions and strip their lines,
|
|
1670
1918
|
// then merge them with any definitions inherited from an enclosing parse
|
|
1671
1919
|
// (the incremental parser passes the whole document's definitions in; a
|
|
@@ -1686,7 +1934,7 @@ function parseMarkdownImpl(
|
|
|
1686
1934
|
const opts: ResolvedOptions =
|
|
1687
1935
|
linkDefs != null ? {...baseOpts, linkDefs} : baseOpts;
|
|
1688
1936
|
const lines = cleaned.split('\n');
|
|
1689
|
-
const blocks:
|
|
1937
|
+
const blocks: MarkdownAstBlockContent[] = [];
|
|
1690
1938
|
// The line each block started on, parallel to `blocks`. Only collected when
|
|
1691
1939
|
// ranges were asked for; a block's end is resolved after the loop, since the
|
|
1692
1940
|
// branch that produced it has already moved `index` past whatever it read.
|
|
@@ -1697,7 +1945,7 @@ function parseMarkdownImpl(
|
|
|
1697
1945
|
? []
|
|
1698
1946
|
: null;
|
|
1699
1947
|
let blockStartLine = 0;
|
|
1700
|
-
const pushBlock = (node:
|
|
1948
|
+
const pushBlock = (node: MarkdownAstBlockContent, endLine?: number) => {
|
|
1701
1949
|
blocks.push(node);
|
|
1702
1950
|
blockStartLines?.push(blockStartLine);
|
|
1703
1951
|
blockEndLines?.push(endLine);
|
|
@@ -1716,7 +1964,7 @@ function parseMarkdownImpl(
|
|
|
1716
1964
|
const fenceMatch = line.match(/^(`{3,}|~{3,})(\w*)/);
|
|
1717
1965
|
if (fenceMatch) {
|
|
1718
1966
|
const fence = fenceMatch[1];
|
|
1719
|
-
const language = fenceMatch[2] ||
|
|
1967
|
+
const language = fenceMatch[2] || null;
|
|
1720
1968
|
const codeLines: string[] = [];
|
|
1721
1969
|
index++;
|
|
1722
1970
|
while (index < lines.length && !lines[index].startsWith(fence)) {
|
|
@@ -1728,7 +1976,7 @@ function parseMarkdownImpl(
|
|
|
1728
1976
|
// can end on them, so it states its own end rather than letting the
|
|
1729
1977
|
// positional derivation trim them off.
|
|
1730
1978
|
pushBlock(
|
|
1731
|
-
{type: '
|
|
1979
|
+
{type: 'code', lang: language, value: codeLines.join('\n')},
|
|
1732
1980
|
Math.min(index, lines.length) - 1,
|
|
1733
1981
|
);
|
|
1734
1982
|
continue;
|
|
@@ -1752,7 +2000,7 @@ function parseMarkdownImpl(
|
|
|
1752
2000
|
if (headingMatch) {
|
|
1753
2001
|
pushBlock({
|
|
1754
2002
|
type: 'heading',
|
|
1755
|
-
|
|
2003
|
+
depth: headingMatch[1].length as 1 | 2 | 3 | 4 | 5 | 6,
|
|
1756
2004
|
children: parseInlineEntry(headingMatch[2], opts),
|
|
1757
2005
|
});
|
|
1758
2006
|
index++;
|
|
@@ -1761,7 +2009,7 @@ function parseMarkdownImpl(
|
|
|
1761
2009
|
|
|
1762
2010
|
// --- HR (must precede list check to handle `- - -`, `* * *`, `_ _ _`) ---
|
|
1763
2011
|
if (isHorizontalRule(line)) {
|
|
1764
|
-
pushBlock({type: '
|
|
2012
|
+
pushBlock({type: 'thematicBreak'});
|
|
1765
2013
|
index++;
|
|
1766
2014
|
continue;
|
|
1767
2015
|
}
|
|
@@ -1775,7 +2023,7 @@ function parseMarkdownImpl(
|
|
|
1775
2023
|
line.trim() === imageMatch[0] &&
|
|
1776
2024
|
isSafeUrl(imageMatch[2])
|
|
1777
2025
|
) {
|
|
1778
|
-
pushBlock({type: 'image', alt: imageMatch[1],
|
|
2026
|
+
pushBlock({type: 'image', alt: imageMatch[1], url: imageMatch[2]});
|
|
1779
2027
|
index++;
|
|
1780
2028
|
continue;
|
|
1781
2029
|
}
|
|
@@ -1866,7 +2114,7 @@ function parseMarkdownImpl(
|
|
|
1866
2114
|
* `lineMap` says which input line each surviving line came from.
|
|
1867
2115
|
*/
|
|
1868
2116
|
function stampSourceRanges(
|
|
1869
|
-
blocks:
|
|
2117
|
+
blocks: MarkdownAstBlockContent[],
|
|
1870
2118
|
blockStartLines: number[],
|
|
1871
2119
|
blockEndLines: (number | undefined)[],
|
|
1872
2120
|
lines: string[],
|
|
@@ -1903,7 +2151,13 @@ function stampSourceRanges(
|
|
|
1903
2151
|
// range that dropped it would slice to something that re-parses
|
|
1904
2152
|
// differently.
|
|
1905
2153
|
const end = lineStart(endLine) + lines[endLine].length;
|
|
1906
|
-
blocks[i] = {
|
|
2154
|
+
blocks[i] = {
|
|
2155
|
+
...blocks[i],
|
|
2156
|
+
position: {
|
|
2157
|
+
start: {offset: lineStart(startLine)},
|
|
2158
|
+
end: {offset: end},
|
|
2159
|
+
},
|
|
2160
|
+
};
|
|
1907
2161
|
}
|
|
1908
2162
|
}
|
|
1909
2163
|
|
|
@@ -1968,6 +2222,13 @@ type IncrementalCache = {
|
|
|
1968
2222
|
/** The effective document-global definitions used by slice parses. */
|
|
1969
2223
|
linkDefs: ReadonlyMap<string, string>;
|
|
1970
2224
|
linkDefsKey: string;
|
|
2225
|
+
/** Canonical settled blocks shared by rendering and compatibility projection. */
|
|
2226
|
+
settledAstBlocks: MarkdownAstBlockContent[];
|
|
2227
|
+
settledRevision: number;
|
|
2228
|
+
projectedRevision: number;
|
|
2229
|
+
projectedSettledBlocks: BlockNodeWithMath[];
|
|
2230
|
+
/** Preserves released settled-node identity across projected snapshots. */
|
|
2231
|
+
projectionCache: LegacyProjectionCache;
|
|
1971
2232
|
work: IncrementalWork;
|
|
1972
2233
|
};
|
|
1973
2234
|
|
|
@@ -1986,6 +2247,11 @@ function makeIncrementalCache(
|
|
|
1986
2247
|
tailLinkDefs: new Map(),
|
|
1987
2248
|
linkDefs: defs,
|
|
1988
2249
|
linkDefsKey: linkDefsSignature(defs),
|
|
2250
|
+
settledAstBlocks: [],
|
|
2251
|
+
settledRevision: 0,
|
|
2252
|
+
projectedRevision: -1,
|
|
2253
|
+
projectedSettledBlocks: [],
|
|
2254
|
+
projectionCache: new WeakMap(),
|
|
1989
2255
|
work: {
|
|
1990
2256
|
splitCharacters: 0,
|
|
1991
2257
|
boundaryLines: 0,
|
|
@@ -2481,9 +2747,9 @@ function atOffset(opts: ResolvedOptions, offset: number): ResolvedOptions {
|
|
|
2481
2747
|
* lists even though the full-text parser joins them per CommonMark §5.3.
|
|
2482
2748
|
*/
|
|
2483
2749
|
function mergeSettledBlocks(
|
|
2484
|
-
prev:
|
|
2485
|
-
delta:
|
|
2486
|
-
):
|
|
2750
|
+
prev: MarkdownAstBlockContent[],
|
|
2751
|
+
delta: MarkdownAstBlockContent[],
|
|
2752
|
+
): MarkdownAstBlockContent[] {
|
|
2487
2753
|
if (prev.length === 0 || delta.length === 0) {
|
|
2488
2754
|
return [...prev, ...delta];
|
|
2489
2755
|
}
|
|
@@ -2495,17 +2761,21 @@ function mergeSettledBlocks(
|
|
|
2495
2761
|
prevLast.ordered === deltaFirst.ordered &&
|
|
2496
2762
|
prevLast.delimiter === deltaFirst.delimiter
|
|
2497
2763
|
) {
|
|
2498
|
-
const merged:
|
|
2764
|
+
const merged: MarkdownAstBlockContent = {
|
|
2499
2765
|
type: 'list',
|
|
2500
2766
|
ordered: prevLast.ordered,
|
|
2501
2767
|
start: prevLast.start,
|
|
2502
2768
|
delimiter: prevLast.delimiter,
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
// One list now, so one
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2769
|
+
spread: true,
|
|
2770
|
+
children: [...prevLast.children, ...deltaFirst.children],
|
|
2771
|
+
// One list now, so one position spans both halves.
|
|
2772
|
+
...(prevLast.position != null && deltaFirst.position != null
|
|
2773
|
+
? {
|
|
2774
|
+
position: {
|
|
2775
|
+
start: prevLast.position.start,
|
|
2776
|
+
end: deltaFirst.position.end,
|
|
2777
|
+
},
|
|
2778
|
+
}
|
|
2509
2779
|
: null),
|
|
2510
2780
|
};
|
|
2511
2781
|
return [...prev.slice(0, -1), merged, ...delta.slice(1)];
|
|
@@ -2515,15 +2785,15 @@ function mergeSettledBlocks(
|
|
|
2515
2785
|
|
|
2516
2786
|
/** Append a newly-settled slice without copying the already-settled prefix. */
|
|
2517
2787
|
function appendSettledBlocks(
|
|
2518
|
-
prev:
|
|
2519
|
-
delta:
|
|
2520
|
-
):
|
|
2788
|
+
prev: MarkdownAstBlockContent[],
|
|
2789
|
+
delta: MarkdownAstBlockContent[],
|
|
2790
|
+
): boolean {
|
|
2521
2791
|
if (delta.length === 0) {
|
|
2522
|
-
return;
|
|
2792
|
+
return false;
|
|
2523
2793
|
}
|
|
2524
2794
|
if (prev.length === 0) {
|
|
2525
2795
|
prev.push(...delta);
|
|
2526
|
-
return;
|
|
2796
|
+
return false;
|
|
2527
2797
|
}
|
|
2528
2798
|
const prevLast = prev[prev.length - 1];
|
|
2529
2799
|
const deltaFirst = delta[0];
|
|
@@ -2538,16 +2808,22 @@ function appendSettledBlocks(
|
|
|
2538
2808
|
ordered: prevLast.ordered,
|
|
2539
2809
|
start: prevLast.start,
|
|
2540
2810
|
delimiter: prevLast.delimiter,
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
...(prevLast.
|
|
2544
|
-
? {
|
|
2811
|
+
spread: true,
|
|
2812
|
+
children: [...prevLast.children, ...deltaFirst.children],
|
|
2813
|
+
...(prevLast.position != null && deltaFirst.position != null
|
|
2814
|
+
? {
|
|
2815
|
+
position: {
|
|
2816
|
+
start: prevLast.position.start,
|
|
2817
|
+
end: deltaFirst.position.end,
|
|
2818
|
+
},
|
|
2819
|
+
}
|
|
2545
2820
|
: null),
|
|
2546
2821
|
};
|
|
2547
2822
|
prev.push(...delta.slice(1));
|
|
2548
|
-
return;
|
|
2823
|
+
return true;
|
|
2549
2824
|
}
|
|
2550
2825
|
prev.push(...delta);
|
|
2826
|
+
return false;
|
|
2551
2827
|
}
|
|
2552
2828
|
|
|
2553
2829
|
function sameUnsettledDefinitions(
|
|
@@ -2583,6 +2859,11 @@ function resetIncrementalCache(
|
|
|
2583
2859
|
cache.tailLinkDefs = new Map();
|
|
2584
2860
|
cache.linkDefs = new Map();
|
|
2585
2861
|
cache.linkDefsKey = '';
|
|
2862
|
+
cache.settledAstBlocks = [];
|
|
2863
|
+
cache.settledRevision++;
|
|
2864
|
+
cache.projectedRevision = -1;
|
|
2865
|
+
cache.projectedSettledBlocks = [];
|
|
2866
|
+
cache.projectionCache = new WeakMap();
|
|
2586
2867
|
cache.work = {
|
|
2587
2868
|
splitCharacters: 0,
|
|
2588
2869
|
boundaryLines: 0,
|
|
@@ -2622,6 +2903,36 @@ export function parseMarkdownIncremental(
|
|
|
2622
2903
|
state: IncrementalState<boolean>,
|
|
2623
2904
|
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
2624
2905
|
): BlockNodeWithMath[] {
|
|
2906
|
+
const root = parseMarkdownAstIncremental(input, state, arg);
|
|
2907
|
+
const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
|
|
2908
|
+
const projected = projectMarkdownRoot(root, cache.projectionCache);
|
|
2909
|
+
if (cache.projectedRevision !== cache.settledRevision) {
|
|
2910
|
+
cache.projectedSettledBlocks = cache.settledAstBlocks.map(block =>
|
|
2911
|
+
projectBlockNode(block, cache.projectionCache),
|
|
2912
|
+
);
|
|
2913
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2914
|
+
}
|
|
2915
|
+
state.settledBlocks = cache.projectedSettledBlocks;
|
|
2916
|
+
return projected;
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
/** @internal Canonical incremental parse used by Markdown rendering. */
|
|
2920
|
+
export function parseMarkdownAstIncremental(
|
|
2921
|
+
input: string,
|
|
2922
|
+
state: IncrementalState<boolean>,
|
|
2923
|
+
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
2924
|
+
): MarkdownAstRoot {
|
|
2925
|
+
return {
|
|
2926
|
+
type: 'root',
|
|
2927
|
+
children: parseMarkdownIncrementalAstBlocks(input, state, arg),
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
function parseMarkdownIncrementalAstBlocks(
|
|
2932
|
+
input: string,
|
|
2933
|
+
state: IncrementalState<boolean>,
|
|
2934
|
+
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
2935
|
+
): MarkdownAstBlockContent[] {
|
|
2625
2936
|
const opts = resolveOptions(arg);
|
|
2626
2937
|
const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
|
|
2627
2938
|
let reparseSettled = false;
|
|
@@ -2746,17 +3057,39 @@ export function parseMarkdownIncremental(
|
|
|
2746
3057
|
|
|
2747
3058
|
let parsedSettledBlocks = 0;
|
|
2748
3059
|
if (reparseSettled) {
|
|
2749
|
-
|
|
3060
|
+
cache.settledAstBlocks = state.settledText
|
|
2750
3061
|
? parseMarkdownImpl(state.settledText, parseOpts)
|
|
2751
3062
|
: [];
|
|
2752
|
-
parsedSettledBlocks =
|
|
3063
|
+
parsedSettledBlocks = cache.settledAstBlocks.length;
|
|
3064
|
+
cache.settledRevision++;
|
|
3065
|
+
cache.projectedSettledBlocks = cache.settledAstBlocks.map(block =>
|
|
3066
|
+
projectBlockNode(block, cache.projectionCache),
|
|
3067
|
+
);
|
|
3068
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2753
3069
|
} else if (settledDelta !== '') {
|
|
2754
3070
|
const deltaBlocks = parseMarkdownImpl(
|
|
2755
3071
|
settledDelta,
|
|
2756
3072
|
atOffset(parseOpts, oldSettledEnd),
|
|
2757
3073
|
);
|
|
2758
|
-
|
|
3074
|
+
const mergeIndex = cache.settledAstBlocks.length - 1;
|
|
3075
|
+
const mergedList = appendSettledBlocks(cache.settledAstBlocks, deltaBlocks);
|
|
2759
3076
|
parsedSettledBlocks = deltaBlocks.length;
|
|
3077
|
+
cache.settledRevision++;
|
|
3078
|
+
const projectedDelta = deltaBlocks.map(block =>
|
|
3079
|
+
projectBlockNode(block, cache.projectionCache),
|
|
3080
|
+
);
|
|
3081
|
+
if (mergedList) {
|
|
3082
|
+
const canonicalMerged = cache.settledAstBlocks[mergeIndex];
|
|
3083
|
+
const projectedMerged = projectBlockNode(
|
|
3084
|
+
canonicalMerged,
|
|
3085
|
+
cache.projectionCache,
|
|
3086
|
+
);
|
|
3087
|
+
cache.projectedSettledBlocks[mergeIndex] = projectedMerged;
|
|
3088
|
+
cache.projectedSettledBlocks.push(...projectedDelta.slice(1));
|
|
3089
|
+
} else {
|
|
3090
|
+
cache.projectedSettledBlocks.push(...projectedDelta);
|
|
3091
|
+
}
|
|
3092
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2760
3093
|
}
|
|
2761
3094
|
|
|
2762
3095
|
// The unsettled tail is trimmed before parsing, so its offset in the
|
|
@@ -2790,7 +3123,7 @@ export function parseMarkdownIncremental(
|
|
|
2790
3123
|
definitionCharacters: settledDelta.length + unsettledInput.length,
|
|
2791
3124
|
renderedBlocks: parsedSettledBlocks + unsettledBlocks.length,
|
|
2792
3125
|
};
|
|
2793
|
-
return mergeSettledBlocks(
|
|
3126
|
+
return mergeSettledBlocks(cache.settledAstBlocks, unsettledBlocks);
|
|
2794
3127
|
}
|
|
2795
3128
|
|
|
2796
3129
|
// ---------------------------------------------------------------------------
|
|
@@ -2801,30 +3134,6 @@ export function parseMarkdownIncremental(
|
|
|
2801
3134
|
// derives its item ids from the same functions, so outline hash links always
|
|
2802
3135
|
// resolve to a rendered heading by construction.
|
|
2803
3136
|
|
|
2804
|
-
/** Flatten inline nodes into their plain text content. */
|
|
2805
|
-
export function inlineText(nodes: ReadonlyArray<InlineNodeWithMath>): string {
|
|
2806
|
-
return nodes
|
|
2807
|
-
.map(node => {
|
|
2808
|
-
switch (node.type) {
|
|
2809
|
-
case 'text':
|
|
2810
|
-
case 'code':
|
|
2811
|
-
case 'math':
|
|
2812
|
-
return node.type === 'math' ? node.value : node.content;
|
|
2813
|
-
case 'bold':
|
|
2814
|
-
case 'italic':
|
|
2815
|
-
case 'strikethrough':
|
|
2816
|
-
case 'link':
|
|
2817
|
-
return inlineText(node.children);
|
|
2818
|
-
case 'image':
|
|
2819
|
-
return node.alt;
|
|
2820
|
-
case 'citation':
|
|
2821
|
-
case 'break':
|
|
2822
|
-
return '';
|
|
2823
|
-
}
|
|
2824
|
-
})
|
|
2825
|
-
.join('');
|
|
2826
|
-
}
|
|
2827
|
-
|
|
2828
3137
|
/** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
|
|
2829
3138
|
export function slugify(value: string): string {
|
|
2830
3139
|
return value
|