Skip to content

feat/client: embed images inline in notes#54

Open
ClemPera wants to merge 5 commits into
mainfrom
feat/client-note-image-embed
Open

feat/client: embed images inline in notes#54
ClemPera wants to merge 5 commits into
mainfrom
feat/client-note-image-embed

Conversation

@ClemPera

@ClemPera ClemPera commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Images can now be inserted anywhere in a note via a toolbar button, paste, or drag-and-drop, and render at the position the user placed them.

Images are embedded as base64 data URIs directly in the markdown content (standard ![alt](data:...) syntax), so they flow through the existing note encryption and sync pipeline unchanged: no new server endpoints, tables, or sync logic. Uploaded images are downscaled/recompressed client-side (max 1600px, JPEG quality 0.85) to keep the encrypted note blob within the server's 16MB column limit; GIFs are left untouched to preserve animation, and oversized originals or still-too-large results are rejected with a toast before insertion.

claude added 5 commits July 2, 2026 07:54
Images can now be inserted anywhere in a note via a toolbar button,
paste, or drag-and-drop, and render at the position the user placed
them.

Images are embedded as base64 data URIs directly in the markdown
content (standard `![alt](data:...)` syntax), so they flow through
the existing note encryption and sync pipeline unchanged: no new
server endpoints, tables, or sync logic. Uploaded images are
downscaled/recompressed client-side (max 1600px, JPEG quality 0.85)
to keep the encrypted note blob within the server's 16MB column
limit; GIFs are left untouched to preserve animation, and oversized
originals or still-too-large results are rejected with a toast
before insertion.
…drop works

Tauri v2 enables its own OS-level drag-drop handling by default
(dragDropEnabled: true), which intercepts drag/drop at the WebView
layer and prevents the browser's native HTML5 drop event from
carrying real file data through dataTransfer.files. This is why the
editor's dropcursor indicator showed correctly (dragover still fires)
but the actual drop silently did nothing: handleDrop's dataTransfer
lookup found no files.

Nothing in the app uses Tauri's native onDragDropEvent API, so
disabling it is safe and restores standard browser drag-and-drop
behavior for the note editor.

Ref: tauri-apps/tauri#14373
Enables tiptap's built-in resize handles on note images (drag a
corner, aspect ratio locked). The default renderMarkdown for images
only emits plain `![alt](src)`, which has no field for width/height,
so a resize would silently revert on the next save. ResizableImage
overrides renderMarkdown to fall back to a raw `<img width height>`
tag whenever a size is set, and plain markdown syntax otherwise;
parseMarkdown already round-trips raw HTML images via the schema's
normal HTML parsing, so this is lossless in both directions.
The resize node view sets width/height as inline styles directly on
the <img>, which beats any CSS class rule (including height: auto)
short of !important. When the note pane got narrower than a resized
image's stored width, max-width: 100% clamped the rendered width but
the inline height stayed pinned at its old value, since a plain
height: auto rule can't win against an inline style, producing the
squished/stretched look. Forcing height: auto with !important makes
the rendered height always follow the (possibly clamped) width via
the image's own intrinsic aspect ratio instead of trusting a
separately-tracked pixel value that can drift out of sync with it.
…ts Files

With dragDropEnabled disabled, OS file drops now reach the browser's
drop event, but WebKitGTK still doesn't populate dataTransfer.files
for them: it only exposes a file:// URI via text/uri-list (this is
what showed up as inserted text before this fix). There's no way to
read a file's bytes from the frontend given just this path due to
browser sandboxing, so handleDrop now falls back to extracting that
path and reading it through a new read_dropped_image Tauri command,
then feeds the resulting bytes through the same prepareImageForInsert
pipeline used everywhere else.

Adds:
- read_image_file in commands.rs (sync, size-capped at 20MB, unit
  tested) plus the read_dropped_image command wrapper
- fileUriToPath/mimeTypeForFilename in lib/image.ts to resolve the
  URI and infer a mime type from the extension, since raw bytes alone
  don't carry one
@ClemPera

ClemPera commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

To test

  • Does images sync correctly? (does the size is the same on all, etc)
  • Does it work fine on android (resize, image import, etc)

Issue

  • If too much images, images too big, etc it failes to sync; either:
    • show something to the user that it's too big (e.g: it's more than x Mb, please reduce the size of the images);
    • fix that so it's never an issue;
    • reduce the quality or compress so even with 30 images it's not an issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants