119 lines
6.6 KiB
HTML
119 lines
6.6 KiB
HTML
<!doctype html>
|
|
<html lang="da" data-theme="dark">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta name="theme-color" content="#0b0f17" media="(prefers-color-scheme: dark)" />
|
|
<meta name="theme-color" content="#f6f8fb" media="(prefers-color-scheme: light)" />
|
|
<link rel="icon" href="/static/icons/icon.svg" type="image/svg+xml" />
|
|
<title>{% block title %}Weircon{% endblock %}</title>
|
|
<!-- no-flash: set theme before any stylesheet paints (key 'theme', shared with the theme JS) -->
|
|
<script>(()=>{try{var t=localStorage.getItem('theme')||(matchMedia('(prefers-color-scheme: light)').matches?'light':'dark');document.documentElement.dataset.theme=t}catch(e){document.documentElement.dataset.theme='dark'}})()</script>
|
|
<!-- shared design system (SSOT: agw/Weircon-wind) hosted on assets.weircon.dk -->
|
|
<link rel="stylesheet" href="https://assets.weircon.dk/css/weirsoe-theme.css" />
|
|
<link rel="stylesheet" href="/static/portal.css" />
|
|
<script defer src="https://assets.weircon.dk/js/weirsoe-theme.js"></script>
|
|
</head>
|
|
<body>
|
|
<header class="wt-header">
|
|
<nav class="wt-nav">
|
|
<a class="wt-brand" href="/">weircon<span class="wt-brand-dot">.</span></a>
|
|
<div class="wt-nav-links">
|
|
<a class="wt-nav-link" href="/">Forside</a>
|
|
{% if is_admin %}<a class="wt-nav-link" href="/admin">Admin</a>{% endif %}
|
|
{% if user %}<span class="who">{{ user.name }}</span> <a class="wt-nav-link" href="/auth/logout">Log ud</a>{% endif %}
|
|
<button type="button" data-theme-toggle class="theme-toggle" title="Skift mellem lyst og mørkt tema" aria-label="Skift tema">
|
|
<svg class="sun-and-moon" aria-hidden="true" width="22" height="22" viewBox="0 0 24 24">
|
|
<mask class="moon" id="moon-mask"><rect x="0" y="0" width="100%" height="100%" fill="white" /><circle cx="24" cy="10" r="6" fill="black" /></mask>
|
|
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
|
|
<g class="sun-beams" stroke="currentColor">
|
|
<line x1="12" y1="1" x2="12" y2="3" /><line x1="12" y1="21" x2="12" y2="23" />
|
|
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /><line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
<line x1="1" y1="12" x2="3" y2="12" /><line x1="21" y1="12" x2="23" y2="12" />
|
|
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /><line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
</g>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
<main class="wt-main">
|
|
{% block body %}{% endblock %}
|
|
</main>
|
|
<footer class="wt-footer">
|
|
<div class="wt-footer-inner">
|
|
Driftet af Asger Weirsøe · self-hosted på Proxmox · <a href="mailto:asger@weirsoe.dk">asger@weirsoe.dk</a>
|
|
</div>
|
|
</footer>
|
|
|
|
<!-- ⌘K / Ctrl+K kommandopalette -->
|
|
<div id="palette" class="wt-palette" hidden>
|
|
<div class="wt-palette-box" role="dialog" aria-modal="true" aria-label="Kommandopalette">
|
|
<input id="palette-input" class="wt-palette-input" type="text" placeholder="Søg tjenester, sider eller kommandoer…" autocomplete="off" spellcheck="false" />
|
|
<ul id="palette-list" class="wt-palette-list" role="listbox"></ul>
|
|
<div class="wt-palette-hint">↑↓ navigér · ↵ vælg · esc luk · ⌘/Ctrl K åbn</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
(function () {
|
|
if (window.__palette) return; window.__palette = true;
|
|
var index = null, items = [], sel = 0;
|
|
var staticActions = [
|
|
{ type:'action', title:'Tema: mørkt', run:function(){ setTheme('dark'); } },
|
|
{ type:'action', title:'Tema: lyst', run:function(){ setTheme('light'); } },
|
|
{ type:'action', title:'Log ud', url:'/auth/logout' },
|
|
];
|
|
function setTheme(t){ document.documentElement.dataset.theme = t; localStorage.setItem('theme', t); }
|
|
function el(id){ return document.getElementById(id); }
|
|
function ensureIndex(){
|
|
if (index) return Promise.resolve();
|
|
return fetch('/api/search-index').then(function(r){ return r.json(); })
|
|
.then(function(j){ index = j; }).catch(function(){ index = []; });
|
|
}
|
|
function score(q, it){
|
|
var hay = (it.title + ' ' + (it.extra || '')).toLowerCase();
|
|
if (!q) return (it.type === 'page' || it.type === 'action') ? 1 : 0.5;
|
|
if (hay.indexOf(q) === 0) return 3;
|
|
if (hay.indexOf(q) > -1) return 2;
|
|
var i = 0; for (var k = 0; k < hay.length; k++) if (hay[k] === q[i]) i++;
|
|
return i === q.length ? 1 : 0;
|
|
}
|
|
function refresh(){
|
|
var q = el('palette-input').value.trim().toLowerCase();
|
|
items = (index || []).concat(staticActions)
|
|
.map(function(it){ return { it: it, s: score(q, it) }; })
|
|
.filter(function(x){ return x.s > 0; })
|
|
.sort(function(a, b){ return b.s - a.s; })
|
|
.slice(0, 9).map(function(x){ return x.it; });
|
|
sel = 0;
|
|
el('palette-list').innerHTML = items.map(function(it, i){
|
|
var badge = it.type === 'app' ? 'app' : it.type === 'action' ? 'kommando' : it.ext ? 'link' : 'side';
|
|
return '<li class="wt-palette-item' + (i === sel ? ' is-selected' : '') + '" data-i="' + i + '" role="option">' +
|
|
'<span>' + String(it.title).replace(/</g, '<') + '</span><span class="wt-palette-badge">' + badge + '</span></li>';
|
|
}).join('') || '<li class="wt-palette-item wt-palette-empty">Ingen resultater</li>';
|
|
}
|
|
function paint(){ var ls = el('palette-list').querySelectorAll('.wt-palette-item');
|
|
for (var i = 0; i < ls.length; i++) ls[i].classList.toggle('is-selected', i === sel); }
|
|
function choose(i){ var 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 = it.url; }
|
|
function open(){ ensureIndex().then(function(){ el('palette').hidden = false; el('palette-input').value = ''; refresh(); el('palette-input').focus(); }); }
|
|
function close(){ var p = el('palette'); if (p) p.hidden = true; }
|
|
document.addEventListener('keydown', function(e){
|
|
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k'){ e.preventDefault(); el('palette').hidden ? open() : close(); return; }
|
|
var 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', function(e){ if (e.target && e.target.id === 'palette-input') refresh(); });
|
|
document.addEventListener('click', function(e){
|
|
if (e.target === el('palette')) close();
|
|
var li = e.target.closest && e.target.closest('.wt-palette-item[data-i]');
|
|
if (li) choose(+li.dataset.i);
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|