mirror of
				https://github.com/usememos/memos.git
				synced 2025-06-05 22:09:59 +02:00 
			
		
		
		
	chore: implement useMemoList store
This commit is contained in:
		| @@ -176,8 +176,10 @@ func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMe | |||||||
| 		return nil, status.Errorf(codes.PermissionDenied, "permission denied") | 		return nil, status.Errorf(codes.PermissionDenied, "permission denied") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	currentTs := time.Now().Unix() | ||||||
| 	update := &store.UpdateMemo{ | 	update := &store.UpdateMemo{ | ||||||
| 		ID:        request.Id, | 		ID:        request.Id, | ||||||
|  | 		UpdatedTs: ¤tTs, | ||||||
| 	} | 	} | ||||||
| 	for _, path := range request.UpdateMask.Paths { | 	for _, path := range request.UpdateMask.Paths { | ||||||
| 		if path == "content" { | 		if path == "content" { | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { useEffect, useRef, useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| import Empty from "@/components/Empty"; | import Empty from "@/components/Empty"; | ||||||
| import MemoFilter from "@/components/MemoFilter"; | import MemoFilter from "@/components/MemoFilter"; | ||||||
| import MemoView from "@/components/MemoView"; | import MemoView from "@/components/MemoView"; | ||||||
| @@ -7,8 +7,7 @@ import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; | |||||||
| import { getTimeStampByDate } from "@/helpers/datetime"; | import { getTimeStampByDate } from "@/helpers/datetime"; | ||||||
| import useCurrentUser from "@/hooks/useCurrentUser"; | import useCurrentUser from "@/hooks/useCurrentUser"; | ||||||
| import { useFilterStore } from "@/store/module"; | import { useFilterStore } from "@/store/module"; | ||||||
| import { useMemoV1Store } from "@/store/v1"; | import { useMemoList, useMemoV1Store } from "@/store/v1"; | ||||||
| import { Memo } from "@/types/proto/api/v2/memo_service"; |  | ||||||
| import { useTranslate } from "@/utils/i18n"; | import { useTranslate } from "@/utils/i18n"; | ||||||
|  |  | ||||||
| const Explore = () => { | const Explore = () => { | ||||||
| @@ -16,14 +15,14 @@ const Explore = () => { | |||||||
|   const user = useCurrentUser(); |   const user = useCurrentUser(); | ||||||
|   const filterStore = useFilterStore(); |   const filterStore = useFilterStore(); | ||||||
|   const memoStore = useMemoV1Store(); |   const memoStore = useMemoV1Store(); | ||||||
|  |   const memoList = useMemoList(); | ||||||
|   const [isComplete, setIsComplete] = useState(false); |   const [isComplete, setIsComplete] = useState(false); | ||||||
|   const [isRequesting, setIsRequesting] = useState(false); |   const [isRequesting, setIsRequesting] = useState(false); | ||||||
|   const memosRef = useRef<Memo[]>([]); |  | ||||||
|   const { tag: tagQuery, text: textQuery } = filterStore.state; |   const { tag: tagQuery, text: textQuery } = filterStore.state; | ||||||
|   const sortedMemos = memosRef.current.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime)); |   const sortedMemos = memoList.value.sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime)); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     memosRef.current = []; |     memoList.reset(); | ||||||
|     fetchMemos(); |     fetchMemos(); | ||||||
|   }, [tagQuery, textQuery]); |   }, [tagQuery, textQuery]); | ||||||
|  |  | ||||||
| @@ -42,11 +41,10 @@ const Explore = () => { | |||||||
|     setIsRequesting(true); |     setIsRequesting(true); | ||||||
|     const data = await memoStore.fetchMemos({ |     const data = await memoStore.fetchMemos({ | ||||||
|       limit: DEFAULT_MEMO_LIMIT, |       limit: DEFAULT_MEMO_LIMIT, | ||||||
|       offset: memosRef.current.length, |       offset: memoList.size(), | ||||||
|       filter: filters.join(" && "), |       filter: filters.join(" && "), | ||||||
|     }); |     }); | ||||||
|     setIsRequesting(false); |     setIsRequesting(false); | ||||||
|     memosRef.current = [...memosRef.current, ...data]; |  | ||||||
|     setIsComplete(data.length < DEFAULT_MEMO_LIMIT); |     setIsComplete(data.length < DEFAULT_MEMO_LIMIT); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -59,12 +57,11 @@ const Explore = () => { | |||||||
|           <MemoView key={memo.id} memo={memo} lazyRendering showCreator showParent /> |           <MemoView key={memo.id} memo={memo} lazyRendering showCreator showParent /> | ||||||
|         ))} |         ))} | ||||||
|  |  | ||||||
|         {isRequesting && ( |         {isRequesting ? ( | ||||||
|           <div className="flex flex-col justify-start items-center w-full my-8"> |           <div className="flex flex-col justify-start items-center w-full my-8"> | ||||||
|             <p className="text-sm text-gray-400 italic">{t("memo.fetching-data")}</p> |             <p className="text-sm text-gray-400 italic">{t("memo.fetching-data")}</p> | ||||||
|           </div> |           </div> | ||||||
|         )} |         ) : isComplete ? ( | ||||||
|         {isComplete ? ( |  | ||||||
|           sortedMemos.length === 0 && ( |           sortedMemos.length === 0 && ( | ||||||
|             <div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic"> |             <div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic"> | ||||||
|               <Empty /> |               <Empty /> | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { useEffect, useRef, useState } from "react"; | import { useEffect, useState } from "react"; | ||||||
| import Empty from "@/components/Empty"; | import Empty from "@/components/Empty"; | ||||||
| import HomeSidebar from "@/components/HomeSidebar"; | import HomeSidebar from "@/components/HomeSidebar"; | ||||||
| import HomeSidebarDrawer from "@/components/HomeSidebarDrawer"; | import HomeSidebarDrawer from "@/components/HomeSidebarDrawer"; | ||||||
| @@ -11,8 +11,7 @@ import { getTimeStampByDate } from "@/helpers/datetime"; | |||||||
| import useCurrentUser from "@/hooks/useCurrentUser"; | import useCurrentUser from "@/hooks/useCurrentUser"; | ||||||
| import useResponsiveWidth from "@/hooks/useResponsiveWidth"; | import useResponsiveWidth from "@/hooks/useResponsiveWidth"; | ||||||
| import { useFilterStore } from "@/store/module"; | import { useFilterStore } from "@/store/module"; | ||||||
| import { useMemoV1Store } from "@/store/v1"; | import { useMemoList, useMemoV1Store } from "@/store/v1"; | ||||||
| import { Memo } from "@/types/proto/api/v2/memo_service"; |  | ||||||
| import { useTranslate } from "@/utils/i18n"; | import { useTranslate } from "@/utils/i18n"; | ||||||
|  |  | ||||||
| const Home = () => { | const Home = () => { | ||||||
| @@ -23,14 +22,14 @@ const Home = () => { | |||||||
|   const memoStore = useMemoV1Store(); |   const memoStore = useMemoV1Store(); | ||||||
|   const [isComplete, setIsComplete] = useState(false); |   const [isComplete, setIsComplete] = useState(false); | ||||||
|   const [isRequesting, setIsRequesting] = useState(false); |   const [isRequesting, setIsRequesting] = useState(false); | ||||||
|   const memosRef = useRef<Memo[]>([]); |   const memoList = useMemoList(); | ||||||
|   const { tag: tagQuery, text: textQuery } = filterStore.state; |   const { tag: tagQuery, text: textQuery } = filterStore.state; | ||||||
|   const sortedMemos = memosRef.current |   const sortedMemos = memoList.value | ||||||
|     .sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime)) |     .sort((a, b) => getTimeStampByDate(b.displayTime) - getTimeStampByDate(a.displayTime)) | ||||||
|     .sort((a, b) => Number(b.pinned) - Number(a.pinned)); |     .sort((a, b) => Number(b.pinned) - Number(a.pinned)); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     memosRef.current = []; |     memoList.reset(); | ||||||
|     fetchMemos(); |     fetchMemos(); | ||||||
|   }, [tagQuery, textQuery]); |   }, [tagQuery, textQuery]); | ||||||
|  |  | ||||||
| @@ -49,36 +48,29 @@ const Home = () => { | |||||||
|     setIsRequesting(true); |     setIsRequesting(true); | ||||||
|     const data = await memoStore.fetchMemos({ |     const data = await memoStore.fetchMemos({ | ||||||
|       limit: DEFAULT_MEMO_LIMIT, |       limit: DEFAULT_MEMO_LIMIT, | ||||||
|       offset: memosRef.current.length, |       offset: memoList.size(), | ||||||
|       filter: filters.join(" && "), |       filter: filters.join(" && "), | ||||||
|     }); |     }); | ||||||
|     setIsRequesting(false); |     setIsRequesting(false); | ||||||
|     memosRef.current = [...memosRef.current, ...data]; |  | ||||||
|     setIsComplete(data.length < DEFAULT_MEMO_LIMIT); |     setIsComplete(data.length < DEFAULT_MEMO_LIMIT); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleMemoCreated = async (memoId: number) => { |  | ||||||
|     const memo = await memoStore.getOrFetchMemoById(memoId); |  | ||||||
|     memosRef.current = [memo, ...memosRef.current]; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div className="w-full max-w-5xl flex flex-row justify-center items-start"> |     <div className="w-full max-w-5xl flex flex-row justify-center items-start"> | ||||||
|       <div className="w-full sm:pt-3 md:pt-6"> |       <div className="w-full sm:pt-3 md:pt-6"> | ||||||
|         <MobileHeader>{!md && <HomeSidebarDrawer />}</MobileHeader> |         <MobileHeader>{!md && <HomeSidebarDrawer />}</MobileHeader> | ||||||
|         <div className="w-full px-4 sm:px-6 md:pr-2"> |         <div className="w-full px-4 sm:px-6 md:pr-2"> | ||||||
|           <MemoEditor className="mb-2" cacheKey="home-memo-editor" onConfirm={handleMemoCreated} /> |           <MemoEditor className="mb-2" cacheKey="home-memo-editor" /> | ||||||
|           <div className="flex flex-col justify-start items-start w-full max-w-full overflow-y-scroll pb-28 hide-scrollbar"> |           <div className="flex flex-col justify-start items-start w-full max-w-full overflow-y-scroll pb-28 hide-scrollbar"> | ||||||
|             <MemoFilter /> |             <MemoFilter /> | ||||||
|             {sortedMemos.map((memo) => ( |             {sortedMemos.map((memo) => ( | ||||||
|               <MemoView key={memo.id} memo={memo} lazyRendering showVisibility showPinnedStyle showParent /> |               <MemoView key={`${memo.id}-${memo.updateTime}`} memo={memo} lazyRendering showVisibility showPinnedStyle showParent /> | ||||||
|             ))} |             ))} | ||||||
|             {isRequesting && ( |             {isRequesting ? ( | ||||||
|               <div className="flex flex-col justify-start items-center w-full my-8"> |               <div className="flex flex-col justify-start items-center w-full my-8"> | ||||||
|                 <p className="text-sm text-gray-400 italic">{t("memo.fetching-data")}</p> |                 <p className="text-sm text-gray-400 italic">{t("memo.fetching-data")}</p> | ||||||
|               </div> |               </div> | ||||||
|             )} |             ) : isComplete ? ( | ||||||
|             {isComplete ? ( |  | ||||||
|               sortedMemos.length === 0 && ( |               sortedMemos.length === 0 && ( | ||||||
|                 <div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic"> |                 <div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic"> | ||||||
|                   <Empty /> |                   <Empty /> | ||||||
|   | |||||||
| @@ -10,32 +10,28 @@ import DatePicker from "@/components/kit/DatePicker"; | |||||||
| import { DAILY_TIMESTAMP } from "@/helpers/consts"; | import { DAILY_TIMESTAMP } from "@/helpers/consts"; | ||||||
| import { getDateStampByDate, getNormalizedDateString, getTimeStampByDate } from "@/helpers/datetime"; | import { getDateStampByDate, getNormalizedDateString, getTimeStampByDate } from "@/helpers/datetime"; | ||||||
| import useCurrentUser from "@/hooks/useCurrentUser"; | import useCurrentUser from "@/hooks/useCurrentUser"; | ||||||
| import { useMemoV1Store } from "@/store/v1"; | import { useMemoList, useMemoV1Store } from "@/store/v1"; | ||||||
| import { Memo } from "@/types/proto/api/v2/memo_service"; |  | ||||||
| import { useTranslate } from "@/utils/i18n"; | import { useTranslate } from "@/utils/i18n"; | ||||||
|  |  | ||||||
| const Timeline = () => { | const Timeline = () => { | ||||||
|   const t = useTranslate(); |   const t = useTranslate(); | ||||||
|   const memoStore = useMemoV1Store(); |  | ||||||
|   const currentUser = useCurrentUser(); |   const currentUser = useCurrentUser(); | ||||||
|  |   const memoStore = useMemoV1Store(); | ||||||
|  |   const memoList = useMemoList(); | ||||||
|   const currentDateStamp = getDateStampByDate(getNormalizedDateString()) as number; |   const currentDateStamp = getDateStampByDate(getNormalizedDateString()) as number; | ||||||
|   const [selectedDateStamp, setSelectedDateStamp] = useState<number>(currentDateStamp as number); |   const [selectedDateStamp, setSelectedDateStamp] = useState<number>(currentDateStamp as number); | ||||||
|   const [memos, setMemos] = useState<Memo[]>([]); |  | ||||||
|   const [showDatePicker, toggleShowDatePicker] = useToggle(false); |   const [showDatePicker, toggleShowDatePicker] = useToggle(false); | ||||||
|   const sortedMemos = memos.sort((a, b) => getTimeStampByDate(a.createTime) - getTimeStampByDate(b.createTime)); |   const sortedMemos = memoList.value.sort((a, b) => getTimeStampByDate(a.createTime) - getTimeStampByDate(b.createTime)); | ||||||
|  |  | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|  |     memoList.reset(); | ||||||
|     const filters = [ |     const filters = [ | ||||||
|       `creator == "${currentUser.name}"`, |       `creator == "${currentUser.name}"`, | ||||||
|       `created_ts_after == ${selectedDateStamp / 1000}`, |       `created_ts_after == ${selectedDateStamp / 1000}`, | ||||||
|       `created_ts_before == ${(selectedDateStamp + DAILY_TIMESTAMP) / 1000}`, |       `created_ts_before == ${(selectedDateStamp + DAILY_TIMESTAMP) / 1000}`, | ||||||
|     ]; |     ]; | ||||||
|     memoStore |     memoStore.fetchMemos({ | ||||||
|       .fetchMemos({ |  | ||||||
|       filter: filters.join(" && "), |       filter: filters.join(" && "), | ||||||
|       }) |  | ||||||
|       .then((memos: Memo[]) => { |  | ||||||
|         setMemos(memos); |  | ||||||
|     }); |     }); | ||||||
|   }, [selectedDateStamp]); |   }, [selectedDateStamp]); | ||||||
|  |  | ||||||
| @@ -44,12 +40,6 @@ const Timeline = () => { | |||||||
|     toggleShowDatePicker(false); |     toggleShowDatePicker(false); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleMemoCreate = async (id: number) => { |  | ||||||
|     await memoStore.getOrFetchMemoById(id).then((memo: Memo) => { |  | ||||||
|       setMemos([memo, ...memos]); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8"> |     <section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8"> | ||||||
|       <MobileHeader /> |       <MobileHeader /> | ||||||
| @@ -108,7 +98,7 @@ const Timeline = () => { | |||||||
|               ))} |               ))} | ||||||
|               {selectedDateStamp === currentDateStamp && ( |               {selectedDateStamp === currentDateStamp && ( | ||||||
|                 <div className="w-full pl-0 sm:pl-12 sm:mt-4"> |                 <div className="w-full pl-0 sm:pl-12 sm:mt-4"> | ||||||
|                   <MemoEditor cacheKey="timeline-editor" onConfirm={handleMemoCreate} /> |                   <MemoEditor cacheKey="timeline-editor" /> | ||||||
|                 </div> |                 </div> | ||||||
|               )} |               )} | ||||||
|             </div> |             </div> | ||||||
|   | |||||||
| @@ -1,13 +1,25 @@ | |||||||
|  | import { cloneDeep } from "lodash-es"; | ||||||
| import { create } from "zustand"; | import { create } from "zustand"; | ||||||
| import { combine } from "zustand/middleware"; | import { combine } from "zustand/middleware"; | ||||||
| import { memoServiceClient } from "@/grpcweb"; | import { memoServiceClient } from "@/grpcweb"; | ||||||
| import { CreateMemoRequest, ListMemosRequest, Memo } from "@/types/proto/api/v2/memo_service"; | import { CreateMemoRequest, ListMemosRequest, Memo } from "@/types/proto/api/v2/memo_service"; | ||||||
|  |  | ||||||
|  | interface State { | ||||||
|  |   memoById: Map<number, Memo>; | ||||||
|  | } | ||||||
|  |  | ||||||
| export const useMemoV1Store = create( | export const useMemoV1Store = create( | ||||||
|   combine({ memoById: new Map<number, Memo>() }, (set, get) => ({ |   combine({ memoById: new Map<number, Memo>() }, (set, get) => ({ | ||||||
|  |     setState: (state: State) => set(state), | ||||||
|     getState: () => get(), |     getState: () => get(), | ||||||
|     fetchMemos: async (request: Partial<ListMemosRequest>) => { |     fetchMemos: async (request: Partial<ListMemosRequest>) => { | ||||||
|       const { memos } = await memoServiceClient.listMemos(request); |       const { memos } = await memoServiceClient.listMemos(request); | ||||||
|  |       set((state) => { | ||||||
|  |         for (const memo of memos) { | ||||||
|  |           state.memoById.set(memo.id, memo); | ||||||
|  |         } | ||||||
|  |         return cloneDeep(state); | ||||||
|  |       }); | ||||||
|       return memos; |       return memos; | ||||||
|     }, |     }, | ||||||
|     getOrFetchMemoById: async (id: number) => { |     getOrFetchMemoById: async (id: number) => { | ||||||
| @@ -25,9 +37,8 @@ export const useMemoV1Store = create( | |||||||
|  |  | ||||||
|       set((state) => { |       set((state) => { | ||||||
|         state.memoById.set(id, res.memo as Memo); |         state.memoById.set(id, res.memo as Memo); | ||||||
|         return state; |         return cloneDeep(state); | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       return res.memo; |       return res.memo; | ||||||
|     }, |     }, | ||||||
|     getMemoById: (id: number) => { |     getMemoById: (id: number) => { | ||||||
| @@ -41,7 +52,7 @@ export const useMemoV1Store = create( | |||||||
|  |  | ||||||
|       set((state) => { |       set((state) => { | ||||||
|         state.memoById.set(memo.id, memo); |         state.memoById.set(memo.id, memo); | ||||||
|         return state; |         return cloneDeep(state); | ||||||
|       }); |       }); | ||||||
|       return memo; |       return memo; | ||||||
|     }, |     }, | ||||||
| @@ -57,18 +68,18 @@ export const useMemoV1Store = create( | |||||||
|  |  | ||||||
|       set((state) => { |       set((state) => { | ||||||
|         state.memoById.set(memo.id, memo); |         state.memoById.set(memo.id, memo); | ||||||
|         return state; |         return cloneDeep(state); | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       return memo; |       return memo; | ||||||
|     }, |     }, | ||||||
|     deleteMemo: async (id: number) => { |     deleteMemo: async (id: number) => { | ||||||
|       await memoServiceClient.deleteMemo({ |       await memoServiceClient.deleteMemo({ | ||||||
|         id: id, |         id: id, | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       set((state) => { |       set((state) => { | ||||||
|         state.memoById.delete(id); |         state.memoById.delete(id); | ||||||
|         return state; |         return cloneDeep(state); | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|     fetchMemoResources: async (id: number) => { |     fetchMemoResources: async (id: number) => { | ||||||
| @@ -85,3 +96,22 @@ export const useMemoV1Store = create( | |||||||
|     }, |     }, | ||||||
|   })) |   })) | ||||||
| ); | ); | ||||||
|  |  | ||||||
|  | export const useMemoList = () => { | ||||||
|  |   const memoStore = useMemoV1Store(); | ||||||
|  |   const memos = Array.from(memoStore.getState().memoById.values()); | ||||||
|  |  | ||||||
|  |   const reset = () => { | ||||||
|  |     memoStore.setState({ memoById: new Map<number, Memo>() }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const size = () => { | ||||||
|  |     return memoStore.getState().memoById.size; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   return { | ||||||
|  |     value: memos, | ||||||
|  |     reset, | ||||||
|  |     size, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user