fix: improve ISBN validation and update related messages in upload forms
This commit is contained in:
+10
-11
@@ -4801,15 +4801,14 @@ def upload_item():
|
|||||||
|
|
||||||
item_isbn = ''
|
item_isbn = ''
|
||||||
item_type = 'general'
|
item_type = 'general'
|
||||||
if cfg.MODULES.is_enabled('library'):
|
if cfg.MODULES.is_enabled('library') and isbn_raw:
|
||||||
item_isbn = normalize_and_validate_isbn(isbn_raw)
|
normalized_isbn = normalize_and_validate_isbn(isbn_raw)
|
||||||
if isbn_raw and not item_isbn:
|
if normalized_isbn:
|
||||||
error_msg = 'Ungültige ISBN. Bitte ISBN-10 oder ISBN-13 verwenden.'
|
item_isbn = normalized_isbn
|
||||||
if is_mobile:
|
item_type = 'book'
|
||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
else:
|
||||||
flash(error_msg, 'error')
|
item_isbn = isbn_raw
|
||||||
return redirect(url_for(success_redirect_endpoint))
|
if upload_mode == 'library':
|
||||||
if item_isbn:
|
|
||||||
item_type = 'book'
|
item_type = 'book'
|
||||||
|
|
||||||
if upload_mode == 'library':
|
if upload_mode == 'library':
|
||||||
@@ -4819,8 +4818,8 @@ def upload_item():
|
|||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
return redirect(url_for(success_redirect_endpoint))
|
return redirect(url_for(success_redirect_endpoint))
|
||||||
if not item_isbn:
|
if not isbn_raw:
|
||||||
error_msg = 'Für Bücher ist eine gültige ISBN erforderlich.'
|
error_msg = 'Für Bücher ist eine ISBN oder ein Barcode erforderlich.'
|
||||||
if is_mobile:
|
if is_mobile:
|
||||||
return jsonify({'success': False, 'message': error_msg}), 400
|
return jsonify({'success': False, 'message': error_msg}), 400
|
||||||
flash(error_msg, 'error')
|
flash(error_msg, 'error')
|
||||||
|
|||||||
+25
-34
@@ -930,6 +930,29 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item.dropdown > .nav-link.dropdown-toggle,
|
||||||
|
.user-menu-wrap > .user-menu-btn {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item.dropdown > .dropdown-menu,
|
||||||
|
.user-menu-wrap > .dropdown-menu {
|
||||||
|
display: block !important;
|
||||||
|
position: static !important;
|
||||||
|
float: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
inset: auto !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav .nav-item.dropdown > .dropdown-menu {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-menu-wrap > .dropdown-menu {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-nav .dropdown-item {
|
.navbar-nav .dropdown-item {
|
||||||
color: rgba(255, 255, 255, 0.9);
|
color: rgba(255, 255, 255, 0.9);
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
@@ -974,6 +997,7 @@
|
|||||||
order: 3;
|
order: 3;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.function-search-form {
|
.function-search-form {
|
||||||
@@ -2055,44 +2079,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile menu is collapsed: avoid hiding links preemptively.
|
// Mobile burger menu should show the full list without overflow pruning.
|
||||||
if (navCollapse && !navCollapse.classList.contains('show')) {
|
|
||||||
restoreAllNavItems();
|
restoreAllNavItems();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreAllNavItems();
|
|
||||||
|
|
||||||
const hiddenSources = [];
|
|
||||||
let candidates = collectTopLevelNavSources();
|
|
||||||
const overflowBuffer = 12;
|
|
||||||
|
|
||||||
while (isNavOverflowing(overflowBuffer) && candidates.length > 0) {
|
|
||||||
const toHide = pickNextNavItemToHide(candidates);
|
|
||||||
if (!toHide) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
toHide.style.display = 'none';
|
|
||||||
hiddenSources.unshift(toHide);
|
|
||||||
candidates = collectTopLevelNavSources();
|
|
||||||
}
|
|
||||||
|
|
||||||
rebuildOverflowControl(hiddenSources);
|
|
||||||
|
|
||||||
// One final pass in case the overflow menu label/count itself changed row width.
|
|
||||||
candidates = collectTopLevelNavSources();
|
|
||||||
while (isNavOverflowing(overflowBuffer) && candidates.length > 0) {
|
|
||||||
const toHide = pickNextNavItemToHide(candidates);
|
|
||||||
if (!toHide) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
toHide.style.display = 'none';
|
|
||||||
hiddenSources.unshift(toHide);
|
|
||||||
rebuildOverflowControl(hiddenSources);
|
|
||||||
candidates = collectTopLevelNavSources();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let resizeTimer = null;
|
let resizeTimer = null;
|
||||||
function debounceAdapt() {
|
function debounceAdapt() {
|
||||||
if (resizeTimer) {
|
if (resizeTimer) {
|
||||||
|
|||||||
@@ -883,7 +883,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="individual_codes">Einzelcodes (optional, je Zeile ein Code)</label>
|
<label for="individual_codes">Einzelcodes (optional, je Zeile ein Code)</label>
|
||||||
<textarea id="individual_codes" name="individual_codes" rows="4" placeholder="z.B.\nABC-001\nABC-002"></textarea>
|
<textarea id="individual_codes" name="individual_codes" rows="4" placeholder="z.B.\nABC-001\nABC-002"></textarea>
|
||||||
<small style="display:block; color:#666;">Bei Anzahl > 1 können hier individuelle Codes pro Item gesetzt werden. Scanner-Eingaben werden bei Mehrfachanzahl automatisch hier angefügt.</small>
|
<small style="display:block; color:#666;">Bei Anzahl > 1 können hier individuelle Codes pro Item gesetzt werden. Der Scanner setzt immer zuerst den Basis-Code im Feld oben; weitere Einzelcodes können hier bei Bedarf manuell ergänzt werden.</small>
|
||||||
</div>
|
</div>
|
||||||
<!-- Image upload (hidden for library mode) -->
|
<!-- Image upload (hidden for library mode) -->
|
||||||
<div class="form-group" {% if show_library_features %}style="display:none;"{% endif %}>
|
<div class="form-group" {% if show_library_features %}style="display:none;"{% endif %}>
|
||||||
@@ -904,7 +904,7 @@
|
|||||||
<button type="button" class="fetch-isbn-button" onclick="fetchBookInfo('upload')">Bild abrufen</button>
|
<button type="button" class="fetch-isbn-button" onclick="fetchBookInfo('upload')">Bild abrufen</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="isbn-scanner" style="width:100%; max-width:520px; display:none; margin-top:10px;"></div>
|
<div id="isbn-scanner" style="width:100%; max-width:520px; display:none; margin-top:10px;"></div>
|
||||||
<small id="isbn-scan-status" style="display:block; color:#666; margin-top:6px;">Scannen oder manuell eingeben. Das Buchcover wird automatisch heruntergeladen.</small>
|
<small id="isbn-scan-status" style="display:block; color:#666; margin-top:6px;">Scannen oder manuell eingeben. Gültige ISBNs helfen beim Abruf von Buchdaten, andere Codes werden trotzdem akzeptiert.</small>
|
||||||
<div id="book-info-container" class="book-info-container"></div>
|
<div id="book-info-container" class="book-info-container"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -965,33 +965,30 @@
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendIndividualCode(scannedCode) {
|
function updateIsbnLiveValidation() {
|
||||||
const textarea = document.getElementById('individual_codes');
|
const isbnField = document.getElementById('isbn');
|
||||||
const itemCountInput = document.getElementById('item_count');
|
const statusEl = document.getElementById('isbn-scan-status');
|
||||||
if (!textarea || !itemCountInput) return false;
|
if (!isbnField || !statusEl) return;
|
||||||
|
|
||||||
const itemCount = Math.max(1, parseInt(itemCountInput.value || '1', 10));
|
const rawValue = isbnField.value.trim();
|
||||||
if (itemCount <= 1) return false;
|
isbnField.classList.remove('code-valid', 'code-invalid');
|
||||||
|
|
||||||
const existing = textarea.value
|
if (!rawValue) {
|
||||||
.split(/\r?\n/)
|
statusEl.textContent = 'Scannen oder manuell eingeben. Gültige ISBNs helfen beim Abruf von Buchdaten, andere Codes werden trotzdem akzeptiert.';
|
||||||
.map(line => line.trim())
|
statusEl.style.color = '#666';
|
||||||
.filter(Boolean);
|
return;
|
||||||
|
|
||||||
if (existing.includes(scannedCode)) {
|
|
||||||
setCode4ScanStatus(`Code ${scannedCode} ist bereits eingetragen.`, true);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing.length >= itemCount) {
|
if (normalizeIsbnClient(rawValue)) {
|
||||||
setCode4ScanStatus('Es sind bereits genug Einzelcodes für die gewählte Anzahl eingetragen.', true);
|
isbnField.classList.add('code-valid');
|
||||||
return true;
|
statusEl.textContent = 'Gültiges ISBN-Format erkannt. Der Wert kann gespeichert werden.';
|
||||||
|
statusEl.style.color = '#666';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
existing.push(scannedCode);
|
isbnField.classList.add('code-invalid');
|
||||||
textarea.value = existing.join('\n');
|
statusEl.textContent = 'Kein gültiges ISBN-10/13-Format erkannt. Der Wert wird trotzdem akzeptiert.';
|
||||||
setCode4ScanStatus(`Code ${scannedCode} hinzugefügt (${existing.length}/${itemCount}).`);
|
statusEl.style.color = '#b00020';
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function startCode4Scanner() {
|
function startCode4Scanner() {
|
||||||
@@ -1027,7 +1024,7 @@
|
|||||||
rememberLastUsedCamera: true
|
rememberLastUsedCamera: true
|
||||||
});
|
});
|
||||||
code4ScannerRunning = true;
|
code4ScannerRunning = true;
|
||||||
setCode4ScanStatus('Scanner läuft. Mehrere Codes nacheinander möglich.');
|
setCode4ScanStatus('Scanner läuft. Der Scan wird im Basis-Codefeld übernommen.');
|
||||||
|
|
||||||
code4ScannerInstance.render((decodedText) => {
|
code4ScannerInstance.render((decodedText) => {
|
||||||
const scannedCode = String(decodedText || '').trim();
|
const scannedCode = String(decodedText || '').trim();
|
||||||
@@ -1040,12 +1037,9 @@
|
|||||||
code4LastScanned = scannedCode;
|
code4LastScanned = scannedCode;
|
||||||
code4LastScannedAt = now;
|
code4LastScannedAt = now;
|
||||||
|
|
||||||
const usedForIndividual = appendIndividualCode(scannedCode);
|
|
||||||
if (!usedForIndividual) {
|
|
||||||
codeField.value = scannedCode;
|
codeField.value = scannedCode;
|
||||||
validateCodeField(codeField);
|
validateCodeField(codeField);
|
||||||
setCode4ScanStatus(`Code_4 gesetzt: ${scannedCode}`);
|
setCode4ScanStatus(`Code_4 gesetzt: ${scannedCode}`);
|
||||||
}
|
|
||||||
}, () => {});
|
}, () => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1094,21 +1088,23 @@
|
|||||||
if (!scannedCode) return;
|
if (!scannedCode) return;
|
||||||
|
|
||||||
const normalizedIsbn = normalizeIsbnClient(scannedCode);
|
const normalizedIsbn = normalizeIsbnClient(scannedCode);
|
||||||
if (!normalizedIsbn) {
|
isbnField.value = normalizedIsbn || scannedCode;
|
||||||
setIsbnScanStatus('Kein gültiger ISBN-Barcode erkannt. Bitte erneut scannen.', true);
|
updateIsbnLiveValidation();
|
||||||
return;
|
if (normalizedIsbn) {
|
||||||
}
|
|
||||||
|
|
||||||
isbnField.value = normalizedIsbn;
|
|
||||||
setIsbnScanStatus(`ISBN gesetzt: ${normalizedIsbn}`);
|
setIsbnScanStatus(`ISBN gesetzt: ${normalizedIsbn}`);
|
||||||
|
} else {
|
||||||
|
setIsbnScanStatus('Kein gültiges ISBN-Format erkannt, der gescannte Wert bleibt aber im Feld.', true);
|
||||||
|
}
|
||||||
|
|
||||||
isbnScannerInstance.clear().catch(() => {});
|
isbnScannerInstance.clear().catch(() => {});
|
||||||
scannerBox.style.display = 'none';
|
scannerBox.style.display = 'none';
|
||||||
scanButton.textContent = 'ISBN scannen';
|
scanButton.textContent = 'ISBN scannen';
|
||||||
isbnScannerRunning = false;
|
isbnScannerRunning = false;
|
||||||
|
|
||||||
// Automatically load book metadata after scan.
|
if (normalizedIsbn) {
|
||||||
|
// Automatically load book metadata after a valid ISBN scan.
|
||||||
fetchBookInfo('upload');
|
fetchBookInfo('upload');
|
||||||
|
}
|
||||||
}, () => {});
|
}, () => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1231,7 +1227,7 @@
|
|||||||
const isbn = normalizeIsbnClient(isbnField.value);
|
const isbn = normalizeIsbnClient(isbnField.value);
|
||||||
|
|
||||||
if (!isbn) {
|
if (!isbn) {
|
||||||
alert('Bitte eine gültige ISBN-10 oder ISBN-13 eingeben.');
|
infoContainer.innerHTML = '<div class="error-message">Bitte geben Sie eine ISBN oder einen Barcode ein.</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1646,6 +1642,13 @@
|
|||||||
scanIsbnBtn.addEventListener('click', startIsbnScanner);
|
scanIsbnBtn.addEventListener('click', startIsbnScanner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isbnField = document.getElementById('isbn');
|
||||||
|
if (isbnField) {
|
||||||
|
isbnField.addEventListener('input', updateIsbnLiveValidation);
|
||||||
|
isbnField.addEventListener('blur', updateIsbnLiveValidation);
|
||||||
|
updateIsbnLiveValidation();
|
||||||
|
}
|
||||||
|
|
||||||
// Setup add new location button
|
// Setup add new location button
|
||||||
const addLocationBtn = document.getElementById('add-new-location-btn');
|
const addLocationBtn = document.getElementById('add-new-location-btn');
|
||||||
if (addLocationBtn) {
|
if (addLocationBtn) {
|
||||||
|
|||||||
Reference in New Issue
Block a user