From f408cdff02f64bb34defe337cb43708cc82a252b Mon Sep 17 00:00:00 2001 From: 01zulfi <85733202+01zulfi@users.noreply.github.com> Date: Thu, 5 Jun 2025 11:44:04 +0500 Subject: [PATCH] web: add move notebook dialog (#8099) Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> Co-authored-by: Abdullah Atta --- apps/web/src/components/notebook/index.tsx | 17 +- apps/web/src/dialogs/add-tags-dialog.tsx | 68 +---- apps/web/src/dialogs/move-note-dialog.tsx | 104 +++---- apps/web/src/dialogs/move-notebook-dialog.tsx | 255 ++++++++++++++++++ packages/intl/locale/en.po | 12 +- packages/intl/locale/pseudo-LOCALE.po | 12 +- packages/intl/src/strings.ts | 4 +- 7 files changed, 331 insertions(+), 141 deletions(-) create mode 100644 apps/web/src/dialogs/move-notebook-dialog.tsx diff --git a/apps/web/src/components/notebook/index.tsx b/apps/web/src/components/notebook/index.tsx index 72e483034..86a7ff6ac 100644 --- a/apps/web/src/components/notebook/index.tsx +++ b/apps/web/src/components/notebook/index.tsx @@ -19,7 +19,7 @@ along with this program. If not, see . import ListItem from "../list-item"; import { Button, Flex, Text } from "@theme-ui/components"; -import { store, useStore as useNotesStore } from "../../stores/note-store"; +import { useStore as useNotesStore } from "../../stores/note-store"; import { Notebook as NotebookType } from "@notesnook/core"; import { ChevronDown, @@ -30,7 +30,8 @@ import { Shortcut, Trash, Notebook as NotebookIcon, - ArrowUp + ArrowUp, + Move } from "../icons"; import { MenuItem } from "@notesnook/ui"; import { hashNavigate, navigate } from "../../navigation"; @@ -45,6 +46,7 @@ import { strings } from "@notesnook/intl"; import { db } from "../../common/db"; import { createSetDefaultHomepageMenuItem } from "../../common"; import { useStore as useNotebookStore } from "../../stores/notebook-store"; +import { MoveNotebookDialog } from "../../dialogs/move-notebook-dialog"; type NotebookProps = { item: NotebookType; @@ -232,6 +234,15 @@ export const notebookMenuItems: ( onClick: () => appStore.addToShortcuts(notebook) }, { key: "sep1", type: "separator" }, + { + type: "button", + key: "move", + icon: Move.path, + title: strings.move(), + onClick: () => { + MoveNotebookDialog.show({ notebook: notebook }); + } + }, { type: "button", key: "move-to-top", @@ -255,7 +266,7 @@ export const notebookMenuItems: ( }, multiSelect: false }, - { key: "sep2", type: "separator", isHidden: context?.isRoot }, + { key: "sep2", type: "separator" }, { type: "button", key: "movetotrash", diff --git a/apps/web/src/dialogs/add-tags-dialog.tsx b/apps/web/src/dialogs/add-tags-dialog.tsx index 06cddb0d8..0bd257ca5 100644 --- a/apps/web/src/dialogs/add-tags-dialog.tsx +++ b/apps/web/src/dialogs/add-tags-dialog.tsx @@ -19,37 +19,22 @@ along with this program. If not, see . import { useEffect, useState } from "react"; import { Flex, Text } from "@theme-ui/components"; -import { - CheckCircleOutline, - CheckRemove, - CircleEmpty -} from "../components/icons"; import { db } from "../common/db"; import Dialog from "../components/dialog"; import { useStore, store } from "../stores/tag-store"; import { store as notestore } from "../stores/note-store"; import { FilteredList } from "../components/filtered-list"; import { ItemReference, Tag } from "@notesnook/core"; -import { create } from "zustand"; import { VirtualizedGrouping } from "@notesnook/core"; import { ResolvedItem } from "@notesnook/common"; import { BaseDialogProps, DialogManager } from "../common/dialog-manager"; import { strings } from "@notesnook/intl"; - -type SelectedReference = { - id: string; - new: boolean; - op: "add" | "remove"; -}; - -interface ISelectionStore { - selected: SelectedReference[]; - setSelected(refs: SelectedReference[]): void; -} -export const useSelectionStore = create((set) => ({ - selected: [], - setSelected: (selected) => set({ selected: selected.slice() }) -})); +import { + SelectedCheck, + SelectedReference, + selectMultiple, + useSelectionStore +} from "./move-note-dialog"; type AddTagsDialogProps = BaseDialogProps & { noteIds: string[] }; export const AddTagsDialog = DialogManager.register(function AddTagsDialog( @@ -91,8 +76,9 @@ export const AddTagsDialog = DialogManager.register(function AddTagsDialog( positiveButton={{ text: strings.done(), onClick: async () => { + const { selected } = useSelectionStore.getState(); for (const id of noteIds) { - for (const item of useSelectionStore.getState().selected) { + for (const item of selected) { const tagRef: ItemReference = { type: "tag", id: item.id }; const noteRef: ItemReference = { id, type: "note" }; if (item.op === "add") await db.relations.add(tagRef, noteRef); @@ -166,47 +152,15 @@ function TagItem(props: { tag: Tag }) { }} onClick={() => { const { selected, setSelected } = useSelectionStore.getState(); - - const copy = selected.slice(); - const index = copy.findIndex((item) => item.id === tag.id); - const isNew = copy[index] && copy[index].new; - if (isNew) { - copy.splice(index, 1); - } else if (index > -1) { - copy[index] = { - ...copy[index], - op: copy[index].op === "add" ? "remove" : "add" - }; - } else { - copy.push({ id: tag.id, new: true, op: "add" }); - } - setSelected(copy); + setSelected(selectMultiple(tag, selected)); }} > - - + + #{tag.title} ); } - -function SelectedCheck({ id, size = 20 }: { id: string; size?: number }) { - const selected = useSelectionStore((store) => store.selected); - const selectedTag = selected.find((item) => item.id === id); - - return selectedTag?.op === "add" ? ( - - ) : selectedTag?.op === "remove" ? ( - - ) : ( - - ); -} diff --git a/apps/web/src/dialogs/move-note-dialog.tsx b/apps/web/src/dialogs/move-note-dialog.tsx index ee393ec7b..bd3c03925 100644 --- a/apps/web/src/dialogs/move-note-dialog.tsx +++ b/apps/web/src/dialogs/move-note-dialog.tsx @@ -49,16 +49,16 @@ import { } from "../components/virtualized-tree"; type MoveNoteDialogProps = BaseDialogProps & { noteIds: string[] }; -type NotebookReference = { +export type SelectedReference = { id: string; new: boolean; op: "add" | "remove"; }; interface ISelectionStore { - selected: NotebookReference[]; + selected: SelectedReference[]; isMultiselect: boolean; - setSelected(refs: NotebookReference[]): void; + setSelected(refs: SelectedReference[]): void; setIsMultiselect(state: boolean): void; } export const useSelectionStore = create((set) => ({ @@ -87,7 +87,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({ useEffect(() => { (async function () { - const selected: NotebookReference[] = useSelectionStore + const selected: SelectedReference[] = useSelectionStore .getState() .selected.slice(); @@ -201,7 +201,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({ + + )} + + + ); + } +); + +async function getParentNotebookId(notebookId: string) { + const relation = await db.relations + .to( + { + id: notebookId, + type: "notebook" + }, + "notebook" + ) + .get(); + return relation[0]?.fromId; +} diff --git a/packages/intl/locale/en.po b/packages/intl/locale/en.po index f48b132c7..edc04f520 100644 --- a/packages/intl/locale/en.po +++ b/packages/intl/locale/en.po @@ -1,17 +1,11 @@ msgid "" msgstr "" -"POT-Creation-Date: 2025-04-10 10:29+0500\n" +"POT-Creation-Date: 2025-06-05 11:43+0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: @lingui/cli\n" "Language: en\n" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language-Team: \n" -"Plural-Forms: \n" #: src/strings.ts:2410 msgid " \"Notebook > Notes\"" @@ -5403,6 +5397,10 @@ msgstr "Select" msgid "Select a backup file from your device to restore backup" msgstr "Select a backup file from your device to restore backup" +#: src/strings.ts:2488 +msgid "Select a notebook to move this notebook into, or unselect to move it to the root level." +msgstr "Select a notebook to move this notebook into, or unselect to move it to the root level." + #: src/strings.ts:2097 msgid "Select a theme" msgstr "Select a theme" diff --git a/packages/intl/locale/pseudo-LOCALE.po b/packages/intl/locale/pseudo-LOCALE.po index 0740330a4..a7239f9ad 100644 --- a/packages/intl/locale/pseudo-LOCALE.po +++ b/packages/intl/locale/pseudo-LOCALE.po @@ -1,17 +1,11 @@ msgid "" msgstr "" -"POT-Creation-Date: 2025-04-10 10:29+0500\n" +"POT-Creation-Date: 2025-06-05 11:43+0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: @lingui/cli\n" "Language: pseudo-LOCALE\n" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language-Team: \n" -"Plural-Forms: \n" #: src/strings.ts:2410 msgid " \"Notebook > Notes\"" @@ -5377,6 +5371,10 @@ msgstr "" msgid "Select a backup file from your device to restore backup" msgstr "" +#: src/strings.ts:2488 +msgid "Select a notebook to move this notebook into, or unselect to move it to the root level." +msgstr "" + #: src/strings.ts:2097 msgid "Select a theme" msgstr "" diff --git a/packages/intl/src/strings.ts b/packages/intl/src/strings.ts index 3906b164e..1e349d5f4 100644 --- a/packages/intl/src/strings.ts +++ b/packages/intl/src/strings.ts @@ -2483,5 +2483,7 @@ Use this if changes from other devices are not appearing on this device. This wi unsetAsHomepage: () => t`Reset homepage`, archive: () => t`Archive`, yourArchiveIsEmpty: () => t`Your archive is empty`, - unarchive: () => t`Unarchive` + unarchive: () => t`Unarchive`, + moveNotebookDesc: () => + t`Select a notebook to move this notebook into, or unselect to move it to the root level.` };