Embed images and files in descriptions and notes #2

Merged
macoun merged 2 commits from feat/inline-media into main 2026-10-16 06:14:49 +00:00
Owner

Images and files can now be inserted straight into a contract description or a
note.

What it does

  • Toolbar button, paste (screenshots), drag and drop, and a camera button on
    touch devices.
  • Images show inline and open larger on click.
  • PDFs show as a card with a first-page thumbnail and open in the browser's
    built-in viewer. Other files show as a card and download on click.
  • The printed guide and the history show "[obrázek: name]" / "[soubor: name]".

How it works

  • Every embedded file is a regular contract attachment (also listed in the
    attachments panel). The HTML only stores a reference (data-attachment-id /
    data-file-id), never a src; content is fetched with the bearer token and
    shown from a blob URL. No database changes (ADR-025).
  • Upload with ?reuseExisting=true returns the existing attachment when the
    same file is already on the contract instead of a 409.
  • Duplicate uploads are detected before writing to disk, so they no longer
    leave orphaned files.
  • Read-only views use a non-editable TipTap with the same nodes after DOMPurify.
  • PDF thumbnails use pdf.js (Apache-2.0), lazy-loaded only when a PDF is shown.
  • SPA CSP gains frame-src 'self' blob: and worker-src 'self' blob: for the
    PDF viewer and pdf.js worker.

Testing

  • 237 backend tests (new: reuse of an existing attachment, no orphan file on
    duplicate, plain-text labels for embedded media).
  • Frontend build and Docker image build OK.
  • Browser check against a stubbed API: multi-file upload, PDF thumbnail, paste,
    read-only rendering, image and PDF preview, malicious HTML is stripped.

Deployment

No migrations. Update the stack as usual.

Images and files can now be inserted straight into a contract description or a note. ## What it does - Toolbar button, paste (screenshots), drag and drop, and a camera button on touch devices. - Images show inline and open larger on click. - PDFs show as a card with a first-page thumbnail and open in the browser's built-in viewer. Other files show as a card and download on click. - The printed guide and the history show "[obrázek: name]" / "[soubor: name]". ## How it works - Every embedded file is a regular contract attachment (also listed in the attachments panel). The HTML only stores a reference (`data-attachment-id` / `data-file-id`), never a `src`; content is fetched with the bearer token and shown from a blob URL. No database changes (ADR-025). - Upload with `?reuseExisting=true` returns the existing attachment when the same file is already on the contract instead of a 409. - Duplicate uploads are detected before writing to disk, so they no longer leave orphaned files. - Read-only views use a non-editable TipTap with the same nodes after DOMPurify. - PDF thumbnails use pdf.js (Apache-2.0), lazy-loaded only when a PDF is shown. - SPA CSP gains `frame-src 'self' blob:` and `worker-src 'self' blob:` for the PDF viewer and pdf.js worker. ## Testing - 237 backend tests (new: reuse of an existing attachment, no orphan file on duplicate, plain-text labels for embedded media). - Frontend build and Docker image build OK. - Browser check against a stubbed API: multi-file upload, PDF thumbnail, paste, read-only rendering, image and PDF preview, malicious HTML is stripped. ## Deployment No migrations. Update the stack as usual.
- Uploading with ?reuseExisting=true returns the existing attachment (200) when the same file is already on the contract, instead of a 409.
- Duplicates are detected before the file is written to disk, so a duplicate upload no longer leaves an orphaned file in contracts/.
- Plain-text rendering for the printed guide and history labels embedded images and files as [obrázek: name] and [soubor: name].
- The SPA CSP allows blob: frames and workers for the PDF preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(web): embed images and files in descriptions and notes
All checks were successful
Build and publish Docker image / migrations-check (pull_request) Successful in 44s
Build and publish Docker image / test (pull_request) Successful in 1m41s
Build and publish Docker image / build-and-push (pull_request) Has been skipped
dc53766ada
The rich text editor can now insert an image or a file into a contract
description or a note: toolbar button, paste (screenshots), drag and drop, and a
camera button on touch devices. The file is uploaded as a regular contract
attachment and the HTML only keeps a reference (<img data-attachment-id> or
<div data-file-id>), never a src; content is fetched with the bearer token and
shown from a blob URL.

- Read-only views render through a non-editable TipTap with the same nodes,
  after DOMPurify, so saved text looks exactly as while editing.
- PDFs get a first-page thumbnail (pdf.js, Apache-2.0, lazy-loaded) and open in
  the browser's built-in viewer; other files download on click.
- Files are inserted after the current selection so several files picked at
  once no longer overwrite each other.
- The attachments panel refreshes after a file is embedded; the description
  collapse re-measures when images finish loading.
- Text that starts with an image or file counts as formatted text (frontend
  and FormattedText).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
macoun merged commit 1d45c14c31 into main 2026-10-16 06:14:49 +00:00
macoun deleted branch feat/inline-media 2026-10-16 06:14:50 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
STATOTEST/contract-microservice!2
No description provided.