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.
@@ -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
- const SUPPORTED_LOCALES = new Set(["en", "zh-CN"]);
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
- function normalizeLocale(value) {
5
- const candidate = String(value ?? "").trim();
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) setLocale(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: "⌘↩", disabled: !draft?.body.trim() })}
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: "⌘↩", disabled: !draft?.body.trim() })}
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 };