Files
pw/templates/partials/palette.html
T
Asger Geel WeirsøeandClaude Opus 5.5 e58a11c2b4 feat: flyt sitet fra Astro til Zola
Samme indhold, URL'er og udseende, bygget af en enkelt zola-binær og Tailwind
standalone-CLI uden Node. Kanonisk domæne er asger.weirsoe.dk.

Blogindlæg ligger som page bundles med frontmatter Zola forstår, så de gamle
stempler er ugyldige, indtil scripts/omstempl.py er kørt med PUBLISH_KEY.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:47:08 +01:00

99 lines
4.4 KiB
HTML

<div id="palette" class="palette-overlay" hidden>
<div class="palette-box" role="dialog" aria-modal="true" aria-label="Kommandopalette">
<input id="palette-input" class="palette-input" type="text" placeholder="Søg sider, indlæg eller kommandoer…"
autocomplete="off" spellcheck="false" />
<ul id="palette-list" class="palette-list" role="listbox"></ul>
<div class="palette-hint">↑↓ navigér · ↵ vælg · esc luk</div>
</div>
</div>
<script>
(() => {
if (window.__palette) return; window.__palette = true;
let index = null, items = [], sel = 0;
const base = document.documentElement.dataset.base || '';
const staticActions = [
{ type: 'action', title: 'Tema: mørkt', run: () => setTheme('dark') },
{ type: 'action', title: 'Tema: lyst', run: () => setTheme('light') },
{ type: 'action', title: 'Tema: terminal', run: () => setTheme('terminal') },
{ type: 'action', title: 'Kopiér email (asger@weirsoe.dk)', run: () => navigator.clipboard?.writeText('asger@weirsoe.dk') },
{ type: 'link', title: 'LinkedIn ↗', url: 'https://www.linkedin.com/in/ag-weirs%C3%B8e/', ext: true },
{ type: 'link', title: 'Mastodon ↗', url: 'https://mastodon.social/@asgerweirsoee', ext: true },
{ type: 'link', title: 'Gitea ↗', url: 'https://gitea.weircon.dk/agw', ext: true },
{ type: 'link', title: 'RSS-feed', url: '/rss.xml' },
];
function setTheme(t) {
document.documentElement.dataset.theme = t;
localStorage.setItem('theme', t);
}
const el = (id) => document.getElementById(id);
async function ensureIndex() {
if (index) return;
try { index = await (await fetch(base + '/search-index.json')).json(); } catch { index = []; }
}
function score(q, it) {
const hay = (it.title + ' ' + (it.extra || '')).toLowerCase();
if (!q) return it.type === 'page' || it.type === 'action' ? 1 : 0.5;
if (hay.startsWith(q)) return 3;
if (hay.includes(q)) return 2;
// simpel subsekvens-match (fzf-agtig)
let i = 0; for (const c of hay) if (c === q[i]) i++;
return i === q.length ? 1 : 0;
}
function refresh() {
const q = el('palette-input').value.trim().toLowerCase();
items = [...(index || []), ...staticActions]
.map((it) => ({ it, s: score(q, it) }))
.filter((x) => x.s > 0)
.sort((a, b) => b.s - a.s)
.slice(0, 9)
.map((x) => x.it);
sel = 0;
el('palette-list').innerHTML = items.map((it, i) => {
const badge = it.type === 'post' ? 'blog' : it.type === 'action' ? 'kommando' : it.ext ? 'link' : 'side';
return `<li class="palette-item${i === sel ? ' is-selected' : ''}" data-i="${i}" role="option">
<span>${it.title.replace(/</g, '&lt;')}</span><span class="palette-badge">${badge}</span></li>`;
}).join('') || '<li class="palette-item palette-empty">Ingen resultater</li>';
}
function paint() {
el('palette-list').querySelectorAll('.palette-item').forEach((li, i) =>
li.classList.toggle('is-selected', i === sel));
}
function choose(i) {
const it = items[i]; if (!it) return;
close();
if (it.run) it.run();
else if (it.ext) window.open(it.url, '_blank', 'noopener');
else window.location.href = base + it.url;
}
async function open() {
await ensureIndex();
el('palette').hidden = false;
el('palette-input').value = '';
refresh();
el('palette-input').focus();
}
function close() { const p = el('palette'); if (p) p.hidden = true; }
document.addEventListener('keydown', (e) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); el('palette')?.hidden ? open() : close(); return; }
const p = el('palette');
if (!p || p.hidden) return;
if (e.key === 'Escape') close();
else if (e.key === 'ArrowDown') { e.preventDefault(); sel = Math.min(sel + 1, items.length - 1); paint(); }
else if (e.key === 'ArrowUp') { e.preventDefault(); sel = Math.max(sel - 1, 0); paint(); }
else if (e.key === 'Enter') { e.preventDefault(); choose(sel); }
});
document.addEventListener('input', (e) => { if (e.target?.id === 'palette-input') refresh(); });
document.addEventListener('click', (e) => {
const trigger = e.target.closest?.('[data-palette-open]');
if (trigger) { e.preventDefault(); el('palette')?.hidden ? open() : close(); return; }
if (e.target === el('palette')) close();
const li = e.target.closest?.('.palette-item[data-i]');
if (li) choose(+li.dataset.i);
});
})();
</script>