+
+ setQuery(e.target.value)}
+ onKeyDown={(e) => {
+ if (isImeComposing(e)) return
+ if (isPaletteNextKey(e)) {
+ e.preventDefault()
+ e.stopPropagation()
+ setActive((a) => Math.min(results.length - 1, a + 1))
+ } else if (isPalettePreviousKey(e)) {
+ e.preventDefault()
+ e.stopPropagation()
+ setActive((a) => Math.max(0, a - 1))
+ } else if (e.key === 'Enter') {
+ e.preventDefault()
+ const note = results[active]
+ if (note) choose(note)
+ } else if (e.key === 'Escape') {
+ e.preventDefault()
+ e.stopPropagation()
+ close()
+ }
+ }}
+ className="w-full bg-transparent text-base text-ink-900 outline-none placeholder:text-ink-400"
+ />
+
+
+ {results.length === 0 ? (
+
+ {drawings.length === 0 ? 'No drawings in this vault yet.' : 'No matches.'}
+
+ ) : (
+ results.map((n, i) => (
+
+ ))
+ )}
+
+
+
+ ↑↓ move
+
+
+ ↵ embed
+
+
+ esc close
+
+
+
+ )
+}
diff --git a/packages/app-core/src/components/ExcalidrawPreview.tsx b/packages/app-core/src/components/ExcalidrawPreview.tsx
new file mode 100644
index 00000000..c8f58090
--- /dev/null
+++ b/packages/app-core/src/components/ExcalidrawPreview.tsx
@@ -0,0 +1,78 @@
+import { useEffect, useState } from 'react'
+import type { CSSProperties } from 'react'
+import { getExcalidrawPreview } from '../lib/excalidraw-preview'
+import { useStore } from '../store'
+
+export interface ExcalidrawPreviewProps {
+ path: string
+ width?: number
+ height?: number
+ className?: string
+ onClick?: () => void
+}
+
+/**
+ * Renders an Excalidraw drawing as a PNG image (exported @2x). Used both by
+ * the editor live-preview widget and the preview-pane hydration to show a
+ * drawing as an image-like embed inside a note. Re-renders when the vault
+ * watcher bumps `excalidrawPreviewVersion`.
+ */
+export function ExcalidrawPreview({
+ path,
+ width,
+ height,
+ className,
+ onClick
+}: ExcalidrawPreviewProps): JSX.Element {
+ const [src, setSrc] = useState