diff --git a/apps/web/__e2e__/models/app.model.ts b/apps/web/__e2e__/models/app.model.ts index 5a01aea04..b152e11fa 100644 --- a/apps/web/__e2e__/models/app.model.ts +++ b/apps/web/__e2e__/models/app.model.ts @@ -61,7 +61,7 @@ export class AppModel { async goToNotes() { await this.navigateTo("Notes"); - return new NotesViewModel(this.page, "home"); + return new NotesViewModel(this.page, "home", "home"); } async goToNotebooks() { @@ -71,7 +71,7 @@ export class AppModel { async goToFavorites() { await this.navigateTo("Favorites"); - return new NotesViewModel(this.page, "notes"); + return new NotesViewModel(this.page, "notes", "favorites"); } async goToReminders() { @@ -86,7 +86,7 @@ export class AppModel { async goToColor(color: string) { await this.navigateTo(color); - return new NotesViewModel(this.page, "notes"); + return new NotesViewModel(this.page, "notes", "notes"); } async goToTrash() { diff --git a/apps/web/__e2e__/models/base-view.model.ts b/apps/web/__e2e__/models/base-view.model.ts index 676a0f3b6..65cffb08d 100644 --- a/apps/web/__e2e__/models/base-view.model.ts +++ b/apps/web/__e2e__/models/base-view.model.ts @@ -29,12 +29,15 @@ export class BaseViewModel { private readonly listPlaceholder: Locator; private readonly sortByButton: Locator; - constructor(page: Page, pageId: string, listType: string) { + constructor(page: Page, pageId: string, readonly listType: string) { this.page = page; - this.list = page.locator(`#${pageId} >> ${getTestId(`${listType}-list`)}`); - this.listPlaceholder = page.locator( - `#${pageId} >> ${getTestId("list-placeholder")}` - ); + this.list = page + .locator(`#${pageId}`) + .locator(getTestId(`${listType}-list`)); + + this.listPlaceholder = page + .locator(`#${pageId}`) + .locator(getTestId("list-placeholder")); this.sortByButton = this.page.locator( // TODO: @@ -43,9 +46,9 @@ export class BaseViewModel { } async findGroup(groupName: string) { - const locator = this.list.locator( - `${getTestId(`virtuoso-item-list`)} >> ${getTestId("group-header")}` - ); + const locator = this.list + .locator(getTestId(`virtualized-list`)) + .locator(getTestId("group-header")); for await (const item of iterateList(locator)) { if ((await item.locator(getTestId("title")).textContent()) === groupName) @@ -56,13 +59,10 @@ export class BaseViewModel { protected async *iterateItems() { await this.waitForList(); - const locator = this.list.locator( - `${getTestId(`virtuoso-item-list`)} >> ${getTestId("list-item")}` - ); - for await (const _item of iterateList(locator)) { + for await (const _item of iterateList(this.items)) { const id = await _item.getAttribute("id"); - if (!id) return; + if (!id) continue; yield this.list.locator(`#${id}`); } @@ -82,11 +82,8 @@ export class BaseViewModel { } async focus() { - const items = this.list.locator( - `${getTestId(`virtuoso-item-list`)} >> ${getTestId("list-item")}` - ); - await items.nth(0).click(); - await items.nth(0).click(); + await this.items.nth(0).click(); + await this.items.nth(0).click(); } // async selectAll() { @@ -98,7 +95,7 @@ export class BaseViewModel { // } async press(key: string) { - const itemList = this.list.locator(getTestId(`virtuoso-item-list`)); + const itemList = this.list.locator(getTestId(`virtualized-list`)); await itemList.press(key); await this.page.waitForTimeout(300); } @@ -135,11 +132,11 @@ export class BaseViewModel { return true; } + get items() { + return this.list.locator(getTestId("list-item")); + } + async isEmpty() { - const items = this.list.locator( - `${getTestId(`virtuoso-item-list`)} >> ${getTestId("list-item")}` - ); - const totalItems = await items.count(); - return totalItems <= 0; + return (await this.items.count()) <= 0; } } diff --git a/apps/web/__e2e__/models/editor.model.ts b/apps/web/__e2e__/models/editor.model.ts index c98c06f29..22bf5ff6d 100644 --- a/apps/web/__e2e__/models/editor.model.ts +++ b/apps/web/__e2e__/models/editor.model.ts @@ -44,7 +44,7 @@ export class EditorModel { this.title = page.locator(getTestId("editor-title")); this.content = page.locator(".ProseMirror"); this.tagInput = page.locator(getTestId("editor-tag-input")); - this.tags = page.locator(`${getTestId("tags")} > ${getTestId("tag")}`); + this.tags = page.locator(`${getTestId("tags")} >> ${getTestId("tag")}`); this.focusModeButton = page.locator(getTestId("Focus mode")); this.normalModeButton = page.locator(getTestId("Normal mode")); this.darkModeButton = page.locator(getTestId("Dark mode")); diff --git a/apps/web/__e2e__/models/item.model.ts b/apps/web/__e2e__/models/item.model.ts index 3b9371abd..b9a29aa48 100644 --- a/apps/web/__e2e__/models/item.model.ts +++ b/apps/web/__e2e__/models/item.model.ts @@ -23,6 +23,7 @@ import { ContextMenuModel } from "./context-menu.model"; import { NotesViewModel } from "./notes-view.model"; import { Item } from "./types"; import { confirmDialog, fillItemDialog } from "./utils"; +import { getTestId } from "../utils"; export class ItemModel extends BaseItemModel { private readonly contextMenu: ContextMenuModel; @@ -35,7 +36,8 @@ export class ItemModel extends BaseItemModel { await this.locator.click(); return new NotesViewModel( this.page, - this.id === "topic" ? "notebook" : "notes" + this.id === "topic" ? "notebook" : "notes", + "notes" ); } @@ -53,7 +55,7 @@ export class ItemModel extends BaseItemModel { if (deleteContainedNotes) await this.page.locator("#deleteContainingNotes").check({ force: true }); - await confirmDialog(this.page); + await confirmDialog(this.page.locator(getTestId("confirm-dialog"))); await this.waitFor("detached"); } diff --git a/apps/web/__e2e__/models/note-properties.model.ts b/apps/web/__e2e__/models/note-properties.model.ts index c0b441f0e..760b11944 100644 --- a/apps/web/__e2e__/models/note-properties.model.ts +++ b/apps/web/__e2e__/models/note-properties.model.ts @@ -22,7 +22,12 @@ import { downloadAndReadFile, getTestId } from "../utils"; import { ContextMenuModel } from "./context-menu.model"; import { ToggleModel } from "./toggle.model"; import { Notebook } from "./types"; -import { fillPasswordDialog, iterateList } from "./utils"; +import { + confirmDialog, + fillNotebookDialog, + fillPasswordDialog, + iterateList +} from "./utils"; abstract class BaseProperties { protected readonly page: Page; @@ -248,51 +253,59 @@ export class NoteContextMenuModel extends BaseProperties { } async addToNotebook(notebook: Notebook) { + async function addSubNotebooks( + page: Page, + dialog: Locator, + item: Locator, + notebook: Notebook + ) { + if (notebook.subNotebooks) { + const addSubNotebookButton = item.locator( + getTestId("add-sub-notebook") + ); + for (const subNotebook of notebook.subNotebooks) { + await addSubNotebookButton.click(); + + await fillNotebookDialog(page, subNotebook); + + const subNotebookItem = dialog.locator(getTestId("notebook"), { + hasText: subNotebook.title + }); + await subNotebookItem.waitFor(); + + await page.keyboard.down("Control"); + await subNotebookItem.click(); + await page.keyboard.up("Control"); + + await addSubNotebooks(page, dialog, subNotebookItem, subNotebook); + } + } + } + await this.open(); await this.menu.clickOnItem("notebooks"); await this.menu.clickOnItem("link-notebooks"); - const filterInput = this.page.locator(getTestId("filter-input")); - await filterInput.type(notebook.title); - await filterInput.press("Enter"); + const dialog = this.page.locator(getTestId("move-note-dialog")); - await this.page.waitForSelector(getTestId("notebook"), { - state: "visible", - strict: false + await dialog.locator(getTestId("add-new-notebook")).click(); + + await fillNotebookDialog(this.page, notebook); + + const notebookItem = dialog.locator(getTestId("notebook"), { + hasText: notebook.title }); - const notebookItems = this.page.locator(getTestId("notebook")); - for await (const item of iterateList(notebookItems)) { - await item.locator(getTestId("notebook-tools")).click(); - const title = item.locator(getTestId("notebook-title")); - const createTopicButton = item.locator(getTestId("create-topic")); - const notebookTitle = await title.textContent(); + await notebookItem.waitFor({ state: "visible" }); - if (notebookTitle?.includes(notebook.title)) { - for (const topic of notebook.topics) { - await createTopicButton.click(); - const newItemInput = item.locator(getTestId("new-topic-input")); + await this.page.keyboard.down("Control"); + await notebookItem.click(); + await this.page.keyboard.up("Control"); - await newItemInput.waitFor({ state: "visible" }); - await newItemInput.fill(topic); - await newItemInput.press("Enter"); + await addSubNotebooks(this.page, dialog, notebookItem, notebook); - await item.locator(getTestId("topic"), { hasText: topic }).waitFor(); - } - - const topicItems = item.locator(getTestId("topic")); - for await (const topicItem of iterateList(topicItems)) { - await this.page.keyboard.down("Control"); - await topicItem.click(); - await this.page.keyboard.up("Control"); - } - } - } - - const dialogConfirm = this.page.locator(getTestId("dialog-yes")); - await dialogConfirm.click(); - await dialogConfirm.waitFor({ state: "detached" }); + await confirmDialog(dialog); } async open() { diff --git a/apps/web/__e2e__/models/notebook-item.model.ts b/apps/web/__e2e__/models/notebook-item.model.ts index eb240f8ac..d310b5e6b 100644 --- a/apps/web/__e2e__/models/notebook-item.model.ts +++ b/apps/web/__e2e__/models/notebook-item.model.ts @@ -25,6 +25,7 @@ import { ItemsViewModel } from "./items-view.model"; import { Notebook } from "./types"; import { confirmDialog, fillNotebookDialog } from "./utils"; import { NotesViewModel } from "./notes-view.model"; +import { getTestId } from "../utils"; export class NotebookItemModel extends BaseItemModel { private readonly contextMenu: ContextMenuModel; @@ -37,7 +38,7 @@ export class NotebookItemModel extends BaseItemModel { await this.locator.click(); return { topics: new ItemsViewModel(this.page, "topics"), - notes: new NotesViewModel(this.page, "notebook") + notes: new NotesViewModel(this.page, "notebook", "notes") }; } @@ -45,7 +46,7 @@ export class NotebookItemModel extends BaseItemModel { await this.contextMenu.open(this.locator); await this.contextMenu.clickOnItem("edit"); - await fillNotebookDialog(this.page, notebook, true); + await fillNotebookDialog(this.page, notebook); } async moveToTrash(deleteContainedNotes = false) { @@ -55,7 +56,7 @@ export class NotebookItemModel extends BaseItemModel { if (deleteContainedNotes) await this.page.locator("#deleteContainingNotes").check({ force: true }); - await confirmDialog(this.page); + await confirmDialog(this.page.locator(getTestId("confirm-dialog"))); await this.waitFor("detached"); } diff --git a/apps/web/__e2e__/models/notes-view.model.ts b/apps/web/__e2e__/models/notes-view.model.ts index e1a72977e..3c7de4528 100644 --- a/apps/web/__e2e__/models/notes-view.model.ts +++ b/apps/web/__e2e__/models/notes-view.model.ts @@ -32,8 +32,12 @@ export class NotesViewModel extends BaseViewModel { private readonly createButton: Locator; readonly editor: EditorModel; - constructor(page: Page, pageId: "home" | "notes" | "notebook") { - super(page, pageId, pageId === "home" ? "home" : "notes"); + constructor( + page: Page, + pageId: "home" | "notes" | "favorites" | "notebook", + listType: string + ) { + super(page, pageId, listType); this.createButton = page.locator( // TODO: getTestId(`notes-action-button`) diff --git a/apps/web/__e2e__/models/reminder-item.model.ts b/apps/web/__e2e__/models/reminder-item.model.ts index aa2391507..3b81cf9cb 100644 --- a/apps/web/__e2e__/models/reminder-item.model.ts +++ b/apps/web/__e2e__/models/reminder-item.model.ts @@ -47,7 +47,7 @@ export class ReminderItemModel extends BaseItemModel { await this.contextMenu.open(this.locator); await this.contextMenu.clickOnItem("delete"); - await confirmDialog(this.page); + await confirmDialog(this.page.locator(getTestId("confirm-dialog"))); await this.waitFor("detached"); } diff --git a/apps/web/__e2e__/models/settings-view.model.ts b/apps/web/__e2e__/models/settings-view.model.ts index 7fa558a8f..ddcdca2df 100644 --- a/apps/web/__e2e__/models/settings-view.model.ts +++ b/apps/web/__e2e__/models/settings-view.model.ts @@ -52,7 +52,7 @@ export class SettingsViewModel { .locator("button"); await logoutButton.click(); - await confirmDialog(this.page); + await confirmDialog(this.page.locator(getTestId("confirm-dialog"))); await this.page .locator(getTestId("not-logged-in")) @@ -75,7 +75,9 @@ export class SettingsViewModel { const key = await this.page .locator(getTestId("recovery-key")) .textContent(); - await confirmDialog(this.page); + + const dialog = this.page.locator(getTestId("recovery-key-dialog")); + await confirmDialog(dialog); return key; } diff --git a/apps/web/__e2e__/models/types.ts b/apps/web/__e2e__/models/types.ts index eb8520681..82092f975 100644 --- a/apps/web/__e2e__/models/types.ts +++ b/apps/web/__e2e__/models/types.ts @@ -19,8 +19,8 @@ along with this program. If not, see . export type Notebook = { title: string; - topics: string[]; description?: string; + subNotebooks?: Notebook[]; }; export type Item = { diff --git a/apps/web/__e2e__/models/utils.ts b/apps/web/__e2e__/models/utils.ts index 4ea1ab11d..b5642a474 100644 --- a/apps/web/__e2e__/models/utils.ts +++ b/apps/web/__e2e__/models/utils.ts @@ -30,42 +30,28 @@ export async function* iterateList(list: Locator) { return null; } -export async function fillNotebookDialog( - page: Page, - notebook: Notebook, - editing = false -) { - const titleInput = page.locator(getTestId("title-input")); - const descriptionInput = page.locator(getTestId("description-input")); - const topicInput = page.locator(getTestId(`edit-topic-input`)); - const topicInputAction = page.locator(getTestId(`edit-topic-action`)); +export async function fillNotebookDialog(page: Page, notebook: Notebook) { + const dialog = page.locator(getTestId("add-notebook-dialog")); + const titleInput = dialog.locator(getTestId("title-input")); + const descriptionInput = dialog.locator(getTestId("description-input")); await titleInput.waitFor({ state: "visible" }); await titleInput.fill(notebook.title); if (notebook.description) await descriptionInput.fill(notebook.description); - const topicItems = page.locator(getTestId("topic-item")); - for (let i = 0; i < notebook.topics.length; ++i) { - if (editing) { - const topicItem = topicItems.nth(i); - await topicItem.click(); - } - await topicInput.fill(notebook.topics[i]); - await topicInputAction.click(); - } - - await confirmDialog(page); + await confirmDialog(dialog); } export async function fillReminderDialog( page: Page, reminder: Partial ) { - const titleInput = page.locator(getTestId("title-input")); - const descriptionInput = page.locator(getTestId("description-input")); - const dateInput = page.locator(getTestId("date-input")); - const timeInput = page.locator(getTestId("time-input")); + const dialog = page.locator(getTestId("reminder-dialog")); + const titleInput = dialog.locator(getTestId("title-input")); + const descriptionInput = dialog.locator(getTestId("description-input")); + const dateInput = dialog.locator(getTestId("date-input")); + const timeInput = dialog.locator(getTestId("time-input")); if (reminder.title) { await titleInput.waitFor({ state: "visible" }); @@ -73,10 +59,10 @@ export async function fillReminderDialog( } if (reminder.description) await descriptionInput.fill(reminder.description); if (reminder.mode) - await page.locator(getTestId(`mode-${reminder.mode}`)).click(); + await dialog.locator(getTestId(`mode-${reminder.mode}`)).click(); if (reminder.priority) - await page.locator(getTestId(`priority-${reminder.priority}`)).click(); + await dialog.locator(getTestId(`priority-${reminder.priority}`)).click(); if (reminder.recurringMode && reminder.mode === "repeat") { await page @@ -89,7 +75,7 @@ export async function fillReminderDialog( reminder.recurringMode !== "day" ) { for (const day of reminder.selectedDays) { - await page.locator(getTestId(`day-${day}`)).click(); + await dialog.locator(getTestId(`day-${day}`)).click(); } } } @@ -111,25 +97,27 @@ export async function fillReminderDialog( await timeInput.fill(time); } - await confirmDialog(page); + await confirmDialog(dialog); } export async function fillItemDialog(page: Page, item: Item) { - const titleInput = page.locator(getTestId("title-input")); + const dialog = page.locator(getTestId("item-dialog")); + const titleInput = dialog.locator(getTestId("title-input")); await titleInput.waitFor({ state: "visible" }); await titleInput.fill(item.title); - await confirmDialog(page); + await confirmDialog(dialog); } export async function fillPasswordDialog(page: Page, password: string) { - await page.locator(getTestId("dialog-password")).fill(password); - await confirmDialog(page); + const dialog = page.locator(getTestId("password-dialog")); + await dialog.locator(getTestId("dialog-password")).fill(password); + await confirmDialog(dialog); } -export async function confirmDialog(page: Page) { - const dialogConfirm = page.locator(getTestId("dialog-yes")); +export async function confirmDialog(dialog: Locator) { + const dialogConfirm = dialog.locator(getTestId("dialog-yes")); await dialogConfirm.click(); // await dialogConfirm.waitFor({ state: "detached" }); } diff --git a/apps/web/__e2e__/notes.test.ts b/apps/web/__e2e__/notes.test.ts index 3d6d8c2fc..051a0ed6d 100644 --- a/apps/web/__e2e__/notes.test.ts +++ b/apps/web/__e2e__/notes.test.ts @@ -92,12 +92,15 @@ test("add a note to notebook", async ({ page }) => { await note?.contextMenu.addToNotebook({ title: "Notebook 1", - topics: ["Hello", "World", "Did", "what"] + subNotebooks: [ + { title: "Hello" }, + { title: "World", subNotebooks: [{ title: "Did" }, { title: "what" }] } + ] }); - expect( - await app.toasts.waitForToast("1 note added to Hello and 3 others.") - ).toBe(true); + expect(await app.toasts.waitForToast("1 note added to 5 notebooks.")).toBe( + true + ); }); const actors = ["contextMenu", "properties"] as const; @@ -165,7 +168,7 @@ for (const actor of actors) { await note?.[actor].color("red"); - const coloredNotes = await app.goToColor("red"); + const coloredNotes = await app.goToColor("Red"); const coloredNote = await coloredNotes.findNote(NOTE); expect(coloredNote).toBeDefined(); expect(await coloredNote?.contextMenu.isColored("red")).toBe(true); @@ -307,7 +310,7 @@ test(`sort notes`, async ({ page }, info) => { }); if (!sortResult) return; - expect(await notes.isEmpty()).toBeFalsy(); + await expect(notes.items).toHaveCount(titles.length); }); } } diff --git a/apps/web/__e2e__/notes.test.ts-snapshots/export-txt-Chromium-win32.txt b/apps/web/__e2e__/notes.test.ts-snapshots/export-txt-Chromium-win32.txt index aa657e922..24dab4114 100644 --- a/apps/web/__e2e__/notes.test.ts-snapshots/export-txt-Chromium-win32.txt +++ b/apps/web/__e2e__/notes.test.ts-snapshots/export-txt-Chromium-win32.txt @@ -1,7 +1,3 @@ Test 1 ----------- -This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1 - ----------- -Tags: \ No newline at end of file + This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1 \ No newline at end of file diff --git a/apps/web/src/common/vault.js b/apps/web/src/common/vault.js index 38235da40..cf2052555 100644 --- a/apps/web/src/common/vault.js +++ b/apps/web/src/common/vault.js @@ -20,6 +20,7 @@ along with this program. If not, see . import { db } from "./db"; import { showPasswordDialog } from "./dialog-controller"; import { showToast } from "../utils/toast"; +import { VAULT_ERRORS } from "@notesnook/core/dist/api/vault"; class Vault { static async createVault() { diff --git a/apps/web/src/components/dialog/index.tsx b/apps/web/src/components/dialog/index.tsx index c096b3fd6..398005e0b 100644 --- a/apps/web/src/components/dialog/index.tsx +++ b/apps/web/src/components/dialog/index.tsx @@ -36,6 +36,7 @@ type DialogButtonProps = ButtonProps & { }; type DialogProps = SxProp & { + testId?: string; isOpen?: boolean; onClose?: ( event?: React.MouseEvent | React.KeyboardEvent @@ -65,6 +66,9 @@ function BaseDialog(props: React.PropsWithChildren) { shouldFocusAfterRender onAfterOpen={(e) => onAfterOpen(e, props)} overlayClassName={"theme-scope-dialog"} + data={{ + "test-id": props.testId + }} style={{ content: { top: 0, diff --git a/apps/web/src/components/field/index.tsx b/apps/web/src/components/field/index.tsx index 0b1f092a0..566be4aa9 100644 --- a/apps/web/src/components/field/index.tsx +++ b/apps/web/src/components/field/index.tsx @@ -44,8 +44,17 @@ export type FieldProps = InputProps & { }; function Field(props: FieldProps) { - const { label, styles, helpText, action, sx, id, type, ...inputProps } = - props; + const { + label, + styles, + helpText, + action, + sx, + id, + type, + inputRef, + ...inputProps + } = props; const [isPasswordVisible, setIsPasswordVisible] = useState(false); const colorScheme = useThemeStore((state) => state.colorScheme); @@ -87,6 +96,7 @@ function Field(props: FieldProps) { { + const isChecked = !!noteColor && noteColor.title === color.title; return { type: "button", key: color.key, title: color.title, icon: Circle.path, - styles: { icon: { color: StaticColors[color.key] } }, - isChecked: noteColor && noteColor.title === color.title, - onClick: () => store.setColor(color.title, ...ids) + styles: { icon: { color: DefaultColors[color.key] } }, + isChecked, + onClick: () => store.setColor(color, isChecked, ...ids) } satisfies MenuItem; }); } diff --git a/apps/web/src/components/properties/index.tsx b/apps/web/src/components/properties/index.tsx index 3774482e2..64d499202 100644 --- a/apps/web/src/components/properties/index.tsx +++ b/apps/web/src/components/properties/index.tsx @@ -204,12 +204,15 @@ function EditorProperties(props: EditorPropertiesProps) { export default React.memo(EditorProperties); function Colors({ noteId }: { noteId: string }) { - const result = usePromise(async () => - ( - await db.relations.to({ id: noteId, type: "note" }, "color").resolve(1) - ).at(0) + const color = useStore((store) => store.color); + const result = usePromise( + async () => + ( + await db.relations.to({ id: noteId, type: "note" }, "color").resolve(1) + ).at(0), + [color] ); - + console.log(result); return ( - {COLORS.map((label) => ( - noteStore.get().setColor(label.key, noteId)} - sx={{ - cursor: "pointer", - position: "relative", - alignItems: "center", - justifyContent: "space-between" - }} - data-test-id={`properties-${label}`} - > - - {result.status === "fulfilled" && - label.key === result.value?.colorCode && ( + {COLORS.map((label) => { + const isChecked = + result.status === "fulfilled" && + DefaultColors[label.key] === result.value?.colorCode; + return ( + noteStore.get().setColor(label, isChecked, noteId)} + sx={{ + cursor: "pointer", + position: "relative", + alignItems: "center", + justifyContent: "space-between" + }} + data-test-id={`properties-${label.key}`} + > + + {isChecked && ( )} - - ))} + + ); + })} ); } @@ -271,9 +273,9 @@ function Notebooks({ noteId }: { noteId: string }) { mode="fixed" estimatedSize={50} getItemKey={(index) => result.value.getKey(index)} - items={result.value.ids} - renderItem={(id) => ( - + items={result.value.ungrouped} + renderItem={({ item: id }) => ( + )} /> @@ -294,9 +296,9 @@ function Reminders({ noteId }: { noteId: string }) { mode="fixed" estimatedSize={54} getItemKey={(index) => result.value.getKey(index)} - items={result.value.ids} - renderItem={(id) => ( - + items={result.value.ungrouped} + renderItem={({ item: id }) => ( + )} /> @@ -352,20 +354,18 @@ function SessionHistory({ mode="fixed" estimatedSize={28} getItemKey={(index) => result.value.getKey(index)} - items={result.value.ids} - renderItem={(id) => ( - - {({ item }) => - item.type === "session" ? ( - - ) : null - } + items={result.value.ungrouped} + renderItem={({ item: id }) => ( + + {({ item }) => ( + + )} )} /> diff --git a/apps/web/src/components/trash-item/index.tsx b/apps/web/src/components/trash-item/index.tsx index 106978225..344216838 100644 --- a/apps/web/src/components/trash-item/index.tsx +++ b/apps/web/src/components/trash-item/index.tsx @@ -24,7 +24,6 @@ import { store } from "../../stores/trash-store"; import { Flex, Text } from "@theme-ui/components"; import TimeAgo from "../time-ago"; import { pluralize, toTitleCase } from "@notesnook/common"; -import { showUndoableToast } from "../../common/toasts"; import { showToast } from "../../utils/toast"; import { hashNavigate } from "../../navigation"; import { useStore } from "../../stores/note-store"; @@ -78,8 +77,8 @@ const menuItems: (item: TrashItem, ids?: string[]) => MenuItem[] = ( key: "restore", title: "Restore", icon: Restore.path, - onClick: () => { - store.restore(ids); + onClick: async () => { + await store.restore(...ids); showToast("success", `${pluralize(ids.length, "item")} restored`); }, multiSelect: true @@ -92,11 +91,10 @@ const menuItems: (item: TrashItem, ids?: string[]) => MenuItem[] = ( variant: "dangerous", onClick: async () => { if (!(await showMultiPermanentDeleteConfirmation(ids.length))) return; - showUndoableToast( - `${pluralize(ids.length, "item")} permanently deleted`, - () => store.delete(ids), - () => store.delete(ids, true), - () => store.refresh() + await store.delete(...ids); + showToast( + "success", + `${pluralize(ids.length, "item")} permanently deleted` ); }, multiSelect: true diff --git a/apps/web/src/components/unlock/index.tsx b/apps/web/src/components/unlock/index.tsx index ea0106f50..4f243633c 100644 --- a/apps/web/src/components/unlock/index.tsx +++ b/apps/web/src/components/unlock/index.tsx @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -import { useRef, useState, useCallback, useEffect, useMemo } from "react"; +import { useRef, useState, useCallback, useEffect } from "react"; import { Flex, Text, Button } from "@theme-ui/components"; import { Lock } from "../icons"; import { db } from "../../common/db"; @@ -26,6 +26,7 @@ import { useStore as useAppStore } from "../../stores/app-store"; import Field from "../field"; import { showToast } from "../../utils/toast"; import { ErrorText } from "../error-text"; +import usePromise from "../../hooks/use-promise"; type UnlockProps = { noteId: string; @@ -35,25 +36,23 @@ function Unlock(props: UnlockProps) { const [isWrong, setIsWrong] = useState(false); const [isUnlocking, setIsUnlocking] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const passwordRef = useRef(); + const passwordRef = useRef(null); - const note = useMemo( - () => (!isLoading ? db.notes.note(noteId)?.data : undefined), - [noteId, isLoading] - ); + const note = usePromise(() => db.notes.note(noteId), [noteId]); const openLockedSession = useEditorStore((store) => store.openLockedSession); const openSession = useEditorStore((store) => store.openSession); const setIsEditorOpen = useAppStore((store) => store.setIsEditorOpen); const submit = useCallback(async () => { + console.log("HELO", passwordRef.current); if (!passwordRef.current) return; setIsUnlocking(true); const password = passwordRef.current.value; try { if (!password) return; const note = await db.vault.open(noteId, password); + console.log(note); openLockedSession(note); } catch (e) { if ( @@ -72,12 +71,8 @@ function Unlock(props: UnlockProps) { useEffect(() => { (async () => { - setIsLoading(true); - await openSession(noteId); setIsEditorOpen(true); - - setIsLoading(false); })(); }, [openSession, setIsEditorOpen, noteId]); @@ -106,7 +101,9 @@ function Unlock(props: UnlockProps) { mt={25} sx={{ fontSize: 36, textAlign: "center" }} > - {note?.title || "Open note"} + {note.status === "fulfilled" && note.value + ? note.value.title + : "Open note"} { + onKeyUp={async (e) => { if (e.key === "Enter") { await submit(); } else if (isWrong) { diff --git a/apps/web/src/components/virtualized-list/index.tsx b/apps/web/src/components/virtualized-list/index.tsx index d883a56a4..2d0035f1e 100644 --- a/apps/web/src/components/virtualized-list/index.tsx +++ b/apps/web/src/components/virtualized-list/index.tsx @@ -78,6 +78,7 @@ export function VirtualizedList(props: VirtualizedListProps) { position: "relative", gap: itemGap }} + data-test-id="virtualized-list" > {virtualItems.map((row) => ( ( return ( store.refresh); const notebooks = useStore((store) => store.notebooks); const reloadItem = useRef<(changedItemIds: TreeItemIndex[]) => void>(); + const treeRef = useRef(null); useEffect(() => { if (!notebooks) { @@ -152,6 +154,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) { return ( )} - {notebooks && ( + {notebooks && notebooks.ids.length > 0 ? ( reloadItem.current?.([props.item.index])} + onCreateItem={() => { + reloadItem.current?.([props.item.index]); + treeRef.current?.expandItem( + props.item.index, + props.info.treeId + ); + }} /> {props.children} @@ -312,6 +322,27 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) { + ) : ( + + + Please add a notebook to start linking notes. + + + )} ); @@ -388,9 +419,17 @@ function NotebookItem(props: { {isExpandable ? ( isExpanded ? ( - + ) : ( - + ) ) : null} @@ -411,7 +450,7 @@ function NotebookItem(props: {