fix(notes): lister overlevede ikke en gemning, og tallene blev klippet af
Seks ting fundet ved at bruge appen, i den rækkefølge de gjorde ondt. En liste inde i en blok blev læst som tekst. htmlToMarkdown havde én gren for ul og ol og lod alt andet falde ned i inlineToMarkdown, som samler efterkommere til én streng — så et blokcitat med en liste i kom tilbage som "> PunktPunkt 2", og en note fra Apple Noter, der er fuld af citater, mistede hvert punktmærke ved den første gemning. Ingen havde rørt de linjer. Nu læses en blok, der rummer blokke, igennem frem for flad, og et citat sætter "> " foran det, der kommer indefra. Indlejrede lister fandtes ikke. Den flade `list`-variabel kunne kun beskrive ét niveau, så et indrykket punkt blev lagt ved siden af sit ophav. Nu en stak, og underlisten lægges inde i sit <li> — det er den forskel, der kan læses tilbage. En liste, der begyndte ved ti, begyndte ved ét bagefter. `start` skrives nu med og læses med, så en gemning uden ændringer ikke gør noten om. Tallet blev klippet af. Mærket sættes uden for tekstkanten, og 16px rakte til et punkttegn og ikke til "10." — resten hang ud over kanten, hvor .page beskærer. Renden er nu 2.2em og deles af begge slags, så punkter og tal flugter. # viste seks vilkårlige projekter ud af tyve og så færdig ud. Loftet er væk, listen kan rulle, og det, der begynder med det tastede, står øverst. Og to ting om, hvordan det ser ud. Noteruden har fået sin egen flade, fordi et ark, der holder op et sted, skiller det, man skriver, fra det program, man skriver det i — `--surface` frem for `#fff`, så de fem temaer hver får deres. Noter stod for sig selv over en streg, hvilket sagde det rigtige og kostede halvfjerds pixels; den står nu i rækken med de faste visninger og bærer et ark i stedet for en prik, hvilket siger det samme gratis. Prøven skriver de fem listeformer, gemmer gennem den rigtige editor og læser filen bagefter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a4b12f3601
commit
8229800767
@@ -22,6 +22,8 @@
|
||||
let { note, onchange, onsave } = $props();
|
||||
|
||||
let editor;
|
||||
// Forslagslisten, så tastaturvalget kan rulle den frem.
|
||||
let list;
|
||||
let stylesOpen = $state(false);
|
||||
// Which marks are on where the caret is, so the toolbar shows state rather than
|
||||
// only offering actions.
|
||||
@@ -166,9 +168,29 @@
|
||||
return;
|
||||
}
|
||||
const term = partial.term.toLowerCase();
|
||||
suggestions = app.projects
|
||||
.filter((p) => !p.is_inbox && p.name.toLowerCase().includes(term))
|
||||
.slice(0, 6);
|
||||
|
||||
// Alle der passer, ikke de seks første.
|
||||
//
|
||||
// Loftet på seks var sat, dengang listen ikke kunne rulle, og det gjorde
|
||||
// noget værre end at skjule: med tyve projekter og et blot tastet `#` viste
|
||||
// den seks vilkårlige og så færdig ud. Man kunne ikke se, at der manglede
|
||||
// noget — kun at ens eget projekt ikke var der.
|
||||
//
|
||||
// Rækkefølgen bærer nu det, loftet gjorde forkert: det, der begynder med
|
||||
// det tastede, står øverst, resten alfabetisk. Skriver man "ga", er
|
||||
// GarageRisteriet den første, ikke den, der tilfældigvis lå først.
|
||||
const matches = app.projects.filter(
|
||||
(p) => !p.is_inbox && p.name.toLowerCase().includes(term)
|
||||
);
|
||||
matches.sort((a, b) => {
|
||||
const an = a.name.toLowerCase();
|
||||
const bn = b.name.toLowerCase();
|
||||
const ap = an.startsWith(term);
|
||||
const bp = bn.startsWith(term);
|
||||
if (ap !== bp) return ap ? -1 : 1;
|
||||
return an.localeCompare(bn, 'da');
|
||||
});
|
||||
suggestions = matches;
|
||||
chosen = 0;
|
||||
}
|
||||
|
||||
@@ -227,6 +249,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruller det valgte forslag frem, når listen er længere end sin egen kasse.
|
||||
*
|
||||
* Efter opdateringen, ikke før: `chosen` er netop sat, og rækken bærer klassen
|
||||
* først når Svelte har skrevet den ud. Uden ventetiden ruller den til den
|
||||
* forrige.
|
||||
*/
|
||||
function keepChosenInView() {
|
||||
requestAnimationFrame(() => {
|
||||
list?.querySelector('button.on')?.scrollIntoView({ block: 'nearest' });
|
||||
});
|
||||
}
|
||||
|
||||
function onkeydown(event) {
|
||||
// The suggestion list owns the arrows and return while it is open, the way
|
||||
// every other completion does. Escape closes it without choosing.
|
||||
@@ -234,11 +269,13 @@
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
chosen = (chosen + 1) % suggestions.length;
|
||||
keepChosenInView();
|
||||
return;
|
||||
}
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
chosen = (chosen - 1 + suggestions.length) % suggestions.length;
|
||||
keepChosenInView();
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Enter' || event.key === 'Tab') {
|
||||
@@ -366,7 +403,7 @@
|
||||
<!-- Anchored to the editor rather than to the caret. Following the caret means
|
||||
measuring it, and a list that jumps a few pixels as you type is worse than
|
||||
one that sits still. -->
|
||||
<ul class="suggestions" role="listbox">
|
||||
<ul class="suggestions" role="listbox" bind:this={list}>
|
||||
{#each suggestions as project, i (project.id)}
|
||||
<li>
|
||||
<button
|
||||
@@ -515,6 +552,10 @@
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 0.25);
|
||||
/* Nu hvor alle der passer står på listen, skal den kunne rulle. Otte rækker
|
||||
er nok til at det ses, at der er flere, uden at menuen dækker noten. */
|
||||
max-height: 15rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.suggestions button {
|
||||
@@ -590,10 +631,23 @@
|
||||
margin: 0 0 var(--s2);
|
||||
}
|
||||
|
||||
/* Renden skal kunne rumme det bredeste mærke.
|
||||
*
|
||||
* Mærket sættes uden for tekstkanten, inde i listens egen indrykning, og en fast
|
||||
* indrykning på 16px rakte til et punkttegn og ikke til "10." — resten hang ud
|
||||
* over kanten, hvor `.page`, som ruller lodret og derfor beskærer vandret,
|
||||
* klippede det af. Man så et punktum uden sit tal.
|
||||
*
|
||||
* Samme rende til begge slags, frem for en bredere til den nummererede: mærket
|
||||
* lægger sig op ad teksten uanset hvor bred renden er, så deler de den, flugter
|
||||
* punkter og tal — og gør de ikke, står to lister under hinanden med hver sin
|
||||
* venstrekant.
|
||||
*
|
||||
* Målt i em, så den følger skriftstørrelsen, og bred nok til tre cifre. */
|
||||
.page :global(ul),
|
||||
.page :global(ol) {
|
||||
margin: 0 0 var(--s2);
|
||||
padding-left: var(--s4);
|
||||
padding-left: 2.2em;
|
||||
}
|
||||
|
||||
.page :global(li) {
|
||||
|
||||
@@ -131,7 +131,21 @@
|
||||
: null,
|
||||
// No href: the inbox is a project and is drawn by projectRow, which knows
|
||||
// its id. It is in this list only so it can be dragged with the others.
|
||||
{ key: 'inbox', label: 'nav.inbox' }
|
||||
{ key: 'inbox', label: 'nav.inbox' },
|
||||
// Noter hører til her, og bærer et ark frem for en prik.
|
||||
//
|
||||
// Den stod før for sig selv over en streg, hvilket sagde det rigtige og
|
||||
// kostede halvfjerds pixels: en streg, luft over og luft under, og
|
||||
// projekterne skubbet så langt ned, at de to første var alt, man så uden
|
||||
// at rulle. Grunden til at skille den ud var, at den ellers læste som
|
||||
// endnu et filter — men det, der gør den til et filter for øjet, er
|
||||
// prikken, ikke pladsen. Et andet mærke siger "en anden slags ting" og
|
||||
// koster ingenting.
|
||||
//
|
||||
// Sidst i rækken, fordi `navOrder` føjer en ukendt nøgle til bagest: står
|
||||
// den et andet sted her, ser en ny konto én rækkefølge og alle
|
||||
// eksisterende en anden.
|
||||
{ key: 'notes', href: '/noter', label: 'notes.title' }
|
||||
].filter(Boolean)
|
||||
);
|
||||
|
||||
@@ -463,7 +477,20 @@
|
||||
ondragleave={() => (overToday = false)}
|
||||
ondrop={(e) => item.key === 'today' && onDropOnToday(e)}
|
||||
>
|
||||
<span class="dot" class:today={item.key === 'today'} aria-hidden="true"></span>
|
||||
{#if item.key === 'notes'}
|
||||
<svg class="mark" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<path
|
||||
d="M4 1.5h5.2L12.5 5v9.5H4z"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.4"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path d="M9 1.7V5h3.3" fill="none" stroke="currentColor" stroke-width="1.4" />
|
||||
</svg>
|
||||
{:else}
|
||||
<span class="dot" class:today={item.key === 'today'} aria-hidden="true"></span>
|
||||
{/if}
|
||||
{t(item.label)}
|
||||
</a>
|
||||
</div>
|
||||
@@ -471,17 +498,6 @@
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Notes stand apart, with a rule above them.
|
||||
They are not a fourth way of looking at the same tasks, which is what
|
||||
everything above is; they are the other half of the program. Sitting in that
|
||||
run made them read as one more filter. -->
|
||||
<div class="apart">
|
||||
<a href="/noter" class:active={current === '/noter'} onclick={onnavigate}>
|
||||
<span class="dot" aria-hidden="true"></span>
|
||||
{t('notes.title')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- One row for every project the sidebar shows — your own, the Inbox and the
|
||||
shared ones — because all three are somewhere a task can be dropped, and
|
||||
three copies of that would be three chances to fix a bug in two of them.
|
||||
@@ -1025,15 +1041,20 @@
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
/* A rule and a little air, which is the whole of "separate". Notes are the other
|
||||
half of the program, not a fifth way of looking at the same tasks. */
|
||||
.apart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
margin: var(--s3) 0;
|
||||
padding-top: var(--s3);
|
||||
border-top: 1px solid var(--line);
|
||||
/* Arket. Samme plads som prikken, så teksten står på linje med resten af runden
|
||||
— mærket skifter, kolonnen gør ikke. */
|
||||
.mark {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
/* Prikken er 6px bred, og et 13px mærke ville skubbe etiketten tre pixels ud
|
||||
af flugt med de andre. Negativ margen på hver side lægger det tilbage. */
|
||||
margin: 0 -3.5px;
|
||||
color: var(--ink-faint);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
a.active .mark {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.group-head {
|
||||
|
||||
Reference in New Issue
Block a user