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
+44
-1
@@ -339,6 +339,14 @@ if (!tableColumns("notebooks").includes("max_versions")) {
|
||||
db.exec("ALTER TABLE notebooks ADD COLUMN max_versions INTEGER");
|
||||
}
|
||||
|
||||
/* How a notebook's notes are ordered on screen. Stored on the server rather
|
||||
* than per device: it is a property of the notebook, and finding a notebook
|
||||
* sorted differently on the phone than on the desktop is its own small
|
||||
* annoyance. Null means the app's default, newest first. */
|
||||
if (!tableColumns("notebooks").includes("sort_mode")) {
|
||||
db.exec("ALTER TABLE notebooks ADD COLUMN sort_mode TEXT");
|
||||
}
|
||||
|
||||
if (!tableColumns("notes").includes("pdf_attachment_id")) {
|
||||
db.exec("ALTER TABLE notes ADD COLUMN pdf_attachment_id TEXT");
|
||||
}
|
||||
@@ -769,8 +777,11 @@ app.post("/api/notebooks", authMiddleware, (req, res) => {
|
||||
res.json({ id, name, coverIndex });
|
||||
});
|
||||
|
||||
/** The orders the note list offers. Anything else is refused rather than stored. */
|
||||
const SORT_MODES = ["manual", "updated", "oldest", "created", "title"];
|
||||
|
||||
app.put("/api/notebooks/:id", authMiddleware, (req, res) => {
|
||||
const { name, coverIndex, sortOrder, maxVersions } = req.body;
|
||||
const { name, coverIndex, sortOrder, maxVersions, sortMode } = req.body;
|
||||
const nb = db.prepare("SELECT * FROM notebooks WHERE id = ? AND user_id = ?").get(req.params.id, req.userId);
|
||||
if (!nb) return res.status(404).json({ error: "Not found" });
|
||||
|
||||
@@ -784,11 +795,18 @@ app.put("/api/notebooks/:id", authMiddleware, (req, res) => {
|
||||
versions = Math.min(Math.round(n), MAX_VERSIONS_CEILING);
|
||||
}
|
||||
|
||||
if (sortMode !== undefined && sortMode !== null && !SORT_MODES.includes(sortMode)) {
|
||||
return res.status(400).json({ error: `sortMode must be one of ${SORT_MODES.join(", ")}` });
|
||||
}
|
||||
|
||||
db.prepare("UPDATE notebooks SET name = COALESCE(?, name), cover_index = COALESCE(?, cover_index), sort_order = COALESCE(?, sort_order), updated_at = datetime('now'), version = version + 1 WHERE id = ?")
|
||||
.run(name, coverIndex, sortOrder, req.params.id);
|
||||
if (maxVersions !== undefined) {
|
||||
db.prepare("UPDATE notebooks SET max_versions = ? WHERE id = ?").run(versions, req.params.id);
|
||||
}
|
||||
if (sortMode !== undefined) {
|
||||
db.prepare("UPDATE notebooks SET sort_mode = ? WHERE id = ?").run(sortMode, req.params.id);
|
||||
}
|
||||
|
||||
broadcastToUser(req.userId, { type: "notebook_updated", data: { id: req.params.id, name, coverIndex } }, req.deviceId);
|
||||
res.json({ success: true, defaultMaxVersions: MAX_VERSIONS_PER_NOTE });
|
||||
@@ -942,6 +960,31 @@ app.get("/api/notes/:id", authMiddleware, (req, res) => {
|
||||
res.json(note);
|
||||
});
|
||||
|
||||
/**
|
||||
* The user's own order for a notebook's notes.
|
||||
*
|
||||
* Deliberately not part of `PUT /api/notes/:id`. That route bumps `updated_at`
|
||||
* and snapshots a version — so dragging a note into place would move it to the
|
||||
* top of "last changed" and burn a slot of its history, for a change that is not
|
||||
* to the note at all. This writes `sort_order` and nothing else.
|
||||
*/
|
||||
const MAX_REORDER = 5000;
|
||||
app.post("/api/notes/reorder", authMiddleware, (req, res) => {
|
||||
const ids = Array.isArray(req.body?.ids) ? req.body.ids.slice(0, MAX_REORDER) : null;
|
||||
if (!ids) return res.status(400).json({ error: "ids must be an array" });
|
||||
|
||||
const write = db.transaction(() => {
|
||||
const update = db.prepare("UPDATE notes SET sort_order = ? WHERE id = ? AND user_id = ?");
|
||||
ids.forEach((id, index) => {
|
||||
if (typeof id === "string" && id) update.run(index, id, req.userId);
|
||||
});
|
||||
});
|
||||
write();
|
||||
|
||||
broadcastToUser(req.userId, { type: "notes_reordered", data: { count: ids.length } }, req.deviceId);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.post("/api/notes", authMiddleware, (req, res) => {
|
||||
const { type, title, content, notebookId, starred, pdfAttachmentId } = req.body;
|
||||
const id = uuid();
|
||||
|
||||
Reference in new issue
Block a user