From ed3c3a80d65173a1df4fd701ee0bb9bc11bbfcce Mon Sep 17 00:00:00 2001 From: StyxX65 <150797939+StyxX65@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:44:10 +0200 Subject: [PATCH] Keep deleted cards in grid until next scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the redact behaviour for the card delete button (๐Ÿ—‘): instead of removing the card on success, mark the item _deleted and keep it in the grid โ€” greyed via card-resolved, shown with a red "๐Ÿ—‘ Deleted" badge, action buttons hidden so it can't be re-processed. The grid is rebuilt on the next scan run, clearing the markers. results.js only โ€” no server change. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 2 +- static/js/results.js | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 766987c..c5b4f27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html ### Changed -- **Redacted cards stay in the grid until the next scan** โ€” previously redacting a card (โœ) removed it from the grid and from `S.flaggedData`/`S.filteredData` immediately. Now the item is kept and marked redacted: the card is greyed (`card-resolved` styling), shows a `โœ Redacted` badge, and its delete/redact action buttons are hidden so it can't be re-processed. The operator can see what was handled during the session; the grid is rebuilt on the next scan run, which clears the redacted markers. Implemented with a `_redacted` flag in `results.js` (`appendCard` + `redactItem`); no server change. +- **Redacted and deleted cards stay in the grid until the next scan** โ€” previously redacting (โœ) or deleting (๐Ÿ—‘) a card removed it from the grid and from `S.flaggedData`/`S.filteredData` immediately. Now the item is kept and marked: the card is greyed (`card-resolved` styling), shows a `โœ Redacted` (green) or `๐Ÿ—‘ Deleted` (red) badge, and its action buttons are hidden so it can't be re-processed. The operator can see what was handled during the session; the grid is rebuilt on the next scan run, which clears the markers. Implemented with `_redacted` / `_deleted` flags in `results.js` (`appendCard` + `redactItem` / `deleteItem`); no server change. ### Fixed diff --git a/static/js/results.js b/static/js/results.js index 3b142c6..13ae0dd 100644 --- a/static/js/results.js +++ b/static/js/results.js @@ -38,7 +38,7 @@ function appendCard(f) { : '/api/thumb?name=' + encodeURIComponent(f.name) + '&type=' + encodeURIComponent(f.source_type); const card = document.createElement('div'); - card.className = 'card' + (S.isListView ? ' list-view' : '') + (S._selectedIds.has(f.id) ? ' card-selected-bulk' : '') + ((f._resolved || f._redacted) ? ' card-resolved' : ''); + card.className = 'card' + (S.isListView ? ' list-view' : '') + (S._selectedIds.has(f.id) ? ' card-selected-bulk' : '') + ((f._resolved || f._redacted || f._deleted) ? ' card-resolved' : ''); card.dataset.id = f.id; card.onclick = (e) => { if (S._selectMode) { toggleCardSelect(f.id, e); } else { openPreview(f); } }; @@ -49,12 +49,12 @@ function appendCard(f) { cb.onclick = (e) => { e.stopPropagation(); toggleCardSelect(f.id, e); }; card.appendChild(cb); - const delBtn = (window.VIEWER_MODE || f._resolved || f._redacted) ? '' : ``; + const delBtn = (window.VIEWER_MODE || f._resolved || f._redacted || f._deleted) ? '' : ``; const _redactExts = new Set(['.docx', '.xlsx', '.txt', '.csv', '.pdf']); const _cloudRedactExts = new Set(['.docx', '.xlsx', '.pdf']); const _m365Types = new Set(['onedrive', 'sharepoint', 'teams']); const _fileExt = (f.name || '').substring((f.name || '').lastIndexOf('.')).toLowerCase(); - const _redactable = !window.VIEWER_MODE && !f._resolved && !f._redacted && f.cpr_count > 0 && ( + const _redactable = !window.VIEWER_MODE && !f._resolved && !f._redacted && !f._deleted && f.cpr_count > 0 && ( f.source_type === 'local' ? _redactExts.has(_fileExt) : _m365Types.has(f.source_type) ? _cloudRedactExts.has(_fileExt) : f.source_type === 'gdrive' ? _cloudRedactExts.has(_fileExt) : @@ -75,7 +75,7 @@ function appendCard(f) { ${f.phone_count > 0 ? '' + f.phone_count + ' ' + t('m365_badge_phones', 'tlf.') + ' ' : ''} ${f.face_count > 0 ? '' + f.face_count + ' ' + t('m365_badge_faces', f.face_count === 1 ? 'face' : 'faces') + ' ' : ''} ${f.exif && f.exif.gps ? '๐ŸŒ GPS ' : ''} - ${f.special_category && f.special_category.length ? 'โš  Art.9 โ€” ' + f.special_category.filter(function(s){return s !== 'gps_location' && s !== 'exif_pii';}).join(', ') + ' ' : ''}${f._redacted ? 'โœ ' + t('redact_badge', 'Redacted') + ' ' : ''}${f._resolved ? 'โœ“ ' + t('history_resolved_badge', 'Resolved') + ' ' : ''}${f.overdue ? '๐Ÿ—“ Overdue' : ''} + ${f.special_category && f.special_category.length ? 'โš  Art.9 โ€” ' + f.special_category.filter(function(s){return s !== 'gps_location' && s !== 'exif_pii';}).join(', ') + ' ' : ''}${f._deleted ? '๐Ÿ—‘ ' + t('delete_badge', 'Deleted') + ' ' : ''}${f._redacted ? 'โœ ' + t('redact_badge', 'Redacted') + ' ' : ''}${f._resolved ? 'โœ“ ' + t('history_resolved_badge', 'Resolved') + ' ' : ''}${f.overdue ? '๐Ÿ—“ Overdue' : ''} ${delBtn}${redactBtn}`; } else { card.innerHTML = ` @@ -85,7 +85,7 @@ function appendCard(f) {
${f.size_kb} KB ยท ${esc(f.modified || '')}
${f.folder ? `
๐Ÿ“‚ ${esc(f.folder)}
` : ''}
${esc(label)}${f.account_name ? ' ' : ''}${f.transfer_risk === "external-recipient" ? ' โš  Ext.' : f.transfer_risk ? ' ๐Ÿ”—' : ''}
- ${f.cpr_count} CPR${f.email_count > 0 ? ' ' + f.email_count + ' ' + t('m365_badge_emails', 'e-mail') + '' : ''}${f.phone_count > 0 ? ' ' + f.phone_count + ' ' + t('m365_badge_phones', 'tlf.') + '' : ''}${f.face_count > 0 ? ' ' + f.face_count + ' ' + t('m365_badge_faces', f.face_count === 1 ? 'face' : 'faces') + '' : ''}${f.exif && f.exif.gps ? ' ๐ŸŒ GPS' : ''}${f._redacted ? ' โœ ' + t('redact_badge', 'Redacted') + '' : ''}${f._resolved ? ' โœ“ ' + t('history_resolved_badge', 'Resolved') + '' : ''}${f.overdue ? ' ๐Ÿ—“ Overdue' : ''} + ${f.cpr_count} CPR${f.email_count > 0 ? ' ' + f.email_count + ' ' + t('m365_badge_emails', 'e-mail') + '' : ''}${f.phone_count > 0 ? ' ' + f.phone_count + ' ' + t('m365_badge_phones', 'tlf.') + '' : ''}${f.face_count > 0 ? ' ' + f.face_count + ' ' + t('m365_badge_faces', f.face_count === 1 ? 'face' : 'faces') + '' : ''}${f.exif && f.exif.gps ? ' ๐ŸŒ GPS' : ''}${f._deleted ? ' ๐Ÿ—‘ ' + t('delete_badge', 'Deleted') + '' : ''}${f._redacted ? ' โœ ' + t('redact_badge', 'Redacted') + '' : ''}${f._resolved ? ' โœ“ ' + t('history_resolved_badge', 'Resolved') + '' : ''}${f.overdue ? ' ๐Ÿ—“ Overdue' : ''} ${delBtn}${redactBtn}`; } @@ -609,9 +609,13 @@ async function deleteItem(f, cardEl) { }); const d = await r.json(); if (d.ok) { - S.flaggedData = S.flaggedData.filter(x => x.id !== f.id); - S.filteredData = S.filteredData.filter(x => x.id !== f.id); - if (cardEl) cardEl.remove(); + // Keep the deleted item in the grid (marked, greyed, action buttons + // hidden) until the next scan run, so the operator can see what was + // handled. The grid is rebuilt on the next scan, clearing these. + const _mark = (x) => { if (x.id === f.id) x._deleted = true; }; + S.flaggedData.forEach(_mark); + S.filteredData.forEach(_mark); + renderGrid(S.filteredData.length ? S.filteredData : S.flaggedData); updateStats(); log(t('m365_log_deleted', 'Deleted:') + ' ' + f.name, 'ok'); if (_previewItemId === f.id) closePreview();