feat(notes): listen står i grupper, og projektet har sin egen farve
Meldt ind som "den er for tæt på en eller anden måde". Grunden var ikke afstanden mellem rækkerne: hver række sagde tre ting, og to af dem gentog sig selv nedad. Datoen stod på hver eneste række — også når tyve noter deler dag — og projektet fik en linje for sig selv. Tre linjer pr. note, gange tolv hundrede. Gentagelserne er flyttet derhen, hvor de siger noget. **Datoen er blevet til overskriften.** I dag · I går · Denne uge · August 2026 … og så er den væk fra rækken. Noten går fra tre linjer til to, og luften kommer af, at der er mindre at skrive — ikke af at listen bliver længere. **Favoritterne har fået deres egen gruppe.** De lå allerede først; der stod bare ikke hvorfor. **Grupperingen følger sorteringen.** Sorteret på "Oprettet" grupperes der på oprettelsesdatoen, på "Senest rørt" på den — ellers ville listen stå i én rækkefølge og være grupperet efter en anden, og så er overskrifterne løgn. Sorteret på navn er der ingen dato at gruppere på, så det bliver forbogstavet, og datoen kommer tilbage i rækken, hvor den så er det eneste tilbage, der siger noget. **Arkivet grupperes med.** Det er dér, de tolv hundrede importerede ligger, og dér, det betyder mest. **Projektet har sin egen farve** — den samme prik som i sidebjælken. Mærkaten sagde det samme som alle de andre i en grå, der var lysere end uddraget, og projektet er dét, man skanner efter, når man leder i tolv hundrede noter. Prikken bærer farven og ikke teksten: fem projektfarver som skriftfarve er fem forskellige grader af læsbarhed, og den lyseste er ikke læsbar. Fire ting, der blev fundet ved at se på det: - Uddraget gentog titlen, fordi titlen *er* første linje. Det var skjult, så længe en dato stod imellem dem. - Uddraget klippes ikke af sig selv. Det lå før i en flex-boks, der ejede afkortningen; da datoen flyttede op, forsvandt boksen, og sætningen løb videre ind under favoritstjernen. - Overskriftens stjerne hed `.star` ligesom rækkens favoritknap og arvede dens gennemsigtighed. Den stod der aldrig. To ting, der ligner hinanden, er ikke det samme. - En `aria-label` på foldeknappen erstatter knappens indhold som dens navn — og knappen *er* overskriften, så overskriften kom til at hedde "Fold I dag sammen". Sidebjælkens gruppehoveder gør det rigtigt, og gør det nu begge steder. Sammenfoldning huskes ikke på tværs af besøg: en gruppe, der åbner foldet uden at nogen bad om det, er en liste, der ser tom ud. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fcd6dca852
commit
08cdadfaa3
@@ -3303,3 +3303,78 @@ test('en opgave kan få et klokkeslæt, og beholder det når den flyttes', async
|
||||
|
||||
expect(trouble).toEqual([]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Notelisten står i grupper.
|
||||
*
|
||||
* Den føltes tæt, og grunden var ikke afstanden mellem rækkerne: hver række sagde
|
||||
* tre ting, og to af dem gentog sig selv nedad. Datoen stod på hver eneste række,
|
||||
* også når tyve noter deler dag, og projektet fik en linje for sig selv.
|
||||
*
|
||||
* Datoen er flyttet op i en overskrift, favoritterne har fået deres egen gruppe —
|
||||
* de lå allerede først, der stod bare ikke hvorfor — og arkivet grupperes på samme
|
||||
* måde, for det er dér, de tolv hundrede importerede ligger.
|
||||
*/
|
||||
test('notelisten grupperer favoritter og datoer, også i arkivet', async ({ page }) => {
|
||||
const trouble = watchForTrouble(page);
|
||||
await page.goto('/noter');
|
||||
|
||||
for (const title of ['Gruppe én', 'Gruppe to']) {
|
||||
await page.getByRole('button', { name: 'Ny note' }).click();
|
||||
const ed = page.getByRole('textbox', { name: 'Notens tekst' });
|
||||
await ed.click();
|
||||
await page.keyboard.type(title);
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.type('Brødtekst i ' + title);
|
||||
await expect(ed.locator('h1')).toHaveText(title);
|
||||
await page.waitForTimeout(800);
|
||||
}
|
||||
await page.reload();
|
||||
|
||||
// Skrevet i dag, så de står under dagens overskrift.
|
||||
const today = page.getByRole('heading', { name: /^I dag/ });
|
||||
await expect(today, 'der er ingen datogruppe').toBeVisible();
|
||||
|
||||
// En favorit får sin egen gruppe. Uden overskriften ligger den bare først, og
|
||||
// der står ikke hvorfor.
|
||||
await expect(page.getByRole('heading', { name: /Favoritter/i })).toBeHidden();
|
||||
const row = page.locator('li').filter({ hasText: 'Gruppe én' }).first();
|
||||
await row.hover();
|
||||
await row.getByRole('button', { name: /favorit/i }).click();
|
||||
const favourites = page.getByRole('heading', { name: /Favoritter/i });
|
||||
await expect(favourites).toBeVisible();
|
||||
|
||||
// Og den ligger under den, ikke under dagen.
|
||||
const inFavourites = page
|
||||
.locator('h3.group', { hasText: /Favoritter/i })
|
||||
.locator('+ ul li')
|
||||
.filter({ hasText: 'Gruppe én' });
|
||||
await expect(inFavourites).toBeVisible();
|
||||
|
||||
// Grupper kan foldes sammen. Ikke husket på tværs af besøg: en gruppe, der
|
||||
// åbner foldet uden at nogen bad om det, er en liste, der ser tom ud.
|
||||
await favourites.getByRole('button').click();
|
||||
await expect(inFavourites).toBeHidden();
|
||||
await favourites.getByRole('button').click();
|
||||
await expect(inFavourites).toBeVisible();
|
||||
|
||||
// En søgning grupperes ikke: serveren har sorteret efter hvor godt hver note
|
||||
// matchede, og at dele det op i måneder ville stille rækkefølgen om efter noget
|
||||
// andet end det, der blev spurgt om.
|
||||
await page.getByPlaceholder('Søg i noter').fill('Brødtekst');
|
||||
await expect(page.locator('li').filter({ hasText: 'Gruppe to' }).first()).toBeVisible();
|
||||
await expect(page.locator('h3.group')).toHaveCount(0);
|
||||
await page.getByPlaceholder('Søg i noter').fill('');
|
||||
|
||||
// Arkivet grupperes på samme måde. Det er dér, det betyder mest.
|
||||
await row.hover();
|
||||
await row.getByRole('button', { name: /Arkivér/i }).click();
|
||||
await page.getByRole('button', { name: 'Vis arkivet' }).click();
|
||||
await expect(
|
||||
page.locator('h3.group'),
|
||||
'arkivet står ugrupperet'
|
||||
).not.toHaveCount(0);
|
||||
await expect(page.locator('li').filter({ hasText: 'Gruppe én' }).first()).toBeVisible();
|
||||
|
||||
expect(trouble).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -75,6 +75,11 @@ export const da = {
|
||||
'notes.syntaxProject': 'projekt',
|
||||
'notes.syntaxNote': 'en anden note',
|
||||
'notes.yesterday': 'i går',
|
||||
'notes.groupFavourites': 'Favoritter',
|
||||
'notes.groupToday': 'I dag',
|
||||
'notes.groupYesterday': 'I går',
|
||||
'notes.groupWeek': 'Denne uge',
|
||||
'notes.groupUnknown': 'Uden dato',
|
||||
'notes.title': 'Noter',
|
||||
'notes.new': 'Ny note',
|
||||
'notes.search': 'Søg i noter',
|
||||
|
||||
@@ -71,6 +71,11 @@ export const en = {
|
||||
'notes.syntaxProject': 'project',
|
||||
'notes.syntaxNote': 'another note',
|
||||
'notes.yesterday': 'yesterday',
|
||||
'notes.groupFavourites': 'Favourites',
|
||||
'notes.groupToday': 'Today',
|
||||
'notes.groupYesterday': 'Yesterday',
|
||||
'notes.groupWeek': 'This week',
|
||||
'notes.groupUnknown': 'No date',
|
||||
'notes.title': 'Notes',
|
||||
'notes.new': 'New note',
|
||||
'notes.search': 'Search notes',
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import { t, tag } from '$lib/i18n.svelte.js';
|
||||
import { NOTE, startDrag } from '$lib/dnd.js';
|
||||
import { colorVar } from '$lib/colors.js';
|
||||
import NoteEditor from '$lib/components/NoteEditor.svelte';
|
||||
|
||||
let notes = $state([]);
|
||||
@@ -71,6 +72,100 @@
|
||||
const field = order === 'created' ? 'created_at' : 'updated_at';
|
||||
return new Date(b[field]).getTime() - new Date(a[field]).getTime();
|
||||
});
|
||||
|
||||
// --- listen i grupper ----------------------------------------------------------
|
||||
//
|
||||
// Listen føltes tæt, og grunden var ikke afstanden mellem rækkerne: hver række
|
||||
// sagde tre ting, og to af dem gentog sig selv nedad. Datoen stod på hver eneste
|
||||
// række — også når tyve noter deler dag — og projektet fik en linje for sig selv.
|
||||
// Tre linjer pr. note, gange tolv hundrede.
|
||||
//
|
||||
// Gentagelserne flyttes derhen, hvor de siger noget. Datoen bliver til
|
||||
// overskriften over en gruppe, og så forsvinder den fra rækken; projektet bliver
|
||||
// en lille mærkat ude til højre. Noten går fra tre linjer til to, og luften
|
||||
// kommer af, at der er mindre at skrive — ikke af at listen bliver længere.
|
||||
|
||||
/** Grupper, der er foldet sammen. Kun i denne session. */
|
||||
let folded = $state(new Set());
|
||||
|
||||
function toggleGroup(key) {
|
||||
const next = new Set(folded);
|
||||
next.has(key) ? next.delete(key) : next.add(key);
|
||||
folded = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hvilken dato en gruppe måles på: den, der er sorteret efter.
|
||||
*
|
||||
* Ellers ville listen stå i én rækkefølge og være grupperet efter en anden, og
|
||||
* så er overskrifterne løgn — en note ville stå under "August" mellem to fra
|
||||
* juli, fordi den var *rørt* i august og *oprettet* i juli.
|
||||
*/
|
||||
const groupDate = (note) => (order === 'created' ? note.created_at : note.updated_at);
|
||||
|
||||
/**
|
||||
* Hvad en gruppe hedder. I dag, i går, denne uge — og derefter måneden.
|
||||
*
|
||||
* Nøglen og etiketten er to ting: etiketten er sproget, og nøglen er den, der
|
||||
* afgør om to noter hører sammen. Måneden som nøgle må bære året med sig, eller
|
||||
* ville august 2026 og august 2025 blive én bunke.
|
||||
*/
|
||||
function bucketOf(note) {
|
||||
if (order === 'title') {
|
||||
// Sorteret på navn er der ingen dato at gruppere på, så det bliver
|
||||
// forbogstavet — og datoen kommer tilbage i rækken, hvor den så er den
|
||||
// eneste, der siger noget.
|
||||
const first = (note.title || '').trim().charAt(0).toUpperCase();
|
||||
const letter = /\p{L}|\p{N}/u.test(first) ? first : '#';
|
||||
return { key: 'a-' + letter, label: letter };
|
||||
}
|
||||
|
||||
const at = new Date(groupDate(note));
|
||||
if (Number.isNaN(at.getTime())) return { key: 'ukendt', label: t('notes.groupUnknown') };
|
||||
|
||||
const now = new Date();
|
||||
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const days = Math.floor((midnight - new Date(at.getFullYear(), at.getMonth(), at.getDate())) / 86400000);
|
||||
|
||||
if (days <= 0) return { key: 'i-dag', label: t('notes.groupToday') };
|
||||
if (days === 1) return { key: 'i-gaar', label: t('notes.groupYesterday') };
|
||||
if (days < 7) return { key: 'ugen', label: t('notes.groupWeek') };
|
||||
|
||||
const label = at.toLocaleDateString(tag(), { month: 'long', year: 'numeric' });
|
||||
return { key: `m-${at.getFullYear()}-${at.getMonth()}`, label };
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen, delt op.
|
||||
*
|
||||
* En søgning grupperes ikke: serveren har sorteret efter hvor godt hver note
|
||||
* matchede, og at dele det op i måneder ville stille rækkefølgen om efter noget,
|
||||
* der ikke er svaret på det, der blev spurgt om.
|
||||
*/
|
||||
let groups = $derived.by(() => {
|
||||
if (query.trim()) return [{ key: 'fundet', label: '', notes: ordered, plain: true }];
|
||||
|
||||
const out = [];
|
||||
const favourites = ordered.filter((n) => n.pinned);
|
||||
if (favourites.length) {
|
||||
out.push({ key: 'favoritter', label: t('notes.groupFavourites'), notes: favourites, star: true });
|
||||
}
|
||||
|
||||
let current = null;
|
||||
for (const note of ordered) {
|
||||
if (note.pinned) continue;
|
||||
const bucket = bucketOf(note);
|
||||
if (!current || current.key !== bucket.key) {
|
||||
current = { key: bucket.key, label: bucket.label, notes: [] };
|
||||
out.push(current);
|
||||
}
|
||||
current.notes.push(note);
|
||||
}
|
||||
return out;
|
||||
});
|
||||
|
||||
/** Projektets egen farve, som i sidebjælken. */
|
||||
const projectColour = (id) => colorVar(app.projects.find((p) => p.id === id)?.color);
|
||||
let selected = $state(null);
|
||||
// Hvilken række der er valgt. Se open(): editoren venter på hele noten.
|
||||
let selectedId = $state(null);
|
||||
@@ -258,6 +353,25 @@
|
||||
* which is exactly the thing the editor was built to stop showing. What is
|
||||
* wanted here is the sentence, not the notation.
|
||||
*/
|
||||
/**
|
||||
* Uddraget, uden titlen foran.
|
||||
*
|
||||
* Titlen *er* første linje — det er hele reglen for, hvad en note hedder — så et
|
||||
* uddrag, der begynder fra toppen, siger navnet én gang til. Det var svært at se,
|
||||
* så længe en dato stod imellem dem; nu står de oven på hinanden, og så står der
|
||||
* "Verdande" og under det "Verdande Forbindelse til connectoren".
|
||||
*/
|
||||
function preview(note) {
|
||||
const body = note.preview ?? note.body ?? '';
|
||||
const nl = body.indexOf('\n');
|
||||
const rest = nl === -1 ? '' : body.slice(nl + 1);
|
||||
// Kun hvis den første linje faktisk *er* titlen. Et uddrag fra serveren er
|
||||
// afkortet, så en lang første linje kan være hele uddraget — og så er der
|
||||
// ikke andet at vise end den.
|
||||
const withoutTitle = plain(rest);
|
||||
return withoutTitle || plain(body);
|
||||
}
|
||||
|
||||
function plain(body) {
|
||||
return (body ?? '')
|
||||
// Et billede har ingen ord i sig. Uden det her stod der
|
||||
@@ -515,69 +629,99 @@
|
||||
{query ? t('notes.noneFound') : showArchive ? t('notes.emptyArchive') : t('notes.noneYet')}
|
||||
</p>
|
||||
{:else}
|
||||
<ul>
|
||||
{#each ordered as note (note.id)}
|
||||
<li>
|
||||
<div class="rowline">
|
||||
<button
|
||||
class="row"
|
||||
class:on={selectedId === note.id}
|
||||
class:picked={picked.has(note.id)}
|
||||
onclick={(e) => rowClick(e, note)}
|
||||
draggable="true"
|
||||
ondragstart={(e) => onDragStart(e, note)}
|
||||
ondragend={() => (dragging = null)}
|
||||
>
|
||||
<strong>{note.title || t('notes.untitled')}</strong>
|
||||
<!-- Dato og begyndelse på samme linje, som Apple Noter gør det: to
|
||||
linjer pr. note frem for tre, og datoen er dét, man skanner
|
||||
efter, når man leder efter noget, man skrev i tirsdags. -->
|
||||
<span class="under">
|
||||
<!-- Datoen, der svarer til den rækkefølge, listen står i.
|
||||
Den viste altid `updated_at`, og for tolv hundrede
|
||||
importerede noter er det i aftes — så en note fra 2017
|
||||
stod som "i går", og den dato, man sorterede efter, var
|
||||
ingen steder at se. -->
|
||||
<span class="when" title={stamp(note)}>{when(shownDate(note))}</span>
|
||||
<span class="preview">{plain(note.preview ?? note.body)}</span>
|
||||
</span>
|
||||
{#if note.project_id}
|
||||
<span class="filed">{projectName(note.project_id)}</span>
|
||||
{/if}
|
||||
</button>
|
||||
<!-- De to knapper står oven på hinanden i en smal søjle.
|
||||
Ved siden af hinanden tog de toogfyrre pixels af en liste,
|
||||
der er tre hundrede bred — og de bruges sjældent, mens
|
||||
titlen og uddraget læses hele tiden. -->
|
||||
<div class="rowactions">
|
||||
<!-- Én note lagt væk, uden at markere den først. Markeringen er
|
||||
til flere; det her er til den ene, man står med — og i
|
||||
arkivet er det vejen tilbage. -->
|
||||
<button
|
||||
class="archive-one"
|
||||
onclick={() => archiveOne(note)}
|
||||
aria-label={note.archived_at ? t('notes.unarchive') : t('notes.archiveOne')}
|
||||
title={note.archived_at ? t('notes.unarchive') : t('notes.archiveOne')}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M3 7h18v3H3zM5 10v9h14v-9M10 14h4" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="star"
|
||||
class:on={note.pinned}
|
||||
onclick={() => favourite(note)}
|
||||
aria-pressed={note.pinned}
|
||||
aria-label={note.pinned ? t('notes.unfavourite') : t('notes.favourite')}
|
||||
title={note.pinned ? t('notes.unfavourite') : t('notes.favourite')}
|
||||
>
|
||||
{note.pinned ? '★' : '☆'}
|
||||
</button>
|
||||
{#each groups as group (group.key)}
|
||||
<!-- Overskriften klæber, mens man ruller. Med tolv hundrede noter er det
|
||||
forskellen på at rulle og på at lede: man kan altid se, hvilken måned
|
||||
man er nede i. -->
|
||||
{#if !group.plain}
|
||||
<h3 class="group">
|
||||
<!-- Ingen `aria-label`. Den ville erstatte knappens indhold som dens
|
||||
navn, og knappen *er* overskriften — så overskriften ville hedde
|
||||
"Fold I dag sammen" i stedet for "I dag". `aria-expanded` siger
|
||||
tilstanden, som den gør på gruppehovederne i sidebjælken. -->
|
||||
<button onclick={() => toggleGroup(group.key)} aria-expanded={!folded.has(group.key)}>
|
||||
{#if group.star}
|
||||
<span class="mark favmark" aria-hidden="true">★</span>
|
||||
{:else}
|
||||
<span class="mark" class:closed={folded.has(group.key)} aria-hidden="true">▾</span>
|
||||
{/if}
|
||||
{group.label}
|
||||
<span class="count">{group.notes.length}</span>
|
||||
</button>
|
||||
</h3>
|
||||
{/if}
|
||||
{#if !folded.has(group.key)}
|
||||
<ul>
|
||||
{#each group.notes as note (note.id)}
|
||||
<li>
|
||||
<div class="rowline">
|
||||
<button
|
||||
class="row"
|
||||
class:on={selectedId === note.id}
|
||||
class:picked={picked.has(note.id)}
|
||||
onclick={(e) => rowClick(e, note)}
|
||||
draggable="true"
|
||||
ondragstart={(e) => onDragStart(e, note)}
|
||||
ondragend={() => (dragging = null)}
|
||||
>
|
||||
<!-- Titel og projekt på linje ét, uddrag på linje to. Datoen står
|
||||
i gruppens overskrift og ikke her — undtagen når der er sorteret
|
||||
på navn, for så er der ingen overskrift, der siger den, og så er
|
||||
den det eneste tilbage, der gør. -->
|
||||
<span class="head">
|
||||
<strong>{note.title || t('notes.untitled')}</strong>
|
||||
{#if order === 'title'}
|
||||
<span class="when" title={stamp(note)}>{when(shownDate(note))}</span>
|
||||
{/if}
|
||||
{#if note.project_id}
|
||||
<!-- Projektets egen farve, den samme som prikken i sidebjælken.
|
||||
Mærkaten sagde det samme som alle de andre i en grå, der var
|
||||
lysere end uddraget — og projektet er dét, man skanner efter,
|
||||
når man leder i tolv hundrede noter. Nu kan Hjem kendes fra
|
||||
Claude uden at læse ordet. -->
|
||||
<span class="filed" style="--project: {projectColour(note.project_id)}">
|
||||
<span class="dot" aria-hidden="true"></span>
|
||||
{projectName(note.project_id)}
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="preview">{preview(note)}</span>
|
||||
</button>
|
||||
<!-- De to knapper står oven på hinanden i en smal søjle.
|
||||
Ved siden af hinanden tog de toogfyrre pixels af en liste,
|
||||
der er tre hundrede bred — og de bruges sjældent, mens
|
||||
titlen og uddraget læses hele tiden. -->
|
||||
<div class="rowactions">
|
||||
<!-- Én note lagt væk, uden at markere den først. Markeringen er
|
||||
til flere; det her er til den ene, man står med — og i
|
||||
arkivet er det vejen tilbage. -->
|
||||
<button
|
||||
class="archive-one"
|
||||
onclick={() => archiveOne(note)}
|
||||
aria-label={note.archived_at ? t('notes.unarchive') : t('notes.archiveOne')}
|
||||
title={note.archived_at ? t('notes.unarchive') : t('notes.archiveOne')}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M3 7h18v3H3zM5 10v9h14v-9M10 14h4" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="star"
|
||||
class:on={note.pinned}
|
||||
onclick={() => favourite(note)}
|
||||
aria-pressed={note.pinned}
|
||||
aria-label={note.pinned ? t('notes.unfavourite') : t('notes.favourite')}
|
||||
title={note.pinned ? t('notes.unfavourite') : t('notes.favourite')}
|
||||
>
|
||||
{note.pinned ? '★' : '☆'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</aside>
|
||||
|
||||
@@ -925,19 +1069,99 @@
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* --- grupperne ----------------------------------------------------------
|
||||
*
|
||||
* Overskriften klæber. Med tolv hundrede noter er det forskellen på at rulle og
|
||||
* på at lede: man kan altid se, hvilken måned man er nede i.
|
||||
*
|
||||
* Baggrunden er ikke gennemsigtig. En klæbende overskrift uden en er en
|
||||
* overskrift med rækkerne kørende igennem sig. */
|
||||
h3.group {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
background: var(--ground);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
h3.group button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s2);
|
||||
width: 100%;
|
||||
padding: var(--s2) var(--s2);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
|
||||
h3.group button:hover {
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
h3.group .mark {
|
||||
flex: none;
|
||||
width: 0.75em;
|
||||
font-size: 0.85em;
|
||||
line-height: 1;
|
||||
color: var(--ink-faint);
|
||||
transition: transform var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
h3.group .mark.closed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/* Sit eget navn og ikke `.star`. Rækkens favoritknap hedder også det og er
|
||||
gennemsigtig, indtil man peger på rækken — så overskriftens stjerne arvede
|
||||
den gennemsigtighed og stod der aldrig. To ting, der ligner hinanden, er
|
||||
ikke det samme, og et delt klassenavn er en påstand om, at de er. */
|
||||
h3.group .favmark {
|
||||
color: var(--accent);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h3.group .count {
|
||||
margin-left: auto;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Stregen mellem rækkerne er væk.
|
||||
*
|
||||
* Grupperne giver strukturen nu, og en streg pr. note er tolv hundrede streger.
|
||||
* Luften kommer af, at rækken siger to ting i stedet for tre — ikke af at listen
|
||||
* bliver længere. */
|
||||
.row {
|
||||
display: block;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
text-align: left;
|
||||
padding: var(--s2) var(--s2) var(--s3);
|
||||
/* Plads til højre for de to knapper, der ligger oven på rækken. Uden den
|
||||
løber uddraget ind under stjernen — usynligt, indtil man peger på rækken
|
||||
og knappen dukker op oven i ordene. */
|
||||
padding: var(--s2) 26px calc(var(--s2) + 2px) var(--s2);
|
||||
border-radius: var(--radius);
|
||||
color: var(--ink-muted);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
li:last-child .row {
|
||||
border-bottom: 0;
|
||||
/* Titel, dato og projekt på én linje. Projektet ude til højre, hvor øjet kan
|
||||
løbe ned ad søjlen uden at læse resten. */
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--s2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.head strong {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
@@ -964,33 +1188,56 @@
|
||||
|
||||
/* Dato og begyndelse på samme linje, som Apple Noter gør det. To linjer pr.
|
||||
note frem for tre, og datoen først, fordi det er den, man skanner efter. */
|
||||
.under {
|
||||
display: flex;
|
||||
gap: var(--s2);
|
||||
min-width: 0;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
|
||||
.when {
|
||||
flex: none;
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
/* `display: block`, og det er ikke pynt.
|
||||
*
|
||||
* Uddraget lå før inde i en flex-boks, der ejede afkortningen. Da datoen flyttede
|
||||
* op i gruppens overskrift, forsvandt den boks — og et inline-element klippes
|
||||
* ikke af `overflow: hidden`, så den sidste sætning løb videre ind under
|
||||
* favoritstjernen. Reglerne skal stå på det element, der faktisk skal klippes. */
|
||||
.preview {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Hvor den ligger, med en mappe foran — Apple Noters egen måde at sige det på,
|
||||
og den eneste linje her, der ikke er notens eget indhold. */
|
||||
/* Projektets egen farve, den samme som prikken i sidebjælken.
|
||||
*
|
||||
* Mærkaten sagde det samme som alle de andre i en grå, der var lysere end
|
||||
* uddraget — og projektet er dét, man skanner efter, når man leder i tolv
|
||||
* hundrede noter. En prik i projektets farve og en tekst, der ikke er lysere end
|
||||
* det, den står ved siden af: Hjem kan kendes fra Claude uden at ordet læses.
|
||||
*
|
||||
* Prikken bærer farven, ikke teksten. Fem projekters farver som skriftfarve på
|
||||
* en lys flade er fem forskellige grader af læsbarhed, og den lyseste af dem er
|
||||
* ikke læsbar. */
|
||||
.filed {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375em;
|
||||
flex: none;
|
||||
margin-left: auto;
|
||||
max-width: 45%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-faint);
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.filed .dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
flex: none;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--project, var(--line-strong));
|
||||
}
|
||||
|
||||
.filed::before {
|
||||
|
||||
Reference in New Issue
Block a user