import { useEffect, useState, useCallback, useRef } from 'react'; import { useFileStore } from '../../stores/fileStore'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; import LineNumbers from './LineNumberPlugin'; import HoverToolbar from './HoverToolbar'; import AutoHighlighterPlugin from '../../plugins/AutoHighlighterPlugin'; import { CloudSavingDone01Icon, Tick02Icon } from 'hugeicons-react'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { INSERT_LINE_BREAK_COMMAND, COMMAND_PRIORITY_EDITOR, $createLineBreakNode, $getSelection, $isRangeSelection } from 'lexical'; // Premium Theme mapping for Lexical const theme = { paragraph: 'editor-paragraph', text: { bold: 'editor-text-bold', italic: 'editor-text-italic', strikethrough: 'editor-text-strikethrough', underline: 'editor-text-underline', }, }; export default function EditorCore() { const { files, activeFileId, updateFileContent } = useFileStore(); const [editorKey, setEditorKey] = useState(0); const [isSaving, setIsSaving] = useState(false); const debounceRef = useRef(null); const ShiftEnterPlugin = () => { const [editor] = useLexicalComposerContext(); useEffect(() => { return editor.registerCommand( INSERT_LINE_BREAK_COMMAND, () => { editor.update(() => { const selection = $getSelection(); if ($isRangeSelection(selection)) { selection.insertNodes([$createLineBreakNode()]); } }); return true; }, COMMAND_PRIORITY_EDITOR ); }, [editor]); return null; }; const activeFile = files.find(f => f.id === activeFileId && f.type === 'file'); // Ensure editor re-initializes exactly on file change useEffect(() => { setEditorKey(k => k + 1); }, [activeFileId]); // Clipboard Transformation (Emdash Prefix) useEffect(() => { const handleCopy = (e: ClipboardEvent) => { const selection = window.getSelection(); if (!selection || selection.rangeCount === 0) return; let text = selection.toString(); if (!text) return; // SPEC: Emdash as very beginning of number line with content // Logic: Prefix lines with content with "— " (emdash + space) const lines = text.split('\n'); const transformed = lines.map(line => { if (line.trim().length > 0) { return `— ${line}`; } return line; }).join('\n'); e.clipboardData?.setData('text/plain', transformed); e.preventDefault(); }; document.addEventListener('copy', handleCopy); return () => document.removeEventListener('copy', handleCopy); }, []); const handleOnChange = useCallback((editorState: any) => { setIsSaving(true); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { editorState.read(() => { const jsonString = JSON.stringify(editorState.toJSON()); if (activeFileId) { updateFileContent(activeFileId, jsonString).then(() => { setIsSaving(false); }); } else { setIsSaving(false); } }); }, 300); // 300ms debounce for near-instant premium persistence }, [activeFileId, updateFileContent]); if (!activeFile) { return (
No active document
Select a file from the sidebar to begin editing.
); } const initialConfig = { namespace: 'HybridDocEditor', theme, nodes: [], onError(error: Error) { console.error('Lexical Error:', error); }, editorState: activeFile.content ? ( activeFile.content.includes('"root":{') ? activeFile.content : undefined ) : undefined, }; return (
} placeholder={
Start your thinking here...
} ErrorBoundary={LexicalErrorBoundary} /> {/* Save Status Indicator */}
{isSaving ? : } {isSaving ? 'Syncing...' : 'Saved'}
); }