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,233 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Use preact hook entrypoints directly — zfb's esbuild step doesn't alias
|
|
4
|
+
// "react" to "preact/compat" the way Astro's `@astrojs/preact` integration
|
|
5
|
+
// did, so importing from "react" here would fail to resolve at SSR/island
|
|
6
|
+
// bundle time. Same pattern as src/components/sidebar-tree.tsx.
|
|
7
|
+
import { useState } from "preact/hooks";
|
|
8
|
+
import type { NavNode } from "@/utils/docs";
|
|
9
|
+
import { INDENT, connectorLeft, ConnectorLines, CategoryLinkIcon } from "./tree-nav-shared";
|
|
10
|
+
|
|
11
|
+
// site-tree-nav uses wider padding than the narrow sidebar
|
|
12
|
+
const SITE_BASE_PAD = "clamp(0.5rem, 0.8vw, 1rem)";
|
|
13
|
+
|
|
14
|
+
function padLeft(depth: number): string {
|
|
15
|
+
if (depth === 0) return SITE_BASE_PAD;
|
|
16
|
+
return `calc(${depth} * ${INDENT} + 1.25rem + 5px)`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function reorderTree(tree: NavNode[], order: string[]): NavNode[] {
|
|
20
|
+
const map = new Map(tree.map((node) => [node.slug, node]));
|
|
21
|
+
const ordered: NavNode[] = [];
|
|
22
|
+
for (const slug of order) {
|
|
23
|
+
const node = map.get(slug);
|
|
24
|
+
if (node) {
|
|
25
|
+
ordered.push(node);
|
|
26
|
+
map.delete(slug);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// append unmatched nodes at end
|
|
30
|
+
for (const node of map.values()) {
|
|
31
|
+
ordered.push(node);
|
|
32
|
+
}
|
|
33
|
+
return ordered;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface SiteTreeNavProps {
|
|
37
|
+
tree: NavNode[];
|
|
38
|
+
ariaLabel?: string;
|
|
39
|
+
categoryOrder?: string[];
|
|
40
|
+
categoryIgnore?: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default function SiteTreeNav({
|
|
44
|
+
tree,
|
|
45
|
+
ariaLabel = "Site index",
|
|
46
|
+
categoryOrder,
|
|
47
|
+
categoryIgnore,
|
|
48
|
+
}: SiteTreeNavProps) {
|
|
49
|
+
let processedTree = tree;
|
|
50
|
+
if (categoryIgnore) {
|
|
51
|
+
const ignoreSet = new Set(categoryIgnore);
|
|
52
|
+
processedTree = processedTree.filter((node) => !ignoreSet.has(node.slug));
|
|
53
|
+
}
|
|
54
|
+
if (categoryOrder) {
|
|
55
|
+
processedTree = reorderTree(processedTree, categoryOrder);
|
|
56
|
+
}
|
|
57
|
+
return (
|
|
58
|
+
<nav
|
|
59
|
+
aria-label={ariaLabel}
|
|
60
|
+
data-site-nav
|
|
61
|
+
className="grid gap-vsp-md"
|
|
62
|
+
style={{
|
|
63
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(min(18rem, 100%), 1fr))",
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
{processedTree.map((node) => (
|
|
67
|
+
<div key={node.slug} className="min-w-0 border border-muted pl-hsp-sm py-vsp-2xs">
|
|
68
|
+
{node.children.length > 0 ? (
|
|
69
|
+
<CategoryNode node={node} depth={0} isLast={true} />
|
|
70
|
+
) : (
|
|
71
|
+
<LeafNode node={node} depth={0} isLast={true} />
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
))}
|
|
75
|
+
</nav>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function NodeList({ nodes, depth }: { nodes: NavNode[]; depth: number }) {
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
{nodes.map((node, index) => {
|
|
83
|
+
const isLast = index === nodes.length - 1;
|
|
84
|
+
return node.children.length > 0 ? (
|
|
85
|
+
<CategoryNode
|
|
86
|
+
key={node.slug}
|
|
87
|
+
node={node}
|
|
88
|
+
depth={depth}
|
|
89
|
+
isLast={isLast}
|
|
90
|
+
/>
|
|
91
|
+
) : (
|
|
92
|
+
<LeafNode
|
|
93
|
+
key={node.slug}
|
|
94
|
+
node={node}
|
|
95
|
+
depth={depth}
|
|
96
|
+
isLast={isLast}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
})}
|
|
100
|
+
</>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function CategoryNode({
|
|
105
|
+
node,
|
|
106
|
+
depth,
|
|
107
|
+
isLast,
|
|
108
|
+
}: {
|
|
109
|
+
node: NavNode;
|
|
110
|
+
depth: number;
|
|
111
|
+
isLast: boolean;
|
|
112
|
+
}) {
|
|
113
|
+
const [open, setOpen] = useState(true);
|
|
114
|
+
const toggle = () => setOpen((prev) => !prev);
|
|
115
|
+
const paddingLeft = padLeft(depth);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className={`${depth >= 1 && !isLast ? "relative" : ""}`}>
|
|
119
|
+
{depth >= 1 && !isLast && open && (
|
|
120
|
+
<div
|
|
121
|
+
className="absolute border-l border-dashed border-muted z-10"
|
|
122
|
+
style={{
|
|
123
|
+
left: connectorLeft(depth),
|
|
124
|
+
top: 0,
|
|
125
|
+
bottom: 0,
|
|
126
|
+
}}
|
|
127
|
+
/>
|
|
128
|
+
)}
|
|
129
|
+
<div className="relative">
|
|
130
|
+
<ConnectorLines
|
|
131
|
+
depth={depth}
|
|
132
|
+
isLast={isLast}
|
|
133
|
+
widthScale={2}
|
|
134
|
+
topPad="calc(0.15rem + var(--spacing-vsp-xs))"
|
|
135
|
+
/>
|
|
136
|
+
<div
|
|
137
|
+
className="flex w-full items-center justify-between text-small font-semibold pt-[0.15rem] text-fg"
|
|
138
|
+
style={{ paddingLeft }}
|
|
139
|
+
>
|
|
140
|
+
{node.href ? (
|
|
141
|
+
<a
|
|
142
|
+
href={node.href}
|
|
143
|
+
className="flex-1 flex items-start gap-hsp-xs py-vsp-xs text-fg hover:text-accent hover:underline focus:underline"
|
|
144
|
+
>
|
|
145
|
+
{depth === 0 && (
|
|
146
|
+
<span className="flex h-[1lh] items-center">
|
|
147
|
+
<CategoryLinkIcon className="w-[18px] 2xl:w-[24px]" />
|
|
148
|
+
</span>
|
|
149
|
+
)}
|
|
150
|
+
{node.label}
|
|
151
|
+
</a>
|
|
152
|
+
) : (
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
onClick={toggle}
|
|
156
|
+
className="flex-1 py-vsp-xs text-left hover:text-accent hover:underline focus:underline"
|
|
157
|
+
>
|
|
158
|
+
{node.label}
|
|
159
|
+
</button>
|
|
160
|
+
)}
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
onClick={toggle}
|
|
164
|
+
className="aspect-square flex items-center justify-center w-[1.75rem] border-y border-l border-muted hover:underline focus:underline"
|
|
165
|
+
aria-expanded={open}
|
|
166
|
+
aria-label={open ? `Collapse ${node.label}` : `Expand ${node.label}`}
|
|
167
|
+
>
|
|
168
|
+
<svg
|
|
169
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
170
|
+
className={`h-icon-xs w-icon-xs transition-transform duration-150 ${open ? "rotate-90" : ""} text-muted`}
|
|
171
|
+
fill="none"
|
|
172
|
+
viewBox="0 0 24 24"
|
|
173
|
+
stroke="currentColor"
|
|
174
|
+
strokeWidth={2}
|
|
175
|
+
>
|
|
176
|
+
<path
|
|
177
|
+
strokeLinecap="round"
|
|
178
|
+
strokeLinejoin="round"
|
|
179
|
+
d="M9 5l7 7-7 7"
|
|
180
|
+
/>
|
|
181
|
+
</svg>
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
{open && (
|
|
186
|
+
<div>
|
|
187
|
+
<NodeList nodes={node.children} depth={depth + 1} />
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function LeafNode({
|
|
195
|
+
node,
|
|
196
|
+
depth,
|
|
197
|
+
isLast,
|
|
198
|
+
}: {
|
|
199
|
+
node: NavNode;
|
|
200
|
+
depth: number;
|
|
201
|
+
isLast: boolean;
|
|
202
|
+
}) {
|
|
203
|
+
if (!node.href) return null;
|
|
204
|
+
const isRoot = depth === 0;
|
|
205
|
+
const paddingLeft = padLeft(depth);
|
|
206
|
+
|
|
207
|
+
const topPad = isRoot
|
|
208
|
+
? "calc(var(--spacing-vsp-xs) + 0.15rem)"
|
|
209
|
+
: "var(--spacing-vsp-2xs)";
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<div>
|
|
213
|
+
<div className="relative">
|
|
214
|
+
<ConnectorLines depth={depth} isLast={isLast} widthScale={2} topPad={topPad} />
|
|
215
|
+
<a
|
|
216
|
+
href={node.href}
|
|
217
|
+
className={isRoot
|
|
218
|
+
? "flex items-start gap-hsp-xs py-[calc(var(--spacing-vsp-xs)+0.15rem)] text-small font-semibold text-fg hover:text-accent hover:underline focus:underline"
|
|
219
|
+
: `block py-vsp-2xs ${isLast ? "pb-vsp-xs" : ""} text-small text-fg hover:text-accent hover:underline focus:underline`
|
|
220
|
+
}
|
|
221
|
+
style={{ paddingLeft }}
|
|
222
|
+
>
|
|
223
|
+
{isRoot && (
|
|
224
|
+
<span className="flex h-[1lh] items-center">
|
|
225
|
+
<CategoryLinkIcon className="w-[18px] 2xl:w-[24px]" />
|
|
226
|
+
</span>
|
|
227
|
+
)}
|
|
228
|
+
{node.label}
|
|
229
|
+
</a>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useState, useEffect } from "preact/compat";
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = "zudo-doc-theme";
|
|
4
|
+
|
|
5
|
+
function SunIcon() {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
aria-hidden="true"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
width="20"
|
|
11
|
+
height="20"
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
fill="none"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
strokeWidth="2"
|
|
16
|
+
strokeLinecap="round"
|
|
17
|
+
strokeLinejoin="round"
|
|
18
|
+
>
|
|
19
|
+
<circle cx="12" cy="12" r="5" />
|
|
20
|
+
<line x1="12" y1="1" x2="12" y2="3" />
|
|
21
|
+
<line x1="12" y1="21" x2="12" y2="23" />
|
|
22
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
23
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
24
|
+
<line x1="1" y1="12" x2="3" y2="12" />
|
|
25
|
+
<line x1="21" y1="12" x2="23" y2="12" />
|
|
26
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
27
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
28
|
+
</svg>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function MoonIcon() {
|
|
33
|
+
return (
|
|
34
|
+
<svg
|
|
35
|
+
aria-hidden="true"
|
|
36
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
+
width="20"
|
|
38
|
+
height="20"
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
strokeWidth="2"
|
|
43
|
+
strokeLinecap="round"
|
|
44
|
+
strokeLinejoin="round"
|
|
45
|
+
>
|
|
46
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
47
|
+
</svg>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface ThemeToggleProps {
|
|
52
|
+
defaultMode?: "light" | "dark";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default function ThemeToggle({ defaultMode = "dark" }: ThemeToggleProps) {
|
|
56
|
+
// Initial state must match server render to avoid hydration mismatch.
|
|
57
|
+
// Actual theme is synced from DOM in useEffect below.
|
|
58
|
+
const [mode, setMode] = useState<"light" | "dark">(defaultMode);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const actual =
|
|
62
|
+
(document.documentElement.getAttribute("data-theme") as
|
|
63
|
+
| "light"
|
|
64
|
+
| "dark") || defaultMode;
|
|
65
|
+
if (actual !== mode) {
|
|
66
|
+
setMode(actual);
|
|
67
|
+
}
|
|
68
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
69
|
+
|
|
70
|
+
function toggle() {
|
|
71
|
+
const next = mode === "dark" ? "light" : "dark";
|
|
72
|
+
setMode(next);
|
|
73
|
+
document.documentElement.setAttribute("data-theme", next);
|
|
74
|
+
document.documentElement.style.colorScheme = next;
|
|
75
|
+
localStorage.setItem(STORAGE_KEY, next);
|
|
76
|
+
// Clear both v1 and v2 tweak state so the new scheme's palette takes effect.
|
|
77
|
+
localStorage.removeItem("zudo-doc-tweak-state");
|
|
78
|
+
localStorage.removeItem("zudo-doc-tweak-state-v2");
|
|
79
|
+
window.dispatchEvent(new CustomEvent("color-scheme-changed"));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const nextMode = mode === "dark" ? "light" : "dark";
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<button
|
|
86
|
+
onClick={toggle}
|
|
87
|
+
aria-label={`Switch to ${nextMode} mode`}
|
|
88
|
+
className="text-muted hover:text-fg transition-colors p-hsp-sm focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-2"
|
|
89
|
+
>
|
|
90
|
+
{mode === "dark" ? <SunIcon /> : <MoonIcon />}
|
|
91
|
+
</button>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "preact/hooks";
|
|
4
|
+
import { useActiveHeading } from "@/hooks/use-active-heading";
|
|
5
|
+
import type { Heading } from "@/types/heading";
|
|
6
|
+
import { SmartBreak } from "@/utils/smart-break";
|
|
7
|
+
import clsx from "clsx";
|
|
8
|
+
|
|
9
|
+
interface TocProps {
|
|
10
|
+
headings: Heading[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Toc({ headings }: TocProps) {
|
|
14
|
+
const filtered = useMemo(
|
|
15
|
+
() => headings.filter((h) => h.depth >= 2 && h.depth <= 4),
|
|
16
|
+
[headings],
|
|
17
|
+
);
|
|
18
|
+
const { activeId, activate } = useActiveHeading(filtered);
|
|
19
|
+
|
|
20
|
+
if (filtered.length === 0) return <nav className="hidden" />;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<nav
|
|
24
|
+
aria-label="Table of contents"
|
|
25
|
+
className={clsx(
|
|
26
|
+
"hidden xl:flex flex-col",
|
|
27
|
+
"w-[280px] shrink-0",
|
|
28
|
+
"sticky top-[3.5rem] self-start z-10",
|
|
29
|
+
"pt-vsp-xl lg:pt-vsp-2xl",
|
|
30
|
+
"h-[calc(100vh-3.5rem)]",
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
<ul className="border-l border-muted pl-hsp-lg overflow-y-auto flex-1 min-h-0">
|
|
34
|
+
{filtered.map((heading, index) => {
|
|
35
|
+
const isActive = heading.slug === activeId;
|
|
36
|
+
return (
|
|
37
|
+
<li
|
|
38
|
+
key={`${heading.slug}-${index}`}
|
|
39
|
+
className={clsx(
|
|
40
|
+
heading.depth === 3 && "ml-hsp-lg",
|
|
41
|
+
heading.depth === 4 && "ml-hsp-2xl",
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
44
|
+
<a
|
|
45
|
+
href={`#${heading.slug}`}
|
|
46
|
+
onClick={() => activate(heading.slug)}
|
|
47
|
+
aria-current={isActive ? "true" : undefined}
|
|
48
|
+
className={clsx(
|
|
49
|
+
"block py-vsp-2xs text-small leading-snug transition-colors",
|
|
50
|
+
isActive
|
|
51
|
+
? "bg-fg text-bg font-medium"
|
|
52
|
+
: "text-muted hover:underline focus:underline",
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
<SmartBreak>{heading.text}</SmartBreak>
|
|
56
|
+
</a>
|
|
57
|
+
</li>
|
|
58
|
+
);
|
|
59
|
+
})}
|
|
60
|
+
</ul>
|
|
61
|
+
</nav>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Shared constants and primitives for sidebar-tree and site-tree-nav
|
|
2
|
+
|
|
3
|
+
// Indentation — fluid clamp values
|
|
4
|
+
export const INDENT = "clamp(0.8rem, 1.2vw, 1.625rem)";
|
|
5
|
+
export const CONNECTOR_OFFSET = "clamp(0.2rem, 0.3vw, 0.5rem)";
|
|
6
|
+
export const CONNECTOR_WIDTH = "clamp(0.4rem, 0.6vw, 1rem)";
|
|
7
|
+
export const BASE_PAD = "clamp(0.4rem, 0.8vw, 1.3rem)";
|
|
8
|
+
|
|
9
|
+
export function connectorLeft(depth: number): string {
|
|
10
|
+
return `calc(${depth} * ${INDENT} + ${CONNECTOR_OFFSET})`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CATEGORY_LINK_PATH =
|
|
14
|
+
"M5.746 5.74 0 11.49l20.987 20.96C34.126 45.572 41.963 53.45 41.948 53.523c-.012.062-9.456 9.544-20.986 21.07L0 95.55l5.714 5.715c3.142 3.143 5.748 5.715 5.79 5.715s2.63-2.563 5.75-5.696l17.939-18.001c21.867-21.94 29.443-29.599 29.443-29.768 0-.114-.665-.804-5.084-5.275C51.872 40.47 11.71.125 11.565.036 11.525.01 8.906 2.578 5.746 5.74m38.345-.066c-3.132 3.13-5.696 5.71-5.696 5.732-.001.022 2.16 2.185 4.8 4.807 2.641 2.623 8.382 8.338 12.758 12.702 15.38 15.337 23.763 23.641 24.314 24.086.19.153.346.336.346.405 0 .07-1.738 1.847-3.887 3.976a17515 17515 0 0 0-20.35 20.264 19555 19555 0 0 1-17.223 17.158c-.416.409-.757.77-.757.8 0 .083 11.415 11.485 11.457 11.445.235-.22 53.542-53.528 53.542-53.543C103.395 53.472 49.891.02 49.837 0c-.028-.01-2.613 2.543-5.746 5.674";
|
|
15
|
+
|
|
16
|
+
export function CategoryLinkIcon({ className }: { className?: string }) {
|
|
17
|
+
return (
|
|
18
|
+
<svg
|
|
19
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
+
fill="currentColor"
|
|
21
|
+
viewBox="0 0 103.395 107.049"
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
className={`shrink-0 ${className ?? ""}`}
|
|
24
|
+
>
|
|
25
|
+
<path d={CATEGORY_LINK_PATH} />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ConnectorLines({
|
|
31
|
+
depth,
|
|
32
|
+
isLast,
|
|
33
|
+
widthScale = 1,
|
|
34
|
+
topPad = "0px",
|
|
35
|
+
}: {
|
|
36
|
+
depth: number;
|
|
37
|
+
isLast: boolean;
|
|
38
|
+
widthScale?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Padding-top of the inner content row that holds the link text. The horizontal
|
|
41
|
+
* connector and (when `isLast`) the vertical clip both anchor to the first-line
|
|
42
|
+
* midpoint computed as `topPad + 0.5lh`, so multi-line labels keep the connector
|
|
43
|
+
* aligned with the first line instead of the row's vertical center.
|
|
44
|
+
*/
|
|
45
|
+
topPad?: string;
|
|
46
|
+
}) {
|
|
47
|
+
if (depth === 0) return null;
|
|
48
|
+
const left = connectorLeft(depth);
|
|
49
|
+
const width = widthScale === 1 ? CONNECTOR_WIDTH : `calc(${CONNECTOR_WIDTH} * ${widthScale})`;
|
|
50
|
+
const firstLineMid = `calc(${topPad} + 0.5lh)`;
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<div
|
|
54
|
+
className="absolute border-l border-dashed border-muted"
|
|
55
|
+
style={{
|
|
56
|
+
left,
|
|
57
|
+
top: 0,
|
|
58
|
+
bottom: isLast ? `calc(100% - ${firstLineMid})` : 0,
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
<div
|
|
62
|
+
className="absolute border-t border-dashed border-muted"
|
|
63
|
+
style={{
|
|
64
|
+
left,
|
|
65
|
+
width,
|
|
66
|
+
top: firstLineMid,
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { colorSchemes, type ColorScheme, type ColorRef } from "./color-schemes";
|
|
2
|
+
import { settings } from "./settings";
|
|
3
|
+
|
|
4
|
+
/** Default mapping: semantic token name → palette index */
|
|
5
|
+
export const SEMANTIC_DEFAULTS: Record<string, number> = {
|
|
6
|
+
surface: 0,
|
|
7
|
+
muted: 8,
|
|
8
|
+
accent: 5,
|
|
9
|
+
accentHover: 14,
|
|
10
|
+
codeBg: 10,
|
|
11
|
+
codeFg: 11,
|
|
12
|
+
success: 2,
|
|
13
|
+
danger: 1,
|
|
14
|
+
warning: 3,
|
|
15
|
+
info: 4,
|
|
16
|
+
mermaidNodeBg: 9,
|
|
17
|
+
mermaidText: 11,
|
|
18
|
+
mermaidLine: 8,
|
|
19
|
+
mermaidLabelBg: 10,
|
|
20
|
+
mermaidNoteBg: 0,
|
|
21
|
+
chatUserBg: 5,
|
|
22
|
+
chatUserText: 9,
|
|
23
|
+
chatAssistantBg: 9,
|
|
24
|
+
chatAssistantText: 11,
|
|
25
|
+
imageOverlayBg: 0,
|
|
26
|
+
imageOverlayFg: 11,
|
|
27
|
+
matchedKeywordBg: 3,
|
|
28
|
+
matchedKeywordFg: 15,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const SEMANTIC_CSS_NAMES: Record<string, string> = {
|
|
32
|
+
surface: "--zd-surface",
|
|
33
|
+
muted: "--zd-muted",
|
|
34
|
+
accent: "--zd-accent",
|
|
35
|
+
accentHover: "--zd-accent-hover",
|
|
36
|
+
codeBg: "--zd-code-bg",
|
|
37
|
+
codeFg: "--zd-code-fg",
|
|
38
|
+
success: "--zd-success",
|
|
39
|
+
danger: "--zd-danger",
|
|
40
|
+
warning: "--zd-warning",
|
|
41
|
+
info: "--zd-info",
|
|
42
|
+
mermaidNodeBg: "--zd-mermaid-node-bg",
|
|
43
|
+
mermaidText: "--zd-mermaid-text",
|
|
44
|
+
mermaidLine: "--zd-mermaid-line",
|
|
45
|
+
mermaidLabelBg: "--zd-mermaid-label-bg",
|
|
46
|
+
mermaidNoteBg: "--zd-mermaid-note-bg",
|
|
47
|
+
chatUserBg: "--zd-chat-user-bg",
|
|
48
|
+
chatUserText: "--zd-chat-user-text",
|
|
49
|
+
chatAssistantBg: "--zd-chat-assistant-bg",
|
|
50
|
+
chatAssistantText: "--zd-chat-assistant-text",
|
|
51
|
+
imageOverlayBg: "--zd-image-overlay-bg",
|
|
52
|
+
imageOverlayFg: "--zd-image-overlay-fg",
|
|
53
|
+
matchedKeywordBg: "--zd-matched-keyword-bg",
|
|
54
|
+
matchedKeywordFg: "--zd-matched-keyword-fg",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const lightDarkPairings = [
|
|
58
|
+
{ light: "Default Light", dark: "Default Dark", label: "Default" },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export function getActiveScheme(): ColorScheme {
|
|
62
|
+
const scheme = colorSchemes[settings.colorScheme];
|
|
63
|
+
if (!scheme) {
|
|
64
|
+
throw new Error(`Unknown color scheme: "${settings.colorScheme}". Available: ${Object.keys(colorSchemes).join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
return scheme;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Resolve a ColorRef to a concrete color string.
|
|
70
|
+
* - number → palette[value]
|
|
71
|
+
* - string → used as-is
|
|
72
|
+
* - undefined → fallback */
|
|
73
|
+
export function resolveColor(
|
|
74
|
+
value: ColorRef | undefined,
|
|
75
|
+
palette: string[],
|
|
76
|
+
fallback: string,
|
|
77
|
+
): string {
|
|
78
|
+
if (value === undefined) return fallback;
|
|
79
|
+
if (typeof value === "number") return palette[value] ?? fallback;
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Resolve semantic colors with fallbacks to default palette slots */
|
|
84
|
+
export function resolveSemanticColors(scheme: ColorScheme) {
|
|
85
|
+
const p = scheme.palette;
|
|
86
|
+
return {
|
|
87
|
+
surface: resolveColor(scheme.semantic?.surface, p, p[0]),
|
|
88
|
+
muted: resolveColor(scheme.semantic?.muted, p, p[8]),
|
|
89
|
+
accent: resolveColor(scheme.semantic?.accent, p, p[5]),
|
|
90
|
+
accentHover: resolveColor(scheme.semantic?.accentHover, p, p[14]),
|
|
91
|
+
codeBg: resolveColor(scheme.semantic?.codeBg, p, p[10]),
|
|
92
|
+
codeFg: resolveColor(scheme.semantic?.codeFg, p, p[11]),
|
|
93
|
+
success: resolveColor(scheme.semantic?.success, p, p[2]),
|
|
94
|
+
danger: resolveColor(scheme.semantic?.danger, p, p[1]),
|
|
95
|
+
warning: resolveColor(scheme.semantic?.warning, p, p[3]),
|
|
96
|
+
info: resolveColor(scheme.semantic?.info, p, p[4]),
|
|
97
|
+
mermaidNodeBg: resolveColor(scheme.semantic?.mermaidNodeBg, p, p[9]),
|
|
98
|
+
mermaidText: resolveColor(scheme.semantic?.mermaidText, p, p[11]),
|
|
99
|
+
mermaidLine: resolveColor(scheme.semantic?.mermaidLine, p, p[8]),
|
|
100
|
+
mermaidLabelBg: resolveColor(scheme.semantic?.mermaidLabelBg, p, p[10]),
|
|
101
|
+
mermaidNoteBg: resolveColor(scheme.semantic?.mermaidNoteBg, p, p[0]),
|
|
102
|
+
chatUserBg: resolveColor(scheme.semantic?.chatUserBg, p, p[5]),
|
|
103
|
+
chatUserText: resolveColor(scheme.semantic?.chatUserText, p, p[9]),
|
|
104
|
+
chatAssistantBg: resolveColor(scheme.semantic?.chatAssistantBg, p, p[9]),
|
|
105
|
+
chatAssistantText: resolveColor(scheme.semantic?.chatAssistantText, p, p[11]),
|
|
106
|
+
imageOverlayBg: resolveColor(scheme.semantic?.imageOverlayBg, p, p[0]),
|
|
107
|
+
imageOverlayFg: resolveColor(scheme.semantic?.imageOverlayFg, p, p[11]),
|
|
108
|
+
matchedKeywordBg: resolveColor(scheme.semantic?.matchedKeywordBg, p, p[3]),
|
|
109
|
+
matchedKeywordFg: resolveColor(scheme.semantic?.matchedKeywordFg, p, p[15]),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function schemeToCssPairs(scheme: ColorScheme): [string, string][] {
|
|
114
|
+
const p = scheme.palette;
|
|
115
|
+
const sem = resolveSemanticColors(scheme);
|
|
116
|
+
return [
|
|
117
|
+
["--zd-bg", resolveColor(scheme.background, p, p[0])],
|
|
118
|
+
["--zd-fg", resolveColor(scheme.foreground, p, p[15])],
|
|
119
|
+
["--zd-cursor", resolveColor(scheme.cursor, p, p[6])],
|
|
120
|
+
["--zd-sel-bg", resolveColor(scheme.selectionBg, p, resolveColor(scheme.background, p, p[0]))],
|
|
121
|
+
["--zd-sel-fg", resolveColor(scheme.selectionFg, p, resolveColor(scheme.foreground, p, p[15]))],
|
|
122
|
+
...p.map((color, i) => [`--zd-${i}`, color] as [string, string]),
|
|
123
|
+
["--zd-surface", sem.surface],
|
|
124
|
+
["--zd-muted", sem.muted],
|
|
125
|
+
["--zd-accent", sem.accent],
|
|
126
|
+
["--zd-accent-hover", sem.accentHover],
|
|
127
|
+
["--zd-code-bg", sem.codeBg],
|
|
128
|
+
["--zd-code-fg", sem.codeFg],
|
|
129
|
+
["--zd-success", sem.success],
|
|
130
|
+
["--zd-danger", sem.danger],
|
|
131
|
+
["--zd-warning", sem.warning],
|
|
132
|
+
["--zd-info", sem.info],
|
|
133
|
+
["--zd-mermaid-node-bg", sem.mermaidNodeBg],
|
|
134
|
+
["--zd-mermaid-text", sem.mermaidText],
|
|
135
|
+
["--zd-mermaid-line", sem.mermaidLine],
|
|
136
|
+
["--zd-mermaid-label-bg", sem.mermaidLabelBg],
|
|
137
|
+
["--zd-mermaid-note-bg", sem.mermaidNoteBg],
|
|
138
|
+
["--zd-chat-user-bg", sem.chatUserBg],
|
|
139
|
+
["--zd-chat-user-text", sem.chatUserText],
|
|
140
|
+
["--zd-chat-assistant-bg", sem.chatAssistantBg],
|
|
141
|
+
["--zd-chat-assistant-text", sem.chatAssistantText],
|
|
142
|
+
["--zd-image-overlay-bg", sem.imageOverlayBg],
|
|
143
|
+
["--zd-image-overlay-fg", sem.imageOverlayFg],
|
|
144
|
+
["--zd-matched-keyword-bg", sem.matchedKeywordBg],
|
|
145
|
+
["--zd-matched-keyword-fg", sem.matchedKeywordFg],
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function generateCssCustomProperties(): string {
|
|
150
|
+
const scheme = getActiveScheme();
|
|
151
|
+
const pairs = schemeToCssPairs(scheme);
|
|
152
|
+
const lines = [":root {", ...pairs.map(([prop, value]) => ` ${prop}: ${value};`), "}"];
|
|
153
|
+
return lines.join("\n");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function generateLightDarkCssProperties(): string {
|
|
157
|
+
if (!settings.colorMode) {
|
|
158
|
+
throw new Error("colorMode is not configured");
|
|
159
|
+
}
|
|
160
|
+
const { lightScheme, darkScheme } = settings.colorMode;
|
|
161
|
+
const light = colorSchemes[lightScheme];
|
|
162
|
+
const dark = colorSchemes[darkScheme];
|
|
163
|
+
if (!light) throw new Error(`Unknown light scheme: "${lightScheme}"`);
|
|
164
|
+
if (!dark) throw new Error(`Unknown dark scheme: "${darkScheme}"`);
|
|
165
|
+
|
|
166
|
+
const lightPairs = schemeToCssPairs(light);
|
|
167
|
+
const darkPairs = schemeToCssPairs(dark);
|
|
168
|
+
|
|
169
|
+
if (lightPairs.length !== darkPairs.length) {
|
|
170
|
+
throw new Error(`Light scheme has ${lightPairs.length} properties but dark scheme has ${darkPairs.length}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const lines = [":root {", " color-scheme: light dark;"];
|
|
174
|
+
for (let i = 0; i < lightPairs.length; i++) {
|
|
175
|
+
const prop = lightPairs[i][0];
|
|
176
|
+
const lightVal = lightPairs[i][1];
|
|
177
|
+
const darkVal = darkPairs[i][1];
|
|
178
|
+
lines.push(` ${prop}: light-dark(${lightVal}, ${darkVal});`);
|
|
179
|
+
}
|
|
180
|
+
lines.push("}");
|
|
181
|
+
return lines.join("\n");
|
|
182
|
+
}
|