feat(web): notes render as markdown — markdown-it+DOMPurify pipeline, rich paste

Objective 6 (web half). Storage/sync stay raw markdown (KOReader shows
literal source, accepted). Notes-grade syntax: headings, emphasis/
strikethrough, code, links, lists, blockquotes, GFM tables, reference
links, footnotes (markdown-it-footnote); images/raw HTML/math excluded
(html:false posture + FORBID img/style/form + default safe-scheme URI
check). Display sites: annotations-drawer note rows and highlight note
lines render pre-sanitized HTML (x-html); render-time sanitize only.

Rich paste: both note textareas intercept paste; a text/html clipboard
flavor converts via turndown (gfm tables incl. a headingless-table rule
emitting pipe syntax; images blanked) and inserts at the caret; plain
pastics fall through to the default paste unchanged.

Verified live in Brave: bold/italic/strike/code/lists/quote/table/
footnote-ref/reference-link render; <script> renders inert literal
text with no alert; javascript: hrefs absent from the DOM; paste of
rich HTML lands markdown at the caret; plain pastes untouched.
This commit is contained in:
John O'Keefe
2026-10-04 13:25:17 -04:00
parent b9f797789f
commit 4b5e193d9f
5 changed files with 143 additions and 9 deletions
+8 -1
View File
@@ -15,13 +15,20 @@
"@bookhoard/foliate-js": "git+https://git.linuxhg.com/Bookhoard/foliate-js.git#b7597d6",
"alpinejs": "^3.15.8",
"chart.js": "^4.5.1",
"dompurify": "^3.4.16",
"highlight.js": "^11.11.1",
"htmx.org": "^2.0.8",
"lunr": "^2.3.9"
"lunr": "^2.3.9",
"markdown-it": "^15.0.2",
"markdown-it-footnote": "^4.0.0",
"turndown": "^7.2.4",
"turndown-plugin-gfm": "^1.0.2"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.11",
"@tailwindcss/typography": "^0.5.19",
"@types/markdown-it": "^14.2.0",
"@types/turndown": "^5.0.6",
"autoprefixer": "^10.4.27",
"tailwindcss": "^3.4.19",
"typescript": "^5.9.3",