mirror of
				https://github.com/usememos/memos.git
				synced 2025-06-05 22:09:59 +02:00 
			
		
		
		
	feat: use dialog instead of page
This commit is contained in:
		
							
								
								
									
										79
									
								
								web/src/components/MemoTrashDialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								web/src/components/MemoTrashDialog.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | import { useCallback, useEffect, useState } from "react"; | ||||||
|  | import useLoading from "../hooks/useLoading"; | ||||||
|  | import { locationService, memoService } from "../services"; | ||||||
|  | import { showDialog } from "./Dialog"; | ||||||
|  | import toastHelper from "./Toast"; | ||||||
|  | import DeletedMemo from "./DeletedMemo"; | ||||||
|  | import "../less/memo-trash-dialog.less"; | ||||||
|  |  | ||||||
|  | interface Props extends DialogProps {} | ||||||
|  |  | ||||||
|  | const MemoTrashDialog: React.FC<Props> = (props: Props) => { | ||||||
|  |   const { destroy } = props; | ||||||
|  |   const loadingState = useLoading(); | ||||||
|  |   const [deletedMemos, setDeletedMemos] = useState<Model.Memo[]>([]); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     memoService.fetchAllMemos(); | ||||||
|  |     memoService | ||||||
|  |       .fetchDeletedMemos() | ||||||
|  |       .then((result) => { | ||||||
|  |         if (result !== false) { | ||||||
|  |           setDeletedMemos(result); | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .catch((error) => { | ||||||
|  |         toastHelper.error("Failed to fetch deleted memos: ", error); | ||||||
|  |       }) | ||||||
|  |       .finally(() => { | ||||||
|  |         loadingState.setFinish(); | ||||||
|  |       }); | ||||||
|  |     locationService.clearQuery(); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   const handleDeletedMemoAction = useCallback((memoId: string) => { | ||||||
|  |     setDeletedMemos((deletedMemos) => deletedMemos.filter((memo) => memo.id !== memoId)); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       <div className="dialog-header-container"> | ||||||
|  |         <p className="title-text"> | ||||||
|  |           <span className="icon-text">🗑️</span> | ||||||
|  |           Trash Bin | ||||||
|  |         </p> | ||||||
|  |         <button className="btn close-btn" onClick={destroy}> | ||||||
|  |           <img className="icon-img" src="/icons/close.svg" /> | ||||||
|  |         </button> | ||||||
|  |       </div> | ||||||
|  |       <div className="dialog-content-container"> | ||||||
|  |         {loadingState.isLoading ? ( | ||||||
|  |           <div className="tip-text-container"> | ||||||
|  |             <p className="tip-text">fetching data...</p> | ||||||
|  |           </div> | ||||||
|  |         ) : deletedMemos.length === 0 ? ( | ||||||
|  |           <div className="tip-text-container"> | ||||||
|  |             <p className="tip-text">Here is No Zettels.</p> | ||||||
|  |           </div> | ||||||
|  |         ) : ( | ||||||
|  |           <div className="deleted-memos-container"> | ||||||
|  |             {deletedMemos.map((memo) => ( | ||||||
|  |               <DeletedMemo key={`${memo.id}-${memo.updatedAt}`} memo={memo} handleDeletedMemoAction={handleDeletedMemoAction} /> | ||||||
|  |             ))} | ||||||
|  |           </div> | ||||||
|  |         )} | ||||||
|  |       </div> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default function showMemoTrashDialog(): void { | ||||||
|  |   showDialog( | ||||||
|  |     { | ||||||
|  |       className: "memo-trash-dialog", | ||||||
|  |       useAppContext: true, | ||||||
|  |     }, | ||||||
|  |     MemoTrashDialog, | ||||||
|  |     {} | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -1,6 +1,8 @@ | |||||||
| import { useEffect, useRef } from "react"; | import { useEffect, useRef } from "react"; | ||||||
| import { locationService, userService } from "../services"; | import { locationService, userService } from "../services"; | ||||||
| import showAboutSiteDialog from "./AboutSiteDialog"; | import showAboutSiteDialog from "./AboutSiteDialog"; | ||||||
|  | import showSettingDialog from "./SettingDialog"; | ||||||
|  | import showMemoTrashDialog from "./MemoTrashDialog"; | ||||||
| import "../less/menu-btns-popup.less"; | import "../less/menu-btns-popup.less"; | ||||||
|  |  | ||||||
| interface Props { | interface Props { | ||||||
| @@ -29,11 +31,11 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => { | |||||||
|   }, [shownStatus]); |   }, [shownStatus]); | ||||||
|  |  | ||||||
|   const handleMyAccountBtnClick = () => { |   const handleMyAccountBtnClick = () => { | ||||||
|     locationService.pushHistory("/setting"); |     showSettingDialog(); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleMemosTrashBtnClick = () => { |   const handleMemosTrashBtnClick = () => { | ||||||
|     locationService.pushHistory("/trash"); |     showMemoTrashDialog(); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   const handleAboutBtnClick = () => { |   const handleAboutBtnClick = () => { | ||||||
| @@ -49,7 +51,7 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => { | |||||||
|   return ( |   return ( | ||||||
|     <div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}> |     <div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}> | ||||||
|       <button className="btn action-btn" onClick={handleMyAccountBtnClick}> |       <button className="btn action-btn" onClick={handleMyAccountBtnClick}> | ||||||
|         <span className="icon">👤</span> Settings |         <span className="icon">👤</span> Setting | ||||||
|       </button> |       </button> | ||||||
|       <button className="btn action-btn" onClick={handleMemosTrashBtnClick}> |       <button className="btn action-btn" onClick={handleMemosTrashBtnClick}> | ||||||
|         <span className="icon">🗑️</span> Recycle Bin |         <span className="icon">🗑️</span> Recycle Bin | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								web/src/components/SettingDialog.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								web/src/components/SettingDialog.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | import { useEffect } from "react"; | ||||||
|  | import { memoService } from "../services"; | ||||||
|  | import { showDialog } from "./Dialog"; | ||||||
|  | import MyAccountSection from "./MyAccountSection"; | ||||||
|  | import PreferencesSection from "./PreferencesSection"; | ||||||
|  | import "../less/setting-dialog.less"; | ||||||
|  |  | ||||||
|  | interface Props extends DialogProps {} | ||||||
|  |  | ||||||
|  | const SettingDialog: React.FC<Props> = (props: Props) => { | ||||||
|  |   const { destroy } = props; | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|  |     memoService.fetchAllMemos(); | ||||||
|  |   }, []); | ||||||
|  |  | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       <div className="dialog-header-container"> | ||||||
|  |         <p className="title-text"> | ||||||
|  |           <span className="icon-text">👤</span> | ||||||
|  |           Setting | ||||||
|  |         </p> | ||||||
|  |         <button className="btn close-btn" onClick={destroy}> | ||||||
|  |           <img className="icon-img" src="/icons/close.svg" /> | ||||||
|  |         </button> | ||||||
|  |       </div> | ||||||
|  |       <div className="dialog-content-container"> | ||||||
|  |         <MyAccountSection /> | ||||||
|  |         <PreferencesSection /> | ||||||
|  |       </div> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default function showSettingDialog(): void { | ||||||
|  |   showDialog( | ||||||
|  |     { | ||||||
|  |       className: "setting-dialog", | ||||||
|  |       useAppContext: true, | ||||||
|  |     }, | ||||||
|  |     SettingDialog, | ||||||
|  |     {} | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -67,14 +67,11 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont | |||||||
|   const { shortcut, isActive } = props; |   const { shortcut, isActive } = props; | ||||||
|   const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false); |   const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false); | ||||||
|  |  | ||||||
|   console.log(props); |  | ||||||
|  |  | ||||||
|   const handleShortcutClick = () => { |   const handleShortcutClick = () => { | ||||||
|     console.log("here"); |  | ||||||
|     if (isActive) { |     if (isActive) { | ||||||
|       locationService.setMemoShortcut(""); |       locationService.setMemoShortcut(""); | ||||||
|     } else { |     } else { | ||||||
|       if (!["/", "/trash"].includes(locationService.getState().pathname)) { |       if (!["/"].includes(locationService.getState().pathname)) { | ||||||
|         locationService.setPathname("/"); |         locationService.setPathname("/"); | ||||||
|       } |       } | ||||||
|       locationService.setMemoShortcut(shortcut.id); |       locationService.setMemoShortcut(shortcut.id); | ||||||
|   | |||||||
| @@ -101,7 +101,7 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain | |||||||
|       locationService.setTagQuery(""); |       locationService.setTagQuery(""); | ||||||
|     } else { |     } else { | ||||||
|       utils.copyTextToClipboard(`#${tag.text} `); |       utils.copyTextToClipboard(`#${tag.text} `); | ||||||
|       if (!["/", "/trash"].includes(locationService.getState().pathname)) { |       if (!["/"].includes(locationService.getState().pathname)) { | ||||||
|         locationService.setPathname("/"); |         locationService.setPathname("/"); | ||||||
|       } |       } | ||||||
|       locationService.setTagQuery(tag.text); |       locationService.setTagQuery(tag.text); | ||||||
|   | |||||||
| @@ -76,7 +76,7 @@ const UsageHeatMap: React.FC<Props> = () => { | |||||||
|       locationService.setFromAndToQuery(0, 0); |       locationService.setFromAndToQuery(0, 0); | ||||||
|       setCurrentStat(null); |       setCurrentStat(null); | ||||||
|     } else if (item.count > 0) { |     } else if (item.count > 0) { | ||||||
|       if (!["/", "/trash"].includes(locationService.getState().pathname)) { |       if (!["/"].includes(locationService.getState().pathname)) { | ||||||
|         locationService.setPathname("/"); |         locationService.setPathname("/"); | ||||||
|       } |       } | ||||||
|       locationService.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP); |       locationService.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP); | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								web/src/less/memo-trash-dialog.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								web/src/less/memo-trash-dialog.less
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | @import "./mixin.less"; | ||||||
|  | @import "./memos-header.less"; | ||||||
|  |  | ||||||
|  | .memo-trash-dialog { | ||||||
|  |   > .dialog-container { | ||||||
|  |     @apply w-128 max-w-full mb-8; | ||||||
|  |  | ||||||
|  |     > .dialog-content-container { | ||||||
|  |       .flex(column, flex-start, flex-start); | ||||||
|  |       @apply w-full overflow-y-scroll; | ||||||
|  |  | ||||||
|  |       > .tip-text-container { | ||||||
|  |         @apply w-full h-32; | ||||||
|  |         .flex(column, center, center); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       > .deleted-memos-container { | ||||||
|  |         .flex(column, flex-start, flex-start); | ||||||
|  |         @apply w-full; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| @import "./mixin.less"; |  | ||||||
| @import "./memos-header.less"; |  | ||||||
|  |  | ||||||
| .memo-trash-wrapper { |  | ||||||
|   @apply px-8; |  | ||||||
|   .flex(column, flex-start, flex-start); |  | ||||||
|   width: 100%; |  | ||||||
|   height: 100%; |  | ||||||
|   flex-grow: 1; |  | ||||||
|   overflow-y: scroll; |  | ||||||
|   .hide-scroll-bar(); |  | ||||||
|  |  | ||||||
|   > .section-header-container { |  | ||||||
|     width: 100%; |  | ||||||
|     height: 40px; |  | ||||||
|     margin-bottom: 0; |  | ||||||
|  |  | ||||||
|     > .title-text { |  | ||||||
|       font-weight: bold; |  | ||||||
|       font-size: 18px; |  | ||||||
|       color: @text-black; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   > .tip-text-container { |  | ||||||
|     width: 100%; |  | ||||||
|     height: 128px; |  | ||||||
|     .flex(column, center, center); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   > .deleted-memos-container { |  | ||||||
|     .flex(column, flex-start, flex-start); |  | ||||||
|     flex-grow: 1; |  | ||||||
|     width: 100%; |  | ||||||
|     overflow-y: scroll; |  | ||||||
|     padding-bottom: 64px; |  | ||||||
|     .hide-scroll-bar(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										33
									
								
								web/src/less/setting-dialog.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								web/src/less/setting-dialog.less
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | @import "./mixin.less"; | ||||||
|  | @import "./memos-header.less"; | ||||||
|  |  | ||||||
|  | .setting-dialog { | ||||||
|  |   > .dialog-container { | ||||||
|  |     @apply w-3/5 max-w-full mb-8; | ||||||
|  |  | ||||||
|  |     > .dialog-content-container { | ||||||
|  |       .flex(column, flex-start, flex-start); | ||||||
|  |       @apply w-full overflow-y-scroll; | ||||||
|  |       .hide-scroll-bar(); | ||||||
|  |  | ||||||
|  |       > .section-container { | ||||||
|  |         .flex(column, flex-start, flex-start); | ||||||
|  |         @apply w-full my-2; | ||||||
|  |  | ||||||
|  |         > .title-text { | ||||||
|  |           @apply text-base font-bold mb-2; | ||||||
|  |           color: @text-black; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         > .form-label { | ||||||
|  |           .flex(row, flex-start, center); | ||||||
|  |           @apply w-full text-sm mb-2; | ||||||
|  |  | ||||||
|  |           > .normal-text { | ||||||
|  |             @apply shrink-0; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| @import "./mixin.less"; |  | ||||||
| @import "./memos-header.less"; |  | ||||||
|  |  | ||||||
| .preference-wrapper { |  | ||||||
|   .flex(column, flex-start, flex-start); |  | ||||||
|   @apply w-full h-full grow overflow-y-scroll px-8; |  | ||||||
|   .hide-scroll-bar(); |  | ||||||
|  |  | ||||||
|   > .section-header-container { |  | ||||||
|     @apply w-full h-10 mb-0; |  | ||||||
|  |  | ||||||
|     > .title-text { |  | ||||||
|       @apply font-bold text-lg; |  | ||||||
|       color: @text-black; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   > .tip-text-container { |  | ||||||
|     .flex(column, center, center); |  | ||||||
|     @apply w-full h-32; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   > .sections-wrapper { |  | ||||||
|     .flex(column, flex-start, flex-start); |  | ||||||
|     @apply grow w-full overflow-y-scroll pb-16; |  | ||||||
|     .hide-scroll-bar(); |  | ||||||
|  |  | ||||||
|     > .section-container { |  | ||||||
|       .flex(column, flex-start, flex-start); |  | ||||||
|       @apply w-full bg-white my-2 mx-0 p-4 pb-2 rounded-lg; |  | ||||||
|  |  | ||||||
|       > .title-text { |  | ||||||
|         @apply text-base font-bold mb-2; |  | ||||||
|         color: @text-black; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       > .form-label { |  | ||||||
|         .flex(row, flex-start, center); |  | ||||||
|         @apply w-full text-sm mb-2; |  | ||||||
|  |  | ||||||
|         > .normal-text { |  | ||||||
|           @apply shrink-0; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| import { useEffect } from "react"; |  | ||||||
| import { memoService } from "../services"; |  | ||||||
| import MyAccountSection from "../components/MyAccountSection"; |  | ||||||
| import PreferencesSection from "../components/PreferencesSection"; |  | ||||||
| import "../less/setting.less"; |  | ||||||
|  |  | ||||||
| interface Props {} |  | ||||||
|  |  | ||||||
| const Setting: React.FC<Props> = () => { |  | ||||||
|   useEffect(() => { |  | ||||||
|     memoService.fetchAllMemos(); |  | ||||||
|   }, []); |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <div className="preference-wrapper"> |  | ||||||
|       <div className="section-header-container"> |  | ||||||
|         <div className="title-text"> |  | ||||||
|           <span className="normal-text">Settings</span> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|  |  | ||||||
|       <div className="sections-wrapper"> |  | ||||||
|         <MyAccountSection /> |  | ||||||
|         <PreferencesSection /> |  | ||||||
|       </div> |  | ||||||
|     </div> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default Setting; |  | ||||||
| @@ -1,129 +0,0 @@ | |||||||
| import { useCallback, useContext, useEffect, useState } from "react"; |  | ||||||
| import appContext from "../stores/appContext"; |  | ||||||
| import useLoading from "../hooks/useLoading"; |  | ||||||
| import { locationService, memoService, shortcutService } from "../services"; |  | ||||||
| import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG } from "../helpers/consts"; |  | ||||||
| import utils from "../helpers/utils"; |  | ||||||
| import { checkShouldShowMemoWithFilters } from "../helpers/filter"; |  | ||||||
| import toastHelper from "../components/Toast"; |  | ||||||
| import DeletedMemo from "../components/DeletedMemo"; |  | ||||||
| import MemoFilter from "../components/MemoFilter"; |  | ||||||
| import "../less/memo-trash.less"; |  | ||||||
|  |  | ||||||
| interface Props {} |  | ||||||
|  |  | ||||||
| const Trash: React.FC<Props> = () => { |  | ||||||
|   const { |  | ||||||
|     locationState: { query }, |  | ||||||
|   } = useContext(appContext); |  | ||||||
|   const loadingState = useLoading(); |  | ||||||
|   const [deletedMemos, setDeletedMemos] = useState<Model.Memo[]>([]); |  | ||||||
|  |  | ||||||
|   const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query; |  | ||||||
|   const queryFilter = shortcutService.getShortcutById(shortcutId); |  | ||||||
|   const showMemoFilter = Boolean(tagQuery || (duration && duration.from < duration.to) || memoType || textQuery || queryFilter); |  | ||||||
|  |  | ||||||
|   const shownMemos = |  | ||||||
|     showMemoFilter || queryFilter |  | ||||||
|       ? deletedMemos.filter((memo) => { |  | ||||||
|           let shouldShow = true; |  | ||||||
|  |  | ||||||
|           if (queryFilter) { |  | ||||||
|             const filters = JSON.parse(queryFilter.payload) as Filter[]; |  | ||||||
|             if (Array.isArray(filters)) { |  | ||||||
|               shouldShow = checkShouldShowMemoWithFilters(memo, filters); |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           if (tagQuery) { |  | ||||||
|             const tagsSet = new Set<string>(); |  | ||||||
|             for (const t of Array.from(memo.content.match(TAG_REG) ?? [])) { |  | ||||||
|               const tag = t.replace(TAG_REG, "$1").trim(); |  | ||||||
|               const items = tag.split("/"); |  | ||||||
|               let temp = ""; |  | ||||||
|               for (const i of items) { |  | ||||||
|                 temp += i; |  | ||||||
|                 tagsSet.add(temp); |  | ||||||
|                 temp += "/"; |  | ||||||
|               } |  | ||||||
|             } |  | ||||||
|             if (!tagsSet.has(tagQuery)) { |  | ||||||
|               shouldShow = false; |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|           if ( |  | ||||||
|             duration && |  | ||||||
|             duration.from < duration.to && |  | ||||||
|             (utils.getTimeStampByDate(memo.createdAt) < duration.from || utils.getTimeStampByDate(memo.createdAt) > duration.to) |  | ||||||
|           ) { |  | ||||||
|             shouldShow = false; |  | ||||||
|           } |  | ||||||
|           if (memoType) { |  | ||||||
|             if (memoType === "NOT_TAGGED" && memo.content.match(TAG_REG) !== null) { |  | ||||||
|               shouldShow = false; |  | ||||||
|             } else if (memoType === "LINKED" && memo.content.match(LINK_REG) === null) { |  | ||||||
|               shouldShow = false; |  | ||||||
|             } else if (memoType === "IMAGED" && memo.content.match(IMAGE_URL_REG) === null) { |  | ||||||
|               shouldShow = false; |  | ||||||
|             } else if (memoType === "CONNECTED" && memo.content.match(MEMO_LINK_REG) === null) { |  | ||||||
|               shouldShow = false; |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|           if (textQuery && !memo.content.includes(textQuery)) { |  | ||||||
|             shouldShow = false; |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           return shouldShow; |  | ||||||
|         }) |  | ||||||
|       : deletedMemos; |  | ||||||
|  |  | ||||||
|   useEffect(() => { |  | ||||||
|     memoService.fetchAllMemos(); |  | ||||||
|     memoService |  | ||||||
|       .fetchDeletedMemos() |  | ||||||
|       .then((result) => { |  | ||||||
|         if (result !== false) { |  | ||||||
|           setDeletedMemos(result); |  | ||||||
|         } |  | ||||||
|       }) |  | ||||||
|       .catch((error) => { |  | ||||||
|         toastHelper.error("Failed to fetch deleted memos: ", error); |  | ||||||
|       }) |  | ||||||
|       .finally(() => { |  | ||||||
|         loadingState.setFinish(); |  | ||||||
|       }); |  | ||||||
|     locationService.clearQuery(); |  | ||||||
|   }, []); |  | ||||||
|  |  | ||||||
|   const handleDeletedMemoAction = useCallback((memoId: string) => { |  | ||||||
|     setDeletedMemos((deletedMemos) => deletedMemos.filter((memo) => memo.id !== memoId)); |  | ||||||
|   }, []); |  | ||||||
|  |  | ||||||
|   return ( |  | ||||||
|     <div className="memo-trash-wrapper"> |  | ||||||
|       <div className="section-header-container"> |  | ||||||
|         <div className="title-text"> |  | ||||||
|           <span className="normal-text">Recycle Bin</span> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|       <MemoFilter /> |  | ||||||
|       {loadingState.isLoading ? ( |  | ||||||
|         <div className="tip-text-container"> |  | ||||||
|           <p className="tip-text">fetching data...</p> |  | ||||||
|         </div> |  | ||||||
|       ) : deletedMemos.length === 0 ? ( |  | ||||||
|         <div className="tip-text-container"> |  | ||||||
|           <p className="tip-text">Here is No Zettels.</p> |  | ||||||
|         </div> |  | ||||||
|       ) : ( |  | ||||||
|         <div className="deleted-memos-container"> |  | ||||||
|           {shownMemos.map((memo) => ( |  | ||||||
|             <DeletedMemo key={`${memo.id}-${memo.updatedAt}`} memo={memo} handleDeletedMemoAction={handleDeletedMemoAction} /> |  | ||||||
|           ))} |  | ||||||
|         </div> |  | ||||||
|       )} |  | ||||||
|     </div> |  | ||||||
|   ); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export default Trash; |  | ||||||
| @@ -1,10 +1,6 @@ | |||||||
| import Memos from "../pages/Memos"; | import Memos from "../pages/Memos"; | ||||||
| import Trash from "../pages/Trash"; |  | ||||||
| import Setting from "../pages/Setting"; |  | ||||||
|  |  | ||||||
| const homeRouter = { | const homeRouter = { | ||||||
|   "/trash": <Trash />, |  | ||||||
|   "/setting": <Setting />, |  | ||||||
|   "*": <Memos />, |   "*": <Memos />, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -185,7 +185,7 @@ class LocationService { | |||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   public getValidPathname = (pathname: string): AppRouter => { |   public getValidPathname = (pathname: string): AppRouter => { | ||||||
|     if (["/", "/signin", "/trash", "/setting"].includes(pathname)) { |     if (["/", "/signin"].includes(pathname)) { | ||||||
|       return pathname as AppRouter; |       return pathname as AppRouter; | ||||||
|     } else { |     } else { | ||||||
|       return "/"; |       return "/"; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								web/src/types/location.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								web/src/types/location.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ interface Query { | |||||||
|   shortcutId: string; |   shortcutId: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| type AppRouter = "/" | "/signin" | "/trash" | "/setting"; | type AppRouter = "/" | "/signin"; | ||||||
|  |  | ||||||
| interface AppLocation { | interface AppLocation { | ||||||
|   pathname: AppRouter; |   pathname: AppRouter; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user