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,175 @@
|
|
|
1
|
+
export interface FindResult {
|
|
2
|
+
matches: number;
|
|
3
|
+
activeMatchOrdinal: number; // 1-based
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface FindInPage {
|
|
7
|
+
find(container: HTMLElement, query: string): FindResult;
|
|
8
|
+
next(): FindResult;
|
|
9
|
+
prev(): FindResult;
|
|
10
|
+
stop(): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const MATCH_CLASS = "find-match";
|
|
14
|
+
const ACTIVE_CLASS = "find-match-active";
|
|
15
|
+
const MATCH_ATTR = "data-find-match";
|
|
16
|
+
const ACTIVE_ATTR = "data-find-active";
|
|
17
|
+
|
|
18
|
+
const EMPTY_RESULT: FindResult = Object.freeze({ matches: 0, activeMatchOrdinal: 0 });
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a DOM-based find-in-page utility.
|
|
22
|
+
*
|
|
23
|
+
* Limitation: only matches within single text nodes. Cross-element matching
|
|
24
|
+
* (e.g. "Hello world" spanning `<strong>Hello</strong> world`) is not supported.
|
|
25
|
+
*/
|
|
26
|
+
export function createFindInPage(): FindInPage {
|
|
27
|
+
let matchElements: HTMLElement[] = [];
|
|
28
|
+
let activeIndex = -1;
|
|
29
|
+
|
|
30
|
+
function clearMarks(): void {
|
|
31
|
+
const parentsToNormalize = new Set<Node>();
|
|
32
|
+
|
|
33
|
+
for (let i = matchElements.length - 1; i >= 0; i--) {
|
|
34
|
+
const mark = matchElements[i];
|
|
35
|
+
const parent = mark.parentNode;
|
|
36
|
+
if (parent) {
|
|
37
|
+
const textNode = document.createTextNode(mark.textContent || "");
|
|
38
|
+
parent.replaceChild(textNode, mark);
|
|
39
|
+
parentsToNormalize.add(parent);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const parent of parentsToNormalize) {
|
|
43
|
+
(parent as Element).normalize();
|
|
44
|
+
}
|
|
45
|
+
matchElements = [];
|
|
46
|
+
activeIndex = -1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setActive(index: number): void {
|
|
50
|
+
if (activeIndex >= 0 && activeIndex < matchElements.length) {
|
|
51
|
+
const prev = matchElements[activeIndex];
|
|
52
|
+
prev.classList.remove(ACTIVE_CLASS);
|
|
53
|
+
prev.removeAttribute(ACTIVE_ATTR);
|
|
54
|
+
}
|
|
55
|
+
activeIndex = index;
|
|
56
|
+
if (activeIndex >= 0 && activeIndex < matchElements.length) {
|
|
57
|
+
const current = matchElements[activeIndex];
|
|
58
|
+
current.classList.add(ACTIVE_CLASS);
|
|
59
|
+
current.setAttribute(ACTIVE_ATTR, "true");
|
|
60
|
+
current.scrollIntoView?.({ block: "center" });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function currentResult(): FindResult {
|
|
65
|
+
if (matchElements.length === 0) {
|
|
66
|
+
return EMPTY_RESULT;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
matches: matchElements.length,
|
|
70
|
+
activeMatchOrdinal: activeIndex + 1,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function find(container: HTMLElement, query: string): FindResult {
|
|
75
|
+
clearMarks();
|
|
76
|
+
|
|
77
|
+
if (!query) {
|
|
78
|
+
return EMPTY_RESULT;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const lowerQuery = query.toLowerCase();
|
|
82
|
+
|
|
83
|
+
const walker = document.createTreeWalker(
|
|
84
|
+
container,
|
|
85
|
+
NodeFilter.SHOW_TEXT,
|
|
86
|
+
null,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const textNodes: Text[] = [];
|
|
90
|
+
let node: Text | null;
|
|
91
|
+
while ((node = walker.nextNode() as Text | null)) {
|
|
92
|
+
textNodes.push(node);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const textNode of textNodes) {
|
|
96
|
+
const text = textNode.textContent || "";
|
|
97
|
+
const lowerText = text.toLowerCase();
|
|
98
|
+
|
|
99
|
+
const positions: number[] = [];
|
|
100
|
+
let searchFrom = 0;
|
|
101
|
+
while (searchFrom < lowerText.length) {
|
|
102
|
+
const idx = lowerText.indexOf(lowerQuery, searchFrom);
|
|
103
|
+
if (idx === -1) break;
|
|
104
|
+
positions.push(idx);
|
|
105
|
+
searchFrom = idx + lowerQuery.length;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (positions.length === 0) continue;
|
|
109
|
+
|
|
110
|
+
const parent = textNode.parentNode;
|
|
111
|
+
if (!parent) continue;
|
|
112
|
+
|
|
113
|
+
let remainingNode: Text = textNode;
|
|
114
|
+
const nodeMarks: HTMLElement[] = [];
|
|
115
|
+
|
|
116
|
+
for (let i = positions.length - 1; i >= 0; i--) {
|
|
117
|
+
const pos = positions[i];
|
|
118
|
+
const matchLen = query.length;
|
|
119
|
+
|
|
120
|
+
if (pos + matchLen < remainingNode.length) {
|
|
121
|
+
remainingNode.splitText(pos + matchLen);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let matchNode: Text;
|
|
125
|
+
if (pos > 0) {
|
|
126
|
+
matchNode = remainingNode.splitText(pos);
|
|
127
|
+
} else {
|
|
128
|
+
matchNode = remainingNode;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const mark = document.createElement("mark");
|
|
132
|
+
mark.className = MATCH_CLASS;
|
|
133
|
+
mark.setAttribute(MATCH_ATTR, "true");
|
|
134
|
+
mark.textContent = matchNode.textContent;
|
|
135
|
+
|
|
136
|
+
parent.replaceChild(mark, matchNode);
|
|
137
|
+
nodeMarks.unshift(mark);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
matchElements.push(...nodeMarks);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (matchElements.length === 0) {
|
|
144
|
+
return EMPTY_RESULT;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setActive(0);
|
|
148
|
+
return currentResult();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function next(): FindResult {
|
|
152
|
+
if (matchElements.length === 0) {
|
|
153
|
+
return EMPTY_RESULT;
|
|
154
|
+
}
|
|
155
|
+
const newIndex = (activeIndex + 1) % matchElements.length;
|
|
156
|
+
setActive(newIndex);
|
|
157
|
+
return currentResult();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function prev(): FindResult {
|
|
161
|
+
if (matchElements.length === 0) {
|
|
162
|
+
return EMPTY_RESULT;
|
|
163
|
+
}
|
|
164
|
+
const newIndex =
|
|
165
|
+
(activeIndex - 1 + matchElements.length) % matchElements.length;
|
|
166
|
+
setActive(newIndex);
|
|
167
|
+
return currentResult();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function stop(): void {
|
|
171
|
+
clearMarks();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return { find, next, prev, stop };
|
|
175
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Build the .app bundle: cargo tauri build
|
|
2
|
+
# (requires `cargo install tauri-cli` or `cargo binstall tauri-cli`)
|
|
3
|
+
|
|
4
|
+
[package]
|
|
5
|
+
name = "zudo-doc"
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
edition = "2021"
|
|
8
|
+
|
|
9
|
+
[dependencies]
|
|
10
|
+
tauri = { version = "2", features = ["devtools"] }
|
|
11
|
+
open = "5"
|
|
12
|
+
|
|
13
|
+
[build-dependencies]
|
|
14
|
+
tauri-build = { version = "2", features = [] }
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// Mode 1 — Standalone offline reader (src-tauri/)
|
|
2
|
+
//
|
|
3
|
+
// `cargo tauri build` → bundles zudo-doc's own static `dist/` into a self-contained .app.
|
|
4
|
+
// This is the shipped offline reader for zudo-doc itself.
|
|
5
|
+
// `cargo tauri dev` → repo-internal contributor convenience only. Uses `beforeDevCommand`
|
|
6
|
+
// to spawn `pnpm dev` and opens the WebView against the local zfb dev
|
|
7
|
+
// server. NOT a shipped product; only for zudo-doc contributors.
|
|
8
|
+
//
|
|
9
|
+
// For the shipped, configurable dev wrapper (for any project), see src-tauri-dev/ (Mode 2).
|
|
10
|
+
// Mode 2 build: cd src-tauri-dev && cargo tauri build
|
|
11
|
+
|
|
12
|
+
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
13
|
+
|
|
14
|
+
use std::sync::Mutex;
|
|
15
|
+
use std::thread;
|
|
16
|
+
|
|
17
|
+
use tauri::menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder};
|
|
18
|
+
use tauri::webview::PageLoadEvent;
|
|
19
|
+
use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
|
|
20
|
+
|
|
21
|
+
const DEV_PORT: u16 = 4321;
|
|
22
|
+
const IS_DEV: bool = cfg!(debug_assertions);
|
|
23
|
+
|
|
24
|
+
struct AppState {
|
|
25
|
+
zoom: Mutex<f64>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Helpers ───────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
fn dev_url() -> String {
|
|
31
|
+
format!("http://localhost:{DEV_PORT}/")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Refresh / Zoom ───────────────────────────────
|
|
35
|
+
|
|
36
|
+
fn do_refresh(app_handle: &AppHandle) {
|
|
37
|
+
if let Some(w) = app_handle.get_webview_window("main") {
|
|
38
|
+
if IS_DEV {
|
|
39
|
+
let _ = w.navigate(
|
|
40
|
+
dev_url()
|
|
41
|
+
.parse()
|
|
42
|
+
.expect("BUG: dev_url produced an invalid URL"),
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
let _ = w.eval("window.location.reload()");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn apply_zoom(app_handle: &AppHandle, level: f64) {
|
|
51
|
+
let state = app_handle.state::<AppState>();
|
|
52
|
+
*state.zoom.lock().unwrap() = level;
|
|
53
|
+
if let Some(w) = app_handle.get_webview_window("main") {
|
|
54
|
+
// quirk: `document.body.style.zoom` is a non-standard Chromium-only CSS property.
|
|
55
|
+
// It is NOT part of any CSS spec (Firefox does not support it). Tauri's embedded
|
|
56
|
+
// Chromium/WebKit (WKWebView on macOS, Chromium on Windows/Linux) supports it, so
|
|
57
|
+
// it works here. The standards-based alternative is `webview.setZoom()` from the
|
|
58
|
+
// Tauri API, but migration is out of scope — keep as-is.
|
|
59
|
+
let _ = w.eval(&format!("document.body.style.zoom = '{level}'"));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Main ──────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
fn main() {
|
|
66
|
+
let app_state = AppState {
|
|
67
|
+
zoom: Mutex::new(1.0),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
tauri::Builder::default()
|
|
71
|
+
.manage(app_state)
|
|
72
|
+
.setup(move |app| {
|
|
73
|
+
// ── Menu ──
|
|
74
|
+
let app_menu = SubmenuBuilder::new(app, "ZudoDoc")
|
|
75
|
+
.about(None)
|
|
76
|
+
.separator()
|
|
77
|
+
.quit()
|
|
78
|
+
.build()?;
|
|
79
|
+
|
|
80
|
+
let edit_menu = SubmenuBuilder::new(app, "Edit")
|
|
81
|
+
.undo()
|
|
82
|
+
.redo()
|
|
83
|
+
.separator()
|
|
84
|
+
.cut()
|
|
85
|
+
.copy()
|
|
86
|
+
.paste()
|
|
87
|
+
.select_all()
|
|
88
|
+
.build()?;
|
|
89
|
+
|
|
90
|
+
let view_menu = SubmenuBuilder::new(app, "View")
|
|
91
|
+
.item(
|
|
92
|
+
&MenuItemBuilder::with_id("refresh", "Refresh")
|
|
93
|
+
.accelerator("CmdOrCtrl+R")
|
|
94
|
+
.build(app)?,
|
|
95
|
+
)
|
|
96
|
+
.item(
|
|
97
|
+
&MenuItemBuilder::with_id("devtools", "Toggle Developer Tools")
|
|
98
|
+
.accelerator("CmdOrCtrl+Alt+I")
|
|
99
|
+
.build(app)?,
|
|
100
|
+
)
|
|
101
|
+
.separator()
|
|
102
|
+
.item(
|
|
103
|
+
&MenuItemBuilder::with_id("actual_size", "Actual Size")
|
|
104
|
+
.accelerator("CmdOrCtrl+0")
|
|
105
|
+
.build(app)?,
|
|
106
|
+
)
|
|
107
|
+
.item(
|
|
108
|
+
&MenuItemBuilder::with_id("zoom_in", "Zoom In")
|
|
109
|
+
.accelerator("CmdOrCtrl+=")
|
|
110
|
+
.build(app)?,
|
|
111
|
+
)
|
|
112
|
+
.item(
|
|
113
|
+
&MenuItemBuilder::with_id("zoom_out", "Zoom Out")
|
|
114
|
+
.accelerator("CmdOrCtrl+-")
|
|
115
|
+
.build(app)?,
|
|
116
|
+
)
|
|
117
|
+
.build()?;
|
|
118
|
+
|
|
119
|
+
let menu = MenuBuilder::new(app)
|
|
120
|
+
.item(&app_menu)
|
|
121
|
+
.item(&edit_menu)
|
|
122
|
+
.item(&view_menu)
|
|
123
|
+
.build()?;
|
|
124
|
+
|
|
125
|
+
app.set_menu(menu)?;
|
|
126
|
+
|
|
127
|
+
app.on_menu_event(|app_handle, event| match event.id().as_ref() {
|
|
128
|
+
"refresh" => {
|
|
129
|
+
let handle = app_handle.clone();
|
|
130
|
+
thread::spawn(move || do_refresh(&handle));
|
|
131
|
+
}
|
|
132
|
+
"devtools" => {
|
|
133
|
+
if let Some(w) = app_handle.get_webview_window("main") {
|
|
134
|
+
if w.is_devtools_open() {
|
|
135
|
+
w.close_devtools();
|
|
136
|
+
} else {
|
|
137
|
+
w.open_devtools();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
"actual_size" => apply_zoom(app_handle, 1.0),
|
|
142
|
+
"zoom_in" => {
|
|
143
|
+
let state = app_handle.state::<AppState>();
|
|
144
|
+
let z = (*state.zoom.lock().unwrap() + 0.1).min(3.0);
|
|
145
|
+
apply_zoom(app_handle, z);
|
|
146
|
+
}
|
|
147
|
+
"zoom_out" => {
|
|
148
|
+
let state = app_handle.state::<AppState>();
|
|
149
|
+
let z = (*state.zoom.lock().unwrap() - 0.1).max(0.1);
|
|
150
|
+
apply_zoom(app_handle, z);
|
|
151
|
+
}
|
|
152
|
+
_ => {}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ── Window ──
|
|
156
|
+
// Dev: connect to zfb dev server via devUrl (contributor convenience only)
|
|
157
|
+
// Production: Tauri serves embedded dist/ files via frontendDist
|
|
158
|
+
let initial_url = if IS_DEV {
|
|
159
|
+
WebviewUrl::External(
|
|
160
|
+
dev_url()
|
|
161
|
+
.parse()
|
|
162
|
+
.expect("BUG: dev_url produced an invalid URL"),
|
|
163
|
+
)
|
|
164
|
+
} else {
|
|
165
|
+
WebviewUrl::default()
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
let window =
|
|
169
|
+
WebviewWindowBuilder::new(app, "main", initial_url)
|
|
170
|
+
.title("ZudoDoc")
|
|
171
|
+
.inner_size(1200.0, 800.0)
|
|
172
|
+
.visible(false)
|
|
173
|
+
.on_navigation(|url| {
|
|
174
|
+
let s = url.as_str();
|
|
175
|
+
// Allow internal schemes and the local dev / bundled app origins.
|
|
176
|
+
// - http://localhost, https://localhost : zfb dev server
|
|
177
|
+
// - tauri:// : bundled app (macOS/Linux)
|
|
178
|
+
// - http://tauri.localhost, https://tauri.localhost
|
|
179
|
+
// : bundled app (Windows)
|
|
180
|
+
// - asset:// : Tauri asset protocol
|
|
181
|
+
// - data: : inline data URLs
|
|
182
|
+
if s.starts_with("http://localhost")
|
|
183
|
+
|| s.starts_with("https://localhost")
|
|
184
|
+
|| s.starts_with("http://tauri.localhost")
|
|
185
|
+
|| s.starts_with("https://tauri.localhost")
|
|
186
|
+
|| s.starts_with("tauri://")
|
|
187
|
+
|| s.starts_with("asset://")
|
|
188
|
+
|| s.starts_with("data:")
|
|
189
|
+
{
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
// External http/https links -> default OS browser.
|
|
193
|
+
if s.starts_with("http://") || s.starts_with("https://") {
|
|
194
|
+
let owned = s.to_string();
|
|
195
|
+
tauri::async_runtime::spawn_blocking(move || {
|
|
196
|
+
let _ = open::that(owned);
|
|
197
|
+
});
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
// mailto: -> default mail client.
|
|
201
|
+
if s.starts_with("mailto:") {
|
|
202
|
+
let owned = s.to_string();
|
|
203
|
+
tauri::async_runtime::spawn_blocking(move || {
|
|
204
|
+
let _ = open::that(owned);
|
|
205
|
+
});
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
// Block any other scheme (javascript:, file:, etc.).
|
|
209
|
+
false
|
|
210
|
+
})
|
|
211
|
+
.build()?;
|
|
212
|
+
|
|
213
|
+
// Show window after page loads to avoid white flash
|
|
214
|
+
let win = window.clone();
|
|
215
|
+
window.on_window_event(move |event| {
|
|
216
|
+
if let tauri::WindowEvent::Focused(true) = event {
|
|
217
|
+
if !win.is_visible().unwrap_or(false) {
|
|
218
|
+
let _ = win.show();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Fallback: show after a short delay if focus event doesn't fire
|
|
224
|
+
let win = window.clone();
|
|
225
|
+
thread::spawn(move || {
|
|
226
|
+
thread::sleep(std::time::Duration::from_millis(500));
|
|
227
|
+
let _ = win.show();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
Ok(())
|
|
231
|
+
})
|
|
232
|
+
.on_page_load(|webview, payload| {
|
|
233
|
+
if webview.label() == "main"
|
|
234
|
+
&& matches!(payload.event(), PageLoadEvent::Finished)
|
|
235
|
+
{
|
|
236
|
+
let _ = webview.window().show();
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
.build(tauri::generate_context!())
|
|
240
|
+
.expect("error while building tauri application")
|
|
241
|
+
.run(|app_handle, event| {
|
|
242
|
+
if let tauri::RunEvent::WindowEvent {
|
|
243
|
+
event: tauri::WindowEvent::Destroyed,
|
|
244
|
+
..
|
|
245
|
+
} = &event
|
|
246
|
+
{
|
|
247
|
+
app_handle.exit(0);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"productName": "ZudoDoc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"identifier": "com.zudolab.zudo-doc",
|
|
5
|
+
"build": {
|
|
6
|
+
"frontendDist": "../dist",
|
|
7
|
+
"beforeDevCommand": "pnpm dev",
|
|
8
|
+
"devUrl": "http://localhost:4321/"
|
|
9
|
+
},
|
|
10
|
+
"app": {
|
|
11
|
+
"windows": [],
|
|
12
|
+
"security": {
|
|
13
|
+
"csp": null
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bundle": {
|
|
17
|
+
"active": false,
|
|
18
|
+
"targets": "all",
|
|
19
|
+
"icon": [],
|
|
20
|
+
"category": "DeveloperTool",
|
|
21
|
+
"macOS": {
|
|
22
|
+
"minimumSystemVersion": "10.15"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "zudo-doc-dev"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
|
|
6
|
+
[dependencies]
|
|
7
|
+
tauri = { version = "2", features = ["devtools"] }
|
|
8
|
+
libc = "0.2"
|
|
9
|
+
serde = { version = "1", features = ["derive"] }
|
|
10
|
+
serde_json = "1"
|
|
11
|
+
|
|
12
|
+
[build-dependencies]
|
|
13
|
+
tauri-build = { version = "2", features = [] }
|
|
14
|
+
|
|
15
|
+
[dev-dependencies]
|