Commit aeb1e5fe authored by Steven's avatar Steven

perf(web): eliminate redundant fetch when opening inline memo editor

parent 69485eec
import { useQueryClient } from "@tanstack/react-query";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { memoKeys } from "@/hooks/useMemoQueries"; import type { Memo, Visibility } from "@/types/proto/api/v1/memo_service_pb";
import type { Visibility } from "@/types/proto/api/v1/memo_service_pb";
import type { EditorRefActions } from "../Editor"; import type { EditorRefActions } from "../Editor";
import { cacheService, memoService } from "../services"; import { cacheService, memoService } from "../services";
import { useEditorContext } from "../state"; import { useEditorContext } from "../state";
export const useMemoInit = ( interface UseMemoInitOptions {
editorRef: React.RefObject<EditorRefActions | null>, editorRef: React.RefObject<EditorRefActions | null>;
memoName: string | undefined, memo?: Memo;
cacheKey: string | undefined, cacheKey?: string;
username: string, username: string;
autoFocus?: boolean, autoFocus?: boolean;
defaultVisibility?: Visibility, defaultVisibility?: Visibility;
) => { }
export const useMemoInit = ({ editorRef, memo, cacheKey, username, autoFocus, defaultVisibility }: UseMemoInitOptions) => {
const { actions, dispatch } = useEditorContext(); const { actions, dispatch } = useEditorContext();
const queryClient = useQueryClient();
const initializedRef = useRef(false); const initializedRef = useRef(false);
useEffect(() => { useEffect(() => {
if (initializedRef.current) return; if (initializedRef.current) return;
initializedRef.current = true; initializedRef.current = true;
const init = async () => { if (memo) {
dispatch(actions.setLoading("loading", true)); dispatch(actions.initMemo(memoService.fromMemo(memo)));
} else {
try { const cachedContent = cacheService.load(cacheService.key(username, cacheKey));
if (memoName) { if (cachedContent) {
// Force refetch from server to prevent stale data issues dispatch(actions.updateContent(cachedContent));
// See: https://github.com/usememos/memos/issues/5470 }
await queryClient.invalidateQueries({ queryKey: memoKeys.detail(memoName) }); if (defaultVisibility !== undefined) {
dispatch(actions.setMetadata({ visibility: defaultVisibility }));
// 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);
}
} }
}; }
init(); if (autoFocus) {
}, [memoName, cacheKey, username, autoFocus, defaultVisibility, actions, dispatch, editorRef, queryClient]); setTimeout(() => editorRef.current?.focus(), 100);
}
}, [memo, cacheKey, username, autoFocus, defaultVisibility, actions, dispatch, editorRef]);
}; };
...@@ -17,29 +17,16 @@ import { cacheService, errorService, memoService, validationService } from "./se ...@@ -17,29 +17,16 @@ import { cacheService, errorService, memoService, validationService } from "./se
import { EditorProvider, useEditorContext } from "./state"; import { EditorProvider, useEditorContext } from "./state";
import type { MemoEditorProps } from "./types"; import type { MemoEditorProps } from "./types";
const MemoEditor = (props: MemoEditorProps) => { const MemoEditor = (props: MemoEditorProps) => (
const { className, cacheKey, memoName, parentMemoName, autoFocus, placeholder, onConfirm, onCancel } = props; <EditorProvider>
<MemoEditorImpl {...props} />
return ( </EditorProvider>
<EditorProvider> );
<MemoEditorImpl
className={className}
cacheKey={cacheKey}
memoName={memoName}
parentMemoName={parentMemoName}
autoFocus={autoFocus}
placeholder={placeholder}
onConfirm={onConfirm}
onCancel={onCancel}
/>
</EditorProvider>
);
};
const MemoEditorImpl: React.FC<MemoEditorProps> = ({ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
className, className,
cacheKey, cacheKey,
memoName, memo,
parentMemoName, parentMemoName,
autoFocus, autoFocus,
placeholder, placeholder,
...@@ -53,10 +40,12 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({ ...@@ -53,10 +40,12 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
const { state, actions, dispatch } = useEditorContext(); const { state, actions, dispatch } = useEditorContext();
const { userGeneralSetting } = useAuth(); const { userGeneralSetting } = useAuth();
const memoName = memo?.name;
// Get default visibility from user settings // Get default visibility from user settings
const defaultVisibility = userGeneralSetting?.memoVisibility ? convertVisibilityFromString(userGeneralSetting.memoVisibility) : undefined; 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 // Auto-save content to localStorage
useAutoSave(state.content, currentUser?.name ?? "", cacheKey); useAutoSave(state.content, currentUser?.name ?? "", cacheKey);
......
...@@ -122,9 +122,8 @@ export const memoService = { ...@@ -122,9 +122,8 @@ export const memoService = {
return { memoName: memo.name, hasChanges: true }; return { memoName: memo.name, hasChanges: true };
}, },
async load(memoName: string): Promise<EditorState> { /** Build editor state from an already-loaded Memo entity (no network request). */
const memo = await memoServiceClient.getMemo({ name: memoName }); fromMemo(memo: Memo): EditorState {
return { return {
content: memo.content, content: memo.content,
metadata: { metadata: {
...@@ -135,11 +134,7 @@ export const memoService = { ...@@ -135,11 +134,7 @@ export const memoService = {
}, },
ui: { ui: {
isFocusMode: false, isFocusMode: false,
isLoading: { isLoading: { saving: false, uploading: false, loading: false },
saving: false,
uploading: false,
loading: false,
},
isDragging: false, isDragging: false,
isComposing: false, isComposing: false,
}, },
......
...@@ -8,7 +8,8 @@ export interface MemoEditorProps { ...@@ -8,7 +8,8 @@ export interface MemoEditorProps {
className?: string; className?: string;
cacheKey?: string; cacheKey?: string;
placeholder?: string; placeholder?: string;
memoName?: string; /** Existing memo to edit. When provided, the editor initializes from it without fetching. */
memo?: Memo;
parentMemoName?: string; parentMemoName?: string;
autoFocus?: boolean; autoFocus?: boolean;
onConfirm?: (memoName: string) => void; onConfirm?: (memoName: string) => void;
......
...@@ -31,8 +31,7 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => { ...@@ -31,8 +31,7 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
const { previewState, openPreview, setPreviewOpen } = useImagePreview(); const { previewState, openPreview, setPreviewOpen } = useImagePreview();
const { unpinMemo } = useMemoActions(memoData, isArchived); const { unpinMemo } = useMemoActions(memoData, isArchived);
const handleEditorConfirm = () => setShowEditor(false); const closeEditor = () => setShowEditor(false);
const handleEditorCancel = () => setShowEditor(false);
const openEditor = () => setShowEditor(true); const openEditor = () => setShowEditor(true);
const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({ const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({
...@@ -63,9 +62,9 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => { ...@@ -63,9 +62,9 @@ const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
autoFocus autoFocus
className="mb-2" className="mb-2"
cacheKey={`inline-memo-editor-${memoData.name}`} cacheKey={`inline-memo-editor-${memoData.name}`}
memoName={memoData.name} memo={memoData}
onConfirm={handleEditorConfirm} onConfirm={closeEditor}
onCancel={handleEditorCancel} onCancel={closeEditor}
/> />
); );
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment