'use client' import { Node, mergeAttributes } from '@tiptap/core' import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react' import { List, X } from 'lucide-react' import { cn } from '@/lib/utils' import { useLanguage } from '@/lib/i18n' import type { Editor } from '@tiptap/core' interface HeadingEntry { id: string level: number text: string pos: number } function collectHeadings(editor: Editor): HeadingEntry[] { const headings: HeadingEntry[] = [] editor.state.doc.descendants((node, pos) => { if (node.type.name === 'heading') { const level = node.attrs.level as number if (level >= 1 && level <= 3) { const text = node.textContent.trim() if (text) { headings.push({ id: node.attrs['data-id'] || `heading-${pos}`, level, text, pos, }) } } } }) return headings } const OutlineView = ({ editor, deleteNode }: any) => { const { t } = useLanguage() const headings = collectHeadings(editor as Editor) const scrollToHeading = (pos: number) => { const docSize = editor.state.doc.content.size const safePos = Math.min(pos + 1, docSize) editor.chain().focus().setTextSelection(safePos).scrollIntoView().run() } return (
{t('richTextEditor.outlineTitle')}
{headings.length === 0 ? (

{t('richTextEditor.outlineEmpty')}

) : ( )}
) } export const OutlineExtension = Node.create({ name: 'outlineBlock', group: 'block', atom: true, defining: true, parseHTML() { return [ { tag: 'div[data-type="outline-block"]' }, ] }, renderHTML({ HTMLAttributes }) { return [ 'div', mergeAttributes(HTMLAttributes, { 'data-type': 'outline-block', class: 'outline-block', }), ] }, addNodeView() { return ReactNodeViewRenderer(OutlineView) }, addKeyboardShortcuts() { return { 'Mod-Shift-O': () => this.editor.commands.insertContent({ type: this.name, }), } }, }) export function insertOutlineBlock(editor: any) { editor.chain().focus().insertContent({ type: 'outlineBlock', }).run() }