Multiple feature additions and improvements across the application: - NextGen Editor: drag handles, smart paste, block actions - Structured views: Kanban and table layouts for notes - Architectural Grid: new brainstorming/agent interface prototype - Flashcards: SM-2 revision algorithm with AI generation - MCP server: robustness improvements - Graph/PDF chat: fix click propagation and copy behavior - Various UI/UX enhancements and bug fixes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
79 lines
2.2 KiB
TypeScript
79 lines
2.2 KiB
TypeScript
'use client'
|
||
|
||
/**
|
||
* Poignée drag globale (Novel / tiptap-extension-global-drag-handle).
|
||
* L’extension TipTap positionne cet élément ; le clic ouvre le menu bloc.
|
||
*/
|
||
import { memo, useEffect, useRef } from 'react'
|
||
import type { Editor } from '@tiptap/core'
|
||
import { BLOCK_DRAG_HANDLE_ID, resolveBlockAtDragHandle } from '@/lib/editor/block-at-drag-handle'
|
||
|
||
type EditorBlockDragHandleProps = {
|
||
editor: Editor | null
|
||
onOpenMenu: (anchorRect: DOMRect) => void
|
||
}
|
||
|
||
export const EditorBlockDragHandle = memo(function EditorBlockDragHandle({
|
||
editor,
|
||
onOpenMenu,
|
||
}: EditorBlockDragHandleProps) {
|
||
const dragStartedRef = useRef(false)
|
||
const onOpenMenuRef = useRef(onOpenMenu)
|
||
onOpenMenuRef.current = onOpenMenu
|
||
|
||
useEffect(() => {
|
||
if (!editor || editor.isDestroyed) return
|
||
const el = document.getElementById(BLOCK_DRAG_HANDLE_ID)
|
||
if (!el) return
|
||
|
||
const onPointerDown = () => {
|
||
dragStartedRef.current = false
|
||
}
|
||
|
||
const onDragStart = () => {
|
||
dragStartedRef.current = true
|
||
}
|
||
|
||
const onClick = (e: MouseEvent) => {
|
||
if (dragStartedRef.current) {
|
||
dragStartedRef.current = false
|
||
return
|
||
}
|
||
e.preventDefault()
|
||
e.stopPropagation()
|
||
const block = resolveBlockAtDragHandle(editor)
|
||
if (!block) return
|
||
onOpenMenuRef.current(el.getBoundingClientRect())
|
||
}
|
||
|
||
el.addEventListener('pointerdown', onPointerDown)
|
||
el.addEventListener('dragstart', onDragStart)
|
||
el.addEventListener('click', onClick)
|
||
|
||
return () => {
|
||
el.removeEventListener('pointerdown', onPointerDown)
|
||
el.removeEventListener('dragstart', onDragStart)
|
||
el.removeEventListener('click', onClick)
|
||
}
|
||
}, [editor])
|
||
|
||
return (
|
||
<div
|
||
id={BLOCK_DRAG_HANDLE_ID}
|
||
className="drag-handle hide"
|
||
role="button"
|
||
tabIndex={-1}
|
||
aria-hidden="true"
|
||
>
|
||
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true">
|
||
<circle cx="4" cy="3" r="1.2" />
|
||
<circle cx="10" cy="3" r="1.2" />
|
||
<circle cx="4" cy="7" r="1.2" />
|
||
<circle cx="10" cy="7" r="1.2" />
|
||
<circle cx="4" cy="11" r="1.2" />
|
||
<circle cx="10" cy="11" r="1.2" />
|
||
</svg>
|
||
</div>
|
||
)
|
||
})
|