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
@@ -19,6 +19,8 @@ A self-hosted note-taking application with rich text editing, freehand drawing,
|
||||
- **Star/Favorite** — Quick access to important notes
|
||||
- **PDF Reading & Annotation** — Import a PDF, read it, select its text, draw on it, write notes beside it, and export it with or without your marks
|
||||
- **Export** — Any note, notebook or the whole account as Markdown or PDF, entirely in the browser
|
||||
- **Import** — Markdown files, or a ZIP this app exported, come back as notes with their images
|
||||
- **Sorting** — Each notebook remembers how its notes are ordered, including an arrangement made by hand
|
||||
- **Dark & Light Mode** — Full theme support with refined color schemes
|
||||
|
||||
### Sync & Storage
|
||||
@@ -302,7 +304,7 @@ failure distinguished by comparing an error's `.message` to a magic string.
|
||||
Failures now carry a typed `SyncError.code`; `.message` still holds the same
|
||||
string, so the remaining JavaScript callers are unaffected.
|
||||
|
||||
The 81 unit tests concentrate on the same places, and several exist because the
|
||||
The 186 unit tests concentrate on the same places, and several exist because the
|
||||
bug they cover actually happened:
|
||||
|
||||
- a queue entry that fails must stay queued, with an attempt counter
|
||||
@@ -334,6 +336,8 @@ docker compose -f docker-compose.dev.yml up -d --build # must be running
|
||||
| `attachments-lifecycle` | 10 | a picture is kept while any version shows it, and freed once none does |
|
||||
| `conflict` | 10 | a late write wins but the state it buries survives as a version |
|
||||
| `export` | 21 | the downloaded bytes are real Markdown, ZIP and PNG, unpacked and read back |
|
||||
| `organise` | 24 | a hand-made order survives a search; a note exported and imported comes back whole |
|
||||
| `offline` | 12 | a cold page load with the network off still opens notes and searches them |
|
||||
|
||||
Each suite creates a throwaway account through the admin API and deletes it
|
||||
afterwards, so runs leave the database as they found it. They need an admin to
|
||||
@@ -354,7 +358,7 @@ needs. Everything else lives behind a hook:
|
||||
| `useSearchIndex` | the local index and the background backfill that fills it |
|
||||
| `useResponsive` | phone vs desktop, sidebar and which pane a phone shows |
|
||||
| `useNavigationHistory` | mapping the current location onto browser history |
|
||||
| `useNotebookDnd` | drag-to-reorder, shared by sidebar and dashboard |
|
||||
| `useNotebookDnd` | drag-to-reorder, shared by the sidebar, the dashboard and the note list |
|
||||
| `usePwaInstall` | the browser's install offer |
|
||||
|
||||
`useNoteData` deliberately has no confirmation prompts in it: whether to ask
|
||||
@@ -703,6 +707,70 @@ rows behind.
|
||||
|
||||
---
|
||||
|
||||
## Sorting a notebook
|
||||
|
||||
Each notebook remembers how its notes are ordered, and remembers it on the
|
||||
server rather than per device — the order is a property of the notebook, and
|
||||
finding one sorted differently on the phone than on the desktop is its own small
|
||||
annoyance. A reference book can stay alphabetical while a journal stays
|
||||
newest-first.
|
||||
|
||||
| Mode | Order |
|
||||
| --- | --- |
|
||||
| `manual` | the user's own arrangement, dragged into place |
|
||||
| `updated` | most recently changed first *(the default)* |
|
||||
| `oldest` | least recently changed first |
|
||||
| `created` | most recently created first |
|
||||
| `title` | A–Z, case- and umlaut-aware, with numbers read as numbers |
|
||||
|
||||
**The manual order has to survive being looked away from.** Searching imposes
|
||||
its own order — relevance — and switching back has to show exactly what was
|
||||
arranged. That is only keepable if the arrangement lives in the notes and is
|
||||
never re-derived from what is on screen, so two rules hold it up:
|
||||
|
||||
- `reindex` renumbers **every note in the notebook**, taken from the store, not
|
||||
the list being displayed. Renumbering a filtered subset would give a hidden
|
||||
note the same position as a visible one and quietly scramble the rest.
|
||||
- Dragging is switched off while a search is narrowing the list, and the list
|
||||
says so rather than silently ignoring the drag.
|
||||
|
||||
Reordering also has its own route, `POST /api/notes/reorder`. Going through
|
||||
`PUT /api/notes/:id` would bump `updated_at` and snapshot 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 at all.
|
||||
|
||||
---
|
||||
|
||||
## Import
|
||||
|
||||
The other half of the promise that notes are not hostage to this program. A
|
||||
`.md` file becomes a note; a `.zip` this app exported comes back nearly whole —
|
||||
text, tables, task boxes, and the images, re-uploaded as attachments so they are
|
||||
encrypted at rest again like anything else.
|
||||
|
||||
The parser is a deliberate subset: what the exporter emits, plus what a
|
||||
hand-written note is likely to use. It is not CommonMark and does not try to be.
|
||||
A full parser is thousands of lines and a permanent maintenance surface, and the
|
||||
failure mode here is mild — an unrecognised construct arrives as the literal
|
||||
text that was written, which is still the user's words in the user's order.
|
||||
|
||||
Two things do not come back, and the import says so afterwards rather than
|
||||
leaving them to be discovered:
|
||||
|
||||
- **A drawing returns as a picture, not as strokes.** It was exported as a PNG,
|
||||
and vectors cannot be recovered from pixels. It stays visible, printable and
|
||||
exportable, but the pen can no longer edit it.
|
||||
- **An image the archive does not contain cannot be restored.** The place it sat
|
||||
in the note is kept, so what used to be there is at least recorded.
|
||||
|
||||
Writing the exporter also turned up a real bug in it: blocks were emitted
|
||||
without a blank line between them, so a list written straight after another list
|
||||
became more items of the first, and a quote or code fence on the next line was
|
||||
read as part of the item above. That is not one reader's quirk — it is what the
|
||||
format says — so the file rendered wrongly everywhere, not only here.
|
||||
|
||||
---
|
||||
|
||||
## Export
|
||||
|
||||
Notes should not be hostage to this program, so everything can leave it — from a
|
||||
|
||||
Reference in new issue
Block a user