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>
99 lines
4.4 KiB
HTML
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, '<')}</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>
|