@bobfrankston/mailx 1.0.205 → 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.
package/client/android.html
CHANGED
|
@@ -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
|
|
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>
|
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
|
|
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>
|
package/client/styles/layout.css
CHANGED
|
@@ -132,9 +132,20 @@ body {
|
|
|
132
132
|
}
|
|
133
133
|
.message-list.narrow-hidden { display: none; }
|
|
134
134
|
|
|
135
|
-
/* Show hamburger
|
|
135
|
+
/* Show hamburger always on narrow */
|
|
136
136
|
#btn-menu { display: inline-flex !important; }
|
|
137
|
-
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
|
|
435
|
-
|
|
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) {
|