@tessera-editor/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/Tessera.tsx","../src/ImageNodeView.tsx","../src/context.ts","../src/TableNodeViews.tsx","../src/EmbedTocViews.tsx","../src/SlashMenu.tsx","../src/EmojiMenu.tsx","../src/EmptyLineToolbar.tsx","../src/portal.ts","../src/SelectionToolbar.tsx","../src/CommentPanel.tsx","../src/Panels.tsx","../src/HistoryPanel.tsx","../src/BlockMenu.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type { CSSProperties } from 'react'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport type { Editor } from '@tiptap/react'\nimport type { Content, EditorOptions } from '@tiptap/core'\nimport { DragHandle } from '@tiptap/extension-drag-handle-react'\nimport {\n createTesseraExtensions,\n createTesseraT,\n} from '@tessera-editor/core'\nimport type {\n TesseraLocale,\n UploadService,\n StorageService,\n CommentStore,\n IdentityService,\n} from '@tessera-editor/core'\nimport { createAiController, aiSlashItems } from '@tessera-editor/ai'\nimport type { AIRuntime, SuggestionSession } from '@tessera-editor/ai'\nimport { ImageBlockView } from './ImageNodeView'\nimport { AiTableCellViewExtension, AiTableHeaderViewExtension } from './TableNodeViews'\nimport { EmbedBlockView, TocBlockView } from './EmbedTocViews'\nimport { createSlashRenderer } from './SlashMenu'\nimport { createEmojiRenderer } from './EmojiMenu'\nimport { EmptyLineToolbar } from './EmptyLineToolbar'\nimport { SelectionToolbar } from './SelectionToolbar'\nimport { FindReplacePanel, AskPanel, SuggestionBar } from './Panels'\nimport { HistoryPanel } from './HistoryPanel'\nimport { CommentPanel } from './CommentPanel'\nimport { BlockContextMenuUI } from './BlockMenu'\nimport { TesseraContext } from './context'\nimport '@tessera-editor/core/styles.css'\n\nexport interface TesseraProps {\n /** Initial document (JSON / HTML / markdown string). Host owns persistence. */\n content?: Content\n locale?: TesseraLocale\n /** Injected image upload capability (ADR-0001 family). */\n upload?: UploadService\n /** v1.1: version-history snapshot storage. */\n storage?: StorageService\n /** v1.1: inline comment persistence. */\n comments?: CommentStore\n /** v1.1: current user (comment authorship). */\n identity?: IdentityService\n /** Injected model runtime; presence enables all AI surfaces. */\n ai?: AIRuntime\n /** v1.1: idle window for history auto-capture (playground uses short ones) */\n historyIdleMs?: number\n /** v1.1: document column max-width in px (Slite-style centered column). */\n docWidth?: number\n onUpdate?: (editor: Editor) => void\n onCreate?: (editor: Editor) => void\n}\n\n/**\n * Batteries-included editor (M1+M2 scope). Slite-style: empty-line toolbar,\n * slash menu, selection toolbar, drag handle — deliberately NOT Notion's\n * floating \"+\".\n */\n\n/** Stable reference is required: see the DragHandle usage comment below. */\nconst DRAG_HANDLE_POSITION_CONFIG = { placement: 'left', strategy: 'absolute' } as const\n// NOTE: DragHandle `nested` mode (per-item list dragging) was evaluated and\n// deferred — with nested rules active the handle stopped appearing on plain\n// hover in smoke checks. Revisit with:\n// nested={{ edgeDetection: 'left', allowedContainers: ['bulletList', 'orderedList', 'taskList'] }}\nexport function Tessera({\n content,\n locale = 'zh-CN',\n upload,\n storage,\n comments,\n identity,\n ai: runtime,\n historyIdleMs,\n docWidth,\n onUpdate,\n onCreate,\n}: TesseraProps) {\n const t = useMemo(() => createTesseraT(locale), [locale])\n const [session, setSession] = useState<SuggestionSession | null>(null)\n const fileInputRef = useRef<HTMLInputElement>(null)\n type DragNodeData = {\n node: { attrs: Record<string, unknown>; type: { name: string } } | null\n pos: number\n }\n const dragNodeRef = useRef<DragNodeData | null>(null)\n const handleDragNodeChange = useCallback((data: DragNodeData) => {\n dragNodeRef.current = data.node ? data : null\n }, [])\n\n // Everything handed to useEditor must be referentially stable: an unstable\n // option (any inline callback) makes @tiptap/react call editor.setOptions\n // on every render, and TipTap's setOptions runs view.updateState, which\n // destroys and recreates ALL plugin views. A recreated suggestion view\n // starts from an already-active state with no \"started\" transition, so\n // renderer.onStart never fires and the slash menu UI never mounts.\n // Latest prop values are therefore read through refs instead.\n const onUpdateRef = useRef(onUpdate)\n onUpdateRef.current = onUpdate\n const onCreateRef = useRef(onCreate)\n onCreateRef.current = onCreate\n const runtimeRef = useRef(runtime)\n runtimeRef.current = runtime\n const initialContentRef = useRef(content)\n const editorRef = useRef<Editor | null>(null)\n\n const extensions = useMemo(\n () =>\n createTesseraExtensions({ locale, historyIdleMs }).map(ext => {\n if (ext.name === 'tesseraSlashMenu') {\n return ext.configure({\n render: createSlashRenderer(t),\n extraItems: (ctx: Parameters<NonNullable<import('@tessera-editor/core').SlashMenuOptions['extraItems']>>[0]) => {\n const rt = runtimeRef.current\n return rt ? aiSlashItems({ editor: ctx.editor, runtime: rt, t: ctx.t }) : []\n },\n })\n }\n if (ext.name === 'tesseraEmojiMenu') {\n return ext.configure({ render: createEmojiRenderer() })\n }\n if (ext.name === 'imageBlock') {\n return ImageBlockView\n }\n if (ext.name === 'tableCell') {\n return AiTableCellViewExtension\n }\n if (ext.name === 'tableHeader') {\n return AiTableHeaderViewExtension\n }\n if (ext.name === 'embedBlock') {\n return EmbedBlockView\n }\n if (ext.name === 'tocBlock') {\n return TocBlockView\n }\n return ext\n }),\n // runtime is read through runtimeRef on purpose: rebuilding the extension\n // list after mount cannot apply anyway (extensions are only read when the\n // editor is created) and would only trigger the setOptions churn above\n [locale, t, historyIdleMs],\n )\n\n async function uploadAndInsert(file: File) {\n const ed = editorRef.current\n if (!ed) {\n return\n }\n const storage = ed.storage as unknown as Record<string, { upload?: UploadService }>\n const svc = storage.tesseraServices?.upload\n if (!svc) {\n return\n }\n try {\n const asset = await svc.uploadImage(file)\n ed.chain().focus().setImage({ src: asset.url, alt: asset.name }).run()\n } catch (err) {\n console.error('[Tessera] image upload failed:', err)\n }\n }\n const uploadAndInsertRef = useRef(uploadAndInsert)\n uploadAndInsertRef.current = uploadAndInsert\n\n const editorProps: EditorOptions['editorProps'] = useMemo(\n () => ({\n attributes: {\n class: 'tessera-doc',\n spellcheck: 'false',\n },\n handlePaste: (_view, event) => {\n const file = Array.from(event.clipboardData?.files ?? []).find(f => f.type.startsWith('image/'))\n if (file) {\n void uploadAndInsertRef.current(file)\n return true\n }\n return false\n },\n handleDrop: (_view, event, _slice, moved) => {\n if (moved) {\n return false\n }\n const file = Array.from(event.dataTransfer?.files ?? []).find(f => f.type.startsWith('image/'))\n if (file) {\n event.preventDefault()\n void uploadAndInsertRef.current(file)\n return true\n }\n return false\n },\n }),\n [],\n )\n\n const handleUpdate = useCallback(({ editor: e }: { editor: Editor }) => onUpdateRef.current?.(e), [])\n const handleCreate = useCallback(({ editor: e }: { editor: Editor }) => onCreateRef.current?.(e), [])\n\n const editor = useEditor({\n extensions,\n content: initialContentRef.current,\n onUpdate: handleUpdate,\n onCreate: handleCreate,\n editorProps,\n })\n\n editorRef.current = editor ?? null\n\n // injected services stay current\n useEffect(() => {\n if (editor) {\n const bag = (editor.storage as unknown as Record<string, Record<string, unknown>>).tesseraServices ?? {}\n ;(editor.storage as unknown as Record<string, Record<string, unknown>>).tesseraServices = {\n ...bag,\n upload,\n storage,\n comments,\n identity,\n }\n }\n }, [editor, upload, storage, comments, identity])\n\n // editor events → UI (image picker, sessions from slash AI items)\n useEffect(() => {\n if (!editor) {\n return\n }\n const pickImage = () => fileInputRef.current?.click()\n const onSession = (payload: { session: SuggestionSession | null }) => setSession(payload.session)\n editor.on('tessera:insertImage', pickImage)\n editor.on('tessera:session', onSession as never)\n return () => {\n editor.off('tessera:insertImage', pickImage)\n editor.off('tessera:session', onSession as never)\n }\n }, [editor])\n\n const ai = useMemo(\n () => (editor && runtime ? createAiController(editor, runtime, locale) : null),\n [editor, runtime, locale],\n )\n\n if (!editor) {\n return null\n }\n\n return (\n <TesseraContext.Provider value={{ editor, locale, t, ai }}>\n <div\n className=\"tessera-root\"\n style={{ '--te-doc-max-width': docWidth ? `${docWidth}px` : undefined } as CSSProperties}\n >\n <EditorContent editor={editor} />\n {/* 'left' (vertical center) instead of the default 'left-start': the\n handle must sit mid-row like Slite, not above multi-line blocks.\n The config object must be referentially stable — DragHandle\n re-registers its plugin whenever its props change identity, and\n that re-registration recreates every PM plugin view (killing any\n active slash-menu UI). */}\n <DragHandle\n editor={editor}\n pluginKey=\"tesseraDragHandle\"\n computePositionConfig={DRAG_HANDLE_POSITION_CONFIG}\n onNodeChange={handleDragNodeChange}\n >\n <div\n className=\"tessera-drag-handle\"\n onClick={event => {\n // handle click opens the block menu (drag still reorders);\n // context menu UI selects the block and positions at pointer\n const data = dragNodeRef.current\n if (!data?.node) {\n return\n }\n const id = (data.node.attrs as { id?: string }).id\n if (!id) {\n return\n }\n editor.emit('tessera:blockMenu', {\n blockId: id,\n blockType: data.node.type.name,\n clientX: event.clientX,\n clientY: event.clientY,\n } as never)\n }}\n >\n ⠿\n </div>\n </DragHandle>\n <EmptyLineToolbar />\n <SelectionToolbar onSession={setSession} />\n <FindReplacePanel />\n <AskPanel />\n <HistoryPanel />\n <CommentPanel />\n <BlockContextMenuUI />\n <SuggestionBar session={session} onClear={() => setSession(null)} />\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\"image/*\"\n hidden\n onChange={e => {\n const file = e.target.files?.[0]\n if (file) {\n void uploadAndInsert(file)\n }\n e.target.value = ''\n }}\n />\n </div>\n </TesseraContext.Provider>\n )\n}\n","import { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'\nimport type { NodeViewProps } from '@tiptap/react'\nimport { ImageBlock } from '@tessera-editor/core'\nimport { useContext, useEffect, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { TesseraContext } from './context'\n\n/**\n * Image NodeView (acceptance §6): 悬停边缘箭头调宽、左/中/全宽对齐。\n * Uploads flow through the injected UploadService (Tessera.tsx handles\n * paste/drop/picker); this view only mutates node attributes.\n */\n\nfunction AiImageNodeView({ node, updateAttributes, selected, decorations }: NodeViewProps) {\n console.log('[imgview] decorations:', Array.isArray(decorations), decorations?.length)\n const galleryAttrs = (() => {\n for (const deco of decorations ?? []) {\n const attrs = (deco as unknown as { type?: { attrs?: Record<string, string> } }).type?.attrs\n if (attrs && 'data-gallery' in attrs) {\n return attrs\n }\n }\n return null\n })()\n const { t } = useContext(TesseraContext)!\n const imgRef = useRef<HTMLImageElement>(null)\n const [preview, setPreview] = useState<string | null>(null)\n\n useEffect(() => {\n if (!preview) return\n const close = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setPreview(null)\n }\n window.addEventListener('keydown', close)\n return () => window.removeEventListener('keydown', close)\n }, [preview])\n const { src, alt, width, align } = node.attrs as {\n src: string\n alt: string | null\n width: number | null\n align: 'left' | 'center' | 'full'\n }\n\n const startResize = (event: React.PointerEvent) => {\n event.preventDefault()\n event.stopPropagation()\n const startX = event.clientX\n const startWidth = imgRef.current?.getBoundingClientRect().width ?? width ?? 400\n const onMove = (e: PointerEvent) => {\n const next = Math.round(Math.max(80, Math.min(startWidth + (e.clientX - startX), 1200)))\n updateAttributes({ width: next })\n }\n const onUp = () => {\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n }\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n }\n\n if (!src) {\n return (\n <NodeViewWrapper className=\"tessera-image tessera-image-empty\" data-selected={selected}>\n <span>{t('imageUploadFailed')}</span>\n </NodeViewWrapper>\n )\n }\n\n return (\n <NodeViewWrapper\n className=\"tessera-image\"\n data-align={align}\n data-selected={selected}\n data-gallery={galleryAttrs ? 'true' : undefined}\n data-gallery-index={galleryAttrs?.['data-gallery-index']}\n data-gallery-size={galleryAttrs?.['data-gallery-size']}\n >\n <div className=\"tessera-image-frame\" style={width ? { width: `${width}px` } : undefined}>\n <img\n ref={imgRef}\n src={src}\n alt={alt ?? ''}\n draggable={false}\n style={{ cursor: 'zoom-in' }}\n onClick={e => {\n e.stopPropagation()\n setPreview(src)\n }}\n />\n <div className=\"tessera-image-resize\" onPointerDown={startResize} title=\"↔\" />\n </div>\n {preview\n ? createPortal(\n <div className=\"tessera-lightbox\" onClick={() => setPreview(null)}>\n <img src={preview} alt={alt ?? ''} />\n </div>,\n document.body,\n )\n : null}\n <div className=\"tessera-image-align\">\n <button type=\"button\" title={t('imageAlignLeft')} data-on={align === 'left'} onClick={() => updateAttributes({ align: 'left' })}>\n ⭰\n </button>\n <button type=\"button\" title={t('imageAlignCenter')} data-on={align === 'center'} onClick={() => updateAttributes({ align: 'center' })}>\n ⭤\n </button>\n <button type=\"button\" title={t('imageAlignFull')} data-on={align === 'full'} onClick={() => updateAttributes({ align: 'full' })}>\n ⭥\n </button>\n </div>\n </NodeViewWrapper>\n )\n}\n\n/** Binding-level image block: core schema + React NodeView. */\nexport const ImageBlockView = ImageBlock.extend({\n addNodeView() {\n return ReactNodeViewRenderer(AiImageNodeView)\n },\n})\n","import { createContext, useContext } from 'react'\nimport type { Editor } from '@tiptap/react'\nimport type { TesseraLocale, TesseraTranslator } from '@tessera-editor/core'\nimport type { AiController } from '@tessera-editor/ai'\n\nexport interface TesseraContextValue {\n editor: Editor\n locale: TesseraLocale\n t: TesseraTranslator\n ai: AiController | null\n}\n\nexport const TesseraContext = createContext<TesseraContextValue | null>(null)\n\nexport function useTesseraContext(): TesseraContextValue {\n const ctx = useContext(TesseraContext)\n if (!ctx) {\n throw new Error('Tessera: useTesseraContext outside provider')\n }\n return ctx\n}\n","import { useContext, useState } from 'react'\nimport { NodeViewWrapper, NodeViewContent, ReactNodeViewRenderer } from '@tiptap/react'\nimport type { NodeViewProps } from '@tiptap/react'\nimport {\n AiTableCell,\n AiTableHeader,\n TABLE_COLUMN_KINDS,\n tableToCsvAt,\n} from '@tessera-editor/core'\nimport type { TableColumnKind } from '@tessera-editor/core'\nimport { TesseraContext } from './context'\n\n/**\n * Table NodeViews (v1.1):\n * - header cell: label + ⋮ column menu (type / sort / insert / delete / table ops)\n * - body cell: typed controls for non-text columns, plain content for text\n */\n\nfunction useT() {\n const { t } = useContext(TesseraContext)!\n return t\n}\nvoid useT\n\nfunction kindLabel(t: ReturnType<typeof useT>, kind: TableColumnKind): string {\n switch (kind) {\n case 'checkbox':\n return t('colTypeCheckbox')\n case 'select':\n return t('colTypeSelect')\n case 'multiSelect':\n return t('colTypeMultiSelect')\n case 'number':\n return t('colTypeNumber')\n case 'date':\n return t('colTypeDate')\n case 'link':\n return t('colTypeLink')\n default:\n return t('colTypeText')\n }\n}\n\nfunction columnIndex(props: NodeViewProps): number {\n const pos = props.getPos()\n if (typeof pos !== 'number') {\n return 0\n }\n const $pos = props.editor.state.doc.resolve(pos)\n return $pos.index($pos.depth)\n}\n\n/** Header cell with the column menu. */\nfunction AiTableHeaderView(props: NodeViewProps) {\n const { editor, t } = useContext(TesseraContext)!\n const [open, setOpen] = useState(false)\n const index = columnIndex(props)\n\n const run = (fn: () => unknown) => {\n setOpen(false)\n // re-select into this cell so table commands locate the table\n const pos = props.getPos()\n if (typeof pos === 'number') {\n editor.commands.setTextSelection(pos + 2)\n }\n void fn()\n }\n\n return (\n <NodeViewWrapper as=\"th\" className=\"tessera-th\" data-index={index}>\n <NodeViewContent className=\"tessera-th-content\" />\n <button\n type=\"button\"\n className=\"tessera-col-menu-btn\"\n contentEditable={false}\n title={t('colMenuTitle')}\n onMouseDown={e => e.preventDefault()}\n onClick={() => setOpen(v => !v)}\n >\n ⌄\n </button>\n {open ? (\n <div className=\"tessera-popover tessera-col-menu\" contentEditable={false} onMouseDown={e => e.preventDefault()}>\n <div className=\"tessera-menu-section\">\n {TABLE_COLUMN_KINDS.map(kind => (\n <button key={kind} type=\"button\" onClick={() => run(() => editor.commands.setColumnType(index, kind))}>\n {kindLabel(t, kind)}\n </button>\n ))}\n </div>\n <div className=\"tessera-menu-sep\" />\n <button type=\"button\" onClick={() => run(() => editor.commands.sortTableByColumn(index, 'asc'))}>\n {t('colMenuSortAsc')}\n </button>\n <button type=\"button\" onClick={() => run(() => editor.commands.sortTableByColumn(index, 'desc'))}>\n {t('colMenuSortDesc')}\n </button>\n <div className=\"tessera-menu-sep\" />\n <button type=\"button\" onClick={() => run(() => editor.commands.addColumnBefore())}>\n {t('colMenuInsertLeft')}\n </button>\n <button type=\"button\" onClick={() => run(() => editor.commands.addColumnAfter())}>\n {t('colMenuInsertRight')}\n </button>\n <button type=\"button\" className=\"tessera-danger\" onClick={() => run(() => editor.commands.deleteColumn())}>\n {t('colMenuDelete')}\n </button>\n <div className=\"tessera-menu-sep\" />\n <button type=\"button\" onClick={() => run(() => editor.commands.toggleHeaderRow())}>\n {t('tableToggleHeader')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n run(() => {\n const csv = tableToCsvAt(editor)\n if (csv) {\n void navigator.clipboard.writeText(csv)\n }\n })\n }\n >\n {t('tableCopyCsv')}\n </button>\n <button type=\"button\" className=\"tessera-danger\" onClick={() => run(() => editor.commands.deleteTable())}>\n {t('tableDelete')}\n </button>\n </div>\n ) : null}\n </NodeViewWrapper>\n )\n}\n\nfunction AiTableCellView(props: NodeViewProps) {\n const { editor } = useContext(TesseraContext)!\n const value = props.node.attrs.value as unknown\n\n // find this column's kind: walk up to the table attrs via position math\n const kind: TableColumnKind = (() => {\n const pos = props.getPos()\n if (typeof pos !== 'number') {\n return 'text'\n }\n const $pos = editor.state.doc.resolve(pos)\n let tableDepth = -1\n for (let d = $pos.depth; d >= 1; d--) {\n if ($pos.node(d).type.name === 'table') {\n tableDepth = d\n break\n }\n }\n if (tableDepth < 1) {\n return 'text'\n }\n const table = $pos.node(tableDepth)\n const rowDepth = tableDepth + 1\n const cellIndex = $pos.index(rowDepth)\n const types = Array.isArray(table.attrs.types) ? (table.attrs.types as TableColumnKind[]) : []\n return types[cellIndex] ?? 'text'\n })()\n\n if (kind === 'text') {\n return (\n <NodeViewWrapper as=\"td\" className=\"tessera-td\">\n <NodeViewContent />\n </NodeViewWrapper>\n )\n }\n\n const setValue = (next: unknown) => props.updateAttributes({ value: next })\n\n return (\n <NodeViewWrapper as=\"td\" className={`tessera-td tessera-td--${kind}`}>\n <NodeViewContent className=\"tessera-td-hidden\" />\n {kind === 'checkbox' ? (\n <input\n type=\"checkbox\"\n className=\"tessera-cell-checkbox\"\n checked={value === true || value === 'true'}\n onChange={e => setValue(e.target.checked)}\n contentEditable={false}\n />\n ) : null}\n {kind === 'number' ? (\n <input\n type=\"number\"\n className=\"tessera-cell-input\"\n value={typeof value === 'number' ? value : ''}\n placeholder=\"—\"\n onChange={e => setValue(e.target.value === '' ? null : Number(e.target.value))}\n contentEditable={false}\n />\n ) : null}\n {kind === 'date' ? (\n <input\n type=\"date\"\n className=\"tessera-cell-input\"\n value={typeof value === 'string' ? value : ''}\n onChange={e => setValue(e.target.value || null)}\n contentEditable={false}\n />\n ) : null}\n {kind === 'select' || kind === 'multiSelect' ? (\n <TagEditor\n multi={kind === 'multiSelect'}\n value={Array.isArray(value) ? (value as string[]) : value ? [String(value)] : []}\n onChange={tags => setValue(kind === 'multiSelect' ? tags : (tags[0] ?? null))}\n />\n ) : null}\n {kind === 'link' ? (\n <input\n type=\"url\"\n className=\"tessera-cell-input\"\n placeholder=\"https://…\"\n value={typeof value === 'string' ? value : ''}\n onChange={e => setValue(e.target.value || null)}\n contentEditable={false}\n />\n ) : null}\n </NodeViewWrapper>\n )\n}\n\nfunction TagEditor({ multi, value, onChange }: { multi: boolean; value: string[]; onChange: (tags: string[]) => void }) {\n const [input, setInput] = useState('')\n const commit = () => {\n const tag = input.trim()\n if (!tag) {\n return\n }\n const next = multi ? Array.from(new Set([...value, tag])) : [tag]\n onChange(next)\n setInput('')\n }\n return (\n <div className=\"tessera-cell-tags\" contentEditable={false}>\n {value.map(tag => (\n <span key={tag} className=\"tessera-tag\">\n {tag}\n <button\n type=\"button\"\n className=\"tessera-tag-x\"\n onClick={() => onChange(value.filter(v => v !== tag))}\n >\n ×\n </button>\n </span>\n ))}\n <input\n className=\"tessera-cell-input tessera-cell-taginput\"\n value={input}\n placeholder={multi ? '+ 标签' : '+ 标签'}\n onChange={e => setInput(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') {\n e.preventDefault()\n commit()\n }\n }}\n onBlur={commit}\n />\n </div>\n )\n}\n\nexport const AiTableCellViewExtension = AiTableCell.extend({\n addNodeView() {\n return ReactNodeViewRenderer(AiTableCellView)\n },\n})\n\nexport const AiTableHeaderViewExtension = AiTableHeader.extend({\n addNodeView() {\n return ReactNodeViewRenderer(AiTableHeaderView)\n },\n})\n","import { useContext, useEffect, useState } from 'react'\nimport { NodeViewWrapper, ReactNodeViewRenderer } from '@tiptap/react'\nimport type { NodeViewProps } from '@tiptap/react'\nimport { EmbedBlock, TocBlock } from '@tessera-editor/core'\nimport { TesseraContext } from './context'\n\n/** Sandboxed iframe embed (default: no scripts, no same-origin). */\nfunction EmbedView({ node, updateAttributes, selected }: NodeViewProps) {\n const { t } = useContext(TesseraContext)!\n const [url, setUrl] = useState(typeof node.attrs.src === 'string' ? node.attrs.src : '')\n const src = typeof node.attrs.src === 'string' ? node.attrs.src : ''\n const height = typeof node.attrs.height === 'number' ? node.attrs.height : 360\n\n if (!src) {\n return (\n <NodeViewWrapper className=\"tessera-embed tessera-embed-empty\" data-selected={selected}>\n <input\n className=\"tessera-embed-input\"\n value={url}\n placeholder={t('embedPlaceholder')}\n onChange={e => setUrl(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') {\n apply()\n }\n }}\n contentEditable={false}\n />\n <button type=\"button\" className=\"tessera-embed-apply\" onClick={apply} contentEditable={false}>\n {t('embedApply')}\n </button>\n </NodeViewWrapper>\n )\n }\n\n function apply() {\n const next = url.trim()\n if (/^https?:\\/\\/.+/.test(next)) {\n updateAttributes({ src: next })\n }\n }\n\n return (\n <NodeViewWrapper className=\"tessera-embed\" data-selected={selected}>\n <div className=\"tessera-embed-frame\" style={{ height: `${height}px` }} contentEditable={false}>\n <iframe\n src={src}\n title={node.attrs.title ?? src}\n sandbox=\"\"\n referrerPolicy=\"no-referrer\"\n loading=\"lazy\"\n />\n </div>\n <a className=\"tessera-embed-link\" href={src} target=\"_blank\" rel=\"noreferrer\" contentEditable={false}>\n {t('embedOpen')} ↗\n </a>\n </NodeViewWrapper>\n )\n}\n\n/** Table of contents derived live from document headings. */\nfunction TocView({ editor }: NodeViewProps) {\n const { t } = useContext(TesseraContext)!\n const [items, setItems] = useState<{ id: string; level: number; text: string }[]>([])\n\n useEffect(() => {\n const scan = () => {\n const found: { id: string; level: number; text: string }[] = []\n editor.state.doc.forEach((node, _offset, index) => {\n void index\n if (node.type.name === 'heading' && typeof node.attrs.id === 'string') {\n found.push({ id: node.attrs.id, level: Number(node.attrs.level), text: node.textContent })\n }\n })\n setItems(found)\n }\n scan()\n editor.on('transaction', scan)\n return () => {\n editor.off('transaction', scan)\n }\n }, [editor])\n\n return (\n <NodeViewWrapper className=\"tessera-toc\">\n <div className=\"tessera-toc-title\">{t('itemToc')}</div>\n {items.length === 0 ? (\n <div className=\"tessera-toc-empty\">{t('tocEmpty')}</div>\n ) : (\n <div className=\"tessera-toc-list\" contentEditable={false}>\n {items.map(item => (\n <button\n key={item.id}\n type=\"button\"\n className=\"tessera-toc-item\"\n data-level={item.level}\n onClick={() => {\n document.querySelector(`[data-id=\"${item.id}\"]`)?.scrollIntoView({ behavior: 'smooth', block: 'start' })\n }}\n >\n {item.text}\n </button>\n ))}\n </div>\n )}\n </NodeViewWrapper>\n )\n}\n\nexport const EmbedBlockView = EmbedBlock.extend({\n addNodeView() {\n return ReactNodeViewRenderer(EmbedView)\n },\n})\n\nexport const TocBlockView = TocBlock.extend({\n addNodeView() {\n return ReactNodeViewRenderer(TocView)\n },\n})\n","import { useEffect, useImperativeHandle, useRef, useState, forwardRef } from 'react'\nimport type { CSSProperties } from 'react'\nimport type { Editor } from '@tiptap/core'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionProps, SuggestionKeyDownProps } from '@tiptap/suggestion'\nimport type { SlashMenuItem, TesseraTranslator } from '@tessera-editor/core'\n\n/**\n * Slash menu UI (acceptance §1): \"/\" 唤起、输入过滤(core 层完成)、\n * 上下键导航、Return 插入、右侧显示快捷键、分组展示。\n */\n\nexport interface SlashMenuViewProps extends SuggestionProps<SlashMenuItem> {\n t: TesseraTranslator\n}\n\nexport const SlashMenuView = forwardRef<{ onKeyDown: (props: SuggestionKeyDownProps) => boolean }, SlashMenuViewProps>(\n function SlashMenuView({ items, command, clientRect, t }, ref) {\n const [selectedIndex, setSelectedIndex] = useState(0)\n const listRef = useRef<HTMLDivElement>(null)\n const [rect, setRect] = useState<DOMRect | null>(null)\n\n useEffect(() => {\n setRect(clientRect?.() ?? null)\n }, [items, clientRect])\n\n useEffect(() => {\n setSelectedIndex(0)\n }, [items])\n\n useEffect(() => {\n listRef.current?.querySelector<HTMLElement>('[data-selected=\"true\"]')?.scrollIntoView({ block: 'nearest' })\n }, [selectedIndex])\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n if (event.key === 'ArrowDown') {\n setSelectedIndex(i => (i + 1) % Math.max(items.length, 1))\n return true\n }\n if (event.key === 'ArrowUp') {\n setSelectedIndex(i => (i - 1 + Math.max(items.length, 1)) % Math.max(items.length, 1))\n return true\n }\n if (event.key === 'Enter') {\n const item = items[selectedIndex]\n if (item) {\n command(item)\n }\n return true\n }\n return false\n },\n }))\n\n if (items.length === 0) {\n return null\n }\n\n const groups = groupBy(items, item => item.group)\n let flatIndex = -1\n\n const style: CSSProperties = rect\n ? (() => {\n // flip by the MENU's max height, not the caret position: a menu\n // opened mid-viewport would otherwise spill past the viewport bottom\n const menuMax = 336 // max-height 320 + padding/border allowance\n const fitsBelow = rect.bottom + 8 + menuMax <= window.innerHeight\n const fitsAbove = rect.top - 8 - menuMax >= 0\n const flip = !fitsBelow && fitsAbove\n return {\n position: 'fixed',\n left: `${Math.min(rect.left, window.innerWidth - 340)}px`,\n top: flip ? undefined : `${Math.min(rect.bottom + 8, window.innerHeight - menuMax)}px`,\n bottom: flip ? `${window.innerHeight - rect.top + 8}px` : undefined,\n width: 320,\n }\n })()\n : { position: 'fixed', left: -9999, top: -9999 }\n\n return (\n <div className=\"tessera-slash-menu\" style={style} data-testid=\"slash-menu\">\n {Array.from(groups.entries()).map(([group, groupItems]) => (\n <div key={group} className=\"tessera-slash-group\">\n <div className=\"tessera-slash-group-title\">\n {group === 'ai' ? t('groupAi') : group === 'advanced' ? t('groupAdvanced') : t('groupBasic')}\n </div>\n {groupItems.map(item => {\n flatIndex += 1\n const idx = flatIndex\n return (\n <button\n key={item.id}\n type=\"button\"\n className=\"tessera-slash-item\"\n data-selected={idx === selectedIndex}\n onMouseEnter={() => setSelectedIndex(idx)}\n onClick={() => command(item)}\n >\n <span className=\"tessera-slash-item-title\">{item.title}</span>\n <span className=\"tessera-slash-item-desc\">{item.description}</span>\n {item.shortcut ? <span className=\"tessera-slash-item-shortcut\">{item.shortcut}</span> : null}\n </button>\n )\n })}\n </div>\n ))}\n </div>\n )\n },\n)\n\nfunction groupBy<T>(items: T[], key: (item: T) => string): Map<string, T[]> {\n const map = new Map<string, T[]>()\n for (const item of items) {\n const k = key(item)\n const list = map.get(k) ?? []\n list.push(item)\n map.set(k, list)\n }\n return map\n}\n\n/** Suggestion render factory: mounts the React view in a body-level wrapper. */\nexport function createSlashRenderer(t: TesseraTranslator) {\n return () => {\n let renderer: ReactRenderer | null = null\n let wrapper: HTMLDivElement | null = null\n\n return {\n onStart: (props: SuggestionProps<SlashMenuItem>) => {\n renderer = new ReactRenderer(SlashMenuView, {\n props: { ...props, t },\n editor: props.editor as Editor,\n })\n wrapper = document.createElement('div')\n wrapper.className = 'tessera-slash-wrapper'\n wrapper.appendChild(renderer.element)\n // theme tokens live on .tessera-root — a body portal renders the menu\n // with unresolved (transparent) chrome\n const host = props.editor.view.dom.closest('.tessera-root') ?? document.body\n host.appendChild(wrapper)\n },\n onUpdate: (props: SuggestionProps<SlashMenuItem>) => {\n renderer?.updateProps({ ...props, t })\n },\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n wrapper?.remove()\n renderer?.destroy()\n renderer = null\n wrapper = null\n return true\n }\n return (renderer?.ref as { onKeyDown?: (p: SuggestionKeyDownProps) => boolean } | null)?.onKeyDown?.(props) ?? false\n },\n onExit: () => {\n wrapper?.remove()\n renderer?.destroy()\n renderer = null\n wrapper = null\n },\n }\n }\n}\n","import { useEffect, useImperativeHandle, useRef, useState, forwardRef } from 'react'\nimport type { CSSProperties } from 'react'\nimport type { Editor } from '@tiptap/core'\nimport { ReactRenderer } from '@tiptap/react'\nimport type { SuggestionProps, SuggestionKeyDownProps } from '@tiptap/suggestion'\nimport type { EmojiItem } from '@tessera-editor/core'\n\n/**\n * Emoji picker UI (acceptance §2, v1.1): `:` opens a grid of emoji filtered\n * by name/keywords; arrows + Return insert, click inserts, Esc closes.\n * Same mounting contract as the slash menu (wrapper appended into\n * .tessera-root so theme tokens resolve).\n */\n\nexport const EmojiMenuView = forwardRef<\n { onKeyDown: (props: SuggestionKeyDownProps) => boolean },\n SuggestionProps<EmojiItem>\n>(function EmojiMenuView({ items, command, clientRect }, ref) {\n const [selectedIndex, setSelectedIndex] = useState(0)\n const [rect, setRect] = useState<DOMRect | null>(null)\n const gridRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n setRect(clientRect?.() ?? null)\n }, [items, clientRect])\n\n useEffect(() => {\n setSelectedIndex(0)\n }, [items])\n\n useEffect(() => {\n gridRef.current\n ?.querySelectorAll<HTMLElement>('.tessera-emoji-item')\n [selectedIndex]?.scrollIntoView({ block: 'nearest' })\n }, [selectedIndex])\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }: SuggestionKeyDownProps) => {\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n setSelectedIndex(i => (i + 1) % Math.max(items.length, 1))\n return true\n }\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n setSelectedIndex(i => (i - 1 + Math.max(items.length, 1)) % Math.max(items.length, 1))\n return true\n }\n if (event.key === 'Enter') {\n const item = items[selectedIndex]\n if (item) {\n command(item)\n }\n return true\n }\n return false\n },\n }))\n\n if (items.length === 0) {\n return null\n }\n\n const menuMax = 264\n const style: CSSProperties = rect\n ? (() => {\n const fitsBelow = rect.bottom + 8 + menuMax <= window.innerHeight\n const fitsAbove = rect.top - 8 - menuMax >= 0\n const flip = !fitsBelow && fitsAbove\n return {\n left: `${Math.min(rect.left, window.innerWidth - 300)}px`,\n top: flip ? undefined : `${Math.min(rect.bottom + 8, window.innerHeight - menuMax)}px`,\n bottom: flip ? `${window.innerHeight - rect.top + 8}px` : undefined,\n }\n })()\n : { left: -9999, top: -9999 }\n\n return (\n <div ref={gridRef} className=\"tessera-emoji-menu\" style={style} data-testid=\"emoji-menu\">\n {items.map((item, i) => (\n <button\n key={item.name}\n type=\"button\"\n className=\"tessera-emoji-item\"\n title={`:${item.name}:`}\n data-selected={i === selectedIndex}\n onMouseEnter={() => setSelectedIndex(i)}\n onClick={() => command(item)}\n >\n {item.char}\n </button>\n ))}\n </div>\n )\n})\n\n/** Suggestion render factory: mounts the grid into the component root. */\nexport function createEmojiRenderer() {\n return () => {\n let renderer: ReactRenderer | null = null\n let wrapper: HTMLDivElement | null = null\n\n return {\n onStart: (props: SuggestionProps<EmojiItem>) => {\n renderer = new ReactRenderer(EmojiMenuView, {\n props: { ...props },\n editor: props.editor as Editor,\n })\n wrapper = document.createElement('div')\n wrapper.className = 'tessera-emoji-wrapper'\n wrapper.appendChild(renderer.element)\n const host = props.editor.view.dom.closest('.tessera-root') ?? document.body\n host.appendChild(wrapper)\n },\n onUpdate: (props: SuggestionProps<EmojiItem>) => {\n renderer?.updateProps({ ...props })\n },\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n wrapper?.remove()\n renderer?.destroy()\n renderer = null\n wrapper = null\n return true\n }\n return (renderer?.ref as { onKeyDown?: (p: SuggestionKeyDownProps) => boolean } | null)?.onKeyDown?.(props) ?? false\n },\n onExit: () => {\n wrapper?.remove()\n renderer?.destroy()\n renderer = null\n wrapper = null\n },\n }\n }\n}\n","import { useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { CSSProperties } from 'react'\nimport { createPortal } from 'react-dom'\nimport { defaultSlashItems } from '@tessera-editor/core'\nimport type { SlashMenuItem, TesseraTranslator } from '@tessera-editor/core'\nimport { aiSlashItems } from '@tessera-editor/ai'\nimport type { Editor } from '@tiptap/react'\nimport { TesseraContext } from './context'\nimport { useTesseraPortalRoot } from './portal'\n\n/**\n * Slite-style empty-line toolbar (acceptance §5): appears when the caret sits\n * in an EMPTY paragraph. Quick row + \"›\" expands the full block list (same\n * items as the slash menu). NOT a Notion-style floating \"+\" button.\n *\n * IME guard: while an input method composition is active we neither show,\n * hide, nor move the toolbar (M0 spike finding).\n */\n\nfunction buildItems(editor: Editor, t: TesseraTranslator, hasAi: boolean): SlashMenuItem[] {\n const base = defaultSlashItems(t)\n if (!hasAi) {\n return base\n }\n // AI items need a runtime; context provides it — reconstructed here via\n // context injection from the parent (see useItems below).\n return base\n}\n\nexport function EmptyLineToolbar() {\n const { editor, t, ai } = useContext(TesseraContext)!\n const [style, setStyle] = useState<CSSProperties | null>(null)\n const [expanded, setExpanded] = useState(false)\n const [panelPlacement, setPanelPlacement] = useState<'bottom' | 'top'>('bottom')\n const [panelMaxHeight, setPanelMaxHeight] = useState<number | undefined>(undefined)\n const composingRef = useRef(false)\n const rootRef = useRef<HTMLDivElement>(null)\n const panelRef = useRef<HTMLDivElement>(null)\n const portalRoot = useTesseraPortalRoot(editor)\n\n const hideAll = useCallback(() => {\n setStyle(null)\n setExpanded(false)\n editor.view.dom.removeAttribute('data-toolbar-line')\n }, [editor])\n\n const reposition = useCallback(() => {\n const dom = editor.view.dom\n if (composingRef.current || !editor.isFocused) {\n setStyle(null)\n dom.removeAttribute('data-toolbar-line')\n return\n }\n const { $from, empty } = editor.state.selection\n const isEmptyParagraph =\n empty && $from.parent.type.name === 'paragraph' && $from.parent.content.size === 0\n if (!isEmptyParagraph) {\n setStyle(null)\n setExpanded(false)\n dom.removeAttribute('data-toolbar-line')\n return\n }\n const coords = editor.view.coordsAtPos($from.pos)\n // the toolbar is glued to the anchor line; once that line leaves the\n // visible part of the editor (inner scroll or page scroll) it must go\n const viewRect = dom.getBoundingClientRect()\n if (\n coords.top < Math.max(viewRect.top, 0) - 2 ||\n coords.top > Math.min(viewRect.bottom, window.innerHeight) + 2\n ) {\n hideAll()\n return\n }\n setStyle({\n position: 'fixed',\n // Float just ABOVE the empty line so the caret stays visible; the\n // toolbar bottom sits a couple of pixels over the line's top edge.\n top: `${Math.max(2, coords.top - 38)}px`,\n left: `${coords.left}px`,\n })\n // hide the placeholder text while the toolbar owns this line\n dom.setAttribute('data-toolbar-line', 'true')\n }, [editor, hideAll])\n\n useEffect(() => {\n const hide = () => {\n setStyle(null)\n setExpanded(false)\n }\n const onCompositionStart = () => {\n composingRef.current = true\n setStyle(null)\n }\n const onCompositionEnd = () => {\n composingRef.current = false\n requestAnimationFrame(reposition)\n }\n // scroll inside our own panel (its scrollbar) must not close the menu;\n // any other scroll keeps the toolbar glued to the anchor line via\n // reposition (which hides it once the line leaves the viewport)\n const onScroll = (event: Event) => {\n const node = rootRef.current\n if (node && event.target instanceof Node && node.contains(event.target)) {\n return\n }\n requestAnimationFrame(reposition)\n }\n\n const dom = editor.view.dom\n editor.on('selectionUpdate', reposition)\n editor.on('focus', reposition)\n editor.on('blur', hide)\n dom.addEventListener('compositionstart', onCompositionStart)\n dom.addEventListener('compositionend', onCompositionEnd)\n window.addEventListener('scroll', onScroll, true)\n return () => {\n editor.off('selectionUpdate', reposition)\n editor.off('focus', reposition)\n editor.off('blur', hide)\n dom.removeEventListener('compositionstart', onCompositionStart)\n dom.removeEventListener('compositionend', onCompositionEnd)\n window.removeEventListener('scroll', onScroll, true)\n }\n }, [editor, reposition])\n\n // Slite-style placement: near the bottom of the viewport the panel opens\n // upward and is clamped to the available space instead of spilling past\n // the content edge\n useLayoutEffect(() => {\n const toolbar = rootRef.current\n if (!expanded || !toolbar) {\n setPanelPlacement('bottom')\n setPanelMaxHeight(undefined)\n return\n }\n const rect = toolbar.getBoundingClientRect()\n const spaceBelow = window.innerHeight - rect.bottom\n const spaceAbove = rect.top\n const natural = panelRef.current?.offsetHeight ?? 320\n const flip = spaceBelow < natural + 12 && spaceAbove > spaceBelow\n setPanelPlacement(flip ? 'top' : 'bottom')\n const avail = (flip ? spaceAbove : spaceBelow) - 12\n setPanelMaxHeight(avail > 120 && avail < natural ? avail : undefined)\n }, [expanded, style])\n\n if (!style || !portalRoot) {\n return null\n }\n\n const items = buildItems(editor, t, !!ai)\n\n const runItem = (item: SlashMenuItem) => {\n const { $from } = editor.state.selection\n item.command({ editor, range: { from: $from.pos, to: $from.pos } })\n setExpanded(false)\n }\n\n return createPortal(\n <div\n ref={rootRef}\n className=\"tessera-emptyline-toolbar\"\n style={style}\n data-testid=\"empty-line-toolbar\"\n onMouseDown={e => e.preventDefault()}\n >\n <button type=\"button\" className=\"tessera-tb-btn\" title={t('itemText')} onClick={() => runItem(items.find(i => i.id === 'text')!)}>\n ¶\n </button>\n <button type=\"button\" className=\"tessera-tb-btn\" title={t('itemH2')} onClick={() => runItem(items.find(i => i.id === 'h2')!)}>\n H2\n </button>\n <button type=\"button\" className=\"tessera-tb-btn\" title={t('itemBullet')} onClick={() => runItem(items.find(i => i.id === 'bulletList')!)}>\n •\n </button>\n <button type=\"button\" className=\"tessera-tb-btn\" title={t('itemTask')} onClick={() => runItem(items.find(i => i.id === 'taskList')!)}>\n ☑\n </button>\n <button type=\"button\" className=\"tessera-tb-btn\" title={t('itemQuote')} onClick={() => runItem(items.find(i => i.id === 'quote')!)}>\n ❝\n </button>\n <button type=\"button\" className=\"tessera-tb-btn\" title={t('itemCode')} onClick={() => runItem(items.find(i => i.id === 'codeBlock')!)}>\n {'</>'}\n </button>\n <button\n type=\"button\"\n className=\"tessera-tb-btn tessera-emptyline-expand\"\n title={t('emptyLineExpand')}\n data-expanded={expanded}\n onClick={() => setExpanded(v => !v)}\n >\n ›\n </button>\n\n {expanded ? (\n <div\n ref={panelRef}\n className=\"tessera-emptyline-panel\"\n data-placement={panelPlacement}\n style={panelMaxHeight ? { maxHeight: panelMaxHeight } : undefined}\n data-testid=\"empty-line-panel\"\n >\n {items.map(item => (\n <button key={item.id} type=\"button\" className=\"tessera-slash-item\" onClick={() => runItem(item)}>\n <span className=\"tessera-slash-item-title\">{item.title}</span>\n <span className=\"tessera-slash-item-desc\">{item.description}</span>\n </button>\n ))}\n </div>\n ) : null}\n </div>,\n portalRoot,\n )\n}\n\nexport { aiSlashItems }\n","import { useEffect, useState } from 'react'\nimport type { Editor } from '@tiptap/react'\n\n/**\n * Floating UI (toolbars, menus, panels) must portal into the component root,\n * never document.body: the --te-* theme tokens — including host overrides and\n * the dark-theme hook — live on .tessera-root, so a body portal renders its\n * chrome with unresolved variables (transparent background, no shadow) and\n * outside the border-box reset.\n *\n * Mount-time resolution can run before EditorContent has inserted the\n * ProseMirror view into the document (closest() on a detached node = null),\n * so resolution is retried on the first editor events — which always precede\n * any floating UI being shown.\n */\nexport function useTesseraPortalRoot(editor: Editor): HTMLElement | null {\n const [target, setTarget] = useState<HTMLElement | null>(null)\n useEffect(() => {\n const resolve = () => {\n setTarget(prev => {\n if (prev && prev !== document.body) {\n return prev\n }\n return (editor.view.dom.closest('.tessera-root') as HTMLElement | null) ?? document.body\n })\n }\n resolve()\n editor.on('focus', resolve)\n editor.on('selectionUpdate', resolve)\n return () => {\n editor.off('focus', resolve)\n editor.off('selectionUpdate', resolve)\n }\n }, [editor])\n return target\n}\n","import { useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { CSSProperties, ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { docToMarkdown } from '@tessera-editor/core'\nimport { IMPROVE_PRESETS, improveSelection } from '@tessera-editor/ai'\nimport type { SuggestionSession } from '@tessera-editor/ai'\nimport { TesseraContext } from './context'\nimport { useTesseraPortalRoot } from './portal'\nimport { CommentComposer } from './CommentPanel'\n\n/**\n * Selection toolbar (acceptance §4): 划词浮现。按钮序:Improve(AI) / B / I /\n * U / S / 行内代码 / 颜色 / 高亮(5色) / 链接 / 转折叠块 / More(Copy as Markdown)。\n */\n\nconst TEXT_COLORS = ['#262629', '#d9414f', '#e8912d', '#31a56f', '#2f9ea8', '#4f6df5', '#7a4fd8', '#98a0ab']\nconst HIGHLIGHT_COLORS = ['#fff2a8', '#d6eaff', '#d3f5df', '#fdd9e7', '#e6dcff']\n\ntype PopoverKind = null | 'color' | 'highlight' | 'link' | 'improve' | 'more' | 'comment'\n\n/**\n * Popover container that flips above the toolbar when there is not enough\n * room below (Slite-style placement). Measured in a layout effect so the\n * flip happens before the first paint.\n */\nfunction FlipPopover({\n posKey,\n className = '',\n testId,\n children,\n}: {\n posKey: unknown\n className?: string\n testId?: string\n children: ReactNode\n}) {\n const ref = useRef<HTMLDivElement>(null)\n const [placement, setPlacement] = useState<'bottom' | 'top'>('bottom')\n const [maxHeight, setMaxHeight] = useState<number | undefined>(undefined)\n\n useLayoutEffect(() => {\n const pop = ref.current\n const toolbar = pop?.parentElement\n if (!pop || !toolbar) {\n return\n }\n const rect = toolbar.getBoundingClientRect()\n const spaceBelow = window.innerHeight - rect.bottom\n const spaceAbove = rect.top\n const natural = pop.offsetHeight\n const flip = spaceBelow < natural + 12 && spaceAbove > spaceBelow\n setPlacement(flip ? 'top' : 'bottom')\n const avail = (flip ? spaceAbove : spaceBelow) - 12\n setMaxHeight(avail > 100 && avail < natural ? avail : undefined)\n }, [posKey])\n\n return (\n <div\n ref={ref}\n className={`tessera-popover${className ? ` ${className}` : ''}`}\n data-placement={placement}\n style={maxHeight ? { maxHeight, overflowY: 'auto' } : undefined}\n data-testid={testId}\n >\n {children}\n </div>\n )\n}\n\nexport function SelectionToolbar({ onSession }: { onSession: (session: SuggestionSession | null) => void }) {\n const { editor, t, ai } = useContext(TesseraContext)!\n const [style, setStyle] = useState<CSSProperties | null>(null)\n const [popover, setPopover] = useState<PopoverKind>(null)\n const [linkValue, setLinkValue] = useState('')\n const composingRef = useRef(false)\n const portalRoot = useTesseraPortalRoot(editor)\n\n const reposition = useCallback(() => {\n if (composingRef.current || !editor.isFocused) {\n setStyle(null)\n return\n }\n const { from, to, empty } = editor.state.selection\n if (empty) {\n setStyle(null)\n return\n }\n if (!editor.state.doc.textBetween(from, to, ' ').trim()) {\n setStyle(null)\n return\n }\n const start = editor.view.coordsAtPos(from)\n const end = editor.view.coordsAtPos(to)\n const left = (Math.min(start.left, end.left) + Math.min(Math.max(start.left, end.left), window.innerWidth - 20)) / 2\n const top = Math.max(8, start.top - 48)\n setStyle({\n position: 'fixed',\n top: `${top}px`,\n left: `${Math.max(8, Math.min(left, window.innerWidth - 380))}px`,\n })\n }, [editor])\n\n useEffect(() => {\n const hide = () => {\n setStyle(null)\n setPopover(null)\n }\n const onSelectionUpdate = () => {\n if (!editor.state.selection.empty) {\n const existing = editor.getAttributes('link').href\n setLinkValue(typeof existing === 'string' ? existing : '')\n } else {\n setPopover(null)\n }\n reposition()\n }\n const onCompositionStart = () => {\n composingRef.current = true\n setStyle(null)\n setPopover(null)\n }\n const onCompositionEnd = () => {\n composingRef.current = false\n requestAnimationFrame(reposition)\n }\n\n const dom = editor.view.dom\n editor.on('selectionUpdate', onSelectionUpdate)\n editor.on('focus', reposition)\n editor.on('blur', hide)\n dom.addEventListener('compositionstart', onCompositionStart)\n dom.addEventListener('compositionend', onCompositionEnd)\n window.addEventListener('scroll', hide, true)\n return () => {\n editor.off('selectionUpdate', onSelectionUpdate)\n editor.off('focus', reposition)\n editor.off('blur', hide)\n dom.removeEventListener('compositionstart', onCompositionStart)\n dom.removeEventListener('compositionend', onCompositionEnd)\n window.removeEventListener('scroll', hide, true)\n }\n }, [editor, reposition])\n\n const chain = () => editor.chain().focus()\n\n if (!style || !portalRoot) {\n return null\n }\n\n const currentColor = (editor.getAttributes('textStyle').color as string | undefined) ?? null\n\n return createPortal(\n <div\n className=\"tessera-selection-toolbar\"\n style={style}\n data-testid=\"selection-toolbar\"\n onMouseDown={e => e.preventDefault()}\n >\n {ai ? (\n <TbBtn title={t('tooltipImprove')} className=\"tessera-tb-ai\" onClick={() => setPopover(p => (p === 'improve' ? null : 'improve'))}>\n ✨\n </TbBtn>\n ) : null}\n <TbBtn title={t('tooltipBold')} active={editor.isActive('bold')} onClick={() => chain().toggleBold().run()}>\n <b>B</b>\n </TbBtn>\n <TbBtn title={t('tooltipItalic')} active={editor.isActive('italic')} onClick={() => chain().toggleItalic().run()}>\n <i>I</i>\n </TbBtn>\n <TbBtn title={t('tooltipUnderline')} active={editor.isActive('underline')} onClick={() => chain().toggleUnderline().run()}>\n <u>U</u>\n </TbBtn>\n <TbBtn title={t('tooltipStrike')} active={editor.isActive('strike')} onClick={() => chain().toggleStrike().run()}>\n <s>S</s>\n </TbBtn>\n <TbBtn title={t('tooltipCode')} active={editor.isActive('code')} onClick={() => chain().toggleCode().run()}>\n {'</>'}\n </TbBtn>\n <TbBtn title={t('tooltipColor')} active={!!currentColor} onClick={() => setPopover(p => (p === 'color' ? null : 'color'))}>\n <span className=\"tessera-tb-colorchip\" style={{ background: currentColor ?? '#262629' }} />\n </TbBtn>\n <TbBtn title={t('tooltipHighlight')} active={editor.isActive('highlight')} onClick={() => setPopover(p => (p === 'highlight' ? null : 'highlight'))}>\n <span className=\"tessera-tb-colorchip\" style={{ background: '#fff2a8' }} />\n </TbBtn>\n <TbBtn title={t('tooltipLink')} active={editor.isActive('link')} onClick={() => setPopover(p => (p === 'link' ? null : 'link'))}>\n 🔗\n </TbBtn>\n <TbBtn\n title={t('tooltipCommentV11')}\n active={editor.isActive('comment')}\n onClick={() => setPopover(p => (p === 'comment' ? null : 'comment'))}\n >\n 💬\n </TbBtn>\n <TbBtn title={t('tooltipTurnCollapsible')} onClick={() => chain().insertCollapsible().run()}>\n ▸\n </TbBtn>\n <TbBtn title={t('tooltipMore')} onClick={() => setPopover(p => (p === 'more' ? null : 'more'))}>\n ⋯\n </TbBtn>\n\n {popover === 'color' ? (\n <FlipPopover posKey={style}>\n <button type=\"button\" className=\"tessera-color-swatch tessera-color-none\" title={t('colorDefault')} onClick={() => chain().unsetColor().run()} />\n {TEXT_COLORS.map(color => (\n <button key={color} type=\"button\" className=\"tessera-color-swatch\" style={{ background: color }} onClick={() => chain().setColor(color).run()} />\n ))}\n </FlipPopover>\n ) : null}\n\n {popover === 'highlight' ? (\n <FlipPopover posKey={style}>\n <button type=\"button\" className=\"tessera-color-swatch tessera-color-none\" title={t('highlightNone')} onClick={() => chain().unsetHighlight().run()} />\n {HIGHLIGHT_COLORS.map(color => (\n <button key={color} type=\"button\" className=\"tessera-color-swatch\" style={{ background: color }} onClick={() => chain().toggleHighlight({ color }).run()} />\n ))}\n </FlipPopover>\n ) : null}\n\n {popover === 'link' ? (\n <FlipPopover posKey={style} className=\"tessera-link-popover\">\n <input\n autoFocus\n value={linkValue}\n placeholder={t('linkPlaceholder')}\n onChange={e => setLinkValue(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter' && linkValue.trim()) {\n chain().toggleLink({ href: linkValue.trim() }).run()\n setPopover(null)\n }\n }}\n />\n <button\n type=\"button\"\n onClick={() => {\n if (linkValue.trim()) {\n chain().toggleLink({ href: linkValue.trim() }).run()\n }\n setPopover(null)\n }}\n >\n {t('linkApply')}\n </button>\n {editor.isActive('link') ? (\n <button\n type=\"button\"\n className=\"tessera-danger\"\n onClick={() => {\n chain().unsetLink().run()\n setPopover(null)\n }}\n >\n {t('linkRemove')}\n </button>\n ) : null}\n </FlipPopover>\n ) : null}\n\n {popover === 'more' ? (\n <FlipPopover posKey={style} className=\"tessera-popover-menu\">\n <button\n type=\"button\"\n onClick={async () => {\n const { to } = editor.state.selection\n const start = editor.state.selection.$from.before(1)\n const sliced = editor.state.doc.cut(start, to)\n await navigator.clipboard.writeText(docToMarkdown(sliced, editor.state.schema))\n setPopover(null)\n }}\n >\n {t('tooltipCopyMarkdown')}\n </button>\n </FlipPopover>\n ) : null}\n\n {popover === 'improve' ? <ImprovePopover posKey={style} onSession={onSession} onClose={() => setPopover(null)} /> : null}\n {popover === 'comment' ? <CommentComposer onClose={() => setPopover(null)} /> : null}\n </div>,\n portalRoot,\n )\n}\n\nfunction TbBtn({\n title,\n active,\n className,\n onClick,\n children,\n}: {\n title: string\n active?: boolean\n className?: string\n onClick: () => void\n children: ReactNode\n}) {\n return (\n <button\n type=\"button\"\n title={title}\n aria-label={title}\n className={`tessera-tb-btn${className ? ` ${className}` : ''}`}\n data-active={active ?? false}\n onClick={onClick}\n >\n {children}\n </button>\n )\n}\n\nfunction ImprovePopover({\n posKey,\n onSession,\n onClose,\n}: {\n posKey: unknown\n onSession: (s: SuggestionSession | null) => void\n onClose: () => void\n}) {\n const { editor, t, locale, ai } = useContext(TesseraContext)!\n const [instruction, setInstruction] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const run = async (presetInstruction?: string) => {\n if (!ai) {\n setError(t('aiRuntimeMissing'))\n return\n }\n onClose()\n setBusy(true)\n try {\n const session = await improveSelection(editor, ai.runtime, { instruction: presetInstruction })\n onSession(session)\n } catch (err) {\n setError(String(err))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <FlipPopover posKey={posKey} className=\"tessera-improve-popover\" testId=\"improve-popover\">\n {IMPROVE_PRESETS.map(preset => (\n <button key={preset.id} type=\"button\" disabled={busy} onClick={() => run(preset.instruction)}>\n {locale === 'zh-CN' ? preset.labelZh : preset.labelEn}\n </button>\n ))}\n <div className=\"tessera-improve-custom\">\n <input\n value={instruction}\n placeholder={t('aiImprovePrompt')}\n onChange={e => setInstruction(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter' && instruction.trim()) {\n run(instruction.trim())\n }\n }}\n />\n <button type=\"button\" disabled={busy || !instruction.trim()} onClick={() => run(instruction.trim())}>\n {t('aiImproveRun')}\n </button>\n </div>\n {error ? <div className=\"tessera-popover-error\">{error}</div> : null}\n </FlipPopover>\n )\n}\n","import { useContext, useEffect, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { getCommentStore, getIdentityService, listCommentRanges } from '@tessera-editor/core'\nimport type { CommentThread } from '@tessera-editor/core'\nimport { TesseraContext } from './context'\nimport { useTesseraPortalRoot } from './portal'\n\n/**\n * Inline comments (v1.1): comment composer on selection (⌘⌥M or the 💬\n * toolbar button), side panel with threads in document order, resolve /\n * delete, ↑/↓ thread navigation. Data flows through the injected\n * CommentStore + IdentityService.\n */\n\ninterface ThreadView extends CommentThread {\n from: number\n}\n\nfunction uid(prefix: string): string {\n return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nexport function CommentPanel() {\n const { editor, t } = useContext(TesseraContext)!\n const [visible, setVisible] = useState(false)\n const [threads, setThreads] = useState<ThreadView[]>([])\n\n const refresh = async () => {\n const store = getCommentStore(editor)\n if (!store) {\n return\n }\n const all = await store.list()\n const ranges = listCommentRanges(editor.state)\n const views: ThreadView[] = []\n for (const range of ranges) {\n const thread = all.find(tr => tr.id === range.threadId)\n if (thread) {\n views.push({ ...thread, from: range.from })\n }\n }\n views.sort((a, b) => a.from - b.from)\n setThreads(views)\n }\n\n useEffect(() => {\n const open = () => {\n setVisible(true)\n void refresh()\n }\n editor.on('tessera:commentPanel', open)\n editor.on('transaction', refresh)\n return () => {\n editor.off('tessera:commentPanel', open)\n editor.off('transaction', refresh)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor])\n\n if (!visible) {\n return null\n }\n\n const store = getCommentStore(editor)\n const identity = getIdentityService(editor)\n const me = identity?.getCurrentUser() ?? { id: 'anonymous', name: 'Anonymous' }\n\n const toggleResolve = async (thread: ThreadView) => {\n if (!store) {\n return\n }\n const next = { ...thread, resolved: !thread.resolved }\n delete (next as Partial<ThreadView>).from\n await store.upsert(next as CommentThread)\n editor.commands.setCommentResolved(thread.id, next.resolved)\n await refresh()\n }\n\n const removeThread = async (thread: ThreadView) => {\n if (!store) {\n return\n }\n await store.remove(thread.id)\n editor.commands.removeCommentThread(thread.id)\n await refresh()\n }\n\n return (\n <div className=\"tessera-comment-panel\" data-testid=\"comment-panel\">\n <div className=\"tessera-ask-header\">\n <span>\n {t('commentTitle')} · {threads.filter(x => !x.resolved).length}\n </span>\n <div className=\"tessera-history-actions\">\n <button type=\"button\" title=\"↑\" onClick={() => editor.commands.focusNextCommentThread()}>\n ↑\n </button>\n <button type=\"button\" title=\"↓\" onClick={() => editor.commands.focusNextCommentThread()}>\n ↓\n </button>\n <button type=\"button\" onClick={() => setVisible(false)}>\n ×\n </button>\n </div>\n </div>\n {!store ? <div className=\"tessera-ask-error\">CommentStore not injected</div> : null}\n <div className=\"tessera-ask-body\">\n {threads.length === 0 ? <div className=\"tessera-toc-empty\">{t('commentEmpty')}</div> : null}\n {threads.map(thread => (\n <div key={thread.id} className={`tessera-thread${thread.resolved ? ' tessera-thread--resolved' : ''}`}>\n <div className=\"tessera-thread-quote\">“{thread.quote}”</div>\n {thread.entries.map(entry => (\n <div key={entry.id} className=\"tessera-thread-entry\">\n <span className=\"tessera-thread-author\">{entry.authorName}</span>\n <span className=\"tessera-thread-text\">{entry.text}</span>\n </div>\n ))}\n <div className=\"tessera-thread-actions\">\n <button type=\"button\" onClick={() => void toggleResolve(thread)}>\n {thread.resolved ? t('commentReopen') : t('commentResolve')}\n </button>\n <button type=\"button\" className=\"tessera-danger\" onClick={() => void removeThread(thread)}>\n {t('commentDelete')}\n </button>\n {thread.resolved ? <span className=\"tessera-thread-badge\">{t('commentResolvedBadge')}</span> : null}\n </div>\n </div>\n ))}\n </div>\n <div className=\"tessera-comment-hint\">⌘⌥M / 💬 · {me.name}</div>\n </div>\n )\n}\n\n/** Selection-toolbar composer popover: quote + comment → thread + mark. */\nexport function CommentComposer({ onClose }: { onClose: () => void }) {\n const { editor, t } = useContext(TesseraContext)!\n const [text, setText] = useState('')\n const portalRoot = useTesseraPortalRoot(editor)\n const quote = editor.state.doc.textBetween(editor.state.selection.from, editor.state.selection.to, ' ')\n\n const submit = async () => {\n if (!text.trim()) {\n return\n }\n const store = getCommentStore(editor)\n if (!store) {\n return\n }\n const identity = getIdentityService(editor)\n const me = identity?.getCurrentUser() ?? { id: 'anonymous', name: 'Anonymous' }\n const thread: CommentThread = {\n id: uid('thread'),\n quote,\n resolved: false,\n createdAt: Date.now(),\n entries: [{ id: uid('c'), authorId: me.id, authorName: me.name, text: text.trim(), ts: Date.now() }],\n }\n await store.upsert(thread)\n editor.commands.addCommentThread(thread.id)\n onClose()\n editor.emit('tessera:commentPanel', {})\n }\n\n if (!portalRoot) {\n return null\n }\n\n return createPortal(\n <div className=\"tessera-popover tessera-comment-composer\" data-testid=\"comment-composer\">\n <div className=\"tessera-thread-quote\">“{quote.slice(0, 60)}{quote.length > 60 ? '…' : ''}”</div>\n <textarea\n autoFocus\n value={text}\n placeholder={t('commentPlaceholder')}\n onChange={e => setText(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault()\n void submit()\n }\n }}\n />\n <div className=\"tessera-comment-composer-actions\">\n <button type=\"button\" disabled={!text.trim()} onClick={() => void submit()}>\n {t('commentSend')}\n </button>\n </div>\n </div>,\n portalRoot,\n )\n}\n","import { useContext, useEffect, useRef, useState } from 'react'\nimport { findReplaceKey } from '@tessera-editor/core'\nimport type { FindReplaceState } from '@tessera-editor/core'\nimport type { SuggestionSession } from '@tessera-editor/ai'\nimport { TesseraContext } from './context'\n\n/** ⌘F find & replace panel — drives the core findReplace commands. */\nexport function FindReplacePanel() {\n const { editor, t } = useContext(TesseraContext)!\n const [visible, setVisible] = useState(false)\n const [query, setQuery] = useState('')\n const [replacement, setReplacement] = useState('')\n const [count, setCount] = useState(0)\n const [active, setActive] = useState(0)\n const queryRef = useRef(query)\n queryRef.current = query\n\n useEffect(() => {\n const open = () => {\n setVisible(true)\n requestAnimationFrame(() => {\n const input = document.querySelector<HTMLInputElement>('.tessera-find-input')\n input?.focus()\n })\n }\n editor.on('tessera:findPanel', open)\n\n const sync = () => {\n const s = findReplaceKey.getState(editor.state) as FindReplaceState | undefined\n if (!s) {\n return\n }\n setVisible(s.visible)\n setCount(s.matches.length)\n setActive(s.active)\n if (s.query !== queryRef.current && !s.visible) {\n setQuery(s.query)\n }\n }\n editor.on('transaction', sync)\n return () => {\n editor.off('tessera:findPanel', open)\n editor.off('transaction', sync)\n }\n }, [editor])\n\n if (!visible) {\n return null\n }\n\n const updateQuery = (value: string) => {\n setQuery(value)\n editor.commands.setFindQuery(value)\n }\n\n return (\n <div className=\"tessera-find-panel\" data-testid=\"find-panel\">\n <input\n className=\"tessera-find-input\"\n autoFocus\n value={query}\n placeholder={t('findPlaceholder')}\n onChange={e => updateQuery(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') {\n editor.commands.findNext()\n }\n if (e.key === 'Escape') {\n editor.commands.closeFindPanel()\n }\n }}\n />\n <span className=\"tessera-find-count\">{count > 0 ? `${active + 1}/${count}` : '0'}</span>\n <button type=\"button\" title={t('findPrev')} onClick={() => editor.commands.findPrev()}>\n ↑\n </button>\n <button type=\"button\" title={t('findNext')} onClick={() => editor.commands.findNext()}>\n ↓\n </button>\n <input\n value={replacement}\n placeholder={t('replacePlaceholder')}\n onChange={e => setReplacement(e.target.value)}\n />\n <button type=\"button\" onClick={() => editor.commands.replaceCurrent(replacement)}>\n {t('replaceOne')}\n </button>\n <button type=\"button\" onClick={() => editor.commands.replaceAll(replacement)}>\n {t('replaceAll')}\n </button>\n <button type=\"button\" className=\"tessera-find-close\" title={t('findClose')} onClick={() => editor.commands.closeFindPanel()}>\n ×\n </button>\n </div>\n )\n}\n\n/** AI 问答面板(/ask 唤起):流式回答,不写文档。 */\nexport function AskPanel() {\n const { editor, t, ai } = useContext(TesseraContext)!\n const [visible, setVisible] = useState(false)\n const [question, setQuestion] = useState('')\n const [answer, setAnswer] = useState('')\n const [thinking, setThinking] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => {\n const open = () => setVisible(true)\n editor.on('tessera:askPanel', open)\n return () => {\n editor.off('tessera:askPanel', open)\n }\n }, [editor])\n\n if (!visible) {\n return null\n }\n\n const ask = async () => {\n if (!ai || !question.trim()) {\n return\n }\n setThinking(true)\n setAnswer('')\n setError(null)\n try {\n await ai.ask(question, { onToken: token => setAnswer(prev => prev + token) })\n } catch (err) {\n setError(String(err))\n } finally {\n setThinking(false)\n }\n }\n\n return (\n <div className=\"tessera-ask-panel\" data-testid=\"ask-panel\">\n <div className=\"tessera-ask-header\">\n <span>{t('aiTitle')}</span>\n <button type=\"button\" onClick={() => setVisible(false)}>\n ×\n </button>\n </div>\n {ai ? null : <div className=\"tessera-ask-error\">{t('aiRuntimeMissing')}</div>}\n <div className=\"tessera-ask-body\">\n {answer ? <div className=\"tessera-ask-answer\">{answer}</div> : null}\n {thinking ? <div className=\"tessera-ask-thinking\">{t('aiThinking')}</div> : null}\n {error ? <div className=\"tessera-ask-error\">{error}</div> : null}\n </div>\n <div className=\"tessera-ask-input\">\n <input\n value={question}\n placeholder={t('aiAskPlaceholder')}\n onChange={e => setQuestion(e.target.value)}\n onKeyDown={e => {\n if (e.key === 'Enter') {\n ask()\n }\n }}\n />\n <button type=\"button\" disabled={!ai || thinking || !question.trim()} onClick={ask}>\n {t('aiSend')}\n </button>\n </div>\n </div>\n )\n}\n\n/** \"Agent 起草、人批准\":pending 建议存在时显示审阅条。 */\nexport function SuggestionBar({\n session,\n onClear,\n}: {\n session: SuggestionSession | null\n onClear: () => void\n}) {\n const { t } = useContext(TesseraContext)!\n if (!session) {\n return null\n }\n return (\n <div className=\"tessera-suggestion-bar\" data-testid=\"suggestion-bar\">\n <span className=\"tessera-suggestion-label\">✨ {t('aiStreaming')}</span>\n <button\n type=\"button\"\n className=\"tessera-suggestion-accept\"\n onClick={() => {\n session.accept()\n onClear()\n }}\n >\n {t('aiAccept')}\n </button>\n <button\n type=\"button\"\n className=\"tessera-suggestion-reject\"\n onClick={() => {\n session.reject()\n onClear()\n }}\n >\n {t('aiReject')}\n </button>\n </div>\n )\n}\n","import { useContext, useEffect, useState } from 'react'\nimport {\n diffDocs,\n diffSummary,\n getStorageService,\n} from '@tessera-editor/core'\nimport type { DocSnapshot, BlockDiffEntry } from '@tessera-editor/core'\nimport { TesseraContext } from './context'\n\n/**\n * Version history panel (v1.1): snapshots from the injected StorageService,\n * block+word level diff against the current doc, one-click restore.\n */\nexport function HistoryPanel() {\n const { editor, t } = useContext(TesseraContext)!\n const [visible, setVisible] = useState(false)\n const [snapshots, setSnapshots] = useState<DocSnapshot[]>([])\n const [selected, setSelected] = useState<DocSnapshot | null>(null)\n const [entries, setEntries] = useState<BlockDiffEntry[] | null>(null)\n const [summary, setSummary] = useState({ added: 0, removed: 0, changed: 0 })\n\n const refresh = async () => {\n const storage = getStorageService(editor)\n if (!storage) {\n return\n }\n const list = await storage.listSnapshots()\n setSnapshots([...list].sort((a, b) => b.ts - a.ts))\n }\n\n useEffect(() => {\n const open = () => {\n setVisible(true)\n void refresh()\n }\n editor.on('tessera:historyPanel', open)\n const saved = () => void refresh()\n editor.on('tessera:snapshotSaved', saved as never)\n return () => {\n editor.off('tessera:historyPanel', open)\n editor.off('tessera:snapshotSaved', saved as never)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor])\n\n if (!visible) {\n return null\n }\n\n const hasStorage = !!getStorageService(editor)\n\n const select = (snap: DocSnapshot) => {\n setSelected(snap)\n const result = diffDocs(snap.doc as never, editor.getJSON() as never)\n setEntries(result)\n setSummary(diffSummary(result.filter(e => e.kind !== 'unchanged')))\n }\n\n const restore = (snap: DocSnapshot) => {\n if (!window.confirm(t('historyConfirmRestore'))) {\n return\n }\n editor.commands.setContent(snap.doc as never)\n setVisible(false)\n }\n\n return (\n <div className=\"tessera-history-panel\" data-testid=\"history-panel\">\n <div className=\"tessera-ask-header\">\n <span>{t('historyTitle')}</span>\n <div className=\"tessera-history-actions\">\n {hasStorage ? (\n <button type=\"button\" onClick={() => editor.commands.captureSnapshot()}>\n {t('historyCapture')}\n </button>\n ) : null}\n <button type=\"button\" onClick={() => setVisible(false)}>\n ×\n </button>\n </div>\n </div>\n {!hasStorage ? (\n <div className=\"tessera-ask-error\">{t('aiRuntimeMissing').replace('AI Runtime', 'StorageService')}</div>\n ) : null}\n <div className=\"tessera-history-body\">\n <div className=\"tessera-history-list\">\n {snapshots.length === 0 ? <div className=\"tessera-toc-empty\">{t('historyEmpty')}</div> : null}\n {snapshots.map(snap => (\n <button\n key={snap.id}\n type=\"button\"\n className=\"tessera-history-item\"\n data-selected={selected?.id === snap.id}\n onClick={() => select(snap)}\n >\n <span className=\"tessera-history-time\">{formatTime(snap.ts)}</span>\n {snap.label ? <span className=\"tessera-history-label\">{snap.label}</span> : null}\n <span className=\"tessera-history-action\" role=\"button\" tabIndex={0}\n onClick={e => {\n e.stopPropagation()\n restore(snap)\n }}\n onKeyDown={e => {\n if (e.key === 'Enter') restore(snap)\n }}\n >\n {t('historyRestore')}\n </span>\n </button>\n ))}\n </div>\n {selected ? (\n <div className=\"tessera-history-diff\">\n <div className=\"tessera-diff-summary\">\n +{summary.added} {t('historyDiffAdded')} · −{summary.removed} {t('historyDiffRemoved')} · ~{summary.changed}{' '}\n {t('historyDiffChanged')}\n </div>\n {(entries ?? [])\n .filter(e => e.kind !== 'unchanged')\n .slice(0, 80)\n .map((entry, i) => (\n <div key={entry.id ?? i} className={`tessera-diff-block tessera-diff-block--${entry.kind}`}>\n <span className=\"tessera-diff-badge\">\n {entry.kind === 'added'\n ? t('historyDiffAdded')\n : entry.kind === 'removed'\n ? t('historyDiffRemoved')\n : t('historyDiffChanged')}\n </span>\n {entry.kind === 'changed' && entry.wordDiff ? (\n <span className=\"tessera-diff-text\">\n {entry.wordDiff.map((part, j) => (\n <span key={j} className={`tessera-diff-part tessera-diff-part--${part.type}`}>\n {part.text}\n </span>\n ))}\n </span>\n ) : (\n <span className=\"tessera-diff-text\">{blockText(entry)}</span>\n )}\n </div>\n ))}\n </div>\n ) : null}\n </div>\n </div>\n )\n}\n\nfunction blockText(entry: BlockDiffEntry): string {\n const json = entry.after ?? entry.before\n const walk = (node: unknown): string => {\n if (!node || typeof node !== 'object') {\n return ''\n }\n const n = node as { text?: string; content?: unknown[] }\n return (n.text ?? '') + (n.content ?? []).map(walk).join('')\n }\n return walk(json).trim().slice(0, 120)\n}\n\nfunction formatTime(ts: number): string {\n const d = new Date(ts)\n const pad = (n: number) => String(n).padStart(2, '0')\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`\n}\n","import { useContext, useEffect, useState } from 'react'\nimport type { CSSProperties } from 'react'\nimport { createPortal } from 'react-dom'\nimport { tableToCsvAt } from '@tessera-editor/core'\nimport { TesseraContext } from './context'\nimport { useTesseraPortalRoot } from './portal'\n\n/**\n * Block context menu (v1.1): right-click a block → 复制锚链接 / 复制块 ID /\n * 删除块; inside tables also row & table operations.\n */\ninterface BlockMenuState {\n blockId: string\n blockType: string\n clientX: number\n clientY: number\n}\n\nexport function BlockContextMenuUI() {\n const { editor, t } = useContext(TesseraContext)!\n const [menu, setMenu] = useState<BlockMenuState | null>(null)\n const portalRoot = useTesseraPortalRoot(editor)\n\n useEffect(() => {\n const onMenu = (payload: BlockMenuState) => {\n // place caret into the block so table commands resolve context\n const found = findBlockPosById(editor, payload.blockId)\n if (found !== null) {\n editor.commands.setTextSelection(Math.min(found + 2, editor.state.doc.content.size))\n }\n setMenu(payload)\n }\n const close = () => setMenu(null)\n editor.on('tessera:blockMenu', onMenu as never)\n window.addEventListener('mousedown', close)\n window.addEventListener('scroll', close, true)\n return () => {\n editor.off('tessera:blockMenu', onMenu as never)\n window.removeEventListener('mousedown', close)\n window.removeEventListener('scroll', close, true)\n }\n }, [editor])\n\n if (!menu || !portalRoot) {\n return null\n }\n\n const inTable = menu.blockType === 'table' || menu.blockType === 'tableRow'\n const style: CSSProperties = {\n position: 'fixed',\n left: `${Math.min(menu.clientX, window.innerWidth - 220)}px`,\n top: `${Math.min(menu.clientY, window.innerHeight - 300)}px`,\n }\n\n const copy = (text: string) => {\n void navigator.clipboard.writeText(text)\n setMenu(null)\n }\n\n return createPortal(\n <div className=\"tessera-block-menu\" style={style} onMouseDown={e => e.stopPropagation()}>\n {inTable ? (\n <>\n <MenuLabel>{t('rowMenuTitle')}</MenuLabel>\n <MenuItem onClick={() => { editor.commands.addRowBefore(); setMenu(null) }}>{t('rowMenuInsertAbove')}</MenuItem>\n <MenuItem onClick={() => { editor.commands.addRowAfter(); setMenu(null) }}>{t('rowMenuInsertBelow')}</MenuItem>\n <MenuItem danger onClick={() => { editor.commands.deleteRow(); setMenu(null) }}>{t('rowMenuDelete')}</MenuItem>\n <div className=\"tessera-menu-sep\" />\n <MenuItem onClick={() => { const csv = tableToCsvAt(editor); if (csv) copy(csv) }}>{t('tableCopyCsv')}</MenuItem>\n <MenuItem onClick={() => { editor.commands.toggleHeaderRow(); setMenu(null) }}>{t('tableToggleHeader')}</MenuItem>\n <MenuItem danger onClick={() => { editor.commands.deleteTable(); setMenu(null) }}>{t('tableDelete')}</MenuItem>\n <div className=\"tessera-menu-sep\" />\n </>\n ) : null}\n <MenuItem onClick={() => copy(anchorUrl(menu.blockId))}>{t('menuCopyAnchor')}</MenuItem>\n <MenuItem onClick={() => copy(menu.blockId)}>{t('menuCopyBlockId')}</MenuItem>\n <MenuItem danger onClick={() => { deleteBlockById(editor, menu.blockId); setMenu(null) }}>\n {t('menuDeleteBlock')}\n </MenuItem>\n </div>,\n portalRoot,\n )\n}\n\nfunction anchorUrl(blockId: string): string {\n const base = `${location.origin}${location.pathname}`\n return `${base}#block-${blockId}`\n}\n\nfunction findBlockPosById(editor: import('@tiptap/react').Editor, id: string): number | null {\n let found: number | null = null\n editor.state.doc.forEach((node, offset) => {\n if (found === null && node.attrs.id === id) {\n found = offset\n }\n })\n return found\n}\n\nfunction deleteBlockById(editor: import('@tiptap/react').Editor, id: string) {\n const pos = findBlockPosById(editor, id)\n if (pos === null) {\n return\n }\n const node = editor.state.doc.nodeAt(pos)\n if (!node) {\n return\n }\n const tr = editor.state.tr.delete(pos, pos + node.nodeSize)\n editor.view.dispatch(tr)\n}\n\nfunction MenuLabel({ children }: { children: React.ReactNode }) {\n return <div className=\"tessera-menu-label\">{children}</div>\n}\n\nfunction MenuItem({\n children,\n onClick,\n danger,\n}: {\n children: React.ReactNode\n onClick: () => void\n danger?: boolean\n}) {\n return (\n <button type=\"button\" className={`tessera-menu-item${danger ? ' tessera-danger' : ''}`} onClick={onClick}>\n {children}\n </button>\n )\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","aiSlashItems","useContext","useContext","useContext","useState","NodeViewWrapper","ReactNodeViewRenderer","jsx","jsxs","useContext","useState","jsxs","NodeViewWrapper","jsx","ReactNodeViewRenderer","useContext","useEffect","useState","NodeViewWrapper","ReactNodeViewRenderer","jsx","jsxs","useContext","useState","NodeViewWrapper","useEffect","ReactNodeViewRenderer","useEffect","useRef","useState","jsx","jsxs","SlashMenuView","useEffect","useImperativeHandle","useRef","useState","forwardRef","ReactRenderer","jsx","EmojiMenuView","useContext","useEffect","useRef","useState","createPortal","useEffect","useState","jsx","jsxs","useContext","useState","useRef","useEffect","createPortal","useCallback","useContext","useEffect","useLayoutEffect","useRef","useState","createPortal","useContext","useEffect","useState","createPortal","jsx","jsxs","useContext","useState","store","useEffect","createPortal","jsx","jsxs","useRef","useState","useLayoutEffect","useContext","useCallback","useEffect","createPortal","useContext","useEffect","useRef","useState","jsx","jsxs","useContext","useState","useRef","useEffect","useContext","useEffect","useState","jsx","jsxs","useContext","useState","useEffect","useContext","useEffect","useState","createPortal","tableToCsvAt","jsx","jsxs","useContext","useState","useEffect","createPortal","tableToCsvAt","jsx","jsxs","useState","useRef","useCallback","aiSlashItems","storage","useEffect"]}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@tessera-editor/react",
3
+ "version": "0.1.0",
4
+ "description": "Tessera React binding with the full default UI",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "development": "./src/index.ts",
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src"
19
+ ],
20
+ "dependencies": {
21
+ "@tiptap/core": "^3.31.3",
22
+ "@tiptap/extension-color": "^3.31.3",
23
+ "@tiptap/extension-drag-handle-react": "^3.31.3",
24
+ "@tiptap/extension-highlight": "^3.31.3",
25
+ "@tiptap/extension-list": "^3.31.3",
26
+ "@tiptap/extension-table": "^3.31.3",
27
+ "@tiptap/extension-text-style": "^3.31.3",
28
+ "@tiptap/extension-unique-id": "^3.31.3",
29
+ "@tiptap/extensions": "^3.31.3",
30
+ "@tiptap/react": "^3.31.3",
31
+ "@tiptap/starter-kit": "^3.31.3",
32
+ "@tiptap/suggestion": "^3.31.3",
33
+ "@tessera-editor/ai": "0.1.0",
34
+ "@tessera-editor/core": "0.1.0"
35
+ },
36
+ "peerDependencies": {
37
+ "react": "^18.0.0 || ^19.0.0",
38
+ "react-dom": "^18.0.0 || ^19.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@testing-library/dom": "^10.4.1",
42
+ "@testing-library/react": "^16.3.3",
43
+ "@types/react": "^18.3.12",
44
+ "@types/react-dom": "^18.3.1",
45
+ "@vitejs/plugin-react": "^5.2.0",
46
+ "jsdom": "^30.0.1",
47
+ "react": "^18.3.1",
48
+ "react-dom": "^18.3.1",
49
+ "vitest": "^5.0.0"
50
+ },
51
+ "keywords": [
52
+ "rich-text",
53
+ "editor",
54
+ "react",
55
+ "tiptap",
56
+ "wysiwyg"
57
+ ],
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/mofishless/tessera-editor.git",
61
+ "directory": "packages/react"
62
+ },
63
+ "homepage": "https://github.com/mofishless/tessera-editor/tree/main/packages/react#readme",
64
+ "bugs": "https://github.com/mofishless/tessera-editor/issues",
65
+ "sideEffects": false,
66
+ "module": "./dist/index.js",
67
+ "publishConfig": {
68
+ "access": "public",
69
+ "registry": "https://registry.npmjs.org/"
70
+ },
71
+ "scripts": {
72
+ "typecheck": "tsc --noEmit",
73
+ "build": "tsup",
74
+ "test": "vitest run"
75
+ }
76
+ }
@@ -0,0 +1,131 @@
1
+ import { useContext, useEffect, useState } from 'react'
2
+ import type { CSSProperties } from 'react'
3
+ import { createPortal } from 'react-dom'
4
+ import { tableToCsvAt } from '@tessera-editor/core'
5
+ import { TesseraContext } from './context'
6
+ import { useTesseraPortalRoot } from './portal'
7
+
8
+ /**
9
+ * Block context menu (v1.1): right-click a block → 复制锚链接 / 复制块 ID /
10
+ * 删除块; inside tables also row & table operations.
11
+ */
12
+ interface BlockMenuState {
13
+ blockId: string
14
+ blockType: string
15
+ clientX: number
16
+ clientY: number
17
+ }
18
+
19
+ export function BlockContextMenuUI() {
20
+ const { editor, t } = useContext(TesseraContext)!
21
+ const [menu, setMenu] = useState<BlockMenuState | null>(null)
22
+ const portalRoot = useTesseraPortalRoot(editor)
23
+
24
+ useEffect(() => {
25
+ const onMenu = (payload: BlockMenuState) => {
26
+ // place caret into the block so table commands resolve context
27
+ const found = findBlockPosById(editor, payload.blockId)
28
+ if (found !== null) {
29
+ editor.commands.setTextSelection(Math.min(found + 2, editor.state.doc.content.size))
30
+ }
31
+ setMenu(payload)
32
+ }
33
+ const close = () => setMenu(null)
34
+ editor.on('tessera:blockMenu', onMenu as never)
35
+ window.addEventListener('mousedown', close)
36
+ window.addEventListener('scroll', close, true)
37
+ return () => {
38
+ editor.off('tessera:blockMenu', onMenu as never)
39
+ window.removeEventListener('mousedown', close)
40
+ window.removeEventListener('scroll', close, true)
41
+ }
42
+ }, [editor])
43
+
44
+ if (!menu || !portalRoot) {
45
+ return null
46
+ }
47
+
48
+ const inTable = menu.blockType === 'table' || menu.blockType === 'tableRow'
49
+ const style: CSSProperties = {
50
+ position: 'fixed',
51
+ left: `${Math.min(menu.clientX, window.innerWidth - 220)}px`,
52
+ top: `${Math.min(menu.clientY, window.innerHeight - 300)}px`,
53
+ }
54
+
55
+ const copy = (text: string) => {
56
+ void navigator.clipboard.writeText(text)
57
+ setMenu(null)
58
+ }
59
+
60
+ return createPortal(
61
+ <div className="tessera-block-menu" style={style} onMouseDown={e => e.stopPropagation()}>
62
+ {inTable ? (
63
+ <>
64
+ <MenuLabel>{t('rowMenuTitle')}</MenuLabel>
65
+ <MenuItem onClick={() => { editor.commands.addRowBefore(); setMenu(null) }}>{t('rowMenuInsertAbove')}</MenuItem>
66
+ <MenuItem onClick={() => { editor.commands.addRowAfter(); setMenu(null) }}>{t('rowMenuInsertBelow')}</MenuItem>
67
+ <MenuItem danger onClick={() => { editor.commands.deleteRow(); setMenu(null) }}>{t('rowMenuDelete')}</MenuItem>
68
+ <div className="tessera-menu-sep" />
69
+ <MenuItem onClick={() => { const csv = tableToCsvAt(editor); if (csv) copy(csv) }}>{t('tableCopyCsv')}</MenuItem>
70
+ <MenuItem onClick={() => { editor.commands.toggleHeaderRow(); setMenu(null) }}>{t('tableToggleHeader')}</MenuItem>
71
+ <MenuItem danger onClick={() => { editor.commands.deleteTable(); setMenu(null) }}>{t('tableDelete')}</MenuItem>
72
+ <div className="tessera-menu-sep" />
73
+ </>
74
+ ) : null}
75
+ <MenuItem onClick={() => copy(anchorUrl(menu.blockId))}>{t('menuCopyAnchor')}</MenuItem>
76
+ <MenuItem onClick={() => copy(menu.blockId)}>{t('menuCopyBlockId')}</MenuItem>
77
+ <MenuItem danger onClick={() => { deleteBlockById(editor, menu.blockId); setMenu(null) }}>
78
+ {t('menuDeleteBlock')}
79
+ </MenuItem>
80
+ </div>,
81
+ portalRoot,
82
+ )
83
+ }
84
+
85
+ function anchorUrl(blockId: string): string {
86
+ const base = `${location.origin}${location.pathname}`
87
+ return `${base}#block-${blockId}`
88
+ }
89
+
90
+ function findBlockPosById(editor: import('@tiptap/react').Editor, id: string): number | null {
91
+ let found: number | null = null
92
+ editor.state.doc.forEach((node, offset) => {
93
+ if (found === null && node.attrs.id === id) {
94
+ found = offset
95
+ }
96
+ })
97
+ return found
98
+ }
99
+
100
+ function deleteBlockById(editor: import('@tiptap/react').Editor, id: string) {
101
+ const pos = findBlockPosById(editor, id)
102
+ if (pos === null) {
103
+ return
104
+ }
105
+ const node = editor.state.doc.nodeAt(pos)
106
+ if (!node) {
107
+ return
108
+ }
109
+ const tr = editor.state.tr.delete(pos, pos + node.nodeSize)
110
+ editor.view.dispatch(tr)
111
+ }
112
+
113
+ function MenuLabel({ children }: { children: React.ReactNode }) {
114
+ return <div className="tessera-menu-label">{children}</div>
115
+ }
116
+
117
+ function MenuItem({
118
+ children,
119
+ onClick,
120
+ danger,
121
+ }: {
122
+ children: React.ReactNode
123
+ onClick: () => void
124
+ danger?: boolean
125
+ }) {
126
+ return (
127
+ <button type="button" className={`tessera-menu-item${danger ? ' tessera-danger' : ''}`} onClick={onClick}>
128
+ {children}
129
+ </button>
130
+ )
131
+ }