@astryxdesign/core 0.6.2 → 0.6.3-canary.01fa439
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 +81 -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/ChatComposer.d.ts.map +1 -1
- package/dist/Chat/ChatComposer.js +10 -5
- package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
- package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
- package/dist/Chat/ChatComposerDrawer.js +48 -23
- package/dist/Chat/ChatComposerInput.d.ts +11 -3
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +63 -16
- package/dist/Chat/ChatDictationButton.d.ts +3 -2
- package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
- package/dist/Chat/ChatDictationButton.js +6 -4
- package/dist/Chat/ChatSendButton.js +1 -1
- package/dist/Chat/index.d.ts +1 -1
- package/dist/Chat/index.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +4 -4
- 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 +8 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +10 -1
- package/dist/Field/InputClearButton.d.ts +4 -2
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +4 -2
- 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 +7 -7
- package/dist/Kbd/Kbd.d.ts +5 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +36 -42
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -2
- package/dist/Markdown/Markdown.d.ts +8 -2
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.bench.js +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 +262 -109
- package/dist/Markdown/ast.d.ts +162 -0
- package/dist/Markdown/ast.d.ts.map +1 -0
- package/dist/Markdown/ast.js +74 -0
- package/dist/Markdown/index.d.ts +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 +126 -58
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +716 -167
- 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 +9 -7
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +7 -5
- package/dist/SideNav/SideNav.d.ts +2 -1
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +9 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +28 -12
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +23 -7
- 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/Table/Table.d.ts +1 -0
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +29 -13
- 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 +9 -6
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +15 -27
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +3 -0
- package/dist/TreeList/TreeListItem.d.ts +6 -1
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +12 -2
- package/dist/TreeList/TreeListTypes.d.ts +8 -1
- package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +16 -7
- package/dist/astryx.css +18 -7
- 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/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +10 -0
- package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +9 -0
- package/locales/af-ZA.json +530 -1
- package/locales/ar-SA.json +490 -1
- package/locales/ca-ES.json +514 -1
- package/locales/cs-CZ.json +498 -1
- package/locales/da-DK.json +522 -1
- package/locales/de-DE.json +522 -1
- package/locales/el-GR.json +498 -1
- package/locales/es-ES.json +510 -1
- package/locales/fi-FI.json +498 -1
- package/locales/fr-FR.json +534 -1
- package/locales/he-IL.json +498 -1
- package/locales/hu-HU.json +498 -1
- package/locales/it-IT.json +510 -1
- package/locales/ja-JP.json +490 -1
- package/locales/ko-KR.json +498 -1
- package/locales/nl-NL.json +534 -1
- package/locales/no-NO.json +510 -1
- package/locales/pl-PL.json +506 -1
- package/locales/pt-BR.json +506 -1
- package/locales/pt-PT.json +506 -1
- package/locales/ro-RO.json +506 -1
- package/locales/ru-RU.json +498 -1
- package/locales/sr-SP.json +498 -1
- package/locales/sv-SE.json +502 -1
- package/locales/tr-TR.json +498 -1
- package/locales/uk-UA.json +498 -1
- package/locales/vi-VN.json +502 -1
- package/locales/zh-CN.json +490 -1
- package/locales/zh-TW.json +490 -1
- package/package.json +19 -4
- 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/ChatComposer.doc.mjs +14 -0
- package/src/Chat/ChatComposer.spec.md +230 -0
- package/src/Chat/ChatComposer.test.tsx +57 -0
- package/src/Chat/ChatComposer.tsx +21 -9
- package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
- package/src/Chat/ChatComposerDrawer.spec.md +267 -0
- package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
- package/src/Chat/ChatComposerDrawer.tsx +38 -9
- package/src/Chat/ChatComposerInput.doc.mjs +114 -26
- package/src/Chat/ChatComposerInput.spec.md +185 -0
- package/src/Chat/ChatComposerInput.test.tsx +113 -3
- package/src/Chat/ChatComposerInput.tsx +78 -13
- package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
- package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
- package/src/Chat/ChatDictationButton.doc.mjs +3 -3
- package/src/Chat/ChatDictationButton.spec.md +160 -0
- package/src/Chat/ChatDictationButton.test.tsx +92 -27
- package/src/Chat/ChatDictationButton.tsx +8 -6
- package/src/Chat/ChatSendButton.test.tsx +1 -4
- package/src/Chat/ChatSendButton.tsx +1 -1
- package/src/Chat/index.ts +1 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -1
- package/src/ClickableCard/ClickableCard.test.tsx +102 -5
- package/src/Collapsible/Collapsible.doc.mjs +11 -0
- package/src/Collapsible/Collapsible.test.tsx +21 -0
- package/src/Collapsible/Collapsible.tsx +5 -0
- package/src/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.tsx +6 -1
- package/src/DateTimeInput/DateTimeInput.tsx +6 -1
- package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
- package/src/Dialog/DialogHeader.doc.mjs +12 -0
- package/src/Dialog/DialogHeader.test.tsx +97 -0
- package/src/Dialog/DialogHeader.tsx +25 -3
- package/src/Dialog/modules/DialogHeader.spec.md +75 -38
- package/src/Field/InputClearButton.test.tsx +17 -3
- package/src/Field/InputClearButton.tsx +5 -2
- package/src/Field/PanelSearchInput.tsx +5 -2
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
- package/src/FileInput/FileInput.tsx +11 -2
- 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/List/List.spec.md +200 -0
- package/src/Markdown/Markdown.doc.mjs +272 -0
- 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 +126 -3
- package/src/Markdown/Markdown.spec.md +197 -44
- package/src/Markdown/Markdown.test.tsx +11 -0
- package/src/Markdown/Markdown.tsx +414 -135
- package/src/Markdown/ast.test.ts +307 -0
- package/src/Markdown/ast.ts +337 -0
- package/src/Markdown/incremental.test.ts +12 -0
- package/src/Markdown/index.ts +37 -1
- package/src/Markdown/modules/remark.spec.md +269 -0
- package/src/Markdown/parser/index.ts +64 -0
- package/src/Markdown/parser.public.test.ts +103 -0
- package/src/Markdown/parser.ts +1180 -235
- 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 +13 -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.doc.mjs +2 -2
- package/src/Selector/Selector.spec.md +51 -49
- package/src/Selector/Selector.test.tsx +61 -55
- package/src/Selector/Selector.tsx +23 -14
- 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 +17 -3
- package/src/Slider/Slider.test.tsx +221 -0
- package/src/Slider/Slider.tsx +40 -9
- package/src/Spinner/Spinner.test.tsx +4 -0
- package/src/Spinner/Spinner.tsx +31 -18
- 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.spec.md +204 -0
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
- package/src/Table/Table.doc.mjs +1 -1
- package/src/Table/Table.spec.md +54 -40
- package/src/Table/Table.test.tsx +83 -10
- package/src/Table/Table.tsx +30 -22
- package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
- 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.doc.mjs +3 -3
- package/src/ToggleButton/ToggleButton.test.tsx +299 -4
- package/src/ToggleButton/ToggleButton.tsx +26 -33
- package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
- package/src/Tokenizer/Tokenizer.spec.md +142 -75
- package/src/TreeList/TreeList.doc.mjs +3 -3
- package/src/TreeList/TreeList.spec.md +123 -75
- package/src/TreeList/TreeList.test.tsx +29 -0
- package/src/TreeList/TreeList.tsx +3 -0
- package/src/TreeList/TreeListItem.tsx +15 -1
- package/src/TreeList/TreeListTypes.ts +11 -1
- 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/tailwind-theme.css +0 -6
- package/src/theme/generateThemeRules.test.ts +30 -0
- package/src/theme/generateThemeRules.ts +10 -0
- package/src/utils/interactionOverlay.stylex.ts +20 -0
package/src/Markdown/parser.ts
CHANGED
|
@@ -2,45 +2,79 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file parser.ts
|
|
5
|
-
* @input Markdown string
|
|
6
|
-
* @output
|
|
7
|
-
* helpers
|
|
8
|
-
*
|
|
9
|
-
* Outline
|
|
10
|
-
* @position Core parser; consumed by Markdown.tsx and Outline
|
|
5
|
+
* @input Markdown string, released parse options, and optional ordered plugins
|
|
6
|
+
* @output Canonical MDAST-aligned nodes for 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
|
|
11
10
|
*/
|
|
12
11
|
|
|
12
|
+
import {
|
|
13
|
+
getMarkdownAstLegacyCodeLanguage,
|
|
14
|
+
markMarkdownAstLegacyCodeLanguage,
|
|
15
|
+
} from './ast';
|
|
16
|
+
import type {
|
|
17
|
+
MarkdownAstBlockContent,
|
|
18
|
+
MarkdownAstList,
|
|
19
|
+
MarkdownAstListItem,
|
|
20
|
+
MarkdownAstPhrasingContent,
|
|
21
|
+
MarkdownAstPosition,
|
|
22
|
+
MarkdownAstRoot,
|
|
23
|
+
MarkdownAstTableCell,
|
|
24
|
+
MarkdownAstTableRow,
|
|
25
|
+
} from './ast';
|
|
26
|
+
import {
|
|
27
|
+
applyMarkdownTransforms,
|
|
28
|
+
freezeMarkdownPluginData,
|
|
29
|
+
isMarkdownPluginData,
|
|
30
|
+
prepareMarkdownPlugins,
|
|
31
|
+
reportMarkdownPluginFailure,
|
|
32
|
+
} from './plugins/protocol';
|
|
33
|
+
import type {
|
|
34
|
+
MarkdownExtensionNode,
|
|
35
|
+
MarkdownExtensionsOf,
|
|
36
|
+
MarkdownPluginData,
|
|
37
|
+
MarkdownPluginEntry,
|
|
38
|
+
PreparedMarkdownPlugins,
|
|
39
|
+
PreparedSyntaxContribution,
|
|
40
|
+
} from './plugins/protocol';
|
|
41
|
+
import {isSafeMarkdownParserUrl} from './url';
|
|
42
|
+
|
|
13
43
|
// ---------------------------------------------------------------------------
|
|
14
44
|
// Types
|
|
15
45
|
// ---------------------------------------------------------------------------
|
|
16
46
|
|
|
17
47
|
/** Nodes returned by default and legacy parser calls. */
|
|
18
|
-
export type InlineNode =
|
|
48
|
+
export type InlineNode<Extension extends MarkdownExtensionNode = never> =
|
|
19
49
|
| {type: 'text'; content: string}
|
|
20
|
-
| {type: 'bold'; children: InlineNode[]}
|
|
21
|
-
| {type: 'italic'; children: InlineNode[]}
|
|
22
|
-
| {type: 'strikethrough'; children: InlineNode[]}
|
|
50
|
+
| {type: 'bold'; children: InlineNode<Extension>[]}
|
|
51
|
+
| {type: 'italic'; children: InlineNode<Extension>[]}
|
|
52
|
+
| {type: 'strikethrough'; children: InlineNode<Extension>[]}
|
|
23
53
|
| {type: 'code'; content: string}
|
|
24
|
-
| {type: 'link'; href: string; children: InlineNode[]}
|
|
54
|
+
| {type: 'link'; href: string; children: InlineNode<Extension>[]}
|
|
25
55
|
| {type: 'image'; src: string; alt: string}
|
|
26
56
|
| {type: 'citation'; sourceId: string}
|
|
27
|
-
| {type: 'break'}
|
|
57
|
+
| {type: 'break'}
|
|
58
|
+
| Extract<Extension, {display: 'inline'}>;
|
|
28
59
|
|
|
29
60
|
/** The additional inline node returned only when parsing with `math: true`. */
|
|
30
61
|
export type MathInlineNode = {type: 'math'; value: string};
|
|
31
62
|
|
|
32
63
|
/** Nodes returned by an explicitly math-enabled inline parse. */
|
|
33
|
-
export type InlineNodeWithMath
|
|
64
|
+
export type InlineNodeWithMath<
|
|
65
|
+
Extension extends MarkdownExtensionNode = never,
|
|
66
|
+
> =
|
|
34
67
|
| {type: 'text'; content: string}
|
|
35
|
-
| {type: 'bold'; children: InlineNodeWithMath[]}
|
|
36
|
-
| {type: 'italic'; children: InlineNodeWithMath[]}
|
|
37
|
-
| {type: 'strikethrough'; children: InlineNodeWithMath[]}
|
|
68
|
+
| {type: 'bold'; children: InlineNodeWithMath<Extension>[]}
|
|
69
|
+
| {type: 'italic'; children: InlineNodeWithMath<Extension>[]}
|
|
70
|
+
| {type: 'strikethrough'; children: InlineNodeWithMath<Extension>[]}
|
|
38
71
|
| {type: 'code'; content: string}
|
|
39
72
|
| MathInlineNode
|
|
40
|
-
| {type: 'link'; href: string; children: InlineNodeWithMath[]}
|
|
73
|
+
| {type: 'link'; href: string; children: InlineNodeWithMath<Extension>[]}
|
|
41
74
|
| {type: 'image'; src: string; alt: string}
|
|
42
75
|
| {type: 'citation'; sourceId: string}
|
|
43
|
-
| {type: 'break'}
|
|
76
|
+
| {type: 'break'}
|
|
77
|
+
| Extract<Extension, {display: 'inline'}>;
|
|
44
78
|
|
|
45
79
|
type BlockMetadata = {
|
|
46
80
|
/**
|
|
@@ -50,11 +84,15 @@ type BlockMetadata = {
|
|
|
50
84
|
range?: SourceRange;
|
|
51
85
|
};
|
|
52
86
|
|
|
53
|
-
type LegacyBlockNodeKind =
|
|
54
|
-
| {
|
|
55
|
-
|
|
87
|
+
type LegacyBlockNodeKind<Extension extends MarkdownExtensionNode = never> =
|
|
88
|
+
| {
|
|
89
|
+
type: 'heading';
|
|
90
|
+
level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
91
|
+
children: InlineNode<Extension>[];
|
|
92
|
+
}
|
|
93
|
+
| {type: 'paragraph'; children: InlineNode<Extension>[]}
|
|
56
94
|
| {type: 'codeblock'; language: string; content: string}
|
|
57
|
-
| {type: 'blockquote'; children: BlockNode[]}
|
|
95
|
+
| {type: 'blockquote'; children: BlockNode<Extension>[]}
|
|
58
96
|
| {
|
|
59
97
|
type: 'list';
|
|
60
98
|
ordered: boolean;
|
|
@@ -62,33 +100,35 @@ type LegacyBlockNodeKind =
|
|
|
62
100
|
/** Ordered-list marker delimiter ('.' or ')'). Undefined for bullets. */
|
|
63
101
|
delimiter?: '.' | ')';
|
|
64
102
|
loose?: boolean;
|
|
65
|
-
items: ListItemNode[];
|
|
103
|
+
items: ListItemNode<Extension>[];
|
|
66
104
|
}
|
|
67
105
|
| {
|
|
68
106
|
type: 'table';
|
|
69
|
-
headers: TableCellNode[];
|
|
107
|
+
headers: TableCellNode<Extension>[];
|
|
70
108
|
alignments: TableAlignment[];
|
|
71
|
-
rows: TableCellNode[][];
|
|
109
|
+
rows: TableCellNode<Extension>[][];
|
|
72
110
|
}
|
|
73
111
|
| {type: 'hr'}
|
|
74
|
-
| {type: 'image'; src: string; alt: string}
|
|
112
|
+
| {type: 'image'; src: string; alt: string}
|
|
113
|
+
| Extract<Extension, {display: 'block'}>;
|
|
75
114
|
|
|
76
115
|
/** Blocks returned by default and legacy parser calls. */
|
|
77
|
-
export type BlockNode =
|
|
116
|
+
export type BlockNode<Extension extends MarkdownExtensionNode = never> =
|
|
117
|
+
LegacyBlockNodeKind<Extension> & BlockMetadata;
|
|
78
118
|
|
|
79
119
|
/** The additional block returned only when parsing with `math: true`. */
|
|
80
120
|
export type MathBlockNode = {type: 'math'; value: string} & BlockMetadata;
|
|
81
121
|
|
|
82
|
-
type MathEnabledBlockNodeKind =
|
|
122
|
+
type MathEnabledBlockNodeKind<Extension extends MarkdownExtensionNode = never> =
|
|
83
123
|
| {
|
|
84
124
|
type: 'heading';
|
|
85
125
|
level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
86
|
-
children: InlineNodeWithMath[];
|
|
126
|
+
children: InlineNodeWithMath<Extension>[];
|
|
87
127
|
}
|
|
88
|
-
| {type: 'paragraph'; children: InlineNodeWithMath[]}
|
|
128
|
+
| {type: 'paragraph'; children: InlineNodeWithMath<Extension>[]}
|
|
89
129
|
| {type: 'codeblock'; language: string; content: string}
|
|
90
130
|
| MathBlockNode
|
|
91
|
-
| {type: 'blockquote'; children: BlockNodeWithMath[]}
|
|
131
|
+
| {type: 'blockquote'; children: BlockNodeWithMath<Extension>[]}
|
|
92
132
|
| {
|
|
93
133
|
type: 'list';
|
|
94
134
|
ordered: boolean;
|
|
@@ -96,19 +136,21 @@ type MathEnabledBlockNodeKind =
|
|
|
96
136
|
/** Ordered-list marker delimiter ('.' or ')'). Undefined for bullets. */
|
|
97
137
|
delimiter?: '.' | ')';
|
|
98
138
|
loose?: boolean;
|
|
99
|
-
items: ListItemNodeWithMath[];
|
|
139
|
+
items: ListItemNodeWithMath<Extension>[];
|
|
100
140
|
}
|
|
101
141
|
| {
|
|
102
142
|
type: 'table';
|
|
103
|
-
headers: TableCellNodeWithMath[];
|
|
143
|
+
headers: TableCellNodeWithMath<Extension>[];
|
|
104
144
|
alignments: TableAlignment[];
|
|
105
|
-
rows: TableCellNodeWithMath[][];
|
|
145
|
+
rows: TableCellNodeWithMath<Extension>[][];
|
|
106
146
|
}
|
|
107
147
|
| {type: 'hr'}
|
|
108
|
-
| {type: 'image'; src: string; alt: string}
|
|
148
|
+
| {type: 'image'; src: string; alt: string}
|
|
149
|
+
| Extract<Extension, {display: 'block'}>;
|
|
109
150
|
|
|
110
151
|
/** Blocks returned by an explicitly math-enabled block parse. */
|
|
111
|
-
export type BlockNodeWithMath =
|
|
152
|
+
export type BlockNodeWithMath<Extension extends MarkdownExtensionNode = never> =
|
|
153
|
+
MathEnabledBlockNodeKind<Extension> & BlockMetadata;
|
|
112
154
|
|
|
113
155
|
/**
|
|
114
156
|
* Where a block sits in the source string handed to `parseMarkdown`:
|
|
@@ -121,15 +163,288 @@ export type BlockNodeWithMath = MathEnabledBlockNodeKind & BlockMetadata;
|
|
|
121
163
|
*/
|
|
122
164
|
export type SourceRange = {readonly start: number; readonly end: number};
|
|
123
165
|
|
|
124
|
-
export type ListItemNode =
|
|
125
|
-
|
|
166
|
+
export type ListItemNode<Extension extends MarkdownExtensionNode = never> = {
|
|
167
|
+
checked?: boolean;
|
|
168
|
+
children: BlockNode<Extension>[];
|
|
169
|
+
};
|
|
170
|
+
type ListItemNodeWithMath<Extension extends MarkdownExtensionNode = never> = {
|
|
126
171
|
checked?: boolean;
|
|
127
|
-
children: BlockNodeWithMath[];
|
|
172
|
+
children: BlockNodeWithMath<Extension>[];
|
|
173
|
+
};
|
|
174
|
+
export type TableCellNode<Extension extends MarkdownExtensionNode = never> = {
|
|
175
|
+
children: InlineNode<Extension>[];
|
|
176
|
+
};
|
|
177
|
+
type TableCellNodeWithMath<Extension extends MarkdownExtensionNode = never> = {
|
|
178
|
+
children: InlineNodeWithMath<Extension>[];
|
|
128
179
|
};
|
|
129
|
-
export type TableCellNode = {children: InlineNode[]};
|
|
130
|
-
type TableCellNodeWithMath = {children: InlineNodeWithMath[]};
|
|
131
180
|
export type TableAlignment = 'left' | 'center' | 'right' | null;
|
|
132
181
|
|
|
182
|
+
type RuntimeExtensionNode =
|
|
183
|
+
| MarkdownExtensionNode<string, string, MarkdownPluginData, 'inline'>
|
|
184
|
+
| MarkdownExtensionNode<string, string, MarkdownPluginData, 'block'>;
|
|
185
|
+
type RuntimeInlineNode = InlineNodeWithMath<RuntimeExtensionNode>;
|
|
186
|
+
type RuntimeBlockNode = BlockNodeWithMath<RuntimeExtensionNode>;
|
|
187
|
+
|
|
188
|
+
type LegacyProjectionCache = WeakMap<object, object>;
|
|
189
|
+
|
|
190
|
+
function projectRange(
|
|
191
|
+
position: MarkdownAstPosition | undefined,
|
|
192
|
+
): BlockMetadata {
|
|
193
|
+
const start = position?.start.offset;
|
|
194
|
+
const end = position?.end.offset;
|
|
195
|
+
return start == null || end == null ? {} : {range: {start, end}};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Canonical nodes always carry Core-authored positions; the released block
|
|
200
|
+
* shape exposes them as `range` only when the caller passed `sourceRanges`,
|
|
201
|
+
* so this projection stays byte-for-byte what it has always returned.
|
|
202
|
+
*
|
|
203
|
+
* An extension node is the one kind the projection passes through instead of
|
|
204
|
+
* rebuilding field by field, so it is the one kind that could carry a
|
|
205
|
+
* canonical `position` out into released output. Strip it unless the caller
|
|
206
|
+
* asked for provenance, in which case the node keeps exactly the `position`
|
|
207
|
+
* it has always exposed.
|
|
208
|
+
*/
|
|
209
|
+
function projectExtensionNode<Node extends RuntimeExtensionNode>(
|
|
210
|
+
node: Node,
|
|
211
|
+
withRanges: boolean,
|
|
212
|
+
): Node {
|
|
213
|
+
if (withRanges || node.position === undefined) {
|
|
214
|
+
return node;
|
|
215
|
+
}
|
|
216
|
+
const {position: _canonicalPosition, ...released} = node;
|
|
217
|
+
return released as Node;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function projectInlineNode(
|
|
221
|
+
node: MarkdownAstPhrasingContent<RuntimeExtensionNode>,
|
|
222
|
+
cache: LegacyProjectionCache,
|
|
223
|
+
withRanges: boolean,
|
|
224
|
+
): RuntimeInlineNode {
|
|
225
|
+
const cached = cache.get(node);
|
|
226
|
+
if (cached != null) {
|
|
227
|
+
return cached as RuntimeInlineNode;
|
|
228
|
+
}
|
|
229
|
+
let projected: RuntimeInlineNode;
|
|
230
|
+
switch (node.type) {
|
|
231
|
+
case 'text':
|
|
232
|
+
projected = {type: 'text', content: node.value};
|
|
233
|
+
break;
|
|
234
|
+
case 'strong':
|
|
235
|
+
projected = {
|
|
236
|
+
type: 'bold',
|
|
237
|
+
children: node.children.map(child =>
|
|
238
|
+
projectInlineNode(child, cache, withRanges),
|
|
239
|
+
),
|
|
240
|
+
};
|
|
241
|
+
break;
|
|
242
|
+
case 'emphasis':
|
|
243
|
+
projected = {
|
|
244
|
+
type: 'italic',
|
|
245
|
+
children: node.children.map(child =>
|
|
246
|
+
projectInlineNode(child, cache, withRanges),
|
|
247
|
+
),
|
|
248
|
+
};
|
|
249
|
+
break;
|
|
250
|
+
case 'delete':
|
|
251
|
+
projected = {
|
|
252
|
+
type: 'strikethrough',
|
|
253
|
+
children: node.children.map(child =>
|
|
254
|
+
projectInlineNode(child, cache, withRanges),
|
|
255
|
+
),
|
|
256
|
+
};
|
|
257
|
+
break;
|
|
258
|
+
case 'inlineCode':
|
|
259
|
+
projected = {type: 'code', content: node.value};
|
|
260
|
+
break;
|
|
261
|
+
case 'inlineMath':
|
|
262
|
+
projected = {type: 'math', value: node.value};
|
|
263
|
+
break;
|
|
264
|
+
case 'link':
|
|
265
|
+
projected = {
|
|
266
|
+
type: 'link',
|
|
267
|
+
href: node.url,
|
|
268
|
+
children: node.children.map(child =>
|
|
269
|
+
projectInlineNode(child, cache, withRanges),
|
|
270
|
+
),
|
|
271
|
+
};
|
|
272
|
+
break;
|
|
273
|
+
case 'image':
|
|
274
|
+
projected = {type: 'image', src: node.url, alt: node.alt};
|
|
275
|
+
break;
|
|
276
|
+
case 'citation':
|
|
277
|
+
projected = {type: 'citation', sourceId: node.sourceId};
|
|
278
|
+
break;
|
|
279
|
+
case 'break':
|
|
280
|
+
projected = {type: 'break'};
|
|
281
|
+
break;
|
|
282
|
+
case 'extension':
|
|
283
|
+
projected = projectExtensionNode(node, withRanges);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
cache.set(node, projected);
|
|
287
|
+
return projected;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function projectTableCell(
|
|
291
|
+
node: MarkdownAstTableCell<RuntimeExtensionNode>,
|
|
292
|
+
cache: LegacyProjectionCache,
|
|
293
|
+
withRanges: boolean,
|
|
294
|
+
): TableCellNodeWithMath<RuntimeExtensionNode> {
|
|
295
|
+
const cached = cache.get(node);
|
|
296
|
+
if (cached != null) {
|
|
297
|
+
return cached as TableCellNodeWithMath<RuntimeExtensionNode>;
|
|
298
|
+
}
|
|
299
|
+
const projected = {
|
|
300
|
+
children: node.children.map(child =>
|
|
301
|
+
projectInlineNode(child, cache, withRanges),
|
|
302
|
+
),
|
|
303
|
+
};
|
|
304
|
+
cache.set(node, projected);
|
|
305
|
+
return projected;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function projectListItem(
|
|
309
|
+
node: MarkdownAstListItem<RuntimeExtensionNode>,
|
|
310
|
+
cache: LegacyProjectionCache,
|
|
311
|
+
withRanges: boolean,
|
|
312
|
+
): ListItemNodeWithMath<RuntimeExtensionNode> {
|
|
313
|
+
const cached = cache.get(node);
|
|
314
|
+
if (cached != null) {
|
|
315
|
+
return cached as ListItemNodeWithMath<RuntimeExtensionNode>;
|
|
316
|
+
}
|
|
317
|
+
const projected = {
|
|
318
|
+
checked: node.checked,
|
|
319
|
+
children: node.children.map(child =>
|
|
320
|
+
projectBlockNode(child, cache, withRanges),
|
|
321
|
+
),
|
|
322
|
+
};
|
|
323
|
+
cache.set(node, projected);
|
|
324
|
+
return projected;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function projectBlockNode(
|
|
328
|
+
node: MarkdownAstBlockContent<RuntimeExtensionNode>,
|
|
329
|
+
cache: LegacyProjectionCache,
|
|
330
|
+
withRanges: boolean,
|
|
331
|
+
): RuntimeBlockNode {
|
|
332
|
+
const cached = cache.get(node);
|
|
333
|
+
if (cached != null) {
|
|
334
|
+
return cached as RuntimeBlockNode;
|
|
335
|
+
}
|
|
336
|
+
const metadata = withRanges ? projectRange(node.position) : {};
|
|
337
|
+
let projected: RuntimeBlockNode;
|
|
338
|
+
switch (node.type) {
|
|
339
|
+
case 'heading':
|
|
340
|
+
projected = {
|
|
341
|
+
type: 'heading',
|
|
342
|
+
level: node.depth,
|
|
343
|
+
children: node.children.map(child =>
|
|
344
|
+
projectInlineNode(child, cache, withRanges),
|
|
345
|
+
),
|
|
346
|
+
...metadata,
|
|
347
|
+
};
|
|
348
|
+
break;
|
|
349
|
+
case 'paragraph':
|
|
350
|
+
projected = {
|
|
351
|
+
type: 'paragraph',
|
|
352
|
+
children: node.children.map(child =>
|
|
353
|
+
projectInlineNode(child, cache, withRanges),
|
|
354
|
+
),
|
|
355
|
+
...metadata,
|
|
356
|
+
};
|
|
357
|
+
break;
|
|
358
|
+
case 'code':
|
|
359
|
+
projected = {
|
|
360
|
+
type: 'codeblock',
|
|
361
|
+
language: getMarkdownAstLegacyCodeLanguage(node) ?? 'plaintext',
|
|
362
|
+
content: node.value,
|
|
363
|
+
...metadata,
|
|
364
|
+
};
|
|
365
|
+
break;
|
|
366
|
+
case 'math':
|
|
367
|
+
projected = {type: 'math', value: node.value, ...metadata};
|
|
368
|
+
break;
|
|
369
|
+
case 'blockquote':
|
|
370
|
+
projected = {
|
|
371
|
+
type: 'blockquote',
|
|
372
|
+
children: node.children.map(child =>
|
|
373
|
+
projectBlockNode(child, cache, withRanges),
|
|
374
|
+
),
|
|
375
|
+
...metadata,
|
|
376
|
+
};
|
|
377
|
+
break;
|
|
378
|
+
case 'list':
|
|
379
|
+
projected = {
|
|
380
|
+
type: 'list',
|
|
381
|
+
ordered: node.ordered,
|
|
382
|
+
start: node.start,
|
|
383
|
+
delimiter: node.delimiter,
|
|
384
|
+
loose: node.spread,
|
|
385
|
+
items: node.children.map(item =>
|
|
386
|
+
projectListItem(item, cache, withRanges),
|
|
387
|
+
),
|
|
388
|
+
...metadata,
|
|
389
|
+
};
|
|
390
|
+
break;
|
|
391
|
+
case 'table': {
|
|
392
|
+
const [header = {type: 'tableRow' as const, children: []}, ...rows] =
|
|
393
|
+
node.children;
|
|
394
|
+
projected = {
|
|
395
|
+
type: 'table',
|
|
396
|
+
headers: header.children.map(cell =>
|
|
397
|
+
projectTableCell(cell, cache, withRanges),
|
|
398
|
+
),
|
|
399
|
+
alignments: [...node.align],
|
|
400
|
+
rows: rows.map(row =>
|
|
401
|
+
row.children.map(cell => projectTableCell(cell, cache, withRanges)),
|
|
402
|
+
),
|
|
403
|
+
...metadata,
|
|
404
|
+
};
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
case 'thematicBreak':
|
|
408
|
+
projected = {type: 'hr', ...metadata};
|
|
409
|
+
break;
|
|
410
|
+
case 'image':
|
|
411
|
+
projected = {type: 'image', alt: node.alt, src: node.url, ...metadata};
|
|
412
|
+
break;
|
|
413
|
+
case 'extension':
|
|
414
|
+
projected = projectExtensionNode(node, withRanges);
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
cache.set(node, projected);
|
|
418
|
+
return projected;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function projectInlineNodes(
|
|
422
|
+
nodes: ReadonlyArray<MarkdownAstPhrasingContent<RuntimeExtensionNode>>,
|
|
423
|
+
withRanges: boolean,
|
|
424
|
+
cache: LegacyProjectionCache = new WeakMap(),
|
|
425
|
+
): RuntimeInlineNode[] {
|
|
426
|
+
return nodes.map(node => projectInlineNode(node, cache, withRanges));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function projectMarkdownRoot(
|
|
430
|
+
root: MarkdownAstRoot<RuntimeExtensionNode>,
|
|
431
|
+
withRanges: boolean,
|
|
432
|
+
cache: LegacyProjectionCache = new WeakMap(),
|
|
433
|
+
): RuntimeBlockNode[] {
|
|
434
|
+
return root.children.map(node => projectBlockNode(node, cache, withRanges));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Whether a caller asked for the released `range` field on its blocks. */
|
|
438
|
+
function wantsLegacyRanges(
|
|
439
|
+
arg: ReadonlySet<string> | RuntimeParseOptions | undefined,
|
|
440
|
+
): boolean {
|
|
441
|
+
return (
|
|
442
|
+
arg != null &&
|
|
443
|
+
typeof (arg as {has?: unknown}).has !== 'function' &&
|
|
444
|
+
(arg as RuntimeParseOptions).sourceRanges === true
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
133
448
|
// ---------------------------------------------------------------------------
|
|
134
449
|
// Parse options
|
|
135
450
|
// ---------------------------------------------------------------------------
|
|
@@ -141,10 +456,14 @@ export type TableAlignment = 'left' | 'center' | 'right' | null;
|
|
|
141
456
|
* `parseMarkdownIncremental` functions also accept the legacy
|
|
142
457
|
* `ReadonlySet<string>` shape as the second argument.
|
|
143
458
|
*/
|
|
144
|
-
type CommonParseOptions
|
|
459
|
+
type CommonParseOptions<
|
|
460
|
+
Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
|
|
461
|
+
> = {
|
|
145
462
|
/** Set of citation source ids — `[id]` / `【id】` markers in this set
|
|
146
463
|
* become citation nodes instead of plain text / links. */
|
|
147
464
|
sourceIds?: ReadonlySet<string>;
|
|
465
|
+
/** Ordered opt-in Markdown extensions. */
|
|
466
|
+
plugins?: Plugins;
|
|
148
467
|
/**
|
|
149
468
|
* Autolink mode. When set to `'gfm'`, the parser turns bare
|
|
150
469
|
* `https?://…` / `www.…` URLs, `<URL>` / `<email>` angle-bracket
|
|
@@ -170,22 +489,56 @@ type CommonParseOptions = {
|
|
|
170
489
|
};
|
|
171
490
|
|
|
172
491
|
/** Options for default and legacy parser results. */
|
|
173
|
-
export type ParseOptions
|
|
492
|
+
export type ParseOptions<
|
|
493
|
+
Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
|
|
494
|
+
> = CommonParseOptions<Plugins> & {math?: false | undefined};
|
|
174
495
|
|
|
175
496
|
/**
|
|
176
497
|
* Options that explicitly parse `$…$` and `$$…$$` into math-enabled result
|
|
177
498
|
* unions. Keeping this separate prevents a legacy `ParseOptions` annotation
|
|
178
499
|
* from silently widening an exhaustive node switch.
|
|
179
500
|
*/
|
|
180
|
-
export type MathParseOptions
|
|
501
|
+
export type MathParseOptions<
|
|
502
|
+
Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
|
|
503
|
+
> = CommonParseOptions<Plugins> & {math: true};
|
|
504
|
+
|
|
505
|
+
export type IncrementalParseOptions<
|
|
506
|
+
Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
|
|
507
|
+
> = ParseOptions<Plugins> & {
|
|
508
|
+
/** False while more source may arrive; true for the terminal snapshot. */
|
|
509
|
+
readonly isFinal?: boolean;
|
|
510
|
+
};
|
|
181
511
|
|
|
182
|
-
type
|
|
512
|
+
export type IncrementalMathParseOptions<
|
|
513
|
+
Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
|
|
514
|
+
> = MathParseOptions<Plugins> & {
|
|
515
|
+
/** False while more source may arrive; true for the terminal snapshot. */
|
|
516
|
+
readonly isFinal?: boolean;
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
type RuntimeParseOptions = CommonParseOptions<
|
|
520
|
+
ReadonlyArray<MarkdownPluginEntry>
|
|
521
|
+
> & {
|
|
522
|
+
math?: boolean;
|
|
523
|
+
isFinal?: boolean;
|
|
524
|
+
};
|
|
183
525
|
|
|
184
526
|
type ResolvedOptions = {
|
|
185
527
|
readonly sourceIds: ReadonlySet<string> | undefined;
|
|
186
528
|
readonly autolink: 'gfm' | undefined;
|
|
187
529
|
readonly math?: boolean;
|
|
530
|
+
/** Whether the caller asked for the released `range` projection. */
|
|
188
531
|
readonly sourceRanges?: boolean;
|
|
532
|
+
/**
|
|
533
|
+
* Whether this parse authors canonical `position` offsets on its blocks.
|
|
534
|
+
* Internal only, and independent of `sourceRanges`: the canonical tree
|
|
535
|
+
* transforms observe always carries provenance, while `sourceRanges` alone
|
|
536
|
+
* decides whether the released projection exposes it as `range`.
|
|
537
|
+
*/
|
|
538
|
+
readonly astPositions?: boolean;
|
|
539
|
+
readonly plugins?: PreparedMarkdownPlugins;
|
|
540
|
+
readonly isFinal: boolean;
|
|
541
|
+
readonly allowBlockSyntax?: boolean;
|
|
189
542
|
/**
|
|
190
543
|
* Offset of this parse's input within the document the ranges are reported
|
|
191
544
|
* against. Internal only — the incremental parser parses slices and needs
|
|
@@ -201,13 +554,42 @@ type ResolvedOptions = {
|
|
|
201
554
|
readonly linkDefs?: ReadonlyMap<string, string>;
|
|
202
555
|
};
|
|
203
556
|
|
|
204
|
-
|
|
557
|
+
/**
|
|
558
|
+
* Every resolved options object is built here, so all of them share one key
|
|
559
|
+
* set in one order — and therefore one hidden class. The parser reads these
|
|
560
|
+
* fields in its hottest loops; letting an omitted-options parse and an
|
|
561
|
+
* options-bag parse produce differently-shaped objects makes those reads
|
|
562
|
+
* polymorphic in any process that does both, which is exactly what a caller
|
|
563
|
+
* comparing an empty pipeline against a configured one does.
|
|
564
|
+
*/
|
|
565
|
+
function makeResolvedOptions(
|
|
566
|
+
fields: Partial<ResolvedOptions> & {readonly isFinal: boolean},
|
|
567
|
+
): ResolvedOptions {
|
|
568
|
+
return {
|
|
569
|
+
sourceIds: fields.sourceIds,
|
|
570
|
+
autolink: fields.autolink,
|
|
571
|
+
math: fields.math,
|
|
572
|
+
sourceRanges: fields.sourceRanges,
|
|
573
|
+
astPositions: fields.astPositions ?? true,
|
|
574
|
+
plugins: fields.plugins,
|
|
575
|
+
isFinal: fields.isFinal,
|
|
576
|
+
allowBlockSyntax: fields.allowBlockSyntax ?? true,
|
|
577
|
+
baseOffset: fields.baseOffset,
|
|
578
|
+
linkDefs: fields.linkDefs,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const EMPTY_OPTS: ResolvedOptions = makeResolvedOptions({isFinal: true});
|
|
583
|
+
const EMPTY_INCREMENTAL_OPTS: ResolvedOptions = makeResolvedOptions({
|
|
584
|
+
isFinal: false,
|
|
585
|
+
});
|
|
205
586
|
|
|
206
587
|
function resolveOptions(
|
|
207
588
|
arg: ReadonlySet<string> | RuntimeParseOptions | undefined,
|
|
589
|
+
incremental = false,
|
|
208
590
|
): ResolvedOptions {
|
|
209
591
|
if (arg == null) {
|
|
210
|
-
return EMPTY_OPTS;
|
|
592
|
+
return incremental ? EMPTY_INCREMENTAL_OPTS : EMPTY_OPTS;
|
|
211
593
|
}
|
|
212
594
|
// Duck-type the legacy `ReadonlySet<string>` form: any object whose
|
|
213
595
|
// `.has` is callable is treated as the legacy sourceIds set. This is
|
|
@@ -215,15 +597,23 @@ function resolveOptions(
|
|
|
215
597
|
// or polyfilled `ReadonlySet` implementations as a `ParseOptions` bag
|
|
216
598
|
// and silently lose citation resolution.
|
|
217
599
|
if (typeof (arg as {has?: unknown}).has === 'function') {
|
|
218
|
-
return {
|
|
600
|
+
return makeResolvedOptions({
|
|
601
|
+
sourceIds: arg as ReadonlySet<string>,
|
|
602
|
+
isFinal: !incremental,
|
|
603
|
+
});
|
|
219
604
|
}
|
|
220
605
|
const opts = arg as RuntimeParseOptions;
|
|
221
|
-
return {
|
|
606
|
+
return makeResolvedOptions({
|
|
222
607
|
sourceIds: opts.sourceIds,
|
|
223
608
|
autolink: opts.autolink,
|
|
224
609
|
math: opts.math === true ? true : undefined,
|
|
225
610
|
sourceRanges: opts.sourceRanges,
|
|
226
|
-
|
|
611
|
+
plugins:
|
|
612
|
+
opts.plugins != null && opts.plugins.length > 0
|
|
613
|
+
? prepareMarkdownPlugins(opts.plugins)
|
|
614
|
+
: undefined,
|
|
615
|
+
isFinal: incremental ? opts.isFinal === true : true,
|
|
616
|
+
});
|
|
227
617
|
}
|
|
228
618
|
|
|
229
619
|
// ---------------------------------------------------------------------------
|
|
@@ -520,6 +910,15 @@ function extractLinkDefinitions(
|
|
|
520
910
|
return {defs, cleaned, lineMap};
|
|
521
911
|
}
|
|
522
912
|
|
|
913
|
+
/** Order-independent signature of a citation-source set, for cache checks. */
|
|
914
|
+
function sourceIdsSignature(
|
|
915
|
+
sourceIds: ReadonlySet<string> | undefined,
|
|
916
|
+
): string {
|
|
917
|
+
return sourceIds == null || sourceIds.size === 0
|
|
918
|
+
? ''
|
|
919
|
+
: [...sourceIds].sort().join('\u0000');
|
|
920
|
+
}
|
|
921
|
+
|
|
523
922
|
/** Order-independent signature of a link-definition set, for cache checks. */
|
|
524
923
|
function linkDefsSignature(defs: ReadonlyMap<string, string>): string {
|
|
525
924
|
if (defs.size === 0) {
|
|
@@ -537,12 +936,19 @@ function linkDefsSignature(defs: ReadonlyMap<string, string>): string {
|
|
|
537
936
|
* Returns the node plus the index just past the reference, or null when it is
|
|
538
937
|
* not a resolvable reference (caller falls through to literal handling).
|
|
539
938
|
*/
|
|
939
|
+
function protectedInlineOptions(opts: ResolvedOptions): ResolvedOptions {
|
|
940
|
+
return opts.plugins == null ? opts : {...opts, plugins: undefined};
|
|
941
|
+
}
|
|
942
|
+
|
|
540
943
|
function matchReferenceLink(
|
|
541
944
|
text: string,
|
|
542
945
|
start: number,
|
|
543
946
|
linkDefs: ReadonlyMap<string, string>,
|
|
544
947
|
opts: ResolvedOptions,
|
|
545
|
-
): {
|
|
948
|
+
): {
|
|
949
|
+
node: MarkdownAstPhrasingContent<RuntimeExtensionNode>;
|
|
950
|
+
end: number;
|
|
951
|
+
} | null {
|
|
546
952
|
const textClose = text.indexOf(']', start + 1);
|
|
547
953
|
if (textClose === -1) {
|
|
548
954
|
return null;
|
|
@@ -558,9 +964,13 @@ function matchReferenceLink(
|
|
|
558
964
|
// matches nothing.
|
|
559
965
|
const label = rawLabel === '' ? linkText : rawLabel;
|
|
560
966
|
const href = linkDefs.get(normalizeLinkLabel(label));
|
|
561
|
-
if (href != null &&
|
|
967
|
+
if (href != null && isSafeMarkdownParserUrl(href)) {
|
|
562
968
|
return {
|
|
563
|
-
node: {
|
|
969
|
+
node: {
|
|
970
|
+
type: 'link',
|
|
971
|
+
url: href,
|
|
972
|
+
children: parseInlineImpl(linkText, protectedInlineOptions(opts)),
|
|
973
|
+
},
|
|
564
974
|
end: labelClose + 1,
|
|
565
975
|
};
|
|
566
976
|
}
|
|
@@ -573,11 +983,15 @@ function matchReferenceLink(
|
|
|
573
983
|
return null;
|
|
574
984
|
}
|
|
575
985
|
const href = linkDefs.get(normalizeLinkLabel(linkText));
|
|
576
|
-
if (href == null || !
|
|
986
|
+
if (href == null || !isSafeMarkdownParserUrl(href)) {
|
|
577
987
|
return null;
|
|
578
988
|
}
|
|
579
989
|
return {
|
|
580
|
-
node: {
|
|
990
|
+
node: {
|
|
991
|
+
type: 'link',
|
|
992
|
+
url: href,
|
|
993
|
+
children: parseInlineImpl(linkText, protectedInlineOptions(opts)),
|
|
994
|
+
},
|
|
581
995
|
end: textClose + 1,
|
|
582
996
|
};
|
|
583
997
|
}
|
|
@@ -587,7 +1001,10 @@ function matchReferenceImage(
|
|
|
587
1001
|
text: string,
|
|
588
1002
|
start: number,
|
|
589
1003
|
linkDefs: ReadonlyMap<string, string>,
|
|
590
|
-
): {
|
|
1004
|
+
): {
|
|
1005
|
+
node: MarkdownAstPhrasingContent<RuntimeExtensionNode>;
|
|
1006
|
+
end: number;
|
|
1007
|
+
} | null {
|
|
591
1008
|
const altClose = text.indexOf(']', start + 2);
|
|
592
1009
|
if (altClose === -1) {
|
|
593
1010
|
return null;
|
|
@@ -599,8 +1016,8 @@ function matchReferenceImage(
|
|
|
599
1016
|
const rawLabel = text.slice(altClose + 2, labelClose);
|
|
600
1017
|
const label = rawLabel === '' ? alt : rawLabel;
|
|
601
1018
|
const src = linkDefs.get(normalizeLinkLabel(label));
|
|
602
|
-
if (src != null &&
|
|
603
|
-
return {node: {type: 'image', src, alt}, end: labelClose + 1};
|
|
1019
|
+
if (src != null && isSafeMarkdownParserUrl(src)) {
|
|
1020
|
+
return {node: {type: 'image', url: src, alt}, end: labelClose + 1};
|
|
604
1021
|
}
|
|
605
1022
|
// No match — fall back to a shortcut `![alt]`.
|
|
606
1023
|
}
|
|
@@ -609,10 +1026,10 @@ function matchReferenceImage(
|
|
|
609
1026
|
return null;
|
|
610
1027
|
}
|
|
611
1028
|
const src = linkDefs.get(normalizeLinkLabel(alt));
|
|
612
|
-
if (src == null || !
|
|
1029
|
+
if (src == null || !isSafeMarkdownParserUrl(src)) {
|
|
613
1030
|
return null;
|
|
614
1031
|
}
|
|
615
|
-
return {node: {type: 'image', src, alt}, end: altClose + 1};
|
|
1032
|
+
return {node: {type: 'image', url: src, alt}, end: altClose + 1};
|
|
616
1033
|
}
|
|
617
1034
|
|
|
618
1035
|
// ---------------------------------------------------------------------------
|
|
@@ -701,30 +1118,6 @@ function findInlineMathEnd(text: string, start: number): number {
|
|
|
701
1118
|
}
|
|
702
1119
|
|
|
703
1120
|
// ---------------------------------------------------------------------------
|
|
704
|
-
// URL scheme sanitization
|
|
705
|
-
// ---------------------------------------------------------------------------
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* Reject URLs with dangerous schemes (javascript:, vbscript:, data:) that
|
|
709
|
-
* could execute arbitrary code when rendered as link hrefs or image srcs.
|
|
710
|
-
* Returns true if the URL is safe to use, false otherwise.
|
|
711
|
-
*/
|
|
712
|
-
function isSafeUrl(url: string): boolean {
|
|
713
|
-
// Trim and collapse whitespace/control chars that browsers tolerate but
|
|
714
|
-
// could bypass a naive prefix check (e.g. "java\nscript:alert(1)").
|
|
715
|
-
// eslint-disable-next-line no-control-regex -- control chars are the bypass
|
|
716
|
-
const normalized = url.replace(/[\x00-\x1f\x7f]/g, '').trim();
|
|
717
|
-
const lower = normalized.toLowerCase();
|
|
718
|
-
if (
|
|
719
|
-
lower.startsWith('javascript:') ||
|
|
720
|
-
lower.startsWith('vbscript:') ||
|
|
721
|
-
lower.startsWith('data:text/html')
|
|
722
|
-
) {
|
|
723
|
-
return false;
|
|
724
|
-
}
|
|
725
|
-
return true;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
1121
|
// ---------------------------------------------------------------------------
|
|
729
1122
|
// Inline parser
|
|
730
1123
|
// ---------------------------------------------------------------------------
|
|
@@ -778,20 +1171,246 @@ function matchBracketCitation(
|
|
|
778
1171
|
return {sourceId: id, end: closeIndex + 1};
|
|
779
1172
|
}
|
|
780
1173
|
|
|
1174
|
+
type ExtensionMatch =
|
|
1175
|
+
| {readonly status: 'none'}
|
|
1176
|
+
| {readonly status: 'defer'}
|
|
1177
|
+
| {
|
|
1178
|
+
readonly status: 'match';
|
|
1179
|
+
readonly node: RuntimeExtensionNode;
|
|
1180
|
+
readonly end: number;
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
function currentLineStart(source: string, offset: number): number {
|
|
1184
|
+
return source.lastIndexOf('\n', offset - 1) + 1;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function matchExtensionSyntax(
|
|
1188
|
+
source: string,
|
|
1189
|
+
offset: number,
|
|
1190
|
+
context: 'inline' | 'block',
|
|
1191
|
+
opts: ResolvedOptions,
|
|
1192
|
+
): ExtensionMatch {
|
|
1193
|
+
const byFirstCharacter =
|
|
1194
|
+
context === 'inline'
|
|
1195
|
+
? opts.plugins?.inlineByFirstCharacter
|
|
1196
|
+
: opts.allowBlockSyntax === false
|
|
1197
|
+
? undefined
|
|
1198
|
+
: opts.plugins?.blockByFirstCharacter;
|
|
1199
|
+
const candidates = byFirstCharacter?.get(source[offset]);
|
|
1200
|
+
if (candidates == null) {
|
|
1201
|
+
return {status: 'none'};
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const seen = new Set<PreparedSyntaxContribution>();
|
|
1205
|
+
for (const candidate of candidates) {
|
|
1206
|
+
if (seen.has(candidate)) {
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
seen.add(candidate);
|
|
1210
|
+
let fullPrefix = false;
|
|
1211
|
+
for (const prefix of candidate.contribution.startsWith) {
|
|
1212
|
+
if (source.startsWith(prefix, offset)) {
|
|
1213
|
+
fullPrefix = true;
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
if (!opts.isFinal) {
|
|
1217
|
+
const remaining = source.slice(offset);
|
|
1218
|
+
if (remaining.length < prefix.length && prefix.startsWith(remaining)) {
|
|
1219
|
+
return {status: 'defer'};
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (!fullPrefix) {
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
const end = Math.min(
|
|
1228
|
+
source.length,
|
|
1229
|
+
offset + candidate.contribution.maxSpan,
|
|
1230
|
+
);
|
|
1231
|
+
let result: ReturnType<typeof candidate.contribution.tokenize>;
|
|
1232
|
+
try {
|
|
1233
|
+
result = candidate.contribution.tokenize({
|
|
1234
|
+
source: end === source.length ? source : source.slice(0, end),
|
|
1235
|
+
offset,
|
|
1236
|
+
end,
|
|
1237
|
+
isFinal: opts.isFinal,
|
|
1238
|
+
context,
|
|
1239
|
+
lineStart: currentLineStart(source, offset),
|
|
1240
|
+
column: offset - currentLineStart(source, offset),
|
|
1241
|
+
});
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
reportMarkdownPluginFailure(candidate.pluginName, 'syntax', error);
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
if (
|
|
1247
|
+
result == null ||
|
|
1248
|
+
typeof result !== 'object' ||
|
|
1249
|
+
typeof (result as {then?: unknown}).then === 'function'
|
|
1250
|
+
) {
|
|
1251
|
+
reportMarkdownPluginFailure(
|
|
1252
|
+
candidate.pluginName,
|
|
1253
|
+
'syntax',
|
|
1254
|
+
new TypeError('Tokenizer returned an invalid result'),
|
|
1255
|
+
);
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
if (result.status === 'no-match') {
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
if (result.status === 'defer') {
|
|
1262
|
+
return !opts.isFinal &&
|
|
1263
|
+
end === source.length &&
|
|
1264
|
+
source.length - offset < candidate.contribution.maxSpan
|
|
1265
|
+
? {status: 'defer'}
|
|
1266
|
+
: {status: 'none'};
|
|
1267
|
+
}
|
|
1268
|
+
if (
|
|
1269
|
+
result.status !== 'match' ||
|
|
1270
|
+
!Number.isInteger(result.end) ||
|
|
1271
|
+
result.end <= offset ||
|
|
1272
|
+
result.end > end ||
|
|
1273
|
+
result.node.type !== 'extension' ||
|
|
1274
|
+
result.node.plugin !== candidate.pluginName ||
|
|
1275
|
+
result.node.display !== context ||
|
|
1276
|
+
result.node.name.trim() === '' ||
|
|
1277
|
+
!isMarkdownPluginData(result.node.data) ||
|
|
1278
|
+
!opts.plugins?.renderers.has(
|
|
1279
|
+
`${candidate.pluginName}\0${result.node.name}`,
|
|
1280
|
+
)
|
|
1281
|
+
) {
|
|
1282
|
+
reportMarkdownPluginFailure(
|
|
1283
|
+
candidate.pluginName,
|
|
1284
|
+
'syntax',
|
|
1285
|
+
new TypeError('Tokenizer returned an invalid extension node'),
|
|
1286
|
+
);
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
const rawNode = result.node as typeof result.node & {
|
|
1290
|
+
readonly source?: unknown;
|
|
1291
|
+
readonly position?: unknown;
|
|
1292
|
+
};
|
|
1293
|
+
const {
|
|
1294
|
+
source: _ignoredSource,
|
|
1295
|
+
position: _ignoredPosition,
|
|
1296
|
+
...safeNode
|
|
1297
|
+
} = rawNode;
|
|
1298
|
+
const absoluteStart = (opts.baseOffset ?? 0) + offset;
|
|
1299
|
+
const absoluteEnd = (opts.baseOffset ?? 0) + result.end;
|
|
1300
|
+
return {
|
|
1301
|
+
status: 'match',
|
|
1302
|
+
end: result.end,
|
|
1303
|
+
node: Object.freeze({
|
|
1304
|
+
...safeNode,
|
|
1305
|
+
data: freezeMarkdownPluginData(result.node.data),
|
|
1306
|
+
source: source.slice(offset, result.end),
|
|
1307
|
+
...(opts.sourceRanges && context === 'block'
|
|
1308
|
+
? {
|
|
1309
|
+
position: Object.freeze({
|
|
1310
|
+
start: Object.freeze({offset: absoluteStart}),
|
|
1311
|
+
end: Object.freeze({offset: absoluteEnd}),
|
|
1312
|
+
}),
|
|
1313
|
+
}
|
|
1314
|
+
: null),
|
|
1315
|
+
}) as RuntimeExtensionNode,
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
return {status: 'none'};
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
type ParseOptionsWithoutPlugins = Omit<ParseOptions, 'plugins'>;
|
|
1322
|
+
type MathParseOptionsWithoutPlugins = Omit<MathParseOptions, 'plugins'>;
|
|
1323
|
+
type IncrementalParseOptionsWithoutPlugins = Omit<
|
|
1324
|
+
IncrementalParseOptions,
|
|
1325
|
+
'plugins'
|
|
1326
|
+
>;
|
|
1327
|
+
type IncrementalMathParseOptionsWithoutPlugins = Omit<
|
|
1328
|
+
IncrementalMathParseOptions,
|
|
1329
|
+
'plugins'
|
|
1330
|
+
>;
|
|
1331
|
+
|
|
781
1332
|
export function parseInline(
|
|
782
1333
|
text: string,
|
|
783
1334
|
sourceIds?: ReadonlySet<string>,
|
|
784
1335
|
): InlineNode[];
|
|
785
1336
|
export function parseInline(
|
|
786
1337
|
text: string,
|
|
787
|
-
options:
|
|
1338
|
+
options: MathParseOptionsWithoutPlugins,
|
|
788
1339
|
): InlineNodeWithMath[];
|
|
789
|
-
export function parseInline(
|
|
1340
|
+
export function parseInline(
|
|
1341
|
+
text: string,
|
|
1342
|
+
options: ParseOptionsWithoutPlugins,
|
|
1343
|
+
): InlineNode[];
|
|
1344
|
+
export function parseInline<
|
|
1345
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
|
|
1346
|
+
>(
|
|
1347
|
+
text: string,
|
|
1348
|
+
options: ParseOptionsWithoutPlugins & {plugins: Plugins},
|
|
1349
|
+
): InlineNode<MarkdownExtensionsOf<Plugins>>[];
|
|
1350
|
+
export function parseInline<
|
|
1351
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
|
|
1352
|
+
>(
|
|
1353
|
+
text: string,
|
|
1354
|
+
options: MathParseOptionsWithoutPlugins & {plugins: Plugins},
|
|
1355
|
+
): InlineNodeWithMath<MarkdownExtensionsOf<Plugins>>[];
|
|
790
1356
|
export function parseInline(
|
|
791
1357
|
text: string,
|
|
792
1358
|
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
793
|
-
):
|
|
794
|
-
return
|
|
1359
|
+
): RuntimeInlineNode[] {
|
|
1360
|
+
return projectInlineNodes(
|
|
1361
|
+
parseInlineAstRuntime(text, arg),
|
|
1362
|
+
wantsLegacyRanges(arg),
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Parse inline Markdown into the canonical immutable Astryx AST.
|
|
1368
|
+
*
|
|
1369
|
+
* Unlike `parseInline`, this returns MDAST-aligned node names and fields. Math
|
|
1370
|
+
* nodes are present only when `math: true`, and plugin extension nodes are
|
|
1371
|
+
* inferred from the ordered `plugins` tuple.
|
|
1372
|
+
*/
|
|
1373
|
+
export function parseInlineAst(
|
|
1374
|
+
text: string,
|
|
1375
|
+
sourceIds?: ReadonlySet<string>,
|
|
1376
|
+
): MarkdownAstPhrasingContent[];
|
|
1377
|
+
export function parseInlineAst(
|
|
1378
|
+
text: string,
|
|
1379
|
+
options: ParseOptionsWithoutPlugins | MathParseOptionsWithoutPlugins,
|
|
1380
|
+
): MarkdownAstPhrasingContent[];
|
|
1381
|
+
export function parseInlineAst<
|
|
1382
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
|
|
1383
|
+
>(
|
|
1384
|
+
text: string,
|
|
1385
|
+
options: (ParseOptionsWithoutPlugins | MathParseOptionsWithoutPlugins) & {
|
|
1386
|
+
plugins: Plugins;
|
|
1387
|
+
},
|
|
1388
|
+
): MarkdownAstPhrasingContent<MarkdownExtensionsOf<Plugins>>[];
|
|
1389
|
+
export function parseInlineAst(
|
|
1390
|
+
text: string,
|
|
1391
|
+
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
1392
|
+
): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
|
|
1393
|
+
return parseInlineAstRuntime(text, arg);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function parseInlineAstRuntime(
|
|
1397
|
+
text: string,
|
|
1398
|
+
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
1399
|
+
): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
|
|
1400
|
+
const opts = resolveOptions(arg);
|
|
1401
|
+
const nodes = parseInlineEntry(text, opts);
|
|
1402
|
+
if ((opts.plugins?.transforms.length ?? 0) === 0) {
|
|
1403
|
+
return nodes;
|
|
1404
|
+
}
|
|
1405
|
+
const transformed = applyMarkdownTransforms(
|
|
1406
|
+
{type: 'root', children: [{type: 'paragraph', children: nodes}]},
|
|
1407
|
+
opts.plugins,
|
|
1408
|
+
text,
|
|
1409
|
+
opts.isFinal,
|
|
1410
|
+
'inline',
|
|
1411
|
+
);
|
|
1412
|
+
const paragraph = transformed.children[0];
|
|
1413
|
+
return paragraph?.type === 'paragraph' ? [...paragraph.children] : nodes;
|
|
795
1414
|
}
|
|
796
1415
|
|
|
797
1416
|
/**
|
|
@@ -806,7 +1425,7 @@ export function parseInline(
|
|
|
806
1425
|
function parseInlineEntry(
|
|
807
1426
|
text: string,
|
|
808
1427
|
opts: ResolvedOptions,
|
|
809
|
-
):
|
|
1428
|
+
): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
|
|
810
1429
|
const nodes = parseInlineImpl(text, opts);
|
|
811
1430
|
return opts.autolink === 'gfm' ? transformAutolinks(nodes) : nodes;
|
|
812
1431
|
}
|
|
@@ -814,14 +1433,22 @@ function parseInlineEntry(
|
|
|
814
1433
|
function parseInlineImpl(
|
|
815
1434
|
text: string,
|
|
816
1435
|
opts: ResolvedOptions,
|
|
817
|
-
):
|
|
818
|
-
const nodes:
|
|
1436
|
+
): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
|
|
1437
|
+
const nodes: MarkdownAstPhrasingContent<RuntimeExtensionNode>[] = [];
|
|
1438
|
+
// Only a plugin that actually contributes INLINE syntax may cost anything
|
|
1439
|
+
// per source position. A transform-only list contributes none, so it takes
|
|
1440
|
+
// the same path as an omitted or empty one: no candidate probe per
|
|
1441
|
+
// position, and no map lookup per character in the plain-text scan below.
|
|
1442
|
+
const inlineExtensionStarts =
|
|
1443
|
+
opts.plugins != null && opts.plugins.inlineByFirstCharacter.size > 0
|
|
1444
|
+
? opts.plugins.inlineByFirstCharacter
|
|
1445
|
+
: undefined;
|
|
819
1446
|
let i = 0;
|
|
820
1447
|
|
|
821
1448
|
while (i < text.length) {
|
|
822
1449
|
// --- Escape ---
|
|
823
1450
|
if (text[i] === '\\' && i + 1 < text.length) {
|
|
824
|
-
nodes.push({type: 'text',
|
|
1451
|
+
nodes.push({type: 'text', value: text[i + 1]});
|
|
825
1452
|
i += 2;
|
|
826
1453
|
continue;
|
|
827
1454
|
}
|
|
@@ -832,7 +1459,10 @@ function parseInlineImpl(
|
|
|
832
1459
|
const openIndex = i + tickCount;
|
|
833
1460
|
const closeIndex = text.indexOf('`'.repeat(tickCount), openIndex);
|
|
834
1461
|
if (closeIndex !== -1) {
|
|
835
|
-
nodes.push({
|
|
1462
|
+
nodes.push({
|
|
1463
|
+
type: 'inlineCode',
|
|
1464
|
+
value: text.slice(openIndex, closeIndex),
|
|
1465
|
+
});
|
|
836
1466
|
i = closeIndex + tickCount;
|
|
837
1467
|
continue;
|
|
838
1468
|
}
|
|
@@ -842,7 +1472,10 @@ function parseInlineImpl(
|
|
|
842
1472
|
if (opts.math && text[i] === '$') {
|
|
843
1473
|
const closeIndex = findInlineMathEnd(text, i);
|
|
844
1474
|
if (closeIndex !== -1) {
|
|
845
|
-
nodes.push({
|
|
1475
|
+
nodes.push({
|
|
1476
|
+
type: 'inlineMath',
|
|
1477
|
+
value: text.slice(i + 1, closeIndex),
|
|
1478
|
+
});
|
|
846
1479
|
i = closeIndex + 1;
|
|
847
1480
|
continue;
|
|
848
1481
|
}
|
|
@@ -865,13 +1498,13 @@ function parseInlineImpl(
|
|
|
865
1498
|
const srcClose = findClosingParen(text, altClose + 2);
|
|
866
1499
|
if (srcClose !== -1) {
|
|
867
1500
|
const src = text.slice(altClose + 2, srcClose);
|
|
868
|
-
if (!
|
|
1501
|
+
if (!isSafeMarkdownParserUrl(src)) {
|
|
869
1502
|
// Dangerous scheme — emit as plain text.
|
|
870
|
-
nodes.push({type: 'text',
|
|
1503
|
+
nodes.push({type: 'text', value: text.slice(i, srcClose + 1)});
|
|
871
1504
|
} else {
|
|
872
1505
|
nodes.push({
|
|
873
1506
|
type: 'image',
|
|
874
|
-
src,
|
|
1507
|
+
url: src,
|
|
875
1508
|
alt: text.slice(i + 2, altClose),
|
|
876
1509
|
});
|
|
877
1510
|
}
|
|
@@ -908,14 +1541,17 @@ function parseInlineImpl(
|
|
|
908
1541
|
const urlClose = findClosingParen(text, textClose + 2);
|
|
909
1542
|
if (urlClose !== -1) {
|
|
910
1543
|
const href = text.slice(textClose + 2, urlClose);
|
|
911
|
-
if (!
|
|
1544
|
+
if (!isSafeMarkdownParserUrl(href)) {
|
|
912
1545
|
// Dangerous scheme — emit as plain text instead of a link.
|
|
913
|
-
nodes.push({type: 'text',
|
|
1546
|
+
nodes.push({type: 'text', value: text.slice(i, urlClose + 1)});
|
|
914
1547
|
} else {
|
|
915
1548
|
nodes.push({
|
|
916
1549
|
type: 'link',
|
|
917
|
-
href,
|
|
918
|
-
children: parseInlineImpl(
|
|
1550
|
+
url: href,
|
|
1551
|
+
children: parseInlineImpl(
|
|
1552
|
+
text.slice(i + 1, textClose),
|
|
1553
|
+
protectedInlineOptions(opts),
|
|
1554
|
+
),
|
|
919
1555
|
});
|
|
920
1556
|
}
|
|
921
1557
|
i = urlClose + 1;
|
|
@@ -950,10 +1586,10 @@ function parseInlineImpl(
|
|
|
950
1586
|
(!isUnderscore || !isWordChar(text[closeIndex + 3]))
|
|
951
1587
|
) {
|
|
952
1588
|
nodes.push({
|
|
953
|
-
type: '
|
|
1589
|
+
type: 'strong',
|
|
954
1590
|
children: [
|
|
955
1591
|
{
|
|
956
|
-
type: '
|
|
1592
|
+
type: 'emphasis',
|
|
957
1593
|
children: parseInlineImpl(text.slice(i + 3, closeIndex), opts),
|
|
958
1594
|
},
|
|
959
1595
|
],
|
|
@@ -980,7 +1616,7 @@ function parseInlineImpl(
|
|
|
980
1616
|
(!isUnderscore || !isWordChar(text[closeIndex + 2]))
|
|
981
1617
|
) {
|
|
982
1618
|
nodes.push({
|
|
983
|
-
type: '
|
|
1619
|
+
type: 'strong',
|
|
984
1620
|
children: parseInlineImpl(text.slice(i + 2, closeIndex), opts),
|
|
985
1621
|
});
|
|
986
1622
|
i = closeIndex + 2;
|
|
@@ -994,7 +1630,7 @@ function parseInlineImpl(
|
|
|
994
1630
|
const closeIndex = text.indexOf('~~', i + 2);
|
|
995
1631
|
if (closeIndex !== -1) {
|
|
996
1632
|
nodes.push({
|
|
997
|
-
type: '
|
|
1633
|
+
type: 'delete',
|
|
998
1634
|
children: parseInlineImpl(text.slice(i + 2, closeIndex), opts),
|
|
999
1635
|
});
|
|
1000
1636
|
i = closeIndex + 2;
|
|
@@ -1015,7 +1651,7 @@ function parseInlineImpl(
|
|
|
1015
1651
|
(!isUnderscore || !isWordChar(text[closeIndex + 1]))
|
|
1016
1652
|
) {
|
|
1017
1653
|
nodes.push({
|
|
1018
|
-
type: '
|
|
1654
|
+
type: 'emphasis',
|
|
1019
1655
|
children: parseInlineImpl(text.slice(i + 1, closeIndex), opts),
|
|
1020
1656
|
});
|
|
1021
1657
|
i = closeIndex + 1;
|
|
@@ -1024,12 +1660,29 @@ function parseInlineImpl(
|
|
|
1024
1660
|
}
|
|
1025
1661
|
}
|
|
1026
1662
|
|
|
1663
|
+
// --- Extension syntax (built-ins and protected contexts win) ---
|
|
1664
|
+
if (inlineExtensionStarts !== undefined) {
|
|
1665
|
+
const extension = matchExtensionSyntax(text, i, 'inline', opts);
|
|
1666
|
+
if (extension.status === 'match') {
|
|
1667
|
+
nodes.push(
|
|
1668
|
+
extension.node as Extract<RuntimeExtensionNode, {display: 'inline'}>,
|
|
1669
|
+
);
|
|
1670
|
+
i = extension.end;
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1673
|
+
if (extension.status === 'defer') {
|
|
1674
|
+
break;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1027
1678
|
// --- Plain text (with line-break detection) ---
|
|
1028
1679
|
let end = i + 1;
|
|
1029
1680
|
while (
|
|
1030
1681
|
end < text.length &&
|
|
1031
1682
|
!'*_~`[!\\\n\u3010'.includes(text[end]) &&
|
|
1032
|
-
!(opts.math && text[end] === '$')
|
|
1683
|
+
!(opts.math && text[end] === '$') &&
|
|
1684
|
+
(inlineExtensionStarts === undefined ||
|
|
1685
|
+
!inlineExtensionStarts.has(text[end]))
|
|
1033
1686
|
) {
|
|
1034
1687
|
end++;
|
|
1035
1688
|
}
|
|
@@ -1043,9 +1696,12 @@ function parseInlineImpl(
|
|
|
1043
1696
|
if (trimmed.length > 0) {
|
|
1044
1697
|
const last = nodes[nodes.length - 1];
|
|
1045
1698
|
if (last?.type === 'text') {
|
|
1046
|
-
|
|
1699
|
+
nodes[nodes.length - 1] = {
|
|
1700
|
+
...last,
|
|
1701
|
+
value: last.value + trimmed,
|
|
1702
|
+
};
|
|
1047
1703
|
} else {
|
|
1048
|
-
nodes.push({type: 'text',
|
|
1704
|
+
nodes.push({type: 'text', value: trimmed});
|
|
1049
1705
|
}
|
|
1050
1706
|
}
|
|
1051
1707
|
nodes.push({type: 'break'});
|
|
@@ -1056,9 +1712,9 @@ function parseInlineImpl(
|
|
|
1056
1712
|
|
|
1057
1713
|
const last = nodes[nodes.length - 1];
|
|
1058
1714
|
if (last?.type === 'text') {
|
|
1059
|
-
last.
|
|
1715
|
+
nodes[nodes.length - 1] = {...last, value: last.value + content};
|
|
1060
1716
|
} else {
|
|
1061
|
-
nodes.push({type: 'text', content});
|
|
1717
|
+
nodes.push({type: 'text', value: content});
|
|
1062
1718
|
}
|
|
1063
1719
|
i = end;
|
|
1064
1720
|
}
|
|
@@ -1183,7 +1839,7 @@ function scanAutolinksInText(text: string): AutolinkMatch[] {
|
|
|
1183
1839
|
while ((m = re.exec(text)) !== null) {
|
|
1184
1840
|
const url = m[1];
|
|
1185
1841
|
// Skip dangerous URL schemes (javascript:, vbscript:, data:text/html)
|
|
1186
|
-
if (!
|
|
1842
|
+
if (!isSafeMarkdownParserUrl(url)) {
|
|
1187
1843
|
continue;
|
|
1188
1844
|
}
|
|
1189
1845
|
matches.push({
|
|
@@ -1303,26 +1959,28 @@ function scanAutolinksInText(text: string): AutolinkMatch[] {
|
|
|
1303
1959
|
* Split a text-node `content` string into a sequence of text + link nodes
|
|
1304
1960
|
* based on autolink matches.
|
|
1305
1961
|
*/
|
|
1306
|
-
function splitTextOnAutolinks(
|
|
1962
|
+
function splitTextOnAutolinks(
|
|
1963
|
+
content: string,
|
|
1964
|
+
): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
|
|
1307
1965
|
const matches = scanAutolinksInText(content);
|
|
1308
1966
|
if (matches.length === 0) {
|
|
1309
|
-
return [{type: 'text', content}];
|
|
1967
|
+
return [{type: 'text', value: content}];
|
|
1310
1968
|
}
|
|
1311
|
-
const out:
|
|
1969
|
+
const out: MarkdownAstPhrasingContent<RuntimeExtensionNode>[] = [];
|
|
1312
1970
|
let cursor = 0;
|
|
1313
1971
|
for (const m of matches) {
|
|
1314
1972
|
if (m.start > cursor) {
|
|
1315
|
-
out.push({type: 'text',
|
|
1973
|
+
out.push({type: 'text', value: content.slice(cursor, m.start)});
|
|
1316
1974
|
}
|
|
1317
1975
|
out.push({
|
|
1318
1976
|
type: 'link',
|
|
1319
|
-
|
|
1320
|
-
children: [{type: 'text',
|
|
1977
|
+
url: m.href,
|
|
1978
|
+
children: [{type: 'text', value: m.display}],
|
|
1321
1979
|
});
|
|
1322
1980
|
cursor = m.end;
|
|
1323
1981
|
}
|
|
1324
1982
|
if (cursor < content.length) {
|
|
1325
|
-
out.push({type: 'text',
|
|
1983
|
+
out.push({type: 'text', value: content.slice(cursor)});
|
|
1326
1984
|
}
|
|
1327
1985
|
return out;
|
|
1328
1986
|
}
|
|
@@ -1335,23 +1993,25 @@ function splitTextOnAutolinks(content: string): InlineNodeWithMath[] {
|
|
|
1335
1993
|
* `image` alt text, `citation`, or `break`. Runs only on the outermost
|
|
1336
1994
|
* block's inline tree (see `parseInlineEntry`).
|
|
1337
1995
|
*/
|
|
1338
|
-
function transformAutolinks(
|
|
1339
|
-
|
|
1996
|
+
function transformAutolinks(
|
|
1997
|
+
nodes: ReadonlyArray<MarkdownAstPhrasingContent<RuntimeExtensionNode>>,
|
|
1998
|
+
): MarkdownAstPhrasingContent<RuntimeExtensionNode>[] {
|
|
1999
|
+
const out: MarkdownAstPhrasingContent<RuntimeExtensionNode>[] = [];
|
|
1340
2000
|
for (const node of nodes) {
|
|
1341
2001
|
if (node.type === 'text') {
|
|
1342
|
-
const split = splitTextOnAutolinks(node.
|
|
2002
|
+
const split = splitTextOnAutolinks(node.value);
|
|
1343
2003
|
for (const seg of split) {
|
|
1344
2004
|
const last = out[out.length - 1];
|
|
1345
2005
|
if (seg.type === 'text' && last?.type === 'text') {
|
|
1346
|
-
last.
|
|
2006
|
+
out[out.length - 1] = {...last, value: last.value + seg.value};
|
|
1347
2007
|
} else {
|
|
1348
2008
|
out.push(seg);
|
|
1349
2009
|
}
|
|
1350
2010
|
}
|
|
1351
2011
|
} else if (
|
|
1352
|
-
node.type === '
|
|
1353
|
-
node.type === '
|
|
1354
|
-
node.type === '
|
|
2012
|
+
node.type === 'strong' ||
|
|
2013
|
+
node.type === 'emphasis' ||
|
|
2014
|
+
node.type === 'delete'
|
|
1355
2015
|
) {
|
|
1356
2016
|
out.push({...node, children: transformAutolinks(node.children)});
|
|
1357
2017
|
} else {
|
|
@@ -1434,7 +2094,16 @@ function isTableSeparator(line: string): boolean {
|
|
|
1434
2094
|
* offset into it would not address the document.
|
|
1435
2095
|
*/
|
|
1436
2096
|
function nested(opts: ResolvedOptions): ResolvedOptions {
|
|
1437
|
-
|
|
2097
|
+
const nestedOptions =
|
|
2098
|
+
opts.allowBlockSyntax === false ? opts : {...opts, allowBlockSyntax: false};
|
|
2099
|
+
return nestedOptions.sourceRanges || nestedOptions.astPositions
|
|
2100
|
+
? {...nestedOptions, sourceRanges: false, astPositions: false}
|
|
2101
|
+
: nestedOptions;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function blockExtensionColumn(line: string): number | null {
|
|
2105
|
+
const indentation = line.length - line.trimStart().length;
|
|
2106
|
+
return indentation <= 3 ? indentation : null;
|
|
1438
2107
|
}
|
|
1439
2108
|
|
|
1440
2109
|
/**
|
|
@@ -1510,10 +2179,10 @@ function parseTable(
|
|
|
1510
2179
|
lines: string[],
|
|
1511
2180
|
lineIndex: number,
|
|
1512
2181
|
opts: ResolvedOptions,
|
|
1513
|
-
): {node:
|
|
1514
|
-
const headers:
|
|
1515
|
-
|
|
1516
|
-
);
|
|
2182
|
+
): {node: MarkdownAstBlockContent<RuntimeExtensionNode>; nextIndex: number} {
|
|
2183
|
+
const headers: MarkdownAstTableCell<RuntimeExtensionNode>[] = splitTableRow(
|
|
2184
|
+
lines[lineIndex],
|
|
2185
|
+
).map(cell => ({type: 'tableCell', children: parseInlineEntry(cell, opts)}));
|
|
1517
2186
|
const alignments: TableAlignment[] = splitTableRow(lines[lineIndex + 1]).map(
|
|
1518
2187
|
cell => {
|
|
1519
2188
|
const trimmed = cell.trim();
|
|
@@ -1528,22 +2197,32 @@ function parseTable(
|
|
|
1528
2197
|
: null;
|
|
1529
2198
|
},
|
|
1530
2199
|
);
|
|
1531
|
-
const rows:
|
|
2200
|
+
const rows: MarkdownAstTableRow<RuntimeExtensionNode>[] = [];
|
|
1532
2201
|
let rowIndex = lineIndex + 2;
|
|
1533
2202
|
while (
|
|
1534
2203
|
rowIndex < lines.length &&
|
|
1535
2204
|
lines[rowIndex].includes('|') &&
|
|
1536
2205
|
lines[rowIndex].trim() !== ''
|
|
1537
2206
|
) {
|
|
1538
|
-
rows.push(
|
|
1539
|
-
|
|
2207
|
+
rows.push({
|
|
2208
|
+
type: 'tableRow',
|
|
2209
|
+
children: splitTableRow(lines[rowIndex]).map(cell => ({
|
|
2210
|
+
type: 'tableCell',
|
|
1540
2211
|
children: parseInlineEntry(cell, opts),
|
|
1541
2212
|
})),
|
|
1542
|
-
);
|
|
2213
|
+
});
|
|
1543
2214
|
rowIndex++;
|
|
1544
2215
|
}
|
|
2216
|
+
const header: MarkdownAstTableRow<RuntimeExtensionNode> = {
|
|
2217
|
+
type: 'tableRow',
|
|
2218
|
+
children: headers,
|
|
2219
|
+
};
|
|
1545
2220
|
return {
|
|
1546
|
-
node: {
|
|
2221
|
+
node: {
|
|
2222
|
+
type: 'table',
|
|
2223
|
+
align: alignments,
|
|
2224
|
+
children: [header, ...rows],
|
|
2225
|
+
},
|
|
1547
2226
|
nextIndex: rowIndex,
|
|
1548
2227
|
};
|
|
1549
2228
|
}
|
|
@@ -1553,8 +2232,8 @@ function parseList(
|
|
|
1553
2232
|
startIndex: number,
|
|
1554
2233
|
ordered: boolean,
|
|
1555
2234
|
opts: ResolvedOptions,
|
|
1556
|
-
): {node:
|
|
1557
|
-
const items:
|
|
2235
|
+
): {node: MarkdownAstBlockContent<RuntimeExtensionNode>; nextIndex: number} {
|
|
2236
|
+
const items: MarkdownAstListItem<RuntimeExtensionNode>[] = [];
|
|
1558
2237
|
const baseIndent = getIndent(lines[startIndex]);
|
|
1559
2238
|
// Ordered lists may use either '.' or ')' as the marker delimiter
|
|
1560
2239
|
// (CommonMark 5.2). Capture which one this list starts with so its items
|
|
@@ -1608,7 +2287,11 @@ function parseList(
|
|
|
1608
2287
|
itemText += '\n' + deindented.join('\n');
|
|
1609
2288
|
}
|
|
1610
2289
|
|
|
1611
|
-
items.push({
|
|
2290
|
+
items.push({
|
|
2291
|
+
type: 'listItem',
|
|
2292
|
+
checked,
|
|
2293
|
+
children: parseMarkdownImpl(itemText, nested(opts)),
|
|
2294
|
+
});
|
|
1612
2295
|
|
|
1613
2296
|
// CommonMark loose list: blank line(s) between items of the same style
|
|
1614
2297
|
// and indent still form one list. Skip the blanks and continue if the
|
|
@@ -1626,17 +2309,15 @@ function parseList(
|
|
|
1626
2309
|
index = lookahead;
|
|
1627
2310
|
}
|
|
1628
2311
|
}
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
items,
|
|
1637
|
-
},
|
|
1638
|
-
nextIndex: index,
|
|
2312
|
+
const node: MarkdownAstList<RuntimeExtensionNode> = {
|
|
2313
|
+
type: 'list',
|
|
2314
|
+
ordered,
|
|
2315
|
+
start,
|
|
2316
|
+
delimiter: ordered ? (delim as '.' | ')') : undefined,
|
|
2317
|
+
spread: loose || undefined,
|
|
2318
|
+
children: items,
|
|
1639
2319
|
};
|
|
2320
|
+
return {node, nextIndex: index};
|
|
1640
2321
|
}
|
|
1641
2322
|
|
|
1642
2323
|
// ---------------------------------------------------------------------------
|
|
@@ -1649,23 +2330,89 @@ export function parseMarkdown(
|
|
|
1649
2330
|
): BlockNode[];
|
|
1650
2331
|
export function parseMarkdown(
|
|
1651
2332
|
input: string,
|
|
1652
|
-
options:
|
|
2333
|
+
options: MathParseOptionsWithoutPlugins,
|
|
1653
2334
|
): BlockNodeWithMath[];
|
|
1654
2335
|
export function parseMarkdown(
|
|
1655
2336
|
input: string,
|
|
1656
|
-
options:
|
|
2337
|
+
options: ParseOptionsWithoutPlugins,
|
|
1657
2338
|
): BlockNode[];
|
|
2339
|
+
export function parseMarkdown<
|
|
2340
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
|
|
2341
|
+
>(
|
|
2342
|
+
input: string,
|
|
2343
|
+
options: ParseOptionsWithoutPlugins & {plugins: Plugins},
|
|
2344
|
+
): BlockNode<MarkdownExtensionsOf<Plugins>>[];
|
|
2345
|
+
export function parseMarkdown<
|
|
2346
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
|
|
2347
|
+
>(
|
|
2348
|
+
input: string,
|
|
2349
|
+
options: MathParseOptionsWithoutPlugins & {plugins: Plugins},
|
|
2350
|
+
): BlockNodeWithMath<MarkdownExtensionsOf<Plugins>>[];
|
|
1658
2351
|
export function parseMarkdown(
|
|
1659
2352
|
input: string,
|
|
1660
2353
|
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
1661
|
-
):
|
|
1662
|
-
return
|
|
2354
|
+
): RuntimeBlockNode[] {
|
|
2355
|
+
return projectMarkdownRoot(
|
|
2356
|
+
parseMarkdownAstInternal(input, arg, true),
|
|
2357
|
+
wantsLegacyRanges(arg),
|
|
2358
|
+
);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
/**
|
|
2362
|
+
* Parse block Markdown into the canonical immutable Astryx AST.
|
|
2363
|
+
*
|
|
2364
|
+
* Unlike `parseMarkdown`, this returns the MDAST-aligned root used by Markdown,
|
|
2365
|
+
* transforms, and Outline. Math nodes are present only when `math: true`, and
|
|
2366
|
+
* plugin extension nodes are inferred from the ordered `plugins` tuple.
|
|
2367
|
+
*/
|
|
2368
|
+
export function parseMarkdownAst(
|
|
2369
|
+
input: string,
|
|
2370
|
+
sourceIds?: ReadonlySet<string>,
|
|
2371
|
+
): MarkdownAstRoot;
|
|
2372
|
+
export function parseMarkdownAst(
|
|
2373
|
+
input: string,
|
|
2374
|
+
options: ParseOptionsWithoutPlugins | MathParseOptionsWithoutPlugins,
|
|
2375
|
+
): MarkdownAstRoot;
|
|
2376
|
+
export function parseMarkdownAst<
|
|
2377
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
|
|
2378
|
+
>(
|
|
2379
|
+
input: string,
|
|
2380
|
+
options: (ParseOptionsWithoutPlugins | MathParseOptionsWithoutPlugins) & {
|
|
2381
|
+
plugins: Plugins;
|
|
2382
|
+
},
|
|
2383
|
+
): MarkdownAstRoot<MarkdownExtensionsOf<Plugins>>;
|
|
2384
|
+
export function parseMarkdownAst(
|
|
2385
|
+
input: string,
|
|
2386
|
+
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
2387
|
+
): MarkdownAstRoot<RuntimeExtensionNode> {
|
|
2388
|
+
return parseMarkdownAstInternal(input, arg, true);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
/** @internal Full-parse entry point with streaming finality for Outline. */
|
|
2392
|
+
export function parseMarkdownAstInternal(
|
|
2393
|
+
input: string,
|
|
2394
|
+
arg: ReadonlySet<string> | RuntimeParseOptions | undefined,
|
|
2395
|
+
isFinal: boolean,
|
|
2396
|
+
): MarkdownAstRoot<RuntimeExtensionNode> {
|
|
2397
|
+
const resolved = resolveOptions(arg);
|
|
2398
|
+
const opts = isFinal ? resolved : {...resolved, isFinal: false};
|
|
2399
|
+
const root: MarkdownAstRoot<RuntimeExtensionNode> = {
|
|
2400
|
+
type: 'root',
|
|
2401
|
+
children: parseMarkdownImpl(input, opts),
|
|
2402
|
+
};
|
|
2403
|
+
return applyMarkdownTransforms(
|
|
2404
|
+
root,
|
|
2405
|
+
opts.plugins,
|
|
2406
|
+
input,
|
|
2407
|
+
opts.isFinal,
|
|
2408
|
+
'block',
|
|
2409
|
+
);
|
|
1663
2410
|
}
|
|
1664
2411
|
|
|
1665
2412
|
function parseMarkdownImpl(
|
|
1666
2413
|
input: string,
|
|
1667
2414
|
baseOpts: ResolvedOptions,
|
|
1668
|
-
):
|
|
2415
|
+
): MarkdownAstBlockContent<RuntimeExtensionNode>[] {
|
|
1669
2416
|
// Collect this input's link reference definitions and strip their lines,
|
|
1670
2417
|
// then merge them with any definitions inherited from an enclosing parse
|
|
1671
2418
|
// (the incremental parser passes the whole document's definitions in; a
|
|
@@ -1686,18 +2433,35 @@ function parseMarkdownImpl(
|
|
|
1686
2433
|
const opts: ResolvedOptions =
|
|
1687
2434
|
linkDefs != null ? {...baseOpts, linkDefs} : baseOpts;
|
|
1688
2435
|
const lines = cleaned.split('\n');
|
|
1689
|
-
const
|
|
2436
|
+
const hasBlockExtensionSyntax =
|
|
2437
|
+
opts.allowBlockSyntax !== false &&
|
|
2438
|
+
(opts.plugins?.blockByFirstCharacter.size ?? 0) > 0;
|
|
2439
|
+
// Derived from the already-split lines rather than a second character
|
|
2440
|
+
// scan: every line contributes its own length plus the newline it ended on.
|
|
2441
|
+
const lineOffsets = [0];
|
|
2442
|
+
if (opts.astPositions === true || hasBlockExtensionSyntax) {
|
|
2443
|
+
let offset = 0;
|
|
2444
|
+
for (let index = 0; index < lines.length; index++) {
|
|
2445
|
+
offset += lines[index].length + 1;
|
|
2446
|
+
lineOffsets.push(offset);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
const blockExtensionMatches = new Map<number, ExtensionMatch>();
|
|
2450
|
+
const blocks: MarkdownAstBlockContent<RuntimeExtensionNode>[] = [];
|
|
1690
2451
|
// The line each block started on, parallel to `blocks`. Only collected when
|
|
1691
2452
|
// ranges were asked for; a block's end is resolved after the loop, since the
|
|
1692
2453
|
// branch that produced it has already moved `index` past whatever it read.
|
|
1693
|
-
const blockStartLines: number[] | null =
|
|
2454
|
+
const blockStartLines: number[] | null =
|
|
2455
|
+
opts.astPositions === true ? [] : null;
|
|
1694
2456
|
// Set only by a block that consumes blank lines as content, where the
|
|
1695
2457
|
// positional end derivation would trim them away.
|
|
1696
|
-
const blockEndLines: (number | undefined)[] | null =
|
|
1697
|
-
? []
|
|
1698
|
-
: null;
|
|
2458
|
+
const blockEndLines: (number | undefined)[] | null =
|
|
2459
|
+
opts.astPositions === true ? [] : null;
|
|
1699
2460
|
let blockStartLine = 0;
|
|
1700
|
-
const pushBlock = (
|
|
2461
|
+
const pushBlock = (
|
|
2462
|
+
node: MarkdownAstBlockContent<RuntimeExtensionNode>,
|
|
2463
|
+
endLine?: number,
|
|
2464
|
+
) => {
|
|
1701
2465
|
blocks.push(node);
|
|
1702
2466
|
blockStartLines?.push(blockStartLine);
|
|
1703
2467
|
blockEndLines?.push(endLine);
|
|
@@ -1713,10 +2477,16 @@ function parseMarkdownImpl(
|
|
|
1713
2477
|
}
|
|
1714
2478
|
|
|
1715
2479
|
// --- Fenced code block ---
|
|
1716
|
-
const fenceMatch = line.match(/^(`{3,}|~{3,})
|
|
2480
|
+
const fenceMatch = line.match(/^(`{3,}|~{3,})/);
|
|
1717
2481
|
if (fenceMatch) {
|
|
1718
2482
|
const fence = fenceMatch[1];
|
|
1719
|
-
const
|
|
2483
|
+
const info = line.slice(fence.length);
|
|
2484
|
+
const language = info.match(/^(\S+)/)?.[1] ?? null;
|
|
2485
|
+
const legacyLanguage = info.match(/^(\w*)/)?.[1] || null;
|
|
2486
|
+
const meta =
|
|
2487
|
+
language == null
|
|
2488
|
+
? undefined
|
|
2489
|
+
: info.slice(language.length).trim() || undefined;
|
|
1720
2490
|
const codeLines: string[] = [];
|
|
1721
2491
|
index++;
|
|
1722
2492
|
while (index < lines.length && !lines[index].startsWith(fence)) {
|
|
@@ -1728,7 +2498,15 @@ function parseMarkdownImpl(
|
|
|
1728
2498
|
// can end on them, so it states its own end rather than letting the
|
|
1729
2499
|
// positional derivation trim them off.
|
|
1730
2500
|
pushBlock(
|
|
1731
|
-
|
|
2501
|
+
markMarkdownAstLegacyCodeLanguage(
|
|
2502
|
+
{
|
|
2503
|
+
type: 'code',
|
|
2504
|
+
lang: language,
|
|
2505
|
+
...(meta == null ? {} : {meta}),
|
|
2506
|
+
value: codeLines.join('\n'),
|
|
2507
|
+
},
|
|
2508
|
+
legacyLanguage,
|
|
2509
|
+
),
|
|
1732
2510
|
Math.min(index, lines.length) - 1,
|
|
1733
2511
|
);
|
|
1734
2512
|
continue;
|
|
@@ -1752,7 +2530,7 @@ function parseMarkdownImpl(
|
|
|
1752
2530
|
if (headingMatch) {
|
|
1753
2531
|
pushBlock({
|
|
1754
2532
|
type: 'heading',
|
|
1755
|
-
|
|
2533
|
+
depth: headingMatch[1].length as 1 | 2 | 3 | 4 | 5 | 6,
|
|
1756
2534
|
children: parseInlineEntry(headingMatch[2], opts),
|
|
1757
2535
|
});
|
|
1758
2536
|
index++;
|
|
@@ -1761,7 +2539,7 @@ function parseMarkdownImpl(
|
|
|
1761
2539
|
|
|
1762
2540
|
// --- HR (must precede list check to handle `- - -`, `* * *`, `_ _ _`) ---
|
|
1763
2541
|
if (isHorizontalRule(line)) {
|
|
1764
|
-
pushBlock({type: '
|
|
2542
|
+
pushBlock({type: 'thematicBreak'});
|
|
1765
2543
|
index++;
|
|
1766
2544
|
continue;
|
|
1767
2545
|
}
|
|
@@ -1773,9 +2551,9 @@ function parseMarkdownImpl(
|
|
|
1773
2551
|
if (
|
|
1774
2552
|
imageMatch &&
|
|
1775
2553
|
line.trim() === imageMatch[0] &&
|
|
1776
|
-
|
|
2554
|
+
isSafeMarkdownParserUrl(imageMatch[2])
|
|
1777
2555
|
) {
|
|
1778
|
-
pushBlock({type: 'image', alt: imageMatch[1],
|
|
2556
|
+
pushBlock({type: 'image', alt: imageMatch[1], url: imageMatch[2]});
|
|
1779
2557
|
index++;
|
|
1780
2558
|
continue;
|
|
1781
2559
|
}
|
|
@@ -1825,16 +2603,80 @@ function parseMarkdownImpl(
|
|
|
1825
2603
|
continue;
|
|
1826
2604
|
}
|
|
1827
2605
|
|
|
2606
|
+
// --- Extension block syntax (built-in blocks take precedence) ---
|
|
2607
|
+
if (hasBlockExtensionSyntax) {
|
|
2608
|
+
const extensionColumn = blockExtensionColumn(line);
|
|
2609
|
+
const extensionOffset =
|
|
2610
|
+
extensionColumn == null
|
|
2611
|
+
? lineOffsets[index]
|
|
2612
|
+
: lineOffsets[index] + extensionColumn;
|
|
2613
|
+
const extension =
|
|
2614
|
+
blockExtensionMatches.get(extensionOffset) ??
|
|
2615
|
+
(extensionColumn == null
|
|
2616
|
+
? {status: 'none' as const}
|
|
2617
|
+
: matchExtensionSyntax(cleaned, extensionOffset, 'block', opts));
|
|
2618
|
+
blockExtensionMatches.delete(extensionOffset);
|
|
2619
|
+
if (extension.status === 'match') {
|
|
2620
|
+
const consumedEnd = extension.end;
|
|
2621
|
+
const consumed = cleaned.slice(lineOffsets[index], consumedEnd);
|
|
2622
|
+
if (
|
|
2623
|
+
consumedEnd < cleaned.length &&
|
|
2624
|
+
cleaned[consumedEnd] !== '\n' &&
|
|
2625
|
+
cleaned[consumedEnd - 1] !== '\n'
|
|
2626
|
+
) {
|
|
2627
|
+
reportMarkdownPluginFailure(
|
|
2628
|
+
extension.node.plugin,
|
|
2629
|
+
'syntax',
|
|
2630
|
+
new TypeError('Block syntax must consume complete lines'),
|
|
2631
|
+
);
|
|
2632
|
+
} else {
|
|
2633
|
+
const newlineCount = consumed.split('\n').length - 1;
|
|
2634
|
+
const consumedLines = Math.max(
|
|
2635
|
+
1,
|
|
2636
|
+
newlineCount + (consumed.endsWith('\n') ? 0 : 1),
|
|
2637
|
+
);
|
|
2638
|
+
pushBlock(
|
|
2639
|
+
extension.node as Extract<RuntimeExtensionNode, {display: 'block'}>,
|
|
2640
|
+
index + consumedLines - 1,
|
|
2641
|
+
);
|
|
2642
|
+
index += consumedLines;
|
|
2643
|
+
continue;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
if (extension.status === 'defer') {
|
|
2647
|
+
break;
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
|
|
1828
2651
|
// --- Paragraph ---
|
|
1829
2652
|
const paraLines: string[] = [line];
|
|
1830
2653
|
index++;
|
|
1831
|
-
while (
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2654
|
+
while (index < lines.length) {
|
|
2655
|
+
const nextLine = lines[index];
|
|
2656
|
+
if (
|
|
2657
|
+
isBlockStart(nextLine) ||
|
|
2658
|
+
(opts.math && matchDisplayMathBlock(lines, index) != null) ||
|
|
2659
|
+
nextLine.trim() === ''
|
|
2660
|
+
) {
|
|
2661
|
+
break;
|
|
2662
|
+
}
|
|
2663
|
+
if (hasBlockExtensionSyntax) {
|
|
2664
|
+
const extensionColumn = blockExtensionColumn(nextLine);
|
|
2665
|
+
if (extensionColumn != null) {
|
|
2666
|
+
const extensionOffset = lineOffsets[index] + extensionColumn;
|
|
2667
|
+
const extension = matchExtensionSyntax(
|
|
2668
|
+
cleaned,
|
|
2669
|
+
extensionOffset,
|
|
2670
|
+
'block',
|
|
2671
|
+
opts,
|
|
2672
|
+
);
|
|
2673
|
+
if (extension.status !== 'none') {
|
|
2674
|
+
blockExtensionMatches.set(extensionOffset, extension);
|
|
2675
|
+
break;
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
paraLines.push(nextLine);
|
|
1838
2680
|
index++;
|
|
1839
2681
|
}
|
|
1840
2682
|
pushBlock({
|
|
@@ -1866,7 +2708,7 @@ function parseMarkdownImpl(
|
|
|
1866
2708
|
* `lineMap` says which input line each surviving line came from.
|
|
1867
2709
|
*/
|
|
1868
2710
|
function stampSourceRanges(
|
|
1869
|
-
blocks:
|
|
2711
|
+
blocks: MarkdownAstBlockContent<RuntimeExtensionNode>[],
|
|
1870
2712
|
blockStartLines: number[],
|
|
1871
2713
|
blockEndLines: (number | undefined)[],
|
|
1872
2714
|
lines: string[],
|
|
@@ -1903,7 +2745,13 @@ function stampSourceRanges(
|
|
|
1903
2745
|
// range that dropped it would slice to something that re-parses
|
|
1904
2746
|
// differently.
|
|
1905
2747
|
const end = lineStart(endLine) + lines[endLine].length;
|
|
1906
|
-
blocks[i] = {
|
|
2748
|
+
blocks[i] = {
|
|
2749
|
+
...blocks[i],
|
|
2750
|
+
position: {
|
|
2751
|
+
start: {offset: lineStart(startLine)},
|
|
2752
|
+
end: {offset: end},
|
|
2753
|
+
},
|
|
2754
|
+
};
|
|
1907
2755
|
}
|
|
1908
2756
|
}
|
|
1909
2757
|
|
|
@@ -1938,6 +2786,10 @@ export interface IncrementalState<MathEnabled extends boolean = false> {
|
|
|
1938
2786
|
* lack the ranges the caller now asks for, or carry ones it did not.
|
|
1939
2787
|
*/
|
|
1940
2788
|
sourceRanges?: boolean;
|
|
2789
|
+
/** Identity of citation sources used by settled nodes. */
|
|
2790
|
+
sourceIdsKey?: string;
|
|
2791
|
+
/** Ordered syntax-only identity used by settled nodes. */
|
|
2792
|
+
pluginSyntaxIdentity?: string;
|
|
1941
2793
|
/**
|
|
1942
2794
|
* Signature of the link reference definitions the cached `settledBlocks`
|
|
1943
2795
|
* were parsed with. Definitions are document-global and typically arrive
|
|
@@ -1968,6 +2820,13 @@ type IncrementalCache = {
|
|
|
1968
2820
|
/** The effective document-global definitions used by slice parses. */
|
|
1969
2821
|
linkDefs: ReadonlyMap<string, string>;
|
|
1970
2822
|
linkDefsKey: string;
|
|
2823
|
+
/** Canonical settled blocks shared by rendering and compatibility projection. */
|
|
2824
|
+
settledAstBlocks: MarkdownAstBlockContent<RuntimeExtensionNode>[];
|
|
2825
|
+
settledRevision: number;
|
|
2826
|
+
projectedRevision: number;
|
|
2827
|
+
projectedSettledBlocks: RuntimeBlockNode[];
|
|
2828
|
+
/** Preserves released settled-node identity across projected snapshots. */
|
|
2829
|
+
projectionCache: LegacyProjectionCache;
|
|
1971
2830
|
work: IncrementalWork;
|
|
1972
2831
|
};
|
|
1973
2832
|
|
|
@@ -1986,6 +2845,11 @@ function makeIncrementalCache(
|
|
|
1986
2845
|
tailLinkDefs: new Map(),
|
|
1987
2846
|
linkDefs: defs,
|
|
1988
2847
|
linkDefsKey: linkDefsSignature(defs),
|
|
2848
|
+
settledAstBlocks: [],
|
|
2849
|
+
settledRevision: 0,
|
|
2850
|
+
projectedRevision: -1,
|
|
2851
|
+
projectedSettledBlocks: [],
|
|
2852
|
+
projectionCache: new WeakMap(),
|
|
1989
2853
|
work: {
|
|
1990
2854
|
splitCharacters: 0,
|
|
1991
2855
|
boundaryLines: 0,
|
|
@@ -2470,7 +3334,7 @@ function trimUnsettledStructural(text: string): string {
|
|
|
2470
3334
|
* rather than into the slice.
|
|
2471
3335
|
*/
|
|
2472
3336
|
function atOffset(opts: ResolvedOptions, offset: number): ResolvedOptions {
|
|
2473
|
-
return opts.
|
|
3337
|
+
return opts.astPositions === true ? {...opts, baseOffset: offset} : opts;
|
|
2474
3338
|
}
|
|
2475
3339
|
|
|
2476
3340
|
/**
|
|
@@ -2481,9 +3345,9 @@ function atOffset(opts: ResolvedOptions, offset: number): ResolvedOptions {
|
|
|
2481
3345
|
* lists even though the full-text parser joins them per CommonMark §5.3.
|
|
2482
3346
|
*/
|
|
2483
3347
|
function mergeSettledBlocks(
|
|
2484
|
-
prev:
|
|
2485
|
-
delta:
|
|
2486
|
-
):
|
|
3348
|
+
prev: MarkdownAstBlockContent<RuntimeExtensionNode>[],
|
|
3349
|
+
delta: MarkdownAstBlockContent<RuntimeExtensionNode>[],
|
|
3350
|
+
): MarkdownAstBlockContent<RuntimeExtensionNode>[] {
|
|
2487
3351
|
if (prev.length === 0 || delta.length === 0) {
|
|
2488
3352
|
return [...prev, ...delta];
|
|
2489
3353
|
}
|
|
@@ -2495,17 +3359,21 @@ function mergeSettledBlocks(
|
|
|
2495
3359
|
prevLast.ordered === deltaFirst.ordered &&
|
|
2496
3360
|
prevLast.delimiter === deltaFirst.delimiter
|
|
2497
3361
|
) {
|
|
2498
|
-
const merged:
|
|
3362
|
+
const merged: MarkdownAstBlockContent<RuntimeExtensionNode> = {
|
|
2499
3363
|
type: 'list',
|
|
2500
3364
|
ordered: prevLast.ordered,
|
|
2501
3365
|
start: prevLast.start,
|
|
2502
3366
|
delimiter: prevLast.delimiter,
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
// One list now, so one
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
3367
|
+
spread: true,
|
|
3368
|
+
children: [...prevLast.children, ...deltaFirst.children],
|
|
3369
|
+
// One list now, so one position spans both halves.
|
|
3370
|
+
...(prevLast.position != null && deltaFirst.position != null
|
|
3371
|
+
? {
|
|
3372
|
+
position: {
|
|
3373
|
+
start: prevLast.position.start,
|
|
3374
|
+
end: deltaFirst.position.end,
|
|
3375
|
+
},
|
|
3376
|
+
}
|
|
2509
3377
|
: null),
|
|
2510
3378
|
};
|
|
2511
3379
|
return [...prev.slice(0, -1), merged, ...delta.slice(1)];
|
|
@@ -2515,15 +3383,15 @@ function mergeSettledBlocks(
|
|
|
2515
3383
|
|
|
2516
3384
|
/** Append a newly-settled slice without copying the already-settled prefix. */
|
|
2517
3385
|
function appendSettledBlocks(
|
|
2518
|
-
prev:
|
|
2519
|
-
delta:
|
|
2520
|
-
):
|
|
3386
|
+
prev: MarkdownAstBlockContent<RuntimeExtensionNode>[],
|
|
3387
|
+
delta: MarkdownAstBlockContent<RuntimeExtensionNode>[],
|
|
3388
|
+
): boolean {
|
|
2521
3389
|
if (delta.length === 0) {
|
|
2522
|
-
return;
|
|
3390
|
+
return false;
|
|
2523
3391
|
}
|
|
2524
3392
|
if (prev.length === 0) {
|
|
2525
3393
|
prev.push(...delta);
|
|
2526
|
-
return;
|
|
3394
|
+
return false;
|
|
2527
3395
|
}
|
|
2528
3396
|
const prevLast = prev[prev.length - 1];
|
|
2529
3397
|
const deltaFirst = delta[0];
|
|
@@ -2538,16 +3406,22 @@ function appendSettledBlocks(
|
|
|
2538
3406
|
ordered: prevLast.ordered,
|
|
2539
3407
|
start: prevLast.start,
|
|
2540
3408
|
delimiter: prevLast.delimiter,
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
...(prevLast.
|
|
2544
|
-
? {
|
|
3409
|
+
spread: true,
|
|
3410
|
+
children: [...prevLast.children, ...deltaFirst.children],
|
|
3411
|
+
...(prevLast.position != null && deltaFirst.position != null
|
|
3412
|
+
? {
|
|
3413
|
+
position: {
|
|
3414
|
+
start: prevLast.position.start,
|
|
3415
|
+
end: deltaFirst.position.end,
|
|
3416
|
+
},
|
|
3417
|
+
}
|
|
2545
3418
|
: null),
|
|
2546
3419
|
};
|
|
2547
3420
|
prev.push(...delta.slice(1));
|
|
2548
|
-
return;
|
|
3421
|
+
return true;
|
|
2549
3422
|
}
|
|
2550
3423
|
prev.push(...delta);
|
|
3424
|
+
return false;
|
|
2551
3425
|
}
|
|
2552
3426
|
|
|
2553
3427
|
function sameUnsettledDefinitions(
|
|
@@ -2577,12 +3451,19 @@ function resetIncrementalCache(
|
|
|
2577
3451
|
state.settledBlocks = [];
|
|
2578
3452
|
state.settledUpTo = 0;
|
|
2579
3453
|
state.linkDefsKey = undefined;
|
|
3454
|
+
state.sourceIdsKey = undefined;
|
|
3455
|
+
state.pluginSyntaxIdentity = undefined;
|
|
2580
3456
|
state.math = undefined;
|
|
2581
3457
|
cache.settledEnd = 0;
|
|
2582
3458
|
cache.settledLinkDefs.clear();
|
|
2583
3459
|
cache.tailLinkDefs = new Map();
|
|
2584
3460
|
cache.linkDefs = new Map();
|
|
2585
3461
|
cache.linkDefsKey = '';
|
|
3462
|
+
cache.settledAstBlocks = [];
|
|
3463
|
+
cache.settledRevision++;
|
|
3464
|
+
cache.projectedRevision = -1;
|
|
3465
|
+
cache.projectedSettledBlocks = [];
|
|
3466
|
+
cache.projectionCache = new WeakMap();
|
|
2586
3467
|
cache.work = {
|
|
2587
3468
|
splitCharacters: 0,
|
|
2588
3469
|
boundaryLines: 0,
|
|
@@ -2610,40 +3491,102 @@ export function parseMarkdownIncremental(
|
|
|
2610
3491
|
export function parseMarkdownIncremental(
|
|
2611
3492
|
input: string,
|
|
2612
3493
|
state: IncrementalState<true>,
|
|
2613
|
-
options:
|
|
3494
|
+
options: IncrementalMathParseOptionsWithoutPlugins,
|
|
2614
3495
|
): BlockNodeWithMath[];
|
|
2615
3496
|
export function parseMarkdownIncremental(
|
|
2616
3497
|
input: string,
|
|
2617
3498
|
state: IncrementalState<false>,
|
|
2618
|
-
options:
|
|
3499
|
+
options: IncrementalParseOptionsWithoutPlugins,
|
|
2619
3500
|
): BlockNode[];
|
|
3501
|
+
export function parseMarkdownIncremental<
|
|
3502
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
|
|
3503
|
+
>(
|
|
3504
|
+
input: string,
|
|
3505
|
+
state: IncrementalState<false>,
|
|
3506
|
+
options: IncrementalParseOptionsWithoutPlugins & {plugins: Plugins},
|
|
3507
|
+
): BlockNode<MarkdownExtensionsOf<Plugins>>[];
|
|
3508
|
+
export function parseMarkdownIncremental<
|
|
3509
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry>,
|
|
3510
|
+
>(
|
|
3511
|
+
input: string,
|
|
3512
|
+
state: IncrementalState<true>,
|
|
3513
|
+
options: IncrementalMathParseOptionsWithoutPlugins & {plugins: Plugins},
|
|
3514
|
+
): BlockNodeWithMath<MarkdownExtensionsOf<Plugins>>[];
|
|
2620
3515
|
export function parseMarkdownIncremental(
|
|
2621
3516
|
input: string,
|
|
2622
3517
|
state: IncrementalState<boolean>,
|
|
2623
3518
|
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
2624
|
-
):
|
|
2625
|
-
const
|
|
3519
|
+
): RuntimeBlockNode[] {
|
|
3520
|
+
const withRanges = wantsLegacyRanges(arg);
|
|
3521
|
+
const root = parseMarkdownAstIncremental(input, state, arg);
|
|
3522
|
+
const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
|
|
3523
|
+
const projected = projectMarkdownRoot(
|
|
3524
|
+
root,
|
|
3525
|
+
withRanges,
|
|
3526
|
+
cache.projectionCache,
|
|
3527
|
+
);
|
|
3528
|
+
if (cache.projectedRevision !== cache.settledRevision) {
|
|
3529
|
+
cache.projectedSettledBlocks = cache.settledAstBlocks.map(block =>
|
|
3530
|
+
projectBlockNode(block, cache.projectionCache, withRanges),
|
|
3531
|
+
);
|
|
3532
|
+
cache.projectedRevision = cache.settledRevision;
|
|
3533
|
+
}
|
|
3534
|
+
state.settledBlocks =
|
|
3535
|
+
cache.projectedSettledBlocks as typeof state.settledBlocks;
|
|
3536
|
+
return projected;
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
/** @internal Canonical incremental parse used by Markdown rendering. */
|
|
3540
|
+
export function parseMarkdownAstIncremental(
|
|
3541
|
+
input: string,
|
|
3542
|
+
state: IncrementalState<boolean>,
|
|
3543
|
+
arg?: ReadonlySet<string> | RuntimeParseOptions,
|
|
3544
|
+
): MarkdownAstRoot<RuntimeExtensionNode> {
|
|
3545
|
+
const opts = resolveOptions(arg, true);
|
|
3546
|
+
const root: MarkdownAstRoot<RuntimeExtensionNode> = {
|
|
3547
|
+
type: 'root',
|
|
3548
|
+
children: parseMarkdownIncrementalAstBlocks(input, state, opts),
|
|
3549
|
+
};
|
|
3550
|
+
return applyMarkdownTransforms(
|
|
3551
|
+
root,
|
|
3552
|
+
opts.plugins,
|
|
3553
|
+
input,
|
|
3554
|
+
opts.isFinal,
|
|
3555
|
+
'block',
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
function parseMarkdownIncrementalAstBlocks(
|
|
3560
|
+
input: string,
|
|
3561
|
+
state: IncrementalState<boolean>,
|
|
3562
|
+
opts: ResolvedOptions,
|
|
3563
|
+
): MarkdownAstBlockContent<RuntimeExtensionNode>[] {
|
|
2626
3564
|
const cache = incrementalCaches.get(state) ?? makeIncrementalCache(state);
|
|
2627
3565
|
let reparseSettled = false;
|
|
2628
3566
|
|
|
3567
|
+
const nextSourceIdsKey = sourceIdsSignature(opts.sourceIds);
|
|
3568
|
+
const nextPluginSyntaxIdentity = opts.plugins?.syntaxIdentity ?? '';
|
|
2629
3569
|
// Invalidate cache when an option that changes parsed nodes flips — cached
|
|
2630
3570
|
// settled blocks were parsed with the previous setting and would otherwise
|
|
2631
3571
|
// be reused unchanged.
|
|
2632
3572
|
if (
|
|
2633
3573
|
state.autolink !== opts.autolink ||
|
|
2634
3574
|
state.math !== opts.math ||
|
|
2635
|
-
Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges)
|
|
3575
|
+
Boolean(state.sourceRanges) !== Boolean(opts.sourceRanges) ||
|
|
3576
|
+
state.sourceIdsKey !== nextSourceIdsKey ||
|
|
3577
|
+
state.pluginSyntaxIdentity !== nextPluginSyntaxIdentity
|
|
2636
3578
|
) {
|
|
2637
3579
|
reparseSettled = true;
|
|
2638
3580
|
state.autolink = opts.autolink;
|
|
2639
3581
|
state.math = opts.math;
|
|
2640
3582
|
state.sourceRanges = opts.sourceRanges;
|
|
3583
|
+
state.sourceIdsKey = nextSourceIdsKey;
|
|
3584
|
+
state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
|
|
2641
3585
|
}
|
|
2642
3586
|
if (input === '') {
|
|
2643
3587
|
resetIncrementalCache(state, cache);
|
|
2644
3588
|
return [];
|
|
2645
3589
|
}
|
|
2646
|
-
|
|
2647
3590
|
// The settled prefix is only reusable while the input still contains it
|
|
2648
3591
|
// verbatim. Lengths alone cannot tell: a same-length or longer replacement
|
|
2649
3592
|
// (new args after reusing a state) disagrees with the prefix without ever
|
|
@@ -2660,6 +3603,8 @@ export function parseMarkdownIncremental(
|
|
|
2660
3603
|
state.autolink = opts.autolink;
|
|
2661
3604
|
state.math = opts.math;
|
|
2662
3605
|
state.sourceRanges = opts.sourceRanges;
|
|
3606
|
+
state.sourceIdsKey = nextSourceIdsKey;
|
|
3607
|
+
state.pluginSyntaxIdentity = nextPluginSyntaxIdentity;
|
|
2663
3608
|
}
|
|
2664
3609
|
|
|
2665
3610
|
// The recurring parse costs are confined to the mutable suffix: splitting,
|
|
@@ -2744,28 +3689,52 @@ export function parseMarkdownIncremental(
|
|
|
2744
3689
|
? trimOpenDisplayMath(unsettledRaw)
|
|
2745
3690
|
: trimUnsettledStructural(unsettledRaw);
|
|
2746
3691
|
|
|
3692
|
+
const legacyRanges = opts.sourceRanges === true;
|
|
2747
3693
|
let parsedSettledBlocks = 0;
|
|
2748
3694
|
if (reparseSettled) {
|
|
2749
|
-
|
|
3695
|
+
cache.settledAstBlocks = state.settledText
|
|
2750
3696
|
? parseMarkdownImpl(state.settledText, parseOpts)
|
|
2751
3697
|
: [];
|
|
2752
|
-
parsedSettledBlocks =
|
|
3698
|
+
parsedSettledBlocks = cache.settledAstBlocks.length;
|
|
3699
|
+
cache.settledRevision++;
|
|
3700
|
+
cache.projectedSettledBlocks = cache.settledAstBlocks.map(block =>
|
|
3701
|
+
projectBlockNode(block, cache.projectionCache, legacyRanges),
|
|
3702
|
+
);
|
|
3703
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2753
3704
|
} else if (settledDelta !== '') {
|
|
2754
3705
|
const deltaBlocks = parseMarkdownImpl(
|
|
2755
3706
|
settledDelta,
|
|
2756
3707
|
atOffset(parseOpts, oldSettledEnd),
|
|
2757
3708
|
);
|
|
2758
|
-
|
|
3709
|
+
const mergeIndex = cache.settledAstBlocks.length - 1;
|
|
3710
|
+
const mergedList = appendSettledBlocks(cache.settledAstBlocks, deltaBlocks);
|
|
2759
3711
|
parsedSettledBlocks = deltaBlocks.length;
|
|
3712
|
+
cache.settledRevision++;
|
|
3713
|
+
const projectedDelta = deltaBlocks.map(block =>
|
|
3714
|
+
projectBlockNode(block, cache.projectionCache, legacyRanges),
|
|
3715
|
+
);
|
|
3716
|
+
if (mergedList) {
|
|
3717
|
+
const canonicalMerged = cache.settledAstBlocks[mergeIndex];
|
|
3718
|
+
const projectedMerged = projectBlockNode(
|
|
3719
|
+
canonicalMerged,
|
|
3720
|
+
cache.projectionCache,
|
|
3721
|
+
legacyRanges,
|
|
3722
|
+
);
|
|
3723
|
+
cache.projectedSettledBlocks[mergeIndex] = projectedMerged;
|
|
3724
|
+
cache.projectedSettledBlocks.push(...projectedDelta.slice(1));
|
|
3725
|
+
} else {
|
|
3726
|
+
cache.projectedSettledBlocks.push(...projectedDelta);
|
|
3727
|
+
}
|
|
3728
|
+
cache.projectedRevision = cache.settledRevision;
|
|
2760
3729
|
}
|
|
2761
3730
|
|
|
2762
3731
|
// The unsettled tail is trimmed before parsing, so its offset in the
|
|
2763
3732
|
// document is where that trimmed text actually starts — not the boundary,
|
|
2764
|
-
// which is a line index. If it somehow
|
|
2765
|
-
//
|
|
2766
|
-
//
|
|
3733
|
+
// which is a line index. If it somehow cannot be located, parse it without
|
|
3734
|
+
// positions rather than report wrong ones. The search starts at the
|
|
3735
|
+
// settled end, so it scans the tail and never the prefix.
|
|
2767
3736
|
const unsettledStart =
|
|
2768
|
-
unsettledText && opts.
|
|
3737
|
+
unsettledText && opts.astPositions === true
|
|
2769
3738
|
? input.indexOf(unsettledText, cache.settledEnd)
|
|
2770
3739
|
: -1;
|
|
2771
3740
|
const unsettledBlocks = unsettledText
|
|
@@ -2790,7 +3759,7 @@ export function parseMarkdownIncremental(
|
|
|
2790
3759
|
definitionCharacters: settledDelta.length + unsettledInput.length,
|
|
2791
3760
|
renderedBlocks: parsedSettledBlocks + unsettledBlocks.length,
|
|
2792
3761
|
};
|
|
2793
|
-
return mergeSettledBlocks(
|
|
3762
|
+
return mergeSettledBlocks(cache.settledAstBlocks, unsettledBlocks);
|
|
2794
3763
|
}
|
|
2795
3764
|
|
|
2796
3765
|
// ---------------------------------------------------------------------------
|
|
@@ -2801,30 +3770,6 @@ export function parseMarkdownIncremental(
|
|
|
2801
3770
|
// derives its item ids from the same functions, so outline hash links always
|
|
2802
3771
|
// resolve to a rendered heading by construction.
|
|
2803
3772
|
|
|
2804
|
-
/** Flatten inline nodes into their plain text content. */
|
|
2805
|
-
export function inlineText(nodes: ReadonlyArray<InlineNodeWithMath>): string {
|
|
2806
|
-
return nodes
|
|
2807
|
-
.map(node => {
|
|
2808
|
-
switch (node.type) {
|
|
2809
|
-
case 'text':
|
|
2810
|
-
case 'code':
|
|
2811
|
-
case 'math':
|
|
2812
|
-
return node.type === 'math' ? node.value : node.content;
|
|
2813
|
-
case 'bold':
|
|
2814
|
-
case 'italic':
|
|
2815
|
-
case 'strikethrough':
|
|
2816
|
-
case 'link':
|
|
2817
|
-
return inlineText(node.children);
|
|
2818
|
-
case 'image':
|
|
2819
|
-
return node.alt;
|
|
2820
|
-
case 'citation':
|
|
2821
|
-
case 'break':
|
|
2822
|
-
return '';
|
|
2823
|
-
}
|
|
2824
|
-
})
|
|
2825
|
-
.join('');
|
|
2826
|
-
}
|
|
2827
|
-
|
|
2828
3773
|
/** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
|
|
2829
3774
|
export function slugify(value: string): string {
|
|
2830
3775
|
return value
|