// Keyboard shortcuts, the shortcuts-help modal, and the mobile nav toggle,
// ported from mnw.js:504-572.
/** Toggle the keyboard-shortcuts help overlay. Ported from mnw.js:531. */
export function toggleShortcutsHelp(): void {
const existing = document.getElementById('shortcuts-help');
if (existing) {
existing.remove();
return;
}
const overlay = document.createElement('div');
overlay.id = 'shortcuts-help';
overlay.className = 'modal-overlay';
overlay.style.display = 'flex';
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
overlay.innerHTML =
'
' +
'' +
'
' +
'| Cmd+K | Search |
' +
'| ? | Show this help |
' +
'| Esc | Close modal / overlay |
' +
'| Cmd+S | Save current form |
' +
'
' +
'
';
// Wire the close button via JS, not inline onclick, the CSP has no
// script-src 'unsafe-inline' so a generated inline handler is dead.
const closeBtn = overlay.querySelector('.modal-close');
if (closeBtn) closeBtn.onclick = () => overlay.remove();
document.body.appendChild(overlay);
}
/**
* Whether this page declares its shortcuts rather than hard-coding them.
*
* `e0c0d991`. A described document carries `quasi-webview`'s chrome: one hidden
* button per binding, marked `data-chrome`, and `?` is bound to the described
* listing. This file loads on described and undescribed pages alike -- both are
* served from one shell -- so without the check both handlers fire on `?` and
* the reader gets two overlays.
*
* The described page wins, because its listing is derived from the bindings and
* this one is a hand-written copy. When the last template converts, this
* function and the overlay below it go with it.
*/
function describedChrome(): boolean {
return document.querySelector('[data-chrome]') !== null;
}
/** Install global keyboard shortcuts + the nav toggle. Ported from mnw.js:504. */
export function initKeyboard(): void {
document.addEventListener('keydown', (e) => {
const tag = document.activeElement?.tagName;
const inInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
if (e.key === 'Escape') {
document.querySelector('.modal-overlay')?.remove();
}
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
e.preventDefault();
const form = document.activeElement?.closest('form');
form?.querySelector('button[type="submit"]')?.click();
}
if ((e.metaKey || e.ctrlKey) && (e.key === 'k' || e.key === 'K')) {
e.preventDefault();
const searchInput = document.querySelector('.chrome-search input');
if (searchInput) {
searchInput.focus();
searchInput.select();
}
}
if (e.key === '?' && !inInput && !e.metaKey && !e.ctrlKey && !describedChrome()) {
e.preventDefault();
toggleShortcutsHelp();
}
});
document.addEventListener('click', (e) => {
// The ids and classes are `quasi-webview`'s: the header is a described
// `Chrome::band` and this script reads what that renderer emits.
const toggle = document.getElementById('quasi-disclose') as HTMLInputElement | null;
if (toggle && toggle.checked && (e.target as Element | null)?.closest('.chrome-nav a')) {
toggle.checked = false;
}
});
}