zele 0.3.10 → 0.3.12

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/dist/mail-tui.js CHANGED
@@ -10,23 +10,33 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
10
  // window.fetch → gets undefined → "fetchImpl is not a function". Fix: ensure
11
11
  // window.fetch is set to the native Bun fetch.
12
12
  const globalWithWindow = globalThis;
13
- if (typeof globalThis.fetch === 'function' && typeof globalWithWindow.window?.fetch !== 'function') {
14
- globalWithWindow.window = { ...globalWithWindow.window, fetch: globalThis.fetch };
13
+ if (typeof globalThis.fetch === 'function' &&
14
+ typeof globalWithWindow.window?.fetch !== 'function') {
15
+ globalWithWindow.window = {
16
+ ...globalWithWindow.window,
17
+ fetch: globalThis.fetch,
18
+ };
15
19
  }
16
20
  import { Action, ActionPanel, Color, Detail, Form, Icon, List, showToast, Toast, useNavigation, showFailureToast, } from 'termcast';
17
21
  import { useTerminalDimensions } from '@opentui/react';
18
22
  import { useCachedPromise } from '@termcast/utils';
19
23
  import { useState, useMemo, useCallback, useEffect } from 'react';
20
- import { getClients, getClient, listAccounts, login, logout } from './auth.js';
24
+ import { getClients, getClient, listAccounts, login, logout, } from './auth.js';
21
25
  import { isTruthy } from './api-utils.js';
22
- import { renderEmailBody, replyParser, formatDate, formatSender } from './output.js';
26
+ import { renderEmailBody, replyParser, formatDate, formatSender, } from './output.js';
23
27
  // ---------------------------------------------------------------------------
24
28
  // Constants
25
29
  // ---------------------------------------------------------------------------
26
30
  const DEFAULT_PAGE_SIZE = 25;
27
31
  const MIN_PAGE_SIZE = 10;
28
32
  const VISIBLE_ROWS_OFFSET = 6;
29
- const ACCOUNT_COLORS = [Color.Blue, Color.Green, Color.Purple, Color.Orange, Color.Magenta];
33
+ const ACCOUNT_COLORS = [
34
+ Color.Blue,
35
+ Color.Green,
36
+ Color.Purple,
37
+ Color.Orange,
38
+ Color.Magenta,
39
+ ];
30
40
  const ADD_ACCOUNT = '__add_account__';
31
41
  const MANAGE_ACCOUNTS = '__manage_accounts__';
32
42
  // ---------------------------------------------------------------------------
@@ -64,16 +74,24 @@ function dateSection(dateStr) {
64
74
  return 'This Month';
65
75
  return 'Older';
66
76
  }
67
- const SECTION_ORDER = ['Last 10 Minutes', 'Last Hour', 'Today', 'Yesterday', 'This Week', 'This Month', 'Older'];
77
+ const SECTION_ORDER = [
78
+ 'Last 10 Minutes',
79
+ 'Last Hour',
80
+ 'Today',
81
+ 'Yesterday',
82
+ 'This Week',
83
+ 'This Month',
84
+ 'Older',
85
+ ];
68
86
  function threadStatusIcon(thread) {
69
87
  const unread = thread.unread;
70
88
  const starred = thread.labelIds?.includes('STARRED') ?? false;
71
89
  if (unread && starred)
72
90
  return { source: Icon.Star, tintColor: Color.Red };
73
91
  if (unread)
74
- return { source: Icon.CircleFilled, tintColor: Color.Orange };
92
+ return { source: Icon.CircleFilled, tintColor: Color.Yellow };
75
93
  if (starred)
76
- return { source: Icon.Star, tintColor: Color.Yellow };
94
+ return { source: Icon.Star, tintColor: Color.Orange };
77
95
  return { source: Icon.Circle, tintColor: Color.SecondaryText };
78
96
  }
79
97
  function getPageSizeFromTerminalHeight(rows) {
@@ -95,7 +113,7 @@ function useAccounts() {
95
113
  // ---------------------------------------------------------------------------
96
114
  function AccountDropdown({ accounts, value, onChange, onAdded, onRemoved, }) {
97
115
  const { push } = useNavigation();
98
- return (_jsxs(List.Dropdown, { tooltip: "Account", value: value, onChange: (newValue) => {
116
+ return (_jsxs(List.Dropdown, { tooltip: 'Account', value: value, onChange: (newValue) => {
99
117
  if (newValue === ADD_ACCOUNT) {
100
118
  push(_jsx(AddAccount, { onAdded: onAdded }));
101
119
  return;
@@ -105,7 +123,10 @@ function AccountDropdown({ accounts, value, onChange, onAdded, onRemoved, }) {
105
123
  return;
106
124
  }
107
125
  onChange(newValue);
108
- }, children: [_jsx(List.Dropdown.Item, { title: "All Accounts", value: "all", icon: Icon.Globe }), _jsx(List.Dropdown.Section, { title: "Accounts", children: accounts.map((a) => (_jsx(List.Dropdown.Item, { title: a.email, value: a.email, icon: { source: Icon.Person, tintColor: accountColor(a.email) } }, a.email))) }), _jsxs(List.Dropdown.Section, { children: [_jsx(List.Dropdown.Item, { title: "Add Account", value: ADD_ACCOUNT, icon: Icon.Plus }), _jsx(List.Dropdown.Item, { title: "Manage Accounts", value: MANAGE_ACCOUNTS, icon: Icon.Gear })] })] }));
126
+ }, children: [_jsx(List.Dropdown.Item, { title: 'All Accounts', value: 'all', icon: Icon.Globe }), _jsx(List.Dropdown.Section, { title: 'Accounts', children: accounts.map((a) => (_jsx(List.Dropdown.Item, { title: a.email, value: a.email, icon: {
127
+ source: Icon.Person,
128
+ tintColor: accountColor(a.email),
129
+ } }, a.email))) }), _jsxs(List.Dropdown.Section, { children: [_jsx(List.Dropdown.Item, { title: 'Add Account', value: ADD_ACCOUNT, icon: Icon.Plus }), _jsx(List.Dropdown.Item, { title: 'Manage Accounts', value: MANAGE_ACCOUNTS, icon: Icon.Gear })] })] }));
109
130
  }
110
131
  // ---------------------------------------------------------------------------
111
132
  // Add Account (interactive login)
@@ -129,7 +150,10 @@ function AddAccount({ onAdded, }) {
129
150
  return;
130
151
  }
131
152
  await onAdded?.(result.email);
132
- await showToast({ style: Toast.Style.Success, title: `Added ${result.email}` });
153
+ await showToast({
154
+ style: Toast.Style.Success,
155
+ title: `Added ${result.email}`,
156
+ });
133
157
  pop();
134
158
  };
135
159
  useEffect(() => {
@@ -138,7 +162,7 @@ function AddAccount({ onAdded, }) {
138
162
  setDidAutoStart(true);
139
163
  void handleLogin();
140
164
  }, [didAutoStart]);
141
- return (_jsx(Detail, { navigationTitle: "Add Account", markdown: `# Add Account\n\n` +
165
+ return (_jsx(Detail, { navigationTitle: 'Add Account', markdown: `# Add Account\n\n` +
142
166
  `The browser opens automatically for Google sign-in.\n\n` +
143
167
  `Complete login in the browser, then come back here. ` +
144
168
  `This screen waits for the localhost callback and will finish automatically.`, actions: _jsx(ActionPanel, { children: _jsx(Action, { title: isLoggingIn ? 'Waiting for Login...' : 'Open Browser Again', icon: Icon.Globe, onAction: handleLogin }) }) }));
@@ -155,14 +179,29 @@ function ManageAccounts({ onAdded, onRemoved, }) {
155
179
  const handleRemoved = async (email) => {
156
180
  const result = await logout(email);
157
181
  if (result instanceof Error) {
158
- await showFailureToast(result, { title: `Failed to remove ${email}` });
182
+ await showFailureToast(result, {
183
+ title: `Failed to remove ${email}`,
184
+ });
159
185
  return;
160
186
  }
161
187
  await accounts.revalidate();
162
188
  await onRemoved?.(email);
163
- await showToast({ style: Toast.Style.Success, title: `Removed ${email}` });
189
+ await showToast({
190
+ style: Toast.Style.Success,
191
+ title: `Removed ${email}`,
192
+ });
164
193
  };
165
- return (_jsxs(List, { navigationTitle: "Manage Accounts", isLoading: accounts.isLoading, children: [accounts.data?.map((a) => (_jsx(List.Item, { title: a.email, icon: { source: Icon.Person, tintColor: accountColor(a.email) }, accessories: [{ tag: { value: a.appId.slice(0, 12) + '...', color: Color.SecondaryText } }], actions: _jsxs(ActionPanel, { children: [_jsx(Action.CopyToClipboard, { title: "Copy Email", content: a.email }), _jsx(Action, { title: "Logout Account", icon: Icon.Trash, style: Action.Style.Destructive, onAction: () => handleRemoved(a.email) })] }) }, `${a.email}-${a.appId}`))), _jsx(List.Item, { title: "Add Account", icon: Icon.Plus, actions: _jsx(ActionPanel, { children: _jsx(Action.Push, { title: "Add Account", target: _jsx(AddAccount, { onAdded: handleAdded }) }) }) }, "add-account")] }));
194
+ return (_jsxs(List, { navigationTitle: 'Manage Accounts', isLoading: accounts.isLoading, children: [accounts.data?.map((a) => (_jsx(List.Item, { title: a.email, icon: {
195
+ source: Icon.Person,
196
+ tintColor: accountColor(a.email),
197
+ }, accessories: [
198
+ {
199
+ tag: {
200
+ value: a.appId.slice(0, 12) + '...',
201
+ color: Color.SecondaryText,
202
+ },
203
+ },
204
+ ], actions: _jsxs(ActionPanel, { children: [_jsx(Action.CopyToClipboard, { title: 'Copy Email', content: a.email }), _jsx(Action, { title: 'Logout Account', icon: Icon.Trash, style: Action.Style.Destructive, onAction: () => handleRemoved(a.email) })] }) }, `${a.email}-${a.appId}`))), _jsx(List.Item, { title: 'Add Account', icon: Icon.Plus, actions: _jsx(ActionPanel, { children: _jsx(Action.Push, { title: 'Add Account', target: _jsx(AddAccount, { onAdded: handleAdded }) }) }) }, 'add-account')] }));
166
205
  }
167
206
  // ---------------------------------------------------------------------------
168
207
  // Reply Form
@@ -172,7 +211,10 @@ function ReplyForm({ threadId, account, replyAll, revalidate, }) {
172
211
  const [isLoading, setIsLoading] = useState(false);
173
212
  const handleSubmit = async (values) => {
174
213
  if (!values.body?.trim()) {
175
- await showToast({ style: Toast.Style.Failure, title: 'Body is required' });
214
+ await showToast({
215
+ style: Toast.Style.Failure,
216
+ title: 'Body is required',
217
+ });
176
218
  return;
177
219
  }
178
220
  setIsLoading(true);
@@ -191,7 +233,7 @@ function ReplyForm({ threadId, account, replyAll, revalidate, }) {
191
233
  revalidate();
192
234
  pop();
193
235
  };
194
- return (_jsx(Form, { isLoading: isLoading, navigationTitle: replyAll ? 'Reply All' : 'Reply', actions: _jsx(ActionPanel, { children: _jsx(Action.SubmitForm, { title: "Send Reply", onSubmit: handleSubmit }) }), children: _jsx(Form.TextArea, { id: "body", title: "Message", placeholder: "Type your reply..." }) }));
236
+ return (_jsx(Form, { isLoading: isLoading, navigationTitle: replyAll ? 'Reply All' : 'Reply', actions: _jsx(ActionPanel, { children: _jsx(Action.SubmitForm, { title: 'Send Reply', onSubmit: handleSubmit }) }), children: _jsx(Form.TextArea, { id: 'body', title: 'Message', placeholder: 'Type your reply...' }) }));
195
237
  }
196
238
  // ---------------------------------------------------------------------------
197
239
  // Forward Form
@@ -201,11 +243,17 @@ function ForwardForm({ threadId, account, revalidate, }) {
201
243
  const [isLoading, setIsLoading] = useState(false);
202
244
  const handleSubmit = async (values) => {
203
245
  if (!values.to?.trim()) {
204
- await showToast({ style: Toast.Style.Failure, title: 'Recipient is required' });
246
+ await showToast({
247
+ style: Toast.Style.Failure,
248
+ title: 'Recipient is required',
249
+ });
205
250
  return;
206
251
  }
207
252
  setIsLoading(true);
208
- const recipients = values.to.split(',').map((e) => ({ email: e.trim() })).filter((e) => e.email);
253
+ const recipients = values.to
254
+ .split(',')
255
+ .map((e) => ({ email: e.trim() }))
256
+ .filter((e) => e.email);
209
257
  const { client } = await getClient([account]);
210
258
  const result = await client.forwardThread({
211
259
  threadId,
@@ -217,11 +265,14 @@ function ForwardForm({ threadId, account, revalidate, }) {
217
265
  await showFailureToast(result, { title: 'Failed to forward' });
218
266
  return;
219
267
  }
220
- await showToast({ style: Toast.Style.Success, title: `Forwarded to ${values.to}` });
268
+ await showToast({
269
+ style: Toast.Style.Success,
270
+ title: `Forwarded to ${values.to}`,
271
+ });
221
272
  revalidate();
222
273
  pop();
223
274
  };
224
- return (_jsxs(Form, { isLoading: isLoading, navigationTitle: "Forward", actions: _jsx(ActionPanel, { children: _jsx(Action.SubmitForm, { title: "Forward", onSubmit: handleSubmit }) }), children: [_jsx(Form.TextField, { id: "to", title: "To", placeholder: "recipient@example.com" }), _jsx(Form.TextArea, { id: "body", title: "Message", placeholder: "Optional message to prepend..." })] }));
275
+ return (_jsxs(Form, { isLoading: isLoading, navigationTitle: 'Forward', actions: _jsx(ActionPanel, { children: _jsx(Action.SubmitForm, { title: 'Forward', onSubmit: handleSubmit }) }), children: [_jsx(Form.TextField, { id: 'to', title: 'To', placeholder: 'recipient@example.com' }), _jsx(Form.TextArea, { id: 'body', title: 'Message', placeholder: 'Optional message to prepend...' })] }));
225
276
  }
226
277
  // ---------------------------------------------------------------------------
227
278
  // Thread Detail (full thread view, pushed via Enter)
@@ -235,7 +286,7 @@ function ThreadDetail({ threadId, account, revalidate, }) {
235
286
  return result.parsed;
236
287
  }, [threadId, account]);
237
288
  if (thread.isLoading || !thread.data) {
238
- return _jsx(Detail, { markdown: "", navigationTitle: "Loading..." });
289
+ return _jsx(Detail, { markdown: '', navigationTitle: 'Loading...' });
239
290
  }
240
291
  const t = thread.data;
241
292
  const messages = t.messages;
@@ -255,7 +306,9 @@ function ThreadDetail({ threadId, account, revalidate, }) {
255
306
  else {
256
307
  body = renderEmailBody(msg.body, msg.mimeType);
257
308
  }
258
- return [heading, attachmentLine, '', body].filter((l) => l !== null).join('\n');
309
+ return [heading, attachmentLine, '', body]
310
+ .filter((l) => l !== null)
311
+ .join('\n');
259
312
  });
260
313
  const markdown = `# ${t.subject}\n\n---\n\n` + parts.join('\n\n---\n\n');
261
314
  // Collect unique participants
@@ -269,7 +322,10 @@ function ThreadDetail({ threadId, account, revalidate, }) {
269
322
  .filter((l) => typeof l === 'string' && !l.startsWith('Label_')) // skip internal IDs
270
323
  .slice(0, 10);
271
324
  const latestMsg = messages[messages.length - 1];
272
- return (_jsx(Detail, { navigationTitle: t.subject, markdown: markdown, metadata: _jsxs(Detail.Metadata, { children: [_jsx(Detail.Metadata.Label, { title: "From", text: formatSender(latestMsg.from) }), _jsx(Detail.Metadata.Label, { title: "To", text: latestMsg.to.map((r) => r.name || r.email).join(', ') }), latestMsg.cc && latestMsg.cc.length > 0 && (_jsx(Detail.Metadata.Label, { title: "Cc", text: latestMsg.cc.map((r) => r.name || r.email).join(', ') })), _jsx(Detail.Metadata.Label, { title: "Date", text: latestMsg.date }), _jsx(Detail.Metadata.Separator, {}), _jsx(Detail.Metadata.Label, { title: "Messages", text: String(t.messageCount) }), _jsx(Detail.Metadata.Label, { title: "Participants", text: [...participants.values()].join(', ') }), labels.length > 0 && (_jsx(Detail.Metadata.TagList, { title: "Labels", children: labels.map((l) => (_jsx(Detail.Metadata.TagList.Item, { text: l, color: labelColor(l) }, l))) })), _jsx(Detail.Metadata.Separator, {}), _jsx(Detail.Metadata.Label, { title: "Thread ID", text: t.id }), _jsx(Detail.Metadata.Label, { title: "Account", text: account })] }), actions: _jsxs(ActionPanel, { children: [_jsxs(ActionPanel.Section, { title: "Reply & Forward", children: [_jsx(Action.Push, { title: "Reply", icon: Icon.Reply, shortcut: { modifiers: ['ctrl'], key: 'r' }, target: _jsx(ReplyForm, { threadId: threadId, account: account, revalidate: revalidate }) }), _jsx(Action.Push, { title: "Reply All", icon: Icon.Reply, shortcut: { modifiers: ['ctrl', 'shift'], key: 'r' }, target: _jsx(ReplyForm, { threadId: threadId, account: account, replyAll: true, revalidate: revalidate }) }), _jsx(Action.Push, { title: "Forward", icon: Icon.Forward, shortcut: { modifiers: ['ctrl'], key: 'f' }, target: _jsx(ForwardForm, { threadId: threadId, account: account, revalidate: revalidate }) })] }), _jsxs(ActionPanel.Section, { title: "Copy", children: [_jsx(Action.CopyToClipboard, { title: "Copy Thread ID", content: t.id }), _jsx(Action.CopyToClipboard, { title: "Copy Subject", content: t.subject }), latestMsg && (_jsx(Action.CopyToClipboard, { title: "Copy Email Body", content: renderEmailBody(latestMsg.body, latestMsg.mimeType) }))] })] }) }));
325
+ return (_jsx(Detail, { navigationTitle: t.subject, markdown: markdown, metadata: _jsxs(Detail.Metadata, { children: [_jsx(Detail.Metadata.Label, { title: 'From', text: formatSender(latestMsg.from) }), _jsx(Detail.Metadata.Label, { title: 'To', text: latestMsg.to.map((r) => r.name || r.email).join(', ') }), latestMsg.cc && latestMsg.cc.length > 0 && (_jsx(Detail.Metadata.Label, { title: 'Cc', text: latestMsg.cc.map((r) => r.name || r.email).join(', ') })), _jsx(Detail.Metadata.Label, { title: 'Date', text: latestMsg.date }), _jsx(Detail.Metadata.Separator, {}), _jsx(Detail.Metadata.Label, { title: 'Messages', text: String(t.messageCount) }), _jsx(Detail.Metadata.Label, { title: 'Participants', text: [...participants.values()].join(', ') }), labels.length > 0 && (_jsx(Detail.Metadata.TagList, { title: 'Labels', children: labels.map((l) => (_jsx(Detail.Metadata.TagList.Item, { text: l, color: labelColor(l) }, l))) })), _jsx(Detail.Metadata.Separator, {}), _jsx(Detail.Metadata.Label, { title: 'Thread ID', text: t.id }), _jsx(Detail.Metadata.Label, { title: 'Account', text: account })] }), actions: _jsxs(ActionPanel, { children: [_jsxs(ActionPanel.Section, { title: 'Reply & Forward', children: [_jsx(Action.Push, { title: 'Reply', icon: Icon.Reply, shortcut: { modifiers: ['ctrl'], key: 'r' }, target: _jsx(ReplyForm, { threadId: threadId, account: account, revalidate: revalidate }) }), _jsx(Action.Push, { title: 'Reply All', icon: Icon.Reply, shortcut: {
326
+ modifiers: ['ctrl', 'shift'],
327
+ key: 'r',
328
+ }, target: _jsx(ReplyForm, { threadId: threadId, account: account, replyAll: true, revalidate: revalidate }) }), _jsx(Action.Push, { title: 'Forward', icon: Icon.Forward, shortcut: { modifiers: ['ctrl'], key: 'f' }, target: _jsx(ForwardForm, { threadId: threadId, account: account, revalidate: revalidate }) })] }), _jsxs(ActionPanel.Section, { title: 'Copy', children: [_jsx(Action.CopyToClipboard, { title: 'Copy Thread ID', content: t.id }), _jsx(Action.CopyToClipboard, { title: 'Copy Subject', content: t.subject }), latestMsg && (_jsx(Action.CopyToClipboard, { title: 'Copy Email Body', content: renderEmailBody(latestMsg.body, latestMsg.mimeType) }))] })] }) }));
273
329
  }
274
330
  // ---------------------------------------------------------------------------
275
331
  // Main Command
@@ -298,10 +354,15 @@ export default function Command() {
298
354
  pageToken: pageToken || undefined,
299
355
  });
300
356
  if (result instanceof Error) {
301
- await showFailureToast(result, { title: 'Failed to fetch emails' });
357
+ await showFailureToast(result, {
358
+ title: 'Failed to fetch emails',
359
+ });
302
360
  return { data: [], hasMore: false };
303
361
  }
304
- const data = result.threads.map((t) => ({ ...t, account: email }));
362
+ const data = result.threads.map((t) => ({
363
+ ...t,
364
+ account: email,
365
+ }));
305
366
  return {
306
367
  data,
307
368
  hasMore: !!result.nextPageToken,
@@ -354,7 +415,10 @@ export default function Command() {
354
415
  return {
355
416
  data: merged,
356
417
  hasMore,
357
- cursor: { mode: 'multi', nextByAccount },
418
+ cursor: {
419
+ mode: 'multi',
420
+ nextByAccount,
421
+ },
358
422
  };
359
423
  };
360
424
  }, [searchText, selectedAccount, pageSize], { keepPreviousData: true });
@@ -390,7 +454,9 @@ export default function Command() {
390
454
  const multiAccount = accountList.length > 1;
391
455
  // Selection helpers
392
456
  const toggleSelection = useCallback((threadId) => {
393
- setSelectedThreads((prev) => prev.includes(threadId) ? prev.filter((id) => id !== threadId) : [...prev, threadId]);
457
+ setSelectedThreads((prev) => prev.includes(threadId)
458
+ ? prev.filter((id) => id !== threadId)
459
+ : [...prev, threadId]);
394
460
  }, []);
395
461
  // Bulk actions
396
462
  const handleBulkAction = useCallback(async (actionName, fn) => {
@@ -410,68 +476,127 @@ export default function Command() {
410
476
  const { client } = await getClient([acct]);
411
477
  const result = await fn(client, ids);
412
478
  if (result instanceof Error) {
413
- await showFailureToast(result, { title: `Failed to ${actionName}` });
479
+ await showFailureToast(result, {
480
+ title: `Failed to ${actionName}`,
481
+ });
414
482
  return;
415
483
  }
416
484
  }
417
- await showToast({ style: Toast.Style.Success, title: `${actionName}: ${selectedThreads.length} thread(s)` });
485
+ await showToast({
486
+ style: Toast.Style.Success,
487
+ title: `${actionName}: ${selectedThreads.length} thread(s)`,
488
+ });
418
489
  setSelectedThreads([]);
419
490
  revalidate();
420
491
  }, [selectedThreads, allThreads, revalidate]);
421
- return (_jsx(List, { isLoading: isLoading || accounts.isLoading, isShowingDetail: isShowingDetail, searchBarPlaceholder: "Search emails...", onSearchTextChange: setSearchText, throttle: true, pagination: pagination ? { ...pagination, pageSize } : undefined, searchBarAccessory: accountList.length > 0 ? (_jsx(AccountDropdown, { accounts: accountList, value: selectedAccount, onChange: setSelectedAccount, onAdded: handleAccountAdded, onRemoved: handleAccountRemoved })) : undefined, children: sections.map((section) => (_jsx(List.Section, { title: section.name, children: section.threads.map((thread) => {
492
+ return (_jsx(List, { isLoading: isLoading || accounts.isLoading, isShowingDetail: isShowingDetail, searchBarPlaceholder: 'Search emails...', onSearchTextChange: setSearchText, throttle: true, pagination: pagination ? { ...pagination, pageSize } : undefined, searchBarAccessory: accountList.length > 0 ? (_jsx(AccountDropdown, { accounts: accountList, value: selectedAccount, onChange: setSelectedAccount, onAdded: handleAccountAdded, onRemoved: handleAccountRemoved })) : undefined, children: sections.map((section) => (_jsx(List.Section, { title: section.name, children: section.threads.map((thread) => {
422
493
  const isSelected = selectedThreads.includes(thread.id);
423
494
  const hasSelection = selectedThreads.length > 0;
424
495
  // Icon: selection mode or status
425
496
  const icon = hasSelection
426
- ? { source: isSelected ? Icon.CheckCircle : Icon.Circle, tintColor: isSelected ? Color.Blue : Color.SecondaryText }
497
+ ? {
498
+ source: isSelected ? Icon.CheckCircle : Icon.Circle,
499
+ tintColor: isSelected ? Color.Blue : Color.SecondaryText,
500
+ }
427
501
  : threadStatusIcon(thread);
428
502
  // Accessories
429
503
  const accessories = [];
430
504
  if (thread.messageCount > 1) {
431
- accessories.push({ tag: { value: String(thread.messageCount), color: Color.SecondaryText } });
505
+ accessories.push({
506
+ tag: {
507
+ value: String(thread.messageCount),
508
+ color: Color.SecondaryText,
509
+ },
510
+ });
432
511
  }
433
512
  if (multiAccount || selectedAccount === 'all') {
434
- accessories.push({ tag: { value: thread.account.split('@')[0] ?? thread.account, color: accountColor(thread.account) } });
513
+ accessories.push({
514
+ tag: {
515
+ value: thread.account.split('@')[0] ?? thread.account,
516
+ color: accountColor(thread.account),
517
+ },
518
+ });
435
519
  }
436
520
  accessories.push({ text: formatDate(thread.date) });
437
521
  // Detail panel: latest message body as markdown
438
- const detail = isShowingDetail ? (_jsx(List.Item.Detail, { markdown: `# ${thread.subject}\n\n${thread.snippet}`, metadata: _jsxs(List.Item.Detail.Metadata, { children: [_jsx(List.Item.Detail.Metadata.Label, { title: "From", text: formatSender(thread.from) }), _jsx(List.Item.Detail.Metadata.Label, { title: "Date", text: thread.date }), _jsx(List.Item.Detail.Metadata.Separator, {}), thread.labelIds.length > 0 && (_jsx(List.Item.Detail.Metadata.TagList, { title: "Labels", children: thread.labelIds
522
+ const detail = isShowingDetail ? (_jsx(List.Item.Detail, { markdown: `# ${thread.subject}\n\n${thread.snippet}`, metadata: _jsxs(List.Item.Detail.Metadata, { children: [_jsx(List.Item.Detail.Metadata.Label, { title: 'From', text: formatSender(thread.from) }), _jsx(List.Item.Detail.Metadata.Label, { title: 'Date', text: thread.date }), _jsx(List.Item.Detail.Metadata.Separator, {}), thread.labelIds.length > 0 && (_jsx(List.Item.Detail.Metadata.TagList, { title: 'Labels', children: thread.labelIds
439
523
  .filter((l) => !l.startsWith('Label_'))
440
524
  .slice(0, 8)
441
- .map((l) => (_jsx(List.Item.Detail.Metadata.TagList.Item, { text: l, color: labelColor(l) }, l))) })), _jsx(List.Item.Detail.Metadata.Separator, {}), _jsx(List.Item.Detail.Metadata.Label, { title: "Messages", text: String(thread.messageCount) }), _jsx(List.Item.Detail.Metadata.Label, { title: "Thread ID", text: thread.id }), multiAccount && (_jsx(List.Item.Detail.Metadata.Label, { title: "Account", text: thread.account }))] }) })) : undefined;
442
- return (_jsx(List.Item, { title: thread.subject || '(no subject)', subtitle: formatSender(thread.from), icon: icon, accessories: accessories, keywords: [thread.from.email, thread.from.name ?? '', thread.account], detail: detail, actions: _jsxs(ActionPanel, { children: [hasSelection && (_jsxs(ActionPanel.Section, { title: "Selection", children: [_jsx(Action, { title: isSelected ? 'Deselect Thread' : 'Select Thread', icon: isSelected ? Icon.CheckCircle : Icon.Circle, onAction: () => toggleSelection(thread.id) }), _jsx(Action, { title: `Archive ${selectedThreads.length} Selected`, icon: Icon.Tray, onAction: () => handleBulkAction('Archived', (c, ids) => c.archive({ threadIds: ids })) }), _jsx(Action, { title: `Mark ${selectedThreads.length} as Read`, icon: Icon.Eye, onAction: () => handleBulkAction('Marked as read', (c, ids) => c.markAsRead({ threadIds: ids })) }), _jsx(Action, { title: `Star ${selectedThreads.length} Selected`, icon: Icon.Star, onAction: () => handleBulkAction('Starred', (c, ids) => c.star({ threadIds: ids })) }), _jsx(Action, { title: `Trash ${selectedThreads.length} Selected`, icon: Icon.Trash, style: Action.Style.Destructive, onAction: () => handleBulkAction('Trashed', async (c, ids) => {
525
+ .map((l) => (_jsx(List.Item.Detail.Metadata.TagList.Item, { text: l, color: labelColor(l) }, l))) })), _jsx(List.Item.Detail.Metadata.Separator, {}), _jsx(List.Item.Detail.Metadata.Label, { title: 'Messages', text: String(thread.messageCount) }), _jsx(List.Item.Detail.Metadata.Label, { title: 'Thread ID', text: thread.id }), multiAccount && (_jsx(List.Item.Detail.Metadata.Label, { title: 'Account', text: thread.account }))] }) })) : undefined;
526
+ return (_jsx(List.Item, { title: thread.subject || '(no subject)', subtitle: formatSender(thread.from), icon: icon, accessories: accessories, keywords: [
527
+ thread.from.email,
528
+ thread.from.name ?? '',
529
+ thread.account,
530
+ ], detail: detail, actions: _jsxs(ActionPanel, { children: [hasSelection && (_jsxs(ActionPanel.Section, { title: 'Selection', children: [_jsx(Action, { title: isSelected ? 'Deselect Thread' : 'Select Thread', icon: isSelected ? Icon.CheckCircle : Icon.Circle, onAction: () => toggleSelection(thread.id) }), _jsx(Action, { title: `Archive ${selectedThreads.length} Selected`, icon: Icon.Tray, onAction: () => handleBulkAction('Archived', (c, ids) => c.archive({
531
+ threadIds: ids,
532
+ })) }), _jsx(Action, { title: `Mark ${selectedThreads.length} as Read`, icon: Icon.Eye, onAction: () => handleBulkAction('Marked as read', (c, ids) => c.markAsRead({
533
+ threadIds: ids,
534
+ })) }), _jsx(Action, { title: `Star ${selectedThreads.length} Selected`, icon: Icon.Star, onAction: () => handleBulkAction('Starred', (c, ids) => c.star({
535
+ threadIds: ids,
536
+ })) }), _jsx(Action, { title: `Trash ${selectedThreads.length} Selected`, icon: Icon.Trash, style: Action.Style.Destructive, onAction: () => handleBulkAction('Trashed', async (c, ids) => {
443
537
  for (const id of ids) {
444
- await c.trash({ threadId: id });
538
+ await c.trash({
539
+ threadId: id,
540
+ });
445
541
  }
446
- }) }), _jsx(Action, { title: "Deselect All", icon: Icon.XMarkCircle, onAction: () => setSelectedThreads([]) })] })), _jsxs(ActionPanel.Section, { children: [_jsx(Action.Push, { title: "Open Thread", icon: Icon.Eye, target: _jsx(ThreadDetail, { threadId: thread.id, account: thread.account, revalidate: revalidate }) }), !hasSelection && (_jsx(Action, { title: "Select Thread", icon: Icon.CheckCircle, shortcut: { modifiers: ['ctrl'], key: 'x' }, onAction: () => toggleSelection(thread.id) })), _jsx(Action, { title: thread.unread ? 'Mark as Read' : 'Mark as Unread', icon: thread.unread ? Icon.Eye : Icon.EyeDisabled, shortcut: { modifiers: ['ctrl'], key: 'u' }, onAction: async () => {
542
+ }) }), _jsx(Action, { title: 'Deselect All', icon: Icon.XMarkCircle, onAction: () => setSelectedThreads([]) })] })), _jsxs(ActionPanel.Section, { children: [_jsx(Action.Push, { title: 'Open Thread', icon: Icon.Eye, target: _jsx(ThreadDetail, { threadId: thread.id, account: thread.account, revalidate: revalidate }) }), !hasSelection && (_jsx(Action, { title: 'Select Thread', icon: Icon.CheckCircle, shortcut: {
543
+ modifiers: ['ctrl'],
544
+ key: 'x',
545
+ }, onAction: () => toggleSelection(thread.id) })), _jsx(Action, { title: thread.unread ? 'Mark as Read' : 'Mark as Unread', icon: thread.unread ? Icon.Eye : Icon.EyeDisabled, shortcut: {
546
+ modifiers: ['ctrl'],
547
+ key: 'u',
548
+ }, onAction: async () => {
447
549
  const { client } = await getClient([thread.account]);
448
550
  const result = thread.unread
449
- ? await client.markAsRead({ threadIds: [thread.id] })
450
- : await client.markAsUnread({ threadIds: [thread.id] });
551
+ ? await client.markAsRead({
552
+ threadIds: [thread.id],
553
+ })
554
+ : await client.markAsUnread({
555
+ threadIds: [thread.id],
556
+ });
451
557
  if (result instanceof Error) {
452
558
  await showFailureToast(result);
453
559
  return;
454
560
  }
455
561
  await showToast({
456
562
  style: Toast.Style.Success,
457
- title: thread.unread ? 'Marked as read' : 'Marked as unread',
563
+ title: thread.unread
564
+ ? 'Marked as read'
565
+ : 'Marked as unread',
458
566
  });
459
567
  revalidate();
460
- } }), _jsx(Action, { title: "Archive", icon: Icon.Tray, shortcut: { modifiers: ['ctrl'], key: 'e' }, onAction: async () => {
568
+ } }), _jsx(Action, { title: 'Archive', icon: Icon.Tray, shortcut: {
569
+ modifiers: ['ctrl'],
570
+ key: 'e',
571
+ }, onAction: async () => {
461
572
  const { client } = await getClient([thread.account]);
462
- const result = await client.archive({ threadIds: [thread.id] });
573
+ const result = await client.archive({
574
+ threadIds: [thread.id],
575
+ });
463
576
  if (result instanceof Error) {
464
577
  await showFailureToast(result);
465
578
  return;
466
579
  }
467
- await showToast({ style: Toast.Style.Success, title: 'Archived' });
580
+ await showToast({
581
+ style: Toast.Style.Success,
582
+ title: 'Archived',
583
+ });
468
584
  revalidate();
469
- } }), _jsx(Action, { title: thread.labelIds.includes('STARRED') ? 'Unstar' : 'Star', icon: Icon.Star, shortcut: { modifiers: ['ctrl'], key: 's' }, onAction: async () => {
585
+ } }), _jsx(Action, { title: thread.labelIds.includes('STARRED')
586
+ ? 'Unstar'
587
+ : 'Star', icon: Icon.Star, shortcut: {
588
+ modifiers: ['ctrl'],
589
+ key: 's',
590
+ }, onAction: async () => {
470
591
  const { client } = await getClient([thread.account]);
471
592
  const isStarred = thread.labelIds.includes('STARRED');
472
593
  const result = isStarred
473
- ? await client.unstar({ threadIds: [thread.id] })
474
- : await client.star({ threadIds: [thread.id] });
594
+ ? await client.unstar({
595
+ threadIds: [thread.id],
596
+ })
597
+ : await client.star({
598
+ threadIds: [thread.id],
599
+ });
475
600
  if (result instanceof Error) {
476
601
  await showFailureToast(result);
477
602
  return;
@@ -481,12 +606,35 @@ export default function Command() {
481
606
  title: isStarred ? 'Unstarred' : 'Starred',
482
607
  });
483
608
  revalidate();
484
- } })] }), _jsxs(ActionPanel.Section, { title: "Reply & Forward", children: [_jsx(Action.Push, { title: "Reply", icon: Icon.Reply, shortcut: { modifiers: ['ctrl'], key: 'r' }, target: _jsx(ReplyForm, { threadId: thread.id, account: thread.account, revalidate: revalidate }) }), _jsx(Action.Push, { title: "Reply All", icon: Icon.Reply, shortcut: { modifiers: ['ctrl', 'shift'], key: 'r' }, target: _jsx(ReplyForm, { threadId: thread.id, account: thread.account, replyAll: true, revalidate: revalidate }) }), _jsx(Action.Push, { title: "Forward", icon: Icon.Forward, shortcut: { modifiers: ['ctrl'], key: 'f' }, target: _jsx(ForwardForm, { threadId: thread.id, account: thread.account, revalidate: revalidate }) })] }), _jsxs(ActionPanel.Section, { title: "Copy", children: [_jsx(Action.CopyToClipboard, { title: "Copy Thread ID", content: thread.id }), _jsx(Action.CopyToClipboard, { title: "Copy Subject", content: thread.subject }), _jsx(Action.CopyToClipboard, { title: "Copy Sender Email", content: thread.from.email })] }), _jsx(ActionPanel.Section, { children: _jsx(Action, { title: "Trash", icon: Icon.Trash, style: Action.Style.Destructive, shortcut: { modifiers: ['ctrl'], key: 'backspace' }, onAction: async () => {
609
+ } })] }), _jsxs(ActionPanel.Section, { title: 'Reply & Forward', children: [_jsx(Action.Push, { title: 'Reply', icon: Icon.Reply, shortcut: {
610
+ modifiers: ['ctrl'],
611
+ key: 'r',
612
+ }, target: _jsx(ReplyForm, { threadId: thread.id, account: thread.account, revalidate: revalidate }) }), _jsx(Action.Push, { title: 'Reply All', icon: Icon.Reply, shortcut: {
613
+ modifiers: ['ctrl', 'shift'],
614
+ key: 'r',
615
+ }, target: _jsx(ReplyForm, { threadId: thread.id, account: thread.account, replyAll: true, revalidate: revalidate }) }), _jsx(Action.Push, { title: 'Forward', icon: Icon.Forward, shortcut: {
616
+ modifiers: ['ctrl'],
617
+ key: 'f',
618
+ }, target: _jsx(ForwardForm, { threadId: thread.id, account: thread.account, revalidate: revalidate }) })] }), _jsxs(ActionPanel.Section, { title: 'Copy', children: [_jsx(Action.CopyToClipboard, { title: 'Copy Thread ID', content: thread.id }), _jsx(Action.CopyToClipboard, { title: 'Copy Subject', content: thread.subject }), _jsx(Action.CopyToClipboard, { title: 'Copy Sender Email', content: thread.from.email })] }), _jsx(ActionPanel.Section, { children: _jsx(Action, { title: 'Trash', icon: Icon.Trash, style: Action.Style.Destructive, shortcut: {
619
+ modifiers: ['ctrl'],
620
+ key: 'backspace',
621
+ }, onAction: async () => {
485
622
  const { client } = await getClient([thread.account]);
486
- await client.trash({ threadId: thread.id });
487
- await showToast({ style: Toast.Style.Success, title: 'Trashed' });
623
+ await client.trash({
624
+ threadId: thread.id,
625
+ });
626
+ await showToast({
627
+ style: Toast.Style.Success,
628
+ title: 'Trashed',
629
+ });
488
630
  revalidate();
489
- } }) }), _jsxs(ActionPanel.Section, { children: [_jsx(Action, { title: "Refresh", icon: Icon.ArrowClockwise, shortcut: { modifiers: ['ctrl', 'shift'], key: 'r' }, onAction: () => revalidate() }), _jsx(Action, { title: "Toggle Detail", icon: Icon.Sidebar, shortcut: { modifiers: ['ctrl'], key: 'd' }, onAction: () => setIsShowingDetail((v) => !v) })] })] }) }, `${thread.account}-${thread.id}`));
631
+ } }) }), _jsxs(ActionPanel.Section, { children: [_jsx(Action, { title: 'Refresh', icon: Icon.ArrowClockwise, shortcut: {
632
+ modifiers: ['ctrl', 'shift'],
633
+ key: 'r',
634
+ }, onAction: () => revalidate() }), _jsx(Action, { title: 'Toggle Detail', icon: Icon.Sidebar, shortcut: {
635
+ modifiers: ['ctrl'],
636
+ key: 'd',
637
+ }, onAction: () => setIsShowingDetail((v) => !v) })] })] }) }, `${thread.account}-${thread.id}`));
490
638
  }) }, section.name))) }));
491
639
  }
492
640
  // ---------------------------------------------------------------------------