minecodex 2.0.55 → 2.1.1
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/README.md +90 -26
- package/apps/dashboard/app.js +20 -14
- package/apps/dashboard/assets/chevron-left.svg +13 -0
- package/apps/dashboard/assets/chevron-right.svg +13 -0
- package/apps/dashboard/assets/feature-showcase.js +185 -0
- package/apps/dashboard/assets/pause.svg +14 -0
- package/apps/dashboard/assets/play.svg +13 -0
- package/apps/dashboard/assets/previews/composer-expand.jpg +0 -0
- package/apps/dashboard/assets/previews/composer-expand.mp4 +0 -0
- package/apps/dashboard/assets/previews/file-navigation.jpg +0 -0
- package/apps/dashboard/assets/previews/file-navigation.mp4 +0 -0
- package/apps/dashboard/assets/previews/files-auto-refresh.jpg +0 -0
- package/apps/dashboard/assets/previews/files-auto-refresh.mp4 +0 -0
- package/apps/dashboard/assets/previews/images.jpg +0 -0
- package/apps/dashboard/assets/previews/images.mp4 +0 -0
- package/apps/dashboard/assets/previews/manifest.json +67 -1
- package/apps/dashboard/assets/previews/markdown-reading.jpg +0 -0
- package/apps/dashboard/assets/previews/markdown-reading.mp4 +0 -0
- package/apps/dashboard/assets/previews/mermaid-diagrams.jpg +0 -0
- package/apps/dashboard/assets/previews/mermaid-diagrams.mp4 +0 -0
- package/apps/dashboard/assets/previews/model-slider.jpg +0 -0
- package/apps/dashboard/assets/previews/model-slider.mp4 +0 -0
- package/apps/dashboard/assets/previews/notes.jpg +0 -0
- package/apps/dashboard/assets/previews/notes.mp4 +0 -0
- package/apps/dashboard/assets/previews/quick-chat.jpg +0 -0
- package/apps/dashboard/assets/previews/quick-chat.mp4 +0 -0
- package/apps/dashboard/assets/previews/session-status.jpg +0 -0
- package/apps/dashboard/assets/previews/session-status.mp4 +0 -0
- package/apps/dashboard/assets/previews/sidebar-focus.jpg +0 -0
- package/apps/dashboard/assets/previews/sidebar-focus.mp4 +0 -0
- package/apps/dashboard/assets/previews/sites.jpg +0 -0
- package/apps/dashboard/assets/previews/sites.mp4 +0 -0
- package/apps/dashboard/assets/previews/usage-meter.jpg +0 -0
- package/apps/dashboard/assets/previews/usage-meter.mp4 +0 -0
- package/apps/dashboard/index.html +26 -2
- package/apps/dashboard/styles.css +58 -39
- package/assets/readme/hero-dark.svg +50 -0
- package/assets/readme/hero.svg +36 -79
- package/features/images/src/adapters/codex/rspack-modules.mjs +9 -3
- package/features/images/src/library-prompt-source.mjs +2 -2
- package/features/notes/codex-feature.json +1 -0
- package/features/quick-chat/src/adapters/codex/renderer.mjs +3 -1
- package/package.json +3 -3
- package/packages/cli/src/config.mjs +1 -2
- package/packages/cli/src/control-server.mjs +1 -1
- package/packages/plugin/assets/app.js +35 -35
- package/packages/runtime-host/src/codex-cdp.mjs +0 -7
- package/packages/runtime-host/src/codex-injection.mjs +50 -2114
- package/packages/runtime-host/src/codex-runtime.mjs +4 -203
- package/packages/runtime-host/src/feature-registry.mjs +10 -91
- package/packages/runtime-host/src/main.mjs +0 -1
- package/packages/ui/src/native/tokens.mjs +0 -28
- package/apps/dashboard/assets/feature-previews.js +0 -206
- package/features/images/src/adapters/codex/cloud-import.mjs +0 -52
- package/features/images/src/backend/cloud-library.mjs +0 -12
- package/features/images/src/chatgpt-image-source-page.mjs +0 -207
- package/features/images/src/chatgpt-image-source.mjs +0 -42
- package/features/images/src/http-server.mjs +0 -487
- package/features/images/src/main.mjs +0 -52
- package/features/images/src/save-as.mjs +0 -69
- package/features/images/web/app.js +0 -1780
- package/features/images/web/i18n.mjs +0 -132
- package/features/images/web/index.html +0 -187
- package/features/images/web/styles.css +0 -1114
- package/packages/runtime-host/src/host-actions.mjs +0 -221
package/README.md
CHANGED
|
@@ -1,30 +1,63 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="./assets/readme/hero-dark.svg">
|
|
4
|
+
<img src="./assets/readme/hero.svg" width="100%" alt="MineCodex:让 Codex 更顺手。13 项按需开启的 Codex Desktop 交互增强,支持 macOS 与 Windows。">
|
|
5
|
+
</picture>
|
|
3
6
|
</p>
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="#安装">安装</a> ·
|
|
10
|
+
<a href="#13-项增强">全部功能</a> ·
|
|
11
|
+
<a href="#它如何工作">工作方式与隐私</a> ·
|
|
12
|
+
<a href="#常见问题">常见问题</a>
|
|
13
|
+
</p>
|
|
6
14
|
|
|
7
|
-
|
|
15
|
+
**MineCodex 是一个给 Codex Desktop 用的增强工具箱。** 它在你熟悉的 Codex 界面里补上一些顺手的小功能:更快地切换模型、一键填入常用提示词、随时看到剩余额度、更舒服地阅读长文档和图表。每一项都可以单独开关,外观沿用 Codex 原生风格,看起来就像 Codex 本来就有。
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
<table>
|
|
18
|
+
<tr>
|
|
19
|
+
<td width="50%"><img src="./apps/dashboard/assets/previews/model-slider.jpg" alt="模型窗口:在输入框旁直接选择模型与思考档位"></td>
|
|
20
|
+
<td width="50%"><img src="./apps/dashboard/assets/previews/usage-meter.jpg" alt="额度环:在侧栏查看五小时与每周剩余额度"></td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<td><b>模型窗口</b> · 在输入框旁选模型和思考档位,常用模型可收藏</td>
|
|
24
|
+
<td><b>额度环</b> · 不打开菜单,也能看到五小时和每周额度</td>
|
|
25
|
+
</tr>
|
|
26
|
+
<tr>
|
|
27
|
+
<td><img src="./apps/dashboard/assets/previews/session-status.jpg" alt="会话状态:查看当前会话的 Token 用量、上下文与生成速度"></td>
|
|
28
|
+
<td><img src="./apps/dashboard/assets/previews/mermaid-diagrams.jpg" alt="Mermaid 图表增强:在 Markdown 预览中查看排版更清楚的图表"></td>
|
|
29
|
+
</tr>
|
|
30
|
+
<tr>
|
|
31
|
+
<td><b>会话状态</b> · 当前会话的用量、上下文和生成速度集中在一处</td>
|
|
32
|
+
<td><b>Mermaid 图表增强</b> · 复杂图表排得更清楚,可缩放、可下载 PNG</td>
|
|
33
|
+
</tr>
|
|
34
|
+
</table>
|
|
10
35
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
36
|
+
## 13 项增强
|
|
37
|
+
|
|
38
|
+
全部功能都可以在控制台单独开关。控制台里每一项都配有真实录屏,可以先看效果再决定是否开启。
|
|
39
|
+
|
|
40
|
+
| 场景 | 功能 | 它帮你解决什么 |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| 选模型与提示词 | **模型窗口** | 模型和思考档位放在一起选,常用模型可搜索、可收藏。 |
|
|
43
|
+
| | **提示词库** | 按分类管理常用提示词,一键填入输入框;不会自动发送,由你检查后再发。 |
|
|
44
|
+
| 掌握用量 | **额度环** | 侧栏常驻五小时与每周剩余额度,点击查看用量详情。 |
|
|
45
|
+
| | **会话状态** | 当前会话的 Token 用量、上下文占用和回复速度集中显示。 |
|
|
46
|
+
| 阅读文档与图表 | **Markdown 阅读增强** | 长文排版更舒服,支持章节目录、脚注跳转、表格展开和代码复制。 |
|
|
47
|
+
| | **Mermaid 图表增强** | 复杂图表布局更清楚,可缩放、可下载 PNG;关闭后恢复原生图表。 |
|
|
48
|
+
| 浏览项目文件 | **文件级联浏览** | 点击文件路径,分列浏览相邻目录并切换文件。 |
|
|
49
|
+
| | **Files 自动刷新** | Agent 新增、删除或重命名文件后,Files 列表自动更新,保留浏览位置。 |
|
|
50
|
+
| 管理素材与网页 | **图片集合** | 浏览本机生成的图片,查看对应提示词,收藏到 Library。 |
|
|
51
|
+
| | **站点收藏** | 自动发现正在运行的本地项目网页,收藏常用网址并快速打开。 |
|
|
52
|
+
| 写作与提问 | **长指令展开编辑** | 写长指令时展开成大尺寸输入框,收起后草稿保持不变。 |
|
|
53
|
+
| | **快捷聊天增强** | 恢复 Quick Chat 快捷键,发送后自动展开,支持历史与窗口固定。 |
|
|
54
|
+
| 整理界面 | **侧栏聚焦** | 一键收起其他任务分组,只看当前分组;再点一次恢复原样。 |
|
|
24
55
|
|
|
25
56
|
## 安装
|
|
26
57
|
|
|
27
|
-
|
|
58
|
+
**准备:** macOS 或 Windows,已安装 Codex Desktop 和 [Node.js](https://nodejs.org/) 22.5 或更高版本。
|
|
59
|
+
|
|
60
|
+
在终端依次运行:
|
|
28
61
|
|
|
29
62
|
```bash
|
|
30
63
|
npm install -g minecodex
|
|
@@ -32,22 +65,53 @@ mcx install
|
|
|
32
65
|
mcx open
|
|
33
66
|
```
|
|
34
67
|
|
|
35
|
-
|
|
68
|
+
`mcx install` 会安装后台服务,完成后询问是否立即重启 Codex;重启后增强即生效。`mcx open` 会在浏览器中打开控制台,你可以在这里按需开关功能,修改后运行 `mcx restart` 应用设置。
|
|
69
|
+
|
|
70
|
+
<details>
|
|
71
|
+
<summary><b>可选:在 Codex 里直接打开控制台和 Prompt 页面</b></summary>
|
|
72
|
+
|
|
73
|
+
<br>
|
|
74
|
+
|
|
75
|
+
1. 先安装 [Codex CLI](https://developers.openai.com/codex/cli/),再运行:
|
|
36
76
|
|
|
37
|
-
|
|
77
|
+
```bash
|
|
78
|
+
mcx plugin install
|
|
79
|
+
```
|
|
38
80
|
|
|
39
|
-
|
|
81
|
+
2. 在 Codex 的插件页点击刷新,从 Explore/探索找到 MineCodex 和 Prompt。
|
|
82
|
+
3. 需要常驻侧边栏时,分别点击它们旁边的“固定到侧边栏”。
|
|
40
83
|
|
|
41
|
-
|
|
84
|
+
</details>
|
|
85
|
+
|
|
86
|
+
### 常用命令
|
|
42
87
|
|
|
43
88
|
| 命令 | 作用 |
|
|
44
89
|
| --- | --- |
|
|
45
90
|
| `mcx open` | 打开控制台 |
|
|
46
|
-
| `mcx update` | 更新到最新版本 |
|
|
47
91
|
| `mcx restart` | 重启 Codex 并应用设置 |
|
|
48
|
-
| `mcx status` |
|
|
49
|
-
| `mcx
|
|
92
|
+
| `mcx status` | 查看安装与运行状态 |
|
|
93
|
+
| `mcx update` | 更新到最新稳定版 |
|
|
94
|
+
| `mcx uninstall` | 卸载,保留个人数据;加 `--purge` 同时删除数据 |
|
|
95
|
+
|
|
96
|
+
## 它如何工作
|
|
97
|
+
|
|
98
|
+
MineCodex 在本机运行一个后台服务。它让 Codex 以只对本机开放(`127.0.0.1`)的调试端口启动,并在运行时把增强加载进 Codex 窗口。
|
|
99
|
+
|
|
100
|
+
- **不修改 Codex 安装文件。** 卸载 MineCodex 后重新打开 Codex,就是原本的样子。
|
|
101
|
+
- **数据留在本机。** 提示词、收藏和设置保存在你的电脑上;除了检查新版本时访问 npm,MineCodex 不上传任何内容。
|
|
102
|
+
- **单项失效不影响整体。** 某个增强出问题时,Codex 和其他增强照常运行。
|
|
103
|
+
|
|
104
|
+
## 常见问题
|
|
105
|
+
|
|
106
|
+
**Codex 更新后某个增强不见了?**
|
|
107
|
+
MineCodex 依赖 Codex 的界面结构。Codex 改版后,个别增强可能暂时失效。先运行 `mcx update` 获取最新版本,再运行 `mcx restart`;问题仍在时,可以在控制台页尾复制诊断信息并[提交 Issue](https://github.com/Vontean/MineCodex/issues)。
|
|
108
|
+
|
|
109
|
+
**会影响我正在进行的会话吗?**
|
|
110
|
+
`mcx restart`(以及安装结束时你确认的重启)会重启 Codex。建议在没有正在运行的任务时操作。
|
|
111
|
+
|
|
112
|
+
**这是 OpenAI 官方产品吗?**
|
|
113
|
+
不是。MineCodex 是非官方的独立项目,与 OpenAI 无关联。
|
|
50
114
|
|
|
51
115
|
## 许可
|
|
52
116
|
|
|
53
|
-
[MIT](LICENSE)
|
|
117
|
+
[MIT](LICENSE)。第三方素材遵循各自的[权利说明](features/model-slider/THIRD_PARTY_NOTICES.md)。
|
package/apps/dashboard/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {installFeatureShowcase} from "./assets/feature-showcase.js";
|
|
2
2
|
import {diagnosticReport} from "./assets/diagnostics.js";
|
|
3
3
|
import {dashboardFetch} from "./transport.js";
|
|
4
4
|
let savedLocale;
|
|
@@ -9,7 +9,8 @@ document.documentElement.lang = locale;
|
|
|
9
9
|
|
|
10
10
|
const messages = {
|
|
11
11
|
en: {
|
|
12
|
-
plugins: "
|
|
12
|
+
plugins: "All enhancements",
|
|
13
|
+
pluginsHint: "Click a card to watch its demo above. Use the switch to turn it on or off.",
|
|
13
14
|
restartTitle: "Restart Codex to apply changes",
|
|
14
15
|
restartCopy: "Your plugin choices are saved. Restart when you are ready.",
|
|
15
16
|
restart: "Restart Codex",
|
|
@@ -50,7 +51,8 @@ const messages = {
|
|
|
50
51
|
failureDetail: "This extension could not be confirmed. Copy diagnostics to help investigate.",
|
|
51
52
|
},
|
|
52
53
|
"zh-CN": {
|
|
53
|
-
plugins: "
|
|
54
|
+
plugins: "全部增强",
|
|
55
|
+
pluginsHint: "点击卡片在上方查看演示,开关控制是否启用。",
|
|
54
56
|
restartTitle: "重启 Codex 以应用更改",
|
|
55
57
|
restartCopy: "插件选择已经保存,可以在准备好后统一重启。",
|
|
56
58
|
restart: "重启 Codex",
|
|
@@ -120,7 +122,7 @@ const elements = {
|
|
|
120
122
|
let state = null;
|
|
121
123
|
let toastTimer = null;
|
|
122
124
|
const pendingFeatureIds = new Set();
|
|
123
|
-
const
|
|
125
|
+
const showcase = installFeatureShowcase(document.querySelector("#feature-showcase"), locale);
|
|
124
126
|
|
|
125
127
|
function applyStaticCopy() {
|
|
126
128
|
document.querySelector("#brand-slogan").textContent = locale === "zh-CN" ? "让 Codex 更顺手。" : "Make Codex work better for you.";
|
|
@@ -136,6 +138,8 @@ function applyStaticCopy() {
|
|
|
136
138
|
elements.restartCopy.textContent = copy.restartCopy;
|
|
137
139
|
elements.restartButton.querySelector(".button-label").textContent = copy.restart;
|
|
138
140
|
elements.pluginsHeading.textContent = copy.plugins;
|
|
141
|
+
document.querySelector("#plugins-hint").textContent = copy.pluginsHint;
|
|
142
|
+
renderEyebrow();
|
|
139
143
|
elements.errorTitle.textContent = copy.loadError;
|
|
140
144
|
elements.errorCopy.textContent = copy.loadErrorCopy;
|
|
141
145
|
elements.retryButton.textContent = copy.retry;
|
|
@@ -208,7 +212,7 @@ function createPluginCard(feature) {
|
|
|
208
212
|
pluginState.append(dot, stateLabel);
|
|
209
213
|
heading.append(icon, label);
|
|
210
214
|
card.append(heading, pluginCopy, pluginState);
|
|
211
|
-
|
|
215
|
+
showcase.attach(card);
|
|
212
216
|
return card;
|
|
213
217
|
}
|
|
214
218
|
|
|
@@ -230,8 +234,6 @@ function updatePluginCard(card, feature) {
|
|
|
230
234
|
input.setAttribute("aria-label", feature.label);
|
|
231
235
|
dot.className = `status-dot ${status.className}`;
|
|
232
236
|
stateLabel.textContent = status.label;
|
|
233
|
-
const previewEntry = card.querySelector(".preview-entry");
|
|
234
|
-
previewEntry?.setAttribute("aria-label", `${locale === "zh-CN" ? "查看效果" : "See how it works"} · ${feature.label}`);
|
|
235
237
|
}
|
|
236
238
|
|
|
237
239
|
function findPluginCard(featureId) {
|
|
@@ -239,8 +241,6 @@ function findPluginCard(featureId) {
|
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
function renderPluginCards(features) {
|
|
242
|
-
// Task 暂停接入,旧版服务返回的卡片也不展示。
|
|
243
|
-
features = features.filter(feature => !["unread-inbox", "hide-upsell-banner"].includes(feature.id));
|
|
244
244
|
for (const child of [...elements.pluginList.children]) {
|
|
245
245
|
if (!child.dataset.featureId) child.remove();
|
|
246
246
|
}
|
|
@@ -256,11 +256,19 @@ function renderPluginCards(features) {
|
|
|
256
256
|
}
|
|
257
257
|
for (const card of [...elements.pluginList.querySelectorAll("[data-feature-id]")]) {
|
|
258
258
|
if (card.dataset.featureId && !featureIds.has(card.dataset.featureId)) {
|
|
259
|
-
|
|
259
|
+
showcase.detach(card);
|
|
260
260
|
card.remove();
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
elements.pluginList.setAttribute("aria-busy", "false");
|
|
264
|
+
showcase.setFeatures(features);
|
|
265
|
+
renderEyebrow();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function renderEyebrow() {
|
|
269
|
+
const total = state?.features?.length;
|
|
270
|
+
const base = locale === "zh-CN" ? "Codex 增强工具箱" : "Enhancements for Codex";
|
|
271
|
+
document.querySelector("#hero-eyebrow-text").textContent = total ? `${base} · ${total}${locale === "zh-CN" ? " 项" : ""}` : base;
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
function renderRuntime() {
|
|
@@ -309,7 +317,7 @@ async function load() {
|
|
|
309
317
|
elements.pluginList.hidden = false;
|
|
310
318
|
render();
|
|
311
319
|
} catch {
|
|
312
|
-
|
|
320
|
+
showcase.setFeatures([]);
|
|
313
321
|
elements.pluginList.hidden = true;
|
|
314
322
|
elements.loadError.hidden = false;
|
|
315
323
|
}
|
|
@@ -429,7 +437,6 @@ function applyTheme() {
|
|
|
429
437
|
let themeTransition = null;
|
|
430
438
|
async function toggleTheme(button) {
|
|
431
439
|
if (themeTransition) return;
|
|
432
|
-
previews.close();
|
|
433
440
|
selectedTheme = document.documentElement.dataset.theme === "dark" ? "light" : "dark";
|
|
434
441
|
if (!window.minecodexMcpApp) {
|
|
435
442
|
try { localStorage.setItem("minecodex-appearance", selectedTheme); } catch { /* 保留内存中的选择。 */ }
|
|
@@ -462,12 +469,11 @@ elements.languageToggle.addEventListener("click", (event) => {
|
|
|
462
469
|
if (choice === locale) return;
|
|
463
470
|
try { localStorage.setItem("minecodex-language", choice); } catch { return; }
|
|
464
471
|
// 浏览器沿用重载;官方 sandbox 在当前上下文内切换,保留主题与通信。
|
|
465
|
-
previews.close();
|
|
466
472
|
if (!window.minecodexMcpApp) { location.reload(); return; }
|
|
467
473
|
locale = choice;
|
|
468
474
|
copy = messages[locale];
|
|
469
475
|
document.documentElement.lang = locale;
|
|
470
|
-
|
|
476
|
+
showcase.setLocale(locale);
|
|
471
477
|
document.querySelector("#language-menu").hidePopover();
|
|
472
478
|
applyStaticCopy();
|
|
473
479
|
applyTheme();
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {previewContent} from './preview-content.js';
|
|
2
|
+
|
|
3
|
+
// 功能演示轮播:按卡片顺序自动播放各功能的真实录屏;点击卡片切换到对应功能。
|
|
4
|
+
export function installFeatureShowcase(root, locale) {
|
|
5
|
+
let zh = locale === 'zh-CN';
|
|
6
|
+
const t = (cn, en) => zh ? cn : en;
|
|
7
|
+
const reduced = matchMedia('(prefers-reduced-motion: reduce)');
|
|
8
|
+
const $ = selector => root.querySelector(selector);
|
|
9
|
+
const media = $('.showcase-media'), video = $('.showcase-video'), poster = $('.showcase-poster'), status = $('.showcase-state');
|
|
10
|
+
const count = $('.showcase-count'), name = $('.showcase-name'), details = $('.showcase-details'), progress = $('.showcase-progress');
|
|
11
|
+
const prevButton = $('[data-showcase="prev"]'), nextButton = $('[data-showcase="next"]'), toggleButton = $('[data-showcase="toggle"]');
|
|
12
|
+
let features = [], index = -1, generation = 0, mediaAbort = null, advanceTimer = 0, frame = 0;
|
|
13
|
+
// userPaused 只由暂停按钮改变;inView / pageVisible 只暂停播放,不改变用户选择。
|
|
14
|
+
let userPaused = reduced.matches, inView = false, pageVisible = !document.hidden, manifest;
|
|
15
|
+
const cards = new Map();
|
|
16
|
+
const urls = new Map();
|
|
17
|
+
|
|
18
|
+
const loadManifest = () => manifest ??= window.minecodexPreviewMedia !== undefined
|
|
19
|
+
? Promise.resolve(window.minecodexPreviewMedia)
|
|
20
|
+
: fetch('/assets/previews/manifest.json').then(r => r.ok ? r.json() : {}).then(value => value && typeof value === 'object' ? value : {}).catch(() => ({}));
|
|
21
|
+
async function resourceUrl(id, kind, value, signal) {
|
|
22
|
+
if (!window.minecodexMcpApp) return `/assets/previews/${value}`;
|
|
23
|
+
const key = `${id}/${kind}`;
|
|
24
|
+
if (urls.has(key)) return urls.get(key);
|
|
25
|
+
const response = await window.minecodexMcpApp.readServerResource({uri:`resource://minecodex/previews/${id}/${kind}`}, {signal});
|
|
26
|
+
const resource = response.contents?.[0];
|
|
27
|
+
if (!resource?.blob) throw new Error('Preview resource unavailable');
|
|
28
|
+
const url = `data:${resource.mimeType};base64,${resource.blob}`;
|
|
29
|
+
urls.set(key, url);
|
|
30
|
+
return url;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const shouldPlay = () => !userPaused && inView && pageVisible;
|
|
34
|
+
const current = () => features[index];
|
|
35
|
+
|
|
36
|
+
function renderControls() {
|
|
37
|
+
const playing = !userPaused;
|
|
38
|
+
toggleButton.querySelector('.ui-icon').className = `ui-icon ${playing ? 'pause-icon' : 'play-icon'}`;
|
|
39
|
+
const label = playing ? t('暂停演示', 'Pause demos') : t('播放演示', 'Play demos');
|
|
40
|
+
toggleButton.setAttribute('aria-label', label); toggleButton.setAttribute('data-minecodex-tooltip', label);
|
|
41
|
+
for (const [button, cn, en] of [[prevButton, '上一个功能', 'Previous feature'], [nextButton, '下一个功能', 'Next feature']]) {
|
|
42
|
+
button.setAttribute('aria-label', t(cn, en)); button.setAttribute('data-minecodex-tooltip', t(cn, en));
|
|
43
|
+
button.disabled = features.length < 2;
|
|
44
|
+
}
|
|
45
|
+
root.setAttribute('aria-label', t('功能演示', 'Feature demos'));
|
|
46
|
+
}
|
|
47
|
+
function renderInfo() {
|
|
48
|
+
const feature = current();
|
|
49
|
+
if (!feature) return;
|
|
50
|
+
const pad = n => String(n).padStart(2, '0');
|
|
51
|
+
count.textContent = `${pad(index + 1)} / ${pad(features.length)}`;
|
|
52
|
+
name.textContent = feature.label;
|
|
53
|
+
details.replaceChildren();
|
|
54
|
+
const labels = [t('出现位置', 'Where'), t('解决问题', 'Problem'), t('操作与效果', 'What it does')];
|
|
55
|
+
previewContent(feature.id, locale).forEach((value, i) => {
|
|
56
|
+
const dt = document.createElement('dt'); dt.textContent = labels[i];
|
|
57
|
+
const dd = document.createElement('dd'); dd.textContent = value;
|
|
58
|
+
details.append(dt, dd);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function renderProgress() {
|
|
62
|
+
if (progress.children.length !== features.length) {
|
|
63
|
+
progress.replaceChildren(...features.map(() => {
|
|
64
|
+
const segment = document.createElement('span'); segment.className = 'showcase-segment';
|
|
65
|
+
segment.append(Object.assign(document.createElement('span'), {className: 'showcase-fill'}));
|
|
66
|
+
return segment;
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
[...progress.children].forEach((segment, i) => {
|
|
70
|
+
segment.classList.toggle('is-done', i < index);
|
|
71
|
+
segment.classList.toggle('is-current', i === index);
|
|
72
|
+
if (i !== index) segment.firstChild.style.transform = i < index ? 'scaleX(1)' : 'scaleX(0)';
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// 当前段进度跟随视频时间;没有视频时停在起点。
|
|
76
|
+
function tick() {
|
|
77
|
+
cancelAnimationFrame(frame);
|
|
78
|
+
const fill = progress.children[index]?.firstChild;
|
|
79
|
+
if (fill && video.duration) fill.style.transform = `scaleX(${Math.min(1, video.currentTime / video.duration)})`;
|
|
80
|
+
if (!video.paused) frame = requestAnimationFrame(tick);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function releaseVideo() {
|
|
84
|
+
cancelAnimationFrame(frame); clearTimeout(advanceTimer);
|
|
85
|
+
video.pause(); video.removeAttribute('src'); video.load(); video.hidden = true;
|
|
86
|
+
}
|
|
87
|
+
function sync() {
|
|
88
|
+
clearTimeout(advanceTimer);
|
|
89
|
+
if (!video.getAttribute('src')) {
|
|
90
|
+
// 没有视频的功能只展示封面或说明,自动轮播时停留后前进。
|
|
91
|
+
if (shouldPlay() && features.length > 1 && media.dataset.state !== 'loading') advanceTimer = setTimeout(() => step(1), 6000);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (shouldPlay()) {
|
|
95
|
+
const ticket = generation;
|
|
96
|
+
video.play().catch(error => {
|
|
97
|
+
// 切换素材或暂停造成的中断,不代表用户选择暂停或宿主拒绝播放。
|
|
98
|
+
if (ticket !== generation || error.name !== 'NotAllowedError' || !shouldPlay()) return;
|
|
99
|
+
userPaused = true; renderControls();
|
|
100
|
+
});
|
|
101
|
+
} else video.pause();
|
|
102
|
+
}
|
|
103
|
+
async function select(nextIndex) {
|
|
104
|
+
if (!features.length) return;
|
|
105
|
+
index = (nextIndex + features.length) % features.length;
|
|
106
|
+
const ticket = ++generation;
|
|
107
|
+
mediaAbort?.abort(); mediaAbort = new AbortController();
|
|
108
|
+
const signal = mediaAbort.signal;
|
|
109
|
+
releaseVideo(); delete video.dataset.ready;
|
|
110
|
+
renderInfo(); renderProgress();
|
|
111
|
+
const feature = current();
|
|
112
|
+
media.dataset.state = 'loading'; status.textContent = t('正在加载演示…', 'Loading demo…');
|
|
113
|
+
poster.hidden = true; poster.removeAttribute('src');
|
|
114
|
+
const resources = (await loadManifest())?.[feature.id];
|
|
115
|
+
if (ticket !== generation) return;
|
|
116
|
+
if (!resources) { media.dataset.state = 'empty'; status.textContent = t('演示视频准备中', 'Demo video coming soon'); sync(); return; }
|
|
117
|
+
const [posterUrl, videoUrl] = await Promise.all(['poster', 'video'].map(kind => resources[kind] ? resourceUrl(feature.id, kind, resources[kind], signal).catch(() => null) : null));
|
|
118
|
+
if (ticket !== generation) return;
|
|
119
|
+
if (posterUrl) { poster.src = posterUrl; poster.hidden = false; }
|
|
120
|
+
media.dataset.state = posterUrl ? 'poster' : 'empty';
|
|
121
|
+
status.textContent = posterUrl ? '' : t('演示暂不可用', 'Demo unavailable');
|
|
122
|
+
if (videoUrl) {
|
|
123
|
+
video.src = videoUrl; video.hidden = false;
|
|
124
|
+
video.addEventListener('loadeddata', () => { if (ticket === generation) { video.dataset.ready = '1'; media.dataset.state = 'video'; poster.hidden = true; } }, {once: true});
|
|
125
|
+
}
|
|
126
|
+
sync();
|
|
127
|
+
}
|
|
128
|
+
const step = delta => select(index + delta);
|
|
129
|
+
|
|
130
|
+
video.addEventListener('play', tick);
|
|
131
|
+
video.addEventListener('ended', () => {
|
|
132
|
+
tick();
|
|
133
|
+
if (shouldPlay()) advanceTimer = setTimeout(() => step(1), 600);
|
|
134
|
+
});
|
|
135
|
+
video.addEventListener('error', () => {
|
|
136
|
+
if (!video.getAttribute('src')) return;
|
|
137
|
+
releaseVideo(); poster.hidden = !poster.getAttribute('src');
|
|
138
|
+
media.dataset.state = poster.hidden ? 'empty' : 'poster';
|
|
139
|
+
status.textContent = poster.hidden ? t('演示暂不可用', 'Demo unavailable') : '';
|
|
140
|
+
sync();
|
|
141
|
+
});
|
|
142
|
+
prevButton.addEventListener('click', () => step(-1));
|
|
143
|
+
nextButton.addEventListener('click', () => step(1));
|
|
144
|
+
toggleButton.addEventListener('click', () => {
|
|
145
|
+
userPaused = !userPaused; renderControls();
|
|
146
|
+
// 播完停在最后一帧时,继续播放从下一个功能开始。
|
|
147
|
+
if (!userPaused && video.ended) step(1); else sync();
|
|
148
|
+
});
|
|
149
|
+
new IntersectionObserver(([entry]) => { inView = entry.isIntersecting && entry.intersectionRatio >= 0.35; sync(); }, {threshold: 0.35}).observe(media);
|
|
150
|
+
document.addEventListener('visibilitychange', () => { pageVisible = !document.hidden; sync(); });
|
|
151
|
+
window.addEventListener('pagehide', () => { mediaAbort?.abort(); releaseVideo(); });
|
|
152
|
+
|
|
153
|
+
function attach(card) {
|
|
154
|
+
const entry = document.createElement('button');
|
|
155
|
+
entry.type = 'button'; entry.className = 'showcase-entry';
|
|
156
|
+
entry.setAttribute('aria-controls', root.id);
|
|
157
|
+
entry.addEventListener('click', () => {
|
|
158
|
+
const target = features.findIndex(feature => feature.id === card.dataset.featureId);
|
|
159
|
+
if (target < 0) return;
|
|
160
|
+
if (target !== index) void select(target);
|
|
161
|
+
root.scrollIntoView({block: 'start', behavior: reduced.matches ? 'auto' : 'smooth'});
|
|
162
|
+
});
|
|
163
|
+
card.append(entry);
|
|
164
|
+
cards.set(card.dataset.featureId, card);
|
|
165
|
+
}
|
|
166
|
+
function detach(card) { cards.delete(card.dataset.featureId); }
|
|
167
|
+
function labelEntries() {
|
|
168
|
+
for (const card of cards.values()) card.querySelector('.showcase-entry')?.setAttribute('aria-label', `${t('在演示中查看', 'Show in demo')} · ${card.querySelector('.plugin-name').textContent || card.dataset.featureId}`);
|
|
169
|
+
}
|
|
170
|
+
function setFeatures(next) {
|
|
171
|
+
const id = current()?.id;
|
|
172
|
+
features = next.map(({id, label}) => ({id, label}));
|
|
173
|
+
root.hidden = features.length === 0;
|
|
174
|
+
renderControls(); labelEntries();
|
|
175
|
+
const kept = features.findIndex(feature => feature.id === id);
|
|
176
|
+
if (kept >= 0) { index = kept; renderInfo(); renderProgress(); }
|
|
177
|
+
else void select(0);
|
|
178
|
+
}
|
|
179
|
+
function setLocale(next) {
|
|
180
|
+
locale = next; zh = locale === 'zh-CN';
|
|
181
|
+
renderControls(); renderInfo(); labelEntries();
|
|
182
|
+
}
|
|
183
|
+
renderControls();
|
|
184
|
+
return {attach, detach, setFeatures, setLocale};
|
|
185
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="currentColor"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<rect x="14" y="4" width="4" height="16" rx="1" />
|
|
13
|
+
<rect x="6" y="4" width="4" height="16" rx="1" />
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3
|
+
width="24"
|
|
4
|
+
height="24"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
fill="none"
|
|
7
|
+
stroke="currentColor"
|
|
8
|
+
stroke-width="2"
|
|
9
|
+
stroke-linecap="round"
|
|
10
|
+
stroke-linejoin="round"
|
|
11
|
+
>
|
|
12
|
+
<polygon points="6 3 20 12 6 21 6 3" />
|
|
13
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1,67 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"composer-expand": {
|
|
3
|
+
"video": "composer-expand.mp4",
|
|
4
|
+
"poster": "composer-expand.jpg",
|
|
5
|
+
"version": "26.1103.60933"
|
|
6
|
+
},
|
|
7
|
+
"file-navigation": {
|
|
8
|
+
"video": "file-navigation.mp4",
|
|
9
|
+
"poster": "file-navigation.jpg",
|
|
10
|
+
"version": "26.1103.60933"
|
|
11
|
+
},
|
|
12
|
+
"files-auto-refresh": {
|
|
13
|
+
"video": "files-auto-refresh.mp4",
|
|
14
|
+
"poster": "files-auto-refresh.jpg",
|
|
15
|
+
"version": "26.1103.60933"
|
|
16
|
+
},
|
|
17
|
+
"images": {
|
|
18
|
+
"video": "images.mp4",
|
|
19
|
+
"poster": "images.jpg",
|
|
20
|
+
"version": "26.1103.60933"
|
|
21
|
+
},
|
|
22
|
+
"markdown-reading": {
|
|
23
|
+
"video": "markdown-reading.mp4",
|
|
24
|
+
"poster": "markdown-reading.jpg",
|
|
25
|
+
"version": "26.1103.60933"
|
|
26
|
+
},
|
|
27
|
+
"mermaid-diagrams": {
|
|
28
|
+
"video": "mermaid-diagrams.mp4",
|
|
29
|
+
"poster": "mermaid-diagrams.jpg",
|
|
30
|
+
"version": "26.1103.60933"
|
|
31
|
+
},
|
|
32
|
+
"model-slider": {
|
|
33
|
+
"video": "model-slider.mp4",
|
|
34
|
+
"poster": "model-slider.jpg",
|
|
35
|
+
"version": "26.1103.60933"
|
|
36
|
+
},
|
|
37
|
+
"notes": {
|
|
38
|
+
"video": "notes.mp4",
|
|
39
|
+
"poster": "notes.jpg",
|
|
40
|
+
"version": "26.1103.60933"
|
|
41
|
+
},
|
|
42
|
+
"quick-chat": {
|
|
43
|
+
"video": "quick-chat.mp4",
|
|
44
|
+
"poster": "quick-chat.jpg",
|
|
45
|
+
"version": "26.1103.60933"
|
|
46
|
+
},
|
|
47
|
+
"session-status": {
|
|
48
|
+
"video": "session-status.mp4",
|
|
49
|
+
"poster": "session-status.jpg",
|
|
50
|
+
"version": "26.1103.60933"
|
|
51
|
+
},
|
|
52
|
+
"sidebar-focus": {
|
|
53
|
+
"video": "sidebar-focus.mp4",
|
|
54
|
+
"poster": "sidebar-focus.jpg",
|
|
55
|
+
"version": "26.1103.60933"
|
|
56
|
+
},
|
|
57
|
+
"sites": {
|
|
58
|
+
"video": "sites.mp4",
|
|
59
|
+
"poster": "sites.jpg",
|
|
60
|
+
"version": "26.1103.60933"
|
|
61
|
+
},
|
|
62
|
+
"usage-meter": {
|
|
63
|
+
"video": "usage-meter.mp4",
|
|
64
|
+
"poster": "usage-meter.jpg",
|
|
65
|
+
"version": "26.1103.60933"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|