Fix selection state: derive from DOM (single source of truth), clear browser-restored form state on load
This commit is contained in:
@@ -51,7 +51,7 @@
|
||||
<a href="/review?status=approved">Approved</a>
|
||||
</header>
|
||||
<div class="filters">
|
||||
<form method="get" action="/review" style="display:flex;gap:.5rem;">
|
||||
<form method="get" action="/review" style="display:flex;gap:.5rem;" autocomplete="off">
|
||||
<select name="status">
|
||||
<option value="">all statuses</option>
|
||||
{% for s in ["scanned", "review", "approved", "rejected"] %}
|
||||
@@ -62,7 +62,7 @@
|
||||
<button type="submit">Filter</button>
|
||||
</form>
|
||||
<button class="selall" id="sel-all" type="button">
|
||||
<input type="checkbox" id="sel-all-box" style="pointer-events:none;"> Select all on page
|
||||
<input type="checkbox" id="sel-all-box" style="pointer-events:none;" autocomplete="off"> Select all on page
|
||||
</button>
|
||||
</div>
|
||||
<div class="bulk-bar" id="bulk-bar">
|
||||
@@ -83,29 +83,26 @@
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
// selection state
|
||||
let selected = new Set();
|
||||
const bar = document.getElementById('bulk-bar');
|
||||
const countEl = document.getElementById('sel-count');
|
||||
// Selection is DERIVED from DOM checkboxes — single source of truth.
|
||||
// No separate Set that can drift from the visible state.
|
||||
|
||||
function getSelected() {
|
||||
return [...document.querySelectorAll('.item-check:checked')].map(cb => cb.value);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
countEl.textContent = selected.size + ' selected';
|
||||
bar.classList.toggle('visible', selected.size > 0);
|
||||
const n = getSelected().length;
|
||||
document.getElementById('sel-count').textContent = n + ' selected';
|
||||
document.getElementById('bulk-bar').classList.toggle('visible', n > 0);
|
||||
document.querySelectorAll('.card').forEach(c => {
|
||||
const cb = c.querySelector('.item-check');
|
||||
if (cb) c.classList.toggle('selected', selected.has(cb.value));
|
||||
if (cb) c.classList.toggle('selected', cb.checked);
|
||||
});
|
||||
}
|
||||
|
||||
// any checkbox change → refresh derived state
|
||||
document.addEventListener('change', e => {
|
||||
if (e.target.classList.contains('item-check')) {
|
||||
if (e.target.checked) selected.add(e.target.value); else selected.delete(e.target.value);
|
||||
refresh();
|
||||
}
|
||||
if (e.target.id === 'sel-all-box') {
|
||||
document.querySelectorAll('.item-check').forEach(cb => {
|
||||
if (e.target.checked) selected.add(cb.value); else selected.delete(cb.value);
|
||||
});
|
||||
if (e.target.classList.contains('item-check') || e.target.id === 'sel-all-box') {
|
||||
refresh();
|
||||
}
|
||||
});
|
||||
@@ -114,15 +111,21 @@
|
||||
const box = document.getElementById('sel-all-box');
|
||||
box.checked = !box.checked;
|
||||
document.querySelectorAll('.item-check').forEach(cb => {
|
||||
if (box.checked) selected.add(cb.value); else selected.delete(cb.value);
|
||||
cb.checked = box.checked;
|
||||
});
|
||||
refresh();
|
||||
});
|
||||
|
||||
// clear any browser-restored form state on load (defensive)
|
||||
window.addEventListener('load', () => {
|
||||
document.querySelectorAll('.item-check').forEach(cb => { cb.checked = false; });
|
||||
document.getElementById('sel-all-box').checked = false;
|
||||
refresh();
|
||||
});
|
||||
|
||||
async function bulk(action) {
|
||||
if (selected.size === 0) return;
|
||||
const shas = [...selected];
|
||||
// disable buttons during the operation (cross-filesystem moves can be slow)
|
||||
const shas = getSelected();
|
||||
if (shas.length === 0) return;
|
||||
setBulkBusy(true);
|
||||
try {
|
||||
const resp = await fetch('/review/bulk', {
|
||||
@@ -131,15 +134,13 @@
|
||||
body: JSON.stringify({ action, shas })
|
||||
});
|
||||
const data = await resp.json();
|
||||
// remove processed cards from DOM immediately (match by sha in value attr)
|
||||
shas.forEach(s => {
|
||||
const cb = document.querySelector(`.item-check[value="${s}"]`);
|
||||
if (cb) cb.closest('.card')?.remove();
|
||||
});
|
||||
selected.clear();
|
||||
refresh();
|
||||
updateCount();
|
||||
showToast(`Bulk ${action}: ${data.moved} done, ${data.missing} missing`);
|
||||
showToast(`Bulk ${action}: ${data.updated ?? data.moved} done, ${data.missing ?? 0} missing`);
|
||||
} catch (e) {
|
||||
showToast('Bulk action failed: ' + e, true);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user