A chapter is a list too, and deleting stops moving things
Deleting a note moved everything else. A row leaving is the same problem as a row arriving, in reverse: a line's position counts the rows above it, so a note deleted above a line left the line counting rows that were no longer there, and everything under it appeared to jump up past it. withoutRow renumbers the list a row leaves, and every place one leaves says so -- the bin, a move, a chapter being removed. Arrivals land at the top: an old position belongs to an arrangement that has moved on. A line could not be dragged under another line. Two lines in the same gap carry the same position, and the comparator claimed "a separator comes first" without asking what the other one was -- both directions at once for that case. A comparator that contradicts itself has no defined result, so the sort returned them in whichever order it happened to walk. A chapter is always in the user's own order and can be arranged like any other list: its notes wear handles under the same switch as the list beside them and are dragged among themselves, strips at both ends included. The two lists keep apart by scope -- a chapter unfolded under its row holds rows inside a row of the notebook's list, so the nearest row above the pointer cannot say which list a drag belongs to. The sort menu is gone inside a chapter: the mode belongs to the notebook, and a menu there would offer a choice that cannot be honoured. The chapters suite no longer assumes the first chapter row is the one that matters. A chapter is a row in the arrangement and can be anywhere in it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
312c4e190e
commit
f01522aa6f
10 files changed
+528
-85
No files matched your search
@@ -11,6 +11,7 @@ import {
|
||||
sortModesFor,
|
||||
sortNotes,
|
||||
withNewSeparator,
|
||||
withoutRow,
|
||||
} from "./noteSort";
|
||||
import type { NoteMeta } from "../../types";
|
||||
|
||||
@@ -277,3 +278,71 @@ describe("placeOnTop", () => {
|
||||
expect(placeOnTop(notes, separators, chapters, "ghost")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("two lines in the same gap", () => {
|
||||
/* A separator's position counts the rows above it, so two lines with nothing
|
||||
between them carry the same number. Which of them comes first is then only
|
||||
the order they are written in — and the comparator used to claim *both*
|
||||
directions for that case, so a line dragged under another sprang back. */
|
||||
const notes = [note("a", { sortOrder: 0 }), note("b", { sortOrder: 1 })];
|
||||
const kinds = (list: ReturnType<typeof mergeEntries>) => list.map((e) => `${e.kind[0]}:${e.id}`);
|
||||
|
||||
it("keeps the order they were written in", () => {
|
||||
const two = [
|
||||
{ id: "s1", label: "Erst", order: 1 },
|
||||
{ id: "s2", label: "Dann", order: 1 },
|
||||
];
|
||||
expect(kinds(mergeEntries(notes, two))).toEqual(["n:a", "s:s1", "s:s2", "n:b"]);
|
||||
expect(kinds(mergeEntries(notes, [two[1]!, two[0]!]))).toEqual(["n:a", "s:s2", "s:s1", "n:b"]);
|
||||
});
|
||||
|
||||
it("can be swapped by dragging one under the other", () => {
|
||||
const two = [
|
||||
{ id: "s1", label: "Erst", order: 1 },
|
||||
{ id: "s2", label: "Dann", order: 1 },
|
||||
];
|
||||
const next = reindexEntries(notes, two, "s1", "s2")!;
|
||||
expect(next.separators.map((s) => s.id)).toEqual(["s2", "s1"]);
|
||||
expect(kinds(mergeEntries(notes, next.separators))).toEqual(["n:a", "s:s2", "s:s1", "n:b"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("withoutRow", () => {
|
||||
/* a │ b │ ─── │ c: deleting a note above the line used to leave the line
|
||||
counting rows that were no longer there, so everything under it appeared to
|
||||
jump up past it. */
|
||||
const notes = [note("a", { sortOrder: 0 }), note("b", { sortOrder: 1 }), note("c", { sortOrder: 2 })];
|
||||
const separators = [{ id: "s1", label: "Teil 2", order: 2 }];
|
||||
const kinds = (list: ReturnType<typeof mergeEntries>) => list.map((e) => `${e.kind[0]}:${e.id}`);
|
||||
|
||||
it("closes the gap and takes the lines down with it", () => {
|
||||
const next = withoutRow(notes, separators, [], "a")!;
|
||||
expect(next.noteIds).toEqual(["b", "c"]);
|
||||
expect(next.separators[0]!.order).toBe(1);
|
||||
const left = [note("b", { sortOrder: 0 }), note("c", { sortOrder: 1 })];
|
||||
expect(kinds(mergeEntries(left, next.separators))).toEqual(["n:b", "s:s1", "n:c"]);
|
||||
});
|
||||
|
||||
it("leaves a line that was already above the gap where it was", () => {
|
||||
const next = withoutRow(notes, [{ id: "s1", label: "", order: 1 }], [], "c")!;
|
||||
expect(next.separators[0]!.order).toBe(1);
|
||||
});
|
||||
|
||||
it("reports nothing to do for a row that is not in the list", () => {
|
||||
expect(withoutRow(notes, separators, [], "ghost")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("placeOnTop with several notes", () => {
|
||||
/* Removing a chapter frees its notes into the notebook. They arrive together,
|
||||
at the top, in the order they had inside it. */
|
||||
const notes = [note("x", { sortOrder: 0 }), note("y", { sortOrder: 1 })];
|
||||
const freed = [note("p"), note("q")];
|
||||
const separators = [{ id: "s1", label: "", order: 1 }];
|
||||
|
||||
it("puts them all in front, in the order given", () => {
|
||||
const next = placeOnTop([...freed, ...notes], separators, [], ["p", "q"])!;
|
||||
expect(next.noteIds).toEqual(["p", "q", "x", "y"]);
|
||||
expect(next.separators[0]!.order).toBe(3);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user