minecodex 1.0.82 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/features/images/src/http-server.mjs +2 -1
- package/features/images/src/save-as.mjs +37 -8
- package/features/images/web/app.js +91 -80
- package/features/images/web/i18n.mjs +127 -0
- package/features/images/web/index.html +33 -33
- package/features/model-slider/README.md +7 -4
- package/features/notes/README.md +1 -1
- package/features/notes/src/http-server.mjs +31 -0
- package/features/notes/web/app.js +13 -306
- package/features/notes/web/file-icons.mjs +68 -0
- package/features/notes/web/i18n.mjs +237 -0
- package/features/notes/web/todo-drag.mjs +18 -0
- package/package.json +4 -2
- package/packages/cli/src/commands.mjs +3 -3
- package/packages/cli/src/npm-adapter.mjs +12 -4
- package/packages/cli/src/paths.mjs +36 -1
- package/packages/cli/src/platform.mjs +517 -0
- package/packages/cli/src/runtime-manager.mjs +9 -2
- package/packages/runtime-host/README.md +5 -3
- package/packages/runtime-host/src/codex-cdp.mjs +153 -0
- package/packages/runtime-host/src/codex-design-contract.mjs +116 -0
- package/packages/runtime-host/src/codex-injection.mjs +4586 -0
- package/packages/runtime-host/src/codex-runtime.mjs +63 -4930
- package/packages/runtime-host/src/host-actions.mjs +221 -0
- package/packages/runtime-host/src/main.mjs +17 -9
|
@@ -1,226 +1,12 @@
|
|
|
1
|
+
const submitShortcut = /Mac|iPhone|iPad/.test(navigator.platform) ? "⌘↩" : "Ctrl+Enter";
|
|
1
2
|
const params = new URL(document.baseURI).searchParams;
|
|
2
|
-
|
|
3
|
+
import { setLocale, getLocale, t, localizedErrorMessage } from "./i18n.mjs";
|
|
4
|
+
import { CODEX_COLOR_FILE_ICONS, codexFileIconKind } from "./file-icons.mjs";
|
|
5
|
+
import { todoDropPosition, reorderedTodoIds } from "./todo-drag.mjs";
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (candidate.toLowerCase().startsWith("zh")) return "zh-CN";
|
|
7
|
-
return SUPPORTED_LOCALES.has(candidate) ? candidate : "en";
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const MESSAGES = Object.freeze({
|
|
11
|
-
en: {
|
|
12
|
-
requestFailed: ({ status }) => `Request failed (${status})`,
|
|
13
|
-
temporarilyUnavailable: "CodexNotes is temporarily unavailable",
|
|
14
|
-
unavailable: "CodexNotes is unavailable",
|
|
15
|
-
retry: "Retry",
|
|
16
|
-
hostUnavailable: "Codex host is not connected",
|
|
17
|
-
hostTimeout: "Codex did not respond",
|
|
18
|
-
hostActionFailed: "Host action failed",
|
|
19
|
-
restoreTodo: "Restore Todo",
|
|
20
|
-
completeTodo: "Complete Todo",
|
|
21
|
-
edit: "Edit",
|
|
22
|
-
delete: "Delete",
|
|
23
|
-
editTodo: "Edit Todo",
|
|
24
|
-
newTodo: "New Todo",
|
|
25
|
-
todoText: "Todo text",
|
|
26
|
-
todoPlaceholder: "What do you want to remember?",
|
|
27
|
-
saveTodo: "Save Todo",
|
|
28
|
-
addTodo: "Add Todo",
|
|
29
|
-
cancel: "Cancel",
|
|
30
|
-
editPrompt: "Edit Prompt",
|
|
31
|
-
newPrompt: "New Prompt",
|
|
32
|
-
optionalTitle: "Optional title",
|
|
33
|
-
promptTitle: "Prompt title",
|
|
34
|
-
promptBody: "Prompt body",
|
|
35
|
-
promptPlaceholder: "Write a reusable prompt…",
|
|
36
|
-
savePrompt: "Save Prompt",
|
|
37
|
-
addPrompt: "Add Prompt",
|
|
38
|
-
insertPrompt: "Insert Prompt",
|
|
39
|
-
morePromptActions: "More prompt actions",
|
|
40
|
-
star: "Star",
|
|
41
|
-
unstar: "Unstar",
|
|
42
|
-
showInFolder: "Show in folder",
|
|
43
|
-
attachToChat: "Attach to chat",
|
|
44
|
-
viewAll: "View all",
|
|
45
|
-
todo: "Todo",
|
|
46
|
-
noTodo: "No Todo yet",
|
|
47
|
-
prompts: "Prompts",
|
|
48
|
-
noPrompts: "No prompts yet",
|
|
49
|
-
files: "Files",
|
|
50
|
-
pinFiles: "Pin files",
|
|
51
|
-
noPinnedFiles: "No pinned files",
|
|
52
|
-
active: "Active",
|
|
53
|
-
add: "Add",
|
|
54
|
-
nothingActive: "Nothing active",
|
|
55
|
-
completed: "Completed",
|
|
56
|
-
deleteAll: "Delete all",
|
|
57
|
-
deleteCompletedTitle: "Delete all completed Todo",
|
|
58
|
-
noCompletedTodo: "No completed Todo",
|
|
59
|
-
starred: "Starred",
|
|
60
|
-
noStarredPrompts: "No starred prompts",
|
|
61
|
-
pinnedFiles: "Pinned files",
|
|
62
|
-
loading: "Loading CodexNotes…",
|
|
63
|
-
insertedAtCaret: "Inserted at the Composer caret",
|
|
64
|
-
addedToComposer: "Added to Composer",
|
|
65
|
-
insertedFilePath: "Inserted file path",
|
|
66
|
-
filesPinned: ({ count }) => `${count} file${count === 1 ? "" : "s"} pinned`,
|
|
67
|
-
alreadyPinned: "Already pinned",
|
|
68
|
-
todoEmpty: "Todo text cannot be empty",
|
|
69
|
-
promptEmpty: "Prompt body cannot be empty",
|
|
70
|
-
editorItemMissing: "This item no longer exists.",
|
|
71
|
-
close: "Close",
|
|
72
|
-
invalidRequest: "The request is invalid",
|
|
73
|
-
invalidContentType: "The request must use JSON",
|
|
74
|
-
invalidJson: "The request body is not valid JSON",
|
|
75
|
-
bodyTooLarge: "The request is too large",
|
|
76
|
-
notFound: "The item was not found",
|
|
77
|
-
invalidOrder: "The Todo order is no longer current",
|
|
78
|
-
invalidUse: "The requested action could not be recorded",
|
|
79
|
-
fileUnavailable: "The file is unavailable",
|
|
80
|
-
fileMissing: "The source file no longer exists",
|
|
81
|
-
directoryNotAllowed: "Folders cannot be pinned; choose regular files",
|
|
82
|
-
pathNotAbsolute: "Only absolute paths can be pinned",
|
|
83
|
-
pickerUnavailable: "The system file picker is unavailable",
|
|
84
|
-
pickerFailed: "The system file picker could not be opened",
|
|
85
|
-
revealUnavailable: "Show in folder is unavailable on this platform",
|
|
86
|
-
revealFailed: "The file could not be shown in the system file manager",
|
|
87
|
-
previewUnavailable: "This file does not have an image preview",
|
|
88
|
-
previewTooLarge: "The image is too large to preview",
|
|
89
|
-
internalError: "CodexNotes could not complete the request",
|
|
90
|
-
actionNotAllowed: "This action is not available",
|
|
91
|
-
},
|
|
92
|
-
"zh-CN": {
|
|
93
|
-
requestFailed: ({ status }) => `请求失败(${status})`,
|
|
94
|
-
temporarilyUnavailable: "CodexNotes 暂时不可用",
|
|
95
|
-
unavailable: "CodexNotes 不可用",
|
|
96
|
-
retry: "重试",
|
|
97
|
-
hostUnavailable: "尚未连接 Codex 宿主",
|
|
98
|
-
hostTimeout: "Codex 未响应",
|
|
99
|
-
hostActionFailed: "宿主操作失败",
|
|
100
|
-
restoreTodo: "恢复待办",
|
|
101
|
-
completeTodo: "完成待办",
|
|
102
|
-
edit: "编辑",
|
|
103
|
-
delete: "删除",
|
|
104
|
-
editTodo: "编辑待办",
|
|
105
|
-
newTodo: "新建待办",
|
|
106
|
-
todoText: "待办内容",
|
|
107
|
-
todoPlaceholder: "你想记住什么?",
|
|
108
|
-
saveTodo: "保存待办",
|
|
109
|
-
addTodo: "添加待办",
|
|
110
|
-
cancel: "取消",
|
|
111
|
-
editPrompt: "编辑提示词",
|
|
112
|
-
newPrompt: "新建提示词",
|
|
113
|
-
optionalTitle: "可选标题",
|
|
114
|
-
promptTitle: "提示词标题",
|
|
115
|
-
promptBody: "提示词正文",
|
|
116
|
-
promptPlaceholder: "编写一条可复用的提示词…",
|
|
117
|
-
savePrompt: "保存提示词",
|
|
118
|
-
addPrompt: "添加提示词",
|
|
119
|
-
insertPrompt: "插入提示词",
|
|
120
|
-
morePromptActions: "更多提示词操作",
|
|
121
|
-
star: "收藏",
|
|
122
|
-
unstar: "取消收藏",
|
|
123
|
-
showInFolder: "在文件夹中显示",
|
|
124
|
-
attachToChat: "添加到对话",
|
|
125
|
-
viewAll: "查看全部",
|
|
126
|
-
todo: "待办",
|
|
127
|
-
noTodo: "暂无待办",
|
|
128
|
-
prompts: "提示词",
|
|
129
|
-
noPrompts: "暂无提示词",
|
|
130
|
-
files: "文件",
|
|
131
|
-
pinFiles: "固定文件",
|
|
132
|
-
noPinnedFiles: "暂无固定文件",
|
|
133
|
-
active: "进行中",
|
|
134
|
-
add: "添加",
|
|
135
|
-
nothingActive: "暂无进行中的待办",
|
|
136
|
-
completed: "已完成",
|
|
137
|
-
deleteAll: "全部删除",
|
|
138
|
-
deleteCompletedTitle: "删除全部已完成待办",
|
|
139
|
-
noCompletedTodo: "暂无已完成待办",
|
|
140
|
-
starred: "已收藏",
|
|
141
|
-
noStarredPrompts: "暂无收藏的提示词",
|
|
142
|
-
pinnedFiles: "已固定文件",
|
|
143
|
-
loading: "正在加载 CodexNotes…",
|
|
144
|
-
insertedAtCaret: "已插入到消息输入框光标处",
|
|
145
|
-
addedToComposer: "已添加到消息输入框",
|
|
146
|
-
insertedFilePath: "已插入文件路径",
|
|
147
|
-
filesPinned: ({ count }) => `已固定 ${count} 个文件`,
|
|
148
|
-
alreadyPinned: "文件已固定",
|
|
149
|
-
todoEmpty: "待办内容不能为空",
|
|
150
|
-
promptEmpty: "提示词正文不能为空",
|
|
151
|
-
editorItemMissing: "该项目已不存在。",
|
|
152
|
-
close: "关闭",
|
|
153
|
-
invalidRequest: "请求无效",
|
|
154
|
-
invalidContentType: "请求必须使用 JSON",
|
|
155
|
-
invalidJson: "请求正文不是有效的 JSON",
|
|
156
|
-
bodyTooLarge: "请求内容过大",
|
|
157
|
-
notFound: "找不到该项目",
|
|
158
|
-
invalidOrder: "待办顺序已发生变化",
|
|
159
|
-
invalidUse: "无法记录这次操作",
|
|
160
|
-
fileUnavailable: "文件不可用",
|
|
161
|
-
fileMissing: "源文件已不存在",
|
|
162
|
-
directoryNotAllowed: "不能固定文件夹,请选择普通文件",
|
|
163
|
-
pathNotAbsolute: "只能固定绝对路径",
|
|
164
|
-
pickerUnavailable: "系统文件选择器不可用",
|
|
165
|
-
pickerFailed: "无法打开系统文件选择器",
|
|
166
|
-
revealUnavailable: "当前平台不支持在文件夹中显示",
|
|
167
|
-
revealFailed: "无法在系统文件管理器中显示该文件",
|
|
168
|
-
previewUnavailable: "该文件没有图片预览",
|
|
169
|
-
previewTooLarge: "图片过大,无法预览",
|
|
170
|
-
internalError: "CodexNotes 无法完成请求",
|
|
171
|
-
actionNotAllowed: "当前操作不可用",
|
|
172
|
-
},
|
|
173
|
-
});
|
|
7
|
+
setLocale(params.get("locale") ?? navigator.languages?.[0] ?? navigator.language);
|
|
8
|
+
document.documentElement.lang = getLocale();
|
|
174
9
|
|
|
175
|
-
let locale = normalizeLocale(params.get("locale") ?? navigator.languages?.[0] ?? navigator.language);
|
|
176
|
-
document.documentElement.lang = locale;
|
|
177
|
-
|
|
178
|
-
function t(key, params = {}) {
|
|
179
|
-
const value = MESSAGES[locale]?.[key] ?? MESSAGES.en[key] ?? key;
|
|
180
|
-
return typeof value === "function" ? value(params) : value;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const ERROR_MESSAGE_KEYS = Object.freeze({
|
|
184
|
-
ACTION_NOT_ALLOWED: "actionNotAllowed",
|
|
185
|
-
BODY_TOO_LARGE: "bodyTooLarge",
|
|
186
|
-
DIRECTORY_NOT_ALLOWED: "directoryNotAllowed",
|
|
187
|
-
EMPTY_BODY: "invalidRequest",
|
|
188
|
-
FILE_MISSING: "fileMissing",
|
|
189
|
-
FILE_UNAVAILABLE: "fileUnavailable",
|
|
190
|
-
HOST_ACTION_FAILED: "hostActionFailed",
|
|
191
|
-
HOST_NOT_ALLOWED: "actionNotAllowed",
|
|
192
|
-
HOST_TIMEOUT: "hostTimeout",
|
|
193
|
-
HOST_UNAVAILABLE: "hostUnavailable",
|
|
194
|
-
INTERNAL_ERROR: "internalError",
|
|
195
|
-
INVALID_BODY: "invalidRequest",
|
|
196
|
-
INVALID_CHANGES: "invalidRequest",
|
|
197
|
-
INVALID_COMPLETED: "invalidRequest",
|
|
198
|
-
INVALID_CONTENT_TYPE: "invalidContentType",
|
|
199
|
-
INVALID_ID: "invalidRequest",
|
|
200
|
-
INVALID_JSON: "invalidJson",
|
|
201
|
-
INVALID_ORDER: "invalidOrder",
|
|
202
|
-
INVALID_PATHS: "invalidRequest",
|
|
203
|
-
INVALID_REQUEST: "invalidRequest",
|
|
204
|
-
INVALID_SURFACE: "invalidRequest",
|
|
205
|
-
INVALID_ORIGIN: "actionNotAllowed",
|
|
206
|
-
INVALID_STAR: "invalidRequest",
|
|
207
|
-
INVALID_TITLE: "invalidRequest",
|
|
208
|
-
INVALID_USE: "invalidUse",
|
|
209
|
-
NOT_FOUND: "notFound",
|
|
210
|
-
ORIGIN_NOT_ALLOWED: "actionNotAllowed",
|
|
211
|
-
PATH_NOT_ABSOLUTE: "pathNotAbsolute",
|
|
212
|
-
PICKER_FAILED: "pickerFailed",
|
|
213
|
-
PICKER_UNAVAILABLE: "pickerUnavailable",
|
|
214
|
-
PREVIEW_TOO_LARGE: "previewTooLarge",
|
|
215
|
-
PREVIEW_UNAVAILABLE: "previewUnavailable",
|
|
216
|
-
REVEAL_FAILED: "revealFailed",
|
|
217
|
-
REVEAL_UNAVAILABLE: "revealUnavailable",
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
function localizedErrorMessage(code, fallback, params = {}) {
|
|
221
|
-
const key = ERROR_MESSAGE_KEYS[code];
|
|
222
|
-
return key ? t(key, params) : fallback;
|
|
223
|
-
}
|
|
224
10
|
|
|
225
11
|
const requestedSurface = params.get("surface") === "notes" ? "prompts" : params.get("surface");
|
|
226
12
|
const surface = new Set(["summary", "todo", "prompts", "files", "editor"]).has(requestedSurface)
|
|
@@ -241,44 +27,6 @@ const DOMAIN_ICON_BY_SURFACE = Object.freeze({
|
|
|
241
27
|
prompts: "message-square-quote",
|
|
242
28
|
files: "paperclip",
|
|
243
29
|
});
|
|
244
|
-
const CODEX_FILE_ICON_BY_EXTENSION = new Map([
|
|
245
|
-
["ts", "typescript"],
|
|
246
|
-
["tsx", "react"], ["jsx", "react"],
|
|
247
|
-
["js", "javascript"], ["mjs", "javascript"], ["cjs", "javascript"], ["hs", "javascript"],
|
|
248
|
-
["py", "python"],
|
|
249
|
-
["java", "java"],
|
|
250
|
-
["rs", "rust"],
|
|
251
|
-
["php", "php"],
|
|
252
|
-
["css", "css"], ["scss", "css"], ["less", "css"], ["sass", "css"],
|
|
253
|
-
["cpp", "cplusplus"], ["cxx", "cplusplus"], ["cc", "cplusplus"], ["c", "cplusplus"],
|
|
254
|
-
["hpp", "cplusplus"], ["hh", "cplusplus"], ["h", "cplusplus"],
|
|
255
|
-
["rb", "code"], ["go", "code"], ["kt", "code"], ["swift", "code"], ["m", "code"],
|
|
256
|
-
["mm", "code"], ["cs", "code"], ["sql", "code"],
|
|
257
|
-
["json", "json"], ["jsonc", "json"],
|
|
258
|
-
["md", "document"], ["mdx", "document"], ["markdown", "document"],
|
|
259
|
-
["mkd", "document"], ["mdown", "document"],
|
|
260
|
-
["html", "html"], ["htm", "html"],
|
|
261
|
-
["yaml", "file"], ["yml", "file"],
|
|
262
|
-
["toml", "toml"],
|
|
263
|
-
["xml", "document"],
|
|
264
|
-
["csv", "spreadsheet"], ["tsv", "spreadsheet"], ["xls", "spreadsheet"],
|
|
265
|
-
["xlsm", "spreadsheet"], ["xlsx", "spreadsheet"],
|
|
266
|
-
["doc", "artifact-document"], ["docx", "artifact-document"],
|
|
267
|
-
["ipynb", "notebook"],
|
|
268
|
-
["ppt", "presentation"], ["pptx", "presentation"],
|
|
269
|
-
["sh", "shell"], ["bash", "shell"], ["zsh", "shell"], ["fish", "shell"], ["ps1", "shell"],
|
|
270
|
-
["dockerfile", "terminal"],
|
|
271
|
-
["env", "document"], ["dotenv", "document"], ["gitignore", "document"], ["lock", "document"],
|
|
272
|
-
["png", "image"], ["jpg", "image"], ["jpeg", "image"], ["gif", "image"],
|
|
273
|
-
["webp", "image"], ["bmp", "image"], ["svg", "image"], ["ico", "image"],
|
|
274
|
-
["build", "build"], ["bazel", "build"], ["bzl", "build"], ["ninja", "build"],
|
|
275
|
-
["gradle", "build"], ["mk", "build"], ["makefile", "build"],
|
|
276
|
-
["sha", "hashes"], ["sha1", "hashes"], ["sha256", "hashes"],
|
|
277
|
-
["md5", "hashes"], ["checksum", "hashes"], ["sum", "hashes"],
|
|
278
|
-
["pdf", "pdf"],
|
|
279
|
-
["zip", "folder"], ["gz", "folder"], ["tgz", "folder"], ["tar", "folder"],
|
|
280
|
-
]);
|
|
281
|
-
const CODEX_COLOR_FILE_ICONS = new Set(["artifact-document", "pdf", "presentation", "spreadsheet"]);
|
|
282
30
|
const ui = {
|
|
283
31
|
state: null,
|
|
284
32
|
todoById: new Map(),
|
|
@@ -312,14 +60,6 @@ const escapeHtml = (value) => String(value ?? "")
|
|
|
312
60
|
.replaceAll('"', """)
|
|
313
61
|
.replaceAll("'", "'");
|
|
314
62
|
|
|
315
|
-
function setLocale(value) {
|
|
316
|
-
const next = normalizeLocale(value);
|
|
317
|
-
if (next === locale) return;
|
|
318
|
-
locale = next;
|
|
319
|
-
document.documentElement.lang = locale;
|
|
320
|
-
if (ui.state) render();
|
|
321
|
-
}
|
|
322
|
-
|
|
323
63
|
function postToHost(message) {
|
|
324
64
|
window.parent.postMessage(message, ui.hostOrigin ?? "*");
|
|
325
65
|
}
|
|
@@ -489,7 +229,10 @@ window.addEventListener("message", (event) => {
|
|
|
489
229
|
if (!message || typeof message !== "object") return;
|
|
490
230
|
if (!ui.hostOrigin && event.origin && event.origin !== "null") ui.hostOrigin = event.origin;
|
|
491
231
|
if (message.type === "codex-personal:theme") {
|
|
492
|
-
if (message.locale
|
|
232
|
+
if (message.locale && setLocale(message.locale)) {
|
|
233
|
+
document.documentElement.lang = getLocale();
|
|
234
|
+
if (ui.state) render();
|
|
235
|
+
}
|
|
493
236
|
const tokens = message.tokens ?? message.theme?.tokens ?? {};
|
|
494
237
|
for (const [name, value] of Object.entries(tokens)) {
|
|
495
238
|
if (typeof value === "string" && (name.startsWith("--") || name.startsWith("codex-"))) {
|
|
@@ -539,29 +282,6 @@ function iconName(action) {
|
|
|
539
282
|
}[action] ?? null;
|
|
540
283
|
}
|
|
541
284
|
|
|
542
|
-
function codexFileIconKind(file) {
|
|
543
|
-
const rawPath = String(file.path || file.name || "").toLowerCase();
|
|
544
|
-
if (/[\\/]$/.test(rawPath)) return "folder";
|
|
545
|
-
const name = rawPath.split(/[\\/]/).at(-1) || "";
|
|
546
|
-
if (name === "skill.md") return "skill";
|
|
547
|
-
const dot = name.lastIndexOf(".");
|
|
548
|
-
const extension = dot > 0 && dot < name.length - 1
|
|
549
|
-
? name.slice(dot + 1)
|
|
550
|
-
: dot === 0 && name.length > 1
|
|
551
|
-
? name.slice(1)
|
|
552
|
-
: dot === -1
|
|
553
|
-
? name
|
|
554
|
-
: null;
|
|
555
|
-
const extensionKind = extension ? CODEX_FILE_ICON_BY_EXTENSION.get(extension) : null;
|
|
556
|
-
if (extensionKind) return extensionKind;
|
|
557
|
-
const mimeType = String(file.mimeType || "").toLowerCase();
|
|
558
|
-
if (mimeType.startsWith("image/")) return "image";
|
|
559
|
-
if (mimeType.startsWith("text/")) return "document";
|
|
560
|
-
if (mimeType.startsWith("application/pdf")) return "pdf";
|
|
561
|
-
if (mimeType.startsWith("application/zip") || mimeType.startsWith("application/gzip")) return "folder";
|
|
562
|
-
return "file";
|
|
563
|
-
}
|
|
564
|
-
|
|
565
285
|
function fileIconMarkup(kind) {
|
|
566
286
|
if (CODEX_COLOR_FILE_ICONS.has(kind)) {
|
|
567
287
|
return `<img class="file-kind is-color" src="/icons/file-${kind}.svg" alt="" aria-hidden="true">`;
|
|
@@ -655,7 +375,7 @@ function renderEditor() {
|
|
|
655
375
|
<textarea name="body" aria-label="${escapeHtml(t("todoText"))}" placeholder="${escapeHtml(t("todoPlaceholder"))}" autofocus>${escapeHtml(draft?.body)}</textarea>
|
|
656
376
|
</div>
|
|
657
377
|
<div class="modal-actions">
|
|
658
|
-
${modalCommand(editing ? t("saveTodo") : t("addTodo"), { submit: true, shortcut:
|
|
378
|
+
${modalCommand(editing ? t("saveTodo") : t("addTodo"), { submit: true, shortcut: submitShortcut, disabled: !draft?.body.trim() })}
|
|
659
379
|
${modalCommand(t("cancel"), { action: "close-editor", shortcut: "esc" })}
|
|
660
380
|
</div>
|
|
661
381
|
</form>
|
|
@@ -671,7 +391,7 @@ function renderEditor() {
|
|
|
671
391
|
<textarea name="body" aria-label="${escapeHtml(t("promptBody"))}" placeholder="${escapeHtml(t("promptPlaceholder"))}" autofocus>${escapeHtml(draft?.body)}</textarea>
|
|
672
392
|
</div>
|
|
673
393
|
<div class="modal-actions">
|
|
674
|
-
${modalCommand(editing ? t("savePrompt") : t("addPrompt"), { submit: true, shortcut:
|
|
394
|
+
${modalCommand(editing ? t("savePrompt") : t("addPrompt"), { submit: true, shortcut: submitShortcut, disabled: !draft?.body.trim() })}
|
|
675
395
|
${modalCommand(t("cancel"), { action: "close-editor", shortcut: "esc" })}
|
|
676
396
|
</div>
|
|
677
397
|
</form>
|
|
@@ -1285,10 +1005,6 @@ function setTodoDropTarget(row, position) {
|
|
|
1285
1005
|
row.dataset.dropPosition = position;
|
|
1286
1006
|
}
|
|
1287
1007
|
|
|
1288
|
-
function todoDropPosition(event, row) {
|
|
1289
|
-
const rect = row.getBoundingClientRect();
|
|
1290
|
-
return event.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
1291
|
-
}
|
|
1292
1008
|
|
|
1293
1009
|
function dragLeavesApp(event) {
|
|
1294
1010
|
if (event.relatedTarget instanceof Node && app.contains(event.relatedTarget)) return false;
|
|
@@ -1358,15 +1074,6 @@ function clearTodoDraggingState() {
|
|
|
1358
1074
|
ui.draggingTodo = null;
|
|
1359
1075
|
}
|
|
1360
1076
|
|
|
1361
|
-
function reorderedTodoIds(ids, draggedId, targetId, position) {
|
|
1362
|
-
if (!ids.includes(draggedId) || !ids.includes(targetId) || draggedId === targetId) return ids;
|
|
1363
|
-
const reordered = ids.filter((id) => id !== draggedId);
|
|
1364
|
-
const targetIndex = reordered.indexOf(targetId);
|
|
1365
|
-
const insertAt = targetIndex + (position === "after" ? 1 : 0);
|
|
1366
|
-
reordered.splice(insertAt, 0, draggedId);
|
|
1367
|
-
return reordered;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
1077
|
app.addEventListener("dragstart", (event) => {
|
|
1371
1078
|
const row = event.target.closest('.todo-row[draggable="true"]');
|
|
1372
1079
|
if (!row) return;
|
|
@@ -1450,7 +1157,7 @@ document.addEventListener("keydown", (event) => {
|
|
|
1450
1157
|
closeEditor();
|
|
1451
1158
|
return;
|
|
1452
1159
|
}
|
|
1453
|
-
if (event.key === "Enter" && event.metaKey) {
|
|
1160
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
1454
1161
|
event.preventDefault();
|
|
1455
1162
|
const form = app.querySelector("form");
|
|
1456
1163
|
const submit = form?.querySelector('button[type="submit"]');
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Codex 文件类型图标规则:文件扩展名 / MIME 到 Codex file-* 图标的映射。
|
|
2
|
+
// 这是文件图标的单一事实来源;app.js 只负责渲染。
|
|
3
|
+
|
|
4
|
+
const CODEX_FILE_ICON_BY_EXTENSION = new Map([
|
|
5
|
+
["ts", "typescript"],
|
|
6
|
+
["tsx", "react"], ["jsx", "react"],
|
|
7
|
+
["js", "javascript"], ["mjs", "javascript"], ["cjs", "javascript"], ["hs", "javascript"],
|
|
8
|
+
["py", "python"],
|
|
9
|
+
["java", "java"],
|
|
10
|
+
["rs", "rust"],
|
|
11
|
+
["php", "php"],
|
|
12
|
+
["css", "css"], ["scss", "css"], ["less", "css"], ["sass", "css"],
|
|
13
|
+
["cpp", "cplusplus"], ["cxx", "cplusplus"], ["cc", "cplusplus"], ["c", "cplusplus"],
|
|
14
|
+
["hpp", "cplusplus"], ["hh", "cplusplus"], ["h", "cplusplus"],
|
|
15
|
+
["rb", "code"], ["go", "code"], ["kt", "code"], ["swift", "code"], ["m", "code"],
|
|
16
|
+
["mm", "code"], ["cs", "code"], ["sql", "code"],
|
|
17
|
+
["json", "json"], ["jsonc", "json"],
|
|
18
|
+
["md", "document"], ["mdx", "document"], ["markdown", "document"],
|
|
19
|
+
["mkd", "document"], ["mdown", "document"],
|
|
20
|
+
["html", "html"], ["htm", "html"],
|
|
21
|
+
["yaml", "file"], ["yml", "file"],
|
|
22
|
+
["toml", "toml"],
|
|
23
|
+
["xml", "document"],
|
|
24
|
+
["csv", "spreadsheet"], ["tsv", "spreadsheet"], ["xls", "spreadsheet"],
|
|
25
|
+
["xlsm", "spreadsheet"], ["xlsx", "spreadsheet"],
|
|
26
|
+
["doc", "artifact-document"], ["docx", "artifact-document"],
|
|
27
|
+
["ipynb", "notebook"],
|
|
28
|
+
["ppt", "presentation"], ["pptx", "presentation"],
|
|
29
|
+
["sh", "shell"], ["bash", "shell"], ["zsh", "shell"], ["fish", "shell"], ["ps1", "shell"],
|
|
30
|
+
["dockerfile", "terminal"],
|
|
31
|
+
["env", "document"], ["dotenv", "document"], ["gitignore", "document"], ["lock", "document"],
|
|
32
|
+
["png", "image"], ["jpg", "image"], ["jpeg", "image"], ["gif", "image"],
|
|
33
|
+
["webp", "image"], ["bmp", "image"], ["svg", "image"], ["ico", "image"],
|
|
34
|
+
["build", "build"], ["bazel", "build"], ["bzl", "build"], ["ninja", "build"],
|
|
35
|
+
["gradle", "build"], ["mk", "build"], ["makefile", "build"],
|
|
36
|
+
["sha", "hashes"], ["sha1", "hashes"], ["sha256", "hashes"],
|
|
37
|
+
["md5", "hashes"], ["checksum", "hashes"], ["sum", "hashes"],
|
|
38
|
+
["pdf", "pdf"],
|
|
39
|
+
["zip", "folder"], ["gz", "folder"], ["tgz", "folder"], ["tar", "folder"],
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const CODEX_COLOR_FILE_ICONS = new Set(["artifact-document", "pdf", "presentation", "spreadsheet"]);
|
|
43
|
+
|
|
44
|
+
export function codexFileIconKind(file) {
|
|
45
|
+
const rawPath = String(file.path || file.name || "").toLowerCase();
|
|
46
|
+
if (/[\\/]$/.test(rawPath)) return "folder";
|
|
47
|
+
const name = rawPath.split(/[\\/]/).at(-1) || "";
|
|
48
|
+
if (name === "skill.md") return "skill";
|
|
49
|
+
const dot = name.lastIndexOf(".");
|
|
50
|
+
const extension = dot > 0 && dot < name.length - 1
|
|
51
|
+
? name.slice(dot + 1)
|
|
52
|
+
: dot === 0 && name.length > 1
|
|
53
|
+
? name.slice(1)
|
|
54
|
+
: dot === -1
|
|
55
|
+
? name
|
|
56
|
+
: null;
|
|
57
|
+
const extensionKind = extension ? CODEX_FILE_ICON_BY_EXTENSION.get(extension) : null;
|
|
58
|
+
if (extensionKind) return extensionKind;
|
|
59
|
+
const mimeType = String(file.mimeType || "").toLowerCase();
|
|
60
|
+
if (mimeType.startsWith("image/")) return "image";
|
|
61
|
+
if (mimeType.startsWith("text/")) return "document";
|
|
62
|
+
if (mimeType.startsWith("application/pdf")) return "pdf";
|
|
63
|
+
if (mimeType.startsWith("application/zip") || mimeType.startsWith("application/gzip")) return "folder";
|
|
64
|
+
return "file";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
export { CODEX_COLOR_FILE_ICONS };
|