@bobfrankston/rmfmail 1.1.199 → 1.1.201
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 +4 -2
- package/client/app.bundle.js +39 -11
- package/client/app.bundle.js.map +2 -2
- package/client/app.js +29 -11
- package/client/app.js.map +1 -1
- package/client/app.ts +29 -11
- package/client/components/message-list.js +48 -0
- package/client/components/message-list.js.map +1 -1
- package/client/components/message-list.ts +47 -0
- package/package.json +3 -3
- package/packages/mailx-imap/index.d.ts.map +1 -1
- package/packages/mailx-imap/index.js +8 -0
- package/packages/mailx-imap/index.js.map +1 -1
- package/packages/mailx-imap/index.ts +8 -0
- package/packages/mailx-imap/package-lock.json +2 -2
- package/packages/mailx-imap/package.json +1 -1
- package/packages/mailx-settings/package.json +1 -1
- /package/packages/mailx-imap/{node_modules.npmglobalize-stash-77016 → node_modules.npmglobalize-stash-50612}/.package-lock.json +0 -0
package/TODO.md
CHANGED
|
@@ -236,10 +236,12 @@ Previously shown as showstoppers; moved here because they haven't recurred on re
|
|
|
236
236
|
|
|
237
237
|
Small, self-contained items. Pick them up between higher-priority blocks without asking. Bump version per fix.
|
|
238
238
|
|
|
239
|
+
- ~~**Q152 — "Edit as new message" / resend on a sent (or any) message.**~~ **DONE 2026-05-31.** New `editAsNew` compose mode in `client/app.ts` (`openCompose` branch + `editAsNewBody` helper) clones the selected message into a fresh compose: original To/Cc/Subject verbatim, body via `sanitizeQuotedBody` with no quote/forward wrapper, no In-Reply-To/References (new Message-ID at send). Right-click menu entry "Edit as new message" added after Forward in `message-list.ts`. Replaces the move-to-Drafts kludge. *(Original entry below.)* Bob 2026-05-29: no clean way to take an already-sent message, tweak it, and send it again — today's only path is move-to-Drafts-then-edit (a kludge) or Forward (adds `Fwd:` + quote wrapper, wrong recipients). Add an **"Edit as new message"** affordance — in the viewer toolbar (next to Forward) and/or the message-list right-click menu — that loads the selected message's `.eml` **straight into a fresh compose** as the author: original To/Cc/Subject/body editable, **no `Fwd:`/`Re:` prefix, no quote indent, no In-Reply-To/References threading headers**, a new Message-ID. Effectively "duplicate into compose." Distinct from the existing Drafts `Edit & Send` (that edits the draft in place); this clones any message into a new outgoing draft. Reuse the compose-init plumbing the draft-edit path already uses (`showComposeOverlay` + the init payload built in `app.ts`); the only difference is which headers get stripped vs. carried. Most natural as the message-viewer From/To right-click "duplicate"-style action plus a toolbar button gated on non-draft messages. [S]
|
|
240
|
+
- ~~**Preview CSS leak — `<style>`/`<head>` contents bled into the message-list one-liner.**~~ DONE 2026-05-31. `extractPreview` (`mailx-imap/index.ts`) flattened HTML by stripping tags only, leaving the CSS *between* `<style>…</style>` (and `<head>`/`<script>`) in the preview as `*{box-sizing…}` / `@media…{…}` garbage (Bob's marketing-mail shot). Now strips those block contents + HTML comments before the tag-strip; `[image]` marker preserved. IMAP-path only (Gmail uses Google's clean snippet). Existing rows refresh via `rmfmail -repair`.
|
|
239
241
|
- ~~**Q138 — Quoted-reply image sizes lost.**~~ DONE 2026-05-11 in `client/app.ts:1000` — strip skips `<img>`. Two-pass loop handles multiple stripped attrs per tag.
|
|
240
|
-
-
|
|
242
|
+
- ~~**Q140 — User-configurable holiday calendars (generalization).**~~ **RETIRED 2026-05-29 — already dynamic, superseded by Q143.** Bob: the holiday list is pulled automatically from whatever calendars the user has selected in Google Calendar, so it's dynamic already. Confirmed in code: `refreshCalendarEvents` enumerates `listCalendars().filter(c => c.selected)` and tags each row with its source; the old hardcoded `HOLIDAY_SOURCES` + `showHolidays`/`showJewishHolidays` toggles are retired (`mailx-service/index.ts:1485`). The user curates the set in Google's web UI — no in-app holiday-calendar editor needed. The curation-gotcha + Hebcal notes below are kept only as reference IF a free-form calendar-ID *picker* is ever added (not currently planned — selection happens in Google, not mailx).
|
|
241
243
|
|
|
242
|
-
**Curation gotcha
|
|
244
|
+
**Curation gotcha (reference only)**: Google's `<locale>.<tradition>#holiday@group.v.calendar.google.com` IDs are NOT all clean per-tradition feeds. The "obvious" IDs are mis-curated catch-alls; the `.official` / proper-noun variant is the actually-clean source for each tradition. Documented examples from 2026-05-11 testing:
|
|
243
245
|
- `en.usa#holiday` — mixed (federal + Christian + Jewish + Orthodox + Armenian + Ethiopian Jewish + …)
|
|
244
246
|
- `en.usa.official#holiday` — clean federal-secular
|
|
245
247
|
- `en.jewish#holiday` — mixed (Jewish + Christian + Orthodox neighbors)
|
package/client/app.bundle.js
CHANGED
|
@@ -2595,10 +2595,29 @@ __export(message_list_exports, {
|
|
|
2595
2595
|
reloadCurrentFolder: () => reloadCurrentFolder,
|
|
2596
2596
|
removeMessagesAndReconcile: () => removeMessagesAndReconcile,
|
|
2597
2597
|
scrollFocusedIntoView: () => scrollFocusedIntoView,
|
|
2598
|
+
setLiveFilter: () => setLiveFilter,
|
|
2598
2599
|
setRowFlagged: () => setRowFlagged,
|
|
2599
2600
|
setRowSeen: () => setRowSeen,
|
|
2600
2601
|
showThreadPopup: () => showThreadPopup
|
|
2601
2602
|
});
|
|
2603
|
+
function setLiveFilter(query) {
|
|
2604
|
+
liveFilterText = (query || "").trim().toLowerCase();
|
|
2605
|
+
const body = document.getElementById("ml-body");
|
|
2606
|
+
if (body)
|
|
2607
|
+
applyLiveFilter(body);
|
|
2608
|
+
}
|
|
2609
|
+
function applyLiveFilter(body) {
|
|
2610
|
+
if (!liveFilterText) {
|
|
2611
|
+
for (const row of body.querySelectorAll(".ml-row.filter-hidden")) {
|
|
2612
|
+
row.classList.remove("filter-hidden");
|
|
2613
|
+
}
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
for (const row of body.querySelectorAll(".ml-row")) {
|
|
2617
|
+
const text = row.textContent?.toLowerCase() || "";
|
|
2618
|
+
row.classList.toggle("filter-hidden", !text.includes(liveFilterText));
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2602
2621
|
function cacheKey(mode, a, f, flagged, q) {
|
|
2603
2622
|
if (mode === "folder")
|
|
2604
2623
|
return `folder:${a}:${f}:${flagged ? "flag" : ""}`;
|
|
@@ -3034,6 +3053,8 @@ function removeMessagesAndReconcile(uids) {
|
|
|
3034
3053
|
function reloadCurrentFolder() {
|
|
3035
3054
|
if (searchMode)
|
|
3036
3055
|
return;
|
|
3056
|
+
if (liveFilterText)
|
|
3057
|
+
return;
|
|
3037
3058
|
if (unifiedMode) {
|
|
3038
3059
|
loadUnifiedInbox(false);
|
|
3039
3060
|
} else if (currentAccountId2 && currentFolderId) {
|
|
@@ -3042,6 +3063,7 @@ function reloadCurrentFolder() {
|
|
|
3042
3063
|
}
|
|
3043
3064
|
function clearSearchMode() {
|
|
3044
3065
|
searchMode = false;
|
|
3066
|
+
liveFilterText = "";
|
|
3045
3067
|
currentSearchQuery = "";
|
|
3046
3068
|
if (wasUnifiedBeforeSearch)
|
|
3047
3069
|
unifiedMode = true;
|
|
@@ -3114,6 +3136,7 @@ async function loadSearchResults(query, scope = "all", accountId = "", folderId
|
|
|
3114
3136
|
if (!searchMode)
|
|
3115
3137
|
wasUnifiedBeforeSearch = unifiedMode;
|
|
3116
3138
|
searchMode = true;
|
|
3139
|
+
liveFilterText = "";
|
|
3117
3140
|
unifiedMode = false;
|
|
3118
3141
|
currentSearchQuery = query;
|
|
3119
3142
|
currentPage = 1;
|
|
@@ -3307,6 +3330,8 @@ function renderMessages(body, accountId, items) {
|
|
|
3307
3330
|
focusedRow = null;
|
|
3308
3331
|
}
|
|
3309
3332
|
}
|
|
3333
|
+
if (liveFilterText)
|
|
3334
|
+
applyLiveFilter(body);
|
|
3310
3335
|
}
|
|
3311
3336
|
function selectFirst(body) {
|
|
3312
3337
|
if (window.innerWidth <= 768)
|
|
@@ -3478,7 +3503,7 @@ function escapeHtml2(s) {
|
|
|
3478
3503
|
div.textContent = s;
|
|
3479
3504
|
return div.innerHTML;
|
|
3480
3505
|
}
|
|
3481
|
-
var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, unifiedMode, searchMode, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, currentSort, currentSortDir, loadGen, listCache, CACHE_KEY_UNIFIED, positionMemory, POSITION_STORAGE_KEY, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
|
|
3506
|
+
var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, unifiedMode, searchMode, liveFilterText, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, currentSort, currentSortDir, loadGen, listCache, CACHE_KEY_UNIFIED, positionMemory, POSITION_STORAGE_KEY, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
|
|
3482
3507
|
var init_message_list = __esm({
|
|
3483
3508
|
"client/components/message-list.js"() {
|
|
3484
3509
|
"use strict";
|
|
@@ -3493,6 +3518,7 @@ var init_message_list = __esm({
|
|
|
3493
3518
|
loading = false;
|
|
3494
3519
|
unifiedMode = false;
|
|
3495
3520
|
searchMode = false;
|
|
3521
|
+
liveFilterText = "";
|
|
3496
3522
|
currentSearchQuery = "";
|
|
3497
3523
|
wasUnifiedBeforeSearch = false;
|
|
3498
3524
|
showToInsteadOfFrom = false;
|
|
@@ -3983,6 +4009,7 @@ var init_message_list = __esm({
|
|
|
3983
4009
|
{ label: "Reply", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "reply" } })) },
|
|
3984
4010
|
{ label: "Reply All", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "replyAll" } })) },
|
|
3985
4011
|
{ label: "Forward", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "forward" } })) },
|
|
4012
|
+
{ label: "Edit as new message", action: () => document.dispatchEvent(new CustomEvent("mailx-compose", { detail: { mode: "editAsNew" } })) },
|
|
3986
4013
|
{ label: "", action: () => {
|
|
3987
4014
|
}, separator: true },
|
|
3988
4015
|
{
|
|
@@ -7434,13 +7461,13 @@ document.getElementById("btn-factory-reset")?.addEventListener("click", async ()
|
|
|
7434
7461
|
async function openCompose(mode, overrideMsg, overrideAccountId) {
|
|
7435
7462
|
logClientEvent("openCompose-entry", { mode });
|
|
7436
7463
|
const current = overrideMsg ? { message: overrideMsg, accountId: overrideAccountId || currentAccountId3 } : getCurrentMessage();
|
|
7437
|
-
if ((mode === "reply" || mode === "replyAll" || mode === "forward") && !current) {
|
|
7464
|
+
if ((mode === "reply" || mode === "replyAll" || mode === "forward" || mode === "editAsNew") && !current) {
|
|
7438
7465
|
console.warn(`[compose] ${mode} \u2014 no message selected`);
|
|
7439
7466
|
return;
|
|
7440
7467
|
}
|
|
7441
7468
|
const accountsP = getAccounts();
|
|
7442
7469
|
const msg = current?.message;
|
|
7443
|
-
const titlePrefix = mode === "reply" ? "Reply" : mode === "replyAll" ? "Reply All" : mode === "forward" ? "Forward" : "Compose";
|
|
7470
|
+
const titlePrefix = mode === "reply" ? "Reply" : mode === "replyAll" ? "Reply All" : mode === "forward" ? "Forward" : mode === "editAsNew" ? "Edit as new" : "Compose";
|
|
7444
7471
|
const titleSubject = mode === "new" ? "" : msg?.subject || "";
|
|
7445
7472
|
const frame = showComposeOverlay(titleSubject ? `${titlePrefix}: ${titleSubject}` : titlePrefix);
|
|
7446
7473
|
const accounts = await accountsP;
|
|
@@ -7548,6 +7575,11 @@ async function openCompose(mode, overrideMsg, overrideAccountId) {
|
|
|
7548
7575
|
init.subject = `Fwd: ${cleanSubject}`;
|
|
7549
7576
|
init.bodyHtml = forwardBody(msg);
|
|
7550
7577
|
init.fromAddress = detectReplyFrom();
|
|
7578
|
+
} else if (msg && mode === "editAsNew") {
|
|
7579
|
+
init.to = Array.isArray(msg.to) ? msg.to : [];
|
|
7580
|
+
init.cc = Array.isArray(msg.cc) ? msg.cc : [];
|
|
7581
|
+
init.subject = msg.subject || "";
|
|
7582
|
+
init.bodyHtml = editAsNewBody(msg);
|
|
7551
7583
|
}
|
|
7552
7584
|
const initJson = JSON.stringify(init);
|
|
7553
7585
|
try {
|
|
@@ -7795,6 +7827,9 @@ function quoteBody(msg) {
|
|
|
7795
7827
|
const body = sanitizeQuotedBody(msg);
|
|
7796
7828
|
return `<p></p><br><br><div class="reply"><p>On ${date}, ${from} wrote:</p><blockquote>${body}</blockquote></div>`;
|
|
7797
7829
|
}
|
|
7830
|
+
function editAsNewBody(msg) {
|
|
7831
|
+
return `<p></p>${sanitizeQuotedBody(msg)}`;
|
|
7832
|
+
}
|
|
7798
7833
|
function forwardBody(msg) {
|
|
7799
7834
|
const date = new Date(msg.date).toLocaleString();
|
|
7800
7835
|
const from = msg.from.name ? `${msg.from.name} <${msg.from.address}>` : msg.from.address;
|
|
@@ -8381,14 +8416,7 @@ function doSearch(immediate = false) {
|
|
|
8381
8416
|
}
|
|
8382
8417
|
cancelServerSearch();
|
|
8383
8418
|
if (localScope === "current" && !serverOn && !immediate) {
|
|
8384
|
-
|
|
8385
|
-
if (body) {
|
|
8386
|
-
const lower = query.toLowerCase();
|
|
8387
|
-
for (const row of body.querySelectorAll(".ml-row")) {
|
|
8388
|
-
const text = row.textContent?.toLowerCase() || "";
|
|
8389
|
-
row.classList.toggle("filter-hidden", !text.includes(lower));
|
|
8390
|
-
}
|
|
8391
|
-
}
|
|
8419
|
+
setLiveFilter(query);
|
|
8392
8420
|
return;
|
|
8393
8421
|
}
|
|
8394
8422
|
const localScopeEff = localScope === "current" ? "current" : "all";
|