Link the margin notes from the notes, and let them be hidden
The linking went the wrong way round. A remark in the margin is short by design; when there is more to say about that passage it belongs in the notes beside the document — and it has to be possible to say *which* passage. So the pins are offered to the editor's `@` menu exactly like notes and notebooks are, labelled with their page, and following one scrolls the document to the spot and opens the remark. Pins are unhidden first: a link leading to something invisible would read as broken. A pin link is the same node with kind "pin", and is deliberately kept out of the links reported to the server. Its target is a marker inside this note's own overlay, so no note has that id, and reporting it would fill the backlink table with rows matching nothing. They can also be hidden altogether. Reading a document as it was printed is a normal thing to want; the toolbar takes the markers and the column away together and the page takes the width back. The other direction — making a note out of a remark — is removed, along with its picker and the pin's noteId: 357 lines fewer than before. The unmount flush it turned up stays, since a drawing save was genuinely being dropped by navigating away inside its debounce. 218 unit tests, 231 browser checks across eleven suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
502aecfc92
commit
4c1b632029
13 files changed
+239
-484
No files matched your search
+63
-43
@@ -633,71 +633,91 @@ try {
|
||||
|
||||
|
||||
|
||||
/* ── a remark that turns into a note ── */
|
||||
/* ── linking a margin note from the notes beside the document ── */
|
||||
// A remark in the margin is short by design. When there is more to say about
|
||||
// that passage it belongs in the notes — and it has to be possible to say
|
||||
// *which* passage.
|
||||
await p.locator("[data-pdf-pin-mode]").click();
|
||||
await p.waitForTimeout(400);
|
||||
const pageL = await p.locator('[data-pdf-page="1"]').boundingBox();
|
||||
await p.mouse.click(pageL.x + pageL.width * 0.35, pageL.y + pageL.height * 0.2);
|
||||
await p.waitForTimeout(800);
|
||||
await p.getByLabel("Pinned note").fill("Zebrastreifen nachschlagen");
|
||||
await p.waitForTimeout(400);
|
||||
await p.getByRole("button", { name: "Fertig" }).click();
|
||||
await p.waitForTimeout(2500);
|
||||
|
||||
const notesBefore = (await api(p, "GET", "/notes")).body.length;
|
||||
await p.locator("[data-pin-make-note]").click();
|
||||
await p.waitForTimeout(4000);
|
||||
await p.locator("[data-pdf-toggle-notes]").click();
|
||||
await p.waitForTimeout(1200);
|
||||
const panel = p.locator("[data-pdf-notes] .ProseMirror");
|
||||
await panel.click();
|
||||
await p.keyboard.type("Dazu ausführlicher: ");
|
||||
await p.keyboard.type("@");
|
||||
await p.waitForTimeout(1200);
|
||||
|
||||
const notesAfter = (await api(p, "GET", "/notes")).body;
|
||||
check(notesAfter.length === notesBefore + 1, "a remark can become a note of its own", `${notesBefore} → ${notesAfter.length}`);
|
||||
|
||||
const made = notesAfter.find((n) => n.title === "Zebrastreifen nachschlagen");
|
||||
check(!!made, "titled from what was written in the margin");
|
||||
const suggestions = await p.locator("body").innerText();
|
||||
check(
|
||||
(await p.locator(".ProseMirror").count()) > 0,
|
||||
"and the app goes straight there rather than leaving it to be found"
|
||||
/Zebrastreifen nachschlagen/.test(suggestions),
|
||||
"the margin notes are offered when linking from the notes",
|
||||
suggestions.slice(0, 160).replace(/\n/g, " ")
|
||||
);
|
||||
|
||||
/* The link has to read in both directions, or a note born from a passage has
|
||||
nothing on it saying which passage. */
|
||||
const body = (await api(p, "GET", `/notes/${made.id}`)).body.content;
|
||||
check(body.includes(pdfNoteId), "the new note carries a link back to the document", body.slice(0, 120));
|
||||
|
||||
const back = await api(p, "GET", `/notes/${pdfNoteId}/backlinks`);
|
||||
await p.keyboard.press("Enter");
|
||||
await p.waitForTimeout(2500);
|
||||
const linkEl = panel.locator("[data-note-link]").first();
|
||||
check((await linkEl.count()) === 1, "one can be inserted as a link");
|
||||
check(
|
||||
Array.isArray(back.body) && back.body.some((b) => b.id === made.id),
|
||||
"so the document lists it among its backlinks",
|
||||
JSON.stringify(back.body).slice(0, 120)
|
||||
(await linkEl.getAttribute("data-kind")) === "pin",
|
||||
"recorded as a link to a spot, not to a note",
|
||||
await linkEl.getAttribute("data-kind")
|
||||
);
|
||||
check(
|
||||
(await linkEl.innerText()).includes("S. 1"),
|
||||
"labelled with the page it points at",
|
||||
await linkEl.innerText()
|
||||
);
|
||||
|
||||
/* Back to the document: the pin now leads to the note. */
|
||||
await p.locator("[data-note-row]").filter({ hasText: "e2e-test" }).first().click();
|
||||
await p.waitForTimeout(4000);
|
||||
const goto = p.locator(`[data-pin-goto="${made.id}"]`);
|
||||
check((await goto.count()) === 1, "and the margin card links to it");
|
||||
/* The link must not reach the backlink table: its target is a marker in this
|
||||
note's own overlay, and no note has that id. */
|
||||
const backAfter = await api(p, "GET", `/notes/${pdfNoteId}/backlinks`);
|
||||
check(
|
||||
(await goto.innerText()).includes("Zebrastreifen"),
|
||||
"showing the note's current title",
|
||||
await goto.innerText()
|
||||
Array.isArray(backAfter.body) && backAfter.body.length === 0,
|
||||
"and is not reported as a link to a note",
|
||||
JSON.stringify(backAfter.body).slice(0, 100)
|
||||
);
|
||||
|
||||
await goto.click();
|
||||
/* Following it goes to the passage. */
|
||||
await panel.locator("[data-note-link]").first().click();
|
||||
await p.waitForTimeout(2500);
|
||||
check(
|
||||
(await p.getByLabel("Note title").inputValue()) === "Zebrastreifen nachschlagen",
|
||||
"clicking it goes to the note",
|
||||
await p.getByLabel("Note title").inputValue()
|
||||
(await p.getByLabel("Pinned note").inputValue()) === "Zebrastreifen nachschlagen",
|
||||
"following the link opens the margin note it points at",
|
||||
await p.getByLabel("Pinned note").inputValue()
|
||||
);
|
||||
|
||||
/* Back once more, and let go of the link — the note must survive that. */
|
||||
await p.locator("[data-note-row]").filter({ hasText: "e2e-test" }).first().click();
|
||||
await p.waitForTimeout(4000);
|
||||
await p.locator(`[data-pin-goto="${made.id}"]`).locator("xpath=ancestor::*[@data-pin-card][1]").click();
|
||||
await p.waitForTimeout(700);
|
||||
await p.locator("[data-pin-unlink]").click();
|
||||
await p.waitForTimeout(2500);
|
||||
check((await p.locator(`[data-pin-goto="${made.id}"]`).count()) === 0, "the link can be let go of");
|
||||
// With the notes panel open there is no room for a margin column beside the
|
||||
// page, so the remark opens in the sheet instead. Closing the panel gives the
|
||||
// column back — and the same remark is still the open one.
|
||||
await p.locator("[data-pdf-toggle-notes]").click();
|
||||
await p.waitForTimeout(1500);
|
||||
const openCard = p.locator("[data-pin-card]").filter({ has: p.getByLabel("Pinned note") });
|
||||
check((await openCard.count()) === 1, "and it is the card that is open in the margin");
|
||||
await p.keyboard.press("Escape");
|
||||
await p.waitForTimeout(600);
|
||||
|
||||
/* ── showing and hiding them ── */
|
||||
check((await p.locator("[data-pdf-show-pins]").count()) === 1, "the margin notes can be hidden");
|
||||
await p.locator("[data-pdf-show-pins]").click();
|
||||
await p.waitForTimeout(1200);
|
||||
check((await p.locator("[data-pin]").count()) === 0, "hiding takes the markers off the page");
|
||||
check((await p.locator("[data-pdf-margin]").count()) === 0, "and the column with them");
|
||||
|
||||
const pageWide = (await p.locator('[data-pdf-page="2"]').boundingBox()).width;
|
||||
await p.locator("[data-pdf-show-pins]").click();
|
||||
await p.waitForTimeout(1200);
|
||||
check((await p.locator("[data-pin]").count()) > 0, "showing brings them back");
|
||||
check(
|
||||
(await api(p, "GET", "/notes")).body.some((n) => n.id === made.id),
|
||||
"and the note itself is still there"
|
||||
(await p.locator('[data-pdf-page="2"]').boundingBox()).width < pageWide,
|
||||
"and the page gives the column its width again"
|
||||
);
|
||||
|
||||
/* ── on a phone there is no margin to write in ── */
|
||||
|
||||
Reference in new issue
Block a user