Initial commit
Deploy to GitHub Pages / build (push) Canceled after 0s
Deploy to GitHub Pages / deploy (push) Canceled after 0s

This commit is contained in:
Your Name committed 2026-10-11 18:09:42 +02:00
1 parent 1ba45f88aa
commit 9e6804c1ab
77 files changed
+3888

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
---
// Ctrl/⌘ K on the website, like in the app: jump to any page or section.
import { config } from '../config';
import { href } from '../lib/url';
interface Item { group: string; label: string; url: string; keys?: string }
const items: Item[] = [
{ group: 'Page', label: 'Home', url: href('/') },
{ group: 'Page', label: 'Features', url: href('/features/'), keys: 'tour' },
{ group: 'Page', label: 'Download', url: href('/download/'), keys: 'install get' },
{ group: 'Page', label: 'Changelog', url: href('/changelog/'), keys: 'release news versions' },
{ group: 'Page', label: 'Docs & FAQ', url: href('/docs/'), keys: 'help questions' },
{ group: 'Action', label: `Download Ink ${config.version} for Linux`, url: config.platforms.linux.url, keys: 'appimage linux get' },
{ group: 'Action', label: 'Verify the download (SHA-256)', url: href('/download/#verify'), keys: 'checksum sha256 hash' },
{ group: 'Action', label: 'Report a bug or request a feature', url: config.issues, keys: 'issue github' },
{ group: 'Features', label: 'Writing: block editor', url: href('/features/#writing'), keys: 'slash menu markdown code math katex mermaid callout table' },
{ group: 'Features', label: 'Linking: wiki links, embeds, backlinks', url: href('/features/#linking'), keys: '[[ ]] link rename' },
{ group: 'Features', label: 'Canvas', url: href('/features/#canvas'), keys: 'json canvas arrows groups export png svg pdf' },
{ group: 'Features', label: 'Kanban boards', url: href('/features/#boards'), keys: 'board columns cards' },
{ group: 'Features', label: 'Tags, search, graph, daily notes', url: href('/features/#organising'), keys: 'tag search palette graph daily templates favorites' },
{ group: 'Features', label: 'Version history, trash, export', url: href('/features/#safety'), keys: 'history restore trash export' },
{ group: 'Features', label: 'Themes, focus mode, spellcheck', url: href('/features/#comfort'), keys: 'dark light theme focus outline text size' },
{ group: 'Docs', label: 'Where are my notes stored?', url: href('/docs/#storage'), keys: 'folder files md .notes' },
{ group: 'Docs', label: 'Obsidian compatibility', url: href('/docs/#obsidian'), keys: 'vault obsidian canvas kanban' },
{ group: 'Docs', label: 'Keyboard shortcuts', url: href('/docs/#shortcuts'), keys: 'keys hotkeys ctrl' },
{ group: 'Docs', label: 'Privacy', url: href('/docs/#privacy'), keys: 'tracking analytics cookies data' },
];
---
<dialog class="palette" data-palette aria-label="Jump to a page or section">
<div class="p-head">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="6.5" /><path d="M16 16l4 4" /></svg>
<input
type="text"
role="combobox"
aria-expanded="true"
aria-controls="palette-list"
aria-autocomplete="list"
aria-label="Search pages and sections"
placeholder="Jump to…"
autocomplete="off"
spellcheck="false"
data-palette-input
/>
<kbd>Esc</kbd>
</div>
<ul id="palette-list" role="listbox" aria-label="Results" data-palette-list>
{items.map((it, i) => (
<li role="option" id={`palette-opt-${i}`} aria-selected="false" data-url={it.url} data-text={`${it.group} ${it.label} ${it.keys ?? ''}`.toLowerCase()}>
<span class="g">{it.group}</span>
<span class="l">{it.label}</span>
<span class="enter" aria-hidden="true">↵</span>
</li>
))}
</ul>
<p class="empty" hidden data-palette-empty>Nothing matches. Try “canvas”, “shortcuts” or “download”.</p>
<div class="p-foot" aria-hidden="true">
<span><kbd>↑</kbd><kbd>↓</kbd> move</span><span><kbd>↵</kbd> open</span><span><kbd>Esc</kbd> close</span>
</div>
</dialog>
<style>
.palette {
width: min(100% - 2rem, 600px); max-height: min(70vh, 560px); padding: 0; margin: 12vh auto auto;
border: 1px solid var(--line-2); border-radius: 16px; background: var(--surface); color: var(--ink);
box-shadow: var(--shadow); overflow: hidden; flex-direction: column;
}
.palette[open] { display: flex; animation: pop 0.22s var(--ease); }
.palette::backdrop { background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(3px); animation: fade 0.22s var(--ease); }
@keyframes pop { from { opacity: 0; transform: translateY(-8px) scale(0.98); } }
@keyframes fade { from { opacity: 0; } }
.p-head { display: flex; align-items: center; gap: 0.7rem; padding: 0.95rem 1.1rem; border-bottom: 1px solid var(--line); }
.p-head svg { width: 18px; height: 18px; fill: none; stroke: var(--muted); stroke-width: 1.8; stroke-linecap: round; flex: none; }
input { flex: 1; min-width: 0; font: 400 1.05rem/1.4 var(--f-ui); color: var(--ink); background: none; border: 0; outline: none; }
input::placeholder { color: var(--muted); }
ul { list-style: none; margin: 0; padding: 0.4rem; overflow-y: auto; }
li {
display: flex; align-items: center; gap: 0.75rem; padding: 0.62rem 0.75rem; border-radius: 10px;
cursor: pointer; color: var(--ink-2);
}
li[hidden] { display: none; }
li[aria-selected='true'] { background: var(--blush-soft); color: var(--ink); }
.g { font: 500 0.7rem/1 var(--f-mono); text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); width: 4.8rem; flex: none; }
.l { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.enter { color: var(--accent-text); opacity: 0; font-size: 0.9rem; }
li[aria-selected='true'] .enter { opacity: 1; }
.empty { padding: 1.5rem 1.1rem; color: var(--muted); }
.p-foot { display: flex; gap: 1.1rem; padding: 0.6rem 1.1rem; border-top: 1px solid var(--line); font-size: 0.78rem; color: var(--muted); }
.p-foot kbd { font-size: 0.7rem; padding: 0.2rem 0.35rem; margin-right: 0.2rem; }
@media (max-width: 520px) {
.palette { margin-top: 1rem; }
.g { display: none; }
.p-foot { display: none; }
}
</style>
<script>
const dlg = document.querySelector<HTMLDialogElement>('[data-palette]')!;
const input = dlg.querySelector<HTMLInputElement>('[data-palette-input]')!;
const opts = [...dlg.querySelectorAll<HTMLLIElement>('[role="option"]')];
const empty = dlg.querySelector<HTMLElement>('[data-palette-empty]')!;
let active = 0;
const visible = () => opts.filter((o) => !o.hidden);
function select(i: number) {
const list = visible();
if (!list.length) { input.removeAttribute('aria-activedescendant'); return; }
active = (i + list.length) % list.length;
opts.forEach((o) => o.setAttribute('aria-selected', 'false'));
const el = list[active];
el.setAttribute('aria-selected', 'true');
input.setAttribute('aria-activedescendant', el.id);
el.scrollIntoView({ block: 'nearest' });
}
function filter() {
const words = input.value.toLowerCase().split(/\s+/).filter(Boolean);
for (const o of opts) o.hidden = !words.every((w) => o.dataset.text!.includes(w));
empty.hidden = visible().length > 0;
select(0);
}
function go(o: HTMLElement | undefined) {
if (!o) return;
dlg.close();
location.href = o.dataset.url!;
}
function open() {
if (dlg.open) return;
input.value = '';
filter();
dlg.showModal();
input.focus();
}
input.addEventListener('input', filter);
input.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') { e.preventDefault(); select(active + 1); }
else if (e.key === 'ArrowUp') { e.preventDefault(); select(active - 1); }
else if (e.key === 'Enter') { e.preventDefault(); go(visible()[active]); }
});
opts.forEach((o) => {
o.addEventListener('click', () => go(o));
o.addEventListener('pointermove', () => { const i = visible().indexOf(o); if (i !== active) select(i); });
});
// Click on the backdrop closes.
dlg.addEventListener('click', (e) => { if (e.target === dlg) dlg.close(); });
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
e.preventDefault();
dlg.open ? dlg.close() : open();
}
});
document.querySelectorAll('[data-palette-open]').forEach((b) => b.addEventListener('click', open));
</script>