fix: prevent image drop overlay from breaking internal DnD and block handle

Three fixes for the image drop overlay interference:

1. Block handle clipping: Add padding (0 4px) to editor container so
   BlockNote's side menu (42px) fits within the overflow clip edge.
   overflow-y:auto forces overflow-x:auto (CSS spec), which was clipping
   the menu 2px past the container's left boundary.

2. Block handle click interference: Extract isInteractiveTarget() to
   exclude .bn-side-menu from handleContainerClick — prevents the
   container from stealing focus when clicking drag handle or add button.

3. Internal drag isolation: Track document-level dragstart/dragend to
   flag internal HTML5 drags (tabs, blocks). Tauri onDragDropEvent
   handler skips entirely during internal drags to prevent interference.
   Extract useInternalDragFlag() and handleTauriDrop() to keep
   useImageDrop under CodeScene complexity threshold.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Test
2026-03-15 20:53:43 +01:00
parent acd048b144
commit 698c4cece1
5 changed files with 168 additions and 132 deletions

View File

@@ -23,7 +23,11 @@
opacity: 0.55;
}
/* BlockNote container */
/* BlockNote container
padding: 0 4px prevents BlockNote's side menu (≈42px wide) from being
clipped — the menu hangs left of the editor body (40px padding), and
overflow-y:auto forces overflow-x:auto (CSS spec). The 4px padding
keeps the menu inside the padding box, which is the overflow clip edge. */
.editor__blocknote-container {
flex: 1;
min-height: 0;
@@ -32,6 +36,7 @@
justify-content: center;
position: relative;
cursor: text;
padding: 0 4px;
}
/* Drag-over state: subtle border highlight */

View File

@@ -22,6 +22,11 @@ function useInsertImageCallback(editor: ReturnType<typeof useCreateBlockNote>) {
}, [])
}
/** Returns true if the click target is an interactive area the container should not steal focus from. */
function isInteractiveTarget(target: HTMLElement): boolean {
return !!(target.closest('[contenteditable="true"]') || target.closest('.bn-side-menu'))
}
/** Single BlockNote editor view — content is swapped via replaceBlocks */
export function SingleEditorView({ editor, entries, onNavigateWikilink, onChange, vaultPath, isDarkTheme, editable = true }: {
editor: ReturnType<typeof useCreateBlockNote>
@@ -40,9 +45,7 @@ export function SingleEditorView({ editor, entries, onNavigateWikilink, onChange
const { isDragOver } = useImageDrop({ containerRef, onImageUrl, vaultPath })
const handleContainerClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (!editable) return
const target = e.target as HTMLElement
if (target.closest('[contenteditable="true"]')) return
if (!editable || isInteractiveTarget(e.target as HTMLElement)) return
const blocks = editor.document
if (blocks.length > 0) {
editor.setTextCursorPosition(blocks[blocks.length - 1].id, 'end')