@bobfrankston/rmfmail 1.2.178 → 1.2.180
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/TODO.md +1 -0
- package/client/android-bootstrap.bundle.js +57 -6
- package/client/android-bootstrap.bundle.js.map +3 -3
- package/client/app.bundle.js +12 -0
- package/client/app.bundle.js.map +3 -3
- package/client/compose/compose.bundle.js +74 -0
- package/client/compose/compose.bundle.js.map +3 -3
- package/client/compose/compose.js +93 -0
- package/client/compose/compose.js.map +1 -1
- package/client/compose/compose.ts +89 -0
- package/client/lib/api-client.js +6 -0
- package/client/lib/api-client.js.map +1 -1
- package/client/lib/api-client.ts +7 -0
- package/client/lib/mailxapi.js +5 -0
- package/package.json +5 -5
- package/packages/mailx-imap/index.d.ts +10 -0
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +18 -0
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +17 -0
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-service/index.d.ts +9 -0
- package/packages/mailx-service/index.d.ts.map +1 -1
- package/packages/mailx-service/index.js +56 -11
- package/packages/mailx-service/index.js.map +1 -1
- package/packages/mailx-service/index.ts +55 -12
- package/packages/mailx-service/jsonrpc.js +2 -0
- package/packages/mailx-service/jsonrpc.js.map +1 -1
- package/packages/mailx-service/jsonrpc.ts +2 -0
- package/packages/mailx-service/package.json +1 -1
- package/packages/mailx-settings/package.json +1 -1
- package/packages/mailx-store/package.json +1 -1
- package/packages/mailx-store-web/android-bootstrap.js +2 -0
- package/packages/mailx-store-web/android-bootstrap.js.map +1 -1
- package/packages/mailx-store-web/android-bootstrap.ts +2 -0
- package/packages/mailx-store-web/package.json +1 -1
- package/packages/mailx-store-web/web-service.d.ts.map +1 -1
- package/packages/mailx-store-web/web-service.js +30 -7
- package/packages/mailx-store-web/web-service.js.map +1 -1
- package/packages/mailx-store-web/web-service.ts +31 -8
- package/packages/mailx-types/index.d.ts +2 -0
- package/packages/mailx-types/index.d.ts.map +1 -1
- package/packages/mailx-types/index.js +3 -0
- package/packages/mailx-types/index.js.map +1 -1
- package/packages/mailx-types/index.ts +5 -0
- package/packages/mailx-types/mime-inline-images.d.ts +38 -0
- package/packages/mailx-types/mime-inline-images.d.ts.map +1 -0
- package/packages/mailx-types/mime-inline-images.js +45 -0
- package/packages/mailx-types/mime-inline-images.js.map +1 -0
- package/packages/mailx-types/mime-inline-images.ts +55 -0
- package/packages/mailx-types/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-7544 → node_modules.npmglobalize-stash-40432}/.package-lock.json +0 -0
package/TODO.md
CHANGED
|
@@ -23,6 +23,7 @@ These items don't need user input — source-only changes, compile-verified befo
|
|
|
23
23
|
| **10** | **C159 — Harper local grammar check** [M] | M | **Desktop SHIPPED v1.2.168** (2026-07-22): daemon-side Harper engine + `grammarLint` IPC, generic squiggle overlay + suggestion popup across ALL THREE editors, Settings toggle default ON, quoted chains + spelling lints skipped. **Remaining: Android** (host LocalLinter in android-bootstrap, harper.js dist in APK assets like sql.js, ~+18 MB) + per-rule config + userdict merge. Details: `docs/harper-integration.md`. |
|
|
24
24
|
| **9** | **C158 — Android double-init after fold-triggered reload** [M] | M | 2026-07-22 13:07:49 logit trail: ONE page reload after unfolding produced TWO module loads, duplicate "bridge installed", duplicate GDrive lookups, and THREE "Starting OAuth flow" launches. Activity did NOT recreate (ConfigChanges already declared; no OnCreate in trail) — find what reloads the WebView on fold and make android-bootstrap init idempotent (single-flight guard on the module-level init, dedupe OAuth intent launches). |
|
|
25
25
|
| **11** | **C160 — Attachment delete: Gmail + Android halves** [M] | M | Desktop IMAP shipped v1.2.173 (right-click chip → Delete one/all: verified local strip + append-confirm-then-delete server replace, .pre-strip.bak backup, UID rebind). Remaining: Gmail REST path (messages.insert + trash of original — changes message id, provider_id rebind) and Android (bridge method + web-service strip using its own parser). Strip helper lives in mailx-service (enumerateMimeLeafParts) — move to mailx-types when Android needs it. |
|
|
26
|
+
| **12** | **C161 — `_rmfmail` IMAP folder for cross-machine state sync** [M] | M | Bob 2026-07-27: use a hidden `_rmfmail` folder on the mail server itself as a per-account sync channel for state that today rides on Google Drive or nowhere — reminder dismissals, per-machine prefs, read-position, contact edits for non-Google accounts. Advantages over Drive files: works for every IMAP account (no Google dependency), naturally per-account, versioned by APPEND, and reuses the existing connection/sync machinery. Shape: one message per state document with a stable `X-Rmfmail-State: <name>` header, save = delete-and-reappend (the draft-dedup pattern), merge semantics per document type (reminders already have per-key max merge in mailx-types). Design Qs: folder visibility (subscribe or not, hide from folder tree), conflict handling when two machines append concurrently, and whether reminders.jsonc migrates or dual-writes. First shipped consumer of the pattern: draft freshness probe (v1.2.180) already proves the search-by-header primitive. |
|
|
26
27
|
| **5** | **C124 — mailto handler on macOS** (Linux ✅ 2026-07-08 v1.2.115) | S | Linux half shipped: `--register-mailto` on linux writes `~/.local/share/applications/rmfmail.desktop` (`MimeType=x-scheme-handler/mailto;`, quoted node+mailx.js Exec, bare `%u`), runs `xdg-mime default` + `update-desktop-database`; `--unregister-mailto` removes it. **Remaining: macOS** — real `.app` bundle (LaunchServices won't register a bare script) with `Info.plist` `CFBundleURLTypes` declaring `mailto` + a tiny launcher binary; `LSSetDefaultHandlerForURLScheme` programmatically sets the default. Rust `rmfmailto-src/` crate already portable — drop the Win32 imports and `cargo build --target x86_64-apple-darwin` produces the macOS launcher. Needs a Mac to build/verify. |
|
|
27
28
|
| ~~7~~ | ~~**C154 — Audit Android bridge vs MailxApi contract**~~ — **DONE** (2026-07-08, v1.2.115). Bridge now has a generic contract-backstop: every service method not explicitly adapted gets a positional pass-through, so a missing method can never again fail as `parent bridge has no method "X"`. Wire-shape mismatches adapted explicitly (`readJsoncFile`/`writeJsoncFile`/`formatJsonc`/`readConfigHelp` → `{content}`/`{ok}`, `drainStoreSync`/`syncFolderNow` → `{ok}`). Contract gained the missing entries (`getMessageSource`, `copyMessages`, `syncFolderNow`, `cancelServerSearch`, `getReminderSound`, `getCalendars`, optional `popoutWindow`) with web-service stubs per the policy (reads → graceful default, writes/OS → notImpl). Bonus fixes: bridge `getMessages` was dropping sort/sortDir/search and `searchMessages` was dropping scope/accountId/folderId — Android sort toggles and scoped search silently didn't work. | S | |
|
|
28
29
|
~~done — C120 read transport diagnostics~~ shipped v1.0.583 (per-folder timeout error now includes `[conn#X r=YB w=ZB writes=N sinceLastRead=Tms]` snapshot for the doomed socket).
|
|
@@ -2386,6 +2386,28 @@ var init_reminder_state = __esm({
|
|
|
2386
2386
|
}
|
|
2387
2387
|
});
|
|
2388
2388
|
|
|
2389
|
+
// packages/mailx-types/mime-inline-images.js
|
|
2390
|
+
function extractInlineImages(html, idDomain) {
|
|
2391
|
+
const images = [];
|
|
2392
|
+
const byDataUri = /* @__PURE__ */ new Map();
|
|
2393
|
+
const stamp = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
2394
|
+
const out = html.replace(/(<img\b[^>]*?\ssrc=)(["'])(data:(image\/[\w.+-]+);base64,([A-Za-z0-9+/=\s]+))\2/gi, (_m, prefix, quote, dataUri, mime, b64) => {
|
|
2395
|
+
let cid = byDataUri.get(dataUri);
|
|
2396
|
+
if (!cid) {
|
|
2397
|
+
cid = `img${images.length + 1}.${stamp}@${idDomain}`;
|
|
2398
|
+
byDataUri.set(dataUri, cid);
|
|
2399
|
+
images.push({ contentId: cid, mime, base64: b64.replace(/\s+/g, "") });
|
|
2400
|
+
}
|
|
2401
|
+
return `${prefix}${quote}cid:${cid}${quote}`;
|
|
2402
|
+
});
|
|
2403
|
+
return { html: out, images };
|
|
2404
|
+
}
|
|
2405
|
+
var init_mime_inline_images = __esm({
|
|
2406
|
+
"packages/mailx-types/mime-inline-images.js"() {
|
|
2407
|
+
"use strict";
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
|
|
2389
2411
|
// packages/mailx-types/index.js
|
|
2390
2412
|
var mailx_types_exports = {};
|
|
2391
2413
|
__export(mailx_types_exports, {
|
|
@@ -2399,6 +2421,7 @@ __export(mailx_types_exports, {
|
|
|
2399
2421
|
encodeQuotedPrintable: () => encodeQuotedPrintable,
|
|
2400
2422
|
expandRecipients: () => expandRecipients,
|
|
2401
2423
|
extractAddress: () => extractAddress,
|
|
2424
|
+
extractInlineImages: () => extractInlineImages,
|
|
2402
2425
|
flaggedOf: () => flaggedOf,
|
|
2403
2426
|
htmlToPlainText: () => htmlToPlainText,
|
|
2404
2427
|
isAddressToken: () => isAddressToken,
|
|
@@ -2607,6 +2630,7 @@ var init_mailx_types = __esm({
|
|
|
2607
2630
|
init_contacts_config();
|
|
2608
2631
|
init_groups();
|
|
2609
2632
|
init_reminder_state();
|
|
2633
|
+
init_mime_inline_images();
|
|
2610
2634
|
_SEEN = "\\Seen";
|
|
2611
2635
|
_FLAGGED = "\\Flagged";
|
|
2612
2636
|
_ANSWERED = "\\Answered";
|
|
@@ -6170,12 +6194,14 @@ var WebMailxService = class _WebMailxService {
|
|
|
6170
6194
|
const cc = msg.cc?.map((a) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
|
|
6171
6195
|
const bcc = msg.bcc?.map((a) => a.name ? `${a.name} <${a.address}>` : a.address).join(", ");
|
|
6172
6196
|
const hasHtml = !!msg.bodyHtml;
|
|
6173
|
-
const
|
|
6197
|
+
const domain = account.email.split("@")[1] || "mailx.local";
|
|
6198
|
+
const messageId = `<${Date.now()}.${Math.random().toString(36).slice(2)}@${domain}>`;
|
|
6199
|
+
const extracted = hasHtml ? extractInlineImages(msg.bodyHtml, domain) : { html: "", images: [] };
|
|
6200
|
+
const htmlBody = extracted.html;
|
|
6201
|
+
const inlineImages = extracted.images;
|
|
6174
6202
|
const textBody = msg.bodyText || (hasHtml ? htmlToPlainText(htmlBody) : "");
|
|
6175
6203
|
const htmlEncoded = hasHtml ? encodeQuotedPrintable(htmlBody) : "";
|
|
6176
6204
|
const textEncoded = encodeQuotedPrintable(textBody);
|
|
6177
|
-
const domain = account.email.split("@")[1] || "mailx.local";
|
|
6178
|
-
const messageId = `<${Date.now()}.${Math.random().toString(36).slice(2)}@${domain}>`;
|
|
6179
6205
|
const envelope = [
|
|
6180
6206
|
`From: ${fromHeader}`,
|
|
6181
6207
|
`To: ${to}`,
|
|
@@ -6188,9 +6214,11 @@ var WebMailxService = class _WebMailxService {
|
|
|
6188
6214
|
msg.references?.length ? `References: ${msg.references.join(" ")}` : null,
|
|
6189
6215
|
`MIME-Version: 1.0`
|
|
6190
6216
|
].filter((h) => h !== null);
|
|
6217
|
+
const newBoundary = () => `mailx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
6218
|
+
const wrap76 = (s) => s.match(/.{1,76}/g)?.join("\r\n") || s;
|
|
6191
6219
|
const makeInner = () => {
|
|
6192
6220
|
if (hasHtml) {
|
|
6193
|
-
const altBoundary =
|
|
6221
|
+
const altBoundary = newBoundary();
|
|
6194
6222
|
const body = `--${altBoundary}\r
|
|
6195
6223
|
Content-Type: text/plain; charset=UTF-8\r
|
|
6196
6224
|
Content-Transfer-Encoding: quoted-printable\r
|
|
@@ -6203,7 +6231,29 @@ Content-Transfer-Encoding: quoted-printable\r
|
|
|
6203
6231
|
${htmlEncoded}\r
|
|
6204
6232
|
--${altBoundary}--\r
|
|
6205
6233
|
`;
|
|
6206
|
-
|
|
6234
|
+
const alt = { headers: [`Content-Type: multipart/alternative; boundary="${altBoundary}"`], body };
|
|
6235
|
+
if (inlineImages.length === 0)
|
|
6236
|
+
return alt;
|
|
6237
|
+
const relBoundary = newBoundary();
|
|
6238
|
+
const relParts = [
|
|
6239
|
+
`--${relBoundary}\r
|
|
6240
|
+
` + alt.headers.join("\r\n") + `\r
|
|
6241
|
+
\r
|
|
6242
|
+
` + alt.body,
|
|
6243
|
+
...inlineImages.map((im) => `--${relBoundary}\r
|
|
6244
|
+
Content-Type: ${im.mime}\r
|
|
6245
|
+
Content-ID: <${im.contentId}>\r
|
|
6246
|
+
Content-Disposition: inline\r
|
|
6247
|
+
Content-Transfer-Encoding: base64\r
|
|
6248
|
+
\r
|
|
6249
|
+
${wrap76(im.base64)}\r
|
|
6250
|
+
`)
|
|
6251
|
+
];
|
|
6252
|
+
return {
|
|
6253
|
+
headers: [`Content-Type: multipart/related; boundary="${relBoundary}"; type="multipart/alternative"`],
|
|
6254
|
+
body: relParts.join("") + `--${relBoundary}--\r
|
|
6255
|
+
`
|
|
6256
|
+
};
|
|
6207
6257
|
}
|
|
6208
6258
|
return {
|
|
6209
6259
|
headers: [`Content-Type: text/plain; charset=UTF-8`, `Content-Transfer-Encoding: quoted-printable`],
|
|
@@ -6213,7 +6263,6 @@ ${htmlEncoded}\r
|
|
|
6213
6263
|
const atts = Array.isArray(msg.attachments) ? msg.attachments : [];
|
|
6214
6264
|
let rawMessage;
|
|
6215
6265
|
if (atts.length > 0) {
|
|
6216
|
-
const wrap76 = (s) => s.match(/.{1,76}/g)?.join("\r\n") || s;
|
|
6217
6266
|
const mixedBoundary = `mailxmix_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
6218
6267
|
const inner = makeInner();
|
|
6219
6268
|
const parts = [];
|
|
@@ -11429,6 +11478,8 @@ function installBridge() {
|
|
|
11429
11478
|
await service.deleteDraft(accountId, draftUid);
|
|
11430
11479
|
return { ok: true };
|
|
11431
11480
|
},
|
|
11481
|
+
// No IMAP-side draft probe on Android — compose keeps its local copy.
|
|
11482
|
+
checkDraftNewer: async () => null,
|
|
11432
11483
|
// Full arg pass-through — dropping scope/accountId/folderId made every
|
|
11433
11484
|
// Android search behave as all-mailboxes regardless of the UI scope.
|
|
11434
11485
|
searchMessages: (query, page, pageSize, scope, accountId, folderId, includeTrashSpam) => service.searchMessages(query, page, pageSize, scope, accountId, folderId, includeTrashSpam),
|