A chapter with an order of its own
A chapter orders its own notes now, chosen in the same dialog as its name, mark and colour -- one decision about one thing. The notebook's setting governs the notebook's own list; a chapter is a place someone made and filled, so a reference chapter can stay alphabetical while the notebook around it stays as it was arranged. It starts hand-made, and a chapter put in one of the other orders loses its lines and its handles exactly as the notebook does: neither means anything against an order the app is choosing. A line still would not go under another line, and the comparator was only half of it. The write was then thrown away by the "did anything change?" test, which sorted the separators before comparing them -- to ignore the other chapters' lines riding along in the same field -- and a sorted comparison cannot see a sequence. Two lines in one gap carry the same position, so swapping them changes nothing else. It compares this list's own lines, in order. An open chapter swelled while rearranging: the strips at the two ends of its list stood open for the whole of the mode, adding half a finger of empty space inside every chapter. They are thin now, and only there while something is actually being carried -- which is also why the suite presses first and aims afterwards. Server: sort_mode on chapters, validated against the same five modes as a notebook's, and set outside the COALESCE so null means the hand-made order rather than "leave it alone". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ea1bca5d25
commit
3b7958b713
11 files changed
+354
-94
No files matched your search
+80
-24
@@ -282,25 +282,14 @@ try {
|
||||
/* `where` picks the part of the row held over: a chapter is a place in the
|
||||
body of its header and a neighbour along the bottom edge of it, which is
|
||||
how a row can still be put in the gap under a chapter. */
|
||||
const holdOver = (dragId, chapterId, where = "body", scope = "") =>
|
||||
p.evaluate(
|
||||
([from, to, part, sc]) => {
|
||||
/* Both ends looked up in one list. The two ends of a list have strips
|
||||
with the same names in every list on screen, so a bare id would find
|
||||
the notebook's when the chapter's was meant. */
|
||||
const find = (id) =>
|
||||
document.querySelector(`[data-reorder-id="${id}"][data-reorder-scope="${sc}"]`);
|
||||
const row = find(from);
|
||||
const grip = row?.querySelector("[data-reorder-grip]");
|
||||
const target = find(to);
|
||||
if (!grip || !target) return `missing ${!!grip} ${!!target}`;
|
||||
const head = target.querySelector("[data-reorder-head]") || target;
|
||||
const box = head.getBoundingClientRect();
|
||||
window.__drop = {
|
||||
x: box.x + box.width / 2,
|
||||
y: part === "edge" ? box.bottom - 2 : box.y + box.height / 2,
|
||||
};
|
||||
const fire = (el, type, x, y) =>
|
||||
/* Pressed first, aimed after.
|
||||
Inside a chapter the strips at the two ends of the list only exist while
|
||||
something is being carried — they are targets, not furniture — so the drop
|
||||
point cannot be looked up until the row is in the hand. */
|
||||
const holdOver = async (dragId, chapterId, where = "body", scope = "") => {
|
||||
const pressed = await p.evaluate(
|
||||
([from, sc]) => {
|
||||
window.__fire = (el, type, x, y) =>
|
||||
el.dispatchEvent(
|
||||
new PointerEvent(type, {
|
||||
pointerType: "touch",
|
||||
@@ -311,14 +300,37 @@ try {
|
||||
cancelable: true,
|
||||
})
|
||||
);
|
||||
/* Looked up in one list: the two ends have the same names in every list
|
||||
on screen, so a bare id would find the notebook's when the chapter's
|
||||
was meant. */
|
||||
const row = document.querySelector(`[data-reorder-id="${from}"][data-reorder-scope="${sc}"]`);
|
||||
const grip = row?.querySelector("[data-reorder-grip]");
|
||||
if (!grip) return "missing grip";
|
||||
const s = grip.getBoundingClientRect();
|
||||
fire(grip, "pointerdown", s.x + s.width / 2, s.y + s.height / 2);
|
||||
fire(window, "pointermove", window.__drop.x, window.__drop.y - 6);
|
||||
fire(window, "pointermove", window.__drop.x, window.__drop.y);
|
||||
window.__fire(grip, "pointerdown", s.x + s.width / 2, s.y + s.height / 2);
|
||||
return "pressed";
|
||||
},
|
||||
[dragId, scope]
|
||||
);
|
||||
if (pressed !== "pressed") return pressed;
|
||||
await p.waitForTimeout(300);
|
||||
return p.evaluate(
|
||||
([to, part, sc]) => {
|
||||
const target = document.querySelector(`[data-reorder-id="${to}"][data-reorder-scope="${sc}"]`);
|
||||
if (!target) return "missing target";
|
||||
const head = target.querySelector("[data-reorder-head]") || target;
|
||||
const box = head.getBoundingClientRect();
|
||||
window.__drop = {
|
||||
x: box.x + box.width / 2,
|
||||
y: part === "edge" ? box.bottom - 2 : box.y + box.height / 2,
|
||||
};
|
||||
window.__fire(window, "pointermove", window.__drop.x, window.__drop.y - 6);
|
||||
window.__fire(window, "pointermove", window.__drop.x, window.__drop.y);
|
||||
return "held";
|
||||
},
|
||||
[dragId, chapterId, where, scope]
|
||||
[chapterId, where, scope]
|
||||
);
|
||||
};
|
||||
|
||||
const letGo = () =>
|
||||
p.evaluate(() => {
|
||||
@@ -490,6 +502,7 @@ try {
|
||||
);
|
||||
|
||||
const beforeInside = await chapterOrder();
|
||||
let afterInside = [];
|
||||
if (beforeInside.length >= 2) {
|
||||
const rows = await inChapterRows();
|
||||
const last = rows.filter((id) => id !== "__start__" && id !== "__end__").pop();
|
||||
@@ -497,7 +510,7 @@ try {
|
||||
await p.waitForTimeout(400);
|
||||
await letGo();
|
||||
await p.waitForTimeout(2500);
|
||||
const afterInside = await chapterOrder();
|
||||
afterInside = await chapterOrder();
|
||||
check(
|
||||
afterInside[0] === beforeInside[beforeInside.length - 1] && afterInside.length === beforeInside.length,
|
||||
"and can be dragged among themselves, the strip at the top included",
|
||||
@@ -519,6 +532,49 @@ try {
|
||||
check(false, "a chapter with two notes to arrange", beforeInside.join(","));
|
||||
}
|
||||
|
||||
/* ── a chapter orders its own notes ──
|
||||
* The notebook's setting governs the notebook's own list. A chapter is a place
|
||||
* someone made and filled, so it carries its own — set in the same dialog as
|
||||
* its name, mark and colour, because they are one decision about one thing. */
|
||||
await openRow.click({ button: "right", force: true });
|
||||
await p.waitForTimeout(600);
|
||||
await p.getByText("Edit", { exact: true }).click();
|
||||
await p.waitForTimeout(700);
|
||||
check((await p.locator("[data-chapter-sort]").count()) === 1, "its dialog offers an order of its own");
|
||||
await p.locator("[data-chapter-sort]").selectOption("title");
|
||||
await p.locator("[data-chapter-save]").click();
|
||||
await p.waitForTimeout(2500);
|
||||
const stored = (await api(p, "GET", "/chapters")).body.find((c) => c.id === busiest);
|
||||
check(stored?.sort_mode === "title", "and keeps it, on the server", JSON.stringify(stored?.sort_mode));
|
||||
|
||||
const alphabetical = await chapterOrder();
|
||||
check(
|
||||
alphabetical.join(",") === [...alphabetical].sort((a, b) => a.localeCompare(b, "de")).join(","),
|
||||
"the chapter's notes come out in that order",
|
||||
alphabetical.join(",")
|
||||
);
|
||||
/* And the things that only mean something in a hand-made order go with it: a
|
||||
line the notes would slide past on every edit, and handles for an order the
|
||||
app is choosing. */
|
||||
check(
|
||||
(await inChapterRows()).length === 0,
|
||||
"with no handles in it, since that order is not dragged into place",
|
||||
JSON.stringify(await inChapterRows())
|
||||
);
|
||||
|
||||
await openRow.click({ button: "right", force: true });
|
||||
await p.waitForTimeout(600);
|
||||
await p.getByText("Edit", { exact: true }).click();
|
||||
await p.waitForTimeout(700);
|
||||
await p.locator("[data-chapter-sort]").selectOption("manual");
|
||||
await p.locator("[data-chapter-save]").click();
|
||||
await p.waitForTimeout(2500);
|
||||
check(
|
||||
(await chapterOrder()).join(",") === afterInside.join(","),
|
||||
"and the hand-made one is still there when it comes back",
|
||||
`${afterInside.join(",")} vs ${(await chapterOrder()).join(",")}`
|
||||
);
|
||||
|
||||
/* ── a dialog does not close because a selection ended outside it ──
|
||||
* Select a word in the field, drag a little past the edge, let go: the click
|
||||
* lands on the backdrop — the nearest ancestor of press and release — and the
|
||||
|
||||
Reference in new issue
Block a user