Email: persist bulk read state to provider
Co-authored-by: ghreprimand <203024559+ghreprimand@users.noreply.github.com>
This commit is contained in:
@@ -4640,6 +4640,7 @@ function _updateBulkBar() {
|
||||
async function _bulkAction(action) {
|
||||
const uids = Array.from(state._selectedUids);
|
||||
if (uids.length === 0) return;
|
||||
let failedReadSync = 0;
|
||||
if (action === 'delete') {
|
||||
const ok = await styledConfirm(
|
||||
`Delete ${uids.length} selected email${uids.length === 1 ? '' : 's'}?`,
|
||||
@@ -4655,11 +4656,19 @@ async function _bulkAction(action) {
|
||||
} else if (action === 'delete') {
|
||||
await fetch(`${API_BASE}/api/email/delete/${uid}?folder=${encodeURIComponent(state._libFolder)}${_acct()}`, { method: 'DELETE' });
|
||||
} else if (action === 'read' || action === 'unread') {
|
||||
// Local toggle for now (no backend endpoint yet)
|
||||
const em = state._libEmails.find(e => e.uid === uid);
|
||||
if (em) em.is_read = (action === 'read');
|
||||
const endpoint = action === 'read' ? 'mark-read' : 'mark-unread';
|
||||
const res = await fetch(`${API_BASE}/api/email/${endpoint}/${uid}?folder=${encodeURIComponent(state._libFolder)}${_acct()}`, { method: 'POST' });
|
||||
let data = null;
|
||||
try { data = await res.json(); } catch (_) {}
|
||||
if (!res.ok || data?.success === false) {
|
||||
throw new Error(data?.error || `HTTP ${res.status}`);
|
||||
}
|
||||
_syncEmailReadState(uid, action === 'read');
|
||||
}
|
||||
} catch (e) { console.error(`Failed to ${action} ${uid}:`, e); }
|
||||
} catch (e) {
|
||||
if (action === 'read' || action === 'unread') failedReadSync += 1;
|
||||
console.error(`Failed to ${action} ${uid}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'archive' || action === 'delete') {
|
||||
@@ -4671,8 +4680,10 @@ async function _bulkAction(action) {
|
||||
state._selectMode = false;
|
||||
_updateBulkBar();
|
||||
_renderGrid();
|
||||
// Sync the local mutation (delete/archive, or in-place read/unread
|
||||
// flag flips on email objects) into the SWR cache so reopen doesn't
|
||||
if (failedReadSync > 0) {
|
||||
showToast(`Failed to update ${failedReadSync} email${failedReadSync === 1 ? '' : 's'}`);
|
||||
}
|
||||
// Sync successful local mutations into the SWR cache so reopen doesn't
|
||||
// briefly show the pre-bulk state.
|
||||
_libCacheWriteBack();
|
||||
}
|
||||
|
||||
36
tests/test_email_library_bulk_actions.py
Normal file
36
tests/test_email_library_bulk_actions.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
_REPO = Path(__file__).resolve().parents[1]
|
||||
_EMAIL_LIBRARY = _REPO / "static" / "js" / "emailLibrary.js"
|
||||
|
||||
|
||||
def _bulk_action_source() -> str:
|
||||
text = _EMAIL_LIBRARY.read_text(encoding="utf-8")
|
||||
start = text.index("async function _bulkAction(action)")
|
||||
end = text.index("\n}\n\n// _extractName", start) + 3
|
||||
return text[start:end]
|
||||
|
||||
|
||||
def test_email_bulk_read_unread_calls_provider_write_routes():
|
||||
"""Bulk read/unread must persist to IMAP/provider, not only mutate UI state.
|
||||
|
||||
Regression for issue #800's email follow-up: list select -> Actions ->
|
||||
Mark Read used to update `em.is_read` locally and cache that fake state,
|
||||
then refresh from the provider made the message unread again.
|
||||
"""
|
||||
src = _bulk_action_source()
|
||||
|
||||
assert "Local toggle for now" not in src
|
||||
assert "mark-read" in src
|
||||
assert "mark-unread" in src
|
||||
assert "method: 'POST'" in src
|
||||
assert "_syncEmailReadState(uid, action === 'read')" in src
|
||||
|
||||
|
||||
def test_email_bulk_read_unread_checks_backend_success_before_syncing_cache():
|
||||
src = _bulk_action_source()
|
||||
|
||||
assert "data?.success === false" in src
|
||||
assert "throw new Error(data?.error" in src
|
||||
assert "_libCacheWriteBack()" in src
|
||||
Reference in New Issue
Block a user