@bobfrankston/mailx 1.0.448 → 1.0.449

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.js CHANGED
@@ -799,19 +799,24 @@ function showComposeOverlay(title = "Compose") {
799
799
  wrapper.appendChild(frame);
800
800
  document.body.appendChild(wrapper);
801
801
  }
802
- function quoteBody(msg) {
803
- const date = new Date(msg.date).toLocaleString();
804
- const from = msg.from.name ? `${msg.from.name} <${msg.from.address}>` : msg.from.address;
805
- // Simplify complex HTML (tables, inline styles) for Quill compatibility
802
+ // Marketing-email layout tables (deeply nested, fixed widths) collapse to
803
+ // 30-40px columns inside a phone-width compose pane and wrap text
804
+ // character-by-character. Strip styles + flatten tables before quoting.
805
+ function sanitizeQuotedBody(msg) {
806
806
  let body = msg.bodyHtml || `<pre>${msg.bodyText || ""}</pre>`;
807
- // Strip style tags and attributes
808
807
  body = body.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
809
808
  body = body.replace(/\s+style="[^"]*"/gi, "");
810
809
  body = body.replace(/\s+class="[^"]*"/gi, "");
811
- // Convert tables to simple divs
810
+ body = body.replace(/\s+(width|height|align|valign|bgcolor|cellpadding|cellspacing|border)="[^"]*"/gi, "");
812
811
  body = body.replace(/<table[^>]*>/gi, "<div>").replace(/<\/table>/gi, "</div>");
813
812
  body = body.replace(/<t[rdh][^>]*>/gi, "").replace(/<\/t[rdh]>/gi, " ");
814
813
  body = body.replace(/<thead[^>]*>|<\/thead>|<tbody[^>]*>|<\/tbody>/gi, "");
814
+ return body;
815
+ }
816
+ function quoteBody(msg) {
817
+ const date = new Date(msg.date).toLocaleString();
818
+ const from = msg.from.name ? `${msg.from.name} &lt;${msg.from.address}&gt;` : msg.from.address;
819
+ const body = sanitizeQuotedBody(msg);
815
820
  // Two blank lines above the quote so the cursor lands with breathing room
816
821
  // between the user's reply and the "On ... wrote:" line.
817
822
  return `<br><br><div class="reply"><p>On ${date}, ${from} wrote:</p><blockquote>${body}</blockquote></div>`;
@@ -820,7 +825,7 @@ function forwardBody(msg) {
820
825
  const date = new Date(msg.date).toLocaleString();
821
826
  const from = msg.from.name ? `${msg.from.name} &lt;${msg.from.address}&gt;` : msg.from.address;
822
827
  const to = msg.to.map((a) => a.name ? `${a.name} &lt;${a.address}&gt;` : a.address).join(", ");
823
- const body = msg.bodyHtml || `<pre>${msg.bodyText || ""}</pre>`;
828
+ const body = sanitizeQuotedBody(msg);
824
829
  return `<br><br><div class="reply"><p>---------- Forwarded message ----------<br>From: ${from}<br>Date: ${date}<br>Subject: ${msg.subject}<br>To: ${to}</p>${body}</div>`;
825
830
  }
826
831
  let lastDeleted = null;
@@ -2905,6 +2910,34 @@ optCheckReputation?.addEventListener("change", () => {
2905
2910
  saveSettings(settings);
2906
2911
  }).catch(() => { });
2907
2912
  });
2913
+ // Auto mark-as-read settings (per-device localStorage; the viewer reads
2914
+ // these directly when showing a message). Default on with a 2s delay so
2915
+ // scrolling through a folder doesn't mark every glanced-at message as
2916
+ // read, but a deliberate read still gets recorded.
2917
+ const optAutomarkRead = document.getElementById("opt-automark-read");
2918
+ const optAutomarkDelay = document.getElementById("opt-automark-delay");
2919
+ try {
2920
+ if (optAutomarkRead)
2921
+ optAutomarkRead.checked = localStorage.getItem("mailx-automark-read") !== "false";
2922
+ if (optAutomarkDelay)
2923
+ optAutomarkDelay.value = localStorage.getItem("mailx-automark-delay") || "2";
2924
+ }
2925
+ catch { /* private mode */ }
2926
+ optAutomarkRead?.addEventListener("change", () => {
2927
+ try {
2928
+ localStorage.setItem("mailx-automark-read", String(optAutomarkRead.checked));
2929
+ }
2930
+ catch { /* */ }
2931
+ });
2932
+ optAutomarkDelay?.addEventListener("change", () => {
2933
+ const v = parseFloat(optAutomarkDelay.value);
2934
+ if (Number.isFinite(v) && v >= 0) {
2935
+ try {
2936
+ localStorage.setItem("mailx-automark-delay", String(v));
2937
+ }
2938
+ catch { /* */ }
2939
+ }
2940
+ });
2908
2941
  const isApp = typeof mailxapi !== "undefined" && mailxapi?.isApp;
2909
2942
  // Wait for server ready signal, then fetch version
2910
2943
  const versionPromise = getVersion();
@@ -297,10 +297,44 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
297
297
  return;
298
298
  currentMessage = msg;
299
299
  currentAccountId = accountId;
300
- // Mark as read
300
+ // Mark as read — gated by user prefs:
301
+ // - mailx-automark-read (default "true"): if "false", never auto-mark
302
+ // - mailx-automark-delay (default "2"): seconds to wait before
303
+ // marking. Lets the user click through messages quickly without
304
+ // marking ones they didn't actually read. The timer is tied to
305
+ // showMessageGeneration; navigating to another message advances
306
+ // the generation and cancels the pending mark.
301
307
  if (!msg.flags.includes("\\Seen")) {
302
- const newFlags = [...msg.flags, "\\Seen"];
303
- updateFlags(accountId, uid, newFlags);
308
+ let enabled = true;
309
+ let delaySec = 2;
310
+ try {
311
+ enabled = localStorage.getItem("mailx-automark-read") !== "false";
312
+ const d = parseFloat(localStorage.getItem("mailx-automark-delay") || "2");
313
+ if (Number.isFinite(d) && d >= 0)
314
+ delaySec = d;
315
+ }
316
+ catch { /* private mode — defaults */ }
317
+ if (enabled) {
318
+ const captureGen = gen;
319
+ const newFlags = [...msg.flags, "\\Seen"];
320
+ if (delaySec === 0) {
321
+ updateFlags(accountId, uid, newFlags);
322
+ }
323
+ else {
324
+ setTimeout(() => {
325
+ // Stale: user moved on before the timer fired.
326
+ if (captureGen !== showMessageGeneration)
327
+ return;
328
+ updateFlags(accountId, uid, newFlags);
329
+ // Reflect locally so the list row stops looking unread.
330
+ msg.flags = newFlags;
331
+ try {
332
+ state.updateMessageFlags(accountId, uid, newFlags);
333
+ }
334
+ catch { /* */ }
335
+ }, delaySec * 1000);
336
+ }
337
+ }
304
338
  }
305
339
  // Header
306
340
  headerEl.hidden = false;
package/client/index.html CHANGED
@@ -57,6 +57,9 @@
57
57
  <label class="tb-menu-item" title="Right-click in compose editor → Proofread (when wired)"><input type="checkbox" id="opt-ai-proofread"> AI proofread (off by default)</label>
58
58
  <label class="tb-menu-item" title="When opening a message with remote content, look up the sender's domain in three free DNS blocklists in parallel: Spamhaus DBL, SURBL, URIBL. The banner shows N-of-3 services flagging the domain. Each query leaks the bare domain to that DNSBL's DNS. Off by default."><input type="checkbox" id="opt-check-reputation"> Check sender reputation (DNSBLs)</label>
59
59
  <hr class="tb-menu-sep">
60
+ <label class="tb-menu-item" title="When viewing a message, automatically mark it as read after a short delay. Uncheck to require an explicit Mark-Read action."><input type="checkbox" id="opt-automark-read" checked> Auto mark-as-read</label>
61
+ <label class="tb-menu-item" title="Seconds to wait before auto-marking a viewed message as read. Higher = scroll past messages without changing their unread state. 0 = mark immediately.">Mark-read delay (sec) <input type="number" id="opt-automark-delay" min="0" max="30" step="0.5" style="width:4em"></label>
62
+ <hr class="tb-menu-sep">
60
63
  <button class="tb-menu-item" id="btn-edit-jsonc" title="Edit accounts.jsonc / allowlist.jsonc">Edit config files...</button>
61
64
  <button class="tb-menu-item" id="btn-open-mailx-dir" title="Open ~/.mailx in file explorer">Open mailx folder...</button>
62
65
  <button class="tb-menu-item" id="btn-open-log" title="Open today's log file">Open log...</button>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.448",
3
+ "version": "1.0.449",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",