domma-cms 1.10.35 → 1.10.36
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.36] - 2026-10-27
|
|
4
|
+
|
|
5
|
+
- Trash folder menus offer Empty Trash with an explicit permanent-deletion confirmation. Clear every message in the server-designated Trash folder, including more than one list page, while retaining the folder and its subfolders. Requires Email Pro 0.10.1.
|
|
6
|
+
|
|
3
7
|
## [1.10.35] - 2026-10-27
|
|
4
8
|
|
|
5
9
|
- Email lists always show the local date and time. Ctrl-click adds or removes individual messages, starting with the open message; Shift-click selects a range from it.
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -5114,6 +5114,12 @@ return {
|
|
|
5114
5114
|
action: () => setFolderVisible(folder, folder.subscribed === false)
|
|
5115
5115
|
},
|
|
5116
5116
|
{type: 'divider'},
|
|
5117
|
+
...(folder.specialUse === '\\Trash' && features.emptyTrash ? [{
|
|
5118
|
+
label: 'Empty Trash...',
|
|
5119
|
+
icon: 'trash',
|
|
5120
|
+
danger: true,
|
|
5121
|
+
action: () => emptyTrashFolder(folder)
|
|
5122
|
+
}, {type: 'divider'}] : []),
|
|
5117
5123
|
{
|
|
5118
5124
|
label: 'Rename',
|
|
5119
5125
|
icon: 'edit-3',
|
|
@@ -5362,6 +5368,33 @@ return {
|
|
|
5362
5368
|
}
|
|
5363
5369
|
}
|
|
5364
5370
|
|
|
5371
|
+
/** Empty Trash after explicitly confirming permanent deletion. */
|
|
5372
|
+
async function emptyTrashFolder(folder) {
|
|
5373
|
+
const accountId = currentAccountId;
|
|
5374
|
+
const count = Number.isFinite(folder.messages) ? `${folder.messages} message${folder.messages === 1 ? '' : 's'}` : 'all messages';
|
|
5375
|
+
const question = `Are you sure you want to permanently delete ${count} from "${folder.path}"? This cannot be undone. The folder and its subfolders will be kept.`;
|
|
5376
|
+
if (!dialogOnce.claim()) return;
|
|
5377
|
+
let confirmed;
|
|
5378
|
+
try {
|
|
5379
|
+
confirmed = await ask(esc(question), {title: 'Empty Trash?', confirmText: 'Permanently delete', cancelText: 'Cancel'});
|
|
5380
|
+
} finally {
|
|
5381
|
+
dialogOnce.release();
|
|
5382
|
+
}
|
|
5383
|
+
if (!confirmed || !here() || accountId !== currentAccountId) return;
|
|
5384
|
+
try {
|
|
5385
|
+
const params = new URLSearchParams({account: accountId ?? ''});
|
|
5386
|
+
const result = await api(`${BASE}/folders/empty?${params}`, 'POST', {path: folder.path, confirmed: true});
|
|
5387
|
+
if (!here() || accountId !== currentAccountId) return;
|
|
5388
|
+
E.toast(`Permanently deleted ${result.deleted} message${result.deleted === 1 ? '' : 's'} from Trash.`, {type: 'success'});
|
|
5389
|
+
const open = currentFolder;
|
|
5390
|
+
await loadFolders({quiet: true});
|
|
5391
|
+
const shown = folders.find(f => f.path === open);
|
|
5392
|
+
if (here() && accountId === currentAccountId && shown && currentFolder !== open) await selectFolder(shown);
|
|
5393
|
+
} catch (err) {
|
|
5394
|
+
E.toast(err.message || 'Trash could not be emptied.', {type: 'error'});
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5397
|
+
|
|
5365
5398
|
/**
|
|
5366
5399
|
* Delete a folder and everything inside it.
|
|
5367
5400
|
*
|
|
@@ -471,3 +471,26 @@ export async function setSpamBulk({
|
|
|
471
471
|
uidMap: moved.uidMap, keywords, mirrored: moved.mirrored
|
|
472
472
|
};
|
|
473
473
|
}
|
|
474
|
+
|
|
475
|
+
/** Permanently empty a server-designated Trash folder, retaining the folder and its children. */
|
|
476
|
+
export async function emptyTrash({pool, store, account, poolKey, connection, folder}) {
|
|
477
|
+
const boxes = await pool.withClient(poolKey, connection, client => client.list());
|
|
478
|
+
if (!boxes.some(box => box.path === folder && box.specialUse === '\\Trash')) {
|
|
479
|
+
const error = new Error('Only the server-designated Trash folder can be emptied.');
|
|
480
|
+
error.statusCode = 409;
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
const deleted = await pool.withMailbox(poolKey, connection, folder, async (client, mailbox) => {
|
|
484
|
+
const count = Number(mailbox.exists);
|
|
485
|
+
if (count === 0) return 0;
|
|
486
|
+
const lastUid = Number(mailbox.uidNext) - 1;
|
|
487
|
+
if (!Number.isSafeInteger(count) || count < 0 || !Number.isSafeInteger(lastUid) || lastUid < 1) {
|
|
488
|
+
throw new Error('The Trash message count could not be verified. Refresh and try again.');
|
|
489
|
+
}
|
|
490
|
+
const result = await client.messageDelete({uid: `1:${lastUid}`}, {uid: true});
|
|
491
|
+
if (result === false) throw new Error('The server refused to empty Trash.');
|
|
492
|
+
return count;
|
|
493
|
+
}, {writable: true});
|
|
494
|
+
if (store) await store.clearFolder(account.id, folder);
|
|
495
|
+
return {deleted, path: folder, expunged: true};
|
|
496
|
+
}
|