Lay the notes screen out like a notes app

The list of notes and the note being written are one screen now, two
panes: the notes on the left, the open one on the right, side by side
where there is room and one at a time on a phone, where the back button
returns to the list.

The search box moved into the top of that list, and its results *are*
the list — searching is a way of finding a note, not a separate place to
be, and a tab for it was a tab too many. Emptying the box brings the
whole list back. Opening a hit opens that day at the lesson that
matched, rather than at the top of a day with six of them.

A row has to say what the note holds, so the listing carries it: the
subjects a day covers, how many lessons, and the first line actually
written in it. One request for the whole list rather than one per note.

`plainText` is now one rule in one place for wherever a note is shown
rather than edited — the search snippet and the list row both went
through their own half-copy of it, and the row's copy rendered a table
as `| | |` and left `_Fazit_` wearing its markers. It strips one leading
marker, not each in turn, because `## 1. Deutsch` keeps its lesson
number and the list rule was eating it.

Driven in Firefox at both widths: the list, the search, opening a hit,
the jump to the lesson, and the phone's list-then-note. 390 unit tests,
116/117 smoke.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-09-21 18:22:47 +02:00
parent 5db098b67f
commit 48c6cf39d5
9 changed files with 478 additions and 190 deletions

View File

@@ -72,6 +72,97 @@ header { border-bottom: 1px solid var(--line); }
.view { flex: 1; min-height: 0; display: flex; flex-direction: column; padding: 0.75rem; gap: 0.5rem; }
/* --- the notes screen: a list, and the note that is open ---------------- */
/*
* Two panes where there is room, one at a time where there is not. The
* breakpoint is about where a phone in landscape stops being a phone: below it
* `data-pane` on the container decides which of the two is on screen, and the
* back button is the way out of the note.
*/
.notes { flex-direction: row; gap: 0; padding: 0; }
.rail {
flex: 0 0 18rem;
min-width: 0;
display: flex;
flex-direction: column;
border-right: 1px solid var(--line);
background: var(--card);
}
.rail-head { display: flex; gap: 0.4rem; padding: 0.6rem 0.6rem 0.4rem; }
.rail-head form { flex: 1; min-width: 0; }
.rail-head input {
width: 100%;
padding: 0.55rem 0.7rem;
border: 1px solid var(--line);
border-radius: 0.5rem;
background: var(--bg);
color: var(--fg);
font: inherit;
font-size: 0.9rem;
}
#today { flex: 0 0 auto; width: 2.5rem; padding: 0; font-size: 1.1rem; line-height: 1; }
#rail-status { padding: 0 0.7rem 0.3rem; }
.rail-list { flex: 1; min-height: 0; overflow-y: auto; padding: 0 0.4rem 0.6rem; }
/* A whole row is the target: on a phone the thing being tapped is the note,
not a link inside it. */
.row {
display: block;
width: 100%;
text-align: left;
padding: 0.5rem 0.6rem;
margin-bottom: 0.25rem;
border: 1px solid transparent;
border-radius: 0.5rem;
background: none;
color: var(--fg);
font: inherit;
cursor: pointer;
}
.row:hover { background: var(--bg); }
.row[aria-current="true"] {
background: var(--bg);
border-color: var(--accent);
}
.row-title { font-weight: 600; font-size: 0.95rem; }
.row-line { margin: 0.1rem 0 0; color: var(--muted); font-size: 0.85rem; line-height: 1.35; }
/* Two lines of preview and no more: a row is a glance, not a read. */
.row-line.clamp {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.row mark { background: color-mix(in srgb, var(--accent) 28%, transparent); color: inherit; border-radius: 0.15rem; }
.detail { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem; min-height: 0; }
/* Only a phone needs a way back to the list; on a wide screen it is never gone. */
.back { display: none; align-self: flex-start; padding: 0.4rem 0.7rem; font-size: 0.9rem; }
@media (max-width: 720px) {
.rail { flex: 1; border-right: 0; }
.notes[data-pane="list"] .detail { display: none; }
.notes[data-pane="note"] .rail { display: none; }
.notes[data-pane="note"] .back { display: block; }
}
.note-preview { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 0.4rem; }
.note-preview h2 { margin: 0; font-size: 1.05rem; }
.note-preview p { margin: 0; }
/* --- the day bar ------------------------------------------------------- */
.daybar { display: flex; align-items: center; gap: 0.5rem; }
@@ -271,51 +362,6 @@ button:disabled { opacity: 0.5; cursor: default; }
font-size: 0.9rem;
}
/* --- search ------------------------------------------------------------ */
.searchbar { display: flex; gap: 0.5rem; }
.searchbar input {
flex: 1;
min-width: 0;
padding: 0.7rem;
border: 1px solid var(--line);
border-radius: 0.5rem;
background: var(--bg);
color: var(--fg);
font: inherit;
}
.searchbar button { background: var(--accent); border-color: var(--accent); color: #ffffff; font-weight: 600; }
.results { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 0.5rem; }
/* A whole result is the target: on a phone the thing being tapped is a card,
not a link inside one. */
.hit {
width: 100%;
text-align: left;
padding: 0.6rem 0.75rem;
border: 1px solid var(--line);
border-radius: 0.5rem;
background: var(--card);
color: var(--fg);
font: inherit;
cursor: pointer;
display: block;
}
.hit-head { display: flex; gap: 0.5rem; align-items: baseline; flex-wrap: wrap; }
.hit-subject { font-weight: 600; }
.hit-date { color: var(--muted); font-size: 0.85rem; }
.hit-snippet { margin: 0.25rem 0 0; color: var(--muted); font-size: 0.9rem; line-height: 1.4; }
.hit mark { background: color-mix(in srgb, var(--accent) 25%, transparent); color: inherit; border-radius: 0.15rem; }
.note-preview { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.note-preview h2 { margin: 0; font-size: 1.05rem; }
.note-preview .editor { cursor: default; }
#search-back { align-self: flex-start; }
/* --- cards (login, settings) ------------------------------------------- */
.card {