@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.
- package/client/components/folder-tree.js +11 -2
- package/package.json +2 -2
- package/packages/mailx-server/index.js +11 -7
- package/packages/mailx-settings/cloud.d.ts +1 -1
- package/packages/mailx-settings/cloud.js +1 -0
- package/packages/mailx-settings/index.d.ts +2 -0
- package/packages/mailx-settings/index.js +32 -2
|
@@ -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
|
-
//
|
|
304
|
-
const
|
|
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.
|
|
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.
|
|
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
|
-
|
|
49
|
+
let settings = loadSettings();
|
|
50
50
|
if (settings.accounts.length === 0) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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>;
|
|
@@ -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 });
|