@terra-symposium/docs 0.1.17 → 0.1.72
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/README.md +26 -7
- package/bin/terra-docs.mjs +187 -0
- 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-BoldItalic.woff +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/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/{Docs.d.ts → components/Docs.d.ts} +1 -1
- 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/{LocalDocsWorkspace.d.ts → components/LocalDocsWorkspace.d.ts} +9 -2
- package/lib-dist/components/LocalDocsWorkspace.d.ts.map +1 -0
- package/lib-dist/{LocalDocsWorkspace.js → components/LocalDocsWorkspace.js} +83 -24
- 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/{StaticWorkspace.d.ts → components/StaticWorkspace.d.ts} +7 -3
- package/lib-dist/components/StaticWorkspace.d.ts.map +1 -0
- package/lib-dist/{StaticWorkspace.js → components/StaticWorkspace.js} +106 -34
- 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.map +1 -0
- package/lib-dist/{blocks.js → components/blocks.js} +88 -13
- package/lib-dist/components/blocks.js.map +1 -0
- package/lib-dist/{navigation.d.ts → components/navigation.d.ts} +3 -1
- package/lib-dist/components/navigation.d.ts.map +1 -0
- package/lib-dist/components/navigation.js.map +1 -0
- package/lib-dist/{terra → components/terra}/Workspace.d.ts +1 -1
- 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 +16 -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/{terra → components/terra}/editor/_blocks.d.ts +3 -4
- 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/{terra → components/terra}/editor/docs/_Callout.d.ts +1 -1
- package/lib-dist/components/terra/editor/docs/_Callout.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_Callout.js +11 -7
- package/lib-dist/components/terra/editor/docs/_Callout.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/_CodeBlock.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_CodeBlock.js +111 -46
- package/lib-dist/components/terra/editor/docs/_CodeBlock.js.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_DocsCode.d.ts +1 -1
- package/lib-dist/components/terra/editor/docs/_DocsCode.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_DocsCode.js +47 -14
- package/lib-dist/components/terra/editor/docs/_DocsCode.js.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_DocsImage.d.ts +1 -1
- 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/{terra → components/terra}/editor/docs/_Embed.d.ts +3 -1
- 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/{terra → components/terra}/editor/docs/_Math.d.ts +2 -2
- package/lib-dist/components/terra/editor/docs/_Math.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_Math.js +29 -17
- package/lib-dist/components/terra/editor/docs/_Math.js.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_Nodes.d.ts +1 -1
- 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/{terra → components/terra}/editor/docs/_Raw.d.ts +1 -1
- package/lib-dist/components/terra/editor/docs/_Raw.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_Raw.js +8 -3
- package/lib-dist/components/terra/editor/docs/_Raw.js.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_Table.d.ts +1 -1
- 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/{terra → components/terra}/editor/docs/_Tabs.d.ts +1 -1
- package/lib-dist/components/terra/editor/docs/_Tabs.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_Tabs.js +36 -15
- package/lib-dist/components/terra/editor/docs/_Tabs.js.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_TsDocs.d.ts +1 -1
- package/lib-dist/components/terra/editor/docs/_TsDocs.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_TsDocs.js +16 -14
- package/lib-dist/components/terra/editor/docs/_TsDocs.js.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_math_inline.d.ts +1 -1
- package/lib-dist/components/terra/editor/docs/_math_inline.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/_math_inline.js +2 -2
- package/lib-dist/components/terra/editor/docs/_math_inline.js.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/heading.d.ts +1 -1
- package/lib-dist/components/terra/editor/docs/heading.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/docs/heading.js +8 -5
- package/lib-dist/components/terra/editor/docs/heading.js.map +1 -0
- package/lib-dist/components/terra/editor/docs/index.d.ts.map +1 -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/{terra → components/terra}/editor/types.d.ts +2 -2
- package/lib-dist/components/terra/editor/types.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/editor/types.js +1 -1
- package/lib-dist/components/terra/editor/types.js.map +1 -0
- package/lib-dist/{terra → components/terra}/flow/Nodes.d.ts +2 -3
- package/lib-dist/components/terra/flow/Nodes.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/flow/Nodes.js +213 -106
- package/lib-dist/components/terra/flow/Nodes.js.map +1 -0
- package/lib-dist/{terra → components/terra}/flow/_chrome.d.ts +7 -9
- package/lib-dist/components/terra/flow/_chrome.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/flow/_chrome.js +329 -118
- package/lib-dist/components/terra/flow/_chrome.js.map +1 -0
- package/lib-dist/components/terra/flow/menuContext.d.ts.map +1 -0
- package/lib-dist/components/terra/flow/menuContext.js.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_attachments.d.ts +1 -1
- package/lib-dist/components/terra/pages/docs/_attachments.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_attachments.js +375 -131
- package/lib-dist/components/terra/pages/docs/_attachments.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_blocks.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_blocks.js +42 -31
- package/lib-dist/components/terra/pages/docs/_blocks.js.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_editable.d.ts +2 -3
- package/lib-dist/components/terra/pages/docs/_editable.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_editable.js +82 -44
- package/lib-dist/components/terra/pages/docs/_editable.js.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_export-dialog.d.ts +1 -1
- package/lib-dist/components/terra/pages/docs/_export-dialog.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_export-dialog.js +17 -17
- package/lib-dist/components/terra/pages/docs/_export-dialog.js.map +1 -0
- package/lib-dist/components/terra/pages/docs/_icons.d.ts.map +1 -0
- package/lib-dist/components/terra/pages/docs/_icons.js.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_main.d.ts +10 -7
- 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/{terra → components/terra}/pages/docs/_sidebar.d.ts +3 -12
- 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/{terra → components/terra}/pages/docs/_toolbar.d.ts +3 -5
- package/lib-dist/components/terra/pages/docs/_toolbar.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_toolbar.js +318 -350
- package/lib-dist/components/terra/pages/docs/_toolbar.js.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_tree.d.ts +1 -4
- package/lib-dist/components/terra/pages/docs/_tree.d.ts.map +1 -0
- package/lib-dist/{terra → components/terra}/pages/docs/_tree.js +219 -264
- 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/{terra/runtime → components/terra}/ui.d.ts +2 -5
- 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-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.map +1 -0
- package/lib-dist/{host-runtime.js → lib/host-runtime.js} +4 -2
- package/lib-dist/lib/host-runtime.js.map +1 -0
- package/lib-dist/{host.d.ts → lib/host.d.ts} +20 -0
- package/lib-dist/lib/host.d.ts.map +1 -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/{markdown-model.d.ts → lib/markdown-model.d.ts} +3 -3
- package/lib-dist/lib/markdown-model.d.ts.map +1 -0
- package/lib-dist/lib/markdown-model.js +2030 -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/{schema.d.ts → lib/schema.d.ts} +55 -23
- package/lib-dist/lib/schema.d.ts.map +1 -0
- package/lib-dist/{schema.js → lib/schema.js} +26 -18
- package/lib-dist/lib/schema.js.map +1 -0
- package/lib-dist/{terra/pages/docs/_custom.d.ts → lib/terra/custom-components.d.ts} +2 -2
- package/lib-dist/lib/terra/custom-components.d.ts.map +1 -0
- package/lib-dist/{terra/pages/docs/_custom.js → lib/terra/custom-components.js} +1 -1
- 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 +185 -0
- package/lib-dist/lib/terra/docs/state.d.ts.map +1 -0
- package/lib-dist/lib/terra/docs/state.js +254 -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/{terra/pages/docs/_helpers.d.ts → lib/terra/docs/workspace.d.ts} +6 -8
- package/lib-dist/lib/terra/docs/workspace.d.ts.map +1 -0
- package/lib-dist/{terra/pages/docs/_helpers.js → lib/terra/docs/workspace.js} +39 -28
- package/lib-dist/lib/terra/docs/workspace.js.map +1 -0
- package/lib-dist/lib/terra/flow/_layout.d.ts.map +1 -0
- package/lib-dist/{terra → lib/terra}/flow/_layout.js +16 -6
- package/lib-dist/lib/terra/flow/_layout.js.map +1 -0
- package/lib-dist/{terra → lib/terra}/flow/powerSource.d.ts +1 -2
- package/lib-dist/lib/terra/flow/powerSource.d.ts.map +1 -0
- package/lib-dist/{terra → lib/terra}/flow/powerSource.js +10 -4
- package/lib-dist/lib/terra/flow/powerSource.js.map +1 -0
- package/lib-dist/{terra → lib/terra}/regex.mjs +11 -6
- 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/{terra → lib/terra}/runtime/action-proxy.js +1 -1
- 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.map +1 -0
- package/lib-dist/{terra → lib/terra}/runtime/content.js +147 -62
- package/lib-dist/lib/terra/runtime/content.js.map +1 -0
- package/lib-dist/lib/terra/runtime/leaf.d.ts.map +1 -0
- package/lib-dist/{terra → lib/terra}/runtime/leaf.js +1 -1
- package/lib-dist/lib/terra/runtime/leaf.js.map +1 -0
- package/lib-dist/lib/terra/runtime/nodes.d.ts.map +1 -0
- package/lib-dist/{terra → lib/terra}/runtime/nodes.js +103 -31
- package/lib-dist/lib/terra/runtime/nodes.js.map +1 -0
- package/lib-dist/{terra → lib/terra}/runtime/runtime.d.ts +8 -2
- 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.map +1 -0
- package/lib-dist/{terra → lib/terra}/runtime/shared.js +56 -11
- 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/{terra → lib/terra}/runtime/types.js +28 -2
- package/lib-dist/lib/terra/runtime/types.js.map +1 -0
- package/lib-dist/lib/workspace-types.d.ts +32 -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 -1
- package/package.json +63 -38
- package/site/astro.config.mjs +67 -0
- package/site/docs-content-plugin.mjs +62 -0
- package/site/src/components/DocsSiteSearch.tsx +108 -0
- package/site/src/lib/catalog.ts +151 -0
- package/site/src/pages/404.astro +38 -0
- package/site/src/pages/[...slug].astro +49 -0
- package/site/src/styles/search.css +56 -0
- package/src/components/NotFound.astro +6 -0
- package/src/components/NotFound.tsx +48 -0
- package/src/{SiteLayout.astro → components/SiteLayout.astro} +17 -5
- package/src/{navigation.tsx → components/navigation.tsx} +3 -1
- package/lib-dist/Docs.d.ts.map +0 -1
- package/lib-dist/Docs.js +0 -60
- package/lib-dist/Docs.js.map +0 -1
- package/lib-dist/LocalDocsWorkspace.d.ts.map +0 -1
- package/lib-dist/LocalDocsWorkspace.js.map +0 -1
- package/lib-dist/SiteLayout.d.ts +0 -30
- package/lib-dist/SiteLayout.d.ts.map +0 -1
- package/lib-dist/SiteLayout.js +0 -34
- package/lib-dist/SiteLayout.js.map +0 -1
- package/lib-dist/StaticWorkspace.d.ts.map +0 -1
- package/lib-dist/StaticWorkspace.js.map +0 -1
- package/lib-dist/_Toolbar.d.ts +0 -7
- package/lib-dist/_Toolbar.d.ts.map +0 -1
- package/lib-dist/_Toolbar.js +0 -85
- package/lib-dist/_Toolbar.js.map +0 -1
- package/lib-dist/blocks.d.ts.map +0 -1
- package/lib-dist/blocks.js.map +0 -1
- package/lib-dist/docs-catalog.d.ts +0 -5
- package/lib-dist/docs-catalog.d.ts.map +0 -1
- package/lib-dist/docs-catalog.js +0 -38
- package/lib-dist/docs-catalog.js.map +0 -1
- package/lib-dist/docs-style.css +0 -31
- package/lib-dist/fonts/README.md +0 -5
- package/lib-dist/host-runtime.d.ts.map +0 -1
- package/lib-dist/host-runtime.js.map +0 -1
- package/lib-dist/host.d.ts.map +0 -1
- package/lib-dist/host.js.map +0 -1
- package/lib-dist/index.d.ts +0 -35
- package/lib-dist/index.d.ts.map +0 -1
- package/lib-dist/index.js +0 -28
- package/lib-dist/index.js.map +0 -1
- package/lib-dist/markdown-model.d.ts.map +0 -1
- package/lib-dist/markdown-model.js +0 -181
- package/lib-dist/markdown-model.js.map +0 -1
- package/lib-dist/markdown.d.ts +0 -3
- package/lib-dist/markdown.d.ts.map +0 -1
- package/lib-dist/markdown.js +0 -83
- package/lib-dist/markdown.js.map +0 -1
- package/lib-dist/navigation.d.ts.map +0 -1
- package/lib-dist/navigation.js.map +0 -1
- package/lib-dist/schema.d.ts.map +0 -1
- package/lib-dist/schema.js.map +0 -1
- package/lib-dist/terra/Workspace.d.ts.map +0 -1
- package/lib-dist/terra/Workspace.js +0 -9
- package/lib-dist/terra/Workspace.js.map +0 -1
- package/lib-dist/terra/editor/TerraContentEditor.d.ts +0 -10
- package/lib-dist/terra/editor/TerraContentEditor.d.ts.map +0 -1
- package/lib-dist/terra/editor/TerraContentEditor.js +0 -47
- package/lib-dist/terra/editor/TerraContentEditor.js.map +0 -1
- package/lib-dist/terra/editor/_blocks.d.ts.map +0 -1
- package/lib-dist/terra/editor/_blocks.js +0 -110
- package/lib-dist/terra/editor/_blocks.js.map +0 -1
- package/lib-dist/terra/editor/docs/_Callout.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_Callout.js.map +0 -1
- package/lib-dist/terra/editor/docs/_CodeBlock.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_CodeBlock.js.map +0 -1
- package/lib-dist/terra/editor/docs/_DocsCode.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_DocsCode.js.map +0 -1
- package/lib-dist/terra/editor/docs/_DocsImage.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_DocsImage.js +0 -62
- package/lib-dist/terra/editor/docs/_DocsImage.js.map +0 -1
- package/lib-dist/terra/editor/docs/_Embed.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_Embed.js +0 -27
- package/lib-dist/terra/editor/docs/_Embed.js.map +0 -1
- package/lib-dist/terra/editor/docs/_Math.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_Math.js.map +0 -1
- package/lib-dist/terra/editor/docs/_Nodes.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_Nodes.js +0 -52
- package/lib-dist/terra/editor/docs/_Nodes.js.map +0 -1
- package/lib-dist/terra/editor/docs/_Raw.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_Raw.js.map +0 -1
- package/lib-dist/terra/editor/docs/_Table.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_Table.js +0 -71
- package/lib-dist/terra/editor/docs/_Table.js.map +0 -1
- package/lib-dist/terra/editor/docs/_Tabs.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_Tabs.js.map +0 -1
- package/lib-dist/terra/editor/docs/_TsDocs.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_TsDocs.js.map +0 -1
- package/lib-dist/terra/editor/docs/_math_inline.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/_math_inline.js.map +0 -1
- package/lib-dist/terra/editor/docs/heading.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/heading.js.map +0 -1
- package/lib-dist/terra/editor/docs/index.d.ts.map +0 -1
- package/lib-dist/terra/editor/docs/index.js.map +0 -1
- package/lib-dist/terra/editor/types.d.ts.map +0 -1
- package/lib-dist/terra/editor/types.js.map +0 -1
- package/lib-dist/terra/flow/Nodes.d.ts.map +0 -1
- package/lib-dist/terra/flow/Nodes.js.map +0 -1
- package/lib-dist/terra/flow/_chrome.d.ts.map +0 -1
- package/lib-dist/terra/flow/_chrome.js.map +0 -1
- package/lib-dist/terra/flow/_layout.d.ts.map +0 -1
- package/lib-dist/terra/flow/_layout.js.map +0 -1
- package/lib-dist/terra/flow/menuContext.d.ts.map +0 -1
- package/lib-dist/terra/flow/menuContext.js.map +0 -1
- package/lib-dist/terra/flow/powerSource.d.ts.map +0 -1
- package/lib-dist/terra/flow/powerSource.js.map +0 -1
- package/lib-dist/terra/pages/docs/_attachments.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_attachments.js.map +0 -1
- package/lib-dist/terra/pages/docs/_blocks.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_blocks.js.map +0 -1
- package/lib-dist/terra/pages/docs/_custom.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_custom.js.map +0 -1
- package/lib-dist/terra/pages/docs/_editable.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_editable.js.map +0 -1
- package/lib-dist/terra/pages/docs/_export-dialog.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_export-dialog.js.map +0 -1
- package/lib-dist/terra/pages/docs/_helpers.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_helpers.js.map +0 -1
- package/lib-dist/terra/pages/docs/_icons.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_icons.js.map +0 -1
- package/lib-dist/terra/pages/docs/_main.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_main.js +0 -1959
- package/lib-dist/terra/pages/docs/_main.js.map +0 -1
- package/lib-dist/terra/pages/docs/_sidebar.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_sidebar.js +0 -2450
- package/lib-dist/terra/pages/docs/_sidebar.js.map +0 -1
- package/lib-dist/terra/pages/docs/_toolbar.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_toolbar.js.map +0 -1
- package/lib-dist/terra/pages/docs/_tree.d.ts.map +0 -1
- package/lib-dist/terra/pages/docs/_tree.js.map +0 -1
- package/lib-dist/terra/runtime/action-proxy.d.ts +0 -5
- package/lib-dist/terra/runtime/action-proxy.d.ts.map +0 -1
- package/lib-dist/terra/runtime/action-proxy.js.map +0 -1
- package/lib-dist/terra/runtime/content.d.ts.map +0 -1
- package/lib-dist/terra/runtime/content.js.map +0 -1
- package/lib-dist/terra/runtime/leaf.d.ts.map +0 -1
- package/lib-dist/terra/runtime/leaf.js.map +0 -1
- package/lib-dist/terra/runtime/nodes.d.ts.map +0 -1
- package/lib-dist/terra/runtime/nodes.js.map +0 -1
- package/lib-dist/terra/runtime/runtime.d.ts.map +0 -1
- package/lib-dist/terra/runtime/runtime.js +0 -50
- package/lib-dist/terra/runtime/runtime.js.map +0 -1
- package/lib-dist/terra/runtime/shared.d.ts.map +0 -1
- package/lib-dist/terra/runtime/shared.js.map +0 -1
- package/lib-dist/terra/runtime/types.d.ts +0 -266
- package/lib-dist/terra/runtime/types.d.ts.map +0 -1
- package/lib-dist/terra/runtime/types.js.map +0 -1
- package/lib-dist/terra/runtime/ui.d.ts.map +0 -1
- package/lib-dist/terra/runtime/ui.js +0 -16
- package/lib-dist/terra/runtime/ui.js.map +0 -1
- package/lib-dist/workspace.d.ts +0 -78
- package/lib-dist/workspace.d.ts.map +0 -1
- package/lib-dist/workspace.js +0 -151
- package/lib-dist/workspace.js.map +0 -1
- package/src/style.css +0 -733
- /package/lib-dist/{fonts → assets/fonts}/Avara-Bold.woff2 +0 -0
- /package/lib-dist/{fonts → assets/fonts}/Avara-BoldItalic.woff2 +0 -0
- /package/lib-dist/{fonts → assets/fonts}/OFL-1.1.txt +0 -0
- /package/lib-dist/{blocks.d.ts → components/blocks.d.ts} +0 -0
- /package/lib-dist/{navigation.js → components/navigation.js} +0 -0
- /package/lib-dist/{terra → components/terra}/editor/docs/_CodeBlock.d.ts +0 -0
- /package/lib-dist/{terra → components/terra}/editor/docs/index.d.ts +0 -0
- /package/lib-dist/{terra → components/terra}/editor/docs/index.js +0 -0
- /package/lib-dist/{terra → components/terra}/flow/menuContext.d.ts +0 -0
- /package/lib-dist/{terra → components/terra}/flow/menuContext.js +0 -0
- /package/lib-dist/{terra → components/terra}/pages/docs/_blocks.d.ts +0 -0
- /package/lib-dist/{terra → components/terra}/pages/docs/_icons.d.ts +0 -0
- /package/lib-dist/{terra → components/terra}/pages/docs/_icons.js +0 -0
- /package/lib-dist/{host-runtime.d.ts → lib/host-runtime.d.ts} +0 -0
- /package/lib-dist/{host.js → lib/host.js} +0 -0
- /package/lib-dist/{terra → lib/terra}/flow/_layout.d.ts +0 -0
- /package/lib-dist/{terra → lib/terra}/runtime/content.d.ts +0 -0
- /package/lib-dist/{terra → lib/terra}/runtime/leaf.d.ts +0 -0
- /package/lib-dist/{terra → lib/terra}/runtime/nodes.d.ts +0 -0
- /package/lib-dist/{terra → lib/terra}/runtime/shared.d.ts +0 -0
|
@@ -1,2450 +0,0 @@
|
|
|
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/_sidebar.tsx
|
|
5
|
-
* Docs sidebar entrypoint, state management, and unified dialog orchestrators.
|
|
6
|
-
* Folder boundary: sidebar navigation and its dialogs; the recursive tree structure remains in `_tree.tsx`.
|
|
7
|
-
*/
|
|
8
|
-
/*//////////////////////////////////////////////////
|
|
9
|
-
IMPORTS
|
|
10
|
-
//////////////////////////////////////////////////*/
|
|
11
|
-
// API
|
|
12
|
-
import { $leafTitle, $mode, $transition, $unsavedDrafts, clearTransition, extractAllNodeIds, getHue, getStableKey, isIndexFile, memoryDrafts, runMutation, sanitizeName, setTransition, uploadAssetChunked, useAtom, } from './_helpers.js';
|
|
13
|
-
import { actions } from '../../runtime/action-proxy.js';
|
|
14
|
-
import { bName, buildRoute as buildRoute, cTitle, dirname, docsPath, docsRootPath, FILES, isDocsPath, parseRoute, toNodeLabel, toPaths } from '../../runtime/shared.js';
|
|
15
|
-
import { cn, createTrace, notify } from '../../runtime/runtime.js';
|
|
16
|
-
import { isDevOrigin } from '../../runtime/runtime.js';
|
|
17
|
-
import { components as repoComponents } from './_custom.js';
|
|
18
|
-
import { currLeafSrc } from '../../runtime/leaf.js';
|
|
19
|
-
import { toast } from 'sonner';
|
|
20
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
21
|
-
// COMPONENTS
|
|
22
|
-
import * as TerraQualityRegex from '../../regex.mjs';
|
|
23
|
-
import React from 'react';
|
|
24
|
-
import Tree, { Menu } from './_tree.js';
|
|
25
|
-
import User from '../../runtime/ui.js';
|
|
26
|
-
import { Add as AddIcon, ArrowLeft as LeftArrowIcon, ArrowUpRight as PopOutIcon, Box as BoxIcon, Catalog as CatalogIcon, ColorPalette as ColorIcon, Doc as DocumentIcon, Download as DownloadIcon, Edit as EditIcon, Image as ImageIcon, Link as LinkIcon, LogoYoutube as YouTubeIcon, MagicWand as MagicIcon, OverflowMenuVertical as OverflowMenuIcon, TrashCan as TrashIcon, Upload as UploadIcon, } from '@carbon/icons-react';
|
|
27
|
-
import { AlertError } from '@terra-symposium/styling/ui/terra-original';
|
|
28
|
-
import { Button } from '@terra-symposium/styling/ui/terra-original';
|
|
29
|
-
import { Card } from '@terra-symposium/styling/ui/terra-original';
|
|
30
|
-
import { ContextMenu, ContextMenuContent, ContextMenuTrigger } from '@terra-symposium/styling/ui/terra-original';
|
|
31
|
-
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@terra-symposium/styling/ui/terra-original';
|
|
32
|
-
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@terra-symposium/styling/ui/terra-original';
|
|
33
|
-
import { ErrorBoundary } from '../../runtime/ui.js';
|
|
34
|
-
import { Empty } from '../../runtime/ui.js';
|
|
35
|
-
import { ExportDialog as SharedExportDialog } from './_export-dialog.js';
|
|
36
|
-
import { Input } from '@terra-symposium/styling/ui/terra-original';
|
|
37
|
-
import { Label } from '@terra-symposium/styling/ui/terra-original';
|
|
38
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@terra-symposium/styling/ui/terra-original';
|
|
39
|
-
import { Separator } from '@terra-symposium/styling/ui/terra-original';
|
|
40
|
-
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarRail } from '@terra-symposium/styling/ui/terra-original';
|
|
41
|
-
import { Slider } from '@terra-symposium/styling/ui/terra-original';
|
|
42
|
-
import { Spinner } from '@terra-symposium/styling/ui/terra-original';
|
|
43
|
-
import { Textarea } from '@terra-symposium/styling/ui/terra-original';
|
|
44
|
-
import { TsDocsSelect } from './_blocks.js';
|
|
45
|
-
// prettier-ignore
|
|
46
|
-
/*//////////////////////////////////////////////////
|
|
47
|
-
CONFIG
|
|
48
|
-
//////////////////////////////////////////////////*/
|
|
49
|
-
/**
|
|
50
|
-
* Handles get sibs.
|
|
51
|
-
*
|
|
52
|
-
* @param nodes - Docs tree nodes used to build the sidebar entries.
|
|
53
|
-
* @param tgtPath - Target path used to identify the active sidebar item.
|
|
54
|
-
*/
|
|
55
|
-
const getSibs = (nodes, tgtPath) => {
|
|
56
|
-
if (!tgtPath || tgtPath === '.')
|
|
57
|
-
return nodes;
|
|
58
|
-
let cur = nodes;
|
|
59
|
-
for (const seg of tgtPath.split('/')) {
|
|
60
|
-
let match;
|
|
61
|
-
for (const n of cur) {
|
|
62
|
-
if (n.type === 'branch' && n.origName === seg) {
|
|
63
|
-
match = n;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (match?.children)
|
|
68
|
-
cur = match.children;
|
|
69
|
-
else
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
return cur;
|
|
73
|
-
};
|
|
74
|
-
/**
|
|
75
|
-
* SANITY MANY-TO-MANY ROUTING ARCHITECTURE
|
|
76
|
-
*
|
|
77
|
-
* Parses explicit Sanity IDs directly from the URL.
|
|
78
|
-
* A single leaf or group can belong to multiple parents. Canonical routing keeps the active
|
|
79
|
-
* section anchored to the first path segment so the sidebar select never drifts.
|
|
80
|
-
*
|
|
81
|
-
* @param pathname - Current browser pathname used to mark the active route.
|
|
82
|
-
*/
|
|
83
|
-
const route = (pathname) => parseRoute(pathname || docsRootPath());
|
|
84
|
-
/** Keeps matching leaves visible while retaining their original section and group hierarchy. */
|
|
85
|
-
const filterTree = (nodes = [], query) => {
|
|
86
|
-
const normalized = query.trim().toLocaleLowerCase();
|
|
87
|
-
if (!normalized)
|
|
88
|
-
return nodes;
|
|
89
|
-
return nodes.flatMap(node => {
|
|
90
|
-
const label = cTitle(toNodeLabel(node.title || node.origName)).toLocaleLowerCase();
|
|
91
|
-
if (label.includes(normalized))
|
|
92
|
-
return [node];
|
|
93
|
-
if (node.type !== 'branch')
|
|
94
|
-
return [];
|
|
95
|
-
const children = filterTree(node.children ?? [], normalized);
|
|
96
|
-
return children.length ? [{ ...node, children }] : [];
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
/**
|
|
100
|
-
* Reads the typed detail payload from a DOM event.
|
|
101
|
-
*
|
|
102
|
-
* @param e - DOM event carrying the custom payload.
|
|
103
|
-
* @returns The event detail payload.
|
|
104
|
-
*/
|
|
105
|
-
const eDetail = (e) => e.detail;
|
|
106
|
-
/**
|
|
107
|
-
* Handles latest content.
|
|
108
|
-
*
|
|
109
|
-
* @param content - Docs content used to derive the sidebar structure.
|
|
110
|
-
*/
|
|
111
|
-
const latestContent = (content) => {
|
|
112
|
-
if (typeof content === 'string')
|
|
113
|
-
return content;
|
|
114
|
-
return Array.isArray(content) ? currLeafSrc(content, 'sidebar.latestContent') : '';
|
|
115
|
-
};
|
|
116
|
-
/*//////////////////////////////////////////////////
|
|
117
|
-
FUNCTIONS
|
|
118
|
-
//////////////////////////////////////////////////*/
|
|
119
|
-
/**
|
|
120
|
-
* Updates the Docs route through client-side history while preserving applicable query parameters.
|
|
121
|
-
*
|
|
122
|
-
* @param props - The destination URL and Docs mode to apply during client-side navigation.
|
|
123
|
-
*/
|
|
124
|
-
export function navigate({ href, mode }) {
|
|
125
|
-
setTransition('local');
|
|
126
|
-
const url = new URL(href, window.location.origin);
|
|
127
|
-
new URL(window.location.href).searchParams.forEach((v, k) => {
|
|
128
|
-
if (k !== 'mode' && !url.searchParams.has(k))
|
|
129
|
-
url.searchParams.set(k, v);
|
|
130
|
-
});
|
|
131
|
-
if (mode !== 'view')
|
|
132
|
-
url.searchParams.set('mode', mode);
|
|
133
|
-
else
|
|
134
|
-
url.searchParams.delete('mode');
|
|
135
|
-
window.history.pushState(null, '', url.toString());
|
|
136
|
-
document.dispatchEvent(new CustomEvent('docs:navigate', { detail: { url: url.pathname + url.search, pathname: url.pathname + url.search } }));
|
|
137
|
-
// safety escape hatch to force unlock the shell if reconciliation fails
|
|
138
|
-
setTimeout(() => {
|
|
139
|
-
clearTransition();
|
|
140
|
-
}, 1000);
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Handles archive.
|
|
144
|
-
*
|
|
145
|
-
* @param v - Paths input supplied to archive's operation.
|
|
146
|
-
* @param r - Root dir input supplied to archive's operation.
|
|
147
|
-
* @param f - Filename base input supplied to archive's operation.
|
|
148
|
-
* @param o - Opts input supplied to archive's operation.
|
|
149
|
-
* @param d - Callback invoked for progress.
|
|
150
|
-
* @param n - Node ids input supplied to archive's operation.
|
|
151
|
-
*/
|
|
152
|
-
const SidebarComponents = {
|
|
153
|
-
async archive(v, r, f, o, d, n = []) {
|
|
154
|
-
console.info('[archive] start', { paths: v, rootDir: r, filenameBase: f, opts: o, nodeIds: n });
|
|
155
|
-
if (!v?.length && !r)
|
|
156
|
-
throw new Error('No docs files found to download.');
|
|
157
|
-
let allPaths = v && v.length > 0 ? [...v] : [];
|
|
158
|
-
const isMdFile = TerraQualityRegex.REGEX_MD_MDX_I_REGEX().test(r) || !r.includes('.');
|
|
159
|
-
if (allPaths.length === 0) {
|
|
160
|
-
if (isMdFile) {
|
|
161
|
-
if (isIndexFile(r)) {
|
|
162
|
-
const dir = dirname(r);
|
|
163
|
-
const { data: entries, error: listErr } = await actions.read.doc.list({ type: 'folderEntries', path: dir === '.' ? '' : dir });
|
|
164
|
-
if (listErr || !entries)
|
|
165
|
-
throw listErr || new Error('Failed to fetch structural archive entries.');
|
|
166
|
-
allPaths = entries.filter(p => p === r || dirname(p) === dir);
|
|
167
|
-
}
|
|
168
|
-
else
|
|
169
|
-
allPaths = [r];
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
const { data: entries, error: listErr } = await actions.read.doc.list({ type: 'folderEntries', path: r === '.' ? '' : r });
|
|
173
|
-
if (listErr || !entries)
|
|
174
|
-
throw listErr || new Error('Failed to fetch structural archive entries.');
|
|
175
|
-
allPaths = entries;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (isMdFile && !allPaths.includes(r))
|
|
179
|
-
allPaths.push(r);
|
|
180
|
-
const attchPaths = [];
|
|
181
|
-
const assetNameMap = new Map();
|
|
182
|
-
// Create a combined list of paths explicitly for fetching attachments
|
|
183
|
-
await Promise.allSettled([...new Set([...allPaths, ...n])].map(async (p) => {
|
|
184
|
-
const c = p.split('?')[0] || p;
|
|
185
|
-
if (TerraQualityRegex.REGEX_MD_MDX_I_REGEX().test(c) || !c.includes('.')) {
|
|
186
|
-
try {
|
|
187
|
-
const { data: atts, error: err } = await actions.read.doc.attachments({ path: p });
|
|
188
|
-
if (!err && Array.isArray(atts)) {
|
|
189
|
-
for (const att of atts) {
|
|
190
|
-
if (att && typeof att === 'object' && 'path' in att && typeof att.path === 'string') {
|
|
191
|
-
if ('name' in att && typeof att.name === 'string') {
|
|
192
|
-
assetNameMap.set(att.path, att.name);
|
|
193
|
-
assetNameMap.set(att.path.split('?')[0] || att.path, att.name);
|
|
194
|
-
}
|
|
195
|
-
if (!allPaths.includes(att.path) && !attchPaths.includes(att.path))
|
|
196
|
-
attchPaths.push(att.path);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
else if (err)
|
|
201
|
-
console.warn(`[archive] Error fetching attachments for ${p}:`, err);
|
|
202
|
-
}
|
|
203
|
-
catch (e) {
|
|
204
|
-
console.error(`[archive] Exception fetching attachments for ${p}:`, e);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}));
|
|
208
|
-
if (attchPaths.length > 0) {
|
|
209
|
-
allPaths.push(...attchPaths);
|
|
210
|
-
allPaths = [...new Set(allPaths)];
|
|
211
|
-
}
|
|
212
|
-
console.info('[archive] discovered paths', { allPaths, attachmentPaths: attchPaths, assetNames: [...assetNameMap.entries()] });
|
|
213
|
-
const zip = new (await import('jszip')).default();
|
|
214
|
-
let mergedPdf = null;
|
|
215
|
-
const wantsPdfMerge = (o.filters.pdfs && o.merges.pdfs) || (o.filters.images && o.merges.images) || (o.filters.pages && o.merges.pages && o.format === 'pdf');
|
|
216
|
-
console.info('[archive] merge plan', { wantsPdfMerge, textMerge: o.filters.pages && o.merges.pages && o.format === 'raw', pdfMerge: o.filters.pdfs && o.merges.pdfs });
|
|
217
|
-
if (wantsPdfMerge) {
|
|
218
|
-
const { PDFDocument } = await import('pdf-lib');
|
|
219
|
-
mergedPdf = await PDFDocument.create();
|
|
220
|
-
}
|
|
221
|
-
const gJson = {};
|
|
222
|
-
const textJson = [];
|
|
223
|
-
const failures = [];
|
|
224
|
-
// Helper to accurately identify file types, prioritizing Sanity's explicit filename when available
|
|
225
|
-
/**
|
|
226
|
-
* Checks whether ext match.
|
|
227
|
-
*
|
|
228
|
-
* @param p - Value passed to the helper.
|
|
229
|
-
* @param regex - Value passed to the helper.
|
|
230
|
-
*/
|
|
231
|
-
const isExtMatch = (p, regex) => {
|
|
232
|
-
if (regex.test(p.split('?')[0] || p))
|
|
233
|
-
return true;
|
|
234
|
-
const filename = assetNameMap.get(p.split('?')[0] || p);
|
|
235
|
-
if (filename && regex.test(filename))
|
|
236
|
-
return true;
|
|
237
|
-
return false;
|
|
238
|
-
};
|
|
239
|
-
const mdPaths = [];
|
|
240
|
-
const textPaths = [];
|
|
241
|
-
const pdfPaths = [];
|
|
242
|
-
const imgPaths = [];
|
|
243
|
-
const ytPaths = [];
|
|
244
|
-
const refPaths = [];
|
|
245
|
-
const otherPaths = [];
|
|
246
|
-
for (const p of allPaths) {
|
|
247
|
-
const c = p.split('?')[0] || p;
|
|
248
|
-
const isTsDoc = p.includes(`${docsRootPath()}/tsdocs/`);
|
|
249
|
-
const isHttp = p.startsWith('http');
|
|
250
|
-
const noDot = !assetNameMap.has(p) && !c.includes('.');
|
|
251
|
-
if (!isHttp && !isTsDoc && (TerraQualityRegex.REGEX_MD_MDX_I_REGEX().test(c) || noDot))
|
|
252
|
-
mdPaths.push(p);
|
|
253
|
-
let matched = false;
|
|
254
|
-
if (!isHttp && !isTsDoc && (/\.(md|mdx|txt|json|cpp|c|h|hpp|js|jsx|ts|tsx|astro|yaml|yml|xml|csv|html|css|scss|sh|py|java|rs|go|rb|swift)$/i.test(assetNameMap.get(c) || c) || noDot)) {
|
|
255
|
-
textPaths.push(p);
|
|
256
|
-
matched = true;
|
|
257
|
-
}
|
|
258
|
-
if (isExtMatch(p, TerraQualityRegex.REGEX_PDF_I_REGEX())) {
|
|
259
|
-
pdfPaths.push(p);
|
|
260
|
-
matched = true;
|
|
261
|
-
}
|
|
262
|
-
if (isExtMatch(p, TerraQualityRegex.REGEX_PNG_JPE_G_GIF_WEBP_SVG_AVIF_I_REGEX_2())) {
|
|
263
|
-
imgPaths.push(p);
|
|
264
|
-
matched = true;
|
|
265
|
-
}
|
|
266
|
-
if (p.endsWith('youtube_transcripts.json') || p.includes('youtube.com/') || p.includes('youtu.be/')) {
|
|
267
|
-
ytPaths.push(p);
|
|
268
|
-
matched = true;
|
|
269
|
-
}
|
|
270
|
-
if (isTsDoc) {
|
|
271
|
-
refPaths.push(p);
|
|
272
|
-
matched = true;
|
|
273
|
-
}
|
|
274
|
-
if (!matched)
|
|
275
|
-
otherPaths.push(p);
|
|
276
|
-
}
|
|
277
|
-
const tasks = [];
|
|
278
|
-
if (o.filters.pages)
|
|
279
|
-
textPaths.forEach(p => tasks.push({ path: p, type: 'pages' }));
|
|
280
|
-
if (o.filters.pdfs)
|
|
281
|
-
pdfPaths.forEach(p => tasks.push({ path: p, type: 'pdfs' }));
|
|
282
|
-
if (o.filters.images)
|
|
283
|
-
imgPaths.forEach(p => tasks.push({ path: p, type: 'images' }));
|
|
284
|
-
if (o.filters.youtube)
|
|
285
|
-
ytPaths.forEach(p => tasks.push({ path: p, type: 'youtube' }));
|
|
286
|
-
if (o.filters.tsdocs)
|
|
287
|
-
refPaths.forEach(p => tasks.push({ path: p, type: 'tsdocs' }));
|
|
288
|
-
if (o.filters.other)
|
|
289
|
-
otherPaths.forEach(p => tasks.push({ path: p, type: 'other' }));
|
|
290
|
-
console.info('[archive] classified tasks', { total: tasks.length, tasks, textPaths, pdfPaths, imgPaths, otherPaths });
|
|
291
|
-
if (tasks.length === 0)
|
|
292
|
-
throw new Error('No files match the selected filters.');
|
|
293
|
-
let completed = 0;
|
|
294
|
-
/**
|
|
295
|
-
* Updates the update Progress for the docs.
|
|
296
|
-
*/
|
|
297
|
-
const updateProgress = () => {
|
|
298
|
-
completed++;
|
|
299
|
-
console.info('[archive] progress', { completed, total: tasks.length, percent: Math.round((completed / tasks.length) * 100) });
|
|
300
|
-
if (d)
|
|
301
|
-
d(Math.round((completed / tasks.length) * 100));
|
|
302
|
-
};
|
|
303
|
-
/**
|
|
304
|
-
* Resolves the Path.
|
|
305
|
-
*
|
|
306
|
-
* @param p - Value passed to the helper.
|
|
307
|
-
* @param forceExt - Value passed to the helper.
|
|
308
|
-
*/
|
|
309
|
-
const resolvePath = (p, forceExt) => {
|
|
310
|
-
let rel = p;
|
|
311
|
-
if (assetNameMap.has(p.split('?')[0] || p))
|
|
312
|
-
rel = assetNameMap.get(p.split('?')[0] || p);
|
|
313
|
-
else if (rel.startsWith('http://') || rel.startsWith('https://')) {
|
|
314
|
-
// prettier-ignore
|
|
315
|
-
try {
|
|
316
|
-
const urlObj = new URL(rel);
|
|
317
|
-
let baseName = urlObj.pathname.split('/').pop() || 'downloaded-file';
|
|
318
|
-
if (urlObj.search) {
|
|
319
|
-
const sQuery = urlObj.search.replace(/[^a-zA-Z0-9]/g, '-').replace(TerraQualityRegex.REGEX_G_REGEX_3(), '-').replace(TerraQualityRegex.REGEX_G_REGEX_20(), '');
|
|
320
|
-
if (sQuery)
|
|
321
|
-
baseName = `${baseName}-${sQuery}`;
|
|
322
|
-
}
|
|
323
|
-
rel = baseName;
|
|
324
|
-
}
|
|
325
|
-
catch (_e) {
|
|
326
|
-
rel = rel.split('/').pop()?.replace(/[^a-zA-Z0-9.-]/g, '-') || 'downloaded-file';
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
if (!isMdFile && p.startsWith(`${r}/`))
|
|
331
|
-
rel = p.slice(r.length + 1);
|
|
332
|
-
else if (isMdFile)
|
|
333
|
-
rel = p.split('/').pop() || p;
|
|
334
|
-
}
|
|
335
|
-
if (o.flatten) {
|
|
336
|
-
if (/(?:\/|^)index\.[^.]+$/i.test(p) && p.includes('/')) {
|
|
337
|
-
const parts = p.split('/');
|
|
338
|
-
rel = `${parts[parts.length - 2]}${p.match(TerraQualityRegex.REGEX_PATTERN_REGEX())?.[0] || ''}`;
|
|
339
|
-
}
|
|
340
|
-
else
|
|
341
|
-
rel = p.split('/').pop() || rel;
|
|
342
|
-
}
|
|
343
|
-
if (forceExt) {
|
|
344
|
-
if (rel.match(TerraQualityRegex.REGEX_A_Z0_9_I_REGEX_2()))
|
|
345
|
-
rel = rel.replace(TerraQualityRegex.REGEX_A_Z0_9_I_REGEX_2(), forceExt);
|
|
346
|
-
else
|
|
347
|
-
rel = `${rel}${forceExt}`;
|
|
348
|
-
}
|
|
349
|
-
return rel || 'untitled-file';
|
|
350
|
-
};
|
|
351
|
-
/**
|
|
352
|
-
* Fetches the fetch Asset Data.
|
|
353
|
-
*
|
|
354
|
-
* @param p - Value passed to the helper.
|
|
355
|
-
*
|
|
356
|
-
* @returns fetch Asset Data.
|
|
357
|
-
*/
|
|
358
|
-
const fetchAssetData = async (p) => {
|
|
359
|
-
if (TerraQualityRegex.REGEX_HTTPS_I_REGEX().test(p)) {
|
|
360
|
-
try {
|
|
361
|
-
console.info('[archive] fetching remote asset directly', { path: p });
|
|
362
|
-
const res = await fetch(p, { signal: AbortSignal.timeout(20_000) });
|
|
363
|
-
if (res.ok) {
|
|
364
|
-
const buffer = await res.arrayBuffer();
|
|
365
|
-
console.info('[archive] remote asset fetched directly', { path: p, bytes: buffer.byteLength });
|
|
366
|
-
return { buffer, contentType: res.headers.get('content-type') || '' };
|
|
367
|
-
}
|
|
368
|
-
console.warn('[archive] direct remote asset fetch failed; trying server proxy', { path: p, status: res.status });
|
|
369
|
-
}
|
|
370
|
-
catch (err) {
|
|
371
|
-
console.warn('[archive] direct remote asset fetch threw; trying server proxy', { path: p, err });
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const { data, error } = await actions.read.asset.get({ path: p });
|
|
375
|
-
if (error || !data) {
|
|
376
|
-
console.error('[archive] Failed to fetch asset through Docs read action', { path: p, error });
|
|
377
|
-
throw error || new Error('Failed to read asset');
|
|
378
|
-
}
|
|
379
|
-
console.info('[archive] asset fetched', { path: p, bytes: data.byteLength });
|
|
380
|
-
return { buffer: data, contentType: '' };
|
|
381
|
-
};
|
|
382
|
-
for (const task of tasks) {
|
|
383
|
-
const { path: p, type } = task;
|
|
384
|
-
console.info('[archive] processing task', { path: p, type, filename: assetNameMap.get(p.split('?')[0] || p), merge: o.merges[type] });
|
|
385
|
-
if (type === 'pages' && !mdPaths.includes(p)) {
|
|
386
|
-
try {
|
|
387
|
-
const res = await fetchAssetData(p);
|
|
388
|
-
if (res) {
|
|
389
|
-
if (o.merges.pages)
|
|
390
|
-
textJson.push({ path: p, filename: resolvePath(p), value: new TextDecoder().decode(res.buffer) });
|
|
391
|
-
else
|
|
392
|
-
zip.file(resolvePath(p), res.buffer);
|
|
393
|
-
console.info('[archive] text attachment processed', { path: p, filename: resolvePath(p), bytes: res.buffer.byteLength, merged: o.merges.pages });
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
catch (e) {
|
|
397
|
-
console.error(`[archive] Error fetching text attachment for ${p}:`, e);
|
|
398
|
-
failures.push({ filename: assetNameMap.get(p.split('?')[0] || p) || resolvePath(p), path: p, reason: e instanceof Error ? e.message : String(e) });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
else if (type === 'pages') {
|
|
402
|
-
if (o.format === 'pdf') {
|
|
403
|
-
try {
|
|
404
|
-
const { data: routeData } = await actions.read.util.pathname({ path: p });
|
|
405
|
-
if (routeData) {
|
|
406
|
-
const url = `${window.location.origin}${routeData}`;
|
|
407
|
-
const { data, error } = await actions.read.external.pdf({ url });
|
|
408
|
-
if (!error && data && data.pdfBase64) {
|
|
409
|
-
const byteChars = atob(data.pdfBase64);
|
|
410
|
-
const byteNums = new Array(byteChars.length);
|
|
411
|
-
for (let i = 0; i < byteChars.length; i++)
|
|
412
|
-
byteNums[i] = byteChars.charCodeAt(i);
|
|
413
|
-
const byteArray = new Uint8Array(byteNums);
|
|
414
|
-
if (o.merges.pages && mergedPdf) {
|
|
415
|
-
const { PDFDocument } = await import('pdf-lib');
|
|
416
|
-
const pdfDoc = await PDFDocument.load(byteArray);
|
|
417
|
-
(await mergedPdf.copyPages(pdfDoc, pdfDoc.getPageIndices())).forEach(p => mergedPdf.addPage(p));
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
zip.file(resolvePath(p, '.pdf'), byteArray);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
else if (error)
|
|
424
|
-
console.error(`[archive] PDF Conversion Error for ${url}:`, error);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
catch (e) {
|
|
428
|
-
console.error(`[archive] Pages -> PDF exception for ${p}:`, e);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
else {
|
|
432
|
-
const { data, error } = await actions.read.doc.get({ path: p });
|
|
433
|
-
if (!error && data) {
|
|
434
|
-
const doc = data;
|
|
435
|
-
const fm = ['---'];
|
|
436
|
-
if (doc.title)
|
|
437
|
-
fm.push(`title: "${doc.title.replace(TerraQualityRegex.REGEX_G_REGEX_5(), '\\"')}"`);
|
|
438
|
-
if (doc.description)
|
|
439
|
-
fm.push(`description: "${doc.description.replace(TerraQualityRegex.REGEX_G_REGEX_5(), '\\"')}"`);
|
|
440
|
-
fm.push('---');
|
|
441
|
-
let localSrc = typeof doc.source === 'string' ? doc.source : '';
|
|
442
|
-
localSrc = localSrc.replace(/\]\((http[^)]+)\)/g, (m, l) => {
|
|
443
|
-
const basename = l.split('/').pop()?.split('?')[0];
|
|
444
|
-
return basename ? `](${basename})` : m;
|
|
445
|
-
});
|
|
446
|
-
localSrc = localSrc.replace(/src=["'](http[^"']+)["']/g, (m, l) => {
|
|
447
|
-
const basename = l.split('/').pop()?.split('?')[0];
|
|
448
|
-
return basename ? `src="${basename}"` : m;
|
|
449
|
-
});
|
|
450
|
-
if (o.merges.pages) {
|
|
451
|
-
let relPath = p;
|
|
452
|
-
if (!isMdFile && p.startsWith(`${r}/`))
|
|
453
|
-
relPath = p.slice(r.length + 1);
|
|
454
|
-
else if (isMdFile)
|
|
455
|
-
relPath = p.split('/').pop() || p;
|
|
456
|
-
const dir = relPath.includes('/') ? relPath.substring(0, relPath.lastIndexOf('/')) : '.';
|
|
457
|
-
const fname = relPath.split('/').pop() || '';
|
|
458
|
-
const key = dir === '.' ? fname.replace(TerraQualityRegex.REGEX_PATTERN_REGEX(), '') : dir;
|
|
459
|
-
if (!gJson[key])
|
|
460
|
-
gJson[key] = [];
|
|
461
|
-
let src = typeof doc.source === 'string' ? doc.source : '';
|
|
462
|
-
src = src.replace(/\]\((?!http|#)([^)]+)\)/g, (m, l) => {
|
|
463
|
-
const basename = l.split('/').pop();
|
|
464
|
-
return basename ? `](${basename})` : m;
|
|
465
|
-
});
|
|
466
|
-
src = src.replace(/src=["'](?!http|#)([^"']+)["']/g, (m, l) => {
|
|
467
|
-
const basename = l.split('/').pop();
|
|
468
|
-
return basename ? `src="${basename}"` : m;
|
|
469
|
-
});
|
|
470
|
-
gJson[key].push({ title: fname, description: doc.description, source: src });
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
zip.file(resolvePath(p, '.mdx'), `${fm.join('\n')}\n\n${localSrc}`);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
else if (error)
|
|
477
|
-
console.error(`[archive] Error fetching doc content for ${p}:`, error);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
else if (type === 'pdfs') {
|
|
481
|
-
try {
|
|
482
|
-
const res = await fetchAssetData(p);
|
|
483
|
-
if (res && res.buffer) {
|
|
484
|
-
if (o.merges.pdfs && mergedPdf) {
|
|
485
|
-
try {
|
|
486
|
-
const { PDFDocument } = await import('pdf-lib');
|
|
487
|
-
const pdfDoc = await PDFDocument.load(res.buffer);
|
|
488
|
-
const copiedPages = await mergedPdf.copyPages(pdfDoc, pdfDoc.getPageIndices());
|
|
489
|
-
copiedPages.forEach(p => mergedPdf.addPage(p));
|
|
490
|
-
console.info('[archive] PDF merged', { path: p, pages: copiedPages.length });
|
|
491
|
-
}
|
|
492
|
-
catch (err) {
|
|
493
|
-
console.error('[archive] PDF merge failed; preserving standalone PDF', { path: p, err });
|
|
494
|
-
zip.file(resolvePath(p, '.pdf'), res.buffer);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
zip.file(resolvePath(p, '.pdf'), res.buffer);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
catch (e) {
|
|
503
|
-
console.error(`[archive] Exception processing PDF ${p}:`, e, { filename: assetNameMap.get(p.split('?')[0] || p), merge: o.merges.pdfs });
|
|
504
|
-
failures.push({ filename: assetNameMap.get(p.split('?')[0] || p) || resolvePath(p), path: p, reason: e instanceof Error ? e.message : String(e) });
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
else if (type === 'images') {
|
|
508
|
-
try {
|
|
509
|
-
const res = await fetchAssetData(p);
|
|
510
|
-
if (res && res.buffer) {
|
|
511
|
-
if (o.merges.images && mergedPdf) {
|
|
512
|
-
try {
|
|
513
|
-
let img;
|
|
514
|
-
if (p.match(/\.(jpe?g)$/i))
|
|
515
|
-
img = await mergedPdf.embedJpg(res.buffer);
|
|
516
|
-
else if (p.match(/\.(png)$/i))
|
|
517
|
-
img = await mergedPdf.embedPng(res.buffer);
|
|
518
|
-
if (img) {
|
|
519
|
-
mergedPdf.addPage([img.width, img.height]).drawImage(img, { x: 0, y: 0, width: img.width, height: img.height });
|
|
520
|
-
}
|
|
521
|
-
else {
|
|
522
|
-
console.warn(`[archive] Unsupported image type for PDF merge, zipping standalone: ${p}`);
|
|
523
|
-
zip.file(resolvePath(p), res.buffer);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
catch (e) {
|
|
527
|
-
console.error(`[archive] Failed to embed image ${p} into PDF, zipping instead:`, e);
|
|
528
|
-
zip.file(resolvePath(p), res.buffer);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
else {
|
|
532
|
-
zip.file(resolvePath(p), res.buffer);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
catch (e) {
|
|
537
|
-
console.error(`[archive] Exception processing Image ${p}:`, e);
|
|
538
|
-
failures.push({ filename: assetNameMap.get(p.split('?')[0] || p) || resolvePath(p), path: p, reason: e instanceof Error ? e.message : String(e) });
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
else if (type === 'youtube') {
|
|
542
|
-
try {
|
|
543
|
-
if (p.includes('youtube.com/') || p.includes('youtu.be/')) {
|
|
544
|
-
const { data: ytData, error: ytErr } = await actions.read.external.youtube({ url: p });
|
|
545
|
-
if (!ytErr && ytData) {
|
|
546
|
-
const cTitle = ytData.title || 'video';
|
|
547
|
-
if (o.merges.youtube) {
|
|
548
|
-
if (!gJson['youtube'])
|
|
549
|
-
gJson['youtube'] = [];
|
|
550
|
-
gJson['youtube'].push({ title: cTitle, source: ytData.transcript || '' });
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
zip.file(resolvePath(`${sanitizeName(cTitle)}.txt`), ytData.transcript || '');
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
else if (ytErr)
|
|
557
|
-
console.error(`[archive] Error fetching YouTube transcript for ${p}:`, ytErr);
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
const res = await fetchAssetData(p);
|
|
561
|
-
if (res && res.buffer) {
|
|
562
|
-
const videos = JSON.parse(new TextDecoder().decode(res.buffer));
|
|
563
|
-
if (Array.isArray(videos)) {
|
|
564
|
-
for (const video of videos) {
|
|
565
|
-
if (video.sourceUrl) {
|
|
566
|
-
const { data: ytData, error: ytErr } = await actions.read.external.youtube({ url: video.sourceUrl });
|
|
567
|
-
if (!ytErr && ytData) {
|
|
568
|
-
const cTitle = ytData.title || video.title || 'video';
|
|
569
|
-
if (o.merges.youtube) {
|
|
570
|
-
if (!gJson['youtube'])
|
|
571
|
-
gJson['youtube'] = [];
|
|
572
|
-
gJson['youtube'].push({ title: cTitle, source: ytData.transcript || '' });
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
zip.file(resolvePath(`${sanitizeName(cTitle)}.txt`), ytData.transcript || '');
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
catch (e) {
|
|
585
|
-
console.error(`[archive] Exception processing YouTube resource ${p}:`, e);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
else if (type === 'tsdocs') {
|
|
589
|
-
try {
|
|
590
|
-
const scopeMatch = p.match(TerraQualityRegex.REGEX_DOCS_TSDOCS_REGEX());
|
|
591
|
-
const scope = scopeMatch ? scopeMatch[1] : p;
|
|
592
|
-
const { data: tsData, error: tsErr } = await actions.read.tsdocs({ scope });
|
|
593
|
-
if (!tsErr && tsData && tsData.docs) {
|
|
594
|
-
const items = tsData.docs;
|
|
595
|
-
if (o.merges.tsdocs) {
|
|
596
|
-
if (!gJson['tsdocs'])
|
|
597
|
-
gJson['tsdocs'] = [];
|
|
598
|
-
items.forEach(d => {
|
|
599
|
-
// prettier-ignore
|
|
600
|
-
gJson['tsdocs'].push({ title: d.title, slug: d.slug, source: d.body });
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
else {
|
|
604
|
-
// prettier-ignore
|
|
605
|
-
zip.file(resolvePath(`${sanitizeName(scope.replace(TerraQualityRegex.REGEX_G_REGEX_10(), '-'))}.json`), JSON.stringify(items.map(d => ({ title: d.title, slug: d.slug, source: d.body })), null, 2));
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
else if (tsErr)
|
|
609
|
-
console.error(`[archive] Error fetching TSDocs for scope ${scope}:`, tsErr);
|
|
610
|
-
}
|
|
611
|
-
catch (e) {
|
|
612
|
-
console.error(`[archive] Exception processing TSDocs ${p}:`, e);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
try {
|
|
617
|
-
const res = await fetchAssetData(p);
|
|
618
|
-
if (res && res.buffer) {
|
|
619
|
-
let tgtPath = resolvePath(p);
|
|
620
|
-
if (!TerraQualityRegex.REGEX_A_Z0_9_I_REGEX_2().test(tgtPath)) {
|
|
621
|
-
let forcedExt = '';
|
|
622
|
-
if (res.contentType.includes('application/pdf'))
|
|
623
|
-
forcedExt = '.pdf';
|
|
624
|
-
else if (res.contentType.includes('image/jpeg'))
|
|
625
|
-
forcedExt = '.jpg';
|
|
626
|
-
else if (res.contentType.includes('image/png'))
|
|
627
|
-
forcedExt = '.png';
|
|
628
|
-
else if (res.contentType.includes('image/webp'))
|
|
629
|
-
forcedExt = '.webp';
|
|
630
|
-
else if (res.contentType.includes('application/json'))
|
|
631
|
-
forcedExt = '.json';
|
|
632
|
-
else {
|
|
633
|
-
const arr = new Uint8Array(res.buffer.slice(0, 4));
|
|
634
|
-
if (arr[0] === 0x25 && arr[1] === 0x50 && arr[2] === 0x44 && arr[3] === 0x44)
|
|
635
|
-
forcedExt = '.pdf';
|
|
636
|
-
}
|
|
637
|
-
if (forcedExt)
|
|
638
|
-
tgtPath = resolvePath(p, forcedExt);
|
|
639
|
-
if (forcedExt === '.pdf' && o.merges.pdfs && mergedPdf) {
|
|
640
|
-
const { PDFDocument } = await import('pdf-lib');
|
|
641
|
-
const pdfDoc = await PDFDocument.load(res.buffer);
|
|
642
|
-
(await mergedPdf.copyPages(pdfDoc, pdfDoc.getPageIndices())).forEach(p => mergedPdf.addPage(p));
|
|
643
|
-
updateProgress();
|
|
644
|
-
continue;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
zip.file(tgtPath, res.buffer);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
catch (e) {
|
|
651
|
-
console.error(`[archive] Exception processing Other attachment ${p}:`, e);
|
|
652
|
-
failures.push({ filename: assetNameMap.get(p.split('?')[0] || p) || resolvePath(p), path: p, reason: e instanceof Error ? e.message : String(e) });
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
updateProgress();
|
|
656
|
-
}
|
|
657
|
-
// FINALISE MERGES
|
|
658
|
-
// Write distinct JSON files for merged categories to prevent structural collisions
|
|
659
|
-
// and isolate domain concerns when consumed by external tools.
|
|
660
|
-
if (o.merges.tsdocs && gJson['tsdocs']) {
|
|
661
|
-
zip.file(`${f}-tsdocs.json`, JSON.stringify(gJson['tsdocs'], null, 2));
|
|
662
|
-
delete gJson['tsdocs'];
|
|
663
|
-
}
|
|
664
|
-
if (o.merges.youtube && gJson['youtube']) {
|
|
665
|
-
zip.file(`${f}-youtube.json`, JSON.stringify(gJson['youtube'], null, 2));
|
|
666
|
-
delete gJson['youtube'];
|
|
667
|
-
}
|
|
668
|
-
if (o.merges.pages && o.format === 'raw' && Object.keys(gJson).length > 0)
|
|
669
|
-
zip.file(`${f}-mdx.json`, JSON.stringify(gJson, null, 2));
|
|
670
|
-
if (o.merges.pages && textJson.length > 0)
|
|
671
|
-
zip.file(`${f}-text.json`, JSON.stringify(textJson, null, 2));
|
|
672
|
-
console.info('[archive] merged text summary', { count: textJson.length, files: textJson.map(i => i.filename) });
|
|
673
|
-
if (failures.length) {
|
|
674
|
-
console.warn('[archive] export completed with failures', { count: failures.length, failures });
|
|
675
|
-
toast.warning(`${failures.length} attachment${failures.length === 1 ? '' : 's'} failed. See the browser console for details.`);
|
|
676
|
-
}
|
|
677
|
-
else
|
|
678
|
-
toast.success('Export completed.');
|
|
679
|
-
if (wantsPdfMerge && mergedPdf && mergedPdf.getPageCount() > 0) {
|
|
680
|
-
try {
|
|
681
|
-
const mergedPdfBytes = await mergedPdf.save();
|
|
682
|
-
zip.file(`${f}-merged.pdf`, mergedPdfBytes);
|
|
683
|
-
console.info('[archive] merged PDF finalized', { pages: mergedPdf.getPageCount(), bytes: mergedPdfBytes.length });
|
|
684
|
-
}
|
|
685
|
-
catch (e) {
|
|
686
|
-
console.error(`[archive] Error generating Master PDF:`, e);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
const href = URL.createObjectURL(await zip.generateAsync({ type: 'blob' }));
|
|
690
|
-
const link = document.createElement('a');
|
|
691
|
-
link.href = href;
|
|
692
|
-
link.download = `${f}.zip`;
|
|
693
|
-
link.click();
|
|
694
|
-
URL.revokeObjectURL(href);
|
|
695
|
-
},
|
|
696
|
-
/**
|
|
697
|
-
* Renders the export dialog component.
|
|
698
|
-
*
|
|
699
|
-
* @param props - Selected export target, target-state setter, and disabled state for the archive dialog.
|
|
700
|
-
* @param tgt - Tgt input supplied to export dialog's operation.
|
|
701
|
-
* @param setTgt - State setter for tgt.
|
|
702
|
-
* @param disabled - Disabled state controlling whether export dialog accepts interaction.
|
|
703
|
-
*
|
|
704
|
-
* @return The shared export dialog for the selected Docs paths, or `null` when no target is active.
|
|
705
|
-
*/
|
|
706
|
-
ExportDialog({ tgt, setTgt, disabled }) {
|
|
707
|
-
return _jsx(SharedExportDialog, { target: tgt, setTarget: setTgt, disabled: disabled, onExport: (t, o, p) => SidebarComponents.archive(t.paths, t.rootDir, t.filename, o, p, t.nodeIds || []) });
|
|
708
|
-
},
|
|
709
|
-
};
|
|
710
|
-
/**
|
|
711
|
-
* RENDER TRIPWIRE
|
|
712
|
-
* A diagnostic tool to catch and analyze React infinite loops.
|
|
713
|
-
*
|
|
714
|
-
* @param componentName - Component name input supplied to use render tripwire's operation.
|
|
715
|
-
* @param trackedVars - Tracked vars input supplied to use render tripwire's operation.
|
|
716
|
-
*/
|
|
717
|
-
function useRenderTripwire(componentName, trackedVars) {
|
|
718
|
-
const renders = useRef(0);
|
|
719
|
-
const prevVars = useRef(trackedVars);
|
|
720
|
-
const now = performance.now();
|
|
721
|
-
const windowStart = useRef(now);
|
|
722
|
-
if (now - windowStart.current > 500) {
|
|
723
|
-
windowStart.current = now;
|
|
724
|
-
renders.current = 0;
|
|
725
|
-
}
|
|
726
|
-
renders.current++;
|
|
727
|
-
if (renders.current > 50) {
|
|
728
|
-
const diff = {};
|
|
729
|
-
for (const key in trackedVars)
|
|
730
|
-
if (trackedVars[key] !== prevVars.current[key])
|
|
731
|
-
diff[key] = { old: prevVars.current[key], new: trackedVars[key] };
|
|
732
|
-
console.error(`🚨 [TRIPWIRE TRIGGERED] Infinite Loop in ${componentName} 🚨`);
|
|
733
|
-
console.error(`Detected 50 renders in rapid succession. Average delta: ${(now - windowStart.current).toFixed(2)}ms`);
|
|
734
|
-
console.error('State Differences (What is causing the thrash):', JSON.stringify(diff));
|
|
735
|
-
console.trace(`Trace for ${componentName} infinite loop`);
|
|
736
|
-
throw new Error(`Render Tripwire killed infinite loop in ${componentName}. Check console for state diffs.`);
|
|
737
|
-
}
|
|
738
|
-
prevVars.current = trackedVars;
|
|
739
|
-
}
|
|
740
|
-
/*//////////////////////////////////////////////////
|
|
741
|
-
COMPONENTS
|
|
742
|
-
//////////////////////////////////////////////////*/
|
|
743
|
-
/*
|
|
744
|
-
* Dialog component for selecting and managing all attachments across a section tree.
|
|
745
|
-
*
|
|
746
|
-
* @param props - Active attachment target, state setter, disabled state, and Docs tree for browsing and managing related assets.
|
|
747
|
-
* @param tgt - The specific target node (Leaf, Group, or Section) receiving the attachment modifications.
|
|
748
|
-
* @param setTgt - State dispatcher to clear or update the active target boundary, closing the dialog.
|
|
749
|
-
* @param disabled - Master lock preventing interactions while network mutations or animations are active.
|
|
750
|
-
* @param tree - Tree input supplied to manage attchs dialog's operation.
|
|
751
|
-
*
|
|
752
|
-
* @return The interactive modal overlay containing the attachment dashboard.
|
|
753
|
-
*
|
|
754
|
-
* @remarks
|
|
755
|
-
* Provides a unified interface for uploading raw media, scraping external web links, linking YouTube videos,
|
|
756
|
-
* and directly associating existing or new internal documentation pages as relational attachments.
|
|
757
|
-
*/
|
|
758
|
-
SidebarComponents.ManageAttchsDialog = function ManageAttchsDialog({ tgt, setTgt, disabled, tree, }) {
|
|
759
|
-
const [atts, setAtts] = useState({});
|
|
760
|
-
const [loading, setLoading] = useState(false);
|
|
761
|
-
const [a, setActive] = useState(null);
|
|
762
|
-
const [addMode, setAddMode] = useState(null);
|
|
763
|
-
const [url, setUrl] = useState('');
|
|
764
|
-
const [query, setQuery] = useState('');
|
|
765
|
-
const [isCreating, setIsCreating] = useState(false);
|
|
766
|
-
const [selAtts, setSelAtts] = useState([]);
|
|
767
|
-
const fileRef = useRef(null);
|
|
768
|
-
useRenderTripwire('ManageAttchsDialog', { tgt, disabled, tree, atts, loading, active: a, addMode, url, query, isCreating, selectedAtts: selAtts });
|
|
769
|
-
const nodeGroups = useMemo(() => {
|
|
770
|
-
if (!tgt)
|
|
771
|
-
return [];
|
|
772
|
-
const n = tgt.node;
|
|
773
|
-
if (tgt.scope === 'leaf') {
|
|
774
|
-
const lTitle = cTitle(toNodeLabel(tgt.title)) || tgt.title;
|
|
775
|
-
return [{ groupTitle: 'Page', nodes: [{ title: lTitle, fullTitle: lTitle, id: n.origName, path: n.path || '', type: 'Page', node: n }] }];
|
|
776
|
-
}
|
|
777
|
-
if (tgt.scope === 'group') {
|
|
778
|
-
const gNodes = [];
|
|
779
|
-
const gTitle = cTitle(toNodeLabel(tgt.title));
|
|
780
|
-
gNodes.push({
|
|
781
|
-
title: `${gTitle} (Index)`,
|
|
782
|
-
fullTitle: gTitle,
|
|
783
|
-
id: n.origName,
|
|
784
|
-
path: 'url' in n && n.url ? n.url.replace(TerraQualityRegex.REGEX_DOCS_REGEX(), '') : '',
|
|
785
|
-
type: 'Index',
|
|
786
|
-
node: n,
|
|
787
|
-
});
|
|
788
|
-
n.children?.forEach(c => {
|
|
789
|
-
if (c.type === 'leaf') {
|
|
790
|
-
const lTitle = cTitle(toNodeLabel(c.title || c.origName));
|
|
791
|
-
gNodes.push({ title: lTitle, fullTitle: `${gTitle} / ${lTitle}`, id: c.origName, path: c.path || '', type: 'Page', node: c });
|
|
792
|
-
}
|
|
793
|
-
});
|
|
794
|
-
return [{ groupTitle: gTitle, nodes: gNodes }];
|
|
795
|
-
}
|
|
796
|
-
if (tgt.scope === 'section') {
|
|
797
|
-
const groups = [];
|
|
798
|
-
const sectTitle = cTitle(toNodeLabel(tgt.title));
|
|
799
|
-
// prettier-ignore
|
|
800
|
-
groups.push({ groupTitle: 'Section Root', nodes: [{ title: `${sectTitle} (Index)`, fullTitle: sectTitle, id: n.origName, path: 'url' in n && n.url ? n.url.replace(TerraQualityRegex.REGEX_DOCS_REGEX(), '') : '', type: 'Index', node: n },] });
|
|
801
|
-
n.children?.forEach(c => {
|
|
802
|
-
if (c.type === 'branch') {
|
|
803
|
-
const gTitle = cTitle(toNodeLabel(c.title || c.origName));
|
|
804
|
-
const gNodes = [];
|
|
805
|
-
// prettier-ignore
|
|
806
|
-
gNodes.push({ title: `${gTitle} (Index)`, fullTitle: `${sectTitle} / ${gTitle}`, id: c.origName, path: 'url' in c && c.url ? c.url.replace(TerraQualityRegex.REGEX_DOCS_REGEX(), '') : '', type: 'Index', node: c });
|
|
807
|
-
c.children?.forEach(l => {
|
|
808
|
-
if (l.type === 'leaf') {
|
|
809
|
-
const lTitle = cTitle(toNodeLabel(l.title || l.origName));
|
|
810
|
-
gNodes.push({ title: lTitle, fullTitle: `${sectTitle} / ${gTitle} / ${lTitle}`, id: l.origName, path: l.path || '', type: 'Page', node: l });
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
groups.push({ groupTitle: gTitle, nodes: gNodes });
|
|
814
|
-
}
|
|
815
|
-
else if (c.type === 'leaf') {
|
|
816
|
-
const lTitle = cTitle(toNodeLabel(c.title || c.origName));
|
|
817
|
-
groups[0].nodes.push({ title: lTitle, fullTitle: `${sectTitle} / ${lTitle}`, id: c.origName, path: c.path || '', type: 'Page', node: c });
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
return groups;
|
|
821
|
-
}
|
|
822
|
-
return [];
|
|
823
|
-
}, [tgt]);
|
|
824
|
-
const filteredLeaves = useMemo(() => {
|
|
825
|
-
const trimmedQuery = query.trim();
|
|
826
|
-
if (!trimmedQuery)
|
|
827
|
-
return [];
|
|
828
|
-
const lowerQuery = trimmedQuery.toLowerCase();
|
|
829
|
-
const leaves = [];
|
|
830
|
-
/*
|
|
831
|
-
* Handles walk.
|
|
832
|
-
*
|
|
833
|
-
* @param nodes - Sidebar nodes being rendered or filtered.
|
|
834
|
-
* @param pathStr - Path prefix used to resolve each node's route.
|
|
835
|
-
*/
|
|
836
|
-
(function walk(nodes, pathStr) {
|
|
837
|
-
nodes.forEach(n => {
|
|
838
|
-
const currTitle = n.title || n.origName;
|
|
839
|
-
if (n.type === 'leaf') {
|
|
840
|
-
if (currTitle.toLowerCase().includes(lowerQuery) || (n.path ? n.path.toLowerCase() : '').includes(lowerQuery)) {
|
|
841
|
-
leaves.push({ path: n.path, title: currTitle, breadcrumb: pathStr });
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
else if (n.type === 'branch' && n.children)
|
|
845
|
-
walk(n.children, pathStr ? `${pathStr} / ${currTitle}` : currTitle);
|
|
846
|
-
});
|
|
847
|
-
})(tree, '');
|
|
848
|
-
return leaves;
|
|
849
|
-
}, [query, tree]);
|
|
850
|
-
useEffect(() => {
|
|
851
|
-
if (tgt?.scope === 'leaf') {
|
|
852
|
-
const lTitle = cTitle(toNodeLabel(tgt.title)) || tgt.title;
|
|
853
|
-
setActive({ title: lTitle, fullTitle: lTitle, id: tgt.node.origName, path: tgt.node.path || '', type: 'Page' });
|
|
854
|
-
}
|
|
855
|
-
else
|
|
856
|
-
setActive(null);
|
|
857
|
-
setAddMode(null);
|
|
858
|
-
setUrl('');
|
|
859
|
-
setQuery('');
|
|
860
|
-
setSelAtts([]);
|
|
861
|
-
}, [tgt]);
|
|
862
|
-
const fetchAtts = useCallback(async () => {
|
|
863
|
-
if (!tgt)
|
|
864
|
-
return;
|
|
865
|
-
setLoading(true);
|
|
866
|
-
let ids = [];
|
|
867
|
-
if (a)
|
|
868
|
-
ids = [a.id];
|
|
869
|
-
else
|
|
870
|
-
ids = nodeGroups.flatMap(g => g.nodes.map(n => n.id));
|
|
871
|
-
ids = [...new Set(ids.filter(Boolean))];
|
|
872
|
-
const { data, error: err } = await actions.read.doc.attachments({ paths: ids });
|
|
873
|
-
if (!err && data)
|
|
874
|
-
setAtts(data);
|
|
875
|
-
else
|
|
876
|
-
setAtts({});
|
|
877
|
-
setLoading(false);
|
|
878
|
-
}, [tgt, a, nodeGroups]);
|
|
879
|
-
useEffect(() => {
|
|
880
|
-
void fetchAtts();
|
|
881
|
-
}, [fetchAtts]);
|
|
882
|
-
// DRAG AND DROP
|
|
883
|
-
/**
|
|
884
|
-
* Handles on drop.
|
|
885
|
-
*
|
|
886
|
-
* @param e - Pointer event received from the sidebar item.
|
|
887
|
-
* @param tgtPath - Destination path associated with the selected item.
|
|
888
|
-
*/
|
|
889
|
-
// SELECTION
|
|
890
|
-
/**
|
|
891
|
-
* Handles toggle sel.
|
|
892
|
-
*
|
|
893
|
-
* @param e - Pointer event received from the document action.
|
|
894
|
-
* @param docPath - Document path targeted by the action.
|
|
895
|
-
* @param a - Action metadata used to perform the document operation.
|
|
896
|
-
*/
|
|
897
|
-
const toggleSel = (e, docPath, a) => {
|
|
898
|
-
e.stopPropagation();
|
|
899
|
-
if (e.metaKey || e.ctrlKey) {
|
|
900
|
-
setSelAtts(v => {
|
|
901
|
-
if (v.find(p => (p.a.id || p.a.path) === (a.id || a.path)))
|
|
902
|
-
return v.filter(p => (p.a.id || p.a.path) !== (a.id || a.path));
|
|
903
|
-
return [...v, { docPath, a }];
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
else {
|
|
907
|
-
setSelAtts([{ docPath, a }]);
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
// ACTIONS
|
|
911
|
-
const handleKeyboardDelete = useCallback(async () => {
|
|
912
|
-
if (!selAtts.length)
|
|
913
|
-
return;
|
|
914
|
-
// 1. Snapshot for rollback
|
|
915
|
-
const originalSel = [...selAtts];
|
|
916
|
-
// 2. Optimistic UI updates
|
|
917
|
-
setAtts(v => {
|
|
918
|
-
const n = { ...v };
|
|
919
|
-
for (const { docPath, a } of originalSel) {
|
|
920
|
-
n[docPath] = (n[docPath] || []).filter(((key) => (p) => (p.id || p.path) !== key)(a.id || a.path));
|
|
921
|
-
}
|
|
922
|
-
return n;
|
|
923
|
-
});
|
|
924
|
-
setSelAtts([]);
|
|
925
|
-
// 3. Background network operation
|
|
926
|
-
try {
|
|
927
|
-
for (const { docPath, a } of originalSel) {
|
|
928
|
-
if (a.isMd)
|
|
929
|
-
await actions.write.doc.rm({ path: a.path, type: 'doc', assets: false });
|
|
930
|
-
else if (a.id) {
|
|
931
|
-
await actions.write.attachment.rm({ docPath, attachmentKey: a.id });
|
|
932
|
-
if (a.path.startsWith('http'))
|
|
933
|
-
await actions.write.asset.delete({ url: a.path });
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
// Background sync just to ensure external alignment
|
|
937
|
-
void fetchAtts();
|
|
938
|
-
}
|
|
939
|
-
catch (_err) {
|
|
940
|
-
// 4. Failure rollback
|
|
941
|
-
setAtts({ ...atts });
|
|
942
|
-
setSelAtts(originalSel);
|
|
943
|
-
toast.error(`Failed to delete ${originalSel.length === 1 ? 'attachment' : 'attachments'}`);
|
|
944
|
-
}
|
|
945
|
-
}, [atts, selAtts, fetchAtts]);
|
|
946
|
-
// GLOBAL KEYBOARD SHORTCUTS
|
|
947
|
-
useEffect(() => {
|
|
948
|
-
if (!tgt)
|
|
949
|
-
return;
|
|
950
|
-
/**
|
|
951
|
-
* Handles handle key down.
|
|
952
|
-
*
|
|
953
|
-
* @param e - Pointer event received from the sidebar control.
|
|
954
|
-
*/
|
|
955
|
-
const handleKeyDown = (e) => {
|
|
956
|
-
if (loading || disabled || selAtts.length === 0)
|
|
957
|
-
return;
|
|
958
|
-
const tgt = e.target;
|
|
959
|
-
if (tgt.tagName === 'INPUT' || tgt.tagName === 'TEXTAREA' || tgt.isContentEditable)
|
|
960
|
-
return;
|
|
961
|
-
if (e.key === 'Backspace' || e.key === 'Delete') {
|
|
962
|
-
e.preventDefault();
|
|
963
|
-
void handleKeyboardDelete();
|
|
964
|
-
}
|
|
965
|
-
};
|
|
966
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
967
|
-
return () => {
|
|
968
|
-
document.removeEventListener('keydown', handleKeyDown);
|
|
969
|
-
};
|
|
970
|
-
}, [tgt, loading, disabled, selAtts, handleKeyboardDelete]);
|
|
971
|
-
/**
|
|
972
|
-
* Handles upload.
|
|
973
|
-
*
|
|
974
|
-
* @param files - Files available to the sidebar resolver.
|
|
975
|
-
* @param tgtPath - Target path used to select the matching file.
|
|
976
|
-
*/
|
|
977
|
-
/**
|
|
978
|
-
* Handles add remote.
|
|
979
|
-
*
|
|
980
|
-
* @param v - Sidebar value being normalized.
|
|
981
|
-
* @param type - Sidebar node type used to select the renderer.
|
|
982
|
-
* @param tgtPath - Target path associated with the node.
|
|
983
|
-
* @param titleOverride - Optional title replacing the node's default title.
|
|
984
|
-
*/
|
|
985
|
-
const addRemote = async (v, type, tgtPath, titleOverride) => {
|
|
986
|
-
if (!tgtPath || !v.trim())
|
|
987
|
-
return;
|
|
988
|
-
const urls = v
|
|
989
|
-
.split(TerraQualityRegex.REGEX_N_S_REGEX())
|
|
990
|
-
.filter(u => (type === 'tsdocs' ? u.trim().length > 0 : u.startsWith('http') || isDocsPath(u) || u.startsWith('/tsdocs') || type === 'page'));
|
|
991
|
-
if (!urls.length)
|
|
992
|
-
return;
|
|
993
|
-
void runMutation(async () => {
|
|
994
|
-
await Promise.allSettled(urls.map(async (u) => {
|
|
995
|
-
let t = type;
|
|
996
|
-
if (t === 'other')
|
|
997
|
-
t = u.toLowerCase().split('?')[0].endsWith('.pdf') ? 'document' : TerraQualityRegex.REGEX_PNG_JPE_G_GIF_WEBP_SVG_AVIF_I_REGEX_2().test(u.toLowerCase().split('?')[0] ?? '') ? 'image' : t;
|
|
998
|
-
if (t === 'page') {
|
|
999
|
-
// directly inject a strict Sanity reference to the existing document
|
|
1000
|
-
// prettier-ignore
|
|
1001
|
-
await actions.write.attachment.docRef({
|
|
1002
|
-
path: tgtPath,
|
|
1003
|
-
refId: u, // u is the pure Sanity path/id
|
|
1004
|
-
title: titleOverride || u.split('/').filter(Boolean).pop()?.replace(TerraQualityRegex.REGEX_G_REGEX_7(), ' ').replace(TerraQualityRegex.REGEX_B_W_G_REGEX(), c => c.toUpperCase()) || 'Page',
|
|
1005
|
-
});
|
|
1006
|
-
}
|
|
1007
|
-
else if (t === 'youtube') {
|
|
1008
|
-
await actions.write.attachment.youtube({ path: tgtPath, url: u });
|
|
1009
|
-
}
|
|
1010
|
-
else if (t === 'website') {
|
|
1011
|
-
const { data: w } = await actions.read.external.website({ url: u });
|
|
1012
|
-
if (!w)
|
|
1013
|
-
throw new Error('Website import returned no content.');
|
|
1014
|
-
const dir = dirname(tgtPath);
|
|
1015
|
-
await actions.write.doc.editor({ path: `${dir === '.' ? '' : `${dir}/`}${sanitizeName(w.title || 'website')}.mdx`, source: w.text, title: w.title, type: 'website', sourceUrl: u });
|
|
1016
|
-
}
|
|
1017
|
-
else if (t === 'tsdocs') {
|
|
1018
|
-
// prettier-ignore
|
|
1019
|
-
await actions.write.attachment.link({ path: tgtPath, url: u.startsWith('http') ? u : u.startsWith('/') ? `${window.location.origin}${u}` : `${window.location.origin}${docsPath(`tsdocs/${u}`)}`, type: 'tsdocs', title: u.split('/').filter(Boolean).pop()?.replace(TerraQualityRegex.REGEX_G_REGEX_7(), ' ').replace(TerraQualityRegex.REGEX_B_W_G_REGEX(), c => c.toUpperCase()) || 'Reference' });
|
|
1020
|
-
}
|
|
1021
|
-
else
|
|
1022
|
-
await actions.write.attachment.remoteFile({ path: tgtPath, url: u, type: t });
|
|
1023
|
-
}));
|
|
1024
|
-
await fetchAtts();
|
|
1025
|
-
setAddMode(null);
|
|
1026
|
-
setUrl('');
|
|
1027
|
-
}, 'Failed to attach', () => {
|
|
1028
|
-
setLoading(true);
|
|
1029
|
-
}, () => {
|
|
1030
|
-
setLoading(false);
|
|
1031
|
-
});
|
|
1032
|
-
};
|
|
1033
|
-
/**
|
|
1034
|
-
* Handles attach page.
|
|
1035
|
-
*
|
|
1036
|
-
* @param path - Document path being created or edited.
|
|
1037
|
-
* @param isNew - Whether the path represents a new document.
|
|
1038
|
-
* @param customTitle - Optional title supplied by the editor.
|
|
1039
|
-
*/
|
|
1040
|
-
const attachPage = async (path, isNew = false, customTitle) => {
|
|
1041
|
-
if (!a)
|
|
1042
|
-
return;
|
|
1043
|
-
let finalPath = path;
|
|
1044
|
-
let finalTitle = customTitle || path;
|
|
1045
|
-
if (isNew) {
|
|
1046
|
-
setIsCreating(true);
|
|
1047
|
-
try {
|
|
1048
|
-
const newId = `temp_l_${Date.now()}`;
|
|
1049
|
-
finalPath = newId;
|
|
1050
|
-
// Anchor document structurally inside the group to bypass verifyWrite 404
|
|
1051
|
-
let parentId = a.id;
|
|
1052
|
-
if (a.type === 'Page') {
|
|
1053
|
-
let groupId;
|
|
1054
|
-
// 1. Primary Fallback: Extract from the current docs route
|
|
1055
|
-
if (typeof window !== 'undefined') {
|
|
1056
|
-
try {
|
|
1057
|
-
const curr = route(window.location.pathname + window.location.search);
|
|
1058
|
-
groupId = curr.groupId || curr.sectionId || undefined;
|
|
1059
|
-
}
|
|
1060
|
-
catch {
|
|
1061
|
-
// Tree lookup below provides the fallback group.
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
// 2. Secondary Fallback: Tree Search (for established documents)
|
|
1065
|
-
if (!groupId) {
|
|
1066
|
-
/*
|
|
1067
|
-
* Handles find group.
|
|
1068
|
-
*
|
|
1069
|
-
* @param nodes - Sidebar nodes containing the target leaf.
|
|
1070
|
-
* @param tgtLeafId - Leaf identifier being selected.
|
|
1071
|
-
* @param currGroupId - Current group identifier used for route context.
|
|
1072
|
-
*/
|
|
1073
|
-
(function findGroup(nodes, tgtLeafId, currGroupId) {
|
|
1074
|
-
for (const n of nodes) {
|
|
1075
|
-
if (n.type === 'leaf' && (n.origName === tgtLeafId || n.path === tgtLeafId)) {
|
|
1076
|
-
groupId = currGroupId;
|
|
1077
|
-
return true;
|
|
1078
|
-
}
|
|
1079
|
-
if (n.type === 'branch' && n.children)
|
|
1080
|
-
if (findGroup(n.children, tgtLeafId, n.origName))
|
|
1081
|
-
return true;
|
|
1082
|
-
}
|
|
1083
|
-
return false;
|
|
1084
|
-
})(tree, a.id);
|
|
1085
|
-
}
|
|
1086
|
-
if (groupId)
|
|
1087
|
-
parentId = groupId;
|
|
1088
|
-
}
|
|
1089
|
-
const writeRes = await actions.write.doc.editor({ path: newId, title: query, source: 'This page is empty.', parentId }).catch(e => ({ data: null, error: e }));
|
|
1090
|
-
if (writeRes.error)
|
|
1091
|
-
throw writeRes.error;
|
|
1092
|
-
if (writeRes.data && typeof writeRes.data === 'object' && 'path' in writeRes.data && typeof writeRes.data.path === 'string')
|
|
1093
|
-
finalPath = writeRes.data.path;
|
|
1094
|
-
finalTitle = query;
|
|
1095
|
-
await new Promise(r => setTimeout(r, 1500));
|
|
1096
|
-
}
|
|
1097
|
-
catch (err) {
|
|
1098
|
-
notify({ error: err instanceof Error ? err : new Error('Creation failed'), fallback: 'Failed to create leaf.' });
|
|
1099
|
-
setIsCreating(false);
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
setIsCreating(false);
|
|
1103
|
-
}
|
|
1104
|
-
// Pass the exact path without forcing it into a fake `/docs/` URL structure
|
|
1105
|
-
await addRemote(finalPath, 'page', a.id, finalTitle);
|
|
1106
|
-
setAddMode(null);
|
|
1107
|
-
setQuery('');
|
|
1108
|
-
};
|
|
1109
|
-
const aPathTitle = useMemo(() => {
|
|
1110
|
-
if (!a)
|
|
1111
|
-
return '';
|
|
1112
|
-
return a.fullTitle || a.title;
|
|
1113
|
-
}, [a]);
|
|
1114
|
-
if (!tgt)
|
|
1115
|
-
return null;
|
|
1116
|
-
return (_jsx(Dialog, { open: !!tgt, onOpenChange: o => {
|
|
1117
|
-
if (!o) {
|
|
1118
|
-
setTgt(null);
|
|
1119
|
-
setActive(null);
|
|
1120
|
-
setAddMode(null);
|
|
1121
|
-
setSelAtts([]);
|
|
1122
|
-
}
|
|
1123
|
-
}, children: _jsxs(DialogContent, { size: 'xl', children: [_jsx(DialogHeader, { children: a ? (_jsxs(_Fragment, { children: [_jsxs(DialogTitle, { children: [tgt.scope !== 'leaf' && (_jsx(Button, { variant: 'ghost', size: 'icon', "aria-label": 'Back to target list', onClick: () => {
|
|
1124
|
-
setActive(null);
|
|
1125
|
-
setAddMode(null);
|
|
1126
|
-
setSelAtts([]);
|
|
1127
|
-
}, children: _jsx(LeftArrowIcon, { className: 'size-5 text-primary-11', "aria-hidden": 'true' }) })), _jsx("span", { className: 'truncate', children: aPathTitle }), addMode && _jsxs("span", { className: 'shrink-0 font-normal text-primary-11', children: ["/ ", addMode === 'select' ? 'Add Attachment' : `Add ${addMode}`] })] }), _jsxs(DialogDescription, { className: 'sr-only', children: ["Manage attachments for ", aPathTitle] })] })) : (_jsxs(_Fragment, { children: [_jsxs(DialogTitle, { children: ["Manage Attachments \u2014 ", cTitle(toNodeLabel(tgt.title))] }), _jsx(DialogDescription, { children: "Select a target layer or drag and drop attachments to move them between nodes." })] })) }), _jsx("div", { className: 'min-h-96 flex-1 overflow-y-auto py-4 outline-none', tabIndex: -1, onClick: () => {
|
|
1128
|
-
setSelAtts([]);
|
|
1129
|
-
}, children: loading || disabled ? (_jsx("div", { className: 'flex h-64 items-center justify-center', children: _jsx(Spinner, { className: 'size-8 text-primary-10' }) })) : !a ? (_jsx("div", { className: 'flex flex-col gap-6', children: nodeGroups.map((g, i) => (_jsxs("div", { className: 'space-y-3', children: [nodeGroups.length > 1 && _jsx("h4", { className: 'text-sm font-semibold uppercase tracking-wider text-primary-11', children: g.groupTitle }), _jsx("div", { className: 'grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3', children: g.nodes.map(n => (_jsxs(Card, { className: 'flex h-full flex-col transition-colors hover:border-primary-8 hover:bg-primary-3/30', onDragOver: e => {
|
|
1130
|
-
e.preventDefault();
|
|
1131
|
-
}, onDrop: e => {
|
|
1132
|
-
e.preventDefault();
|
|
1133
|
-
const sPath = e.dataTransfer.getData('application/source-path');
|
|
1134
|
-
const id = e.dataTransfer.getData('application/attachment-id');
|
|
1135
|
-
if (!sPath || !id || sPath === n.id)
|
|
1136
|
-
return;
|
|
1137
|
-
const srcList = atts[sPath] || [];
|
|
1138
|
-
const match = srcList.find(a => a.id === id);
|
|
1139
|
-
if (!match)
|
|
1140
|
-
return;
|
|
1141
|
-
setAtts(p => ({ ...p, [sPath]: srcList.filter(a => a.id !== id), [n.id]: [...(p[n.id] || []), match] }));
|
|
1142
|
-
void actions.write.attachment.move({ sourceDocPath: sPath, targetDocPath: n.id, attachmentKey: id }).then(({ error: err }) => {
|
|
1143
|
-
if (err) {
|
|
1144
|
-
setAtts({ ...atts });
|
|
1145
|
-
toast.error('Move failed');
|
|
1146
|
-
notify({ error: new Error(err.message || 'Move failed'), fallback: 'Failed to relocate attachment relational schema node.' });
|
|
1147
|
-
}
|
|
1148
|
-
});
|
|
1149
|
-
}, children: [_jsx("button", { type: 'button', "aria-label": `Open ${n.title}`, onClick: e => {
|
|
1150
|
-
e.stopPropagation();
|
|
1151
|
-
setActive(n);
|
|
1152
|
-
}, className: 'flex w-full items-center justify-between border-b border-primary-6 bg-primary-3/30 p-3 text-left transition-colors hover:bg-primary-4/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-8', children: _jsxs("p", { className: 'flex items-center text-sm font-medium', children: [n.type === 'Page' ? (_jsx(DocumentIcon, { className: 'mr-2 size-4 text-primary-10', "aria-hidden": 'true' })) : (_jsx(CatalogIcon, { className: 'mr-2 size-4 text-primary-10', "aria-hidden": 'true' })), n.title] }) }), _jsx("div", { className: 'flex flex-1 flex-wrap content-start gap-2 p-4', children: (atts[n.id] || []).length === 0 ? (_jsx("div", { className: 'flex h-12 w-full items-center justify-center opacity-50', children: _jsx(UploadIcon, { className: 'size-6 text-primary-8', "aria-hidden": 'true' }) })) : ((atts[n.id] || []).map(a => {
|
|
1153
|
-
const isSelected = selAtts.some(s => (s.a.id || s.a.path) === (a.id || a.path));
|
|
1154
|
-
return (_jsxs("button", { type: 'button', "aria-pressed": isSelected, "aria-label": `Select attachment ${a.name}`, draggable: true, onDragStart: e => {
|
|
1155
|
-
e.dataTransfer.setData('application/source-path', n.id);
|
|
1156
|
-
e.dataTransfer.setData('application/attachment-id', a.id || '');
|
|
1157
|
-
e.dataTransfer.effectAllowed = 'move';
|
|
1158
|
-
}, onKeyDown: e => {
|
|
1159
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
1160
|
-
e.preventDefault();
|
|
1161
|
-
toggleSel(e, n.id, a);
|
|
1162
|
-
}
|
|
1163
|
-
}, onClick: e => {
|
|
1164
|
-
toggleSel(e, n.id, a);
|
|
1165
|
-
}, className: cn('flex cursor-grab items-center gap-2 rounded-md border px-2 py-1 text-xs shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-8', isSelected ? 'border-primary-9 bg-primary-5 ring-1 ring-primary-8' : 'border-primary-6 bg-primary-3'), children: [_jsx(DocumentIcon, { className: 'size-3 shrink-0 text-primary-10', "aria-hidden": 'true' }), _jsx("span", { className: 'max-w-[100px] truncate', children: a.name })] }, a.id || a.path));
|
|
1166
|
-
})) })] }, n.id))) })] }, g.groupTitle + i))) })) : (_jsxs("div", { className: 'space-y-6', children: [addMode ? (_jsx("div", { className: 'flex flex-col gap-6', children: addMode === 'select' ? (_jsx(_Fragment, { children: _jsx("div", { className: 'grid grid-cols-2 gap-4 sm:grid-cols-3', children: [
|
|
1167
|
-
{ Icon: DocumentIcon, text: 'Page', type: 'page' },
|
|
1168
|
-
{ Icon: DocumentIcon, text: 'Document', type: 'document' },
|
|
1169
|
-
{ Icon: ImageIcon, text: 'Image', type: 'image' },
|
|
1170
|
-
{ Icon: YouTubeIcon, text: 'YouTube', type: 'youtube' },
|
|
1171
|
-
{ Icon: LinkIcon, text: 'Website', type: 'website' },
|
|
1172
|
-
{ Icon: CatalogIcon, text: 'TsDocs', type: 'tsdocs' },
|
|
1173
|
-
{ Icon: BoxIcon, text: 'Other', type: 'other' },
|
|
1174
|
-
].map(({ Icon: i, text: t, type: v }) => (_jsxs("button", { type: 'button', "aria-label": `Add ${t}`, className: 'flex cursor-pointer flex-col items-center justify-center rounded-xl border border-primary-6 bg-transparent p-4 transition-colors hover:border-primary-8 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-8', onClick: e => {
|
|
1175
|
-
e.stopPropagation();
|
|
1176
|
-
setAddMode(v);
|
|
1177
|
-
setUrl('');
|
|
1178
|
-
setQuery('');
|
|
1179
|
-
}, children: [_jsx(React.Fragment, { children: React.createElement(i, { className: 'size-5 text-primary-11', 'aria-hidden': 'true' }) }), _jsx("span", { className: 'mt-2 text-sm font-medium text-primary-11', children: t })] }, t))) }) })) : addMode === 'page' ? (_jsxs("div", { className: 'flex flex-col gap-4', onClick: e => {
|
|
1180
|
-
e.stopPropagation();
|
|
1181
|
-
}, children: [_jsx(Input, { autoFocus: true, placeholder: 'Search or type to create...', value: query, onChange: e => {
|
|
1182
|
-
setQuery(e.target.value);
|
|
1183
|
-
} }), _jsxs("div", { className: 'flex max-h-64 min-h-24 flex-col overflow-y-auto pr-2', children: [!query.trim() &&
|
|
1184
|
-
tree.map(n => (_jsx(Button, { variant: 'ghost', size: 'sm', className: 'mt-0.5 h-auto w-full justify-start py-1.5 pl-6 text-left font-normal text-primary-11 hover:text-primary-12', onClick: () => void attachPage(n.type === 'leaf' ? n.path || '' : '', false, n.title || n.origName), children: _jsx("span", { className: 'truncate', children: n.title || n.origName }) }, n.origName))), query.trim() && (_jsxs("div", { className: 'flex flex-col gap-1', children: [_jsx(Button, { variant: 'ghost', className: 'mb-2 h-auto w-full justify-start border border-dashed border-primary-6 py-2 text-left text-primary-10 hover:border-primary-8 hover:text-primary-12', onClick: () => void attachPage('', true), disabled: isCreating, children: _jsxs("div", { className: 'flex flex-col', children: [_jsx("span", { className: 'text-sm font-medium', children: isCreating ? 'Creating...' : `Create "${query}"` }), _jsx("span", { className: 'text-xs opacity-70', children: "Create page and attach" })] }) }), filteredLeaves.length > 0 &&
|
|
1185
|
-
filteredLeaves.map(r => (_jsx(Button, { variant: 'ghost', className: 'h-auto w-full justify-start py-2 text-left', onClick: () => void attachPage(r.path, false, r.title), children: _jsxs("div", { className: 'flex flex-col', children: [_jsx("span", { className: 'text-sm font-medium', children: r.title }), _jsx("span", { className: 'text-xs text-primary-11 opacity-70', children: r.breadcrumb ? `${r.breadcrumb} / ${r.path}` : r.path })] }) }, r.path)))] }))] })] })) : (_jsxs("div", { className: 'flex flex-col gap-4', onClick: e => {
|
|
1186
|
-
e.stopPropagation();
|
|
1187
|
-
}, children: [!['youtube', 'website', 'tsdocs'].includes(addMode) && (_jsxs(Button, { variant: 'outline', className: 'w-full border-dashed py-8', onClick: () => fileRef.current?.click(), children: [_jsx(UploadIcon, { className: 'mr-2 size-5', "aria-hidden": 'true' }), " Select Files"] })), _jsxs("div", { className: 'space-y-2', children: [_jsx(Label, { className: 'text-xs font-semibold uppercase text-primary-11', children: "Import via URL" }), addMode === 'tsdocs' ? (_jsx(TsDocsSelect, { variant: 'inline', multiple: true, value: url ? url.split('\n').filter(Boolean) : [], onChange: v => {
|
|
1188
|
-
setUrl(Array.isArray(v) ? v.join('\n') : v);
|
|
1189
|
-
} })) : (_jsx(Textarea, { autoFocus: true, placeholder: 'https://...', value: url, onChange: e => {
|
|
1190
|
-
setUrl(e.target.value);
|
|
1191
|
-
}, rows: 3, onKeyDown: e => {
|
|
1192
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
1193
|
-
e.preventDefault();
|
|
1194
|
-
void addRemote(url, addMode, a.id);
|
|
1195
|
-
}
|
|
1196
|
-
} }))] })] })) })) : (_jsx(_Fragment, { children: _jsx("div", { className: 'flex min-h-24 flex-wrap content-start gap-3 rounded-md border border-primary-6 bg-primary-2 p-4', children: (atts[a.id] || []).length === 0 ? (_jsx(Empty, { variant: 'compact', children: "No attachments yet." })) : ((atts[a.id] || []).map(v => (_jsxs(ContextMenu, { children: [_jsxs(ContextMenuTrigger, { className: cn('flex cursor-pointer items-center gap-2 rounded-md border px-3 py-1.5 text-xs shadow-sm outline-none transition-colors', selAtts.some(s => (s.a.id || s.a.path) === (v.id || v.path)) ? 'border-primary-9 bg-primary-5 ring-1 ring-primary-8' : 'border-primary-6 bg-primary-3'), onClick: e => {
|
|
1197
|
-
toggleSel(e, a.id, v);
|
|
1198
|
-
}, children: [_jsx(DocumentIcon, { className: 'size-3 shrink-0 text-primary-10' }), _jsx("span", { className: 'max-w-[200px] truncate font-medium', children: v.name })] }), _jsx(ContextMenuContent, { children: _jsx(Menu, { actions: [
|
|
1199
|
-
{
|
|
1200
|
-
label: 'Copy URL',
|
|
1201
|
-
icon: _jsx(LinkIcon, {}),
|
|
1202
|
-
onSelect: () => {
|
|
1203
|
-
void navigator.clipboard.writeText(`${window.location.origin}${docsPath(v.path)}`);
|
|
1204
|
-
toast.success('URL copied');
|
|
1205
|
-
},
|
|
1206
|
-
},
|
|
1207
|
-
...(v.isMd
|
|
1208
|
-
? [
|
|
1209
|
-
{
|
|
1210
|
-
icon: _jsx(MagicIcon, {}),
|
|
1211
|
-
label: 'Synthesize Content',
|
|
1212
|
-
onSelect: () => {
|
|
1213
|
-
document.dispatchEvent(new CustomEvent('docs:synthesize', { detail: { path: v.path, name: v.name } }));
|
|
1214
|
-
},
|
|
1215
|
-
},
|
|
1216
|
-
]
|
|
1217
|
-
: []),
|
|
1218
|
-
] }) })] }, v.id || v.path)))) }) })), _jsx("input", { ref: fileRef, type: 'file', className: 'hidden', multiple: true, onChange: e => {
|
|
1219
|
-
const files = e.currentTarget.files;
|
|
1220
|
-
const tgtPath = a.id;
|
|
1221
|
-
void (async () => {
|
|
1222
|
-
if (!files || files.length === 0 || !tgtPath)
|
|
1223
|
-
return;
|
|
1224
|
-
void runMutation(async () => {
|
|
1225
|
-
const arr = Array.from(files);
|
|
1226
|
-
for (let i = 0; i < arr.length; i++) {
|
|
1227
|
-
const f = arr[i];
|
|
1228
|
-
if (!f)
|
|
1229
|
-
continue;
|
|
1230
|
-
await uploadAssetChunked(f, tgtPath);
|
|
1231
|
-
}
|
|
1232
|
-
await fetchAtts();
|
|
1233
|
-
setAddMode(null);
|
|
1234
|
-
if (fileRef.current)
|
|
1235
|
-
fileRef.current.value = '';
|
|
1236
|
-
}, 'Upload failed', () => {
|
|
1237
|
-
setLoading(true);
|
|
1238
|
-
}, () => {
|
|
1239
|
-
setLoading(false);
|
|
1240
|
-
});
|
|
1241
|
-
})();
|
|
1242
|
-
} })] })) }), _jsxs(DialogFooter, { className: 'flex shrink-0 items-center justify-end gap-2 pt-4', children: [a && !addMode && _jsxs(Button, { onClick: () => { setAddMode('select'); }, children: [_jsx(AddIcon, { className: 'mr-2 size-4', "aria-hidden": 'true' }), " Add Attachment"] }), a && addMode === 'select' && _jsx(Button, { variant: 'ghost', onClick: () => { setAddMode(null); }, children: "Cancel" }), a && addMode === 'page' && _jsx(Button, { variant: 'ghost', onClick: () => { setAddMode('select'); setQuery(''); }, children: "Back" }), a && addMode && addMode !== 'select' && addMode !== 'page' && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: 'ghost', onClick: () => { setAddMode('select'); }, children: "Back" }), _jsx(Button, { disabled: !url.trim(), onClick: () => void addRemote(url, addMode, a.id), children: "Attach" })] }))] })] }) }));
|
|
1243
|
-
};
|
|
1244
|
-
/*
|
|
1245
|
-
* Re-useable structural prompt component.
|
|
1246
|
-
*
|
|
1247
|
-
* @param props - Prompt title, label, current value, save/cancel callbacks, and disabled state for the reusable input dialog.
|
|
1248
|
-
* @param state - State input supplied to prompt dialog's operation.
|
|
1249
|
-
* @param setState - State setter for state.
|
|
1250
|
-
* @param disabled - Disabled state controlling whether prompt dialog accepts interaction.
|
|
1251
|
-
*
|
|
1252
|
-
* @return The prompt dialog while prompt state is active, or `null` when it is closed.
|
|
1253
|
-
*/
|
|
1254
|
-
SidebarComponents.PromptDialog = function PromptDialog({ state: s, setState: setS, disabled }) {
|
|
1255
|
-
if (!s)
|
|
1256
|
-
return null;
|
|
1257
|
-
return (_jsx(Dialog, { open: s.open, onOpenChange: o => {
|
|
1258
|
-
if (!o) {
|
|
1259
|
-
s.onCancel?.();
|
|
1260
|
-
setS(null);
|
|
1261
|
-
}
|
|
1262
|
-
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: s.title }), _jsx(DialogDescription, { children: s.description || `Enter a new ${s.label.toLowerCase()}.` })] }), _jsx("div", { className: 'py-4', children: _jsx(Input, { autoFocus: true, "aria-label": s.label, value: s.value, onChange: e => {
|
|
1263
|
-
setS({ ...s, value: e.target.value });
|
|
1264
|
-
}, className: 'w-full focus:ring-0', disabled: disabled, onKeyDown: e => {
|
|
1265
|
-
if (e.key === 'Enter') {
|
|
1266
|
-
e.preventDefault();
|
|
1267
|
-
if (s.value.trim()) {
|
|
1268
|
-
s.onSave(s.value.trim());
|
|
1269
|
-
setS(null);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
} }) }), _jsx(DialogFooter, { children: _jsx(Button, { disabled: disabled || !s.value.trim(), onClick: () => {
|
|
1273
|
-
if (s.value.trim()) {
|
|
1274
|
-
s.onSave(s.value.trim());
|
|
1275
|
-
setS(null);
|
|
1276
|
-
}
|
|
1277
|
-
}, children: "Save" }) })] }) }));
|
|
1278
|
-
};
|
|
1279
|
-
/*
|
|
1280
|
-
* Renders the delete dialog component.
|
|
1281
|
-
*
|
|
1282
|
-
* @param props - The selected deletion target, state setter, confirmation callback, and disabled state for the delete confirmation.
|
|
1283
|
-
* @param setTgt - Clears or updates the active target.
|
|
1284
|
-
* @param tgt - Tgt input supplied to delete dialog's operation.
|
|
1285
|
-
* @param disabled - Disabled state controlling whether delete dialog accepts interaction.
|
|
1286
|
-
* @param onConfirm - Callback invoked for confirm.
|
|
1287
|
-
*
|
|
1288
|
-
* @return The delete dialog or nothing when no target is selected.
|
|
1289
|
-
*
|
|
1290
|
-
* @remarks Renders the delete confirmation dialog for sidebar content.
|
|
1291
|
-
*/
|
|
1292
|
-
SidebarComponents.DeleteDialog = function DeleteDialog({ tgt, setTgt, onConfirm, disabled, }) {
|
|
1293
|
-
if (!tgt)
|
|
1294
|
-
return null;
|
|
1295
|
-
return (_jsx(Dialog, { open: !!tgt, onOpenChange: o => {
|
|
1296
|
-
if (!o)
|
|
1297
|
-
setTgt(null);
|
|
1298
|
-
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Delete ", tgt.kind === 'leaf' ? 'page' : tgt.kind === 'branch' ? 'group' : 'section', "?"] }), _jsxs(DialogDescription, { children: ["Delete ", _jsx("code", { children: tgt.title || tgt.path }), " ", tgt.kind !== 'leaf' ? ' and its nested docs.' : '.', " This action cannot be undone."] })] }), _jsx(DialogFooter, { children: _jsx(Button, { variant: 'destructive', disabled: disabled, onClick: () => {
|
|
1299
|
-
onConfirm();
|
|
1300
|
-
}, children: "Delete" }) })] }) }));
|
|
1301
|
-
};
|
|
1302
|
-
/**
|
|
1303
|
-
* Renders the top collapsed header boundary on mobile-view structurally isolating context safely cleanly.
|
|
1304
|
-
*
|
|
1305
|
-
* @return {JSX.Element} Visual mobile heading frame.
|
|
1306
|
-
*/
|
|
1307
|
-
/**
|
|
1308
|
-
* Main sidebar controller connecting context providers natively driving application visual trees.
|
|
1309
|
-
*
|
|
1310
|
-
* @param {object} props - Input definitions matching specific nav tree routes.
|
|
1311
|
-
* @param docs - Docs input supplied to nav's operation.
|
|
1312
|
-
* @param pathname - Pathname input supplied to nav's operation.
|
|
1313
|
-
* @param permission - Permission input supplied to nav's operation.
|
|
1314
|
-
* @param userData - user record supplied to nav.
|
|
1315
|
-
*
|
|
1316
|
-
* @return {JSX.Element} Fully formatted UI sidebar assembly correctly binding handlers.
|
|
1317
|
-
*/
|
|
1318
|
-
export default (function Nav({ docs: items, pathname, permission, userData, siteTitle = 'Terra Symposium', siteDescription, homePath = '/', ...props }) {
|
|
1319
|
-
// STATES
|
|
1320
|
-
const leafTitle = useAtom($leafTitle);
|
|
1321
|
-
const curMode = useAtom($mode);
|
|
1322
|
-
const transitioning = useAtom($transition);
|
|
1323
|
-
const [creating, setCreating] = useState(false);
|
|
1324
|
-
const [loading, setLoading] = useState(false);
|
|
1325
|
-
const [drag, setDrag] = useState(null);
|
|
1326
|
-
const [dState, setDState] = useState(null);
|
|
1327
|
-
const [hovered, setHovered] = useState(null);
|
|
1328
|
-
const [cPathname, setCPathname] = useState(() => route(pathname || '').pathname);
|
|
1329
|
-
const urlIds = useMemo(() => route(cPathname), [cPathname]);
|
|
1330
|
-
const [viewedSecId, setViewedSecId] = useState(() => route(pathname || '').sectionId || null);
|
|
1331
|
-
const [sectionOpen, setSectionOpen] = useState(false);
|
|
1332
|
-
const [searchPages, setSearchPages] = useState('');
|
|
1333
|
-
const [tree, setTree] = useState(items);
|
|
1334
|
-
const prevTree = useRef(items);
|
|
1335
|
-
const [selLeaves, setSelLeaves] = useState([]);
|
|
1336
|
-
const [lastSel, setLastSel] = useState(null);
|
|
1337
|
-
// DIALOG STATES
|
|
1338
|
-
const [manageAttTgt, setManageAttTgt] = useState(null);
|
|
1339
|
-
const [exportTgt, setExportTgt] = useState(null);
|
|
1340
|
-
const [assetTgt, setAssetTgt] = useState(null);
|
|
1341
|
-
const [assetDialogOpen, setAssetDialogOpen] = useState(false);
|
|
1342
|
-
const [assetBusy, setAssetBusy] = useState(false);
|
|
1343
|
-
const [assetComponent, setAssetComponent] = useState(repoComponents.find(c => c.name !== 'Leaf' && c.name !== 'Visualize')?.name ?? repoComponents[0]?.name ?? '');
|
|
1344
|
-
const [hueDlg, setHueDlg] = useState(null);
|
|
1345
|
-
const [inputDlg, setInputDlg] = useState(null);
|
|
1346
|
-
const [delTgt, setDelTgt] = useState(null);
|
|
1347
|
-
// INJECT TRIPWIRE TO CATCH STATE THRASHING
|
|
1348
|
-
useRenderTripwire('Nav', {
|
|
1349
|
-
curMode,
|
|
1350
|
-
transitioning,
|
|
1351
|
-
creating,
|
|
1352
|
-
loading,
|
|
1353
|
-
drag,
|
|
1354
|
-
dState,
|
|
1355
|
-
hovered,
|
|
1356
|
-
cPathname,
|
|
1357
|
-
urlSectionId: urlIds.sectionId,
|
|
1358
|
-
viewedSecId,
|
|
1359
|
-
sectionOpen,
|
|
1360
|
-
treeHash: tree.length,
|
|
1361
|
-
selLeavesCount: selLeaves.length,
|
|
1362
|
-
lastSel,
|
|
1363
|
-
manageAttTgtScope: manageAttTgt?.scope,
|
|
1364
|
-
exportTargetPresent: !!exportTgt,
|
|
1365
|
-
assetTargetPresent: !!assetTgt,
|
|
1366
|
-
hueDlgOpen: hueDlg?.open,
|
|
1367
|
-
inputDlgOpen: inputDlg?.open,
|
|
1368
|
-
delTargetPresent: !!delTgt,
|
|
1369
|
-
});
|
|
1370
|
-
// Sync viewed section with URL on navigation
|
|
1371
|
-
useEffect(() => {
|
|
1372
|
-
if (urlIds.sectionId)
|
|
1373
|
-
setViewedSecId(urlIds.sectionId);
|
|
1374
|
-
}, [urlIds.sectionId]);
|
|
1375
|
-
const sects = useMemo(() => {
|
|
1376
|
-
let s = Array.isArray(tree) ? tree.filter((d) => d.type === 'branch') : [];
|
|
1377
|
-
// 1. Deduplicate by origName to prevent React key collisions and redundant entries
|
|
1378
|
-
const uniqueMap = new Map();
|
|
1379
|
-
s.forEach(n => {
|
|
1380
|
-
if (!uniqueMap.has(n.origName))
|
|
1381
|
-
uniqueMap.set(n.origName, n);
|
|
1382
|
-
});
|
|
1383
|
-
s = Array.from(uniqueMap.values());
|
|
1384
|
-
// 2. Filter out top-level nodes that are actually children of another top-level node.
|
|
1385
|
-
// (This happens if the API returns explicit warrants for both a Section and its inner Group).
|
|
1386
|
-
const nestedBranchIds = new Set();
|
|
1387
|
-
s.forEach(n => {
|
|
1388
|
-
if (n.children)
|
|
1389
|
-
(function walk(children) {
|
|
1390
|
-
children.forEach(c => {
|
|
1391
|
-
if (c.type === 'branch') {
|
|
1392
|
-
nestedBranchIds.add(c.origName);
|
|
1393
|
-
if (c.children)
|
|
1394
|
-
walk(c.children);
|
|
1395
|
-
}
|
|
1396
|
-
});
|
|
1397
|
-
})(n.children);
|
|
1398
|
-
});
|
|
1399
|
-
s = s.filter(n => !nestedBranchIds.has(n.origName));
|
|
1400
|
-
// prettier-ignore
|
|
1401
|
-
if ( /**
|
|
1402
|
-
* SIMULATED SECTION WRAPPER
|
|
1403
|
-
*
|
|
1404
|
-
* If a user accesses a group directly (e.g., via granular permission /docs/{groupId}/{leafId}),
|
|
1405
|
-
* the nav tree will return Groups as the top-level branches instead of Sections.
|
|
1406
|
-
* The sidebar structurally expects Sections at the top level. We intercept this and wrap
|
|
1407
|
-
* the Groups in a simulated "Shared Context" Section to render the tree seamlessly.
|
|
1408
|
-
*/(s.length > 0 && s.every(b => b.children?.every(c => c.type === 'leaf'))) && s[0]?.origName !== urlIds.sectionId)
|
|
1409
|
-
s = [{ type: 'branch', origName: urlIds.sectionId, permission: 'read', simulated: true, title: s[0]?.title || 'Shared Context', url: docsPath(urlIds.sectionId), children: s }];
|
|
1410
|
-
// prettier-ignore
|
|
1411
|
-
if (s.length === 0 && permission === 'write')
|
|
1412
|
-
s.push({ type: 'branch', origName: 'new-section', permission: 'read', simulated: true, url: docsPath('new-section'), children: [{ type: 'leaf', origName: 'index', path: 'index.mdx', url: docsRootPath(), permission: 'read', title: 'Untitled', fileType: 'mdx' }] });
|
|
1413
|
-
return s;
|
|
1414
|
-
}, [tree, permission, urlIds.sectionId]);
|
|
1415
|
-
const sectsMap = useMemo(() => {
|
|
1416
|
-
const map = new Map();
|
|
1417
|
-
for (const s of sects)
|
|
1418
|
-
map.set(s.origName, s);
|
|
1419
|
-
return map;
|
|
1420
|
-
}, [sects]);
|
|
1421
|
-
const aViewed = useMemo(() => {
|
|
1422
|
-
if (viewedSecId) {
|
|
1423
|
-
const match = sectsMap.get(viewedSecId);
|
|
1424
|
-
if (match)
|
|
1425
|
-
return match;
|
|
1426
|
-
}
|
|
1427
|
-
if (urlIds.sectionId) {
|
|
1428
|
-
const fallback = sectsMap.get(urlIds.sectionId);
|
|
1429
|
-
if (fallback)
|
|
1430
|
-
return fallback;
|
|
1431
|
-
}
|
|
1432
|
-
return sects[0];
|
|
1433
|
-
}, [sects, sectsMap, viewedSecId, urlIds.sectionId]);
|
|
1434
|
-
const children = useMemo(() => {
|
|
1435
|
-
return aViewed?.type === 'branch' ? aViewed.children : [];
|
|
1436
|
-
}, [aViewed]);
|
|
1437
|
-
const visibleChildren = useMemo(() => filterTree(children ?? [], searchPages), [children, searchPages]);
|
|
1438
|
-
// INIT / MOUNT
|
|
1439
|
-
useEffect(() => {
|
|
1440
|
-
if (JSON.stringify(items) !== JSON.stringify(prevTree.current)) {
|
|
1441
|
-
setTree(items);
|
|
1442
|
-
prevTree.current = items;
|
|
1443
|
-
}
|
|
1444
|
-
}, [items]);
|
|
1445
|
-
useEffect(() => {
|
|
1446
|
-
setCPathname(route(pathname).pathname);
|
|
1447
|
-
}, [pathname]);
|
|
1448
|
-
const refreshTree = useCallback(async () => {
|
|
1449
|
-
try {
|
|
1450
|
-
const { data, error: err } = (await actions.read.doc.list({ type: 'nav', pathname: docsRootPath(), full: true }));
|
|
1451
|
-
if (!err && Array.isArray(data) && data.length !== 0)
|
|
1452
|
-
setTree(data);
|
|
1453
|
-
}
|
|
1454
|
-
catch (err) {
|
|
1455
|
-
console.error('[DEBUG:Sidebar] refreshTree err: ', err);
|
|
1456
|
-
}
|
|
1457
|
-
}, []);
|
|
1458
|
-
useEffect(() => {
|
|
1459
|
-
/*
|
|
1460
|
-
* Handles update sidebar node title.
|
|
1461
|
-
*
|
|
1462
|
-
* @param nodes - Sidebar nodes containing the document to rename.
|
|
1463
|
-
* @param targetPath - Path of the document whose title should change.
|
|
1464
|
-
* @param newTitle - New title assigned to the matching document.
|
|
1465
|
-
*/
|
|
1466
|
-
if (leafTitle?.path)
|
|
1467
|
-
setTree(c => (function updateSidebarNodeTitle(nodes, tgtPath, newTitle) {
|
|
1468
|
-
/**
|
|
1469
|
-
* Normalizes a sidebar asset path for comparisons by removing draft and file-extension markers.
|
|
1470
|
-
*
|
|
1471
|
-
* @param val - Sidebar document path or basename to normalize before matching a title update.
|
|
1472
|
-
*/
|
|
1473
|
-
const cPath = (val) => val.replace(TerraQualityRegex.REGEX_DRAFTS_REGEX(), '').replace(TerraQualityRegex.REGEX_MD_MDX_PDF_I_REGEX(), '');
|
|
1474
|
-
return nodes.map(n => {
|
|
1475
|
-
const nodePath = n.type === 'leaf' ? n.path || '' : '';
|
|
1476
|
-
if (n.type === 'leaf' &&
|
|
1477
|
-
(nodePath === tgtPath || n.origName === tgtPath || cPath(nodePath) === cPath(tgtPath) || cPath(nodePath.split('/').at(-1) || nodePath) === cPath(tgtPath.split('/').at(-1) || tgtPath)))
|
|
1478
|
-
return { ...n, title: newTitle };
|
|
1479
|
-
if (n.type === 'branch' && n.children)
|
|
1480
|
-
return { ...n, children: updateSidebarNodeTitle(n.children, tgtPath, newTitle) };
|
|
1481
|
-
return n;
|
|
1482
|
-
});
|
|
1483
|
-
})(c, leafTitle.path, leafTitle.title));
|
|
1484
|
-
}, [leafTitle]);
|
|
1485
|
-
useEffect(() => {
|
|
1486
|
-
/**
|
|
1487
|
-
* Handles rename event.
|
|
1488
|
-
*
|
|
1489
|
-
* @param e - Rename event carrying the updated document path.
|
|
1490
|
-
*/
|
|
1491
|
-
const ES = [
|
|
1492
|
-
{
|
|
1493
|
-
handler: (e) => {
|
|
1494
|
-
const d = eDetail(e);
|
|
1495
|
-
if (!d?.path || !d.nfilepath)
|
|
1496
|
-
return;
|
|
1497
|
-
createTrace('DOCS:shared', isDevOrigin(globalThis.location?.origin ?? ''))('rename.start', { next: d.nfilepath, previous: d.path });
|
|
1498
|
-
setTree(current => {
|
|
1499
|
-
const renamed = [];
|
|
1500
|
-
const pending = [{ source: current, target: renamed, index: 0 }];
|
|
1501
|
-
while (pending.length > 0) {
|
|
1502
|
-
const frame = pending[pending.length - 1];
|
|
1503
|
-
if (frame.index >= frame.source.length) {
|
|
1504
|
-
pending.pop();
|
|
1505
|
-
continue;
|
|
1506
|
-
}
|
|
1507
|
-
const node = frame.source[frame.index++];
|
|
1508
|
-
if (node.type === 'leaf' && node.path === d.path) {
|
|
1509
|
-
const slug = d.nfilepath.replace(TerraQualityRegex.REGEX_PATTERN_REGEX_6(), '').replace(FILES.markdown, '').replace(TerraQualityRegex.REGEX_INDEX_I_REGEX(), '');
|
|
1510
|
-
frame.target.push({ ...node, path: d.nfilepath, title: d.title ?? node.title, url: docsPath(slug) });
|
|
1511
|
-
continue;
|
|
1512
|
-
}
|
|
1513
|
-
if (node.type === 'branch') {
|
|
1514
|
-
const children = [];
|
|
1515
|
-
frame.target.push({ ...node, children });
|
|
1516
|
-
pending.push({ source: node.children ?? [], target: children, index: 0 });
|
|
1517
|
-
continue;
|
|
1518
|
-
}
|
|
1519
|
-
frame.target.push(node);
|
|
1520
|
-
}
|
|
1521
|
-
return renamed;
|
|
1522
|
-
});
|
|
1523
|
-
if (d.isActive)
|
|
1524
|
-
setCPathname(route(d.pathname).pathname);
|
|
1525
|
-
},
|
|
1526
|
-
type: 'docs:rename',
|
|
1527
|
-
},
|
|
1528
|
-
{
|
|
1529
|
-
handler: (e) => {
|
|
1530
|
-
const d = eDetail(e);
|
|
1531
|
-
if (d?.url || d?.pathname)
|
|
1532
|
-
setCPathname(route(d.url || d.pathname || '').pathname);
|
|
1533
|
-
},
|
|
1534
|
-
type: 'docs:navigate',
|
|
1535
|
-
},
|
|
1536
|
-
];
|
|
1537
|
-
ES.forEach(({ type: t, handler: h }) => {
|
|
1538
|
-
document.addEventListener(t, h);
|
|
1539
|
-
});
|
|
1540
|
-
return () => {
|
|
1541
|
-
ES.forEach(({ type: t, handler: h }) => {
|
|
1542
|
-
document.removeEventListener(t, h);
|
|
1543
|
-
});
|
|
1544
|
-
};
|
|
1545
|
-
}, []);
|
|
1546
|
-
const findActiveLeaf = useCallback((v, t) => {
|
|
1547
|
-
for (const n of v) {
|
|
1548
|
-
if (n.type === 'leaf' && n.url === t)
|
|
1549
|
-
return n;
|
|
1550
|
-
if (n.type === 'branch' && n.children) {
|
|
1551
|
-
const found = findActiveLeaf(n.children, t);
|
|
1552
|
-
if (found)
|
|
1553
|
-
return found;
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
return null;
|
|
1557
|
-
}, []);
|
|
1558
|
-
// MULTI-SELECT HANDLERS
|
|
1559
|
-
const handleSelect = useCallback((v, i, r) => {
|
|
1560
|
-
setSelLeaves(s => {
|
|
1561
|
-
if (r) {
|
|
1562
|
-
const anchor = lastSel || findActiveLeaf(tree, cPathname)?.path;
|
|
1563
|
-
if (anchor) {
|
|
1564
|
-
const allLeaves = [];
|
|
1565
|
-
/*
|
|
1566
|
-
* Handles walk.
|
|
1567
|
-
*
|
|
1568
|
-
* @param nodes - Sidebar nodes traversed while collecting leaf paths.
|
|
1569
|
-
*/
|
|
1570
|
-
(function walk(nodes) {
|
|
1571
|
-
nodes.forEach(n => {
|
|
1572
|
-
if (n.type === 'leaf' && n.path)
|
|
1573
|
-
allLeaves.push(n.path);
|
|
1574
|
-
if (n.type === 'branch' && n.children)
|
|
1575
|
-
walk(n.children);
|
|
1576
|
-
});
|
|
1577
|
-
})(tree);
|
|
1578
|
-
const i1 = allLeaves.indexOf(anchor);
|
|
1579
|
-
const i2 = allLeaves.indexOf(v);
|
|
1580
|
-
if (i1 !== -1 && i2 !== -1)
|
|
1581
|
-
return Array.from(new Set([...(i ? s : []), ...allLeaves.slice(Math.min(i1, i2), Math.max(i1, i2) + 1)]));
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
if (i) {
|
|
1585
|
-
setLastSel(v);
|
|
1586
|
-
if (s.includes(v))
|
|
1587
|
-
return s.filter(p => p !== v);
|
|
1588
|
-
return [...s, v];
|
|
1589
|
-
}
|
|
1590
|
-
setLastSel(v);
|
|
1591
|
-
return [v];
|
|
1592
|
-
});
|
|
1593
|
-
}, [tree, lastSel, cPathname, findActiveLeaf]);
|
|
1594
|
-
const clearSelection = useCallback(() => {
|
|
1595
|
-
setSelLeaves([]);
|
|
1596
|
-
setLastSel(null);
|
|
1597
|
-
}, []);
|
|
1598
|
-
const mergeLeaves = useCallback((v) => {
|
|
1599
|
-
const leafOrder = new Map();
|
|
1600
|
-
(function walk(nodes, order = 0) {
|
|
1601
|
-
for (const node of nodes) {
|
|
1602
|
-
if (node.type === 'leaf' && node.path)
|
|
1603
|
-
leafOrder.set(node.path, order++);
|
|
1604
|
-
if (node.type === 'branch' && node.children)
|
|
1605
|
-
order = walk(node.children, order);
|
|
1606
|
-
}
|
|
1607
|
-
return order;
|
|
1608
|
-
})(tree);
|
|
1609
|
-
const orderedPaths = Array.from(new Set(v)).sort((a, b) => (leafOrder.get(a) ?? Number.MAX_SAFE_INTEGER) - (leafOrder.get(b) ?? Number.MAX_SAFE_INTEGER));
|
|
1610
|
-
if (orderedPaths.length < 2)
|
|
1611
|
-
return;
|
|
1612
|
-
setInputDlg({
|
|
1613
|
-
open: true,
|
|
1614
|
-
title: 'Merge Pages',
|
|
1615
|
-
label: 'New page title',
|
|
1616
|
-
value: 'Merged Page',
|
|
1617
|
-
onCancel: () => { },
|
|
1618
|
-
onSave: nTitle => {
|
|
1619
|
-
// prettier-ignore
|
|
1620
|
-
void runMutation(async () => {
|
|
1621
|
-
try {
|
|
1622
|
-
const [firstPath, ...restPaths] = orderedPaths;
|
|
1623
|
-
if (!firstPath)
|
|
1624
|
-
throw new Error('Not enough valid documents to merge');
|
|
1625
|
-
setTree(v => {
|
|
1626
|
-
/*
|
|
1627
|
-
* Handles prune.
|
|
1628
|
-
*
|
|
1629
|
-
* @param items - Sidebar items being pruned from the tree.
|
|
1630
|
-
*/
|
|
1631
|
-
return (function prune(items) {
|
|
1632
|
-
const n = [];
|
|
1633
|
-
for (const node of items) {
|
|
1634
|
-
if (node.type === 'leaf') {
|
|
1635
|
-
if (!node.path || new Set(restPaths).has(node.path))
|
|
1636
|
-
continue;
|
|
1637
|
-
n.push(node.path === firstPath ? { ...node, title: nTitle } : node);
|
|
1638
|
-
continue;
|
|
1639
|
-
}
|
|
1640
|
-
if (!node.children) {
|
|
1641
|
-
n.push(node);
|
|
1642
|
-
continue;
|
|
1643
|
-
}
|
|
1644
|
-
const children = prune(node.children);
|
|
1645
|
-
if (children.length > 0)
|
|
1646
|
-
n.push({ ...node, children });
|
|
1647
|
-
}
|
|
1648
|
-
return n;
|
|
1649
|
-
})(v);
|
|
1650
|
-
});
|
|
1651
|
-
const pages = (await Promise.all(orderedPaths.map(p => actions.read.doc.get({ path: p }))))
|
|
1652
|
-
.map((r, i) => ({ path: orderedPaths[i], doc: r?.data }))
|
|
1653
|
-
.filter((e) => Boolean(e.doc));
|
|
1654
|
-
if (pages.length < 2)
|
|
1655
|
-
throw new Error('Not enough valid documents to merge');
|
|
1656
|
-
const hoisted = new Set();
|
|
1657
|
-
const mergedContent = (hoisted.size > 0 ? Array.from(hoisted).join('\n') + '\n\n' : '') + (pages.map(({ doc: d }) => {
|
|
1658
|
-
let raw = (typeof d.source === 'string' ? d.source : '') || latestContent(d.content) || '';
|
|
1659
|
-
// extract standard imports securely without collapsing body blocks
|
|
1660
|
-
raw = raw.replace(/^import\s+[^;]+;/gm, m => {
|
|
1661
|
-
hoisted.add(m.trim());
|
|
1662
|
-
return '';
|
|
1663
|
-
});
|
|
1664
|
-
// extract generated MDX exports targeting typical runtime const outputs
|
|
1665
|
-
raw = raw.replace(/^export\s+(const|let|var|type|interface)\s+[^;]+;/gm, m => {
|
|
1666
|
-
hoisted.add(m.trim());
|
|
1667
|
-
return '';
|
|
1668
|
-
});
|
|
1669
|
-
return `## ${d.title || 'Untitled'}\n\n${raw.trim()}`;
|
|
1670
|
-
})).join('\n\n');
|
|
1671
|
-
const mergedDesc = pages.map(({ doc: d }) => d.description).filter(Boolean).join(' ').substring(0, 500);
|
|
1672
|
-
const { data: nDoc, error: createErr } = (await actions.write.doc.editor({ path: firstPath, title: nTitle, description: mergedDesc, source: mergedContent }));
|
|
1673
|
-
if (createErr || !nDoc)
|
|
1674
|
-
throw createErr || new Error('Failed to merge doc');
|
|
1675
|
-
const { error: srcErr } = await actions.write.doc.retargetSources({ sourceIds: restPaths, targetId: nDoc.path || firstPath });
|
|
1676
|
-
if (srcErr)
|
|
1677
|
-
throw srcErr;
|
|
1678
|
-
// pass the raw path string straight out of the selection array to the RM action to safely hit Sanity identifiers without verifyWrite injecting `.mdx`
|
|
1679
|
-
for (const p of restPaths) {
|
|
1680
|
-
if ((TerraQualityRegex.REGEX_INDEX_MD_MDX_I_REGEX().test(p)))
|
|
1681
|
-
await actions.write.doc.rm({ path: dirname(p), type: 'folder' });
|
|
1682
|
-
else
|
|
1683
|
-
await actions.write.doc.rm({ path: p, assets: false, type: 'doc' });
|
|
1684
|
-
}
|
|
1685
|
-
setSelLeaves([]);
|
|
1686
|
-
setLastSel(null);
|
|
1687
|
-
const tgtRoute = nDoc.pathname || docsPath(dirname(firstPath).toLowerCase());
|
|
1688
|
-
memoryDrafts.set(tgtRoute, { source: (typeof nDoc.source === 'string' ? nDoc.source : '') || latestContent(nDoc.content) || '', draft: { content: Array.isArray(nDoc.content) ? nDoc.content : [], description: nDoc.description || '', path: nDoc.path || firstPath, title: nDoc.title || nTitle }, metadata: nDoc });
|
|
1689
|
-
$unsavedDrafts.setKey(tgtRoute, false);
|
|
1690
|
-
const tgtUrl = new URL(tgtRoute, window.location.origin);
|
|
1691
|
-
tgtUrl.searchParams.set('mode', 'edit');
|
|
1692
|
-
window.history.pushState(null, '', tgtUrl.toString());
|
|
1693
|
-
document.dispatchEvent(new CustomEvent('docs:navigate', { detail: { url: tgtRoute, pathname: tgtRoute, path: nDoc.path || firstPath, title: nDoc.title || nTitle, description: mergedDesc, source: mergedContent, content: Array.isArray(nDoc.content) ? nDoc.content : undefined, compiledMDX: '' } }));
|
|
1694
|
-
toast.success('Pages merged successfully.');
|
|
1695
|
-
}
|
|
1696
|
-
catch (err) {
|
|
1697
|
-
setTree(JSON.parse(JSON.stringify(tree)));
|
|
1698
|
-
throw err;
|
|
1699
|
-
}
|
|
1700
|
-
}, 'Failed to merge pages.', () => { setLoading(true); }, () => { setLoading(false); });
|
|
1701
|
-
},
|
|
1702
|
-
});
|
|
1703
|
-
}, [tree]);
|
|
1704
|
-
const addSection = useCallback((o) => {
|
|
1705
|
-
if ($transition.get())
|
|
1706
|
-
return;
|
|
1707
|
-
setInputDlg({
|
|
1708
|
-
open: true,
|
|
1709
|
-
title: o?.title || 'Add Section',
|
|
1710
|
-
description: o?.description,
|
|
1711
|
-
label: 'Section name',
|
|
1712
|
-
value: 'Untitled Section',
|
|
1713
|
-
onCancel: () => { },
|
|
1714
|
-
onSave: name => {
|
|
1715
|
-
setInputDlg(null);
|
|
1716
|
-
setCreating(true);
|
|
1717
|
-
(async () => {
|
|
1718
|
-
// prettier-ignore
|
|
1719
|
-
try {
|
|
1720
|
-
const { data: sNameRes } = await actions.read.util.node({ op: 'name', val: name });
|
|
1721
|
-
const tSectId = `temp_s_${Date.now()}`;
|
|
1722
|
-
const tGrpId = `temp_g_${Date.now()}`;
|
|
1723
|
-
const tLeafId = `temp_l_${Date.now()}`;
|
|
1724
|
-
const dPath = `${(sNameRes || 'untitled')}/general/untitled`;
|
|
1725
|
-
const url = buildRoute(tSectId, tGrpId, tLeafId);
|
|
1726
|
-
const sectUrl = docsPath(tSectId);
|
|
1727
|
-
setTree(c => {
|
|
1728
|
-
const nTree = JSON.parse(JSON.stringify(c));
|
|
1729
|
-
if (!nTree.find(n => n.type === 'branch' && 'url' in n && n.url === sectUrl))
|
|
1730
|
-
nTree.push({
|
|
1731
|
-
type: 'branch', origName: tSectId, permission: 'write', title: name, url: sectUrl,
|
|
1732
|
-
children: [{
|
|
1733
|
-
type: 'branch', origName: tGrpId, permission: 'write', title: 'General', url: buildRoute(tSectId, tGrpId),
|
|
1734
|
-
children: [{ type: 'leaf', fileType: 'mdx', origName: tLeafId, path: dPath, permission: 'write', title: 'Untitled', description: '', url, source: '' }],
|
|
1735
|
-
}],
|
|
1736
|
-
firstLeafUrl: url, hue: getHue(name),
|
|
1737
|
-
});
|
|
1738
|
-
return nTree;
|
|
1739
|
-
});
|
|
1740
|
-
memoryDrafts.set(url, { source: '', draft: { content: [], description: '', path: dPath, title: 'Untitled' }, metadata: { path: dPath, title: 'Untitled', description: '', pathname: url } });
|
|
1741
|
-
$unsavedDrafts.setKey(url, false);
|
|
1742
|
-
const tgtUrl = new URL(url, window.location.origin);
|
|
1743
|
-
tgtUrl.searchParams.set('mode', 'edit');
|
|
1744
|
-
window.history.pushState(null, '', tgtUrl.toString());
|
|
1745
|
-
document.dispatchEvent(new CustomEvent('docs:navigate', { detail: { url: url, pathname: url, path: dPath, title: 'Untitled', description: '', source: '', content: [], compiledMDX: '' } }));
|
|
1746
|
-
const { data, error } = await actions.write.doc.createSection({ title: name });
|
|
1747
|
-
if (error || !data)
|
|
1748
|
-
throw new Error(error?.message || 'Failed to create section.');
|
|
1749
|
-
setTimeout(() => {
|
|
1750
|
-
void refreshTree();
|
|
1751
|
-
}, 2500);
|
|
1752
|
-
}
|
|
1753
|
-
catch (err) {
|
|
1754
|
-
console.error('[addSection] Creation failed:', err);
|
|
1755
|
-
toast.error(err instanceof Error ? err.message : 'Failed to create section.');
|
|
1756
|
-
void refreshTree();
|
|
1757
|
-
}
|
|
1758
|
-
finally {
|
|
1759
|
-
setCreating(false);
|
|
1760
|
-
}
|
|
1761
|
-
})();
|
|
1762
|
-
},
|
|
1763
|
-
});
|
|
1764
|
-
}, [refreshTree]);
|
|
1765
|
-
const uploadAsLeaf = useCallback(async (f, t) => {
|
|
1766
|
-
if ($transition.get() || !f || f.length === 0)
|
|
1767
|
-
return;
|
|
1768
|
-
runMutation(async () => {
|
|
1769
|
-
const dir = dirname(t);
|
|
1770
|
-
for (let i = 0; i < f.length; i++) {
|
|
1771
|
-
const file = f[i];
|
|
1772
|
-
if (!file || !TerraQualityRegex.REGEX_MD_MDX_I_REGEX().test(file.name)) {
|
|
1773
|
-
toast.error(`File ${file?.name} must be a markdown document to be a page.`);
|
|
1774
|
-
continue;
|
|
1775
|
-
}
|
|
1776
|
-
const baseName = sanitizeName(file.name.replace(TerraQualityRegex.REGEX_I_REGEX(), '')) || 'untitled';
|
|
1777
|
-
const { error: wErr } = await actions.write.doc.editor({
|
|
1778
|
-
path: `${dir && dir !== '.' ? `${dir}/` : ''}${baseName}`,
|
|
1779
|
-
title: baseName.replace(TerraQualityRegex.REGEX_G_REGEX_2(), ' '),
|
|
1780
|
-
source: await file.text(),
|
|
1781
|
-
});
|
|
1782
|
-
if (wErr)
|
|
1783
|
-
throw wErr;
|
|
1784
|
-
}
|
|
1785
|
-
await refreshTree();
|
|
1786
|
-
toast.success('Pages uploaded successfully.');
|
|
1787
|
-
}, 'Failed to upload as page.', () => {
|
|
1788
|
-
setLoading(true);
|
|
1789
|
-
}, () => {
|
|
1790
|
-
setLoading(false);
|
|
1791
|
-
});
|
|
1792
|
-
}, [refreshTree]);
|
|
1793
|
-
const triggerRenameSection = useCallback((t) => {
|
|
1794
|
-
const cName = t.type === 'branch' ? t.origName : '';
|
|
1795
|
-
const currTitle = cTitle(toNodeLabel(t.title || cName)) || cName;
|
|
1796
|
-
setInputDlg({
|
|
1797
|
-
open: true,
|
|
1798
|
-
title: 'Rename Section',
|
|
1799
|
-
label: 'Section name',
|
|
1800
|
-
value: currTitle,
|
|
1801
|
-
onCancel: () => { },
|
|
1802
|
-
onSave: nTitle => {
|
|
1803
|
-
if ($transition.get() || t.type !== 'branch')
|
|
1804
|
-
return;
|
|
1805
|
-
if (!nTitle || nTitle === currTitle)
|
|
1806
|
-
return;
|
|
1807
|
-
runMutation(async () => {
|
|
1808
|
-
const { error: err } = await actions.write.doc.retitle({ id: t.origName, title: nTitle });
|
|
1809
|
-
if (err)
|
|
1810
|
-
throw err;
|
|
1811
|
-
await refreshTree();
|
|
1812
|
-
}, 'Failed to process rename.', () => {
|
|
1813
|
-
setLoading(true);
|
|
1814
|
-
}, () => {
|
|
1815
|
-
setLoading(false);
|
|
1816
|
-
});
|
|
1817
|
-
},
|
|
1818
|
-
});
|
|
1819
|
-
}, [refreshTree]);
|
|
1820
|
-
const SidebarActions = {
|
|
1821
|
-
saveHue: useCallback(() => {
|
|
1822
|
-
if ($transition.get() || !hueDlg?.section || hueDlg.section.type !== 'branch')
|
|
1823
|
-
return;
|
|
1824
|
-
runMutation(async () => {
|
|
1825
|
-
const tgtSectId = hueDlg.section.origName;
|
|
1826
|
-
// Apply hue mutation to Sanity
|
|
1827
|
-
const { error: err } = await actions.write.doc.hue({ id: tgtSectId, hue: hueDlg.hue });
|
|
1828
|
-
if (err)
|
|
1829
|
-
throw err;
|
|
1830
|
-
// 1. Optimistically update our local tree state so the sidebar dot updates immediately
|
|
1831
|
-
setTree(p => {
|
|
1832
|
-
const n = [...p];
|
|
1833
|
-
const idx = n.findIndex(s => s.origName === tgtSectId);
|
|
1834
|
-
const section = n[idx];
|
|
1835
|
-
if (idx !== -1 && section?.type === 'branch')
|
|
1836
|
-
n[idx] = { ...section, hue: hueDlg.hue };
|
|
1837
|
-
return n;
|
|
1838
|
-
});
|
|
1839
|
-
setHueDlg(null);
|
|
1840
|
-
// 2. Wait 2.5 seconds to allow the CDN to index the hue change so it persists properly across page loads.
|
|
1841
|
-
await new Promise(r => setTimeout(r, 2500));
|
|
1842
|
-
// 3. Refresh tree so subsequent queries use the correct backend hue.
|
|
1843
|
-
await refreshTree();
|
|
1844
|
-
// 4. Hot-swap the CSS variables robustly
|
|
1845
|
-
try {
|
|
1846
|
-
const html = await (await fetch(window.location.href)).text();
|
|
1847
|
-
const styleMatch = html.match(/<style[^>]*>([\s\S]*?--primary-1:[\s\S]*?)<\/style>/i);
|
|
1848
|
-
if (styleMatch) {
|
|
1849
|
-
let styleEl = document.getElementById('terra-dynamic-hue');
|
|
1850
|
-
if (!styleEl) {
|
|
1851
|
-
styleEl = document.createElement('style');
|
|
1852
|
-
styleEl.id = 'terra-dynamic-hue';
|
|
1853
|
-
document.head.appendChild(styleEl);
|
|
1854
|
-
}
|
|
1855
|
-
styleEl.innerHTML = styleMatch[1] || '';
|
|
1856
|
-
document.dispatchEvent(new CustomEvent('docs:hue-change'));
|
|
1857
|
-
}
|
|
1858
|
-
else {
|
|
1859
|
-
const inlineMatch = html.match(/<[^>]+style=["']([^"']*?--primary-1:[^"']*?)["']/i);
|
|
1860
|
-
if (inlineMatch)
|
|
1861
|
-
document.documentElement.style.cssText = inlineMatch[1] || '';
|
|
1862
|
-
else
|
|
1863
|
-
window.location.reload();
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
catch (_e) {
|
|
1867
|
-
window.location.reload();
|
|
1868
|
-
}
|
|
1869
|
-
}, 'Failed to update color.', () => {
|
|
1870
|
-
setLoading(true);
|
|
1871
|
-
}, () => {
|
|
1872
|
-
setLoading(false);
|
|
1873
|
-
});
|
|
1874
|
-
}, [hueDlg, refreshTree]),
|
|
1875
|
-
};
|
|
1876
|
-
const rmBranch = useCallback(async (f, _bPath, s) => {
|
|
1877
|
-
const pNorm = dirname(f) === '.' ? '' : dirname(f);
|
|
1878
|
-
let prevTree = [];
|
|
1879
|
-
setTree(c => {
|
|
1880
|
-
prevTree = c;
|
|
1881
|
-
const nTree = JSON.parse(JSON.stringify(c));
|
|
1882
|
-
const sibs = getSibs(nTree, pNorm);
|
|
1883
|
-
const idx = sibs.findIndex(n => n.type === 'branch' && [pNorm, n.origName].filter(Boolean).join('/') === f);
|
|
1884
|
-
if (idx !== -1)
|
|
1885
|
-
sibs.splice(idx, 1);
|
|
1886
|
-
return nTree;
|
|
1887
|
-
});
|
|
1888
|
-
const currPath = cPathname.toLowerCase().replace(TerraQualityRegex.REGEX_PATTERN_REGEX_3(), '');
|
|
1889
|
-
const currPrefix = docsPath(s).toLowerCase().replace(TerraQualityRegex.REGEX_PATTERN_REGEX_3(), '');
|
|
1890
|
-
const nPrefix = docsPath(dirname(s));
|
|
1891
|
-
const redirect = currPath !== currPrefix && !currPath.startsWith(`${currPrefix}/`) ? null : `${nPrefix.replace(TerraQualityRegex.REGEX_PATTERN_REGEX_3(), '')}${currPath.slice(currPrefix.length)}` || nPrefix;
|
|
1892
|
-
try {
|
|
1893
|
-
const { error: err } = await actions.write.doc.rm({ path: s, type: 'folder' });
|
|
1894
|
-
if (err && !err.message?.includes('folder not found'))
|
|
1895
|
-
throw err;
|
|
1896
|
-
await refreshTree();
|
|
1897
|
-
if (redirect && redirect !== cPathname)
|
|
1898
|
-
navigate({ href: redirect, mode: 'view' });
|
|
1899
|
-
}
|
|
1900
|
-
catch (err) {
|
|
1901
|
-
setTree(prevTree);
|
|
1902
|
-
throw err;
|
|
1903
|
-
}
|
|
1904
|
-
}, [cPathname, refreshTree]);
|
|
1905
|
-
const rmLeaf = useCallback(async (p, b, a) => {
|
|
1906
|
-
const isIdx = TerraQualityRegex.REGEX_INDEX_MD_MDX_I_REGEX().test(p);
|
|
1907
|
-
const tgtPath = isIdx ? dirname(p) : p;
|
|
1908
|
-
const tgtType = isIdx ? 'folder' : 'doc';
|
|
1909
|
-
let prevTree = [];
|
|
1910
|
-
setTree(c => {
|
|
1911
|
-
prevTree = c;
|
|
1912
|
-
const nTree = JSON.parse(JSON.stringify(c));
|
|
1913
|
-
const sibs = getSibs(nTree, b);
|
|
1914
|
-
const i = sibs.findIndex(n => (n.type === 'leaf' ? n.path === p : false));
|
|
1915
|
-
if (i !== -1)
|
|
1916
|
-
sibs.splice(i, 1);
|
|
1917
|
-
return nTree;
|
|
1918
|
-
});
|
|
1919
|
-
try {
|
|
1920
|
-
const { error: err } = await actions.write.doc.rm(isIdx ? { path: tgtPath, type: tgtType } : { assets: a, path: tgtPath, type: tgtType });
|
|
1921
|
-
if (err && !err.message?.includes('folder not found'))
|
|
1922
|
-
throw err;
|
|
1923
|
-
await refreshTree();
|
|
1924
|
-
if (a)
|
|
1925
|
-
navigate({ href: docsPath(dirname(p).replace(/^\.\/?/, '')), mode: 'view' });
|
|
1926
|
-
}
|
|
1927
|
-
catch (err) {
|
|
1928
|
-
setTree(prevTree);
|
|
1929
|
-
throw err;
|
|
1930
|
-
}
|
|
1931
|
-
}, [refreshTree]);
|
|
1932
|
-
const openCreate = useCallback((p, v) => {
|
|
1933
|
-
if ($transition.get())
|
|
1934
|
-
return;
|
|
1935
|
-
setInputDlg({
|
|
1936
|
-
open: true,
|
|
1937
|
-
title: 'Add Page',
|
|
1938
|
-
label: 'Page title',
|
|
1939
|
-
value: 'Untitled Page',
|
|
1940
|
-
onCancel: () => { },
|
|
1941
|
-
onSave: name => {
|
|
1942
|
-
setInputDlg(null);
|
|
1943
|
-
setCreating(true);
|
|
1944
|
-
(async () => {
|
|
1945
|
-
// prettier-ignore
|
|
1946
|
-
try {
|
|
1947
|
-
const { data: res, error: resErr } = await actions.read.doc.resolvePath({ path: p, title: name });
|
|
1948
|
-
if (resErr || !res)
|
|
1949
|
-
throw resErr || new Error('Failed to resolve path');
|
|
1950
|
-
const dPath = (res).path;
|
|
1951
|
-
const tLeafId = `temp_l_${Date.now()}`;
|
|
1952
|
-
const sectId = urlIds.sectionId;
|
|
1953
|
-
let url = '';
|
|
1954
|
-
// safety Fallback for parentId mapping
|
|
1955
|
-
let aParentId = v;
|
|
1956
|
-
if (typeof window !== 'undefined' && (!aParentId || aParentId === '.' || aParentId === sectId)) {
|
|
1957
|
-
try {
|
|
1958
|
-
const curr = route(window.location.pathname + window.location.search);
|
|
1959
|
-
if (curr.groupId)
|
|
1960
|
-
aParentId = curr.groupId;
|
|
1961
|
-
else if (curr.sectionId)
|
|
1962
|
-
aParentId = curr.sectionId;
|
|
1963
|
-
}
|
|
1964
|
-
catch {
|
|
1965
|
-
// The existing parent remains the safe fallback.
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
url = aParentId === sectId ? buildRoute(sectId, undefined, tLeafId) : buildRoute(sectId, aParentId, tLeafId);
|
|
1969
|
-
setTree(v => {
|
|
1970
|
-
const nTree = JSON.parse(JSON.stringify(v));
|
|
1971
|
-
(function insertLeaf(nodes, tgtId) {
|
|
1972
|
-
for (const n of nodes) {
|
|
1973
|
-
if (n.type === 'branch') {
|
|
1974
|
-
if (n.origName === tgtId) {
|
|
1975
|
-
if (!n.children)
|
|
1976
|
-
n.children = [];
|
|
1977
|
-
// prevent duplicate pushes if it re-renders
|
|
1978
|
-
if (!n.children.find(c => c.type === 'leaf' && c.url === url))
|
|
1979
|
-
n.children.push({ type: 'leaf', fileType: 'mdx', origName: tLeafId, path: dPath, permission: 'write', title: name, description: '', url, source: '' });
|
|
1980
|
-
return true;
|
|
1981
|
-
}
|
|
1982
|
-
if (n.children && insertLeaf(n.children, tgtId))
|
|
1983
|
-
return true;
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
return false;
|
|
1987
|
-
})(nTree, aParentId);
|
|
1988
|
-
return nTree;
|
|
1989
|
-
});
|
|
1990
|
-
memoryDrafts.set(url, { source: '', draft: { content: [], description: '', path: dPath, title: name }, metadata: { path: dPath, title: name, description: '', pathname: url } });
|
|
1991
|
-
$unsavedDrafts.setKey(url, false);
|
|
1992
|
-
const tgtUrl = new URL(url, window.location.origin);
|
|
1993
|
-
tgtUrl.searchParams.set('mode', 'edit');
|
|
1994
|
-
window.history.pushState(null, '', tgtUrl.toString());
|
|
1995
|
-
document.dispatchEvent(new CustomEvent('docs:navigate', { detail: { url: url, pathname: url, path: dPath, title: name, description: '', source: '', content: [], compiledMDX: '' } }));
|
|
1996
|
-
const { data, error: err } = (await actions.write.doc.editor({ source: '', description: '', path: dPath, title: name, parentId: aParentId }));
|
|
1997
|
-
if (err || !data)
|
|
1998
|
-
throw err || new Error('Failed to create docs page.');
|
|
1999
|
-
setTimeout(() => {
|
|
2000
|
-
void refreshTree();
|
|
2001
|
-
}, 2500);
|
|
2002
|
-
}
|
|
2003
|
-
catch (err) {
|
|
2004
|
-
console.error('[openCreate] Creation failed:', err);
|
|
2005
|
-
toast.error('Failed to create docs page.');
|
|
2006
|
-
void refreshTree();
|
|
2007
|
-
}
|
|
2008
|
-
finally {
|
|
2009
|
-
setCreating(false);
|
|
2010
|
-
}
|
|
2011
|
-
})();
|
|
2012
|
-
},
|
|
2013
|
-
});
|
|
2014
|
-
}, [refreshTree, urlIds.sectionId]);
|
|
2015
|
-
const addGroup = useCallback((p, s) => {
|
|
2016
|
-
if ($transition.get())
|
|
2017
|
-
return;
|
|
2018
|
-
setInputDlg({
|
|
2019
|
-
open: true,
|
|
2020
|
-
title: 'Add Group',
|
|
2021
|
-
label: 'Group name',
|
|
2022
|
-
value: 'Untitled Group',
|
|
2023
|
-
onCancel: () => { },
|
|
2024
|
-
onSave: name => {
|
|
2025
|
-
setInputDlg(null);
|
|
2026
|
-
setCreating(true);
|
|
2027
|
-
(async () => {
|
|
2028
|
-
// prettier-ignore
|
|
2029
|
-
try {
|
|
2030
|
-
const { data: res, error: resErr } = await actions.read.doc.resolvePath({ path: p, title: name });
|
|
2031
|
-
if (resErr || !res)
|
|
2032
|
-
throw resErr || new Error('Failed to resolve path');
|
|
2033
|
-
const dPath = `${((res).dir)}/untitled`;
|
|
2034
|
-
const tGrpId = `temp_g_${Date.now()}`;
|
|
2035
|
-
const tLeafId = `temp_l_${Date.now()}`;
|
|
2036
|
-
const rootId = s || urlIds.sectionId;
|
|
2037
|
-
const url = buildRoute(rootId, tGrpId, tLeafId);
|
|
2038
|
-
const gUrl = buildRoute(rootId, tGrpId);
|
|
2039
|
-
setTree(v => {
|
|
2040
|
-
const nTree = JSON.parse(JSON.stringify(v));
|
|
2041
|
-
(function insertGroup(nodes, tgtId) {
|
|
2042
|
-
for (const n of nodes) {
|
|
2043
|
-
if (n.type === 'branch') {
|
|
2044
|
-
if (n.origName === tgtId) {
|
|
2045
|
-
if (!n.children)
|
|
2046
|
-
n.children = [];
|
|
2047
|
-
if (!n.children.find(c => c.type === 'branch' && 'url' in c && c.url === gUrl))
|
|
2048
|
-
n.children.push({
|
|
2049
|
-
type: 'branch', origName: tGrpId, permission: 'write', title: name, url: gUrl,
|
|
2050
|
-
children: [{ type: 'leaf', fileType: 'mdx', origName: tLeafId, path: dPath, permission: 'write', title: 'Untitled', description: '', url, source: '' }],
|
|
2051
|
-
});
|
|
2052
|
-
return true;
|
|
2053
|
-
}
|
|
2054
|
-
if (n.children && insertGroup(n.children, tgtId))
|
|
2055
|
-
return true;
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
return false;
|
|
2059
|
-
})(nTree, rootId);
|
|
2060
|
-
return nTree;
|
|
2061
|
-
});
|
|
2062
|
-
memoryDrafts.set(url, { source: '', draft: { content: [], description: '', path: dPath, title: 'Untitled' }, metadata: { path: dPath, title: 'Untitled', description: '', pathname: url } });
|
|
2063
|
-
$unsavedDrafts.setKey(url, false);
|
|
2064
|
-
const tgtUrl = new URL(url, window.location.origin);
|
|
2065
|
-
tgtUrl.searchParams.set('mode', 'edit');
|
|
2066
|
-
window.history.pushState(null, '', tgtUrl.toString());
|
|
2067
|
-
document.dispatchEvent(new CustomEvent('docs:navigate', { detail: { url: url, pathname: url, path: dPath, title: 'Untitled', description: '', source: '', content: [], compiledMDX: '' } }));
|
|
2068
|
-
const { data: gData, error: gErr } = await actions.write.doc.createGroup({ title: name, path: gUrl, sectionId: rootId });
|
|
2069
|
-
if (gErr || !gData)
|
|
2070
|
-
throw gErr || new Error('Failed to create group.');
|
|
2071
|
-
const { data, error: err } = (await actions.write.doc.editor({ source: '', description: '', path: dPath, title: 'Untitled', parentId: (typeof gData === 'object' && '_id' in gData && typeof gData._id === 'string' ? gData._id : undefined) }));
|
|
2072
|
-
if (err || !data)
|
|
2073
|
-
throw err || new Error('Failed to create group leaf.');
|
|
2074
|
-
setTimeout(() => {
|
|
2075
|
-
void refreshTree();
|
|
2076
|
-
}, 2500);
|
|
2077
|
-
}
|
|
2078
|
-
catch (err) {
|
|
2079
|
-
console.error('[addGroup] Creation failed:', err);
|
|
2080
|
-
toast.error('Failed to create group.');
|
|
2081
|
-
void refreshTree();
|
|
2082
|
-
}
|
|
2083
|
-
finally {
|
|
2084
|
-
setCreating(false);
|
|
2085
|
-
}
|
|
2086
|
-
})();
|
|
2087
|
-
},
|
|
2088
|
-
});
|
|
2089
|
-
}, [refreshTree, urlIds.sectionId]);
|
|
2090
|
-
const mvLeaf = useCallback(async ({ path: p, tTarget: tTgt, tpath: v, tPosition: r }) => {
|
|
2091
|
-
console.warn('[docs:mv-leaf]', { path: p, tTarget: tTgt, tpath: v, tPosition: r });
|
|
2092
|
-
const prevTree = JSON.parse(JSON.stringify(tree));
|
|
2093
|
-
let _nPath = p;
|
|
2094
|
-
try {
|
|
2095
|
-
/**
|
|
2096
|
-
* Handles find parent path.
|
|
2097
|
-
*
|
|
2098
|
-
* @param nodes - Sidebar nodes searched for the target document.
|
|
2099
|
-
* @param tgtPath - Target document path being resolved.
|
|
2100
|
-
* @param currPath - Current traversal path accumulated during the search.
|
|
2101
|
-
*/
|
|
2102
|
-
const pPath = (function findParentPath(nodes, tgtPath, currPath = '') {
|
|
2103
|
-
for (const n of nodes) {
|
|
2104
|
-
if (n.type === 'leaf' && n.path === tgtPath)
|
|
2105
|
-
return currPath || '.';
|
|
2106
|
-
if (n.type === 'branch' && n.children) {
|
|
2107
|
-
const found = findParentPath(n.children, tgtPath, currPath ? `${currPath}/${n.origName}` : n.origName);
|
|
2108
|
-
if (found)
|
|
2109
|
-
return found;
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
return null;
|
|
2113
|
-
})(tree, p) || '';
|
|
2114
|
-
const isMovingBranch = pPath !== v;
|
|
2115
|
-
const nTree = JSON.parse(JSON.stringify(tree));
|
|
2116
|
-
const oldSibs = getSibs(nTree, pPath);
|
|
2117
|
-
const sIdx = oldSibs.findIndex(n => (n.type === 'leaf' ? n.path === p : false));
|
|
2118
|
-
let movedNode = null;
|
|
2119
|
-
if (sIdx !== -1)
|
|
2120
|
-
movedNode = oldSibs.splice(sIdx, 1)[0];
|
|
2121
|
-
const tgtSibs = getSibs(nTree, v);
|
|
2122
|
-
let iIdx = tgtSibs.length;
|
|
2123
|
-
if (tTgt) {
|
|
2124
|
-
const tgtIdx = tgtSibs.findIndex(n => (n.type === 'leaf' ? n.path === tTgt : n.type === 'branch' && [v, n.origName].filter(Boolean).join('/') === tTgt));
|
|
2125
|
-
if (tgtIdx !== -1)
|
|
2126
|
-
iIdx = (r ?? 'before') === 'before' ? tgtIdx : tgtIdx + 1;
|
|
2127
|
-
}
|
|
2128
|
-
if (movedNode)
|
|
2129
|
-
tgtSibs.splice(iIdx, 0, movedNode);
|
|
2130
|
-
setTree(nTree);
|
|
2131
|
-
if (isMovingBranch) {
|
|
2132
|
-
const tgtNorm = v === '.' ? '' : v;
|
|
2133
|
-
const folderBacked = TerraQualityRegex.REGEX_INDEX_MD_MDX_I_REGEX().test(p);
|
|
2134
|
-
const leafDirName = folderBacked ? p.split('/').slice(-2)[0] : bName(p, p.match(TerraQualityRegex.REGEX_I_REGEX())?.[0]);
|
|
2135
|
-
const ext = p.match(TerraQualityRegex.REGEX_I_REGEX())?.[0] ?? '.mdx';
|
|
2136
|
-
const nPath = folderBacked ? `${tgtNorm === '.' ? '' : `${tgtNorm}/`}${leafDirName}/index${ext}` : `${tgtNorm === '.' ? '' : `${tgtNorm}/`}${leafDirName}${ext}`;
|
|
2137
|
-
if (p !== nPath) {
|
|
2138
|
-
const { data: docData } = await actions.read.doc.get({ path: p });
|
|
2139
|
-
const { data: movedDoc, error } = await actions.write.doc.commit({
|
|
2140
|
-
cur: p,
|
|
2141
|
-
next: nPath,
|
|
2142
|
-
source: docData && typeof docData === 'object' && 'source' in docData && typeof docData.source === 'string' ? docData.source : '',
|
|
2143
|
-
});
|
|
2144
|
-
_nPath = movedDoc?.path || nPath;
|
|
2145
|
-
if (error) {
|
|
2146
|
-
setTree(prevTree);
|
|
2147
|
-
toast.error(error.message || 'Move failed');
|
|
2148
|
-
notify({ error: new Error(error.message), fallback: 'Failed to move docs page.' });
|
|
2149
|
-
return;
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
if (tgtSibs.length > 0) {
|
|
2154
|
-
const parentIds = v.split('/');
|
|
2155
|
-
const parentId = parentIds[parentIds.length - 1];
|
|
2156
|
-
if (parentId && parentId !== '.') {
|
|
2157
|
-
const { error } = await actions.write.doc.reorder({
|
|
2158
|
-
id: parentId,
|
|
2159
|
-
field: /* Identify whether we are patching a Group or Section natively */ parentIds.length > 1 ? 'leaves' : 'groups',
|
|
2160
|
-
items: tgtSibs.filter(s => s.type === 'leaf').map(s => ({ _type: 'reference', _ref: s === movedNode ? _nPath : s.origName })),
|
|
2161
|
-
});
|
|
2162
|
-
if (error)
|
|
2163
|
-
throw error;
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
if (isMovingBranch && oldSibs) {
|
|
2167
|
-
const oldParentIds = pPath.split('/');
|
|
2168
|
-
const oldParentId = oldParentIds[oldParentIds.length - 1];
|
|
2169
|
-
if (oldParentId && oldParentId !== '.') {
|
|
2170
|
-
const { error } = await actions.write.doc.reorder({
|
|
2171
|
-
id: oldParentId,
|
|
2172
|
-
field: oldParentIds.length > 1 ? 'leaves' : 'groups',
|
|
2173
|
-
items: oldSibs.filter(s => s.type === 'leaf').map(s => ({ _type: 'reference', _ref: s.origName })),
|
|
2174
|
-
});
|
|
2175
|
-
if (error)
|
|
2176
|
-
throw error;
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
await refreshTree();
|
|
2180
|
-
}
|
|
2181
|
-
catch (err) {
|
|
2182
|
-
setTree(prevTree);
|
|
2183
|
-
toast.error(err instanceof Error ? err.message : 'Move failed');
|
|
2184
|
-
notify({ error: err instanceof Error ? err : new Error('Move failed'), fallback: 'Failed to move docs page.' });
|
|
2185
|
-
void refreshTree();
|
|
2186
|
-
}
|
|
2187
|
-
}, [tree, refreshTree]);
|
|
2188
|
-
const mvBranch = useCallback(async ({ fPath: f, pPath: p, tpath: t, tPosition: v, tTarget: tTgt }) => {
|
|
2189
|
-
if (p !== t)
|
|
2190
|
-
return;
|
|
2191
|
-
try {
|
|
2192
|
-
const nTree = JSON.parse(JSON.stringify(tree));
|
|
2193
|
-
const oldSibs = getSibs(nTree, p);
|
|
2194
|
-
const srcIdx = oldSibs.findIndex(n => (n.type === 'branch' ? [p, n.origName].filter(Boolean).join('/') === f : false));
|
|
2195
|
-
let movedNode = null;
|
|
2196
|
-
if (srcIdx !== -1)
|
|
2197
|
-
movedNode = oldSibs.splice(srcIdx, 1)[0];
|
|
2198
|
-
const tgtSibs = getSibs(nTree, t);
|
|
2199
|
-
let iIdx = tgtSibs.length;
|
|
2200
|
-
const tIdx = tgtSibs.findIndex(n => n.type === 'branch' && [t, n.origName].filter(Boolean).join('/') === tTgt);
|
|
2201
|
-
if (tIdx !== -1)
|
|
2202
|
-
iIdx = (v ?? 'before') === 'before' ? tIdx : tIdx + 1;
|
|
2203
|
-
if (movedNode)
|
|
2204
|
-
tgtSibs.splice(iIdx, 0, movedNode);
|
|
2205
|
-
setTree(nTree);
|
|
2206
|
-
if (tgtSibs.length > 0) {
|
|
2207
|
-
const parentIds = t.split('/');
|
|
2208
|
-
const parentId = parentIds[parentIds.length - 1];
|
|
2209
|
-
if (parentId && parentId !== '.') {
|
|
2210
|
-
const { error } = await actions.write.doc.reorder({ id: parentId, field: 'groups', items: tgtSibs.filter(s => s.type === 'branch').map(s => ({ _type: 'reference', _ref: s.origName })) });
|
|
2211
|
-
if (error)
|
|
2212
|
-
throw error;
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2215
|
-
await refreshTree();
|
|
2216
|
-
}
|
|
2217
|
-
catch (err) {
|
|
2218
|
-
setTree(JSON.parse(JSON.stringify(tree)));
|
|
2219
|
-
toast.error(err instanceof Error ? err.message : 'Move failed');
|
|
2220
|
-
notify({ error: err instanceof Error ? err : new Error('Move failed'), fallback: 'Failed to reorder docs groups.' });
|
|
2221
|
-
void refreshTree();
|
|
2222
|
-
}
|
|
2223
|
-
}, [tree, refreshTree]);
|
|
2224
|
-
const actionDisabled = creating || loading;
|
|
2225
|
-
const assetChoices = useMemo(() => repoComponents.filter(c => c.name !== 'Leaf' && c.name !== 'Visualize').map(c => ({ label: c.name.replace(/([a-z])([A-Z])/g, '$1 $2'), name: c.name })), []);
|
|
2226
|
-
useEffect(() => {
|
|
2227
|
-
if (!assetTgt)
|
|
2228
|
-
return;
|
|
2229
|
-
if (!assetChoices.some(c => c.name === assetComponent))
|
|
2230
|
-
setAssetComponent(assetChoices[0]?.name ?? '');
|
|
2231
|
-
setAssetDialogOpen(true);
|
|
2232
|
-
}, [assetTgt, assetChoices, assetComponent]);
|
|
2233
|
-
SidebarActions.makeAsset = useCallback(async () => {
|
|
2234
|
-
if (!assetTgt || !assetComponent || assetBusy)
|
|
2235
|
-
return;
|
|
2236
|
-
try {
|
|
2237
|
-
setAssetBusy(true);
|
|
2238
|
-
const { error } = await actions.write.doc.asset({ path: assetTgt.path, component: assetComponent, title: assetTgt.title, description: '' });
|
|
2239
|
-
if (error)
|
|
2240
|
-
throw error;
|
|
2241
|
-
setAssetDialogOpen(false);
|
|
2242
|
-
setAssetTgt(null);
|
|
2243
|
-
toast.success('Asset created successfully.');
|
|
2244
|
-
await refreshTree();
|
|
2245
|
-
}
|
|
2246
|
-
catch (err) {
|
|
2247
|
-
console.error('[Sidebar] Failed to convert page to asset:', err);
|
|
2248
|
-
notify({ error: err instanceof Error ? err : new Error('Failed to convert page to asset.'), fallback: 'Failed to convert page to asset.' });
|
|
2249
|
-
}
|
|
2250
|
-
finally {
|
|
2251
|
-
setAssetBusy(false);
|
|
2252
|
-
}
|
|
2253
|
-
}, [assetBusy, assetComponent, assetTgt, refreshTree]);
|
|
2254
|
-
SidebarActions.renderSects = useCallback((t) => {
|
|
2255
|
-
const origName = t.type === 'branch' ? t.origName : '';
|
|
2256
|
-
const slugPath = 'url' in t && t.url ? t.url.replace(TerraQualityRegex.REGEX_DOCS_REGEX(), '') : '';
|
|
2257
|
-
const titleStr = cTitle(toNodeLabel(t.title || origName));
|
|
2258
|
-
return [
|
|
2259
|
-
{
|
|
2260
|
-
icon: _jsx(LinkIcon, {}),
|
|
2261
|
-
label: 'Copy link',
|
|
2262
|
-
disabled: actionDisabled,
|
|
2263
|
-
onSelect: () => {
|
|
2264
|
-
void navigator.clipboard.writeText(`${window.location.origin}${'url' in t && t.url ? t.url : ''}`);
|
|
2265
|
-
toast.success('Link copied');
|
|
2266
|
-
},
|
|
2267
|
-
},
|
|
2268
|
-
{
|
|
2269
|
-
icon: _jsx(BoxIcon, {}),
|
|
2270
|
-
label: 'Manage attachments',
|
|
2271
|
-
disabled: actionDisabled,
|
|
2272
|
-
onSelect: () => {
|
|
2273
|
-
setManageAttTgt({ path: slugPath, title: titleStr || slugPath, scope: 'section', node: t });
|
|
2274
|
-
},
|
|
2275
|
-
},
|
|
2276
|
-
{
|
|
2277
|
-
icon: _jsx(DownloadIcon, {}),
|
|
2278
|
-
label: 'Download section',
|
|
2279
|
-
disabled: actionDisabled,
|
|
2280
|
-
onSelect: ((paths, nodeIds) => () => {
|
|
2281
|
-
setExportTgt({ paths, rootDir: slugPath, filename: sanitizeName(titleStr || slugPath), title: `${titleStr} docs`, nodeIds });
|
|
2282
|
-
})(Array.from(new Set([...toPaths(t.type === 'branch' ? t.children : [])])), extractAllNodeIds([t])),
|
|
2283
|
-
},
|
|
2284
|
-
{
|
|
2285
|
-
icon: _jsx(PopOutIcon, {}),
|
|
2286
|
-
label: 'Make asset',
|
|
2287
|
-
disabled: actionDisabled,
|
|
2288
|
-
onSelect: () => {
|
|
2289
|
-
setAssetTgt({ path: slugPath, title: titleStr || slugPath });
|
|
2290
|
-
},
|
|
2291
|
-
},
|
|
2292
|
-
...(permission === 'write'
|
|
2293
|
-
? [
|
|
2294
|
-
{
|
|
2295
|
-
disabled: actionDisabled,
|
|
2296
|
-
icon: _jsx(EditIcon, {}),
|
|
2297
|
-
label: 'Rename section',
|
|
2298
|
-
onSelect: () => {
|
|
2299
|
-
triggerRenameSection(t);
|
|
2300
|
-
},
|
|
2301
|
-
},
|
|
2302
|
-
{
|
|
2303
|
-
disabled: actionDisabled,
|
|
2304
|
-
icon: _jsx(ColorIcon, {}),
|
|
2305
|
-
label: 'Change color',
|
|
2306
|
-
onSelect: () => {
|
|
2307
|
-
setHueDlg({ open: true, hue: getHue(t.title || slugPath, t.type === 'branch' ? t.hue : undefined), section: t });
|
|
2308
|
-
},
|
|
2309
|
-
},
|
|
2310
|
-
{
|
|
2311
|
-
destructive: true,
|
|
2312
|
-
disabled: actionDisabled,
|
|
2313
|
-
icon: _jsx(TrashIcon, {}),
|
|
2314
|
-
label: 'Delete section',
|
|
2315
|
-
onSelect: () => {
|
|
2316
|
-
setDelTgt({ kind: 'section', path: origName, slugPath, title: titleStr || slugPath, origNode: t });
|
|
2317
|
-
},
|
|
2318
|
-
},
|
|
2319
|
-
]
|
|
2320
|
-
: []),
|
|
2321
|
-
];
|
|
2322
|
-
}, [permission, actionDisabled, triggerRenameSection]);
|
|
2323
|
-
const getTree = useCallback(() => tree, [tree]);
|
|
2324
|
-
// CONTEXT
|
|
2325
|
-
const treeCtx = useMemo(() => ({
|
|
2326
|
-
creating,
|
|
2327
|
-
transitioning,
|
|
2328
|
-
drag,
|
|
2329
|
-
dState,
|
|
2330
|
-
mode: curMode,
|
|
2331
|
-
mvBranch,
|
|
2332
|
-
mvLeaf,
|
|
2333
|
-
pathname: cPathname,
|
|
2334
|
-
refreshTree,
|
|
2335
|
-
shellPermission: permission,
|
|
2336
|
-
openCreate,
|
|
2337
|
-
addGroup,
|
|
2338
|
-
uploadAsLeaf,
|
|
2339
|
-
rmBranch,
|
|
2340
|
-
rmLeaf,
|
|
2341
|
-
setDrag: (d) => {
|
|
2342
|
-
setDrag(d);
|
|
2343
|
-
if (!d)
|
|
2344
|
-
setDState(null);
|
|
2345
|
-
},
|
|
2346
|
-
setDState,
|
|
2347
|
-
setHovered,
|
|
2348
|
-
hovered,
|
|
2349
|
-
setInputDlg,
|
|
2350
|
-
setTree,
|
|
2351
|
-
getTree,
|
|
2352
|
-
findActiveLeaf,
|
|
2353
|
-
setCPathname,
|
|
2354
|
-
selectedLeaves: selLeaves,
|
|
2355
|
-
handleSelect,
|
|
2356
|
-
clearSelection,
|
|
2357
|
-
mergeLeaves,
|
|
2358
|
-
setDelTarget: setDelTgt,
|
|
2359
|
-
setExportTarget: setExportTgt,
|
|
2360
|
-
setManageAttTgt,
|
|
2361
|
-
setAssetTarget: setAssetTgt,
|
|
2362
|
-
}),
|
|
2363
|
-
// prettier-ignore
|
|
2364
|
-
[creating, transitioning, drag, dState, curMode, mvBranch, mvLeaf, cPathname, refreshTree, permission, openCreate, addGroup, uploadAsLeaf, rmBranch, rmLeaf, hovered, selLeaves, handleSelect, clearSelection, mergeLeaves, getTree]);
|
|
2365
|
-
// RENDERS
|
|
2366
|
-
return (_jsx(ErrorBoundary, { name: 'Sidebar', fallback: () => (_jsx(Sidebar, { className: 'not-prose z-50 overflow-y-auto p-4', children: _jsx(AlertError, { description: 'An unexpected error occurred while rendering the sidebar panel. The remaining workspace continues to function.', title: 'Sidebar render crash' }) })), children: _jsxs(Sidebar, { ...props, sticky: true, className: 'not-prose bg-primary-2 p-2', onClick: () => {
|
|
2367
|
-
// prevent clearing if a dialog is actively open
|
|
2368
|
-
if (inputDlg?.open || hueDlg?.open || delTgt)
|
|
2369
|
-
return;
|
|
2370
|
-
if (selLeaves.length > 0)
|
|
2371
|
-
clearSelection();
|
|
2372
|
-
}, children: [_jsx("style", { dangerouslySetInnerHTML: { __html: ` [data-sidebar="sidebar"], [data-sidebar="sidebar"][data-mobile="true"], [data-mobile="true"] .bg-sidebar, .bg-sidebar { background-color: rgb(var(--primary-2)) !important; } ` } }), _jsx(SidebarComponents.ExportDialog, { tgt: exportTgt, setTgt: setExportTgt, disabled: loading || transitioning }), _jsx(SidebarComponents.ManageAttchsDialog, { tgt: manageAttTgt, setTgt: setManageAttTgt, disabled: loading || transitioning, tree: tree }), _jsx(Dialog, { open: assetDialogOpen, onOpenChange: o => {
|
|
2373
|
-
if (assetBusy)
|
|
2374
|
-
return;
|
|
2375
|
-
setAssetDialogOpen(o);
|
|
2376
|
-
if (!o)
|
|
2377
|
-
setAssetTgt(null);
|
|
2378
|
-
}, children: _jsxs(DialogContent, { className: 'sm:max-w-lg', children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Make Asset" }), _jsx(DialogDescription, { children: "This will replace the page history with one standalone component version." })] }), _jsxs("div", { className: 'space-y-4 py-4', children: [_jsxs("div", { className: 'space-y-1.5', children: [_jsx(Label, { children: "Component" }), _jsxs(Select, { value: assetComponent, onValueChange: setAssetComponent, children: [_jsx(SelectTrigger, { "aria-label": 'Select a component...', children: _jsx(SelectValue, { placeholder: 'Select a component...' }) }), _jsx(SelectContent, { children: assetChoices.map(c => _jsx(SelectItem, { value: c.name, children: c.label }, c.name)) })] })] }), _jsx(AlertError, { title: 'Warning', description: 'Converting this page to an asset erases every existing version except the selected component.' })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: 'ghost', disabled: assetBusy, onClick: () => { setAssetTgt(null); }, children: "Cancel" }), _jsx(Button, { disabled: !assetComponent || assetBusy, loading: assetBusy, onClick: () => void SidebarActions.makeAsset(), children: "Make Asset" })] })] }) }), _jsx(SidebarComponents.PromptDialog, { state: inputDlg, setState: setInputDlg, disabled: loading || transitioning }), _jsx(SidebarComponents.DeleteDialog, { tgt: delTgt, setTgt: setDelTgt, onConfirm: () => {
|
|
2379
|
-
if (!delTgt || transitioning)
|
|
2380
|
-
return;
|
|
2381
|
-
const { kind, origNode, slugPath, path, bPath, active: a } = delTgt;
|
|
2382
|
-
setDelTgt(null);
|
|
2383
|
-
runMutation(async () => {
|
|
2384
|
-
if (kind === 'section' && origNode) {
|
|
2385
|
-
const sectionPath = slugPath;
|
|
2386
|
-
let prev = [];
|
|
2387
|
-
setTree(c => {
|
|
2388
|
-
prev = c;
|
|
2389
|
-
return c.filter(n => !(n.type === 'branch' && n.origName === origNode.origName));
|
|
2390
|
-
});
|
|
2391
|
-
const { error: err } = await actions.write.doc.rm({ path: sectionPath, type: 'folder' });
|
|
2392
|
-
if (err && !err.message?.includes('folder not found')) {
|
|
2393
|
-
setTree(prev);
|
|
2394
|
-
throw err;
|
|
2395
|
-
}
|
|
2396
|
-
await refreshTree();
|
|
2397
|
-
if (cPathname.toLowerCase().startsWith(docsPath(sectionPath).toLowerCase()))
|
|
2398
|
-
navigate({ href: docsRootPath(), mode: 'view' });
|
|
2399
|
-
}
|
|
2400
|
-
else if (kind === 'branch')
|
|
2401
|
-
await rmBranch(path, bPath, slugPath);
|
|
2402
|
-
else if (kind === 'leaf')
|
|
2403
|
-
await rmLeaf(path, bPath, !!a);
|
|
2404
|
-
}, `Failed to delete ${kind}.`, () => setLoading(true), () => setLoading(false));
|
|
2405
|
-
}, disabled: loading || transitioning }), _jsx(Dialog, { open: hueDlg?.open ?? false, onOpenChange: o => {
|
|
2406
|
-
if (loading || transitioning)
|
|
2407
|
-
return;
|
|
2408
|
-
setHueDlg(p => (p ? { ...p, open: o } : null));
|
|
2409
|
-
}, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Section Color" }), _jsx(DialogDescription, { children: "Adjust the hue for this section to customize its appearance." })] }), _jsx(Slider, { min: 0, max: 360, step: 1, value: [hueDlg?.hue ?? 0], onValueChange: ([v]) => {
|
|
2410
|
-
setHueDlg(p => (p ? { ...p, hue: v ?? 0 } : null));
|
|
2411
|
-
}, disabled: loading || transitioning, className: 'my-6 w-full [&_[data-slot=slider-range]]:bg-[color:var(--slider-color)] [&_[data-slot=slider-thumb]]:border-[color:var(--slider-color)] [&_[data-slot=slider-thumb]]:bg-[color:var(--slider-color)]', style: { '--slider-color': `hsl(${hueDlg?.hue ?? 0} 65% 55%)` } }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: 'ghost', disabled: loading || transitioning, onClick: () => {
|
|
2412
|
-
if (hueDlg?.section)
|
|
2413
|
-
setHueDlg({ ...hueDlg, hue: getHue(hueDlg.section.title || (hueDlg.section.type === 'branch' ? hueDlg.section.origName : '')) });
|
|
2414
|
-
}, children: "Reset" }), _jsx(Button, { disabled: loading || transitioning, loading: loading, onClick: () => {
|
|
2415
|
-
SidebarActions.saveHue();
|
|
2416
|
-
}, children: "Save" })] })] }) }), _jsxs(SidebarHeader, { className: 'print:hidden', "data-docs-navigation": 'true', children: [_jsxs("div", { className: 'mb-4 space-y-1 border-b border-primary-6 pb-4', children: [_jsx("a", { className: 'font-serif text-2xl hover:italic hover:tracking-tight', href: homePath, children: siteTitle }), siteDescription && _jsx("p", { className: 'm-0 text-sm text-primary-11', children: siteDescription })] }), _jsxs("div", { className: 'mt-2', children: [_jsx(Label, { htmlFor: 'docs-section-picker', className: 'mb-1 block text-[10px] font-semibold tracking-[0.12em] text-primary-10', children: "SECTION" }), _jsxs("div", { className: 'flex items-center gap-1', children: [_jsx("div", { className: cn('block min-w-0 flex-1 rounded-md transition-opacity'), children: _jsxs(Select, { disabled: transitioning, open: sectionOpen, onOpenChange: setSectionOpen, value: aViewed ? aViewed.origName : '', onValueChange: async (v) => {
|
|
2417
|
-
if (v === '__add_section__') {
|
|
2418
|
-
setTimeout(() => {
|
|
2419
|
-
addSection();
|
|
2420
|
-
}, 0);
|
|
2421
|
-
return;
|
|
2422
|
-
}
|
|
2423
|
-
if (aViewed?.origName === v)
|
|
2424
|
-
return;
|
|
2425
|
-
setSearchPages('');
|
|
2426
|
-
setViewedSecId(v);
|
|
2427
|
-
}, children: [_jsx(SelectTrigger, { id: 'docs-section-picker', className: 'w-full', "aria-label": 'Section', "data-docs-navigation": 'true', onPointerDown: e => {
|
|
2428
|
-
if (e.button === 0)
|
|
2429
|
-
setSectionOpen(true);
|
|
2430
|
-
}, children: _jsx(SelectValue, { placeholder: 'Section' }) }), _jsxs(SelectContent, { className: 'w-[var(--radix-select-trigger-width)]', children: [sects.map(s => {
|
|
2431
|
-
if (s.simulated) {
|
|
2432
|
-
return (_jsx(SelectItem, { value: s.origName, icon: _jsx("span", { "aria-hidden": 'true', className: 'size-3.5 rounded-[3px]', style: { backgroundColor: `hsl(${getHue(s.title || 'New Section')} 65% 55%)` } }), className: 'data-[state=checked]:text-primary-10 dark:data-[state=checked]:text-primary-12', children: s.title || 'New Section' }, s.origName));
|
|
2433
|
-
}
|
|
2434
|
-
// prettier-ignore
|
|
2435
|
-
return (_jsx(SelectItem, { value: s.origName, icon: _jsx("span", { "aria-hidden": 'true', className: 'size-3.5 rounded-[3px]', style: { backgroundColor: `hsl(${hueDlg?.open && hueDlg.section.origName === s.origName ? hueDlg.hue : getHue(s.title || s.origName, s.hue)} 65% 55%)` } }), className: 'data-[state=checked]:text-primary-10 dark:data-[state=checked]:text-primary-12', children: _jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { className: 'flex w-full items-center justify-between', children: _jsx("span", { className: 'line-clamp-1', children: cTitle(toNodeLabel(s.title || s.origName)) }) }), _jsx(ContextMenuContent, { children: _jsx(Menu, { actions: SidebarActions.renderSects(s) }) })] }) }, s.origName));
|
|
2436
|
-
}), permission === 'write' && (_jsxs(_Fragment, { children: [sects.length > 0 && !sects[0]?.simulated && _jsx(Separator, { className: 'my-1' }), _jsx(SelectItem, { value: '__add_section__', icon: _jsx(AddIcon, { className: 'size-4' }), className: 'font-medium text-primary-11 data-[state=checked]:text-primary-10 dark:data-[state=checked]:text-primary-12', children: _jsx("span", { children: "Add section" }) })] }))] })] }) }), aViewed && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: 'ghost', size: 'xs', "aria-label": `Actions for section ${aViewed.title || aViewed.origName}`, className: 'h-8 w-8 shrink-0 px-1', children: _jsx(OverflowMenuIcon, { size: 16 }) }) }), _jsx(DropdownMenuContent, { align: 'end', children: (SidebarActions.renderSects?.(aViewed) ?? []).map((action, index) => (_jsxs(DropdownMenuItem, { disabled: action.disabled, onSelect: () => action.onSelect?.(), className: action.destructive ? 'text-destructive focus:text-destructive' : undefined, children: [action.icon, action.label] }, `${action.label}-${index}`))) })] }))] }), _jsx(Input, { "aria-label": 'Search pages', placeholder: 'Search pages', value: searchPages, onChange: event => setSearchPages(event.currentTarget.value), className: 'mt-2' })] })] }), _jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { className: 'flex min-h-0 flex-1 flex-col outline-none', children: _jsx(SidebarContent, { className: 'min-h-0 flex-1 overflow-y-auto', children: _jsx(SidebarMenu, { children: visibleChildren.map(n => (_jsx(Tree, { bPath: aViewed?.origName ?? '', node: n, ctx: treeCtx, siblings: children }, getStableKey(n)))) }) }) }), permission === 'write' && aViewed && (_jsx(ContextMenuContent, { children: _jsx(Menu, { actions: [
|
|
2437
|
-
{
|
|
2438
|
-
disabled: actionDisabled,
|
|
2439
|
-
icon: _jsx(AddIcon, {}),
|
|
2440
|
-
label: 'Add group',
|
|
2441
|
-
onSelect: () => {
|
|
2442
|
-
if (aViewed.simulated)
|
|
2443
|
-
addSection({ title: 'Create Section', description: 'A section must be created first to hold your new group.' });
|
|
2444
|
-
else
|
|
2445
|
-
treeCtx.addGroup(aViewed.origName, aViewed.origName);
|
|
2446
|
-
},
|
|
2447
|
-
},
|
|
2448
|
-
] }) }))] }), _jsx(SidebarFooter, { children: userData && _jsx(User, { user: userData, className: 'mr-auto pl-0' }) }), _jsx(SidebarRail, {})] }) }));
|
|
2449
|
-
});
|
|
2450
|
-
//# sourceMappingURL=_sidebar.js.map
|