zele 0.3.12 → 0.3.13
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/gmail-client.d.ts +11 -12
- package/dist/gmail-client.js +55 -40
- package/dist/gmail-client.js.map +1 -1
- package/dist/mail-tui.js +151 -184
- package/dist/mail-tui.js.map +1 -1
- package/package.json +3 -3
- package/src/gmail-client.test.ts +8 -3
- package/src/gmail-client.ts +64 -58
- package/src/mail-tui.tsx +398 -416
package/dist/mail-tui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
// Termcast email extension — browse and read emails in a Raycast-like TUI.
|
|
3
3
|
// Uses List with detail view, sections for date grouping, infinite scroll via
|
|
4
4
|
// useCachedPromise pagination, and a dropdown for account selection.
|
|
@@ -30,6 +30,9 @@ import { renderEmailBody, replyParser, formatDate, formatSender, } from './outpu
|
|
|
30
30
|
const DEFAULT_PAGE_SIZE = 25;
|
|
31
31
|
const MIN_PAGE_SIZE = 10;
|
|
32
32
|
const VISIBLE_ROWS_OFFSET = 6;
|
|
33
|
+
/** Spacing mode for the mail list. 'relaxed' renders each item as 3 lines. */
|
|
34
|
+
const LIST_SPACING_MODE = 'relaxed';
|
|
35
|
+
const LINES_PER_ITEM = LIST_SPACING_MODE === 'relaxed' ? 3 : 1;
|
|
33
36
|
const ACCOUNT_COLORS = [
|
|
34
37
|
Color.Blue,
|
|
35
38
|
Color.Green,
|
|
@@ -97,7 +100,9 @@ function threadStatusIcon(thread) {
|
|
|
97
100
|
function getPageSizeFromTerminalHeight(rows) {
|
|
98
101
|
if (typeof rows !== 'number' || rows <= 0)
|
|
99
102
|
return DEFAULT_PAGE_SIZE;
|
|
100
|
-
|
|
103
|
+
const visibleRows = rows - VISIBLE_ROWS_OFFSET;
|
|
104
|
+
const itemCount = Math.floor(visibleRows / LINES_PER_ITEM);
|
|
105
|
+
return Math.max(MIN_PAGE_SIZE, itemCount);
|
|
101
106
|
}
|
|
102
107
|
// ---------------------------------------------------------------------------
|
|
103
108
|
// Data fetching
|
|
@@ -126,7 +131,7 @@ function AccountDropdown({ accounts, value, onChange, onAdded, onRemoved, }) {
|
|
|
126
131
|
}, 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
132
|
source: Icon.Person,
|
|
128
133
|
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 })] })] }));
|
|
134
|
+
} }, a.email))) }), _jsxs(List.Dropdown.Section, { title: 'Manage Accounts', 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 })] })] }));
|
|
130
135
|
}
|
|
131
136
|
// ---------------------------------------------------------------------------
|
|
132
137
|
// Add Account (interactive login)
|
|
@@ -201,83 +206,77 @@ function ManageAccounts({ onAdded, onRemoved, }) {
|
|
|
201
206
|
color: Color.SecondaryText,
|
|
202
207
|
},
|
|
203
208
|
},
|
|
204
|
-
], actions: _jsxs(ActionPanel, { children: [_jsx(Action.CopyToClipboard, { title: 'Copy Email', content: a.email }), _jsx(Action, { title: 'Logout Account', icon: Icon.Trash,
|
|
209
|
+
], actions: _jsxs(ActionPanel, { children: [_jsx(Action.CopyToClipboard, { title: 'Copy Email', content: a.email }), _jsx(Action, { title: 'Logout Account', icon: Icon.Trash, 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')] }));
|
|
205
210
|
}
|
|
206
|
-
|
|
207
|
-
// Reply Form
|
|
208
|
-
// ---------------------------------------------------------------------------
|
|
209
|
-
function ReplyForm({ threadId, account, replyAll, revalidate, }) {
|
|
211
|
+
function ComposeForm({ mode, initialAccount, accounts, onSent }) {
|
|
210
212
|
const { pop } = useNavigation();
|
|
211
213
|
const [isLoading, setIsLoading] = useState(false);
|
|
214
|
+
const [selectedAccount, setSelectedAccount] = useState(initialAccount);
|
|
215
|
+
const navigationTitle = mode.type === 'forward'
|
|
216
|
+
? 'Forward'
|
|
217
|
+
: mode.replyAll
|
|
218
|
+
? 'Reply All'
|
|
219
|
+
: 'Reply';
|
|
220
|
+
const bodyPlaceholder = mode.type === 'forward'
|
|
221
|
+
? 'Add a message (optional)...'
|
|
222
|
+
: 'Type your reply...';
|
|
212
223
|
const handleSubmit = async (values) => {
|
|
213
|
-
|
|
224
|
+
// Validate based on mode
|
|
225
|
+
if (mode.type === 'forward' && !values.to?.trim()) {
|
|
214
226
|
await showToast({
|
|
215
227
|
style: Toast.Style.Failure,
|
|
216
|
-
title: '
|
|
228
|
+
title: 'Recipient is required',
|
|
217
229
|
});
|
|
218
230
|
return;
|
|
219
231
|
}
|
|
220
|
-
|
|
221
|
-
const { client } = await getClient([account]);
|
|
222
|
-
const result = await client.replyToThread({
|
|
223
|
-
threadId,
|
|
224
|
-
body: values.body,
|
|
225
|
-
replyAll,
|
|
226
|
-
});
|
|
227
|
-
setIsLoading(false);
|
|
228
|
-
if (result instanceof Error) {
|
|
229
|
-
await showFailureToast(result, { title: 'Failed to send reply' });
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
await showToast({ style: Toast.Style.Success, title: 'Reply sent' });
|
|
233
|
-
revalidate();
|
|
234
|
-
pop();
|
|
235
|
-
};
|
|
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...' }) }));
|
|
237
|
-
}
|
|
238
|
-
// ---------------------------------------------------------------------------
|
|
239
|
-
// Forward Form
|
|
240
|
-
// ---------------------------------------------------------------------------
|
|
241
|
-
function ForwardForm({ threadId, account, revalidate, }) {
|
|
242
|
-
const { pop } = useNavigation();
|
|
243
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
244
|
-
const handleSubmit = async (values) => {
|
|
245
|
-
if (!values.to?.trim()) {
|
|
232
|
+
if (mode.type !== 'forward' && !values.body?.trim()) {
|
|
246
233
|
await showToast({
|
|
247
234
|
style: Toast.Style.Failure,
|
|
248
|
-
title: '
|
|
235
|
+
title: 'Message is required',
|
|
249
236
|
});
|
|
250
237
|
return;
|
|
251
238
|
}
|
|
252
239
|
setIsLoading(true);
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
240
|
+
const { client } = await getClient([selectedAccount]);
|
|
241
|
+
let result;
|
|
242
|
+
if (mode.type === 'forward') {
|
|
243
|
+
const recipients = (values.to ?? '')
|
|
244
|
+
.split(',')
|
|
245
|
+
.map((e) => ({ email: e.trim() }))
|
|
246
|
+
.filter((e) => e.email);
|
|
247
|
+
result = await client.forwardThread({
|
|
248
|
+
threadId: mode.threadId,
|
|
249
|
+
to: recipients,
|
|
250
|
+
body: values.body || undefined,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
result = await client.replyToThread({
|
|
255
|
+
threadId: mode.threadId,
|
|
256
|
+
body: values.body ?? '',
|
|
257
|
+
replyAll: mode.replyAll,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
263
260
|
setIsLoading(false);
|
|
264
261
|
if (result instanceof Error) {
|
|
265
|
-
await showFailureToast(result, {
|
|
262
|
+
await showFailureToast(result, {
|
|
263
|
+
title: mode.type === 'forward' ? 'Failed to forward' : 'Failed to send reply',
|
|
264
|
+
});
|
|
266
265
|
return;
|
|
267
266
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
});
|
|
272
|
-
|
|
267
|
+
const successTitle = mode.type === 'forward'
|
|
268
|
+
? `Forwarded to ${values.to}`
|
|
269
|
+
: 'Reply sent';
|
|
270
|
+
await showToast({ style: Toast.Style.Success, title: successTitle });
|
|
271
|
+
onSent?.();
|
|
273
272
|
pop();
|
|
274
273
|
};
|
|
275
|
-
return (_jsxs(Form, { isLoading: isLoading, navigationTitle:
|
|
274
|
+
return (_jsxs(Form, { isLoading: isLoading, navigationTitle: navigationTitle, actions: _jsx(ActionPanel, { children: _jsx(Action.SubmitForm, { title: mode.type === 'forward' ? 'Forward' : 'Send Reply', onSubmit: handleSubmit }) }), children: [accounts.length > 1 && (_jsx(Form.Dropdown, { id: 'account', title: 'From', value: selectedAccount, onChange: (v) => setSelectedAccount(Array.isArray(v) ? v[0] ?? initialAccount : v), children: accounts.map((a) => (_jsx(Form.Dropdown.Item, { value: a.email, title: a.email }, a.email))) })), mode.type === 'forward' && (_jsx(Form.TextField, { id: 'to', title: 'To', placeholder: 'recipient@example.com' })), _jsx(Form.TextArea, { id: 'body', title: 'Message', placeholder: bodyPlaceholder })] }));
|
|
276
275
|
}
|
|
277
276
|
// ---------------------------------------------------------------------------
|
|
278
277
|
// Thread Detail (full thread view, pushed via Enter)
|
|
279
278
|
// ---------------------------------------------------------------------------
|
|
280
|
-
function ThreadDetail({ threadId, account, revalidate, }) {
|
|
279
|
+
function ThreadDetail({ threadId, account, accounts, revalidate, }) {
|
|
281
280
|
const thread = useCachedPromise(async (tid, acct) => {
|
|
282
281
|
const { client } = await getClient([acct]);
|
|
283
282
|
const result = await client.getThread({ threadId: tid });
|
|
@@ -322,10 +321,10 @@ function ThreadDetail({ threadId, account, revalidate, }) {
|
|
|
322
321
|
.filter((l) => typeof l === 'string' && !l.startsWith('Label_')) // skip internal IDs
|
|
323
322
|
.slice(0, 10);
|
|
324
323
|
const latestMsg = messages[messages.length - 1];
|
|
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(
|
|
324
|
+
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(ComposeForm, { mode: { type: 'reply', threadId }, initialAccount: account, accounts: accounts, onSent: revalidate }) }), _jsx(Action.Push, { title: 'Reply All', icon: Icon.Reply, shortcut: {
|
|
326
325
|
modifiers: ['ctrl', 'shift'],
|
|
327
326
|
key: 'r',
|
|
328
|
-
}, target: _jsx(
|
|
327
|
+
}, target: _jsx(ComposeForm, { mode: { type: 'reply', threadId, replyAll: true }, initialAccount: account, accounts: accounts, onSent: revalidate }) }), _jsx(Action.Push, { title: 'Forward', icon: Icon.Forward, shortcut: { modifiers: ['ctrl'], key: 'f' }, target: _jsx(ComposeForm, { mode: { type: 'forward', threadId }, initialAccount: account, accounts: accounts, onSent: 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) }))] })] }) }));
|
|
329
328
|
}
|
|
330
329
|
// ---------------------------------------------------------------------------
|
|
331
330
|
// Main Command
|
|
@@ -335,7 +334,19 @@ export default function Command() {
|
|
|
335
334
|
const [searchText, setSearchText] = useState('');
|
|
336
335
|
const [isShowingDetail, setIsShowingDetail] = useState(true);
|
|
337
336
|
const [selectedThreads, setSelectedThreads] = useState([]);
|
|
337
|
+
const [activeMutations, setActiveMutations] = useState(0);
|
|
338
|
+
const isMutating = activeMutations > 0;
|
|
338
339
|
const { height: terminalRows } = useTerminalDimensions();
|
|
340
|
+
/** Wrap async mutation calls to track global loading state. */
|
|
341
|
+
const withMutation = useCallback(async (fn) => {
|
|
342
|
+
setActiveMutations((n) => n + 1);
|
|
343
|
+
try {
|
|
344
|
+
return await fn();
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
setActiveMutations((n) => n - 1);
|
|
348
|
+
}
|
|
349
|
+
}, []);
|
|
339
350
|
const pageSize = getPageSizeFromTerminalHeight(terminalRows);
|
|
340
351
|
const accounts = useAccounts();
|
|
341
352
|
const accountList = accounts.data ?? [];
|
|
@@ -462,34 +473,36 @@ export default function Command() {
|
|
|
462
473
|
const handleBulkAction = useCallback(async (actionName, fn) => {
|
|
463
474
|
if (selectedThreads.length === 0)
|
|
464
475
|
return;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
for (const [acct, ids] of byAccount) {
|
|
476
|
-
const { client } = await getClient([acct]);
|
|
477
|
-
const result = await fn(client, ids);
|
|
478
|
-
if (result instanceof Error) {
|
|
479
|
-
await showFailureToast(result, {
|
|
480
|
-
title: `Failed to ${actionName}`,
|
|
481
|
-
});
|
|
482
|
-
return;
|
|
476
|
+
await withMutation(async () => {
|
|
477
|
+
// Group selected threads by account
|
|
478
|
+
const byAccount = new Map();
|
|
479
|
+
for (const tid of selectedThreads) {
|
|
480
|
+
const thread = allThreads.find((t) => t.id === tid);
|
|
481
|
+
if (!thread)
|
|
482
|
+
continue;
|
|
483
|
+
const list = byAccount.get(thread.account) ?? [];
|
|
484
|
+
list.push(tid);
|
|
485
|
+
byAccount.set(thread.account, list);
|
|
483
486
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
487
|
+
for (const [acct, ids] of byAccount) {
|
|
488
|
+
const { client } = await getClient([acct]);
|
|
489
|
+
const result = await fn(client, ids);
|
|
490
|
+
if (result instanceof Error) {
|
|
491
|
+
await showFailureToast(result, {
|
|
492
|
+
title: `Failed to ${actionName}`,
|
|
493
|
+
});
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
await showToast({
|
|
498
|
+
style: Toast.Style.Success,
|
|
499
|
+
title: `${actionName}: ${selectedThreads.length} thread(s)`,
|
|
500
|
+
});
|
|
501
|
+
setSelectedThreads([]);
|
|
502
|
+
revalidate();
|
|
488
503
|
});
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}, [selectedThreads, allThreads, revalidate]);
|
|
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) => {
|
|
504
|
+
}, [selectedThreads, allThreads, revalidate, withMutation]);
|
|
505
|
+
return (_jsx(List, { isLoading: isLoading || accounts.isLoading || isMutating, isShowingDetail: isShowingDetail, spacingMode: LIST_SPACING_MODE, 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) => {
|
|
493
506
|
const isSelected = selectedThreads.includes(thread.id);
|
|
494
507
|
const hasSelection = selectedThreads.length > 0;
|
|
495
508
|
// Icon: selection mode or status
|
|
@@ -527,114 +540,68 @@ export default function Command() {
|
|
|
527
540
|
thread.from.email,
|
|
528
541
|
thread.from.name ?? '',
|
|
529
542
|
thread.account,
|
|
530
|
-
], detail: detail, actions:
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
543
|
+
], detail: detail, actions: _jsx(ActionPanel, { children: hasSelection ? (
|
|
544
|
+
// ─────────────────────────────────────────────
|
|
545
|
+
// SELECTION MODE
|
|
546
|
+
// ─────────────────────────────────────────────
|
|
547
|
+
_jsxs(_Fragment, { children: [_jsxs(ActionPanel.Section, { title: 'Selection', children: [_jsx(Action, { title: isSelected ? 'Deselect Thread' : 'Select Thread', icon: isSelected ? Icon.Circle : Icon.CheckCircle, shortcut: { modifiers: ['ctrl'], key: 'x' }, 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, onAction: () => handleBulkAction('Trashed', async (c, ids) => {
|
|
548
|
+
for (const id of ids) {
|
|
549
|
+
await c.trash({ threadId: id });
|
|
550
|
+
}
|
|
551
|
+
}) }), _jsx(Action, { title: 'Deselect All', icon: Icon.XMarkCircle, onAction: () => setSelectedThreads([]) })] }), _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) })] })] })) : (
|
|
552
|
+
// ─────────────────────────────────────────────
|
|
553
|
+
// NORMAL MODE
|
|
554
|
+
// ─────────────────────────────────────────────
|
|
555
|
+
_jsxs(_Fragment, { children: [_jsxs(ActionPanel.Section, { children: [_jsx(Action.Push, { title: 'Open Thread', icon: Icon.Eye, target: _jsx(ThreadDetail, { threadId: thread.id, account: thread.account, accounts: accountList, revalidate: revalidate }) }), _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: () => withMutation(async () => {
|
|
556
|
+
const { client } = await getClient([thread.account]);
|
|
557
|
+
const result = thread.unread
|
|
558
|
+
? await client.markAsRead({ threadIds: [thread.id] })
|
|
559
|
+
: await client.markAsUnread({ threadIds: [thread.id] });
|
|
560
|
+
if (result instanceof Error) {
|
|
561
|
+
await showFailureToast(result);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
await showToast({
|
|
565
|
+
style: Toast.Style.Success,
|
|
566
|
+
title: thread.unread ? 'Marked as read' : 'Marked as unread',
|
|
540
567
|
});
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
threadIds: [thread.id],
|
|
553
|
-
})
|
|
554
|
-
: await client.markAsUnread({
|
|
555
|
-
threadIds: [thread.id],
|
|
568
|
+
revalidate();
|
|
569
|
+
}) }), _jsx(Action, { title: 'Archive', icon: Icon.Tray, shortcut: { modifiers: ['ctrl'], key: 'e' }, onAction: () => withMutation(async () => {
|
|
570
|
+
const { client } = await getClient([thread.account]);
|
|
571
|
+
const result = await client.archive({ threadIds: [thread.id] });
|
|
572
|
+
if (result instanceof Error) {
|
|
573
|
+
await showFailureToast(result);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
await showToast({
|
|
577
|
+
style: Toast.Style.Success,
|
|
578
|
+
title: 'Archived',
|
|
556
579
|
});
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}, onAction: async () => {
|
|
572
|
-
const { client } = await getClient([thread.account]);
|
|
573
|
-
const result = await client.archive({
|
|
574
|
-
threadIds: [thread.id],
|
|
575
|
-
});
|
|
576
|
-
if (result instanceof Error) {
|
|
577
|
-
await showFailureToast(result);
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
await showToast({
|
|
581
|
-
style: Toast.Style.Success,
|
|
582
|
-
title: 'Archived',
|
|
583
|
-
});
|
|
584
|
-
revalidate();
|
|
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 () => {
|
|
591
|
-
const { client } = await getClient([thread.account]);
|
|
592
|
-
const isStarred = thread.labelIds.includes('STARRED');
|
|
593
|
-
const result = isStarred
|
|
594
|
-
? await client.unstar({
|
|
595
|
-
threadIds: [thread.id],
|
|
596
|
-
})
|
|
597
|
-
: await client.star({
|
|
598
|
-
threadIds: [thread.id],
|
|
580
|
+
revalidate();
|
|
581
|
+
}) }), _jsx(Action, { title: thread.labelIds.includes('STARRED') ? 'Unstar' : 'Star', icon: Icon.Star, shortcut: { modifiers: ['ctrl'], key: 's' }, onAction: () => withMutation(async () => {
|
|
582
|
+
const { client } = await getClient([thread.account]);
|
|
583
|
+
const isStarred = thread.labelIds.includes('STARRED');
|
|
584
|
+
const result = isStarred
|
|
585
|
+
? await client.unstar({ threadIds: [thread.id] })
|
|
586
|
+
: await client.star({ threadIds: [thread.id] });
|
|
587
|
+
if (result instanceof Error) {
|
|
588
|
+
await showFailureToast(result);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
await showToast({
|
|
592
|
+
style: Toast.Style.Success,
|
|
593
|
+
title: isStarred ? 'Unstarred' : 'Starred',
|
|
599
594
|
});
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}
|
|
595
|
+
revalidate();
|
|
596
|
+
}) })] }), _jsxs(ActionPanel.Section, { title: 'Reply & Forward', children: [_jsx(Action.Push, { title: 'Reply', icon: Icon.Reply, shortcut: { modifiers: ['ctrl'], key: 'r' }, target: _jsx(ComposeForm, { mode: { type: 'reply', threadId: thread.id }, initialAccount: thread.account, accounts: accountList, onSent: revalidate }) }), _jsx(Action.Push, { title: 'Reply All', icon: Icon.Reply, shortcut: { modifiers: ['ctrl', 'shift'], key: 'r' }, target: _jsx(ComposeForm, { mode: { type: 'reply', threadId: thread.id, replyAll: true }, initialAccount: thread.account, accounts: accountList, onSent: revalidate }) }), _jsx(Action.Push, { title: 'Forward', icon: Icon.Forward, shortcut: { modifiers: ['ctrl'], key: 'f' }, target: _jsx(ComposeForm, { mode: { type: 'forward', threadId: thread.id }, initialAccount: thread.account, accounts: accountList, onSent: 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, shortcut: { modifiers: ['ctrl'], key: 'backspace' }, onAction: () => withMutation(async () => {
|
|
597
|
+
const { client } = await getClient([thread.account]);
|
|
598
|
+
await client.trash({ threadId: thread.id });
|
|
604
599
|
await showToast({
|
|
605
600
|
style: Toast.Style.Success,
|
|
606
|
-
title:
|
|
601
|
+
title: 'Trashed',
|
|
607
602
|
});
|
|
608
603
|
revalidate();
|
|
609
|
-
} })
|
|
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 () => {
|
|
622
|
-
const { client } = await getClient([thread.account]);
|
|
623
|
-
await client.trash({
|
|
624
|
-
threadId: thread.id,
|
|
625
|
-
});
|
|
626
|
-
await showToast({
|
|
627
|
-
style: Toast.Style.Success,
|
|
628
|
-
title: 'Trashed',
|
|
629
|
-
});
|
|
630
|
-
revalidate();
|
|
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}`));
|
|
604
|
+
}) }) }), _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}`));
|
|
638
605
|
}) }, section.name))) }));
|
|
639
606
|
}
|
|
640
607
|
// ---------------------------------------------------------------------------
|