diff options
| author | grm <grm@eyesin.space> | 2026-09-17 21:29:44 +0300 |
|---|---|---|
| committer | grm <grm@eyesin.space> | 2026-09-17 21:29:44 +0300 |
| commit | e96bbec551c0388e972894b349413fa6ef607352 (patch) | |
| tree | c63b50d515fc0a0f754f970b8d316dd37f73ac80 /internal/web/filetype.go | |
| parent | 69912c4ac04ada3286c09400ce523d5d3ab4cfb4 (diff) | |
| download | blogspace-e96bbec551c0388e972894b349413fa6ef607352.tar.gz blogspace-e96bbec551c0388e972894b349413fa6ef607352.tar.bz2 blogspace-e96bbec551c0388e972894b349413fa6ef607352.zip | |
Add custom fonts: upload WOFF2/WOFF/TTF/OTF files and pick them on the Design tab
Fonts become a file kind of their own (sniffed from the bytes like images,
served inline from /media), and the theme gets custom_font and
custom_heading_font: a chosen file is declared as @font-face and put first
in the stack, the built-in Font / Heading font choice behind it as the
fallback, so there is no "custom" enum value and no invalid state. The
pickers are plain selects with an upload input, no script. Fonts uploaded
before this are reclassified by the migration.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/filetype.go')
| -rw-r--r-- | internal/web/filetype.go | 19 |
1 files changed, 13 insertions, 6 deletions
diff --git a/internal/web/filetype.go b/internal/web/filetype.go index 9c0f590..60a220e 100644 --- a/internal/web/filetype.go +++ b/internal/web/filetype.go @@ -17,7 +17,8 @@ import ( // SVG or XML page must never render there. The rules below make that a // property of the stored content type: the sniffer is trusted first, a // filename extension may only refine a generic sniff to a type on an -// allowlist, and anything not on the inline list is a download. +// allowlist, and anything not on the inline list is a download. Fonts are +// on it: a browser only ever parses one inside @font-face, never as a page. // maxUploadFiles is how many files one Files-page request may carry; the body // cap of that route is this many upload limits. @@ -27,10 +28,10 @@ const maxUploadFiles = 10 // offsets, and the whole upload sits in memory while it is stored. const maxUploadMB = 1024 -var fileKinds = []string{"image", "document", "audio", "video", "archive", "other"} +var fileKinds = []string{"image", "document", "audio", "video", "font", "archive", "other"} // fileKindNames are the tab labels (translated where used, like moduleNames). -var fileKindNames = map[string]string{"image": "Images", "document": "Documents", "audio": "Audio", "video": "Video", "archive": "Archives", "other": "Other"} +var fileKindNames = map[string]string{"image": "Images", "document": "Documents", "audio": "Audio", "video": "Video", "font": "Fonts", "archive": "Archives", "other": "Other"} var imageTypes = map[string]bool{"image/png": true, "image/jpeg": true, "image/gif": true, "image/webp": true, "image/x-icon": true, "image/avif": true, "image/bmp": true} @@ -50,6 +51,10 @@ var archiveTypes = map[string]bool{ "application/x-tar": true, "application/x-bzip2": true, "application/x-xz": true, "application/x-rar-compressed": true, "application/vnd.rar": true, } +// fontTypes are what the design page's font pickers take. Like images they +// sniff exactly, so the name never refines into one; TTC and EOT stay "other". +var fontTypes = map[string]bool{"font/woff2": true, "font/woff": true, "font/ttf": true, "font/otf": true} + var mediaTypes = map[string]bool{ "audio/mpeg": true, "audio/mp4": true, "audio/ogg": true, "audio/flac": true, "audio/wav": true, "audio/wave": true, "audio/x-wav": true, "audio/webm": true, "audio/aac": true, "video/mp4": true, "video/ogg": true, "video/webm": true, "video/x-matroska": true, "video/quicktime": true, @@ -91,8 +96,8 @@ func fileType(head []byte, filename string) (contentType, kind string) { ct = "application/octet-stream" } } - if !imageTypes[ct] && !extAllowed(ct) && !strings.HasPrefix(ct, "audio/") && !strings.HasPrefix(ct, "video/") { - ct = "application/octet-stream" // sniffed HTML/XML, fonts, and everything else we do not name + if !imageTypes[ct] && !fontTypes[ct] && !extAllowed(ct) && !strings.HasPrefix(ct, "audio/") && !strings.HasPrefix(ct, "video/") { + ct = "application/octet-stream" // sniffed HTML/XML and everything else we do not name } return ct, kindOf(ct) } @@ -123,6 +128,8 @@ func kindOf(ct string) string { return "audio" case strings.HasPrefix(ct, "video/"): return "video" + case fontTypes[ct]: + return "font" case archiveTypes[ct]: return "archive" } @@ -131,7 +138,7 @@ func kindOf(ct string) string { // inlineOK says whether a browser may render the type in place. func inlineOK(ct string) bool { - return imageTypes[ct] || ct == "application/pdf" || ct == "text/plain" || strings.HasPrefix(ct, "audio/") || strings.HasPrefix(ct, "video/") + return imageTypes[ct] || fontTypes[ct] || ct == "application/pdf" || ct == "text/plain" || strings.HasPrefix(ct, "audio/") || strings.HasPrefix(ct, "video/") } // servedAs is the Content-Type and disposition /media answers with. |
