feat(notes): formateringen overlever, og monospace bliver til en terminal

Konverteringen læser nu Apple Noters HTML frem for at flade den ud med textutil.
Overskrifter, fed, kursiv, understreget, lister og monospace kommer med. En note er
ikke kun sine ord — et referat med overskrifter er ikke det samme referat som en
klump — og ti prøvenoter viste hvad det kostede.

Skrevet om til Python, fordi HTML skal læses og ikke gættes med regulære udtryk.

Den alvorligste fejl, prøvekørslen fandt: navn og krop blev hentet i hvert sit
kald, og Noter sorterer listen efter ændringsdato. Flyttede noget sig imellem de
to kald, fik en note én titel og en andens indhold. På ti noter er det synligt; på
tolv hundrede ville det være ubemærket rod. Der hentes nu ved id, og navn og krop
kommer i samme kald.

Sammenhængende monospace-linjer bliver til én blok. Hver linje i Noter er pakket
ind i sin egen div, hvis starttag tømmer den forrige — så mellem to tt-linjer kom
der altid en tom flush forbi, og hver linje i en terminaludskrift blev sin egen
blok.

Og i editoren ser en kodeblok ud som en terminal: mørk flade, monospace, sproget i
hjørnet, farver på strenge, tal, kommentarer, nøgleord og prompten. Monospace ER
kode i praksis, og en mørk flade siger det på afstand.

Farverne lægges på de blokke, markøren IKKE står i. At farve mens nogen skriver
betyder at bygge indholdet om ved hvert tastetryk og sætte markøren tilbage
bagefter — det virker ni gange ud af ti og flytter markøren midt i en sætning den
tiende.

Et API-token er ét ord, der er bredere end skærmen. Uden min-width: 0 skubbede en
sådan note hele listen bredere og klemte editoren sammen — på en note, man ikke
engang havde åben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kristian Wind
2026-08-19 20:31:10 +02:00
co-authored by Claude Opus 5
parent 08fc355bf9
commit 43ad60a84a
7 changed files with 633 additions and 141 deletions
+54
View File
@@ -2258,3 +2258,57 @@ test('en opgave viser de noter, der nævner den, og linket åbner noten', async
expect(trouble).toEqual([]);
});
test('en kodeblok ser ud som en terminal og bliver farvet', async ({ page }) => {
const trouble = watchForTrouble(page);
await page.goto('/noter');
// Skrevet ind som Markdown, sådan som importen fra Apple Noter leverer den.
const id = await page.evaluate(async () => {
const r = await fetch('/api/v1/notes', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json', 'Sec-Fetch-Site': 'same-origin' },
body: JSON.stringify({
body: '# Terminalprøven\n\nHer er udskriften:\n\n```bash\nkw@shell:~$ ls -la\n# en kommentar\necho "hej"\n```\n\nog resten.'
})
});
return (await r.json()).id;
});
await page.goto('/noter?note=' + id);
const ed = page.getByRole('textbox', { name: 'Notens tekst' });
const pre = ed.locator('pre');
await expect(pre).toBeVisible();
// Mørk flade uanset tema: en terminal er sort, også midt på dagen.
const look = await pre.evaluate((el) => {
const s = getComputedStyle(el);
return { bg: s.backgroundColor, font: s.fontFamily };
});
expect(look.bg).toBe('rgb(20, 24, 27)');
expect(look.font.toLowerCase()).toMatch(/mono/);
// Og farvet: prompten, kommentaren og strengen skal kunne skelnes.
await expect(pre.locator('.tok-prompt')).toBeVisible();
await expect(pre.locator('.tok-comment')).toContainText('en kommentar');
await expect(pre.locator('.tok-string')).toContainText('hej');
// Teksten omkring er ikke blevet til kode.
await expect(ed.locator('h1')).toHaveText('Terminalprøven');
await expect(ed).toContainText('og resten.');
// Og blokken overlever turen tilbage til Markdown: den skal komme ud som en
// hegnet blok med sit sprog, ikke som en klump linjer.
const body = await page.evaluate(async (noteId) => {
const r = await fetch('/api/v1/notes/' + noteId, {
credentials: 'include',
headers: { 'Sec-Fetch-Site': 'same-origin' }
});
return (await r.json()).body;
}, id);
expect(body).toContain('```bash');
expect(body).toContain('kw@shell:~$ ls -la');
expect(trouble).toEqual([]);
});