diff --git a/memento-note/components/block-action-menu.tsx b/memento-note/components/block-action-menu.tsx index 54ad401..83474cb 100644 --- a/memento-note/components/block-action-menu.tsx +++ b/memento-note/components/block-action-menu.tsx @@ -322,8 +322,10 @@ export function BlockActionMenu({ const menuStyle: React.CSSProperties = { position: 'fixed', left: menuLeft > window.innerWidth - 230 ? anchorRect.left - 215 : menuLeft, - top: menuTop + 320 > window.innerHeight ? window.innerHeight - 330 : menuTop, + top: Math.max(10, Math.min(menuTop, window.innerHeight - 620)), zIndex: 9999, + maxHeight: window.innerHeight - 20, + overflowY: 'auto', } return createPortal( diff --git a/memento-note/components/editor-block-drag-handle.tsx b/memento-note/components/editor-block-drag-handle.tsx index 1421ede..0e921c0 100644 --- a/memento-note/components/editor-block-drag-handle.tsx +++ b/memento-note/components/editor-block-drag-handle.tsx @@ -2,7 +2,7 @@ /** * Poignée drag globale (Novel / tiptap-extension-global-drag-handle). - * L’extension TipTap positionne cet élément ; le clic ouvre le menu bloc. + * 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' @@ -46,14 +46,37 @@ export const EditorBlockDragHandle = memo(function EditorBlockDragHandle({ onOpenMenuRef.current(el.getBoundingClientRect()) } + const clampPosition = () => { + if (el.classList.contains('hide') || el.style.display === 'none') return + const rect = el.getBoundingClientRect() + if (rect.height === 0) return + const overshootBottom = rect.bottom - window.innerHeight + 10 + const overshootTop = 10 - rect.top + if (overshootBottom > 0) { + const currentTop = parseFloat(el.style.top) || 0 + el.style.top = `${currentTop - overshootBottom}px` + } else if (overshootTop > 0) { + const currentTop = parseFloat(el.style.top) || 0 + el.style.top = `${currentTop + overshootTop}px` + } + } + el.addEventListener('pointerdown', onPointerDown) el.addEventListener('dragstart', onDragStart) el.addEventListener('click', onClick) + const observer = new MutationObserver(clampPosition) + observer.observe(el, { attributes: true, attributeFilter: ['style'] }) + window.addEventListener('scroll', clampPosition, { passive: true }) + window.addEventListener('resize', clampPosition) + return () => { el.removeEventListener('pointerdown', onPointerDown) el.removeEventListener('dragstart', onDragStart) el.removeEventListener('click', onClick) + observer.disconnect() + window.removeEventListener('scroll', clampPosition) + window.removeEventListener('resize', clampPosition) } }, [editor]) diff --git a/memento-note/lib/editor/block-at-drag-handle.ts b/memento-note/lib/editor/block-at-drag-handle.ts index 8402f88..56a7b40 100644 --- a/memento-note/lib/editor/block-at-drag-handle.ts +++ b/memento-note/lib/editor/block-at-drag-handle.ts @@ -21,5 +21,20 @@ export function resolveBlockAtDragHandle(editor: Editor): { node: PMNode; pos: n const node = editor.state.doc.nodeAt(blockPos) if (!node) return null + // Climb up to container blocks (columns, toggleBlock, calloutBlock) + // so the drag handle operates on the whole container, not inner paragraphs + const CONTAINER_TYPES = ['columns', 'toggleBlock', 'calloutBlock'] + const $blockPos = editor.state.doc.resolve(blockPos) + for (let depth = $blockPos.depth; depth > 0; depth--) { + const ancestor = $blockPos.node(depth) + if (CONTAINER_TYPES.includes(ancestor.type.name)) { + const containerPos = $blockPos.before(depth) + const containerNode = editor.state.doc.nodeAt(containerPos) + if (containerNode) { + return { node: containerNode, pos: containerPos } + } + } + } + return { node, pos: blockPos } }