web: add move notebook dialog (#8099)

Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
This commit is contained in:
01zulfi
2025-06-05 11:44:04 +05:00
committed by Abdullah Atta
parent fdd25a11e7
commit 542a628435
7 changed files with 331 additions and 141 deletions

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import ListItem from "../list-item"; import ListItem from "../list-item";
import { Button, Flex, Text } from "@theme-ui/components"; 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 { Notebook as NotebookType } from "@notesnook/core";
import { import {
ChevronDown, ChevronDown,
@@ -30,7 +30,8 @@ import {
Shortcut, Shortcut,
Trash, Trash,
Notebook as NotebookIcon, Notebook as NotebookIcon,
ArrowUp ArrowUp,
Move
} from "../icons"; } from "../icons";
import { MenuItem } from "@notesnook/ui"; import { MenuItem } from "@notesnook/ui";
import { hashNavigate, navigate } from "../../navigation"; import { hashNavigate, navigate } from "../../navigation";
@@ -45,6 +46,7 @@ import { strings } from "@notesnook/intl";
import { db } from "../../common/db"; import { db } from "../../common/db";
import { createSetDefaultHomepageMenuItem } from "../../common"; import { createSetDefaultHomepageMenuItem } from "../../common";
import { useStore as useNotebookStore } from "../../stores/notebook-store"; import { useStore as useNotebookStore } from "../../stores/notebook-store";
import { MoveNotebookDialog } from "../../dialogs/move-notebook-dialog";
type NotebookProps = { type NotebookProps = {
item: NotebookType; item: NotebookType;
@@ -232,6 +234,15 @@ export const notebookMenuItems: (
onClick: () => appStore.addToShortcuts(notebook) onClick: () => appStore.addToShortcuts(notebook)
}, },
{ key: "sep1", type: "separator" }, { key: "sep1", type: "separator" },
{
type: "button",
key: "move",
icon: Move.path,
title: strings.move(),
onClick: () => {
MoveNotebookDialog.show({ notebook: notebook });
}
},
{ {
type: "button", type: "button",
key: "move-to-top", key: "move-to-top",
@@ -255,7 +266,7 @@ export const notebookMenuItems: (
}, },
multiSelect: false multiSelect: false
}, },
{ key: "sep2", type: "separator", isHidden: context?.isRoot }, { key: "sep2", type: "separator" },
{ {
type: "button", type: "button",
key: "movetotrash", key: "movetotrash",

View File

@@ -19,37 +19,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Flex, Text } from "@theme-ui/components"; import { Flex, Text } from "@theme-ui/components";
import {
CheckCircleOutline,
CheckRemove,
CircleEmpty
} from "../components/icons";
import { db } from "../common/db"; import { db } from "../common/db";
import Dialog from "../components/dialog"; import Dialog from "../components/dialog";
import { useStore, store } from "../stores/tag-store"; import { useStore, store } from "../stores/tag-store";
import { store as notestore } from "../stores/note-store"; import { store as notestore } from "../stores/note-store";
import { FilteredList } from "../components/filtered-list"; import { FilteredList } from "../components/filtered-list";
import { ItemReference, Tag } from "@notesnook/core"; import { ItemReference, Tag } from "@notesnook/core";
import { create } from "zustand";
import { VirtualizedGrouping } from "@notesnook/core"; import { VirtualizedGrouping } from "@notesnook/core";
import { ResolvedItem } from "@notesnook/common"; import { ResolvedItem } from "@notesnook/common";
import { BaseDialogProps, DialogManager } from "../common/dialog-manager"; import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
import { strings } from "@notesnook/intl"; import { strings } from "@notesnook/intl";
import {
type SelectedReference = { SelectedCheck,
id: string; SelectedReference,
new: boolean; selectMultiple,
op: "add" | "remove"; useSelectionStore
}; } from "./move-note-dialog";
interface ISelectionStore {
selected: SelectedReference[];
setSelected(refs: SelectedReference[]): void;
}
export const useSelectionStore = create<ISelectionStore>((set) => ({
selected: [],
setSelected: (selected) => set({ selected: selected.slice() })
}));
type AddTagsDialogProps = BaseDialogProps<boolean> & { noteIds: string[] }; type AddTagsDialogProps = BaseDialogProps<boolean> & { noteIds: string[] };
export const AddTagsDialog = DialogManager.register(function AddTagsDialog( export const AddTagsDialog = DialogManager.register(function AddTagsDialog(
@@ -91,8 +76,9 @@ export const AddTagsDialog = DialogManager.register(function AddTagsDialog(
positiveButton={{ positiveButton={{
text: strings.done(), text: strings.done(),
onClick: async () => { onClick: async () => {
const { selected } = useSelectionStore.getState();
for (const id of noteIds) { 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 tagRef: ItemReference = { type: "tag", id: item.id };
const noteRef: ItemReference = { id, type: "note" }; const noteRef: ItemReference = { id, type: "note" };
if (item.op === "add") await db.relations.add(tagRef, noteRef); if (item.op === "add") await db.relations.add(tagRef, noteRef);
@@ -166,47 +152,15 @@ function TagItem(props: { tag: Tag }) {
}} }}
onClick={() => { onClick={() => {
const { selected, setSelected } = useSelectionStore.getState(); const { selected, setSelected } = useSelectionStore.getState();
setSelected(selectMultiple(tag, selected));
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);
}} }}
> >
<Flex sx={{ alignItems: "center" }}> <Flex sx={{ alignItems: "center" }}>
<SelectedCheck size={20} id={tag.id} /> <SelectedCheck size={18} item={tag} />
<Text <Text className="title" data-test-id="tag-title" variant="body">
className="title"
data-test-id="notebook-title"
variant="subtitle"
sx={{ fontWeight: "body", color: "paragraph" }}
>
#{tag.title} #{tag.title}
</Text> </Text>
</Flex> </Flex>
</Flex> </Flex>
); );
} }
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" ? (
<CheckCircleOutline size={size} sx={{ mr: 1 }} color="accent" />
) : selectedTag?.op === "remove" ? (
<CheckRemove size={size} sx={{ mr: 1 }} color="icon-error" />
) : (
<CircleEmpty size={size} sx={{ mr: 1, opacity: 0.4 }} />
);
}

View File

@@ -49,16 +49,16 @@ import {
} from "../components/virtualized-tree"; } from "../components/virtualized-tree";
type MoveNoteDialogProps = BaseDialogProps<boolean> & { noteIds: string[] }; type MoveNoteDialogProps = BaseDialogProps<boolean> & { noteIds: string[] };
type NotebookReference = { export type SelectedReference = {
id: string; id: string;
new: boolean; new: boolean;
op: "add" | "remove"; op: "add" | "remove";
}; };
interface ISelectionStore { interface ISelectionStore {
selected: NotebookReference[]; selected: SelectedReference[];
isMultiselect: boolean; isMultiselect: boolean;
setSelected(refs: NotebookReference[]): void; setSelected(refs: SelectedReference[]): void;
setIsMultiselect(state: boolean): void; setIsMultiselect(state: boolean): void;
} }
export const useSelectionStore = create<ISelectionStore>((set) => ({ export const useSelectionStore = create<ISelectionStore>((set) => ({
@@ -87,7 +87,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({
useEffect(() => { useEffect(() => {
(async function () { (async function () {
const selected: NotebookReference[] = useSelectionStore const selected: SelectedReference[] = useSelectionStore
.getState() .getState()
.selected.slice(); .selected.slice();
@@ -201,7 +201,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({
<Button <Button
variant="anchor" variant="anchor"
onClick={() => { onClick={() => {
const originalSelection: NotebookReference[] = useSelectionStore const originalSelection: SelectedReference[] = useSelectionStore
.getState() .getState()
.selected.filter((a) => !a.new) .selected.filter((a) => !a.new)
.map((s) => ({ ...s, op: "add" })); .map((s) => ({ ...s, op: "add" }));
@@ -268,6 +268,7 @@ export const MoveNoteDialog = DialogManager.register(function MoveNoteDialog({
expand: true expand: true
}); });
}} }}
multiSelectable
/> />
)} )}
/> />
@@ -310,20 +311,28 @@ function calculateIndentation(
depth: number, depth: number,
base: number base: number
) { ) {
if (expandable && depth > 0) return depth * 7 + base; if (expandable && depth > 0) return depth * 18 + base;
else if (depth === 0) return 0; else if (depth === 0) return 0;
else return depth * 12 + base; else return depth * 24 + base;
} }
function NotebookItem(props: { export function NotebookItem(props: {
notebook: Notebook; notebook: Notebook;
isExpanded: boolean; isExpanded: boolean;
isExpandable: boolean; isExpandable: boolean;
toggle: () => void; toggle: () => void;
depth: number; depth: number;
onCreateItem: () => void; onCreateItem: () => void;
multiSelectable?: boolean;
}) { }) {
const { notebook, isExpanded, toggle, depth, isExpandable, onCreateItem } = const {
props; notebook,
isExpanded,
toggle,
depth,
isExpandable,
onCreateItem,
multiSelectable
} = props;
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect); const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
const setSelected = useSelectionStore((store) => store.setSelected); const setSelected = useSelectionStore((store) => store.setSelected);
@@ -339,13 +348,13 @@ function NotebookItem(props: {
const isCtrlPressed = e.ctrlKey || e.metaKey; const isCtrlPressed = e.ctrlKey || e.metaKey;
if (isCtrlPressed) setIsMultiselect(true); if (isCtrlPressed) setIsMultiselect(true);
if (isMultiselect || isCtrlPressed) { if (multiSelectable && (isMultiselect || isCtrlPressed)) {
setSelected(selectMultiple(notebook, selected)); setSelected(selectMultiple(notebook, selected));
} else { } else {
setSelected(selectSingle(notebook, selected)); setSelected(selectSingle(notebook, selected));
} }
}, },
[isMultiselect, notebook, setIsMultiselect, setSelected] [isMultiselect, multiSelectable, notebook, setIsMultiselect, setSelected]
); );
return ( return (
@@ -377,35 +386,17 @@ function NotebookItem(props: {
<Flex sx={{ alignItems: "center" }}> <Flex sx={{ alignItems: "center" }}>
{isExpandable ? ( {isExpandable ? (
isExpanded ? ( isExpanded ? (
<ChevronDown <ChevronDown data-test-id="collapse-notebook" size={18} />
data-test-id="collapse-notebook"
size={20}
sx={{ height: "20px" }}
/>
) : ( ) : (
<ChevronRight <ChevronRight data-test-id="expand-notebook" size={18} />
data-test-id="expand-notebook"
size={20}
sx={{ height: "20px" }}
/>
) )
) : null} ) : null}
<SelectedCheck size={20} item={notebook} onClick={check} /> <SelectedCheck size={18} item={notebook} onClick={check} />
<Text <Text className="title" data-test-id="notebook-title" variant="body">
className="title"
data-test-id="notebook-title"
variant="subtitle"
sx={{ fontWeight: "body" }}
>
{notebook.title} {notebook.title}
{/* <Text variant="subBody" sx={{ fontWeight: "body" }}>
{" "}
({pluralize(notebook.topics.length, "topic")})
</Text> */}
</Text> </Text>
</Flex> </Flex>
<Flex data-test-id="notebook-tools" sx={{ alignItems: "center" }}> <Flex data-test-id="notebook-tools" sx={{ alignItems: "center" }}>
<TopicSelectionIndicator notebook={notebook} />
<Button <Button
variant="secondary" variant="secondary"
data-test-id="add-sub-notebook" data-test-id="add-sub-notebook"
@@ -426,46 +417,27 @@ function NotebookItem(props: {
); );
} }
function TopicSelectionIndicator({ notebook }: { notebook: Notebook }) { export function SelectedCheck({
const hasSelectedTopics = useSelectionStore(
(store) => store.selected.filter((nb) => nb.id === notebook.id).length > 0
);
if (!hasSelectedTopics) return null;
return <Circle size={8} color="accent" sx={{ mr: 1 }} />;
}
function SelectedCheck({
item, item,
size = 20, size = 20,
onClick onClick
}: { }: {
item?: Notebook; item?: { id: string };
size?: number; size?: number;
onClick?: React.MouseEventHandler<HTMLDivElement>; onClick?: React.MouseEventHandler<HTMLDivElement>;
}) { }) {
const selectedItems = useSelectionStore((store) => store.selected); const selectedItems = useSelectionStore((store) => store.selected);
const selectedItem = const selectedItem =
item && selectedItems[findSelectionIndex(item, selectedItems)]; item && selectedItems[findSelectionIndex(item, selectedItems)];
const selected =
selectedItem?.op === "remove" ? "remove" : selectedItem?.op === "add";
return selected === true ? ( return selectedItem?.op === "add" ? (
<CheckCircleOutline <CheckCircleOutline
size={size} size={size}
sx={{ mr: 1 }} sx={{ mr: 1 }}
color="accent" color="accent"
onClick={onClick} onClick={onClick}
/> />
) : selected === null ? ( ) : selectedItem?.op === "remove" ? (
<CheckIntermediate
size={size}
sx={{ mr: 1 }}
color="var(--accent-secondary)"
onClick={onClick}
/>
) : selected === "remove" ? (
<CheckRemove <CheckRemove
size={size} size={size}
sx={{ mr: 1 }} sx={{ mr: 1 }}
@@ -477,18 +449,15 @@ function SelectedCheck({
); );
} }
function createSelection(notebook: Notebook): NotebookReference { function createSelection(item: { id: string }): SelectedReference {
return { return {
id: notebook.id, id: item.id,
op: "add", op: "add",
new: true new: true
}; };
} }
function findSelectionIndex( function findSelectionIndex(ref: { id: string }, array: { id: string }[]) {
ref: NotebookReference | Notebook,
array: NotebookReference[]
) {
return array.findIndex((a) => a.id === ref.id); return array.findIndex((a) => a.id === ref.id);
} }
@@ -498,7 +467,10 @@ function notebookHasNotes(notebookId: string, noteIds: string[]) {
.has(...noteIds); .has(...noteIds);
} }
function selectMultiple(topic: Notebook, selected: NotebookReference[]) { export function selectMultiple(
topic: { id: string },
selected: SelectedReference[]
) {
const index = findSelectionIndex(topic, selected); const index = findSelectionIndex(topic, selected);
const isSelected = index > -1; const isSelected = index > -1;
const item = selected[index]; const item = selected[index];
@@ -514,8 +486,8 @@ function selectMultiple(topic: Notebook, selected: NotebookReference[]) {
return selected; return selected;
} }
function selectSingle(topic: Notebook, array: NotebookReference[]) { function selectSingle(topic: { id: string }, array: SelectedReference[]) {
const selected: NotebookReference[] = array.filter((ref) => !ref.new); const selected: SelectedReference[] = array.filter((ref) => !ref.new);
const index = findSelectionIndex(topic, array); const index = findSelectionIndex(topic, array);
const item = array[index]; const item = array[index];

View File

@@ -0,0 +1,255 @@
/*
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 { Notebook } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { Button, Flex, Text } from "@theme-ui/components";
import { useCallback, useEffect, useRef, useState } from "react";
import { db } from "../common/db";
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
import Dialog from "../components/dialog";
import Field from "../components/field";
import {
TreeNode,
VirtualizedTree,
VirtualizedTreeHandle
} from "../components/virtualized-tree";
import { store as notebookStore, useStore } from "../stores/notebook-store";
import { AddNotebookDialog } from "./add-notebook-dialog";
import { NotebookItem, useSelectionStore } from "./move-note-dialog";
type MoveNotebookDialogProps = BaseDialogProps<boolean> & {
notebook: Notebook;
};
export const MoveNotebookDialog = DialogManager.register(
function MoveNotebookDialog({ onClose, notebook }: MoveNotebookDialogProps) {
const setSelected = useSelectionStore((store) => store.setSelected);
const refreshNotebooks = useStore((store) => store.refresh);
const treeRef = useRef<VirtualizedTreeHandle<Notebook>>(null);
const [notebooks, setNotebooks] = useState<string[]>([]);
const notebookId = notebook.id;
useEffect(() => {
db.notebooks.roots
.ids(db.settings.getGroupOptions("notebooks"))
.then((ids) => setNotebooks(ids));
}, []);
useEffect(() => {
(async function () {
const parentNotebookId = await getParentNotebookId(notebookId);
const selected = useSelectionStore.getState().selected[0];
if (!selected && parentNotebookId) {
setSelected([{ id: parentNotebookId, new: false, op: "add" }]);
}
})();
}, [notebookId, refreshNotebooks, setSelected]);
useEffect(() => {
treeRef.current?.refresh();
}, [notebooks]);
const _onClose = useCallback(
(result: boolean) => {
setSelected([]);
onClose(result);
},
[setSelected, onClose]
);
return (
<Dialog
testId="move-notebook-dialog"
isOpen={true}
title={`${strings.move()} ${notebook.title}`}
description={strings.moveNotebookDesc()}
onClose={() => _onClose(false)}
width={500}
noScroll
positiveButton={{
text: strings.done(),
onClick: async () => {
const { selected } = useSelectionStore.getState();
for (const item of selected) {
if (item.op === "remove") {
await db.relations.unlink(
{ type: "notebook", id: item.id },
{ id: notebookId, type: "notebook" }
);
} else if (item.op === "add") {
await db.relations.add(
{ type: "notebook", id: item.id },
{ id: notebookId, type: "notebook" }
);
}
}
await notebookStore.refresh();
_onClose(true);
}
}}
negativeButton={{
text: strings.cancel(),
onClick: () => _onClose(false)
}}
>
<Flex
id="subnotebooks"
variant="columnFill"
sx={{
height: "80vh",
px: 4
}}
>
<Field
autoFocus
sx={{ m: 0, mb: 2 }}
styles={{
input: { p: "7.5px" }
}}
placeholder={strings.searchNotebooks()}
onChange={async (e) => {
const query = e.target.value.trim();
const ids = await (query
? db.lookup.notebooks(query).ids()
: db.notebooks.roots.ids(
db.settings.getGroupOptions("notebooks")
));
setNotebooks(ids);
}}
/>
{notebooks.length > 0 ? (
<>
<VirtualizedTree
rootId={"root"}
itemHeight={30}
treeRef={treeRef}
getChildNodes={async (id, depth) => {
const parentNotebookId = await getParentNotebookId(
notebookId
);
const nodes: TreeNode<Notebook>[] = [];
if (id === "root") {
for (const id of notebooks) {
if (id === notebookId) continue;
const notebook = (await db.notebooks.notebook(id))!;
const childrenCount = await db.relations
.from(notebook, "notebook")
.count();
const isParent = parentNotebookId === notebook.id;
nodes.push({
data: notebook,
depth: depth + 1,
id,
parentId: "root",
hasChildren: isParent
? childrenCount !== 1
: childrenCount > 0
});
}
return nodes;
}
const subNotebooks = await db.relations
.from({ type: "notebook", id }, "notebook")
.resolve();
for (const notebook of subNotebooks) {
if (notebook.id === notebookId) continue;
const childrenCount = await db.relations
.from(notebook, "notebook")
.count();
const isParent = parentNotebookId === notebook.id;
nodes.push({
parentId: id,
id: notebook.id,
data: notebook,
depth: depth + 1,
hasChildren: isParent
? childrenCount !== 1
: childrenCount > 0
});
}
return nodes;
}}
renderItem={({ item, expanded, index, collapse, expand }) => (
<NotebookItem
notebook={item.data}
depth={item.depth}
isExpandable={item.hasChildren}
isExpanded={expanded}
toggle={expanded ? collapse : expand}
onCreateItem={() => {
treeRef.current?.refreshItem(index, item.data, {
expand: true
});
}}
/>
)}
/>
</>
) : (
<Flex
sx={{
my: 2,
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}
>
<Text variant="body">{strings.notebooksEmpty()}</Text>
<Button
data-test-id="add-new-notebook"
variant="secondary"
sx={{ mt: 2 }}
onClick={() =>
AddNotebookDialog.show({}).then((res) =>
res
? db.notebooks.roots
.ids(db.settings.getGroupOptions("notebooks"))
.then((ids) => setNotebooks(ids))
: null
)
}
>
{strings.addNotebook()}
</Button>
</Flex>
)}
</Flex>
</Dialog>
);
}
);
async function getParentNotebookId(notebookId: string) {
const relation = await db.relations
.to(
{
id: notebookId,
type: "notebook"
},
"notebook"
)
.get();
return relation[0]?.fromId;
}

View File

@@ -1,17 +1,11 @@
msgid "" msgid ""
msgstr "" 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" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n" "X-Generator: @lingui/cli\n"
"Language: en\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 #: src/strings.ts:2410
msgid " \"Notebook > Notes\"" msgid " \"Notebook > Notes\""
@@ -5403,6 +5397,10 @@ msgstr "Select"
msgid "Select a backup file from your device to restore backup" msgid "Select a backup file from your device to restore backup"
msgstr "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 #: src/strings.ts:2097
msgid "Select a theme" msgid "Select a theme"
msgstr "Select a theme" msgstr "Select a theme"

View File

@@ -1,17 +1,11 @@
msgid "" msgid ""
msgstr "" 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" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n" "X-Generator: @lingui/cli\n"
"Language: pseudo-LOCALE\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 #: src/strings.ts:2410
msgid " \"Notebook > Notes\"" msgid " \"Notebook > Notes\""
@@ -5377,6 +5371,10 @@ msgstr ""
msgid "Select a backup file from your device to restore backup" msgid "Select a backup file from your device to restore backup"
msgstr "" 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 #: src/strings.ts:2097
msgid "Select a theme" msgid "Select a theme"
msgstr "" msgstr ""

View File

@@ -2483,5 +2483,7 @@ Use this if changes from other devices are not appearing on this device. This wi
unsetAsHomepage: () => t`Reset homepage`, unsetAsHomepage: () => t`Reset homepage`,
archive: () => t`Archive`, archive: () => t`Archive`,
yourArchiveIsEmpty: () => t`Your archive is empty`, 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.`
}; };