@remit/data-ports 0.0.67 → 0.0.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/folder-role.test.ts +38 -0
- package/src/folder-role.ts +35 -0
package/package.json
CHANGED
package/src/folder-role.test.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
composeFolderRoleAppointmentLabelName,
|
|
11
11
|
composeFolderRoleAppointmentName,
|
|
12
|
+
isReservedFolderName,
|
|
12
13
|
meetsTrashAssurance,
|
|
13
14
|
parseFolderRoleAppointmentLabelName,
|
|
14
15
|
parseFolderRoleAppointmentName,
|
|
@@ -382,3 +383,40 @@ describe("the appointment name and its label sibling", () => {
|
|
|
382
383
|
});
|
|
383
384
|
});
|
|
384
385
|
});
|
|
386
|
+
|
|
387
|
+
describe("isReservedFolderName", () => {
|
|
388
|
+
/**
|
|
389
|
+
* The sweep's own predicate. A folder whose leaf name is a role's
|
|
390
|
+
* conventional name but which lacks the server's flag is read as a duplicate
|
|
391
|
+
* and its row is deleted — under D8, with the folder's mail — as soon as
|
|
392
|
+
* another folder holds the flag. A rename to one of these settles `synced`
|
|
393
|
+
* and is then reaped, which is why the API refuses it (D5).
|
|
394
|
+
*/
|
|
395
|
+
for (const name of [
|
|
396
|
+
"archive",
|
|
397
|
+
"Archive",
|
|
398
|
+
"ARCHIVES",
|
|
399
|
+
"trash",
|
|
400
|
+
"Deleted Items",
|
|
401
|
+
"drafts",
|
|
402
|
+
"Sent Mail",
|
|
403
|
+
"junk",
|
|
404
|
+
"Spam",
|
|
405
|
+
"All Mail",
|
|
406
|
+
]) {
|
|
407
|
+
it(`reserves "${name}"`, () => {
|
|
408
|
+
assert.equal(isReservedFolderName(name), true);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* #843 dropped a bare "Deleted" and "Bin" from the Trash hints precisely
|
|
414
|
+
* because they are ordinary folder names a user keeps mail in. Reserving
|
|
415
|
+
* them here would refuse a rename nothing would ever have reaped.
|
|
416
|
+
*/
|
|
417
|
+
for (const name of ["Deleted", "Bin", "Receipts", "Work", "Inbox 2024"]) {
|
|
418
|
+
it(`leaves "${name}" alone`, () => {
|
|
419
|
+
assert.equal(isReservedFolderName(name), false);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
});
|
package/src/folder-role.ts
CHANGED
|
@@ -134,6 +134,41 @@ export const ROLE_NAME_HINTS: Partial<
|
|
|
134
134
|
[CanonicalMailboxRole.All]: ["all mail", "all"],
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The role a folder's own leaf name is most conventionally for. A name that
|
|
139
|
+
* several roles list goes to the role that ranks it highest, so `All Mail` is
|
|
140
|
+
* an All folder rather than a lookalike of Archive.
|
|
141
|
+
*
|
|
142
|
+
* `name` is the leaf segment, lower case.
|
|
143
|
+
*/
|
|
144
|
+
export const roleForFolderName = (
|
|
145
|
+
name: string,
|
|
146
|
+
): CanonicalMailboxRoleValue | undefined => {
|
|
147
|
+
let best: { role: CanonicalMailboxRoleValue; rank: number } | undefined;
|
|
148
|
+
for (const role of CANONICAL_ROLES) {
|
|
149
|
+
const rank = ROLE_NAME_HINTS[role]?.indexOf(name) ?? -1;
|
|
150
|
+
if (rank < 0) continue;
|
|
151
|
+
if (!best || rank < best.rank) best = { role, rank };
|
|
152
|
+
}
|
|
153
|
+
return best?.role;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Whether the mailbox sweep would read a folder with this leaf name as a
|
|
158
|
+
* duplicate of a role's canonical folder — and so delete its row, and under D8
|
|
159
|
+
* the mail in it, as soon as another folder carries the role's SPECIAL-USE
|
|
160
|
+
* flag. It is the sweep's own predicate, so a rename refused on it and a folder
|
|
161
|
+
* reaped by it can never disagree (docs/architecture/folder-rename-and-delete.md
|
|
162
|
+
* D5).
|
|
163
|
+
*
|
|
164
|
+
* A role with no flag to be a duplicate of is not reserved: nothing reaps a
|
|
165
|
+
* folder for it.
|
|
166
|
+
*/
|
|
167
|
+
export const isReservedFolderName = (name: string): boolean => {
|
|
168
|
+
const role = roleForFolderName(name.toLowerCase());
|
|
169
|
+
return role !== undefined && ROLE_SPECIAL_USE[role] !== undefined;
|
|
170
|
+
};
|
|
171
|
+
|
|
137
172
|
/**
|
|
138
173
|
* The pane where a user appoints a folder to a role, named as it is labelled.
|
|
139
174
|
* Every refusal that asks for an appointment points here, in one wording — the
|