@bobfrankston/mailx 1.0.185 → 1.0.186

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.
@@ -1 +1 @@
1
- {"height":1344,"width":1946,"x":172,"y":85}
1
+ {"height":1344,"width":1586,"x":793,"y":81}
@@ -15,7 +15,7 @@
15
15
  "@bobfrankston/mailx-store-web": "../packages/mailx-store-web/index.js",
16
16
  "@bobfrankston/mailx-store-web/": "../packages/mailx-store-web/",
17
17
  "@bobfrankston/mailx-types": "../packages/mailx-types/index.js",
18
- "sql.js": "../node_modules/sql.js/dist/sql-wasm.js"
18
+ "sql.js": "../packages/mailx-store-web/sql-wasm-esm.js"
19
19
  }
20
20
  }
21
21
  </script>
package/client/app.js CHANGED
@@ -305,14 +305,10 @@ async function openCompose(mode) {
305
305
  }
306
306
  // Store init data for compose window to pick up
307
307
  sessionStorage.setItem("composeInit", JSON.stringify(init));
308
- // IPC mode: navigate in same window (popups don't have custom protocol)
309
- // HTTP mode: open as popup window
310
- if (typeof mailxapi !== "undefined") {
311
- window.location.href = "compose/compose.html";
312
- }
313
- else {
314
- window.open("compose/compose.html", "_blank", "width=800,height=600,menubar=no,toolbar=no,status=no");
315
- }
308
+ // Open compose always as popup via window.open().
309
+ // In IPC mode, msger's new_window_req_handler allows msger.localhost popups.
310
+ // DO NOT use window.location.href — msger exits on navigation.
311
+ window.open("compose/compose.html", "_blank", "width=800,height=600,menubar=no,toolbar=no,status=no");
316
312
  }
317
313
  function quoteBody(msg) {
318
314
  const date = new Date(msg.date).toLocaleString();
@@ -2,7 +2,7 @@
2
2
  * Folder tree component -- renders account folders with hierarchy,
3
3
  * expand/collapse, and optional unified inbox.
4
4
  */
5
- import { getAccounts, getFolders, moveMessage, moveMessages, markFolderRead, createFolder, renameFolder, deleteFolder, emptyFolder, setupAccount, getVersion } from "../lib/api-client.js";
5
+ import { getAccounts, getFolders, moveMessage, moveMessages, markFolderRead, createFolder, renameFolder, deleteFolder, emptyFolder, setupAccount, getDeviceAccounts, getVersion } from "../lib/api-client.js";
6
6
  import { showContextMenu } from "./context-menu.js";
7
7
  let onFolderSelect;
8
8
  let onUnifiedInbox = null;
@@ -357,8 +357,9 @@ async function loadFolderTree(container) {
357
357
  if (mainBody) {
358
358
  mainBody.innerHTML = `<div style="padding:2rem;line-height:1.8;max-width:500px">
359
359
  <h2 style="margin-bottom:1rem">Welcome to mailx</h2>
360
+ <div id="setup-device-accounts"></div>
360
361
  <div id="setup-cloud-status"></div>
361
- <p>Add your email account to get started.</p>
362
+ <p id="setup-form-intro">Add your email account to get started.</p>
362
363
  <form id="setup-form" style="margin-top:1rem">
363
364
  <label style="display:block;margin-bottom:0.5rem">
364
365
  Your name
@@ -462,6 +463,41 @@ async function loadFolderTree(container) {
462
463
  </div>`;
463
464
  }
464
465
  }).catch(() => { });
466
+ // On Android, check for device Google accounts and show picker
467
+ getDeviceAccounts().then(async (deviceAccounts) => {
468
+ const pickerEl = document.getElementById("setup-device-accounts");
469
+ if (!pickerEl || deviceAccounts.length === 0)
470
+ return;
471
+ const formEl = document.getElementById("setup-form");
472
+ const introEl = document.getElementById("setup-form-intro");
473
+ if (introEl)
474
+ introEl.textContent = "Select an account:";
475
+ if (formEl)
476
+ formEl.style.display = "none";
477
+ pickerEl.innerHTML = deviceAccounts.map((a) => `<button class="device-account-btn" data-email="${a.email}" data-name="${a.name}" style="display:block;width:100%;padding:0.75rem 1rem;margin-bottom:0.5rem;background:var(--color-bg-surface);color:var(--color-text);border:1px solid var(--color-border);border-radius:6px;cursor:pointer;font-size:1rem;text-align:left">${a.email}</button>`).join("") + `<button id="setup-show-form" style="margin-top:0.5rem;padding:0.5rem 1rem;background:none;color:var(--color-text-muted);border:none;cursor:pointer;font-size:0.9rem">Use a different account...</button>`;
478
+ pickerEl.querySelectorAll(".device-account-btn").forEach((btn) => {
479
+ btn.addEventListener("click", async () => {
480
+ const email = btn.dataset.email || "";
481
+ const name = btn.dataset.name || "";
482
+ pickerEl.innerHTML = `<div style="padding:0.5rem;color:var(--color-text-muted)">Setting up ${email}...</div>`;
483
+ const result = await setupAccount(name, email, "");
484
+ if (result?.ok) {
485
+ pickerEl.innerHTML = `<div style="padding:0.5rem;color:var(--color-accent)">${result.message || "Account added!"}</div>`;
486
+ setTimeout(() => location.reload(), 2000);
487
+ }
488
+ else {
489
+ pickerEl.innerHTML = `<div style="padding:0.5rem;color:#f55">${result?.error || "Setup failed"}</div>`;
490
+ }
491
+ });
492
+ });
493
+ document.getElementById("setup-show-form")?.addEventListener("click", () => {
494
+ pickerEl.style.display = "none";
495
+ if (formEl)
496
+ formEl.style.display = "block";
497
+ if (introEl)
498
+ introEl.textContent = "Add your email account to get started.";
499
+ });
500
+ }).catch(() => { });
465
501
  }
466
502
  // Dismiss startup overlay
467
503
  const overlay = document.getElementById("startup-overlay");
@@ -144,12 +144,7 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
144
144
  draftFolderId: msg.folderId,
145
145
  };
146
146
  sessionStorage.setItem("composeInit", JSON.stringify(init));
147
- if (typeof window.mailxapi !== "undefined") {
148
- window.location.href = "compose/compose.html";
149
- }
150
- else {
151
- window.open("compose/compose.html", "_blank", "width=800,height=600,menubar=no,toolbar=no,status=no");
152
- }
147
+ window.open("compose/compose.html", "_blank", "width=800,height=600,menubar=no,toolbar=no,status=no");
153
148
  };
154
149
  }
155
150
  else {
@@ -5,14 +5,9 @@
5
5
  */
6
6
  import { createEditor } from "./editor.js";
7
7
  import { getVersion, getSettings, getAccounts, searchContacts, sendMessage, saveDraft as apiSaveDraft, deleteDraft } from "../lib/api-client.js";
8
- /** Close compose — navigate back in IPC mode, window.close() in HTTP mode */
8
+ /** Close compose window */
9
9
  function closeCompose() {
10
- if (typeof window.mailxapi !== "undefined") {
11
- window.location.href = "../index.html";
12
- }
13
- else {
14
- closeCompose();
15
- }
10
+ window.close();
16
11
  }
17
12
  // ── Load editor scripts dynamically ──
18
13
  function loadScript(src) {
@@ -309,6 +309,11 @@ export function setupAccount(name, email, password) {
309
309
  return getIpc().setupAccount?.(name, email, password);
310
310
  return api("/setup", { method: "POST", body: JSON.stringify({ name, email, password }) });
311
311
  }
312
+ export async function getDeviceAccounts() {
313
+ if (hasIPC())
314
+ return getIpc().getDeviceAccounts?.() ?? [];
315
+ return [];
316
+ }
312
317
  // Legacy exports for backward compatibility
313
318
  export const connectWebSocket = connectEvents;
314
319
  export const onWsEvent = onEvent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.185",
3
+ "version": "1.0.186",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -14,7 +14,7 @@
14
14
  import { WebMailxDB } from "./db.js";
15
15
  import { WebMessageStore } from "./web-message-store.js";
16
16
  import { WebMailxService } from "./web-service.js";
17
- import { loadAccounts, clearSettings } from "./web-settings.js";
17
+ import { loadAccounts, saveAccounts, clearSettings } from "./web-settings.js";
18
18
  import { GmailApiWebProvider } from "./gmail-api-web.js";
19
19
  // ── State ──
20
20
  let db;
@@ -327,7 +327,49 @@ function installBridge() {
327
327
  },
328
328
  getAutocompleteSettings: () => service.getAutocompleteSettings(),
329
329
  saveAutocompleteSettings: async (settings) => { await service.saveAutocompleteSettings(settings); return { ok: true }; },
330
- setupAccount: async () => ({ ok: false, error: "Use desktop for initial setup" }),
330
+ getDeviceAccounts: async () => {
331
+ const bridge = window._nativeBridge;
332
+ if (bridge?.app?.getDeviceAccounts) {
333
+ return bridge.app.getDeviceAccounts();
334
+ }
335
+ return [];
336
+ },
337
+ setupAccount: async (name, email, _password) => {
338
+ try {
339
+ if (!email || !email.includes("@")) {
340
+ return { ok: false, error: "Email address required" };
341
+ }
342
+ const domain = email.split("@")[1].toLowerCase();
343
+ const id = domain.split(".")[0] || "account";
344
+ const account = {
345
+ id,
346
+ name: name || email.split("@")[0],
347
+ email,
348
+ enabled: true,
349
+ imap: { host: `imap.${domain}`, port: 993, tls: true, auth: "oauth2", user: email },
350
+ smtp: { host: `smtp.${domain}`, port: 587, tls: true, auth: "oauth2", user: email },
351
+ };
352
+ // Apply known provider defaults
353
+ if (domain === "gmail.com" || domain === "googlemail.com") {
354
+ account.label = "Gmail";
355
+ account.imap = { host: "imap.gmail.com", port: 993, tls: true, auth: "oauth2", user: email };
356
+ account.smtp = { host: "smtp.gmail.com", port: 587, tls: true, auth: "oauth2", user: email };
357
+ }
358
+ const existing = await loadAccounts();
359
+ if (existing.some(a => a.email === email)) {
360
+ return { ok: true, message: "Account already exists" };
361
+ }
362
+ existing.push(account);
363
+ await saveAccounts(existing);
364
+ await syncManager.addAccount(account);
365
+ db.upsertAccount(account.id, account.name, account.email, JSON.stringify(account));
366
+ console.log(`[android] Account added: ${email}`);
367
+ return { ok: true, message: `Added ${email}. Syncing...` };
368
+ }
369
+ catch (e) {
370
+ return { ok: false, error: e.message };
371
+ }
372
+ },
331
373
  repairAccounts: async () => ({ ok: false, error: "Use desktop for repair" }),
332
374
  resetStore: () => resetStore(),
333
375
  restart: () => { location.reload(); },
@@ -0,0 +1,10 @@
1
+ // ESM wrapper for sql.js (UMD) — loads as classic script, re-exports the global
2
+ await new Promise((resolve, reject) => {
3
+ const s = document.createElement("script");
4
+ s.src = new URL("../../node_modules/sql.js/dist/sql-wasm.js", import.meta.url).href;
5
+ s.onload = resolve;
6
+ s.onerror = reject;
7
+ document.head.appendChild(s);
8
+ });
9
+ const initSqlJs = globalThis.initSqlJs;
10
+ export default initSqlJs;