minecodex 2.0.14 → 2.0.16
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 +9 -0
- package/apps/dashboard/app.js +8 -5
- package/apps/dashboard/assets/data-transfer.js +2 -1
- package/apps/dashboard/transport.js +18 -0
- package/assets/brand/minecodex-icon-selected.svg +4 -0
- package/features/markdown-reading/src/adapters/codex/rich-text.mjs +12 -5
- package/features/markdown-reading/src/adapters/codex/table-actions.mjs +13 -2
- package/features/markdown-reading/src/domain/footnotes.mjs +4 -2
- package/features/markdown-reading/src/injection-source.mjs +2 -0
- package/features/markdown-reading/src/reading.css +3 -3
- package/features/notes/assets/icons/prompt-entry-selected.svg +1 -0
- package/features/notes/assets/icons/prompt-entry.svg +16 -0
- package/features/notes/codex-feature.json +3 -2
- package/features/notes/src/adapters/codex/insertion.mjs +92 -0
- package/features/notes/src/adapters/codex/requests.mjs +7 -0
- package/features/notes/src/frontend/entry.mjs +39 -137
- package/features/notes/src/frontend/icons.json +0 -2
- package/features/notes/src/injection-source.mjs +5 -0
- package/package.json +18 -5
- package/packages/cli/src/commands.mjs +10 -0
- package/packages/codex-adapter/src/renderer/environment.mjs +30 -1
- package/packages/plugin/assets/app.js +116 -0
- package/packages/plugin/assets/prompts.js +437 -0
- package/packages/plugin/plugin.json +16 -0
- package/packages/plugin/src/app.mjs +17 -0
- package/packages/plugin/src/control-client.mjs +41 -0
- package/packages/plugin/src/install.mjs +26 -0
- package/packages/plugin/src/navigation-icons.mjs +8 -0
- package/packages/plugin/src/prompts-app.mjs +45 -0
- package/packages/plugin/src/prompts-client.mjs +16 -0
- package/packages/plugin/src/server.mjs +74 -0
- package/packages/plugin/src/ui-resource.mjs +22 -0
- package/packages/runtime-host/src/codex-injection.mjs +5 -12
- package/packages/runtime-host/src/codex-runtime.mjs +3 -16
- package/packages/ui/src/install.mjs +4 -3
- package/packages/ui/src/native/mcp-app.mjs +32 -0
- package/packages/ui/src/native/mcp-navigation.mjs +52 -0
- package/packages/ui/src/native/mcp-theme.css +4 -0
- package/packages/ui/src/native/mcp-theme.mjs +20 -0
- package/packages/ui/assets/arrow-up-right.svg +0 -13
- package/packages/ui/assets/minecodex-menu-16.svg +0 -7
- package/packages/ui/assets/minecodex-menu-20.svg +0 -7
- package/packages/ui/src/components/account-menu.mjs +0 -88
- package/packages/ui/src/native/account-menu.mjs +0 -21
package/README.md
CHANGED
|
@@ -50,11 +50,18 @@ mcx open
|
|
|
50
50
|
| `mcx uninstall [--purge]` | 移除扩展;默认保留个人数据,带 `--purge` 参数才会清理 |
|
|
51
51
|
| `mcx update` | 更新到最新的稳定版本 |
|
|
52
52
|
| `mcx open` | 打开 MineCodex 控制台 |
|
|
53
|
+
| `mcx plugin install` | 通过官方插件机制注册 Codex 内的 MineCodex 控制台入口 |
|
|
53
54
|
| `mcx status [--json]` | 查看安装与插件运行状态 |
|
|
54
55
|
| `mcx restart` | 重启 Codex 并应用插件开启/关闭状态 |
|
|
55
56
|
|
|
56
57
|
关闭某项功能仅停用对应的扩展服务,不会删除任何个人数据;修改设置后运行 `mcx restart` 即可应用更改。
|
|
57
58
|
|
|
59
|
+
运行 `mcx plugin install` 后,在 Codex 的 Explore/探索中打开 MineCodex,也可按原生
|
|
60
|
+
导航设置固定到侧栏。该本地插件使用官方 MCP Apps 入口与面板,复用原有控制台。
|
|
61
|
+
关闭面板不会停止增强;各项功能继续由 MineCodex 本地服务和注入运行时提供。
|
|
62
|
+
当前正在运行的窗口若未刷新插件目录,下次正常启动后查看。安装命令不会重启 Codex。
|
|
63
|
+
插件卸载使用 `codex plugin remove minecodex@minecodex-local`,不删除增强配置或数据。
|
|
64
|
+
|
|
58
65
|
运行 `mcx uninstall` 会移除名为 MineCodex 的后台项目,并默认保留配置设置、Notes 笔记和 Images 图片索引;只有显式加上 `--purge` 参数,才会清理 MineCodex 产生的本地配置。MineCodex 绝不会删除 `~/.codex/generated_images` 中的原始图片或 Notes 引用的本地文件。
|
|
59
66
|
|
|
60
67
|
## 数据与图片操作
|
|
@@ -106,3 +113,5 @@ MineCodex 是非官方独立扩展项目,与 OpenAI 无关联。项目自有
|
|
|
106
113
|
支持 Prompts 与分类、公网 Sites、模型收藏。模型旧收藏在新版模型滑条首次启动后收录,
|
|
107
114
|
导入收藏在下次启动 Codex 后载入。图片文件、本地站点、账号信息与设备设置不包含在文件中;
|
|
108
115
|
可迁移功能的特殊说明可通过 info 查看。这是手动迁移,不是自动云同步。文件包含提示词正文,请妥善保管。
|
|
116
|
+
|
|
117
|
+
官方插件提供 MineCodex 控制台与 **Prompt** 两个入口,分别使用 MCX 品牌图标与 Prompt 图标。Prompt 仍由 MCX 管理,沿用原有提示词数据;输入框里的快捷提示词继续只填入、不发送。
|
package/apps/dashboard/app.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {diagnosticReport} from "./assets/diagnostics.js";
|
|
2
|
+
import {dashboardFetch} from "./transport.js";
|
|
2
3
|
let savedLocale;
|
|
3
4
|
try { savedLocale = localStorage.getItem("minecodex-language"); } catch { /* 存储不可用时跟随浏览器语言。 */ }
|
|
4
5
|
const locale = ["en", "zh-CN"].includes(savedLocale) ? savedLocale
|
|
@@ -310,7 +311,7 @@ function replaceFeatureInState(featureId, patch) {
|
|
|
310
311
|
async function load() {
|
|
311
312
|
elements.loadError.hidden = true;
|
|
312
313
|
try {
|
|
313
|
-
const response = await
|
|
314
|
+
const response = await dashboardFetch("/api/status", { headers: { "Accept-Language": locale } });
|
|
314
315
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
315
316
|
const fresh = await response.json();
|
|
316
317
|
if (pendingFeatureIds.size && state) fresh.features = fresh.features.map(feature =>
|
|
@@ -339,7 +340,7 @@ elements.pluginList.addEventListener("change", async (event) => {
|
|
|
339
340
|
updatePluginCard(card, state.features.find((feature) => feature.id === featureId));
|
|
340
341
|
elements.saveState.textContent = copy.saving;
|
|
341
342
|
try {
|
|
342
|
-
const response = await
|
|
343
|
+
const response = await dashboardFetch(`/api/features/${encodeURIComponent(featureId)}`, {
|
|
343
344
|
method: "PATCH",
|
|
344
345
|
headers: { "Content-Type": "application/json", "Accept-Language": locale },
|
|
345
346
|
body: JSON.stringify({ enabled: input.checked }),
|
|
@@ -369,7 +370,7 @@ elements.restartButton.addEventListener("click", async () => {
|
|
|
369
370
|
elements.restartButton.disabled = true;
|
|
370
371
|
elements.restartButton.querySelector(".button-label").textContent = copy.restarting;
|
|
371
372
|
try {
|
|
372
|
-
const response = await
|
|
373
|
+
const response = await dashboardFetch("/api/restart", {
|
|
373
374
|
method: "POST",
|
|
374
375
|
headers: { "Content-Type": "application/json", "Accept-Language": locale },
|
|
375
376
|
body: "{}",
|
|
@@ -404,7 +405,7 @@ elements.updateButton.addEventListener("click", async () => {
|
|
|
404
405
|
elements.updateButton.querySelector(".button-label").textContent = copy.checkingUpdates;
|
|
405
406
|
elements.updateResult.hidden = true;
|
|
406
407
|
try {
|
|
407
|
-
const response = await
|
|
408
|
+
const response = await dashboardFetch("/api/update");
|
|
408
409
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
409
410
|
const update = await response.json();
|
|
410
411
|
elements.updateResult.textContent = update.available ? copy.updateAvailable(update.latest) : copy.current;
|
|
@@ -422,7 +423,8 @@ const systemTheme = matchMedia("(prefers-color-scheme: dark)");
|
|
|
422
423
|
let selectedTheme = null;
|
|
423
424
|
try { selectedTheme = localStorage.getItem("minecodex-appearance"); } catch { /* 存储不可用时仍允许本次切换。 */ }
|
|
424
425
|
function applyTheme() {
|
|
425
|
-
const
|
|
426
|
+
const hostDark = window.minecodexHostTheme ? window.minecodexHostTheme === "dark" : systemTheme.matches;
|
|
427
|
+
const dark = selectedTheme === "dark" || (selectedTheme !== "light" && hostDark);
|
|
426
428
|
document.documentElement.dataset.theme = dark ? "dark" : "light";
|
|
427
429
|
elements.themeToggle.setAttribute("aria-label", locale === "zh-CN" ? "外观" : "Appearance");
|
|
428
430
|
for (const button of elements.themeButtons) {
|
|
@@ -447,6 +449,7 @@ elements.languageToggle.addEventListener("click", (event) => {
|
|
|
447
449
|
location.reload();
|
|
448
450
|
});
|
|
449
451
|
systemTheme.addEventListener("change", applyTheme);
|
|
452
|
+
window.addEventListener("minecodex-host-theme", applyTheme);
|
|
450
453
|
document.addEventListener("keydown", () => { document.documentElement.dataset.input = "keyboard"; });
|
|
451
454
|
document.addEventListener("pointerdown", () => { document.documentElement.dataset.input = "pointer"; });
|
|
452
455
|
applyStaticCopy();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { installFeatureTooltips } from './ui-tooltip.js';
|
|
2
|
+
import {dashboardFetch} from '../transport.js';
|
|
2
3
|
const tooltipLifetime = new AbortController();
|
|
3
4
|
const { tooltipSurface } = installFeatureTooltips(tooltipLifetime.signal);
|
|
4
5
|
window.addEventListener('pagehide', event => { if (!event.persisted) tooltipLifetime.abort(); });
|
|
@@ -20,7 +21,7 @@ document.querySelector('#data-close').setAttribute('aria-label', t('关闭', 'Cl
|
|
|
20
21
|
exportButton.querySelector('.button-label').textContent = t('导出', 'Export');
|
|
21
22
|
importButton.querySelector('.button-label').textContent = t('导入', 'Import');
|
|
22
23
|
async function request(url, data) {
|
|
23
|
-
const response = await
|
|
24
|
+
const response = await dashboardFetch(url, data ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) } : {});
|
|
24
25
|
if (!response.ok) throw new Error(String(response.status));
|
|
25
26
|
return response.json();
|
|
26
27
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 浏览器继续走同源 HTTP;官方插件面板通过 MCP 调用同一套本地服务。
|
|
2
|
+
export async function dashboardFetch(url, options = {}) {
|
|
3
|
+
const app = window.minecodexMcpApp;
|
|
4
|
+
if (!app) return fetch(url, options);
|
|
5
|
+
const method = options.method ?? "GET";
|
|
6
|
+
const result = await app.callServerTool({
|
|
7
|
+
name: method === "GET" ? "minecodex_read" : "minecodex_write",
|
|
8
|
+
arguments: {
|
|
9
|
+
route: url,
|
|
10
|
+
method,
|
|
11
|
+
locale: new Headers(options.headers).get("Accept-Language") ?? document.documentElement.lang,
|
|
12
|
+
...(options.body ? { body: JSON.parse(options.body) } : {}),
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
if (result.isError) throw new Error(result.content?.find(item => item.type === "text")?.text ?? "MineCodex request failed");
|
|
16
|
+
const { status, body } = result.structuredContent;
|
|
17
|
+
return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
|
|
18
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-brand-icons.mjs from assets/brand/minecodex-icon.svg. -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><defs><mask id="pickaxe-cutout" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="24" style="mask-type:luminance"><rect width="24" height="24" fill="white"/><g id="pickaxe" transform="translate(6.24 6.24) scale(.48)" fill="black">
|
|
3
|
+
<path d="M15.686 4.314 16.3 3.7a1 1 0 0 1 1.4 0l2.6 2.6a1 1 0 0 1 0 1.4l-.614.614a12.501 12.501 0 0 1 1.356 10.225 1 1 0 0 1-1.751-.119 22 22 0 0 0-3.393-6.319l-.198.199a1 1 0 0 1-1.4 0l-.219.169L6.619 20.38a1 1 0 1 1-3-3l7.912-7.912.169.232a1 1 0 0 1 0-1.4l.198-.197A22 22 0 0 0 5.58 4.71a1 1 0 0 1-.119-1.752 12.5 12.5 0 0 1 10.225 1.356Z"/>
|
|
4
|
+
</g></mask></defs><path fill="currentColor" mask="url(#pickaxe-cutout)" d="M9.3316 2.03768C11.1644 1.54688 13.0268 2.04668 14.356 3.20174C16.0859 2.86512 17.9511 3.36418 19.2935 4.70663C20.6356 6.04882 21.1326 7.91288 20.7964 9.64217C21.9527 10.9716 22.4534 12.8349 21.9625 14.6685C21.4711 16.5025 20.1051 17.8657 18.439 18.439C17.8658 20.1047 16.503 21.47 14.6695 21.9615C12.8356 22.4528 10.9727 21.9519 9.64312 20.7955C7.91371 21.1319 6.04993 20.635 4.70757 19.2926C3.36558 17.9505 2.86659 16.0866 3.20269 14.357C2.04693 13.0273 1.54757 11.1638 2.03863 9.33065C2.52997 7.49759 3.89436 6.13282 5.56011 5.55917C6.13382 3.8935 7.49837 2.52891 9.3316 2.03768Z"/></svg>
|
|
@@ -176,7 +176,8 @@ export function installMarkdownRichText(view, api, native, codex) {
|
|
|
176
176
|
button.dataset.mcxFootnoteRef = this.options.id;
|
|
177
177
|
button.addEventListener('click', (event) => {
|
|
178
178
|
event.preventDefault();
|
|
179
|
-
|
|
179
|
+
// 虚拟段落重新测量后像素高度会变化,使用原生文档锚点恢复阅读位置。
|
|
180
|
+
footnoteReturns.set(this.options.id, view.scrollSnapshot());
|
|
180
181
|
jump(this.options.to);
|
|
181
182
|
});
|
|
182
183
|
sup.append(button);
|
|
@@ -201,8 +202,7 @@ export function installMarkdownRichText(view, api, native, codex) {
|
|
|
201
202
|
const previous = footnoteReturns.get(this.options.id);
|
|
202
203
|
if (previous === undefined) jump(this.options.to);
|
|
203
204
|
else {
|
|
204
|
-
view.
|
|
205
|
-
view.requestMeasure();
|
|
205
|
+
view.dispatch({ effects: previous });
|
|
206
206
|
}
|
|
207
207
|
});
|
|
208
208
|
element.append(back);
|
|
@@ -478,7 +478,14 @@ export function installMarkdownRichText(view, api, native, codex) {
|
|
|
478
478
|
);
|
|
479
479
|
}
|
|
480
480
|
}
|
|
481
|
-
const { definitions: footnotes, references } = collectMarkdownFootnotes(text, protectedRanges);
|
|
481
|
+
const { definitions: footnotes, references, definitionRanges } = collectMarkdownFootnotes(text, protectedRanges);
|
|
482
|
+
// 同名定义沿用最后一条;早期定义不再被误作引用或普通正文。
|
|
483
|
+
for (const note of definitionRanges) {
|
|
484
|
+
if (footnotes.get(note.id).from === note.from) continue;
|
|
485
|
+
replace(note.from, note.to);
|
|
486
|
+
for (let i = doc.lineAt(note.from).number; i <= doc.lineAt(note.to).number; i++)
|
|
487
|
+
hiddenLines.add(doc.line(i).from);
|
|
488
|
+
}
|
|
482
489
|
for (const reference of references)
|
|
483
490
|
replace(
|
|
484
491
|
reference.from,
|
|
@@ -505,7 +512,7 @@ export function installMarkdownRichText(view, api, native, codex) {
|
|
|
505
512
|
const line = doc.line(i);
|
|
506
513
|
if (
|
|
507
514
|
protectedAt(line.from) ||
|
|
508
|
-
|
|
515
|
+
definitionRanges.some((n) => line.from >= n.from && line.from <= n.to)
|
|
509
516
|
)
|
|
510
517
|
continue;
|
|
511
518
|
for (const m of line.text.matchAll(/(?<![\\$])\$(?![\s$])([^$\n]+?)(?<![\s\\])\$(?!\$)/g)) {
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { loadNativeMarkdownRenderer } from './markdown-native.mjs';
|
|
2
2
|
import { captureMarkdownProviders } from './providers.mjs';
|
|
3
3
|
|
|
4
|
+
export function markdownExportAlias(source, name) {
|
|
5
|
+
const start = source.lastIndexOf('export{');
|
|
6
|
+
if (start < 0) return undefined;
|
|
7
|
+
const entries = source.slice(start + 7).split('}')[0].split(',');
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
const [local, alias = local] = entry.trim().split(/\s+as\s+/);
|
|
10
|
+
if (local === name) return alias;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
// 从 Chat Markdown 的公开组件发现悬浮操作栏和控件,不绑定构建 hash。
|
|
5
16
|
export async function loadNativeMarkdownTableActions(native) {
|
|
6
17
|
const url = [...document.querySelectorAll('link[rel="modulepreload"],script[src]')]
|
|
@@ -21,7 +32,7 @@ export async function loadNativeMarkdownTableActions(native) {
|
|
|
21
32
|
const exported = source.slice(source.lastIndexOf('export{'));
|
|
22
33
|
const module = await import(url);
|
|
23
34
|
const publicBinding = (name) => {
|
|
24
|
-
const alias = exported
|
|
35
|
+
const alias = markdownExportAlias(exported, name);
|
|
25
36
|
if (!alias || !module[alias]) throw new Error('native-table-actions-export-unavailable');
|
|
26
37
|
return module[alias];
|
|
27
38
|
};
|
|
@@ -56,7 +67,7 @@ export async function loadNativeMarkdownTableActions(native) {
|
|
|
56
67
|
);
|
|
57
68
|
if (wrapAt < 0 || !wrapPair) throw new Error('native-code-wrap-icons-unavailable');
|
|
58
69
|
const sharedExports = sharedSource.slice(sharedSource.lastIndexOf('export{'));
|
|
59
|
-
const sharedBinding = (name) => shared[sharedExports
|
|
70
|
+
const sharedBinding = (name) => shared[markdownExportAlias(sharedExports, name)];
|
|
60
71
|
const assignment = sharedSource.indexOf(`${wrapPair[1]}=`);
|
|
61
72
|
const initialize = sharedSource
|
|
62
73
|
.slice(sharedSource.lastIndexOf('function ', assignment))
|
|
@@ -8,6 +8,7 @@ export function collectMarkdownFootnotes(text, excludedRanges) {
|
|
|
8
8
|
});
|
|
9
9
|
const excluded = (position) => excludedRanges.some(([from, to]) => position >= from && position < to);
|
|
10
10
|
const definitions = new Map();
|
|
11
|
+
const definitionRanges = [];
|
|
11
12
|
for (let index = 0; index < lines.length; index++) {
|
|
12
13
|
const line = lines[index];
|
|
13
14
|
const match = line.text.match(/^ {0,3}\[\^([^\]]+)\]:\s*(.*)$/);
|
|
@@ -25,6 +26,7 @@ export function collectMarkdownFootnotes(text, excludedRanges) {
|
|
|
25
26
|
refs: [],
|
|
26
27
|
number: 0,
|
|
27
28
|
});
|
|
29
|
+
definitionRanges.push({ id: match[1], from: line.from, to: lines[end].to });
|
|
28
30
|
index = end;
|
|
29
31
|
}
|
|
30
32
|
const notes = [...definitions.values()];
|
|
@@ -37,7 +39,7 @@ export function collectMarkdownFootnotes(text, excludedRanges) {
|
|
|
37
39
|
escaped ||
|
|
38
40
|
!note ||
|
|
39
41
|
excluded(match.index) ||
|
|
40
|
-
|
|
42
|
+
definitionRanges.some((note) => match.index >= note.from && match.index < note.to)
|
|
41
43
|
)
|
|
42
44
|
continue;
|
|
43
45
|
if (!note.number) note.number = ++number;
|
|
@@ -52,5 +54,5 @@ export function collectMarkdownFootnotes(text, excludedRanges) {
|
|
|
52
54
|
}
|
|
53
55
|
// 没有引用的定义仍保留,沿用正文末尾的连续编号。
|
|
54
56
|
for (const note of notes) if (!note.number) note.number = ++number;
|
|
55
|
-
return { definitions, references };
|
|
57
|
+
return { definitions, references, definitionRanges };
|
|
56
58
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from './adapters/codex/reading-runtime.mjs';
|
|
8
8
|
import { loadNativeMarkdownRichText, installMarkdownRichText } from './adapters/codex/rich-text.mjs';
|
|
9
9
|
import {
|
|
10
|
+
markdownExportAlias,
|
|
10
11
|
loadNativeMarkdownTableActions,
|
|
11
12
|
installMarkdownTableActions,
|
|
12
13
|
} from './adapters/codex/table-actions.mjs';
|
|
@@ -32,6 +33,7 @@ export async function createPageScriptSource() {
|
|
|
32
33
|
loadNativeMarkdownToc,
|
|
33
34
|
installMarkdownToc,
|
|
34
35
|
loadNativeMarkdownRenderer,
|
|
36
|
+
markdownExportAlias,
|
|
35
37
|
loadNativeMarkdownTableActions,
|
|
36
38
|
installMarkdownTableActions,
|
|
37
39
|
loadNativeMarkdownRichText,
|
|
@@ -313,18 +313,18 @@
|
|
|
313
313
|
padding-inline-start: calc(var(--mcx-list-indent) + var(--mcx-list-marker-width));
|
|
314
314
|
}
|
|
315
315
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-alert-title {font-weight:var(--font-weight-semibold,600); color:var(--color-text-primary);}
|
|
316
|
-
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-code {
|
|
316
|
+
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-code {padding-block:16px; max-width:100%;}
|
|
317
317
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block {position:relative; min-width:0;}
|
|
318
318
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block [data-block-actions] {top:4px; right:4px; transform:none; transition:opacity 120ms linear;}
|
|
319
319
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block:focus-within [data-block-actions],
|
|
320
320
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-block:hover [data-block-actions] {opacity:1; pointer-events:auto;}
|
|
321
321
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-code-scroll {max-width:100%; scrollbar-width:thin;}
|
|
322
|
-
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-html-block {
|
|
322
|
+
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-html-block {padding-block:1em;}
|
|
323
323
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich mark {background:color-mix(in srgb, var(--color-content-yellow) 25%, transparent); color:inherit; border-radius:2px; padding-inline:2px;}
|
|
324
324
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich kbd {font-family:var(--font-mono,monospace); font-size:.9em; background:var(--color-token-bg-secondary); border:1px solid var(--color-border); border-radius:4px; padding:1px 4px;}
|
|
325
325
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich a,
|
|
326
326
|
.cm-editor[data-minecodex-markdown-reading] :is(.mcx-reading-rich-footnote-ref,.mcx-reading-footnote-body) button {color:var(--color-text-accent, var(--color-token-text-link-foreground)); cursor:var(--cursor-interaction,pointer);}
|
|
327
|
-
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-footnote-ref button {font:inherit; padding:0 2px; background:transparent; border:0;}
|
|
327
|
+
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-rich-footnote-ref button {font:inherit; line-height:1; padding:0 2px; background:transparent; border:0;}
|
|
328
328
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-footnote-body {font-size:.9em; color:var(--color-text-secondary);}
|
|
329
329
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-footnote-body button {margin-inline-start:8px; background:transparent; border:0;}
|
|
330
330
|
.cm-editor[data-minecodex-markdown-reading] .mcx-reading-definition-term {font-weight:var(--font-weight-semibold,600);}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor"><path d="M240,56V200a8,8,0,0,1-8,8H160a24,24,0,0,0-24,23.94,7.9,7.9,0,0,1-5.12,7.55A8,8,0,0,1,120,232a24,24,0,0,0-24-24H24a8,8,0,0,1-8-8V56a8,8,0,0,1,8-8H88a32,32,0,0,1,32,32v87.73a8.17,8.17,0,0,0,7.47,8.25,8,8,0,0,0,8.53-8V80a32,32,0,0,1,32-32h64A8,8,0,0,1,240,56Z"/></svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- @license lucide-static v0.577.0 - ISC -->
|
|
2
|
+
<svg
|
|
3
|
+
class="lucide lucide-book-open"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="20"
|
|
6
|
+
height="20"
|
|
7
|
+
viewBox="-1.5 -1.5 27 27"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
stroke-width="1.8"
|
|
11
|
+
stroke-linecap="round"
|
|
12
|
+
stroke-linejoin="round"
|
|
13
|
+
>
|
|
14
|
+
<path d="M12 7v14" />
|
|
15
|
+
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
|
|
16
|
+
</svg>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"id": "notes",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.12",
|
|
5
5
|
"label": {
|
|
6
6
|
"en": "Prompt Library",
|
|
7
7
|
"zh-CN": "提示词库"
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"icons": "src/frontend/icons.json",
|
|
20
20
|
"styles": "src/frontend/styles.css",
|
|
21
21
|
"previews": "assets/prompt-previews.json"
|
|
22
|
-
}
|
|
22
|
+
},
|
|
23
|
+
"sourceFactory": "src/injection-source.mjs"
|
|
23
24
|
},
|
|
24
25
|
"placement": {
|
|
25
26
|
"order": 20
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// 请求只到达发起 App 所在窗口,不经共享服务队列寻找任意聚焦窗口。
|
|
2
|
+
export function installPromptInsertion(signal, {codex, api, insertText}) {
|
|
3
|
+
const navigation = codex.navigation();
|
|
4
|
+
let remembered = null, busy = false;
|
|
5
|
+
function remember(composer) {
|
|
6
|
+
const location = navigation?.location();
|
|
7
|
+
if (!location || location.path.startsWith('/mcp-app/') || !composer) return;
|
|
8
|
+
remembered = {location, editable:composer.editable, threadId:composer.threadId,
|
|
9
|
+
placement:composer.root.getAttribute('data-composer-placement')};
|
|
10
|
+
}
|
|
11
|
+
function available() {
|
|
12
|
+
const target = remembered?.editable;
|
|
13
|
+
return codex.composer(target ?? null, {purpose:'insertion'});
|
|
14
|
+
}
|
|
15
|
+
function waitForComposer(valid, original, deadline) {
|
|
16
|
+
return new Promise((resolve,reject) => {
|
|
17
|
+
let observer, timer;
|
|
18
|
+
const finish = (error, value) => {observer?.disconnect();clearTimeout(timer);error ? reject(error) : resolve(value);};
|
|
19
|
+
const check = () => {
|
|
20
|
+
if (!valid()) return finish(new Error('Input changed before insertion'));
|
|
21
|
+
const candidates = [...document.querySelectorAll('[contenteditable="true"]')]
|
|
22
|
+
.map(editable => codex.composer(editable,{purpose:'insertion'}))
|
|
23
|
+
.filter(composer => composer && composer.threadId === original.threadId
|
|
24
|
+
&& composer.root.getAttribute('data-composer-placement') === original.placement);
|
|
25
|
+
if (candidates.length > 1) return finish(new Error('Input changed before insertion'));
|
|
26
|
+
if (candidates.length === 1) finish(null,candidates[0]);
|
|
27
|
+
};
|
|
28
|
+
observer = new MutationObserver(check);
|
|
29
|
+
observer.observe(document.documentElement,{childList:true,subtree:true,attributes:true,attributeFilter:['hidden','inert','aria-hidden']});
|
|
30
|
+
timer = setTimeout(() => finish(new Error('No available input in the focused window')),Math.max(0,Math.min(2000,deadline-Date.now())));
|
|
31
|
+
check();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function receive(event) {
|
|
35
|
+
if (event.data?.type !== 'minecodex:insert-prompt' || typeof event.data.promptId !== 'string') return;
|
|
36
|
+
const port = event.ports?.[0];
|
|
37
|
+
const source = codex.mcpAppSource(event.source,{server:'minecodex',pluginId:'minecodex@minecodex-local',resourceUri:'ui://minecodex/prompts.html'});
|
|
38
|
+
if (!source || !port) return;
|
|
39
|
+
if (busy || !document.hasFocus()) {port.postMessage({error:'Input changed before insertion'});port.close();return;}
|
|
40
|
+
busy = true;
|
|
41
|
+
const deadline = Math.min(Date.now() + 4000, event.data.expiresAt ?? Infinity);
|
|
42
|
+
let cancelled = false, navigating = false, restoring = false, expected = navigation?.location(), inserted = false;
|
|
43
|
+
const target = available();
|
|
44
|
+
const original = target ? {editable:target.editable,threadId:target.threadId,placement:target.root.getAttribute('data-composer-placement')} : remembered;
|
|
45
|
+
const onBlur = () => {queueMicrotask(() => {if (!document.hasFocus()) cancelled = true;});};
|
|
46
|
+
const onFocus = event => {
|
|
47
|
+
const editable = event.target.closest?.('[contenteditable="true"]');
|
|
48
|
+
if (editable && editable !== original?.editable) {
|
|
49
|
+
const focused = codex.composer(editable,{purpose:'insertion'});
|
|
50
|
+
if (!restoring || navigation.location().path !== remembered?.location.path
|
|
51
|
+
|| focused?.threadId !== original?.threadId
|
|
52
|
+
|| focused?.root.getAttribute('data-composer-placement') !== original?.placement) cancelled = true;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
window.addEventListener('blur',onBlur);
|
|
56
|
+
document.addEventListener('focusin',onFocus);
|
|
57
|
+
const unsubscribe = navigation?.subscribe(location => {
|
|
58
|
+
if (navigating && location.path === remembered?.location.path) {expected = location;navigating = false;}
|
|
59
|
+
else if (location.key !== expected?.key) cancelled = true;
|
|
60
|
+
});
|
|
61
|
+
const valid = () => !cancelled && !signal.aborted && Date.now() < deadline && document.hasFocus();
|
|
62
|
+
try {
|
|
63
|
+
const state = await api('/api/state');
|
|
64
|
+
const prompt = state.prompts.find(item => item.id === event.data.promptId);
|
|
65
|
+
if (!prompt) throw new Error('Prompt was not found');
|
|
66
|
+
if (!valid() || !source.connected()) throw new Error('Input changed before insertion');
|
|
67
|
+
let composer = target && codex.composer(target.editable,{purpose:'insertion'});
|
|
68
|
+
if (target && !composer) throw new Error('Input changed before insertion');
|
|
69
|
+
if (!composer) {
|
|
70
|
+
if (!original || !remembered || !navigation) throw new Error('No available input in the focused window');
|
|
71
|
+
navigating = restoring = true;
|
|
72
|
+
let timer;
|
|
73
|
+
try {await Promise.race([navigation.navigate(remembered.location.path),new Promise((_,reject) => {
|
|
74
|
+
timer = setTimeout(() => {cancelled = true;reject(new Error('No available input in the focused window'));},2000);
|
|
75
|
+
})]);} finally {clearTimeout(timer);}
|
|
76
|
+
composer = await waitForComposer(valid,original,deadline);
|
|
77
|
+
}
|
|
78
|
+
if (!valid() || composer.threadId !== original?.threadId
|
|
79
|
+
|| composer.root.getAttribute('data-composer-placement') !== original?.placement
|
|
80
|
+
|| (!navigating && navigation && navigation.location().key !== expected?.key)) throw new Error('Input changed before insertion');
|
|
81
|
+
// 校验与同步填入之间不再等待;成功后计数失败不报告为可重试的插入失败。
|
|
82
|
+
insertText(prompt.body,composer.editable);
|
|
83
|
+
inserted = true;
|
|
84
|
+
port.postMessage({ok:true});
|
|
85
|
+
try {await api(`/api/prompts/${encodeURIComponent(prompt.id)}/use`,{method:'POST',body:{action:'insert',success:true}});}
|
|
86
|
+
catch (error) {console.warn('[Prompts] Usage could not be saved',error);}
|
|
87
|
+
} catch (error) {if (!inserted) port.postMessage({error:error.message});}
|
|
88
|
+
finally {unsubscribe?.();window.removeEventListener('blur',onBlur);document.removeEventListener('focusin',onFocus);port.close();busy=false;}
|
|
89
|
+
}
|
|
90
|
+
window.addEventListener('message',receive,{signal});
|
|
91
|
+
return {remember};
|
|
92
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function assertPromptsRequest(route, method = 'GET') {
|
|
2
|
+
const allowed = (route === '/api/state' && method === 'GET')
|
|
3
|
+
|| (['/api/prompts', '/api/categories', '/api/reorder'].includes(route) && method === 'POST')
|
|
4
|
+
|| (/^\/api\/(prompts|categories)\/[a-zA-Z0-9-]+$/.test(route) && ['PATCH','DELETE'].includes(method))
|
|
5
|
+
|| (/^\/api\/prompts\/[a-zA-Z0-9-]+\/use$/.test(route) && method === 'POST');
|
|
6
|
+
if (!allowed) throw new Error('Unsupported Prompts operation');
|
|
7
|
+
}
|