'use client'
import React, { useCallback, useMemo, useState } from 'react'
import { createEditor, Descendant, Editor, Transforms, BaseEditor, Element, Text } from 'slate'
import { Slate, Editable, withReact, RenderElementProps, RenderLeafProps, ReactEditor } from 'slate-react'
import { withHistory, HistoryEditor } from 'slate-history'
import { useDocumentStore } from '@/stores/document-store'
import { useDesignStore } from '@/stores/design-store'
import { FileText, Bold, Italic, Type, List, ListOrdered } from 'lucide-react'
import { withMentions, getMentionSearch, insertMention } from './mention-plugin'
import MentionSuggestions from './MentionSuggestions'
import MentionElementComponent from './MentionElement'
// Define custom types for Slate
interface CustomElement {
type?: string
children: CustomText[]
nodeId?: string
nodeType?: string
}
interface CustomText {
text: string
bold?: boolean
italic?: boolean
underline?: boolean
}
declare module 'slate' {
interface CustomTypes {
Editor: BaseEditor & ReactEditor & HistoryEditor
Element: CustomElement
Text: CustomText
}
}
const CustomElement = ({ attributes, children, element }: RenderElementProps) => {
const customElement = element as CustomElement
switch (customElement.type) {
case 'mention':
return
case 'heading-one':
return
{children}
case 'heading-two':
return {children}
case 'heading-three':
return {children}
case 'bulleted-list':
return
case 'numbered-list':
return {children}
case 'list-item':
return {children}
case 'paragraph':
default:
return {children}
}
}
const CustomLeaf = ({ attributes, children, leaf }: RenderLeafProps) => {
const customLeaf = leaf as CustomText
if (customLeaf.bold) {
children = {children}
}
if (customLeaf.italic) {
children = {children}
}
if (customLeaf.underline) {
children = {children}
}
return {children}
}
const DocumentEditor: React.FC = () => {
const { getCurrentDocument, updateCurrentDocumentContent } = useDocumentStore()
const { currentDesign } = useDesignStore()
const editor = useMemo(() => withMentions(withHistory(withReact(createEditor()))), [])
const currentDoc = getCurrentDocument()
const [mentionSearch, setMentionSearch] = useState<{ search: string } | null>(null)
// Reset editor when document changes to prevent invalid path errors
React.useEffect(() => {
if (editor) {
// Clear history and reset editor state when document changes
editor.history = { undos: [], redos: [] }
editor.selection = null
}
}, [currentDoc.id, editor])
const renderElement = useCallback((props: RenderElementProps) => , [])
const renderLeaf = useCallback((props: RenderLeafProps) => , [])
const handleChange = useCallback((newValue: Descendant[]) => {
updateCurrentDocumentContent(newValue)
// Check for mention triggers
const mentionSearchResult = getMentionSearch(editor)
setMentionSearch(mentionSearchResult ? { search: mentionSearchResult.search } : null)
}, [updateCurrentDocumentContent, editor])
const isMarkActive = (format: string) => {
const marks = Editor.marks(editor)
return marks ? (marks as any)[format] === true : false
}
const isBlockActive = (format: string) => {
const { selection } = editor
if (!selection) return false
const [match] = Array.from(
Editor.nodes(editor, {
at: Editor.unhangRange(editor, selection),
match: n => !Editor.isEditor(n) && Element.isElement(n) && (n as CustomElement).type === format,
})
)
return !!match
}
const toggleMark = (format: string) => {
const isActive = isMarkActive(format)
if (isActive) {
Editor.removeMark(editor, format)
} else {
Editor.addMark(editor, format, true)
}
}
const toggleBlock = (format: string) => {
const isActive = isBlockActive(format)
const isList = ['numbered-list', 'bulleted-list'].includes(format)
Transforms.unwrapNodes(editor, {
match: n =>
!Editor.isEditor(n) &&
Element.isElement(n) &&
['numbered-list', 'bulleted-list'].includes((n as CustomElement).type as string),
split: true,
})
let newProperties: Partial
if (isActive) {
newProperties = { type: 'paragraph' }
} else if (isList) {
newProperties = { type: 'list-item' }
} else {
newProperties = { type: format }
}
Transforms.setNodes(editor, newProperties)
if (!isActive && isList) {
const block = { type: format, children: [] } as CustomElement
Transforms.wrapNodes(editor, block)
}
}
const handleMentionSelect = useCallback((suggestion: { id: string; type: string; label: string }) => {
insertMention(editor, suggestion.id, suggestion.type, suggestion.label)
setMentionSearch(null)
}, [editor])
const handleMentionClose = useCallback(() => setMentionSearch(null), [])
if (!currentDesign) {
return (
No design selected
)
}
return (
{/* Toolbar */}
{/* Editor */}
{/* Mention Suggestions */}
{mentionSearch && (
)}
)
}
export default DocumentEditor