@bobfrankston/rmfmail 1.2.69 → 1.2.71

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.
@@ -18,11 +18,52 @@
18
18
  */
19
19
  import * as esbuild from "esbuild";
20
20
  import path from "node:path";
21
+ import fs from "node:fs";
22
+ import crypto from "node:crypto";
21
23
  import { fileURLToPath } from "node:url";
22
24
 
23
25
  const root = path.resolve(fileURLToPath(import.meta.url), "..", "..");
24
26
  const watch = process.argv.includes("--watch");
25
27
 
28
+ /** Cache-bust the bundle references in the HTML entry points with a content
29
+ * hash. The msger custom protocol serves bundles with no cache headers, so
30
+ * WebView2 caches `app.bundle.js` and keeps serving the OLD bundle across
31
+ * daemon relaunches — every client-side fix silently failed to reach the user
32
+ * until the cache happened to evict (Bob 2026-06-26: flag/Ctrl+A fixes "still
33
+ * happening" while the daemon was already on the new version). Stamping a
34
+ * per-build `?v=<hash>` makes the URL change whenever the bundle changes, so
35
+ * the WebView is forced to fetch the new one. */
36
+ function shortHash(file) {
37
+ try { return crypto.createHash("sha1").update(fs.readFileSync(file)).digest("hex").slice(0, 10); }
38
+ catch { return String(Date.now()); }
39
+ }
40
+ function stampHtml(htmlFile, refs) {
41
+ let html;
42
+ try { html = fs.readFileSync(htmlFile, "utf8"); } catch { return; }
43
+ let changed = false;
44
+ for (const { name, file } of refs) {
45
+ const v = shortHash(file);
46
+ // Match the bundle name + optional existing ?v=… up to the closing
47
+ // quote, so re-runs replace the prior hash instead of stacking.
48
+ const re = new RegExp(`(${name.replace(/[.\\/]/g, m => "\\" + m)})(\\?v=[a-z0-9]+)?(")`, "g");
49
+ const next = html.replace(re, `$1?v=${v}$3`);
50
+ if (next !== html) { html = next; changed = true; }
51
+ }
52
+ if (changed) {
53
+ fs.writeFileSync(htmlFile, html);
54
+ console.log(` stamped ${path.relative(root, htmlFile)}`);
55
+ }
56
+ }
57
+ function stampAll() {
58
+ stampHtml(path.join(root, "client", "index.html"), [
59
+ { name: "./app.bundle.js", file: path.join(root, "client", "app.bundle.js") },
60
+ { name: "./android-bootstrap.bundle.js", file: path.join(root, "client", "android-bootstrap.bundle.js") },
61
+ ]);
62
+ stampHtml(path.join(root, "client", "compose", "compose.html"), [
63
+ { name: "compose.bundle.js", file: path.join(root, "client", "compose", "compose.bundle.js") },
64
+ ]);
65
+ }
66
+
26
67
  // Each entry has its own external list because the Android bootstrap bundle
27
68
  // needs to INCLUDE @bobfrankston/mailx-store-web (entire purpose of the
28
69
  // bundle), while app.bundle.js externalizes it so the desktop build doesn't
@@ -132,6 +173,7 @@ async function buildAll() {
132
173
  }
133
174
  const failed = results.filter(r => !r.ok).length;
134
175
  if (failed) process.exit(1);
176
+ stampAll();
135
177
  console.log(`build-bundles: done in ${Date.now() - t0} ms`);
136
178
  }
137
179
 
@@ -2749,6 +2749,7 @@ var init_folder_picker = __esm({
2749
2749
  var message_list_exports = {};
2750
2750
  __export(message_list_exports, {
2751
2751
  clearSearchMode: () => clearSearchMode,
2752
+ exitMultiSelect: () => exitMultiSelect,
2752
2753
  getCurrentFocused: () => getCurrentFocused,
2753
2754
  getSelectedMessages: () => getSelectedMessages,
2754
2755
  initMessageList: () => initMessageList,
@@ -3891,7 +3892,7 @@ function escapeHtml2(s) {
3891
3892
  div.textContent = s;
3892
3893
  return div.innerHTML;
3893
3894
  }
3894
- 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;
3895
+ var onMessageSelect, currentAccountId2, currentFolderId, currentSpecialUse, lastClickedRow, currentPage, totalMessages, loading, unifiedMode, searchMode, liveFilterText, currentSearchQuery, wasUnifiedBeforeSearch, showToInsteadOfFrom, touchWasScroll, lastPointerWasTouch, currentSort, currentSortDir, loadGen, listCache, CACHE_KEY_UNIFIED, positionMemory, POSITION_STORAGE_KEY, focusedRow, rowByKey, prioritySenders, priorityDomains, timeFmt, dateFmt, dateFmtSameYear, MessageRow;
3895
3896
  var init_message_list = __esm({
3896
3897
  "client/components/message-list.js"() {
3897
3898
  "use strict";
@@ -3911,6 +3912,12 @@ var init_message_list = __esm({
3911
3912
  wasUnifiedBeforeSearch = false;
3912
3913
  showToInsteadOfFrom = false;
3913
3914
  touchWasScroll = false;
3915
+ lastPointerWasTouch = false;
3916
+ if (typeof document !== "undefined") {
3917
+ document.addEventListener("pointerdown", (e) => {
3918
+ lastPointerWasTouch = e.pointerType === "touch";
3919
+ }, true);
3920
+ }
3914
3921
  currentSort = "date";
3915
3922
  currentSortDir = "desc";
3916
3923
  loadGen = 0;
@@ -4239,8 +4246,7 @@ var init_message_list = __esm({
4239
4246
  return;
4240
4247
  }
4241
4248
  if (body?.classList.contains("multi-select-on")) {
4242
- const isTouch = !!window.matchMedia?.("(pointer: coarse)")?.matches;
4243
- if (!isTouch && !e.ctrlKey && !e.metaKey) {
4249
+ if (!lastPointerWasTouch && !e.ctrlKey && !e.metaKey) {
4244
4250
  exitMultiSelect();
4245
4251
  focusRow(this);
4246
4252
  lastClickedRow = this.el;
@@ -8798,11 +8804,6 @@ document.getElementById("btn-spam-report")?.addEventListener("click", async () =
8798
8804
  }
8799
8805
  });
8800
8806
  document.getElementById("btn-compose")?.addEventListener("click", () => openCompose("new"));
8801
- document.getElementById("btn-selection")?.addEventListener("click", (e) => {
8802
- e.stopPropagation();
8803
- const r = e.currentTarget.getBoundingClientRect();
8804
- void showSelectionMenu(r.left, r.bottom + 2);
8805
- });
8806
8807
  document.getElementById("btn-mark-unread")?.addEventListener("click", () => {
8807
8808
  const sel = getCurrentFocused();
8808
8809
  if (!sel) return;
@@ -9930,6 +9931,15 @@ document.addEventListener("keydown", (e) => {
9930
9931
  e.preventDefault();
9931
9932
  applyFontSize(next);
9932
9933
  }
9934
+ if (e.key === "Escape" && document.getElementById("ml-body")?.classList.contains("multi-select-on")) {
9935
+ const t = e.target;
9936
+ const tag = t?.tagName;
9937
+ if (tag !== "INPUT" && tag !== "TEXTAREA" && tag !== "SELECT" && !t?.isContentEditable) {
9938
+ e.preventDefault();
9939
+ exitMultiSelect();
9940
+ return;
9941
+ }
9942
+ }
9933
9943
  if (e.ctrlKey && e.key === "a") {
9934
9944
  const t = e.target;
9935
9945
  const tag = t?.tagName;