diff --git a/apps/web/__e2e__/colors.issues.test.js b/apps/web/__e2e__/colors.issues.test.js
index e9f3f0ec1..cd85b1cd6 100644
--- a/apps/web/__e2e__/colors.issues.test.js
+++ b/apps/web/__e2e__/colors.issues.test.js
@@ -25,13 +25,12 @@ test("delete the last note of a color", async ({ page }) => {
const noteSelector = await createNoteAndCheckPresence();
await useContextMenu(noteSelector, async () => {
- await clickMenuItem("colors-Red");
+ await clickMenuItem("colors");
+ await clickMenuItem("red");
});
- expect(await page.isVisible(new Menu("navitem").item("red").build())).toBe(
- true
- );
- await page.waitForTimeout(500);
+ const navItem = new Menu("navitem").item("red").build();
+ await page.waitForSelector(navItem);
await useContextMenu(noteSelector, async () => {
await clickMenuItem("movetotrash");
diff --git a/apps/web/__e2e__/editor.test.js b/apps/web/__e2e__/editor.test.js
index f63e32ba9..4417f2315 100644
--- a/apps/web/__e2e__/editor.test.js
+++ b/apps/web/__e2e__/editor.test.js
@@ -207,7 +207,7 @@ test("select all & backspace should clear all content in editor", async () => {
await expect(getEditorContent()).resolves.toBe("");
});
-test.only("last line doesn't get saved if it's font is different", async () => {
+test("last line doesn't get saved if it's font is different", async () => {
const selector = await createNoteAndCheckPresence();
await page.keyboard.press("Enter");
diff --git a/apps/web/__e2e__/notebooks.test.js b/apps/web/__e2e__/notebooks.test.js
index 67bcdd888..d0ed3fcc8 100644
--- a/apps/web/__e2e__/notebooks.test.js
+++ b/apps/web/__e2e__/notebooks.test.js
@@ -10,7 +10,11 @@ const {
} = require("./utils/actions");
const List = require("./utils/listitemidbuilder");
const Menu = require("./utils/menuitemidbuilder");
-const { checkNotePresence, isPresent } = require("./utils/conditions");
+const {
+ checkNotePresence,
+ isPresent,
+ checkMenuItemText,
+} = require("./utils/conditions");
/**
* @type {Page}
@@ -203,7 +207,7 @@ test("permanently delete a notebook", async () => {
).resolves.toBeFalsy();
});
-test("pin a notebook", async () => {
+test("pin a notebook", async ({ page }) => {
const notebookSelector = await createNotebookAndCheckPresence();
await useContextMenu(notebookSelector, () => clickMenuItem("pin"));
@@ -211,10 +215,9 @@ test("pin a notebook", async () => {
// wait for the menu to properly close
await page.waitForTimeout(500);
- await useContextMenu(notebookSelector, () =>
- expect(
- isPresent(Menu.new("menuitem").item("unpin").build())
- ).resolves.toBeTruthy()
+ await useContextMenu(
+ notebookSelector,
+ async () => await checkMenuItemText("pin", "Unpin")
);
// wait for the menu to properly close
diff --git a/apps/web/__e2e__/notes.test.js b/apps/web/__e2e__/notes.test.js
index c64e13ac1..3655d76b5 100644
--- a/apps/web/__e2e__/notes.test.js
+++ b/apps/web/__e2e__/notes.test.js
@@ -30,6 +30,7 @@ const {
isAbsent,
checkNotePresence,
createNoteAndCheckPresence,
+ checkMenuItemText,
} = require("./utils/conditions");
const List = require("./utils/listitemidbuilder");
const Menu = require("./utils/menuitemidbuilder");
@@ -66,10 +67,10 @@ async function deleteNoteAndCheckAbsence(viewId = "home") {
return trashItemSelector;
}
-async function lockUnlockNote(noteSelector, type) {
+async function lockUnlockNote(noteSelector) {
await openContextMenu(noteSelector);
- await clickMenuItem(type);
+ await clickMenuItem("lock");
await page.fill(getTestId("dialog-password"), PASSWORD);
@@ -93,9 +94,7 @@ async function openLockedNote(noteSelector) {
async function checkNotePinned(noteSelector, pause) {
await openContextMenu(noteSelector);
- const unpinSelector = Menu.new("menuitem").item("unpin").build();
-
- await expect(isPresent(unpinSelector)).resolves.toBeTruthy();
+ await checkMenuItemText("pin", "Unpin");
await closeContextMenu(noteSelector);
@@ -114,11 +113,11 @@ async function checkNoteLocked(noteSelector) {
isAbsent(List.new("note").grouped().atIndex(0).body().build())
).resolves.toBeTruthy();
- await useContextMenu(noteSelector, () =>
- expect(
- isPresent(Menu.new("menuitem").item("unlock").build())
- ).resolves.toBeTruthy()
- );
+ await openContextMenu(noteSelector);
+
+ await checkMenuItemText("lock", "Unlock");
+
+ await closeContextMenu(noteSelector);
}
async function checkNoteColored(noteSelector) {
@@ -126,8 +125,10 @@ async function checkNoteColored(noteSelector) {
await openContextMenu(noteSelector);
+ await page.click(Menu.new("menuitem").item("colors").build());
+
await expect(
- isPresent(Menu.new("menuitem").colorCheck("Red").build())
+ isPresent(Menu.new("menuitem").item("red").checked().build())
).resolves.toBeTruthy();
await closeContextMenu(noteSelector);
@@ -188,10 +189,7 @@ async function exportNote(format) {
Date.prototype.toLocaleString = () => "xxx";
});
- const output = await downloadFile(
- getTestId(`export-dialog-${format}`),
- "utf-8"
- );
+ const output = await downloadFile(getTestId(`menuitem-${format}`), "utf-8");
expect(output).toMatchSnapshot(`export-${format}.txt`);
}
@@ -230,7 +228,7 @@ test.describe("run tests independently", () => {
await openContextMenu(noteSelector);
- await clickMenuItem("addtonotebook(s)");
+ await clickMenuItem("addtonotebook");
await addNoteToNotebook();
});
@@ -242,11 +240,13 @@ test.describe("run tests independently", () => {
await clickMenuItem("favorite");
});
- await useContextMenu(noteSelector, async () => {
- await expect(
- isPresent(Menu.new("menuitem").item("unfavorite").build())
- ).resolves.toBeTruthy();
- });
+ await useContextMenu(
+ noteSelector,
+ async () => {
+ await checkMenuItemText("favorite", "Unfavorite");
+ },
+ true
+ );
await navigateTo("favorites");
@@ -265,14 +265,16 @@ test.describe("run tests independently", () => {
await page.waitForTimeout(500);
await useContextMenu(noteSelector, async () => {
- await clickMenuItem("unfavorite");
+ await clickMenuItem("favorite");
});
- await useContextMenu(noteSelector, async () => {
- await expect(
- isPresent(Menu.new("menuitem").item("favorite").build())
- ).resolves.toBeTruthy();
- });
+ await useContextMenu(
+ noteSelector,
+ async () => {
+ await checkMenuItemText("favorite", "Favorite");
+ },
+ true
+ );
});
test("favorite a note from properties", async () => {
@@ -288,11 +290,13 @@ test.describe("run tests independently", () => {
noteSelector = await checkNotePresence("notes");
- await useContextMenu(noteSelector, async () => {
- await expect(
- isPresent(Menu.new("menuitem").item("unfavorite").build())
- ).resolves.toBeTruthy();
- });
+ await useContextMenu(
+ noteSelector,
+ async () => {
+ await checkMenuItemText("favorite", "Unfavorite");
+ },
+ true
+ );
await navigateTo("notes");
});
@@ -302,7 +306,8 @@ test.describe("run tests independently", () => {
await openContextMenu(noteSelector);
- await page.click(Menu.new("menuitem").color("Red").build());
+ await page.click(Menu.new("menuitem").item("colors").build());
+ await page.click(Menu.new("menuitem").item("red").build());
await page.click(getTestId("properties"));
@@ -330,12 +335,12 @@ test.describe("run tests independently", () => {
await page.waitForTimeout(500);
- await useContextMenu(noteSelector, () => clickMenuItem("unpin"));
+ await useContextMenu(noteSelector, () => clickMenuItem("pin"));
- await useContextMenu(noteSelector, () =>
- expect(
- isPresent(Menu.new("menuitem").item("pin").build())
- ).resolves.toBeTruthy()
+ await useContextMenu(
+ noteSelector,
+ async () => await checkMenuItemText("pin", "Pin"),
+ true
);
});
@@ -369,7 +374,7 @@ test.describe("run tests independently", () => {
test("lock a note", async () => {
const noteSelector = await createNoteAndCheckPresence();
- await lockUnlockNote(noteSelector, "lock");
+ await lockUnlockNote(noteSelector);
await checkNoteLocked(noteSelector);
});
@@ -393,9 +398,7 @@ test.describe("run tests independently", () => {
await openContextMenu(noteSelector);
- await expect(
- isPresent(Menu.new("menuitem").item("lock").build())
- ).resolves.toBeTruthy();
+ await checkMenuItemText("lock", "Lock");
await closeContextMenu(noteSelector);
});
diff --git a/apps/web/__e2e__/tags.test.js b/apps/web/__e2e__/tags.test.js
index 4470df5d8..c61ba4ec2 100644
--- a/apps/web/__e2e__/tags.test.js
+++ b/apps/web/__e2e__/tags.test.js
@@ -67,7 +67,7 @@ async function createTagAndCheckPresence(title) {
async function renameTag(tagSelector, newTitle, expected) {
await useContextMenu(tagSelector.build(), async () => {
- await clickMenuItem("renametag");
+ await clickMenuItem("rename");
});
await page.fill(getTestId("item-dialog-title"), newTitle);
@@ -167,7 +167,7 @@ test("create a shortcut of a tag", async ({ page }) => {
const tagSelector = await createTagAndCheckPresence("helloworld");
await useContextMenu(tagSelector.build(), async () => {
- await clickMenuItem("createshortcut");
+ await clickMenuItem("shortcut");
});
expect(
@@ -183,7 +183,7 @@ test("delete a shortcut of a tag", async ({ page }) => {
const tagSelector = await createTagAndCheckPresence("helloworld");
await useContextMenu(tagSelector.build(), async () => {
- await clickMenuItem("createshortcut");
+ await clickMenuItem("shortcut");
});
expect(
@@ -208,7 +208,7 @@ test("delete the last note of a tag that is also a shortcut", async ({
const tagSelector = await createTagAndCheckPresence("helloworld");
await useContextMenu(tagSelector.build(), async () => {
- await clickMenuItem("createshortcut");
+ await clickMenuItem("shortcut");
});
await page.click(tagSelector.build());
diff --git a/apps/web/__e2e__/utils/actions.js b/apps/web/__e2e__/utils/actions.js
index 1bf9bb15e..f2c9509f1 100644
--- a/apps/web/__e2e__/utils/actions.js
+++ b/apps/web/__e2e__/utils/actions.js
@@ -18,15 +18,15 @@ async function openContextMenu(selector) {
}
async function closeContextMenu() {
- await page.click("body", { button: "left" });
+ await page.keyboard.press("Escape");
}
-async function useContextMenu(selector, action) {
+async function useContextMenu(selector, action, close = false) {
await openContextMenu(selector);
await action();
- await closeContextMenu();
+ if (close) await closeContextMenu();
}
async function confirmDialog() {
diff --git a/apps/web/__e2e__/utils/conditions.js b/apps/web/__e2e__/utils/conditions.js
index e60fed2ca..edfe4f09c 100644
--- a/apps/web/__e2e__/utils/conditions.js
+++ b/apps/web/__e2e__/utils/conditions.js
@@ -3,6 +3,7 @@
const { expect } = require("@playwright/test");
const { getTestId, NOTE, createNote } = require(".");
const List = require("./listitemidbuilder");
+const Menu = require("./menuitemidbuilder");
async function isPresent(selector) {
try {
@@ -59,10 +60,17 @@ async function createNoteAndCheckPresence(
return noteSelector;
}
+async function checkMenuItemText(itemId, expectedText) {
+ await expect(
+ page.textContent(Menu.new("menuitem").item(itemId).build())
+ ).resolves.toBe(expectedText);
+}
+
module.exports = {
isPresent,
isAbsent,
isToastPresent,
checkNotePresence,
createNoteAndCheckPresence,
+ checkMenuItemText,
};
diff --git a/apps/web/__e2e__/utils/index.js b/apps/web/__e2e__/utils/index.js
index 3d1e2cc52..a11fe9e8d 100644
--- a/apps/web/__e2e__/utils/index.js
+++ b/apps/web/__e2e__/utils/index.js
@@ -71,7 +71,7 @@ async function downloadFile(downloadActionSelector, encoding) {
return new Promise(async (resolve) => {
page.on("download", async (download) => {
const path = await download.path();
- resolve(fs.readFileSync(path, { encoding }).toString());
+ resolve(fs.readFileSync(path, { encoding }));
});
await page.waitForSelector(downloadActionSelector);
diff --git a/apps/web/__e2e__/utils/menuitemidbuilder.js b/apps/web/__e2e__/utils/menuitemidbuilder.js
index 3eb18a5f6..cfffdfa3e 100644
--- a/apps/web/__e2e__/utils/menuitemidbuilder.js
+++ b/apps/web/__e2e__/utils/menuitemidbuilder.js
@@ -6,6 +6,7 @@ class MenuItemIDBuilder {
}
constructor(type) {
this.type = type;
+ this.suffix = "";
}
item(itemId) {
@@ -13,18 +14,15 @@ class MenuItemIDBuilder {
return this;
}
- colorCheck(color) {
- this.itemId = "colors-" + color + "-check";
- return this;
- }
-
- color(color) {
- this.itemId = "colors-" + color;
+ checked() {
+ this.suffix = "checked";
return this;
}
build() {
- return getTestId(`${this.type}-${this.itemId}`);
+ return getTestId(
+ `${this.type}-${this.itemId}${this.suffix ? `-${this.suffix}` : ""}`
+ );
}
}
module.exports = MenuItemIDBuilder;
diff --git a/apps/web/playwright.config.js b/apps/web/playwright.config.js
index ef431785f..ef94b343f 100644
--- a/apps/web/playwright.config.js
+++ b/apps/web/playwright.config.js
@@ -42,9 +42,9 @@ module.exports = {
timeout: 30000,
workers: IS_CI ? 3 : 4,
reporter: "list",
- retries: IS_CI ? 3 : 1,
+ retries: IS_CI ? 3 : 0,
use: {
- headless: false,
+ headless: true,
acceptDownloads: true,
// Artifacts
diff --git a/apps/web/src/common/multi-select.ts b/apps/web/src/common/multi-select.ts
index cf3f05499..3eeac4655 100644
--- a/apps/web/src/common/multi-select.ts
+++ b/apps/web/src/common/multi-select.ts
@@ -1,20 +1,15 @@
-import { removeStatus, updateStatus } from "../hooks/use-status";
-import {
- showMultiDeleteConfirmation,
- showMultiPermanentDeleteConfirmation,
-} from "./dialog-controller";
-import { store as editorStore } from "../stores/editor-store";
-import { store as appStore } from "../stores/app-store";
+import { showMultiDeleteConfirmation } from "./dialog-controller";
import { store as noteStore } from "../stores/note-store";
import { store as notebookStore } from "../stores/notebook-store";
import { db } from "./db";
-import { hashNavigate } from "../navigation";
import { showToast } from "../utils/toast";
import Vault from "./vault";
import { showItemDeletedToast } from "./toasts";
import { TaskManager } from "./task-manager";
async function moveNotesToTrash(notes: any[]) {
+ console.log(notes);
+
const item = notes[0];
const isMultiselect = notes.length > 1;
if (isMultiselect) {
diff --git a/apps/web/src/components/list-item/index.js b/apps/web/src/components/list-item/index.js
index 491036a4b..fec0f597c 100644
--- a/apps/web/src/components/list-item/index.js
+++ b/apps/web/src/components/list-item/index.js
@@ -68,12 +68,12 @@ function ListItem(props) {
let title = props.item.title;
let selectedItems = selectionStore.get().selectedItems.slice();
- if (isSelected || isFocused) {
+ if (isSelected) {
title = `${selectedItems.length} items selected`;
items = items.filter((item) => item.multiSelect);
} else if (Config.get("debugMode", false)) {
items.push(...debugMenuItems(props.item.type));
- } else {
+ } else if (selectedItems.indexOf(props.item) === -1) {
selectedItems.push(props.item);
}
diff --git a/apps/web/src/components/menu/index.js b/apps/web/src/components/menu/index.js
index d26150369..062efe55a 100644
--- a/apps/web/src/components/menu/index.js
+++ b/apps/web/src/components/menu/index.js
@@ -131,7 +131,10 @@ function Menu({ items, data, title, closeMenu }) {
key={item.key}
index={index}
item={item}
- onClick={(e) => onAction(e, item)}
+ onClick={(e) => {
+ if (item.items?.length) setIsSubmenuOpen(true);
+ else onAction(e, item);
+ }}
isFocused={focusIndex === index}
onHover={() => {
setFocusIndex(index);
diff --git a/apps/web/src/components/menu/menu-item.js b/apps/web/src/components/menu/menu-item.js
index d1572dc40..76b1b5a25 100644
--- a/apps/web/src/components/menu/menu-item.js
+++ b/apps/web/src/components/menu/menu-item.js
@@ -35,7 +35,7 @@ function MenuItem({ item, isFocused, onHover, onClick }) {
}
- {isChecked && }
+ {isChecked && (
+
+ )}
{hasSubmenu && }
{modifier && (
({
- key: label,
- title: db.colors.alias(label.toLowerCase()) || label.toLowerCase(),
- icon: Icon.Circle,
- iconColor: label.toLowerCase(),
- checked: ({ note }) => {
- return note.color === label.toLowerCase();
- },
- onClick: ({ note }) => {
- const { id } = note;
- store.setColor(id, label.toLowerCase());
- },
- })),
+ items: colorsToMenuItems(),
},
{
key: "publish",
@@ -300,7 +288,7 @@ const menuItems = [
isPro: true,
},
{
- key: "unlocknote",
+ key: "lock",
title: ({ note }) => (note.locked ? "Unlock" : "Lock"),
icon: Icon.Lock,
onClick: async ({ note }) => {
@@ -349,3 +337,22 @@ const topicNoteMenuItems = [
multiSelect: true,
},
];
+
+function colorsToMenuItems() {
+ return COLORS.map((label) => {
+ label = label.toLowerCase();
+ return {
+ key: label,
+ title: db.colors.alias(label) || label,
+ icon: Icon.Circle,
+ iconColor: label,
+ checked: ({ note }) => {
+ return note.color === label;
+ },
+ onClick: ({ note }) => {
+ const { id } = note;
+ store.setColor(id, label);
+ },
+ };
+ });
+}
diff --git a/apps/web/src/components/notebook/index.js b/apps/web/src/components/notebook/index.js
index 415fbe381..01c17dfc6 100644
--- a/apps/web/src/components/notebook/index.js
+++ b/apps/web/src/components/notebook/index.js
@@ -105,12 +105,13 @@ const pin = (notebook) => {
const menuItems = [
{
+ key: "edit",
title: "Edit",
icon: Icon.NotebookEdit,
onClick: ({ notebook }) => hashNavigate(`/notebooks/${notebook.id}/edit`),
},
{
- key: "pinnotebook",
+ key: "pin",
icon: Icon.Pin,
title: ({ notebook }) => (notebook.pinned ? "Unpin" : "Pin"),
onClick: ({ notebook }) => pin(notebook),
@@ -123,6 +124,7 @@ const menuItems = [
onClick: ({ notebook }) => appStore.pinItemToMenu(notebook),
},
{
+ key: "movetotrash",
title: "Move to trash",
color: "error",
iconColor: "error",
diff --git a/apps/web/src/components/tag/index.js b/apps/web/src/components/tag/index.js
index a992095af..5ec83fd71 100644
--- a/apps/web/src/components/tag/index.js
+++ b/apps/web/src/components/tag/index.js
@@ -11,6 +11,7 @@ import { showToast } from "../../utils/toast";
const menuItems = [
{
+ key: "rename",
title: "Rename tag",
icon: Icon.Edit,
onClick: ({ tag }) => {
@@ -18,12 +19,14 @@ const menuItems = [
},
},
{
+ key: "shortcut",
title: ({ tag }) =>
db.settings.isPinned(tag.id) ? "Remove shortcut" : "Create shortcut",
icon: Icon.Shortcut,
onClick: ({ tag }) => appStore.pinItemToMenu(tag),
},
{
+ key: "delete",
color: "error",
iconColor: "error",
title: "Delete",
diff --git a/apps/web/src/components/trash-item/index.js b/apps/web/src/components/trash-item/index.js
index 64d8fabec..4f4e9c5be 100644
--- a/apps/web/src/components/trash-item/index.js
+++ b/apps/web/src/components/trash-item/index.js
@@ -34,6 +34,7 @@ export default TrashItem;
const menuItems = [
{
+ key: "restore",
title: "Restore",
icon: Icon.Restore,
onClick: ({ items }) => {
@@ -43,6 +44,7 @@ const menuItems = [
multiSelect: true,
},
{
+ key: "delete",
title: "Delete",
icon: Icon.DeleteForver,
color: "error",