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.
Files changed (212) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/bin/create-zudo-doc.js +2 -0
  4. package/dist/api.d.ts +20 -0
  5. package/dist/api.js +13 -0
  6. package/dist/claude-md-gen.d.ts +2 -0
  7. package/dist/claude-md-gen.js +113 -0
  8. package/dist/cli.d.ts +39 -0
  9. package/dist/cli.js +157 -0
  10. package/dist/compose.d.ts +95 -0
  11. package/dist/compose.js +206 -0
  12. package/dist/constants.d.ts +20 -0
  13. package/dist/constants.js +224 -0
  14. package/dist/features/body-foot-util.d.ts +10 -0
  15. package/dist/features/body-foot-util.js +12 -0
  16. package/dist/features/claude-resources.d.ts +2 -0
  17. package/dist/features/claude-resources.js +6 -0
  18. package/dist/features/design-token-panel.d.ts +14 -0
  19. package/dist/features/design-token-panel.js +27 -0
  20. package/dist/features/doc-history.d.ts +9 -0
  21. package/dist/features/doc-history.js +11 -0
  22. package/dist/features/doc-tags.d.ts +19 -0
  23. package/dist/features/doc-tags.js +33 -0
  24. package/dist/features/footer-taglist.d.ts +14 -0
  25. package/dist/features/footer-taglist.js +17 -0
  26. package/dist/features/footer.d.ts +8 -0
  27. package/dist/features/footer.js +10 -0
  28. package/dist/features/i18n.d.ts +22 -0
  29. package/dist/features/i18n.js +41 -0
  30. package/dist/features/image-enlarge.d.ts +11 -0
  31. package/dist/features/image-enlarge.js +13 -0
  32. package/dist/features/index.d.ts +15 -0
  33. package/dist/features/index.js +53 -0
  34. package/dist/features/llms-txt.d.ts +11 -0
  35. package/dist/features/llms-txt.js +13 -0
  36. package/dist/features/search.d.ts +9 -0
  37. package/dist/features/search.js +11 -0
  38. package/dist/features/sidebar-resizer.d.ts +14 -0
  39. package/dist/features/sidebar-resizer.js +16 -0
  40. package/dist/features/sidebar-toggle.d.ts +13 -0
  41. package/dist/features/sidebar-toggle.js +15 -0
  42. package/dist/features/tag-governance.d.ts +14 -0
  43. package/dist/features/tag-governance.js +16 -0
  44. package/dist/features/tauri-dev.d.ts +2 -0
  45. package/dist/features/tauri-dev.js +25 -0
  46. package/dist/features/tauri.d.ts +11 -0
  47. package/dist/features/tauri.js +52 -0
  48. package/dist/features/versioning.d.ts +27 -0
  49. package/dist/features/versioning.js +43 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.js +150 -0
  52. package/dist/preset.d.ts +37 -0
  53. package/dist/preset.js +156 -0
  54. package/dist/prompts.d.ts +32 -0
  55. package/dist/prompts.js +248 -0
  56. package/dist/scaffold.d.ts +4 -0
  57. package/dist/scaffold.js +344 -0
  58. package/dist/settings-gen.d.ts +2 -0
  59. package/dist/settings-gen.js +237 -0
  60. package/dist/utils.d.ts +8 -0
  61. package/dist/utils.js +34 -0
  62. package/dist/zfb-config-gen.d.ts +19 -0
  63. package/dist/zfb-config-gen.js +222 -0
  64. package/package.json +65 -0
  65. package/templates/base/.htmlvalidate.json +5 -0
  66. package/templates/base/.zfb/doc-history-meta.json +1 -0
  67. package/templates/base/pages/404.tsx +55 -0
  68. package/templates/base/pages/_data.ts +179 -0
  69. package/templates/base/pages/_mdx-components.ts +249 -0
  70. package/templates/base/pages/docs/[...slug].tsx +448 -0
  71. package/templates/base/pages/index.tsx +158 -0
  72. package/templates/base/pages/lib/_body-end-islands.tsx +201 -0
  73. package/templates/base/pages/lib/_category-nav.tsx +148 -0
  74. package/templates/base/pages/lib/_category-tree-nav.tsx +104 -0
  75. package/templates/base/pages/lib/_compose-meta-title.ts +29 -0
  76. package/templates/base/pages/lib/_details.tsx +30 -0
  77. package/templates/base/pages/lib/_doc-history-area.tsx +178 -0
  78. package/templates/base/pages/lib/_doc-metainfo-area.tsx +100 -0
  79. package/templates/base/pages/lib/_doc-tags-area.tsx +89 -0
  80. package/templates/base/pages/lib/_extract-headings.ts +81 -0
  81. package/templates/base/pages/lib/_footer-with-defaults.tsx +234 -0
  82. package/templates/base/pages/lib/_frontmatter-preview-data.ts +53 -0
  83. package/templates/base/pages/lib/_head-with-defaults.tsx +113 -0
  84. package/templates/base/pages/lib/_header-with-defaults.tsx +386 -0
  85. package/templates/base/pages/lib/_inline-version-switcher.tsx +84 -0
  86. package/templates/base/pages/lib/_math-block.tsx +63 -0
  87. package/templates/base/pages/lib/_nav-source-docs.ts +68 -0
  88. package/templates/base/pages/lib/_preset-generator.tsx +81 -0
  89. package/templates/base/pages/lib/_search-widget-script.ts +388 -0
  90. package/templates/base/pages/lib/_search-widget.tsx +196 -0
  91. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +176 -0
  92. package/templates/base/pages/lib/_site-tree-nav.tsx +128 -0
  93. package/templates/base/pages/lib/locale-merge.ts +58 -0
  94. package/templates/base/pages/lib/route-enumerators.ts +302 -0
  95. package/templates/base/pages/sitemap.xml.tsx +51 -0
  96. package/templates/base/plugins/connect-adapter.mjs +144 -0
  97. package/templates/base/plugins/copy-public-plugin.mjs +50 -0
  98. package/templates/base/plugins/search-index-plugin.mjs +54 -0
  99. package/templates/base/scripts/run-b4push.sh +102 -0
  100. package/templates/base/src/components/ai-chat-modal.tsx +15 -0
  101. package/templates/base/src/components/client-router-bootstrap.tsx +14 -0
  102. package/templates/base/src/components/content/component-map.ts +25 -0
  103. package/templates/base/src/components/content/content-blockquote.tsx +16 -0
  104. package/templates/base/src/components/content/content-code.tsx +117 -0
  105. package/templates/base/src/components/content/content-link.tsx +83 -0
  106. package/templates/base/src/components/content/content-ol.tsx +19 -0
  107. package/templates/base/src/components/content/content-paragraph.tsx +10 -0
  108. package/templates/base/src/components/content/content-strong.tsx +16 -0
  109. package/templates/base/src/components/content/content-table.tsx +18 -0
  110. package/templates/base/src/components/content/content-ul.tsx +18 -0
  111. package/templates/base/src/components/content/heading-h2.tsx +26 -0
  112. package/templates/base/src/components/content/heading-h3.tsx +26 -0
  113. package/templates/base/src/components/content/heading-h4.tsx +26 -0
  114. package/templates/base/src/components/design-token-panel-bootstrap.tsx +15 -0
  115. package/templates/base/src/components/desktop-sidebar-toggle.tsx +15 -0
  116. package/templates/base/src/components/doc-history.tsx +18 -0
  117. package/templates/base/src/components/html-preview/highlighted-code.tsx +74 -0
  118. package/templates/base/src/components/html-preview/html-preview.tsx +108 -0
  119. package/templates/base/src/components/html-preview/preflight.ts +112 -0
  120. package/templates/base/src/components/html-preview/preview-base.tsx +159 -0
  121. package/templates/base/src/components/image-enlarge.tsx +19 -0
  122. package/templates/base/src/components/mobile-toc.tsx +94 -0
  123. package/templates/base/src/components/preset-generator.tsx +14 -0
  124. package/templates/base/src/components/sidebar-toggle.tsx +98 -0
  125. package/templates/base/src/components/sidebar-tree.tsx +543 -0
  126. package/templates/base/src/components/site-tree-nav.tsx +233 -0
  127. package/templates/base/src/components/theme-toggle.tsx +93 -0
  128. package/templates/base/src/components/toc.tsx +63 -0
  129. package/templates/base/src/components/tree-nav-shared.tsx +71 -0
  130. package/templates/base/src/config/color-scheme-utils.ts +182 -0
  131. package/templates/base/src/config/color-schemes.ts +128 -0
  132. package/templates/base/src/config/frontmatter-preview-defaults.ts +24 -0
  133. package/templates/base/src/config/frontmatter-preview-renderers.tsx +46 -0
  134. package/templates/base/src/config/i18n.ts +225 -0
  135. package/templates/base/src/config/settings-types.ts +162 -0
  136. package/templates/base/src/config/sidebars.ts +66 -0
  137. package/templates/base/src/config/tag-vocabulary-types.ts +39 -0
  138. package/templates/base/src/config/tag-vocabulary.ts +20 -0
  139. package/templates/base/src/hooks/use-active-heading.ts +133 -0
  140. package/templates/base/src/plugins/docs-source-map.ts +103 -0
  141. package/templates/base/src/plugins/hast-utils.ts +10 -0
  142. package/templates/base/src/plugins/rehype-code-title.ts +50 -0
  143. package/templates/base/src/plugins/rehype-heading-links.ts +53 -0
  144. package/templates/base/src/plugins/rehype-image-enlarge.ts +113 -0
  145. package/templates/base/src/plugins/rehype-mermaid.ts +41 -0
  146. package/templates/base/src/plugins/rehype-strip-md-extension.ts +58 -0
  147. package/templates/base/src/plugins/remark-admonitions.ts +99 -0
  148. package/templates/base/src/plugins/remark-resolve-markdown-links.ts +127 -0
  149. package/templates/base/src/plugins/url-utils.ts +4 -0
  150. package/templates/base/src/styles/global.css +1066 -0
  151. package/templates/base/src/types/docs-entry.ts +39 -0
  152. package/templates/base/src/types/heading.ts +5 -0
  153. package/templates/base/src/types/locale.ts +10 -0
  154. package/templates/base/src/utils/base.ts +139 -0
  155. package/templates/base/src/utils/content-files.ts +106 -0
  156. package/templates/base/src/utils/dedent.ts +24 -0
  157. package/templates/base/src/utils/docs.ts +335 -0
  158. package/templates/base/src/utils/git-info.ts +70 -0
  159. package/templates/base/src/utils/github.ts +19 -0
  160. package/templates/base/src/utils/header-right-items.ts +38 -0
  161. package/templates/base/src/utils/nav-scope.ts +63 -0
  162. package/templates/base/src/utils/sidebar.ts +104 -0
  163. package/templates/base/src/utils/slug.ts +10 -0
  164. package/templates/base/src/utils/smart-break.tsx +126 -0
  165. package/templates/base/src/utils/tags.ts +126 -0
  166. package/templates/base/tsconfig.json +36 -0
  167. package/templates/features/bodyFootUtil/files/src/utils/github.ts +19 -0
  168. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +137 -0
  169. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +34 -0
  170. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +376 -0
  171. package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +93 -0
  172. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +586 -0
  173. package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +15 -0
  174. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +99 -0
  175. package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +177 -0
  176. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +50 -0
  177. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +99 -0
  178. package/templates/features/docHistory/files/src/components/doc-history.tsx +598 -0
  179. package/templates/features/docHistory/files/src/types/doc-history.ts +23 -0
  180. package/templates/features/docHistory/files/src/utils/doc-history.ts +180 -0
  181. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +116 -0
  182. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +99 -0
  183. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +101 -0
  184. package/templates/features/docTags/files/pages/docs/tags/index.tsx +86 -0
  185. package/templates/features/i18n/files/pages/[locale]/docs/[...slug].tsx +467 -0
  186. package/templates/features/i18n/files/pages/[locale]/index.tsx +213 -0
  187. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +248 -0
  188. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +74 -0
  189. package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +185 -0
  190. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +126 -0
  191. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +576 -0
  192. package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +428 -0
  193. package/templates/features/tauri/files/src/components/find-bar.tsx +122 -0
  194. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +53 -0
  195. package/templates/features/tauri/files/src/utils/find-in-page.ts +175 -0
  196. package/templates/features/tauri/files/src-tauri/Cargo.toml +14 -0
  197. package/templates/features/tauri/files/src-tauri/build.rs +3 -0
  198. package/templates/features/tauri/files/src-tauri/capabilities/default.json +11 -0
  199. package/templates/features/tauri/files/src-tauri/src/main.rs +250 -0
  200. package/templates/features/tauri/files/src-tauri/tauri.conf.json +25 -0
  201. package/templates/features/tauriDev/files/src-tauri-dev/Cargo.toml +15 -0
  202. package/templates/features/tauriDev/files/src-tauri-dev/build.rs +3 -0
  203. package/templates/features/tauriDev/files/src-tauri-dev/capabilities/default.json +7 -0
  204. package/templates/features/tauriDev/files/src-tauri-dev/frontend/index.html +187 -0
  205. package/templates/features/tauriDev/files/src-tauri-dev/icons/icon.png +0 -0
  206. package/templates/features/tauriDev/files/src-tauri-dev/src/main.rs +995 -0
  207. package/templates/features/tauriDev/files/src-tauri-dev/tauri.conf.json +22 -0
  208. package/templates/features/tauriDev/files/src-tauri-dev/test-launch.sh +65 -0
  209. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +100 -0
  210. package/templates/features/versioning/files/pages/docs/versions.tsx +78 -0
  211. package/templates/features/versioning/files/pages/v/[version]/docs/[...slug].tsx +451 -0
  212. 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,3 @@
1
+ fn main() {
2
+ tauri_build::build()
3
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "identifier": "default",
3
+ "description": "Default capabilities for the main window",
4
+ "windows": ["main"],
5
+ "remote": {
6
+ "urls": ["http://localhost:*/**"]
7
+ },
8
+ "permissions": [
9
+ "core:default"
10
+ ]
11
+ }
@@ -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]
@@ -0,0 +1,3 @@
1
+ fn main() {
2
+ tauri_build::build()
3
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "identifier": "default",
3
+ "description": "Default capabilities for the main window",
4
+ "windows": ["main"],
5
+ "remote": { "urls": ["http://localhost:*/*"] },
6
+ "permissions": ["core:default"]
7
+ }