create-zudo-doc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/bin/create-zudo-doc.js +2 -0
- package/dist/api.d.ts +20 -0
- package/dist/api.js +13 -0
- package/dist/claude-md-gen.d.ts +2 -0
- package/dist/claude-md-gen.js +113 -0
- package/dist/cli.d.ts +39 -0
- package/dist/cli.js +157 -0
- package/dist/compose.d.ts +95 -0
- package/dist/compose.js +206 -0
- package/dist/constants.d.ts +20 -0
- package/dist/constants.js +224 -0
- package/dist/features/body-foot-util.d.ts +10 -0
- package/dist/features/body-foot-util.js +12 -0
- package/dist/features/claude-resources.d.ts +2 -0
- package/dist/features/claude-resources.js +6 -0
- package/dist/features/design-token-panel.d.ts +14 -0
- package/dist/features/design-token-panel.js +27 -0
- package/dist/features/doc-history.d.ts +9 -0
- package/dist/features/doc-history.js +11 -0
- package/dist/features/doc-tags.d.ts +19 -0
- package/dist/features/doc-tags.js +33 -0
- package/dist/features/footer-taglist.d.ts +14 -0
- package/dist/features/footer-taglist.js +17 -0
- package/dist/features/footer.d.ts +8 -0
- package/dist/features/footer.js +10 -0
- package/dist/features/i18n.d.ts +22 -0
- package/dist/features/i18n.js +41 -0
- package/dist/features/image-enlarge.d.ts +11 -0
- package/dist/features/image-enlarge.js +13 -0
- package/dist/features/index.d.ts +15 -0
- package/dist/features/index.js +53 -0
- package/dist/features/llms-txt.d.ts +11 -0
- package/dist/features/llms-txt.js +13 -0
- package/dist/features/search.d.ts +9 -0
- package/dist/features/search.js +11 -0
- package/dist/features/sidebar-resizer.d.ts +14 -0
- package/dist/features/sidebar-resizer.js +16 -0
- package/dist/features/sidebar-toggle.d.ts +13 -0
- package/dist/features/sidebar-toggle.js +15 -0
- package/dist/features/tag-governance.d.ts +14 -0
- package/dist/features/tag-governance.js +16 -0
- package/dist/features/tauri-dev.d.ts +2 -0
- package/dist/features/tauri-dev.js +25 -0
- package/dist/features/tauri.d.ts +11 -0
- package/dist/features/tauri.js +52 -0
- package/dist/features/versioning.d.ts +27 -0
- package/dist/features/versioning.js +43 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +150 -0
- package/dist/preset.d.ts +37 -0
- package/dist/preset.js +156 -0
- package/dist/prompts.d.ts +32 -0
- package/dist/prompts.js +248 -0
- package/dist/scaffold.d.ts +4 -0
- package/dist/scaffold.js +344 -0
- package/dist/settings-gen.d.ts +2 -0
- package/dist/settings-gen.js +237 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +34 -0
- package/dist/zfb-config-gen.d.ts +19 -0
- package/dist/zfb-config-gen.js +222 -0
- package/package.json +65 -0
- package/templates/base/.htmlvalidate.json +5 -0
- package/templates/base/.zfb/doc-history-meta.json +1 -0
- package/templates/base/pages/404.tsx +55 -0
- package/templates/base/pages/_data.ts +179 -0
- package/templates/base/pages/_mdx-components.ts +249 -0
- package/templates/base/pages/docs/[...slug].tsx +448 -0
- package/templates/base/pages/index.tsx +158 -0
- package/templates/base/pages/lib/_body-end-islands.tsx +201 -0
- package/templates/base/pages/lib/_category-nav.tsx +148 -0
- package/templates/base/pages/lib/_category-tree-nav.tsx +104 -0
- package/templates/base/pages/lib/_compose-meta-title.ts +29 -0
- package/templates/base/pages/lib/_details.tsx +30 -0
- package/templates/base/pages/lib/_doc-history-area.tsx +178 -0
- package/templates/base/pages/lib/_doc-metainfo-area.tsx +100 -0
- package/templates/base/pages/lib/_doc-tags-area.tsx +89 -0
- package/templates/base/pages/lib/_extract-headings.ts +81 -0
- package/templates/base/pages/lib/_footer-with-defaults.tsx +234 -0
- package/templates/base/pages/lib/_frontmatter-preview-data.ts +53 -0
- package/templates/base/pages/lib/_head-with-defaults.tsx +113 -0
- package/templates/base/pages/lib/_header-with-defaults.tsx +386 -0
- package/templates/base/pages/lib/_inline-version-switcher.tsx +84 -0
- package/templates/base/pages/lib/_math-block.tsx +63 -0
- package/templates/base/pages/lib/_nav-source-docs.ts +68 -0
- package/templates/base/pages/lib/_preset-generator.tsx +81 -0
- package/templates/base/pages/lib/_search-widget-script.ts +388 -0
- package/templates/base/pages/lib/_search-widget.tsx +196 -0
- package/templates/base/pages/lib/_sidebar-with-defaults.tsx +176 -0
- package/templates/base/pages/lib/_site-tree-nav.tsx +128 -0
- package/templates/base/pages/lib/locale-merge.ts +58 -0
- package/templates/base/pages/lib/route-enumerators.ts +302 -0
- package/templates/base/pages/sitemap.xml.tsx +51 -0
- package/templates/base/plugins/connect-adapter.mjs +144 -0
- package/templates/base/plugins/copy-public-plugin.mjs +50 -0
- package/templates/base/plugins/search-index-plugin.mjs +54 -0
- package/templates/base/scripts/run-b4push.sh +102 -0
- package/templates/base/src/components/ai-chat-modal.tsx +15 -0
- package/templates/base/src/components/client-router-bootstrap.tsx +14 -0
- package/templates/base/src/components/content/component-map.ts +25 -0
- package/templates/base/src/components/content/content-blockquote.tsx +16 -0
- package/templates/base/src/components/content/content-code.tsx +117 -0
- package/templates/base/src/components/content/content-link.tsx +83 -0
- package/templates/base/src/components/content/content-ol.tsx +19 -0
- package/templates/base/src/components/content/content-paragraph.tsx +10 -0
- package/templates/base/src/components/content/content-strong.tsx +16 -0
- package/templates/base/src/components/content/content-table.tsx +18 -0
- package/templates/base/src/components/content/content-ul.tsx +18 -0
- package/templates/base/src/components/content/heading-h2.tsx +26 -0
- package/templates/base/src/components/content/heading-h3.tsx +26 -0
- package/templates/base/src/components/content/heading-h4.tsx +26 -0
- package/templates/base/src/components/design-token-panel-bootstrap.tsx +15 -0
- package/templates/base/src/components/desktop-sidebar-toggle.tsx +15 -0
- package/templates/base/src/components/doc-history.tsx +18 -0
- package/templates/base/src/components/html-preview/highlighted-code.tsx +74 -0
- package/templates/base/src/components/html-preview/html-preview.tsx +108 -0
- package/templates/base/src/components/html-preview/preflight.ts +112 -0
- package/templates/base/src/components/html-preview/preview-base.tsx +159 -0
- package/templates/base/src/components/image-enlarge.tsx +19 -0
- package/templates/base/src/components/mobile-toc.tsx +94 -0
- package/templates/base/src/components/preset-generator.tsx +14 -0
- package/templates/base/src/components/sidebar-toggle.tsx +98 -0
- package/templates/base/src/components/sidebar-tree.tsx +543 -0
- package/templates/base/src/components/site-tree-nav.tsx +233 -0
- package/templates/base/src/components/theme-toggle.tsx +93 -0
- package/templates/base/src/components/toc.tsx +63 -0
- package/templates/base/src/components/tree-nav-shared.tsx +71 -0
- package/templates/base/src/config/color-scheme-utils.ts +182 -0
- package/templates/base/src/config/color-schemes.ts +128 -0
- package/templates/base/src/config/frontmatter-preview-defaults.ts +24 -0
- package/templates/base/src/config/frontmatter-preview-renderers.tsx +46 -0
- package/templates/base/src/config/i18n.ts +225 -0
- package/templates/base/src/config/settings-types.ts +162 -0
- package/templates/base/src/config/sidebars.ts +66 -0
- package/templates/base/src/config/tag-vocabulary-types.ts +39 -0
- package/templates/base/src/config/tag-vocabulary.ts +20 -0
- package/templates/base/src/hooks/use-active-heading.ts +133 -0
- package/templates/base/src/plugins/docs-source-map.ts +103 -0
- package/templates/base/src/plugins/hast-utils.ts +10 -0
- package/templates/base/src/plugins/rehype-code-title.ts +50 -0
- package/templates/base/src/plugins/rehype-heading-links.ts +53 -0
- package/templates/base/src/plugins/rehype-image-enlarge.ts +113 -0
- package/templates/base/src/plugins/rehype-mermaid.ts +41 -0
- package/templates/base/src/plugins/rehype-strip-md-extension.ts +58 -0
- package/templates/base/src/plugins/remark-admonitions.ts +99 -0
- package/templates/base/src/plugins/remark-resolve-markdown-links.ts +127 -0
- package/templates/base/src/plugins/url-utils.ts +4 -0
- package/templates/base/src/styles/global.css +1066 -0
- package/templates/base/src/types/docs-entry.ts +39 -0
- package/templates/base/src/types/heading.ts +5 -0
- package/templates/base/src/types/locale.ts +10 -0
- package/templates/base/src/utils/base.ts +139 -0
- package/templates/base/src/utils/content-files.ts +106 -0
- package/templates/base/src/utils/dedent.ts +24 -0
- package/templates/base/src/utils/docs.ts +335 -0
- package/templates/base/src/utils/git-info.ts +70 -0
- package/templates/base/src/utils/github.ts +19 -0
- package/templates/base/src/utils/header-right-items.ts +38 -0
- package/templates/base/src/utils/nav-scope.ts +63 -0
- package/templates/base/src/utils/sidebar.ts +104 -0
- package/templates/base/src/utils/slug.ts +10 -0
- package/templates/base/src/utils/smart-break.tsx +126 -0
- package/templates/base/src/utils/tags.ts +126 -0
- package/templates/base/tsconfig.json +36 -0
- package/templates/features/bodyFootUtil/files/src/utils/github.ts +19 -0
- package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +137 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +34 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +376 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +93 -0
- package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +586 -0
- package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +15 -0
- package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +99 -0
- package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +177 -0
- package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +50 -0
- package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +99 -0
- package/templates/features/docHistory/files/src/components/doc-history.tsx +598 -0
- package/templates/features/docHistory/files/src/types/doc-history.ts +23 -0
- package/templates/features/docHistory/files/src/utils/doc-history.ts +180 -0
- package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +116 -0
- package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +99 -0
- package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +101 -0
- package/templates/features/docTags/files/pages/docs/tags/index.tsx +86 -0
- package/templates/features/i18n/files/pages/[locale]/docs/[...slug].tsx +467 -0
- package/templates/features/i18n/files/pages/[locale]/index.tsx +213 -0
- package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +248 -0
- package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +74 -0
- package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +185 -0
- package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +126 -0
- package/templates/features/tagGovernance/files/scripts/tags-audit.ts +576 -0
- package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +428 -0
- package/templates/features/tauri/files/src/components/find-bar.tsx +122 -0
- package/templates/features/tauri/files/src/components/find-in-page-init.tsx +53 -0
- package/templates/features/tauri/files/src/utils/find-in-page.ts +175 -0
- package/templates/features/tauri/files/src-tauri/Cargo.toml +14 -0
- package/templates/features/tauri/files/src-tauri/build.rs +3 -0
- package/templates/features/tauri/files/src-tauri/capabilities/default.json +11 -0
- package/templates/features/tauri/files/src-tauri/src/main.rs +250 -0
- package/templates/features/tauri/files/src-tauri/tauri.conf.json +25 -0
- package/templates/features/tauriDev/files/src-tauri-dev/Cargo.toml +15 -0
- package/templates/features/tauriDev/files/src-tauri-dev/build.rs +3 -0
- package/templates/features/tauriDev/files/src-tauri-dev/capabilities/default.json +7 -0
- package/templates/features/tauriDev/files/src-tauri-dev/frontend/index.html +187 -0
- package/templates/features/tauriDev/files/src-tauri-dev/icons/icon.png +0 -0
- package/templates/features/tauriDev/files/src-tauri-dev/src/main.rs +995 -0
- package/templates/features/tauriDev/files/src-tauri-dev/tauri.conf.json +22 -0
- package/templates/features/tauriDev/files/src-tauri-dev/test-launch.sh +65 -0
- package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +100 -0
- package/templates/features/versioning/files/pages/docs/versions.tsx +78 -0
- package/templates/features/versioning/files/pages/v/[version]/docs/[...slug].tsx +451 -0
- package/templates/features/versioning/files/pages/v/[version]/ja/docs/[...slug].tsx +490 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Host-side body-end islands helper.
|
|
4
|
+
//
|
|
5
|
+
// Wave 8 (Path A — super-epic #1333 / child epic #1355) drops the local
|
|
6
|
+
// SSR-skip wrapper functions in `@takazudo/zudo-doc/ssr-skip` and uses
|
|
7
|
+
// zfb's native `<Island ssrFallback={...}>` API directly with the real
|
|
8
|
+
// component constructors imported by the host.
|
|
9
|
+
//
|
|
10
|
+
// The previous indirection (page → wrapper → placeholder div) created an
|
|
11
|
+
// orphan-component bug: the real components were `"use client"` modules
|
|
12
|
+
// that no page module ever imported transitively, so zfb's island scanner
|
|
13
|
+
// never walked page → real-component, the manifest never bound the marker
|
|
14
|
+
// to the real constructor, and the bundle never contained the real
|
|
15
|
+
// component body. PR #150 to zfb fixed the marker-name alignment but the
|
|
16
|
+
// orphan problem stayed — see issue zudolab/zudo-doc#1355 Wave 7 report.
|
|
17
|
+
//
|
|
18
|
+
// This helper is the page → real-component import chain the scanner
|
|
19
|
+
// needs. Each island below is composed with zfb's `<Island>` wrapper,
|
|
20
|
+
// which emits `<div data-zfb-island-skip-ssr="<ComponentName>">…</div>`
|
|
21
|
+
// at SSR (zfb's `captureComponentName` derives the marker from
|
|
22
|
+
// `child.displayName ?? child.name`). Because the page imports this
|
|
23
|
+
// file, and this file imports the real components, the scanner walks
|
|
24
|
+
// page → helper → real component and registers the constructor under
|
|
25
|
+
// the SSR marker name.
|
|
26
|
+
//
|
|
27
|
+
// Pattern mirrors `_header-with-defaults.tsx`: the JSX-shim widens
|
|
28
|
+
// `Island`'s return type to `unknown`, so call-sites cast through
|
|
29
|
+
// `as unknown as VNode` at the boundary.
|
|
30
|
+
|
|
31
|
+
import type { VNode, JSX } from "preact";
|
|
32
|
+
import { Island } from "@takazudo/zfb";
|
|
33
|
+
import { settings } from "@/config/settings";
|
|
34
|
+
|
|
35
|
+
import AiChatModal from "@/components/ai-chat-modal";
|
|
36
|
+
import ClientRouterBootstrap from "@/components/client-router-bootstrap";
|
|
37
|
+
import DesignTokenPanelBootstrap from "@/components/design-token-panel-bootstrap";
|
|
38
|
+
import ImageEnlarge, { ImageEnlargeSsrFallback } from "@/components/image-enlarge";
|
|
39
|
+
import { PageLoadingOverlay } from "@takazudo/zudo-doc/page-loading";
|
|
40
|
+
|
|
41
|
+
// Set explicit `displayName` on each default-exported island so zfb's
|
|
42
|
+
// `captureComponentName` produces a stable marker even after the SSR
|
|
43
|
+
// pipeline runs the components through a function-name-rewriting layer.
|
|
44
|
+
// The marker must match the third-arg literal that zfb's scanner records
|
|
45
|
+
// for the same source-level identifier (zfb PR #150). esbuild preserves
|
|
46
|
+
// function names by default, but the explicit assignment is a
|
|
47
|
+
// belt-and-braces guard for production minification regressions.
|
|
48
|
+
(AiChatModal as { displayName?: string }).displayName = "AiChatModal";
|
|
49
|
+
(ClientRouterBootstrap as { displayName?: string }).displayName =
|
|
50
|
+
"ClientRouterBootstrap";
|
|
51
|
+
(DesignTokenPanelBootstrap as { displayName?: string }).displayName =
|
|
52
|
+
"DesignTokenPanelBootstrap";
|
|
53
|
+
(ImageEnlarge as { displayName?: string }).displayName = "ImageEnlarge";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default sr-only label rendered as the AiChatModal SSR fallback. This
|
|
57
|
+
* mirrors the body-label string the deleted `AiChatModalIsland` wrapper
|
|
58
|
+
* produced verbatim so assistive tech can discover the chat entrypoint
|
|
59
|
+
* in the static HTML before JS hydration. English-only for now — the
|
|
60
|
+
* previous default was also English-only; pass `aiChatBodyLabel` to
|
|
61
|
+
* localise.
|
|
62
|
+
*/
|
|
63
|
+
const DEFAULT_AI_CHAT_BODY_LABEL = "Ask a question about the documentation.";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* SSR-emitted inline script that acts as a pre-hydration shim for the
|
|
67
|
+
* `toggle-design-token-panel` window event. Because the
|
|
68
|
+
* DesignTokenPanelBootstrap Island is deferred, zdtp's real
|
|
69
|
+
* `toggle-design-token-panel` listener (registered in index.tsx at
|
|
70
|
+
* module init) is not yet installed when the user clicks the header
|
|
71
|
+
* palette button. This shim:
|
|
72
|
+
*
|
|
73
|
+
* 1. Records the first (and only meaningful) click as a boolean flag.
|
|
74
|
+
* 2. Exposes `window.__zdtpReadyClicks` so the bootstrap Island can
|
|
75
|
+
* drain the queue and re-dispatch a single event once the real
|
|
76
|
+
* listener is live.
|
|
77
|
+
* 3. Guards against double-installation across any re-evaluation path
|
|
78
|
+
* (SPA body swap, HMR, etc.) via `__zdtpToggleShimInstalled`.
|
|
79
|
+
*
|
|
80
|
+
* A single boolean (not an array) is used because the panel is a toggle —
|
|
81
|
+
* any number of pre-hydration clicks should result in at most one open.
|
|
82
|
+
*/
|
|
83
|
+
const ZDTP_TOGGLE_SHIM_SRC = `(function(){
|
|
84
|
+
if(window.__zdtpToggleShimInstalled)return;
|
|
85
|
+
window.__zdtpToggleShimInstalled=true;
|
|
86
|
+
var pending=false;
|
|
87
|
+
function shim(){pending=true;}
|
|
88
|
+
window.addEventListener('toggle-design-token-panel',shim);
|
|
89
|
+
window.__zdtpReadyClicks=function(){
|
|
90
|
+
window.removeEventListener('toggle-design-token-panel',shim);
|
|
91
|
+
delete window.__zdtpReadyClicks;
|
|
92
|
+
if(pending){pending=false;window.dispatchEvent(new CustomEvent('toggle-design-token-panel'));}
|
|
93
|
+
};
|
|
94
|
+
})();`;
|
|
95
|
+
|
|
96
|
+
/** Props for {@link BodyEndIslands}. */
|
|
97
|
+
export interface BodyEndIslandsProps {
|
|
98
|
+
/** Base path the AI chat modal uses to construct API URLs. */
|
|
99
|
+
basePath: string;
|
|
100
|
+
/**
|
|
101
|
+
* Sr-only label rendered as the AiChatModal SSR fallback. Defaults to
|
|
102
|
+
* the English string. Pass a locale-translated string for non-default
|
|
103
|
+
* locales so screen readers announce the chat entrypoint correctly
|
|
104
|
+
* before hydration.
|
|
105
|
+
*/
|
|
106
|
+
aiChatBodyLabel?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The three default body-end islands every doc page mounts: the
|
|
111
|
+
* design-token tweak panel (overlay, fixed-position), the AI chat
|
|
112
|
+
* modal (`<dialog>` overlay), and the image-enlarge dialog (mounted
|
|
113
|
+
* lazily based on viewport scan).
|
|
114
|
+
*
|
|
115
|
+
* Each island is wrapped in `<Island ssrFallback>` so the heavy
|
|
116
|
+
* component is NOT evaluated server-side — they depend on
|
|
117
|
+
* `dialog.showModal()`, `localStorage`, `ResizeObserver`, runtime
|
|
118
|
+
* fetch, etc. The hydration runtime swaps each placeholder on the
|
|
119
|
+
* client.
|
|
120
|
+
*
|
|
121
|
+
* The `<h2 class="sr-only">AI Assistant</h2>` heading is emitted in
|
|
122
|
+
* the SSG output so screen readers and crawlers can discover the chat
|
|
123
|
+
* section landmark before JS hydration.
|
|
124
|
+
*/
|
|
125
|
+
export function BodyEndIslands({
|
|
126
|
+
basePath,
|
|
127
|
+
aiChatBodyLabel = DEFAULT_AI_CHAT_BODY_LABEL,
|
|
128
|
+
}: BodyEndIslandsProps): JSX.Element {
|
|
129
|
+
// Hydrates first (when="load") so the SPA-router click intercept is
|
|
130
|
+
// registered as soon as the islands runtime mounts the marker. The
|
|
131
|
+
// component renders nothing visually — the island bundle's top-level
|
|
132
|
+
// `import "@takazudo/zfb-runtime/client-router"` is what actually
|
|
133
|
+
// wires up the router (zudolab/zudo-doc#1524 W7A fix).
|
|
134
|
+
const clientRouterBootstrap = Island({
|
|
135
|
+
when: "load",
|
|
136
|
+
children: <ClientRouterBootstrap />,
|
|
137
|
+
}) as unknown as VNode;
|
|
138
|
+
|
|
139
|
+
// Hydrates on load so configurePanel() runs as early as possible and
|
|
140
|
+
// the `toggle-design-token-panel` window listener is registered before
|
|
141
|
+
// the user can click the header trigger. Renders nothing visually —
|
|
142
|
+
// the zdtp panel self-mounts as a side-effect (zudolab/zudo-doc#1623).
|
|
143
|
+
//
|
|
144
|
+
// The inline <script> emitted alongside the Island is the pre-hydration
|
|
145
|
+
// toggle shim (zudolab/zudo-doc#1627 Part B). It captures the first
|
|
146
|
+
// click as a boolean flag and exposes window.__zdtpReadyClicks so the
|
|
147
|
+
// bootstrap module can drain and re-dispatch once the real zdtp listener
|
|
148
|
+
// is registered. Mirrors the PageLoadingOverlay SSR-script pattern.
|
|
149
|
+
const designTokenPanelBootstrap =
|
|
150
|
+
settings.designTokenPanel || settings.colorTweakPanel
|
|
151
|
+
? (
|
|
152
|
+
<>
|
|
153
|
+
<script
|
|
154
|
+
dangerouslySetInnerHTML={{ __html: ZDTP_TOGGLE_SHIM_SRC }}
|
|
155
|
+
/>
|
|
156
|
+
{Island({
|
|
157
|
+
when: "load",
|
|
158
|
+
children: <DesignTokenPanelBootstrap />,
|
|
159
|
+
}) as unknown as VNode}
|
|
160
|
+
</>
|
|
161
|
+
)
|
|
162
|
+
: null;
|
|
163
|
+
|
|
164
|
+
// Use a visually-hidden paragraph as the AiChatModal SSR fallback so
|
|
165
|
+
// the body label is present in static HTML for screen readers before
|
|
166
|
+
// JS hydration. sr-only keeps it invisible to sighted users.
|
|
167
|
+
const aiChat = Island({
|
|
168
|
+
ssrFallback: <p class="sr-only">{aiChatBodyLabel}</p>,
|
|
169
|
+
children: <AiChatModal basePath={basePath} />,
|
|
170
|
+
}) as unknown as VNode;
|
|
171
|
+
|
|
172
|
+
// Wave 11 (zudolab/zudo-doc#1355): the SSR fallback is the empty,
|
|
173
|
+
// closed `<dialog class="zd-enlarge-dialog ...">` shell so the dist
|
|
174
|
+
// HTML carries one dialog from the start. Without this the smoke
|
|
175
|
+
// "exactly one zd-enlarge-dialog element" assertion sees zero
|
|
176
|
+
// (skip-ssr placeholders are empty divs) and the no-JS path has no
|
|
177
|
+
// dialog at all. Hydration replaces this shell with the real
|
|
178
|
+
// ImageEnlarge component when the page goes idle.
|
|
179
|
+
const imageEnlarge = Island({
|
|
180
|
+
when: "idle",
|
|
181
|
+
ssrFallback: <ImageEnlargeSsrFallback />,
|
|
182
|
+
children: <ImageEnlarge />,
|
|
183
|
+
}) as unknown as VNode;
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<>
|
|
187
|
+
{/* Pure SSR — no Island wrap. The component emits its overlay div,
|
|
188
|
+
inline styles, and a small inline script that self-wires
|
|
189
|
+
zfb:before-preparation / zfb:after-swap listeners at runtime. */}
|
|
190
|
+
<PageLoadingOverlay />
|
|
191
|
+
{clientRouterBootstrap}
|
|
192
|
+
{designTokenPanelBootstrap}
|
|
193
|
+
{/* Emits the "AI Assistant" heading in the SSG output so screen
|
|
194
|
+
readers can discover the chat section landmark before JS
|
|
195
|
+
hydration. */}
|
|
196
|
+
<h2 class="sr-only">AI Assistant</h2>
|
|
197
|
+
{aiChat}
|
|
198
|
+
{imageEnlarge}
|
|
199
|
+
</>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Host-side MDX wrapper for <CategoryNav category="..." />.
|
|
4
|
+
//
|
|
5
|
+
// Mirrors the data-resolution shape of src/components/category-nav.astro:
|
|
6
|
+
// 1. Load docs for the active locale (or defaultLocale when not passed).
|
|
7
|
+
// 2. Build the nav tree with buildNavTree().
|
|
8
|
+
// 3. Find the target category node via findNode().
|
|
9
|
+
// 4. Filter to children with hasPage === true.
|
|
10
|
+
// 5. Forward the resolved children to the v2 CategoryNav component.
|
|
11
|
+
//
|
|
12
|
+
// All data access is synchronous (ADR-004 zfb content snapshot contract)
|
|
13
|
+
// via loadDocs() from pages/_data.ts.
|
|
14
|
+
//
|
|
15
|
+
// The `lang` prop is injected by createMdxComponents() in
|
|
16
|
+
// pages/_mdx-components.ts so locale routes get locale-aware nav data.
|
|
17
|
+
|
|
18
|
+
import type { JSX } from "preact";
|
|
19
|
+
import { CategoryNav as CategoryNavV2 } from "@takazudo/zudo-doc/nav-indexing";
|
|
20
|
+
import type { NavNode as V2NavNode } from "@takazudo/zudo-doc/nav-indexing/types";
|
|
21
|
+
import {
|
|
22
|
+
buildNavTree,
|
|
23
|
+
findNode,
|
|
24
|
+
loadCategoryMeta,
|
|
25
|
+
isNavVisible,
|
|
26
|
+
} from "@/utils/docs";
|
|
27
|
+
import { settings } from "@/config/settings";
|
|
28
|
+
import { defaultLocale, type Locale } from "@/config/i18n";
|
|
29
|
+
import { docsUrl } from "@/utils/base";
|
|
30
|
+
import { loadDocs } from "../_data";
|
|
31
|
+
|
|
32
|
+
export interface CategoryNavWrapperProps {
|
|
33
|
+
/**
|
|
34
|
+
* Slug of the category whose immediate children should be listed, e.g.
|
|
35
|
+
* "getting-started" or "guides/layout-demos".
|
|
36
|
+
*/
|
|
37
|
+
category?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Explicit list of top-level category slugs to render as cards. Use this
|
|
40
|
+
* when the target categories are not children of a single parent node in
|
|
41
|
+
* the nav tree (e.g. "claude-md", "claude-skills" are top-level siblings
|
|
42
|
+
* of "claude", not children). Each slug is resolved to its nav node; nodes
|
|
43
|
+
* not found in the tree are silently skipped.
|
|
44
|
+
*
|
|
45
|
+
* For nodes with noPage=true (no index.mdx), the href falls back to the
|
|
46
|
+
* auto-generated category index URL via docsUrl(slug, lang).
|
|
47
|
+
*/
|
|
48
|
+
categories?: string[];
|
|
49
|
+
/**
|
|
50
|
+
* Active locale. Injected via createMdxComponents() closure.
|
|
51
|
+
* Defaults to defaultLocale when not provided.
|
|
52
|
+
*/
|
|
53
|
+
lang?: Locale | string;
|
|
54
|
+
/** Optional extra CSS classes forwarded to the <nav> element. */
|
|
55
|
+
class?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load merged docs + categoryMeta for the given locale.
|
|
60
|
+
* Mirrors the locale-merge strategy from _header-with-defaults.tsx:
|
|
61
|
+
* default locale → "docs"; non-default → locale-first + EN fallback.
|
|
62
|
+
*/
|
|
63
|
+
function loadNavSource(
|
|
64
|
+
locale: string,
|
|
65
|
+
): { docs: ReturnType<typeof loadDocs>; categoryMeta: Map<string, import("@/utils/docs").CategoryMeta> } {
|
|
66
|
+
if (locale === defaultLocale) {
|
|
67
|
+
return {
|
|
68
|
+
docs: loadDocs("docs").filter((d) => !d.data.draft),
|
|
69
|
+
categoryMeta: loadCategoryMeta(settings.docsDir),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Non-default locale: locale-first merge with EN fallback.
|
|
74
|
+
const localeDocs = loadDocs(`docs-${locale}`).filter((d) => !d.data.draft);
|
|
75
|
+
const baseDocs = loadDocs("docs").filter((d) => !d.data.draft);
|
|
76
|
+
const localeSlugSet = new Set(localeDocs.map((d) => d.data.slug ?? d.id));
|
|
77
|
+
const fallbackDocs = baseDocs.filter(
|
|
78
|
+
(d) => !localeSlugSet.has(d.data.slug ?? d.id),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const localeDir =
|
|
82
|
+
(settings.locales as Record<string, { dir?: string }>)[locale]?.dir ??
|
|
83
|
+
settings.docsDir;
|
|
84
|
+
const categoryMeta = new Map([
|
|
85
|
+
...loadCategoryMeta(settings.docsDir),
|
|
86
|
+
...loadCategoryMeta(localeDir),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
return { docs: [...localeDocs, ...fallbackDocs], categoryMeta };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* MDX wrapper for CategoryNav. Resolves nav tree data host-side and forwards
|
|
94
|
+
* the resolved category children into the v2 CategoryNav component.
|
|
95
|
+
*
|
|
96
|
+
* Supports two modes:
|
|
97
|
+
* - `category`: resolves immediate children of a single category node.
|
|
98
|
+
* - `categories`: resolves an explicit list of top-level slugs as cards.
|
|
99
|
+
* Use this when the target categories are siblings in the nav tree rather
|
|
100
|
+
* than children of a common parent (e.g. claude-md / claude-skills are
|
|
101
|
+
* top-level peers of claude, not children of it). Nodes with noPage=true
|
|
102
|
+
* get their href computed via docsUrl() since auto-index pages exist.
|
|
103
|
+
*
|
|
104
|
+
* Returns null when no visible children are resolved.
|
|
105
|
+
*/
|
|
106
|
+
export function CategoryNavWrapper({
|
|
107
|
+
category,
|
|
108
|
+
categories,
|
|
109
|
+
lang = defaultLocale,
|
|
110
|
+
class: className,
|
|
111
|
+
}: CategoryNavWrapperProps): JSX.Element | null {
|
|
112
|
+
const locale = lang as Locale;
|
|
113
|
+
|
|
114
|
+
const { docs, categoryMeta } = loadNavSource(locale);
|
|
115
|
+
const navDocs = docs.filter(isNavVisible);
|
|
116
|
+
const tree = buildNavTree(navDocs, locale, categoryMeta);
|
|
117
|
+
|
|
118
|
+
let children: V2NavNode[];
|
|
119
|
+
|
|
120
|
+
if (categories !== undefined) {
|
|
121
|
+
// Explicit slug list mode: resolve each slug to its nav node and build
|
|
122
|
+
// a card for it. noPage nodes have no href in the tree but their
|
|
123
|
+
// auto-generated category index page is reachable via docsUrl().
|
|
124
|
+
children = categories
|
|
125
|
+
.map((slug): V2NavNode | null => {
|
|
126
|
+
const node = findNode(tree, slug);
|
|
127
|
+
if (!node) return null;
|
|
128
|
+
const href = node.href ?? docsUrl(slug, locale);
|
|
129
|
+
return {
|
|
130
|
+
label: node.label,
|
|
131
|
+
description: node.description,
|
|
132
|
+
href,
|
|
133
|
+
hasPage: true,
|
|
134
|
+
children: [],
|
|
135
|
+
};
|
|
136
|
+
})
|
|
137
|
+
.filter((n): n is V2NavNode => n !== null);
|
|
138
|
+
} else if (category !== undefined) {
|
|
139
|
+
const categoryNode = findNode(tree, category);
|
|
140
|
+
children = (categoryNode?.children.filter((c) => c.hasPage) ?? []) as V2NavNode[];
|
|
141
|
+
} else {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (children.length === 0) return null;
|
|
146
|
+
|
|
147
|
+
return <CategoryNavV2 children={children} class={className} />;
|
|
148
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Host-side MDX wrapper for <CategoryTreeNav category="..." />.
|
|
4
|
+
//
|
|
5
|
+
// Mirrors the data-resolution shape of src/components/category-tree-nav.astro:
|
|
6
|
+
// 1. Load docs for the active locale (defaultLocale when not passed).
|
|
7
|
+
// 2. Build the full nav tree with buildNavTree() + groupSatelliteNodes()
|
|
8
|
+
// (category slug is passed as the grouping prefix list).
|
|
9
|
+
// 3. Find the target category node via findNode().
|
|
10
|
+
// 4. Filter to children with hasPage === true or children.length > 0.
|
|
11
|
+
// 5. Forward the resolved children to the v2 CategoryTreeNav component.
|
|
12
|
+
//
|
|
13
|
+
// All data access is synchronous (ADR-004 zfb content snapshot contract).
|
|
14
|
+
// The `lang` prop is injected by createMdxComponents() in
|
|
15
|
+
// pages/_mdx-components.ts so locale routes get locale-aware nav data.
|
|
16
|
+
|
|
17
|
+
import type { JSX } from "preact";
|
|
18
|
+
import { CategoryTreeNav as CategoryTreeNavV2 } from "@takazudo/zudo-doc/nav-indexing";
|
|
19
|
+
import {
|
|
20
|
+
buildNavTree,
|
|
21
|
+
groupSatelliteNodes,
|
|
22
|
+
findNode,
|
|
23
|
+
loadCategoryMeta,
|
|
24
|
+
isNavVisible,
|
|
25
|
+
} from "@/utils/docs";
|
|
26
|
+
import { settings } from "@/config/settings";
|
|
27
|
+
import { defaultLocale, type Locale } from "@/config/i18n";
|
|
28
|
+
import { loadDocs } from "../_data";
|
|
29
|
+
|
|
30
|
+
export interface CategoryTreeNavWrapperProps {
|
|
31
|
+
/**
|
|
32
|
+
* Slug of the category whose children should be rendered as a tree,
|
|
33
|
+
* e.g. "guides" or "getting-started".
|
|
34
|
+
*/
|
|
35
|
+
category: string;
|
|
36
|
+
/**
|
|
37
|
+
* Active locale. Injected via createMdxComponents() closure.
|
|
38
|
+
* Defaults to defaultLocale when not provided.
|
|
39
|
+
*/
|
|
40
|
+
lang?: Locale | string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load merged docs + categoryMeta for the given locale.
|
|
45
|
+
* Matches the locale-merge strategy used by _category-nav.tsx.
|
|
46
|
+
*/
|
|
47
|
+
function loadNavSource(
|
|
48
|
+
locale: string,
|
|
49
|
+
): { docs: ReturnType<typeof loadDocs>; categoryMeta: Map<string, import("@/utils/docs").CategoryMeta> } {
|
|
50
|
+
if (locale === defaultLocale) {
|
|
51
|
+
return {
|
|
52
|
+
docs: loadDocs("docs").filter((d) => !d.data.draft),
|
|
53
|
+
categoryMeta: loadCategoryMeta(settings.docsDir),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const localeDocs = loadDocs(`docs-${locale}`).filter((d) => !d.data.draft);
|
|
58
|
+
const baseDocs = loadDocs("docs").filter((d) => !d.data.draft);
|
|
59
|
+
const localeSlugSet = new Set(localeDocs.map((d) => d.data.slug ?? d.id));
|
|
60
|
+
const fallbackDocs = baseDocs.filter(
|
|
61
|
+
(d) => !localeSlugSet.has(d.data.slug ?? d.id),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const localeDir =
|
|
65
|
+
(settings.locales as Record<string, { dir?: string }>)[locale]?.dir ??
|
|
66
|
+
settings.docsDir;
|
|
67
|
+
const categoryMeta = new Map([
|
|
68
|
+
...loadCategoryMeta(settings.docsDir),
|
|
69
|
+
...loadCategoryMeta(localeDir),
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
return { docs: [...localeDocs, ...fallbackDocs], categoryMeta };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* MDX wrapper for CategoryTreeNav. Resolves nav tree data host-side and
|
|
77
|
+
* forwards the resolved category children into the v2 CategoryTreeNav
|
|
78
|
+
* component.
|
|
79
|
+
*
|
|
80
|
+
* Returns null when the category is not found or has no renderable children —
|
|
81
|
+
* matching the original Astro component's guard.
|
|
82
|
+
*/
|
|
83
|
+
export function CategoryTreeNavWrapper({
|
|
84
|
+
category,
|
|
85
|
+
lang = defaultLocale,
|
|
86
|
+
}: CategoryTreeNavWrapperProps): JSX.Element | null {
|
|
87
|
+
const locale = lang as Locale;
|
|
88
|
+
|
|
89
|
+
const { docs, categoryMeta } = loadNavSource(locale);
|
|
90
|
+
const navDocs = docs.filter(isNavVisible);
|
|
91
|
+
const rawTree = buildNavTree(navDocs, locale, categoryMeta);
|
|
92
|
+
// groupSatelliteNodes with [category] groups satellite nodes under the
|
|
93
|
+
// target category — matching the original Astro component.
|
|
94
|
+
const tree = groupSatelliteNodes(rawTree, [category]);
|
|
95
|
+
|
|
96
|
+
const categoryNode = findNode(tree, category);
|
|
97
|
+
const children =
|
|
98
|
+
categoryNode?.children.filter((c) => c.hasPage || c.children.length > 0) ??
|
|
99
|
+
[];
|
|
100
|
+
|
|
101
|
+
if (children.length === 0) return null;
|
|
102
|
+
|
|
103
|
+
return <CategoryTreeNavV2 children={children} />;
|
|
104
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose the canonical "<title> | <siteName>" page-title shape used by
|
|
3
|
+
* both <title> (emitted by DocLayout) and og:title (emitted by
|
|
4
|
+
* HeadWithDefaults).
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the legacy Astro layout synthesised this suffix
|
|
7
|
+
* inline. The zfb DocLayout shell intentionally renders only what the
|
|
8
|
+
* host passes as `title`, so the host has to compose the suffix itself.
|
|
9
|
+
* Centralising the composition in one helper keeps every host call
|
|
10
|
+
* site in sync and matches the SEO/UX-recognised shape the original
|
|
11
|
+
* site shipped (also asserted by smoke-seo.spec.ts).
|
|
12
|
+
*
|
|
13
|
+
* Edge cases:
|
|
14
|
+
* - When `title` is identical to `settings.siteName` (e.g. the home
|
|
15
|
+
* page already passes `settings.siteName` as the title), do NOT
|
|
16
|
+
* duplicate — return just the bare site name. Mirrors the legacy
|
|
17
|
+
* Astro behaviour.
|
|
18
|
+
* - When `siteName` is missing/empty (defensive — settings.ts always
|
|
19
|
+
* has it in practice), fall back to the bare title.
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
import { settings } from "@/config/settings";
|
|
23
|
+
|
|
24
|
+
export function composeMetaTitle(title: string): string {
|
|
25
|
+
const siteName = settings.siteName;
|
|
26
|
+
if (!siteName) return title;
|
|
27
|
+
if (title === siteName) return siteName;
|
|
28
|
+
return `${title} | ${siteName}`;
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** @jsxRuntime automatic */
|
|
2
|
+
/** @jsxImportSource preact */
|
|
3
|
+
// Host-side MDX wrapper for <Details> — trivial passthrough to the v2
|
|
4
|
+
// Details component.
|
|
5
|
+
//
|
|
6
|
+
// The legacy src/components/details.astro used Astro's <slot /> for children;
|
|
7
|
+
// v2's Details accepts standard Preact children. MDX passes slot content as
|
|
8
|
+
// `children`, so the mapping is direct. The title prop (default "Details")
|
|
9
|
+
// is forwarded unchanged.
|
|
10
|
+
|
|
11
|
+
import type { ComponentChildren, VNode } from "preact";
|
|
12
|
+
import { Details as DetailsV2 } from "@takazudo/zudo-doc/details";
|
|
13
|
+
|
|
14
|
+
export interface DetailsWrapperProps {
|
|
15
|
+
/** Summary label shown in the <summary> element. Defaults to "Details". */
|
|
16
|
+
title?: string;
|
|
17
|
+
/** MDX slot content rendered inside the collapsed body. */
|
|
18
|
+
children?: ComponentChildren;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Passthrough wrapper for the v2 Details component.
|
|
23
|
+
*
|
|
24
|
+
* Used in pages/_mdx-components.ts as the Details binding so that MDX
|
|
25
|
+
* content using `<Details title="...">...</Details>` renders correctly
|
|
26
|
+
* on zfb routes.
|
|
27
|
+
*/
|
|
28
|
+
export function DetailsWrapper({ title, children }: DetailsWrapperProps): VNode {
|
|
29
|
+
return <DetailsV2 title={title}>{children}</DetailsV2>;
|
|
30
|
+
}
|