From 6a7332494d21d302e991c4f4cb3ae37a2d0624ef Mon Sep 17 00:00:00 2001 From: jedarden Date: Mon, 1 Jun 2026 08:09:51 -0400 Subject: [PATCH] feat(pdftract-2wqir): implement keyboard shortcuts in inspector Added comprehensive keyboard shortcuts for the inspector frontend: - ArrowLeft/Right: navigate to previous/next page - ArrowUp/Down: scroll within page - /: focus search input - Esc: blur input / close help overlay - ?: show/hide keyboard shortcuts help overlay - 1-9: toggle overlay layers (1=spans, 2=blocks, ..., 9=diff) Changes: - app.js: extended setupKeyboard() with new handlers, added prevPage()/nextPage() wrappers, scrollPage() and toggleHelp() helpers, setupHelp() for button wiring - index.html: added ? button and help overlay with all shortcuts listed - style.css: added styles for .btn-help, .help-overlay, .help-content, and related classes Acceptance criteria met: - ArrowLeft/Right navigation works - / focuses search input - 1-8 toggle overlays with visual feedback - Esc blurs input and closes help - ? shows help overlay listing all shortcuts See: notes/pdftract-2wqir.md for verification details. --- .../pdftract-cli/src/inspect/frontend/app.js | 98 +++++++++++++++++-- .../src/inspect/frontend/index.html | 66 +++++++++++++ .../src/inspect/frontend/style.css | 11 +++ 3 files changed, 167 insertions(+), 8 deletions(-) diff --git a/crates/pdftract-cli/src/inspect/frontend/app.js b/crates/pdftract-cli/src/inspect/frontend/app.js index 48a449d..ab0aaf4 100644 --- a/crates/pdftract-cli/src/inspect/frontend/app.js +++ b/crates/pdftract-cli/src/inspect/frontend/app.js @@ -16,7 +16,7 @@ let scrollSync=true; let matchedSpans=[]; let currentMatchIndex=-1; -function init(){loadLayerState();setupKeyboard();setupToggles();setupSearch();setupNav();setupComparisonMode();loadFragment()} +function init(){loadLayerState();setupKeyboard();setupToggles();setupSearch();setupNav();setupComparisonMode();setupHelp();loadFragment()} async function loadDocument(){ const res=await fetch('/api/document'); @@ -427,6 +427,33 @@ function setupComparisonMode(){ } } +function setupHelp(){ + const helpBtn=document.getElementById('btn-help'); + const helpOverlay=document.getElementById('help-overlay'); + const closeBtn=document.querySelector('.help-close'); + + if(helpBtn){ + helpBtn.addEventListener('click',()=>{ + toggleHelp(true); + }); + } + + if(closeBtn){ + closeBtn.addEventListener('click',()=>{ + toggleHelp(false); + }); + } + + // Close on overlay click (outside content) + if(helpOverlay){ + helpOverlay.addEventListener('click',e=>{ + if(e.target===helpOverlay){ + toggleHelp(false); + } + }); + } +} + function showComparisonMode(show){ const diffBtn=document.getElementById('btn-diff'); const compareControls=document.querySelector('.comparison-controls'); @@ -443,25 +470,53 @@ function showComparisonMode(show){ function setupKeyboard(){ document.addEventListener('keydown',e=>{ const searchInput=document.getElementById('search-input'); - if(e.target.tagName==='INPUT'&&e.target!==searchInput)return; + const helpOverlay=document.getElementById('help-overlay'); + + // Close help overlay on Escape + if(e.key==='Escape'&&helpOverlay&&!helpOverlay.hidden){ + e.preventDefault(); + toggleHelp(false); + return; + } + + // Skip keyboard shortcuts when typing in inputs (except search) + if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA'){ + // Allow Escape to blur input + if(e.key==='Escape'){ + e.preventDefault(); + e.target.blur(); + if(e.target===searchInput)clearSearch(); + } + return; + } + if(e.key==='ArrowLeft'){ e.preventDefault(); navigatePage(-1) }else if(e.key==='ArrowRight'){ e.preventDefault(); navigatePage(1) + }else if(e.key==='ArrowUp'){ + // Scroll up within page + e.preventDefault(); + scrollPage(-1) + }else if(e.key==='ArrowDown'){ + // Scroll down within page + e.preventDefault(); + scrollPage(1) }else if(e.key==='/'){ - if(e.target!==searchInput){ - e.preventDefault(); - searchInput.focus() - } + e.preventDefault(); + searchInput.focus() + }else if(e.key==='?'){ + e.preventDefault(); + toggleHelp() }else if(e.key>='1'&&e.key<='9'){ const idx=parseInt(e.key)-1; const layer=LAYERS[idx]; if(layer)toggleLayer(layer) - }else if(e.key==='Escape'&&e.target===searchInput){ + }else if(e.key==='Escape'){ e.preventDefault(); - clearSearch() + document.activeElement.blur() } }) } @@ -570,6 +625,14 @@ function navigatePage(delta){ if(newPage>=0&&newPage=totalPages-1 @@ -633,6 +696,25 @@ function updateActiveThumbnail(){ }); } +function scrollPage(delta){ + const container=document.getElementById('canvas-container'); + if(container){ + const scrollAmount=100; + container.scrollTop+=delta*scrollAmount + } +} + +function toggleHelp(show){ + const overlay=document.getElementById('help-overlay'); + if(!overlay)return; + overlay.hidden=show===undefined?!overlay.hidden:!show; + if(!overlay.hidden){ + // Focus on close button for accessibility + const closeBtn=overlay.querySelector('.help-close'); + if(closeBtn)closeBtn.focus(); + } +} + function updateFragment(){ history.replaceState(null,'',`#page=${currentPage}`) } diff --git a/crates/pdftract-cli/src/inspect/frontend/index.html b/crates/pdftract-cli/src/inspect/frontend/index.html index 6fe26a5..46e2d8a 100644 --- a/crates/pdftract-cli/src/inspect/frontend/index.html +++ b/crates/pdftract-cli/src/inspect/frontend/index.html @@ -18,6 +18,7 @@ +
@@ -56,6 +57,71 @@ Sync scroll
+ diff --git a/crates/pdftract-cli/src/inspect/frontend/style.css b/crates/pdftract-cli/src/inspect/frontend/style.css index 77079fd..bbb11ba 100644 --- a/crates/pdftract-cli/src/inspect/frontend/style.css +++ b/crates/pdftract-cli/src/inspect/frontend/style.css @@ -63,3 +63,14 @@ html[data-layers~="spans"] .layer-spans,html[data-layers~="blocks"] .layer-block .diff-removed{fill:none;stroke:#dc3545;stroke-width:2;stroke-dasharray:4,2;opacity:.7} .diff-added{fill:none;stroke:#28a745;stroke-width:2;stroke-dasharray:4,2;opacity:.7} .diff-changed{fill:none;stroke:#ffc107;stroke-width:2;stroke-dasharray:4,2;opacity:.7} +.btn-help{width:32px;height:32px;padding:0;font-size:16px;font-weight:600;border-radius:50%} +.help-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);z-index:2000;display:flex;align-items:center;justify-content:center} +.help-content{background:#fff;border-radius:8px;box-shadow:0 4px 20px rgba(0,0,0,.2);max-width:500px;width:90%;max-height:80vh;overflow:auto;padding:0;position:relative} +.help-close{position:absolute;top:12px;right:12px;width:32px;height:32px;border:none;background:transparent;font-size:24px;cursor:pointer;color:#666;border-radius:4px;display:flex;align-items:center;justify-content:center} +.help-close:hover{background:#f0f0f0;color:#333} +.help-content h2{margin:0;padding:20px 20px 10px;border-bottom:1px solid #ddd;font-size:18px} +.help-shortcuts{padding:20px;display:flex;flex-direction:column;gap:8px} +.help-shortcut{display:flex;align-items:center;gap:12px;padding:4px 0} +.help-key{min-width:32px;padding:4px 8px;background:#f5f5f5;border:1px solid #ddd;border-radius:4px;font-family:ui-monospace,monospace;font-size:13px;text-align:center;display:inline-block} +.help-desc{font-size:14px;color:#333} +.help-divider{border-top:1px solid #eee;margin:8px 0}