mirror of https://github.com/usememos/memos.git
perf(web): eliminate redundant fetch when opening inline memo editor
This commit is contained in:
parent
69485eecb3
commit
aeb1e5fe40
|
|
@ -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<EditorRefActions | null>,
|
||||
memoName: string | undefined,
|
||||
cacheKey: string | undefined,
|
||||
username: string,
|
||||
autoFocus?: boolean,
|
||||
defaultVisibility?: Visibility,
|
||||
) => {
|
||||
interface UseMemoInitOptions {
|
||||
editorRef: React.RefObject<EditorRefActions | null>;
|
||||
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]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<EditorProvider>
|
||||
<MemoEditorImpl
|
||||
className={className}
|
||||
cacheKey={cacheKey}
|
||||
memoName={memoName}
|
||||
parentMemoName={parentMemoName}
|
||||
autoFocus={autoFocus}
|
||||
placeholder={placeholder}
|
||||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</EditorProvider>
|
||||
);
|
||||
};
|
||||
const MemoEditor = (props: MemoEditorProps) => (
|
||||
<EditorProvider>
|
||||
<MemoEditorImpl {...props} />
|
||||
</EditorProvider>
|
||||
);
|
||||
|
||||
const MemoEditorImpl: React.FC<MemoEditorProps> = ({
|
||||
className,
|
||||
cacheKey,
|
||||
memoName,
|
||||
memo,
|
||||
parentMemoName,
|
||||
autoFocus,
|
||||
placeholder,
|
||||
|
|
@ -53,10 +40,12 @@ const MemoEditorImpl: React.FC<MemoEditorProps> = ({
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -122,9 +122,8 @@ export const memoService = {
|
|||
return { memoName: memo.name, hasChanges: true };
|
||||
},
|
||||
|
||||
async load(memoName: string): Promise<EditorState> {
|
||||
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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ const MemoView: React.FC<MemoViewProps> = (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<MemoViewProps> = (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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue