@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
|
@@ -275,6 +275,17 @@ body {
|
|
|
275
275
|
read a large message being replied to (Bob 2026-06-23). Forcing min-height:0
|
|
276
276
|
bounds each wrapper so the edit-area iframe gets a fixed height and TinyMCE's
|
|
277
277
|
own internal scrollbar engages. */
|
|
278
|
+
/* The toolbar wraps onto extra rows (toolbar_mode:"wrap" in rmf-tiny) rather
|
|
279
|
+
than hiding buttons behind a "…" popover that covers the message body. For
|
|
280
|
+
that to be an ENLARGED bar rather than a clipped one, the header must not be
|
|
281
|
+
a shrink target: it keeps its natural height and the edit area below gives up
|
|
282
|
+
the space (Bob 2026-08-09: "better to just make the bar bigger if it cannot
|
|
283
|
+
be wider"). Without this the header inherits flex-shrink:1 and a narrow
|
|
284
|
+
compose window silently crops the second row. */
|
|
285
|
+
.editor-tinymce .tox-editor-header {
|
|
286
|
+
flex: 0 0 auto !important;
|
|
287
|
+
}
|
|
288
|
+
|
|
278
289
|
.editor-tinymce .tox-editor-container,
|
|
279
290
|
.editor-tinymce .tox-sidebar-wrap,
|
|
280
291
|
.editor-tinymce .tox-edit-area {
|
package/client/lib/rmf-tiny.js
CHANGED
|
@@ -135,6 +135,20 @@ export async function createTinyMceEditor(container, opts = {}) {
|
|
|
135
135
|
// 2026-07-28: pasted/inserted text needs a one-click quote.
|
|
136
136
|
"blockquote bullist numlist outdent indent | link table image code rmfcode | emoticons charmap | help",
|
|
137
137
|
].join(" | "),
|
|
138
|
+
// Wrap the toolbar onto as many rows as it needs instead of hiding
|
|
139
|
+
// the overflow behind a "…" that opens a FLOATING popover over the
|
|
140
|
+
// message body (TinyMCE default is toolbar_mode:"floating").
|
|
141
|
+
//
|
|
142
|
+
// Two things were wrong with the popover: it covers the text you
|
|
143
|
+
// are writing, and it does not dismiss on its own (Bob 2026-08-08:
|
|
144
|
+
// "you temporarily extend the menu as a popover but that does not
|
|
145
|
+
// go away on its own. It makes more sense to just wrap the buttons
|
|
146
|
+
// onto a separate line. That works because the width is stable so
|
|
147
|
+
// there is not any fibrillation"). Wrapping grows the header, which
|
|
148
|
+
// is the honest use of the space — the compose window width does
|
|
149
|
+
// not change while you type, so the row count is stable and there
|
|
150
|
+
// is no reflow churn.
|
|
151
|
+
toolbar_mode: "wrap",
|
|
138
152
|
// Include "tools" so wordcount and searchreplace are reachable.
|
|
139
153
|
menubar: "file edit view insert format tools",
|
|
140
154
|
// View menu — append zoom commands. fullscreen omitted: this editor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/rmfmail",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.242",
|
|
4
4
|
"description": "Local-first email client with IMAP sync and standalone native app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/mailx.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@bobfrankston/mailx-store-web": "^0.1.76",
|
|
39
39
|
"@bobfrankston/mailx-sync": "^0.1.29",
|
|
40
40
|
"@bobfrankston/miscinfo": "^1.0.16",
|
|
41
|
-
"@bobfrankston/msger": "^0.1.
|
|
41
|
+
"@bobfrankston/msger": "^0.1.424",
|
|
42
42
|
"@bobfrankston/node-tcp-transport": "^0.1.10",
|
|
43
43
|
"@bobfrankston/oauthsupport": "^1.0.34",
|
|
44
44
|
"@bobfrankston/rmf-tiny": "^0.1.45",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"@bobfrankston/mailx-store-web": "^0.1.76",
|
|
119
119
|
"@bobfrankston/mailx-sync": "^0.1.29",
|
|
120
120
|
"@bobfrankston/miscinfo": "^1.0.16",
|
|
121
|
-
"@bobfrankston/msger": "^0.1.
|
|
121
|
+
"@bobfrankston/msger": "^0.1.424",
|
|
122
122
|
"@bobfrankston/node-tcp-transport": "^0.1.10",
|
|
123
123
|
"@bobfrankston/oauthsupport": "^1.0.34",
|
|
124
124
|
"@bobfrankston/rmf-tiny": "^0.1.45",
|
|
@@ -2038,6 +2038,11 @@ export class MailxService implements MailxApi {
|
|
|
2038
2038
|
let foldersSearched = 0;
|
|
2039
2039
|
let foldersFailed = 0;
|
|
2040
2040
|
const failedFolders: string[] = [];
|
|
2041
|
+
// Folders the server says are gone: counted apart from transient
|
|
2042
|
+
// failures so the UI never tells the user to retry something that
|
|
2043
|
+
// cannot succeed.
|
|
2044
|
+
let foldersGone = 0;
|
|
2045
|
+
const goneFolders: string[] = [];
|
|
2041
2046
|
let droppedHits = 0;
|
|
2042
2047
|
|
|
2043
2048
|
// Bounded concurrency. The per-account ops queue already
|
|
@@ -2071,9 +2076,28 @@ export class MailxService implements MailxApi {
|
|
|
2071
2076
|
for (let j = 0; j < results.length; j++) {
|
|
2072
2077
|
const r = results[j];
|
|
2073
2078
|
if (r.status !== "fulfilled") {
|
|
2079
|
+
const why = String(r.reason?.message || r.reason || "");
|
|
2080
|
+
// A mailbox the server says does not exist is not a
|
|
2081
|
+
// timeout and will never succeed — every search pays
|
|
2082
|
+
// for it again and the user is told to "retry to
|
|
2083
|
+
// search them again", which cannot work. Gmail
|
|
2084
|
+
// answers `[NONEXISTENT] Unknown Mailbox` for the
|
|
2085
|
+
// `[Imap]/…` rows mailx keeps locally; Dovecot says
|
|
2086
|
+
// "doesn't exist". Drop the local folder row (the
|
|
2087
|
+
// IMAP sync path already does this on its own
|
|
2088
|
+
// wording) and count it separately.
|
|
2089
|
+
if (/\[NONEXISTENT\]|Unknown Mailbox|doesn't exist|does not exist/i.test(why)) {
|
|
2090
|
+
foldersGone++;
|
|
2091
|
+
goneFolders.push(`${acct.id}/${batch[j].path}`);
|
|
2092
|
+
console.log(` [server-search] ${acct.id}/${batch[j].path}: mailbox not on server — removing the stale local folder row`);
|
|
2093
|
+
try { this.db.deleteFolder(batch[j].id); } catch (e: any) {
|
|
2094
|
+
console.error(` [server-search] could not remove stale folder ${batch[j].path}: ${e.message}`);
|
|
2095
|
+
}
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2074
2098
|
foldersFailed++;
|
|
2075
2099
|
failedFolders.push(`${acct.id}/${batch[j].path}`);
|
|
2076
|
-
console.error(` [server-search] ${acct.id}/${batch[j].path}: ${
|
|
2100
|
+
console.error(` [server-search] ${acct.id}/${batch[j].path}: ${why}`);
|
|
2077
2101
|
continue;
|
|
2078
2102
|
}
|
|
2079
2103
|
foldersSearched++;
|
|
@@ -2101,14 +2125,20 @@ export class MailxService implements MailxApi {
|
|
|
2101
2125
|
// Newest first, then paginate.
|
|
2102
2126
|
items.sort((a: any, b: any) => (b.date?.getTime?.() || 0) - (a.date?.getTime?.() || 0));
|
|
2103
2127
|
const sliced = items.slice((page - 1) * pageSize, page * pageSize);
|
|
2104
|
-
if (foldersFailed > 0 || droppedHits > 0) {
|
|
2105
|
-
console.log(` [server-search] q="${q}" — ${total} hits across ${foldersSearched} folders; ${foldersFailed}
|
|
2128
|
+
if (foldersFailed > 0 || droppedHits > 0 || foldersGone > 0) {
|
|
2129
|
+
console.log(` [server-search] q="${q}" — ${total} hits across ${foldersSearched} folders; ${foldersFailed} failed, ${foldersGone} stale folder(s) removed, ${droppedHits} hits dropped${aborted ? " (superseded)" : ""}`);
|
|
2106
2130
|
}
|
|
2107
2131
|
return {
|
|
2108
2132
|
items: sliced, total, page, pageSize,
|
|
2109
2133
|
aborted,
|
|
2110
|
-
|
|
2134
|
+
// A superseded search is NOT a partial result — the user typed
|
|
2135
|
+
// another character and this generation was abandoned on
|
|
2136
|
+
// purpose. Reporting its untouched folders as failures is what
|
|
2137
|
+
// produced "99 could not be searched (timeout / connection)"
|
|
2138
|
+
// for a search nothing had gone wrong with (Bob 2026-08-09).
|
|
2139
|
+
partial: !aborted && (foldersFailed > 0 || droppedHits > 0),
|
|
2111
2140
|
foldersSearched, foldersFailed, failedFolders, droppedHits,
|
|
2141
|
+
foldersGone, goneFolders,
|
|
2112
2142
|
};
|
|
2113
2143
|
} else if (scope === "current" && accountId && folderId) {
|
|
2114
2144
|
// Per-folder search — folder scope is the user's explicit
|