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") | ||||
| 	} | ||||
|  | ||||
| 	currentTs := time.Now().Unix() | ||||
| 	update := &store.UpdateMemo{ | ||||
| 		ID:        request.Id, | ||||
| 		UpdatedTs: ¤tTs, | ||||
| 	} | ||||
| 	for _, path := range request.UpdateMask.Paths { | ||||
| 		if path == "content" { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { useEffect, useRef, useState } from "react"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import Empty from "@/components/Empty"; | ||||
| import MemoFilter from "@/components/MemoFilter"; | ||||
| import MemoView from "@/components/MemoView"; | ||||
| @@ -7,8 +7,7 @@ import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts"; | ||||
| import { getTimeStampByDate } from "@/helpers/datetime"; | ||||
| import useCurrentUser from "@/hooks/useCurrentUser"; | ||||
| import { useFilterStore } from "@/store/module"; | ||||
| import { useMemoV1Store } from "@/store/v1"; | ||||
| import { Memo } from "@/types/proto/api/v2/memo_service"; | ||||
| import { useMemoList, useMemoV1Store } from "@/store/v1"; | ||||
| import { useTranslate } from "@/utils/i18n"; | ||||
|  | ||||
| const Explore = () => { | ||||
| @@ -16,14 +15,14 @@ const Explore = () => { | ||||
|   const user = useCurrentUser(); | ||||
|   const filterStore = useFilterStore(); | ||||
|   const memoStore = useMemoV1Store(); | ||||
|   const memoList = useMemoList(); | ||||
|   const [isComplete, setIsComplete] = useState(false); | ||||
|   const [isRequesting, setIsRequesting] = useState(false); | ||||
|   const memosRef = useRef<Memo[]>([]); | ||||
|   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(() => { | ||||
|     memosRef.current = []; | ||||
|     memoList.reset(); | ||||
|     fetchMemos(); | ||||
|   }, [tagQuery, textQuery]); | ||||
|  | ||||
| @@ -42,11 +41,10 @@ const Explore = () => { | ||||
|     setIsRequesting(true); | ||||
|     const data = await memoStore.fetchMemos({ | ||||
|       limit: DEFAULT_MEMO_LIMIT, | ||||
|       offset: memosRef.current.length, | ||||
|       offset: memoList.size(), | ||||
|       filter: filters.join(" && "), | ||||
|     }); | ||||
|     setIsRequesting(false); | ||||
|     memosRef.current = [...memosRef.current, ...data]; | ||||
|     setIsComplete(data.length < DEFAULT_MEMO_LIMIT); | ||||
|   }; | ||||
|  | ||||
| @@ -59,12 +57,11 @@ const Explore = () => { | ||||
|           <MemoView key={memo.id} memo={memo} lazyRendering showCreator showParent /> | ||||
|         ))} | ||||
|  | ||||
|         {isRequesting && ( | ||||
|         {isRequesting ? ( | ||||
|           <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> | ||||
|           </div> | ||||
|         )} | ||||
|         {isComplete ? ( | ||||
|         ) : isComplete ? ( | ||||
|           sortedMemos.length === 0 && ( | ||||
|             <div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic"> | ||||
|               <Empty /> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { useEffect, useRef, useState } from "react"; | ||||
| import { useEffect, useState } from "react"; | ||||
| import Empty from "@/components/Empty"; | ||||
| import HomeSidebar from "@/components/HomeSidebar"; | ||||
| import HomeSidebarDrawer from "@/components/HomeSidebarDrawer"; | ||||
| @@ -11,8 +11,7 @@ import { getTimeStampByDate } from "@/helpers/datetime"; | ||||
| import useCurrentUser from "@/hooks/useCurrentUser"; | ||||
| import useResponsiveWidth from "@/hooks/useResponsiveWidth"; | ||||
| import { useFilterStore } from "@/store/module"; | ||||
| import { useMemoV1Store } from "@/store/v1"; | ||||
| import { Memo } from "@/types/proto/api/v2/memo_service"; | ||||
| import { useMemoList, useMemoV1Store } from "@/store/v1"; | ||||
| import { useTranslate } from "@/utils/i18n"; | ||||
|  | ||||
| const Home = () => { | ||||
| @@ -23,14 +22,14 @@ const Home = () => { | ||||
|   const memoStore = useMemoV1Store(); | ||||
|   const [isComplete, setIsComplete] = useState(false); | ||||
|   const [isRequesting, setIsRequesting] = useState(false); | ||||
|   const memosRef = useRef<Memo[]>([]); | ||||
|   const memoList = useMemoList(); | ||||
|   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) => Number(b.pinned) - Number(a.pinned)); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     memosRef.current = []; | ||||
|     memoList.reset(); | ||||
|     fetchMemos(); | ||||
|   }, [tagQuery, textQuery]); | ||||
|  | ||||
| @@ -49,36 +48,29 @@ const Home = () => { | ||||
|     setIsRequesting(true); | ||||
|     const data = await memoStore.fetchMemos({ | ||||
|       limit: DEFAULT_MEMO_LIMIT, | ||||
|       offset: memosRef.current.length, | ||||
|       offset: memoList.size(), | ||||
|       filter: filters.join(" && "), | ||||
|     }); | ||||
|     setIsRequesting(false); | ||||
|     memosRef.current = [...memosRef.current, ...data]; | ||||
|     setIsComplete(data.length < DEFAULT_MEMO_LIMIT); | ||||
|   }; | ||||
|  | ||||
|   const handleMemoCreated = async (memoId: number) => { | ||||
|     const memo = await memoStore.getOrFetchMemoById(memoId); | ||||
|     memosRef.current = [memo, ...memosRef.current]; | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div className="w-full max-w-5xl flex flex-row justify-center items-start"> | ||||
|       <div className="w-full sm:pt-3 md:pt-6"> | ||||
|         <MobileHeader>{!md && <HomeSidebarDrawer />}</MobileHeader> | ||||
|         <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"> | ||||
|             <MemoFilter /> | ||||
|             {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"> | ||||
|                 <p className="text-sm text-gray-400 italic">{t("memo.fetching-data")}</p> | ||||
|               </div> | ||||
|             )} | ||||
|             {isComplete ? ( | ||||
|             ) : isComplete ? ( | ||||
|               sortedMemos.length === 0 && ( | ||||
|                 <div className="w-full mt-12 mb-8 flex flex-col justify-center items-center italic"> | ||||
|                   <Empty /> | ||||
|   | ||||
| @@ -10,32 +10,28 @@ import DatePicker from "@/components/kit/DatePicker"; | ||||
| import { DAILY_TIMESTAMP } from "@/helpers/consts"; | ||||
| import { getDateStampByDate, getNormalizedDateString, getTimeStampByDate } from "@/helpers/datetime"; | ||||
| import useCurrentUser from "@/hooks/useCurrentUser"; | ||||
| import { useMemoV1Store } from "@/store/v1"; | ||||
| import { Memo } from "@/types/proto/api/v2/memo_service"; | ||||
| import { useMemoList, useMemoV1Store } from "@/store/v1"; | ||||
| import { useTranslate } from "@/utils/i18n"; | ||||
|  | ||||
| const Timeline = () => { | ||||
|   const t = useTranslate(); | ||||
|   const memoStore = useMemoV1Store(); | ||||
|   const currentUser = useCurrentUser(); | ||||
|   const memoStore = useMemoV1Store(); | ||||
|   const memoList = useMemoList(); | ||||
|   const currentDateStamp = getDateStampByDate(getNormalizedDateString()) as number; | ||||
|   const [selectedDateStamp, setSelectedDateStamp] = useState<number>(currentDateStamp as number); | ||||
|   const [memos, setMemos] = useState<Memo[]>([]); | ||||
|   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(() => { | ||||
|     memoList.reset(); | ||||
|     const filters = [ | ||||
|       `creator == "${currentUser.name}"`, | ||||
|       `created_ts_after == ${selectedDateStamp / 1000}`, | ||||
|       `created_ts_before == ${(selectedDateStamp + DAILY_TIMESTAMP) / 1000}`, | ||||
|     ]; | ||||
|     memoStore | ||||
|       .fetchMemos({ | ||||
|     memoStore.fetchMemos({ | ||||
|       filter: filters.join(" && "), | ||||
|       }) | ||||
|       .then((memos: Memo[]) => { | ||||
|         setMemos(memos); | ||||
|     }); | ||||
|   }, [selectedDateStamp]); | ||||
|  | ||||
| @@ -44,12 +40,6 @@ const Timeline = () => { | ||||
|     toggleShowDatePicker(false); | ||||
|   }; | ||||
|  | ||||
|   const handleMemoCreate = async (id: number) => { | ||||
|     await memoStore.getOrFetchMemoById(id).then((memo: Memo) => { | ||||
|       setMemos([memo, ...memos]); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   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"> | ||||
|       <MobileHeader /> | ||||
| @@ -108,7 +98,7 @@ const Timeline = () => { | ||||
|               ))} | ||||
|               {selectedDateStamp === currentDateStamp && ( | ||||
|                 <div className="w-full pl-0 sm:pl-12 sm:mt-4"> | ||||
|                   <MemoEditor cacheKey="timeline-editor" onConfirm={handleMemoCreate} /> | ||||
|                   <MemoEditor cacheKey="timeline-editor" /> | ||||
|                 </div> | ||||
|               )} | ||||
|             </div> | ||||
|   | ||||
| @@ -1,13 +1,25 @@ | ||||
| import { cloneDeep } from "lodash-es"; | ||||
| import { create } from "zustand"; | ||||
| import { combine } from "zustand/middleware"; | ||||
| import { memoServiceClient } from "@/grpcweb"; | ||||
| import { CreateMemoRequest, ListMemosRequest, Memo } from "@/types/proto/api/v2/memo_service"; | ||||
|  | ||||
| interface State { | ||||
|   memoById: Map<number, Memo>; | ||||
| } | ||||
|  | ||||
| export const useMemoV1Store = create( | ||||
|   combine({ memoById: new Map<number, Memo>() }, (set, get) => ({ | ||||
|     setState: (state: State) => set(state), | ||||
|     getState: () => get(), | ||||
|     fetchMemos: async (request: Partial<ListMemosRequest>) => { | ||||
|       const { memos } = await memoServiceClient.listMemos(request); | ||||
|       set((state) => { | ||||
|         for (const memo of memos) { | ||||
|           state.memoById.set(memo.id, memo); | ||||
|         } | ||||
|         return cloneDeep(state); | ||||
|       }); | ||||
|       return memos; | ||||
|     }, | ||||
|     getOrFetchMemoById: async (id: number) => { | ||||
| @@ -25,9 +37,8 @@ export const useMemoV1Store = create( | ||||
|  | ||||
|       set((state) => { | ||||
|         state.memoById.set(id, res.memo as Memo); | ||||
|         return state; | ||||
|         return cloneDeep(state); | ||||
|       }); | ||||
|  | ||||
|       return res.memo; | ||||
|     }, | ||||
|     getMemoById: (id: number) => { | ||||
| @@ -41,7 +52,7 @@ export const useMemoV1Store = create( | ||||
|  | ||||
|       set((state) => { | ||||
|         state.memoById.set(memo.id, memo); | ||||
|         return state; | ||||
|         return cloneDeep(state); | ||||
|       }); | ||||
|       return memo; | ||||
|     }, | ||||
| @@ -57,18 +68,18 @@ export const useMemoV1Store = create( | ||||
|  | ||||
|       set((state) => { | ||||
|         state.memoById.set(memo.id, memo); | ||||
|         return state; | ||||
|         return cloneDeep(state); | ||||
|       }); | ||||
|  | ||||
|       return memo; | ||||
|     }, | ||||
|     deleteMemo: async (id: number) => { | ||||
|       await memoServiceClient.deleteMemo({ | ||||
|         id: id, | ||||
|       }); | ||||
|  | ||||
|       set((state) => { | ||||
|         state.memoById.delete(id); | ||||
|         return state; | ||||
|         return cloneDeep(state); | ||||
|       }); | ||||
|     }, | ||||
|     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