@bobfrankston/mailx 1.0.39 → 1.0.41
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 +16 -9
- package/client/components/message-list.js +13 -8
- package/package.json +2 -2
- package/packages/mailx-imap/index.js +1 -1
- package/packages/mailx-server/index.js +2 -2
- package/packages/mailx-settings/index.js +14 -5
- package/packages/mailx-types/index.d.ts +1 -0
|
@@ -317,17 +317,23 @@ async function loadFolderTree(container) {
|
|
|
317
317
|
try {
|
|
318
318
|
const accounts = await getAccounts();
|
|
319
319
|
if (accounts.length === 0) {
|
|
320
|
-
container.innerHTML = `<div class="folder-loading"
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
320
|
+
container.innerHTML = `<div class="folder-loading">No accounts</div>`;
|
|
321
|
+
// Show setup instructions in the main content area (full width)
|
|
322
|
+
const mainBody = document.getElementById("ml-body");
|
|
323
|
+
if (mainBody) {
|
|
324
|
+
mainBody.innerHTML = `<div style="padding:2rem;line-height:1.8;max-width:600px">
|
|
325
|
+
<h2 style="margin-bottom:1rem">Welcome to mailx</h2>
|
|
326
|
+
<p>Create <code>~/.mailx/settings.jsonc</code> with your email accounts.</p>
|
|
327
|
+
<p>If you have settings on OneDrive, create <code>~/.mailx/config.jsonc</code>:</p>
|
|
328
|
+
<code style="display:block;padding:0.75rem;background:var(--color-bg-surface);border:1px solid var(--color-border);border-radius:4px;margin:0.5rem 0;white-space:pre;font-size:0.85rem">{ "sharedDir": "~/OneDrive/home/.mailx" }</code>
|
|
329
|
+
<h3 style="margin-top:1.5rem">Gmail (just your email):</h3>
|
|
330
|
+
<code style="display:block;padding:0.75rem;background:var(--color-bg-surface);border:1px solid var(--color-border);border-radius:4px;margin:0.5rem 0;white-space:pre;font-size:0.85rem">{ "name": "Your Name",
|
|
325
331
|
"accounts": [
|
|
326
332
|
{ "email": "you@gmail.com" }
|
|
327
333
|
]
|
|
328
334
|
}</code>
|
|
329
|
-
|
|
330
|
-
|
|
335
|
+
<h3 style="margin-top:1rem">Standard IMAP:</h3>
|
|
336
|
+
<code style="display:block;padding:0.75rem;background:var(--color-bg-surface);border:1px solid var(--color-border);border-radius:4px;margin:0.5rem 0;white-space:pre;font-size:0.85rem">{ "name": "Your Name",
|
|
331
337
|
"accounts": [
|
|
332
338
|
{ "email": "you@example.com",
|
|
333
339
|
"password": "secret",
|
|
@@ -336,8 +342,9 @@ async function loadFolderTree(container) {
|
|
|
336
342
|
}
|
|
337
343
|
]
|
|
338
344
|
}</code>
|
|
339
|
-
|
|
340
|
-
|
|
345
|
+
<p style="margin-top:1rem;color:var(--color-text-muted)">Known providers (Gmail, Outlook, Yahoo, iCloud) auto-configure from just the email address. For other providers, specify IMAP/SMTP hosts. Restart after editing settings.</p>
|
|
346
|
+
</div>`;
|
|
347
|
+
}
|
|
341
348
|
// Dismiss startup overlay
|
|
342
349
|
const overlay = document.getElementById("startup-overlay");
|
|
343
350
|
if (overlay) {
|
|
@@ -197,7 +197,10 @@ export async function loadMessages(accountId, folderId, page = 1, specialUse = "
|
|
|
197
197
|
// Save scroll position and selected UID for non-autoSelect reloads
|
|
198
198
|
const savedScroll = !autoSelect ? body.scrollTop : 0;
|
|
199
199
|
const savedUid = !autoSelect ? body.querySelector(".ml-row.selected")?.getAttribute("data-uid") : null;
|
|
200
|
-
|
|
200
|
+
// Only show loading indicator on fresh navigation, not reloads
|
|
201
|
+
if (autoSelect) {
|
|
202
|
+
body.innerHTML = `<div class="ml-empty">Loading...</div>`;
|
|
203
|
+
}
|
|
201
204
|
try {
|
|
202
205
|
const result = await getMessages(accountId, folderId, 1);
|
|
203
206
|
totalMessages = result.total;
|
|
@@ -215,13 +218,15 @@ export async function loadMessages(accountId, folderId, page = 1, specialUse = "
|
|
|
215
218
|
firstRow.click();
|
|
216
219
|
}
|
|
217
220
|
else {
|
|
218
|
-
// Sync reload — restore scroll position and selection
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
row
|
|
224
|
-
|
|
221
|
+
// Sync reload — restore scroll position and selection after layout
|
|
222
|
+
requestAnimationFrame(() => {
|
|
223
|
+
body.scrollTop = savedScroll;
|
|
224
|
+
if (savedUid) {
|
|
225
|
+
const row = body.querySelector(`.ml-row[data-uid="${savedUid}"]`);
|
|
226
|
+
if (row)
|
|
227
|
+
row.classList.add("selected");
|
|
228
|
+
}
|
|
229
|
+
});
|
|
225
230
|
}
|
|
226
231
|
}
|
|
227
232
|
catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.41",
|
|
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.21",
|
|
24
24
|
"@bobfrankston/miscinfo": "^1.0.6",
|
|
25
25
|
"@bobfrankston/oauthsupport": "^1.0.11",
|
|
26
26
|
"@bobfrankston/rust-builder": "^0.1.2",
|
|
@@ -1103,7 +1103,7 @@ export class ImapManager extends EventEmitter {
|
|
|
1103
1103
|
async syncAllContacts() {
|
|
1104
1104
|
const settings = loadSettings();
|
|
1105
1105
|
for (const account of settings.accounts) {
|
|
1106
|
-
if (account.imap.auth === "oauth2" && account.enabled) {
|
|
1106
|
+
if (account.imap.auth === "oauth2" && (account.enabled || account.syncContacts)) {
|
|
1107
1107
|
await this.syncGoogleContacts(account.id);
|
|
1108
1108
|
}
|
|
1109
1109
|
}
|
|
@@ -185,11 +185,11 @@ async function start() {
|
|
|
185
185
|
}, 5000);
|
|
186
186
|
// Add configured accounts
|
|
187
187
|
for (const account of settings.accounts) {
|
|
188
|
-
if (!account.enabled)
|
|
188
|
+
if (!account.enabled && !account.syncContacts)
|
|
189
189
|
continue;
|
|
190
190
|
try {
|
|
191
191
|
await imapManager.addAccount(account);
|
|
192
|
-
console.log(` Account added: ${account.name} (${account.id})`);
|
|
192
|
+
console.log(` Account added: ${account.name} (${account.id})${!account.enabled ? " [contacts only]" : ""}`);
|
|
193
193
|
}
|
|
194
194
|
catch (e) {
|
|
195
195
|
console.error(` Failed to add account ${account.id}: ${e.message}`);
|
|
@@ -71,7 +71,7 @@ function readJsonc(filePath) {
|
|
|
71
71
|
return null;
|
|
72
72
|
}
|
|
73
73
|
try {
|
|
74
|
-
return parseJsonc(fs.readFileSync(actual, "utf-8"));
|
|
74
|
+
return parseJsonc(fs.readFileSync(actual, "utf-8").replace(/\r/g, ""));
|
|
75
75
|
}
|
|
76
76
|
catch (e) {
|
|
77
77
|
console.error(`Failed to read ${actual}: ${e.message}`);
|
|
@@ -176,6 +176,7 @@ function normalizeAccount(acct, globalName) {
|
|
|
176
176
|
},
|
|
177
177
|
enabled: acct.enabled ?? true,
|
|
178
178
|
defaultSend: acct.defaultSend,
|
|
179
|
+
syncContacts: acct.syncContacts ?? (provider?.imap.auth === "oauth2"),
|
|
179
180
|
relayDomains: acct.relayDomains,
|
|
180
181
|
deliveredToPrefix: acct.deliveredToPrefix,
|
|
181
182
|
};
|
|
@@ -266,8 +267,14 @@ export function saveAllowlist(list) {
|
|
|
266
267
|
// ── Legacy compatibility ──
|
|
267
268
|
function loadLegacySettings() {
|
|
268
269
|
const config = readLocalConfig();
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
if (config.settingsPath)
|
|
271
|
+
return readJsonc(resolvePath(config.settingsPath));
|
|
272
|
+
// Try shared dir first, then local
|
|
273
|
+
const sharedDir = getSharedDir();
|
|
274
|
+
const shared = readJsonc(path.join(sharedDir, "settings.jsonc"));
|
|
275
|
+
if (shared)
|
|
276
|
+
return shared;
|
|
277
|
+
return readJsonc(path.join(LOCAL_DIR, "settings.jsonc"));
|
|
271
278
|
}
|
|
272
279
|
/** Load settings — unified view combining all files (backward compatible) */
|
|
273
280
|
export function loadSettings() {
|
|
@@ -308,10 +315,12 @@ function detectSharedDir() {
|
|
|
308
315
|
process.env.OneDrive && path.join(process.env.OneDrive, "home", ".mailx"),
|
|
309
316
|
process.env.OneDriveConsumer && path.join(process.env.OneDriveConsumer, "home", ".mailx"),
|
|
310
317
|
home && path.join(home, "OneDrive", "home", ".mailx"),
|
|
311
|
-
// Linux/Mac
|
|
312
|
-
home && path.join(home, "
|
|
318
|
+
// Linux/Mac — case variations
|
|
319
|
+
home && path.join(home, "onedrive", "home", ".mailx"),
|
|
320
|
+
home && path.join(home, "Onedrive", "home", ".mailx"),
|
|
313
321
|
// Dropbox
|
|
314
322
|
home && path.join(home, "Dropbox", ".mailx"),
|
|
323
|
+
home && path.join(home, "dropbox", ".mailx"),
|
|
315
324
|
// Local fallback — just use ~/.mailx itself
|
|
316
325
|
].filter(Boolean);
|
|
317
326
|
for (const dir of candidates) {
|
|
@@ -30,6 +30,7 @@ export interface AccountConfig {
|
|
|
30
30
|
};
|
|
31
31
|
enabled: boolean;
|
|
32
32
|
defaultSend?: boolean; /** Use this account's SMTP when From doesn't match any account */
|
|
33
|
+
syncContacts?: boolean; /** Sync contacts even when account is disabled (contacts-only Gmail) */
|
|
33
34
|
relayDomains?: string[]; /** Domains to skip in Delivered-To chain (e.g., ["m.connectivity.xyz"]) */
|
|
34
35
|
deliveredToPrefix?: string[]; /** Prefixes to strip from Delivered-To to get clean alias (e.g., ["bobf-ma-", "bobf-"]) — order matters, longest first */
|
|
35
36
|
}
|