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.
This commit is contained in:
parent
9a38117865
commit
6a7332494d
3 changed files with 167 additions and 8 deletions
|
|
@ -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)loadPage(newPage)
|
||||
}
|
||||
|
||||
function prevPage(){
|
||||
navigatePage(-1)
|
||||
}
|
||||
|
||||
function nextPage(){
|
||||
navigatePage(1)
|
||||
}
|
||||
|
||||
function updateNavState(){
|
||||
document.getElementById('btn-prev').disabled=currentPage<=0;
|
||||
document.getElementById('btn-next').disabled=currentPage>=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}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<button id="btn-next" class="btn" aria-label="Next page">Next →</button>
|
||||
<input id="search-input" type="search" placeholder="Search spans... (press /)" aria-label="Search spans" class="search-input">
|
||||
<span id="match-count" class="match-count"></span>
|
||||
<button id="btn-help" class="btn btn-help" aria-label="Show keyboard shortcuts" title="Keyboard shortcuts (?)">?</button>
|
||||
<div class="toggles">
|
||||
<button class="layer-toggle" data-layer="spans" aria-label="Toggle spans layer">1 Spans</button>
|
||||
<button class="layer-toggle" data-layer="blocks" aria-label="Toggle blocks layer">2 Blocks</button>
|
||||
|
|
@ -56,6 +57,71 @@ Sync scroll
|
|||
</aside>
|
||||
</div>
|
||||
<div id="tooltip" class="tooltip" hidden></div>
|
||||
<div id="help-overlay" class="help-overlay" hidden>
|
||||
<div class="help-content">
|
||||
<button class="help-close" aria-label="Close help">×</button>
|
||||
<h2>Keyboard Shortcuts</h2>
|
||||
<div class="help-shortcuts">
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">←</span><span class="help-key">→</span>
|
||||
<span class="help-desc">Previous / Next page</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">↑</span><span class="help-key">↓</span>
|
||||
<span class="help-desc">Scroll within page</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">/</span>
|
||||
<span class="help-desc">Focus search input</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">Esc</span>
|
||||
<span class="help-desc">Blur input / Close help</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">?</span>
|
||||
<span class="help-desc">Show / Hide this help</span>
|
||||
</div>
|
||||
<div class="help-divider"></div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">1</span>
|
||||
<span class="help-desc">Toggle Spans layer</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">2</span>
|
||||
<span class="help-desc">Toggle Blocks layer</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">3</span>
|
||||
<span class="help-desc">Toggle Columns layer</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">4</span>
|
||||
<span class="help-desc">Toggle Reading Order layer</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">5</span>
|
||||
<span class="help-desc">Toggle Confidence Heatmap layer</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">6</span>
|
||||
<span class="help-desc">Toggle OCR layer</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">7</span>
|
||||
<span class="help-desc">Toggle MCID layer</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">8</span>
|
||||
<span class="help-desc">Toggle Anchors layer</span>
|
||||
</div>
|
||||
<div class="help-shortcut">
|
||||
<span class="help-key">9</span>
|
||||
<span class="help-desc">Toggle Diff overlay (comparison mode)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue