feat: improve trash empty feedback and selection toolbar refs #21

This commit is contained in:
yoma committed 2026-07-14 11:04:17 +08:00
1 parent f58f02d4b2
commit ece135931c
4 files changed
+65 -6

No files matched your search

+2 -2
View File
@@ -130,8 +130,8 @@ export const MemoSelectionActionBar = ({
const { t } = useTranslation();
return (
<div className="hidden h-full min-h-0 flex-1 items-center justify-center bg-white px-16 py-10 lg:flex xl:px-24">
<div className="w-72 -translate-x-20 overflow-hidden rounded-md border border-slate-200 bg-white py-1 shadow-lg xl:-translate-x-28">
<div className="hidden h-full min-h-0 flex-1 items-center justify-start bg-white px-16 py-10 lg:flex lg:pl-44 xl:px-24 xl:pl-44">
<div className="w-72 overflow-hidden rounded-md border border-slate-200 bg-white py-1 shadow-lg">
<div className="flex h-9 items-center gap-2 px-3 text-xs font-semibold text-slate-400">
<CheckSquare className="h-4 w-4" />
{getSelectionCountLabel(selectedCount, t)}
+59 -4
View File
@@ -147,6 +147,13 @@ type MemoDeleteOptimisticContext = {
previousSelectedMemoId: string | null;
};
type EmptyTrashOptimisticContext = {
previousMemoLists: Array<[readonly unknown[], MemoListQueryData | undefined]>;
previousMemoDetails: Array<[readonly unknown[], { memo: MemoDetail } | undefined]>;
previousActivePane: Pane;
previousSelectedMemoId: string | null;
};
const memoToSummary = (memo: MemoDetail): MemoSummary => ({
id: memo.id,
notebookId: memo.notebookId,
@@ -331,6 +338,19 @@ const removeMemoSummariesFromLists = (queryClient: QueryClient, memoIds: Set<str
});
};
const clearTrashMemoLists = (queryClient: QueryClient) => {
for (const [queryKey, current] of queryClient.getQueriesData<MemoListQueryData>({ queryKey: ["memos", "trash"] })) {
if (!current) {
continue;
}
queryClient.setQueryData(queryKey, {
...current,
pages: current.pages.map((page) => ({ ...page, memos: [], totalCount: 0, nextCursor: null })),
});
}
};
const decrementNotebookMemoCounts = (queryClient: QueryClient, removedMemos: MemoSummary[]) => {
if (removedMemos.length === 0) {
return;
@@ -1504,16 +1524,51 @@ export const WorkspaceApp = ({
const emptyTrashMutation = useMutation({
mutationFn: api.emptyTrash,
onSuccess: async () => {
onMutate: async (): Promise<EmptyTrashOptimisticContext> => {
setEmptyTrashConfirmationOpen(false);
clearMemoSelection();
setSelectedMemoId(null);
setActivePane("memos");
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["memos"] }),
queryClient.invalidateQueries({ queryKey: ["memo"] }),
queryClient.invalidateQueries({ queryKey: ["resources"] }),
queryClient.cancelQueries({ queryKey: ["memos"] }),
queryClient.cancelQueries({ queryKey: ["memo"] }),
queryClient.cancelQueries({ queryKey: ["resources"] }),
]);
const previousMemoLists = queryClient.getQueriesData<MemoListQueryData>({ queryKey: ["memos"] });
const previousMemoDetails = queryClient.getQueriesData<{ memo: MemoDetail }>({ queryKey: ["memo"] });
clearTrashMemoLists(queryClient);
for (const [queryKey] of previousMemoDetails) {
if (queryKey[2] === "trash") {
queryClient.removeQueries({ queryKey });
}
}
return { previousMemoLists, previousMemoDetails, previousActivePane: activePane, previousSelectedMemoId: selectedMemoId };
},
onError: (_error, _variables, context) => {
context?.previousMemoLists.forEach(([queryKey, data]) => {
queryClient.setQueryData(queryKey, data);
});
context?.previousMemoDetails.forEach(([queryKey, data]) => {
queryClient.setQueryData(queryKey, data);
});
setSelectedMemoId(context?.previousSelectedMemoId ?? null);
setActivePane(context?.previousActivePane ?? "memos");
setAppNoticeDialog({
title: t("workspaceDialogs.emptyTrashFailedTitle"),
description: t("workspaceDialogs.emptyTrashFailedDescription"),
});
},
onSettled: (_data, error) => {
const refetchType = error ? "active" : "inactive";
void Promise.all([
queryClient.invalidateQueries({ queryKey: ["memos"], refetchType }),
queryClient.invalidateQueries({ queryKey: ["memo"], refetchType }),
queryClient.invalidateQueries({ queryKey: ["resources"], refetchType }),
]);
},
});
+2
View File
@@ -486,6 +486,8 @@ export const enUS = {
emptyTrashTitle: "Empty trash",
emptyTrashDescription: "All notes in Trash and still-linked attachments will be deleted. This action cannot be undone.",
emptyTrashConfirm: "Empty trash",
emptyTrashFailedTitle: "Couldn't empty trash",
emptyTrashFailedDescription: "Your notes have been restored to Trash. Please try again.",
deleteNotebookTitle: "Delete notebook “{{name}}”",
deleteNotebookDescription: "Clear its notes and child notebooks first. It cannot be restored from here after deletion.",
ok: "OK",
+2
View File
@@ -486,6 +486,8 @@ export const zhCN = {
emptyTrashTitle: "清空回收站",
emptyTrashDescription: "回收站中的全部笔记和仍关联的附件都会删除,这个操作不可恢复。",
emptyTrashConfirm: "清空回收站",
emptyTrashFailedTitle: "清空回收站失败",
emptyTrashFailedDescription: "笔记已恢复到回收站,请稍后重试。",
deleteNotebookTitle: "删除笔记本「{{name}}」",
deleteNotebookDescription: "请先清空其中的笔记和子笔记本。删除后无法从这里恢复。",
ok: "知道了",