/** * Block Handle — Notion-style drag handle and context menu * * Pure logic module: handle state, context menu actions, and turn-into items. * No DOM dependencies — rendering is handled in main.js. */ import type { BlockHandlePosition, BlockHandleAction, TurnIntoItem, BlockHandleMode } from './types.js'; // ============================================================ // Icons // ============================================================ /** 6-dot grip icon for the drag handle */ export const BLOCK_HANDLE_ICON = '\u2807'; /** Plus icon for the add-block button */ export const BLOCK_HANDLE_ADD_ICON = '+'; // ============================================================ // Context Menu Actions // ============================================================ export const BLOCK_HANDLE_ACTIONS: BlockHandleAction[] = [ { id: 'turnInto', label: 'Turn into...', icon: '\u21C4' }, { id: 'delete', label: 'Delete', icon: '\uD83D\uDDD1' }, { id: 'duplicate', label: 'Duplicate', icon: '\u2398' }, { id: 'moveUp', label: 'Move up', icon: '\u2191' }, { id: 'moveDown', label: 'Move down', icon: '\u2193' }, ]; // ============================================================ // Turn Into Items (block type conversion targets) // ============================================================ export const TURN_INTO_ITEMS: TurnIntoItem[] = [ { id: 'paragraph', name: 'Paragraph', icon: '\u00B6' }, { id: 'heading1', name: 'Heading 1', icon: 'H1' }, { id: 'heading2', name: 'Heading 2', icon: 'H2' }, { id: 'heading3', name: 'Heading 3', icon: 'H3' }, { id: 'bulletList', name: 'Bullet List', icon: '\u2022' }, { id: 'numberedList', name: 'Numbered List', icon: '1.' }, { id: 'taskList', name: 'Task List', icon: '\u2611' }, { id: 'blockquote', name: 'Blockquote', icon: '\u201C' }, { id: 'codeBlock', name: 'Code Block', icon: '' }, ]; /** * Filter turn-into items by a search query. */ export function filterTurnIntoItems(query: string): TurnIntoItem[] { const q = (query || '').trim().toLowerCase(); if (!q) return [...TURN_INTO_ITEMS]; return TURN_INTO_ITEMS.filter(item => item.name.toLowerCase().includes(q)); } // ============================================================ // Block Handle State // ============================================================ /** * Manages the state of the block drag handle and its context menus. * Pure state object — no DOM coupling. */ export class BlockHandleState { visible: boolean; position: BlockHandlePosition | null; blockPos: number | null; contextMenuOpen: boolean; turnIntoMenuOpen: boolean; constructor() { this.visible = false; this.position = null; this.blockPos = null; this.contextMenuOpen = false; this.turnIntoMenuOpen = false; } /** * Show the handle at a position, associated with a block at the given * ProseMirror document position. */ show(position: BlockHandlePosition, blockPos: number): void { this.visible = true; this.position = { ...position }; this.blockPos = blockPos; } hide(): void { this.visible = false; this.position = null; this.blockPos = null; this.contextMenuOpen = false; this.turnIntoMenuOpen = false; } updatePosition(position: BlockHandlePosition): void { this.position = { ...position }; } openContextMenu(): void { this.contextMenuOpen = true; } closeContextMenu(): void { this.contextMenuOpen = false; this.turnIntoMenuOpen = false; } openTurnIntoMenu(): void { this.turnIntoMenuOpen = true; } closeTurnIntoMenu(): void { this.turnIntoMenuOpen = false; } /** * Determine if the handle should be hidden in a given mode. */ isHiddenInMode(mode: BlockHandleMode): boolean { return mode === 'zen' || mode === 'print'; } }