feat(files): a file that is not media opens in a viewer — an image, a PDF, text as text

- GET /api/files/{id}/preview: an allow-list on the stored extension (downloads/preview.py) picks
  image / PDF / text; served inline under `CSP: sandbox; frame-ancestors 'self'`, the type from the
  table; HTML and SVG arrive as plain text; a text is its first 1 MiB (X-Preview-Truncated), sent
  as UTF-8 whatever it was written in (BOM, UTF-8, else cp1250); a NUL means binary: 415
- the list says which preview each file gets (asset.preview); FE/BE contract on kinds and header
- the viewer (shared dialog shell): ← / → through the list's such files in its order, Save, open in
  a tab, the file's menu; one stage height for every kind; a no-preview card offers the file
- the name, the card's picture and the menu's Preview open it; an image up to 8 MB is its own
  thumbnail
This commit is contained in:
2026-10-12 15:28:31 +02:00
parent 6707550513
commit fb8fb980e5
26 changed files with 1090 additions and 48 deletions
+12 -1
View File
@@ -55,7 +55,8 @@
"keep": "Keep",
"unkeep": "Stop keeping",
"details": "Details…",
"upload": "Upload"
"upload": "Upload",
"view": "Preview"
},
"folder": {
"menu": "Folder actions",
@@ -91,5 +92,15 @@
"label": "A link to download",
"placeholder": "Paste a link and I'll download it (YouTube, TikTok, Facebook…)",
"download": "Download"
},
"preview": {
"prev": "Previous file",
"next": "Next file",
"position": "{{n}} / {{total}}",
"openInTab": "Open in a new tab",
"none": "There is no preview for this file",
"asText": "Shown as plain text",
"truncated": "Showing the start of this {{size}} file",
"wrap": "Wrap long lines"
}
}