@yufengtadian/freedom-cli 1.12.1 → 1.12.13

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 (47) hide show
  1. package/README.md +31 -14
  2. package/bin/freedom.js +5 -1
  3. package/lib/build.js +49 -8
  4. package/lib/cli.js +107 -80
  5. package/lib/config.js +108 -31
  6. package/lib/shell.js +73 -20
  7. package/lib/theme.js +105 -0
  8. package/lib/tui.js +21 -3
  9. package/lib/update.js +109 -0
  10. package/lib/utils.js +14 -1
  11. package/package.json +1 -1
  12. package/shell/win-x64/freedom-shell.exe +0 -0
  13. package/templates/go/go.mod +6 -0
  14. package/templates/go/go.sum +2 -2
  15. package/templates/go/pkg/freedom/assets/freedom.js +6 -0
  16. package/templates/go/pkg/freedom/assets/index.html +1 -1
  17. package/templates/go/pkg/freedom/backend_proc.go +4 -4
  18. package/templates/go/pkg/freedom/configfile.go +12 -7
  19. package/templates/go/pkg/freedom/freedom.go +27 -11
  20. package/templates/go/pkg/freedom/window_other.go +64 -12
  21. package/templates/go/pkg/freedom/window_windows.go +244 -29
  22. package/templates/go/webview_go/.github/workflows/ci.yaml +62 -0
  23. package/templates/go/webview_go/CHANGELOG.md +15 -0
  24. package/templates/go/webview_go/LICENSE +22 -0
  25. package/templates/go/webview_go/README.md +50 -0
  26. package/templates/go/webview_go/examples/basic/main.go +12 -0
  27. package/templates/go/webview_go/examples/bind/main.go +38 -0
  28. package/templates/go/webview_go/glue.c +36 -0
  29. package/templates/go/webview_go/go.mod +3 -0
  30. package/templates/go/webview_go/go.sum +0 -0
  31. package/templates/go/webview_go/libs/mswebview2/LICENSE +27 -0
  32. package/templates/go/webview_go/libs/mswebview2/include/WebView2.h +23568 -0
  33. package/templates/go/webview_go/libs/mswebview2/include/vendor.go +2 -0
  34. package/templates/go/webview_go/libs/mswebview2/vendor.go +2 -0
  35. package/templates/go/webview_go/libs/mswebview2/version.txt +1 -0
  36. package/templates/go/webview_go/libs/webview/LICENSE +22 -0
  37. package/templates/go/webview_go/libs/webview/include/vendor.go +2 -0
  38. package/templates/go/webview_go/libs/webview/include/webview.h +3871 -0
  39. package/templates/go/webview_go/libs/webview/vendor.go +2 -0
  40. package/templates/go/webview_go/libs/webview/version.txt +1 -0
  41. package/templates/go/webview_go/webview.cc +1 -0
  42. package/templates/go/webview_go/webview.go +379 -0
  43. package/templates/go/webview_go/webview_test.go +50 -0
  44. package/templates/project/freedom.config.js +5 -5
  45. package/templates/project/index.html +45 -5
  46. package/templates/project/src/main.js +149 -8
  47. package/tutorial/tutorial.html +4 -5
@@ -5,20 +5,161 @@
5
5
  // window.minimize() / maximize() / close() 等窗口控制(无边框模式自绘按钮用)
6
6
 
7
7
  // 无边框模式:显示自绘标题栏并绑定按钮。
8
- if (window.freedom && window.freedom.window) {
9
- window.freedom.window.isFrameless().then((frameless) => {
10
- if (frameless) {
11
- document.body.classList.add('freedom-frameless');
12
- window.freedom.window.bindButtons({ min: '#tbMin', max: '#tbMax', close: '#tbClose' });
8
+ // 壳桥接(__freedom_window)注入时机与页面脚本加载先后不定,早期调用可能被 reject 吞掉导致标题栏不显示,
9
+ // 因此先轮询等待桥接就绪(最多 150 次 × 100ms = 15s)再初始化;超时后明确告警而非静默失效。
10
+ function waitForBridge(tries = 150, interval = 100) {
11
+ return new Promise((resolve) => {
12
+ const check = () => {
13
+ if (window.freedom && window.freedom.window && typeof window.__freedom_window === 'function') {
14
+ resolve(true);
15
+ } else if (tries-- > 0) {
16
+ setTimeout(check, interval);
17
+ } else {
18
+ resolve(false);
19
+ }
20
+ };
21
+ check();
22
+ });
23
+ }
24
+
25
+ // 统一执行窗口控制动作:成功返回结果,失败打印告警并返回 undefined。
26
+ // 避免 mac/linux 等平台窗口控制不可用时的"静默无反应"假死体验。
27
+ function runWindow(w, action, fallback) {
28
+ return w[action]().catch((e) => {
29
+ console.warn('[freedom] window action "' + action + '" failed:', e && e.message ? e.message : e);
30
+ if (typeof fallback === 'function') return fallback();
31
+ return undefined;
32
+ });
33
+ }
34
+
35
+ // 同步最大化状态到自绘按钮图标(最大化 ⇄ 还原)与 body 状态类。
36
+ async function syncMaxState(w) {
37
+ const maxBtn = document.getElementById('tbMax');
38
+ if (!maxBtn) return;
39
+ try {
40
+ const isMax = await w.isMaximized();
41
+ // Segoe MDL2:E922=最大化,E923=还原
42
+ maxBtn.innerHTML = isMax ? '\uE923' : '\uE922';
43
+ document.body.classList.toggle('freedom-maximized', !!isMax);
44
+ } catch (e) {
45
+ // 平台不支持状态查询(mac/linux)时保持默认最大化图标
46
+ }
47
+ }
48
+
49
+ async function initFrameless() {
50
+ const ready = await waitForBridge();
51
+ if (!ready) {
52
+ console.warn('[freedom] 桥接 15s 未就绪,无边框标题栏未初始化(窗口仍可正常使用)。');
53
+ return;
54
+ }
55
+ try {
56
+ const frameless = await window.freedom.window.isFrameless();
57
+ if (!frameless) return;
58
+ document.body.classList.add('freedom-frameless');
59
+ const w = window.freedom.window;
60
+ w.bindButtons({ min: '#tbMin', max: '#tbMax', close: '#tbClose' });
61
+
62
+ // 应用图标:从 exe 内嵌图标提取,不依赖 resources 资源文件夹
63
+ const iconUrl = await w.appIcon();
64
+ const iconEl = document.getElementById('tbIcon');
65
+ if (iconUrl && iconEl) {
66
+ iconEl.src = iconUrl;
67
+ iconEl.style.display = 'block';
13
68
  }
14
- }).catch(() => {});
69
+
70
+ // JS 拖动标题栏(保留双击 / 右键事件;非 Windows 平台桥接失败则退化为原生 drag 体验)
71
+ const bar = document.getElementById('titlebar');
72
+ if (bar) {
73
+ bar.addEventListener('mousedown', (e) => {
74
+ if (e.button === 0 && e.target.closest('.tb-btn') === null) {
75
+ runWindow(w, 'startDrag');
76
+ }
77
+ });
78
+ // 双击最大化 / 还原
79
+ bar.addEventListener('dblclick', async (e) => {
80
+ if (e.target.closest('.tb-btn') !== null) return;
81
+ const isMax = await runWindow(w, 'isMaximized', async () => false);
82
+ if (isMax) { await runWindow(w, 'restore', () => runWindow(w, 'toggleMaximize')); }
83
+ else { await runWindow(w, 'maximize', () => runWindow(w, 'toggleMaximize')); }
84
+ syncMaxState(w);
85
+ });
86
+ // 右键菜单
87
+ bar.addEventListener('contextmenu', async (e) => {
88
+ if (e.target.closest('.tb-btn') !== null) return;
89
+ e.preventDefault();
90
+ openTitlebarMenu(e.clientX, e.clientY);
91
+ });
92
+ }
93
+
94
+ // 初始化同步最大化按钮状态;窗口尺寸变化时(含拖拽到屏幕边缘触发的系统最大化)重新同步
95
+ syncMaxState(w);
96
+ window.addEventListener('resize', () => syncMaxState(w));
97
+ } catch (e) {
98
+ /* 桥接异常时保持默认样式 */
99
+ }
100
+ }
101
+
102
+ // 标题栏右键菜单
103
+ const tbMenu = () => document.getElementById('tbMenu');
104
+
105
+ function openTitlebarMenu(x, y) {
106
+ const menu = tbMenu();
107
+ const win = window.freedom.window;
108
+ menu.style.left = x + 'px';
109
+ menu.style.top = y + 'px';
110
+ menu.style.display = 'block';
111
+
112
+ // 根据当前窗口状态动态置灰「最大化 / 还原」
113
+ const updateItems = async () => {
114
+ const isMax = await runWindow(win, 'isMaximized', () => false);
115
+ menu.querySelectorAll('.tb-menu-item').forEach((item) => {
116
+ const action = item.dataset.action;
117
+ const disabled = (isMax && action === 'maximize') || (!isMax && action === 'restore');
118
+ item.style.opacity = disabled ? '0.4' : '1';
119
+ item.style.pointerEvents = disabled ? 'none' : 'auto';
120
+ });
121
+ };
122
+ updateItems();
123
+
124
+ const close = () => {
125
+ menu.style.display = 'none';
126
+ document.removeEventListener('mousedown', onDocDown);
127
+ window.removeEventListener('blur', close);
128
+ };
129
+ const onDocDown = (e) => {
130
+ if (!menu.contains(e.target)) close();
131
+ };
132
+ document.addEventListener('mousedown', onDocDown);
133
+ window.addEventListener('blur', close);
15
134
  }
16
135
 
17
- // 桥接自检。
136
+ function initTitlebarMenu() {
137
+ const menu = tbMenu();
138
+ if (!menu) return;
139
+ menu.addEventListener('click', (e) => {
140
+ const item = e.target.closest('.tb-menu-item');
141
+ if (!item) return;
142
+ const action = item.dataset.action;
143
+ const w = window.freedom.window;
144
+ menu.style.display = 'none';
145
+ if (action === 'close') runWindow(w, 'close');
146
+ else if (action === 'minimize') runWindow(w, 'minimize');
147
+ else if (action === 'maximize') { runWindow(w, 'maximize', () => runWindow(w, 'toggleMaximize')); syncMaxState(w); }
148
+ else if (action === 'restore') { runWindow(w, 'restore', () => runWindow(w, 'toggleMaximize')); syncMaxState(w); }
149
+ });
150
+ }
151
+
152
+ initTitlebarMenu();
153
+ initFrameless();
154
+
155
+ // 桥接自检:__freedom__ping 是壳注入的全局函数,优先直调;
156
+ // 低版本壳不存在时回退经 bridge 路由(壳层 bridge 对 ping 特判兼容)。
18
157
  document.getElementById('pingBtn').addEventListener('click', async () => {
19
158
  const el = document.getElementById('result');
20
159
  try {
21
- const r = await window.freedom.call('__freedom__ping');
160
+ const r = (typeof window.__freedom__ping === 'function')
161
+ ? await window.__freedom__ping()
162
+ : await window.freedom.call('__freedom__ping');
22
163
  el.textContent = '桥接正常:' + r;
23
164
  } catch (e) {
24
165
  el.textContent = '桥接异常:' + e.message;
@@ -48,9 +48,8 @@ npm install</code></pre>
48
48
 
49
49
  <h2>2. 调整标题栏(终端一键切换)</h2>
50
50
  <ul>
51
- <li><code>freedom titlebar native</code> — 保留系统原生标题栏(默认)</li>
52
- <li><code>freedom titlebar hidden</code> — 隐藏标题栏视觉,仅保留 Windows 原生最小化 / 最大化 / 关闭按钮</li>
53
- <li><code>freedom titlebar frameless</code> — 完全无边框,按钮由前端自绘(模板已内置示例)</li>
51
+ <li><code>freedom titlebar native</code> — 保留系统原生标题栏,标题栏图标与 exe 图标一致</li>
52
+ <li><code>freedom titlebar frameless</code> — 完全无边框(默认),标题栏与 Windows 原生最小化 / 最大化 / 关闭按钮均不存在,按钮由前端自绘(模板已内置示例)</li>
54
53
  </ul>
55
54
  <p>也可以直接编辑 <code>freedom.config.js</code> 里的 <code>titlebar</code> 字段。</p>
56
55
 
@@ -58,7 +57,7 @@ npm install</code></pre>
58
57
  <pre><code>freedom build
59
58
  freedom build --platform all # 三平台全量(win + mac + linux)
60
59
  freedom build --platform win-x64 # 仅 Windows
61
- freedom build --platform mac-arm64 # 仅 macOS Apple Silicon
60
+ freedom build --platform darwin-arm64 # 仅 macOS Apple Silicon
62
61
  freedom build --platform linux-x64 # 仅 Linux</code></pre>
63
62
  <p>前端经 Vite 打成单文件 HTML 后写入 <code>dist/resources/</code>,与随包分发的预编译壳二进制合并出可执行文件。不再依赖本机 Go 工具链,任一平台都能一键产出三平台产物。产物以 GUI 子系统运行,不会弹出 cmd 黑窗。</p>
64
63
  <p><code>resources/</code> 内含壳运行时加载的前端页面 <code>index.html</code> 与配置 <code>config.json</code>;声明了 <code>backend</code> 时 <code>backend/</code> 目录会被原样分发到 <code>resources/backend/</code>,壳启动后自动以 <code>resources/</code> 为工作目录拉起后端进程(黑窗同样隐藏)。</p>
@@ -75,7 +74,7 @@ freedom config set outDir dist # 恢复默认 dist/</code></pre>
75
74
  minWidth: 400, minHeight: 300,
76
75
  center: true, // 启动居中
77
76
  debug: false, // 开发者工具
78
- titlebar: 'native', // native | hidden | frameless
77
+ titlebar: 'frameless', // native | frameless
79
78
  outDir: 'dist', // 产物目录:dist(默认)| .(项目根目录)| 任意路径
80
79
  backend: undefined, // 任意语言后端进程,如 { command: 'node', args: ['backend/main.mjs'] }
81
80
  }</code></pre>