@astryxdesign/core 0.6.1 → 0.6.2-canary.023c3e4
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/CHANGELOG.md +33 -0
- package/dist/BottomSheet/BottomSheet.d.ts +1 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +3 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +56 -19
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +2 -2
- 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/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +1 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +4 -4
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +16 -16
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +2 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +20 -13
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +8 -8
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
- package/dist/Dialog/DialogHeader.d.ts +1 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +10 -7
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -10
- 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 +18 -4
- 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 +150 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
- package/dist/Markdown/Markdown.js +311 -114
- 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 +5 -3
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/index.js +3 -2
- package/dist/Markdown/parser/index.d.ts +10 -0
- package/dist/Markdown/parser/index.d.ts.map +1 -0
- package/dist/Markdown/parser/index.js +10 -0
- package/dist/Markdown/parser.d.ts +220 -38
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +1072 -188
- package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
- package/dist/Markdown/plugins/frontmatter.js +132 -0
- package/dist/Markdown/plugins/index.d.ts +16 -0
- package/dist/Markdown/plugins/index.d.ts.map +1 -0
- package/dist/Markdown/plugins/index.js +13 -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/remark.d.ts +196 -0
- package/dist/Markdown/remark.d.ts.map +1 -0
- package/dist/Markdown/remark.js +1062 -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/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +4 -4
- 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/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +36 -31
- 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/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +1 -1
- 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 +19 -10
- package/dist/Spinner/Spinner.d.ts +1 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +45 -21
- 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/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +1 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +4 -4
- 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 +16 -7
- package/dist/astryx.css +19 -8
- package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
- package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
- package/dist/hooks/scrollKeyboardDelegation.js +146 -0
- package/dist/hooks/useScrollableArea.d.ts +6 -2
- package/dist/hooks/useScrollableArea.d.ts.map +1 -1
- package/dist/hooks/useScrollableArea.js +17 -5
- 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/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +22 -5
- package/scripts/agent-doc-state.mjs +1 -1
- package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
- package/src/BottomSheet/BottomSheet.spec.md +46 -20
- package/src/BottomSheet/BottomSheet.test.tsx +6 -3
- package/src/BottomSheet/BottomSheet.tsx +3 -1
- package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
- package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
- 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/Chat/ChatComposerInput.doc.mjs +3 -3
- package/src/Chat/ChatComposerInput.tsx +10 -2
- 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/CommandPalette/CommandPaletteInput.tsx +6 -1
- package/src/ComplexSelector/ComplexSelector.tsx +6 -1
- package/src/DateInput/DateInput.tsx +6 -1
- package/src/DateInput/NativeDateField.tsx +24 -8
- package/src/DateInput/TouchDateField.tsx +6 -2
- package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
- package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
- package/src/DateRangeInput/DateRangeInput.tsx +35 -21
- package/src/DateTimeInput/DateTimeInput.tsx +6 -1
- package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
- package/src/Dialog/Dialog.doc.mjs +3 -0
- package/src/Dialog/Dialog.spec.md +1 -1
- package/src/Dialog/DialogHeader.doc.mjs +38 -0
- package/src/Dialog/DialogHeader.test.tsx +49 -0
- package/src/Dialog/DialogHeader.tsx +23 -4
- package/src/Dialog/modules/DialogHeader.spec.md +152 -0
- package/src/Field/PanelSearchInput.tsx +5 -2
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
- package/src/FileInput/FileInput.doc.mjs +2 -0
- package/src/FileInput/FileInput.spec.md +199 -0
- package/src/FileInput/FileInput.test.tsx +14 -0
- package/src/FileInput/FileInput.tsx +24 -5
- 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/Layout/LayoutFooter.doc.mjs +17 -0
- 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 +439 -42
- package/src/Markdown/Markdown.fr23.bench.ts +174 -0
- package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
- package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
- package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
- package/src/Markdown/Markdown.public.test.ts +280 -0
- package/src/Markdown/Markdown.spec.md +304 -72
- package/src/Markdown/Markdown.test.tsx +118 -3
- package/src/Markdown/Markdown.tsx +489 -129
- package/src/Markdown/ast.test.ts +307 -0
- package/src/Markdown/ast.ts +337 -0
- package/src/Markdown/incremental.test.ts +187 -7
- package/src/Markdown/index.ts +43 -1
- package/src/Markdown/modules/remark.spec.md +269 -0
- package/src/Markdown/parser/index.ts +64 -0
- package/src/Markdown/parser.perf.test.ts +3 -1
- package/src/Markdown/parser.public.test.ts +103 -0
- package/src/Markdown/parser.test.ts +122 -0
- package/src/Markdown/parser.ts +1721 -248
- package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
- package/src/Markdown/plugins/frontmatter.ts +198 -0
- package/src/Markdown/plugins/index.ts +53 -0
- package/src/Markdown/plugins/protocol.test.tsx +1022 -0
- package/src/Markdown/plugins/protocol.ts +1618 -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/remark.test.tsx +1877 -0
- package/src/Markdown/remark.ts +1574 -0
- package/src/Markdown/url.ts +30 -0
- package/src/Markdown/utils.ts +19 -1
- package/src/MultiSelector/MultiSelector.test.tsx +1 -35
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
- package/src/NumberInput/NumberInput.tsx +6 -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 +40 -10
- package/src/Outline/useOutlineFromMarkdown.ts +15 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- package/src/ProgressBar/ProgressBar.test.tsx +29 -11
- package/src/ProgressBar/ProgressBar.tsx +59 -72
- 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/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
- 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/Selector/Selector.spec.md +12 -9
- package/src/Selector/Selector.tsx +6 -1
- package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
- package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
- package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
- package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
- package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
- 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 +27 -0
- package/src/Slider/Slider.spec.md +61 -47
- package/src/Slider/Slider.test.tsx +146 -0
- package/src/Slider/Slider.tsx +37 -13
- package/src/Spinner/Spinner.doc.mjs +6 -3
- package/src/Spinner/Spinner.test.tsx +41 -0
- package/src/Spinner/Spinner.tsx +62 -32
- 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/TextArea/TextArea.tsx +6 -1
- package/src/TextInput/TextInput.test.tsx +20 -1
- package/src/TextInput/TextInput.tsx +6 -1
- package/src/TimeInput/TimeInput.tsx +6 -1
- 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/TreeList/TreeList.spec.md +123 -75
- package/src/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +21 -7
- 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/hooks/scrollKeyboardDelegation.test.ts +155 -0
- package/src/hooks/scrollKeyboardDelegation.ts +233 -0
- package/src/hooks/useScrollableArea.doc.mjs +15 -3
- package/src/hooks/useScrollableArea.test.tsx +59 -1
- package/src/hooks/useScrollableArea.ts +34 -10
- package/src/inputFontFloor.test.ts +106 -0
- package/src/theme/derivedVarRegistry.test.ts +6 -4
- package/src/utils/interactionOverlay.stylex.ts +20 -0
package/dist/Markdown/parser.js
CHANGED
|
@@ -2,17 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file parser.ts
|
|
5
|
-
* @input Markdown string
|
|
6
|
-
* @output
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* @input Markdown string, released parse options, and optional ordered plugins
|
|
6
|
+
* @output Canonical MDAST-aligned nodes for public/server consumers plus unchanged
|
|
7
|
+
* released parser-node projections; shared heading slug helpers
|
|
8
|
+
* @position Core parser and compatibility boundary; consumed by public parser entry,
|
|
9
|
+
* Markdown, and Outline
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { getMarkdownAstLegacyCodeLanguage, markMarkdownAstLegacyCodeLanguage } from "./ast.js";
|
|
13
|
+
import { applyMarkdownTransforms, freezeMarkdownPluginData, isMarkdownPluginData, prepareMarkdownPlugins, reportMarkdownPluginFailure } from "./plugins/protocol.js";
|
|
14
|
+
import { isSafeMarkdownParserUrl } from "./url.js";
|
|
15
|
+
|
|
12
16
|
// ---------------------------------------------------------------------------
|
|
13
17
|
// Types
|
|
14
18
|
// ---------------------------------------------------------------------------
|
|
15
19
|
|
|
20
|
+
/** Nodes returned by default and legacy parser calls. */
|
|
21
|
+
|
|
22
|
+
/** The additional inline node returned only when parsing with `math: true`. */
|
|
23
|
+
|
|
24
|
+
/** Nodes returned by an explicitly math-enabled inline parse. */
|
|
25
|
+
|
|
26
|
+
/** Blocks returned by default and legacy parser calls. */
|
|
27
|
+
|
|
28
|
+
/** The additional block returned only when parsing with `math: true`. */
|
|
29
|
+
|
|
30
|
+
/** Blocks returned by an explicitly math-enabled block parse. */
|
|
31
|
+
|
|
16
32
|
/**
|
|
17
33
|
* Where a block sits in the source string handed to `parseMarkdown`:
|
|
18
34
|
* `source.slice(start, end)` is the block, and `end` excludes the block's
|
|
@@ -23,6 +39,240 @@
|
|
|
23
39
|
* optional fields without breaking anyone.
|
|
24
40
|
*/
|
|
25
41
|
|
|
42
|
+
function projectRange(position) {
|
|
43
|
+
const start = position?.start.offset;
|
|
44
|
+
const end = position?.end.offset;
|
|
45
|
+
return start == null || end == null ? {} : {
|
|
46
|
+
range: {
|
|
47
|
+
start,
|
|
48
|
+
end
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Canonical nodes always carry Core-authored positions; the released block
|
|
55
|
+
* shape exposes them as `range` only when the caller passed `sourceRanges`,
|
|
56
|
+
* so this projection stays byte-for-byte what it has always returned.
|
|
57
|
+
*
|
|
58
|
+
* An extension node is the one kind the projection passes through instead of
|
|
59
|
+
* rebuilding field by field, so it is the one kind that could carry a
|
|
60
|
+
* canonical `position` out into released output. Strip it unless the caller
|
|
61
|
+
* asked for provenance, in which case the node keeps exactly the `position`
|
|
62
|
+
* it has always exposed.
|
|
63
|
+
*/
|
|
64
|
+
function projectExtensionNode(node, withRanges) {
|
|
65
|
+
if (withRanges || node.position === undefined) {
|
|
66
|
+
return node;
|
|
67
|
+
}
|
|
68
|
+
const {
|
|
69
|
+
position: _canonicalPosition,
|
|
70
|
+
...released
|
|
71
|
+
} = node;
|
|
72
|
+
return released;
|
|
73
|
+
}
|
|
74
|
+
function projectInlineNode(node, cache, withRanges) {
|
|
75
|
+
const cached = cache.get(node);
|
|
76
|
+
if (cached != null) {
|
|
77
|
+
return cached;
|
|
78
|
+
}
|
|
79
|
+
let projected;
|
|
80
|
+
switch (node.type) {
|
|
81
|
+
case 'text':
|
|
82
|
+
projected = {
|
|
83
|
+
type: 'text',
|
|
84
|
+
content: node.value
|
|
85
|
+
};
|
|
86
|
+
break;
|
|
87
|
+
case 'strong':
|
|
88
|
+
projected = {
|
|
89
|
+
type: 'bold',
|
|
90
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
91
|
+
};
|
|
92
|
+
break;
|
|
93
|
+
case 'emphasis':
|
|
94
|
+
projected = {
|
|
95
|
+
type: 'italic',
|
|
96
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
97
|
+
};
|
|
98
|
+
break;
|
|
99
|
+
case 'delete':
|
|
100
|
+
projected = {
|
|
101
|
+
type: 'strikethrough',
|
|
102
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
103
|
+
};
|
|
104
|
+
break;
|
|
105
|
+
case 'inlineCode':
|
|
106
|
+
projected = {
|
|
107
|
+
type: 'code',
|
|
108
|
+
content: node.value
|
|
109
|
+
};
|
|
110
|
+
break;
|
|
111
|
+
case 'inlineMath':
|
|
112
|
+
projected = {
|
|
113
|
+
type: 'math',
|
|
114
|
+
value: node.value
|
|
115
|
+
};
|
|
116
|
+
break;
|
|
117
|
+
case 'link':
|
|
118
|
+
projected = {
|
|
119
|
+
type: 'link',
|
|
120
|
+
href: node.url,
|
|
121
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
122
|
+
};
|
|
123
|
+
break;
|
|
124
|
+
case 'image':
|
|
125
|
+
projected = {
|
|
126
|
+
type: 'image',
|
|
127
|
+
src: node.url,
|
|
128
|
+
alt: node.alt
|
|
129
|
+
};
|
|
130
|
+
break;
|
|
131
|
+
case 'citation':
|
|
132
|
+
projected = {
|
|
133
|
+
type: 'citation',
|
|
134
|
+
sourceId: node.sourceId
|
|
135
|
+
};
|
|
136
|
+
break;
|
|
137
|
+
case 'break':
|
|
138
|
+
projected = {
|
|
139
|
+
type: 'break'
|
|
140
|
+
};
|
|
141
|
+
break;
|
|
142
|
+
case 'extension':
|
|
143
|
+
projected = projectExtensionNode(node, withRanges);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
cache.set(node, projected);
|
|
147
|
+
return projected;
|
|
148
|
+
}
|
|
149
|
+
function projectTableCell(node, cache, withRanges) {
|
|
150
|
+
const cached = cache.get(node);
|
|
151
|
+
if (cached != null) {
|
|
152
|
+
return cached;
|
|
153
|
+
}
|
|
154
|
+
const projected = {
|
|
155
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges))
|
|
156
|
+
};
|
|
157
|
+
cache.set(node, projected);
|
|
158
|
+
return projected;
|
|
159
|
+
}
|
|
160
|
+
function projectListItem(node, cache, withRanges) {
|
|
161
|
+
const cached = cache.get(node);
|
|
162
|
+
if (cached != null) {
|
|
163
|
+
return cached;
|
|
164
|
+
}
|
|
165
|
+
const projected = {
|
|
166
|
+
checked: node.checked,
|
|
167
|
+
children: node.children.map(child => projectBlockNode(child, cache, withRanges))
|
|
168
|
+
};
|
|
169
|
+
cache.set(node, projected);
|
|
170
|
+
return projected;
|
|
171
|
+
}
|
|
172
|
+
function projectBlockNode(node, cache, withRanges) {
|
|
173
|
+
const cached = cache.get(node);
|
|
174
|
+
if (cached != null) {
|
|
175
|
+
return cached;
|
|
176
|
+
}
|
|
177
|
+
const metadata = withRanges ? projectRange(node.position) : {};
|
|
178
|
+
let projected;
|
|
179
|
+
switch (node.type) {
|
|
180
|
+
case 'heading':
|
|
181
|
+
projected = {
|
|
182
|
+
type: 'heading',
|
|
183
|
+
level: node.depth,
|
|
184
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges)),
|
|
185
|
+
...metadata
|
|
186
|
+
};
|
|
187
|
+
break;
|
|
188
|
+
case 'paragraph':
|
|
189
|
+
projected = {
|
|
190
|
+
type: 'paragraph',
|
|
191
|
+
children: node.children.map(child => projectInlineNode(child, cache, withRanges)),
|
|
192
|
+
...metadata
|
|
193
|
+
};
|
|
194
|
+
break;
|
|
195
|
+
case 'code':
|
|
196
|
+
projected = {
|
|
197
|
+
type: 'codeblock',
|
|
198
|
+
language: getMarkdownAstLegacyCodeLanguage(node) ?? 'plaintext',
|
|
199
|
+
content: node.value,
|
|
200
|
+
...metadata
|
|
201
|
+
};
|
|
202
|
+
break;
|
|
203
|
+
case 'math':
|
|
204
|
+
projected = {
|
|
205
|
+
type: 'math',
|
|
206
|
+
value: node.value,
|
|
207
|
+
...metadata
|
|
208
|
+
};
|
|
209
|
+
break;
|
|
210
|
+
case 'blockquote':
|
|
211
|
+
projected = {
|
|
212
|
+
type: 'blockquote',
|
|
213
|
+
children: node.children.map(child => projectBlockNode(child, cache, withRanges)),
|
|
214
|
+
...metadata
|
|
215
|
+
};
|
|
216
|
+
break;
|
|
217
|
+
case 'list':
|
|
218
|
+
projected = {
|
|
219
|
+
type: 'list',
|
|
220
|
+
ordered: node.ordered,
|
|
221
|
+
start: node.start,
|
|
222
|
+
delimiter: node.delimiter,
|
|
223
|
+
loose: node.spread,
|
|
224
|
+
items: node.children.map(item => projectListItem(item, cache, withRanges)),
|
|
225
|
+
...metadata
|
|
226
|
+
};
|
|
227
|
+
break;
|
|
228
|
+
case 'table':
|
|
229
|
+
{
|
|
230
|
+
const [header = {
|
|
231
|
+
type: 'tableRow',
|
|
232
|
+
children: []
|
|
233
|
+
}, ...rows] = node.children;
|
|
234
|
+
projected = {
|
|
235
|
+
type: 'table',
|
|
236
|
+
headers: header.children.map(cell => projectTableCell(cell, cache, withRanges)),
|
|
237
|
+
alignments: [...node.align],
|
|
238
|
+
rows: rows.map(row => row.children.map(cell => projectTableCell(cell, cache, withRanges))),
|
|
239
|
+
...metadata
|
|
240
|
+
};
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case 'thematicBreak':
|
|
244
|
+
projected = {
|
|
245
|
+
type: 'hr',
|
|
246
|
+
...metadata
|
|
247
|
+
};
|
|
248
|
+
break;
|
|
249
|
+
case 'image':
|
|
250
|
+
projected = {
|
|
251
|
+
type: 'image',
|
|
252
|
+
alt: node.alt,
|
|
253
|
+
src: node.url,
|
|
254
|
+
...metadata
|
|
255
|
+
};
|
|
256
|
+
break;
|
|
257
|
+
case 'extension':
|
|
258
|
+
projected = projectExtensionNode(node, withRanges);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
cache.set(node, projected);
|
|
262
|
+
return projected;
|
|
263
|
+
}
|
|
264
|
+
function projectInlineNodes(nodes, withRanges, cache = new WeakMap()) {
|
|
265
|
+
return nodes.map(node => projectInlineNode(node, cache, withRanges));
|
|
266
|
+
}
|
|
267
|
+
function projectMarkdownRoot(root, withRanges, cache = new WeakMap()) {
|
|
268
|
+
return root.children.map(node => projectBlockNode(node, cache, withRanges));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Whether a caller asked for the released `range` field on its blocks. */
|
|
272
|
+
function wantsLegacyRanges(arg) {
|
|
273
|
+
return arg != null && typeof arg.has !== 'function' && arg.sourceRanges === true;
|
|
274
|
+
}
|
|
275
|
+
|
|
26
276
|
// ---------------------------------------------------------------------------
|
|
27
277
|
// Parse options
|
|
28
278
|
// ---------------------------------------------------------------------------
|
|
@@ -35,13 +285,45 @@
|
|
|
35
285
|
* `ReadonlySet<string>` shape as the second argument.
|
|
36
286
|
*/
|
|
37
287
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
288
|
+
/** Options for default and legacy parser results. */
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Options that explicitly parse `$…$` and `$$…$$` into math-enabled result
|
|
292
|
+
* unions. Keeping this separate prevents a legacy `ParseOptions` annotation
|
|
293
|
+
* from silently widening an exhaustive node switch.
|
|
294
|
+
*/
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Every resolved options object is built here, so all of them share one key
|
|
298
|
+
* set in one order — and therefore one hidden class. The parser reads these
|
|
299
|
+
* fields in its hottest loops; letting an omitted-options parse and an
|
|
300
|
+
* options-bag parse produce differently-shaped objects makes those reads
|
|
301
|
+
* polymorphic in any process that does both, which is exactly what a caller
|
|
302
|
+
* comparing an empty pipeline against a configured one does.
|
|
303
|
+
*/
|
|
304
|
+
function makeResolvedOptions(fields) {
|
|
305
|
+
return {
|
|
306
|
+
sourceIds: fields.sourceIds,
|
|
307
|
+
autolink: fields.autolink,
|
|
308
|
+
math: fields.math,
|
|
309
|
+
sourceRanges: fields.sourceRanges,
|
|
310
|
+
astPositions: fields.astPositions ?? true,
|
|
311
|
+
plugins: fields.plugins,
|
|
312
|
+
isFinal: fields.isFinal,
|
|
313
|
+
allowBlockSyntax: fields.allowBlockSyntax ?? true,
|
|
314
|
+
baseOffset: fields.baseOffset,
|
|
315
|
+
linkDefs: fields.linkDefs
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const EMPTY_OPTS = makeResolvedOptions({
|
|
319
|
+
isFinal: true
|
|
320
|
+
});
|
|
321
|
+
const EMPTY_INCREMENTAL_OPTS = makeResolvedOptions({
|
|
322
|
+
isFinal: false
|
|
323
|
+
});
|
|
324
|
+
function resolveOptions(arg, incremental = false) {
|
|
43
325
|
if (arg == null) {
|
|
44
|
-
return EMPTY_OPTS;
|
|
326
|
+
return incremental ? EMPTY_INCREMENTAL_OPTS : EMPTY_OPTS;
|
|
45
327
|
}
|
|
46
328
|
// Duck-type the legacy `ReadonlySet<string>` form: any object whose
|
|
47
329
|
// `.has` is callable is treated as the legacy sourceIds set. This is
|
|
@@ -49,17 +331,20 @@ function resolveOptions(arg) {
|
|
|
49
331
|
// or polyfilled `ReadonlySet` implementations as a `ParseOptions` bag
|
|
50
332
|
// and silently lose citation resolution.
|
|
51
333
|
if (typeof arg.has === 'function') {
|
|
52
|
-
return {
|
|
334
|
+
return makeResolvedOptions({
|
|
53
335
|
sourceIds: arg,
|
|
54
|
-
|
|
55
|
-
};
|
|
336
|
+
isFinal: !incremental
|
|
337
|
+
});
|
|
56
338
|
}
|
|
57
339
|
const opts = arg;
|
|
58
|
-
return {
|
|
340
|
+
return makeResolvedOptions({
|
|
59
341
|
sourceIds: opts.sourceIds,
|
|
60
342
|
autolink: opts.autolink,
|
|
61
|
-
|
|
62
|
-
|
|
343
|
+
math: opts.math === true ? true : undefined,
|
|
344
|
+
sourceRanges: opts.sourceRanges,
|
|
345
|
+
plugins: opts.plugins != null && opts.plugins.length > 0 ? prepareMarkdownPlugins(opts.plugins) : undefined,
|
|
346
|
+
isFinal: incremental ? opts.isFinal === true : true
|
|
347
|
+
});
|
|
63
348
|
}
|
|
64
349
|
|
|
65
350
|
// ---------------------------------------------------------------------------
|
|
@@ -83,6 +368,106 @@ const LINK_TITLE_ONLY_RE = /^ {0,3}(?:"[^"\n]*"|'[^'\n]*'|\([^()\n]*\))[ \t]*$/;
|
|
|
83
368
|
function normalizeLinkLabel(label) {
|
|
84
369
|
return label.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
85
370
|
}
|
|
371
|
+
function stripBlockquoteMarkers(line) {
|
|
372
|
+
let content = line.endsWith('\r') ? line.slice(0, -1) : line;
|
|
373
|
+
let quoteDepth = 0;
|
|
374
|
+
while (true) {
|
|
375
|
+
const marker = /^ {0,3}> ?/.exec(content);
|
|
376
|
+
if (marker == null) {
|
|
377
|
+
return {
|
|
378
|
+
content,
|
|
379
|
+
quoteDepth
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
content = content.slice(marker[0].length);
|
|
383
|
+
quoteDepth++;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function stripExactBlockquoteDepth(line, quoteDepth) {
|
|
387
|
+
let content = line;
|
|
388
|
+
for (let depth = 0; depth < quoteDepth; depth++) {
|
|
389
|
+
const marker = /^ {0,3}> ?/.exec(content);
|
|
390
|
+
if (marker == null) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
content = content.slice(marker[0].length);
|
|
394
|
+
}
|
|
395
|
+
return content;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Recognize a standalone display-math marker at the current container boundary.
|
|
400
|
+
* A list opener carries its base indent so an indented continuation marker can
|
|
401
|
+
* close it; blockquotes must keep the same quote depth.
|
|
402
|
+
*/
|
|
403
|
+
function displayMathContainer(line) {
|
|
404
|
+
const outer = stripBlockquoteMarkers(line);
|
|
405
|
+
const listMarker = /^( {0,9})(?:[-*+]|\d+[.)]) +(.*)$/.exec(outer.content);
|
|
406
|
+
if (listMarker != null) {
|
|
407
|
+
const taskMarker = /^\[[ xX]\] +(.*)$/.exec(listMarker[2]);
|
|
408
|
+
const inner = stripBlockquoteMarkers(taskMarker?.[1] ?? listMarker[2]);
|
|
409
|
+
return inner.content.trim() === '$$' ? {
|
|
410
|
+
outerQuoteDepth: outer.quoteDepth,
|
|
411
|
+
listBaseIndent: listMarker[1].length,
|
|
412
|
+
innerQuoteDepth: inner.quoteDepth
|
|
413
|
+
} : null;
|
|
414
|
+
}
|
|
415
|
+
return outer.content.trim() === '$$' ? {
|
|
416
|
+
outerQuoteDepth: outer.quoteDepth,
|
|
417
|
+
listBaseIndent: null,
|
|
418
|
+
innerQuoteDepth: 0
|
|
419
|
+
} : null;
|
|
420
|
+
}
|
|
421
|
+
function displayMathLineState(line, container) {
|
|
422
|
+
const outerContent = stripExactBlockquoteDepth(line, container.outerQuoteDepth);
|
|
423
|
+
if (outerContent == null) {
|
|
424
|
+
return 'outside';
|
|
425
|
+
}
|
|
426
|
+
if (container.listBaseIndent == null) {
|
|
427
|
+
// A deeper quote starts a different container. It cannot close or continue
|
|
428
|
+
// the math expression owned by the shallower quote.
|
|
429
|
+
if (/^ {0,3}> ?/.test(outerContent)) {
|
|
430
|
+
return 'outside';
|
|
431
|
+
}
|
|
432
|
+
return outerContent.trim() === '$$' ? 'close' : 'inside';
|
|
433
|
+
}
|
|
434
|
+
const continuationIndent = outerContent.length - outerContent.trimStart().length;
|
|
435
|
+
if (continuationIndent <= container.listBaseIndent) {
|
|
436
|
+
return 'outside';
|
|
437
|
+
}
|
|
438
|
+
const innerContent = stripExactBlockquoteDepth(outerContent.trimStart(), container.innerQuoteDepth);
|
|
439
|
+
if (innerContent == null || /^ {0,3}> ?/.test(innerContent)) {
|
|
440
|
+
return 'outside';
|
|
441
|
+
}
|
|
442
|
+
return innerContent.trim() === '$$' ? 'close' : 'inside';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Match a complete `$$…$$` display-math block without consuming partial input. */
|
|
446
|
+
function matchDisplayMathBlock(lines, lineIndex) {
|
|
447
|
+
const trimmed = lines[lineIndex].trim();
|
|
448
|
+
if (trimmed.length > 4 && trimmed.startsWith('$$') && trimmed.endsWith('$$')) {
|
|
449
|
+
const value = trimmed.slice(2, -2);
|
|
450
|
+
return value.trim() === '' ? null : {
|
|
451
|
+
value,
|
|
452
|
+
nextIndex: lineIndex + 1,
|
|
453
|
+
endLine: lineIndex
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (trimmed !== '$$') {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
for (let index = lineIndex + 1; index < lines.length; index++) {
|
|
460
|
+
if (lines[index].trim() === '$$') {
|
|
461
|
+
const value = lines.slice(lineIndex + 1, index).join('\n');
|
|
462
|
+
return value.trim() === '' ? null : {
|
|
463
|
+
value,
|
|
464
|
+
nextIndex: index + 1,
|
|
465
|
+
endLine: index
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
86
471
|
function matchLinkDefinition(line) {
|
|
87
472
|
const match = LINK_DEFINITION_RE.exec(line);
|
|
88
473
|
if (match == null) {
|
|
@@ -121,7 +506,7 @@ function matchLinkDefinition(line) {
|
|
|
121
506
|
* where every definition is global. Separating a footer definition block with
|
|
122
507
|
* a blank line — the usual form — always works.
|
|
123
508
|
*/
|
|
124
|
-
function extractLinkDefinitions(input) {
|
|
509
|
+
function extractLinkDefinitions(input, math = false) {
|
|
125
510
|
const lines = input.split('\n');
|
|
126
511
|
const defs = new Map();
|
|
127
512
|
const keep = new Array(lines.length).fill(true);
|
|
@@ -141,6 +526,16 @@ function extractLinkDefinitions(input) {
|
|
|
141
526
|
}
|
|
142
527
|
continue;
|
|
143
528
|
}
|
|
529
|
+
if (math) {
|
|
530
|
+
const displayMath = matchDisplayMathBlock(lines, index);
|
|
531
|
+
if (displayMath != null) {
|
|
532
|
+
// Math is opaque Markdown content: definition-shaped TeX must not leak
|
|
533
|
+
// into the document-wide link-definition map.
|
|
534
|
+
index = displayMath.endLine;
|
|
535
|
+
atBoundary = true;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
144
539
|
const fenceMatch = line.match(/^(`{3,}|~{3,})/);
|
|
145
540
|
if (fenceMatch) {
|
|
146
541
|
inFence = true;
|
|
@@ -193,6 +588,11 @@ function extractLinkDefinitions(input) {
|
|
|
193
588
|
};
|
|
194
589
|
}
|
|
195
590
|
|
|
591
|
+
/** Order-independent signature of a citation-source set, for cache checks. */
|
|
592
|
+
function sourceIdsSignature(sourceIds) {
|
|
593
|
+
return sourceIds == null || sourceIds.size === 0 ? '' : [...sourceIds].sort().join('\u0000');
|
|
594
|
+
}
|
|
595
|
+
|
|
196
596
|
/** Order-independent signature of a link-definition set, for cache checks. */
|
|
197
597
|
function linkDefsSignature(defs) {
|
|
198
598
|
if (defs.size === 0) {
|
|
@@ -207,6 +607,12 @@ function linkDefsSignature(defs) {
|
|
|
207
607
|
* Returns the node plus the index just past the reference, or null when it is
|
|
208
608
|
* not a resolvable reference (caller falls through to literal handling).
|
|
209
609
|
*/
|
|
610
|
+
function protectedInlineOptions(opts) {
|
|
611
|
+
return opts.plugins == null ? opts : {
|
|
612
|
+
...opts,
|
|
613
|
+
plugins: undefined
|
|
614
|
+
};
|
|
615
|
+
}
|
|
210
616
|
function matchReferenceLink(text, start, linkDefs, opts) {
|
|
211
617
|
const textClose = text.indexOf(']', start + 1);
|
|
212
618
|
if (textClose === -1) {
|
|
@@ -223,12 +629,12 @@ function matchReferenceLink(text, start, linkDefs, opts) {
|
|
|
223
629
|
// matches nothing.
|
|
224
630
|
const label = rawLabel === '' ? linkText : rawLabel;
|
|
225
631
|
const href = linkDefs.get(normalizeLinkLabel(label));
|
|
226
|
-
if (href != null &&
|
|
632
|
+
if (href != null && isSafeMarkdownParserUrl(href)) {
|
|
227
633
|
return {
|
|
228
634
|
node: {
|
|
229
635
|
type: 'link',
|
|
230
|
-
href,
|
|
231
|
-
children: parseInlineImpl(linkText, opts)
|
|
636
|
+
url: href,
|
|
637
|
+
children: parseInlineImpl(linkText, protectedInlineOptions(opts))
|
|
232
638
|
},
|
|
233
639
|
end: labelClose + 1
|
|
234
640
|
};
|
|
@@ -242,14 +648,14 @@ function matchReferenceLink(text, start, linkDefs, opts) {
|
|
|
242
648
|
return null;
|
|
243
649
|
}
|
|
244
650
|
const href = linkDefs.get(normalizeLinkLabel(linkText));
|
|
245
|
-
if (href == null || !
|
|
651
|
+
if (href == null || !isSafeMarkdownParserUrl(href)) {
|
|
246
652
|
return null;
|
|
247
653
|
}
|
|
248
654
|
return {
|
|
249
655
|
node: {
|
|
250
656
|
type: 'link',
|
|
251
|
-
href,
|
|
252
|
-
children: parseInlineImpl(linkText, opts)
|
|
657
|
+
url: href,
|
|
658
|
+
children: parseInlineImpl(linkText, protectedInlineOptions(opts))
|
|
253
659
|
},
|
|
254
660
|
end: textClose + 1
|
|
255
661
|
};
|
|
@@ -268,11 +674,11 @@ function matchReferenceImage(text, start, linkDefs) {
|
|
|
268
674
|
const rawLabel = text.slice(altClose + 2, labelClose);
|
|
269
675
|
const label = rawLabel === '' ? alt : rawLabel;
|
|
270
676
|
const src = linkDefs.get(normalizeLinkLabel(label));
|
|
271
|
-
if (src != null &&
|
|
677
|
+
if (src != null && isSafeMarkdownParserUrl(src)) {
|
|
272
678
|
return {
|
|
273
679
|
node: {
|
|
274
680
|
type: 'image',
|
|
275
|
-
src,
|
|
681
|
+
url: src,
|
|
276
682
|
alt
|
|
277
683
|
},
|
|
278
684
|
end: labelClose + 1
|
|
@@ -285,13 +691,13 @@ function matchReferenceImage(text, start, linkDefs) {
|
|
|
285
691
|
return null;
|
|
286
692
|
}
|
|
287
693
|
const src = linkDefs.get(normalizeLinkLabel(alt));
|
|
288
|
-
if (src == null || !
|
|
694
|
+
if (src == null || !isSafeMarkdownParserUrl(src)) {
|
|
289
695
|
return null;
|
|
290
696
|
}
|
|
291
697
|
return {
|
|
292
698
|
node: {
|
|
293
699
|
type: 'image',
|
|
294
|
-
src,
|
|
700
|
+
url: src,
|
|
295
701
|
alt
|
|
296
702
|
},
|
|
297
703
|
end: altClose + 1
|
|
@@ -324,27 +730,50 @@ function isWordChar(ch) {
|
|
|
324
730
|
return /\w/.test(ch);
|
|
325
731
|
}
|
|
326
732
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
733
|
+
/** True when the character at `index` is preceded by an odd backslash run. */
|
|
734
|
+
function isEscaped(text, index) {
|
|
735
|
+
let backslashes = 0;
|
|
736
|
+
for (let i = index - 1; i >= 0 && text[i] === '\\'; i--) {
|
|
737
|
+
backslashes++;
|
|
738
|
+
}
|
|
739
|
+
return backslashes % 2 === 1;
|
|
740
|
+
}
|
|
330
741
|
|
|
331
742
|
/**
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
743
|
+
* Find the closing delimiter for `$…$` math on the same line.
|
|
744
|
+
*
|
|
745
|
+
* The whitespace and numeric-edge rules mirror common dollar-math parsers:
|
|
746
|
+
* whitespace cannot hug the delimiters, a digit cannot sit immediately before
|
|
747
|
+
* the opener or after the closer, and `$$` is reserved for display math. The
|
|
748
|
+
* numeric guard prevents ordinary prose such as "$20 and $30" from becoming a
|
|
749
|
+
* formula even in a math-enabled document.
|
|
335
750
|
*/
|
|
336
|
-
function
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (lower.startsWith('javascript:') || lower.startsWith('vbscript:') || lower.startsWith('data:text/html')) {
|
|
343
|
-
return false;
|
|
751
|
+
function isInlineMathStart(text, index) {
|
|
752
|
+
return text[index] === '$' && text[index - 1] !== '$' && text[index + 1] !== '$' && text[index + 1] != null && !/\s/.test(text[index + 1]) && !/\d/.test(text[index - 1] ?? '') && !isEscaped(text, index);
|
|
753
|
+
}
|
|
754
|
+
function findInlineMathEnd(text, start) {
|
|
755
|
+
if (!isInlineMathStart(text, start)) {
|
|
756
|
+
return -1;
|
|
344
757
|
}
|
|
345
|
-
|
|
758
|
+
for (let index = start + 1; index < text.length; index++) {
|
|
759
|
+
if (text[index] === '\n') {
|
|
760
|
+
return -1;
|
|
761
|
+
}
|
|
762
|
+
if (text[index] !== '$' || isEscaped(text, index)) {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
// An unescaped dollar ends this candidate: it either forms a valid closer
|
|
766
|
+
// or makes the whole span literal. Never skip over one and pair with a
|
|
767
|
+
// later dollar, which would swallow currency or another expression.
|
|
768
|
+
if (text[index - 1] === '$' || text[index + 1] === '$' || /\s/.test(text[index - 1]) || /\d/.test(text[index + 1] ?? '')) {
|
|
769
|
+
return -1;
|
|
770
|
+
}
|
|
771
|
+
return index;
|
|
772
|
+
}
|
|
773
|
+
return -1;
|
|
346
774
|
}
|
|
347
775
|
|
|
776
|
+
// ---------------------------------------------------------------------------
|
|
348
777
|
// ---------------------------------------------------------------------------
|
|
349
778
|
// Inline parser
|
|
350
779
|
// ---------------------------------------------------------------------------
|
|
@@ -395,8 +824,137 @@ function matchBracketCitation(text, i, opts) {
|
|
|
395
824
|
end: closeIndex + 1
|
|
396
825
|
};
|
|
397
826
|
}
|
|
827
|
+
function currentLineStart(source, offset) {
|
|
828
|
+
return source.lastIndexOf('\n', offset - 1) + 1;
|
|
829
|
+
}
|
|
830
|
+
function matchExtensionSyntax(source, offset, context, opts) {
|
|
831
|
+
const byFirstCharacter = context === 'inline' ? opts.plugins?.inlineByFirstCharacter : opts.allowBlockSyntax === false ? undefined : opts.plugins?.blockByFirstCharacter;
|
|
832
|
+
const candidates = byFirstCharacter?.get(source[offset]);
|
|
833
|
+
if (candidates == null) {
|
|
834
|
+
return {
|
|
835
|
+
status: 'none'
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
const seen = new Set();
|
|
839
|
+
for (const candidate of candidates) {
|
|
840
|
+
if (seen.has(candidate)) {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
seen.add(candidate);
|
|
844
|
+
let fullPrefix = false;
|
|
845
|
+
for (const prefix of candidate.contribution.startsWith) {
|
|
846
|
+
if (source.startsWith(prefix, offset)) {
|
|
847
|
+
fullPrefix = true;
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
850
|
+
if (!opts.isFinal) {
|
|
851
|
+
const remaining = source.slice(offset);
|
|
852
|
+
if (remaining.length < prefix.length && prefix.startsWith(remaining)) {
|
|
853
|
+
return {
|
|
854
|
+
status: 'defer'
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (!fullPrefix) {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
const end = Math.min(source.length, offset + candidate.contribution.maxSpan);
|
|
863
|
+
let result;
|
|
864
|
+
try {
|
|
865
|
+
result = candidate.contribution.tokenize({
|
|
866
|
+
source: end === source.length ? source : source.slice(0, end),
|
|
867
|
+
offset,
|
|
868
|
+
end,
|
|
869
|
+
isFinal: opts.isFinal,
|
|
870
|
+
context,
|
|
871
|
+
lineStart: currentLineStart(source, offset),
|
|
872
|
+
column: offset - currentLineStart(source, offset)
|
|
873
|
+
});
|
|
874
|
+
} catch (error) {
|
|
875
|
+
reportMarkdownPluginFailure(candidate.pluginName, 'syntax', error);
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (result == null || typeof result !== 'object' || typeof result.then === 'function') {
|
|
879
|
+
reportMarkdownPluginFailure(candidate.pluginName, 'syntax', new TypeError('Tokenizer returned an invalid result'));
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
if (result.status === 'no-match') {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (result.status === 'defer') {
|
|
886
|
+
return !opts.isFinal && end === source.length && source.length - offset < candidate.contribution.maxSpan ? {
|
|
887
|
+
status: 'defer'
|
|
888
|
+
} : {
|
|
889
|
+
status: 'none'
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
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}`)) {
|
|
893
|
+
reportMarkdownPluginFailure(candidate.pluginName, 'syntax', new TypeError('Tokenizer returned an invalid extension node'));
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
const rawNode = result.node;
|
|
897
|
+
const {
|
|
898
|
+
source: _ignoredSource,
|
|
899
|
+
position: _ignoredPosition,
|
|
900
|
+
...safeNode
|
|
901
|
+
} = rawNode;
|
|
902
|
+
const absoluteStart = (opts.baseOffset ?? 0) + offset;
|
|
903
|
+
const absoluteEnd = (opts.baseOffset ?? 0) + result.end;
|
|
904
|
+
return {
|
|
905
|
+
status: 'match',
|
|
906
|
+
end: result.end,
|
|
907
|
+
node: Object.freeze({
|
|
908
|
+
...safeNode,
|
|
909
|
+
data: freezeMarkdownPluginData(result.node.data),
|
|
910
|
+
source: source.slice(offset, result.end),
|
|
911
|
+
...(opts.sourceRanges && context === 'block' ? {
|
|
912
|
+
position: Object.freeze({
|
|
913
|
+
start: Object.freeze({
|
|
914
|
+
offset: absoluteStart
|
|
915
|
+
}),
|
|
916
|
+
end: Object.freeze({
|
|
917
|
+
offset: absoluteEnd
|
|
918
|
+
})
|
|
919
|
+
})
|
|
920
|
+
} : null)
|
|
921
|
+
})
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
status: 'none'
|
|
926
|
+
};
|
|
927
|
+
}
|
|
398
928
|
export function parseInline(text, arg) {
|
|
399
|
-
return
|
|
929
|
+
return projectInlineNodes(parseInlineAstRuntime(text, arg), wantsLegacyRanges(arg));
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Parse inline Markdown into the canonical immutable Astryx AST.
|
|
934
|
+
*
|
|
935
|
+
* Unlike `parseInline`, this returns MDAST-aligned node names and fields. Math
|
|
936
|
+
* nodes are present only when `math: true`, and plugin extension nodes are
|
|
937
|
+
* inferred from the ordered `plugins` tuple.
|
|
938
|
+
*/
|
|
939
|
+
|
|
940
|
+
export function parseInlineAst(text, arg) {
|
|
941
|
+
return parseInlineAstRuntime(text, arg);
|
|
942
|
+
}
|
|
943
|
+
function parseInlineAstRuntime(text, arg) {
|
|
944
|
+
const opts = resolveOptions(arg);
|
|
945
|
+
const nodes = parseInlineEntry(text, opts);
|
|
946
|
+
if ((opts.plugins?.transforms.length ?? 0) === 0) {
|
|
947
|
+
return nodes;
|
|
948
|
+
}
|
|
949
|
+
const transformed = applyMarkdownTransforms({
|
|
950
|
+
type: 'root',
|
|
951
|
+
children: [{
|
|
952
|
+
type: 'paragraph',
|
|
953
|
+
children: nodes
|
|
954
|
+
}]
|
|
955
|
+
}, opts.plugins, text, opts.isFinal, 'inline');
|
|
956
|
+
const paragraph = transformed.children[0];
|
|
957
|
+
return paragraph?.type === 'paragraph' ? [...paragraph.children] : nodes;
|
|
400
958
|
}
|
|
401
959
|
|
|
402
960
|
/**
|
|
@@ -406,7 +964,7 @@ export function parseInline(text, arg) {
|
|
|
406
964
|
* wrapper and call `parseInlineImpl` directly so the transform runs only on
|
|
407
965
|
* the outermost block's inline tree — letting `transformAutolinks` decide
|
|
408
966
|
* which subtrees to descend into (text, bold, italic, strikethrough) and
|
|
409
|
-
* which to skip (link, code, image, citation, break).
|
|
967
|
+
* which to skip (link, code, math, image, citation, break).
|
|
410
968
|
*/
|
|
411
969
|
function parseInlineEntry(text, opts) {
|
|
412
970
|
const nodes = parseInlineImpl(text, opts);
|
|
@@ -414,13 +972,18 @@ function parseInlineEntry(text, opts) {
|
|
|
414
972
|
}
|
|
415
973
|
function parseInlineImpl(text, opts) {
|
|
416
974
|
const nodes = [];
|
|
975
|
+
// Only a plugin that actually contributes INLINE syntax may cost anything
|
|
976
|
+
// per source position. A transform-only list contributes none, so it takes
|
|
977
|
+
// the same path as an omitted or empty one: no candidate probe per
|
|
978
|
+
// position, and no map lookup per character in the plain-text scan below.
|
|
979
|
+
const inlineExtensionStarts = opts.plugins != null && opts.plugins.inlineByFirstCharacter.size > 0 ? opts.plugins.inlineByFirstCharacter : undefined;
|
|
417
980
|
let i = 0;
|
|
418
981
|
while (i < text.length) {
|
|
419
982
|
// --- Escape ---
|
|
420
983
|
if (text[i] === '\\' && i + 1 < text.length) {
|
|
421
984
|
nodes.push({
|
|
422
985
|
type: 'text',
|
|
423
|
-
|
|
986
|
+
value: text[i + 1]
|
|
424
987
|
});
|
|
425
988
|
i += 2;
|
|
426
989
|
continue;
|
|
@@ -433,14 +996,27 @@ function parseInlineImpl(text, opts) {
|
|
|
433
996
|
const closeIndex = text.indexOf('`'.repeat(tickCount), openIndex);
|
|
434
997
|
if (closeIndex !== -1) {
|
|
435
998
|
nodes.push({
|
|
436
|
-
type: '
|
|
437
|
-
|
|
999
|
+
type: 'inlineCode',
|
|
1000
|
+
value: text.slice(openIndex, closeIndex)
|
|
438
1001
|
});
|
|
439
1002
|
i = closeIndex + tickCount;
|
|
440
1003
|
continue;
|
|
441
1004
|
}
|
|
442
1005
|
}
|
|
443
1006
|
|
|
1007
|
+
// --- Inline math (opt-in; code takes precedence) ---
|
|
1008
|
+
if (opts.math && text[i] === '$') {
|
|
1009
|
+
const closeIndex = findInlineMathEnd(text, i);
|
|
1010
|
+
if (closeIndex !== -1) {
|
|
1011
|
+
nodes.push({
|
|
1012
|
+
type: 'inlineMath',
|
|
1013
|
+
value: text.slice(i + 1, closeIndex)
|
|
1014
|
+
});
|
|
1015
|
+
i = closeIndex + 1;
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
444
1020
|
// --- Citation: fullwidth 【id】 ---
|
|
445
1021
|
{
|
|
446
1022
|
const citation = matchFullwidthCitation(text, i, opts);
|
|
@@ -461,16 +1037,16 @@ function parseInlineImpl(text, opts) {
|
|
|
461
1037
|
const srcClose = findClosingParen(text, altClose + 2);
|
|
462
1038
|
if (srcClose !== -1) {
|
|
463
1039
|
const src = text.slice(altClose + 2, srcClose);
|
|
464
|
-
if (!
|
|
1040
|
+
if (!isSafeMarkdownParserUrl(src)) {
|
|
465
1041
|
// Dangerous scheme — emit as plain text.
|
|
466
1042
|
nodes.push({
|
|
467
1043
|
type: 'text',
|
|
468
|
-
|
|
1044
|
+
value: text.slice(i, srcClose + 1)
|
|
469
1045
|
});
|
|
470
1046
|
} else {
|
|
471
1047
|
nodes.push({
|
|
472
1048
|
type: 'image',
|
|
473
|
-
src,
|
|
1049
|
+
url: src,
|
|
474
1050
|
alt: text.slice(i + 2, altClose)
|
|
475
1051
|
});
|
|
476
1052
|
}
|
|
@@ -510,17 +1086,17 @@ function parseInlineImpl(text, opts) {
|
|
|
510
1086
|
const urlClose = findClosingParen(text, textClose + 2);
|
|
511
1087
|
if (urlClose !== -1) {
|
|
512
1088
|
const href = text.slice(textClose + 2, urlClose);
|
|
513
|
-
if (!
|
|
1089
|
+
if (!isSafeMarkdownParserUrl(href)) {
|
|
514
1090
|
// Dangerous scheme — emit as plain text instead of a link.
|
|
515
1091
|
nodes.push({
|
|
516
1092
|
type: 'text',
|
|
517
|
-
|
|
1093
|
+
value: text.slice(i, urlClose + 1)
|
|
518
1094
|
});
|
|
519
1095
|
} else {
|
|
520
1096
|
nodes.push({
|
|
521
1097
|
type: 'link',
|
|
522
|
-
href,
|
|
523
|
-
children: parseInlineImpl(text.slice(i + 1, textClose), opts)
|
|
1098
|
+
url: href,
|
|
1099
|
+
children: parseInlineImpl(text.slice(i + 1, textClose), protectedInlineOptions(opts))
|
|
524
1100
|
});
|
|
525
1101
|
}
|
|
526
1102
|
i = urlClose + 1;
|
|
@@ -549,9 +1125,9 @@ function parseInlineImpl(text, opts) {
|
|
|
549
1125
|
const closeIndex = text.indexOf(marker, i + 3);
|
|
550
1126
|
if (closeIndex !== -1 && (!isUnderscore || !isWordChar(text[closeIndex + 3]))) {
|
|
551
1127
|
nodes.push({
|
|
552
|
-
type: '
|
|
1128
|
+
type: 'strong',
|
|
553
1129
|
children: [{
|
|
554
|
-
type: '
|
|
1130
|
+
type: 'emphasis',
|
|
555
1131
|
children: parseInlineImpl(text.slice(i + 3, closeIndex), opts)
|
|
556
1132
|
}]
|
|
557
1133
|
});
|
|
@@ -571,7 +1147,7 @@ function parseInlineImpl(text, opts) {
|
|
|
571
1147
|
const closeIndex = text.indexOf(marker, i + 2);
|
|
572
1148
|
if (closeIndex !== -1 && (!isUnderscore || !isWordChar(text[closeIndex + 2]))) {
|
|
573
1149
|
nodes.push({
|
|
574
|
-
type: '
|
|
1150
|
+
type: 'strong',
|
|
575
1151
|
children: parseInlineImpl(text.slice(i + 2, closeIndex), opts)
|
|
576
1152
|
});
|
|
577
1153
|
i = closeIndex + 2;
|
|
@@ -585,7 +1161,7 @@ function parseInlineImpl(text, opts) {
|
|
|
585
1161
|
const closeIndex = text.indexOf('~~', i + 2);
|
|
586
1162
|
if (closeIndex !== -1) {
|
|
587
1163
|
nodes.push({
|
|
588
|
-
type: '
|
|
1164
|
+
type: 'delete',
|
|
589
1165
|
children: parseInlineImpl(text.slice(i + 2, closeIndex), opts)
|
|
590
1166
|
});
|
|
591
1167
|
i = closeIndex + 2;
|
|
@@ -602,7 +1178,7 @@ function parseInlineImpl(text, opts) {
|
|
|
602
1178
|
const closeIndex = text.indexOf(text[i], i + 1);
|
|
603
1179
|
if (closeIndex !== -1 && closeIndex > i + 1 && (!isUnderscore || !isWordChar(text[closeIndex + 1]))) {
|
|
604
1180
|
nodes.push({
|
|
605
|
-
type: '
|
|
1181
|
+
type: 'emphasis',
|
|
606
1182
|
children: parseInlineImpl(text.slice(i + 1, closeIndex), opts)
|
|
607
1183
|
});
|
|
608
1184
|
i = closeIndex + 1;
|
|
@@ -611,9 +1187,22 @@ function parseInlineImpl(text, opts) {
|
|
|
611
1187
|
}
|
|
612
1188
|
}
|
|
613
1189
|
|
|
1190
|
+
// --- Extension syntax (built-ins and protected contexts win) ---
|
|
1191
|
+
if (inlineExtensionStarts !== undefined) {
|
|
1192
|
+
const extension = matchExtensionSyntax(text, i, 'inline', opts);
|
|
1193
|
+
if (extension.status === 'match') {
|
|
1194
|
+
nodes.push(extension.node);
|
|
1195
|
+
i = extension.end;
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
if (extension.status === 'defer') {
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
614
1203
|
// --- Plain text (with line-break detection) ---
|
|
615
1204
|
let end = i + 1;
|
|
616
|
-
while (end < text.length && !'*_~`[!\\\n\u3010'.includes(text[end])) {
|
|
1205
|
+
while (end < text.length && !'*_~`[!\\\n\u3010'.includes(text[end]) && !(opts.math && text[end] === '$') && (inlineExtensionStarts === undefined || !inlineExtensionStarts.has(text[end]))) {
|
|
617
1206
|
end++;
|
|
618
1207
|
}
|
|
619
1208
|
const content = text.slice(i, end);
|
|
@@ -625,11 +1214,14 @@ function parseInlineImpl(text, opts) {
|
|
|
625
1214
|
if (trimmed.length > 0) {
|
|
626
1215
|
const last = nodes[nodes.length - 1];
|
|
627
1216
|
if (last?.type === 'text') {
|
|
628
|
-
|
|
1217
|
+
nodes[nodes.length - 1] = {
|
|
1218
|
+
...last,
|
|
1219
|
+
value: last.value + trimmed
|
|
1220
|
+
};
|
|
629
1221
|
} else {
|
|
630
1222
|
nodes.push({
|
|
631
1223
|
type: 'text',
|
|
632
|
-
|
|
1224
|
+
value: trimmed
|
|
633
1225
|
});
|
|
634
1226
|
}
|
|
635
1227
|
}
|
|
@@ -642,11 +1234,14 @@ function parseInlineImpl(text, opts) {
|
|
|
642
1234
|
}
|
|
643
1235
|
const last = nodes[nodes.length - 1];
|
|
644
1236
|
if (last?.type === 'text') {
|
|
645
|
-
|
|
1237
|
+
nodes[nodes.length - 1] = {
|
|
1238
|
+
...last,
|
|
1239
|
+
value: last.value + content
|
|
1240
|
+
};
|
|
646
1241
|
} else {
|
|
647
1242
|
nodes.push({
|
|
648
1243
|
type: 'text',
|
|
649
|
-
content
|
|
1244
|
+
value: content
|
|
650
1245
|
});
|
|
651
1246
|
}
|
|
652
1247
|
i = end;
|
|
@@ -750,7 +1345,7 @@ function scanAutolinksInText(text) {
|
|
|
750
1345
|
while ((m = re.exec(text)) !== null) {
|
|
751
1346
|
const url = m[1];
|
|
752
1347
|
// Skip dangerous URL schemes (javascript:, vbscript:, data:text/html)
|
|
753
|
-
if (!
|
|
1348
|
+
if (!isSafeMarkdownParserUrl(url)) {
|
|
754
1349
|
continue;
|
|
755
1350
|
}
|
|
756
1351
|
matches.push({
|
|
@@ -875,7 +1470,7 @@ function splitTextOnAutolinks(content) {
|
|
|
875
1470
|
if (matches.length === 0) {
|
|
876
1471
|
return [{
|
|
877
1472
|
type: 'text',
|
|
878
|
-
content
|
|
1473
|
+
value: content
|
|
879
1474
|
}];
|
|
880
1475
|
}
|
|
881
1476
|
const out = [];
|
|
@@ -884,15 +1479,15 @@ function splitTextOnAutolinks(content) {
|
|
|
884
1479
|
if (m.start > cursor) {
|
|
885
1480
|
out.push({
|
|
886
1481
|
type: 'text',
|
|
887
|
-
|
|
1482
|
+
value: content.slice(cursor, m.start)
|
|
888
1483
|
});
|
|
889
1484
|
}
|
|
890
1485
|
out.push({
|
|
891
1486
|
type: 'link',
|
|
892
|
-
|
|
1487
|
+
url: m.href,
|
|
893
1488
|
children: [{
|
|
894
1489
|
type: 'text',
|
|
895
|
-
|
|
1490
|
+
value: m.display
|
|
896
1491
|
}]
|
|
897
1492
|
});
|
|
898
1493
|
cursor = m.end;
|
|
@@ -900,7 +1495,7 @@ function splitTextOnAutolinks(content) {
|
|
|
900
1495
|
if (cursor < content.length) {
|
|
901
1496
|
out.push({
|
|
902
1497
|
type: 'text',
|
|
903
|
-
|
|
1498
|
+
value: content.slice(cursor)
|
|
904
1499
|
});
|
|
905
1500
|
}
|
|
906
1501
|
return out;
|
|
@@ -918,16 +1513,19 @@ function transformAutolinks(nodes) {
|
|
|
918
1513
|
const out = [];
|
|
919
1514
|
for (const node of nodes) {
|
|
920
1515
|
if (node.type === 'text') {
|
|
921
|
-
const split = splitTextOnAutolinks(node.
|
|
1516
|
+
const split = splitTextOnAutolinks(node.value);
|
|
922
1517
|
for (const seg of split) {
|
|
923
1518
|
const last = out[out.length - 1];
|
|
924
1519
|
if (seg.type === 'text' && last?.type === 'text') {
|
|
925
|
-
|
|
1520
|
+
out[out.length - 1] = {
|
|
1521
|
+
...last,
|
|
1522
|
+
value: last.value + seg.value
|
|
1523
|
+
};
|
|
926
1524
|
} else {
|
|
927
1525
|
out.push(seg);
|
|
928
1526
|
}
|
|
929
1527
|
}
|
|
930
|
-
} else if (node.type === '
|
|
1528
|
+
} else if (node.type === 'strong' || node.type === 'emphasis' || node.type === 'delete') {
|
|
931
1529
|
out.push({
|
|
932
1530
|
...node,
|
|
933
1531
|
children: transformAutolinks(node.children)
|
|
@@ -1012,10 +1610,19 @@ function isTableSeparator(line) {
|
|
|
1012
1610
|
* offset into it would not address the document.
|
|
1013
1611
|
*/
|
|
1014
1612
|
function nested(opts) {
|
|
1015
|
-
|
|
1613
|
+
const nestedOptions = opts.allowBlockSyntax === false ? opts : {
|
|
1016
1614
|
...opts,
|
|
1017
|
-
|
|
1018
|
-
}
|
|
1615
|
+
allowBlockSyntax: false
|
|
1616
|
+
};
|
|
1617
|
+
return nestedOptions.sourceRanges || nestedOptions.astPositions ? {
|
|
1618
|
+
...nestedOptions,
|
|
1619
|
+
sourceRanges: false,
|
|
1620
|
+
astPositions: false
|
|
1621
|
+
} : nestedOptions;
|
|
1622
|
+
}
|
|
1623
|
+
function blockExtensionColumn(line) {
|
|
1624
|
+
const indentation = line.length - line.trimStart().length;
|
|
1625
|
+
return indentation <= 3 ? indentation : null;
|
|
1019
1626
|
}
|
|
1020
1627
|
|
|
1021
1628
|
/**
|
|
@@ -1083,6 +1690,7 @@ function splitTableRow(line) {
|
|
|
1083
1690
|
}
|
|
1084
1691
|
function parseTable(lines, lineIndex, opts) {
|
|
1085
1692
|
const headers = splitTableRow(lines[lineIndex]).map(cell => ({
|
|
1693
|
+
type: 'tableCell',
|
|
1086
1694
|
children: parseInlineEntry(cell, opts)
|
|
1087
1695
|
}));
|
|
1088
1696
|
const alignments = splitTableRow(lines[lineIndex + 1]).map(cell => {
|
|
@@ -1094,17 +1702,24 @@ function parseTable(lines, lineIndex, opts) {
|
|
|
1094
1702
|
const rows = [];
|
|
1095
1703
|
let rowIndex = lineIndex + 2;
|
|
1096
1704
|
while (rowIndex < lines.length && lines[rowIndex].includes('|') && lines[rowIndex].trim() !== '') {
|
|
1097
|
-
rows.push(
|
|
1098
|
-
|
|
1099
|
-
|
|
1705
|
+
rows.push({
|
|
1706
|
+
type: 'tableRow',
|
|
1707
|
+
children: splitTableRow(lines[rowIndex]).map(cell => ({
|
|
1708
|
+
type: 'tableCell',
|
|
1709
|
+
children: parseInlineEntry(cell, opts)
|
|
1710
|
+
}))
|
|
1711
|
+
});
|
|
1100
1712
|
rowIndex++;
|
|
1101
1713
|
}
|
|
1714
|
+
const header = {
|
|
1715
|
+
type: 'tableRow',
|
|
1716
|
+
children: headers
|
|
1717
|
+
};
|
|
1102
1718
|
return {
|
|
1103
1719
|
node: {
|
|
1104
1720
|
type: 'table',
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
rows
|
|
1721
|
+
align: alignments,
|
|
1722
|
+
children: [header, ...rows]
|
|
1108
1723
|
},
|
|
1109
1724
|
nextIndex: rowIndex
|
|
1110
1725
|
};
|
|
@@ -1147,6 +1762,7 @@ function parseList(lines, startIndex, ordered, opts) {
|
|
|
1147
1762
|
itemText += '\n' + deindented.join('\n');
|
|
1148
1763
|
}
|
|
1149
1764
|
items.push({
|
|
1765
|
+
type: 'listItem',
|
|
1150
1766
|
checked,
|
|
1151
1767
|
children: parseMarkdownImpl(itemText, nested(opts))
|
|
1152
1768
|
});
|
|
@@ -1163,15 +1779,16 @@ function parseList(lines, startIndex, ordered, opts) {
|
|
|
1163
1779
|
index = lookahead;
|
|
1164
1780
|
}
|
|
1165
1781
|
}
|
|
1782
|
+
const node = {
|
|
1783
|
+
type: 'list',
|
|
1784
|
+
ordered,
|
|
1785
|
+
start,
|
|
1786
|
+
delimiter: ordered ? delim : undefined,
|
|
1787
|
+
spread: loose || undefined,
|
|
1788
|
+
children: items
|
|
1789
|
+
};
|
|
1166
1790
|
return {
|
|
1167
|
-
node
|
|
1168
|
-
type: 'list',
|
|
1169
|
-
ordered,
|
|
1170
|
-
start,
|
|
1171
|
-
delimiter: ordered ? delim : undefined,
|
|
1172
|
-
loose: loose || undefined,
|
|
1173
|
-
items
|
|
1174
|
-
},
|
|
1791
|
+
node,
|
|
1175
1792
|
nextIndex: index
|
|
1176
1793
|
};
|
|
1177
1794
|
}
|
|
@@ -1181,7 +1798,33 @@ function parseList(lines, startIndex, ordered, opts) {
|
|
|
1181
1798
|
// ---------------------------------------------------------------------------
|
|
1182
1799
|
|
|
1183
1800
|
export function parseMarkdown(input, arg) {
|
|
1184
|
-
return
|
|
1801
|
+
return projectMarkdownRoot(parseMarkdownAstInternal(input, arg, true), wantsLegacyRanges(arg));
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* Parse block Markdown into the canonical immutable Astryx AST.
|
|
1806
|
+
*
|
|
1807
|
+
* Unlike `parseMarkdown`, this returns the MDAST-aligned root used by Markdown,
|
|
1808
|
+
* transforms, and Outline. Math nodes are present only when `math: true`, and
|
|
1809
|
+
* plugin extension nodes are inferred from the ordered `plugins` tuple.
|
|
1810
|
+
*/
|
|
1811
|
+
|
|
1812
|
+
export function parseMarkdownAst(input, arg) {
|
|
1813
|
+
return parseMarkdownAstInternal(input, arg, true);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
/** @internal Full-parse entry point with streaming finality for Outline. */
|
|
1817
|
+
export function parseMarkdownAstInternal(input, arg, isFinal) {
|
|
1818
|
+
const resolved = resolveOptions(arg);
|
|
1819
|
+
const opts = isFinal ? resolved : {
|
|
1820
|
+
...resolved,
|
|
1821
|
+
isFinal: false
|
|
1822
|
+
};
|
|
1823
|
+
const root = {
|
|
1824
|
+
type: 'root',
|
|
1825
|
+
children: parseMarkdownImpl(input, opts)
|
|
1826
|
+
};
|
|
1827
|
+
return applyMarkdownTransforms(root, opts.plugins, input, opts.isFinal, 'block');
|
|
1185
1828
|
}
|
|
1186
1829
|
function parseMarkdownImpl(input, baseOpts) {
|
|
1187
1830
|
// Collect this input's link reference definitions and strip their lines,
|
|
@@ -1195,7 +1838,7 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1195
1838
|
defs,
|
|
1196
1839
|
cleaned,
|
|
1197
1840
|
lineMap
|
|
1198
|
-
} = extractLinkDefinitions(input);
|
|
1841
|
+
} = extractLinkDefinitions(input, baseOpts.math);
|
|
1199
1842
|
const inherited = baseOpts.linkDefs;
|
|
1200
1843
|
let linkDefs;
|
|
1201
1844
|
if (defs.size === 0) {
|
|
@@ -1210,14 +1853,26 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1210
1853
|
linkDefs
|
|
1211
1854
|
} : baseOpts;
|
|
1212
1855
|
const lines = cleaned.split('\n');
|
|
1856
|
+
const hasBlockExtensionSyntax = opts.allowBlockSyntax !== false && (opts.plugins?.blockByFirstCharacter.size ?? 0) > 0;
|
|
1857
|
+
// Derived from the already-split lines rather than a second character
|
|
1858
|
+
// scan: every line contributes its own length plus the newline it ended on.
|
|
1859
|
+
const lineOffsets = [0];
|
|
1860
|
+
if (opts.astPositions === true || hasBlockExtensionSyntax) {
|
|
1861
|
+
let offset = 0;
|
|
1862
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1863
|
+
offset += lines[index].length + 1;
|
|
1864
|
+
lineOffsets.push(offset);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
const blockExtensionMatches = new Map();
|
|
1213
1868
|
const blocks = [];
|
|
1214
1869
|
// The line each block started on, parallel to `blocks`. Only collected when
|
|
1215
1870
|
// ranges were asked for; a block's end is resolved after the loop, since the
|
|
1216
1871
|
// branch that produced it has already moved `index` past whatever it read.
|
|
1217
|
-
const blockStartLines = opts.
|
|
1872
|
+
const blockStartLines = opts.astPositions === true ? [] : null;
|
|
1218
1873
|
// Set only by a block that consumes blank lines as content, where the
|
|
1219
1874
|
// positional end derivation would trim them away.
|
|
1220
|
-
const blockEndLines = opts.
|
|
1875
|
+
const blockEndLines = opts.astPositions === true ? [] : null;
|
|
1221
1876
|
let blockStartLine = 0;
|
|
1222
1877
|
const pushBlock = (node, endLine) => {
|
|
1223
1878
|
blocks.push(node);
|
|
@@ -1234,10 +1889,13 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1234
1889
|
}
|
|
1235
1890
|
|
|
1236
1891
|
// --- Fenced code block ---
|
|
1237
|
-
const fenceMatch = line.match(/^(`{3,}|~{3,})
|
|
1892
|
+
const fenceMatch = line.match(/^(`{3,}|~{3,})/);
|
|
1238
1893
|
if (fenceMatch) {
|
|
1239
1894
|
const fence = fenceMatch[1];
|
|
1240
|
-
const
|
|
1895
|
+
const info = line.slice(fence.length);
|
|
1896
|
+
const language = info.match(/^(\S+)/)?.[1] ?? null;
|
|
1897
|
+
const legacyLanguage = info.match(/^(\w*)/)?.[1] || null;
|
|
1898
|
+
const meta = language == null ? undefined : info.slice(language.length).trim() || undefined;
|
|
1241
1899
|
const codeLines = [];
|
|
1242
1900
|
index++;
|
|
1243
1901
|
while (index < lines.length && !lines[index].startsWith(fence)) {
|
|
@@ -1248,20 +1906,36 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1248
1906
|
// A fence owns its blank lines, and an unterminated one (mid-stream)
|
|
1249
1907
|
// can end on them, so it states its own end rather than letting the
|
|
1250
1908
|
// positional derivation trim them off.
|
|
1251
|
-
pushBlock({
|
|
1252
|
-
type: '
|
|
1253
|
-
language,
|
|
1254
|
-
|
|
1255
|
-
|
|
1909
|
+
pushBlock(markMarkdownAstLegacyCodeLanguage({
|
|
1910
|
+
type: 'code',
|
|
1911
|
+
lang: language,
|
|
1912
|
+
...(meta == null ? {} : {
|
|
1913
|
+
meta
|
|
1914
|
+
}),
|
|
1915
|
+
value: codeLines.join('\n')
|
|
1916
|
+
}, legacyLanguage), Math.min(index, lines.length) - 1);
|
|
1256
1917
|
continue;
|
|
1257
1918
|
}
|
|
1258
1919
|
|
|
1920
|
+
// --- Display math (opt-in; fenced code takes precedence) ---
|
|
1921
|
+
if (opts.math) {
|
|
1922
|
+
const displayMath = matchDisplayMathBlock(lines, index);
|
|
1923
|
+
if (displayMath != null) {
|
|
1924
|
+
pushBlock({
|
|
1925
|
+
type: 'math',
|
|
1926
|
+
value: displayMath.value
|
|
1927
|
+
}, displayMath.endLine);
|
|
1928
|
+
index = displayMath.nextIndex;
|
|
1929
|
+
continue;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1259
1933
|
// --- Heading ---
|
|
1260
1934
|
const headingMatch = line.match(/^(#{1,6}) +(.*)/);
|
|
1261
1935
|
if (headingMatch) {
|
|
1262
1936
|
pushBlock({
|
|
1263
1937
|
type: 'heading',
|
|
1264
|
-
|
|
1938
|
+
depth: headingMatch[1].length,
|
|
1265
1939
|
children: parseInlineEntry(headingMatch[2], opts)
|
|
1266
1940
|
});
|
|
1267
1941
|
index++;
|
|
@@ -1271,7 +1945,7 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1271
1945
|
// --- HR (must precede list check to handle `- - -`, `* * *`, `_ _ _`) ---
|
|
1272
1946
|
if (isHorizontalRule(line)) {
|
|
1273
1947
|
pushBlock({
|
|
1274
|
-
type: '
|
|
1948
|
+
type: 'thematicBreak'
|
|
1275
1949
|
});
|
|
1276
1950
|
index++;
|
|
1277
1951
|
continue;
|
|
@@ -1281,11 +1955,11 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1281
1955
|
// An unsafe src falls through to the paragraph path and renders as
|
|
1282
1956
|
// literal text, the same rule the inline image path applies.
|
|
1283
1957
|
const imageMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)/);
|
|
1284
|
-
if (imageMatch && line.trim() === imageMatch[0] &&
|
|
1958
|
+
if (imageMatch && line.trim() === imageMatch[0] && isSafeMarkdownParserUrl(imageMatch[2])) {
|
|
1285
1959
|
pushBlock({
|
|
1286
1960
|
type: 'image',
|
|
1287
1961
|
alt: imageMatch[1],
|
|
1288
|
-
|
|
1962
|
+
url: imageMatch[2]
|
|
1289
1963
|
});
|
|
1290
1964
|
index++;
|
|
1291
1965
|
continue;
|
|
@@ -1329,11 +2003,52 @@ function parseMarkdownImpl(input, baseOpts) {
|
|
|
1329
2003
|
continue;
|
|
1330
2004
|
}
|
|
1331
2005
|
|
|
2006
|
+
// --- Extension block syntax (built-in blocks take precedence) ---
|
|
2007
|
+
if (hasBlockExtensionSyntax) {
|
|
2008
|
+
const extensionColumn = blockExtensionColumn(line);
|
|
2009
|
+
const extensionOffset = extensionColumn == null ? lineOffsets[index] : lineOffsets[index] + extensionColumn;
|
|
2010
|
+
const extension = blockExtensionMatches.get(extensionOffset) ?? (extensionColumn == null ? {
|
|
2011
|
+
status: 'none'
|
|
2012
|
+
} : matchExtensionSyntax(cleaned, extensionOffset, 'block', opts));
|
|
2013
|
+
blockExtensionMatches.delete(extensionOffset);
|
|
2014
|
+
if (extension.status === 'match') {
|
|
2015
|
+
const consumedEnd = extension.end;
|
|
2016
|
+
const consumed = cleaned.slice(lineOffsets[index], consumedEnd);
|
|
2017
|
+
if (consumedEnd < cleaned.length && cleaned[consumedEnd] !== '\n' && cleaned[consumedEnd - 1] !== '\n') {
|
|
2018
|
+
reportMarkdownPluginFailure(extension.node.plugin, 'syntax', new TypeError('Block syntax must consume complete lines'));
|
|
2019
|
+
} else {
|
|
2020
|
+
const newlineCount = consumed.split('\n').length - 1;
|
|
2021
|
+
const consumedLines = Math.max(1, newlineCount + (consumed.endsWith('\n') ? 0 : 1));
|
|
2022
|
+
pushBlock(extension.node, index + consumedLines - 1);
|
|
2023
|
+
index += consumedLines;
|
|
2024
|
+
continue;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (extension.status === 'defer') {
|
|
2028
|
+
break;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
1332
2032
|
// --- Paragraph ---
|
|
1333
2033
|
const paraLines = [line];
|
|
1334
2034
|
index++;
|
|
1335
|
-
while (index < lines.length
|
|
1336
|
-
|
|
2035
|
+
while (index < lines.length) {
|
|
2036
|
+
const nextLine = lines[index];
|
|
2037
|
+
if (isBlockStart(nextLine) || opts.math && matchDisplayMathBlock(lines, index) != null || nextLine.trim() === '') {
|
|
2038
|
+
break;
|
|
2039
|
+
}
|
|
2040
|
+
if (hasBlockExtensionSyntax) {
|
|
2041
|
+
const extensionColumn = blockExtensionColumn(nextLine);
|
|
2042
|
+
if (extensionColumn != null) {
|
|
2043
|
+
const extensionOffset = lineOffsets[index] + extensionColumn;
|
|
2044
|
+
const extension = matchExtensionSyntax(cleaned, extensionOffset, 'block', opts);
|
|
2045
|
+
if (extension.status !== 'none') {
|
|
2046
|
+
blockExtensionMatches.set(extensionOffset, extension);
|
|
2047
|
+
break;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
paraLines.push(nextLine);
|
|
1337
2052
|
index++;
|
|
1338
2053
|
}
|
|
1339
2054
|
pushBlock({
|
|
@@ -1385,9 +2100,13 @@ function stampSourceRanges(blocks, blockStartLines, blockEndLines, lines, lineMa
|
|
|
1385
2100
|
const end = lineStart(endLine) + lines[endLine].length;
|
|
1386
2101
|
blocks[i] = {
|
|
1387
2102
|
...blocks[i],
|
|
1388
|
-
|
|
1389
|
-
start:
|
|
1390
|
-
|
|
2103
|
+
position: {
|
|
2104
|
+
start: {
|
|
2105
|
+
offset: lineStart(startLine)
|
|
2106
|
+
},
|
|
2107
|
+
end: {
|
|
2108
|
+
offset: end
|
|
2109
|
+
}
|
|
1391
2110
|
}
|
|
1392
2111
|
};
|
|
1393
2112
|
}
|
|
@@ -1401,13 +2120,18 @@ const incrementalCaches = new WeakMap();
|
|
|
1401
2120
|
function makeIncrementalCache(state) {
|
|
1402
2121
|
const {
|
|
1403
2122
|
defs
|
|
1404
|
-
} = extractLinkDefinitions(state.settledText);
|
|
2123
|
+
} = extractLinkDefinitions(state.settledText, state.math);
|
|
1405
2124
|
const cache = {
|
|
1406
2125
|
settledEnd: state.settledText.length,
|
|
1407
2126
|
settledLinkDefs: new Map(defs),
|
|
1408
2127
|
tailLinkDefs: new Map(),
|
|
1409
2128
|
linkDefs: defs,
|
|
1410
2129
|
linkDefsKey: linkDefsSignature(defs),
|
|
2130
|
+
settledAstBlocks: [],
|
|
2131
|
+
settledRevision: 0,
|
|
2132
|
+
projectedRevision: -1,
|
|
2133
|
+
projectedSettledBlocks: [],
|
|
2134
|
+
projectionCache: new WeakMap(),
|
|
1411
2135
|
work: {
|
|
1412
2136
|
splitCharacters: 0,
|
|
1413
2137
|
boundaryLines: 0,
|
|
@@ -1418,6 +2142,11 @@ function makeIncrementalCache(state) {
|
|
|
1418
2142
|
incrementalCaches.set(state, cache);
|
|
1419
2143
|
return cache;
|
|
1420
2144
|
}
|
|
2145
|
+
|
|
2146
|
+
/**
|
|
2147
|
+
* Create an incremental parser cache. Use the `<true>` type argument with
|
|
2148
|
+
* `MathParseOptions` so the cache and returned nodes share the math contract.
|
|
2149
|
+
*/
|
|
1421
2150
|
export function createIncrementalState() {
|
|
1422
2151
|
const state = {
|
|
1423
2152
|
prevInput: '',
|
|
@@ -1456,33 +2185,70 @@ export function getIncrementalParseWork(state) {
|
|
|
1456
2185
|
* the boundary to -1 even though the content before the fence opened cannot be
|
|
1457
2186
|
* changed by anything typed inside it.
|
|
1458
2187
|
*/
|
|
1459
|
-
function findSettledBoundary(lines) {
|
|
2188
|
+
function findSettledBoundary(lines, math = false) {
|
|
1460
2189
|
let inFence = false;
|
|
1461
2190
|
let fenceMarker = '';
|
|
2191
|
+
let mathContainer = null;
|
|
2192
|
+
let suppressMathUntilBoundary = false;
|
|
1462
2193
|
let lastBoundary = -1;
|
|
1463
2194
|
let boundaryBeforeFence = -1;
|
|
2195
|
+
let boundaryBeforeMath = -1;
|
|
1464
2196
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
1465
2197
|
const line = lines[lineIndex];
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
if (fenceMatch) {
|
|
1470
|
-
if (!inFence) {
|
|
1471
|
-
inFence = true;
|
|
1472
|
-
fenceMarker = fenceMatch[1];
|
|
1473
|
-
boundaryBeforeFence = lastBoundary;
|
|
1474
|
-
} else if (fenceMatch[1].startsWith(fenceMarker[0]) && fenceMatch[1].length >= fenceMarker.length) {
|
|
2198
|
+
if (inFence) {
|
|
2199
|
+
const fenceMatch = line.match(/^(`{3,}|~{3,})/);
|
|
2200
|
+
if (fenceMatch && fenceMatch[1].startsWith(fenceMarker[0]) && fenceMatch[1].length >= fenceMarker.length) {
|
|
1475
2201
|
inFence = false;
|
|
1476
2202
|
fenceMarker = '';
|
|
1477
2203
|
}
|
|
2204
|
+
continue;
|
|
2205
|
+
}
|
|
2206
|
+
if (mathContainer != null) {
|
|
2207
|
+
const state = displayMathLineState(line, mathContainer);
|
|
2208
|
+
if (state === 'close') {
|
|
2209
|
+
mathContainer = null;
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
if (state === 'inside') {
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
// The list item or blockquote ended before a closer arrived. The parser
|
|
2216
|
+
// treats that unmatched opener literally, so resume ordinary boundary
|
|
2217
|
+
// detection on this first line outside the container.
|
|
2218
|
+
mathContainer = null;
|
|
2219
|
+
suppressMathUntilBoundary = true;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
// A complete same-line `$$…$$` expression never changes boundary state.
|
|
2223
|
+
// A standalone marker may belong to the top level, a blockquote, or one
|
|
2224
|
+
// list item; remember that container so its continuation marker closes the
|
|
2225
|
+
// same expression instead of opening a new one.
|
|
2226
|
+
if (math && !suppressMathUntilBoundary) {
|
|
2227
|
+
const container = displayMathContainer(line);
|
|
2228
|
+
if (container != null) {
|
|
2229
|
+
mathContainer = container;
|
|
2230
|
+
boundaryBeforeMath = lastBoundary;
|
|
2231
|
+
continue;
|
|
2232
|
+
}
|
|
1478
2233
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
2234
|
+
const fenceMatch = line.match(/^(`{3,}|~{3,})/);
|
|
2235
|
+
if (fenceMatch) {
|
|
2236
|
+
inFence = true;
|
|
2237
|
+
fenceMarker = fenceMatch[1];
|
|
2238
|
+
boundaryBeforeFence = lastBoundary;
|
|
2239
|
+
continue;
|
|
2240
|
+
}
|
|
2241
|
+
if (line.trim() === '') {
|
|
2242
|
+
suppressMathUntilBoundary = false;
|
|
2243
|
+
if (lineIndex > 0 && lineIndex < lines.length - 1) {
|
|
2244
|
+
lastBoundary = lineIndex;
|
|
2245
|
+
}
|
|
1481
2246
|
}
|
|
1482
2247
|
}
|
|
1483
2248
|
return {
|
|
1484
|
-
boundary: inFence ? boundaryBeforeFence : lastBoundary,
|
|
1485
|
-
openFence: inFence
|
|
2249
|
+
boundary: inFence ? boundaryBeforeFence : mathContainer != null ? boundaryBeforeMath : lastBoundary,
|
|
2250
|
+
openFence: inFence,
|
|
2251
|
+
openMath: mathContainer != null
|
|
1486
2252
|
};
|
|
1487
2253
|
}
|
|
1488
2254
|
|
|
@@ -1490,10 +2256,45 @@ function findSettledBoundary(lines) {
|
|
|
1490
2256
|
* Strip trailing incomplete inline syntax that appears during streaming.
|
|
1491
2257
|
* Only affects the tail of the last line — safe to apply to the full string.
|
|
1492
2258
|
*/
|
|
1493
|
-
export function trimStreamingArtifacts(input) {
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
2259
|
+
export function trimStreamingArtifacts(input, options) {
|
|
2260
|
+
// First remove an incomplete display expression as one structural unit. This
|
|
2261
|
+
// full-input scan distinguishes a terminal nested closer from a new opener;
|
|
2262
|
+
// looking only at the final `$$` line cannot.
|
|
2263
|
+
const displayTrimmed = options?.math ? trimOpenDisplayMath(input) : input;
|
|
2264
|
+
const lastNL = displayTrimmed.lastIndexOf('\n');
|
|
2265
|
+
const prefix = lastNL === -1 ? '' : displayTrimmed.slice(0, lastNL + 1);
|
|
2266
|
+
let tail = lastNL === -1 ? displayTrimmed : displayTrimmed.slice(lastNL + 1);
|
|
2267
|
+
if (options?.math) {
|
|
2268
|
+
// Hold an unmatched inline opener so raw TeX syntax does not flash while
|
|
2269
|
+
// streaming. If another unescaped dollar is already present but fails the
|
|
2270
|
+
// closing-boundary rule, keep both literal (the currency case).
|
|
2271
|
+
for (let index = 0; index < tail.length; index++) {
|
|
2272
|
+
if (tail[index] === '$' && tail[index + 1] == null && tail[index - 1] !== '$' && !/\d/.test(tail[index - 1] ?? '') && !isEscaped(tail, index)) {
|
|
2273
|
+
tail = tail.slice(0, index);
|
|
2274
|
+
break;
|
|
2275
|
+
}
|
|
2276
|
+
if (!isInlineMathStart(tail, index)) {
|
|
2277
|
+
continue;
|
|
2278
|
+
}
|
|
2279
|
+
const close = findInlineMathEnd(tail, index);
|
|
2280
|
+
if (close !== -1) {
|
|
2281
|
+
index = close;
|
|
2282
|
+
continue;
|
|
2283
|
+
}
|
|
2284
|
+
let laterDollar = -1;
|
|
2285
|
+
for (let next = index + 1; next < tail.length; next++) {
|
|
2286
|
+
if (tail[next] === '$' && !isEscaped(tail, next)) {
|
|
2287
|
+
laterDollar = next;
|
|
2288
|
+
break;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
if (laterDollar === -1) {
|
|
2292
|
+
tail = tail.slice(0, index);
|
|
2293
|
+
break;
|
|
2294
|
+
}
|
|
2295
|
+
index = laterDollar;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
1497
2298
|
|
|
1498
2299
|
// Scan backwards for unclosed syntax markers — no regex to avoid ReDoS
|
|
1499
2300
|
// Find the last unclosed [ or ![ (link/image start)
|
|
@@ -1650,6 +2451,54 @@ export function trimStreamingArtifacts(input) {
|
|
|
1650
2451
|
* Once a table is established (header + separator exist), new data rows
|
|
1651
2452
|
* render immediately — no suppression.
|
|
1652
2453
|
*/
|
|
2454
|
+
function trimOpenDisplayMath(text) {
|
|
2455
|
+
const lines = text.split('\n');
|
|
2456
|
+
let inFence = false;
|
|
2457
|
+
let fenceMarker = '';
|
|
2458
|
+
let mathContainer = null;
|
|
2459
|
+
let suppressMathUntilBoundary = false;
|
|
2460
|
+
let mathStartLine = -1;
|
|
2461
|
+
for (let index = 0; index < lines.length; index++) {
|
|
2462
|
+
const line = lines[index];
|
|
2463
|
+
if (inFence) {
|
|
2464
|
+
const fence = line.match(/^(`{3,}|~{3,})/);
|
|
2465
|
+
if (fence != null && fence[1].startsWith(fenceMarker[0]) && fence[1].length >= fenceMarker.length) {
|
|
2466
|
+
inFence = false;
|
|
2467
|
+
fenceMarker = '';
|
|
2468
|
+
}
|
|
2469
|
+
continue;
|
|
2470
|
+
}
|
|
2471
|
+
if (mathContainer != null) {
|
|
2472
|
+
const state = displayMathLineState(line, mathContainer);
|
|
2473
|
+
if (state === 'close') {
|
|
2474
|
+
mathContainer = null;
|
|
2475
|
+
mathStartLine = -1;
|
|
2476
|
+
continue;
|
|
2477
|
+
}
|
|
2478
|
+
if (state === 'inside') {
|
|
2479
|
+
continue;
|
|
2480
|
+
}
|
|
2481
|
+
mathContainer = null;
|
|
2482
|
+
mathStartLine = -1;
|
|
2483
|
+
suppressMathUntilBoundary = true;
|
|
2484
|
+
}
|
|
2485
|
+
if (line.trim() === '') {
|
|
2486
|
+
suppressMathUntilBoundary = false;
|
|
2487
|
+
}
|
|
2488
|
+
const fence = line.match(/^(`{3,}|~{3,})/);
|
|
2489
|
+
if (fence != null) {
|
|
2490
|
+
inFence = true;
|
|
2491
|
+
fenceMarker = fence[1];
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
const container = suppressMathUntilBoundary ? null : displayMathContainer(line);
|
|
2495
|
+
if (container != null) {
|
|
2496
|
+
mathContainer = container;
|
|
2497
|
+
mathStartLine = index;
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
return mathContainer != null && mathStartLine >= 0 ? lines.slice(0, mathStartLine).join('\n').trimEnd() : text;
|
|
2501
|
+
}
|
|
1653
2502
|
function trimUnsettledStructural(text) {
|
|
1654
2503
|
const lines = text.split('\n');
|
|
1655
2504
|
|
|
@@ -1715,7 +2564,7 @@ function trimUnsettledStructural(text) {
|
|
|
1715
2564
|
* rather than into the slice.
|
|
1716
2565
|
*/
|
|
1717
2566
|
function atOffset(opts, offset) {
|
|
1718
|
-
return opts.
|
|
2567
|
+
return opts.astPositions === true ? {
|
|
1719
2568
|
...opts,
|
|
1720
2569
|
baseOffset: offset
|
|
1721
2570
|
} : opts;
|
|
@@ -1740,14 +2589,13 @@ function mergeSettledBlocks(prev, delta) {
|
|
|
1740
2589
|
ordered: prevLast.ordered,
|
|
1741
2590
|
start: prevLast.start,
|
|
1742
2591
|
delimiter: prevLast.delimiter,
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
// One list now, so one
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
end: deltaFirst.range.end
|
|
2592
|
+
spread: true,
|
|
2593
|
+
children: [...prevLast.children, ...deltaFirst.children],
|
|
2594
|
+
// One list now, so one position spans both halves.
|
|
2595
|
+
...(prevLast.position != null && deltaFirst.position != null ? {
|
|
2596
|
+
position: {
|
|
2597
|
+
start: prevLast.position.start,
|
|
2598
|
+
end: deltaFirst.position.end
|
|
1751
2599
|
}
|
|
1752
2600
|
} : null)
|
|
1753
2601
|
};
|
|
@@ -1759,11 +2607,11 @@ function mergeSettledBlocks(prev, delta) {
|
|
|
1759
2607
|
/** Append a newly-settled slice without copying the already-settled prefix. */
|
|
1760
2608
|
function appendSettledBlocks(prev, delta) {
|
|
1761
2609
|
if (delta.length === 0) {
|
|
1762
|
-
return;
|
|
2610
|
+
return false;
|
|
1763
2611
|
}
|
|
1764
2612
|
if (prev.length === 0) {
|
|
1765
2613
|
prev.push(...delta);
|
|
1766
|
-
return;
|
|
2614
|
+
return false;
|
|
1767
2615
|
}
|
|
1768
2616
|
const prevLast = prev[prev.length - 1];
|
|
1769
2617
|
const deltaFirst = delta[0];
|
|
@@ -1773,19 +2621,20 @@ function appendSettledBlocks(prev, delta) {
|
|
|
1773
2621
|
ordered: prevLast.ordered,
|
|
1774
2622
|
start: prevLast.start,
|
|
1775
2623
|
delimiter: prevLast.delimiter,
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
...(prevLast.
|
|
1779
|
-
|
|
1780
|
-
start: prevLast.
|
|
1781
|
-
end: deltaFirst.
|
|
2624
|
+
spread: true,
|
|
2625
|
+
children: [...prevLast.children, ...deltaFirst.children],
|
|
2626
|
+
...(prevLast.position != null && deltaFirst.position != null ? {
|
|
2627
|
+
position: {
|
|
2628
|
+
start: prevLast.position.start,
|
|
2629
|
+
end: deltaFirst.position.end
|
|
1782
2630
|
}
|
|
1783
2631
|
} : null)
|
|
1784
2632
|
};
|
|
1785
2633
|
prev.push(...delta.slice(1));
|
|
1786
|
-
return;
|
|
2634
|
+
return true;
|
|
1787
2635
|
}
|
|
1788
2636
|
prev.push(...delta);
|
|
2637
|
+
return false;
|
|
1789
2638
|
}
|
|
1790
2639
|
function sameUnsettledDefinitions(previous, next, settled) {
|
|
1791
2640
|
for (const [label, destination] of previous) {
|
|
@@ -1806,11 +2655,19 @@ function resetIncrementalCache(state, cache) {
|
|
|
1806
2655
|
state.settledBlocks = [];
|
|
1807
2656
|
state.settledUpTo = 0;
|
|
1808
2657
|
state.linkDefsKey = undefined;
|
|
2658
|
+
state.sourceIdsKey = undefined;
|
|
2659
|
+
state.pluginSyntaxIdentity = undefined;
|
|
2660
|
+
state.math = undefined;
|
|
1809
2661
|
cache.settledEnd = 0;
|
|
1810
2662
|
cache.settledLinkDefs.clear();
|
|
1811
2663
|
cache.tailLinkDefs = new Map();
|
|
1812
2664
|
cache.linkDefs = new Map();
|
|
1813
2665
|
cache.linkDefsKey = '';
|
|
2666
|
+
cache.settledAstBlocks = [];
|
|
2667
|
+
cache.settledRevision++;
|
|
2668
|
+
cache.projectedRevision = -1;
|
|
2669
|
+
cache.projectedSettledBlocks = [];
|
|
2670
|
+
cache.projectionCache = new WeakMap();
|
|
1814
2671
|
cache.work = {
|
|
1815
2672
|
splitCharacters: 0,
|
|
1816
2673
|
boundaryLines: 0,
|
|
@@ -1832,23 +2689,47 @@ function resetIncrementalCache(state, cache) {
|
|
|
1832
2689
|
*/
|
|
1833
2690
|
|
|
1834
2691
|
export function parseMarkdownIncremental(input, state, arg) {
|
|
1835
|
-
const
|
|
2692
|
+
const withRanges = wantsLegacyRanges(arg);
|
|
2693
|
+
const root = parseMarkdownAstIncremental(input, state, arg);
|
|
1836
2694
|
const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
|
|
1837
|
-
|
|
2695
|
+
const projected = projectMarkdownRoot(root, withRanges, cache.projectionCache);
|
|
2696
|
+
if (cache.projectedRevision !== cache.settledRevision) {
|
|
2697
|
+
cache.projectedSettledBlocks = cache.settledAstBlocks.map(block => projectBlockNode(block, cache.projectionCache, withRanges));
|
|
2698
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2699
|
+
}
|
|
2700
|
+
state.settledBlocks = cache.projectedSettledBlocks;
|
|
2701
|
+
return projected;
|
|
2702
|
+
}
|
|
1838
2703
|
|
|
1839
|
-
|
|
2704
|
+
/** @internal Canonical incremental parse used by Markdown rendering. */
|
|
2705
|
+
export function parseMarkdownAstIncremental(input, state, arg) {
|
|
2706
|
+
const opts = resolveOptions(arg, true);
|
|
2707
|
+
const root = {
|
|
2708
|
+
type: 'root',
|
|
2709
|
+
children: parseMarkdownIncrementalAstBlocks(input, state, opts)
|
|
2710
|
+
};
|
|
2711
|
+
return applyMarkdownTransforms(root, opts.plugins, input, opts.isFinal, 'block');
|
|
2712
|
+
}
|
|
2713
|
+
function parseMarkdownIncrementalAstBlocks(input, state, opts) {
|
|
2714
|
+
const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
|
|
2715
|
+
let reparseSettled = false;
|
|
2716
|
+
const nextSourceIdsKey = sourceIdsSignature(opts.sourceIds);
|
|
2717
|
+
const nextPluginSyntaxIdentity = opts.plugins?.syntaxIdentity ?? '';
|
|
2718
|
+
// Invalidate cache when an option that changes parsed nodes flips — cached
|
|
1840
2719
|
// settled blocks were parsed with the previous setting and would otherwise
|
|
1841
2720
|
// be reused unchanged.
|
|
1842
|
-
if (state.autolink !== opts.autolink || Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges)) {
|
|
2721
|
+
if (state.autolink !== opts.autolink || state.math !== opts.math || Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges) || state.sourceIdsKey !== nextSourceIdsKey || state.pluginSyntaxIdentity !== nextPluginSyntaxIdentity) {
|
|
1843
2722
|
reparseSettled = true;
|
|
1844
2723
|
state.autolink = opts.autolink;
|
|
2724
|
+
state.math = opts.math;
|
|
1845
2725
|
state.sourceRanges = opts.sourceRanges;
|
|
2726
|
+
state.sourceIdsKey = nextSourceIdsKey;
|
|
2727
|
+
state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
|
|
1846
2728
|
}
|
|
1847
2729
|
if (input === '') {
|
|
1848
2730
|
resetIncrementalCache(state, cache);
|
|
1849
2731
|
return [];
|
|
1850
2732
|
}
|
|
1851
|
-
|
|
1852
2733
|
// The settled prefix is only reusable while the input still contains it
|
|
1853
2734
|
// verbatim. Lengths alone cannot tell: a same-length or longer replacement
|
|
1854
2735
|
// (new args after reusing a state) disagrees with the prefix without ever
|
|
@@ -1860,7 +2741,10 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
1860
2741
|
if (state.settledText.length !== cache.settledEnd || !input.startsWith(state.settledText)) {
|
|
1861
2742
|
resetIncrementalCache(state, cache);
|
|
1862
2743
|
state.autolink = opts.autolink;
|
|
2744
|
+
state.math = opts.math;
|
|
1863
2745
|
state.sourceRanges = opts.sourceRanges;
|
|
2746
|
+
state.sourceIdsKey = nextSourceIdsKey;
|
|
2747
|
+
state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
|
|
1864
2748
|
}
|
|
1865
2749
|
|
|
1866
2750
|
// The recurring parse costs are confined to the mutable suffix: splitting,
|
|
@@ -1871,8 +2755,9 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
1871
2755
|
const tailLines = tailRaw.split('\n');
|
|
1872
2756
|
const {
|
|
1873
2757
|
boundary,
|
|
1874
|
-
openFence
|
|
1875
|
-
|
|
2758
|
+
openFence,
|
|
2759
|
+
openMath
|
|
2760
|
+
} = findSettledBoundary(tailLines, opts.math);
|
|
1876
2761
|
const settledDelta = boundary >= 0 ? tailLines.slice(0, boundary).join('\n') : '';
|
|
1877
2762
|
const nextSettledEnd = oldSettledEnd + settledDelta.length;
|
|
1878
2763
|
const unsettledInput = input.slice(nextSettledEnd);
|
|
@@ -1885,7 +2770,7 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
1885
2770
|
if (settledDelta !== '') {
|
|
1886
2771
|
const {
|
|
1887
2772
|
defs: deltaDefs
|
|
1888
|
-
} = extractLinkDefinitions(settledDelta);
|
|
2773
|
+
} = extractLinkDefinitions(settledDelta, opts.math);
|
|
1889
2774
|
for (const [label, destination] of deltaDefs) {
|
|
1890
2775
|
if (!cache.settledLinkDefs.has(label)) {
|
|
1891
2776
|
cache.settledLinkDefs.set(label, destination);
|
|
@@ -1897,7 +2782,7 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
1897
2782
|
}
|
|
1898
2783
|
const {
|
|
1899
2784
|
defs: tailLinkDefs
|
|
1900
|
-
} = extractLinkDefinitions(unsettledInput);
|
|
2785
|
+
} = extractLinkDefinitions(unsettledInput, opts.math);
|
|
1901
2786
|
if (!sameUnsettledDefinitions(cache.tailLinkDefs, tailLinkDefs, cache.settledLinkDefs)) {
|
|
1902
2787
|
definitionsChanged = true;
|
|
1903
2788
|
}
|
|
@@ -1921,27 +2806,47 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
1921
2806
|
}
|
|
1922
2807
|
cache.settledEnd = nextSettledEnd;
|
|
1923
2808
|
}
|
|
1924
|
-
const
|
|
2809
|
+
const trimmedUnsettledInput = unsettledInput.trim();
|
|
2810
|
+
// String#trim removes the CR that belongs to the final content line of a
|
|
2811
|
+
// CRLF snapshot along with trailing blank lines. Keep that one byte so
|
|
2812
|
+
// source ranges and delimiter content remain identical to a full parse.
|
|
2813
|
+
const unsettledRaw = trimmedUnsettledInput !== '' && /\r(?:\n[\s]*)?$/.test(unsettledInput) ? `${trimmedUnsettledInput}\r` : trimmedUnsettledInput;
|
|
1925
2814
|
// Structural trimming holds back lines that look like an incomplete list or
|
|
1926
2815
|
// table, which inside a fence is ordinary code: a TypeScript union or a `- `
|
|
1927
2816
|
// would disappear from the code block as it streams.
|
|
1928
|
-
const unsettledText = openFence ? unsettledRaw : trimUnsettledStructural(unsettledRaw);
|
|
2817
|
+
const unsettledText = openFence ? unsettledRaw : openMath ? trimOpenDisplayMath(unsettledRaw) : trimUnsettledStructural(unsettledRaw);
|
|
2818
|
+
const legacyRanges = opts.sourceRanges === true;
|
|
1929
2819
|
let parsedSettledBlocks = 0;
|
|
1930
2820
|
if (reparseSettled) {
|
|
1931
|
-
|
|
1932
|
-
parsedSettledBlocks =
|
|
2821
|
+
cache.settledAstBlocks = state.settledText ? parseMarkdownImpl(state.settledText, parseOpts) : [];
|
|
2822
|
+
parsedSettledBlocks = cache.settledAstBlocks.length;
|
|
2823
|
+
cache.settledRevision++;
|
|
2824
|
+
cache.projectedSettledBlocks = cache.settledAstBlocks.map(block => projectBlockNode(block, cache.projectionCache, legacyRanges));
|
|
2825
|
+
cache.projectedRevision = cache.settledRevision;
|
|
1933
2826
|
} else if (settledDelta !== '') {
|
|
1934
2827
|
const deltaBlocks = parseMarkdownImpl(settledDelta, atOffset(parseOpts, oldSettledEnd));
|
|
1935
|
-
|
|
2828
|
+
const mergeIndex = cache.settledAstBlocks.length - 1;
|
|
2829
|
+
const mergedList = appendSettledBlocks(cache.settledAstBlocks, deltaBlocks);
|
|
1936
2830
|
parsedSettledBlocks = deltaBlocks.length;
|
|
2831
|
+
cache.settledRevision++;
|
|
2832
|
+
const projectedDelta = deltaBlocks.map(block => projectBlockNode(block, cache.projectionCache, legacyRanges));
|
|
2833
|
+
if (mergedList) {
|
|
2834
|
+
const canonicalMerged = cache.settledAstBlocks[mergeIndex];
|
|
2835
|
+
const projectedMerged = projectBlockNode(canonicalMerged, cache.projectionCache, legacyRanges);
|
|
2836
|
+
cache.projectedSettledBlocks[mergeIndex] = projectedMerged;
|
|
2837
|
+
cache.projectedSettledBlocks.push(...projectedDelta.slice(1));
|
|
2838
|
+
} else {
|
|
2839
|
+
cache.projectedSettledBlocks.push(...projectedDelta);
|
|
2840
|
+
}
|
|
2841
|
+
cache.projectedRevision = cache.settledRevision;
|
|
1937
2842
|
}
|
|
1938
2843
|
|
|
1939
2844
|
// The unsettled tail is trimmed before parsing, so its offset in the
|
|
1940
2845
|
// document is where that trimmed text actually starts — not the boundary,
|
|
1941
|
-
// which is a line index. If it somehow
|
|
1942
|
-
//
|
|
1943
|
-
//
|
|
1944
|
-
const unsettledStart = unsettledText && opts.
|
|
2846
|
+
// which is a line index. If it somehow cannot be located, parse it without
|
|
2847
|
+
// positions rather than report wrong ones. The search starts at the
|
|
2848
|
+
// settled end, so it scans the tail and never the prefix.
|
|
2849
|
+
const unsettledStart = unsettledText && opts.astPositions === true ? input.indexOf(unsettledText, cache.settledEnd) : -1;
|
|
1945
2850
|
const unsettledBlocks = unsettledText ? parseMarkdownImpl(unsettledText, unsettledStart >= 0 ? atOffset(parseOpts, unsettledStart) : nested(parseOpts)) : [];
|
|
1946
2851
|
state.prevInput = input;
|
|
1947
2852
|
|
|
@@ -1956,7 +2861,7 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
1956
2861
|
definitionCharacters: settledDelta.length + unsettledInput.length,
|
|
1957
2862
|
renderedBlocks: parsedSettledBlocks + unsettledBlocks.length
|
|
1958
2863
|
};
|
|
1959
|
-
return mergeSettledBlocks(
|
|
2864
|
+
return mergeSettledBlocks(cache.settledAstBlocks, unsettledBlocks);
|
|
1960
2865
|
}
|
|
1961
2866
|
|
|
1962
2867
|
// ---------------------------------------------------------------------------
|
|
@@ -1967,27 +2872,6 @@ export function parseMarkdownIncremental(input, state, arg) {
|
|
|
1967
2872
|
// derives its item ids from the same functions, so outline hash links always
|
|
1968
2873
|
// resolve to a rendered heading by construction.
|
|
1969
2874
|
|
|
1970
|
-
/** Flatten inline nodes into their plain text content. */
|
|
1971
|
-
export function inlineText(nodes) {
|
|
1972
|
-
return nodes.map(node => {
|
|
1973
|
-
switch (node.type) {
|
|
1974
|
-
case 'text':
|
|
1975
|
-
case 'code':
|
|
1976
|
-
return node.content;
|
|
1977
|
-
case 'bold':
|
|
1978
|
-
case 'italic':
|
|
1979
|
-
case 'strikethrough':
|
|
1980
|
-
case 'link':
|
|
1981
|
-
return inlineText(node.children);
|
|
1982
|
-
case 'image':
|
|
1983
|
-
return node.alt;
|
|
1984
|
-
case 'citation':
|
|
1985
|
-
case 'break':
|
|
1986
|
-
return '';
|
|
1987
|
-
}
|
|
1988
|
-
}).join('');
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
2875
|
/** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
|
|
1992
2876
|
export function slugify(value) {
|
|
1993
2877
|
return value.trim().toLowerCase().replace(/['"]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|