feat(attachments): 全面扩展笔记附件专属图标与标签类型体系

- 细分支持各平台原生安装包 (APK, EXE, DMG, Linux 包)
- 新增设计工程 (AI/PSD/FIG/SKETCH) 与 3D/CAD 建模 (BLEND/STL/DWG)
- 新增终端运维自动化脚本 (SH/PS1/BAT) 与系统日志排障 (LOG/CRASH)
- 新增证书密钥安全凭据 (CRT/PEM/PUB/KEY) 与思维导图架构 (XMIND/DRAWIO)
- 新增电子书 (EPUB/MOBI)、字体资源 (WOFF2/TTF)、磁盘镜像 (ISO) 与数据库 (DB)
- 增加大写扩展名精准徽标输出与 .key 密钥/Keynote 消歧
- 跨端同步更新 Web、iOS 原生 EditorBundle 与 Android/Mobile 编辑器样式
- 补全单元测试与格式映射断言
This commit is contained in:
tianma-if committed 2026-09-30 19:42:06 +08:00
1 parent d2ddb07a0f
commit fbb41aee9d
8 files changed
+977 -703

No files matched your search

File diff suppressed because it is too large. Load diff
+342 -342
View File
File diff suppressed because it is too large. Load diff
+41 -1
View File
@@ -611,8 +611,16 @@ html[data-theme="dark"] .ProseMirror a:active {
.ProseMirror a.edgeever-attachment-kind-code::before { background: #faf5ff; color: #8b5cf6; content: "</>"; }
.edgeever-prose a.edgeever-attachment-kind-text::before,
.ProseMirror a.edgeever-attachment-kind-text::before { background: #f1f5f9; color: #64748b; content: "TXT"; }
.edgeever-prose a.edgeever-attachment-kind-apk::before,
.ProseMirror a.edgeever-attachment-kind-apk::before { background: #ecfdf5; color: #059669; content: "APK"; }
.edgeever-prose a.edgeever-attachment-kind-exe::before,
.ProseMirror a.edgeever-attachment-kind-exe::before { background: #f0f9ff; color: #0284c7; content: "EXE"; }
.edgeever-prose a.edgeever-attachment-kind-dmg::before,
.ProseMirror a.edgeever-attachment-kind-dmg::before { background: #f5f3ff; color: #7c3aed; content: "DMG"; }
.edgeever-prose a.edgeever-attachment-kind-linux::before,
.ProseMirror a.edgeever-attachment-kind-linux::before { background: #fff7ed; color: #ea580c; content: "LINUX"; }
.edgeever-prose a.edgeever-attachment-kind-executable::before,
.ProseMirror a.edgeever-attachment-kind-executable::before { background: #ccfbf1; color: #0d9488; content: "PKG"; }
.ProseMirror a.edgeever-attachment-kind-executable::before { background: #ccfbf1; color: #0d9488; content: "BIN"; }
.edgeever-prose a.edgeever-attachment-kind-book::before,
.ProseMirror a.edgeever-attachment-kind-book::before { background: #fef3c7; color: #b45309; content: "BOOK"; }
.edgeever-prose a.edgeever-attachment-kind-font::before,
@@ -621,6 +629,18 @@ html[data-theme="dark"] .ProseMirror a:active {
.ProseMirror a.edgeever-attachment-kind-diskimage::before { background: #cffafe; color: #0891b2; content: "ISO"; }
.edgeever-prose a.edgeever-attachment-kind-database::before,
.ProseMirror a.edgeever-attachment-kind-database::before { background: #fae8ff; color: #c026d3; content: "DB"; }
.edgeever-prose a.edgeever-attachment-kind-design::before,
.ProseMirror a.edgeever-attachment-kind-design::before { background: #fdf2f8; color: #db2777; content: "DESIGN"; }
.edgeever-prose a.edgeever-attachment-kind-model3d::before,
.ProseMirror a.edgeever-attachment-kind-model3d::before { background: #eff6ff; color: #2563eb; content: "3D"; }
.edgeever-prose a.edgeever-attachment-kind-script::before,
.ProseMirror a.edgeever-attachment-kind-script::before { background: #ecfdf5; color: #059669; content: "SHELL"; }
.edgeever-prose a.edgeever-attachment-kind-log::before,
.ProseMirror a.edgeever-attachment-kind-log::before { background: #f4f4f5; color: #71717a; content: "LOG"; }
.edgeever-prose a.edgeever-attachment-kind-certificate::before,
.ProseMirror a.edgeever-attachment-kind-certificate::before { background: #fefce8; color: #ca8a04; content: "KEY"; }
.edgeever-prose a.edgeever-attachment-kind-diagram::before,
.ProseMirror a.edgeever-attachment-kind-diagram::before { background: #f0fdfa; color: #0d9488; content: "DIAG"; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-link,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-link,
@@ -653,6 +673,14 @@ html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-code::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-code::before { background: #581c87; color: #d8b4fe; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-text::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-text::before { background: #334155; color: #cbd5e1; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-apk::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-apk::before { background: #064e3b; color: #6ee7b7; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-exe::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-exe::before { background: #0c4a6e; color: #7dd3fc; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-dmg::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-dmg::before { background: #4c1d95; color: #c4b5fd; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-linux::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-linux::before { background: #7c2d12; color: #fdba74; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-executable::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-executable::before { background: #134e4a; color: #5eead4; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-book::before,
@@ -663,6 +691,18 @@ html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-diskimage::be
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-diskimage::before { background: #164e63; color: #67e8f9; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-database::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-database::before { background: #701a75; color: #f0abfc; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-design::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-design::before { background: #831843; color: #f472b6; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-model3d::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-model3d::before { background: #1e3a8a; color: #93c5fd; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-script::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-script::before { background: #064e3b; color: #6ee7b7; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-log::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-log::before { background: #27272a; color: #a1a1aa; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-certificate::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-certificate::before { background: #713f12; color: #fde047; }
html[data-theme="dark"] .edgeever-prose a.edgeever-attachment-kind-diagram::before,
html[data-theme="dark"] .ProseMirror a.edgeever-attachment-kind-diagram::before { background: #134e4a; color: #5eead4; }
.edgeever-prose .edgeever-unsupported-content,
.ProseMirror .edgeever-unsupported-content {
@@ -3236,11 +3236,21 @@ const getEditorStyles = (theme: "light" | "dark", options?: { viewer?: boolean }
.edgeever-editor-content a.edgeever-attachment-kind-archive::before { background: ${theme === "dark" ? "#713f12" : "#fffbeb"}; color: ${theme === "dark" ? "#fde68a" : "#d97706"}; content: "ZIP"; }
.edgeever-editor-content a.edgeever-attachment-kind-code::before { background: ${theme === "dark" ? "#581c87" : "#faf5ff"}; color: ${theme === "dark" ? "#d8b4fe" : "#8b5cf6"}; content: "</>"; }
.edgeever-editor-content a.edgeever-attachment-kind-text::before { background: ${theme === "dark" ? "#334155" : "#f1f5f9"}; color: ${theme === "dark" ? "#cbd5e1" : "#64748b"}; content: "TXT"; }
.edgeever-editor-content a.edgeever-attachment-kind-executable::before { background: ${theme === "dark" ? "#134e4a" : "#ccfbf1"}; color: ${theme === "dark" ? "#5eead4" : "#0d9488"}; content: "PKG"; }
.edgeever-editor-content a.edgeever-attachment-kind-apk::before { background: ${theme === "dark" ? "#064e3b" : "#ecfdf5"}; color: ${theme === "dark" ? "#6ee7b7" : "#059669"}; content: "APK"; }
.edgeever-editor-content a.edgeever-attachment-kind-exe::before { background: ${theme === "dark" ? "#0c4a6e" : "#f0f9ff"}; color: ${theme === "dark" ? "#7dd3fc" : "#0284c7"}; content: "EXE"; }
.edgeever-editor-content a.edgeever-attachment-kind-dmg::before { background: ${theme === "dark" ? "#4c1d95" : "#f5f3ff"}; color: ${theme === "dark" ? "#c4b5fd" : "#7c3aed"}; content: "DMG"; }
.edgeever-editor-content a.edgeever-attachment-kind-linux::before { background: ${theme === "dark" ? "#7c2d12" : "#fff7ed"}; color: ${theme === "dark" ? "#fdba74" : "#ea580c"}; content: "LINUX"; }
.edgeever-editor-content a.edgeever-attachment-kind-executable::before { background: ${theme === "dark" ? "#134e4a" : "#ccfbf1"}; color: ${theme === "dark" ? "#5eead4" : "#0d9488"}; content: "BIN"; }
.edgeever-editor-content a.edgeever-attachment-kind-book::before { background: ${theme === "dark" ? "#78350f" : "#fef3c7"}; color: ${theme === "dark" ? "#fcd34d" : "#b45309"}; content: "BOOK"; }
.edgeever-editor-content a.edgeever-attachment-kind-font::before { background: ${theme === "dark" ? "#312e81" : "#e0e7ff"}; color: ${theme === "dark" ? "#a5b4fc" : "#4f46e5"}; content: "FONT"; }
.edgeever-editor-content a.edgeever-attachment-kind-diskimage::before { background: ${theme === "dark" ? "#164e63" : "#cffafe"}; color: ${theme === "dark" ? "#67e8f9" : "#0891b2"}; content: "ISO"; }
.edgeever-editor-content a.edgeever-attachment-kind-database::before { background: ${theme === "dark" ? "#701a75" : "#fae8ff"}; color: ${theme === "dark" ? "#f0abfc" : "#c026d3"}; content: "DB"; }
.edgeever-editor-content a.edgeever-attachment-kind-design::before { background: ${theme === "dark" ? "#831843" : "#fdf2f8"}; color: ${theme === "dark" ? "#f472b6" : "#db2777"}; content: "DESIGN"; }
.edgeever-editor-content a.edgeever-attachment-kind-model3d::before { background: ${theme === "dark" ? "#1e3a8a" : "#eff6ff"}; color: ${theme === "dark" ? "#93c5fd" : "#2563eb"}; content: "3D"; }
.edgeever-editor-content a.edgeever-attachment-kind-script::before { background: ${theme === "dark" ? "#064e3b" : "#ecfdf5"}; color: ${theme === "dark" ? "#6ee7b7" : "#059669"}; content: "SHELL"; }
.edgeever-editor-content a.edgeever-attachment-kind-log::before { background: ${theme === "dark" ? "#27272a" : "#f4f4f5"}; color: ${theme === "dark" ? "#a1a1aa" : "#71717a"}; content: "LOG"; }
.edgeever-editor-content a.edgeever-attachment-kind-certificate::before { background: ${theme === "dark" ? "#713f12" : "#fefce8"}; color: ${theme === "dark" ? "#fde047" : "#ca8a04"}; content: "KEY"; }
.edgeever-editor-content a.edgeever-attachment-kind-diagram::before { background: ${theme === "dark" ? "#134e4a" : "#f0fdfa"}; color: ${theme === "dark" ? "#5eead4" : "#0d9488"}; content: "DIAG"; }
.edgeever-editor-content .edgeever-unsupported-content {
border: 1px dashed ${theme === "dark" ? "#64748b" : "#94a3b8"};
border-radius: 8px;
@@ -1,5 +1,8 @@
import {
AppWindow,
BookOpen,
Box,
Cpu,
Database,
Disc,
File,
@@ -8,9 +11,16 @@ import {
FileSpreadsheet,
FileText,
ImageIcon,
KeyRound,
Music,
Network,
Package,
Palette,
Presentation,
Scroll,
Smartphone,
SquareTerminal,
Terminal,
Type,
Video,
} from "lucide-react";
@@ -39,12 +49,22 @@ export const AttachmentFileIcon = ({
case "presentation": return <Presentation className={cn("text-orange-500", commonClassName)} aria-hidden="true" />;
case "archive": return <FileArchive className={cn("text-amber-500", commonClassName)} aria-hidden="true" />;
case "code": return <FileCode2 className={cn("text-violet-500", commonClassName)} aria-hidden="true" />;
case "script": return <SquareTerminal className={cn("text-emerald-600", commonClassName)} aria-hidden="true" />;
case "text": return <FileText className={cn("text-slate-500", commonClassName)} aria-hidden="true" />;
case "executable": return <Package className={cn("text-teal-600", commonClassName)} aria-hidden="true" />;
case "apk": return <Smartphone className={cn("text-emerald-500", commonClassName)} aria-hidden="true" />;
case "exe": return <AppWindow className={cn("text-sky-500", commonClassName)} aria-hidden="true" />;
case "dmg": return <Package className={cn("text-violet-500", commonClassName)} aria-hidden="true" />;
case "linux": return <Terminal className={cn("text-orange-500", commonClassName)} aria-hidden="true" />;
case "executable": return <Cpu className={cn("text-teal-600", commonClassName)} aria-hidden="true" />;
case "book": return <BookOpen className={cn("text-amber-700", commonClassName)} aria-hidden="true" />;
case "font": return <Type className={cn("text-indigo-500", commonClassName)} aria-hidden="true" />;
case "diskimage": return <Disc className={cn("text-cyan-600", commonClassName)} aria-hidden="true" />;
case "database": return <Database className={cn("text-fuchsia-600", commonClassName)} aria-hidden="true" />;
case "design": return <Palette className={cn("text-pink-500", commonClassName)} aria-hidden="true" />;
case "model3d": return <Box className={cn("text-blue-500", commonClassName)} aria-hidden="true" />;
case "log": return <Scroll className={cn("text-zinc-500", commonClassName)} aria-hidden="true" />;
case "certificate": return <KeyRound className={cn("text-amber-600", commonClassName)} aria-hidden="true" />;
case "diagram": return <Network className={cn("text-teal-600", commonClassName)} aria-hidden="true" />;
default: return <File className={cn("text-slate-400", commonClassName)} aria-hidden="true" />;
}
};
+79 -9
View File
@@ -5,6 +5,7 @@ import {
resolvePlayableMediaMimeType,
resolveVideoMimeType,
} from "./attachment-kind.ts";
import { getAttachmentTypeLabel } from "./attachment-metadata.ts";
describe("attachment kind", () => {
test("uses MIME types for media and filenames for common documents", () => {
@@ -42,15 +43,34 @@ describe("attachment kind", () => {
expect(resolvePlayableMediaMimeType("application/octet-stream", "voice.mp3")).toBe("audio/mpeg");
});
test("identifies executables, books, fonts, disk images, and databases", () => {
expect(resolveAttachmentKind("application/vnd.android.package-archive", "app.apk")).toBe("executable");
expect(resolveAttachmentKind("application/octet-stream", "EdgeEver-Setup.exe")).toBe("executable");
expect(resolveAttachmentKind(null, "EdgeEver-arm64.dmg")).toBe("executable");
expect(resolveAttachmentKind(null, "installer.msi")).toBe("executable");
expect(resolveAttachmentKind(null, "package.deb")).toBe("executable");
expect(resolveAttachmentKind(null, "package.rpm")).toBe("executable");
expect(resolveAttachmentKind(null, "app.AppImage")).toBe("executable");
expect(resolveAttachmentKind("application/zip", "app.apk")).toBe("executable");
test("identifies platform executables, books, fonts, disk images, and databases", () => {
expect(resolveAttachmentKind("application/vnd.android.package-archive", "app.apk")).toBe("apk");
expect(resolveAttachmentKind("application/zip", "app.apk")).toBe("apk");
expect(resolveAttachmentKind(null, "app.aab")).toBe("apk");
expect(getAttachmentTypeLabel(null, "app.apk")).toBe("APK");
expect(getAttachmentTypeLabel(null, "app.aab")).toBe("AAB");
expect(resolveAttachmentKind("application/octet-stream", "EdgeEver-Setup.exe")).toBe("exe");
expect(resolveAttachmentKind(null, "installer.msi")).toBe("exe");
expect(getAttachmentTypeLabel(null, "EdgeEver-Setup.exe")).toBe("EXE");
expect(getAttachmentTypeLabel(null, "installer.msi")).toBe("MSI");
expect(resolveAttachmentKind(null, "EdgeEver-arm64.dmg")).toBe("dmg");
expect(resolveAttachmentKind(null, "bundle.pkg")).toBe("dmg");
expect(resolveAttachmentKind(null, "release.ipa")).toBe("dmg");
expect(getAttachmentTypeLabel(null, "EdgeEver-arm64.dmg")).toBe("DMG");
expect(getAttachmentTypeLabel(null, "bundle.pkg")).toBe("PKG");
expect(getAttachmentTypeLabel(null, "release.ipa")).toBe("IPA");
expect(resolveAttachmentKind(null, "package.deb")).toBe("linux");
expect(resolveAttachmentKind(null, "package.rpm")).toBe("linux");
expect(resolveAttachmentKind(null, "app.AppImage")).toBe("linux");
expect(getAttachmentTypeLabel(null, "package.deb")).toBe("DEB");
expect(getAttachmentTypeLabel(null, "package.rpm")).toBe("RPM");
expect(getAttachmentTypeLabel(null, "app.AppImage")).toBe("APPIMAGE");
expect(resolveAttachmentKind("application/x-executable", "payload.bin")).toBe("executable");
expect(getAttachmentTypeLabel("application/x-executable", "payload.bin")).toBe("BIN");
expect(resolveAttachmentKind("application/epub+zip", "manual.epub")).toBe("book");
expect(resolveAttachmentKind(null, "novel.mobi")).toBe("book");
@@ -72,6 +92,56 @@ describe("attachment kind", () => {
expect(resolveAttachmentKind(null, "main.go")).toBe("code");
expect(resolveAttachmentKind(null, "Main.java")).toBe("code");
expect(resolveAttachmentKind(null, "script.py")).toBe("code");
// Scripts
expect(resolveAttachmentKind(null, "deploy.sh")).toBe("script");
expect(resolveAttachmentKind(null, "run.bash")).toBe("script");
expect(resolveAttachmentKind(null, "install.ps1")).toBe("script");
expect(resolveAttachmentKind(null, "build.bat")).toBe("script");
expect(getAttachmentTypeLabel(null, "deploy.sh")).toBe("SH");
expect(getAttachmentTypeLabel(null, "install.ps1")).toBe("PS1");
// Design
expect(resolveAttachmentKind(null, "ui.sketch")).toBe("design");
expect(resolveAttachmentKind(null, "banner.psd")).toBe("design");
expect(resolveAttachmentKind(null, "icon.ai")).toBe("design");
expect(resolveAttachmentKind(null, "mockup.fig")).toBe("design");
expect(getAttachmentTypeLabel(null, "banner.psd")).toBe("PSD");
expect(getAttachmentTypeLabel(null, "icon.ai")).toBe("AI");
expect(getAttachmentTypeLabel(null, "mockup.fig")).toBe("FIG");
// 3D & CAD
expect(resolveAttachmentKind(null, "character.blend")).toBe("model3d");
expect(resolveAttachmentKind(null, "part.stl")).toBe("model3d");
expect(resolveAttachmentKind(null, "mesh.obj")).toBe("model3d");
expect(resolveAttachmentKind(null, "floorplan.dwg")).toBe("model3d");
expect(getAttachmentTypeLabel(null, "character.blend")).toBe("BLEND");
expect(getAttachmentTypeLabel(null, "part.stl")).toBe("STL");
// Logs
expect(resolveAttachmentKind(null, "server.log")).toBe("log");
expect(resolveAttachmentKind(null, "panic.crash")).toBe("log");
expect(getAttachmentTypeLabel(null, "server.log")).toBe("LOG");
// Certificates & Keys
expect(resolveAttachmentKind(null, "cert.pem")).toBe("certificate");
expect(resolveAttachmentKind(null, "server.crt")).toBe("certificate");
expect(resolveAttachmentKind(null, "id_rsa.pub")).toBe("certificate");
expect(resolveAttachmentKind(null, "private.key")).toBe("certificate");
expect(getAttachmentTypeLabel(null, "cert.pem")).toBe("PEM");
expect(getAttachmentTypeLabel(null, "id_rsa.pub")).toBe("PUB");
// Diagrams & Mindmaps
expect(resolveAttachmentKind(null, "roadmap.xmind")).toBe("diagram");
expect(resolveAttachmentKind(null, "architecture.drawio")).toBe("diagram");
expect(resolveAttachmentKind(null, "whiteboard.excalidraw")).toBe("diagram");
expect(getAttachmentTypeLabel(null, "roadmap.xmind")).toBe("XMIND");
expect(getAttachmentTypeLabel(null, "architecture.drawio")).toBe("DRAWIO");
// Common office/data extensions
expect(getAttachmentTypeLabel(null, "table.csv")).toBe("CSV");
expect(getAttachmentTypeLabel(null, "README.md")).toBe("MD");
expect(getAttachmentTypeLabel(null, "notes.txt")).toBe("TXT");
});
test("falls back predictably for text and unknown files", () => {
+60 -5
View File
@@ -8,12 +8,22 @@ export const ATTACHMENT_KINDS = [
"presentation",
"archive",
"code",
"script",
"text",
"apk",
"exe",
"dmg",
"linux",
"executable",
"book",
"font",
"diskimage",
"database",
"design",
"model3d",
"log",
"certificate",
"diagram",
"file",
] as const;
@@ -94,19 +104,37 @@ export const resolveAttachmentKind = (
) return "document";
if (
mime.includes("presentation") || mime.includes("powerpoint") ||
["ppt", "pptx", "odp", "key"].includes(extension)
mime.includes("presentation") || mime.includes("powerpoint") || mime.includes("keynote") ||
["ppt", "pptx", "odp", "keynote"].includes(extension) ||
(extension === "key" && mime.includes("keynote"))
) return "presentation";
if (
mime.includes("android.package-archive") ||
["apk", "xapk", "apks", "aab"].includes(extension)
) return "apk";
if (
mime.includes("application/x-msdownload") ||
mime.includes("application/x-msdos-program") ||
mime.includes("application/x-msi") ||
["exe", "msi"].includes(extension)
) return "exe";
if (
mime.includes("application/x-apple-diskimage") ||
["dmg", "pkg", "ipa"].includes(extension)
) return "dmg";
if (
mime.includes("application/x-debian-package") ||
mime.includes("application/x-redhat-package-manager") ||
["appimage", "deb", "rpm", "flatpak"].includes(extension)
) return "linux";
if (
mime.includes("application/x-executable") ||
["apk", "xapk", "apks", "aab", "ipa", "exe", "msi", "dmg", "pkg", "deb", "rpm", "appimage"].includes(extension)
["run", "elf"].includes(extension)
) return "executable";
if (
@@ -129,6 +157,33 @@ export const resolveAttachmentKind = (
["sqlite", "sqlite3", "db", "db3"].includes(extension)
) return "database";
if (
mime.includes("photoshop") ||
["psd", "psb", "ai", "sketch", "fig", "xd", "afphoto", "afdesign", "cdr"].includes(extension)
) return "design";
if (
mime.startsWith("model/") ||
["blend", "obj", "stl", "fbx", "gltf", "glb", "step", "stp", "iges", "igs", "dwg", "dxf"].includes(extension)
) return "model3d";
if (
mime.includes("pkix") || mime.includes("x-x509") || mime.includes("pkcs") ||
["pem", "crt", "cer", "key", "pub", "pfx", "p12", "der", "csr"].includes(extension)
) return "certificate";
if (
["xmind", "drawio", "excalidraw", "vsdx", "vsd", "mindnode", "mmap"].includes(extension)
) return "diagram";
if (
["log", "crash", "out", "trace"].includes(extension)
) return "log";
if (
["sh", "bash", "zsh", "fish", "bat", "cmd", "ps1"].includes(extension)
) return "script";
if (
mime.includes("zip") || mime.includes("compressed") || mime.includes("tar") ||
mime.includes("rar") || mime.includes("gzip") ||
@@ -140,11 +195,11 @@ export const resolveAttachmentKind = (
mime.includes("xml") || mime.includes("yaml") ||
[
"js", "jsx", "ts", "tsx", "json", "xml", "yaml", "yml", "html", "css", "scss", "less",
"sh", "bash", "zsh", "py", "java", "go", "rs", "c", "cpp", "h", "hpp", "cs",
"py", "java", "go", "rs", "c", "cpp", "h", "hpp", "cs",
"swift", "kt", "kts", "rb", "php", "lua", "sql", "toml", "ini", "conf", "env",
].includes(extension)
) return "code";
if (mime.startsWith("text/") || ["txt", "md", "log"].includes(extension)) return "text";
if (mime.startsWith("text/") || ["txt", "md"].includes(extension)) return "text";
return "file";
};
+81 -2
View File
@@ -10,15 +10,88 @@ const TYPE_LABELS = {
presentation: "PPT",
archive: "ZIP",
code: "CODE",
script: "SHELL",
text: "TXT",
executable: "PKG",
apk: "APK",
exe: "EXE",
dmg: "DMG",
linux: "LINUX",
executable: "BIN",
book: "BOOK",
font: "FONT",
diskimage: "ISO",
database: "DB",
design: "DESIGN",
model3d: "3D",
log: "LOG",
certificate: "KEY",
diagram: "DIAGRAM",
file: "FILE",
} as const;
const EXTENSION_LABELS: Readonly<Record<string, string>> = {
// Installers
msi: "MSI",
appimage: "APPIMAGE",
deb: "DEB",
rpm: "RPM",
pkg: "PKG",
ipa: "IPA",
aab: "AAB",
// Design
psd: "PSD",
ai: "AI",
sketch: "SKETCH",
fig: "FIG",
xd: "XD",
// 3D
stl: "STL",
obj: "OBJ",
blend: "BLEND",
fbx: "FBX",
gltf: "GLTF",
glb: "GLB",
dwg: "DWG",
// Scripts
sh: "SH",
bash: "BASH",
zsh: "ZSH",
bat: "BAT",
cmd: "CMD",
ps1: "PS1",
// Certs & Keys
pem: "PEM",
crt: "CERT",
cer: "CERT",
pub: "PUB",
key: "KEY",
// Diagrams
xmind: "XMIND",
drawio: "DRAWIO",
vsdx: "VSDX",
// Books & Fonts & Media
epub: "EPUB",
mobi: "MOBI",
woff2: "WOFF2",
woff: "WOFF",
ttf: "TTF",
otf: "OTF",
// Common office/text
csv: "CSV",
tsv: "TSV",
md: "MD",
"7z": "7Z",
tar: "TAR",
gz: "GZ",
};
export const normalizeAttachmentByteSize = (value: unknown): number | null => {
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
return Number.isFinite(parsed) && parsed > 0 ? Math.round(parsed) : null;
@@ -36,7 +109,13 @@ export const formatAttachmentByteSize = (value: unknown): string | null => {
export const getAttachmentTypeLabel = (
mimeType: string | null | undefined,
filename: string | null | undefined,
) => TYPE_LABELS[resolveAttachmentKind(mimeType, filename)];
) => {
const extension = filename?.trim().toLowerCase().match(/\.([a-z0-9]+)(?:[?#].*)?$/)?.[1] ?? "";
if (EXTENSION_LABELS[extension]) {
return EXTENSION_LABELS[extension];
}
return TYPE_LABELS[resolveAttachmentKind(mimeType, filename)];
};
export const formatAttachmentMetadata = (
mimeType: string | null | undefined,