| @@ -36,14 +36,20 @@ | ||
| 36 | 36 | var opts; |
| 37 | 37 | try { opts = JSON.parse(root.getAttribute('data-opts')); } catch (e) { return; } |
| 38 | 38 | |
| 39 | 39 | var a = opts; |
| 40 | - var rawCards; | |
| 41 | - try { rawCards = JSON.parse(a.cards); } catch (e) { rawCards = []; } | |
| 40 | + // cards may be an array (new saves) or a JSON string (legacy data-opts). | |
| 41 | + var rawCards = []; | |
| 42 | + if (Array.isArray(a.cards)) { | |
| 43 | + rawCards = a.cards; | |
| 44 | + } else if (typeof a.cards === 'string' && a.cards) { | |
| 45 | + try { rawCards = JSON.parse(a.cards); } catch (e) { rawCards = []; } | |
| 46 | + } | |
| 42 | 47 | |
| 43 | 48 | var deck = rawCards.map(function (c) { return { id: c.id, front: c.front, back: c.back, known: false }; }); |
| 44 | 49 | var currentIndex = 0; |
| 45 | 50 | var isFlipped = false; |
| 51 | + var keyboardShortcutsEnabled = a.enableKeyboardShortcuts !== false; | |
| 46 | 52 | |
| 47 | 53 | root.innerHTML = ''; |
| 48 | 54 | |
| 49 | 55 | var wrap = document.createElement('div'); |
| @@ -103,8 +109,11 @@ | ||
| 103 | 109 | |
| 104 | 110 | var card = document.createElement('div'); |
| 105 | 111 | card.className = 'bkbg-fc-card'; |
| 106 | 112 | card.style.height = a.cardHeight + 'px'; |
| 113 | + card.setAttribute('role', 'button'); | |
| 114 | + card.setAttribute('tabindex', '0'); | |
| 115 | + card.setAttribute('aria-label', 'Flip flashcard'); | |
| 107 | 116 | scene.appendChild(card); |
| 108 | 117 | |
| 109 | 118 | var front = document.createElement('div'); |
| 110 | 119 | front.className = 'bkbg-fc-face bkbg-fc-front'; |
| @@ -206,9 +215,9 @@ | ||
| 206 | 215 | }); |
| 207 | 216 | counterRow.appendChild(resetBtn); |
| 208 | 217 | |
| 209 | 218 | var skHint = null; |
| 210 | - if (a.showKeyboardHint) { | |
| 219 | + if (keyboardShortcutsEnabled && a.showKeyboardHint) { | |
| 211 | 220 | skHint = document.createElement('div'); |
| 212 | 221 | skHint.className = 'bkbg-fc-keyboard-hint'; |
| 213 | 222 | skHint.style.color = a.subtitleColor; |
| 214 | 223 | skHint.textContent = '← → Arrow keys to navigate • Space to flip'; |
| @@ -312,17 +321,20 @@ | ||
| 312 | 321 | progressLabel.textContent = knownCount + ' / ' + deck.length + ' known'; |
| 313 | 322 | } |
| 314 | 323 | } |
| 315 | 324 | |
| 316 | - // Keyboard nav | |
| 317 | - document.addEventListener('keydown', function (e) { | |
| 318 | - if (!root.closest(':focus-within') && document.activeElement !== document.body) { | |
| 319 | - var rect = root.getBoundingClientRect(); | |
| 320 | - if (rect.top > window.innerHeight || rect.bottom < 0) return; | |
| 321 | - } | |
| 322 | - if (e.key === 'ArrowLeft') { prevBtn.click(); } | |
| 323 | - if (e.key === 'ArrowRight') { nextBtn.click(); } | |
| 324 | - if (e.key === ' ') { e.preventDefault(); card.click(); } | |
| 325 | + function isTypingTarget(target) { | |
| 326 | + if (!target || target === document.body) return false; | |
| 327 | + if (target.isContentEditable) return true; | |
| 328 | + return !!target.closest('input, textarea, select, button, [contenteditable="true"], [role="textbox"]'); | |
| 329 | + } | |
| 330 | + | |
| 331 | + // Keyboard nav is scoped to this block so typing in other plugins is never intercepted. | |
| 332 | + wrap.addEventListener('keydown', function (e) { | |
| 333 | + if (!keyboardShortcutsEnabled || isTypingTarget(e.target)) return; | |
| 334 | + if (e.key === 'ArrowLeft') { e.preventDefault(); prevBtn.click(); } | |
| 335 | + if (e.key === 'ArrowRight') { e.preventDefault(); nextBtn.click(); } | |
| 336 | + if (e.key === ' ' || e.key === 'Spacebar') { e.preventDefault(); card.click(); } | |
| 325 | 337 | }); |
| 326 | 338 | |
| 327 | 339 | render(); |
| 328 | 340 | } |