feat(notes): arkiv, flere ad gangen, og en kopiér-knap på koden
Release / Build and publish image (push) Failing after 9m55s
Release / Build and publish image (push) Failing after 9m55s
**Arkivet, fordi papirkurven er det forkerte redskab.** Den siger "det her var en fejl, og om tredive dage er det væk" — så alt, der er værd at beholde, kan ikke komme i den. Et arkiv på tolv hundrede importerede noter er præcis det tilfælde, hvor de fleste er værd at beholde og næsten ingen er værd at kigge på; uden et sted at lægge dem er listen hele arkivet for evigt, og at finde ugens note betyder at læse forbi et årti. En kolonne, ikke en mappe. Noterne afviste et andet hierarki i 0018, og arkivering er ikke arkivering i den forstand: det er én oplysning om, hvorvidt en note er aktuel, og den skal kunne besvares ved siden af `deleted_at`, som er den anden af samme slags. `updated_at` røres ikke — at rydde op er ikke at skrive. **Flere ad gangen.** ⌘-klik lægger til, skift-klik tager intervallet i den rækkefølge, listen faktisk står i — ikke notens egen, som er en anden, når der er sorteret eller søgt. Trækker man en note, der er markeret, følger hele markeringen med; trækker man en, der ikke er, er det den ene, for ellers ville et træk i en tilfældig række flytte halvtreds noter, man havde glemt var markeret. Ét kald frem for et pr. note: efter en import af tolv hundrede er det at vælge halvtreds det almindelige tilfælde. Hver note kontrolleres stadig for sig — et sæt er en bekvemmelighed for den, der kalder, og aldrig en vej til noget, de ikke kunne have rørt én ad gangen. En kant *og* en tone på det markerede: `--accent-sunken` er meget lys i lyst tema, og tre markerede rækker var svære at skelne fra tre, der ikke var — en dårlig ting at være i tvivl om lige inden man trykker Slet. **Kopiér-knappen spiste næsten den kode, den skulle kopiere.** Den ligger inde i `<pre>` for at kunne stå i hjørnet af blokken, og `colourCode` læste `pre.textContent` og malede resultatet tilbage — så et klik på "Kopiér" skrev "Kopieret" ind i koden, og næste gemning lagde det i filen. Nu ét sted, der siger hvad en kodeblok indeholder, brugt af både farvelægningen og oversættelsen til Markdown: var de to uenige, ville forskellen ende i noten. Prøven klikker, læser udklipsholderen 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
8aad7c13b0
commit
aadfb197b6
@@ -345,6 +345,7 @@ export const api = {
|
||||
|
||||
uploadAttachment: (taskId, file) => upload(`/tasks/${taskId}/attachments`, file),
|
||||
uploadNoteFile: (noteId, file) => upload(`/notes/${noteId}/attachments`, file),
|
||||
noteBulk: (body) => post('/notes/bulk', body),
|
||||
deleteAttachment: (id) => del(`/attachments/${id}`),
|
||||
attachmentURL: (id) => `/api/v1/attachments/${id}`,
|
||||
|
||||
|
||||
@@ -121,12 +121,71 @@
|
||||
* monospace hele tiden; farverne kommer, når man forlader den, hvilket er når
|
||||
* man læser den.
|
||||
*/
|
||||
/**
|
||||
* En kopiér-knap på hver kodeblok.
|
||||
*
|
||||
* Lagt oven på blokken frem for skrevet ind i den: alt inde i `.page` er tekst,
|
||||
* der bliver til Markdown, når noten gemmes, så en knap derinde ville ende i
|
||||
* filen. Den her er `contenteditable="false"` og hænger uden for tekstflowet —
|
||||
* den findes for øjet og musen, ikke for dokumentet.
|
||||
*
|
||||
* `navigator.clipboard` fejler, når siden ikke er sikker eller ikke har fokus,
|
||||
* og en knap, der siger "Kopieret" uden at have gjort det, er værre end ingen
|
||||
* knap. Derfor siges det kun, når skrivningen faktisk lykkedes.
|
||||
*/
|
||||
function addCopyButtons() {
|
||||
if (!editor) return;
|
||||
for (const pre of editor.querySelectorAll('pre')) {
|
||||
if (pre.querySelector('.copy')) continue;
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'copy';
|
||||
btn.type = 'button';
|
||||
btn.contentEditable = 'false';
|
||||
btn.textContent = t('notes.copyCode');
|
||||
btn.addEventListener('mousedown', (e) => e.preventDefault());
|
||||
btn.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const text = codeTextOf(pre);
|
||||
try {
|
||||
await navigator.clipboard.writeText(text.replace(/\n$/, ''));
|
||||
btn.textContent = t('notes.copied');
|
||||
setTimeout(() => (btn.textContent = t('notes.copyCode')), 1200);
|
||||
} catch {
|
||||
app.toast(t('notes.copyFailed'));
|
||||
}
|
||||
});
|
||||
pre.appendChild(btn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Teksten i en kodeblok, uden knapper og andet, editoren har lagt oven på den.
|
||||
*
|
||||
* Ét sted, fordi to steder skal bruge det samme svar: farvelægningen, som
|
||||
* skriver resultatet tilbage i blokken, og oversættelsen til Markdown, som
|
||||
* skriver det i filen. Var de to uenige, ville forskellen ende i noten.
|
||||
*/
|
||||
function codeTextOf(pre) {
|
||||
const from = pre.querySelector('code') ?? pre;
|
||||
return [...from.childNodes]
|
||||
.filter((n) => n.nodeType !== Node.ELEMENT_NODE || !n.classList?.contains('copy'))
|
||||
.map((n) => n.textContent ?? '')
|
||||
.join('');
|
||||
}
|
||||
|
||||
function colourCode() {
|
||||
if (!editor) return;
|
||||
const inside = currentBlock();
|
||||
for (const pre of editor.querySelectorAll('pre')) {
|
||||
if (pre === inside || pre.contains(inside)) continue;
|
||||
const code = pre.textContent ?? '';
|
||||
// Blokkens tekst uden det, vi selv har lagt oven på den.
|
||||
//
|
||||
// `pre.textContent` tog kopiér-knappens eget ord med, og linjen under
|
||||
// maler resultatet tilbage ind i blokken — så et klik på "Kopiér" skrev
|
||||
// "Kopieret" ind i koden, og den næste gemning lagde det i filen. Knappen
|
||||
// spiste den kode, den var sat der for at kopiere.
|
||||
const code = codeTextOf(pre);
|
||||
const lang = pre.getAttribute('data-lang') || guessLanguage(code);
|
||||
if (lang) pre.setAttribute('data-lang', lang);
|
||||
const painted = highlight(code, lang);
|
||||
@@ -135,6 +194,7 @@
|
||||
const target = pre.querySelector('code') ?? pre;
|
||||
if (target.innerHTML !== painted) target.innerHTML = painted;
|
||||
}
|
||||
addCopyButtons();
|
||||
}
|
||||
|
||||
function changed() {
|
||||
@@ -915,6 +975,40 @@
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
/* Kopiér-knappen, øverst til højre i blokken.
|
||||
*
|
||||
* Synlig hele tiden, ikke kun ved hover. En knap, der først findes, når musen
|
||||
* er det rigtige sted, er en knap, folk ikke ved er der — og på en berøringsskærm
|
||||
* findes den slet ikke. Den er i stedet dæmpet nok til ikke at tage
|
||||
* opmærksomhed fra koden.
|
||||
*
|
||||
* `z-index` sagt højt: uden det lå den bag blokkens eget indhold i
|
||||
* stakrækkefølgen, og et klik ramte <pre> i stedet for knappen. */
|
||||
.page :global(pre) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page :global(pre .copy) {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
font-family: var(--font);
|
||||
font-size: var(--text-xs);
|
||||
padding: 2px var(--s2);
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgb(255 255 255 / 0.08);
|
||||
color: rgb(255 255 255 / 0.55);
|
||||
transition: background var(--fast) var(--ease), color var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
.page :global(pre:hover .copy),
|
||||
.page :global(pre .copy:hover),
|
||||
.page :global(pre .copy:focus-visible) {
|
||||
background: rgb(255 255 255 / 0.2);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* En kodeblok ser ud som en terminal, fordi det er hvad den er.
|
||||
Monospace er altid kode i praksis — Apple Noters "Monotype" er stort set kun
|
||||
brugt til kommandoer og udskrifter — og en mørk flade siger det på afstand,
|
||||
|
||||
@@ -380,9 +380,12 @@
|
||||
// The same target takes both. A note dropped on a project is filed there,
|
||||
// which is also how it gets shared — one gesture, one meaning, and the
|
||||
// sidebar already reads as "put this here".
|
||||
const noteID = dragged(event, NOTE);
|
||||
if (noteID) {
|
||||
await app.moveNoteToProject(noteID, project.id);
|
||||
// Nyttelasten er ét eller flere id'er adskilt af mellemrum. Markerer man
|
||||
// halvtreds noter og trækker dem herhen, hører de alle sammen til det ene
|
||||
// sted, man slap dem — det er én handling, uanset hvor mange den flytter.
|
||||
const noteIDs = (dragged(event, NOTE) ?? '').split(' ').filter(Boolean);
|
||||
if (noteIDs.length) {
|
||||
for (const id of noteIDs) await app.moveNoteToProject(id, project.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,17 @@ export const da = {
|
||||
'notes.noneInProject': 'Ingen noter endnu. En note, der nævner #projektet, dukker også op her.',
|
||||
'notes.clearSearch': 'Ryd søgningen',
|
||||
'notes.found': '{count} noter',
|
||||
'notes.picked': '{count} markeret',
|
||||
'notes.archive': 'Arkivér',
|
||||
'notes.unarchive': 'Tag frem igen',
|
||||
'notes.clearPick': 'Ryd markering',
|
||||
'notes.showArchive': 'Vis arkivet',
|
||||
'notes.deleteMany': 'Slet {count} noter?',
|
||||
'notes.archiveOne': 'Arkivér noten',
|
||||
'notes.emptyArchive': 'Arkivet er tomt.',
|
||||
'notes.copyCode': 'Kopiér',
|
||||
'notes.copied': 'Kopieret',
|
||||
'notes.copyFailed': 'Kunne ikke kopiere',
|
||||
'notes.strike': 'Gennemstreget',
|
||||
'notes.showSource': 'Vis som kode',
|
||||
'notes.source': 'Notens kildetekst',
|
||||
|
||||
@@ -44,6 +44,17 @@ export const en = {
|
||||
'notes.noneInProject': 'No notes yet. A note that mentions #theproject turns up here too.',
|
||||
'notes.clearSearch': 'Clear the search',
|
||||
'notes.found': '{count} notes',
|
||||
'notes.picked': '{count} selected',
|
||||
'notes.archive': 'Archive',
|
||||
'notes.unarchive': 'Bring back',
|
||||
'notes.clearPick': 'Clear selection',
|
||||
'notes.showArchive': 'Show the archive',
|
||||
'notes.deleteMany': 'Delete {count} notes?',
|
||||
'notes.archiveOne': 'Archive this note',
|
||||
'notes.emptyArchive': 'The archive is empty.',
|
||||
'notes.copyCode': 'Copy',
|
||||
'notes.copied': 'Copied',
|
||||
'notes.copyFailed': 'Could not copy',
|
||||
'notes.strike': 'Strikethrough',
|
||||
'notes.showSource': 'Show as code',
|
||||
'notes.source': 'Note source',
|
||||
|
||||
@@ -339,7 +339,15 @@ export function htmlToMarkdown(root) {
|
||||
// else and an indent does not.
|
||||
const lang = child.getAttribute('data-lang') ?? '';
|
||||
emit('```' + lang);
|
||||
for (const l of (child.textContent ?? '').replace(/\n$/, '').split('\n')) {
|
||||
// Knapper og andet, editoren har lagt oven på blokken, hører ikke til
|
||||
// i filen. Kopiér-knappen ligger inde i <pre> for at kunne placeres i
|
||||
// hjørnet af den, og `textContent` ville ellers skrive ordet "Kopiér"
|
||||
// ind i hver eneste kodeblok, hver gang noten blev gemt.
|
||||
const code = [...child.childNodes]
|
||||
.filter((n) => n.nodeType !== Node.ELEMENT_NODE || !n.classList?.contains('copy'))
|
||||
.map((n) => n.textContent ?? '')
|
||||
.join('');
|
||||
for (const l of code.replace(/\n$/, '').split('\n')) {
|
||||
emit(l);
|
||||
}
|
||||
emit('```');
|
||||
|
||||
@@ -74,6 +74,77 @@
|
||||
let selected = $state(null);
|
||||
// Hvilken række der er valgt. Se open(): editoren venter på hele noten.
|
||||
let selectedId = $state(null);
|
||||
|
||||
/**
|
||||
* Flere ad gangen.
|
||||
*
|
||||
* Et sæt frem for et flag på noten, fordi markeringen hører til skærmen og ikke
|
||||
* til noten: den forsvinder, når man går et andet sted hen, og den skal ikke
|
||||
* gemmes nogen steder.
|
||||
*
|
||||
* `anchor` er den, et skift-klik måler fra. Uden den ville et interval skulle
|
||||
* gættes ud fra "den valgte", og den flytter sig, hver gang man klikker.
|
||||
*/
|
||||
let picked = $state(new Set());
|
||||
let anchor = $state(null);
|
||||
let showArchive = $state(false);
|
||||
|
||||
function rowClick(event, note) {
|
||||
const many = event.metaKey || event.ctrlKey;
|
||||
const range = event.shiftKey;
|
||||
|
||||
if (!many && !range) {
|
||||
picked = new Set();
|
||||
anchor = note.id;
|
||||
open(note);
|
||||
return;
|
||||
}
|
||||
|
||||
const next = new Set(picked);
|
||||
if (range && anchor) {
|
||||
// Fra ankeret til her, i den rækkefølge listen faktisk står i — ikke i
|
||||
// notens egen, som er en anden, når der er sorteret eller søgt.
|
||||
const ids = ordered.map((n) => n.id);
|
||||
const from = ids.indexOf(anchor);
|
||||
const to = ids.indexOf(note.id);
|
||||
if (from !== -1 && to !== -1) {
|
||||
for (const id of ids.slice(Math.min(from, to), Math.max(from, to) + 1)) next.add(id);
|
||||
}
|
||||
} else {
|
||||
// Den, der lige er åbnet, hører med i markeringen: ellers ville et
|
||||
// ⌘-klik nummer to markere to noter og glemme den, man stod på.
|
||||
if (!next.size && selectedId) next.add(selectedId);
|
||||
next.has(note.id) ? next.delete(note.id) : next.add(note.id);
|
||||
anchor = note.id;
|
||||
}
|
||||
picked = next;
|
||||
}
|
||||
|
||||
/** Arkivér, tag frem igen, eller slet — for det, der er markeret. */
|
||||
async function bulk(change) {
|
||||
const ids = [...picked];
|
||||
if (!ids.length) return;
|
||||
if (change.delete && !confirm(t('notes.deleteMany', { count: ids.length }))) return;
|
||||
try {
|
||||
await api.noteBulk({ ids, ...change });
|
||||
picked = new Set();
|
||||
if (ids.includes(selectedId)) open(null);
|
||||
await load(query);
|
||||
} catch (e) {
|
||||
app.toast(humanMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
/** Én note lagt væk, uden at markere den først. */
|
||||
async function archiveOne(note) {
|
||||
try {
|
||||
await api.noteBulk({ ids: [note.id], archived: !note.archived_at });
|
||||
if (selectedId === note.id) open(null);
|
||||
await load(query);
|
||||
} catch (e) {
|
||||
app.toast(humanMessage(e));
|
||||
}
|
||||
}
|
||||
let query = $state('');
|
||||
|
||||
/**
|
||||
@@ -108,6 +179,7 @@
|
||||
let timer;
|
||||
|
||||
$effect(() => {
|
||||
showArchive;
|
||||
load(query);
|
||||
});
|
||||
|
||||
@@ -123,7 +195,8 @@
|
||||
|
||||
async function load(q) {
|
||||
try {
|
||||
notes = (await api.notes(q ? { q } : {})).notes ?? [];
|
||||
const params = q ? { q } : showArchive ? { archived: '1' } : {};
|
||||
notes = (await api.notes(params)).notes ?? [];
|
||||
status = 'ready';
|
||||
if (selectedId && !notes.some((n) => n.id === selectedId)) open(notes[0] ?? null);
|
||||
} catch (e) {
|
||||
@@ -243,7 +316,11 @@
|
||||
|
||||
function onDragStart(event, note) {
|
||||
dragging = note.id;
|
||||
startDrag(event, NOTE, note.id);
|
||||
// Trækker man en note, der er markeret, følger hele markeringen med. Trækker
|
||||
// man en, der ikke er, er det den ene — ellers ville et træk i en tilfældig
|
||||
// række flytte halvtreds noter, man havde glemt var markeret.
|
||||
const ids = picked.has(note.id) ? [...picked] : [note.id];
|
||||
startDrag(event, NOTE, ids.join(' '));
|
||||
}
|
||||
|
||||
async function create() {
|
||||
@@ -380,6 +457,18 @@
|
||||
<option value={o.key}>{t(o.label)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button
|
||||
class="archive-toggle"
|
||||
class:on={showArchive}
|
||||
onclick={() => { showArchive = !showArchive; picked = new Set(); }}
|
||||
aria-pressed={showArchive}
|
||||
aria-label={t('notes.showArchive')}
|
||||
title={t('notes.showArchive')}
|
||||
>
|
||||
<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="new" onclick={create} aria-label={t('notes.new')}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -404,14 +493,27 @@
|
||||
<button class="clear" onclick={() => search('')} aria-label={t('notes.clearSearch')}>×</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if query && status === 'ready'}
|
||||
{#if picked.size}
|
||||
<!-- Kun mens der er markeret noget. En bjælke, der altid står der, er en
|
||||
bjælke, der tager plads fra listen hver dag for at gøre gavn sjældent. -->
|
||||
<div class="picked-bar">
|
||||
<span>{t('notes.picked', { count: picked.size })}</span>
|
||||
<button onclick={() => bulk({ archived: !showArchive })}>
|
||||
{showArchive ? t('notes.unarchive') : t('notes.archive')}
|
||||
</button>
|
||||
<button class="danger" onclick={() => bulk({ delete: true })}>{t('notes.delete')}</button>
|
||||
<button onclick={() => (picked = new Set())}>{t('notes.clearPick')}</button>
|
||||
</div>
|
||||
{:else if query && status === 'ready'}
|
||||
<p class="found">{t('notes.found', { count: notes.length })}</p>
|
||||
{/if}
|
||||
|
||||
{#if status === 'loading'}
|
||||
<p class="hint">{t('history.loading')}</p>
|
||||
{:else if !notes.length}
|
||||
<p class="hint">{query ? t('notes.noneFound') : t('notes.noneYet')}</p>
|
||||
<p class="hint">
|
||||
{query ? t('notes.noneFound') : showArchive ? t('notes.emptyArchive') : t('notes.noneYet')}
|
||||
</p>
|
||||
{:else}
|
||||
<ul>
|
||||
{#each ordered as note (note.id)}
|
||||
@@ -420,7 +522,8 @@
|
||||
<button
|
||||
class="row"
|
||||
class:on={selectedId === note.id}
|
||||
onclick={() => open(note)}
|
||||
class:picked={picked.has(note.id)}
|
||||
onclick={(e) => rowClick(e, note)}
|
||||
draggable="true"
|
||||
ondragstart={(e) => onDragStart(e, note)}
|
||||
ondragend={() => (dragging = null)}
|
||||
@@ -442,6 +545,19 @@
|
||||
<span class="filed">{projectName(note.project_id)}</span>
|
||||
{/if}
|
||||
</button>
|
||||
<!-- É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}
|
||||
@@ -639,6 +755,96 @@
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
/* Bjælken over listen, mens der er markeret noget. Den erstatter antallet af
|
||||
fundne frem for at lægge sig oven i det: kun ét af de to er relevant ad
|
||||
gangen. */
|
||||
.picked-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s2);
|
||||
flex-wrap: wrap;
|
||||
padding: var(--s2) var(--s1);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.picked-bar button {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink);
|
||||
padding: 2px var(--s2);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.picked-bar button:hover {
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.picked-bar button.danger {
|
||||
color: var(--danger);
|
||||
border-color: var(--danger-sunken);
|
||||
}
|
||||
|
||||
/* En markeret række.
|
||||
*
|
||||
* En kant og en tone, ikke en tone alene: `--accent-sunken` er meget lys i lyst
|
||||
* tema, og tre markerede rækker var næsten ikke til at skelne fra tre, der ikke
|
||||
* var — hvilket er en dårlig ting at være i tvivl om, lige inden man trykker
|
||||
* Slet. Kanten kan ses uanset tema og uanset hvor lys tonen er. */
|
||||
.row.picked {
|
||||
background: var(--accent-sunken);
|
||||
box-shadow: inset 3px 0 0 var(--accent);
|
||||
}
|
||||
|
||||
/* Vises som stjernen: usynlig, indtil rækken er under musen, så listen er
|
||||
rolig at skanne. */
|
||||
.archive-one {
|
||||
flex: none;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: var(--ink-faint);
|
||||
border-radius: var(--radius-sm);
|
||||
opacity: 0;
|
||||
transition: opacity var(--fast) var(--ease);
|
||||
}
|
||||
|
||||
.archive-one svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.rowline:hover .archive-one,
|
||||
.archive-one:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.archive-one:hover {
|
||||
color: var(--ink);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.archive-toggle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: var(--ink-faint);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.archive-toggle svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.archive-toggle:hover {
|
||||
color: var(--ink);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.archive-toggle.on {
|
||||
color: var(--accent);
|
||||
background: var(--accent-sunken);
|
||||
}
|
||||
|
||||
.found {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--ink-faint);
|
||||
|
||||
Reference in New Issue
Block a user