Skip to main content

max / makenotwork

2.9 KB · 76 lines History Blame Raw
1 // Keyboard shortcuts, the shortcuts-help modal, and the mobile nav toggle,
2 // ported from mnw.js:504-572.
3
4 /** Toggle the keyboard-shortcuts help overlay. Ported from mnw.js:531. */
5 export function toggleShortcutsHelp(): void {
6 const existing = document.getElementById('shortcuts-help');
7 if (existing) {
8 existing.remove();
9 return;
10 }
11
12 const overlay = document.createElement('div');
13 overlay.id = 'shortcuts-help';
14 overlay.className = 'modal-overlay';
15 overlay.style.display = 'flex';
16 overlay.onclick = (e) => {
17 if (e.target === overlay) overlay.remove();
18 };
19 overlay.innerHTML =
20 '<div class="modal-content" style="max-width: 420px; padding: 2rem;">' +
21 '<div class="modal-header" style="margin-bottom: 1rem;">' +
22 '<h2>Keyboard Shortcuts</h2>' +
23 '<button type="button" class="modal-close" aria-label="Close">&times;</button>' +
24 '</div>' +
25 '<table style="width: 100%; font-size: 0.9rem;">' +
26 '<tr><td style="padding: 0.3rem 0;"><kbd>Cmd+K</kbd></td><td>Search</td></tr>' +
27 '<tr><td style="padding: 0.3rem 0;"><kbd>?</kbd></td><td>Show this help</td></tr>' +
28 '<tr><td style="padding: 0.3rem 0;"><kbd>Esc</kbd></td><td>Close modal / overlay</td></tr>' +
29 '<tr><td style="padding: 0.3rem 0;"><kbd>Cmd+S</kbd></td><td>Save current form</td></tr>' +
30 '</table>' +
31 '</div>';
32
33 // Wire the close button via JS, not inline onclick — the CSP has no
34 // script-src 'unsafe-inline' so a generated inline handler is dead.
35 const closeBtn = overlay.querySelector<HTMLElement>('.modal-close');
36 if (closeBtn) closeBtn.onclick = () => overlay.remove();
37
38 document.body.appendChild(overlay);
39 }
40
41 /** Install global keyboard shortcuts + the nav toggle. Ported from mnw.js:504. */
42 export function initKeyboard(): void {
43 document.addEventListener('keydown', (e) => {
44 const tag = document.activeElement?.tagName;
45 const inInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
46
47 if (e.key === 'Escape') {
48 document.querySelector('.modal-overlay')?.remove();
49 }
50 if ((e.metaKey || e.ctrlKey) && e.key === 's') {
51 e.preventDefault();
52 const form = document.activeElement?.closest('form');
53 form?.querySelector<HTMLElement>('button[type="submit"]')?.click();
54 }
55 if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')) {
56 e.preventDefault();
57 const searchInput = document.getElementById('header-search-input') as HTMLInputElement | null;
58 if (searchInput) {
59 searchInput.focus();
60 searchInput.select();
61 }
62 }
63 if (e.key === '?' && !inInput && !e.metaKey && !e.ctrlKey) {
64 e.preventDefault();
65 toggleShortcutsHelp();
66 }
67 });
68
69 document.addEventListener('click', (e) => {
70 const toggle = document.getElementById('nav-toggle') as HTMLInputElement | null;
71 if (toggle && toggle.checked && (e.target as Element | null)?.closest('.nav-links a, .nav-links .btn--link')) {
72 toggle.checked = false;
73 }
74 });
75 }
76