* feat: add copy_image_to_vault Rust command for native drag-drop Adds a new Tauri command that copies an image file from a source path (provided by Tauri's drag-drop event) directly into vault/attachments/. More efficient than base64 encoding for filesystem drag-drop. Also adds core:webview:allow-on-drag-drop-event permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: handle Tauri native drag-drop for filesystem images Listen for Tauri's onDragDropEvent to intercept OS-level file drops that bypass the webview's HTML5 DnD API. When image files are dropped: 1. Copy to vault/attachments via copy_image_to_vault command 2. Insert image block into BlockNote at cursor position Also passes vaultPath through Editor → EditorContent → SingleEditorView so the hook can invoke the Tauri command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove nonexistent drag-drop permission from capabilities The onDragDropEvent API works through core:event:default (included in core:default), not a separate webview permission. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct DragDropEvent type handling for 'over' events Tauri's DragDropEvent discriminated union only provides `paths` on 'drop' events, not 'over'. Show drag overlay unconditionally on 'over' since we can't filter by image paths at that stage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * design: drag-drop-images wireframes (idle + drag-over overlay) * style: rustfmt mcp.rs and image.rs --------- Co-authored-by: Test <test@test.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
116 lines
4.1 KiB
TypeScript
116 lines
4.1 KiB
TypeScript
import type { VaultEntry, NoteStatus } from '../types'
|
|
import type { useCreateBlockNote } from '@blocknote/react'
|
|
import { DiffView } from './DiffView'
|
|
import { BreadcrumbBar } from './BreadcrumbBar'
|
|
import { countWords } from '../utils/wikilinks'
|
|
import { SingleEditorView } from './SingleEditorView'
|
|
|
|
interface Tab {
|
|
entry: VaultEntry
|
|
content: string
|
|
}
|
|
|
|
interface EditorContentProps {
|
|
activeTab: Tab | null
|
|
isLoadingNewTab: boolean
|
|
entries: VaultEntry[]
|
|
editor: ReturnType<typeof useCreateBlockNote>
|
|
diffMode: boolean
|
|
diffContent: string | null
|
|
diffLoading: boolean
|
|
onToggleDiff: () => void
|
|
activeStatus: NoteStatus
|
|
showDiffToggle: boolean
|
|
showAIChat?: boolean
|
|
onToggleAIChat?: () => void
|
|
inspectorCollapsed: boolean
|
|
onToggleInspector: () => void
|
|
onNavigateWikilink: (target: string) => void
|
|
onEditorChange?: () => void
|
|
onTrashNote?: (path: string) => void
|
|
onRestoreNote?: (path: string) => void
|
|
onArchiveNote?: (path: string) => void
|
|
onUnarchiveNote?: (path: string) => void
|
|
vaultPath?: string
|
|
}
|
|
|
|
function EditorLoadingSkeleton() {
|
|
return (
|
|
<div className="flex flex-1 flex-col gap-3 p-8 animate-pulse" style={{ minHeight: 0 }}>
|
|
<div className="h-6 w-2/5 rounded bg-muted" />
|
|
<div className="h-4 w-4/5 rounded bg-muted" />
|
|
<div className="h-4 w-3/5 rounded bg-muted" />
|
|
<div className="h-4 w-4/5 rounded bg-muted" />
|
|
<div className="h-4 w-2/5 rounded bg-muted" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function DiffModeView({ diffContent, onToggleDiff }: { diffContent: string | null; onToggleDiff: () => void }) {
|
|
return (
|
|
<div className="flex-1 overflow-auto">
|
|
<button
|
|
className="flex items-center gap-1.5 px-4 py-2 text-xs text-primary bg-muted border-b border-border cursor-pointer hover:bg-accent transition-colors w-full border-t-0 border-l-0 border-r-0"
|
|
onClick={onToggleDiff}
|
|
title="Back to editor"
|
|
>
|
|
<span style={{ fontSize: 14, lineHeight: 1 }}>←</span>
|
|
Back to editor
|
|
</button>
|
|
<DiffView diff={diffContent ?? ''} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/** Bind an optional callback to a path, returning undefined if callback is absent */
|
|
function bindPath(cb: ((path: string) => void) | undefined, path: string) {
|
|
return cb ? () => cb(path) : undefined
|
|
}
|
|
|
|
function ActiveTabBreadcrumb({ activeTab, props }: {
|
|
activeTab: Tab
|
|
props: Omit<EditorContentProps, 'activeTab' | 'isLoadingNewTab' | 'entries' | 'editor' | 'onNavigateWikilink' | 'onEditorChange'>
|
|
}) {
|
|
const wordCount = countWords(activeTab.content)
|
|
const path = activeTab.entry.path
|
|
return (
|
|
<BreadcrumbBar
|
|
entry={activeTab.entry}
|
|
wordCount={wordCount}
|
|
noteStatus={props.activeStatus}
|
|
showDiffToggle={props.showDiffToggle}
|
|
diffMode={props.diffMode}
|
|
diffLoading={props.diffLoading}
|
|
onToggleDiff={props.onToggleDiff}
|
|
showAIChat={props.showAIChat}
|
|
onToggleAIChat={props.onToggleAIChat}
|
|
inspectorCollapsed={props.inspectorCollapsed}
|
|
onToggleInspector={props.onToggleInspector}
|
|
onTrash={bindPath(props.onTrashNote, path)}
|
|
onRestore={bindPath(props.onRestoreNote, path)}
|
|
onArchive={bindPath(props.onArchiveNote, path)}
|
|
onUnarchive={bindPath(props.onUnarchiveNote, path)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export function EditorContent({
|
|
activeTab, isLoadingNewTab, entries, editor,
|
|
diffMode, diffContent, onToggleDiff,
|
|
onNavigateWikilink, onEditorChange, vaultPath,
|
|
...breadcrumbProps
|
|
}: EditorContentProps) {
|
|
return (
|
|
<div className="flex flex-1 flex-col min-w-0 min-h-0">
|
|
{activeTab && <ActiveTabBreadcrumb activeTab={activeTab} props={{ diffMode, diffContent, onToggleDiff, ...breadcrumbProps }} />}
|
|
{diffMode && <DiffModeView diffContent={diffContent} onToggleDiff={onToggleDiff} />}
|
|
{!diffMode && activeTab && (
|
|
<div style={{ display: 'flex', flex: 1, flexDirection: 'column', minHeight: 0 }}>
|
|
<SingleEditorView editor={editor} entries={entries} onNavigateWikilink={onNavigateWikilink} onChange={onEditorChange} vaultPath={vaultPath} />
|
|
</div>
|
|
)}
|
|
{isLoadingNewTab && !diffMode && <EditorLoadingSkeleton />}
|
|
</div>
|
|
)
|
|
}
|