import { observer } from "mobx-react-lite"; import { useMemo, useRef } from "react"; import { toast } from "react-hot-toast"; import useCurrentUser from "@/hooks/useCurrentUser"; import { cn } from "@/lib/utils"; import { useTranslate } from "@/utils/i18n"; import { EditorContent, EditorMetadata, EditorToolbar, FocusModeOverlay } from "./components"; import type { EditorRefActions } from "./Editor"; import { useAutoSave, useKeyboard, useMemoInit } from "./hooks"; import { cacheService, errorService, memoService, validationService } from "./services"; import { EditorProvider, useEditorContext } from "./state"; import { MemoEditorContext } from "./types"; export interface Props { className?: string; cacheKey?: string; placeholder?: string; memoName?: string; parentMemoName?: string; autoFocus?: boolean; onConfirm?: (memoName: string) => void; onCancel?: () => void; } const MemoEditor = observer((props: Props) => { const { className, cacheKey, memoName, parentMemoName, autoFocus, placeholder, onConfirm, onCancel } = props; return ( ); }); const MemoEditorImpl: React.FC = ({ className, cacheKey, memoName, parentMemoName, autoFocus, placeholder, onConfirm, onCancel, }) => { const t = useTranslate(); const currentUser = useCurrentUser(); const editorRef = useRef(null); const { state, actions, dispatch } = useEditorContext(); // Bridge for old MemoEditorContext (used by InsertMenu and other components) const legacyContextValue = useMemo( () => ({ attachmentList: state.metadata.attachments, relationList: state.metadata.relations, setAttachmentList: (attachments: typeof state.metadata.attachments) => dispatch(actions.setMetadata({ attachments })), setRelationList: (relations: typeof state.metadata.relations) => dispatch(actions.setMetadata({ relations })), memoName, addLocalFiles: (files: typeof state.localFiles) => { files.forEach((file) => dispatch(actions.addLocalFile(file))); }, removeLocalFile: (previewUrl: string) => dispatch(actions.removeLocalFile(previewUrl)), localFiles: state.localFiles, }), [state.metadata.attachments, state.metadata.relations, state.localFiles, memoName, actions, dispatch], ); // Initialize editor (load memo or cache) useMemoInit(editorRef, memoName, cacheKey, currentUser.name, autoFocus); // Auto-save content to localStorage useAutoSave(state.content, currentUser.name, cacheKey); // Keyboard shortcuts useKeyboard(editorRef, { onSave: handleSave }); async function handleSave() { const { valid, reason } = validationService.canSave(state); if (!valid) { toast.error(reason || "Cannot save"); return; } actions.setLoading("saving", true); try { const result = await memoService.save(state, { memoName, parentMemoName }); if (!result.hasChanges) { toast.error(t("editor.no-changes-detected")); onCancel?.(); return; } // Clear cache on successful save cacheService.clear(cacheService.key(currentUser.name, cacheKey)); // Reset editor state actions.reset(); // Notify parent onConfirm?.(result.memoName); toast.success("Saved successfully"); } catch (error) { const message = errorService.handle(error, t); toast.error(message); } finally { actions.setLoading("saving", false); } } return (
); }; export default MemoEditor;