diff options
| author | Bobby <[email protected]> | 2026-03-09 15:29:06 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2026-03-09 15:29:06 +0530 |
| commit | ed6c3bc61c02a5ca6998b39781dc60877f1cfe82 (patch) | |
| tree | 9294e22850413183c0b1475db5872acc3713ef8b | |
| parent | e0e9cb791c6f1aedc2e4de83e9ecc0411729fee8 (diff) | |
| download | pagoda-ed6c3bc61c02a5ca6998b39781dc60877f1cfe82.tar.xz pagoda-ed6c3bc61c02a5ca6998b39781dc60877f1cfe82.zip | |
feat: add category detection for file attachments and update related interfaces
| -rw-r--r-- | garden/src/components/Editor.tsx | 46 | ||||
| -rw-r--r-- | shrine/models/letter.go | 2 | ||||
| -rw-r--r-- | shrine/services/letter.go | 2 | ||||
| -rw-r--r-- | shrine/types/letter/response.go | 1 | ||||
| -rw-r--r-- | shrine/utils/files/category.go | 97 |
5 files changed, 122 insertions, 26 deletions
diff --git a/garden/src/components/Editor.tsx b/garden/src/components/Editor.tsx index c00669f..ea69eb1 100644 --- a/garden/src/components/Editor.tsx +++ b/garden/src/components/Editor.tsx @@ -34,7 +34,7 @@ import { IconMusic, IconVideo, IconFileZip, - IconFileTypePdf, + IconTypography, IconDatabase, IconX, @@ -50,6 +50,7 @@ interface AttachmentResult { url: string; file_size: number; content_type: string; + category: string; } interface EditorProps { @@ -173,29 +174,22 @@ export default function Editor(props: EditorProps) { return dot >= 0 ? name.slice(dot + 1).toUpperCase() : ""; } - const archiveMimes = ["application/zip", "application/x-rar-compressed", "application/gzip", "application/x-7z-compressed", "application/x-tar", "application/x-bzip2", "application/x-xz", "application/x-compress"]; - const codeMimes = ["application/javascript", "application/json", "application/xml", "application/x-httpd-php", "application/x-sh", "application/x-python", "application/typescript"]; - const docMimes = ["application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.oasis.opendocument.text", "application/rtf", "application/epub+zip"]; - const sheetMimes = ["application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.oasis.opendocument.spreadsheet", "text/csv"]; - const slideMimes = ["application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.oasis.opendocument.presentation"]; - const dbMimes = ["application/x-sqlite3", "application/vnd.ms-access"]; - - function fileIcon(contentType: string) { + function fileIcon(category: string) { const s = "24"; const k = "1.5"; - if (contentType.startsWith("image/")) return <IconPhoto size={s} stroke={k} />; - if (contentType.startsWith("audio/")) return <IconMusic size={s} stroke={k} />; - if (contentType.startsWith("video/")) return <IconVideo size={s} stroke={k} />; - if (contentType.startsWith("font/") || contentType === "application/font-sfnt" || contentType === "application/vnd.ms-fontobject" || contentType === "application/font-woff" || contentType === "application/font-woff2") return <IconTypography size={s} stroke={k} />; - if (contentType === "application/pdf") return <IconFileTypePdf size={s} stroke={k} />; - if (archiveMimes.includes(contentType)) return <IconFileZip size={s} stroke={k} />; - if (dbMimes.includes(contentType)) return <IconDatabase size={s} stroke={k} />; - if (docMimes.includes(contentType)) return <IconFileText size={s} stroke={k} />; - if (sheetMimes.includes(contentType)) return <IconFileSpreadsheet size={s} stroke={k} />; - if (slideMimes.includes(contentType)) return <IconPresentation size={s} stroke={k} />; - if (codeMimes.includes(contentType) || contentType.startsWith("text/x-")) return <IconFileCode size={s} stroke={k} />; - if (contentType.startsWith("text/")) return <IconFileText size={s} stroke={k} />; - return <IconFile size={s} stroke={k} />; + const icons: Record<string, () => any> = { + image: () => <IconPhoto size={s} stroke={k} />, + video: () => <IconVideo size={s} stroke={k} />, + audio: () => <IconMusic size={s} stroke={k} />, + font: () => <IconTypography size={s} stroke={k} />, + archive: () => <IconFileZip size={s} stroke={k} />, + database: () => <IconDatabase size={s} stroke={k} />, + document: () => <IconFileText size={s} stroke={k} />, + spreadsheet: () => <IconFileSpreadsheet size={s} stroke={k} />, + presentation: () => <IconPresentation size={s} stroke={k} />, + code: () => <IconFileCode size={s} stroke={k} />, + }; + return (icons[category] || (() => <IconFile size={s} stroke={k} />))(); } function closeAllPopups() { @@ -621,10 +615,10 @@ export default function Editor(props: EditorProps) { <For each={attachments()}> {(attachment) => ( <div class="editor-attachment" title={attachment.file_name}> - <Show when={attachment.content_type.startsWith("image/")} fallback={ - <Show when={attachment.content_type.startsWith("video/")} fallback={ + <Show when={attachment.category === "image"} fallback={ + <Show when={attachment.category === "video"} fallback={ <div class="editor-attachment-tile"> - {fileIcon(attachment.content_type)} + {fileIcon(attachment.category)} <span class="editor-attachment-ext">{fileExtension(attachment.file_name)}</span> </div> }> @@ -648,7 +642,7 @@ export default function Editor(props: EditorProps) { <Show when={pending.type.startsWith("image/") && pending.preview} fallback={ <Show when={pending.type.startsWith("video/") && pending.preview} fallback={ <div class="editor-attachment-tile"> - {fileIcon(pending.type)} + {fileIcon(pending.type.split("/")[0] || "other")} <span class="editor-attachment-ext">{fileExtension(pending.name)}</span> </div> }> diff --git a/shrine/models/letter.go b/shrine/models/letter.go index f74de66..7668758 100644 --- a/shrine/models/letter.go +++ b/shrine/models/letter.go @@ -50,6 +50,7 @@ type LetterAttachment struct { FilePath string `gorm:"size:512;not null"` FileSize int64 `gorm:"not null"` ContentType string `gorm:"size:100;not null"` + Category string `gorm:"size:20;not null;default:other"` } func (self *Letter) BeforeCreate(tx *gorm.DB) error { @@ -89,6 +90,7 @@ func (self *LetterAttachment) ToResponse() letter.AttachmentResponse { URL: storage.ResolveCDN(self.FilePath), FileSize: self.FileSize, ContentType: self.ContentType, + Category: self.Category, } } diff --git a/shrine/services/letter.go b/shrine/services/letter.go index e9bd825..940e9c7 100644 --- a/shrine/services/letter.go +++ b/shrine/services/letter.go @@ -11,6 +11,7 @@ import ( "shrine/types/common" "shrine/types/hypertext" "shrine/types/letter" + "shrine/utils/files" "shrine/utils/meta" "shrine/utils/storage" "strings" @@ -256,6 +257,7 @@ func UploadLetterAttachment(userID uint, fileName string, fileSize int64, conten FileName: fileName, FileSize: fileSize, ContentType: contentType, + Category: files.DetectCategory(contentType), } if err := repositories.UploadAttachment(userID, &attachment); err != nil { diff --git a/shrine/types/letter/response.go b/shrine/types/letter/response.go index 6cbd6ed..fe1c0d7 100644 --- a/shrine/types/letter/response.go +++ b/shrine/types/letter/response.go @@ -18,6 +18,7 @@ type AttachmentResponse struct { URL string `json:"url"` FileSize int64 `json:"file_size"` ContentType string `json:"content_type"` + Category string `json:"category"` } type MessageResponse struct { diff --git a/shrine/utils/files/category.go b/shrine/utils/files/category.go new file mode 100644 index 0000000..d9d87e4 --- /dev/null +++ b/shrine/utils/files/category.go @@ -0,0 +1,97 @@ +package files + +import ( + "shrine/utils/collections" + "strings" +) + +var archiveMimes = collections.SetOf( + "application/zip", + "application/x-rar-compressed", + "application/gzip", + "application/x-7z-compressed", + "application/x-tar", + "application/x-bzip2", + "application/x-xz", + "application/x-compress", +) + +var documentMimes = collections.SetOf( + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.oasis.opendocument.text", + "application/rtf", + "application/epub+zip", +) + +var spreadsheetMimes = collections.SetOf( + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.oasis.opendocument.spreadsheet", + "text/csv", +) + +var presentationMimes = collections.SetOf( + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.oasis.opendocument.presentation", +) + +var codeMimes = collections.SetOf( + "application/javascript", + "application/json", + "application/xml", + "application/x-httpd-php", + "application/x-sh", + "application/x-python", + "application/typescript", +) + +var databaseMimes = collections.SetOf( + "application/x-sqlite3", + "application/vnd.ms-access", +) + + +func DetectCategory(contentType string) string { + if strings.HasPrefix(contentType, "image/") { + return "image" + } + if strings.HasPrefix(contentType, "video/") { + return "video" + } + if strings.HasPrefix(contentType, "audio/") { + return "audio" + } + if strings.Contains(contentType, "font") { + return "font" + } + if contentType == "application/pdf" { + return "document" + } + if archiveMimes.Has(contentType) { + return "archive" + } + if documentMimes.Has(contentType) { + return "document" + } + if spreadsheetMimes.Has(contentType) { + return "spreadsheet" + } + if presentationMimes.Has(contentType) { + return "presentation" + } + if codeMimes.Has(contentType) { + return "code" + } + if databaseMimes.Has(contentType) { + return "database" + } + if strings.HasPrefix(contentType, "text/x-") { + return "code" + } + if strings.HasPrefix(contentType, "text/") { + return "document" + } + return "other" +}
\ No newline at end of file |
