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:
20
CLAUDE.md
20
CLAUDE.md
@@ -67,6 +67,11 @@ are stale — they were last taken before the notes and class-register work, and
|
||||
could not be retaken because the live session had lapsed. Every Schulcloud check fails with 401 when the live
|
||||
session has lapsed — check the container's keepalive log before suspecting code.
|
||||
|
||||
The editor's Markdown round trip is unit-tested; the **browser** side of
|
||||
`editor.js` is not, because nothing here runs one. It was checked by hand in
|
||||
Firefox against a page that drives the toolbar — WebKit, which is the engine on
|
||||
the phone this is written on, has still never run it.
|
||||
|
||||
Store tests need a database and skip without one:
|
||||
`TEST_DATABASE_URL=postgresql://… npm test`. They use a real Postgres on
|
||||
purpose — the generation/diff semantics are entirely SQL, so a mock would test
|
||||
@@ -145,7 +150,20 @@ bin/cli.ts ──HTTP──────────┘ cli/{config,client,sync
|
||||
**files** under `src/http/app/`, copied to `dist/` by `scripts/copy-assets.mjs`
|
||||
and read relative to `import.meta.dirname` — real HTML, CSS and JS that an
|
||||
editor and a linter understand, which is also what the CSP requires, since it
|
||||
forbids inline script.
|
||||
forbids inline script. `app.js` is an ES **module**; a new asset must be added
|
||||
to `ASSETS` *and* to the route's regex in `app-page.ts`, or it 404s.
|
||||
- **`http/app/markdown.js` + `editor.js`** — the note is edited as formatted
|
||||
text and stored as Markdown, and these two are that translation.
|
||||
`markdown.js` is the pair `markdownToHtml` / `markdownFromDom`; `editor.js`
|
||||
drives a `contenteditable` element with `execCommand` (no library: the CSP
|
||||
allows no outside script and the app has no bundler). **The round trip must
|
||||
settle**: one pass may tidy a note, a second must change nothing, and
|
||||
`editor.js` checks exactly that before opening a note formatted — a note that
|
||||
fails opens in the Markdown view instead. `test/app-markdown.test.ts` covers
|
||||
it against `test/mini-dom.ts`, ~60 lines of read-only DOM, because losing a
|
||||
lesson's notes to a lossy serializer is not a bug anyone can recover from.
|
||||
Pasted HTML goes through Markdown before it reaches the document, which is
|
||||
where sanitising and formatting are the same operation.
|
||||
- **`http/web-auth.ts`** — the app's login, which is a different kind of
|
||||
credential from everything else here: a password a person types, not a token a
|
||||
program was configured with. scrypt at startup, a signed `HttpOnly` /
|
||||
|
||||
Reference in New Issue
Block a user