feat: defer selected updates as unread batch (Closes #659)
CI / lint (pull_request) Successful in 1m36s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-12 15:11:51 +00:00
1 parent 7421a8f62d
commit ea3712ca3e
6 files changed
+109 -3

No files matched your search

+3
View File
@@ -268,6 +268,9 @@ textarea { resize: vertical; min-height: 120px; }
.retry-work-route[hidden] { display:none; }
.my-work-bulk { position:sticky; bottom:0; z-index:4; margin:10px -4px -12px; padding:10px 4px; padding-bottom:calc(10px + env(safe-area-inset-bottom)); background:rgba(11,21,38,.98); border-top:1px solid #2a496e; }
.my-work-bulk button { min-height:44px; width:100%; }
.my-work-bulk-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }
.my-work-bulk-actions button { min-height:44px; width:100%; }
.my-work-bulk-actions #bulk-mark-read { grid-column:1 / -1; }
.my-work[data-stale="true"] { border-color:#fcd34d; }
.review-sheet { position:fixed; inset:0; z-index:50; display:none; justify-content:flex-end; background:rgba(5,12,21,.72); backdrop-filter:blur(4px); }
.review-sheet.open { display:flex; }
+16
View File
@@ -2504,6 +2504,7 @@
});
const bulkBar = qs('#bulk-mark-read-bar');
const bulkButton = qs('#bulk-mark-read');
const updateIds = notificationIds(visible);
const selectionControls = qs('#update-selection-controls');
selectionControls.hidden = selectedWorkFilter !== 'update' || updateIds.length === 0;
@@ -2515,6 +2516,9 @@
qs('#load-more-notifications').hidden =
selectedWorkFilter !== 'update' || !notificationPagination.has_more;
bulkButton.disabled = bulkMarkPending || selection.count === 0;
document.querySelectorAll('[data-defer-selected]').forEach(button => {
button.disabled = bulkMarkPending || selection.count === 0 || !planningOwnerLogin;
});
bulkButton.textContent = bulkConfirmationPending ?
'Confirm marking ' + selection.count + ' selected read' :
'Mark ' + selection.count + ' selected read';
@@ -5514,6 +5518,18 @@
notificationSelection.cancel();
qs('#select-updates').focus();
});
document.querySelectorAll('[data-defer-selected]').forEach(button => button.addEventListener('click', () => {
const selection = notificationSelection.snapshot();
const ids = new Set(selection.ids);
const selectedUpdates = lastMyWork.filter(item => item.has_update && ids.has(item.notification_id));
const until = laterWork.presetUntil(button.dataset.deferSelected);
const result = laterWork.deferMany(selectedUpdates, until);
qs('#my-work-action-status').textContent = result === 'deferred' ?
selection.count + ' updates deferred until ' + fmt(until) + '; work stays unread and unchanged in Gitea.' :
'Could not save Later on this device. Selection unchanged.';
if (result === 'deferred') notificationSelection.cancel();
refreshMyWorkView();
}));
qs('#bulk-mark-read').addEventListener('click', async () => {
const selection = notificationSelection.snapshot();
if (!selection.ids.length || bulkMarkPending) return;
+5 -1
View File
@@ -178,7 +178,11 @@
<button class="retry-work-route" id="retry-work-route" type="button" hidden>Retry shared work item</button>
<div class="small" id="work-route-share-status" aria-live="polite"></div>
<div class="my-work-bulk" id="bulk-mark-read-bar" hidden>
<button id="bulk-mark-read" type="button"></button>
<div class="my-work-bulk-actions">
<button id="defer-selected-today" data-defer-selected="today" type="button">Later today</button>
<button id="defer-selected-tomorrow" data-defer-selected="tomorrow" type="button">Tomorrow</button>
<button id="bulk-mark-read" type="button"></button>
</div>
</div>
</section>
<aside class="sidebar">
+17 -1
View File
@@ -52,6 +52,22 @@ function createLaterWork({ storage, getLogin, now = () => new Date(), setTimer =
return 'deferred';
}
function deferMany(items, until) {
const key = storageKey();
const wake = new Date(until);
const entries = (items || []).map(item => [identity(item), item]);
if (!key) return 'unavailable';
if (!entries.length || entries.some(([id]) => !id) || Number.isNaN(wake.getTime()) || wake <= now()) {
return 'invalid';
}
const records = read();
const wakeAt = wake.toISOString();
entries.forEach(([id]) => { records[id] = wakeAt; });
if (!write(records)) return 'unavailable';
entries.forEach(([id]) => onChange('defer', id, wakeAt));
return 'deferred';
}
function presetUntil(preset) {
const current = new Date(now());
if (preset === 'today') return new Date(current.getTime() + 4 * 60 * 60 * 1000);
@@ -127,7 +143,7 @@ function createLaterWork({ storage, getLogin, now = () => new Date(), setTimer =
return { active, later };
}
return { identity, read, adopt, defer, restore, presetUntil, partition };
return { identity, read, adopt, defer, deferMany, restore, presetUntil, partition };
}
if (typeof module !== 'undefined' && module.exports) module.exports = createLaterWork;