summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-03-09 15:29:06 +0530
committerBobby <[email protected]>2026-03-09 15:29:06 +0530
commited6c3bc61c02a5ca6998b39781dc60877f1cfe82 (patch)
tree9294e22850413183c0b1475db5872acc3713ef8b
parente0e9cb791c6f1aedc2e4de83e9ecc0411729fee8 (diff)
downloadpagoda-ed6c3bc61c02a5ca6998b39781dc60877f1cfe82.tar.xz
pagoda-ed6c3bc61c02a5ca6998b39781dc60877f1cfe82.zip
feat: add category detection for file attachments and update related interfaces
-rw-r--r--garden/src/components/Editor.tsx46
-rw-r--r--shrine/models/letter.go2
-rw-r--r--shrine/services/letter.go2
-rw-r--r--shrine/types/letter/response.go1
-rw-r--r--shrine/utils/files/category.go97
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