@terra-symposium/docs 0.0.0-stage → 0.1.71
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/LICENSE +201 -0
- package/README.md +72 -2
- package/lib-dist/assets/fonts/AUTHENTICSans-130.woff +0 -0
- package/lib-dist/assets/fonts/AUTHENTICSans-130.woff2 +0 -0
- package/lib-dist/assets/fonts/AUTHENTICSans-150.woff +0 -0
- package/lib-dist/assets/fonts/AUTHENTICSans-150.woff2 +0 -0
- package/lib-dist/assets/fonts/AUTHENTICSans-90.woff +0 -0
- package/lib-dist/assets/fonts/AUTHENTICSans-90.woff2 +0 -0
- package/lib-dist/assets/fonts/Avara-Bold.woff +0 -0
- package/lib-dist/assets/fonts/Avara-Bold.woff2 +0 -0
- package/lib-dist/assets/fonts/Avara-BoldItalic.woff +0 -0
- package/lib-dist/assets/fonts/Avara-BoldItalic.woff2 +0 -0
- package/lib-dist/assets/fonts/InterVariable-Italic-latin-20260728.woff2 +0 -0
- package/lib-dist/assets/fonts/InterVariable-latin-20260728.woff2 +0 -0
- package/lib-dist/assets/fonts/OFL-1.1.txt +93 -0
- package/lib-dist/assets/fonts/README.md +7 -0
- package/lib-dist/assets/fonts/licenses/Inter-OFL-1.1.txt +92 -0
- package/lib-dist/assets/fonts/licenses/OFL-1.1.txt +93 -0
- package/lib-dist/components/Docs.d.ts +22 -0
- package/lib-dist/components/Docs.d.ts.map +1 -0
- package/lib-dist/components/Docs.js +9 -0
- package/lib-dist/components/Docs.js.map +1 -0
- package/lib-dist/components/LocalDocsWorkspace.d.ts +37 -0
- package/lib-dist/components/LocalDocsWorkspace.d.ts.map +1 -0
- package/lib-dist/components/LocalDocsWorkspace.js +207 -0
- package/lib-dist/components/LocalDocsWorkspace.js.map +1 -0
- package/lib-dist/components/NotFound.d.ts +8 -0
- package/lib-dist/components/NotFound.d.ts.map +1 -0
- package/lib-dist/components/NotFound.js +6 -0
- package/lib-dist/components/NotFound.js.map +1 -0
- package/lib-dist/components/SiteLayout.d.ts +25 -0
- package/lib-dist/components/SiteLayout.d.ts.map +1 -0
- package/lib-dist/components/SiteLayout.js +12 -0
- package/lib-dist/components/SiteLayout.js.map +1 -0
- package/lib-dist/components/StaticWorkspace.d.ts +25 -0
- package/lib-dist/components/StaticWorkspace.d.ts.map +1 -0
- package/lib-dist/components/StaticWorkspace.js +245 -0
- package/lib-dist/components/StaticWorkspace.js.map +1 -0
- package/lib-dist/components/StrictLocalWorkspaceHarness.d.ts +10 -0
- package/lib-dist/components/StrictLocalWorkspaceHarness.d.ts.map +1 -0
- package/lib-dist/components/StrictLocalWorkspaceHarness.js +12 -0
- package/lib-dist/components/StrictLocalWorkspaceHarness.js.map +1 -0
- package/lib-dist/components/blocks.d.ts +23 -0
- package/lib-dist/components/blocks.d.ts.map +1 -0
- package/lib-dist/components/blocks.js +396 -0
- package/lib-dist/components/blocks.js.map +1 -0
- package/lib-dist/components/navigation.d.ts +18 -0
- package/lib-dist/components/navigation.d.ts.map +1 -0
- package/lib-dist/components/navigation.js +9 -0
- package/lib-dist/components/navigation.js.map +1 -0
- package/lib-dist/components/terra/Workspace.d.ts +5 -0
- package/lib-dist/components/terra/Workspace.d.ts.map +1 -0
- package/lib-dist/components/terra/Workspace.js +190 -0
- package/lib-dist/components/terra/Workspace.js.map +1 -0
- package/lib-dist/components/terra/editor/TerraContentEditor.d.ts +15 -0
- package/lib-dist/components/terra/editor/TerraContentEditor.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/TerraContentEditor.js +358 -0
- package/lib-dist/components/terra/editor/TerraContentEditor.js.map +1 -0
- package/lib-dist/components/terra/editor/_blocks.d.ts +70 -0
- package/lib-dist/components/terra/editor/_blocks.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/_blocks.js +120 -0
- package/lib-dist/components/terra/editor/_blocks.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/BlockContext.d.ts +7 -0
- package/lib-dist/components/terra/editor/docs/BlockContext.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/BlockContext.js +60 -0
- package/lib-dist/components/terra/editor/docs/BlockContext.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/DocsAttachmentPicker.d.ts +12 -0
- package/lib-dist/components/terra/editor/docs/DocsAttachmentPicker.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/DocsAttachmentPicker.js +66 -0
- package/lib-dist/components/terra/editor/docs/DocsAttachmentPicker.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/TerraToolbar.d.ts +12 -0
- package/lib-dist/components/terra/editor/docs/TerraToolbar.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/TerraToolbar.js +169 -0
- package/lib-dist/components/terra/editor/docs/TerraToolbar.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Callout.d.ts +14 -0
- package/lib-dist/components/terra/editor/docs/_Callout.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Callout.js +45 -0
- package/lib-dist/components/terra/editor/docs/_Callout.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_CodeBlock.d.ts +39 -0
- package/lib-dist/components/terra/editor/docs/_CodeBlock.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_CodeBlock.js +560 -0
- package/lib-dist/components/terra/editor/docs/_CodeBlock.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_DocsCode.d.ts +14 -0
- package/lib-dist/components/terra/editor/docs/_DocsCode.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_DocsCode.js +128 -0
- package/lib-dist/components/terra/editor/docs/_DocsCode.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_DocsImage.d.ts +18 -0
- package/lib-dist/components/terra/editor/docs/_DocsImage.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_DocsImage.js +62 -0
- package/lib-dist/components/terra/editor/docs/_DocsImage.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Embed.d.ts +17 -0
- package/lib-dist/components/terra/editor/docs/_Embed.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Embed.js +73 -0
- package/lib-dist/components/terra/editor/docs/_Embed.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Math.d.ts +31 -0
- package/lib-dist/components/terra/editor/docs/_Math.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Math.js +138 -0
- package/lib-dist/components/terra/editor/docs/_Math.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Nodes.d.ts +15 -0
- package/lib-dist/components/terra/editor/docs/_Nodes.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Nodes.js +62 -0
- package/lib-dist/components/terra/editor/docs/_Nodes.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Raw.d.ts +15 -0
- package/lib-dist/components/terra/editor/docs/_Raw.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Raw.js +31 -0
- package/lib-dist/components/terra/editor/docs/_Raw.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Table.d.ts +12 -0
- package/lib-dist/components/terra/editor/docs/_Table.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Table.js +99 -0
- package/lib-dist/components/terra/editor/docs/_Table.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Tabs.d.ts +14 -0
- package/lib-dist/components/terra/editor/docs/_Tabs.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_Tabs.js +125 -0
- package/lib-dist/components/terra/editor/docs/_Tabs.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_TsDocs.d.ts +15 -0
- package/lib-dist/components/terra/editor/docs/_TsDocs.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_TsDocs.js +83 -0
- package/lib-dist/components/terra/editor/docs/_TsDocs.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_math_inline.d.ts +14 -0
- package/lib-dist/components/terra/editor/docs/_math_inline.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/_math_inline.js +30 -0
- package/lib-dist/components/terra/editor/docs/_math_inline.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/heading.d.ts +23 -0
- package/lib-dist/components/terra/editor/docs/heading.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/heading.js +63 -0
- package/lib-dist/components/terra/editor/docs/heading.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/index.d.ts +13 -0
- package/lib-dist/components/terra/editor/docs/index.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/docs/index.js +14 -0
- package/lib-dist/components/terra/editor/docs/index.js.map +1 -0
- package/lib-dist/components/terra/editor/terra-content.styles.d.ts +3 -0
- package/lib-dist/components/terra/editor/terra-content.styles.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/terra-content.styles.js +3 -0
- package/lib-dist/components/terra/editor/terra-content.styles.js.map +1 -0
- package/lib-dist/components/terra/editor/types.d.ts +40 -0
- package/lib-dist/components/terra/editor/types.d.ts.map +1 -0
- package/lib-dist/components/terra/editor/types.js +11 -0
- package/lib-dist/components/terra/editor/types.js.map +1 -0
- package/lib-dist/components/terra/flow/Nodes.d.ts +78 -0
- package/lib-dist/components/terra/flow/Nodes.d.ts.map +1 -0
- package/lib-dist/components/terra/flow/Nodes.js +1019 -0
- package/lib-dist/components/terra/flow/Nodes.js.map +1 -0
- package/lib-dist/components/terra/flow/_chrome.d.ts +267 -0
- package/lib-dist/components/terra/flow/_chrome.d.ts.map +1 -0
- package/lib-dist/components/terra/flow/_chrome.js +1009 -0
- package/lib-dist/components/terra/flow/_chrome.js.map +1 -0
- package/lib-dist/components/terra/flow/menuContext.d.ts +13 -0
- package/lib-dist/components/terra/flow/menuContext.d.ts.map +1 -0
- package/lib-dist/components/terra/flow/menuContext.js +10 -0
- package/lib-dist/components/terra/flow/menuContext.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_attachments.d.ts +20 -0
- package/lib-dist/components/terra/pages/docs/_attachments.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_attachments.js +1148 -0
- package/lib-dist/components/terra/pages/docs/_attachments.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_blocks.d.ts +48 -0
- package/lib-dist/components/terra/pages/docs/_blocks.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_blocks.js +203 -0
- package/lib-dist/components/terra/pages/docs/_blocks.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_editable.d.ts +61 -0
- package/lib-dist/components/terra/pages/docs/_editable.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_editable.js +442 -0
- package/lib-dist/components/terra/pages/docs/_editable.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_export-dialog.d.ts +19 -0
- package/lib-dist/components/terra/pages/docs/_export-dialog.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_export-dialog.js +78 -0
- package/lib-dist/components/terra/pages/docs/_export-dialog.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_icons.d.ts +20 -0
- package/lib-dist/components/terra/pages/docs/_icons.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_icons.js +29 -0
- package/lib-dist/components/terra/pages/docs/_icons.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_main.d.ts +54 -0
- package/lib-dist/components/terra/pages/docs/_main.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_main.js +710 -0
- package/lib-dist/components/terra/pages/docs/_main.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_manage-attachments-dialog.d.ts +10 -0
- package/lib-dist/components/terra/pages/docs/_manage-attachments-dialog.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_manage-attachments-dialog.js +597 -0
- package/lib-dist/components/terra/pages/docs/_manage-attachments-dialog.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_sidebar.d.ts +33 -0
- package/lib-dist/components/terra/pages/docs/_sidebar.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_sidebar.js +1437 -0
- package/lib-dist/components/terra/pages/docs/_sidebar.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_toc.d.ts +4 -0
- package/lib-dist/components/terra/pages/docs/_toc.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_toc.js +212 -0
- package/lib-dist/components/terra/pages/docs/_toc.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_toolbar-find-replace.d.ts +15 -0
- package/lib-dist/components/terra/pages/docs/_toolbar-find-replace.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_toolbar-find-replace.js +207 -0
- package/lib-dist/components/terra/pages/docs/_toolbar-find-replace.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_toolbar.d.ts +22 -0
- package/lib-dist/components/terra/pages/docs/_toolbar.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_toolbar.js +1508 -0
- package/lib-dist/components/terra/pages/docs/_toolbar.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_tree.d.ts +178 -0
- package/lib-dist/components/terra/pages/docs/_tree.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_tree.js +1123 -0
- package/lib-dist/components/terra/pages/docs/_tree.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/use-block-nav.d.ts +5 -0
- package/lib-dist/components/terra/pages/docs/use-block-nav.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/use-block-nav.js +1306 -0
- package/lib-dist/components/terra/pages/docs/use-block-nav.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/use-media-paste.d.ts +5 -0
- package/lib-dist/components/terra/pages/docs/use-media-paste.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/use-media-paste.js +391 -0
- package/lib-dist/components/terra/pages/docs/use-media-paste.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/use-render-tripwire.d.ts +2 -0
- package/lib-dist/components/terra/pages/docs/use-render-tripwire.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/use-render-tripwire.js +25 -0
- package/lib-dist/components/terra/pages/docs/use-render-tripwire.js.map +1 -0
- package/lib-dist/components/terra/ui.d.ts +28 -0
- package/lib-dist/components/terra/ui.d.ts.map +1 -0
- package/lib-dist/components/terra/ui.js +20 -0
- package/lib-dist/components/terra/ui.js.map +1 -0
- package/lib-dist/components/workspace-demo-fixture.d.ts +35 -0
- package/lib-dist/components/workspace-demo-fixture.d.ts.map +1 -0
- package/lib-dist/components/workspace-demo-fixture.js +820 -0
- package/lib-dist/components/workspace-demo-fixture.js.map +1 -0
- package/lib-dist/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/lib-dist/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/lib-dist/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Main-Bold.woff +0 -0
- package/lib-dist/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/lib-dist/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Main-Italic.woff +0 -0
- package/lib-dist/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Main-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/lib-dist/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Math-Italic.woff +0 -0
- package/lib-dist/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Script-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/lib-dist/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/lib-dist/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/lib-dist/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/lib-dist/lib/astro.d.ts +8 -0
- package/lib-dist/lib/astro.d.ts.map +1 -0
- package/lib-dist/lib/astro.js +27 -0
- package/lib-dist/lib/astro.js.map +1 -0
- package/lib-dist/lib/docs-catalog.d.ts +5 -0
- package/lib-dist/lib/docs-catalog.d.ts.map +1 -0
- package/lib-dist/lib/docs-catalog.js +75 -0
- package/lib-dist/lib/docs-catalog.js.map +1 -0
- package/lib-dist/lib/docs-theme.d.ts +7 -0
- package/lib-dist/lib/docs-theme.d.ts.map +1 -0
- package/lib-dist/lib/docs-theme.js +51 -0
- package/lib-dist/lib/docs-theme.js.map +1 -0
- package/lib-dist/lib/host-runtime.d.ts +5 -0
- package/lib-dist/lib/host-runtime.d.ts.map +1 -0
- package/lib-dist/lib/host-runtime.js +23 -0
- package/lib-dist/lib/host-runtime.js.map +1 -0
- package/lib-dist/lib/host.d.ts +190 -0
- package/lib-dist/lib/host.d.ts.map +1 -0
- package/lib-dist/lib/host.js +2 -0
- package/lib-dist/lib/host.js.map +1 -0
- package/lib-dist/lib/index.d.ts +34 -0
- package/lib-dist/lib/index.d.ts.map +1 -0
- package/lib-dist/lib/index.js +27 -0
- package/lib-dist/lib/index.js.map +1 -0
- package/lib-dist/lib/markdown-model.d.ts +7 -0
- package/lib-dist/lib/markdown-model.d.ts.map +1 -0
- package/lib-dist/lib/markdown-model.js +2031 -0
- package/lib-dist/lib/markdown-model.js.map +1 -0
- package/lib-dist/lib/markdown.d.ts +3 -0
- package/lib-dist/lib/markdown.d.ts.map +1 -0
- package/lib-dist/lib/markdown.js +222 -0
- package/lib-dist/lib/markdown.js.map +1 -0
- package/lib-dist/lib/schema.d.ts +167 -0
- package/lib-dist/lib/schema.d.ts.map +1 -0
- package/lib-dist/lib/schema.js +63 -0
- package/lib-dist/lib/schema.js.map +1 -0
- package/lib-dist/lib/terra/custom-components.d.ts +5 -0
- package/lib-dist/lib/terra/custom-components.d.ts.map +1 -0
- package/lib-dist/lib/terra/custom-components.js +6 -0
- package/lib-dist/lib/terra/custom-components.js.map +1 -0
- package/lib-dist/lib/terra/docs/archive.d.ts +3 -0
- package/lib-dist/lib/terra/docs/archive.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/archive.js +654 -0
- package/lib-dist/lib/terra/docs/archive.js.map +1 -0
- package/lib-dist/lib/terra/docs/assets.d.ts +26 -0
- package/lib-dist/lib/terra/docs/assets.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/assets.js +74 -0
- package/lib-dist/lib/terra/docs/assets.js.map +1 -0
- package/lib-dist/lib/terra/docs/attachments.d.ts +4 -0
- package/lib-dist/lib/terra/docs/attachments.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/attachments.js +5 -0
- package/lib-dist/lib/terra/docs/attachments.js.map +1 -0
- package/lib-dist/lib/terra/docs/editor-dom.d.ts +50 -0
- package/lib-dist/lib/terra/docs/editor-dom.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/editor-dom.js +236 -0
- package/lib-dist/lib/terra/docs/editor-dom.js.map +1 -0
- package/lib-dist/lib/terra/docs/mutations.d.ts +10 -0
- package/lib-dist/lib/terra/docs/mutations.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/mutations.js +105 -0
- package/lib-dist/lib/terra/docs/mutations.js.map +1 -0
- package/lib-dist/lib/terra/docs/navigation.d.ts +6 -0
- package/lib-dist/lib/terra/docs/navigation.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/navigation.js +21 -0
- package/lib-dist/lib/terra/docs/navigation.js.map +1 -0
- package/lib-dist/lib/terra/docs/state.d.ts +186 -0
- package/lib-dist/lib/terra/docs/state.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/state.js +255 -0
- package/lib-dist/lib/terra/docs/state.js.map +1 -0
- package/lib-dist/lib/terra/docs/toolbar.d.ts +11 -0
- package/lib-dist/lib/terra/docs/toolbar.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/toolbar.js +41 -0
- package/lib-dist/lib/terra/docs/toolbar.js.map +1 -0
- package/lib-dist/lib/terra/docs/tree.d.ts +15 -0
- package/lib-dist/lib/terra/docs/tree.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/tree.js +61 -0
- package/lib-dist/lib/terra/docs/tree.js.map +1 -0
- package/lib-dist/lib/terra/docs/workspace.d.ts +396 -0
- package/lib-dist/lib/terra/docs/workspace.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/workspace.js +923 -0
- package/lib-dist/lib/terra/docs/workspace.js.map +1 -0
- package/lib-dist/lib/terra/flow/_layout.d.ts +30 -0
- package/lib-dist/lib/terra/flow/_layout.d.ts.map +1 -0
- package/lib-dist/lib/terra/flow/_layout.js +63 -0
- package/lib-dist/lib/terra/flow/_layout.js.map +1 -0
- package/lib-dist/lib/terra/flow/powerSource.d.ts +16 -0
- package/lib-dist/lib/terra/flow/powerSource.d.ts.map +1 -0
- package/lib-dist/lib/terra/flow/powerSource.js +65 -0
- package/lib-dist/lib/terra/flow/powerSource.js.map +1 -0
- package/lib-dist/lib/terra/regex.mjs +403 -0
- package/lib-dist/lib/terra/runtime/action-proxy.d.ts +45 -0
- package/lib-dist/lib/terra/runtime/action-proxy.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/action-proxy.js +19 -0
- package/lib-dist/lib/terra/runtime/action-proxy.js.map +1 -0
- package/lib-dist/lib/terra/runtime/content/block-jsx.d.ts +23 -0
- package/lib-dist/lib/terra/runtime/content/block-jsx.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/content/block-jsx.js +225 -0
- package/lib-dist/lib/terra/runtime/content/block-jsx.js.map +1 -0
- package/lib-dist/lib/terra/runtime/content/document-markdown.d.ts +41 -0
- package/lib-dist/lib/terra/runtime/content/document-markdown.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/content/document-markdown.js +536 -0
- package/lib-dist/lib/terra/runtime/content/document-markdown.js.map +1 -0
- package/lib-dist/lib/terra/runtime/content/markdown.d.ts +109 -0
- package/lib-dist/lib/terra/runtime/content/markdown.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/content/markdown.js +428 -0
- package/lib-dist/lib/terra/runtime/content/markdown.js.map +1 -0
- package/lib-dist/lib/terra/runtime/content.d.ts +150 -0
- package/lib-dist/lib/terra/runtime/content.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/content.js +1256 -0
- package/lib-dist/lib/terra/runtime/content.js.map +1 -0
- package/lib-dist/lib/terra/runtime/leaf.d.ts +5 -0
- package/lib-dist/lib/terra/runtime/leaf.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/leaf.js +13 -0
- package/lib-dist/lib/terra/runtime/leaf.js.map +1 -0
- package/lib-dist/lib/terra/runtime/nodes.d.ts +112 -0
- package/lib-dist/lib/terra/runtime/nodes.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/nodes.js +606 -0
- package/lib-dist/lib/terra/runtime/nodes.js.map +1 -0
- package/lib-dist/lib/terra/runtime/runtime.d.ts +31 -0
- package/lib-dist/lib/terra/runtime/runtime.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/runtime.js +86 -0
- package/lib-dist/lib/terra/runtime/runtime.js.map +1 -0
- package/lib-dist/lib/terra/runtime/shared.d.ts +35 -0
- package/lib-dist/lib/terra/runtime/shared.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/shared.js +124 -0
- package/lib-dist/lib/terra/runtime/shared.js.map +1 -0
- package/lib-dist/lib/terra/runtime/types.d.ts +529 -0
- package/lib-dist/lib/terra/runtime/types.d.ts.map +1 -0
- package/lib-dist/lib/terra/runtime/types.js +44 -0
- package/lib-dist/lib/terra/runtime/types.js.map +1 -0
- package/lib-dist/lib/workspace-types.d.ts +29 -0
- package/lib-dist/lib/workspace-types.d.ts.map +1 -0
- package/lib-dist/lib/workspace-types.js +2 -0
- package/lib-dist/lib/workspace-types.js.map +1 -0
- package/lib-dist/style.css +237 -0
- package/package.json +108 -3
- package/src/components/NotFound.astro +6 -0
- package/src/components/NotFound.tsx +48 -0
- package/src/components/SiteLayout.astro +42 -0
- package/src/components/navigation.tsx +48 -0
|
@@ -0,0 +1,1508 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// @ts-nocheck -- source-preserving extraction; public host contracts are typed in src/host.ts.
|
|
3
|
+
/**
|
|
4
|
+
* astro/src/pages/docs/_toolbar.tsx
|
|
5
|
+
* Shared UI components, toolbars, and context menus for the visual MDX Editor.
|
|
6
|
+
* Folder boundary: editor chrome and commands; document state, block rendering, and navigation remain outside this module.
|
|
7
|
+
*/
|
|
8
|
+
/*//////////////////////////////////////////////////
|
|
9
|
+
IMPORTS
|
|
10
|
+
//////////////////////////////////////////////////*/
|
|
11
|
+
// API
|
|
12
|
+
import { clearTransition, setTransition, useAtom } from '../../../../lib/terra/docs/state.js';
|
|
13
|
+
import { $attachmentFilter } from '../../../../lib/terra/docs/attachments.js';
|
|
14
|
+
import { codeViewOf } from '../../../../lib/terra/docs/editor-dom.js';
|
|
15
|
+
import { FindReplace } from './_toolbar-find-replace.js';
|
|
16
|
+
import { toolbarFocusValue } from '../../../../lib/terra/docs/toolbar.js';
|
|
17
|
+
import { isDocBody } from '../../../../lib/terra/runtime/types.js';
|
|
18
|
+
import { $createHeadingNode } from '@lexical/rich-text';
|
|
19
|
+
import { $createParagraphNode, $getNearestNodeFromDOMNode, $isElementNode } from 'lexical';
|
|
20
|
+
import { actions } from '../../../../lib/terra/runtime/action-proxy.js';
|
|
21
|
+
import { docsRootPath } from '../../../../lib/terra/runtime/shared.js';
|
|
22
|
+
import { cn, notify } from '../../../../lib/terra/runtime/runtime.js';
|
|
23
|
+
import { components as repoComponents } from '../../../../lib/terra/custom-components.js';
|
|
24
|
+
import { dirname, toPascal } from '../../../../lib/terra/runtime/shared.js';
|
|
25
|
+
import { redo, redoDepth, undo, undoDepth } from '@codemirror/commands';
|
|
26
|
+
import { stringifyDocBody } from '../../../../lib/terra/runtime/content/document-markdown.js';
|
|
27
|
+
import { toast } from 'sonner';
|
|
28
|
+
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
29
|
+
import { getDocsHostAdapter } from '../../../../lib/host-runtime.js';
|
|
30
|
+
// COMPONENTS
|
|
31
|
+
import * as TerraQualityRegex from '../../../../lib/terra/regex.mjs';
|
|
32
|
+
import React from 'react';
|
|
33
|
+
import { TextTooltip, TooltipProvider } from '@terra-symposium/styling/ui/terra-original';
|
|
34
|
+
import { ArrowUpRight as PopOutIcon, CaretDown as CaretDownIcon, DataShare as ShareIcon, Image as ImageIcon, Link as LinkIcon, ListBulleted as BulletListIcon, ListNumbered as NumberListIcon, Paste as PasteIcon, Redo as RedoIcon, RepoSourceCode as ModuleIcon, Search as SearchIcon, Task as CheckListIcon, TextBold as BoldIcon, TextFont as TextFontIcon, TextItalic as ItalicIcon, TextStrikethrough as StrikethroughIcon, TextUnderline as UnderlineIcon, Undo as UndoIcon, } from '@carbon/icons-react';
|
|
35
|
+
import { Button as UiButton } from '@terra-symposium/styling/ui/terra-original';
|
|
36
|
+
import { ButtonGroup } from '@terra-symposium/styling/ui/terra-original';
|
|
37
|
+
import { CodeBlock } from '../../editor/docs/_CodeBlock.js';
|
|
38
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@terra-symposium/styling/ui/terra-original';
|
|
39
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@terra-symposium/styling/ui/terra-original';
|
|
40
|
+
import { EditStateCtx } from '../../../../lib/terra/docs/state.js';
|
|
41
|
+
import { ErrorBoundary } from '../../ui.js';
|
|
42
|
+
import { Input } from '@terra-symposium/styling/ui/terra-original';
|
|
43
|
+
import { Label } from '@terra-symposium/styling/ui/terra-original';
|
|
44
|
+
import { Popover, PopoverContent, PopoverTrigger, } from '@terra-symposium/styling/ui/terra-original';
|
|
45
|
+
import { SearchEmpty } from '@terra-symposium/styling/ui/terra-original';
|
|
46
|
+
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, } from '@terra-symposium/styling/ui/terra-original';
|
|
47
|
+
import { ToggleGroup, ToggleGroupItem } from '@terra-symposium/styling/ui/terra-original';
|
|
48
|
+
// DATA
|
|
49
|
+
import { ATTCH_TYPES } from '../../../../lib/terra/docs/attachments.js';
|
|
50
|
+
import { DOCS_BLOCK_ICONS } from './_icons.js';
|
|
51
|
+
import { EMPTY_NODES } from '../../../../lib/terra/runtime/nodes.js';
|
|
52
|
+
/*//////////////////////////////////////////////////
|
|
53
|
+
CONFIG
|
|
54
|
+
//////////////////////////////////////////////////*/
|
|
55
|
+
const FIND_WORD = String.raw `[\p{L}\p{N}_]`;
|
|
56
|
+
const BLIND_REGEX = [
|
|
57
|
+
/<[^>]+>/gu,
|
|
58
|
+
/^[\t ]*(?:#{1,6}|>+|[-+*]|\d+\.)[\t ]*/gmu,
|
|
59
|
+
/:::[^\n]*|===|\|\|\|/gu,
|
|
60
|
+
/[`*_~[\]()]/gu,
|
|
61
|
+
/\s+/gu,
|
|
62
|
+
];
|
|
63
|
+
const ICON_CLASS = 'size-4 text-primary-11';
|
|
64
|
+
const SECONDARY_TEXT_CLASS = 'text-xs text-primary-11';
|
|
65
|
+
const MENU_GAP_CLASS = 'gap-2';
|
|
66
|
+
const FIELD_GAP_CLASS = 'space-y-1.5';
|
|
67
|
+
const MENU_WIDTH_CLASS = 'w-56';
|
|
68
|
+
/*//////////////////////////////////////////////////
|
|
69
|
+
HELPERS
|
|
70
|
+
//////////////////////////////////////////////////*/
|
|
71
|
+
/**
|
|
72
|
+
* Handles code offset.
|
|
73
|
+
*
|
|
74
|
+
* @param content - Docs content containing the target block.
|
|
75
|
+
* @param node - Block node being transformed by the toolbar action.
|
|
76
|
+
* @param value - Replacement value supplied by the toolbar control.
|
|
77
|
+
*/
|
|
78
|
+
const codeOffset = (content, node, val) => {
|
|
79
|
+
if (!content.contains(node))
|
|
80
|
+
return content.innerText.length;
|
|
81
|
+
const range = document.createRange();
|
|
82
|
+
range.selectNodeContents(content);
|
|
83
|
+
range.setEnd(node, val);
|
|
84
|
+
return range.toString().length;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Derives a clean, human-readable label from a raw filename or URL.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} v - The raw filename or path string.
|
|
90
|
+
* @param {string} fallback - The default label if extraction fails.
|
|
91
|
+
*
|
|
92
|
+
* @return {string} The formatted, capitalized label.
|
|
93
|
+
*/
|
|
94
|
+
/**
|
|
95
|
+
* Prevents the active selection range from being lost when clicking toolbar buttons.
|
|
96
|
+
*
|
|
97
|
+
* @param {MouseEvent<HTMLElement>} e - The triggered mouse event.
|
|
98
|
+
*
|
|
99
|
+
* @return Nothing; the browser's default mouse action is cancelled so toolbar commands retain the editor selection.
|
|
100
|
+
*/
|
|
101
|
+
function preserveSel(e) {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Constructs a Regular Expression used to power the Find & Replace feature.
|
|
106
|
+
*
|
|
107
|
+
* @param cfg - Search options controlling markup removal, case sensitivity, the query text, and whole-word matching.
|
|
108
|
+
* @param {boolean} cfg.blind - Whether to ignore markdown syntax.
|
|
109
|
+
* @param {boolean} cfg.case - Whether the search is case-sensitive.
|
|
110
|
+
* @param {string} cfg.find - The string sequence to search for.
|
|
111
|
+
* @param {boolean} cfg.full - Whether to match full words only.
|
|
112
|
+
*
|
|
113
|
+
* @return {RegExp | null} The compiled RegExp object or null if no search term exists.
|
|
114
|
+
*/
|
|
115
|
+
/**
|
|
116
|
+
* Normalizes a saved document version into its structured body and preview representations.
|
|
117
|
+
*
|
|
118
|
+
* @param ver - Ver input supplied to parse ver body's operation.
|
|
119
|
+
*
|
|
120
|
+
* @return The parsed body, original JSON text, and Markdown preview; empty values are returned when no version is available or parsing fails.
|
|
121
|
+
*/
|
|
122
|
+
function parseVerBody(ver) {
|
|
123
|
+
if (!ver)
|
|
124
|
+
return { body: [], json: '', markdown: '' };
|
|
125
|
+
const json = typeof ver.contentJson === 'string' ? ver.contentJson : '';
|
|
126
|
+
if (!json.trim())
|
|
127
|
+
return { body: [], json, markdown: '' };
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(json);
|
|
130
|
+
const body = isDocBody(parsed) ? parsed : [];
|
|
131
|
+
return { body, json, markdown: stringifyDocBody(body) };
|
|
132
|
+
}
|
|
133
|
+
catch (_err) {
|
|
134
|
+
console.error('[Toolbar:versions:preview.parseVersionBody.fail]', {
|
|
135
|
+
savedAt: ver.savedAt,
|
|
136
|
+
title: ver.title,
|
|
137
|
+
message: _err instanceof Error ? _err.message : String(_err),
|
|
138
|
+
jsonHead: json.slice(0, 400),
|
|
139
|
+
jsonTail: json.slice(-400),
|
|
140
|
+
});
|
|
141
|
+
return { body: [], json, markdown: '' };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Detects long JSON-like text that should be treated as structured source rather than ordinary prose.
|
|
146
|
+
*
|
|
147
|
+
* @param val - Source text to check for JSON delimiters and a minimum payload length.
|
|
148
|
+
*
|
|
149
|
+
* @return `true` when the text has JSON-like boundaries and content; otherwise `false`.
|
|
150
|
+
*/
|
|
151
|
+
function looksLikeJsonBlob(val) {
|
|
152
|
+
const v = val.trim();
|
|
153
|
+
// prettier-ignore
|
|
154
|
+
return ['{}', '[]'].some(p => v.startsWith(p[0]) && v.endsWith(p[1])) && v.length > 80;
|
|
155
|
+
}
|
|
156
|
+
/*//////////////////////////////////////////////////
|
|
157
|
+
COMPONENTS
|
|
158
|
+
//////////////////////////////////////////////////*/
|
|
159
|
+
const Button = React.forwardRef(function Button({ children: c, className: v, disabled: d, lbl: l, shortcut: s, onClick: o, onMouseDown: r, active: a, ...p }, x) {
|
|
160
|
+
void a;
|
|
161
|
+
return (_jsx(TextTooltip, { content: s ? `${l} (${s})` : l, side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { ref: x, variant: "ghost", size: "xs", "aria-label": l, disabled: d, onClick: o, onMouseDown: r, className: cn('inline-flex h-6 !p-0.5 shadow-none', v), ...p, children: c }) }));
|
|
162
|
+
});
|
|
163
|
+
Button.displayName = 'Button';
|
|
164
|
+
/**
|
|
165
|
+
* Mode control for the document viewer, visual editor, and attachment browser.
|
|
166
|
+
*
|
|
167
|
+
* @param props - Current toolbar mode, mode-change callback, edit permission, optional disabled state, and control size.
|
|
168
|
+
* @param {ToolbarMode} props.mode - The active interface mode.
|
|
169
|
+
* @param {Function} props.onModeChange - Callback fired when a new mode is selected.
|
|
170
|
+
* @param {boolean} [props.disabled] - Locks the control.
|
|
171
|
+
* @param {boolean} props.editable - Validates if the user possesses edit warrants.
|
|
172
|
+
*
|
|
173
|
+
* @return {JSX.Element} The Select dropdown for mode switching.
|
|
174
|
+
*/
|
|
175
|
+
function Mode({ mode, onModeChange, disabled, editable, size = 'xs', }) {
|
|
176
|
+
void editable;
|
|
177
|
+
return (_jsxs(Select, { size: size, disabled: disabled, value: mode, onValueChange: (v) => {
|
|
178
|
+
const n = (v || 'view');
|
|
179
|
+
if (mode === n)
|
|
180
|
+
return;
|
|
181
|
+
setTransition(`to-${n}`);
|
|
182
|
+
onModeChange(n);
|
|
183
|
+
}, children: [_jsx(SelectTrigger, { "aria-label": "Mode",
|
|
184
|
+
// Match the original full-row selector in View/Edit. Attachments adds
|
|
185
|
+
// its type filters beside this control, so only that mode sizes to fit.
|
|
186
|
+
className: mode === 'attachments' ? 'w-fit' : undefined, children: _jsx(SelectValue, { placeholder: "Mode" }) }), _jsx(SelectContent, { align: 'start', children: (editable ? ['view', 'edit', 'attachments'] : ['view', 'attachments']).map(o => _jsx(SelectItem, { value: o, children: o[0].toUpperCase() + o.slice(1) }, o)) })] }));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Renders Docs-specific controls beside a content editor toolbar.
|
|
190
|
+
*
|
|
191
|
+
* Formatting and content insertion remain editor-owned; these controls manage
|
|
192
|
+
* the document shell's mode and persistence state.
|
|
193
|
+
*
|
|
194
|
+
* @param props - Docs toolbar state and current mode.
|
|
195
|
+
*
|
|
196
|
+
* @return The mode, history, save, or version actions supported by the active Docs editor toolbar.
|
|
197
|
+
*/
|
|
198
|
+
const TOOLBAR_COMPONENTS = {
|
|
199
|
+
DocsToolbarActions({ activeMode, editable, size = 'xs', toolbar, }) {
|
|
200
|
+
const edit = useContext(EditStateCtx);
|
|
201
|
+
const [versions, setVersions] = useState([]);
|
|
202
|
+
const [historyOpen, setHistoryOpen] = useState(false);
|
|
203
|
+
const [selectedVersionId, setSelectedVersionId] = useState('');
|
|
204
|
+
const historyEnabled = getDocsHostAdapter()?.capabilities.history !== false;
|
|
205
|
+
const selectedVersion = versions.find((version) => version.savedAt === selectedVersionId);
|
|
206
|
+
const preview = useMemo(() => parseVerBody(selectedVersion), [selectedVersion]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
let live = true;
|
|
209
|
+
if (!editable || !historyEnabled || !toolbar?.path || !historyOpen) {
|
|
210
|
+
setVersions([]);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const path = toolbar.path;
|
|
214
|
+
const id = path.split('/').pop()?.replace(TerraQualityRegex.REGEX_MDX_I_REGEX(), '') || path;
|
|
215
|
+
void (async () => {
|
|
216
|
+
try {
|
|
217
|
+
const result = await actions.write.doc.versions.get({ path });
|
|
218
|
+
if (result.error)
|
|
219
|
+
throw result.error;
|
|
220
|
+
let data = result.data;
|
|
221
|
+
if (Array.isArray(data) && data.length === 0 && path !== id) {
|
|
222
|
+
const fallback = await actions.write.doc.versions.get({ path: id });
|
|
223
|
+
if (fallback.error)
|
|
224
|
+
throw fallback.error;
|
|
225
|
+
if (Array.isArray(fallback.data) && fallback.data.length > 0)
|
|
226
|
+
data = fallback.data;
|
|
227
|
+
}
|
|
228
|
+
if (live && Array.isArray(data))
|
|
229
|
+
setVersions(data);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
console.error('[Toolbar:versions] load-failed', { path, error });
|
|
233
|
+
toast.error(error instanceof Error ? error.message : 'Failed to load doc versions.');
|
|
234
|
+
}
|
|
235
|
+
})();
|
|
236
|
+
return () => {
|
|
237
|
+
live = false;
|
|
238
|
+
};
|
|
239
|
+
}, [editable, historyEnabled, historyOpen, toolbar?.path]);
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
if (historyOpen && !selectedVersionId && versions[0])
|
|
242
|
+
setSelectedVersionId(versions[0].savedAt);
|
|
243
|
+
}, [historyOpen, selectedVersionId, versions]);
|
|
244
|
+
const save = async (event) => {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
if (!toolbar?.save)
|
|
247
|
+
return;
|
|
248
|
+
try {
|
|
249
|
+
await Promise.resolve(toolbar.save());
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
console.error('[Docs:save] failed', error);
|
|
253
|
+
toast.error(error instanceof Error ? error.message : 'Unable to save document.');
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (activeMode === 'view')
|
|
258
|
+
return;
|
|
259
|
+
const onKeyDown = (event) => {
|
|
260
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 's')
|
|
261
|
+
void save(event);
|
|
262
|
+
};
|
|
263
|
+
document.addEventListener('keydown', onKeyDown);
|
|
264
|
+
return () => document.removeEventListener('keydown', onKeyDown);
|
|
265
|
+
}, [activeMode, toolbar]);
|
|
266
|
+
if (!editable)
|
|
267
|
+
return null;
|
|
268
|
+
return (_jsx(TooltipProvider, { children: _jsxs("div", { className: "flex items-center gap-1", "data-docs-toolbar-actions": true, children: [_jsx(Mode, { editable: editable, mode: activeMode, size: size, disabled: !toolbar, onModeChange: (mode) => {
|
|
269
|
+
if (!toolbar)
|
|
270
|
+
return;
|
|
271
|
+
void toolbar.setMode(mode).catch((error) => {
|
|
272
|
+
console.error('[Toolbar:mode] change-failed', { mode, error });
|
|
273
|
+
toast.error(error instanceof Error ? error.message : 'Failed to change mode.');
|
|
274
|
+
clearTransition();
|
|
275
|
+
});
|
|
276
|
+
} }), activeMode === 'edit' && (_jsxs(ButtonGroup, { children: [_jsx(TextTooltip, { content: "Save (\u2318S)", side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { size: size, disabled: !toolbar || !toolbar.dirty, loading: toolbar?.busy === 'saving', onMouseDown: preserveSel, onClick: (event) => void save(event), children: "Save" }) }), historyEnabled && (_jsx(TextTooltip, { content: "Version History", side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { size: size, className: "px-0.5", disabled: !toolbar, "aria-label": "Version History", onMouseDown: preserveSel, onClick: () => {
|
|
277
|
+
if (versions.length > 0 && !selectedVersionId)
|
|
278
|
+
setSelectedVersionId(versions[0].savedAt);
|
|
279
|
+
setHistoryOpen(true);
|
|
280
|
+
}, children: _jsx(CaretDownIcon, {}) }) })), _jsx(Dialog, { open: historyOpen, onOpenChange: setHistoryOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Version History" }), _jsx(DialogDescription, { className: "sr-only", children: "Select a version to preview and restore." }), _jsxs(Select, { value: selectedVersionId, onValueChange: setSelectedVersionId, children: [_jsx(SelectTrigger, { className: "h-14", "aria-label": "Select a version", children: _jsx(SelectValue, { placeholder: "Select a version" }) }), _jsx(SelectContent, { children: versions.map((version) => (_jsx(SelectItem, { value: version.savedAt, children: _jsxs("span", { className: "flex flex-col text-left", children: [_jsxs("span", { children: [version.title, " (", new Date(version.savedAt).toLocaleString(), ")"] }), _jsx("span", { className: SECONDARY_TEXT_CLASS, children: version.description })] }) }, version.savedAt))) })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(CodeBlock, { tone: "view", language: "mdx", docsSource: true, code: preview.markdown.trim() || ' ' }) }), _jsxs(DialogFooter, { children: [_jsx(UiButton, { variant: "ghost", onClick: () => setHistoryOpen(false), children: "Cancel" }), _jsx(UiButton, { disabled: !selectedVersion, onClick: () => {
|
|
281
|
+
if (!edit || !selectedVersionId)
|
|
282
|
+
return;
|
|
283
|
+
edit.update({
|
|
284
|
+
content: preview.body,
|
|
285
|
+
description: selectedVersion?.description || '',
|
|
286
|
+
source: preview.markdown,
|
|
287
|
+
title: selectedVersion?.title || '',
|
|
288
|
+
});
|
|
289
|
+
(edit.setCode ?? edit.setSrc)?.(preview.markdown);
|
|
290
|
+
setHistoryOpen(false);
|
|
291
|
+
notify({
|
|
292
|
+
error: null,
|
|
293
|
+
fallback: 'Restored version to local draft. Save to apply.',
|
|
294
|
+
});
|
|
295
|
+
}, children: "Restore" })] })] }) })] }))] }) }));
|
|
296
|
+
},
|
|
297
|
+
/**
|
|
298
|
+
* Undo and Redo control group dynamically checking CodeMirror depth levels.
|
|
299
|
+
*
|
|
300
|
+
* @param props - Editor mouse handler, disabled state, and optional toolbar adapter used to inspect undo/redo depth.
|
|
301
|
+
* @param {Function} props.onMouseDown - Bound handler guarding focus.
|
|
302
|
+
* @param {boolean} [props.disabled] - Locks the controls.
|
|
303
|
+
* @param adapter - Adapter input supplied to undo's operation.
|
|
304
|
+
*
|
|
305
|
+
* @return {JSX.Element} The paired undo/redo button group.
|
|
306
|
+
*/
|
|
307
|
+
Undo({ onMouseDown, disabled, adapter, }) {
|
|
308
|
+
// STATES
|
|
309
|
+
const [state, setState] = useState({
|
|
310
|
+
redo: false,
|
|
311
|
+
undo: false,
|
|
312
|
+
});
|
|
313
|
+
// INIT
|
|
314
|
+
useEffect(() => {
|
|
315
|
+
let frame = 0;
|
|
316
|
+
/**
|
|
317
|
+
* Handles sync.
|
|
318
|
+
*/
|
|
319
|
+
const sync = () => {
|
|
320
|
+
const view = codeViewOf();
|
|
321
|
+
const n = view
|
|
322
|
+
? { redo: redoDepth(view.state) > 0, undo: undoDepth(view.state) > 0 }
|
|
323
|
+
: { redo: false, undo: false };
|
|
324
|
+
setState((c) => (c.undo === n.undo && c.redo === n.redo ? c : n));
|
|
325
|
+
};
|
|
326
|
+
sync();
|
|
327
|
+
/**
|
|
328
|
+
* Handles refresh.
|
|
329
|
+
*/
|
|
330
|
+
const refresh = () => {
|
|
331
|
+
cancelAnimationFrame(frame);
|
|
332
|
+
frame = requestAnimationFrame(sync);
|
|
333
|
+
};
|
|
334
|
+
['focusin', 'keyup', 'mouseup'].forEach((e) => {
|
|
335
|
+
document.addEventListener(e, refresh, true);
|
|
336
|
+
});
|
|
337
|
+
return () => {
|
|
338
|
+
cancelAnimationFrame(frame);
|
|
339
|
+
['focusin', 'keyup', 'mouseup'].forEach((e) => {
|
|
340
|
+
document.removeEventListener(e, refresh, true);
|
|
341
|
+
});
|
|
342
|
+
};
|
|
343
|
+
}, []);
|
|
344
|
+
// RENDER
|
|
345
|
+
return (_jsx(ButtonGroup, { children: [
|
|
346
|
+
{
|
|
347
|
+
lbl: 'Undo',
|
|
348
|
+
canFire: adapter?.undoable ?? state.undo,
|
|
349
|
+
action: adapter?.undo ?? undo,
|
|
350
|
+
icon: UndoIcon,
|
|
351
|
+
shortcut: '⌘Z',
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
lbl: 'Redo',
|
|
355
|
+
canFire: adapter?.redoable ?? state.redo,
|
|
356
|
+
action: adapter?.redo ?? redo,
|
|
357
|
+
icon: RedoIcon,
|
|
358
|
+
shortcut: '⌘Y',
|
|
359
|
+
},
|
|
360
|
+
].map((b) => (_jsx(Button, { lbl: b.lbl, shortcut: b.shortcut, disabled: disabled || !b.canFire, onMouseDown: onMouseDown, onClick: () => {
|
|
361
|
+
if (adapter) {
|
|
362
|
+
if (b.lbl === 'Undo')
|
|
363
|
+
adapter.undo?.();
|
|
364
|
+
else
|
|
365
|
+
adapter.redo?.();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const view = codeViewOf();
|
|
369
|
+
if (!view)
|
|
370
|
+
return;
|
|
371
|
+
b.action(view);
|
|
372
|
+
view.focus();
|
|
373
|
+
}, children: _jsx(b.icon, {}) }, b.lbl))) }));
|
|
374
|
+
},
|
|
375
|
+
/**
|
|
376
|
+
* Dropdown dialog facilitating attachment uploads or selecting document attachments natively bound to the leaf.
|
|
377
|
+
*
|
|
378
|
+
* @param props - Icon component, optional insertion callback, selection-preserving mouse handler, disabled state, and attachment context.
|
|
379
|
+
* @param {React.ElementType} props.icon - The base icon to display.
|
|
380
|
+
* @param {Function} [props.insert] - Callback mutating the AST text content natively.
|
|
381
|
+
* @param {Function} props.onMouseDown - Preserves focus bindings.
|
|
382
|
+
* @param {boolean} [props.disabled] - Sets interactability state.
|
|
383
|
+
* @param {string} [props.ctx] - Current physical parent path evaluating available local attachments.
|
|
384
|
+
*
|
|
385
|
+
* @return {JSX.Element} The rendered Uploader dialog button.
|
|
386
|
+
*/
|
|
387
|
+
Uploader({ icon: Icon, insert, onMouseDown, disabled, ctx, }) {
|
|
388
|
+
// STATES
|
|
389
|
+
const [cfg, setCfg] = useState({ busy: false, open: false, attachment: '' });
|
|
390
|
+
const [atts, setAtts] = useState([]);
|
|
391
|
+
const [insertMode, setInsertMode] = useState('embed');
|
|
392
|
+
// EFFECTS
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
let a = true;
|
|
395
|
+
if (cfg.open && ctx) {
|
|
396
|
+
actions.read.doc
|
|
397
|
+
.attachments({ path: ctx })
|
|
398
|
+
.then(({ data: d }) => {
|
|
399
|
+
// prettier-ignore
|
|
400
|
+
if (a && Array.isArray(d))
|
|
401
|
+
setAtts(d
|
|
402
|
+
.filter((a) => Boolean(a && typeof a === 'object' && typeof a.path === 'string'))
|
|
403
|
+
.map(a => ({
|
|
404
|
+
path: a.path,
|
|
405
|
+
name: typeof a.name === 'string' ? a.name : a.path.split('/').pop() || 'attachment',
|
|
406
|
+
})));
|
|
407
|
+
})
|
|
408
|
+
.catch((e) => {
|
|
409
|
+
console.error('[Toolbar:attachments] load-failed', { path: ctx, error: e });
|
|
410
|
+
toast.error(e instanceof Error ? e.message : 'Failed to load attachments.');
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
return () => {
|
|
414
|
+
a = false;
|
|
415
|
+
};
|
|
416
|
+
}, [cfg.open, ctx]);
|
|
417
|
+
// HELPERS
|
|
418
|
+
/**
|
|
419
|
+
* Handles submit.
|
|
420
|
+
*/
|
|
421
|
+
const uploaderActions = {
|
|
422
|
+
reset: useCallback(() => {
|
|
423
|
+
setCfg({ busy: false, open: false, attachment: '' });
|
|
424
|
+
setInsertMode('embed');
|
|
425
|
+
}, []),
|
|
426
|
+
submit: async () => {
|
|
427
|
+
if (!cfg.attachment || cfg.attachment === 'none' || !insert)
|
|
428
|
+
return;
|
|
429
|
+
setCfg((p) => ({ ...p, busy: true }));
|
|
430
|
+
try {
|
|
431
|
+
const att = atts.find((a) => a.path === cfg.attachment);
|
|
432
|
+
if (!att)
|
|
433
|
+
throw new Error('Attachment not found');
|
|
434
|
+
const lbl = att.name
|
|
435
|
+
.split('/')
|
|
436
|
+
.pop()
|
|
437
|
+
?.split('.')[0]
|
|
438
|
+
?.replace(TerraQualityRegex.REGEX_G_REGEX_7(), ' ')
|
|
439
|
+
.replace(TerraQualityRegex.REGEX_B_W_G_REGEX(), (c) => c.toUpperCase()) ||
|
|
440
|
+
'attachment';
|
|
441
|
+
const c = (att.path.startsWith('http') ? att.path : att.name).replace(TerraQualityRegex.REGEX_G_REGEX(), '%20');
|
|
442
|
+
let fmt = '';
|
|
443
|
+
if (insertMode === 'link')
|
|
444
|
+
fmt = `[${lbl}](<${c}>)`;
|
|
445
|
+
else {
|
|
446
|
+
if (TerraQualityRegex.REGEX_PNG_JPE_G_GIF_WEBP_SVG_AVIF_I_REGEX_2().test(att.name))
|
|
447
|
+
fmt = `\n\n`;
|
|
448
|
+
else
|
|
449
|
+
fmt = `\n<Embed src="${c}" title="${lbl}" />\n`;
|
|
450
|
+
}
|
|
451
|
+
insert(fmt);
|
|
452
|
+
uploaderActions.reset();
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
setCfg((p) => ({ ...p, busy: false }));
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
};
|
|
459
|
+
// RENDER
|
|
460
|
+
return (_jsxs(Popover, { open: cfg.open, onOpenChange: (o) => {
|
|
461
|
+
if (!o)
|
|
462
|
+
uploaderActions.reset();
|
|
463
|
+
else
|
|
464
|
+
setCfg((p) => ({ ...p, open: o }));
|
|
465
|
+
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { disabled: disabled, lbl: 'Insert attachment', onMouseDown: onMouseDown, children: _jsx(Icon, {}) }) }), _jsxs(PopoverContent, { align: "start", className: "w-80 space-y-4", children: [_jsxs("div", { className: FIELD_GAP_CLASS, children: [_jsx(Label, { className: SECONDARY_TEXT_CLASS, children: "Document Attachments" }), _jsxs(Select, { value: cfg.attachment, onValueChange: (p) => {
|
|
466
|
+
setCfg((v) => ({ ...v, attachment: p }));
|
|
467
|
+
}, children: [_jsx(SelectTrigger, { "aria-label": "Document Attachment", children: _jsx(SelectValue, { placeholder: "Select an attachment" }) }), _jsx(SelectContent, { children: atts.length === 0 ? (_jsx(SelectItem, { value: 'none', disabled: true, children: "No attachments found" })) : (atts.map(a => (_jsx(SelectItem, { value: a.path, children: a.name }, a.path)))) })] })] }), _jsxs("div", { className: FIELD_GAP_CLASS, children: [_jsx(Label, { children: "Format" }), _jsx(ToggleGroup, { type: 'single', value: insertMode, onValueChange: v => v && setInsertMode(v), className: 'justify-start', children: [['embed', 'Visual Embed'], ['link', 'Text Link']].map(([v, l]) => (_jsx(ToggleGroupItem, { value: v, size: 'sm', className: 'h-7 px-3 text-xs', children: l }, v))) })] }), _jsx(UiButton, { className: "w-full", disabled: !cfg.attachment || cfg.attachment === 'none' || cfg.busy, loading: cfg.busy, onClick: () => void uploaderActions.submit(), children: "Insert" })] })] }));
|
|
468
|
+
},
|
|
469
|
+
/**
|
|
470
|
+
* Recursive structural map converting component tree branches into native interactive dropdown triggers natively.
|
|
471
|
+
*
|
|
472
|
+
* @param props - Component name, its nested menu tree, and optional callback for copying a selected component into the editor.
|
|
473
|
+
* @param {string} props.name - Branch literal mapping component segment text explicitly.
|
|
474
|
+
* @param {MenuTree} props.tree - Context bounding branch constraints natively.
|
|
475
|
+
* @param {Function} [props.onInsert] - Passed insertion trigger executing AST injections cleanly safely.
|
|
476
|
+
* @param onCopy - Callback invoked for copy.
|
|
477
|
+
*
|
|
478
|
+
* @return {JSX.Element} React rendered menu node explicitly containing nested branches cleanly.
|
|
479
|
+
*/
|
|
480
|
+
GlobalComponentMenuNode({ name, tree, onCopy, }) {
|
|
481
|
+
return (_jsxs(DropdownMenuSub, { children: [_jsx(DropdownMenuSubTrigger, { className: "capitalize", children: name.replace(TerraQualityRegex.REGEX_G_REGEX_7(), ' ') }), _jsxs(DropdownMenuSubContent, { children: [tree.components.map((c) => (_jsx(DropdownMenuItem, { onMouseDown: preserveSel, onSelect: (e) => {
|
|
482
|
+
e.preventDefault();
|
|
483
|
+
onCopy?.(c);
|
|
484
|
+
}, children: c.displayLabel
|
|
485
|
+
.replace(TerraQualityRegex.REGEX_G_REGEX_7(), ' ')
|
|
486
|
+
.replace(TerraQualityRegex.REGEX_B_W_G_REGEX(), (c) => c.toUpperCase()) }, `${c.path}:${c.name}`))), Object.entries(tree.folders).map(([f, s]) => (_jsx(TOOLBAR_COMPONENTS.GlobalComponentMenuNode, { name: f, tree: s, onCopy: onCopy }, f)))] })] }));
|
|
487
|
+
},
|
|
488
|
+
/**
|
|
489
|
+
* Complex unified insertion menu tracking local sister references alongside globally configured repo components.
|
|
490
|
+
*
|
|
491
|
+
* @param props - Mapping parameters defining the interaction boundaries.
|
|
492
|
+
* @param onInsert - Executes JS AST modifications natively by parsing safe import and JSX parameters.
|
|
493
|
+
* @param onInsertMd - Fallback routine injecting pure un-compiled Markdown strings directly into the editor state.
|
|
494
|
+
* @param onMouseDown - Guards boundary constraints reliably to protect CodeMirror/Lexical focus tracking.
|
|
495
|
+
* @param disabled - Sets the interaction scope natively tracking external loading locks cleanly.
|
|
496
|
+
* @param ctx - Physical source directory routing constraints properly evaluating sibling lists and local scope.
|
|
497
|
+
*
|
|
498
|
+
* @return The React JSX Element representing the fully interactive Dropdown and Dialog boundary.
|
|
499
|
+
*
|
|
500
|
+
* @remarks
|
|
501
|
+
* Acts as the primary interface for embedding both local media, external components, and other documents
|
|
502
|
+
* directly into the active MDX editor. It natively handles Sanity ID resolution and AST injection.
|
|
503
|
+
*/
|
|
504
|
+
Custom({ onInsert, onInsertMd, onMouseDown, disabled, ctx, }) {
|
|
505
|
+
// STATES
|
|
506
|
+
const [sis, setSis] = useState([]);
|
|
507
|
+
const [search, setSearch] = useState({
|
|
508
|
+
open: false,
|
|
509
|
+
query: '',
|
|
510
|
+
tree: [],
|
|
511
|
+
busy: false,
|
|
512
|
+
});
|
|
513
|
+
const savedSel = useRef(null);
|
|
514
|
+
const copyLock = useRef(null);
|
|
515
|
+
// 1. FETCH LOCAL FILES (Allows ALL leaves as valid attachments)
|
|
516
|
+
useEffect(() => {
|
|
517
|
+
if (!ctx)
|
|
518
|
+
return;
|
|
519
|
+
const dir = dirname(TerraQualityRegex.REGEX_INDEX_MD_MDX_I_REGEX_2().test(ctx.split('/').pop() || '')
|
|
520
|
+
? dirname(ctx)
|
|
521
|
+
: ctx);
|
|
522
|
+
actions.read.doc
|
|
523
|
+
.list({ type: 'folderEntries', path: dir })
|
|
524
|
+
.then(({ data: d }) => {
|
|
525
|
+
// prettier-ignore
|
|
526
|
+
if (Array.isArray(d))
|
|
527
|
+
setSis(d.filter((p) => p !== ctx && dirname(p) === dir && /\.(md|mdx|astro|tsx)$/i.test(p.split('/').pop() || '')).map((p) => ({ path: p, name: p.split('/').pop() || '' })));
|
|
528
|
+
})
|
|
529
|
+
.catch((e) => {
|
|
530
|
+
console.error('[Toolbar:insert.load-failed]', { path: dir, error: e });
|
|
531
|
+
toast.error(e instanceof Error ? e.message : 'Failed to load insert targets.');
|
|
532
|
+
});
|
|
533
|
+
}, [ctx]);
|
|
534
|
+
// 2. BUILD GLOBAL COMPONENT TREE
|
|
535
|
+
const componentTree = useMemo(() => {
|
|
536
|
+
const root = { components: [], folders: {} };
|
|
537
|
+
const counts = new Map();
|
|
538
|
+
repoComponents.forEach((c) => counts.set(c.path, (counts.get(c.path) ?? 0) + 1));
|
|
539
|
+
repoComponents.forEach((c) => {
|
|
540
|
+
const parts = c.path.split('/_custom/');
|
|
541
|
+
if (parts.length <= 1) {
|
|
542
|
+
root.components.push({ ...c, displayLabel: c.name });
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const segs = parts[1].split('/');
|
|
546
|
+
const file = segs.pop().replace(TerraQualityRegex.REGEX_PATTERN_REGEX(), '');
|
|
547
|
+
if (file !== 'index' && (counts.get(c.path) ?? 0) > 1)
|
|
548
|
+
segs.push(file);
|
|
549
|
+
let cur = root;
|
|
550
|
+
segs.forEach((s) => {
|
|
551
|
+
cur = cur.folders[s] ??= { components: [], folders: {} };
|
|
552
|
+
});
|
|
553
|
+
// prettier-ignore
|
|
554
|
+
cur.components.push({ ...c, displayLabel: c.name });
|
|
555
|
+
});
|
|
556
|
+
return root;
|
|
557
|
+
}, []);
|
|
558
|
+
const copyComponent = useCallback(async (c) => {
|
|
559
|
+
if (onInsert) {
|
|
560
|
+
onInsert(`import ${c.name} from '${c.path}';`, c.clipboardValue ?? `<${c.name} />`);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const text = c.clipboardValue ?? `<${c.name} />`;
|
|
564
|
+
const key = `${c.path}:${c.name}`;
|
|
565
|
+
if (copyLock.current === key)
|
|
566
|
+
return;
|
|
567
|
+
copyLock.current = key;
|
|
568
|
+
try {
|
|
569
|
+
try {
|
|
570
|
+
await navigator.clipboard.writeText(text);
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
const fallback = document.createElement('textarea');
|
|
574
|
+
fallback.value = text;
|
|
575
|
+
fallback.setAttribute('readonly', 'true');
|
|
576
|
+
fallback.style.position = 'fixed';
|
|
577
|
+
fallback.style.opacity = '0';
|
|
578
|
+
document.body.appendChild(fallback);
|
|
579
|
+
fallback.select();
|
|
580
|
+
if (!document.execCommand('copy')) {
|
|
581
|
+
fallback.remove();
|
|
582
|
+
throw new Error('Clipboard write failed');
|
|
583
|
+
}
|
|
584
|
+
fallback.remove();
|
|
585
|
+
}
|
|
586
|
+
window.setTimeout(() => toast.success('Copied to clipboard', { duration: 4000 }), 0);
|
|
587
|
+
}
|
|
588
|
+
catch (_err) {
|
|
589
|
+
toast.error('Failed to copy to clipboard');
|
|
590
|
+
}
|
|
591
|
+
finally {
|
|
592
|
+
window.setTimeout(() => {
|
|
593
|
+
if (copyLock.current === key)
|
|
594
|
+
copyLock.current = null;
|
|
595
|
+
}, 0);
|
|
596
|
+
}
|
|
597
|
+
}, [onInsert]);
|
|
598
|
+
// 3. FETCH FULL DOC TREE FOR SEARCH/BROWSE
|
|
599
|
+
useEffect(() => {
|
|
600
|
+
if (!search.open)
|
|
601
|
+
return;
|
|
602
|
+
let a = true;
|
|
603
|
+
if (search.tree.length === 0) {
|
|
604
|
+
setSearch((s) => ({ ...s, busy: true }));
|
|
605
|
+
actions.read.doc
|
|
606
|
+
.list({ type: 'nav', pathname: docsRootPath() })
|
|
607
|
+
.then(({ data: d }) => {
|
|
608
|
+
if (a)
|
|
609
|
+
setSearch((s) => ({
|
|
610
|
+
...s,
|
|
611
|
+
tree: Array.isArray(d)
|
|
612
|
+
? d.filter((n) => Boolean(n && typeof n === 'object' && 'type' in n))
|
|
613
|
+
: [],
|
|
614
|
+
busy: false,
|
|
615
|
+
}));
|
|
616
|
+
})
|
|
617
|
+
.catch((_err) => {
|
|
618
|
+
console.error(`[Toolbar:Embed] Failed to fetch document tree:`, _err);
|
|
619
|
+
if (a)
|
|
620
|
+
setSearch((s) => ({ ...s, busy: false }));
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
return () => {
|
|
624
|
+
a = false;
|
|
625
|
+
};
|
|
626
|
+
}, [search.open]);
|
|
627
|
+
// 4. FILTER LEAVES ON QUERY (Returns all leaves when query is empty)
|
|
628
|
+
const filteredLeaves = useMemo(() => {
|
|
629
|
+
const leaves = [];
|
|
630
|
+
// Track seen paths to prevent duplicate React key warnings when the nav tree
|
|
631
|
+
// contains the same node referenced from multiple locations.
|
|
632
|
+
const seen = new Set();
|
|
633
|
+
const lowerQuery = search.query.trim().toLowerCase();
|
|
634
|
+
const searchFrames = [
|
|
635
|
+
{ nodes: search.tree, index: 0, path: '' },
|
|
636
|
+
];
|
|
637
|
+
while (searchFrames.length) {
|
|
638
|
+
const frame = searchFrames[searchFrames.length - 1];
|
|
639
|
+
if (!frame)
|
|
640
|
+
break;
|
|
641
|
+
if (frame.index >= frame.nodes.length) {
|
|
642
|
+
searchFrames.pop();
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
const n = frame.nodes[frame.index];
|
|
646
|
+
frame.index += 1;
|
|
647
|
+
const currTitle = n.title || n.origName || 'Untitled';
|
|
648
|
+
if (n.type === 'leaf') {
|
|
649
|
+
const nodePath = n.path;
|
|
650
|
+
if (typeof nodePath !== 'string' || seen.has(nodePath))
|
|
651
|
+
continue;
|
|
652
|
+
if (!lowerQuery ||
|
|
653
|
+
(typeof currTitle === 'string' ? currTitle.toLowerCase() : '').includes(lowerQuery) ||
|
|
654
|
+
(n.path && typeof n.path === 'string' ? n.path.toLowerCase() : '').includes(lowerQuery)) {
|
|
655
|
+
seen.add(nodePath);
|
|
656
|
+
leaves.push({ path: nodePath, title: currTitle, breadcrumb: frame.path });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
else if (n.type === 'branch' && n.children) {
|
|
660
|
+
searchFrames.push({
|
|
661
|
+
nodes: n.children,
|
|
662
|
+
index: 0,
|
|
663
|
+
path: frame.path ? `${frame.path} / ${currTitle}` : currTitle,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return leaves;
|
|
668
|
+
}, [search.query, search.tree]);
|
|
669
|
+
// 5. EMBED OR CREATE LEAF
|
|
670
|
+
/**
|
|
671
|
+
* Handles embed.
|
|
672
|
+
*
|
|
673
|
+
* @param targetPath - Destination path for the moved or renamed document.
|
|
674
|
+
* @param isNew - Whether the destination represents a newly created document.
|
|
675
|
+
*/
|
|
676
|
+
const embed = async (tgtPath, isNew = false) => {
|
|
677
|
+
let finalPath = tgtPath;
|
|
678
|
+
let leafId = tgtPath;
|
|
679
|
+
// 1. Close Modal immediately to prevent multiple submissions
|
|
680
|
+
setSearch((s) => ({ ...s, open: false, busy: true }));
|
|
681
|
+
setTransition('p');
|
|
682
|
+
try {
|
|
683
|
+
if (isNew) {
|
|
684
|
+
// scope the new document natively UNDER the current document's path.
|
|
685
|
+
// this ensures string-based `verifyWrite` auth checks pass by matching the prefix of the inherited warrant.
|
|
686
|
+
const basePath = ctx ? ctx.replace(TerraQualityRegex.REGEX_PATTERN_REGEX_2(), '') : '';
|
|
687
|
+
const sName = search.query.trim()
|
|
688
|
+
? search.query.replace(TerraQualityRegex.REGEX_A_Z0_9_GI_REGEX_2(), '-').toLowerCase()
|
|
689
|
+
: 'untitled';
|
|
690
|
+
const nId = basePath
|
|
691
|
+
? `${basePath}/${sName}-${Date.now()}.mdx`
|
|
692
|
+
: `${sName}-${Date.now()}.mdx`;
|
|
693
|
+
finalPath = nId;
|
|
694
|
+
// create the page purely as a stand-alone document without anchoring to the group
|
|
695
|
+
const writeRes = await actions.write.doc
|
|
696
|
+
.editor({ path: nId, title: search.query, source: 'This page is empty.' })
|
|
697
|
+
.catch((_err) => ({ data: null, error: _err }));
|
|
698
|
+
if (writeRes.error) {
|
|
699
|
+
console.error(`[Toolbar:Embed] write.doc.editor action failed:`, writeRes.error);
|
|
700
|
+
throw writeRes.error;
|
|
701
|
+
}
|
|
702
|
+
finalPath = writeRes.data?.path || finalPath;
|
|
703
|
+
}
|
|
704
|
+
// map leaf explicitly to the editor's attachment array unconditionally
|
|
705
|
+
if (ctx) {
|
|
706
|
+
const attRes = await actions.write.attachment
|
|
707
|
+
.docRef({ path: ctx, refId: finalPath, title: search.query || 'Embedded Page' })
|
|
708
|
+
.catch((_err) => {
|
|
709
|
+
console.error(`[Toolbar:Embed] write.attachment.docRef action failed:`, _err);
|
|
710
|
+
return { data: null, error: _err };
|
|
711
|
+
});
|
|
712
|
+
if (attRes && attRes.data) {
|
|
713
|
+
// Resolves the exact Sanity UUID returned to map directly into the React AST parameter
|
|
714
|
+
const attachment = {
|
|
715
|
+
leafId: typeof attRes.data === 'object' &&
|
|
716
|
+
'refId' in attRes.data &&
|
|
717
|
+
typeof attRes.data.refId === 'string'
|
|
718
|
+
? attRes.data.refId
|
|
719
|
+
: leafId,
|
|
720
|
+
sister: { path: finalPath, name: finalPath.split('/').pop() || finalPath },
|
|
721
|
+
};
|
|
722
|
+
leafId = attachment.leafId;
|
|
723
|
+
setSis((p) => {
|
|
724
|
+
if (!p.some((s) => s.path === finalPath))
|
|
725
|
+
return [...p, attachment.sister];
|
|
726
|
+
return p;
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
// delay AST injection to allow Sanity CDN replica propagation to finish
|
|
731
|
+
if (isNew || ctx)
|
|
732
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
733
|
+
clearTransition();
|
|
734
|
+
// 2. Yield to event loop to allow DOM reconciliation
|
|
735
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
736
|
+
// 3. Force focus back into the active editor securely
|
|
737
|
+
const aEditor = document.querySelector('[contenteditable="true"], .cm-content');
|
|
738
|
+
if (aEditor) {
|
|
739
|
+
aEditor.focus({ preventScroll: true });
|
|
740
|
+
const sel = window.getSelection();
|
|
741
|
+
if (sel && savedSel.current) {
|
|
742
|
+
sel.removeAllRanges();
|
|
743
|
+
sel.addRange(savedSel.current);
|
|
744
|
+
savedSel.current = null; // clear after use
|
|
745
|
+
}
|
|
746
|
+
else if (!sel || sel.rangeCount === 0) {
|
|
747
|
+
try {
|
|
748
|
+
const range = document.createRange();
|
|
749
|
+
range.selectNodeContents(aEditor);
|
|
750
|
+
range.collapse(false);
|
|
751
|
+
sel?.removeAllRanges();
|
|
752
|
+
sel?.addRange(range);
|
|
753
|
+
}
|
|
754
|
+
catch {
|
|
755
|
+
// Selection restoration is best effort.
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// clean up busy state and search query now that injection is ready
|
|
760
|
+
setSearch((s) => ({ ...s, query: '', busy: false }));
|
|
761
|
+
await new Promise((r) => requestAnimationFrame(r));
|
|
762
|
+
// 4. Inject purely as AST text (bypassing the import statement injection)
|
|
763
|
+
// This mirrors how globally configured components like <Nodes /> and <DocsMath /> are inserted.
|
|
764
|
+
if (onInsertMd) {
|
|
765
|
+
try {
|
|
766
|
+
onInsertMd(`\n\n<Leaf id="${leafId}" />\n\n`);
|
|
767
|
+
}
|
|
768
|
+
catch (_err) {
|
|
769
|
+
console.error(`[Toolbar:Embed] Exception thrown during onInsertMd AST mutation:`, _err);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
else
|
|
773
|
+
console.error(`[Toolbar:Embed] onInsertMd callback is MISSING. Aborting AST injection.`);
|
|
774
|
+
}
|
|
775
|
+
catch (_err) {
|
|
776
|
+
clearTransition();
|
|
777
|
+
console.error(`[Toolbar:Embed] Critical pipeline failure during leaf embed process:`, _err);
|
|
778
|
+
notify({
|
|
779
|
+
error: _err instanceof Error ? _err : new Error('Embed failed'),
|
|
780
|
+
fallback: 'Failed to embed leaf.',
|
|
781
|
+
});
|
|
782
|
+
setSearch((s) => ({ ...s, query: '', busy: false }));
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
// 6. LOCAL MENUS
|
|
786
|
+
// prettier-ignore
|
|
787
|
+
const menuGroups = {
|
|
788
|
+
globalComponents: componentTree.components,
|
|
789
|
+
globalFolders: componentTree.folders,
|
|
790
|
+
local: [
|
|
791
|
+
{
|
|
792
|
+
label: 'Copy attachments',
|
|
793
|
+
icon: _jsx(PasteIcon, { className: ICON_CLASS }),
|
|
794
|
+
items: sis,
|
|
795
|
+
onSelect: (s) => actions.read.doc
|
|
796
|
+
.get({ path: s.path })
|
|
797
|
+
.then(({ data: d }) => onInsertMd?.(`\n${d?.source || ''}\n`))
|
|
798
|
+
.catch(e => {
|
|
799
|
+
console.error('[Toolbar:insert.partial-load-failed]', { path: s.path, error: e });
|
|
800
|
+
toast.error(e instanceof Error ? e.message : 'Failed to load partial.');
|
|
801
|
+
}),
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
label: 'Share attachment',
|
|
805
|
+
icon: _jsx(ShareIcon, { className: ICON_CLASS }),
|
|
806
|
+
items: sis.filter(s => TerraQualityRegex.REGEX_MD_MDX_I_REGEX().test(s.name)),
|
|
807
|
+
onSelect: (s) => {
|
|
808
|
+
const importName = toPascal(s.name.replace(TerraQualityRegex.REGEX_PATTERN_REGEX(), ''));
|
|
809
|
+
onInsert?.(`import ${importName} from '${TerraQualityRegex.REGEX_INDEX_MD_MDX_I_REGEX_2().test(ctx?.split('/').pop() || '') ? '../' : './'}${s.name}';`, `<${importName} />\n`);
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
],
|
|
813
|
+
};
|
|
814
|
+
return (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { disabled: disabled, lbl: "Insert component", onMouseDown: onMouseDown, children: _jsx(DOCS_BLOCK_ICONS.code, {}) }) }), _jsxs(DropdownMenuContent, { align: "start", className: MENU_WIDTH_CLASS, children: [_jsxs(DropdownMenuItem, { onMouseDown: preserveSel, onSelect: (e) => {
|
|
815
|
+
e.preventDefault();
|
|
816
|
+
const sel = window.getSelection();
|
|
817
|
+
if (sel && sel.rangeCount > 0)
|
|
818
|
+
savedSel.current = sel.getRangeAt(0).cloneRange();
|
|
819
|
+
setSearch((s) => ({ ...s, open: true }));
|
|
820
|
+
}, className: MENU_GAP_CLASS, children: [_jsx(PopOutIcon, { className: ICON_CLASS }), " Embed Page"] }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuSub, { children: [_jsxs(DropdownMenuSubTrigger, { className: MENU_GAP_CLASS, children: [_jsx(ModuleIcon, { className: ICON_CLASS }), " Modules"] }), _jsxs(DropdownMenuSubContent, { className: MENU_WIDTH_CLASS, children: [menuGroups.globalComponents.map((c) => (_jsx(DropdownMenuItem, { onMouseDown: preserveSel, onSelect: (e) => {
|
|
821
|
+
e.preventDefault();
|
|
822
|
+
void copyComponent(c);
|
|
823
|
+
}, children: c.displayLabel
|
|
824
|
+
.replace(TerraQualityRegex.REGEX_G_REGEX_7(), ' ')
|
|
825
|
+
.replace(TerraQualityRegex.REGEX_B_W_G_REGEX(), (c) => c.toUpperCase()) }, c.path))), Object.entries(menuGroups.globalFolders).map(([f, s]) => (_jsx(TOOLBAR_COMPONENTS.GlobalComponentMenuNode, { name: f, tree: s, onCopy: copyComponent }, f)))] })] }), menuGroups.local.map((m) => (_jsx(React.Fragment, { children: _jsxs(DropdownMenuSub, { children: [_jsxs(DropdownMenuSubTrigger, { disabled: !m.items.length, className: MENU_GAP_CLASS, children: [m.icon, " ", m.label] }), _jsx(DropdownMenuSubContent, { children: m.items.map((s) => (_jsx(DropdownMenuItem, { onMouseDown: preserveSel, onSelect: (e) => {
|
|
826
|
+
e.preventDefault();
|
|
827
|
+
m.onSelect(s);
|
|
828
|
+
}, children: s.name }, s.path))) })] }) }, m.label)))] })] }), _jsx(Dialog, { open: search.open, onOpenChange: (o) => {
|
|
829
|
+
setSearch((s) => ({ ...s, open: o }));
|
|
830
|
+
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Embed Document" }), _jsx(DialogDescription, { className: "sr-only", children: "Browse the document tree or search to embed a page." })] }), _jsxs("div", { className: 'space-y-4 pt-2', children: [_jsx(Input, { icon: _jsx(SearchIcon, { className: ICON_CLASS }), autoFocus: true, placeholder: 'Search or type to create...', value: search.query, onChange: e => { setSearch(s => ({ ...s, query: e.target.value })); } }), _jsxs("div", { className: 'flex max-h-64 min-h-24 flex-col gap-1 overflow-y-auto pr-2', children: [search.busy && search.tree.length === 0 && _jsx("p", { className: 'py-4 text-center text-sm text-primary-11', children: "Loading..." }), search.query.trim() && (_jsxs(UiButton, { variant: 'outline', className: 'mb-2 flex-col items-start gap-y-0 border-dashed py-6', onClick: () => void embed('', true), disabled: search.busy, children: [_jsx("span", { className: 'text-sm font-medium', children: search.busy ? 'Creating...' : `Create "${search.query}"` }), _jsx("span", { className: SECONDARY_TEXT_CLASS, children: "Create page and embed as attachment" })] })), search.query.trim() && !search.busy && filteredLeaves.length === 0 && _jsx(SearchEmpty, { query: search.query }), filteredLeaves.map(l => (_jsxs(UiButton, { variant: 'ghost', className: 'flex-col items-start gap-y-0 py-6', onClick: () => embed(l.path), children: [_jsx("span", { className: 'text-sm font-medium capitalize', children: l.title }), _jsxs("span", { className: SECONDARY_TEXT_CLASS, children: [l.breadcrumb ? `${l.breadcrumb} / ` : '', l.title] })] }, l.path)))] })] })] }) })] }));
|
|
831
|
+
},
|
|
832
|
+
/**
|
|
833
|
+
* Dropdown control for quickly converting a block or inserting a supported block template.
|
|
834
|
+
*
|
|
835
|
+
* @param props - Disabled state, toolbar context, optional editor adapter, available block options, and JSON mode for the block selector.
|
|
836
|
+
* @param {object} - Value passed to the helper.
|
|
837
|
+
* @param {boolean} [props.disabled] - Locks the control.
|
|
838
|
+
* @param {ToolbarState | null} props.toolbar - System toolbar state reference to execute replacements.
|
|
839
|
+
* @param {BlockOpt[]} props.options - Select actions with icon prefixes.
|
|
840
|
+
* @param adapter - Adapter input supplied to block type's operation.
|
|
841
|
+
* @param json - Json input supplied to block type's operation.
|
|
842
|
+
*
|
|
843
|
+
* @return {JSX.Element} The Select dropdown block wrapper.
|
|
844
|
+
*/
|
|
845
|
+
BlockType({ disabled, toolbar, adapter, options: opts, json = false, }) {
|
|
846
|
+
// STATES
|
|
847
|
+
const [val, setVal] = useState('p');
|
|
848
|
+
// refs to persist the last known location of the user's cursor
|
|
849
|
+
const savedBlock = useRef(null);
|
|
850
|
+
// INIT
|
|
851
|
+
useEffect(() => {
|
|
852
|
+
let frame = 0;
|
|
853
|
+
if (disabled || !toolbar)
|
|
854
|
+
return;
|
|
855
|
+
/**
|
|
856
|
+
* Synchronizes the current view state with the source data.
|
|
857
|
+
*
|
|
858
|
+
*/
|
|
859
|
+
const sync = () => {
|
|
860
|
+
const view = codeViewOf();
|
|
861
|
+
// 1. Visual Mode (Lexical) - Read from DOM selection safely
|
|
862
|
+
if (!view || view.dom.offsetParent === null) {
|
|
863
|
+
const sel = window.getSelection();
|
|
864
|
+
const blockCursor = document.querySelector('[data-docs-block-cursor="true"]');
|
|
865
|
+
if (blockCursor && sel?.anchorNode && !blockCursor.contains(sel.anchorNode)) {
|
|
866
|
+
const blockVal = toolbarFocusValue(blockCursor, opts.map((o) => o.value));
|
|
867
|
+
if (blockVal) {
|
|
868
|
+
setVal((c) => (c === blockVal ? c : blockVal));
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// Only update the saved reference if the selection is actively inside the editor.
|
|
873
|
+
// This prevents the dropdown click from overwriting the last known cursor location.
|
|
874
|
+
if (sel && sel.rangeCount > 0 && sel.anchorNode) {
|
|
875
|
+
const node = sel.anchorNode;
|
|
876
|
+
const el = node.nodeType === 3 ? node.parentElement : node;
|
|
877
|
+
if (el?.closest('.mdxeditor-root, [contenteditable="true"]')) {
|
|
878
|
+
const textBlock = el?.closest('h1, h2, h3, h4, h5, h6, p');
|
|
879
|
+
if (textBlock)
|
|
880
|
+
savedBlock.current = textBlock;
|
|
881
|
+
else if (el
|
|
882
|
+
?.closest('td, th')
|
|
883
|
+
?.closest('[data-docs-editor-block="DocsTable"], [data-terra-block="table"]'))
|
|
884
|
+
savedBlock.current = null;
|
|
885
|
+
const blockVal = el
|
|
886
|
+
? toolbarFocusValue(el, opts.map((o) => o.value))
|
|
887
|
+
: null;
|
|
888
|
+
if (blockVal) {
|
|
889
|
+
setVal((c) => (c === blockVal ? c : blockVal));
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
// A focused rich code block uses CodeMirror for its own content.
|
|
897
|
+
const { head } = view.state.selection.main;
|
|
898
|
+
const text = view.state.doc.lineAt(head).text;
|
|
899
|
+
let n = 'p';
|
|
900
|
+
if (TerraQualityRegex.REGEX_S_REGEX_5().test(text))
|
|
901
|
+
n = 'h3';
|
|
902
|
+
else if (TerraQualityRegex.REGEX_S_REGEX_6().test(text))
|
|
903
|
+
n = 'h4';
|
|
904
|
+
else if (TerraQualityRegex.REGEX_S_REGEX_4().test(text))
|
|
905
|
+
n = 'h2';
|
|
906
|
+
setVal((c) => (c === n ? c : n));
|
|
907
|
+
};
|
|
908
|
+
sync();
|
|
909
|
+
/**
|
|
910
|
+
* Handles refresh.
|
|
911
|
+
*/
|
|
912
|
+
const refresh = () => {
|
|
913
|
+
cancelAnimationFrame(frame);
|
|
914
|
+
frame = requestAnimationFrame(sync);
|
|
915
|
+
};
|
|
916
|
+
/**
|
|
917
|
+
* Handles on block select.
|
|
918
|
+
*
|
|
919
|
+
* @param e - Change event received from the toolbar input.
|
|
920
|
+
*/
|
|
921
|
+
const onBlockSelect = (e) => {
|
|
922
|
+
const { block: selBlock, decorator } = e.detail ?? {};
|
|
923
|
+
const block = selBlock instanceof HTMLElement
|
|
924
|
+
? selBlock
|
|
925
|
+
: decorator instanceof HTMLElement
|
|
926
|
+
? decorator.querySelector('[data-docs-editor-block], .docs-math-wrapper, [data-math-code], [data-docs-view-root="true"]')
|
|
927
|
+
: null;
|
|
928
|
+
const blockVal = block
|
|
929
|
+
? toolbarFocusValue(block, opts.map((o) => o.value))
|
|
930
|
+
: null;
|
|
931
|
+
if (!blockVal)
|
|
932
|
+
return;
|
|
933
|
+
savedBlock.current = null;
|
|
934
|
+
setVal((c) => (c === blockVal ? c : blockVal));
|
|
935
|
+
};
|
|
936
|
+
['focusin', 'keyup', 'mouseup', 'selectionchange'].forEach((e) => {
|
|
937
|
+
document.addEventListener(e, refresh, true);
|
|
938
|
+
});
|
|
939
|
+
document.addEventListener('docs:block-select', onBlockSelect);
|
|
940
|
+
document.addEventListener('docs:block-selected', onBlockSelect);
|
|
941
|
+
return () => {
|
|
942
|
+
cancelAnimationFrame(frame);
|
|
943
|
+
['focusin', 'keyup', 'mouseup', 'selectionchange'].forEach((e) => {
|
|
944
|
+
document.removeEventListener(e, refresh, true);
|
|
945
|
+
});
|
|
946
|
+
document.removeEventListener('docs:block-select', onBlockSelect);
|
|
947
|
+
document.removeEventListener('docs:block-selected', onBlockSelect);
|
|
948
|
+
};
|
|
949
|
+
}, [disabled, toolbar]);
|
|
950
|
+
// RENDER
|
|
951
|
+
return (_jsxs(Select, { disabled: disabled || !toolbar, value: val, onValueChange: (v) => {
|
|
952
|
+
const item = opts.find((i) => i.value === v);
|
|
953
|
+
if (!item)
|
|
954
|
+
return;
|
|
955
|
+
if (item.group === 'type') {
|
|
956
|
+
if (json) {
|
|
957
|
+
window.dispatchEvent(new CustomEvent('docs:json-command', {
|
|
958
|
+
detail: { type: 'style', style: v === 'p' ? 'normal' : v },
|
|
959
|
+
}));
|
|
960
|
+
setVal(v);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (adapter?.setBlockType) {
|
|
964
|
+
adapter.setBlockType(v);
|
|
965
|
+
setVal(v);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const view = codeViewOf();
|
|
969
|
+
if (view && view.dom.offsetParent !== null) {
|
|
970
|
+
const { head } = view.state.selection.main;
|
|
971
|
+
const line = view.state.doc.lineAt(head);
|
|
972
|
+
const text = line.text;
|
|
973
|
+
let n;
|
|
974
|
+
let prefix = '';
|
|
975
|
+
if (v === 'h2') {
|
|
976
|
+
prefix = '## ';
|
|
977
|
+
n = text.replace(TerraQualityRegex.REGEX_S_REGEX_4(), '').trimStart() || 'Heading';
|
|
978
|
+
}
|
|
979
|
+
else if (v === 'h3') {
|
|
980
|
+
prefix = '### ';
|
|
981
|
+
n = text.replace(TerraQualityRegex.REGEX_S_REGEX_5(), '').trimStart() || 'Heading';
|
|
982
|
+
}
|
|
983
|
+
else if (v === 'h4') {
|
|
984
|
+
prefix = '#### ';
|
|
985
|
+
n = text.replace(TerraQualityRegex.REGEX_S_REGEX_6(), '').trimStart() || 'Heading';
|
|
986
|
+
}
|
|
987
|
+
else
|
|
988
|
+
n = text.replace(/^#{1,6}\s+/, '').trimStart() || 'Text';
|
|
989
|
+
const replacementStr = `${prefix}${n}`;
|
|
990
|
+
view.dispatch({
|
|
991
|
+
changes: { from: line.from, to: line.to, insert: replacementStr },
|
|
992
|
+
selection: { anchor: line.from + replacementStr.length },
|
|
993
|
+
});
|
|
994
|
+
view.focus();
|
|
995
|
+
setVal(v);
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
try {
|
|
999
|
+
if (savedBlock.current) {
|
|
1000
|
+
const editor = savedBlock.current.closest('[contenteditable="true"]')?.__lexicalEditor;
|
|
1001
|
+
if (editor) {
|
|
1002
|
+
editor.update(() => {
|
|
1003
|
+
const lexicalNode = $getNearestNodeFromDOMNode(savedBlock.current);
|
|
1004
|
+
if (lexicalNode) {
|
|
1005
|
+
let blockNode = lexicalNode;
|
|
1006
|
+
while (blockNode && !$isElementNode(blockNode))
|
|
1007
|
+
blockNode = blockNode.getParent();
|
|
1008
|
+
if (blockNode && $isElementNode(blockNode)) {
|
|
1009
|
+
let replacementNode;
|
|
1010
|
+
if (v === 'p')
|
|
1011
|
+
replacementNode = $createParagraphNode();
|
|
1012
|
+
else if (v === 'h2' || v === 'h3' || v === 'h4')
|
|
1013
|
+
replacementNode = $createHeadingNode(v);
|
|
1014
|
+
if (replacementNode) {
|
|
1015
|
+
blockNode.getChildren().forEach((c) => {
|
|
1016
|
+
replacementNode.append(c);
|
|
1017
|
+
});
|
|
1018
|
+
blockNode.replace(replacementNode);
|
|
1019
|
+
replacementNode.selectStart();
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
else
|
|
1026
|
+
console.error('[BlockType] [VisualMode] ABORTED: Could not locate __lexicalEditor on editorRoot.');
|
|
1027
|
+
}
|
|
1028
|
+
else
|
|
1029
|
+
console.error('[BlockType] [VisualMode] ABORTED: No saved cursor context. Ensure the cursor was placed inside the editor before selection.');
|
|
1030
|
+
}
|
|
1031
|
+
catch (_err) {
|
|
1032
|
+
console.error('[BlockType] [VisualMode] CRITICAL PIPELINE FAILURE:', _err);
|
|
1033
|
+
}
|
|
1034
|
+
setVal(v);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
item.onSelect();
|
|
1038
|
+
}, size: "xs", children: [_jsx(SelectTrigger, { "aria-label": "Block type", className: "w-40", onMouseDown: preserveSel, children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { align: "start", children: [_jsxs(SelectGroup, { children: [_jsx(SelectLabel, { children: "Block Type" }), opts.filter(o => o.group === 'type').map(({ value: v, label: l, icon: i }) => (_jsx(SelectItem, { value: v, icon: React.createElement(i, { className: ICON_CLASS }), children: l }, v)))] }), _jsx(SelectSeparator, {}), _jsxs(SelectGroup, { children: [_jsx(SelectLabel, { children: "Insert Block" }), opts
|
|
1039
|
+
.filter((o) => o.group === 'insert')
|
|
1040
|
+
.map(({ value: v, label: l, icon: i }) => (_jsx(SelectItem, { value: v, icon: React.createElement(i, { className: ICON_CLASS }), children: l }, v)))] })] })] }));
|
|
1041
|
+
},
|
|
1042
|
+
/**
|
|
1043
|
+
* Main floating action bar assembling all text and format mutation interactions into a single cohesive boundary.
|
|
1044
|
+
*
|
|
1045
|
+
* @param props - Active mode, editability, toolbar state, optional block list and adapter, and whether Docs-specific actions are enabled.
|
|
1046
|
+
* @param {object} - Value passed to the helper.
|
|
1047
|
+
* @param {ToolbarMode} [props.activeMode] - Inherited mode state isolating interactive parameters.
|
|
1048
|
+
* @param {ToolbarState | null} props.toolbar - System level hook connecting buttons smoothly back into abstract DOM memory states.
|
|
1049
|
+
* @param editable - Editability flag controlling whether toolbar accepts changes.
|
|
1050
|
+
* @param blocks - Blocks input supplied to toolbar's operation.
|
|
1051
|
+
* @param adapter - Adapter input supplied to toolbar's operation.
|
|
1052
|
+
* @param docs - Docs input supplied to toolbar's operation.
|
|
1053
|
+
*
|
|
1054
|
+
* @return {JSX.Element | null} Assembled floating bar mapping components.
|
|
1055
|
+
*/
|
|
1056
|
+
Toolbar({ activeMode: aMode, editable, toolbar, blocks, adapter, docs = true, }) {
|
|
1057
|
+
// STATES
|
|
1058
|
+
const toolbarCtx = {
|
|
1059
|
+
edit: useContext(EditStateCtx),
|
|
1060
|
+
attachmentFilter: useAtom($attachmentFilter),
|
|
1061
|
+
};
|
|
1062
|
+
const mode = aMode ?? 'view';
|
|
1063
|
+
const [vers, setVers] = useState([]);
|
|
1064
|
+
const [verDialogOpen, setVerDialogOpen] = useState(false);
|
|
1065
|
+
const [selVerId, setSelVerId] = useState('');
|
|
1066
|
+
const codeAtEnd = useRef(false);
|
|
1067
|
+
const codeSel = useRef(null);
|
|
1068
|
+
const codeLength = useRef(0);
|
|
1069
|
+
useEffect(() => {
|
|
1070
|
+
/**
|
|
1071
|
+
* Handles sync.
|
|
1072
|
+
*/
|
|
1073
|
+
const sync = () => {
|
|
1074
|
+
const view = codeViewOf();
|
|
1075
|
+
if (!view)
|
|
1076
|
+
return;
|
|
1077
|
+
const { from, to } = view.state.selection.main;
|
|
1078
|
+
codeLength.current = view.state.doc.length;
|
|
1079
|
+
codeSel.current = {
|
|
1080
|
+
code: { from: Math.min(from, to), to: Math.max(from, to) },
|
|
1081
|
+
dom: null,
|
|
1082
|
+
visible: true,
|
|
1083
|
+
};
|
|
1084
|
+
codeAtEnd.current = false;
|
|
1085
|
+
};
|
|
1086
|
+
document.addEventListener('selectionchange', sync);
|
|
1087
|
+
return () => {
|
|
1088
|
+
document.removeEventListener('selectionchange', sync);
|
|
1089
|
+
};
|
|
1090
|
+
}, []);
|
|
1091
|
+
const [endpoint, setEndpoint] = useState('');
|
|
1092
|
+
const [endpointOpen, setEndpointOpen] = useState(false);
|
|
1093
|
+
const ready = adapter?.ready ?? !!toolbar;
|
|
1094
|
+
const saving = toolbar?.busy ?? null;
|
|
1095
|
+
const dirty = toolbar?.dirty ?? false;
|
|
1096
|
+
const publishDraft = getDocsHostAdapter()?.publishDraft;
|
|
1097
|
+
const publish = async () => {
|
|
1098
|
+
if (!publishDraft || !toolbar?.path)
|
|
1099
|
+
return;
|
|
1100
|
+
try {
|
|
1101
|
+
if (dirty && toolbar.save)
|
|
1102
|
+
await toolbar.save();
|
|
1103
|
+
await publishDraft(toolbar.path);
|
|
1104
|
+
toast.success('Published.');
|
|
1105
|
+
}
|
|
1106
|
+
catch (error) {
|
|
1107
|
+
toast.error(error instanceof Error ? error.message : 'Unable to publish document.');
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
useEffect(() => {
|
|
1111
|
+
let live = true;
|
|
1112
|
+
if (!docs || !editable || !toolbar?.path || !verDialogOpen) {
|
|
1113
|
+
setVers([]);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const tgtPath = toolbar.path;
|
|
1117
|
+
// prettier-ignore
|
|
1118
|
+
const tgtId = tgtPath.split('/').pop()?.replace(TerraQualityRegex.REGEX_MDX_I_REGEX(), '') || tgtPath;
|
|
1119
|
+
(async () => {
|
|
1120
|
+
try {
|
|
1121
|
+
const res = await actions.write.doc.versions.get({ path: tgtPath });
|
|
1122
|
+
if (res.error)
|
|
1123
|
+
throw res.error;
|
|
1124
|
+
let resultData = res.data;
|
|
1125
|
+
if (Array.isArray(resultData) && resultData.length === 0 && tgtPath !== tgtId) {
|
|
1126
|
+
const fallback = await actions.write.doc.versions.get({ path: tgtId });
|
|
1127
|
+
if (fallback.error)
|
|
1128
|
+
throw fallback.error;
|
|
1129
|
+
if (Array.isArray(fallback.data) && fallback.data.length > 0)
|
|
1130
|
+
resultData = fallback.data;
|
|
1131
|
+
}
|
|
1132
|
+
if (!live || !Array.isArray(resultData))
|
|
1133
|
+
return;
|
|
1134
|
+
setVers(resultData);
|
|
1135
|
+
}
|
|
1136
|
+
catch (err) {
|
|
1137
|
+
console.error('[Toolbar:versions] load-failed', { path: tgtPath, error: err });
|
|
1138
|
+
toast.error(err instanceof Error ? err.message : 'Failed to load doc versions.');
|
|
1139
|
+
}
|
|
1140
|
+
})();
|
|
1141
|
+
return () => {
|
|
1142
|
+
live = false;
|
|
1143
|
+
};
|
|
1144
|
+
}, [docs, editable, toolbar?.path, verDialogOpen]);
|
|
1145
|
+
// HANDLERS
|
|
1146
|
+
const onSave = toolbar
|
|
1147
|
+
? async (e) => {
|
|
1148
|
+
e.preventDefault();
|
|
1149
|
+
if (!toolbar.save)
|
|
1150
|
+
return;
|
|
1151
|
+
try {
|
|
1152
|
+
const res = toolbar.save();
|
|
1153
|
+
if (res instanceof Promise)
|
|
1154
|
+
await res;
|
|
1155
|
+
}
|
|
1156
|
+
catch (err) {
|
|
1157
|
+
console.error('[Docs:save] failed', err);
|
|
1158
|
+
toast.error(err instanceof Error ? err.message : 'Unable to save document.');
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
: undefined;
|
|
1162
|
+
const selVer = vers.find((v) => v.savedAt === selVerId);
|
|
1163
|
+
useEffect(() => {
|
|
1164
|
+
if (verDialogOpen && !selVerId && vers[0])
|
|
1165
|
+
setSelVerId(vers[0].savedAt);
|
|
1166
|
+
}, [verDialogOpen, selVerId, vers]);
|
|
1167
|
+
const previewPack = useMemo(() => parseVerBody(selVer), [selVer]);
|
|
1168
|
+
useEffect(() => {
|
|
1169
|
+
previewPack.markdown.trim();
|
|
1170
|
+
}, [toolbar?.path, vers, selVer, previewPack.json, previewPack.markdown, previewPack.body]);
|
|
1171
|
+
/**
|
|
1172
|
+
* On restore preview.
|
|
1173
|
+
*/
|
|
1174
|
+
const toolbarActions = {
|
|
1175
|
+
discard: toolbar
|
|
1176
|
+
? async (e) => {
|
|
1177
|
+
e.preventDefault();
|
|
1178
|
+
if (!toolbar.discard)
|
|
1179
|
+
return;
|
|
1180
|
+
try {
|
|
1181
|
+
await Promise.resolve(toolbar.discard());
|
|
1182
|
+
}
|
|
1183
|
+
catch (_err) {
|
|
1184
|
+
console.error('[Thundra] Discard failed:', _err);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
: undefined,
|
|
1188
|
+
restorePreview: async () => {
|
|
1189
|
+
if (!toolbarCtx.edit || !selVerId)
|
|
1190
|
+
return;
|
|
1191
|
+
console.info('[Toolbar:history:restore]', JSON.stringify({
|
|
1192
|
+
path: toolbar?.path ?? null,
|
|
1193
|
+
savedAt: selVerId,
|
|
1194
|
+
sourceLength: previewPack.markdown.length,
|
|
1195
|
+
sourceHead: previewPack.markdown.slice(0, 120),
|
|
1196
|
+
sourceTail: previewPack.markdown.slice(-120),
|
|
1197
|
+
}));
|
|
1198
|
+
if (toolbarCtx.edit.update)
|
|
1199
|
+
toolbarCtx.edit.update({
|
|
1200
|
+
content: previewPack.body,
|
|
1201
|
+
title: selVer?.title || '',
|
|
1202
|
+
description: selVer?.description || '',
|
|
1203
|
+
source: previewPack.markdown,
|
|
1204
|
+
});
|
|
1205
|
+
const setCode = toolbarCtx.edit.setCode ?? toolbarCtx.edit.setSrc;
|
|
1206
|
+
if (setCode)
|
|
1207
|
+
setCode(previewPack.markdown);
|
|
1208
|
+
setVerDialogOpen(false);
|
|
1209
|
+
notify({ error: null, fallback: 'Restored version to local draft. Save to apply.' });
|
|
1210
|
+
},
|
|
1211
|
+
};
|
|
1212
|
+
const insert = toolbar?.insert ?? adapter?.insert;
|
|
1213
|
+
/**
|
|
1214
|
+
* Handles json command.
|
|
1215
|
+
*
|
|
1216
|
+
* @param detail - Toolbar detail describing the requested document update.
|
|
1217
|
+
*/
|
|
1218
|
+
const jsonCmd = (detail) => {
|
|
1219
|
+
if (mode !== 'edit')
|
|
1220
|
+
return false;
|
|
1221
|
+
window.dispatchEvent(new CustomEvent('docs:json-command', { detail }));
|
|
1222
|
+
return true;
|
|
1223
|
+
};
|
|
1224
|
+
/**
|
|
1225
|
+
* Handles json block.
|
|
1226
|
+
*
|
|
1227
|
+
* @param _type - Sanity field type associated with the value.
|
|
1228
|
+
* @param value - Field value being normalized for the toolbar.
|
|
1229
|
+
*/
|
|
1230
|
+
const jsonBlock = (_type, val) => ({
|
|
1231
|
+
_key: crypto.randomUUID(),
|
|
1232
|
+
_type,
|
|
1233
|
+
...val,
|
|
1234
|
+
});
|
|
1235
|
+
// SELECTION HANDLER
|
|
1236
|
+
/**
|
|
1237
|
+
* Safe sel.
|
|
1238
|
+
*
|
|
1239
|
+
* @param fallback - Value passed to the helper.
|
|
1240
|
+
* @param isInline - Value passed to the helper.
|
|
1241
|
+
*
|
|
1242
|
+
* @returns safe Sel.
|
|
1243
|
+
*/
|
|
1244
|
+
const sSel = (fallback, isInline = false) => {
|
|
1245
|
+
let text = '';
|
|
1246
|
+
try {
|
|
1247
|
+
const view = codeViewOf();
|
|
1248
|
+
if (view && view.dom.offsetParent !== null) {
|
|
1249
|
+
if (!view.state.selection.main.empty)
|
|
1250
|
+
text = view.state.sliceDoc(view.state.selection.main.from, view.state.selection.main.to);
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
const winSel = window.getSelection();
|
|
1254
|
+
// explicitly check that the selection is not collapsed to avoid capturing the whole block bounds
|
|
1255
|
+
if (winSel && winSel.rangeCount > 0 && !winSel.isCollapsed)
|
|
1256
|
+
text = winSel.toString();
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
catch {
|
|
1260
|
+
// Fall back to the caller-provided text below.
|
|
1261
|
+
}
|
|
1262
|
+
if (!text.trim())
|
|
1263
|
+
return fallback;
|
|
1264
|
+
// strip structural markdown (tables, asides, newlines) that break inline link/bold syntax
|
|
1265
|
+
// prettier-ignore
|
|
1266
|
+
if (isInline)
|
|
1267
|
+
return text.replace(/[\r\n]+/g, ' ').replace(/(:::|\|\|\||===|\|)/g, '').replace(/\s{2,}/g, ' ').trim() || fallback;
|
|
1268
|
+
return text;
|
|
1269
|
+
};
|
|
1270
|
+
/**
|
|
1271
|
+
* Handles click toolbar button.
|
|
1272
|
+
*
|
|
1273
|
+
* @param label - Label displayed beside the toolbar control.
|
|
1274
|
+
*/
|
|
1275
|
+
const clickToolbarButton = (label) => {
|
|
1276
|
+
const button = document.querySelector(`button[aria-label="${label}"]`);
|
|
1277
|
+
button?.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, button: 0 }));
|
|
1278
|
+
button?.click();
|
|
1279
|
+
};
|
|
1280
|
+
// prettier-ignore
|
|
1281
|
+
const toolbarOpts = {
|
|
1282
|
+
blocks: [
|
|
1283
|
+
{ group: 'type', value: 'p', label: 'Text', icon: TextFontIcon, onSelect: () => undefined },
|
|
1284
|
+
{ group: 'type', value: 'h2', label: 'Heading 2', icon: DOCS_BLOCK_ICONS.heading, onSelect: () => undefined },
|
|
1285
|
+
{ group: 'type', value: 'h3', label: 'Heading 3', icon: DOCS_BLOCK_ICONS.heading, onSelect: () => undefined },
|
|
1286
|
+
{ group: 'type', value: 'h4', label: 'Heading 4', icon: DOCS_BLOCK_ICONS.heading, onSelect: () => undefined },
|
|
1287
|
+
{ group: 'insert', value: 'table', label: 'Insert table', icon: DOCS_BLOCK_ICONS.table, onSelect: () => jsonCmd({ type: 'insert', block: jsonBlock('table', { header: 'Column 1|Column 2', rows: [jsonBlock('row', { cells: [jsonBlock('cell', { content: [jsonBlock('block', { children: [jsonBlock('span', { marks: [], text: 'Text' })], markDefs: [], style: 'normal' })] }), jsonBlock('cell', { content: [jsonBlock('block', { children: [jsonBlock('span', { marks: [], text: 'Text' })], markDefs: [], style: 'normal' })] })] })] }) }) || insert?.(`\n\n<table>\n\t<thead>\n\t\t<tr>\n\t\t\t<th>Column 1</th>\n\t\t\t<th>Column 2</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t<tr>\n\t\t\t<td>Text</td>\n\t\t\t<td>Text</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n`) },
|
|
1288
|
+
{ group: 'insert', value: 'nodes', label: 'Insert nodes', icon: DOCS_BLOCK_ICONS.nodes, onSelect: () => jsonCmd({ type: 'insert', block: jsonBlock('nodes', { edges: [], nodes: [{ id: 'start', type: 'origin', data: { title: 'Start' } }] }) }) || insert?.(EMPTY_NODES) },
|
|
1289
|
+
{ group: 'insert', value: 'code-local', label: 'Insert code block', icon: DOCS_BLOCK_ICONS.code, onSelect: () => insertionActions.insertLocalCode() },
|
|
1290
|
+
{ group: 'insert', value: 'code-received', label: 'Receive value', icon: ShareIcon, onSelect: () => { setEndpointOpen(true); } },
|
|
1291
|
+
{ group: 'insert', value: 'math', label: 'Insert math', icon: DOCS_BLOCK_ICONS.math, onSelect: () => jsonCmd({ type: 'insert', block: jsonBlock('math', { code: "f'(x) = \\lim_{h \\to 0} \\frac{f(x+h) - f(x)}{h}" }) }) || insert?.(`\n\n<DocsMath code="f'(x) = \\lim_{h \\to 0} \\frac{f(x+h) - f(x)}{h}" />\n\n`) },
|
|
1292
|
+
{ group: 'insert', value: 'tsdocs', label: 'Insert TypeDoc', icon: DOCS_BLOCK_ICONS.tsDocs, onSelect: () => jsonCmd({ type: 'insert', block: jsonBlock('tsDocs', { scope: '' }) }) || insert?.(`\n\n<TsDocs scope="" />\n\n`) },
|
|
1293
|
+
{ group: 'insert', value: 'accordion', label: 'Insert accordion', icon: DOCS_BLOCK_ICONS.accordion, onSelect: () => jsonCmd({ type: 'insert', block: jsonBlock('accordion', { items: [jsonBlock('item', { content: [jsonBlock('block', { children: [jsonBlock('span', { marks: [], text: sSel('Content...') })], markDefs: [], style: 'normal' })], title: 'Accordion item' })] }) }) || insert?.(`\n\n=== [Accordion Item]\n\n${sSel('Content...')}\n\n===\n\n`) },
|
|
1294
|
+
{ group: 'insert', value: 'tabs', label: 'Insert tabs', icon: DOCS_BLOCK_ICONS.tabs, onSelect: () => jsonCmd({ type: 'insert', block: jsonBlock('tabs', { items: [jsonBlock('item', { content: [jsonBlock('block', { children: [jsonBlock('span', { marks: [], text: sSel('Content...') })], markDefs: [], style: 'normal' })], title: 'Tab' })] }) }) || insert?.(`\n\n||| [Tabs Item]\n\n${sSel('Content...')}\n\n|||\n\n`) },
|
|
1295
|
+
{ group: 'insert', value: 'aside', label: 'Insert aside', icon: DOCS_BLOCK_ICONS.callout, onSelect: () => jsonCmd({ type: 'insert', block: jsonBlock('callout', { content: [jsonBlock('block', { children: [jsonBlock('span', { marks: [], text: sSel('Content...') })], markDefs: [], style: 'normal' })], kind: 'note', title: 'Note' }) }) || insert?.(`\n\n:::note[Note]\n\n${sSel('Content...')}\n\n:::\n\n`) },
|
|
1296
|
+
{ group: 'insert', value: 'attachment', label: 'Insert image', icon: DOCS_BLOCK_ICONS.docImage, onSelect: () => jsonCmd({ type: 'insert', block: jsonBlock('docImage', { alt: '', url: '' }) }) || clickToolbarButton('Insert attachment') },
|
|
1297
|
+
{ group: 'insert', value: 'component', label: 'Insert component', icon: DOCS_BLOCK_ICONS.code, onSelect: () => { clickToolbarButton('Insert component'); } },
|
|
1298
|
+
...(mode === 'edit' ? [] : (blocks ?? []).map(({ type: v, label: l, icon: i, insert: c }) => ({ group: 'insert', value: v, label: `Insert ${l?.toLowerCase()}`, icon: i, onSelect: () => insert?.(c()) }))),
|
|
1299
|
+
],
|
|
1300
|
+
formats: [
|
|
1301
|
+
{ key: 'bold', lbl: 'Bold', icon: BoldIcon, shortcut: '⌘B', format: (val) => `**${val}**` },
|
|
1302
|
+
{ key: 'italic', lbl: 'Italic', icon: ItalicIcon, shortcut: '⌘I', format: (val) => `_${val}_` },
|
|
1303
|
+
{ key: 'underline', lbl: 'Underline', icon: UnderlineIcon, shortcut: '⌘U', format: (val) => `<u>${val}</u>` },
|
|
1304
|
+
{ key: 'strikethrough', lbl: 'Strikethrough', icon: StrikethroughIcon, shortcut: '⌘X', format: (val) => `~~${val}~~` },
|
|
1305
|
+
{ key: 'link', lbl: 'Link', icon: LinkIcon, shortcut: '⌘K', format: (val) => `[${val}](url)` },
|
|
1306
|
+
],
|
|
1307
|
+
};
|
|
1308
|
+
// DATA
|
|
1309
|
+
/**
|
|
1310
|
+
* Handles insert code.
|
|
1311
|
+
*
|
|
1312
|
+
* @param source - Docs source payload sent to the endpoint.
|
|
1313
|
+
* @param endpoint - API endpoint receiving the toolbar request.
|
|
1314
|
+
*/
|
|
1315
|
+
const insertionActions = {
|
|
1316
|
+
insertLocalCode: () => {
|
|
1317
|
+
if (jsonCmd({
|
|
1318
|
+
type: 'insert',
|
|
1319
|
+
block: jsonBlock('code', { language: 'text', value: 'Code...' }),
|
|
1320
|
+
}))
|
|
1321
|
+
return;
|
|
1322
|
+
insert?.(`\n\n<Code language="text">\n{\`\nCode...\n\`}\n</Code>\n\n`);
|
|
1323
|
+
},
|
|
1324
|
+
insertCode: (src, endpoint = '') => {
|
|
1325
|
+
const code = src === 'received'
|
|
1326
|
+
? `\n\n<Code language="json" source=${JSON.stringify(endpoint)} />\n\n`
|
|
1327
|
+
: '\n\n<Code language="text">\n{`\nCode...\n`}\n</Code>\n\n';
|
|
1328
|
+
if (jsonCmd({
|
|
1329
|
+
type: 'insert',
|
|
1330
|
+
block: jsonBlock('code', src === 'received'
|
|
1331
|
+
? { language: 'json', source: endpoint, value: '' }
|
|
1332
|
+
: { language: 'text', value: 'Code...' }),
|
|
1333
|
+
}))
|
|
1334
|
+
return;
|
|
1335
|
+
if (insert) {
|
|
1336
|
+
const srcLength = codeLength.current;
|
|
1337
|
+
insert(code, codeAtEnd.current || (srcLength > 0 && (codeSel.current?.code?.to ?? -1) >= srcLength), codeSel.current);
|
|
1338
|
+
codeAtEnd.current = false;
|
|
1339
|
+
codeSel.current = null;
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
const curr = toolbarCtx.edit?.code ?? toolbarCtx.edit?.source ?? '';
|
|
1343
|
+
const sel = codeSel.current?.code;
|
|
1344
|
+
const range = codeAtEnd.current || !sel
|
|
1345
|
+
? { from: curr.length, to: curr.length }
|
|
1346
|
+
: { from: Math.min(sel.from, curr.length), to: Math.min(sel.to, curr.length) };
|
|
1347
|
+
const n = `${curr.slice(0, range.from)}${code}${curr.slice(range.to)}`;
|
|
1348
|
+
toolbarCtx.edit?.update({ source: n });
|
|
1349
|
+
toolbarCtx.edit?.setCode(n);
|
|
1350
|
+
if (toolbarCtx.edit) {
|
|
1351
|
+
codeAtEnd.current = false;
|
|
1352
|
+
codeSel.current = null;
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
const view = codeViewOf();
|
|
1356
|
+
if (view) {
|
|
1357
|
+
const range = codeAtEnd.current
|
|
1358
|
+
? { from: view.state.doc.length, to: view.state.doc.length }
|
|
1359
|
+
: (codeSel.current?.code ?? view.state.selection.main);
|
|
1360
|
+
view.dispatch({
|
|
1361
|
+
changes: { from: range.from, insert: code, to: range.to },
|
|
1362
|
+
scrollIntoView: true,
|
|
1363
|
+
selection: { anchor: range.from + code.length },
|
|
1364
|
+
});
|
|
1365
|
+
view.focus();
|
|
1366
|
+
codeAtEnd.current = false;
|
|
1367
|
+
codeSel.current = null;
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
codeAtEnd.current = false;
|
|
1371
|
+
codeSel.current = null;
|
|
1372
|
+
},
|
|
1373
|
+
/*
|
|
1374
|
+
* Handles receive code.
|
|
1375
|
+
*/
|
|
1376
|
+
receiveCode: () => {
|
|
1377
|
+
const src = endpoint.trim();
|
|
1378
|
+
if (!src)
|
|
1379
|
+
return;
|
|
1380
|
+
insertionActions.insertCode('received', src);
|
|
1381
|
+
setEndpoint('');
|
|
1382
|
+
setEndpointOpen(false);
|
|
1383
|
+
},
|
|
1384
|
+
};
|
|
1385
|
+
// KEYBOARD SHORTCUTS
|
|
1386
|
+
useEffect(() => {
|
|
1387
|
+
if (mode === 'view' || !onSave)
|
|
1388
|
+
return;
|
|
1389
|
+
/**
|
|
1390
|
+
* Handles key down.
|
|
1391
|
+
*
|
|
1392
|
+
* @param e - Value passed to the helper.
|
|
1393
|
+
*/
|
|
1394
|
+
const handleKeyDown = (e) => {
|
|
1395
|
+
if ((e.metaKey || e.ctrlKey) && e.key?.toLowerCase() === 's') {
|
|
1396
|
+
e.preventDefault();
|
|
1397
|
+
void onSave(e);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
1401
|
+
return () => {
|
|
1402
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
1403
|
+
};
|
|
1404
|
+
}, [mode, onSave]);
|
|
1405
|
+
// RENDER
|
|
1406
|
+
return (_jsx(ErrorBoundary, { name: "Toolbar", children: _jsxs("div", { className: "docs-toolbar-scroll not-prose pointer-events-auto sticky top-10 isolate z-50 -mx-3 -mt-5 mb-5 w-[calc(100%+1.5rem)] max-w-[calc(100%+1.5rem)] overflow-visible text-primary-12 print:hidden", "data-docs-toolbar-frame": true, "data-docs-toolbar-scroll": true, "data-can-edit": editable ? 'true' : 'false', children: [_jsx("div", { "aria-hidden": "true", className: "docs-toolbar-backdrop pointer-events-none absolute -inset-x-4 -bottom-12 -top-16 z-0" }), _jsx("div", { className: "relative z-10 rounded-lg border border-primary-6 bg-primary-3", children: _jsx(TooltipProvider, { children: _jsx("div", { className: "mdxeditor-toolbar not-prose relative z-10 box-border flex w-full min-w-0 flex-wrap items-center justify-between gap-1.5 gap-x-3 px-2 py-1 text-primary-12 print:hidden", "data-docs-main-toolbar": true, children: mode === 'view' ? (docs && (_jsx(Mode, { editable: editable, mode: mode, disabled: !ready, onModeChange: (v) => {
|
|
1407
|
+
if (!toolbar)
|
|
1408
|
+
return;
|
|
1409
|
+
toolbar.setMode(v).catch((e) => {
|
|
1410
|
+
console.error('[Toolbar:mode] change-failed', { mode: v, error: e });
|
|
1411
|
+
toast.error(e instanceof Error ? e.message : 'Failed to change mode.');
|
|
1412
|
+
clearTransition();
|
|
1413
|
+
});
|
|
1414
|
+
} }))) : mode === 'attachments' ? (
|
|
1415
|
+
// ATTACHMENT FILTERS
|
|
1416
|
+
_jsxs(_Fragment, { children: [docs && (_jsx(Mode, { editable: editable, mode: mode, disabled: !ready, onModeChange: (v) => {
|
|
1417
|
+
if (!toolbar)
|
|
1418
|
+
return;
|
|
1419
|
+
toolbar.setMode(v).catch((e) => {
|
|
1420
|
+
console.error('[Toolbar:mode] change-failed', { mode: v, error: e });
|
|
1421
|
+
toast.error(e instanceof Error ? e.message : 'Failed to change mode.');
|
|
1422
|
+
clearTransition();
|
|
1423
|
+
});
|
|
1424
|
+
} })), _jsx(ToggleGroup, { type: "multiple", value: [...toolbarCtx.attachmentFilter], onValueChange: (v) => {
|
|
1425
|
+
if (v)
|
|
1426
|
+
$attachmentFilter.set(v);
|
|
1427
|
+
}, className: "ml-auto max-w-full flex-wrap justify-end", children: ATTCH_TYPES.map((f) => (_jsx(ToggleGroupItem, { value: f, size: "sm", className: "h-6 px-2 text-xs capitalize", onMouseDown: preserveSel, children: f }, f))) })] })) : (_jsxs(_Fragment, { children: [_jsxs(ButtonGroup, { children: [_jsx(TOOLBAR_COMPONENTS.BlockType, { disabled: !ready, toolbar: toolbar, adapter: adapter, options: toolbarOpts.blocks, json: mode === 'edit' }), toolbarOpts.formats
|
|
1428
|
+
.map(({ key: k, lbl: l, icon: i, shortcut: s, format: f }) => ({
|
|
1429
|
+
key: k,
|
|
1430
|
+
lbl: l,
|
|
1431
|
+
icon: i,
|
|
1432
|
+
shortcut: s,
|
|
1433
|
+
onClick: () => jsonCmd({
|
|
1434
|
+
type: 'decorator',
|
|
1435
|
+
decorator: k === 'strikethrough' ? 'strike-through' : k,
|
|
1436
|
+
}) ||
|
|
1437
|
+
(adapter?.format
|
|
1438
|
+
? adapter.format(k === 'strikethrough'
|
|
1439
|
+
? 'strike'
|
|
1440
|
+
: k)
|
|
1441
|
+
: insert?.(f(sSel('text', true)))),
|
|
1442
|
+
}))
|
|
1443
|
+
.map(({ key: k, lbl: l, icon: i, shortcut: s, onClick: o }) => (_jsx(Button, { lbl: l, shortcut: s, disabled: !ready, onMouseDown: preserveSel, onClick: o, children: React.createElement(i) }, k))), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { lbl: "Insert list", disabled: !ready, onMouseDown: preserveSel, children: _jsx(BulletListIcon, {}) }) }), _jsx(DropdownMenuContent, { align: "start", children: [
|
|
1444
|
+
{
|
|
1445
|
+
key: 'bullet',
|
|
1446
|
+
lbl: 'Bulleted list',
|
|
1447
|
+
icon: BulletListIcon,
|
|
1448
|
+
prefix: '\n- ',
|
|
1449
|
+
fallback: 'Item',
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
key: 'number',
|
|
1453
|
+
lbl: 'Numbered list',
|
|
1454
|
+
icon: NumberListIcon,
|
|
1455
|
+
prefix: '\n1. ',
|
|
1456
|
+
fallback: 'Item',
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
key: 'check',
|
|
1460
|
+
lbl: 'Checklist',
|
|
1461
|
+
icon: CheckListIcon,
|
|
1462
|
+
prefix: '\n- [ ] ',
|
|
1463
|
+
fallback: 'Task',
|
|
1464
|
+
},
|
|
1465
|
+
]
|
|
1466
|
+
.map(({ key: k, lbl: l, icon: i, prefix: p, fallback: f }) => ({
|
|
1467
|
+
key: k,
|
|
1468
|
+
lbl: l,
|
|
1469
|
+
icon: i,
|
|
1470
|
+
onClick: () => jsonCmd({ type: 'list', list: k === 'check' ? 'bullet' : k }) ||
|
|
1471
|
+
(adapter?.toggleList
|
|
1472
|
+
? adapter.toggleList(k)
|
|
1473
|
+
: toolbar?.toggleList
|
|
1474
|
+
? toolbar.toggleList(k)
|
|
1475
|
+
: insert?.(`${p}${sSel(f)}\n`)),
|
|
1476
|
+
}))
|
|
1477
|
+
.map(({ key: k, lbl: l, icon: i, onClick: o }) => (_jsxs(DropdownMenuItem, { disabled: !ready, onSelect: () => {
|
|
1478
|
+
if (o)
|
|
1479
|
+
o();
|
|
1480
|
+
}, className: MENU_GAP_CLASS, children: [React.createElement(i, { className: ICON_CLASS }), _jsx("span", { children: l })] }, k))) })] })] }), _jsxs("div", { className: "flex items-center gap-1", children: [docs && (_jsx(Mode, { editable: editable, mode: mode, disabled: !ready, onModeChange: (v) => {
|
|
1481
|
+
if (!editable || !toolbar)
|
|
1482
|
+
return;
|
|
1483
|
+
toolbar.setMode(v).catch((e) => {
|
|
1484
|
+
console.error('[Toolbar:mode] change-failed', { mode: v, error: e });
|
|
1485
|
+
toast.error(e instanceof Error ? e.message : 'Failed to change mode.');
|
|
1486
|
+
clearTransition();
|
|
1487
|
+
});
|
|
1488
|
+
} })), _jsx(TextTooltip, { content: "Find (\u2318F)", side: "top", align: "center", delayDuration: 400, children: docs && _jsx(FindReplace, { mode: mode, toolbar: toolbar }) }), _jsx(TOOLBAR_COMPONENTS.Undo, { onMouseDown: preserveSel, disabled: !ready, adapter: adapter }), docs && (_jsxs(_Fragment, { children: [_jsx(UiButton, { size: "xs", variant: "tonal", disabled: !ready || !dirty || saving !== null || (toolbar?.optimistic ?? false), onMouseDown: (e) => {
|
|
1489
|
+
e.preventDefault();
|
|
1490
|
+
}, onClick: (e) => void toolbarActions.discard?.(e), children: "Discard" }), _jsxs(ButtonGroup, { children: [_jsx(TextTooltip, { content: "Save (\u2318S)", side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { size: "xs", disabled: !ready || !dirty, loading: saving === 'saving', onMouseDown: (e) => {
|
|
1491
|
+
e.preventDefault();
|
|
1492
|
+
}, onClick: (e) => void onSave?.(e), children: "Save" }) }), _jsx(TextTooltip, { content: "Version History", side: "top", align: "center", delayDuration: 400, children: _jsx(UiButton, { size: "xs", className: "px-0.5", disabled: !ready, "aria-label": "Version History", onMouseDown: (e) => {
|
|
1493
|
+
e.preventDefault();
|
|
1494
|
+
}, onClick: () => {
|
|
1495
|
+
if (vers.length > 0 && !selVerId)
|
|
1496
|
+
setSelVerId(vers[0].savedAt);
|
|
1497
|
+
setVerDialogOpen(true);
|
|
1498
|
+
}, children: _jsx(CaretDownIcon, {}) }) }), _jsx(Dialog, { open: verDialogOpen, onOpenChange: setVerDialogOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Version History" }), _jsx(DialogDescription, { className: "sr-only", children: "Select a version to preview and restore." }), _jsxs(Select, { value: selVerId, onValueChange: setSelVerId, children: [_jsx(SelectTrigger, { className: "h-14", "aria-label": "Select a version...", children: _jsx(SelectValue, { placeholder: "Select a version..." }) }), _jsx(SelectContent, { children: vers.map((v) => (_jsx(SelectItem, { value: v.savedAt, children: _jsxs("span", { className: "flex flex-col text-left", children: [_jsxs("span", { children: [v.title, " (", new Date(v.savedAt).toLocaleString(), ")"] }), _jsx("span", { className: SECONDARY_TEXT_CLASS, children: v.description })] }) }, v.savedAt))) })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx(CodeBlock, { tone: "view", language: "mdx", docsSource: true, code: previewPack.markdown.trim() || ' ' }) }), _jsxs(DialogFooter, { children: [_jsx(UiButton, { variant: 'ghost', onClick: () => { setVerDialogOpen(false); }, children: "Cancel" }), _jsx(UiButton, { disabled: !selVer, onClick: toolbarActions.restorePreview, children: "Restore" })] })] }) })] }), docs && publishDraft && (_jsx(UiButton, { size: "xs", disabled: !ready || saving === 'saving', onMouseDown: (e) => e.preventDefault(), onClick: () => void publish(), children: "Publish" }))] }))] }), _jsxs("div", { className: "flex items-center gap-0.5", children: [_jsx(TOOLBAR_COMPONENTS.Uploader, { icon: ImageIcon, insert: insert, onMouseDown: preserveSel, disabled: !ready, ctx: toolbar?.path }), _jsx(TOOLBAR_COMPONENTS.Custom, { onInsert: (_statement, v) => insert?.(v, true), onInsertMd: insert, onMouseDown: preserveSel, disabled: !ready, ctx: toolbar?.path })] }), _jsx(Dialog, { open: endpointOpen, onOpenChange: (o) => {
|
|
1499
|
+
setEndpointOpen(o);
|
|
1500
|
+
if (!o)
|
|
1501
|
+
setEndpoint('');
|
|
1502
|
+
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Receive value" }), _jsx(DialogDescription, { className: "sr-only", children: "Enter an API endpoint to receive code value into the editor." })] }), _jsx(Label, { htmlFor: "docs-received-code-endpoint", children: "API endpoint" }), _jsx(Input, { id: "docs-received-code-endpoint", value: endpoint, onChange: (e) => {
|
|
1503
|
+
setEndpoint(e.target.value);
|
|
1504
|
+
}, placeholder: "/api/docs?action=testimonials" }), _jsxs("div", { className: 'flex justify-end gap-2', children: [_jsx(UiButton, { variant: 'outline', onClick: () => { setEndpointOpen(false); }, children: "Cancel" }), _jsx(UiButton, { disabled: !endpoint.trim(), onClick: insertionActions.receiveCode, children: "Receive value" })] })] }) })] })) }) }) })] }) }));
|
|
1505
|
+
},
|
|
1506
|
+
};
|
|
1507
|
+
export const { DocsToolbarActions, Toolbar } = TOOLBAR_COMPONENTS;
|
|
1508
|
+
//# sourceMappingURL=_toolbar.js.map
|