@bobfrankston/mailx 1.0.204 → 1.0.206

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":2151,"x":206,"y":12}
1
+ {"height":1344,"width":2151,"x":476,"y":58}
@@ -135,7 +135,7 @@
135
135
  <section class="message-viewer" id="message-viewer">
136
136
  <div class="mv-header" id="mv-header" hidden>
137
137
  <div class="mv-toolbar">
138
- <button class="tb-btn" id="btn-back" title="Back to list" hidden>←</button>
138
+ <button class="tb-btn btn-back-list" id="btn-back" title="Back to message list" hidden>← Inbox</button>
139
139
  <button class="tb-btn" id="btn-reply" title="Reply">↩</button>
140
140
  <button class="tb-btn" id="btn-reply-all" title="Reply All">↩↩</button>
141
141
  <button class="tb-btn" id="btn-forward" title="Forward">→</button>
@@ -290,10 +290,11 @@ export async function showMessage(accountId, uid, folderId, specialUse, isRetry
290
290
  else {
291
291
  bodyEl.innerHTML = `<div class="mv-empty">No content</div>`;
292
292
  }
293
- // Attachments
293
+ // Attachments — always clear first to avoid stale chips from previous message
294
+ attEl.innerHTML = "";
295
+ attEl.hidden = true;
294
296
  if (msg.attachments?.length) {
295
297
  attEl.hidden = false;
296
- attEl.innerHTML = "";
297
298
  for (let i = 0; i < msg.attachments.length; i++) {
298
299
  const att = msg.attachments[i];
299
300
  const chip = document.createElement("a");
package/client/index.html CHANGED
@@ -99,7 +99,7 @@
99
99
  <section class="message-viewer" id="message-viewer">
100
100
  <div class="mv-header" id="mv-header" hidden>
101
101
  <div class="mv-toolbar">
102
- <button class="tb-btn" id="btn-back" title="Back to list" hidden>←</button>
102
+ <button class="tb-btn btn-back-list" id="btn-back" title="Back to message list" hidden>← Inbox</button>
103
103
  <button class="tb-btn" id="btn-reply" title="Reply (Ctrl+R)">↩</button>
104
104
  <button class="tb-btn" id="btn-reply-all" title="Reply All (Ctrl+Shift+R)">↩↩</button>
105
105
  <button class="tb-btn" id="btn-forward" title="Forward">→</button>
@@ -132,9 +132,20 @@ body {
132
132
  }
133
133
  .message-list.narrow-hidden { display: none; }
134
134
 
135
- /* Show hamburger and back buttons */
135
+ /* Show hamburger always on narrow */
136
136
  #btn-menu { display: inline-flex !important; }
137
- #btn-back { display: inline-flex !important; }
137
+ /* Back button: only show when viewer is active (message list hidden) */
138
+ #btn-back { display: none !important; }
139
+ .message-viewer.narrow-active ~ * #btn-back,
140
+ body:has(.message-viewer.narrow-active) #btn-back {
141
+ display: inline-flex !important;
142
+ font-size: 1.1em;
143
+ font-weight: 600;
144
+ padding: 0.5em 1em;
145
+ background: var(--color-accent, #1a6dd4);
146
+ color: white;
147
+ border-radius: 6px;
148
+ }
138
149
 
139
150
  /* Message list: full width, two-line rows */
140
151
  .message-list {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.204",
3
+ "version": "1.0.206",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -24,7 +24,7 @@
24
24
  "@bobfrankston/iflow-node": "^0.1.2",
25
25
  "@bobfrankston/miscinfo": "^1.0.8",
26
26
  "@bobfrankston/oauthsupport": "^1.0.22",
27
- "@bobfrankston/msger": "^0.1.265",
27
+ "@bobfrankston/msger": "^0.1.267",
28
28
  "@capacitor/android": "^8.3.0",
29
29
  "@capacitor/cli": "^8.3.0",
30
30
  "@capacitor/core": "^8.3.0",
@@ -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, loadAccountsFromCloud, saveAccounts, clearSettings, setGDriveTokenProvider, setGDriveFolderId } from "./web-settings.js";
17
+ import { loadAccounts, loadAccountsFromCloud, saveAccounts, clearSettings, getDeviceId, setGDriveTokenProvider, setGDriveFolderId } from "./web-settings.js";
18
18
  import { GmailApiWebProvider } from "./gmail-api-web.js";
19
19
  // ── State ──
20
20
  let db;
@@ -364,6 +364,56 @@ function createNativeTokenProvider(email) {
364
364
  };
365
365
  }
366
366
  // ── GDrive folder lookup ──
367
+ async function registerDeviceInGDrive(tokenProvider, folderId, accountIds) {
368
+ try {
369
+ const token = await tokenProvider();
370
+ const deviceId = `android-${getDeviceId().substring(0, 8)}`;
371
+ // Read existing clients.jsonc
372
+ const q = encodeURIComponent(`name='clients.jsonc' and '${folderId}' in parents and trashed=false`);
373
+ const listRes = await fetch(`https://www.googleapis.com/drive/v3/files?q=${q}&fields=files(id)`, { headers: { "Authorization": `Bearer ${token}` } });
374
+ if (!listRes.ok) {
375
+ console.warn(`[gdrive] clients.jsonc list failed: ${listRes.status}`);
376
+ return;
377
+ }
378
+ const listData = await listRes.json();
379
+ const fileId = listData.files?.[0]?.id;
380
+ let clients = {};
381
+ if (fileId) {
382
+ const readRes = await fetch(`https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, { headers: { "Authorization": `Bearer ${token}` } });
383
+ if (readRes.ok) {
384
+ try {
385
+ clients = JSON.parse(await readRes.text());
386
+ }
387
+ catch { /* */ }
388
+ }
389
+ }
390
+ clients[deviceId] = {
391
+ hostname: deviceId,
392
+ platform: "android",
393
+ accounts: accountIds,
394
+ lastSeen: new Date().toISOString(),
395
+ version: window._nativeBridge?.info?.version || "?",
396
+ };
397
+ const content = JSON.stringify(clients, null, 2);
398
+ if (fileId) {
399
+ const upRes = await fetch(`https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=media`, {
400
+ method: "PATCH",
401
+ headers: {
402
+ "Authorization": `Bearer ${token}`,
403
+ "Content-Type": "application/json",
404
+ },
405
+ body: content,
406
+ });
407
+ if (upRes.ok)
408
+ console.log(`[android] Registered device in clients.jsonc as ${deviceId}`);
409
+ else
410
+ console.warn(`[gdrive] clients.jsonc update failed: ${upRes.status}`);
411
+ }
412
+ }
413
+ catch (e) {
414
+ console.warn(`[android] Device registration failed: ${e.message}`);
415
+ }
416
+ }
367
417
  async function findGDriveMailxFolder(tokenProvider) {
368
418
  const token = await tokenProvider();
369
419
  const q = encodeURIComponent("name='mailx' and mimeType='application/vnd.google-apps.folder' and trashed=false");
@@ -425,16 +475,21 @@ export async function initAndroid() {
425
475
  if (gmailTokenProvider) {
426
476
  setGDriveTokenProvider(gmailTokenProvider);
427
477
  try {
478
+ console.log("[android] Looking up GDrive mailx folder...");
428
479
  const folderId = await findGDriveMailxFolder(gmailTokenProvider);
429
- if (folderId) {
480
+ if (!folderId) {
481
+ console.warn("[android] GDrive mailx folder not found");
482
+ }
483
+ else {
430
484
  setGDriveFolderId(folderId);
431
485
  console.log(`[android] GDrive mailx folder: ${folderId}`);
432
- // Re-read accounts directly from GDrive (bypass cache to pick up bob.ma etc.)
486
+ // Read accounts directly from GDrive (bypass IndexedDB cache)
487
+ console.log("[android] Reading accounts.jsonc from GDrive...");
433
488
  const gdriveAccounts = await loadAccountsFromCloud();
434
- if (gdriveAccounts.length > accounts.length) {
435
- console.log(`[android] GDrive has ${gdriveAccounts.length} accounts (was ${accounts.length})`);
489
+ console.log(`[android] GDrive returned ${gdriveAccounts.length} accounts: ${gdriveAccounts.map(a => a.id).join(",")}`);
490
+ if (gdriveAccounts.length > 0) {
491
+ // Use canonical GDrive accounts (upsert handles overwrites)
436
492
  accounts = gdriveAccounts;
437
- // Register any new accounts
438
493
  for (const account of accounts) {
439
494
  if (!account.enabled)
440
495
  continue;
@@ -444,7 +499,10 @@ export async function initAndroid() {
444
499
  }
445
500
  await syncManager.addAccount(account);
446
501
  }
502
+ console.log(`[android] Loaded ${accounts.length} accounts from GDrive`);
447
503
  }
504
+ // Register this Android device in clients.jsonc
505
+ await registerDeviceInGDrive(gmailTokenProvider, folderId, accounts.map(a => a.id));
448
506
  }
449
507
  }
450
508
  catch (e) {