Write notes as formatted text, store them as Markdown

The editor was a textarea holding raw Markdown, which is the wrong thing
to hand someone taking notes during a lesson: nobody types `##` and `**`
while a teacher is talking. It now shows the note formatted and puts a
toolbar above it — headings, bold, lists, tick boxes, quotes, links,
tables — while the file on disk stays exactly what it was, because that
is what the indexer reads and what outlives this app.

`markdown.js` is the whole translation: `markdownToHtml` on the way in,
`markdownFromDom` on the way out. The property that matters is that the
round trip settles — one pass may tidy a note, a second must change
nothing — because these notes are the only record of what was said in
the room and there is nothing to restore a lossy save from. `editor.js`
checks exactly that before opening a note formatted, and a note it
cannot hold unchanged opens in the Markdown view and says so instead of
being quietly reduced.

No editor library: the content security policy allows no outside script
and the app has no bundler, so this is `contenteditable` and
`execCommand` with a tolerant serializer behind it — an element it does
not model keeps its words and loses its tag. Pasted HTML is converted to
Markdown before it reaches the document, which is the one place where
sanitising and formatting are the same operation.

Tested against `test/mini-dom.ts`, sixty lines of read-only DOM, rather
than a headless browser or a DOM dependency; the toolbar itself was
driven by hand in Firefox. WebKit has still never run it.
This commit is contained in:
MechaCat02
2026-09-19 21:24:42 +02:00
parent 5c0b658855
commit 534b1b0f58
13 changed files with 1690 additions and 33 deletions

View File

@@ -87,6 +87,41 @@ thing I open in a free period".
`add_note` — the save is refused and you are asked which version wins. It
never silently overwrites.
### Writing, without typing Markdown
The editor shows the note **formatted** — headings as headings, bold as bold,
tables as tables — and the toolbar above it writes the Markdown. Nobody types
`##` or `**` during a lesson.
| Button | What it writes |
| --- | --- |
| `H2` | a lesson heading — the one that makes the lesson separately searchable |
| `H3` | a subheading inside a lesson |
| `F` `K` `S` | **fett**, _kursiv_, ~~durchgestrichen~~ (`Strg`/`Cmd` + B, I) |
| `<>` | inline code (`Strg`/`Cmd` + E) |
| `• —` `1. —` | bullet and numbered lists; nest them with Tab |
| `☐` | a box to tick off |
| `❝` | a quote — the teacher's exact wording |
| `🔗` `▦` | a link (`Strg`/`Cmd` + K) and a table |
| `MD` | the Markdown itself |
`Enter` starts a new paragraph, `Shift+Enter` a new line in the same one. A
paste from a web page or a PDF keeps its structure and loses its fonts, colours
and anything else that is not in the list above — pasted HTML is converted to
Markdown before it reaches the page, which is what keeps a copied page from
bringing its script along.
**The file is still Markdown.** `MD` shows it and lets you edit it directly,
which is the way to write something the toolbar has no button for. There is no
underline, because Markdown cannot store one — `F` or `K` instead.
Opening a note may tidy it once: `*so*` becomes `_so_`, a table typed unevenly
lines up. Nothing is rewritten until you actually change something, and a note
whose formatting the view cannot hold unchanged **opens as Markdown** and says
so rather than being quietly reduced. `test/app-markdown.test.ts` is what holds
that promise up: every construct in this document goes in and comes back out
unchanged.
**Einstellungen** holds the Schulcloud token: how long it has left, and the box
to paste a fresh `jwt` cookie into when it expires (the same thing `schulcloud
token set` and the older `/token` page do). It also shows the index's state and