Put the interface into English, and give the notebook one way in
The interface had drifted into two languages — the frame in English, everything added later in German — which is worse than either, since the same idea appeared under two names depending on when that part was written. Sorting by title still uses the German collation, so Übung 2 comes before Übung 10: that is about the notes, not the chrome. Two archive folders were renamed with it (bilder/ → images/, zeichnungen/ → drawings/). The importer reads both spellings, and both the old and new marker line for a drawing — archives exported before this are on disk, and an import that quietly dropped their drawings would be the worst kind of tidy-up. Making a note, uploading a PDF and importing Markdown were three buttons side by side, two of them icons you had to hover to identify. They are the same act — putting something new in this notebook — so they are now one + menu. The search box moves out of the sidebar and into the note list, labelled with the notebook it narrows. Nothing changed about what it does: it has always searched only the notebook you are in, but from the sidebar it read as searching everything. A separator no longer carries a sentence explaining the order the sort menu already names. Renaming joins removing on the right-click menu — a left-click on the label started an edit, so the row could not be touched without falling into one. 218 unit tests, 238 browser checks across eleven suites. Two of those caught real breakage from this change rather than needing a translation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0a7cf26b28
commit
52bd2b8f3d
27 files changed
+469
-336
No files matched your search
+26
-18
@@ -12,7 +12,9 @@ import {
|
||||
createNote,
|
||||
createNotebook,
|
||||
launch,
|
||||
openAddMenu,
|
||||
report,
|
||||
search,
|
||||
setTitle,
|
||||
typeInEditor,
|
||||
withFreshUser,
|
||||
@@ -106,13 +108,11 @@ try {
|
||||
);
|
||||
|
||||
/* The point of the whole feature. */
|
||||
await p.getByPlaceholder("Search notes...").fill("Beta");
|
||||
await p.waitForTimeout(1200);
|
||||
await search(p, "Beta");
|
||||
const searched = await titles();
|
||||
check(searched.length < 3, "searching narrows the list", searched.join(", "));
|
||||
|
||||
await p.getByPlaceholder("Search notes...").fill("");
|
||||
await p.waitForTimeout(1500);
|
||||
await search(p, "");
|
||||
check(
|
||||
(await titles()).join(",") === arranged.join(","),
|
||||
"and clearing the search shows exactly the arrangement again",
|
||||
@@ -150,15 +150,21 @@ try {
|
||||
await p.waitForTimeout(1800);
|
||||
check((await p.locator("[data-separator]").count()) === 1, "a separator can be added");
|
||||
|
||||
/* Renaming is right-click, like removing. A left-click on the label used to
|
||||
start an edit, so the row could not be touched without falling into one. */
|
||||
const label = p.getByLabel("Separator label");
|
||||
await p.locator("[data-separator] button").first().click();
|
||||
await p.waitForTimeout(300);
|
||||
check((await label.count()) === 0, "clicking a separator does not drop straight into renaming");
|
||||
|
||||
await p.locator("[data-separator]").click({ button: "right" });
|
||||
await p.waitForTimeout(600);
|
||||
await p.getByText("Rename", { exact: true }).click();
|
||||
await p.waitForTimeout(500);
|
||||
await label.fill("Wichtig");
|
||||
await label.press("Enter");
|
||||
await p.waitForTimeout(1800);
|
||||
check(
|
||||
/wichtig/i.test(await p.locator("[data-separator]").innerText()),
|
||||
"and named",
|
||||
"and it can be named from the same menu",
|
||||
await p.locator("[data-separator]").innerText()
|
||||
);
|
||||
|
||||
@@ -197,14 +203,14 @@ try {
|
||||
|
||||
await p.locator("[data-separator]").click({ button: "right" });
|
||||
await p.waitForTimeout(600);
|
||||
await p.getByText("Trenner entfernen").click();
|
||||
await p.getByText("Remove separator").click();
|
||||
await p.waitForTimeout(600);
|
||||
check(
|
||||
/Notizen bleiben/.test(await p.locator("body").innerText()),
|
||||
/notes stay/i.test(await p.locator("body").innerText()),
|
||||
"right-clicking it asks first, and says the notes are not affected"
|
||||
);
|
||||
|
||||
await p.getByRole("button", { name: "Entfernen" }).click();
|
||||
await p.getByRole("button", { name: "Remove" }).click();
|
||||
await p.waitForTimeout(2000);
|
||||
check((await p.locator("[data-separator]").count()) === 0, "and then it is gone");
|
||||
check((await titles()).length === 3, "with every note still there", (await titles()).join(", "));
|
||||
@@ -251,7 +257,7 @@ try {
|
||||
check(markdown.includes("- [x] erledigt"), "the export carries the ticked box", JSON.stringify(markdown.slice(-60)));
|
||||
check(markdown.startsWith("# Beta"), "and the title as a heading");
|
||||
|
||||
await p.locator("[data-export-dialog] button").filter({ hasText: /Schließen|Fertig|Close/ }).first().click().catch(() => {});
|
||||
await p.locator("[data-export-dialog] button").filter({ hasText: /Close|Done/ }).first().click().catch(() => {});
|
||||
await p.keyboard.press("Escape");
|
||||
await p.waitForTimeout(600);
|
||||
|
||||
@@ -270,14 +276,15 @@ try {
|
||||
}, markdown);
|
||||
|
||||
const chooser = p.waitForEvent("filechooser");
|
||||
await p.locator("[data-import-button]").click();
|
||||
await openAddMenu(p);
|
||||
await p.locator('[data-add-option="Import Markdown"]').click();
|
||||
const fc = await chooser;
|
||||
await fc.setFiles({ name: "Beta.md", mimeType: "text/markdown", buffer: Buffer.from(markdown) });
|
||||
await p.waitForTimeout(4000);
|
||||
|
||||
const importedText = await p.locator("body").innerText();
|
||||
check(/importiert/i.test(importedText), "the import reports what it did", importedText.slice(0, 200).replace(/\n/g, " "));
|
||||
await p.getByRole("button", { name: /Fertig/ }).click();
|
||||
check(/imported/i.test(importedText), "the import reports what it did", importedText.slice(0, 200).replace(/\n/g, " "));
|
||||
await p.getByRole("button", { name: /Done/ }).click();
|
||||
await p.waitForTimeout(2500);
|
||||
|
||||
const after = (await api(p, "GET", "/notes")).body.length;
|
||||
@@ -307,7 +314,7 @@ try {
|
||||
check(await field.isVisible(), "the link dialog is a real dialog in the page");
|
||||
|
||||
await field.fill("javascript:alert(1)");
|
||||
await p.getByRole("button", { name: /Einfügen/ }).click();
|
||||
await p.getByRole("button", { name: /^Insert$/ }).click();
|
||||
await p.waitForTimeout(400);
|
||||
check(
|
||||
await field.isVisible(),
|
||||
@@ -315,7 +322,7 @@ try {
|
||||
);
|
||||
|
||||
await field.fill("example.com");
|
||||
await p.getByRole("button", { name: /Einfügen/ }).click();
|
||||
await p.getByRole("button", { name: /^Insert$/ }).click();
|
||||
await p.waitForTimeout(1800);
|
||||
const href = await p.locator(".ProseMirror a[href]").first().getAttribute("href");
|
||||
check(href === "https://example.com", "a bare domain is taken as https", String(href));
|
||||
@@ -341,7 +348,8 @@ try {
|
||||
await p.keyboard.press("Escape");
|
||||
await p.locator("h2").first().click();
|
||||
await p.waitForTimeout(500);
|
||||
await p.getByTitle("New Note").click();
|
||||
await openAddMenu(p);
|
||||
await p.locator('[data-add-option="New note"]').click();
|
||||
await p.waitForTimeout(900);
|
||||
|
||||
const books0 = (await api(p, "GET", "/notebooks")).body;
|
||||
@@ -352,7 +360,7 @@ try {
|
||||
// sort order, so "the last one" is not a stable way to mean a particular book.
|
||||
const target = books0.find((b) => b.name === "Zweites Buch");
|
||||
await p.locator(`[data-notebook-option="${target.id}"]`).click();
|
||||
await p.getByRole("button", { name: /Weiter/ }).click();
|
||||
await p.getByRole("button", { name: /Continue/ }).click();
|
||||
await p.waitForTimeout(2500);
|
||||
|
||||
// By title, not by position: the list is ordered by a timestamp with one
|
||||
|
||||
Reference in new issue
Block a user