@emailthing/cli 0.0.0-alpha.1
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 +24 -0
- package/dist/agent.js +359 -0
- package/dist/config-cfsae2jm.js +154 -0
- package/dist/index-afyvwmt5.js +47 -0
- package/dist/index-xp4bqj3r.js +180 -0
- package/dist/index.js +17 -0
- package/dist/main.js +1082 -0
- package/package.json +46 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
import {
|
|
4
|
+
EmailThingCLI,
|
|
5
|
+
syncData
|
|
6
|
+
} from "./index-xp4bqj3r.js";
|
|
7
|
+
import {
|
|
8
|
+
getDB,
|
|
9
|
+
loadAuth,
|
|
10
|
+
saveAuth
|
|
11
|
+
} from "./config-cfsae2jm.js";
|
|
12
|
+
import {
|
|
13
|
+
__callDispose,
|
|
14
|
+
__require,
|
|
15
|
+
__toESM,
|
|
16
|
+
__using
|
|
17
|
+
} from "./index-afyvwmt5.js";
|
|
18
|
+
|
|
19
|
+
// src/utils/colors.ts
|
|
20
|
+
var colors = {
|
|
21
|
+
reset: "\x1B[0m" /* RESET */,
|
|
22
|
+
bright: "\x1B[1m" /* BRIGHT */,
|
|
23
|
+
dim: "\x1B[2m" /* DIM */,
|
|
24
|
+
fg: {
|
|
25
|
+
black: "\x1B[30m" /* BLACK */,
|
|
26
|
+
red: "\x1B[31m" /* RED */,
|
|
27
|
+
green: "\x1B[32m" /* GREEN */,
|
|
28
|
+
yellow: "\x1B[33m" /* YELLOW */,
|
|
29
|
+
blue: "\x1B[34m" /* BLUE */,
|
|
30
|
+
magenta: "\x1B[35m" /* MAGENTA */,
|
|
31
|
+
cyan: "\x1B[36m" /* CYAN */,
|
|
32
|
+
white: "\x1B[37m" /* WHITE */,
|
|
33
|
+
gray: "\x1B[90m" /* GRAY */
|
|
34
|
+
},
|
|
35
|
+
bg: {
|
|
36
|
+
black: "\x1B[40m" /* BLACK */,
|
|
37
|
+
red: "\x1B[41m" /* RED */,
|
|
38
|
+
green: "\x1B[42m" /* GREEN */,
|
|
39
|
+
yellow: "\x1B[43m" /* YELLOW */,
|
|
40
|
+
blue: "\x1B[44m" /* BLUE */,
|
|
41
|
+
magenta: "\x1B[45m" /* MAGENTA */,
|
|
42
|
+
cyan: "\x1B[46m" /* CYAN */,
|
|
43
|
+
white: "\x1B[47m" /* WHITE */
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/ui/renderer.ts
|
|
48
|
+
var { stdout, stdin } = process;
|
|
49
|
+
var defaultKeys = [
|
|
50
|
+
"up" /* UP */,
|
|
51
|
+
"down" /* DOWN */,
|
|
52
|
+
"left" /* LEFT */,
|
|
53
|
+
"right" /* RIGHT */,
|
|
54
|
+
"pageup" /* PAGEUP */,
|
|
55
|
+
"pagedown" /* PAGEDOWN */,
|
|
56
|
+
"home" /* HOME */,
|
|
57
|
+
"end" /* END */,
|
|
58
|
+
"enter" /* ENTER */,
|
|
59
|
+
"escape" /* ESCAPE */,
|
|
60
|
+
"ctrl-c" /* CTRL_C */,
|
|
61
|
+
"backspace" /* BACKSPACE */,
|
|
62
|
+
"tab" /* TAB */,
|
|
63
|
+
"ctrl-up" /* CTRL_UP */,
|
|
64
|
+
"ctrl-down" /* CTRL_DOWN */
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
class TerminalRenderer {
|
|
68
|
+
lastBuffer = [];
|
|
69
|
+
constructor() {
|
|
70
|
+
this.setupTerminal(), this.watchResize(() => this.lastBuffer = []);
|
|
71
|
+
}
|
|
72
|
+
setupTerminal() {
|
|
73
|
+
stdout.write("\x1B[?1049h" /* ALT_SCREEN_ON */), stdout.write("\x1B[?25l" /* HIDE_CURSOR */), stdout.write("\x1B[2J" /* CLEAR_SCREEN */), stdin.setRawMode?.(!0), stdin.resume();
|
|
74
|
+
}
|
|
75
|
+
cleanup() {
|
|
76
|
+
stdout.write("\x1B[?25l" /* HIDE_CURSOR */), stdout.write("\x1B[?1049l" /* ALT_SCREEN_OFF */), stdin.setRawMode?.(!1), this.watchListeners.forEach((fn) => this.unwatchResize(fn));
|
|
77
|
+
}
|
|
78
|
+
[Symbol.dispose]() {
|
|
79
|
+
this.cleanup();
|
|
80
|
+
}
|
|
81
|
+
watchListeners = [];
|
|
82
|
+
watchResize(fn) {
|
|
83
|
+
stdout?.on("resize", fn), this.watchListeners.push(fn);
|
|
84
|
+
}
|
|
85
|
+
unwatchResize(fn) {
|
|
86
|
+
stdout?.off("resize", fn), this.watchListeners = this.watchListeners.filter((f) => f !== fn);
|
|
87
|
+
}
|
|
88
|
+
render(buffer) {
|
|
89
|
+
let { lines, cursor } = buffer, height = stdout.rows || 24, width = stdout.columns || 80, paddedLines = [...lines];
|
|
90
|
+
while (paddedLines.length < height)
|
|
91
|
+
paddedLines.push("");
|
|
92
|
+
let output = "";
|
|
93
|
+
for (let i = 0;i < Math.min(paddedLines.length, height); i++) {
|
|
94
|
+
let line = paddedLines[i], lastLine = this.lastBuffer[i] ?? "";
|
|
95
|
+
if (line !== lastLine)
|
|
96
|
+
output += `\x1B[${i + 1};1H\x1B[2K${line}`;
|
|
97
|
+
}
|
|
98
|
+
if (cursor)
|
|
99
|
+
output += `\x1B[${cursor.row + 1};${cursor.col + 1}H\x1B[?25h`;
|
|
100
|
+
else
|
|
101
|
+
output += "\x1B[?25l" /* HIDE_CURSOR */;
|
|
102
|
+
if (output)
|
|
103
|
+
stdout.write(output);
|
|
104
|
+
this.lastBuffer = paddedLines.slice(0, height);
|
|
105
|
+
}
|
|
106
|
+
clear() {
|
|
107
|
+
stdout.write("\x1B[2J\x1B[H"), this.lastBuffer = [];
|
|
108
|
+
}
|
|
109
|
+
getSize() {
|
|
110
|
+
return { width: stdout.columns || 80, height: stdout.rows || 24 };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
var mapKey = (key) => {
|
|
114
|
+
if (key === "\x1B[A")
|
|
115
|
+
return "up" /* UP */;
|
|
116
|
+
else if (key === "\x1B[B")
|
|
117
|
+
return "down" /* DOWN */;
|
|
118
|
+
else if (key === "\x1B[C")
|
|
119
|
+
return "right" /* RIGHT */;
|
|
120
|
+
else if (key === "\x1B[D")
|
|
121
|
+
return "left" /* LEFT */;
|
|
122
|
+
else if (key === "\x1B[1;5A")
|
|
123
|
+
return "ctrl-up" /* CTRL_UP */;
|
|
124
|
+
else if (key === "\x1B[1;5B")
|
|
125
|
+
return "ctrl-down" /* CTRL_DOWN */;
|
|
126
|
+
else if (key === "\x1B[5~")
|
|
127
|
+
return "pageup" /* PAGEUP */;
|
|
128
|
+
else if (key === "\x1B[6~")
|
|
129
|
+
return "pagedown" /* PAGEDOWN */;
|
|
130
|
+
else if (key === "\x1B[H" || key === "\x1B[1~")
|
|
131
|
+
return "home" /* HOME */;
|
|
132
|
+
else if (key === "\x1B[F" || key === "\x1B[4~")
|
|
133
|
+
return "end" /* END */;
|
|
134
|
+
else if (key === "\r")
|
|
135
|
+
return "enter" /* ENTER */;
|
|
136
|
+
else if (key === "\x1B")
|
|
137
|
+
return "escape" /* ESCAPE */;
|
|
138
|
+
else if (key === "\x03")
|
|
139
|
+
return "ctrl-c" /* CTRL_C */;
|
|
140
|
+
else if (key === "\x7F")
|
|
141
|
+
return "backspace" /* BACKSPACE */;
|
|
142
|
+
else if (key === "\x1B[3~")
|
|
143
|
+
return "delete" /* DELETE */;
|
|
144
|
+
else if (key === "\t")
|
|
145
|
+
return "tab" /* TAB */;
|
|
146
|
+
else if (key === "\x1B[Z")
|
|
147
|
+
return "backtab" /* BACKTAB */;
|
|
148
|
+
else
|
|
149
|
+
return key;
|
|
150
|
+
};
|
|
151
|
+
function readKeys() {
|
|
152
|
+
let queuedKeys = [], resolveNext = null, onData = (data) => {
|
|
153
|
+
let key = mapKey(data.toString());
|
|
154
|
+
if (resolveNext)
|
|
155
|
+
resolveNext(key), resolveNext = null;
|
|
156
|
+
else
|
|
157
|
+
queuedKeys.push(key);
|
|
158
|
+
};
|
|
159
|
+
return stdin.on("data", onData), {
|
|
160
|
+
[Symbol.asyncIterator]() {
|
|
161
|
+
return {
|
|
162
|
+
next: () => new Promise((resolve) => {
|
|
163
|
+
if (queuedKeys.length > 0)
|
|
164
|
+
resolve({ value: queuedKeys.shift(), done: !1 });
|
|
165
|
+
else
|
|
166
|
+
resolveNext = (key) => resolve({ value: key, done: !1 });
|
|
167
|
+
}),
|
|
168
|
+
return: () => {
|
|
169
|
+
return stdin.off("data", onData), Promise.resolve({ value: void 0, done: !0 });
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function truncate(text, maxWidth) {
|
|
176
|
+
let visibleLength = Bun.stringWidth(text, { ambiguousIsNarrow: !1, countAnsiEscapeCodes: !1 });
|
|
177
|
+
if (visibleLength <= maxWidth)
|
|
178
|
+
return text + " ".repeat(maxWidth - visibleLength);
|
|
179
|
+
while (Bun.stringWidth(text, { ambiguousIsNarrow: !1, countAnsiEscapeCodes: !1 }) > maxWidth - 1)
|
|
180
|
+
text = text.slice(0, -1);
|
|
181
|
+
return text + "\u2026";
|
|
182
|
+
}
|
|
183
|
+
function formatDate(dateStr) {
|
|
184
|
+
let date = new Date(dateStr), diff = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
|
|
185
|
+
if (diff < 86400000 /* ONE_DAY_MS */)
|
|
186
|
+
return date.toLocaleTimeString("en-US", {
|
|
187
|
+
hour: "2-digit",
|
|
188
|
+
minute: "2-digit"
|
|
189
|
+
});
|
|
190
|
+
if (diff < 604800000 /* ONE_WEEK_MS */)
|
|
191
|
+
return date.toLocaleDateString("en-US", { weekday: "short" });
|
|
192
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/ui/login.ts
|
|
196
|
+
async function loginScreen() {
|
|
197
|
+
let __stack = [];
|
|
198
|
+
try {
|
|
199
|
+
const renderer = __using(__stack, new TerminalRenderer, 0);
|
|
200
|
+
let state = {
|
|
201
|
+
username: "",
|
|
202
|
+
password: "",
|
|
203
|
+
focusedField: "username"
|
|
204
|
+
};
|
|
205
|
+
let renderLogin = () => {
|
|
206
|
+
let { width, height } = renderer.getSize(), lines = [], title = "EmailThing CLI - Login", centerX = Math.floor((width - 22) / 2), centerY = Math.floor(height / 2) - 5;
|
|
207
|
+
for (let i = 0;i < centerY; i++)
|
|
208
|
+
lines.push("");
|
|
209
|
+
lines.push(" ".repeat(centerX) + colors.bright + "EmailThing CLI - Login" + colors.reset), lines.push(""), lines.push("");
|
|
210
|
+
let labelWidth = 12, inputWidth = 30, totalWidth = labelWidth + inputWidth + 4, startX = Math.floor((width - totalWidth) / 2), usernameFocused = state.focusedField === "username", passwordFocused = state.focusedField === "password", submitFocused = state.focusedField === "submit", usernameColor = usernameFocused ? colors.fg.cyan : colors.fg.white, passwordColor = passwordFocused ? colors.fg.cyan : colors.fg.white, submitColor = submitFocused ? colors.bg.cyan + colors.fg.black : colors.fg.white, usernameDisplay = state.username.padEnd(inputWidth - 2), passwordDisplay = "\u2022".repeat(state.password.length).padEnd(inputWidth - 2), usernameLineIdx = lines.length;
|
|
211
|
+
lines.push(" ".repeat(startX) + usernameColor + "Username: " + colors.reset + (usernameFocused ? colors.bg.white + colors.fg.black : "") + "[" + usernameDisplay + "]" + colors.reset), lines.push("");
|
|
212
|
+
let passwordLineIdx = lines.length;
|
|
213
|
+
lines.push(" ".repeat(startX) + passwordColor + "Password: " + colors.reset + (passwordFocused ? colors.bg.white + colors.fg.black : "") + "[" + passwordDisplay + "]" + colors.reset), lines.push(""), lines.push("");
|
|
214
|
+
let submitBtn = " Login ", btnStartX = Math.floor((width - submitBtn.length) / 2);
|
|
215
|
+
lines.push(" ".repeat(btnStartX) + submitColor + submitBtn + colors.reset), lines.push(""), lines.push(""), lines.push(" ".repeat(Math.floor((width - 50) / 2)) + colors.dim + "Tab: Next field | Enter: Submit | Ctrl+C: Exit" + colors.reset);
|
|
216
|
+
let cursor = void 0;
|
|
217
|
+
if (usernameFocused)
|
|
218
|
+
cursor = {
|
|
219
|
+
row: usernameLineIdx,
|
|
220
|
+
col: startX + labelWidth + 1 + state.username.length
|
|
221
|
+
};
|
|
222
|
+
else if (passwordFocused)
|
|
223
|
+
cursor = {
|
|
224
|
+
row: passwordLineIdx,
|
|
225
|
+
col: startX + labelWidth + 1 + state.password.length
|
|
226
|
+
};
|
|
227
|
+
renderer.render({ lines, cursor });
|
|
228
|
+
};
|
|
229
|
+
try {
|
|
230
|
+
renderLogin(), renderer.watchResize(renderLogin);
|
|
231
|
+
for await (let key of readKeys()) {
|
|
232
|
+
if (key === "ctrl-c" /* CTRL_C */)
|
|
233
|
+
return renderer.cleanup(), null;
|
|
234
|
+
if (key === "tab" /* TAB */)
|
|
235
|
+
if (state.focusedField === "username")
|
|
236
|
+
state.focusedField = "password";
|
|
237
|
+
else if (state.focusedField === "password")
|
|
238
|
+
state.focusedField = "submit";
|
|
239
|
+
else
|
|
240
|
+
state.focusedField = "username";
|
|
241
|
+
if (key === "enter" /* ENTER */) {
|
|
242
|
+
if (state.focusedField === "submit" || state.username && state.password)
|
|
243
|
+
return { username: state.username, password: state.password };
|
|
244
|
+
if (state.focusedField === "username")
|
|
245
|
+
state.focusedField = "password";
|
|
246
|
+
else if (state.focusedField === "password")
|
|
247
|
+
state.focusedField = "submit";
|
|
248
|
+
}
|
|
249
|
+
if (key === "backspace" /* BACKSPACE */) {
|
|
250
|
+
if (state.focusedField === "username" && state.username.length > 0)
|
|
251
|
+
state.username = state.username.slice(0, -1);
|
|
252
|
+
else if (state.focusedField === "password" && state.password.length > 0)
|
|
253
|
+
state.password = state.password.slice(0, -1);
|
|
254
|
+
}
|
|
255
|
+
if (key.length >= 1 && key >= " " && key <= "~" && !defaultKeys.includes(key)) {
|
|
256
|
+
if (state.focusedField === "username")
|
|
257
|
+
state.username += key;
|
|
258
|
+
else if (state.focusedField === "password")
|
|
259
|
+
state.password += key;
|
|
260
|
+
}
|
|
261
|
+
renderLogin();
|
|
262
|
+
}
|
|
263
|
+
throw Error("Unexpected end of input");
|
|
264
|
+
} finally {
|
|
265
|
+
renderer.cleanup();
|
|
266
|
+
}
|
|
267
|
+
} catch (_catch) {
|
|
268
|
+
var _err = _catch, _hasErr = 1;
|
|
269
|
+
} finally {
|
|
270
|
+
__callDispose(__stack, _err, _hasErr);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/ui/email-list.ts
|
|
275
|
+
async function emailListScreen(db, mailboxId, syncCallback = null, restoreId, modifyEmail) {
|
|
276
|
+
let __stack = [];
|
|
277
|
+
try {
|
|
278
|
+
const renderer = __using(__stack, new TerminalRenderer, 0);
|
|
279
|
+
let loadEmails = () => {
|
|
280
|
+
return db.query("SELECT id, subject, from_addr, snippet, isRead, isStarred, createdAt, categoryId FROM emails WHERE mailboxId = ? AND isDeleted = false ORDER BY createdAt DESC").all(mailboxId).map((e) => ({
|
|
281
|
+
...e,
|
|
282
|
+
isRead: e.isRead === 1,
|
|
283
|
+
isStarred: e.isStarred === 1
|
|
284
|
+
}));
|
|
285
|
+
};
|
|
286
|
+
let loadCategories = () => {
|
|
287
|
+
let cats = db.query("SELECT * FROM categories WHERE mailboxId = ? AND isDeleted = false").all(mailboxId);
|
|
288
|
+
return new Map(cats.map((c) => [c.id, c]));
|
|
289
|
+
};
|
|
290
|
+
let mailbox = db.query("SELECT * FROM mailboxes WHERE id = ? AND isDeleted = false").get(mailboxId);
|
|
291
|
+
let emails = loadEmails();
|
|
292
|
+
let isSyncing = !1;
|
|
293
|
+
let state = {
|
|
294
|
+
emails,
|
|
295
|
+
categories: loadCategories(),
|
|
296
|
+
selectedEmailId: emails.length > 0 ? restoreId && emails.find((e) => e.id === restoreId)?.id || emails[0].id : null,
|
|
297
|
+
scrollOffset: restoreId ? Math.max(0, Math.min(emails.findIndex((e) => e.id === restoreId) - 1, emails.length - renderer.getSize().height + 4)) : 0,
|
|
298
|
+
mailboxName: mailbox?.address || "Inbox",
|
|
299
|
+
syncDots: 0
|
|
300
|
+
};
|
|
301
|
+
if (syncCallback && !restoreId)
|
|
302
|
+
isSyncing = !0, syncCallback().then(() => {
|
|
303
|
+
isSyncing = !1, state.emails = loadEmails(), state.categories = loadCategories(), renderEmailList();
|
|
304
|
+
});
|
|
305
|
+
let renderEmailList = () => {
|
|
306
|
+
let { width, height } = renderer.getSize(), lines = [], header = `${colors.bright}${state.mailboxName}${colors.reset} (${state.emails.length} emails)`;
|
|
307
|
+
lines.push(header), lines.push("\u2500".repeat(width));
|
|
308
|
+
let listHeight = height - 4, needsScroll = state.emails.length > listHeight, visibleStart = needsScroll ? state.scrollOffset : 0, visibleEnd = Math.min(visibleStart + listHeight, state.emails.length), scrollbarHeight = listHeight, scrollbarThumbSize = Math.max(1, Math.floor(listHeight / state.emails.length * scrollbarHeight)), scrollbarThumbPosition = Math.floor(visibleStart / state.emails.length * scrollbarHeight);
|
|
309
|
+
if (state.emails.length === 0)
|
|
310
|
+
lines.push(""), lines.push(colors.dim + "No emails" + colors.reset);
|
|
311
|
+
else
|
|
312
|
+
for (let i = visibleStart;i < visibleEnd; i++) {
|
|
313
|
+
let email = state.emails[i], isSelected = email.id === state.selectedEmailId, from = truncate(email.from_addr.split("<")[0].trim() || email.from_addr, 20), subject = truncate(email.subject || "(no subject)", width - 37), date = formatDate(email.createdAt), icon = " ", dot = email.isStarred ? "\u272A" : "\u25CF";
|
|
314
|
+
if (email.categoryId) {
|
|
315
|
+
let category = state.categories.get(email.categoryId);
|
|
316
|
+
if (category?.color) {
|
|
317
|
+
let color = Bun.color(category.color, "ansi");
|
|
318
|
+
if (email.isRead)
|
|
319
|
+
icon = `${colors.dim}${color}${dot}${colors.reset}`;
|
|
320
|
+
else
|
|
321
|
+
icon = `${color}${dot}${colors.reset}`;
|
|
322
|
+
}
|
|
323
|
+
} else if (!email.isRead)
|
|
324
|
+
icon = email.isStarred ? `${"\x1B[33m" /* YELLOW */}${dot}` : dot;
|
|
325
|
+
else if (email.isStarred)
|
|
326
|
+
icon = `${colors.dim}${"\x1B[33m" /* YELLOW */}\u2605`;
|
|
327
|
+
let bg = isSelected ? colors.bg.blue : "", fg = email.isStarred ? email.isRead ? colors.fg.yellow + colors.dim : colors.bright + colors.fg.yellow : email.isRead ? colors.dim : colors.bright, reset = colors.reset, scrollbarChar = " ";
|
|
328
|
+
if (needsScroll) {
|
|
329
|
+
let lineIndex = i - visibleStart;
|
|
330
|
+
if (lineIndex >= scrollbarThumbPosition && lineIndex < scrollbarThumbPosition + scrollbarThumbSize)
|
|
331
|
+
scrollbarChar = colors.dim + "\u2588" + colors.reset;
|
|
332
|
+
else
|
|
333
|
+
scrollbarChar = colors.dim + "\u2502" + colors.reset;
|
|
334
|
+
}
|
|
335
|
+
let line = bg + " " + icon + fg + bg + " " + from.padEnd(20) + " " + subject.padEnd(width - 52) + " " + date.padStart(10) + reset + " " + scrollbarChar;
|
|
336
|
+
lines.push(line);
|
|
337
|
+
}
|
|
338
|
+
lines.push("\u2500".repeat(width));
|
|
339
|
+
let statusLine = "Enter: View | s: Star | u: Read/Unread | c: Compose | r: Refresh | m: Mailbox | q: Quit", syncIndicator = "";
|
|
340
|
+
if (isSyncing)
|
|
341
|
+
syncIndicator = " Syncing" + (".".repeat(state.syncDots + 1) + " ".repeat(3 - state.syncDots - 1));
|
|
342
|
+
let _truncate = (text, length) => text.length > length ? text.substring(0, length - 1) + "\u2026" : text.padEnd(length, " ");
|
|
343
|
+
lines.push(colors.dim + _truncate(statusLine, width - syncIndicator.length) + syncIndicator + colors.reset), renderer.render({ lines });
|
|
344
|
+
};
|
|
345
|
+
let animInterval = setInterval(() => {
|
|
346
|
+
if (isSyncing)
|
|
347
|
+
state.syncDots = (state.syncDots + 1) % 3;
|
|
348
|
+
renderEmailList();
|
|
349
|
+
}, 500);
|
|
350
|
+
try {
|
|
351
|
+
if (syncCallback && !restoreId)
|
|
352
|
+
isSyncing = !0, syncCallback().then(() => {
|
|
353
|
+
isSyncing = !1, state.emails = loadEmails(), state.categories = loadCategories(), renderEmailList();
|
|
354
|
+
});
|
|
355
|
+
renderEmailList(), renderer.watchResize(renderEmailList);
|
|
356
|
+
for await (let key of readKeys()) {
|
|
357
|
+
if (key === "q" || key === "ctrl-c" /* CTRL_C */)
|
|
358
|
+
return renderer.cleanup(), { action: "quit" };
|
|
359
|
+
if (key === "c")
|
|
360
|
+
return { action: "compose" };
|
|
361
|
+
if (key === "r") {
|
|
362
|
+
if (syncCallback && !isSyncing)
|
|
363
|
+
isSyncing = !0, syncCallback().then(() => {
|
|
364
|
+
isSyncing = !1, state.emails = loadEmails(), state.categories = loadCategories(), renderEmailList();
|
|
365
|
+
});
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (key === "m")
|
|
369
|
+
return { action: "switch" };
|
|
370
|
+
if (key === "s" && modifyEmail && state.emails.length > 0 && state.selectedEmailId) {
|
|
371
|
+
let idx = state.emails.findIndex((e) => e.id === state.selectedEmailId);
|
|
372
|
+
if (idx !== -1) {
|
|
373
|
+
let email = state.emails[idx], newStarred = !email.isStarred;
|
|
374
|
+
db.run("UPDATE emails SET isStarred = ? WHERE id = ?", [newStarred ? 1 : 0, email.id]), state.emails[idx].isStarred = newStarred, modifyEmail({ id: email.id, mailboxId, isStarred: newStarred });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (key === "u" && modifyEmail && state.emails.length > 0 && state.selectedEmailId) {
|
|
378
|
+
let idx = state.emails.findIndex((e) => e.id === state.selectedEmailId);
|
|
379
|
+
if (idx !== -1) {
|
|
380
|
+
let email = state.emails[idx], newRead = !email.isRead;
|
|
381
|
+
db.run("UPDATE emails SET isRead = ? WHERE id = ?", [newRead ? 1 : 0, email.id]), state.emails[idx].isRead = newRead, modifyEmail({ id: email.id, mailboxId, isRead: newRead });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
let FAST_SCROLL_AMOUNT = 5;
|
|
385
|
+
if (key === "up" /* UP */ && state.emails.length > 0 && state.selectedEmailId) {
|
|
386
|
+
let idx = state.emails.findIndex((e) => e.id === state.selectedEmailId);
|
|
387
|
+
if (idx > 0) {
|
|
388
|
+
state.selectedEmailId = state.emails[idx - 1].id;
|
|
389
|
+
let { height } = renderer.getSize(), listHeight = height - 4;
|
|
390
|
+
if (idx - 1 < state.scrollOffset + 1 && state.scrollOffset > 0)
|
|
391
|
+
state.scrollOffset = Math.max(0, idx - 1 - 1);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (key === "ctrl-up" /* CTRL_UP */ && state.emails.length > 0 && state.selectedEmailId) {
|
|
395
|
+
let idx = state.emails.findIndex((e) => e.id === state.selectedEmailId);
|
|
396
|
+
if (idx > 0) {
|
|
397
|
+
let newIdx = Math.max(0, idx - FAST_SCROLL_AMOUNT);
|
|
398
|
+
state.selectedEmailId = state.emails[newIdx].id;
|
|
399
|
+
let { height } = renderer.getSize(), listHeight = height - 4;
|
|
400
|
+
state.scrollOffset = Math.max(0, newIdx - Math.floor(listHeight / 2));
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (key === "down" /* DOWN */ && state.emails.length > 0 && state.selectedEmailId) {
|
|
404
|
+
let idx = state.emails.findIndex((e) => e.id === state.selectedEmailId);
|
|
405
|
+
if (idx < state.emails.length - 1) {
|
|
406
|
+
state.selectedEmailId = state.emails[idx + 1].id;
|
|
407
|
+
let { height } = renderer.getSize(), listHeight = height - 4;
|
|
408
|
+
if (idx + 1 >= state.scrollOffset + listHeight - 1)
|
|
409
|
+
state.scrollOffset = Math.min(state.emails.length - listHeight, idx + 1 - listHeight + 2);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (key === "ctrl-down" /* CTRL_DOWN */ && state.emails.length > 0 && state.selectedEmailId) {
|
|
413
|
+
let idx = state.emails.findIndex((e) => e.id === state.selectedEmailId);
|
|
414
|
+
if (idx < state.emails.length - 1) {
|
|
415
|
+
let newIdx = Math.min(state.emails.length - 1, idx + FAST_SCROLL_AMOUNT);
|
|
416
|
+
state.selectedEmailId = state.emails[newIdx].id;
|
|
417
|
+
let { height } = renderer.getSize(), listHeight = height - 4;
|
|
418
|
+
state.scrollOffset = Math.max(0, Math.min(state.emails.length - listHeight, newIdx - Math.floor(listHeight / 2)));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (key === "pageup" /* PAGEUP */ && state.emails.length > 0 && state.selectedEmailId) {
|
|
422
|
+
let idx = state.emails.findIndex((e) => e.id === state.selectedEmailId);
|
|
423
|
+
if (idx > 0) {
|
|
424
|
+
let { height } = renderer.getSize(), listHeight = height - 4, newIdx = Math.max(0, idx - listHeight);
|
|
425
|
+
state.selectedEmailId = state.emails[newIdx].id, state.scrollOffset = Math.max(0, state.scrollOffset - listHeight);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (key === "pagedown" /* PAGEDOWN */ && state.emails.length > 0 && state.selectedEmailId) {
|
|
429
|
+
let idx = state.emails.findIndex((e) => e.id === state.selectedEmailId);
|
|
430
|
+
if (idx < state.emails.length - 1) {
|
|
431
|
+
let { height } = renderer.getSize(), listHeight = height - 4, newIdx = Math.min(state.emails.length - 1, idx + listHeight);
|
|
432
|
+
state.selectedEmailId = state.emails[newIdx].id;
|
|
433
|
+
let maxScroll = Math.max(0, state.emails.length - listHeight);
|
|
434
|
+
state.scrollOffset = Math.min(maxScroll, state.scrollOffset + listHeight);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (key === "home" /* HOME */ && state.emails.length > 0)
|
|
438
|
+
state.selectedEmailId = state.emails[0].id, state.scrollOffset = 0;
|
|
439
|
+
if (key === "end" /* END */ && state.emails.length > 0) {
|
|
440
|
+
state.selectedEmailId = state.emails[state.emails.length - 1].id;
|
|
441
|
+
let { height } = renderer.getSize(), listHeight = height - 4;
|
|
442
|
+
state.scrollOffset = Math.max(0, state.emails.length - listHeight);
|
|
443
|
+
}
|
|
444
|
+
if (key === "enter" /* ENTER */ && state.emails.length > 0 && state.selectedEmailId)
|
|
445
|
+
return {
|
|
446
|
+
action: "view",
|
|
447
|
+
emailId: state.selectedEmailId,
|
|
448
|
+
emailIds: state.emails.map((e) => e.id)
|
|
449
|
+
};
|
|
450
|
+
renderEmailList();
|
|
451
|
+
}
|
|
452
|
+
throw Error("Unexpected end of input");
|
|
453
|
+
} finally {
|
|
454
|
+
clearInterval(animInterval), renderer.cleanup();
|
|
455
|
+
}
|
|
456
|
+
} catch (_catch) {
|
|
457
|
+
var _err = _catch, _hasErr = 1;
|
|
458
|
+
} finally {
|
|
459
|
+
__callDispose(__stack, _err, _hasErr);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/ui/markdown-highlight.ts
|
|
464
|
+
function markdownHighlight(text) {
|
|
465
|
+
let dimWrapper = (text2) => colors.dim + text2 + colors.reset, linkAnsi = (text2, href) => `\x1B]8;;${href}\x1B\\${text2}\x1B]8;;\x1B\\`;
|
|
466
|
+
return Bun.markdown.render(text, {
|
|
467
|
+
heading: (children, { level }) => `${"\x1B[1m" /* BRIGHT */}${"#".repeat(level)} ${children.replaceAll("\x1B[0m" /* RESET */, "\x1B[0m" /* RESET */ + "\x1B[1m" /* BRIGHT */)}${"\x1B[0m" /* RESET */}
|
|
468
|
+
|
|
469
|
+
`,
|
|
470
|
+
paragraph: (children) => children + `
|
|
471
|
+
|
|
472
|
+
`,
|
|
473
|
+
strong: (children) => `${"\x1B[1m" /* BRIGHT */}${children}${"\x1B[0m" /* RESET */}`,
|
|
474
|
+
emphasis: (children) => `${"\x1B[3m" /* ITALIC */}${children}${"\x1B[0m" /* RESET */}`,
|
|
475
|
+
strikethrough: (children) => `${"\x1B[9m" /* STRIKETHROUGH */}${children}${"\x1B[0m" /* RESET */}`,
|
|
476
|
+
link: (children, { href, title }) => `${"\x1B[34m" /* BLUE */}${linkAnsi(children || "", href)}${"\x1B[0m" /* RESET */}`,
|
|
477
|
+
blockquote: (children) => {
|
|
478
|
+
return children.trimEnd().split(`
|
|
479
|
+
`).map((line) => colors.fg.blue + "> " + line.replaceAll("\x1B[0m" /* RESET */, "\x1B[0m" /* RESET */ + colors.fg.blue) + colors.reset).join(`
|
|
480
|
+
`) + `
|
|
481
|
+
|
|
482
|
+
`;
|
|
483
|
+
},
|
|
484
|
+
code: (children, meta) => dimWrapper("```" + (meta?.language || "") + `
|
|
485
|
+
`) + children.split(`
|
|
486
|
+
`).map((line) => colors.fg.cyan + line.replaceAll("\x1B[0m" /* RESET */, "\x1B[0m" /* RESET */ + colors.fg.cyan)).join(`
|
|
487
|
+
`) + colors.reset + dimWrapper("```\n\n"),
|
|
488
|
+
codespan: (children) => dimWrapper("`") + colors.fg.cyan + children + colors.reset + dimWrapper("`"),
|
|
489
|
+
hr: () => dimWrapper(`---
|
|
490
|
+
|
|
491
|
+
`),
|
|
492
|
+
image: (children, { src, title }) => `${"\x1B[34m" /* BLUE */}${linkAnsi((children || src) + ` ${colors.dim}(image)`, src)}${colors.reset}`,
|
|
493
|
+
list: (children, { ordered, start }) => children.split(`
|
|
494
|
+
`).slice(0, -1).map((line, idx) => {
|
|
495
|
+
if (line.startsWith("- ")) {
|
|
496
|
+
let bullet = ordered ? `${start + idx}.` : "\u2022";
|
|
497
|
+
return `${colors.dim}${bullet}${colors.reset} ${line.slice(2)}`;
|
|
498
|
+
} else
|
|
499
|
+
return (ordered ? " " : " ") + line;
|
|
500
|
+
}).join(`
|
|
501
|
+
`) + `
|
|
502
|
+
|
|
503
|
+
`,
|
|
504
|
+
listItem: (children, meta) => {
|
|
505
|
+
return `${meta?.checked === !0 ? "[x]" : meta?.checked === !1 ? "[ ]" : "-"} ${children}
|
|
506
|
+
`;
|
|
507
|
+
},
|
|
508
|
+
text: (children) => children,
|
|
509
|
+
html: (children) => colors.dim + children + colors.reset
|
|
510
|
+
}, {
|
|
511
|
+
autolinks: !0,
|
|
512
|
+
hardSoftBreaks: !0,
|
|
513
|
+
tables: !1
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/ui/email-view.ts
|
|
518
|
+
async function emailViewScreen(db, mailboxId, emailId, modifyEmail) {
|
|
519
|
+
let __stack = [];
|
|
520
|
+
try {
|
|
521
|
+
const renderer = __using(__stack, new TerminalRenderer, 0);
|
|
522
|
+
let _rawEmail = db.query("SELECT * FROM emails WHERE id = ? AND mailboxId = ?").get(emailId, mailboxId);
|
|
523
|
+
let email = _rawEmail && {
|
|
524
|
+
..._rawEmail,
|
|
525
|
+
isRead: _rawEmail.isRead === 1,
|
|
526
|
+
isStarred: _rawEmail.isStarred === 1
|
|
527
|
+
};
|
|
528
|
+
if (!email)
|
|
529
|
+
return renderer.cleanup(), "back";
|
|
530
|
+
if (!email.isRead)
|
|
531
|
+
db.run("UPDATE emails SET isRead = 1 WHERE id = ?", [emailId]);
|
|
532
|
+
let scrollOffset = 0;
|
|
533
|
+
let bodyText = email.body || email.html || "(empty)";
|
|
534
|
+
let bodyMarkdowned = markdownHighlight(bodyText);
|
|
535
|
+
let getBodyLines = (width) => Bun.wrapAnsi(bodyMarkdowned, width - 2, { hard: !0, ambiguousIsNarrow: !1, trim: !1 }).split(`
|
|
536
|
+
`);
|
|
537
|
+
let renderEmail = () => {
|
|
538
|
+
let { width, height } = renderer.getSize(), lines = [];
|
|
539
|
+
lines.push(colors.bright + (email.subject || "(no subject)") + colors.reset), lines.push(" ".repeat(width)), lines.push(colors.dim + "From: " + colors.reset + email.from_addr), lines.push(colors.dim + "To: " + colors.reset + email.to_addr), lines.push(colors.dim + "Date: " + colors.reset + new Date(email.createdAt).toLocaleString()), lines.push(colors.dim + "\u2500".repeat(Math.min(width, 60)) + colors.reset);
|
|
540
|
+
let bodyLines = getBodyLines(width), viewHeight = height - 8, visibleStart = scrollOffset, visibleEnd = Math.min(scrollOffset + viewHeight, bodyLines.length), showScrollbar = bodyLines.length > viewHeight || scrollOffset > 0, scrollbarHeight = viewHeight, scrollbarThumbSize = Math.max(1, Math.floor(viewHeight / bodyLines.length * scrollbarHeight)), scrollbarThumbPosition = Math.floor(scrollOffset / bodyLines.length * scrollbarHeight);
|
|
541
|
+
for (let i = visibleStart;i < visibleEnd; i++) {
|
|
542
|
+
let line = bodyLines[i] || "", lineIndex = i - visibleStart, scrollbarChar = " ";
|
|
543
|
+
if (showScrollbar)
|
|
544
|
+
if (lineIndex >= scrollbarThumbPosition && lineIndex < scrollbarThumbPosition + scrollbarThumbSize)
|
|
545
|
+
scrollbarChar += colors.reset + colors.dim + "\u2588" + colors.reset;
|
|
546
|
+
else
|
|
547
|
+
scrollbarChar += colors.reset + colors.dim + "\u2502" + colors.reset;
|
|
548
|
+
let plainLength = Bun.stringWidth(line, { ambiguousIsNarrow: !1, countAnsiEscapeCodes: !1 }), padding = " ".repeat(Math.max(0, width - 2 - plainLength));
|
|
549
|
+
lines.push(line + padding + scrollbarChar);
|
|
550
|
+
}
|
|
551
|
+
while (lines.length < height - 1)
|
|
552
|
+
lines.push(" ".repeat(width));
|
|
553
|
+
let starStatus = email.isStarred ? "Unstar" : "Star", readStatus = email.isRead ? "Mark Unread (u)" : "Mark Read (u)", statusBarText = `\u2191/\u2193: Scroll | \u2190/\u2192: Prev/Next | Enter\xD72: Browser | s: ${starStatus} | u: ${readStatus} | Esc: Back | q: Quit`, statusBarTruncated = Bun.stringWidth(statusBarText, { ambiguousIsNarrow: !1, countAnsiEscapeCodes: !1 }) > width ? statusBarText.substring(0, width - 1) + "\u2026" : statusBarText, statusBar = colors.dim + statusBarTruncated.replace("Esc: Back", colors.bright + colors.fg.cyan + "Esc: Back" + colors.reset + colors.dim);
|
|
554
|
+
colors.reset, lines.push(statusBar), renderer.render({ lines });
|
|
555
|
+
};
|
|
556
|
+
try {
|
|
557
|
+
renderEmail(), renderer.watchResize(renderEmail);
|
|
558
|
+
let DOUBLE_PRESS_THRESHOLD_MS = 500, lastEnterTime = 0;
|
|
559
|
+
for await (let key of readKeys()) {
|
|
560
|
+
if (key === "q" || key === "ctrl-c" /* CTRL_C */)
|
|
561
|
+
return renderer.cleanup(), "quit";
|
|
562
|
+
if (key === "escape" /* ESCAPE */ || key === "backspace" /* BACKSPACE */)
|
|
563
|
+
return "back";
|
|
564
|
+
if (key === "enter" /* ENTER */) {
|
|
565
|
+
let now = Date.now();
|
|
566
|
+
if (now - lastEnterTime < DOUBLE_PRESS_THRESHOLD_MS) {
|
|
567
|
+
let url = `https://emailthing.app/mail/${mailboxId}/${emailId}`, platform = process.platform, openCmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
568
|
+
try {
|
|
569
|
+
Bun.spawn([openCmd, url], { stdout: "inherit", stderr: "inherit" });
|
|
570
|
+
} catch (error) {}
|
|
571
|
+
lastEnterTime = 0;
|
|
572
|
+
} else
|
|
573
|
+
lastEnterTime = now;
|
|
574
|
+
}
|
|
575
|
+
if (key === "left" /* LEFT */)
|
|
576
|
+
return "prev";
|
|
577
|
+
if (key === "right" /* RIGHT */)
|
|
578
|
+
return "next";
|
|
579
|
+
if (key === "s" && modifyEmail) {
|
|
580
|
+
let newStarred = !email.isStarred;
|
|
581
|
+
email.isStarred = newStarred, db.run("UPDATE emails SET isStarred = ? WHERE id = ?", [newStarred ? 1 : 0, emailId]), modifyEmail({ id: emailId, mailboxId, isStarred: newStarred });
|
|
582
|
+
}
|
|
583
|
+
if (key === "u" && modifyEmail) {
|
|
584
|
+
let newRead = !email.isRead;
|
|
585
|
+
email.isRead = newRead, db.run("UPDATE emails SET isRead = ? WHERE id = ?", [newRead ? 1 : 0, emailId]), modifyEmail({ id: emailId, mailboxId, isRead: newRead });
|
|
586
|
+
}
|
|
587
|
+
let FAST_SCROLL_AMOUNT = 5;
|
|
588
|
+
if (key === "up" /* UP */ && scrollOffset > 0)
|
|
589
|
+
scrollOffset--;
|
|
590
|
+
if (key === "ctrl-up" /* CTRL_UP */ && scrollOffset > 0)
|
|
591
|
+
scrollOffset = Math.max(0, scrollOffset - FAST_SCROLL_AMOUNT);
|
|
592
|
+
if (key === "down" /* DOWN */) {
|
|
593
|
+
let { width, height } = renderer.getSize(), bodyLines = getBodyLines(width), maxScroll = Math.max(0, bodyLines.length - (height - 9));
|
|
594
|
+
if (scrollOffset < maxScroll)
|
|
595
|
+
scrollOffset++;
|
|
596
|
+
}
|
|
597
|
+
if (key === "ctrl-down" /* CTRL_DOWN */) {
|
|
598
|
+
let { width, height } = renderer.getSize(), bodyLines = getBodyLines(width), maxScroll = Math.max(0, bodyLines.length - (height - 9));
|
|
599
|
+
scrollOffset = Math.min(maxScroll, scrollOffset + FAST_SCROLL_AMOUNT);
|
|
600
|
+
}
|
|
601
|
+
if (key === "pageup" /* PAGEUP */) {
|
|
602
|
+
let { width, height } = renderer.getSize(), pageSize = height - 9;
|
|
603
|
+
scrollOffset = Math.max(0, scrollOffset - pageSize);
|
|
604
|
+
}
|
|
605
|
+
if (key === "pagedown" /* PAGEDOWN */) {
|
|
606
|
+
let { width, height } = renderer.getSize(), bodyLines = getBodyLines(width), pageSize = height - 9, maxScroll = Math.max(0, bodyLines.length - pageSize);
|
|
607
|
+
scrollOffset = Math.min(maxScroll, scrollOffset + pageSize);
|
|
608
|
+
}
|
|
609
|
+
if (key === "home" /* HOME */)
|
|
610
|
+
scrollOffset = 0;
|
|
611
|
+
if (key === "end" /* END */) {
|
|
612
|
+
let { width, height } = renderer.getSize(), bodyLines = getBodyLines(width);
|
|
613
|
+
scrollOffset = Math.max(0, bodyLines.length - (height - 9));
|
|
614
|
+
}
|
|
615
|
+
renderEmail();
|
|
616
|
+
}
|
|
617
|
+
throw Error("Unexpected end of input");
|
|
618
|
+
} finally {
|
|
619
|
+
renderer.cleanup();
|
|
620
|
+
}
|
|
621
|
+
} catch (_catch) {
|
|
622
|
+
var _err = _catch, _hasErr = 1;
|
|
623
|
+
} finally {
|
|
624
|
+
__callDispose(__stack, _err, _hasErr);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/ui/compose.ts
|
|
629
|
+
async function composeScreen() {
|
|
630
|
+
let __stack = [];
|
|
631
|
+
try {
|
|
632
|
+
const renderer = __using(__stack, new TerminalRenderer, 0);
|
|
633
|
+
let state = {
|
|
634
|
+
to: "",
|
|
635
|
+
subject: "",
|
|
636
|
+
body: "",
|
|
637
|
+
focusedField: "to",
|
|
638
|
+
bodyLines: [""],
|
|
639
|
+
bodyCursor: 0,
|
|
640
|
+
bodyCol: 0,
|
|
641
|
+
bodyScrollOffset: 0
|
|
642
|
+
};
|
|
643
|
+
let FAST_SCROLL_AMOUNT = 5;
|
|
644
|
+
let renderCompose = () => {
|
|
645
|
+
let { width, height } = renderer.getSize(), lines = [];
|
|
646
|
+
lines.push(colors.bright + "Compose Email" + colors.reset), lines.push("\u2500".repeat(width));
|
|
647
|
+
let prefixWidth = 2, scrollbarWidth = 2, bodyUsableWidth = width - prefixWidth - scrollbarWidth, visualLines = [];
|
|
648
|
+
for (let i = 0;i < state.bodyLines.length; ++i) {
|
|
649
|
+
let line = state.bodyLines[i] || "", wrapped = Bun.wrapAnsi(line, bodyUsableWidth, { hard: !0, ambiguousIsNarrow: !1, trim: !1 }).split(`
|
|
650
|
+
`), offset = 0;
|
|
651
|
+
for (let r = 0;r < wrapped.length; ++r) {
|
|
652
|
+
let visualText = wrapped[r], charsStart = offset, charsEnd = offset + visualText.length;
|
|
653
|
+
visualLines.push({ logicalLine: i, wrapRow: r, charsStart, charsEnd, visualText }), offset = charsEnd;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
let toFocused = state.focusedField === "to", subjectFocused = state.focusedField === "subject", bodyFocused = state.focusedField === "body", sendFocused = state.focusedField === "send", cancelFocused = state.focusedField === "cancel", toColor = toFocused ? colors.fg.cyan : colors.fg.white, subjectColor = subjectFocused ? colors.fg.cyan : colors.fg.white, bodyColor = bodyFocused ? colors.fg.cyan : colors.fg.white, toLineIdx = lines.length;
|
|
657
|
+
lines.push(toColor + "To: " + colors.reset + (toFocused ? colors.bg.white + colors.fg.black : "") + state.to + colors.reset);
|
|
658
|
+
let subjectLineIdx = lines.length;
|
|
659
|
+
lines.push(subjectColor + "Subject: " + colors.reset + (subjectFocused ? colors.bg.white + colors.fg.black : "") + state.subject + colors.reset), lines.push("\u2500".repeat(width)), lines.push(bodyColor + "Body:" + colors.reset);
|
|
660
|
+
let bodyStartIdx = lines.length, bodyHeight = height - lines.length - 3, cursorVisualRow = 0, cursorVisualCol = 0;
|
|
661
|
+
for (let vi = 0;vi < visualLines.length; ++vi) {
|
|
662
|
+
let meta = visualLines[vi];
|
|
663
|
+
if (meta.logicalLine === state.bodyCursor && state.bodyCol >= meta.charsStart && state.bodyCol <= meta.charsEnd) {
|
|
664
|
+
cursorVisualRow = vi, cursorVisualCol = state.bodyCol - meta.charsStart;
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
let maxScroll = Math.max(0, visualLines.length - bodyHeight), bodyScrollVisualOffset = Math.max(0, Math.min(cursorVisualRow - Math.floor(bodyHeight / 2), maxScroll)), showScrollbar = visualLines.length > bodyHeight || bodyScrollVisualOffset > 0, scrollbarHeight = bodyHeight, scrollbarThumbSize = Math.max(1, Math.floor(bodyHeight / visualLines.length * scrollbarHeight)), availableScrollSpace = scrollbarHeight - scrollbarThumbSize, scrollbarThumbPosition = maxScroll === 0 ? 0 : Math.round(bodyScrollVisualOffset / maxScroll * availableScrollSpace);
|
|
669
|
+
for (let i = 0;i < bodyHeight; i++) {
|
|
670
|
+
let visIdx = bodyScrollVisualOffset + i;
|
|
671
|
+
if (visIdx >= visualLines.length) {
|
|
672
|
+
lines.push(" ".repeat(width));
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
let meta = visualLines[visIdx], scrollbarChar = " ";
|
|
676
|
+
if (showScrollbar)
|
|
677
|
+
if (i >= scrollbarThumbPosition && i < scrollbarThumbPosition + scrollbarThumbSize)
|
|
678
|
+
scrollbarChar += colors.reset + colors.dim + "\u2588" + colors.reset;
|
|
679
|
+
else
|
|
680
|
+
scrollbarChar += colors.reset + colors.dim + "\u2502" + colors.reset;
|
|
681
|
+
let padded = meta.visualText + " ".repeat(bodyUsableWidth - Bun.stringWidth(meta.visualText)), prefix = meta.wrapRow === 0 && meta.logicalLine === state.bodyCursor ? "> " : " ";
|
|
682
|
+
lines.push(prefix + padded + scrollbarChar);
|
|
683
|
+
}
|
|
684
|
+
lines.push("\u2500".repeat(width));
|
|
685
|
+
let sendBtn = sendFocused ? colors.bg.green + colors.fg.black + " Send " + colors.reset : " Send ", cancelBtn = cancelFocused ? colors.bg.red + colors.fg.black + " Cancel " + colors.reset : " Cancel ";
|
|
686
|
+
lines.push(sendBtn + " " + cancelBtn), lines.push(colors.dim + "Tab: Next | Ctrl+C: Cancel" + colors.reset);
|
|
687
|
+
let cursor = void 0;
|
|
688
|
+
if (toFocused)
|
|
689
|
+
cursor = { row: toLineIdx, col: 4 + state.to.length };
|
|
690
|
+
else if (subjectFocused)
|
|
691
|
+
cursor = { row: subjectLineIdx, col: 9 + state.subject.length };
|
|
692
|
+
else if (bodyFocused)
|
|
693
|
+
cursor = {
|
|
694
|
+
row: bodyStartIdx + (cursorVisualRow - bodyScrollVisualOffset),
|
|
695
|
+
col: 2 + cursorVisualCol
|
|
696
|
+
};
|
|
697
|
+
renderer.render({ lines, cursor });
|
|
698
|
+
};
|
|
699
|
+
function ensureBodyCursorVisible() {
|
|
700
|
+
let { height } = renderer.getSize(), bodyHeight = height - 11;
|
|
701
|
+
if (state.bodyCursor < state.bodyScrollOffset)
|
|
702
|
+
state.bodyScrollOffset = state.bodyCursor;
|
|
703
|
+
else if (state.bodyCursor >= state.bodyScrollOffset + bodyHeight)
|
|
704
|
+
state.bodyScrollOffset = state.bodyCursor - bodyHeight + 1;
|
|
705
|
+
let maxScroll = Math.max(0, state.bodyLines.length - bodyHeight);
|
|
706
|
+
state.bodyScrollOffset = Math.max(0, Math.min(state.bodyScrollOffset, maxScroll));
|
|
707
|
+
}
|
|
708
|
+
try {
|
|
709
|
+
renderCompose(), renderer.watchResize(renderCompose);
|
|
710
|
+
for await (let key of readKeys()) {
|
|
711
|
+
if (key === "ctrl-c" /* CTRL_C */)
|
|
712
|
+
return renderer.cleanup(), null;
|
|
713
|
+
else if (key === "tab" /* TAB */) {
|
|
714
|
+
let fields = ["to", "subject", "body", "send", "cancel"], currentIndex = fields.indexOf(state.focusedField);
|
|
715
|
+
state.focusedField = fields[(currentIndex + 1) % fields.length];
|
|
716
|
+
} else if (key === "backtab" /* BACKTAB */) {
|
|
717
|
+
let fields = ["to", "subject", "body", "send", "cancel"], currentIndex = fields.indexOf(state.focusedField);
|
|
718
|
+
state.focusedField = fields[(currentIndex - 1 + fields.length) % fields.length];
|
|
719
|
+
} else if (key === "enter" /* ENTER */) {
|
|
720
|
+
if (state.focusedField === "send")
|
|
721
|
+
return state.body = state.bodyLines.join(`
|
|
722
|
+
`), {
|
|
723
|
+
to: state.to,
|
|
724
|
+
subject: state.subject,
|
|
725
|
+
body: state.body
|
|
726
|
+
};
|
|
727
|
+
else if (state.focusedField === "cancel")
|
|
728
|
+
return null;
|
|
729
|
+
else if (state.focusedField === "to")
|
|
730
|
+
state.focusedField = "subject";
|
|
731
|
+
else if (state.focusedField === "subject")
|
|
732
|
+
state.focusedField = "body";
|
|
733
|
+
else if (state.focusedField === "body") {
|
|
734
|
+
let line = state.bodyLines[state.bodyCursor] || "", left = line.slice(0, state.bodyCol), right = line.slice(state.bodyCol);
|
|
735
|
+
state.bodyLines[state.bodyCursor] = left, state.bodyLines.splice(state.bodyCursor + 1, 0, right), state.bodyCursor++, state.bodyCol = 0, ensureBodyCursorVisible();
|
|
736
|
+
}
|
|
737
|
+
} else if (key === "backspace" /* BACKSPACE */) {
|
|
738
|
+
if (state.focusedField === "to" && state.to.length > 0)
|
|
739
|
+
state.to = state.to.slice(0, -1);
|
|
740
|
+
else if (state.focusedField === "subject" && state.subject.length > 0)
|
|
741
|
+
state.subject = state.subject.slice(0, -1);
|
|
742
|
+
else if (state.focusedField === "body") {
|
|
743
|
+
let currentLine = state.bodyLines[state.bodyCursor] || "";
|
|
744
|
+
if (state.bodyCol > 0)
|
|
745
|
+
state.bodyLines[state.bodyCursor] = currentLine.slice(0, state.bodyCol - 1) + currentLine.slice(state.bodyCol), state.bodyCol--;
|
|
746
|
+
else if (state.bodyCursor > 0) {
|
|
747
|
+
let prev = state.bodyLines[state.bodyCursor - 1] || "";
|
|
748
|
+
state.bodyCol = prev.length, state.bodyLines[state.bodyCursor - 1] = prev + currentLine, state.bodyLines.splice(state.bodyCursor, 1), state.bodyCursor--, ensureBodyCursorVisible();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
} else if (key === "delete" /* DELETE */ && state.focusedField === "body") {
|
|
752
|
+
let currentLine = state.bodyLines[state.bodyCursor] || "";
|
|
753
|
+
if (state.bodyCol < currentLine.length)
|
|
754
|
+
state.bodyLines[state.bodyCursor] = currentLine.slice(0, state.bodyCol) + currentLine.slice(state.bodyCol + 1);
|
|
755
|
+
else if (state.bodyCursor < state.bodyLines.length - 1) {
|
|
756
|
+
let next = state.bodyLines[state.bodyCursor + 1] || "";
|
|
757
|
+
state.bodyLines[state.bodyCursor] = currentLine + next, state.bodyLines.splice(state.bodyCursor + 1, 1);
|
|
758
|
+
}
|
|
759
|
+
} else if ((key === "pageup" /* PAGEUP */ || key === "ctrl-up" /* CTRL_UP */) && state.focusedField === "body") {
|
|
760
|
+
state.bodyCursor = Math.max(0, state.bodyCursor - FAST_SCROLL_AMOUNT);
|
|
761
|
+
let newLine = state.bodyLines[state.bodyCursor] || "";
|
|
762
|
+
state.bodyCol = Math.min(state.bodyCol, newLine.length), ensureBodyCursorVisible();
|
|
763
|
+
} else if ((key === "pagedown" /* PAGEDOWN */ || key === "ctrl-down" /* CTRL_DOWN */) && state.focusedField === "body") {
|
|
764
|
+
state.bodyCursor = Math.min(state.bodyLines.length - 1, state.bodyCursor + FAST_SCROLL_AMOUNT);
|
|
765
|
+
let newLine = state.bodyLines[state.bodyCursor] || "";
|
|
766
|
+
state.bodyCol = Math.min(state.bodyCol, newLine.length), ensureBodyCursorVisible();
|
|
767
|
+
} else if (key === "home" /* HOME */ && state.focusedField === "body")
|
|
768
|
+
state.bodyCursor = 0, state.bodyCol = 0, ensureBodyCursorVisible();
|
|
769
|
+
else if (key === "end" /* END */ && state.focusedField === "body")
|
|
770
|
+
state.bodyCursor = state.bodyLines.length - 1, state.bodyCol = (state.bodyLines[state.bodyCursor] || "").length, ensureBodyCursorVisible();
|
|
771
|
+
else if (key === "up" /* UP */ && state.focusedField === "body") {
|
|
772
|
+
let { width } = renderer.getSize(), prefixWidth = 2, scrollbarWidth = 2, bodyUsableWidth = width - 2 - 2, visualLines = [];
|
|
773
|
+
for (let i = 0;i < state.bodyLines.length; ++i) {
|
|
774
|
+
let line = state.bodyLines[i] || "", wrapped = Bun.wrapAnsi(line, bodyUsableWidth, { hard: !0, ambiguousIsNarrow: !1, trim: !1 }).split(`
|
|
775
|
+
`), offset = 0;
|
|
776
|
+
for (let r = 0;r < wrapped.length; ++r) {
|
|
777
|
+
let visualText = wrapped[r], charsStart = offset, charsEnd = offset + visualText.length;
|
|
778
|
+
visualLines.push({ logicalLine: i, wrapRow: r, charsStart, charsEnd, visualText }), offset = charsEnd;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
let cursorVisualRow = 0, cursorVisualCol = 0;
|
|
782
|
+
for (let vi = 0;vi < visualLines.length; ++vi) {
|
|
783
|
+
let meta = visualLines[vi];
|
|
784
|
+
if (meta.logicalLine === state.bodyCursor && state.bodyCol >= meta.charsStart && state.bodyCol <= meta.charsEnd) {
|
|
785
|
+
cursorVisualRow = vi, cursorVisualCol = state.bodyCol - meta.charsStart;
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (cursorVisualRow === 0)
|
|
790
|
+
state.bodyCursor = 0, state.bodyCol = 0, ensureBodyCursorVisible();
|
|
791
|
+
else {
|
|
792
|
+
let prevVisual = visualLines[cursorVisualRow - 1];
|
|
793
|
+
state.bodyCursor = prevVisual.logicalLine, state.bodyCol = Math.min(prevVisual.charsStart + cursorVisualCol, prevVisual.charsEnd), ensureBodyCursorVisible();
|
|
794
|
+
}
|
|
795
|
+
} else if (key === "down" /* DOWN */ && state.focusedField === "body") {
|
|
796
|
+
let { width } = renderer.getSize(), prefixWidth = 2, scrollbarWidth = 2, bodyUsableWidth = width - 2 - 2, visualLines = [];
|
|
797
|
+
for (let i = 0;i < state.bodyLines.length; ++i) {
|
|
798
|
+
let line = state.bodyLines[i] || "", wrapped = Bun.wrapAnsi(line, bodyUsableWidth, { hard: !0, ambiguousIsNarrow: !1, trim: !1 }).split(`
|
|
799
|
+
`), offset = 0;
|
|
800
|
+
for (let r = 0;r < wrapped.length; ++r) {
|
|
801
|
+
let visualText = wrapped[r], charsStart = offset, charsEnd = offset + visualText.length;
|
|
802
|
+
visualLines.push({ logicalLine: i, wrapRow: r, charsStart, charsEnd, visualText }), offset = charsEnd;
|
|
803
|
+
}
|
|
804
|
+
if (line.length === 0)
|
|
805
|
+
visualLines.push({ logicalLine: i, wrapRow: 0, charsStart: 0, charsEnd: 0, visualText: "" });
|
|
806
|
+
}
|
|
807
|
+
let cursorVisualRow = 0, cursorVisualCol = 0;
|
|
808
|
+
for (let vi = 0;vi < visualLines.length; ++vi) {
|
|
809
|
+
let meta = visualLines[vi];
|
|
810
|
+
if (meta.logicalLine === state.bodyCursor && state.bodyCol >= meta.charsStart && state.bodyCol <= meta.charsEnd) {
|
|
811
|
+
cursorVisualRow = vi, cursorVisualCol = state.bodyCol - meta.charsStart;
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (state.bodyLines[state.bodyCursor].length === 0 && state.bodyCursor < state.bodyLines.length - 1)
|
|
816
|
+
state.bodyCursor++, state.bodyCol = 0, ensureBodyCursorVisible();
|
|
817
|
+
cursorVisualRow = -1, cursorVisualCol = 0;
|
|
818
|
+
for (let vi = 0;vi < visualLines.length; ++vi) {
|
|
819
|
+
let meta = visualLines[vi];
|
|
820
|
+
if (meta.logicalLine === state.bodyCursor && (meta.charsStart <= state.bodyCol && state.bodyCol <= meta.charsEnd || meta.charsStart === 0 && meta.charsEnd === 0)) {
|
|
821
|
+
cursorVisualRow = vi, cursorVisualCol = state.bodyCol - meta.charsStart;
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (cursorVisualRow === -1)
|
|
826
|
+
cursorVisualRow = visualLines.findIndex((meta) => meta.logicalLine === state.bodyCursor && meta.wrapRow === 0), cursorVisualCol = 0;
|
|
827
|
+
if (cursorVisualRow === visualLines.length - 1 || visualLines[cursorVisualRow + 1] && visualLines[cursorVisualRow + 1].logicalLine !== state.bodyCursor) {
|
|
828
|
+
if (state.bodyCursor < state.bodyLines.length - 1)
|
|
829
|
+
state.bodyCursor++, state.bodyCol = 0;
|
|
830
|
+
else
|
|
831
|
+
state.bodyCursor = state.bodyLines.length - 1, state.bodyCol = (state.bodyLines[state.bodyCursor] || "").length;
|
|
832
|
+
ensureBodyCursorVisible();
|
|
833
|
+
} else {
|
|
834
|
+
let nextVisual = visualLines[cursorVisualRow + 1];
|
|
835
|
+
state.bodyCursor = nextVisual.logicalLine, state.bodyCol = Math.min(nextVisual.charsStart + cursorVisualCol, nextVisual.charsEnd), ensureBodyCursorVisible();
|
|
836
|
+
}
|
|
837
|
+
} else if (key === "left" /* LEFT */ && state.focusedField === "body") {
|
|
838
|
+
if (state.bodyCol > 0)
|
|
839
|
+
state.bodyCol--;
|
|
840
|
+
else if (state.bodyCursor > 0) {
|
|
841
|
+
state.bodyCursor--;
|
|
842
|
+
let newLine = state.bodyLines[state.bodyCursor] || "";
|
|
843
|
+
state.bodyCol = newLine.length, ensureBodyCursorVisible();
|
|
844
|
+
}
|
|
845
|
+
} else if (key === "right" /* RIGHT */ && state.focusedField === "body") {
|
|
846
|
+
let curLine = state.bodyLines[state.bodyCursor] || "";
|
|
847
|
+
if (state.bodyCol < curLine.length)
|
|
848
|
+
state.bodyCol++;
|
|
849
|
+
else if (state.bodyCursor < state.bodyLines.length - 1)
|
|
850
|
+
state.bodyCursor++, state.bodyCol = 0, ensureBodyCursorVisible();
|
|
851
|
+
} else if (key.length >= 1 && !defaultKeys.includes(key)) {
|
|
852
|
+
if (state.focusedField === "to")
|
|
853
|
+
state.to += key;
|
|
854
|
+
else if (state.focusedField === "subject")
|
|
855
|
+
state.subject += key;
|
|
856
|
+
else if (state.focusedField === "body") {
|
|
857
|
+
let line = state.bodyLines[state.bodyCursor] || "";
|
|
858
|
+
state.bodyLines[state.bodyCursor] = line.slice(0, state.bodyCol) + key + line.slice(state.bodyCol), state.bodyCol += key.length, ensureBodyCursorVisible();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
renderCompose();
|
|
862
|
+
}
|
|
863
|
+
throw Error("Unexpected end of input");
|
|
864
|
+
} finally {
|
|
865
|
+
renderer.cleanup();
|
|
866
|
+
}
|
|
867
|
+
} catch (_catch) {
|
|
868
|
+
var _err = _catch, _hasErr = 1;
|
|
869
|
+
} finally {
|
|
870
|
+
__callDispose(__stack, _err, _hasErr);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/ui/mailbox-switcher.ts
|
|
875
|
+
async function mailboxSwitcher(db, currentMailboxId) {
|
|
876
|
+
let __stack = [];
|
|
877
|
+
try {
|
|
878
|
+
const renderer = __using(__stack, new TerminalRenderer, 0);
|
|
879
|
+
let mailboxes = db.query(`
|
|
880
|
+
SELECT mailboxId as id, mailbox_aliases.alias AS default_alias
|
|
881
|
+
FROM mailboxes
|
|
882
|
+
INNER JOIN mailbox_aliases
|
|
883
|
+
ON mailboxes.id = mailbox_aliases.mailboxId AND mailbox_aliases.\`default\` = true AND mailbox_aliases.isDeleted = false
|
|
884
|
+
WHERE mailboxes.isDeleted = false
|
|
885
|
+
`).all();
|
|
886
|
+
let options = [
|
|
887
|
+
...mailboxes,
|
|
888
|
+
{ id: "switch-user", default_alias: "(switch user)" }
|
|
889
|
+
];
|
|
890
|
+
let selectedIndex = options.findIndex((m) => m.id === currentMailboxId);
|
|
891
|
+
if (selectedIndex === -1)
|
|
892
|
+
selectedIndex = 0;
|
|
893
|
+
let renderMailboxes = () => {
|
|
894
|
+
let { width, height } = renderer.getSize(), lines = [];
|
|
895
|
+
lines.push(colors.bright + "Switch Mailbox" + colors.reset), lines.push("\u2500".repeat(width));
|
|
896
|
+
for (let i = 0;i < options.length; i++) {
|
|
897
|
+
let mailbox = options[i], isSelected = i === selectedIndex, isCurrent = mailbox.id === currentMailboxId, isSwitchUser = mailbox.id === "switch-user", marker = !isSwitchUser && isCurrent ? "\u25CF " : " ", bg = isSelected ? colors.bg.blue : "", fg = !isSwitchUser && isCurrent ? colors.bright : "";
|
|
898
|
+
lines.push(bg + fg + marker + mailbox.default_alias + colors.reset);
|
|
899
|
+
}
|
|
900
|
+
while (lines.length < height - 2)
|
|
901
|
+
lines.push("");
|
|
902
|
+
lines.push("\u2500".repeat(width)), lines.push(colors.dim + "Enter: Select | Esc: Cancel | q: Quit" + colors.reset), renderer.render({ lines });
|
|
903
|
+
};
|
|
904
|
+
try {
|
|
905
|
+
renderMailboxes(), renderer.watchResize(renderMailboxes);
|
|
906
|
+
for await (let key of readKeys()) {
|
|
907
|
+
if (key === "escape" /* ESCAPE */ || key === "q" || key === "ctrl-c" /* CTRL_C */) {
|
|
908
|
+
if (key === "ctrl-c" /* CTRL_C */)
|
|
909
|
+
renderer.cleanup();
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
if (key === "up" /* UP */ && selectedIndex > 0)
|
|
913
|
+
selectedIndex--;
|
|
914
|
+
if (key === "down" /* DOWN */ && selectedIndex < options.length - 1)
|
|
915
|
+
selectedIndex++;
|
|
916
|
+
if (key === "enter" /* ENTER */)
|
|
917
|
+
return options[selectedIndex].id;
|
|
918
|
+
renderMailboxes();
|
|
919
|
+
}
|
|
920
|
+
throw Error("Unexpected end of input");
|
|
921
|
+
} finally {
|
|
922
|
+
renderer.cleanup();
|
|
923
|
+
}
|
|
924
|
+
} catch (_catch) {
|
|
925
|
+
var _err = _catch, _hasErr = 1;
|
|
926
|
+
} finally {
|
|
927
|
+
__callDispose(__stack, _err, _hasErr);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/main.ts
|
|
932
|
+
var usingProcess = (key, fn) => {
|
|
933
|
+
return process.on(key, fn), {
|
|
934
|
+
[Symbol.dispose]() {
|
|
935
|
+
process.off(key, fn);
|
|
936
|
+
},
|
|
937
|
+
getValue() {}
|
|
938
|
+
};
|
|
939
|
+
};
|
|
940
|
+
if (globalThis._cli_route_cache)
|
|
941
|
+
process.removeAllListeners("SIGINT"), process.stdin.removeAllListeners("data");
|
|
942
|
+
async function main() {
|
|
943
|
+
let __stack = [];
|
|
944
|
+
try {
|
|
945
|
+
let db = getDB();
|
|
946
|
+
let client = new EmailThingCLI;
|
|
947
|
+
let isExiting = !1;
|
|
948
|
+
const _ = __using(__stack, usingProcess("SIGINT", () => {
|
|
949
|
+
if (isExiting)
|
|
950
|
+
return;
|
|
951
|
+
isExiting = !0, process.stdout.write("\x1B[?25h" /* SHOW_CURSOR */ + "\x1B[?1049l" /* ALT_SCREEN_OFF */), db.close(), process.exit(0);
|
|
952
|
+
}), 0);
|
|
953
|
+
let auth = loadAuth(db);
|
|
954
|
+
console.log(`Welcome to EmailThing CLI!
|
|
955
|
+
`);
|
|
956
|
+
if (!auth) {
|
|
957
|
+
let credentials = await loginScreen();
|
|
958
|
+
if (!credentials)
|
|
959
|
+
console.log("Login cancelled"), process.exit(0);
|
|
960
|
+
try {
|
|
961
|
+
let loginData = await client.login(credentials.username, credentials.password);
|
|
962
|
+
saveAuth(db, loginData), console.log("Login successful!");
|
|
963
|
+
} catch (error) {
|
|
964
|
+
console.error("Login failed:", error), process.exit(1);
|
|
965
|
+
}
|
|
966
|
+
} else
|
|
967
|
+
client.setAuth(auth.token, auth.refreshToken, auth.tokenExpiresAt);
|
|
968
|
+
let mailboxes = db.query("SELECT * FROM mailboxes").all();
|
|
969
|
+
if (mailboxes.length === 0)
|
|
970
|
+
await syncData(client, db);
|
|
971
|
+
let mailboxesWithCounts = db.query(`
|
|
972
|
+
SELECT m.*, COUNT(e.id) as emailCount
|
|
973
|
+
FROM mailboxes m
|
|
974
|
+
LEFT JOIN emails e ON m.id = e.mailboxId
|
|
975
|
+
GROUP BY m.id
|
|
976
|
+
ORDER BY emailCount DESC
|
|
977
|
+
`).all();
|
|
978
|
+
if (!mailboxesWithCounts.length)
|
|
979
|
+
console.error("No mailboxes found"), process.exit(1);
|
|
980
|
+
let defaultMailbox = mailboxesWithCounts[0];
|
|
981
|
+
let currentMailboxId = defaultMailbox.id;
|
|
982
|
+
let backgroundSync = async () => {
|
|
983
|
+
try {
|
|
984
|
+
await syncData(client, db, !0);
|
|
985
|
+
} catch (error) {}
|
|
986
|
+
};
|
|
987
|
+
let modifyEmailFn = async (updates) => {
|
|
988
|
+
try {
|
|
989
|
+
await client.modifyEmail(updates);
|
|
990
|
+
} catch (error) {
|
|
991
|
+
console.error("Failed to modify email:", error);
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
if (mailboxes.length > 0)
|
|
995
|
+
backgroundSync();
|
|
996
|
+
let route = globalThis._cli_route_cache || { route: "list", mailboxId: currentMailboxId };
|
|
997
|
+
while (route.route !== "quit")
|
|
998
|
+
switch (globalThis._cli_route_cache = route, route.route) {
|
|
999
|
+
case "list": {
|
|
1000
|
+
let result = await emailListScreen(db, route.mailboxId, backgroundSync, route.restoreId, modifyEmailFn);
|
|
1001
|
+
if (result.action === "quit")
|
|
1002
|
+
route = { route: "quit" };
|
|
1003
|
+
else if (result.action === "switch")
|
|
1004
|
+
route = { route: "switch", mailboxId: route.mailboxId };
|
|
1005
|
+
else if (result.action === "compose")
|
|
1006
|
+
route = { route: "compose", mailboxId: route.mailboxId };
|
|
1007
|
+
else if (result.action === "view" && result.emailId)
|
|
1008
|
+
route = {
|
|
1009
|
+
route: "view",
|
|
1010
|
+
mailboxId: route.mailboxId,
|
|
1011
|
+
emailId: result.emailId,
|
|
1012
|
+
_emailListCache: result.emailIds || void 0
|
|
1013
|
+
};
|
|
1014
|
+
else
|
|
1015
|
+
route = { route: "quit" };
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
1018
|
+
case "switch": {
|
|
1019
|
+
let newMailboxId = await mailboxSwitcher(db, route.mailboxId);
|
|
1020
|
+
if (newMailboxId === "switch-user") {
|
|
1021
|
+
let { clearAuth, resetDB } = await import("./config-cfsae2jm.js");
|
|
1022
|
+
return clearAuth(db), resetDB(db), main();
|
|
1023
|
+
} else if (newMailboxId)
|
|
1024
|
+
route = { route: "list", mailboxId: newMailboxId };
|
|
1025
|
+
else
|
|
1026
|
+
route = { route: "list", mailboxId: route.mailboxId };
|
|
1027
|
+
break;
|
|
1028
|
+
}
|
|
1029
|
+
case "compose": {
|
|
1030
|
+
let composeData = await composeScreen();
|
|
1031
|
+
if (composeData)
|
|
1032
|
+
try {
|
|
1033
|
+
console.log("Sending email..."), await client.sendDraft({
|
|
1034
|
+
draftId: crypto.randomUUID(),
|
|
1035
|
+
mailboxId: route.mailboxId,
|
|
1036
|
+
body: composeData.body,
|
|
1037
|
+
subject: composeData.subject,
|
|
1038
|
+
from: defaultMailbox.address,
|
|
1039
|
+
to: [{ address: composeData.to }]
|
|
1040
|
+
}), console.log("Email sent!"), await syncData(client, db);
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
console.error("Send failed:", error);
|
|
1043
|
+
}
|
|
1044
|
+
route = { route: "list", mailboxId: route.mailboxId };
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
case "view": {
|
|
1048
|
+
let emails = route._emailListCache || db.query("SELECT id FROM emails WHERE mailboxId = ? AND isDeleted = FALSE ORDER BY createdAt DESC").all(route.mailboxId).map((e) => e.id), currentEmailIndex = emails.indexOf(route.emailId);
|
|
1049
|
+
while (route.route === "view") {
|
|
1050
|
+
globalThis._cli_route_cache = route;
|
|
1051
|
+
let idx = currentEmailIndex, currentEmail = emails[idx], viewResult = await emailViewScreen(db, route.mailboxId, currentEmail, modifyEmailFn);
|
|
1052
|
+
if (viewResult === "quit")
|
|
1053
|
+
route = { route: "quit" };
|
|
1054
|
+
else if (viewResult === "back")
|
|
1055
|
+
route = { route: "list", mailboxId: route.mailboxId, restoreId: currentEmail };
|
|
1056
|
+
else if (viewResult === "next" && idx < emails.length - 1)
|
|
1057
|
+
route = { route: "view", mailboxId: route.mailboxId, emailId: emails[idx + 1], _emailListCache: emails }, currentEmailIndex = idx + 1;
|
|
1058
|
+
else if (viewResult === "prev" && idx > 0)
|
|
1059
|
+
route = { route: "view", mailboxId: route.mailboxId, emailId: emails[idx - 1], _emailListCache: emails }, currentEmailIndex = idx - 1;
|
|
1060
|
+
else
|
|
1061
|
+
route = { route: "list", mailboxId: route.mailboxId, restoreId: currentEmail };
|
|
1062
|
+
}
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
case "quit":
|
|
1066
|
+
default: {
|
|
1067
|
+
route = { route: "quit" };
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
process.stdout.write(`${"\x1B[?25h" /* SHOW_CURSOR */}${"\x1B[?1049l" /* ALT_SCREEN_OFF */}`);
|
|
1072
|
+
db.close();
|
|
1073
|
+
process.exit(0);
|
|
1074
|
+
} catch (_catch) {
|
|
1075
|
+
var _err = _catch, _hasErr = 1;
|
|
1076
|
+
} finally {
|
|
1077
|
+
__callDispose(__stack, _err, _hasErr);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
main().catch((error) => {
|
|
1081
|
+
process.stdout.write(`${"\x1B[?25h" /* SHOW_CURSOR */}${"\x1B[?1049l" /* ALT_SCREEN_OFF */}`), console.error("Fatal error:", error), process.exit(1);
|
|
1082
|
+
});
|