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:
@@ -93,8 +93,129 @@ header { border-bottom: 1px solid var(--line); }
|
||||
.daybar-centre strong { font-size: 1.05rem; }
|
||||
.daybar-centre input { border: 0; background: none; color: var(--muted); font: inherit; font-size: 0.85rem; }
|
||||
|
||||
/* --- the toolbar ------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* One row that scrolls sideways rather than wrapping into two: a second row
|
||||
* would take a line of editor away from every note to hold buttons that are
|
||||
* used once a lesson, and a thumb swipes a row far more easily than it hunts
|
||||
* through a grid.
|
||||
*/
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
padding-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.toolbar::-webkit-scrollbar { display: none; }
|
||||
|
||||
.toolbar button {
|
||||
flex: 0 0 auto;
|
||||
min-width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
padding: 0 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toolbar button[aria-pressed="true"] {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 12%, var(--card));
|
||||
}
|
||||
|
||||
.toolbar button code { font-family: ui-monospace, monospace; font-size: 0.85rem; }
|
||||
.sep { flex: 0 0 auto; width: 1px; height: 1.5rem; background: var(--line); margin: 0 0.15rem; }
|
||||
|
||||
/* --- the editor -------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* The formatted document. It is the note as it will read, not as it is stored
|
||||
* — the file underneath is still Markdown, and `MD` in the toolbar shows it.
|
||||
*/
|
||||
.editor {
|
||||
flex: 1;
|
||||
min-height: 12rem;
|
||||
overflow-y: auto;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1rem;
|
||||
line-height: 1.55;
|
||||
/* A long URL or a wide table must not push the page sideways. */
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.editor:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
|
||||
|
||||
.editor.empty::before {
|
||||
content: attr(data-placeholder);
|
||||
color: var(--muted);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.editor > :first-child { margin-top: 0; }
|
||||
.editor > :last-child { margin-bottom: 0; }
|
||||
.editor p { margin: 0 0 0.75rem; }
|
||||
|
||||
/* A lesson heading is the note's structure — each one is indexed as its own
|
||||
lesson — so it is given a rule to sit on rather than just a larger size. */
|
||||
.editor h2 {
|
||||
margin: 1.25rem 0 0.5rem;
|
||||
padding-bottom: 0.2rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.editor h1 { font-size: 1.25rem; margin: 1.25rem 0 0.5rem; }
|
||||
.editor h3, .editor h4, .editor h5, .editor h6 { margin: 1rem 0 0.35rem; font-size: 1rem; }
|
||||
|
||||
.editor ul, .editor ol { margin: 0 0 0.75rem; padding-left: 1.4rem; }
|
||||
.editor li { margin: 0.15rem 0; }
|
||||
.editor li.task { list-style: none; margin-left: -1.2rem; }
|
||||
.editor li.task input { margin-right: 0.4rem; }
|
||||
|
||||
.editor blockquote {
|
||||
margin: 0 0 0.75rem;
|
||||
padding-left: 0.75rem;
|
||||
border-left: 3px solid var(--line);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.editor code {
|
||||
padding: 0.1em 0.3em;
|
||||
border-radius: 0.25rem;
|
||||
background: var(--card);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.editor pre {
|
||||
margin: 0 0 0.75rem;
|
||||
padding: 0.6rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
background: var(--card);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.editor pre code { padding: 0; background: none; }
|
||||
.editor hr { border: 0; border-top: 1px solid var(--line); margin: 1rem 0; }
|
||||
.editor a { color: var(--accent); }
|
||||
|
||||
/* A table wider than the phone scrolls inside the note rather than stretching
|
||||
it: the caret has to stay reachable. */
|
||||
.editor table { display: block; overflow-x: auto; border-collapse: collapse; margin: 0 0 0.75rem; font-size: 0.9rem; }
|
||||
.editor th, .editor td { border: 1px solid var(--line); padding: 0.3rem 0.5rem; text-align: left; min-width: 3rem; }
|
||||
.editor th { background: var(--card); }
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
min-height: 12rem;
|
||||
@@ -104,8 +225,8 @@ textarea {
|
||||
border-radius: 0.5rem;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
/* Monospace: the notes are Markdown, and headings and list markers have to
|
||||
line up to be read back as structure. */
|
||||
/* Monospace: this is the Markdown view, and headings and list markers have
|
||||
to line up to be read back as structure. */
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
|
||||
Reference in New Issue
Block a user