@bobfrankston/mailx 1.0.106 → 1.0.108

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.
@@ -300,8 +300,17 @@ async function loadFolderTree(container) {
300
300
  const accounts = await getAccounts();
301
301
  if (accounts.length === 0) {
302
302
  container.innerHTML = `<div class="folder-loading">No accounts</div>`;
303
- // Show simple setup form in the main content area
304
- const mainBody = document.getElementById("ml-body");
303
+ // Hide the message list and show setup in the viewer pane (full width)
304
+ const mlSection = document.querySelector(".message-list");
305
+ if (mlSection)
306
+ mlSection.style.display = "none";
307
+ const splitter = document.getElementById("splitter-h");
308
+ if (splitter)
309
+ splitter.style.display = "none";
310
+ const mvHeader = document.getElementById("mv-header");
311
+ if (mvHeader)
312
+ mvHeader.style.display = "none";
313
+ const mainBody = document.getElementById("mv-body");
305
314
  if (mainBody) {
306
315
  mainBody.innerHTML = `<div style="padding:2rem;line-height:1.8;max-width:500px">
307
316
  <h2 style="margin-bottom:1rem">Welcome to mailx</h2>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.106",
3
+ "version": "1.0.108",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -20,7 +20,7 @@
20
20
  "postinstall": "node launcher/builder/postinstall.js"
21
21
  },
22
22
  "dependencies": {
23
- "@bobfrankston/iflow": "^1.0.41",
23
+ "@bobfrankston/iflow": "^1.0.42",
24
24
  "@bobfrankston/miscinfo": "^1.0.7",
25
25
  "@bobfrankston/oauthsupport": "^1.0.20",
26
26
  "@bobfrankston/rust-builder": "^0.1.3",
@@ -9,7 +9,7 @@ import * as fs from "node:fs";
9
9
  import { MailxDB } from "@bobfrankston/mailx-store";
10
10
  import { ImapManager } from "@bobfrankston/mailx-imap";
11
11
  import { createApiRouter } from "@bobfrankston/mailx-api";
12
- import { loadSettings, getConfigDir, getStorePath, getStorageInfo, initLocalConfig } from "@bobfrankston/mailx-settings";
12
+ import { loadSettings, loadAccountsAsync, getConfigDir, getStorePath, getStorageInfo, initLocalConfig } from "@bobfrankston/mailx-settings";
13
13
  import { ports } from "@bobfrankston/miscinfo";
14
14
  import { createServer } from "node:http";
15
15
  const PORT = ports.mailx;
@@ -46,13 +46,17 @@ const rootPkg = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, "..",
46
46
  const SERVER_VERSION = rootPkg.version;
47
47
  // ── Initialize ──
48
48
  initLocalConfig();
49
- const settings = loadSettings();
49
+ let settings = loadSettings();
50
50
  if (settings.accounts.length === 0) {
51
- console.log(" No accounts configured.");
52
- console.log(" See README for setup: https://github.com/BobFrankston/mailx#first-time-setup");
53
- console.log(" Quick: create ~/.mailx/settings.jsonc with your IMAP/SMTP account details.");
54
- console.log(" Or: place shared settings on OneDrive at home/.mailx/settings.jsonc");
55
- console.log(" Server will start at http://127.0.0.1:9333 configure accounts to begin.");
51
+ // Try cloud API fallback (Google Drive, OneDrive) if filesystem mount not available
52
+ const cloudAccounts = await loadAccountsAsync();
53
+ if (cloudAccounts.length > 0) {
54
+ settings = { ...settings, accounts: cloudAccounts };
55
+ console.log(` Loaded ${cloudAccounts.length} account(s) from cloud API`);
56
+ }
57
+ else {
58
+ console.log(" No accounts configured. Open http://127.0.0.1:9333 to set up.");
59
+ }
56
60
  }
57
61
  const dbDir = getConfigDir();
58
62
  const db = new MailxDB(dbDir);
@@ -4,7 +4,7 @@
4
4
  * when the cloud drive is not mounted locally.
5
5
  * Falls back to local cache when offline.
6
6
  */
7
- export type CloudProvider = "onedrive" | "gdrive" | "dropbox" | "local";
7
+ export type CloudProvider = "onedrive" | "gdrive" | "google" | "dropbox" | "local";
8
8
  export interface CloudFile {
9
9
  read(filePath: string): Promise<string | null>;
10
10
  write(filePath: string, content: string): Promise<boolean>;
@@ -275,6 +275,7 @@ export function getCloudProvider(provider) {
275
275
  write: oneDriveWrite,
276
276
  exists: oneDriveExists,
277
277
  };
278
+ case "google":
278
279
  case "gdrive":
279
280
  return {
280
281
  read: gDriveRead,
@@ -60,6 +60,8 @@ declare const DEFAULT_ALLOWLIST: {
60
60
  };
61
61
  /** Load account configs */
62
62
  export declare function loadAccounts(): AccountConfig[];
63
+ /** Load accounts with cloud API fallback (async — use when cloud settings may not be mounted) */
64
+ export declare function loadAccountsAsync(): Promise<AccountConfig[]>;
63
65
  /** Save account configs */
64
66
  export declare function saveAccounts(accounts: AccountConfig[]): void;
65
67
  /** Load preferences (shared + local overrides, with legacy fallback) */
@@ -63,6 +63,7 @@ function resolveProvider(cfg) {
63
63
  ].filter(Boolean);
64
64
  return candidates.find(p => fs.existsSync(p));
65
65
  }
66
+ case "google":
66
67
  case "gdrive": {
67
68
  const candidates = [
68
69
  home && path.join(home, "Google Drive", "My Drive", rel),
@@ -166,7 +167,7 @@ export function getStorageInfo() {
166
167
  // Mounted cloud drive
167
168
  const name = typeof entry === "string" ? "cloud" :
168
169
  entry.provider === "onedrive" ? "OneDrive" :
169
- entry.provider === "gdrive" ? "Google Drive" :
170
+ (entry.provider === "gdrive" || entry.provider === "google") ? "Google Drive" :
170
171
  entry.provider === "dropbox" ? "Dropbox" : entry.provider;
171
172
  return { provider: name, mode: "mount" };
172
173
  }
@@ -174,7 +175,7 @@ export function getStorageInfo() {
174
175
  // Not mounted but using API fallback
175
176
  if (pendingCloudConfig) {
176
177
  const name = pendingCloudConfig.provider === "onedrive" ? "OneDrive" :
177
- pendingCloudConfig.provider === "gdrive" ? "Google Drive" :
178
+ (pendingCloudConfig.provider === "gdrive" || pendingCloudConfig.provider === "google") ? "Google Drive" :
178
179
  pendingCloudConfig.provider === "dropbox" ? "Dropbox" : pendingCloudConfig.provider;
179
180
  return { provider: name, mode: "api" };
180
181
  }
@@ -390,6 +391,35 @@ function applyAccountOverrides(accounts) {
390
391
  }
391
392
  return accounts;
392
393
  }
394
+ /** Load accounts with cloud API fallback (async — use when cloud settings may not be mounted) */
395
+ export async function loadAccountsAsync() {
396
+ // Try sync first (filesystem)
397
+ const accounts = loadAccounts();
398
+ if (accounts.length > 0)
399
+ return accounts;
400
+ // Try cloud API fallback
401
+ if (pendingCloudConfig) {
402
+ console.log(" [cloud] Trying cloud API for accounts...");
403
+ const content = await cloudRead("accounts.jsonc");
404
+ if (content) {
405
+ const data = parseJsonc(content);
406
+ if (data?.accounts || Array.isArray(data)) {
407
+ const raw = data.accounts || data;
408
+ const globalName = data.name || "";
409
+ return applyAccountOverrides(raw.map((a) => normalizeAccount(a, globalName)));
410
+ }
411
+ }
412
+ // Also try legacy settings.jsonc
413
+ const legacy = await cloudRead("settings.jsonc");
414
+ if (legacy) {
415
+ const data = parseJsonc(legacy);
416
+ if (data?.accounts) {
417
+ return applyAccountOverrides(data.accounts.map((a) => normalizeAccount(a, data.name)));
418
+ }
419
+ }
420
+ }
421
+ return [];
422
+ }
393
423
  /** Save account configs */
394
424
  export function saveAccounts(accounts) {
395
425
  saveFile("accounts.jsonc", { accounts });