@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.
@@ -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" style="padding:1rem;line-height:1.8">
321
- <strong>No accounts configured</strong><br>
322
- Create <code>~/.mailx/settings.jsonc</code> with your email accounts.<br><br>
323
- Minimal Gmail example:<br>
324
- <code style="display:block;padding:0.5rem;background:var(--color-bg);border-radius:4px;margin:0.5rem 0;white-space:pre">{ "name": "Your Name",
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
- Standard IMAP:<br>
330
- <code style="display:block;padding:0.5rem;background:var(--color-bg);border-radius:4px;margin:0.5rem 0;white-space:pre">{ "name": "Your Name",
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
- <a href="https://github.com/BobFrankston/mailx#first-time-setup" target="_blank" style="color:var(--color-accent)">Full setup guide</a>
340
- </div>`;
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
- body.innerHTML = `<div class="ml-empty">Loading...</div>`;
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
- body.scrollTop = savedScroll;
220
- if (savedUid) {
221
- const row = body.querySelector(`.ml-row[data-uid="${savedUid}"]`);
222
- if (row)
223
- row.classList.add("selected");
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.39",
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.10",
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
- const settingsPath = config.settingsPath || path.join(LOCAL_DIR, "settings.jsonc");
270
- return readJsonc(settingsPath);
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 OneDrive
312
- home && path.join(home, "OneDrive", "home", ".mailx"),
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
  }