@astryxdesign/core 0.6.1 → 0.6.2-canary.023c3e4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/BottomSheet/BottomSheet.d.ts +1 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +3 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +56 -19
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +12 -2
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +6 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +1 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +4 -4
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +16 -16
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +2 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +20 -13
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +8 -8
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
- package/dist/Dialog/DialogHeader.d.ts +1 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +10 -7
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -10
- package/dist/Kbd/Kbd.d.ts +5 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +36 -42
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -2
- package/dist/Markdown/Markdown.d.ts +18 -4
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.bench.js +150 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
- package/dist/Markdown/Markdown.js +311 -114
- package/dist/Markdown/ast.d.ts +162 -0
- package/dist/Markdown/ast.d.ts.map +1 -0
- package/dist/Markdown/ast.js +74 -0
- package/dist/Markdown/index.d.ts +5 -3
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/index.js +3 -2
- package/dist/Markdown/parser/index.d.ts +10 -0
- package/dist/Markdown/parser/index.d.ts.map +1 -0
- package/dist/Markdown/parser/index.js +10 -0
- package/dist/Markdown/parser.d.ts +220 -38
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +1072 -188
- package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
- package/dist/Markdown/plugins/frontmatter.js +132 -0
- package/dist/Markdown/plugins/index.d.ts +16 -0
- package/dist/Markdown/plugins/index.d.ts.map +1 -0
- package/dist/Markdown/plugins/index.js +13 -0
- package/dist/Markdown/plugins/protocol.d.ts +191 -0
- package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
- package/dist/Markdown/plugins/protocol.js +957 -0
- package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
- package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
- package/dist/Markdown/plugins/semanticFence.js +213 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
- package/dist/Markdown/plugins/sourceDecoration.js +393 -0
- package/dist/Markdown/plugins/textTransform.d.ts +30 -0
- package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
- package/dist/Markdown/plugins/textTransform.js +374 -0
- package/dist/Markdown/remark.d.ts +196 -0
- package/dist/Markdown/remark.d.ts.map +1 -0
- package/dist/Markdown/remark.js +1062 -0
- package/dist/Markdown/url.d.ts +4 -0
- package/dist/Markdown/url.d.ts.map +1 -0
- package/dist/Markdown/url.js +25 -0
- package/dist/Markdown/utils.d.ts +4 -2
- package/dist/Markdown/utils.d.ts.map +1 -1
- package/dist/Markdown/utils.js +2 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +4 -4
- package/dist/Outline/index.d.ts +1 -0
- package/dist/Outline/index.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
- package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
- package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/useOutlineFromMarkdown.js +8 -3
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +36 -31
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +1 -1
- package/dist/SideNav/SideNav.d.ts +2 -1
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +9 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +19 -10
- package/dist/Spinner/Spinner.d.ts +1 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +45 -21
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +11 -0
- package/dist/TabList/Tab.d.ts +1 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +20 -7
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +1 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +4 -4
- package/dist/ToggleButton/ToggleButton.d.ts +2 -1
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +7 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +16 -7
- package/dist/astryx.css +19 -8
- package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
- package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
- package/dist/hooks/scrollKeyboardDelegation.js +146 -0
- package/dist/hooks/useScrollableArea.d.ts +6 -2
- package/dist/hooks/useScrollableArea.d.ts.map +1 -1
- package/dist/hooks/useScrollableArea.js +17 -5
- package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +9 -0
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +22 -5
- package/scripts/agent-doc-state.mjs +1 -1
- package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
- package/src/BottomSheet/BottomSheet.spec.md +46 -20
- package/src/BottomSheet/BottomSheet.test.tsx +6 -3
- package/src/BottomSheet/BottomSheet.tsx +3 -1
- package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
- package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
- package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
- package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
- package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
- package/src/Button/__tests__/Button.a11y.states.ts +9 -0
- package/src/Chat/ChatComposerInput.doc.mjs +3 -3
- package/src/Chat/ChatComposerInput.tsx +10 -2
- package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -1
- package/src/ClickableCard/ClickableCard.test.tsx +102 -5
- package/src/Collapsible/Collapsible.doc.mjs +11 -0
- package/src/Collapsible/Collapsible.test.tsx +21 -0
- package/src/Collapsible/Collapsible.tsx +5 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
- package/src/ComplexSelector/ComplexSelector.tsx +6 -1
- package/src/DateInput/DateInput.tsx +6 -1
- package/src/DateInput/NativeDateField.tsx +24 -8
- package/src/DateInput/TouchDateField.tsx +6 -2
- package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
- package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
- package/src/DateRangeInput/DateRangeInput.tsx +35 -21
- package/src/DateTimeInput/DateTimeInput.tsx +6 -1
- package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
- package/src/Dialog/Dialog.doc.mjs +3 -0
- package/src/Dialog/Dialog.spec.md +1 -1
- package/src/Dialog/DialogHeader.doc.mjs +38 -0
- package/src/Dialog/DialogHeader.test.tsx +49 -0
- package/src/Dialog/DialogHeader.tsx +23 -4
- package/src/Dialog/modules/DialogHeader.spec.md +152 -0
- package/src/Field/PanelSearchInput.tsx +5 -2
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
- package/src/FileInput/FileInput.doc.mjs +2 -0
- package/src/FileInput/FileInput.spec.md +199 -0
- package/src/FileInput/FileInput.test.tsx +14 -0
- package/src/FileInput/FileInput.tsx +24 -5
- package/src/Kbd/Kbd.doc.mjs +3 -3
- package/src/Kbd/Kbd.test.tsx +57 -1
- package/src/Kbd/Kbd.tsx +57 -37
- package/src/Layout/LayoutFooter.doc.mjs +17 -0
- package/src/Link/Link.doc.mjs +11 -0
- package/src/Link/Link.test.tsx +24 -0
- package/src/Link/Link.tsx +5 -0
- package/src/Markdown/Markdown.doc.mjs +439 -42
- package/src/Markdown/Markdown.fr23.bench.ts +174 -0
- package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
- package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
- package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
- package/src/Markdown/Markdown.public.test.ts +280 -0
- package/src/Markdown/Markdown.spec.md +304 -72
- package/src/Markdown/Markdown.test.tsx +118 -3
- package/src/Markdown/Markdown.tsx +489 -129
- package/src/Markdown/ast.test.ts +307 -0
- package/src/Markdown/ast.ts +337 -0
- package/src/Markdown/incremental.test.ts +187 -7
- package/src/Markdown/index.ts +43 -1
- package/src/Markdown/modules/remark.spec.md +269 -0
- package/src/Markdown/parser/index.ts +64 -0
- package/src/Markdown/parser.perf.test.ts +3 -1
- package/src/Markdown/parser.public.test.ts +103 -0
- package/src/Markdown/parser.test.ts +122 -0
- package/src/Markdown/parser.ts +1721 -248
- package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
- package/src/Markdown/plugins/frontmatter.ts +198 -0
- package/src/Markdown/plugins/index.ts +53 -0
- package/src/Markdown/plugins/protocol.test.tsx +1022 -0
- package/src/Markdown/plugins/protocol.ts +1618 -0
- package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
- package/src/Markdown/plugins/semanticFence.ts +424 -0
- package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
- package/src/Markdown/plugins/sourceDecoration.ts +539 -0
- package/src/Markdown/plugins/textTransform.test.ts +467 -0
- package/src/Markdown/plugins/textTransform.ts +554 -0
- package/src/Markdown/remark.test.tsx +1877 -0
- package/src/Markdown/remark.ts +1574 -0
- package/src/Markdown/url.ts +30 -0
- package/src/Markdown/utils.ts +19 -1
- package/src/MultiSelector/MultiSelector.test.tsx +1 -35
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
- package/src/NumberInput/NumberInput.tsx +6 -1
- package/src/Outline/Outline.doc.mjs +7 -5
- package/src/Outline/Outline.spec.md +1 -1
- package/src/Outline/index.ts +1 -0
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
- package/src/Outline/useOutlineFromMarkdown.ts +15 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- package/src/ProgressBar/ProgressBar.test.tsx +29 -11
- package/src/ProgressBar/ProgressBar.tsx +59 -72
- package/src/RadioList/RadioList.doc.mjs +11 -0
- package/src/RadioList/RadioList.test.tsx +32 -0
- package/src/RadioList/RadioListItem.tsx +25 -1
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
- package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
- package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
- package/src/Selector/Selector.spec.md +12 -9
- package/src/Selector/Selector.tsx +6 -1
- package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
- package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
- package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
- package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
- package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
- package/src/SideNav/SideNav.doc.mjs +1 -1
- package/src/SideNav/SideNav.test.tsx +10 -0
- package/src/SideNav/SideNav.tsx +14 -2
- package/src/Slider/Slider.doc.mjs +27 -0
- package/src/Slider/Slider.spec.md +61 -47
- package/src/Slider/Slider.test.tsx +146 -0
- package/src/Slider/Slider.tsx +37 -13
- package/src/Spinner/Spinner.doc.mjs +6 -3
- package/src/Spinner/Spinner.test.tsx +41 -0
- package/src/Spinner/Spinner.tsx +62 -32
- package/src/Switch/Switch.doc.mjs +11 -0
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +28 -0
- package/src/TabList/Tab.tsx +35 -7
- package/src/TabList/TabList.doc.mjs +11 -0
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
- package/src/TextArea/TextArea.tsx +6 -1
- package/src/TextInput/TextInput.test.tsx +20 -1
- package/src/TextInput/TextInput.tsx +6 -1
- package/src/TimeInput/TimeInput.tsx +6 -1
- package/src/ToggleButton/ToggleButton.test.tsx +133 -0
- package/src/ToggleButton/ToggleButton.tsx +9 -2
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
- package/src/Tokenizer/Tokenizer.spec.md +142 -75
- package/src/TreeList/TreeList.spec.md +123 -75
- package/src/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +21 -7
- package/src/Typeahead/Typeahead.test.tsx +53 -0
- package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
- package/src/__tests__/pressState.ts +93 -0
- package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
- package/src/hooks/scrollKeyboardDelegation.ts +233 -0
- package/src/hooks/useScrollableArea.doc.mjs +15 -3
- package/src/hooks/useScrollableArea.test.tsx +59 -1
- package/src/hooks/useScrollableArea.ts +34 -10
- package/src/inputFontFloor.test.ts +106 -0
- package/src/theme/derivedVarRegistry.test.ts +6 -4
- package/src/utils/interactionOverlay.stylex.ts +20 -0
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Markdown.tsx
|
|
7
|
-
* @input Markdown string,
|
|
8
|
-
* @output Exports Markdown component and
|
|
7
|
+
* @input Markdown string, canonical AST, optional plugins and custom renderers
|
|
8
|
+
* @output Exports Markdown component, MarkdownProps, and renderer contracts
|
|
9
9
|
* @position Core implementation; renders markdown as Astryx components
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {useMemo, useRef} from 'react';
|
|
12
|
+
import {Component, Suspense, useMemo, useRef} from 'react';
|
|
13
13
|
import type React from 'react';
|
|
14
14
|
import {Fragment} from 'react';
|
|
15
15
|
import * as stylex from '@stylexjs/stylex';
|
|
@@ -48,20 +48,43 @@ import type {CitationSource} from '../Citation/Citation';
|
|
|
48
48
|
import {useLinkComponent} from '../Link/useLinkComponent';
|
|
49
49
|
import type {LinkComponentType} from '../Link/types';
|
|
50
50
|
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
parseInlineAst,
|
|
52
|
+
parseMarkdownAst,
|
|
53
|
+
parseMarkdownAstIncremental,
|
|
54
54
|
createIncrementalState,
|
|
55
55
|
trimStreamingArtifacts,
|
|
56
|
-
inlineText,
|
|
57
56
|
slugify,
|
|
58
57
|
uniqueSlug,
|
|
59
58
|
} from './parser';
|
|
60
|
-
import type {
|
|
59
|
+
import type {IncrementalState, MathParseOptions, ParseOptions} from './parser';
|
|
60
|
+
import {getMarkdownAstLegacyCodeLanguage, markdownAstText} from './ast';
|
|
61
|
+
import type {
|
|
62
|
+
MarkdownAstBlockContent,
|
|
63
|
+
MarkdownAstPhrasingContent,
|
|
64
|
+
MarkdownAstTable,
|
|
65
|
+
} from './ast';
|
|
66
|
+
import {
|
|
67
|
+
applyMarkdownTransforms,
|
|
68
|
+
getMarkdownExtensionRenderer,
|
|
69
|
+
markdownExtensionText,
|
|
70
|
+
prepareMarkdownPlugins,
|
|
71
|
+
reportMarkdownPluginFailure,
|
|
72
|
+
} from './plugins/protocol';
|
|
73
|
+
import {getMarkdownFenceProposal} from './plugins/semanticFence';
|
|
74
|
+
import type {
|
|
75
|
+
MarkdownExtensionNode,
|
|
76
|
+
MarkdownPluginEntry,
|
|
77
|
+
PreparedMarkdownPlugins,
|
|
78
|
+
} from './plugins/protocol';
|
|
79
|
+
import {sanitizeMarkdownUrl} from './url';
|
|
61
80
|
import {themeProps} from '../utils/themeProps';
|
|
62
81
|
import {useTranslator, type TranslatorFn} from '../i18n';
|
|
63
82
|
|
|
64
83
|
type SyncReactNode = Exclude<React.ReactNode, Promise<unknown>>;
|
|
84
|
+
type RenderExtensionNode = MarkdownExtensionNode;
|
|
85
|
+
type RenderInlineNode = MarkdownAstPhrasingContent<RenderExtensionNode>;
|
|
86
|
+
type RenderBlockNode = MarkdownAstBlockContent<RenderExtensionNode>;
|
|
87
|
+
type RenderTable = MarkdownAstTable<RenderExtensionNode>;
|
|
65
88
|
|
|
66
89
|
// ---------------------------------------------------------------------------
|
|
67
90
|
// Props
|
|
@@ -70,7 +93,7 @@ type SyncReactNode = Exclude<React.ReactNode, Promise<unknown>>;
|
|
|
70
93
|
/**
|
|
71
94
|
* A plugin that transforms text patterns into custom React elements
|
|
72
95
|
* inside Markdown. Applied to parsed text nodes only — code blocks,
|
|
73
|
-
* inline code, and other non-prose contexts are unaffected.
|
|
96
|
+
* inline code, math, and other non-prose contexts are unaffected.
|
|
74
97
|
*
|
|
75
98
|
* Follows Lexical's TextMatchTransformer architecture:
|
|
76
99
|
* - `pattern` for initial regex matching
|
|
@@ -102,6 +125,14 @@ export type MarkdownSource = CitationSource;
|
|
|
102
125
|
export interface MarkdownComponents {
|
|
103
126
|
code?: React.ComponentType<{code: string; language?: string}>;
|
|
104
127
|
inlineCode?: React.ComponentType<{children: string}>;
|
|
128
|
+
/**
|
|
129
|
+
* Renders opt-in `$…$` and `$$…$$` math. Supplying this renderer enables
|
|
130
|
+
* math parsing; Astryx passes the raw expression and does not execute it.
|
|
131
|
+
*/
|
|
132
|
+
math?: React.ComponentType<{
|
|
133
|
+
value: string;
|
|
134
|
+
display: 'inline' | 'block';
|
|
135
|
+
}>;
|
|
105
136
|
citation?: React.ComponentType<{
|
|
106
137
|
source: CitationSource;
|
|
107
138
|
number: number;
|
|
@@ -126,7 +157,9 @@ export interface MarkdownComponents {
|
|
|
126
157
|
hr?: React.ComponentType<object>;
|
|
127
158
|
}
|
|
128
159
|
|
|
129
|
-
export interface MarkdownProps
|
|
160
|
+
export interface MarkdownProps<
|
|
161
|
+
Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
|
|
162
|
+
> extends BaseProps<HTMLElement> {
|
|
130
163
|
ref?: React.Ref<HTMLDivElement> | React.Ref<HTMLSpanElement>;
|
|
131
164
|
children: string;
|
|
132
165
|
/**
|
|
@@ -183,10 +216,15 @@ export interface MarkdownProps extends BaseProps<HTMLElement> {
|
|
|
183
216
|
components?: Partial<MarkdownComponents>;
|
|
184
217
|
/**
|
|
185
218
|
* Plugins that transform text patterns into custom React elements.
|
|
186
|
-
* Applied to text nodes after parsing — code blocks
|
|
219
|
+
* Applied to text nodes after parsing — code blocks, inline code, and math
|
|
187
220
|
* are unaffected. Patterns are matched in order; first match wins
|
|
188
221
|
* for overlapping ranges.
|
|
189
222
|
*/
|
|
223
|
+
/** Ordered syntax, immutable transforms, and typed extension renderers. */
|
|
224
|
+
plugins?: Plugins;
|
|
225
|
+
/**
|
|
226
|
+
* Legacy text-match renderers. Unchanged and independent from `plugins`.
|
|
227
|
+
*/
|
|
190
228
|
inlinePlugins?: MarkdownInlinePlugin[];
|
|
191
229
|
/**
|
|
192
230
|
* Opt-in autolinking of bare URLs and emails inside text.
|
|
@@ -473,22 +511,25 @@ interface StreamingCursor {
|
|
|
473
511
|
* Count the total text characters in inline nodes without rendering.
|
|
474
512
|
* Used to advance the cursor past a block that will be faded as a whole unit.
|
|
475
513
|
*/
|
|
476
|
-
function countInlineTextLength(nodes:
|
|
514
|
+
function countInlineTextLength(nodes: ReadonlyArray<RenderInlineNode>): number {
|
|
477
515
|
let len = 0;
|
|
478
516
|
for (const node of nodes) {
|
|
479
517
|
switch (node.type) {
|
|
480
518
|
case 'text':
|
|
481
|
-
len += node.
|
|
519
|
+
len += node.value.length;
|
|
482
520
|
break;
|
|
483
|
-
case '
|
|
484
|
-
len += node.
|
|
521
|
+
case 'inlineCode':
|
|
522
|
+
len += node.value.length;
|
|
523
|
+
break;
|
|
524
|
+
case 'inlineMath':
|
|
525
|
+
len += node.value.length;
|
|
485
526
|
break;
|
|
486
527
|
case 'image':
|
|
487
528
|
len += node.alt.length;
|
|
488
529
|
break;
|
|
489
|
-
case '
|
|
490
|
-
case '
|
|
491
|
-
case '
|
|
530
|
+
case 'strong':
|
|
531
|
+
case 'emphasis':
|
|
532
|
+
case 'delete':
|
|
492
533
|
case 'link':
|
|
493
534
|
len += countInlineTextLength(node.children);
|
|
494
535
|
break;
|
|
@@ -498,6 +539,9 @@ function countInlineTextLength(nodes: InlineNode[]): number {
|
|
|
498
539
|
case 'citation':
|
|
499
540
|
len += 1;
|
|
500
541
|
break;
|
|
542
|
+
case 'extension':
|
|
543
|
+
len += node.source?.length ?? 0;
|
|
544
|
+
break;
|
|
501
545
|
}
|
|
502
546
|
}
|
|
503
547
|
return len;
|
|
@@ -506,7 +550,7 @@ function countInlineTextLength(nodes: InlineNode[]): number {
|
|
|
506
550
|
/**
|
|
507
551
|
* Count total text characters in a block node tree.
|
|
508
552
|
*/
|
|
509
|
-
function countBlockTextLength(nodes:
|
|
553
|
+
function countBlockTextLength(nodes: ReadonlyArray<RenderBlockNode>): number {
|
|
510
554
|
let len = 0;
|
|
511
555
|
for (const node of nodes) {
|
|
512
556
|
switch (node.type) {
|
|
@@ -514,28 +558,31 @@ function countBlockTextLength(nodes: BlockNode[]): number {
|
|
|
514
558
|
case 'paragraph':
|
|
515
559
|
len += countInlineTextLength(node.children);
|
|
516
560
|
break;
|
|
517
|
-
case '
|
|
518
|
-
len += node.
|
|
561
|
+
case 'code':
|
|
562
|
+
len += node.value.length;
|
|
563
|
+
break;
|
|
564
|
+
case 'math':
|
|
565
|
+
len += node.value.length;
|
|
519
566
|
break;
|
|
520
567
|
case 'blockquote':
|
|
521
568
|
len += countBlockTextLength(node.children);
|
|
522
569
|
break;
|
|
523
570
|
case 'list':
|
|
524
|
-
for (const item of node.
|
|
571
|
+
for (const item of node.children) {
|
|
525
572
|
len += countBlockTextLength(item.children);
|
|
526
573
|
}
|
|
527
574
|
break;
|
|
528
575
|
case 'table':
|
|
529
|
-
for (const
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
for (const row of node.rows) {
|
|
533
|
-
for (const cell of row) {
|
|
576
|
+
for (const row of node.children) {
|
|
577
|
+
for (const cell of row.children) {
|
|
534
578
|
len += countInlineTextLength(cell.children);
|
|
535
579
|
}
|
|
536
580
|
}
|
|
537
581
|
break;
|
|
538
|
-
case '
|
|
582
|
+
case 'extension':
|
|
583
|
+
len += node.source?.length ?? 0;
|
|
584
|
+
break;
|
|
585
|
+
case 'thematicBreak':
|
|
539
586
|
break;
|
|
540
587
|
case 'image':
|
|
541
588
|
len += node.alt.length;
|
|
@@ -555,27 +602,6 @@ const headingStyles = {
|
|
|
555
602
|
} as const;
|
|
556
603
|
|
|
557
604
|
// ---------------------------------------------------------------------------
|
|
558
|
-
// URL sanitization — block dangerous protocols
|
|
559
|
-
// ---------------------------------------------------------------------------
|
|
560
|
-
|
|
561
|
-
const DANGEROUS_URL_PATTERN = /^(javascript|data|vbscript):/i;
|
|
562
|
-
|
|
563
|
-
function sanitizeUrl(url: string): string | null {
|
|
564
|
-
// Strip control characters before testing, the same normalization the
|
|
565
|
-
// parser's isSafeUrl applies — the anchored pattern must see the URL the
|
|
566
|
-
// way a browser will. Return the normalized value so the stripped
|
|
567
|
-
// characters don't ride along into an attribute or component override.
|
|
568
|
-
// eslint-disable-next-line no-control-regex -- control chars are the bypass
|
|
569
|
-
const normalized = url.replace(/[\x00-\x1f\x7f]/g, '').trim();
|
|
570
|
-
if (normalized.length === 0) {
|
|
571
|
-
return null;
|
|
572
|
-
}
|
|
573
|
-
if (DANGEROUS_URL_PATTERN.test(normalized)) {
|
|
574
|
-
return null;
|
|
575
|
-
}
|
|
576
|
-
return normalized;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
605
|
// ---------------------------------------------------------------------------
|
|
580
606
|
// Inline plugin matching
|
|
581
607
|
// ---------------------------------------------------------------------------
|
|
@@ -681,6 +707,81 @@ function applyInlinePlugins(
|
|
|
681
707
|
return segments;
|
|
682
708
|
}
|
|
683
709
|
|
|
710
|
+
interface MarkdownPluginBoundaryProps {
|
|
711
|
+
children: React.ReactNode;
|
|
712
|
+
fallback: React.ReactNode;
|
|
713
|
+
pluginName: string;
|
|
714
|
+
resetKey: unknown;
|
|
715
|
+
resetRenderer: unknown;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
interface MarkdownPluginBoundaryState {
|
|
719
|
+
failed: boolean;
|
|
720
|
+
resetKey: unknown;
|
|
721
|
+
resetRenderer: unknown;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
class MarkdownPluginBoundary extends Component<
|
|
725
|
+
MarkdownPluginBoundaryProps,
|
|
726
|
+
MarkdownPluginBoundaryState
|
|
727
|
+
> {
|
|
728
|
+
state: MarkdownPluginBoundaryState = {
|
|
729
|
+
failed: false,
|
|
730
|
+
resetKey: this.props.resetKey,
|
|
731
|
+
resetRenderer: this.props.resetRenderer,
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
static getDerivedStateFromError(): Partial<MarkdownPluginBoundaryState> {
|
|
735
|
+
return {failed: true};
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
static getDerivedStateFromProps(
|
|
739
|
+
props: MarkdownPluginBoundaryProps,
|
|
740
|
+
state: MarkdownPluginBoundaryState,
|
|
741
|
+
): Partial<MarkdownPluginBoundaryState> | null {
|
|
742
|
+
return props.resetKey === state.resetKey &&
|
|
743
|
+
props.resetRenderer === state.resetRenderer
|
|
744
|
+
? null
|
|
745
|
+
: {
|
|
746
|
+
failed: false,
|
|
747
|
+
resetKey: props.resetKey,
|
|
748
|
+
resetRenderer: props.resetRenderer,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
componentDidCatch(error: unknown): void {
|
|
753
|
+
reportMarkdownPluginFailure(this.props.pluginName, 'render', error);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
render(): React.ReactNode {
|
|
757
|
+
return this.state.failed ? this.props.fallback : this.props.children;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function useStableMarkdownSyntaxEntries(
|
|
762
|
+
plugins: PreparedMarkdownPlugins | undefined,
|
|
763
|
+
): ReadonlyArray<MarkdownPluginEntry> | undefined {
|
|
764
|
+
const identity = plugins?.syntaxIdentity ?? '';
|
|
765
|
+
const syntaxEntriesRef = useRef<
|
|
766
|
+
| {
|
|
767
|
+
identity: string;
|
|
768
|
+
entries: ReadonlyArray<MarkdownPluginEntry> | undefined;
|
|
769
|
+
}
|
|
770
|
+
| undefined
|
|
771
|
+
>(undefined);
|
|
772
|
+
if (
|
|
773
|
+
syntaxEntriesRef.current == null ||
|
|
774
|
+
syntaxEntriesRef.current.identity !== identity
|
|
775
|
+
) {
|
|
776
|
+
syntaxEntriesRef.current = {
|
|
777
|
+
identity,
|
|
778
|
+
entries:
|
|
779
|
+
plugins == null || identity === '' ? undefined : plugins.syntaxEntries,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
return syntaxEntriesRef.current.entries;
|
|
783
|
+
}
|
|
784
|
+
|
|
684
785
|
// ---------------------------------------------------------------------------
|
|
685
786
|
// Inline renderer
|
|
686
787
|
// ---------------------------------------------------------------------------
|
|
@@ -748,7 +849,7 @@ function getCitationNumber(ctx: CitationContext, sourceId: string): number {
|
|
|
748
849
|
}
|
|
749
850
|
|
|
750
851
|
function renderInline(
|
|
751
|
-
node:
|
|
852
|
+
node: RenderInlineNode,
|
|
752
853
|
index: number,
|
|
753
854
|
onLinkClick: MarkdownProps['onLinkClick'] | undefined,
|
|
754
855
|
cursor: StreamingCursor,
|
|
@@ -756,11 +857,12 @@ function renderInline(
|
|
|
756
857
|
linkComponent: LinkComponentType = 'a',
|
|
757
858
|
inlinePlugins?: MarkdownInlinePlugin[],
|
|
758
859
|
components?: Partial<MarkdownComponents>,
|
|
860
|
+
preparedPlugins?: PreparedMarkdownPlugins,
|
|
759
861
|
): SyncReactNode {
|
|
760
862
|
switch (node.type) {
|
|
761
863
|
case 'text': {
|
|
762
864
|
if (inlinePlugins && inlinePlugins.length > 0) {
|
|
763
|
-
const segments = applyInlinePlugins(node.
|
|
865
|
+
const segments = applyInlinePlugins(node.value, inlinePlugins);
|
|
764
866
|
// If no plugin matched, fall through to the normal path
|
|
765
867
|
// O(1) guard: applyInlinePlugins returns a single text segment when
|
|
766
868
|
// nothing matched — skip the plugin path entirely in that case.
|
|
@@ -783,9 +885,9 @@ function renderInline(
|
|
|
783
885
|
return <Fragment key={index}>{result}</Fragment>;
|
|
784
886
|
}
|
|
785
887
|
}
|
|
786
|
-
return wrapTextWithFade(node.
|
|
888
|
+
return wrapTextWithFade(node.value, cursor, index);
|
|
787
889
|
}
|
|
788
|
-
case '
|
|
890
|
+
case 'strong':
|
|
789
891
|
return (
|
|
790
892
|
<strong key={index} {...stylex.props(styles.bold)}>
|
|
791
893
|
{node.children.map((c, i) =>
|
|
@@ -798,11 +900,12 @@ function renderInline(
|
|
|
798
900
|
linkComponent,
|
|
799
901
|
inlinePlugins,
|
|
800
902
|
components,
|
|
903
|
+
preparedPlugins,
|
|
801
904
|
),
|
|
802
905
|
)}
|
|
803
906
|
</strong>
|
|
804
907
|
);
|
|
805
|
-
case '
|
|
908
|
+
case 'emphasis':
|
|
806
909
|
return (
|
|
807
910
|
<em key={index}>
|
|
808
911
|
{node.children.map((c, i) =>
|
|
@@ -815,11 +918,12 @@ function renderInline(
|
|
|
815
918
|
linkComponent,
|
|
816
919
|
inlinePlugins,
|
|
817
920
|
components,
|
|
921
|
+
preparedPlugins,
|
|
818
922
|
),
|
|
819
923
|
)}
|
|
820
924
|
</em>
|
|
821
925
|
);
|
|
822
|
-
case '
|
|
926
|
+
case 'delete':
|
|
823
927
|
return (
|
|
824
928
|
<del key={index} {...stylex.props(styles.strikethrough)}>
|
|
825
929
|
{node.children.map((c, i) =>
|
|
@@ -832,23 +936,32 @@ function renderInline(
|
|
|
832
936
|
linkComponent,
|
|
833
937
|
inlinePlugins,
|
|
834
938
|
components,
|
|
939
|
+
preparedPlugins,
|
|
835
940
|
),
|
|
836
941
|
)}
|
|
837
942
|
</del>
|
|
838
943
|
);
|
|
839
|
-
case '
|
|
944
|
+
case 'inlineCode': {
|
|
840
945
|
// Track code content length for cursor but don't split inside code
|
|
841
|
-
cursor.offset += node.
|
|
946
|
+
cursor.offset += node.value.length;
|
|
842
947
|
const InlineCodeComp = components?.inlineCode;
|
|
843
948
|
const codeEl = InlineCodeComp ? (
|
|
844
|
-
<InlineCodeComp key={index}>{node.
|
|
949
|
+
<InlineCodeComp key={index}>{node.value}</InlineCodeComp>
|
|
845
950
|
) : (
|
|
846
|
-
<Code key={index}>{node.
|
|
951
|
+
<Code key={index}>{node.value}</Code>
|
|
847
952
|
);
|
|
848
953
|
return codeEl;
|
|
849
954
|
}
|
|
955
|
+
case 'inlineMath': {
|
|
956
|
+
const MathComp = components?.math;
|
|
957
|
+
if (MathComp == null) {
|
|
958
|
+
return wrapTextWithFade(`$${node.value}$`, cursor, index);
|
|
959
|
+
}
|
|
960
|
+
cursor.offset += node.value.length;
|
|
961
|
+
return <MathComp key={index} value={node.value} display="inline" />;
|
|
962
|
+
}
|
|
850
963
|
case 'link': {
|
|
851
|
-
const safeHref =
|
|
964
|
+
const safeHref = sanitizeMarkdownUrl(node.url);
|
|
852
965
|
if (safeHref == null) {
|
|
853
966
|
// Unsafe URL — render as plain text
|
|
854
967
|
return (
|
|
@@ -863,6 +976,7 @@ function renderInline(
|
|
|
863
976
|
linkComponent,
|
|
864
977
|
inlinePlugins,
|
|
865
978
|
components,
|
|
979
|
+
preparedPlugins,
|
|
866
980
|
),
|
|
867
981
|
)}
|
|
868
982
|
</span>
|
|
@@ -882,6 +996,7 @@ function renderInline(
|
|
|
882
996
|
linkComponent,
|
|
883
997
|
inlinePlugins,
|
|
884
998
|
components,
|
|
999
|
+
preparedPlugins,
|
|
885
1000
|
),
|
|
886
1001
|
)}
|
|
887
1002
|
</LinkComp>
|
|
@@ -920,13 +1035,14 @@ function renderInline(
|
|
|
920
1035
|
linkComponent,
|
|
921
1036
|
inlinePlugins,
|
|
922
1037
|
components,
|
|
1038
|
+
preparedPlugins,
|
|
923
1039
|
),
|
|
924
1040
|
)}
|
|
925
1041
|
</LinkTag>
|
|
926
1042
|
);
|
|
927
1043
|
}
|
|
928
1044
|
case 'image': {
|
|
929
|
-
const safeSrc =
|
|
1045
|
+
const safeSrc = sanitizeMarkdownUrl(node.url);
|
|
930
1046
|
if (safeSrc == null) {
|
|
931
1047
|
return <span key={index}>[{node.alt}]</span>;
|
|
932
1048
|
}
|
|
@@ -943,6 +1059,34 @@ function renderInline(
|
|
|
943
1059
|
/>
|
|
944
1060
|
);
|
|
945
1061
|
}
|
|
1062
|
+
case 'extension': {
|
|
1063
|
+
const renderer = getMarkdownExtensionRenderer(preparedPlugins, node);
|
|
1064
|
+
const fallback = wrapTextWithFade(
|
|
1065
|
+
node.source ?? markdownExtensionText(preparedPlugins, node),
|
|
1066
|
+
cursor,
|
|
1067
|
+
index,
|
|
1068
|
+
);
|
|
1069
|
+
if (renderer == null) {
|
|
1070
|
+
return fallback;
|
|
1071
|
+
}
|
|
1072
|
+
let rendered: React.ReactNode;
|
|
1073
|
+
try {
|
|
1074
|
+
rendered = renderer.render({node});
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
reportMarkdownPluginFailure(node.plugin, 'render', error);
|
|
1077
|
+
return fallback;
|
|
1078
|
+
}
|
|
1079
|
+
return (
|
|
1080
|
+
<MarkdownPluginBoundary
|
|
1081
|
+
key={index}
|
|
1082
|
+
pluginName={node.plugin}
|
|
1083
|
+
resetKey={node}
|
|
1084
|
+
resetRenderer={renderer.render}
|
|
1085
|
+
fallback={fallback}>
|
|
1086
|
+
<Suspense fallback={fallback}>{rendered}</Suspense>
|
|
1087
|
+
</MarkdownPluginBoundary>
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
946
1090
|
case 'break':
|
|
947
1091
|
cursor.offset += 1;
|
|
948
1092
|
return <br key={index} />;
|
|
@@ -984,13 +1128,13 @@ function renderInline(
|
|
|
984
1128
|
// ---------------------------------------------------------------------------
|
|
985
1129
|
|
|
986
1130
|
function getElementSpacing(
|
|
987
|
-
node:
|
|
1131
|
+
node: RenderBlockNode,
|
|
988
1132
|
density: 'default' | 'compact',
|
|
989
1133
|
): StyleXStyles {
|
|
990
1134
|
const compact = density === 'compact';
|
|
991
1135
|
switch (node.type) {
|
|
992
1136
|
case 'heading':
|
|
993
|
-
return node.
|
|
1137
|
+
return node.depth <= 3
|
|
994
1138
|
? compact
|
|
995
1139
|
? styles.spacingHeadingMajorCompact
|
|
996
1140
|
: styles.spacingHeadingMajorDefault
|
|
@@ -1001,7 +1145,8 @@ function getElementSpacing(
|
|
|
1001
1145
|
return compact
|
|
1002
1146
|
? styles.spacingParagraphCompact
|
|
1003
1147
|
: styles.spacingParagraphDefault;
|
|
1004
|
-
case '
|
|
1148
|
+
case 'code':
|
|
1149
|
+
case 'math':
|
|
1005
1150
|
return compact
|
|
1006
1151
|
? styles.spacingCodeblockCompact
|
|
1007
1152
|
: styles.spacingCodeblockDefault;
|
|
@@ -1013,7 +1158,11 @@ function getElementSpacing(
|
|
|
1013
1158
|
return compact ? styles.spacingListCompact : styles.spacingListDefault;
|
|
1014
1159
|
case 'table':
|
|
1015
1160
|
return compact ? styles.spacingTableCompact : styles.spacingTableDefault;
|
|
1016
|
-
case '
|
|
1161
|
+
case 'extension':
|
|
1162
|
+
return compact
|
|
1163
|
+
? styles.spacingParagraphCompact
|
|
1164
|
+
: styles.spacingParagraphDefault;
|
|
1165
|
+
case 'thematicBreak':
|
|
1017
1166
|
return compact ? styles.spacingHrCompact : styles.spacingHrDefault;
|
|
1018
1167
|
case 'image':
|
|
1019
1168
|
return compact ? styles.spacingImageCompact : styles.spacingImageDefault;
|
|
@@ -1028,15 +1177,17 @@ function getElementSpacing(
|
|
|
1028
1177
|
* Compute per-column min-widths from table AST content.
|
|
1029
1178
|
* Buckets: ≤6 chars → 60px, 7–15 → 80px, >15 → 120px.
|
|
1030
1179
|
*/
|
|
1031
|
-
function computeTableColumnMinWidths(node: {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1180
|
+
function computeTableColumnMinWidths(node: RenderTable): number[] {
|
|
1181
|
+
const [header, ...rows] = node.children;
|
|
1182
|
+
if (header == null) {
|
|
1183
|
+
return [];
|
|
1184
|
+
}
|
|
1185
|
+
return header.children.map((cell, colIdx) => {
|
|
1186
|
+
let maxLen = countInlineTextLength(cell.children);
|
|
1187
|
+
for (const row of rows) {
|
|
1188
|
+
const rowCell = row.children[colIdx];
|
|
1189
|
+
if (rowCell != null) {
|
|
1190
|
+
const len = countInlineTextLength(rowCell.children);
|
|
1040
1191
|
if (len > maxLen) {
|
|
1041
1192
|
maxLen = len;
|
|
1042
1193
|
}
|
|
@@ -1047,7 +1198,7 @@ function computeTableColumnMinWidths(node: {
|
|
|
1047
1198
|
}
|
|
1048
1199
|
|
|
1049
1200
|
function renderBlock(
|
|
1050
|
-
node:
|
|
1201
|
+
node: RenderBlockNode,
|
|
1051
1202
|
index: number,
|
|
1052
1203
|
blockCount: number,
|
|
1053
1204
|
density: 'default' | 'compact',
|
|
@@ -1060,8 +1211,9 @@ function renderBlock(
|
|
|
1060
1211
|
linkComponent: LinkComponentType = 'a',
|
|
1061
1212
|
inlinePlugins: MarkdownInlinePlugin[] | undefined,
|
|
1062
1213
|
components: Partial<MarkdownComponents> | undefined,
|
|
1214
|
+
preparedPlugins: PreparedMarkdownPlugins | undefined,
|
|
1063
1215
|
t: TranslatorFn,
|
|
1064
|
-
headingIdMap?: ReadonlyMap<
|
|
1216
|
+
headingIdMap?: ReadonlyMap<RenderBlockNode, string>,
|
|
1065
1217
|
): SyncReactNode {
|
|
1066
1218
|
const blockAlignMargin = BLOCK_ALIGN_MARGIN[contentAlign];
|
|
1067
1219
|
const blockAlignStyle =
|
|
@@ -1074,7 +1226,7 @@ function renderBlock(
|
|
|
1074
1226
|
|
|
1075
1227
|
switch (node.type) {
|
|
1076
1228
|
case 'heading': {
|
|
1077
|
-
const level = Math.min(node.
|
|
1229
|
+
const level = Math.min(node.depth + headingLevelStart - 1, 6) as
|
|
1078
1230
|
1 | 2 | 3 | 4 | 5 | 6;
|
|
1079
1231
|
const headingChildren = node.children.map((c, i) =>
|
|
1080
1232
|
renderInline(
|
|
@@ -1086,6 +1238,7 @@ function renderBlock(
|
|
|
1086
1238
|
linkComponent,
|
|
1087
1239
|
inlinePlugins,
|
|
1088
1240
|
components,
|
|
1241
|
+
preparedPlugins,
|
|
1089
1242
|
),
|
|
1090
1243
|
);
|
|
1091
1244
|
// Only top-level headings get an id: the map is built from the same
|
|
@@ -1137,6 +1290,7 @@ function renderBlock(
|
|
|
1137
1290
|
linkComponent,
|
|
1138
1291
|
inlinePlugins,
|
|
1139
1292
|
components,
|
|
1293
|
+
preparedPlugins,
|
|
1140
1294
|
),
|
|
1141
1295
|
);
|
|
1142
1296
|
const ParagraphComp = components?.paragraph;
|
|
@@ -1174,20 +1328,17 @@ function renderBlock(
|
|
|
1174
1328
|
</div>
|
|
1175
1329
|
);
|
|
1176
1330
|
}
|
|
1177
|
-
case '
|
|
1331
|
+
case 'code': {
|
|
1178
1332
|
// Track codeblock content in cursor for accurate character counting
|
|
1179
|
-
cursor.offset += node.
|
|
1333
|
+
cursor.offset += node.value.length;
|
|
1334
|
+
const language = getMarkdownAstLegacyCodeLanguage(node) ?? 'plaintext';
|
|
1180
1335
|
const CodeBlockComp = components?.code;
|
|
1181
1336
|
if (CodeBlockComp) {
|
|
1182
1337
|
return (
|
|
1183
|
-
<CodeBlockComp
|
|
1184
|
-
key={index}
|
|
1185
|
-
code={node.content}
|
|
1186
|
-
language={node.language}
|
|
1187
|
-
/>
|
|
1338
|
+
<CodeBlockComp key={index} code={node.value} language={language} />
|
|
1188
1339
|
);
|
|
1189
1340
|
}
|
|
1190
|
-
|
|
1341
|
+
const fallback = (
|
|
1191
1342
|
<div
|
|
1192
1343
|
key={index}
|
|
1193
1344
|
{...mergeProps(
|
|
@@ -1200,8 +1351,8 @@ function renderBlock(
|
|
|
1200
1351
|
),
|
|
1201
1352
|
)}>
|
|
1202
1353
|
<CodeBlock
|
|
1203
|
-
code={node.
|
|
1204
|
-
language={
|
|
1354
|
+
code={node.value}
|
|
1355
|
+
language={language}
|
|
1205
1356
|
isCollapsible
|
|
1206
1357
|
xstyle={[
|
|
1207
1358
|
contentWidthValue != null
|
|
@@ -1212,6 +1363,48 @@ function renderBlock(
|
|
|
1212
1363
|
/>
|
|
1213
1364
|
</div>
|
|
1214
1365
|
);
|
|
1366
|
+
const proposal = getMarkdownFenceProposal(node);
|
|
1367
|
+
if (proposal == null) {
|
|
1368
|
+
return fallback;
|
|
1369
|
+
}
|
|
1370
|
+
const renderer = getMarkdownExtensionRenderer(
|
|
1371
|
+
preparedPlugins,
|
|
1372
|
+
proposal.node,
|
|
1373
|
+
);
|
|
1374
|
+
if (renderer == null) {
|
|
1375
|
+
return fallback;
|
|
1376
|
+
}
|
|
1377
|
+
try {
|
|
1378
|
+
const rendered = renderer.render({node: proposal.node});
|
|
1379
|
+
if (rendered == null || typeof rendered === 'boolean') {
|
|
1380
|
+
return fallback;
|
|
1381
|
+
}
|
|
1382
|
+
return (
|
|
1383
|
+
<MarkdownPluginBoundary
|
|
1384
|
+
key={index}
|
|
1385
|
+
pluginName={proposal.node.plugin}
|
|
1386
|
+
resetKey={proposal.node}
|
|
1387
|
+
resetRenderer={renderer.render}
|
|
1388
|
+
fallback={fallback}>
|
|
1389
|
+
<Suspense fallback={fallback}>{rendered}</Suspense>
|
|
1390
|
+
</MarkdownPluginBoundary>
|
|
1391
|
+
);
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
reportMarkdownPluginFailure(proposal.node.plugin, 'render', error);
|
|
1394
|
+
return fallback;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
case 'math': {
|
|
1398
|
+
cursor.offset += node.value.length;
|
|
1399
|
+
const MathComp = components?.math;
|
|
1400
|
+
if (MathComp == null) {
|
|
1401
|
+
return (
|
|
1402
|
+
<div key={index} role="paragraph">
|
|
1403
|
+
{`$$${node.value}$$`}
|
|
1404
|
+
</div>
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
return <MathComp key={index} value={node.value} display="block" />;
|
|
1215
1408
|
}
|
|
1216
1409
|
case 'blockquote': {
|
|
1217
1410
|
const BlockquoteComp = components?.blockquote;
|
|
@@ -1231,6 +1424,7 @@ function renderBlock(
|
|
|
1231
1424
|
linkComponent,
|
|
1232
1425
|
inlinePlugins,
|
|
1233
1426
|
components,
|
|
1427
|
+
preparedPlugins,
|
|
1234
1428
|
t,
|
|
1235
1429
|
),
|
|
1236
1430
|
);
|
|
@@ -1266,6 +1460,7 @@ function renderBlock(
|
|
|
1266
1460
|
linkComponent,
|
|
1267
1461
|
inlinePlugins,
|
|
1268
1462
|
components,
|
|
1463
|
+
preparedPlugins,
|
|
1269
1464
|
t,
|
|
1270
1465
|
),
|
|
1271
1466
|
)}
|
|
@@ -1275,11 +1470,12 @@ function renderBlock(
|
|
|
1275
1470
|
case 'list': {
|
|
1276
1471
|
// Detect task lists: all items have a checked state
|
|
1277
1472
|
const isTaskList =
|
|
1278
|
-
node.
|
|
1473
|
+
node.children.length > 0 &&
|
|
1474
|
+
node.children.every(item => item.checked != null);
|
|
1279
1475
|
|
|
1280
1476
|
if (isTaskList) {
|
|
1281
1477
|
// Extract labels from task items — render as rich inline content
|
|
1282
|
-
const checkedValues = node.
|
|
1478
|
+
const checkedValues = node.children
|
|
1283
1479
|
.map((item, i) => ({item, key: `task-${i}`}))
|
|
1284
1480
|
.filter(({item}) => item.checked)
|
|
1285
1481
|
.map(({key}) => key);
|
|
@@ -1302,7 +1498,7 @@ function renderBlock(
|
|
|
1302
1498
|
xstyle={styles.blockIndent}
|
|
1303
1499
|
isReadOnly
|
|
1304
1500
|
density="compact">
|
|
1305
|
-
{node.
|
|
1501
|
+
{node.children.map((item, i) => {
|
|
1306
1502
|
const firstChild = item.children[0];
|
|
1307
1503
|
const isInline =
|
|
1308
1504
|
item.children.length === 1 &&
|
|
@@ -1320,6 +1516,7 @@ function renderBlock(
|
|
|
1320
1516
|
linkComponent,
|
|
1321
1517
|
inlinePlugins,
|
|
1322
1518
|
components,
|
|
1519
|
+
preparedPlugins,
|
|
1323
1520
|
),
|
|
1324
1521
|
)}
|
|
1325
1522
|
</>
|
|
@@ -1340,6 +1537,7 @@ function renderBlock(
|
|
|
1340
1537
|
linkComponent,
|
|
1341
1538
|
inlinePlugins,
|
|
1342
1539
|
components,
|
|
1540
|
+
preparedPlugins,
|
|
1343
1541
|
t,
|
|
1344
1542
|
),
|
|
1345
1543
|
)}
|
|
@@ -1382,7 +1580,7 @@ function renderBlock(
|
|
|
1382
1580
|
density="compact"
|
|
1383
1581
|
start={node.ordered ? node.start : undefined}
|
|
1384
1582
|
xstyle={styles.blockIndent}>
|
|
1385
|
-
{node.
|
|
1583
|
+
{node.children.map((item, i) => {
|
|
1386
1584
|
const firstChild = item.children[0];
|
|
1387
1585
|
const isInline =
|
|
1388
1586
|
item.children.length === 1 && firstChild?.type === 'paragraph';
|
|
@@ -1402,6 +1600,7 @@ function renderBlock(
|
|
|
1402
1600
|
linkComponent,
|
|
1403
1601
|
inlinePlugins,
|
|
1404
1602
|
components,
|
|
1603
|
+
preparedPlugins,
|
|
1405
1604
|
),
|
|
1406
1605
|
)}
|
|
1407
1606
|
</>
|
|
@@ -1422,6 +1621,7 @@ function renderBlock(
|
|
|
1422
1621
|
linkComponent,
|
|
1423
1622
|
inlinePlugins,
|
|
1424
1623
|
components,
|
|
1624
|
+
preparedPlugins,
|
|
1425
1625
|
t,
|
|
1426
1626
|
),
|
|
1427
1627
|
)}
|
|
@@ -1442,6 +1642,7 @@ function renderBlock(
|
|
|
1442
1642
|
}
|
|
1443
1643
|
case 'table': {
|
|
1444
1644
|
const colMinWidths = computeTableColumnMinWidths(node);
|
|
1645
|
+
const [header, ...rows] = node.children;
|
|
1445
1646
|
|
|
1446
1647
|
return (
|
|
1447
1648
|
<div
|
|
@@ -1471,14 +1672,14 @@ function renderBlock(
|
|
|
1471
1672
|
<Table dividers="rows" textOverflow="wrap">
|
|
1472
1673
|
<TableHeader>
|
|
1473
1674
|
<TableRow>
|
|
1474
|
-
{
|
|
1675
|
+
{header?.children.map((h, i) => (
|
|
1475
1676
|
<TableHeaderCell
|
|
1476
1677
|
// eslint-disable-next-line @eslint-react/no-array-index-key -- markdown table columns are positional by definition
|
|
1477
1678
|
key={i}
|
|
1478
1679
|
xstyle={[
|
|
1479
1680
|
dynamicStyles.cellMinWidth(`${colMinWidths[i]}px`),
|
|
1480
|
-
node.
|
|
1481
|
-
node.
|
|
1681
|
+
node.align[i] === 'center' && cellAlignStyles.center,
|
|
1682
|
+
node.align[i] === 'right' && cellAlignStyles.end,
|
|
1482
1683
|
]}>
|
|
1483
1684
|
{h.children.map((c, j) =>
|
|
1484
1685
|
renderInline(
|
|
@@ -1490,6 +1691,7 @@ function renderBlock(
|
|
|
1490
1691
|
linkComponent,
|
|
1491
1692
|
inlinePlugins,
|
|
1492
1693
|
components,
|
|
1694
|
+
preparedPlugins,
|
|
1493
1695
|
),
|
|
1494
1696
|
)}
|
|
1495
1697
|
</TableHeaderCell>
|
|
@@ -1497,14 +1699,14 @@ function renderBlock(
|
|
|
1497
1699
|
</TableRow>
|
|
1498
1700
|
</TableHeader>
|
|
1499
1701
|
<TableBody>
|
|
1500
|
-
{
|
|
1501
|
-
const cells = row.map((cell, j) => (
|
|
1702
|
+
{rows.map((row, i) => {
|
|
1703
|
+
const cells = row.children.map((cell, j) => (
|
|
1502
1704
|
<TableCell
|
|
1503
1705
|
// eslint-disable-next-line @eslint-react/no-array-index-key -- markdown table cells are positional by row and column
|
|
1504
1706
|
key={j}
|
|
1505
1707
|
xstyle={[
|
|
1506
|
-
node.
|
|
1507
|
-
node.
|
|
1708
|
+
node.align[j] === 'center' && cellAlignStyles.center,
|
|
1709
|
+
node.align[j] === 'right' && cellAlignStyles.end,
|
|
1508
1710
|
]}>
|
|
1509
1711
|
{cell.children.map((c, k) =>
|
|
1510
1712
|
renderInline(
|
|
@@ -1516,6 +1718,7 @@ function renderBlock(
|
|
|
1516
1718
|
linkComponent,
|
|
1517
1719
|
inlinePlugins,
|
|
1518
1720
|
components,
|
|
1721
|
+
preparedPlugins,
|
|
1519
1722
|
),
|
|
1520
1723
|
)}
|
|
1521
1724
|
</TableCell>
|
|
@@ -1533,7 +1736,47 @@ function renderBlock(
|
|
|
1533
1736
|
</div>
|
|
1534
1737
|
);
|
|
1535
1738
|
}
|
|
1536
|
-
case '
|
|
1739
|
+
case 'extension': {
|
|
1740
|
+
const renderer = getMarkdownExtensionRenderer(preparedPlugins, node);
|
|
1741
|
+
const fallbackText =
|
|
1742
|
+
node.source ?? markdownExtensionText(preparedPlugins, node);
|
|
1743
|
+
const fallback = wrapTextWithFade(fallbackText, cursor, index);
|
|
1744
|
+
let content: React.ReactNode = fallback;
|
|
1745
|
+
if (renderer != null) {
|
|
1746
|
+
try {
|
|
1747
|
+
const rendered = renderer.render({node});
|
|
1748
|
+
content = (
|
|
1749
|
+
<MarkdownPluginBoundary
|
|
1750
|
+
pluginName={node.plugin}
|
|
1751
|
+
resetKey={node}
|
|
1752
|
+
resetRenderer={renderer.render}
|
|
1753
|
+
fallback={fallback}>
|
|
1754
|
+
<Suspense fallback={fallback}>{rendered}</Suspense>
|
|
1755
|
+
</MarkdownPluginBoundary>
|
|
1756
|
+
);
|
|
1757
|
+
} catch (error) {
|
|
1758
|
+
reportMarkdownPluginFailure(node.plugin, 'render', error);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
return (
|
|
1762
|
+
<div
|
|
1763
|
+
key={index}
|
|
1764
|
+
{...stylex.props(
|
|
1765
|
+
spacing,
|
|
1766
|
+
contentWidthValue != null
|
|
1767
|
+
? dynamicStyles.proseWidth(contentWidthValue)
|
|
1768
|
+
: null,
|
|
1769
|
+
contentAlign !== 'start'
|
|
1770
|
+
? dynamicStyles.proseAlign(ALIGN_MARGIN[contentAlign])
|
|
1771
|
+
: null,
|
|
1772
|
+
isFirst && styles.noMarginBlockStart,
|
|
1773
|
+
isLast && styles.noMarginBlockEnd,
|
|
1774
|
+
)}>
|
|
1775
|
+
{content}
|
|
1776
|
+
</div>
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
case 'thematicBreak': {
|
|
1537
1780
|
const HrComp = components?.hr;
|
|
1538
1781
|
if (HrComp) {
|
|
1539
1782
|
return <HrComp key={index} />;
|
|
@@ -1554,7 +1797,7 @@ function renderBlock(
|
|
|
1554
1797
|
);
|
|
1555
1798
|
}
|
|
1556
1799
|
case 'image': {
|
|
1557
|
-
const safeSrc =
|
|
1800
|
+
const safeSrc = sanitizeMarkdownUrl(node.url);
|
|
1558
1801
|
if (safeSrc == null) {
|
|
1559
1802
|
return (
|
|
1560
1803
|
<div
|
|
@@ -1608,7 +1851,9 @@ function renderBlock(
|
|
|
1608
1851
|
* </Markdown>
|
|
1609
1852
|
* ```
|
|
1610
1853
|
*/
|
|
1611
|
-
export function Markdown
|
|
1854
|
+
export function Markdown<
|
|
1855
|
+
const Plugins extends ReadonlyArray<MarkdownPluginEntry> = readonly [],
|
|
1856
|
+
>({
|
|
1612
1857
|
ref,
|
|
1613
1858
|
children,
|
|
1614
1859
|
display = 'block',
|
|
@@ -1621,6 +1866,7 @@ export function Markdown({
|
|
|
1621
1866
|
contentWidth = 680,
|
|
1622
1867
|
contentAlign = 'start',
|
|
1623
1868
|
components,
|
|
1869
|
+
plugins,
|
|
1624
1870
|
inlinePlugins,
|
|
1625
1871
|
autolink,
|
|
1626
1872
|
xstyle,
|
|
@@ -1628,7 +1874,7 @@ export function Markdown({
|
|
|
1628
1874
|
style,
|
|
1629
1875
|
'data-testid': testId,
|
|
1630
1876
|
...props
|
|
1631
|
-
}: MarkdownProps): React.ReactElement {
|
|
1877
|
+
}: MarkdownProps<Plugins>): React.ReactElement {
|
|
1632
1878
|
const t = useTranslator();
|
|
1633
1879
|
const LinkComponent = useLinkComponent();
|
|
1634
1880
|
// Derive the set of source IDs for the parser (stable across renders when sources don't change)
|
|
@@ -1637,44 +1883,109 @@ export function Markdown({
|
|
|
1637
1883
|
[sources],
|
|
1638
1884
|
);
|
|
1639
1885
|
|
|
1640
|
-
const
|
|
1641
|
-
()
|
|
1642
|
-
|
|
1886
|
+
const preparedPlugins = useMemo(() => {
|
|
1887
|
+
if (plugins == null) {
|
|
1888
|
+
return undefined;
|
|
1889
|
+
}
|
|
1890
|
+
try {
|
|
1891
|
+
return prepareMarkdownPlugins(plugins);
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
reportMarkdownPluginFailure('configuration', 'transform', error);
|
|
1894
|
+
return undefined;
|
|
1895
|
+
}
|
|
1896
|
+
}, [plugins]);
|
|
1897
|
+
const syntaxPlugins = useStableMarkdownSyntaxEntries(preparedPlugins);
|
|
1898
|
+
const hasMathRenderer = components?.math != null;
|
|
1899
|
+
const legacyParseOptions = useMemo<
|
|
1900
|
+
ParseOptions<ReadonlyArray<MarkdownPluginEntry>>
|
|
1901
|
+
>(
|
|
1902
|
+
() => ({sourceIds, autolink, plugins: syntaxPlugins}),
|
|
1903
|
+
[sourceIds, autolink, syntaxPlugins],
|
|
1904
|
+
);
|
|
1905
|
+
const mathParseOptions = useMemo<
|
|
1906
|
+
MathParseOptions<ReadonlyArray<MarkdownPluginEntry>>
|
|
1907
|
+
>(
|
|
1908
|
+
() => ({sourceIds, autolink, math: true, plugins: syntaxPlugins}),
|
|
1909
|
+
[sourceIds, autolink, syntaxPlugins],
|
|
1643
1910
|
);
|
|
1644
1911
|
|
|
1645
1912
|
// Smooth bursty streamed chunks into a steady character-by-character reveal.
|
|
1646
1913
|
// When not streaming, the hook returns children unchanged (no-op).
|
|
1647
1914
|
const smoothedText = useStreamingText(children, isStreaming);
|
|
1648
1915
|
|
|
1649
|
-
const incrementalStateRef = useRef<IncrementalState
|
|
1650
|
-
createIncrementalState(),
|
|
1916
|
+
const incrementalStateRef = useRef<IncrementalState<boolean>>(
|
|
1917
|
+
createIncrementalState<boolean>(),
|
|
1651
1918
|
);
|
|
1652
|
-
// Reset incremental cache when
|
|
1653
|
-
// settled blocks were parsed with the previous setting.
|
|
1919
|
+
// Reset incremental cache when parser-affecting component options toggle —
|
|
1920
|
+
// cached settled blocks were parsed with the previous setting.
|
|
1654
1921
|
const prevAutolinkRef = useRef(autolink);
|
|
1655
|
-
|
|
1656
|
-
|
|
1922
|
+
const prevMathRef = useRef(hasMathRenderer);
|
|
1923
|
+
if (
|
|
1924
|
+
prevAutolinkRef.current !== autolink ||
|
|
1925
|
+
prevMathRef.current !== hasMathRenderer
|
|
1926
|
+
) {
|
|
1927
|
+
incrementalStateRef.current = createIncrementalState<boolean>();
|
|
1657
1928
|
prevAutolinkRef.current = autolink;
|
|
1929
|
+
prevMathRef.current = hasMathRenderer;
|
|
1658
1930
|
}
|
|
1659
1931
|
|
|
1660
|
-
const
|
|
1932
|
+
const parsedBlocks = useMemo(() => {
|
|
1661
1933
|
if (display === 'inline') {
|
|
1662
1934
|
return [];
|
|
1663
1935
|
}
|
|
1664
1936
|
if (isStreaming) {
|
|
1665
1937
|
if (smoothedText === '') {
|
|
1666
|
-
incrementalStateRef.current = createIncrementalState();
|
|
1938
|
+
incrementalStateRef.current = createIncrementalState<boolean>();
|
|
1667
1939
|
return [];
|
|
1668
1940
|
}
|
|
1669
|
-
const
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1941
|
+
const options = hasMathRenderer ? mathParseOptions : legacyParseOptions;
|
|
1942
|
+
const input = trimStreamingArtifacts(smoothedText, options);
|
|
1943
|
+
return (
|
|
1944
|
+
hasMathRenderer
|
|
1945
|
+
? parseMarkdownAstIncremental(
|
|
1946
|
+
input,
|
|
1947
|
+
incrementalStateRef.current,
|
|
1948
|
+
mathParseOptions,
|
|
1949
|
+
)
|
|
1950
|
+
: parseMarkdownAstIncremental(
|
|
1951
|
+
input,
|
|
1952
|
+
incrementalStateRef.current,
|
|
1953
|
+
legacyParseOptions,
|
|
1954
|
+
)
|
|
1955
|
+
).children;
|
|
1675
1956
|
}
|
|
1676
|
-
return
|
|
1677
|
-
|
|
1957
|
+
return (
|
|
1958
|
+
hasMathRenderer
|
|
1959
|
+
? parseMarkdownAst(children, mathParseOptions)
|
|
1960
|
+
: parseMarkdownAst(children, legacyParseOptions)
|
|
1961
|
+
).children;
|
|
1962
|
+
}, [
|
|
1963
|
+
display,
|
|
1964
|
+
smoothedText,
|
|
1965
|
+
children,
|
|
1966
|
+
isStreaming,
|
|
1967
|
+
hasMathRenderer,
|
|
1968
|
+
mathParseOptions,
|
|
1969
|
+
legacyParseOptions,
|
|
1970
|
+
]);
|
|
1971
|
+
|
|
1972
|
+
const transformSource = isStreaming
|
|
1973
|
+
? trimStreamingArtifacts(
|
|
1974
|
+
smoothedText,
|
|
1975
|
+
hasMathRenderer ? mathParseOptions : legacyParseOptions,
|
|
1976
|
+
)
|
|
1977
|
+
: children;
|
|
1978
|
+
const blocks = useMemo(
|
|
1979
|
+
() =>
|
|
1980
|
+
applyMarkdownTransforms(
|
|
1981
|
+
{type: 'root', children: parsedBlocks},
|
|
1982
|
+
preparedPlugins,
|
|
1983
|
+
transformSource,
|
|
1984
|
+
!isStreaming,
|
|
1985
|
+
'block',
|
|
1986
|
+
).children,
|
|
1987
|
+
[parsedBlocks, preparedPlugins, transformSource, isStreaming],
|
|
1988
|
+
);
|
|
1678
1989
|
|
|
1679
1990
|
// Assign each top-level heading the slug that parseOutlineFromMarkdown
|
|
1680
1991
|
// would derive for it, so Outline hash links built from the same source
|
|
@@ -1686,24 +1997,71 @@ export function Markdown({
|
|
|
1686
1997
|
if (display === 'inline' || blocks.length === 0) {
|
|
1687
1998
|
return undefined;
|
|
1688
1999
|
}
|
|
1689
|
-
const map = new Map<
|
|
2000
|
+
const map = new Map<RenderBlockNode, string>();
|
|
1690
2001
|
const counts = new Map<string, number>();
|
|
1691
2002
|
for (const block of blocks) {
|
|
1692
2003
|
if (block.type === 'heading') {
|
|
1693
|
-
const label =
|
|
2004
|
+
const label = markdownAstText(block.children, node =>
|
|
2005
|
+
markdownExtensionText(preparedPlugins, node),
|
|
2006
|
+
).trim();
|
|
1694
2007
|
map.set(block, uniqueSlug(slugify(label), counts));
|
|
1695
2008
|
}
|
|
1696
2009
|
}
|
|
1697
2010
|
return map;
|
|
1698
|
-
}, [display, blocks]);
|
|
2011
|
+
}, [display, blocks, preparedPlugins]);
|
|
1699
2012
|
|
|
2013
|
+
const parsedInlineNodes = useMemo(() => {
|
|
2014
|
+
if (display !== 'inline') {
|
|
2015
|
+
return [];
|
|
2016
|
+
}
|
|
2017
|
+
const options = hasMathRenderer ? mathParseOptions : legacyParseOptions;
|
|
2018
|
+
const input = isStreaming
|
|
2019
|
+
? trimStreamingArtifacts(smoothedText, options)
|
|
2020
|
+
: children;
|
|
2021
|
+
return hasMathRenderer
|
|
2022
|
+
? parseInlineAst(input, mathParseOptions)
|
|
2023
|
+
: parseInlineAst(input, legacyParseOptions);
|
|
2024
|
+
}, [
|
|
2025
|
+
display,
|
|
2026
|
+
smoothedText,
|
|
2027
|
+
children,
|
|
2028
|
+
isStreaming,
|
|
2029
|
+
hasMathRenderer,
|
|
2030
|
+
mathParseOptions,
|
|
2031
|
+
legacyParseOptions,
|
|
2032
|
+
]);
|
|
2033
|
+
|
|
2034
|
+
const inlineTransformSource = isStreaming
|
|
2035
|
+
? trimStreamingArtifacts(
|
|
2036
|
+
smoothedText,
|
|
2037
|
+
hasMathRenderer ? mathParseOptions : legacyParseOptions,
|
|
2038
|
+
)
|
|
2039
|
+
: children;
|
|
1700
2040
|
const inlineNodes = useMemo(() => {
|
|
1701
2041
|
if (display !== 'inline') {
|
|
1702
2042
|
return [];
|
|
1703
2043
|
}
|
|
1704
|
-
const
|
|
1705
|
-
|
|
1706
|
-
|
|
2044
|
+
const transformed = applyMarkdownTransforms(
|
|
2045
|
+
{
|
|
2046
|
+
type: 'root',
|
|
2047
|
+
children: [{type: 'paragraph', children: parsedInlineNodes}],
|
|
2048
|
+
},
|
|
2049
|
+
preparedPlugins,
|
|
2050
|
+
inlineTransformSource,
|
|
2051
|
+
!isStreaming,
|
|
2052
|
+
'inline',
|
|
2053
|
+
);
|
|
2054
|
+
const paragraph = transformed.children[0];
|
|
2055
|
+
return paragraph?.type === 'paragraph'
|
|
2056
|
+
? [...paragraph.children]
|
|
2057
|
+
: parsedInlineNodes;
|
|
2058
|
+
}, [
|
|
2059
|
+
display,
|
|
2060
|
+
parsedInlineNodes,
|
|
2061
|
+
preparedPlugins,
|
|
2062
|
+
inlineTransformSource,
|
|
2063
|
+
isStreaming,
|
|
2064
|
+
]);
|
|
1707
2065
|
|
|
1708
2066
|
// Track recent boundaries for stacked fade-in animation.
|
|
1709
2067
|
// The number of spans needed = ceil(animationDuration / tickInterval).
|
|
@@ -1719,8 +2077,8 @@ export function Markdown({
|
|
|
1719
2077
|
return Math.min(Math.ceil(duration / tickMs), 12);
|
|
1720
2078
|
}, [token]);
|
|
1721
2079
|
|
|
1722
|
-
const prevBlocksRef = useRef<
|
|
1723
|
-
const prevInlineNodesRef = useRef<
|
|
2080
|
+
const prevBlocksRef = useRef<ReadonlyArray<RenderBlockNode>>([]);
|
|
2081
|
+
const prevInlineNodesRef = useRef<ReadonlyArray<RenderInlineNode>>([]);
|
|
1724
2082
|
const boundariesRef = useRef<number[]>([]);
|
|
1725
2083
|
const smoothedLen = smoothedText.length;
|
|
1726
2084
|
const boundaries = useMemo(() => {
|
|
@@ -1769,6 +2127,7 @@ export function Markdown({
|
|
|
1769
2127
|
LinkComponent,
|
|
1770
2128
|
inlinePlugins,
|
|
1771
2129
|
components,
|
|
2130
|
+
preparedPlugins,
|
|
1772
2131
|
),
|
|
1773
2132
|
)}
|
|
1774
2133
|
</span>
|
|
@@ -1814,6 +2173,7 @@ export function Markdown({
|
|
|
1814
2173
|
LinkComponent,
|
|
1815
2174
|
inlinePlugins,
|
|
1816
2175
|
components,
|
|
2176
|
+
preparedPlugins,
|
|
1817
2177
|
t,
|
|
1818
2178
|
headingIdMap,
|
|
1819
2179
|
),
|