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,108 @@
|
|
|
1
|
+
import { type VNode, useMemo } from "preact/compat";
|
|
2
|
+
import PreviewBase from "./preview-base";
|
|
3
|
+
import { dedent } from "@/utils/dedent";
|
|
4
|
+
import { preflightCss } from "./preflight";
|
|
5
|
+
|
|
6
|
+
interface HtmlPreviewProps {
|
|
7
|
+
html: string;
|
|
8
|
+
css?: string;
|
|
9
|
+
head?: string;
|
|
10
|
+
js?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
height?: number;
|
|
13
|
+
defaultOpen?: boolean;
|
|
14
|
+
/** Per-component css for code block display (before global merge) */
|
|
15
|
+
componentCss?: string;
|
|
16
|
+
/** Per-component head for code block display (before global merge) */
|
|
17
|
+
componentHead?: string;
|
|
18
|
+
/** Per-component js for code block display (before global merge) */
|
|
19
|
+
componentJs?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function containsScript(head?: string, js?: string): boolean {
|
|
23
|
+
if (js) return true;
|
|
24
|
+
if (head && /<script/i.test(head)) return true;
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildSrcdoc(
|
|
29
|
+
html: string,
|
|
30
|
+
css?: string,
|
|
31
|
+
head?: string,
|
|
32
|
+
js?: string,
|
|
33
|
+
): string {
|
|
34
|
+
return `<!doctype html>
|
|
35
|
+
<html>
|
|
36
|
+
<head>
|
|
37
|
+
<meta charset="utf-8">
|
|
38
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
39
|
+
<style>${preflightCss}</style>
|
|
40
|
+
${head ?? ""}
|
|
41
|
+
${css ? `<style>${css}</style>` : ""}
|
|
42
|
+
</head>
|
|
43
|
+
<body>${html}
|
|
44
|
+
${js ? `<script>${js}</script>` : ""}
|
|
45
|
+
</body>
|
|
46
|
+
</html>`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default function HtmlPreview({
|
|
50
|
+
html,
|
|
51
|
+
css,
|
|
52
|
+
head,
|
|
53
|
+
js,
|
|
54
|
+
title,
|
|
55
|
+
height,
|
|
56
|
+
defaultOpen,
|
|
57
|
+
componentCss,
|
|
58
|
+
componentHead,
|
|
59
|
+
componentJs,
|
|
60
|
+
}: HtmlPreviewProps): VNode {
|
|
61
|
+
const srcdoc = useMemo(
|
|
62
|
+
() => buildSrcdoc(html, css, head, js),
|
|
63
|
+
[html, css, head, js],
|
|
64
|
+
);
|
|
65
|
+
const hasScripts = containsScript(head, js);
|
|
66
|
+
const syncDelay = hasScripts ? 300 : 0;
|
|
67
|
+
// allow-same-origin is always required so that syncHeight can access
|
|
68
|
+
// iframe.contentDocument for auto-height measurement; sandbox="" without
|
|
69
|
+
// allow-same-origin gives the srcdoc iframe an opaque origin and blocks
|
|
70
|
+
// contentDocument reads even from the parent page.
|
|
71
|
+
const sandboxValue = hasScripts
|
|
72
|
+
? "allow-scripts allow-same-origin"
|
|
73
|
+
: "allow-same-origin";
|
|
74
|
+
|
|
75
|
+
const codeBlocks = useMemo(
|
|
76
|
+
() => [
|
|
77
|
+
{ language: "html", title: "HTML", code: dedent(html) },
|
|
78
|
+
...(componentCss
|
|
79
|
+
? [{ language: "css", title: "CSS", code: dedent(componentCss) }]
|
|
80
|
+
: []),
|
|
81
|
+
...(componentHead
|
|
82
|
+
? [{ language: "html", title: "Head", code: dedent(componentHead) }]
|
|
83
|
+
: []),
|
|
84
|
+
...(componentJs
|
|
85
|
+
? [
|
|
86
|
+
{
|
|
87
|
+
language: "javascript",
|
|
88
|
+
title: "JS",
|
|
89
|
+
code: dedent(componentJs),
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
: []),
|
|
93
|
+
],
|
|
94
|
+
[html, componentCss, componentHead, componentJs],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<PreviewBase
|
|
99
|
+
title={title}
|
|
100
|
+
height={height}
|
|
101
|
+
srcdoc={srcdoc}
|
|
102
|
+
defaultOpen={defaultOpen}
|
|
103
|
+
sandbox={sandboxValue}
|
|
104
|
+
syncDelay={syncDelay}
|
|
105
|
+
codeBlocks={codeBlocks}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind CSS v4 preflight (modern-normalize/reset).
|
|
3
|
+
* Source: https://unpkg.com/tailwindcss@4/preflight.css
|
|
4
|
+
*
|
|
5
|
+
* The --theme() references are replaced with standard fallback values
|
|
6
|
+
* so the reset works standalone inside an iframe.
|
|
7
|
+
*/
|
|
8
|
+
export const preflightCss = `
|
|
9
|
+
*,
|
|
10
|
+
::after,
|
|
11
|
+
::before,
|
|
12
|
+
::backdrop,
|
|
13
|
+
::file-selector-button {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
margin: 0;
|
|
16
|
+
padding: 0;
|
|
17
|
+
border: 0 solid;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
html,
|
|
21
|
+
:host {
|
|
22
|
+
line-height: 1.5;
|
|
23
|
+
-webkit-text-size-adjust: 100%;
|
|
24
|
+
tab-size: 4;
|
|
25
|
+
font-family: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
|
26
|
+
-webkit-tap-highlight-color: transparent;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
hr {
|
|
30
|
+
height: 0;
|
|
31
|
+
color: inherit;
|
|
32
|
+
border-top-width: 1px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
abbr:where([title]) {
|
|
36
|
+
-webkit-text-decoration: underline dotted;
|
|
37
|
+
text-decoration: underline dotted;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
h1, h2, h3, h4, h5, h6 {
|
|
41
|
+
font-size: inherit;
|
|
42
|
+
font-weight: inherit;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
a {
|
|
46
|
+
color: inherit;
|
|
47
|
+
-webkit-text-decoration: inherit;
|
|
48
|
+
text-decoration: inherit;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
b, strong {
|
|
52
|
+
font-weight: bolder;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
code, kbd, samp, pre {
|
|
56
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
57
|
+
font-size: 1em;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
small {
|
|
61
|
+
font-size: 80%;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
sub, sup {
|
|
65
|
+
font-size: 75%;
|
|
66
|
+
line-height: 0;
|
|
67
|
+
position: relative;
|
|
68
|
+
vertical-align: baseline;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
sub { bottom: -0.25em; }
|
|
72
|
+
sup { top: -0.5em; }
|
|
73
|
+
|
|
74
|
+
table {
|
|
75
|
+
text-indent: 0;
|
|
76
|
+
border-color: inherit;
|
|
77
|
+
border-collapse: collapse;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ol, ul, menu {
|
|
81
|
+
list-style: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
img, svg, video, canvas, audio, iframe, embed, object {
|
|
85
|
+
display: block;
|
|
86
|
+
vertical-align: middle;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
img, video {
|
|
90
|
+
max-width: 100%;
|
|
91
|
+
height: auto;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
button, input, select, optgroup, textarea, ::file-selector-button {
|
|
95
|
+
font: inherit;
|
|
96
|
+
font-feature-settings: inherit;
|
|
97
|
+
font-variation-settings: inherit;
|
|
98
|
+
letter-spacing: inherit;
|
|
99
|
+
color: inherit;
|
|
100
|
+
border-radius: 0;
|
|
101
|
+
background-color: transparent;
|
|
102
|
+
opacity: 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
textarea {
|
|
106
|
+
resize: vertical;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
[hidden]:where(:not([hidden='until-found'])) {
|
|
110
|
+
display: none !important;
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { type VNode, useCallback, useEffect, useRef, useState } from "preact/compat";
|
|
2
|
+
import HighlightedCode from "./highlighted-code";
|
|
3
|
+
|
|
4
|
+
export interface CodeBlockData {
|
|
5
|
+
language: string;
|
|
6
|
+
title: string;
|
|
7
|
+
code: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PreviewBaseProps {
|
|
11
|
+
title?: string;
|
|
12
|
+
height?: number;
|
|
13
|
+
srcdoc: string;
|
|
14
|
+
sandbox?: string;
|
|
15
|
+
syncDelay: number;
|
|
16
|
+
codeBlocks: CodeBlockData[];
|
|
17
|
+
defaultOpen?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Viewport = { label: string; width: string };
|
|
21
|
+
|
|
22
|
+
const VIEWPORTS: Viewport[] = [
|
|
23
|
+
{ label: "Mobile", width: "320px" },
|
|
24
|
+
{ label: "Tablet", width: "768px" },
|
|
25
|
+
{ label: "Full", width: "100%" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export default function PreviewBase({
|
|
29
|
+
title,
|
|
30
|
+
height,
|
|
31
|
+
srcdoc,
|
|
32
|
+
sandbox,
|
|
33
|
+
syncDelay,
|
|
34
|
+
codeBlocks,
|
|
35
|
+
defaultOpen,
|
|
36
|
+
}: PreviewBaseProps): VNode {
|
|
37
|
+
const [activeViewport, setActiveViewport] = useState(2); // default: Full
|
|
38
|
+
const [codeOpen, setCodeOpen] = useState(defaultOpen ?? false);
|
|
39
|
+
const [iframeHeight, setIframeHeight] = useState(height ?? 200);
|
|
40
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
41
|
+
|
|
42
|
+
const syncHeight = useCallback(() => {
|
|
43
|
+
const iframe = iframeRef.current;
|
|
44
|
+
if (!iframe || height != null) return;
|
|
45
|
+
try {
|
|
46
|
+
const doc = iframe.contentDocument;
|
|
47
|
+
if (doc?.body) {
|
|
48
|
+
const h = doc.body.scrollHeight;
|
|
49
|
+
if (h > 0) setIframeHeight(Math.max(h + 16, 200));
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// cross-origin or not yet loaded — ignore
|
|
53
|
+
}
|
|
54
|
+
}, [height]);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const iframe = iframeRef.current;
|
|
58
|
+
if (!iframe) return;
|
|
59
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
60
|
+
const onLoad = () => {
|
|
61
|
+
if (syncDelay > 0) {
|
|
62
|
+
timeoutId = setTimeout(syncHeight, syncDelay);
|
|
63
|
+
} else {
|
|
64
|
+
syncHeight();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
iframe.addEventListener("load", onLoad);
|
|
68
|
+
return () => {
|
|
69
|
+
iframe.removeEventListener("load", onLoad);
|
|
70
|
+
clearTimeout(timeoutId);
|
|
71
|
+
};
|
|
72
|
+
}, [syncHeight, srcdoc, syncDelay]);
|
|
73
|
+
|
|
74
|
+
// Re-measure height when viewport changes (content reflows)
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (height != null) return;
|
|
77
|
+
const id = setTimeout(syncHeight, 150);
|
|
78
|
+
return () => clearTimeout(id);
|
|
79
|
+
}, [activeViewport, syncHeight, height]);
|
|
80
|
+
|
|
81
|
+
const containerWidth = VIEWPORTS[activeViewport].width;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className="border border-muted rounded-lg overflow-hidden my-vsp-md">
|
|
85
|
+
{/* Title bar with viewport buttons */}
|
|
86
|
+
<div className="flex items-center justify-between px-hsp-md py-hsp-sm bg-surface border-b border-muted gap-hsp-sm flex-wrap">
|
|
87
|
+
{title && <span className="text-caption font-semibold text-fg">{title}</span>}
|
|
88
|
+
<div className="flex gap-hsp-2xs" role="group" aria-label="Viewport size">
|
|
89
|
+
{VIEWPORTS.map((vp, i) => (
|
|
90
|
+
<button
|
|
91
|
+
key={vp.label}
|
|
92
|
+
type="button"
|
|
93
|
+
className={`px-hsp-sm py-hsp-2xs text-caption border rounded-full cursor-pointer transition-[background,color,border-color] duration-150 leading-snug ${
|
|
94
|
+
i === activeViewport
|
|
95
|
+
? "bg-accent text-bg border-accent hover:bg-accent-hover hover:border-accent-hover"
|
|
96
|
+
: "bg-transparent text-muted border-muted hover:bg-[color-mix(in_srgb,var(--color-surface)_80%,var(--color-fg)_20%)]"
|
|
97
|
+
}`}
|
|
98
|
+
aria-pressed={i === activeViewport}
|
|
99
|
+
onClick={() => setActiveViewport(i)}
|
|
100
|
+
>
|
|
101
|
+
{vp.label}
|
|
102
|
+
</button>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Preview area */}
|
|
108
|
+
<div className="bg-surface p-hsp-lg">
|
|
109
|
+
<div
|
|
110
|
+
className="resize-x overflow-auto max-w-full mx-auto"
|
|
111
|
+
style={{ width: containerWidth }}
|
|
112
|
+
>
|
|
113
|
+
{/* Intentional: white canvas regardless of site theme — matches standard browser context */}
|
|
114
|
+
<iframe
|
|
115
|
+
ref={iframeRef}
|
|
116
|
+
className="block w-full border-none bg-[#fff] rounded shadow-[0_1px_3px_color-mix(in_srgb,var(--color-fg)_8%,transparent)]"
|
|
117
|
+
srcDoc={srcdoc}
|
|
118
|
+
sandbox={sandbox}
|
|
119
|
+
style={{ height: iframeHeight }}
|
|
120
|
+
title={title ?? "Preview"}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Code section */}
|
|
126
|
+
<div className="border-t border-muted">
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
className="flex items-center w-full px-hsp-md py-hsp-sm text-caption font-medium text-muted bg-surface border-none cursor-pointer gap-hsp-xs hover:bg-[color-mix(in_srgb,var(--color-surface)_80%,var(--color-fg)_20%)]"
|
|
130
|
+
onClick={() => setCodeOpen((v) => !v)}
|
|
131
|
+
aria-expanded={codeOpen}
|
|
132
|
+
>
|
|
133
|
+
<span
|
|
134
|
+
className={`text-caption transition-transform duration-200 ${codeOpen ? "rotate-90" : ""}`}
|
|
135
|
+
aria-hidden="true"
|
|
136
|
+
>
|
|
137
|
+
▶
|
|
138
|
+
</span>
|
|
139
|
+
{codeOpen ? "Hide code" : "Show code"}
|
|
140
|
+
</button>
|
|
141
|
+
{codeOpen && (
|
|
142
|
+
<div>
|
|
143
|
+
{codeBlocks.map((block, idx) => (
|
|
144
|
+
<div key={block.title} className={`overflow-x-auto ${idx > 0 ? "border-t border-muted" : ""}`}>
|
|
145
|
+
<span className="block px-hsp-md py-hsp-xs text-caption font-semibold text-muted bg-surface border-b border-muted uppercase tracking-wider">
|
|
146
|
+
{block.title}
|
|
147
|
+
</span>
|
|
148
|
+
<HighlightedCode
|
|
149
|
+
code={block.code}
|
|
150
|
+
language={block.language}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
))}
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// W6A stub — no-op default + ImageEnlargeSsrFallback named exports.
|
|
2
|
+
//
|
|
3
|
+
// When the imageEnlarge feature is enabled, the feature template
|
|
4
|
+
// overwrites this file with the real island and SSR fallback. Generated
|
|
5
|
+
// projects without the feature ship the no-op so the unconditional
|
|
6
|
+
// `pages/lib/_body-end-islands` import resolves (the body-end islands
|
|
7
|
+
// renderer references both the default and the SSR fallback).
|
|
8
|
+
import type { JSX } from "preact";
|
|
9
|
+
|
|
10
|
+
function ImageEnlarge(): JSX.Element | null {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
ImageEnlarge.displayName = "ImageEnlarge";
|
|
14
|
+
|
|
15
|
+
export default ImageEnlarge;
|
|
16
|
+
|
|
17
|
+
export function ImageEnlargeSsrFallback(): JSX.Element | null {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo } from "preact/hooks";
|
|
4
|
+
import type { Heading } from "@/types/heading";
|
|
5
|
+
import { SmartBreak } from "@/utils/smart-break";
|
|
6
|
+
import clsx from "clsx";
|
|
7
|
+
|
|
8
|
+
interface MobileTocProps {
|
|
9
|
+
headings: Heading[];
|
|
10
|
+
title?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function MobileToc({ headings, title = "On this page" }: MobileTocProps) {
|
|
14
|
+
const filtered = useMemo(
|
|
15
|
+
() => headings.filter((h) => h.depth >= 2 && h.depth <= 4),
|
|
16
|
+
[headings],
|
|
17
|
+
);
|
|
18
|
+
const [open, setOpen] = useState(false);
|
|
19
|
+
|
|
20
|
+
// No qualifying headings: emit a CSS-hidden container that still
|
|
21
|
+
// carries the locale title text in the static markup so no-JS users
|
|
22
|
+
// and crawlers still see it ("On this page" / "目次"). aria-hidden
|
|
23
|
+
// prevents screen readers from announcing the invisible label.
|
|
24
|
+
if (filtered.length === 0) {
|
|
25
|
+
return (
|
|
26
|
+
<div className="hidden" aria-hidden="true">
|
|
27
|
+
{title}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="xl:hidden border border-muted mb-vsp-lg">
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onClick={() => setOpen((prev) => !prev)}
|
|
37
|
+
aria-expanded={open}
|
|
38
|
+
className="flex w-full items-center justify-between px-hsp-lg py-vsp-xs text-small font-medium text-fg"
|
|
39
|
+
>
|
|
40
|
+
<span>{title}</span>
|
|
41
|
+
<svg
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
aria-hidden="true"
|
|
44
|
+
className={clsx(
|
|
45
|
+
"h-icon-sm w-icon-sm text-muted transition-transform duration-150",
|
|
46
|
+
open && "rotate-180",
|
|
47
|
+
)}
|
|
48
|
+
fill="none"
|
|
49
|
+
viewBox="0 0 24 24"
|
|
50
|
+
stroke="currentColor"
|
|
51
|
+
strokeWidth={2}
|
|
52
|
+
>
|
|
53
|
+
<path
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
d="M19 9l-7 7-7-7"
|
|
57
|
+
/>
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
{/* Items list is always rendered so the SSG HTML carries every
|
|
61
|
+
anchor link for crawlers / JS-off readers AND so the hydration
|
|
62
|
+
DOM tree matches the SSR DOM byte-for-byte (no conditional
|
|
63
|
+
subtree-mount on hydration that could otherwise drop the
|
|
64
|
+
rendered markup or detach already-attached event handlers).
|
|
65
|
+
Visibility is toggled via the `hidden` utility — display:none
|
|
66
|
+
when closed, intrinsic display when open. */}
|
|
67
|
+
<ul
|
|
68
|
+
className={clsx(
|
|
69
|
+
"border-t border-muted px-hsp-lg py-vsp-xs space-y-vsp-2xs",
|
|
70
|
+
!open && "hidden",
|
|
71
|
+
)}
|
|
72
|
+
aria-hidden={!open}
|
|
73
|
+
>
|
|
74
|
+
{filtered.map((heading, index) => (
|
|
75
|
+
<li
|
|
76
|
+
key={`${heading.slug}-${index}`}
|
|
77
|
+
className={clsx(
|
|
78
|
+
heading.depth === 3 && "ml-hsp-lg",
|
|
79
|
+
heading.depth === 4 && "ml-hsp-2xl",
|
|
80
|
+
)}
|
|
81
|
+
>
|
|
82
|
+
<a
|
|
83
|
+
href={`#${heading.slug}`}
|
|
84
|
+
onClick={() => setOpen(false)}
|
|
85
|
+
className="block py-vsp-2xs text-small text-muted hover:text-fg hover:underline focus-visible:underline"
|
|
86
|
+
>
|
|
87
|
+
<SmartBreak>{heading.text}</SmartBreak>
|
|
88
|
+
</a>
|
|
89
|
+
</li>
|
|
90
|
+
))}
|
|
91
|
+
</ul>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// W6A stub — no-op default export.
|
|
2
|
+
//
|
|
3
|
+
// The host (zudo-doc showcase) ships an interactive preset-generator
|
|
4
|
+
// island used by its onboarding pages. Generated downstream projects
|
|
5
|
+
// ship the no-op so unconditional page imports (`pages/lib/_preset-generator`)
|
|
6
|
+
// resolve. Wire a real implementation by replacing this file.
|
|
7
|
+
import type { JSX } from "preact";
|
|
8
|
+
|
|
9
|
+
function PresetGenerator(): JSX.Element | null {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
PresetGenerator.displayName = "PresetGenerator";
|
|
13
|
+
|
|
14
|
+
export default PresetGenerator;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useState, useEffect } from "preact/compat";
|
|
2
|
+
import type { ComponentChildren } from "preact";
|
|
3
|
+
|
|
4
|
+
interface SidebarToggleProps {
|
|
5
|
+
children: ComponentChildren;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function SidebarToggle({ children }: SidebarToggleProps) {
|
|
9
|
+
const [open, setOpen] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (open) {
|
|
13
|
+
document.body.style.overflow = "hidden";
|
|
14
|
+
} else {
|
|
15
|
+
document.body.style.overflow = "";
|
|
16
|
+
}
|
|
17
|
+
return () => {
|
|
18
|
+
document.body.style.overflow = "";
|
|
19
|
+
};
|
|
20
|
+
}, [open]);
|
|
21
|
+
|
|
22
|
+
// Close mobile sidebar on View Transition navigation
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
function handleSwap() {
|
|
25
|
+
setOpen(false);
|
|
26
|
+
}
|
|
27
|
+
// zfb's `<ViewTransitions />` does a real page load on every
|
|
28
|
+
// navigation, so `DOMContentLoaded` is the post-navigate signal.
|
|
29
|
+
document.addEventListener("DOMContentLoaded", handleSwap);
|
|
30
|
+
return () => document.removeEventListener("DOMContentLoaded", handleSwap);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
{/* Hamburger button - visible only on mobile */}
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={() => setOpen(!open)}
|
|
39
|
+
className="lg:hidden px-hsp-sm py-vsp-xs -ml-hsp-sm mr-hsp-sm text-muted hover:text-fg"
|
|
40
|
+
aria-label={open ? "Close sidebar" : "Open sidebar"}
|
|
41
|
+
>
|
|
42
|
+
{open ? (
|
|
43
|
+
<svg
|
|
44
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
45
|
+
className="h-icon-lg w-icon-lg"
|
|
46
|
+
fill="none"
|
|
47
|
+
viewBox="0 0 24 24"
|
|
48
|
+
stroke="currentColor"
|
|
49
|
+
strokeWidth={2}
|
|
50
|
+
>
|
|
51
|
+
<path
|
|
52
|
+
strokeLinecap="round"
|
|
53
|
+
strokeLinejoin="round"
|
|
54
|
+
d="M6 18L18 6M6 6l12 12"
|
|
55
|
+
/>
|
|
56
|
+
</svg>
|
|
57
|
+
) : (
|
|
58
|
+
<svg
|
|
59
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
60
|
+
className="h-icon-lg w-icon-lg"
|
|
61
|
+
fill="none"
|
|
62
|
+
viewBox="0 0 24 24"
|
|
63
|
+
stroke="currentColor"
|
|
64
|
+
strokeWidth={2}
|
|
65
|
+
>
|
|
66
|
+
<path
|
|
67
|
+
strokeLinecap="round"
|
|
68
|
+
strokeLinejoin="round"
|
|
69
|
+
d="M4 6h16M4 12h16M4 18h16"
|
|
70
|
+
/>
|
|
71
|
+
</svg>
|
|
72
|
+
)}
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
{/* Backdrop overlay - mobile only */}
|
|
76
|
+
{open && (
|
|
77
|
+
<div
|
|
78
|
+
className="fixed inset-0 z-30 bg-overlay/30 lg:hidden"
|
|
79
|
+
onClick={() => setOpen(false)}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
{/* Sidebar panel - mobile only (desktop sidebar is in doc-layout) */}
|
|
84
|
+
<aside
|
|
85
|
+
className={`
|
|
86
|
+
fixed top-[3.5rem] left-0 z-40 h-[calc(100vh-3.5rem)] w-[16rem] flex flex-col
|
|
87
|
+
border-r border-muted bg-bg transition-transform duration-200
|
|
88
|
+
lg:hidden
|
|
89
|
+
${open ? "translate-x-0" : "-translate-x-full"}
|
|
90
|
+
`}
|
|
91
|
+
>
|
|
92
|
+
<div className="flex-1 overflow-y-auto">
|
|
93
|
+
{children}
|
|
94
|
+
</div>
|
|
95
|
+
</aside>
|
|
96
|
+
</>
|
|
97
|
+
);
|
|
98
|
+
}
|