keys.templ RawHistoryFolder
1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5// Keys is the keyboard map. ermite.js turns keys into k-<name> events on
6// <body>; this element (hx-on + hx-live helpers) is the only place that
7// reacts to them.
8templ Keys() {
9 <div
10 id="keys"
11 hidden
12 hx-on="k-j from:body -> ((d) => {
13 if (q('dialog[open]').count) return;
14 let rows = q('[data-rows] > :not([hidden])').arr();
15 if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));
16 let i = rows.findIndex(r => r.hasAttribute('data-cursor'));
17 let n = rows[Math.max(0, Math.min(rows.length - 1, i + d))];
18 q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' })
19 })(1);
20 k-k from:body -> ((d) => {
21 if (q('dialog[open]').count) return;
22 let rows = q('[data-rows] > :not([hidden])').arr();
23 if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));
24 let i = rows.findIndex(r => r.hasAttribute('data-cursor'));
25 let n = rows[Math.max(0, Math.min(rows.length - 1, i + d))];
26 q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' })
27 })(-1);
28 k-top from:body -> q('[data-rows] > :not([hidden])').arr()[0]?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 0), q('#main').arr()[0].scrollTo(0, 0));
29 k-bottom from:body -> q('[data-rows] > :not([hidden])').arr().at(-1)?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 1e9), q('#main').arr()[0].scrollTo(0, 1e9));
30 k-open from:body -> q('dialog[open]').count || document.querySelector('[data-cursor] a')?.click();
31 k-esc from:body -> q('dialog[open]').arr().forEach(d => d.close());
32 k-help from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-help').arr()[0].show();
33 k-refs from:body -> q('#panel-refs').count && (q('dialog[open]').arr().forEach(d => d.close()), q('#panel-refs').arr()[0].show(), q('#refs-filter').arr()[0].focus());
34 k-cmd from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-cmd').arr()[0].show(); q('#cmd-q').arr()[0].focus();
35 k-filter from:body -> q('[data-filter]').arr()[0]?.focus();
36 k-gh from:body -> document.querySelector('.top a[data-g=h]')?.click();
37 k-gt from:body -> document.querySelector('.secs [data-key=t]')?.click();
38 k-gc from:body -> document.querySelector('.secs [data-key=c]')?.click();
39 k-gr from:body -> document.querySelector('.secs [data-key=r]')?.click();
40 k-s from:body -> document.querySelector('.secs [data-key=s]')?.click();
41 k-t from:body -> document.querySelector('.secs [data-key=t]')?.click();
42 k-c from:body -> document.querySelector('.secs [data-key=c]')?.click();
43 k-r from:body -> document.querySelector('.secs [data-key=r]')?.click();
44 k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');
45 k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');
46 k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);
47 k-permalink from:body -> {
48 let b = document.body.dataset;
49 if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }
50 history.replaceState(null, '', location.pathname.replace('/' + b.ref + '/', '/' + b.sha + '/') + location.hash);
51 navigator.clipboard?.writeText(location.href);
52 b.flash = 'permalink ' + b.sha.slice(0, 7) + ' copied'; await timeout('2s'); b.flash = ''
53 }"
54 ></div>
55}