mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 08:06:13 +00:00
Every keyword change sent the message's whole `keywords` map, which the
server takes as a replacement. Opening a message that wasn't in the
loaded list sent `{"$seen": true}` and wiped its star and tags; starring
a listed message sent the list's stale map and reverted keywords another
client had changed since the list loaded.
Keyword changes now go out as `keywords/<name>` patch pointers (`true`
to set, `null` to clear) everywhere:
- mark read/unread, star, pin and tagging in the email store
- spam / not spam, trash-and-read, and the spam undo, which now puts
back only the keywords the action touched
- star, tag and unread from the message viewer
- the offline outbox, whose keyword ops carry a patch and merge per
keyword when coalesced
Ops queued by earlier builds still hold a whole map. They replay as a
patch that only sets the keywords in it (clearing the other half of
`$junk`/`$notjunk`), so an unread or unstar queued before the update is
lost but nothing gets erased. The list and the offline cache apply the
same patch locally instead of overwriting their keyword maps.
40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
/**
|
|
* Keyword changes as an `Email/set` update sends them: one
|
|
* `keywords/<name>` pointer per keyword, `true` to set it and `null` (or
|
|
* `false`) to clear it. Keywords the patch does not name are left alone.
|
|
*
|
|
* Sending the whole `keywords` map instead replaces it on the server, which
|
|
* erases whatever the local copy lacked: a star or tag another client set
|
|
* since the list loaded, or everything on a message that was never loaded.
|
|
*/
|
|
export type KeywordPatch = Record<string, boolean | null>;
|
|
|
|
/** `keywords` with `patch` applied: the local mirror of the server update. */
|
|
export function applyKeywordPatch(
|
|
keywords: Record<string, boolean> | undefined,
|
|
patch: KeywordPatch,
|
|
): Record<string, boolean> {
|
|
const next = { ...keywords };
|
|
for (const [keyword, value] of Object.entries(patch)) {
|
|
if (value) next[keyword] = true;
|
|
else delete next[keyword];
|
|
}
|
|
return next;
|
|
}
|
|
|
|
/**
|
|
* The patch that undoes `patch` on a message whose keywords were `original`
|
|
* before it: each keyword the patch touched goes back to what it was, and
|
|
* nothing else changes.
|
|
*/
|
|
export function revertKeywordPatch(
|
|
patch: KeywordPatch,
|
|
original: Record<string, boolean> | undefined,
|
|
): KeywordPatch {
|
|
const revert: KeywordPatch = {};
|
|
for (const keyword of Object.keys(patch)) {
|
|
revert[keyword] = original?.[keyword] ? true : null;
|
|
}
|
|
return revert;
|
|
}
|