Skip to content

app-core: add excalidraw drawing embeds#360

Closed
junereycasuga wants to merge 1 commit into
ZenNotes:mainfrom
junereycasuga:feat/embed-excalidraw-on-notes
Closed

app-core: add excalidraw drawing embeds#360
junereycasuga wants to merge 1 commit into
ZenNotes:mainfrom
junereycasuga:feat/embed-excalidraw-on-notes

Conversation

@junereycasuga

@junereycasuga junereycasuga commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds first-class Excalidraw drawing support in notes.

Drawings can now be created, opened, embedded, and previewed inline from markdown using the same wikilink embed syntax as images:

![[diagram.excalidraw]]
![[diagram.excalidraw|600]]
![[diagram.excalidraw|600x400]]

Would close #276 and #278

Changes

  • Add New Drawing command to create and open a .excalidraw file.
  • Add Embed Existing Drawing… command with a searchable drawing picker.
  • Add Embed New Drawing command to create a drawing, insert an embed at the cursor, then open the drawing.
  • Render .excalidraw and Obsidian .excalidraw.md embeds as PNG previews in:
  • editor live preview
  • rendered preview mode
  • Cache rendered previews in-memory by path + mtime.
  • Invalidate previews when the drawing file changes.
  • Click embedded previews to open the drawing in an Excalidraw tab.
  • Prevent note transclusion from expanding .excalidraw files as raw JSON.
  • Update Help docs for creating and embedding drawings.

Demo

Cap.2026-07-10.at.13.31.06.mp4

Testing

  • Added tests for:
    • Excalidraw embed size hints
    • Excalidraw embed path resolution
    • Markdown rendering of Excalidraw embed placeholders

@junereycasuga
junereycasuga requested a review from adibhanna as a code owner July 10, 2026 06:40
@junereycasuga
junereycasuga force-pushed the feat/embed-excalidraw-on-notes branch from 594acdb to 1b4c6c5 Compare July 10, 2026 06:46
adibhanna added a commit that referenced this pull request Jul 10, 2026
…w]] (#276, #278)

Render .excalidraw and Obsidian .excalidraw.md drawings inline in notes via the
wikilink embed syntax (![[d.excalidraw]], |600, |600x400) in both the editor
live-preview and the rendered reading view. Adds New Drawing / Embed Existing
Drawing / Embed New Drawing commands with a searchable picker, an
exportToBlob-based PNG preview (lazy-loaded, cached by path+mtime, refreshed by
the vault watcher), and a transclusion guard so drawings never inline as raw
JSON.

Based on @junereycasuga's PR #360. Polished on merge:
- Bounded the preview cache with a 24-entry LRU (it was unbounded; each entry is
  a full @2x base64 PNG) and removed the dead invalidateAllExcalidrawPreviews.
- Fixed a reading-view re-mount flash: the preview pane rebuilds its DOM on every
  render, tearing down each embed's React root, so cache hits still flashed the
  loading pulse. ExcalidrawPreview now seeds from a synchronous cache peek and
  holds the last image while it re-resolves.

Verified end to end in-app over CDP: an embedded drawing renders as an inline PNG
(full size and |240) with no stuck loaders. 28 tests pass; typecheck + build green.

Co-authored-by: junereycasuga <junerey.casuga@gmail.com>
@adibhanna

Copy link
Copy Markdown
Contributor

Thanks @junereycasuga, this is a really nice, well-tested feature and it slotted into the existing embed pipeline cleanly. Merged into release/v2.13.0 in 1c5493a (ships in v2.13.0), credited to you as co-author.

I gave it a thorough review (no regressions to ![[image]]/![[Note]] embeds, no XSS/traversal, no blob leak, and the heavy Excalidraw dep is genuinely lazy-loaded), and polished two things on the way in:

  • Bounded the preview cache. previewCache had no cap, and each entry is a full @2x base64 PNG, so browsing a vault with many drawings would slowly grow the heap. Added a 24-entry LRU (mirroring the markdown cache) and removed invalidateAllExcalidrawPreviews, which had no callers.
  • Fixed a reading-view re-mount flash. The preview pane rebuilds its DOM on every render, tearing down each embed's React root, so even a cache hit flashed the loading pulse. ExcalidrawPreview now seeds from a synchronous cache peek and holds the last image while it re-resolves.

Verified end to end in-app: an embedded drawing renders as an inline PNG at full size and with a |240 size hint, no stuck loaders. Your 28 tests pass.

A few non-blocking notes for later: bare ![[Drawing]] (no extension) still renders as a link rather than a preview; the picker's secondary label shows the folder enum rather than a subfolder path; and there's no dedicated keybinding for the three commands yet. None block the merge.

Closing since it has landed on the release branch (it reaches main when v2.13.0 ships). Thanks again for the excellent contribution!

@adibhanna adibhanna closed this Jul 10, 2026
adibhanna added a commit that referenced this pull request Jul 10, 2026
… mode (#360)

The drawing editor themes itself to match the app (#363), but the inline
embed preview exported via exportToBlob in its default light mode, so a dark
note showed a stark white drawing box. Pass exportWithDarkMode based on the
resolved theme (the data-theme-mode <html> attribute), key the preview cache
by mode as well as mtime, and bump excalidrawPreviewVersion when the resolved
mode flips so open embeds re-render.

Verified in-app over CDP: the exported PNG background samples rgb(17,17,17) in
dark mode and rgb(255,255,255) in light mode, and embeds refresh on theme flip.
adibhanna added a commit that referenced this pull request Jul 10, 2026
…wnload (#360)

Clicking an embedded drawing opened it via assetTabPath (zen://asset/…),
which EditorPane routes to the generic asset viewer since isAssetTabPath is
checked before isExcalidrawPath — and for a .excalidraw (not an image/pdf)
that viewer offers to download the file. Open the plain drawing path instead
so it routes through isExcalidrawPath to the Excalidraw editor, the same as
opening the drawing from the sidebar. Fixes both the reading-view embed and
the editor live-preview widget.

Verified in-app over CDP: clicking the embed opens the Excalidraw editor
(canvas mounted, no cm-content), and no download is triggered.
adibhanna added a commit that referenced this pull request Jul 10, 2026
…#360)

Right-clicking an embedded drawing fell through to the editor's generic text
context menu (Cut/Copy/Paste/Select All/…), which is meaningless over a
rendered image. Add a focused menu with "Open drawing" and "Copy image",
shared by the reading view and the editor live-preview widget: both dispatch a
zen:excalidraw-embed-menu event (suppressing the text menu) that a small
ContextMenu host renders.

Verified in-app over CDP: right-clicking the embed shows Open drawing / Copy
image (not Paste/Select All).
adibhanna added a commit that referenced this pull request Jul 10, 2026
…nu (#360)

Round out the embedded-drawing right-click menu beyond Open drawing / Copy image:
- Open in split — open the drawing in a pane beside the note (splitPaneWithTab)
- Copy embed — copy the ![[name.excalidraw]] syntax to paste into another note
- Reveal in File Manager — reveal the .excalidraw file on disk (revealNote)

Verified in-app over CDP: the menu shows all five items; Copy embed writes
![[demo.excalidraw]] to the clipboard; Open in split opens the drawing in a
second pane (two panes, drawing canvas rendered).
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.

Automatically resolve and render/link converted Obsidian Excalidraw embeds

2 participants