diff --git a/web/src/components/MemoEditor/hooks/useMemoInit.ts b/web/src/components/MemoEditor/hooks/useMemoInit.ts index 6cdc0f8f6..704a64672 100644 --- a/web/src/components/MemoEditor/hooks/useMemoInit.ts +++ b/web/src/components/MemoEditor/hooks/useMemoInit.ts @@ -1,69 +1,40 @@ -import { useQueryClient } from "@tanstack/react-query"; import { useEffect, useRef } from "react"; -import { memoKeys } from "@/hooks/useMemoQueries"; -import type { Visibility } from "@/types/proto/api/v1/memo_service_pb"; +import type { Memo, Visibility } from "@/types/proto/api/v1/memo_service_pb"; import type { EditorRefActions } from "../Editor"; import { cacheService, memoService } from "../services"; import { useEditorContext } from "../state"; -export const useMemoInit = ( - editorRef: React.RefObject, - memoName: string | undefined, - cacheKey: string | undefined, - username: string, - autoFocus?: boolean, - defaultVisibility?: Visibility, -) => { +interface UseMemoInitOptions { + editorRef: React.RefObject; + memo?: Memo; + cacheKey?: string; + username: string; + autoFocus?: boolean; + defaultVisibility?: Visibility; +} + +export const useMemoInit = ({ editorRef, memo, cacheKey, username, autoFocus, defaultVisibility }: UseMemoInitOptions) => { const { actions, dispatch } = useEditorContext(); - const queryClient = useQueryClient(); const initializedRef = useRef(false); useEffect(() => { if (initializedRef.current) return; initializedRef.current = true; - const init = async () => { - dispatch(actions.setLoading("loading", true)); - - try { - if (memoName) { - // Force refetch from server to prevent stale data issues - // See: https://github.com/usememos/memos/issues/5470 - await queryClient.invalidateQueries({ queryKey: memoKeys.detail(memoName) }); - - // Load existing memo - const loadedState = await memoService.load(memoName); - dispatch( - actions.initMemo({ - content: loadedState.content, - metadata: loadedState.metadata, - timestamps: loadedState.timestamps, - }), - ); - } else { - // Load from cache for new memo - const cachedContent = cacheService.load(cacheService.key(username, cacheKey)); - if (cachedContent) { - dispatch(actions.updateContent(cachedContent)); - } - // Apply default visibility for new memos - if (defaultVisibility !== undefined) { - dispatch(actions.setMetadata({ visibility: defaultVisibility })); - } - } - } catch (error) { - console.error("Failed to initialize editor:", error); - } finally { - dispatch(actions.setLoading("loading", false)); - - if (autoFocus) { - setTimeout(() => { - editorRef.current?.focus(); - }, 100); - } + if (memo) { + dispatch(actions.initMemo(memoService.fromMemo(memo))); + } else { + const cachedContent = cacheService.load(cacheService.key(username, cacheKey)); + if (cachedContent) { + dispatch(actions.updateContent(cachedContent)); } - }; + if (defaultVisibility !== undefined) { + dispatch(actions.setMetadata({ visibility: defaultVisibility })); + } + } - init(); - }, [memoName, cacheKey, username, autoFocus, defaultVisibility, actions, dispatch, editorRef, queryClient]); + if (autoFocus) { + setTimeout(() => editorRef.current?.focus(), 100); + } + }, [memo, cacheKey, username, autoFocus, defaultVisibility, actions, dispatch, editorRef]); }; diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 1c20055df..53e70b00f 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -17,29 +17,16 @@ import { cacheService, errorService, memoService, validationService } from "./se import { EditorProvider, useEditorContext } from "./state"; import type { MemoEditorProps } from "./types"; -const MemoEditor = (props: MemoEditorProps) => { - const { className, cacheKey, memoName, parentMemoName, autoFocus, placeholder, onConfirm, onCancel } = props; - - return ( - - - - ); -}; +const MemoEditor = (props: MemoEditorProps) => ( + + + +); const MemoEditorImpl: React.FC = ({ className, cacheKey, - memoName, + memo, parentMemoName, autoFocus, placeholder, @@ -53,10 +40,12 @@ const MemoEditorImpl: React.FC = ({ const { state, actions, dispatch } = useEditorContext(); const { userGeneralSetting } = useAuth(); + const memoName = memo?.name; + // Get default visibility from user settings const defaultVisibility = userGeneralSetting?.memoVisibility ? convertVisibilityFromString(userGeneralSetting.memoVisibility) : undefined; - useMemoInit(editorRef, memoName, cacheKey, currentUser?.name ?? "", autoFocus, defaultVisibility); + useMemoInit({ editorRef, memo, cacheKey, username: currentUser?.name ?? "", autoFocus, defaultVisibility }); // Auto-save content to localStorage useAutoSave(state.content, currentUser?.name ?? "", cacheKey); diff --git a/web/src/components/MemoEditor/services/memoService.ts b/web/src/components/MemoEditor/services/memoService.ts index 53b553293..db6338fc4 100644 --- a/web/src/components/MemoEditor/services/memoService.ts +++ b/web/src/components/MemoEditor/services/memoService.ts @@ -122,9 +122,8 @@ export const memoService = { return { memoName: memo.name, hasChanges: true }; }, - async load(memoName: string): Promise { - const memo = await memoServiceClient.getMemo({ name: memoName }); - + /** Build editor state from an already-loaded Memo entity (no network request). */ + fromMemo(memo: Memo): EditorState { return { content: memo.content, metadata: { @@ -135,11 +134,7 @@ export const memoService = { }, ui: { isFocusMode: false, - isLoading: { - saving: false, - uploading: false, - loading: false, - }, + isLoading: { saving: false, uploading: false, loading: false }, isDragging: false, isComposing: false, }, diff --git a/web/src/components/MemoEditor/types/components.ts b/web/src/components/MemoEditor/types/components.ts index 13f91ff48..df1a439be 100644 --- a/web/src/components/MemoEditor/types/components.ts +++ b/web/src/components/MemoEditor/types/components.ts @@ -8,7 +8,8 @@ export interface MemoEditorProps { className?: string; cacheKey?: string; placeholder?: string; - memoName?: string; + /** Existing memo to edit. When provided, the editor initializes from it without fetching. */ + memo?: Memo; parentMemoName?: string; autoFocus?: boolean; onConfirm?: (memoName: string) => void; diff --git a/web/src/components/MemoView/MemoView.tsx b/web/src/components/MemoView/MemoView.tsx index fef97e7be..f59a3f676 100644 --- a/web/src/components/MemoView/MemoView.tsx +++ b/web/src/components/MemoView/MemoView.tsx @@ -31,8 +31,7 @@ const MemoView: React.FC = (props: MemoViewProps) => { const { previewState, openPreview, setPreviewOpen } = useImagePreview(); const { unpinMemo } = useMemoActions(memoData, isArchived); - const handleEditorConfirm = () => setShowEditor(false); - const handleEditorCancel = () => setShowEditor(false); + const closeEditor = () => setShowEditor(false); const openEditor = () => setShowEditor(true); const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({ @@ -63,9 +62,9 @@ const MemoView: React.FC = (props: MemoViewProps) => { autoFocus className="mb-2" cacheKey={`inline-memo-editor-${memoData.name}`} - memoName={memoData.name} - onConfirm={handleEditorConfirm} - onCancel={handleEditorCancel} + memo={memoData} + onConfirm={closeEditor} + onCancel={closeEditor} /> ); }