domma-cms 0.112.0 → 0.112.1
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
CHANGED
|
@@ -277,6 +277,9 @@ return {
|
|
|
277
277
|
// State
|
|
278
278
|
// ---------------------------------------------------------------
|
|
279
279
|
let mailboxes = [];
|
|
280
|
+
let refreshTimer = null;
|
|
281
|
+
let refreshing = false;
|
|
282
|
+
let observedThreadPage = null;
|
|
280
283
|
let currentAccountId = null;
|
|
281
284
|
let folders = [];
|
|
282
285
|
let currentFolder = 'INBOX';
|
|
@@ -554,6 +557,8 @@ return {
|
|
|
554
557
|
openPanels.clear();
|
|
555
558
|
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
556
559
|
cancelMarkRead();
|
|
560
|
+
clearInterval(refreshTimer);
|
|
561
|
+
refreshTimer = null;
|
|
557
562
|
clearTimeout(undoTimer);
|
|
558
563
|
if (keyboardHandler) document.removeEventListener('keydown', keyboardHandler);
|
|
559
564
|
keyboardHandler = null;
|
|
@@ -1349,13 +1354,40 @@ return {
|
|
|
1349
1354
|
// ---------------------------------------------------------------
|
|
1350
1355
|
|
|
1351
1356
|
/**
|
|
1352
|
-
*
|
|
1357
|
+
* Check for arrivals before reloading, including in conversation mode.
|
|
1353
1358
|
*
|
|
1354
1359
|
* @returns {Promise<void>}
|
|
1355
1360
|
*/
|
|
1356
|
-
async function
|
|
1361
|
+
async function refreshMailbox() {
|
|
1362
|
+
if (refreshing || !currentAccountId) return;
|
|
1363
|
+
refreshing = true;
|
|
1364
|
+
const account = currentAccountId;
|
|
1365
|
+
try {
|
|
1366
|
+
if (features.search) {
|
|
1367
|
+
try {
|
|
1368
|
+
const result = await api(`${BASE}/sync/run?${new URLSearchParams({account})}`, 'POST');
|
|
1369
|
+
const failure = result.folders?.[currentFolder]?.mode === 'error'
|
|
1370
|
+
? result.folders[currentFolder] : null;
|
|
1371
|
+
if (failure) throw new Error(failure.reason);
|
|
1372
|
+
} catch (err) {
|
|
1373
|
+
// Reduced mode still reads live from IMAP.
|
|
1374
|
+
if (err.status !== 503) throw err;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (!here() || currentAccountId !== account) return;
|
|
1378
|
+
page = 1;
|
|
1379
|
+
await loadMessages({fresh: true});
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
handleError(err);
|
|
1382
|
+
} finally {
|
|
1383
|
+
refreshing = false;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/** Fetch and draw the current page. */
|
|
1388
|
+
async function loadMessages({fresh = false} = {}) {
|
|
1357
1389
|
if (searchQuery) return runSearch();
|
|
1358
|
-
if (threadsOn) return loadThreads();
|
|
1390
|
+
if (threadsOn) return loadThreads({fresh});
|
|
1359
1391
|
|
|
1360
1392
|
const loader = E.loader(listEl, {type: 'dots'});
|
|
1361
1393
|
try {
|
|
@@ -1364,6 +1396,7 @@ return {
|
|
|
1364
1396
|
folder: currentFolder,
|
|
1365
1397
|
page: String(page)
|
|
1366
1398
|
});
|
|
1399
|
+
if (fresh) params.set('fresh', '1');
|
|
1367
1400
|
const result = await api(`${BASE}/messages?${params}`);
|
|
1368
1401
|
lastListSource = result.source ?? 'imap';
|
|
1369
1402
|
messages = result.messages;
|
|
@@ -1389,7 +1422,7 @@ return {
|
|
|
1389
1422
|
*
|
|
1390
1423
|
* @returns {Promise<void>}
|
|
1391
1424
|
*/
|
|
1392
|
-
async function loadThreads() {
|
|
1425
|
+
async function loadThreads({fresh = false} = {}) {
|
|
1393
1426
|
const loader = E.loader(listEl, {type: 'dots'});
|
|
1394
1427
|
try {
|
|
1395
1428
|
const params = new URLSearchParams({
|
|
@@ -1405,6 +1438,7 @@ return {
|
|
|
1405
1438
|
// next folder may well be mirrored, and silently
|
|
1406
1439
|
// forgetting the setting because one folder was not is
|
|
1407
1440
|
// worse than one line of explanation.
|
|
1441
|
+
if (fresh) params.set('fresh', '1');
|
|
1408
1442
|
const flat = await api(`${BASE}/messages?${params}`);
|
|
1409
1443
|
lastListSource = flat.source ?? 'imap';
|
|
1410
1444
|
messages = flat.messages;
|
|
@@ -4656,7 +4690,7 @@ return {
|
|
|
4656
4690
|
id: `${pluginName}-refresh`,
|
|
4657
4691
|
icon: 'refresh-cw',
|
|
4658
4692
|
title: 'Refresh',
|
|
4659
|
-
onClick: () =>
|
|
4693
|
+
onClick: () => refreshMailbox()
|
|
4660
4694
|
});
|
|
4661
4695
|
if (refreshPlaced) $container.find('#mail-refresh-btn').get(0)?.remove();
|
|
4662
4696
|
const placed = chrome.addBannerAction?.($container, {
|
|
@@ -4668,7 +4702,7 @@ return {
|
|
|
4668
4702
|
if (placed) $container.find('#mail-settings-btn').get(0)?.remove();
|
|
4669
4703
|
} catch { /* no banner: the pane buttons stay */ }
|
|
4670
4704
|
if (!here()) return;
|
|
4671
|
-
$container.find('#mail-refresh-btn').on('click', () =>
|
|
4705
|
+
$container.find('#mail-refresh-btn').on('click', () => refreshMailbox());
|
|
4672
4706
|
|
|
4673
4707
|
// Folder management was right-click only, which is the same as not
|
|
4674
4708
|
// existing for anyone who does not already know it is there.
|
|
@@ -4760,6 +4794,31 @@ return {
|
|
|
4760
4794
|
|
|
4761
4795
|
await start();
|
|
4762
4796
|
if (!here()) return;
|
|
4797
|
+
// The sidebar updates independently; keep an open inbox in step with
|
|
4798
|
+
// the mirror without interrupting selection, search or older pages.
|
|
4799
|
+
refreshTimer = setInterval(async () => {
|
|
4800
|
+
if (!here() || document.hidden || refreshing || !currentAccountId ||
|
|
4801
|
+
page !== 1 || searchQuery || selected.size) return;
|
|
4802
|
+
const account = currentAccountId;
|
|
4803
|
+
const folder = currentFolder;
|
|
4804
|
+
refreshing = true;
|
|
4805
|
+
try {
|
|
4806
|
+
const params = new URLSearchParams({account, folder, page: '1'});
|
|
4807
|
+
const result = await api(`${BASE}/messages?${params}`);
|
|
4808
|
+
if (!here() || currentAccountId !== account || currentFolder !== folder ||
|
|
4809
|
+
page !== 1 || searchQuery || selected.size) return;
|
|
4810
|
+
const signature = rows => JSON.stringify(rows.map(m => [m.uid, m.flags]));
|
|
4811
|
+
const snapshot = JSON.stringify([account, folder, result.total, signature(result.messages)]);
|
|
4812
|
+
const changed = threadsOn
|
|
4813
|
+
? snapshot !== observedThreadPage
|
|
4814
|
+
: result.total !== total || signature(result.messages) !== signature(messages);
|
|
4815
|
+
observedThreadPage = snapshot;
|
|
4816
|
+
if (changed) {
|
|
4817
|
+
await loadMessages();
|
|
4818
|
+
}
|
|
4819
|
+
} catch { /* A temporary failure must not replace the open reader. */ }
|
|
4820
|
+
finally { refreshing = false; }
|
|
4821
|
+
}, 30_000);
|
|
4763
4822
|
Domma.icons.scan($container.get(0));
|
|
4764
4823
|
|
|
4765
4824
|
// Domma's router does not call this; the lifetime does the work. Kept so a
|
|
@@ -385,7 +385,7 @@ export function registerReadRoutes(fastify, {auth, hooks, config, pool, pluginNa
|
|
|
385
385
|
// answered from the store - instant, and no connection needed. It
|
|
386
386
|
// returns null for anything not mirrored, which falls through to the
|
|
387
387
|
// wire exactly as the free edition always does.
|
|
388
|
-
if (cachedList) {
|
|
388
|
+
if (cachedList && request.query.fresh !== '1') {
|
|
389
389
|
try {
|
|
390
390
|
const cached = await cachedList({
|
|
391
391
|
account: ctx.account, userId: ctx.uid, folder, page, limit
|