feat: add message-list category tabs

This commit is contained in:
Linus Rath
2026-07-22 17:22:20 +02:00
parent 66bc10fa0f
commit 5105e000f5
15 changed files with 1158 additions and 11 deletions
+45
View File
@@ -249,6 +249,51 @@ function buildPluginApi(manifest: PluginManifest) {
downloadFile: (opts: { content: string; filename: string; contentType?: string }) =>
callApi('ui.downloadFile', [opts]) as Promise<void>,
},
// Email keyword mutations (permission: email:write). Keywords follow JMAP
// syntax, e.g. '$category-promotions' or '$label:<tagId>'.
email: {
setKeyword: (emailId: string, keyword: string, accountId?: string) =>
callApi('email.setKeyword', [emailId, keyword, accountId]) as Promise<void>,
removeKeyword: (emailId: string, keyword: string, accountId?: string) =>
callApi('email.removeKeyword', [emailId, keyword, accountId]) as Promise<void>,
},
// Message-list category tabs (permission: ui:message-list-tabs; categorize
// additionally needs email:write). The host renders the strip natively and
// ANDs the active tab's JMAP search filter (`query`) or keyword into the
// mailbox Email/query - see MessageListTabsConfig in plugin-types. Tabs
// are cleared automatically when the plugin unloads.
tabs: {
/** Register (or replace) this plugin's tab set. */
set: (config: unknown) => callApi('tabs.set', [config]) as Promise<void>,
/** Remove this plugin's tabs from the strip. */
clear: () => callApi('tabs.clear', []) as Promise<void>,
/** Current merged tabs, active tab id and unread counts. */
getState: () => callApi('tabs.getState', []) as Promise<{
tabs: unknown[]; activeTabId: string | null; tabCounts: Record<string, number>;
}>,
/** Re-query per-tab unread counts for the current mailbox. */
refreshCounts: () => callApi('tabs.refreshCounts', []) as Promise<Record<string, number>>,
/**
* Move messages to a tab (patches the category keywords via Email/set).
* Fires onBeforeEmailCategorize (cancellable) and onEmailCategorize.
* Resolves false when cancelled.
*/
categorize: (emailIds: string[], tabId: string) =>
callApi('tabs.categorize', [emailIds, tabId]) as Promise<boolean>,
},
// Sieve integration for delivery-time classification (permissions:
// filters:read / filters:write). Plugins never write scripts directly -
// they register an onSieveScriptGenerate transform hook and call
// regenerate(), so user filter rules and plugin sections coexist in the
// single active script.
sieve: {
isSupported: () => callApi('sieve.isSupported', []) as Promise<boolean>,
getActiveScript: () => callApi('sieve.getActiveScript', []) as Promise<{ id: string; name: string; content: string } | null>,
validateScript: (content: string) =>
callApi('sieve.validateScript', [content]) as Promise<{ isValid: boolean; errors?: string[] }>,
/** Rebuild + re-upload the active script, running onSieveScriptGenerate. */
regenerate: () => callApi('sieve.regenerate', []) as Promise<void>,
},
admin: {
getConfig: (key: string) => callApi('admin.getConfig', [key]),
getAllConfig: () => callApi('admin.getAllConfig', []),