Keep deleted cards in grid until next scan
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 <noreply@anthropic.com>
This commit is contained in:
parent
7c1c2b390d
commit
ed3c3a80d6
@ -23,7 +23,7 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html
|
|||||||
|
|
||||||
### Changed
|
### 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
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ function appendCard(f) {
|
|||||||
: '/api/thumb?name=' + encodeURIComponent(f.name) + '&type=' + encodeURIComponent(f.source_type);
|
: '/api/thumb?name=' + encodeURIComponent(f.name) + '&type=' + encodeURIComponent(f.source_type);
|
||||||
|
|
||||||
const card = document.createElement('div');
|
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.dataset.id = f.id;
|
||||||
card.onclick = (e) => { if (S._selectMode) { toggleCardSelect(f.id, e); } else { openPreview(f); } };
|
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); };
|
cb.onclick = (e) => { e.stopPropagation(); toggleCardSelect(f.id, e); };
|
||||||
card.appendChild(cb);
|
card.appendChild(cb);
|
||||||
|
|
||||||
const delBtn = (window.VIEWER_MODE || f._resolved || f._redacted) ? '' : `<button class="card-delete-btn" title="${t('m365_delete_confirm','Delete')}" onclick="event.stopPropagation();deleteItem(${JSON.stringify(f).replace(/"/g,'"')},this.closest('.card'))">🗑</button>`;
|
const delBtn = (window.VIEWER_MODE || f._resolved || f._redacted || f._deleted) ? '' : `<button class="card-delete-btn" title="${t('m365_delete_confirm','Delete')}" onclick="event.stopPropagation();deleteItem(${JSON.stringify(f).replace(/"/g,'"')},this.closest('.card'))">🗑</button>`;
|
||||||
const _redactExts = new Set(['.docx', '.xlsx', '.txt', '.csv', '.pdf']);
|
const _redactExts = new Set(['.docx', '.xlsx', '.txt', '.csv', '.pdf']);
|
||||||
const _cloudRedactExts = new Set(['.docx', '.xlsx', '.pdf']);
|
const _cloudRedactExts = new Set(['.docx', '.xlsx', '.pdf']);
|
||||||
const _m365Types = new Set(['onedrive', 'sharepoint', 'teams']);
|
const _m365Types = new Set(['onedrive', 'sharepoint', 'teams']);
|
||||||
const _fileExt = (f.name || '').substring((f.name || '').lastIndexOf('.')).toLowerCase();
|
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) :
|
f.source_type === 'local' ? _redactExts.has(_fileExt) :
|
||||||
_m365Types.has(f.source_type) ? _cloudRedactExts.has(_fileExt) :
|
_m365Types.has(f.source_type) ? _cloudRedactExts.has(_fileExt) :
|
||||||
f.source_type === 'gdrive' ? _cloudRedactExts.has(_fileExt) :
|
f.source_type === 'gdrive' ? _cloudRedactExts.has(_fileExt) :
|
||||||
@ -75,7 +75,7 @@ function appendCard(f) {
|
|||||||
${f.phone_count > 0 ? '<span class="phone-badge">' + f.phone_count + ' ' + t('m365_badge_phones', 'tlf.') + '</span> ' : ''}
|
${f.phone_count > 0 ? '<span class="phone-badge">' + f.phone_count + ' ' + t('m365_badge_phones', 'tlf.') + '</span> ' : ''}
|
||||||
${f.face_count > 0 ? '<span class="photo-face-badge">' + f.face_count + ' ' + t('m365_badge_faces', f.face_count === 1 ? 'face' : 'faces') + '</span> ' : ''}
|
${f.face_count > 0 ? '<span class="photo-face-badge">' + f.face_count + ' ' + t('m365_badge_faces', f.face_count === 1 ? 'face' : 'faces') + '</span> ' : ''}
|
||||||
${f.exif && f.exif.gps ? '<span class="photo-face-badge" style="background:#0a3a5a;color:#7ec8d0">🌍 GPS</span> ' : ''}
|
${f.exif && f.exif.gps ? '<span class="photo-face-badge" style="background:#0a3a5a;color:#7ec8d0">🌍 GPS</span> ' : ''}
|
||||||
${f.special_category && f.special_category.length ? '<span class="special-cat-badge">⚠ Art.9 — ' + f.special_category.filter(function(s){return s !== 'gps_location' && s !== 'exif_pii';}).join(', ') + '</span> ' : ''}${f._redacted ? '<span class="resolved-badge">✏ ' + t('redact_badge', 'Redacted') + '</span> ' : ''}${f._resolved ? '<span class="resolved-badge">✓ ' + t('history_resolved_badge', 'Resolved') + '</span> ' : ''}${f.overdue ? '<span class="overdue-badge">🗓 Overdue</span>' : ''}
|
${f.special_category && f.special_category.length ? '<span class="special-cat-badge">⚠ Art.9 — ' + f.special_category.filter(function(s){return s !== 'gps_location' && s !== 'exif_pii';}).join(', ') + '</span> ' : ''}${f._deleted ? '<span class="resolved-badge" style="background:#3a1a1a;color:#ff9b9b">🗑 ' + t('delete_badge', 'Deleted') + '</span> ' : ''}${f._redacted ? '<span class="resolved-badge">✏ ' + t('redact_badge', 'Redacted') + '</span> ' : ''}${f._resolved ? '<span class="resolved-badge">✓ ' + t('history_resolved_badge', 'Resolved') + '</span> ' : ''}${f.overdue ? '<span class="overdue-badge">🗓 Overdue</span>' : ''}
|
||||||
${delBtn}${redactBtn}`;
|
${delBtn}${redactBtn}`;
|
||||||
} else {
|
} else {
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
@ -85,7 +85,7 @@ function appendCard(f) {
|
|||||||
<div class="card-meta">${f.size_kb} KB · ${esc(f.modified || '')}</div>
|
<div class="card-meta">${f.size_kb} KB · ${esc(f.modified || '')}</div>
|
||||||
${f.folder ? `<div class="card-meta" style="font-size:10px" title="${esc(f.folder)}">📂 ${esc(f.folder)}</div>` : ''}
|
${f.folder ? `<div class="card-meta" style="font-size:10px" title="${esc(f.folder)}">📂 ${esc(f.folder)}</div>` : ''}
|
||||||
<div class="card-source"><span class="source-badge ${badgeCls}">${esc(label)}</span>${f.account_name ? ' <span class="account-pill" title="' + esc(f.account_name) + '">' + (f.user_role === "student" ? '<span class="role-badge">' + t("role_student","Elev") + "</span>" : f.user_role === "staff" ? '<span class="role-badge">' + t("role_staff","Ansat") + "</span>" : "") + esc(f.account_name) + '</span>' : ''}${f.transfer_risk === "external-recipient" ? ' <span class="role-pill" style="background:#7B2D00;color:#FFD0B0">⚠ Ext.</span>' : f.transfer_risk ? ' <span class="role-pill" style="background:#003D7B;color:#B0D4FF">🔗</span>' : ''}</div>
|
<div class="card-source"><span class="source-badge ${badgeCls}">${esc(label)}</span>${f.account_name ? ' <span class="account-pill" title="' + esc(f.account_name) + '">' + (f.user_role === "student" ? '<span class="role-badge">' + t("role_student","Elev") + "</span>" : f.user_role === "staff" ? '<span class="role-badge">' + t("role_staff","Ansat") + "</span>" : "") + esc(f.account_name) + '</span>' : ''}${f.transfer_risk === "external-recipient" ? ' <span class="role-pill" style="background:#7B2D00;color:#FFD0B0">⚠ Ext.</span>' : f.transfer_risk ? ' <span class="role-pill" style="background:#003D7B;color:#B0D4FF">🔗</span>' : ''}</div>
|
||||||
<span class="cpr-badge">${f.cpr_count} CPR</span>${f.email_count > 0 ? ' <span class="email-badge">' + f.email_count + ' ' + t('m365_badge_emails', 'e-mail') + '</span>' : ''}${f.phone_count > 0 ? ' <span class="phone-badge">' + f.phone_count + ' ' + t('m365_badge_phones', 'tlf.') + '</span>' : ''}${f.face_count > 0 ? ' <span class="photo-face-badge">' + f.face_count + ' ' + t('m365_badge_faces', f.face_count === 1 ? 'face' : 'faces') + '</span>' : ''}${f.exif && f.exif.gps ? ' <span class="photo-face-badge" style="background:#0a3a5a;color:#7ec8d0">🌍 GPS</span>' : ''}${f._redacted ? ' <span class="resolved-badge">✏ ' + t('redact_badge', 'Redacted') + '</span>' : ''}${f._resolved ? ' <span class="resolved-badge">✓ ' + t('history_resolved_badge', 'Resolved') + '</span>' : ''}${f.overdue ? ' <span class="overdue-badge">🗓 Overdue</span>' : ''}
|
<span class="cpr-badge">${f.cpr_count} CPR</span>${f.email_count > 0 ? ' <span class="email-badge">' + f.email_count + ' ' + t('m365_badge_emails', 'e-mail') + '</span>' : ''}${f.phone_count > 0 ? ' <span class="phone-badge">' + f.phone_count + ' ' + t('m365_badge_phones', 'tlf.') + '</span>' : ''}${f.face_count > 0 ? ' <span class="photo-face-badge">' + f.face_count + ' ' + t('m365_badge_faces', f.face_count === 1 ? 'face' : 'faces') + '</span>' : ''}${f.exif && f.exif.gps ? ' <span class="photo-face-badge" style="background:#0a3a5a;color:#7ec8d0">🌍 GPS</span>' : ''}${f._deleted ? ' <span class="resolved-badge" style="background:#3a1a1a;color:#ff9b9b">🗑 ' + t('delete_badge', 'Deleted') + '</span>' : ''}${f._redacted ? ' <span class="resolved-badge">✏ ' + t('redact_badge', 'Redacted') + '</span>' : ''}${f._resolved ? ' <span class="resolved-badge">✓ ' + t('history_resolved_badge', 'Resolved') + '</span>' : ''}${f.overdue ? ' <span class="overdue-badge">🗓 Overdue</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
${delBtn}${redactBtn}`;
|
${delBtn}${redactBtn}`;
|
||||||
}
|
}
|
||||||
@ -609,9 +609,13 @@ async function deleteItem(f, cardEl) {
|
|||||||
});
|
});
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
if (d.ok) {
|
if (d.ok) {
|
||||||
S.flaggedData = S.flaggedData.filter(x => x.id !== f.id);
|
// Keep the deleted item in the grid (marked, greyed, action buttons
|
||||||
S.filteredData = S.filteredData.filter(x => x.id !== f.id);
|
// hidden) until the next scan run, so the operator can see what was
|
||||||
if (cardEl) cardEl.remove();
|
// 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();
|
updateStats();
|
||||||
log(t('m365_log_deleted', 'Deleted:') + ' ' + f.name, 'ok');
|
log(t('m365_log_deleted', 'Deleted:') + ' ' + f.name, 'ok');
|
||||||
if (_previewItemId === f.id) closePreview();
|
if (_previewItemId === f.id) closePreview();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user