Sort a notebook, import Markdown back, and stop the page shrinking in Draw
The paper measurably shrank on switching to Draw — 733px to 460px on the same screen. The editor was a flex item at its default `flex: 0 1 auto`, so its width was its widest row's: the toolbar. Swapping a dozen formatting buttons for six drawing ones collapsed it. It now fills the pane, so the width is a property of the window. Each notebook remembers how its notes are ordered, on the server rather than per device. The manual order has to survive being looked away from, so `reindex` renumbers every note in the notebook rather than the filtered view, and dragging is switched off while a search narrows the list. Reordering has its own route: `PUT /notes/:id` would bump `updated_at` and snapshot a version, so a drag would move a note to the top of "last changed" and spend a slot of its history. Markdown and our own ZIP archives come back as notes, with their images re-uploaded as attachments. A drawing returns as the PNG it was exported as — vectors are not recoverable from pixels — and the import says so rather than leaving it to be discovered. Writing the parser turned up a real bug in the exporter: blocks were emitted with no blank line between them, so a list after a list became one list and a quote after an item was read as part of it. Creating or importing from "All Notes" now asks which notebook instead of silently taking the first one. The web-link prompt is a real dialog: a bare domain becomes https, javascript: is refused with a reason. The PDF viewer's hide-drawing control is a labelled button rather than a filled-or-hollow dot, and the export icon points down into a tray like every other download. 186 unit tests, 179 browser checks across eleven suites, including a new `offline` suite that loads the app cold with the network off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3acb8b66a4
commit
0a0864c58b
27 files changed
+2962
-86
No files matched your search
@@ -72,6 +72,7 @@ interface ServerNotebook {
|
||||
cover_index?: number;
|
||||
sort_order?: number;
|
||||
max_versions?: number | null;
|
||||
sort_mode?: string | null;
|
||||
}
|
||||
|
||||
interface ServerNote {
|
||||
@@ -83,6 +84,7 @@ interface ServerNote {
|
||||
updated_at?: string;
|
||||
deleted_at?: string;
|
||||
type?: string;
|
||||
sort_order?: number;
|
||||
pdf_attachment_id?: string | null;
|
||||
}
|
||||
|
||||
@@ -403,6 +405,7 @@ export async function fullSync(): Promise<void> {
|
||||
cover: nb.cover_index || 0,
|
||||
sortOrder: nb.sort_order || 0,
|
||||
maxVersions: nb.max_versions ?? null,
|
||||
sortMode: (nb.sort_mode as Notebook["sortMode"]) ?? null,
|
||||
}));
|
||||
const serverNbIds = new Set(nbs.map((nb) => nb.id));
|
||||
const unsyncedNbs = localNbs.filter((nb) => nb._pendingCreate && !serverNbIds.has(nb.id));
|
||||
@@ -454,6 +457,7 @@ function toNoteMeta(n: ServerNote, trashed: boolean): NoteMeta {
|
||||
createdAt: n.created_at,
|
||||
updatedAt: n.updated_at || n.deleted_at || n.created_at,
|
||||
type: n.type === "pdf" ? "pdf" : "text",
|
||||
sortOrder: n.sort_order ?? 0,
|
||||
pdfAttachmentId: n.pdf_attachment_id ?? null,
|
||||
};
|
||||
}
|
||||
@@ -465,7 +469,14 @@ type QueuedAction =
|
||||
| { action: "createNotebook"; data: { localId: string; name: string; cover: number; sortOrder: number } }
|
||||
| {
|
||||
action: "updateNotebook";
|
||||
data: { id: string; name?: string; cover?: number; sortOrder?: number; maxVersions?: number | null };
|
||||
data: {
|
||||
id: string;
|
||||
name?: string;
|
||||
cover?: number;
|
||||
sortOrder?: number;
|
||||
maxVersions?: number | null;
|
||||
sortMode?: Notebook["sortMode"];
|
||||
};
|
||||
}
|
||||
| { action: "deleteNotebook"; data: { id: string } }
|
||||
| { action: "createNote"; data: { localId: string; title: string; folderId: string | null } }
|
||||
@@ -550,6 +561,7 @@ async function processEntry(entry: QueueEntry): Promise<void> {
|
||||
coverIndex: a.data.cover,
|
||||
sortOrder: a.data.sortOrder,
|
||||
...(a.data.maxVersions !== undefined ? { maxVersions: a.data.maxVersions } : {}),
|
||||
...(a.data.sortMode !== undefined ? { sortMode: a.data.sortMode } : {}),
|
||||
});
|
||||
break;
|
||||
case "deleteNotebook":
|
||||
@@ -706,6 +718,7 @@ export async function updateNotebook(id: string, updates: Partial<Notebook>): Pr
|
||||
// null is meaningful here — it clears the notebook's own setting — so it
|
||||
// has to travel, while undefined must stay away and change nothing.
|
||||
...(updates.maxVersions !== undefined ? { maxVersions: updates.maxVersions } : {}),
|
||||
...(updates.sortMode !== undefined ? { sortMode: updates.sortMode } : {}),
|
||||
});
|
||||
return;
|
||||
} catch (e) {
|
||||
@@ -720,6 +733,7 @@ export async function updateNotebook(id: string, updates: Partial<Notebook>): Pr
|
||||
cover: updates.cover,
|
||||
sortOrder: updates.sortOrder,
|
||||
maxVersions: updates.maxVersions,
|
||||
sortMode: updates.sortMode,
|
||||
},
|
||||
});
|
||||
notifyStatus("pending");
|
||||
@@ -763,6 +777,29 @@ export async function getNotes(): Promise<NoteMeta[]> {
|
||||
return db.getAllNotes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the user's own order for a set of notes.
|
||||
*
|
||||
* Its own route, not a note update: `PUT /notes/:id` bumps `updated_at` and
|
||||
* snapshots a version, so dragging a note would move it to the top of "last
|
||||
* changed" and spend a slot of its history on a change that is not to the note.
|
||||
*
|
||||
* Applied locally first, so a drag holds even offline, and queued as a plain
|
||||
* note update would be wrong for the same reason — it is sent when there is a
|
||||
* connection and simply re-derived from the local order otherwise.
|
||||
*/
|
||||
export async function reorderNotes(orderedIds: string[]): Promise<void> {
|
||||
const notes = await db.getAllNotes();
|
||||
for (let i = 0; i < orderedIds.length; i++) {
|
||||
const note = notes.find((n) => n.id === orderedIds[i]);
|
||||
if (note && note.sortOrder !== i) await db.putNote({ ...note, sortOrder: i });
|
||||
}
|
||||
notifyData("notes", null);
|
||||
if (_online && api.token) {
|
||||
api.post("/notes/reorder", { ids: orderedIds }).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
const shapeContent = (r: (ServerNoteBody & Partial<NoteContent>) | undefined | null): NoteContent => ({
|
||||
content: r?.content || "",
|
||||
drawingData: r?.drawingData || null,
|
||||
|
||||
Reference in new issue
Block a user