Export notes as Markdown or PDF

The reason to build this is not a missing feature — it is that trusting a
program you maintain alone with years of notes should not mean the notes
can only ever be read by that program.

Reachable from a note's toolbar, a notebook's context menu, and Account →
Alle Notizen exportieren.

Markdown carries over everything with an equivalent: headings, lists,
quotes, code, tables, and task state as `- [x]`. A link to another note in
the same export becomes a relative link to its file; a link to a note
outside it degrades to the plain title rather than leaving a dead
reference. Notes with images or a drawing arrive as a ZIP so the files
travel along, and a multi-note export gets a README index.

PDF goes through the browser's print dialog rather than a bundled PDF
library — the browser already has a good one, and it is the only format
where drawings sit inside the document. Markdown gets them as a PNG
beside the text, with a line saying so, because dropping them silently
would be the wrong kind of quiet.

Two decisions worth recording:

- the ZIP writer is eighty lines here rather than a dependency. Note text
  is small and images are already compressed, so compression bought
  nothing, and stored entries are a handful of fixed-layout records.
- Markdown escaping only covers what changes meaning mid-line, plus a
  leading list or heading marker. Escaping every character Markdown ever
  uses turns a sentence into `Hallo\. Alles gut\!`, which defeats the
  point of choosing a readable format.

42 unit tests on the serialiser and the archive format, plus a browser
suite that unpacks a real download and reads the bytes back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-08 14:46:53 +02:00
1 parent 311e992496
commit 2611d87f8c
14 files changed
+1707 -2

No files matched your search

+38
View File
@@ -18,6 +18,7 @@ A cross-platform note-taking application with rich text editing, freehand drawin
- **PDF Import & Annotation** — Import PDFs, overlay drawing/highlighting, persistent annotations
- **Notebooks & Folders** — Create notebooks with custom gradient covers, organize notes by folder
- **Star/Favorite** — Quick access to important notes
- **Export** — Any note, notebook or the whole account as Markdown or PDF, entirely in the browser
- **Dark & Light Mode** — Full theme support with refined color schemes
### Sync & Storage
@@ -57,6 +58,7 @@ inknote/
│ │ ├── offlineStore.ts # IndexedDB wrapper
│ │ ├── search.ts # Local full-text index
│ │ ├── hooks/ # Note data, search, layout, history, drag
│ │ ├── export/ # Markdown, ZIP and print-to-PDF
│ │ ├── ui/ # Sidebar, dialogs, icons, palette
│ │ ├── features/notes/ # List, editor pane, version history
│ │ ├── features/notebooks/ # Dashboard
@@ -65,6 +67,7 @@ inknote/
│ ├── public/sw.js # PWA service worker
│ ├── nginx.conf # Static serving + API/WS proxy
│ └── dockerfile # Web container
├── e2e/ # Browser suites — run with ./e2e/run.sh
└── readme.md # This file
```
@@ -329,6 +332,7 @@ docker compose -f docker-compose.dev.yml up -d --build # must be running
| `search-attachments` | 12 | body-only matches, ranking, umlauts; images as blob URLs, not inline bytes |
| `history` | 10 | a large deletion is snapshotted, restoring works, restoring is itself undoable |
| `drawing` | 7 | strokes survive the poller, and match across four viewport widths |
| `export` | 12 | the downloaded bytes are real Markdown and a real ZIP, unpacked and read back |
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
@@ -475,6 +479,40 @@ rows behind.
---
## Export
Notes should not be hostage to this program, so everything can leave it — from a
single note up to every note in the account, reachable from the note's toolbar,
a notebook's context menu, and Account → *Alle Notizen exportieren*.
**Markdown** keeps the notes editable elsewhere. Headings, lists, quotes, code,
tables and task state all have equivalents and are carried over; a link to
another exported note becomes a relative link to its file, and a link to a note
outside the export degrades to its plain title rather than a dead reference.
A note with images or a drawing arrives as a ZIP so the files travel with it,
and a whole notebook gets a `README.md` index grouped by notebook.
**PDF** goes through the browser's own print dialog rather than a bundled PDF
library — every browser already has a good one behind *Save as PDF*, and it
handles fonts and page breaks better than anything that would fit in here. This
is the format that keeps drawings inside the document.
Two things Markdown cannot represent, and what happens instead:
| | |
| --- | --- |
| drawings | rendered to PNG beside the note, with a line in the text saying so |
| underline | written as `<u>`, which every Markdown renderer passes through |
The ZIP writer is [`export/zip.ts`](frontend/src/export/zip.ts) — about eighty
lines for stored (uncompressed) entries, rather than a dependency. Note text is
small and images are already compressed, so there was nothing to gain by
compressing, and this is one fewer thing to keep updated.
Everything runs in the browser. Nothing is uploaded anywhere to produce a file.
---
## Drawings
Drawings are **vector strokes**, not bitmaps. Every stroke is recorded in one canonical