web: add clear trash option in trash nav item context menu

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

web: disable clear trash if empty && update confirm message
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
01zulfi
2026-04-08 16:23:01 +05:00
committed by Abdullah Atta
parent 74921ae1b3
commit f2c088fb18
9 changed files with 798 additions and 692 deletions

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Locator, Page } from "@playwright/test";
import { getTestId } from "../utils";
import { ContextMenuModel } from "./context-menu.model";
import { fillItemDialog, iterateList } from "./utils";
import { confirmDialog, fillItemDialog, iterateList } from "./utils";
export class NavigationMenuModel {
protected readonly page: Page;
@@ -101,4 +101,15 @@ class NavigationItemModel {
await this.menu.open(this.locator);
await this.menu.clickOnItem("remove-shortcut");
}
async clearTrash() {
await this.menu.open(this.locator);
await this.menu.clickOnItem("clear-trash");
await confirmDialog(this.page.locator(getTestId("confirm-dialog")));
}
async getClearTrashItem() {
await this.menu.open(this.locator);
return this.menu.getItem("clear-trash");
}
}

View File

@@ -0,0 +1,49 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { AppModel } from "./models/app.model";
import { expect, NOTE, test } from "./utils";
test("clear trash", async ({ page }) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
await note?.contextMenu.moveToTrash();
const trash = await app.goToTrash();
let trashedNote = await trash.findItem(NOTE.title);
expect(trashedNote).toBeDefined();
const trashItem = await app.navigation.findItem("Trash");
await trashItem?.clearTrash();
trashedNote = await trash.findItem(NOTE.title);
expect(trashedNote).toBeUndefined();
});
test("clear trash option should be disabled if trash is empty", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const trashItem = await app.navigation.findItem("Trash");
const clearTrashMenuItem = await trashItem?.getClearTrashItem();
expect(await clearTrashMenuItem?.isDisabled()).toBe(true);
});

View File

@@ -228,7 +228,8 @@ import {
mdiNoteEditOutline,
mdiArrowUp,
mdiInbox,
mdiConsoleLine
mdiConsoleLine,
mdiDeleteSweepOutline
} from "@mdi/js";
import { useTheme } from "@emotion/react";
import { Theme } from "@notesnook/theme";
@@ -584,3 +585,4 @@ export const ExpandSidebar = createIcon(mdiArrowCollapseRight);
export const HamburgerMenu = createIcon(mdiMenu);
export const ArrowUp = createIcon(mdiArrowUp);
export const Inbox = createIcon(mdiInbox);
export const ClearTrash = createIcon(mdiDeleteSweepOutline);

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { forwardRef, useEffect, useRef, useState } from "react";
import { Flex, Button } from "@theme-ui/components";
import { SxProp } from "@theme-ui/core";
import { Plus } from "../icons";
import { Plus, Icon } from "../icons";
import {
useStore as useSelectionStore,
store as selectionStore
@@ -76,6 +76,7 @@ type ListContainerProps = {
isSearching?: boolean;
onDrop?: (e: React.DragEvent<HTMLDivElement>) => void;
button?: {
Icon?: Icon;
onClick: () => void;
};
Scroller?: Components["Scroller"];
@@ -275,7 +276,11 @@ function ListContainer(props: ListContainerProps) {
height: 45
}}
>
<Plus color="accentForeground" />
{button.Icon ? (
<button.Icon color="accentForeground" />
) : (
<Plus color="accentForeground" />
)}
</Button>
)}
</Flex>

View File

@@ -47,7 +47,8 @@ import {
Notebook as NotebookIcon,
Plus,
SortBy,
Tag as TagIcon
Tag as TagIcon,
ClearTrash
} from "../icons";
import { SortableNavigationItem } from "./navigation-item";
import {
@@ -93,6 +94,8 @@ import { Color, Notebook, Tag } from "@notesnook/core";
import { handleDrop } from "../../common/drop-handler";
import { Menu, useMenuStore, useMenuTrigger } from "../../hooks/use-menu";
import { RenameColorDialog } from "../../dialogs/item-dialog";
import { ConfirmDialog } from "../../dialogs/confirm";
import { showToast } from "../../utils/toast";
import { strings } from "@notesnook/intl";
import Tags from "../../views/tags";
import { Notebooks } from "../../views/notebooks";
@@ -515,6 +518,7 @@ function RouteItem({
context?: { isCollapsed: boolean; collapse: () => void };
}) {
const [location] = useLocation();
const trash = useTrashStore((store) => store.trash);
return (
<SortableNavigationItem
@@ -545,6 +549,39 @@ function RouteItem({
context?.collapse();
}}
menuItems={[
...(item.id === "trash"
? [
{
type: "button" as const,
key: "clear-trash",
title: strings.clearTrash(),
isDisabled: !trash || trash.length === 0,
icon: ClearTrash.path,
onClick: async () => {
const ok = await ConfirmDialog.show({
title: strings.clearTrash(),
positiveButtonText: strings.clear(),
negativeButtonText: strings.cancel(),
message: strings.clearTrashDesc()
});
if (!ok) return;
try {
await useTrashStore.getState().clear();
showToast("success", strings.trashCleared());
} catch (e) {
if (e instanceof Error)
showToast(
"error",
`${strings.couldNotClearTrash()} ${strings.error()}: ${
e.message
}`
);
}
}
}
]
: []),
{
type: "lazy-loader",
key: "sidebar-items-loader",

View File

@@ -27,6 +27,7 @@ import { db } from "../common/db";
import { ListLoader } from "../components/loaders/list-loader";
import { ConfirmDialog } from "../dialogs/confirm";
import { strings } from "@notesnook/intl";
import { ClearTrash } from "../components/icons";
function Trash() {
useNavigate("trash", store.refresh);
@@ -47,13 +48,13 @@ function Trash() {
placeholder={<Placeholder context={filteredItems ? "search" : "trash"} />}
items={filteredItems || items}
button={{
Icon: ClearTrash,
onClick: function () {
ConfirmDialog.show({
title: strings.clearTrash(),
subtitle: strings.clearTrashDesc(),
positiveButtonText: strings.clear(),
negativeButtonText: strings.cancel(),
message: strings.areYouSure()
message: strings.clearTrashDesc()
}).then(async (res) => {
if (res) {
try {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2225,7 +2225,8 @@ Use this if changes from other devices are not appearing on this device. This wi
goBackToNotebooks: () => t`Go back to notebooks`,
goBackToTags: () => t`Go back to tags`,
okay: () => t`Okay`,
clearTrashDesc: () => t`Do you want to clear the trash?`,
clearTrashDesc: () =>
t`Clearing trash will permanently delete all the items in your trash. This action is IRREVERSIBLE.`,
createdAt: () => t`Created at`,
lastEditedAt: () => t`Last edited at`,
enter6DigitCode: () =>