aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/static
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-14 23:51:55 +0300
committergrm <grm@eyesin.space>2026-09-14 23:51:55 +0300
commit778cb72c8a0902bd0b8159ebd3bb7eff93f28c83 (patch)
treede21228c247e735591e88acbbf3bf4c83f2142e0 /internal/web/static
parent5f9fc2a8667a9438b6336d75026f9a455fc9c4ce (diff)
downloadblogspace-778cb72c8a0902bd0b8159ebd3bb7eff93f28c83.tar.gz
blogspace-778cb72c8a0902bd0b8159ebd3bb7eff93f28c83.tar.bz2
blogspace-778cb72c8a0902bd0b8159ebd3bb7eff93f28c83.zip
Turn the image library into a file library, with a per-blog upload limit
Bloggers want to attach PDFs, archives, audio and other files to posts, not only images. The Images tab becomes Files: any type is accepted, listed by kind with search, paging, rename and multi-file upload, and the editor's paste/drop/"Insert file" takes anything (images are shown, everything else becomes a link). The default limit goes from 5 to 10 MB and the superadmin can override it per blog from /admin/. Files stay in Postgres so one pg_dump is still the whole blog. The bytea column is STORAGE EXTERNAL and /media streams it in substring() slices, so serving never holds a whole file in memory whatever limit a blog gets. Serving any type on the root domain, which carries the session cookie, needs a policy: uploads are typed by sniffing (the extension may only refine a generic sniff to an allowlisted type) and only images, PDF, plain text, audio and video render inline; HTML, SVG, XML, scripts, archives and binaries always go out as application/octet-stream with Content-Disposition: attachment. The body cap moves out of requireAuth into guardPOST, which runs after withBlog has resolved the blog and so knows its limit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/static')
-rw-r--r--internal/web/static/dashboard.css25
1 files changed, 18 insertions, 7 deletions
diff --git a/internal/web/static/dashboard.css b/internal/web/static/dashboard.css
index 5c766e7..8fe5f8a 100644
--- a/internal/web/static/dashboard.css
+++ b/internal/web/static/dashboard.css
@@ -118,12 +118,23 @@ details.help[open] summary { margin-bottom: 0.5em; }
.editor-tools .upload input { display: inline-block; width: auto; max-width: 14em; margin: 0 0 0 0.4em; padding: 0; border: 0; background: none; font-size: 0.9em; }
.editor-tools .upload-status { font-family: "IBM Plex Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.85em; margin-right: 0.6em; }
-/* ---- images -------------------------------------------------------------- */
-.gallery { overflow: hidden; }
-.thumb { float: left; width: 200px; margin: 0 1.2em 1.6em 0; padding: 0.6em; text-align: center; }
-.thumb img { max-width: 100%; max-height: 140px; border: 1px solid #ddd6c7; }
-.thumb .meta { font-size: 0.85em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 0.4em; }
-.thumb .copy { font-size: 0.75em; margin: 0.4em 0; padding: 0.3em; font-family: "IBM Plex Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace; }
+/* ---- files --------------------------------------------------------------- */
+.dropzone.over { border-style: dashed; background: #ebe5d6; }
+.progress { list-style: none; margin: 0.6em 0 0; padding: 0; font-family: "IBM Plex Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.85em; }
+.progress:empty { display: none; }
+.files-filter { overflow: hidden; }
+.files-filter input[type=search] { width: auto; margin: 0 0.3em 0.5em 0; padding: 0.3em 0.5em; vertical-align: middle; }
+.kinds { display: inline-block; margin: 0 1em 0.5em 0; vertical-align: middle; }
+.kinds a { display: inline-block; padding: 0.2em 0.7em; margin: 0 0.3em 0.3em 0; border: 2px solid #1d1a17; color: #1d1a17; text-decoration: none; font-family: "IBM Plex Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.8em; text-transform: uppercase; letter-spacing: 0.05em; }
+.kinds a:hover { background: #ebe5d6; }
+.kinds a.active { background: #1d1a17; color: #f4efe4; }
+table.files td.icon { width: 56px; }
+table.files .thumb { display: block; width: 48px; height: 48px; object-fit: contain; border: 1px solid #ddd6c7; background: #f4efe4; }
+table.files .badge { display: block; width: 48px; padding: 0.9em 0; border: 1px solid #1d1a17; background: #fffdf8; text-align: center; font-family: "IBM Plex Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.65em; letter-spacing: 0.05em; overflow: hidden; }
+table.files td.name { word-break: break-all; }
+table.files .copy { width: 100%; min-width: 11em; padding: 0.3em; font-family: "IBM Plex Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.75em; }
+.pager { margin: 0 4px 1.6em 0; text-align: center; font-family: "IBM Plex Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.9em; }
+.pager a, .pager span { margin: 0 0.8em; }
.imagepick { margin-top: 0.5em; padding-top: 0.5em; border-top: 2px dashed #ddd6c7; }
.imagepick > label { margin-bottom: 0.3em; }
.imagepick .picks { overflow: hidden; margin: 0.4em 0 0; }
@@ -184,7 +195,7 @@ details.reset { margin-top: 1.6em; }
.login-brand { text-align: center; font-family: "IBM Plex Mono", "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace; text-transform: uppercase; letter-spacing: 0.2em; margin: 2em 0 1em; }
@media (max-width: 700px) {
- .cols .card, .row label, .thumb { float: none; width: auto; margin-right: 4px; }
+ .cols .card, .row label { float: none; width: auto; margin-right: 4px; }
.imagepick .pick { width: 46%; margin-right: 4%; }
.presets .preset { width: 46%; margin-right: 4%; }
.addrow .add { float: none; width: auto; margin-right: 0; }