Add in-app document preview

Adds an inline preview for stored document files alongside download.

- API Server: GET /api/documents/{id}/file honours ?inline=1, serving an
  inline Content-Disposition so the browser renders the file instead of
  forcing a download (default stays attachment).
- Web App: BFF forwards the inline flag; documentPreviewUrl() helper; an eye
  icon; and a preview modal (Teleported to body) that picks a viewer from the
  file extension — images -> <img>, PDFs/text -> <iframe>, else a
  download-instead fallback. Closes on backdrop click or Escape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-07-13 20:18:40 +02:00
co-authored by Claude Opus 4.8
parent 52f84ad6cf
commit 28291e27d7
10 changed files with 132 additions and 29 deletions
+7 -1
View File
@@ -518,6 +518,8 @@ func (s *Server) handleDeleteDocument(w http.ResponseWriter, r *http.Request) {
// ---------------------------------------------------------------------------
// GET /api/documents/{id}/file — stream the stored blob for a document.
// By default the response is an attachment (download); `?inline=1` serves it
// with an inline disposition so the browser renders it in-place (preview).
func (s *Server) handleDownloadDocument(w http.ResponseWriter, r *http.Request) {
who := caller(r)
id := r.PathValue("id")
@@ -554,7 +556,11 @@ func (s *Server) handleDownloadDocument(w http.ResponseWriter, r *http.Request)
if cl := resp.Header.Get("Content-Length"); cl != "" {
w.Header().Set("Content-Length", cl)
}
w.Header().Set("Content-Disposition", "attachment; filename=\""+sanitizeFilename(rec.File)+"\"")
disposition := "attachment"
if r.URL.Query().Get("inline") == "1" {
disposition = "inline"
}
w.Header().Set("Content-Disposition", disposition+"; filename=\""+sanitizeFilename(rec.File)+"\"")
w.WriteHeader(http.StatusOK)
_, _ = io.Copy(w, resp.Body)
}