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>
81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useRef } from 'react'
|
|
import { createPortal } from 'react-dom'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
import { Link2, Blocks } from 'lucide-react'
|
|
import type { ParsedBlockReference } from '@/lib/editor/parse-block-reference'
|
|
|
|
export type SmartPasteMenuProps = {
|
|
anchor: { top: number; left: number }
|
|
reference: ParsedBlockReference
|
|
sourceNoteTitle?: string
|
|
onLive: () => void
|
|
onPlain: () => void
|
|
onClose: () => void
|
|
}
|
|
|
|
export function SmartPasteMenu({
|
|
anchor,
|
|
reference,
|
|
sourceNoteTitle,
|
|
onLive,
|
|
onPlain,
|
|
onClose,
|
|
}: SmartPasteMenuProps) {
|
|
const { t } = useLanguage()
|
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
function handleClickOutside(e: MouseEvent) {
|
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
onClose()
|
|
}
|
|
}
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape') onClose()
|
|
}
|
|
document.addEventListener('mousedown', handleClickOutside)
|
|
document.addEventListener('keydown', handleKeyDown)
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside)
|
|
document.removeEventListener('keydown', handleKeyDown)
|
|
}
|
|
}, [onClose])
|
|
|
|
const menuStyle: React.CSSProperties = {
|
|
position: 'fixed',
|
|
left: anchor.left,
|
|
top: anchor.top + 8,
|
|
zIndex: 9999,
|
|
maxWidth: 320,
|
|
}
|
|
|
|
if (Number(menuStyle.left) + 320 > window.innerWidth) {
|
|
menuStyle.left = Math.max(8, window.innerWidth - 328)
|
|
}
|
|
if (Number(menuStyle.top) + 140 > window.innerHeight) {
|
|
menuStyle.top = anchor.top - 132
|
|
}
|
|
|
|
const previewTitle = sourceNoteTitle?.trim() || t('smartPaste.unknownNote')
|
|
|
|
return createPortal(
|
|
<div ref={menuRef} style={menuStyle} className="block-action-menu smart-paste-menu">
|
|
<p className="smart-paste-menu__hint">{t('smartPaste.prompt')}</p>
|
|
<p className="smart-paste-menu__source" title={reference.raw}>
|
|
{previewTitle}
|
|
</p>
|
|
<button type="button" className="block-action-item" onClick={onLive}>
|
|
<Blocks size={16} />
|
|
<span>{t('smartPaste.liveBlock')}</span>
|
|
</button>
|
|
<button type="button" className="block-action-item" onClick={onPlain}>
|
|
<Link2 size={16} />
|
|
<span>{t('smartPaste.plainLink')}</span>
|
|
</button>
|
|
</div>,
|
|
document.body,
|
|
)
|
|
}
|