diff --git a/Web/templates/library_table.html b/Web/templates/library_table.html
index 7a45c31..98cbc27 100644
--- a/Web/templates/library_table.html
+++ b/Web/templates/library_table.html
@@ -843,6 +843,8 @@
let keyboardScanBuffer = '';
let keyboardLastKeyAt = 0;
const KEYBOARD_SCAN_INTERCHAR_MS = 100; // max time between keystrokes to consider them one scan
+ const physicalScanQueue = [];
+ let keyboardScanProcessing = false;
let editLibraryState = {
itemId: '',
seriesGroupId: '',
@@ -1188,38 +1190,49 @@
// Only active when explicitly enabled
if (!keyboardScannerEnabled) return;
- // Ignore if focus is in an input/textarea/contenteditable to avoid interfering with typing
- const active = document.activeElement;
- if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.isContentEditable)) return;
-
const now = Date.now();
// If Enter/Return pressed -> finalize buffer
if (e.key === 'Enter') {
+ e.preventDefault();
const code = keyboardScanBuffer.trim();
keyboardScanBuffer = '';
keyboardLastKeyAt = 0;
if (!code) return;
- // Process exactly like a camera scan - centralized logic handles the rest!
- handleScanSuccess(code);
+ // Scanner keystrokes must not be submitted into the focused form field.
+ processPhysicalScan(code);
return;
}
- // Only accept common printable characters; ignore modifier keys
- if (e.key.length === 1) {
+ // Physical scanners in this workflow emit numeric codes only.
+ if (e.key.length === 1 && /\d/.test(e.key)) {
// If time gap too big, start new buffer
if (keyboardLastKeyAt && (now - keyboardLastKeyAt) > KEYBOARD_SCAN_INTERCHAR_MS) {
keyboardScanBuffer = '';
}
keyboardScanBuffer += e.key;
keyboardLastKeyAt = now;
- // Prevent default so scanner input doesn't accidentally move focus or trigger shortcuts
+ // Prevent default so scanner input is captured even while an input has focus.
e.preventDefault();
}
}
- function handleScanSuccess(decodedText) {
+ async function processPhysicalScan(code) {
+ physicalScanQueue.push(code);
+ if (keyboardScanProcessing) return;
+
+ keyboardScanProcessing = true;
+ try {
+ while (physicalScanQueue.length > 0) {
+ await handleScanSuccess(physicalScanQueue.shift());
+ }
+ } finally {
+ keyboardScanProcessing = false;
+ }
+ }
+
+ async function handleScanSuccess(decodedText) {
const scannedCode = typeof normalizeScannedCode === "function" ? normalizeScannedCode(decodedText) : decodedText;
if (!scannedCode) return;
@@ -1235,26 +1248,27 @@
// 1. Modus: Nur Rückgabe
if (mode === 'return_only') {
- returnByCode(scannedCode);
+ await returnByCode(scannedCode);
return;
}
// 2. Modus: Nur Ausweis
if (mode === 'card_only') {
setActiveStudentCard(scannedCode);
- setScanStatus(`Ausweis gesetzt: ${activeStudentCardId}`, 'ok');
+ setScanStatus(`Ausweis gesetzt: ${activeStudentCardId}. Bitte den nächsten Ausweis scannen.`, 'ok');
+ showSmallConfirm(`Ausweis erkannt: ${activeStudentCardId}. Sie können den nächsten scannen.`, 'ok');
return;
}
// 3. Modus: Schnellmodus (1x Ausweis, 1x Buch)
if (mode === 'quick_toggle') {
- processQuickToggleScan(scannedCode);
+ await processQuickToggleScan(scannedCode);
return;
}
// 4. Modus: Dauermodus (1x Ausweis, Nx Bücher)
if (mode === 'continuous') {
- processContinuousScan(scannedCode);
+ await processContinuousScan(scannedCode);
return;
}
}
@@ -1341,7 +1355,8 @@
// 2. Wenn kein Ausweis gesetzt ist, wird der Code als Ausweis interpretiert
if (!activeStudentCardId) {
setActiveStudentCard(scannedCode);
- setScanStatus(`Ausweis gesetzt: ${activeStudentCardId}`, 'ok');
+ setScanStatus(`Ausweis gesetzt: ${activeStudentCardId}. Bitte den nächsten Mediencode scannen.`, 'ok');
+ showSmallConfirm(`Ausweis erkannt: ${activeStudentCardId}. Bitte jetzt den nächsten Mediencode scannen.`, 'ok');
return;
}