'use client'
import React, { useCallback, useEffect, useRef } from 'react'
import { useEditor, EditorContent, type Editor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableHeader from '@tiptap/extension-table-header'
import TableCell from '@tiptap/extension-table-cell'
import Image from '@tiptap/extension-image'
interface TechFileEditorProps {
content: string
onSave: (content: string) => void
readOnly?: boolean
}
function normalizeContent(content: string): string {
if (!content) return '
'
const trimmed = content.trim()
// If it looks like JSON array or has no HTML tags, wrap in
if (trimmed.startsWith('[') || !/<[a-z][\s\S]*>/i.test(trimmed)) {
return `
${trimmed.replace(/\n/g, '
')}
`
}
return trimmed
}
interface ToolbarButtonProps {
onClick: () => void
isActive?: boolean
disabled?: boolean
title: string
children: React.ReactNode
}
function ToolbarButton({ onClick, isActive, disabled, title, children }: ToolbarButtonProps) {
return (
)
}
export function TechFileEditor({ content, onSave, readOnly = false }: TechFileEditorProps) {
const debounceTimer = useRef | null>(null)
const onSaveRef = useRef(onSave)
onSaveRef.current = onSave
const debouncedSave = useCallback((html: string) => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current)
}
debounceTimer.current = setTimeout(() => {
onSaveRef.current(html)
}, 3000)
}, [])
const editor = useEditor({
extensions: [
StarterKit.configure({
heading: { levels: [2, 3, 4] },
}),
Table.configure({
resizable: true,
HTMLAttributes: { class: 'border-collapse border border-gray-300' },
}),
TableRow,
TableHeader,
TableCell,
Image.configure({
HTMLAttributes: { class: 'max-w-full rounded' },
}),
],
content: normalizeContent(content),
editable: !readOnly,
onUpdate: ({ editor: ed }: { editor: Editor }) => {
if (!readOnly) {
debouncedSave(ed.getHTML())
}
},
editorProps: {
attributes: {
class: 'prose prose-sm max-w-none dark:prose-invert focus:outline-none min-h-[300px] px-4 py-3',
},
},
})
// Update content when parent prop changes
useEffect(() => {
if (editor && content) {
const normalized = normalizeContent(content)
const currentHTML = editor.getHTML()
if (normalized !== currentHTML) {
editor.commands.setContent(normalized)
}
}
}, [content, editor])
// Update editable state when readOnly changes
useEffect(() => {
if (editor) {
editor.setEditable(!readOnly)
}
}, [readOnly, editor])
// Cleanup debounce timer
useEffect(() => {
return () => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current)
}
}
}, [])
if (!editor) {
return (
)
}
return (
{/* Toolbar */}
{!readOnly && (
{/* Text formatting */}
editor.chain().focus().toggleBold().run()}
isActive={editor.isActive('bold')}
title="Fett (Ctrl+B)"
>
editor.chain().focus().toggleItalic().run()}
isActive={editor.isActive('italic')}
title="Kursiv (Ctrl+I)"
>
{/* Headings */}
editor.chain().focus().toggleHeading({ level: 2 }).run()}
isActive={editor.isActive('heading', { level: 2 })}
title="Ueberschrift 2"
>
H2
editor.chain().focus().toggleHeading({ level: 3 }).run()}
isActive={editor.isActive('heading', { level: 3 })}
title="Ueberschrift 3"
>
H3
editor.chain().focus().toggleHeading({ level: 4 }).run()}
isActive={editor.isActive('heading', { level: 4 })}
title="Ueberschrift 4"
>
H4
{/* Lists */}
editor.chain().focus().toggleBulletList().run()}
isActive={editor.isActive('bulletList')}
title="Aufzaehlung"
>
editor.chain().focus().toggleOrderedList().run()}
isActive={editor.isActive('orderedList')}
title="Nummerierte Liste"
>
{/* Table */}
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}
title="Tabelle einfuegen (3x3)"
>
{/* Blockquote */}
editor.chain().focus().toggleBlockquote().run()}
isActive={editor.isActive('blockquote')}
title="Zitat"
>
{/* Code Block */}
editor.chain().focus().toggleCodeBlock().run()}
isActive={editor.isActive('codeBlock')}
title="Code-Block"
>
{/* Undo / Redo */}
editor.chain().focus().undo().run()}
disabled={!editor.can().undo()}
title="Rueckgaengig (Ctrl+Z)"
>
editor.chain().focus().redo().run()}
disabled={!editor.can().redo()}
title="Wiederholen (Ctrl+Shift+Z)"
>
)}
{/* Editor Content */}
)
}