@bobfrankston/rmfmail 1.2.241 → 1.2.242
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/client/app.bundle.js +26 -2
- package/client/app.bundle.js.map +3 -3
- package/client/components/message-list.ts +13 -2
- package/client/components/message-viewer.js +62 -1
- package/client/components/message-viewer.js.map +1 -1
- package/client/components/message-viewer.ts +73 -1
- package/client/compose/compose.bundle.js +14 -0
- package/client/compose/compose.bundle.js.map +2 -2
- package/client/compose/compose.css +11 -0
- package/client/lib/rmf-tiny.js +14 -0
- package/package.json +3 -3
- package/packages/mailx-service/index.ts +34 -4
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-26520 → node_modules.npmglobalize-stash-33484}/.package-lock.json +0 -0
|
@@ -236,6 +236,60 @@ export function showPreviewBodyMenu(absX: number, absY: number, selectedText: st
|
|
|
236
236
|
showContextMenu(absX, absY, items);
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
/** How long the viewer waits for a body before it stops counting and
|
|
240
|
+
* explains itself. Long enough that a slow Dovecot read or a cold Gmail
|
|
241
|
+
* batch still lands inside it; short enough that nobody watches a counter
|
|
242
|
+
* tick past a minute wondering whether the app is alive. */
|
|
243
|
+
const WAIT_GIVE_UP_SECS = 45;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Replace the elapsed-time counter with something that tells the user what
|
|
247
|
+
* happened and what they can still do.
|
|
248
|
+
*
|
|
249
|
+
* The counter existed to distinguish "slow" from "wedged" (v1.0.x, Bob
|
|
250
|
+
* 2026-05-24) and it does — but only for the first half-minute. Past that it
|
|
251
|
+
* is a progress bar for a process that is not running: nothing retries, no
|
|
252
|
+
* error was reported, and the number just grows. This says so.
|
|
253
|
+
*/
|
|
254
|
+
function renderBodyFetchGaveUp(
|
|
255
|
+
bodyEl: HTMLElement,
|
|
256
|
+
accountId: string,
|
|
257
|
+
uid: number,
|
|
258
|
+
folderId: number | undefined,
|
|
259
|
+
secs: number,
|
|
260
|
+
previewText: string,
|
|
261
|
+
specialUse?: string,
|
|
262
|
+
envelope?: any,
|
|
263
|
+
): void {
|
|
264
|
+
// A Gmail account addresses bodies by a uid derived from the Gmail
|
|
265
|
+
// message id — a ~10^14 number. A small sequential uid on such an
|
|
266
|
+
// account means this row came from the IMAP side (server search, an
|
|
267
|
+
// older sync), and the Gmail API fetcher has no way to name it. That is
|
|
268
|
+
// not a slow network and retrying will not help, so say which it is
|
|
269
|
+
// instead of offering false hope.
|
|
270
|
+
const looksImapUidOnApiAccount = uid > 0 && uid < 1_000_000_000;
|
|
271
|
+
const why = looksImapUidOnApiAccount
|
|
272
|
+
? `This message was indexed over IMAP, but its account fetches bodies through the Gmail API, which addresses messages by a different id. The body can't be requested for this row.`
|
|
273
|
+
: `The server accepted the request but never returned the body.`;
|
|
274
|
+
const snippet = previewText
|
|
275
|
+
? `<div class="mv-preview-placeholder">${escapeHtml(previewText)}<div class="mv-tear-line"><span>✂ snippet — this is all that arrived ✂</span></div></div>`
|
|
276
|
+
: "";
|
|
277
|
+
bodyEl.innerHTML = `${snippet}<div class="mv-system-message mv-system-error">
|
|
278
|
+
<div class="mv-system-tag">rmfmail</div>
|
|
279
|
+
<div class="mv-system-title">Body didn't arrive after ${secs}s — not still loading</div>
|
|
280
|
+
<div class="mv-system-body">${escapeHtml(why)}
|
|
281
|
+
<div style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap">
|
|
282
|
+
<button id="mv-body-retry" class="tb-btn">Try again</button>
|
|
283
|
+
</div></div></div>`;
|
|
284
|
+
bodyEl.querySelector("#mv-body-retry")?.addEventListener("click", () => {
|
|
285
|
+
invalidateParsedCache(accountId, folderId, uid);
|
|
286
|
+
showMessage(accountId, uid, folderId, specialUse, true, envelope).catch(() => { /* reported by the next render */ });
|
|
287
|
+
});
|
|
288
|
+
// No "View source" button here — the viewer toolbar already has Source,
|
|
289
|
+
// and a second control that dispatches an event nothing listens for is
|
|
290
|
+
// the silent-no-op pattern this session spent the day removing.
|
|
291
|
+
}
|
|
292
|
+
|
|
239
293
|
/** Toggle preview-fullscreen mode. CSS rule on body.fullscreen-preview hides
|
|
240
294
|
* toolbar / rail / folder tree / list / statusbar and expands the viewer to
|
|
241
295
|
* the full viewport. Exits via second toggle, Esc, or the floating ✕. */
|
|
@@ -777,12 +831,30 @@ export async function showMessage(accountId: string, uid: number, folderId?: num
|
|
|
777
831
|
// After 30 s a still-counting timer is suspicious — surface
|
|
778
832
|
// a hint so the user knows the daemon should be looked at.
|
|
779
833
|
if (secs === 30) span.classList.add("mv-wait-slow");
|
|
834
|
+
// …and at 45 s, stop counting and SAY something. A counter
|
|
835
|
+
// that runs forever is not information: the fetch is not
|
|
836
|
+
// "still working", it has failed in a way nothing reports —
|
|
837
|
+
// e.g. a Gmail row indexed over IMAP (small sequential uid)
|
|
838
|
+
// whose body the Gmail API path cannot address at all,
|
|
839
|
+
// because it only knows uids derived from Gmail message ids
|
|
840
|
+
// (Bob 2026-08-09: "definitely need an appropriate message
|
|
841
|
+
// rather than an infinitely counting timer"). Whatever the
|
|
842
|
+
// cause, at this point the honest thing is to say the body
|
|
843
|
+
// did not arrive, keep the snippet visible, and offer the
|
|
844
|
+
// paths that DO work.
|
|
845
|
+
if (secs >= WAIT_GIVE_UP_SECS) {
|
|
846
|
+
clearInterval(tick);
|
|
847
|
+
off?.();
|
|
848
|
+
renderBodyFetchGaveUp(bodyEl, accountId, uid, folderId, secs, previewText, specialUse, envelope || cached || undefined);
|
|
849
|
+
}
|
|
780
850
|
}, 1000);
|
|
781
851
|
// Subscribe via Store bus (the post-refactor surface). The
|
|
782
852
|
// reconciler publishes `bodyAvailable` on `message:<uuid>` (or
|
|
783
853
|
// `folder:<id>` when uuid isn't available); the wildcard catches
|
|
784
854
|
// both. Match by account+uid because the viewer keys off those.
|
|
785
|
-
const
|
|
855
|
+
// eslint-disable-next-line prefer-const — assigned below, referenced by the tick above
|
|
856
|
+
let off: (() => void) | undefined;
|
|
857
|
+
off = subscribeStore("*", (ev: any) => {
|
|
786
858
|
if (ev.kind !== "bodyAvailable") return;
|
|
787
859
|
if (ev.accountId !== accountId || ev.uid !== uid) return;
|
|
788
860
|
if (captureGen !== showMessageGeneration) { off(); clearInterval(tick); return; }
|
|
@@ -2645,6 +2645,20 @@ async function createTinyMceEditor(container2, opts = {}) {
|
|
|
2645
2645
|
// 2026-07-28: pasted/inserted text needs a one-click quote.
|
|
2646
2646
|
"blockquote bullist numlist outdent indent | link table image code rmfcode | emoticons charmap | help"
|
|
2647
2647
|
].join(" | "),
|
|
2648
|
+
// Wrap the toolbar onto as many rows as it needs instead of hiding
|
|
2649
|
+
// the overflow behind a "…" that opens a FLOATING popover over the
|
|
2650
|
+
// message body (TinyMCE default is toolbar_mode:"floating").
|
|
2651
|
+
//
|
|
2652
|
+
// Two things were wrong with the popover: it covers the text you
|
|
2653
|
+
// are writing, and it does not dismiss on its own (Bob 2026-08-08:
|
|
2654
|
+
// "you temporarily extend the menu as a popover but that does not
|
|
2655
|
+
// go away on its own. It makes more sense to just wrap the buttons
|
|
2656
|
+
// onto a separate line. That works because the width is stable so
|
|
2657
|
+
// there is not any fibrillation"). Wrapping grows the header, which
|
|
2658
|
+
// is the honest use of the space — the compose window width does
|
|
2659
|
+
// not change while you type, so the row count is stable and there
|
|
2660
|
+
// is no reflow churn.
|
|
2661
|
+
toolbar_mode: "wrap",
|
|
2648
2662
|
// Include "tools" so wordcount and searchreplace are reachable.
|
|
2649
2663
|
menubar: "file edit view insert format tools",
|
|
2650
2664
|
// View menu — append zoom commands. fullscreen omitted: this editor
|