Fix a permanent black line in the note list, and add separators
The drop indicator was a `borderTop` set in the same style object as a `border` shorthand. Clearing the longhand resets its width and colour to the defaults while the shorthand's `solid` stays, so a row kept a 2px black line for good — measured at rest, before any drag. It is now its own element, which also stops the list twitching as a row grows and shrinks by a pixel. That element then turned out to be a dead strip of its own: it pushes the row down as it appears, so the pointer ended up over the line rather than over the row and releasing there dropped on nothing. The drag handlers moved to the whole row and the indicator no longer takes pointer events. A strip below the last row means "put it at the end", which otherwise needs a drop onto the final note. Separators are lines drawn between notes, optionally named, stored on the notebook beside its sort mode. They drag like notes do, and their position is "how many notes come before me", so they hold their place as the notes around them are renamed or edited. Shown only in the manual order — in "newest first" the notes would slide past the line on every edit. "All Notes" and "Starred" no longer offer a hand-made order: they are questions asked of every notebook, so there is nothing for an arrangement to belong to. 199 unit tests, 190 browser checks across eleven suites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0a0864c58b
commit
2cee00e38e
10 files changed
+731
-125
No files matched your search
@@ -1,6 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { canReorder, reindex, sortNotes } from "./noteSort";
|
||||
import {
|
||||
END_ID,
|
||||
canReorder,
|
||||
mergeEntries,
|
||||
reindex,
|
||||
reindexEntries,
|
||||
sortModesFor,
|
||||
sortNotes,
|
||||
withNewSeparator,
|
||||
} from "./noteSort";
|
||||
import type { NoteMeta } from "../../types";
|
||||
|
||||
const note = (id: string, over: Partial<NoteMeta> = {}): NoteMeta => ({
|
||||
@@ -129,3 +138,89 @@ describe("canReorder", () => {
|
||||
expect(canReorder("manual", true)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sortModesFor", () => {
|
||||
it("offers a hand-made order inside a notebook", () => {
|
||||
expect(sortModesFor(true)).toContain("manual");
|
||||
});
|
||||
|
||||
it("does not offer one in a view, which has nowhere to keep it", () => {
|
||||
// "All Notes" is a question asked of every notebook, not a place — there is
|
||||
// nothing for an arrangement to belong to.
|
||||
expect(sortModesFor(false)).not.toContain("manual");
|
||||
expect(sortModesFor(false)).toContain("updated");
|
||||
});
|
||||
});
|
||||
|
||||
describe("separators", () => {
|
||||
const notes = [note("a", { sortOrder: 0 }), note("b", { sortOrder: 1 }), note("c", { sortOrder: 2 })];
|
||||
const kinds = (list: ReturnType<typeof mergeEntries>) => list.map((e) => `${e.kind[0]}:${e.id}`);
|
||||
|
||||
it("sits between the notes it was placed between", () => {
|
||||
const merged = mergeEntries(notes, [{ id: "s1", label: "Später", order: 2 }]);
|
||||
expect(kinds(merged)).toEqual(["n:a", "n:b", "s:s1", "n:c"]);
|
||||
});
|
||||
|
||||
it("comes before the note it shares a position with, so it reads as a header", () => {
|
||||
const merged = mergeEntries(notes, [{ id: "s1", label: "", order: 0 }]);
|
||||
expect(kinds(merged)[0]).toBe("s:s1");
|
||||
});
|
||||
|
||||
it("still appears when the notebook has shrunk beneath it", () => {
|
||||
// Its label is the user's work; dropping it silently because two notes were
|
||||
// deleted would be worse than showing it at the end.
|
||||
const merged = mergeEntries(notes, [{ id: "s1", label: "Alt", order: 99 }]);
|
||||
expect(kinds(merged)[merged.length - 1]).toBe("s:s1");
|
||||
});
|
||||
|
||||
it("is not pushed behind notes that have never been dragged", () => {
|
||||
// Untouched notes all carry position 0. Merging on the stored numbers rather
|
||||
// than on rank would pile them all at 0 and put every separator last.
|
||||
const fresh = [note("x"), note("y"), note("z")];
|
||||
const merged = mergeEntries(fresh, [{ id: "s1", label: "", order: 1 }]);
|
||||
expect(kinds(merged)[1]).toBe("s:s1");
|
||||
});
|
||||
|
||||
it("keeps its place as the notes around it are edited", () => {
|
||||
const separators = [{ id: "s1", label: "Archiv", order: 2 }];
|
||||
const touched = notes.map((n) => (n.id === "a" ? { ...n, updatedAt: "2030-01-01T00:00:00Z" } : n));
|
||||
expect(kinds(mergeEntries(touched, separators))).toEqual(["n:a", "n:b", "s:s1", "n:c"]);
|
||||
});
|
||||
|
||||
it("is added at the top, where it can be seen and then moved", () => {
|
||||
const added = withNewSeparator([{ id: "old", label: "", order: 3 }], "new");
|
||||
expect(added[0]!.id).toBe("new");
|
||||
expect(added[0]!.order).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reindexEntries", () => {
|
||||
const notes = [note("a", { sortOrder: 0 }), note("b", { sortOrder: 1 }), note("c", { sortOrder: 2 })];
|
||||
const separators = [{ id: "s1", label: "Teil 2", order: 1 }];
|
||||
|
||||
it("moves a note across a separator and keeps the separator where it was", () => {
|
||||
const next = reindexEntries(notes, separators, "c", "a")!;
|
||||
expect(next.noteIds).toEqual(["c", "a", "b"]);
|
||||
expect(next.separators[0]!.order).toBe(2);
|
||||
});
|
||||
|
||||
it("moves a separator itself", () => {
|
||||
const next = reindexEntries(notes, separators, "s1", "a")!;
|
||||
expect(next.separators[0]!.order).toBe(0);
|
||||
expect(next.noteIds).toEqual(["a", "b", "c"]);
|
||||
});
|
||||
|
||||
it("sends a row to the end when dropped below the last one", () => {
|
||||
expect(reindexEntries(notes, [], "a", END_ID)!.noteIds).toEqual(["b", "c", "a"]);
|
||||
});
|
||||
|
||||
it("reports nothing to do for a row that is not there", () => {
|
||||
expect(reindexEntries(notes, separators, "ghost", "a")).toBeNull();
|
||||
expect(reindexEntries(notes, separators, "a", "ghost")).toBeNull();
|
||||
});
|
||||
|
||||
it("leaves the separators untouched when only notes moved past each other", () => {
|
||||
const next = reindexEntries(notes, [{ id: "s1", label: "x", order: 0 }], "c", "b")!;
|
||||
expect(next.separators).toEqual([{ id: "s1", label: "x", order: 0 }]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user