@bobfrankston/mailx 1.0.36 → 1.0.37
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/README.md +35 -19
- package/client/app.js +8 -0
- package/client/components/folder-tree.js +10 -1
- package/client/styles/variables.css +27 -8
- package/package.json +2 -2
- package/packages/mailx-server/index.js +1 -1
- package/packages/mailx-settings/index.d.ts +2 -2
- package/packages/mailx-settings/index.js +9 -3
- package/packages/mailx-types/index.d.ts +1 -1
package/README.md
CHANGED
|
@@ -29,17 +29,11 @@ If you already have mailx configured on another machine with settings on OneDriv
|
|
|
29
29
|
|
|
30
30
|
Once OneDrive syncs, mailx picks up your accounts automatically.
|
|
31
31
|
|
|
32
|
-
### Option B: New user --
|
|
32
|
+
### Option B: New user -- single machine, local settings
|
|
33
33
|
|
|
34
|
-
**Step 1.** Create
|
|
34
|
+
**Step 1.** Create `~/.mailx/settings.jsonc` with your account(s):
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
mkdir ~/.mailx
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
(On Windows: `mkdir %USERPROFILE%\.mailx`)
|
|
41
|
-
|
|
42
|
-
**Step 2.** Create `~/.mailx/settings.jsonc` with your account(s):
|
|
36
|
+
(On Windows `~` means `%USERPROFILE%`, e.g., `C:\Users\You\.mailx\settings.jsonc`)
|
|
43
37
|
|
|
44
38
|
```jsonc
|
|
45
39
|
{
|
|
@@ -66,35 +60,57 @@ mkdir ~/.mailx
|
|
|
66
60
|
"password": "your-password"
|
|
67
61
|
},
|
|
68
62
|
"enabled": true,
|
|
69
|
-
"defaultSend": true
|
|
63
|
+
"defaultSend": true, // Use this account for sending when From doesn't match
|
|
64
|
+
"relayDomains": [], // Domains to skip in Delivered-To (optional)
|
|
65
|
+
"deliveredToPrefix": [] // Prefixes to strip from Delivered-To alias (optional)
|
|
70
66
|
}
|
|
71
67
|
],
|
|
72
68
|
"sync": {
|
|
73
69
|
"intervalMinutes": 5, // Full sync interval (minutes)
|
|
74
70
|
"historyDays": 0 // Days of history to sync (0 = all)
|
|
71
|
+
},
|
|
72
|
+
"ui": {
|
|
73
|
+
"theme": "system", // "system", "dark", or "light"
|
|
74
|
+
"fontSize": 15
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
No `config.jsonc` needed -- when it doesn't exist, mailx reads settings directly from `~/.mailx/`.
|
|
80
|
+
|
|
81
|
+
**Step 2.** Run `mailx` and open `http://127.0.0.1:9333` in your browser.
|
|
80
82
|
|
|
81
83
|
### Option C: Multi-machine with shared settings (OneDrive/Dropbox)
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
To share settings across machines, put `settings.jsonc` in a cloud-synced folder and create a local pointer file.
|
|
86
|
+
|
|
87
|
+
**Step 1.** Move `settings.jsonc` to a shared location:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
# Example: OneDrive
|
|
91
|
+
mkdir "%OneDrive%\home\.mailx"
|
|
92
|
+
move "%USERPROFILE%\.mailx\settings.jsonc" "%OneDrive%\home\.mailx\settings.jsonc"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Step 2.** Create `~/.mailx/config.jsonc` pointing to the shared location:
|
|
84
96
|
|
|
85
|
-
**`~/.mailx/config.jsonc`:**
|
|
86
97
|
```jsonc
|
|
87
98
|
{
|
|
88
|
-
//
|
|
99
|
+
// Where the shared settings live (OneDrive, Dropbox, network share, etc.)
|
|
89
100
|
"sharedDir": "C:/Users/You/OneDrive/home/.mailx",
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"historyDays": 90
|
|
101
|
+
|
|
102
|
+
// Local-only settings (not synced):
|
|
103
|
+
"storePath": "C:/Users/You/.mailx/mailxstore", // cached message bodies
|
|
104
|
+
"historyDays": 90 // override sync depth per machine
|
|
94
105
|
}
|
|
95
106
|
```
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
**How it works:**
|
|
109
|
+
- `config.jsonc` is local to each machine -- never synced
|
|
110
|
+
- `sharedDir` points to the cloud folder containing `settings.jsonc`, `allowlist.jsonc`, `preferences.jsonc`
|
|
111
|
+
- mailx caches shared files locally for offline use
|
|
112
|
+
- `historyDays` in `config.jsonc` overrides the shared default per machine
|
|
113
|
+
- On new machines, if `config.jsonc` doesn't exist, mailx auto-detects OneDrive at `%OneDrive%/home/.mailx/`
|
|
98
114
|
|
|
99
115
|
### Adding a Gmail account
|
|
100
116
|
|
package/client/app.js
CHANGED
|
@@ -639,6 +639,14 @@ setInterval(async () => {
|
|
|
639
639
|
}, 5000);
|
|
640
640
|
console.log("mailx client initialized, location:", location.href);
|
|
641
641
|
updateNewMessageCount();
|
|
642
|
+
// ── Apply theme from settings ──
|
|
643
|
+
fetch("/api/version").then(r => r.json()).then(d => {
|
|
644
|
+
if (d.theme === "dark")
|
|
645
|
+
document.documentElement.classList.add("theme-dark");
|
|
646
|
+
else if (d.theme === "light")
|
|
647
|
+
document.documentElement.classList.add("theme-light");
|
|
648
|
+
// "system" or missing = no class, CSS media query handles it
|
|
649
|
+
}).catch(() => { });
|
|
642
650
|
// Diagnostic: test API connectivity (helps debug WebView2 blank screen)
|
|
643
651
|
fetch("/api/version").then(r => r.json()).then(d => {
|
|
644
652
|
console.log("API reachable:", d);
|
|
@@ -387,7 +387,16 @@ async function loadFolderTree(container) {
|
|
|
387
387
|
// Re-select previous folder on refresh, or auto-select Inbox on first load
|
|
388
388
|
const allFolderEls = container.querySelectorAll('.ft-folder');
|
|
389
389
|
let target = null;
|
|
390
|
-
if (
|
|
390
|
+
if (selectedFolderId === -1) {
|
|
391
|
+
// Unified inbox was selected — just re-highlight it, don't click
|
|
392
|
+
const unified = container.querySelector('.ft-unified');
|
|
393
|
+
if (unified) {
|
|
394
|
+
unified.classList.add("selected");
|
|
395
|
+
selectedElement = unified;
|
|
396
|
+
target = unified;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
else if (selectedAccountId && selectedFolderId !== null && selectedFolderId >= 0) {
|
|
391
400
|
for (const f of allFolderEls) {
|
|
392
401
|
const el = f;
|
|
393
402
|
if (el.dataset.accountId === selectedAccountId && el.dataset.folderId === String(selectedFolderId)) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* mailx design tokens -- all theming via custom properties */
|
|
2
|
+
/* Theme controlled by class on <html>: theme-dark, theme-light, or absent (system) */
|
|
2
3
|
|
|
3
4
|
:root {
|
|
4
5
|
/* Layout */
|
|
@@ -8,19 +9,19 @@
|
|
|
8
9
|
--splitter-size: 5px;
|
|
9
10
|
--list-viewer-split: 40%;
|
|
10
11
|
|
|
11
|
-
/* Colors
|
|
12
|
-
--color-brand: oklch(0.88 0.04 240);
|
|
13
|
-
--color-brand-dark: oklch(0.45 0.10 240);
|
|
12
|
+
/* Colors — dark by default */
|
|
13
|
+
--color-brand: oklch(0.88 0.04 240);
|
|
14
|
+
--color-brand-dark: oklch(0.45 0.10 240);
|
|
14
15
|
--color-bg: oklch(0.20 0.02 270);
|
|
15
16
|
--color-bg-surface: oklch(0.25 0.03 270);
|
|
16
17
|
--color-bg-hover: color-mix(in oklch, var(--color-bg-surface) 80%, white);
|
|
17
18
|
--color-bg-selected: var(--color-brand);
|
|
18
19
|
--color-bg-toolbar: oklch(0.22 0.02 270);
|
|
19
|
-
--color-text: oklch(0.
|
|
20
|
-
--color-text-muted: oklch(0.
|
|
20
|
+
--color-text: oklch(0.92 0.01 270);
|
|
21
|
+
--color-text-muted: oklch(0.68 0.02 270);
|
|
21
22
|
--color-accent: var(--color-brand-dark);
|
|
22
23
|
--color-unread: oklch(0.95 0.02 270);
|
|
23
|
-
--color-border: oklch(0.
|
|
24
|
+
--color-border: oklch(0.40 0.02 270);
|
|
24
25
|
--color-danger: oklch(0.65 0.15 15);
|
|
25
26
|
--color-success: oklch(0.65 0.15 145);
|
|
26
27
|
--color-badge: oklch(0.60 0.18 250);
|
|
@@ -46,9 +47,26 @@
|
|
|
46
47
|
color-scheme: dark light;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
/* Light
|
|
50
|
+
/* Light overrides */
|
|
51
|
+
.theme-light {
|
|
52
|
+
--color-bg: oklch(0.97 0.005 270);
|
|
53
|
+
--color-bg-surface: oklch(1.0 0 0);
|
|
54
|
+
--color-bg-hover: oklch(0.95 0.005 270);
|
|
55
|
+
--color-brand: oklch(0.88 0.04 240);
|
|
56
|
+
--color-brand-dark: oklch(0.35 0.12 240);
|
|
57
|
+
--color-bg-selected: var(--color-brand);
|
|
58
|
+
--color-bg-toolbar: oklch(0.95 0.005 270);
|
|
59
|
+
--color-text: oklch(0.20 0.02 270);
|
|
60
|
+
--color-text-muted: oklch(0.45 0.02 270);
|
|
61
|
+
--color-accent: var(--color-brand-dark);
|
|
62
|
+
--color-unread: oklch(0.10 0.02 270);
|
|
63
|
+
--color-border: oklch(0.85 0.01 270);
|
|
64
|
+
color-scheme: light;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* System preference: light — apply when no explicit theme class */
|
|
50
68
|
@media (prefers-color-scheme: light) {
|
|
51
|
-
:root {
|
|
69
|
+
:root:not(.theme-dark):not(.theme-light) {
|
|
52
70
|
--color-bg: oklch(0.97 0.005 270);
|
|
53
71
|
--color-bg-surface: oklch(1.0 0 0);
|
|
54
72
|
--color-bg-hover: oklch(0.95 0.005 270);
|
|
@@ -61,5 +79,6 @@
|
|
|
61
79
|
--color-accent: var(--color-brand-dark);
|
|
62
80
|
--color-unread: oklch(0.10 0.02 270);
|
|
63
81
|
--color-border: oklch(0.85 0.01 270);
|
|
82
|
+
color-scheme: light;
|
|
64
83
|
}
|
|
65
84
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.37",
|
|
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.8",
|
|
24
24
|
"@bobfrankston/miscinfo": "^1.0.6",
|
|
25
25
|
"@bobfrankston/oauthsupport": "^1.0.11",
|
|
26
26
|
"@bobfrankston/rust-builder": "^0.1.2",
|
|
@@ -81,7 +81,7 @@ app.use("/node_modules", express.static(path.join(rootDir, "node_modules"), { et
|
|
|
81
81
|
// Mount API
|
|
82
82
|
const apiRouter = createApiRouter(db, imapManager);
|
|
83
83
|
app.use("/api", apiRouter);
|
|
84
|
-
app.get("/api/version", (req, res) => res.json({ server: SERVER_VERSION, client: CLIENT_VERSION }));
|
|
84
|
+
app.get("/api/version", (req, res) => res.json({ server: SERVER_VERSION, client: CLIENT_VERSION, theme: settings.ui?.theme || "system" }));
|
|
85
85
|
app.get("/status", (req, res) => {
|
|
86
86
|
const accounts = db.getAccounts();
|
|
87
87
|
const pendingSync = db.getTotalPendingSyncCount();
|
|
@@ -20,7 +20,7 @@ declare const LOCAL_DIR: string;
|
|
|
20
20
|
declare function getSharedDir(): string;
|
|
21
21
|
declare const DEFAULT_PREFERENCES: {
|
|
22
22
|
ui: {
|
|
23
|
-
theme: "dark" | "light";
|
|
23
|
+
theme: "system" | "dark" | "light";
|
|
24
24
|
folderWidth: number;
|
|
25
25
|
listViewerSplit: number;
|
|
26
26
|
fontSize: number;
|
|
@@ -39,7 +39,7 @@ declare const DEFAULT_ALLOWLIST: {
|
|
|
39
39
|
export declare function loadAccounts(): AccountConfig[];
|
|
40
40
|
/** Save account configs */
|
|
41
41
|
export declare function saveAccounts(accounts: AccountConfig[]): void;
|
|
42
|
-
/** Load preferences (shared + local overrides) */
|
|
42
|
+
/** Load preferences (shared + local overrides, with legacy fallback) */
|
|
43
43
|
export declare function loadPreferences(): typeof DEFAULT_PREFERENCES;
|
|
44
44
|
/** Save preferences */
|
|
45
45
|
export declare function savePreferences(prefs: typeof DEFAULT_PREFERENCES): void;
|
|
@@ -109,7 +109,7 @@ function saveFile(filename, data) {
|
|
|
109
109
|
const DEFAULT_ACCOUNTS = [];
|
|
110
110
|
const DEFAULT_PREFERENCES = {
|
|
111
111
|
ui: {
|
|
112
|
-
theme: "
|
|
112
|
+
theme: "system",
|
|
113
113
|
folderWidth: 220,
|
|
114
114
|
listViewerSplit: 40,
|
|
115
115
|
fontSize: 15,
|
|
@@ -155,9 +155,15 @@ export function loadAccounts() {
|
|
|
155
155
|
export function saveAccounts(accounts) {
|
|
156
156
|
saveFile("accounts.jsonc", { accounts });
|
|
157
157
|
}
|
|
158
|
-
/** Load preferences (shared + local overrides) */
|
|
158
|
+
/** Load preferences (shared + local overrides, with legacy fallback) */
|
|
159
159
|
export function loadPreferences() {
|
|
160
|
-
|
|
160
|
+
let shared = loadFile("preferences.jsonc", DEFAULT_PREFERENCES);
|
|
161
|
+
// Legacy fallback: read ui/sync from settings.jsonc if preferences.jsonc had only defaults
|
|
162
|
+
const legacy = loadLegacySettings();
|
|
163
|
+
if (legacy?.ui)
|
|
164
|
+
shared = { ...shared, ui: { ...shared.ui, ...legacy.ui } };
|
|
165
|
+
if (legacy?.sync)
|
|
166
|
+
shared = { ...shared, sync: { ...shared.sync, ...legacy.sync } };
|
|
161
167
|
const localConfig = readLocalConfig();
|
|
162
168
|
// Local overrides
|
|
163
169
|
if (localConfig.historyDays !== undefined) {
|
|
@@ -175,7 +175,7 @@ export type WsEvent = {
|
|
|
175
175
|
export interface MailxSettings {
|
|
176
176
|
accounts: AccountConfig[];
|
|
177
177
|
ui: {
|
|
178
|
-
theme: "dark" | "light";
|
|
178
|
+
theme: "system" | "dark" | "light";
|
|
179
179
|
folderWidth: number;
|
|
180
180
|
listViewerSplit: number; /** Percentage for message list height */
|
|
181
181
|
fontSize: number;
|