feat: add plugin hooks for compose, attachments, search, lifecycle, and routing
This commit is contained in:
+54
-2
@@ -23,7 +23,7 @@ import {
|
||||
taskHooks, templateHooks, smimeHooks, vacationHooks,
|
||||
uiHooks, themeHooks, toastHooks, dragDropHooks,
|
||||
keyboardHooks, appLifecycleHooks, accountSecurityHooks,
|
||||
sidebarAppHooks, avatarHooks, renderHooks,
|
||||
sidebarAppHooks, avatarHooks, renderHooks, routerHooks,
|
||||
} from './plugin-hooks';
|
||||
import { createPluginI18n } from './plugin-i18n';
|
||||
import { toast as appToast } from '@/stores/toast-store';
|
||||
@@ -187,6 +187,22 @@ export interface PluginHooksAPI {
|
||||
onQuotaChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
/** Intercept - receives MailtoContext, return false to prevent the system mail client */
|
||||
onMailtoIntercept: (handler: (ctx: import('./plugin-types').MailtoContext) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||
/** Transform - receives the OutgoingEmail and returns a (possibly modified) copy */
|
||||
onTransformOutgoingEmail: (handler: (email: import('./plugin-types').OutgoingEmail) => import('./plugin-types').OutgoingEmail | void | Promise<import('./plugin-types').OutgoingEmail | void>) => Disposable;
|
||||
/** Intercept - receives ReplyContext, return false to cancel */
|
||||
onBeforeReply: (handler: (ctx: import('./plugin-types').ReplyContext) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||
onBeforeReplyAll: (handler: (ctx: import('./plugin-types').ReplyContext) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||
onBeforeForward: (handler: (ctx: import('./plugin-types').ReplyContext) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||
/** Intercept - receives AttachmentInfo, return false to refuse the upload */
|
||||
onBeforeAttachmentUpload: (handler: (info: import('./plugin-types').AttachmentInfo) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||
onAfterAttachmentUpload: (handler: (info: import('./plugin-types').AttachmentInfo) => void) => Disposable;
|
||||
onAttachmentDownload: (handler: (info: import('./plugin-types').AttachmentInfo) => void) => Disposable;
|
||||
/** Transform - receives AttachmentPreview, may return a modified preview */
|
||||
onAttachmentPreview: (handler: (preview: import('./plugin-types').AttachmentPreview, info: import('./plugin-types').AttachmentInfo) => import('./plugin-types').AttachmentPreview | void | Promise<import('./plugin-types').AttachmentPreview | void>) => Disposable;
|
||||
/** Transform - receives ExternalSearchResult[] and returns an extended array */
|
||||
onProvideSearchResults: (handler: (results: import('./plugin-types').ExternalSearchResult[], ctx: { query: string; filters: import('./plugin-types').SearchFilters }) => import('./plugin-types').ExternalSearchResult[] | void | Promise<import('./plugin-types').ExternalSearchResult[] | void>) => Disposable;
|
||||
/** Observer - debounced snapshot of the composer draft */
|
||||
onDraftChange: (handler: (draft: import('./plugin-types').DraftView) => void) => Disposable;
|
||||
// Calendar
|
||||
onCalendarEventOpen: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onBeforeEventCreate: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
@@ -204,6 +220,8 @@ export interface PluginHooksAPI {
|
||||
onICalSubscriptionChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onCalendarAlert: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onCalendarAlertAcknowledge: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
/** Transform - receives ConflictWarning[] and returns an extended array */
|
||||
onCheckEventConflicts: (handler: (warnings: import('./plugin-types').ConflictWarning[], ctx: { event: import('./plugin-types').CalendarEventFormView }) => import('./plugin-types').ConflictWarning[] | void | Promise<import('./plugin-types').ConflictWarning[] | void>) => Disposable;
|
||||
// Calendar Form
|
||||
onCalendarEventFormOpen: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onCalendarEventFormSave: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
@@ -220,6 +238,8 @@ export interface PluginHooksAPI {
|
||||
onContactGroupChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onContactGroupMemberChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onContactMove: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
/** Transform - receives RecipientSuggestion[] and returns an extended array */
|
||||
onProvideRecipientSuggestions: (handler: (suggestions: import('./plugin-types').RecipientSuggestion[], ctx: { query: string }) => import('./plugin-types').RecipientSuggestion[] | void | Promise<import('./plugin-types').RecipientSuggestion[] | void>) => Disposable;
|
||||
// Files
|
||||
onFileNavigate: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onBeforeFileUpload: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
@@ -297,6 +317,10 @@ export interface PluginHooksAPI {
|
||||
onColumnResize: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onMobileBack: (handler: () => void) => Disposable;
|
||||
onMobileViewSwitch: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
/** Intercept - receives ExternalLinkContext, return false to cancel navigation */
|
||||
onBeforeExternalLink: (handler: (ctx: import('./plugin-types').ExternalLinkContext) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||
/** Observer - debounced text-selection change */
|
||||
onTextSelectionChange: (handler: (ctx: import('./plugin-types').SelectionContext) => void) => Disposable;
|
||||
// Theme
|
||||
onThemeChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onCustomThemeChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
@@ -305,6 +329,8 @@ export interface PluginHooksAPI {
|
||||
onToastShow: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onToastDismiss: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onBrowserNotification: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
/** Observer fired when an OS-level notification is clicked */
|
||||
onNotificationClick: (handler: (ctx: { tag: string; data?: unknown }) => void) => Disposable;
|
||||
// Drag & Drop
|
||||
onDragStart: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onDragEnd: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
@@ -320,6 +346,12 @@ export interface PluginHooksAPI {
|
||||
onBeforeUnload: (handler: () => void) => Disposable;
|
||||
onAppError: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
onInterval: (handler: () => void, intervalMs: number) => Disposable;
|
||||
/** Observer - browser window focus / blur */
|
||||
onWindowFocus: (handler: () => void) => Disposable;
|
||||
onWindowBlur: (handler: () => void) => Disposable;
|
||||
/** Observer - network connectivity transitions */
|
||||
onOnline: (handler: () => void) => Disposable;
|
||||
onOffline: (handler: () => void) => Disposable;
|
||||
// Account Security
|
||||
onPasswordChange: (handler: () => void) => Disposable;
|
||||
onTotpChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
@@ -335,6 +367,11 @@ export interface PluginHooksAPI {
|
||||
// Render - transform hook for email list row badges
|
||||
// Handler: (badges: EmailListBadge[], ctx: { emailId: string; email: EmailReadView }) => EmailListBadge[]
|
||||
onEmailListItemRender: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||
// Router
|
||||
/** Observer - fired on every in-app navigation. RouteContext.from holds the previous path. */
|
||||
onNavigate: (handler: (ctx: import('./plugin-types').RouteContext) => void) => Disposable;
|
||||
onRouteEnter: (handler: (ctx: import('./plugin-types').RouteContext) => void) => Disposable;
|
||||
onRouteLeave: (handler: (ctx: import('./plugin-types').RouteContext) => void) => Disposable;
|
||||
}
|
||||
|
||||
// --- Permission mapping for hooks ----------------------------
|
||||
@@ -350,7 +387,13 @@ const HOOK_PERMISSIONS: Record<string, Permission> = {
|
||||
onEmailSelectionChange: 'email:read', onNewEmailReceived: 'email:read',
|
||||
onPushConnectionChange: 'email:read', onQuotaChange: 'email:read',
|
||||
onMailtoIntercept: 'email:read', onEmailListItemRender: 'email:read',
|
||||
onBeforeReply: 'email:read', onBeforeReplyAll: 'email:read',
|
||||
onBeforeForward: 'email:read', onAttachmentDownload: 'email:read',
|
||||
onAttachmentPreview: 'email:read', onProvideSearchResults: 'email:read',
|
||||
onDraftChange: 'email:read',
|
||||
onBeforeAttachmentUpload: 'email:write', onAfterAttachmentUpload: 'email:write',
|
||||
onBeforeEmailSend: 'email:send', onAfterEmailSend: 'email:send',
|
||||
onTransformOutgoingEmail: 'email:send',
|
||||
onBeforeEmailDelete: 'email:write', onAfterEmailDelete: 'email:write',
|
||||
onBeforeEmailMove: 'email:write', onAfterEmailMove: 'email:write',
|
||||
onEmailArchive: 'email:write', onEmailUnarchive: 'email:write',
|
||||
@@ -362,6 +405,7 @@ const HOOK_PERMISSIONS: Record<string, Permission> = {
|
||||
onCalendarEventOpen: 'calendar:read', onCalendarDateChange: 'calendar:read',
|
||||
onCalendarViewChange: 'calendar:read', onCalendarVisibilityToggle: 'calendar:read',
|
||||
onCalendarAlert: 'calendar:read', onCalendarAlertAcknowledge: 'calendar:read',
|
||||
onCheckEventConflicts: 'calendar:read',
|
||||
onCalendarEventFormOpen: 'calendar:read', onCalendarEventFormSave: 'calendar:write',
|
||||
onBeforeEventCreate: 'calendar:write', onAfterEventCreate: 'calendar:write',
|
||||
onBeforeEventUpdate: 'calendar:write', onAfterEventUpdate: 'calendar:write',
|
||||
@@ -370,6 +414,7 @@ const HOOK_PERMISSIONS: Record<string, Permission> = {
|
||||
onCalendarChange: 'calendar:write', onICalSubscriptionChange: 'calendar:write',
|
||||
// Contacts
|
||||
onContactOpen: 'contacts:read', onContactSelectionChange: 'contacts:read',
|
||||
onProvideRecipientSuggestions: 'contacts:read',
|
||||
onBeforeContactCreate: 'contacts:write', onAfterContactCreate: 'contacts:write',
|
||||
onBeforeContactUpdate: 'contacts:write', onAfterContactUpdate: 'contacts:write',
|
||||
onBeforeContactDelete: 'contacts:write', onAfterContactDelete: 'contacts:write',
|
||||
@@ -419,12 +464,13 @@ const HOOK_PERMISSIONS: Record<string, Permission> = {
|
||||
onSidebarCollapse: 'ui:observe', onDeviceTypeChange: 'ui:observe',
|
||||
onColumnResize: 'ui:observe', onMobileBack: 'ui:observe',
|
||||
onMobileViewSwitch: 'ui:observe',
|
||||
onBeforeExternalLink: 'ui:observe', onTextSelectionChange: 'ui:observe',
|
||||
// Theme
|
||||
onThemeChange: 'ui:observe', onCustomThemeChange: 'ui:observe',
|
||||
onLocaleChange: 'ui:observe',
|
||||
// Toast
|
||||
onToastShow: 'ui:observe', onToastDismiss: 'ui:observe',
|
||||
onBrowserNotification: 'ui:observe',
|
||||
onBrowserNotification: 'ui:observe', onNotificationClick: 'ui:observe',
|
||||
// Drag & Drop
|
||||
onDragStart: 'ui:observe', onDragEnd: 'ui:observe',
|
||||
onEmailDrop: 'ui:observe', onTagDrop: 'ui:observe',
|
||||
@@ -435,6 +481,8 @@ const HOOK_PERMISSIONS: Record<string, Permission> = {
|
||||
onAppReady: 'app:lifecycle', onVisibilityChange: 'app:lifecycle',
|
||||
onBeforeUnload: 'app:lifecycle', onAppError: 'app:lifecycle',
|
||||
onInterval: 'app:lifecycle',
|
||||
onWindowFocus: 'app:lifecycle', onWindowBlur: 'app:lifecycle',
|
||||
onOnline: 'app:lifecycle', onOffline: 'app:lifecycle',
|
||||
// Account Security
|
||||
onPasswordChange: 'security:read', onTotpChange: 'security:read',
|
||||
onAppPasswordChange: 'security:read', onEncryptionChange: 'security:read',
|
||||
@@ -444,6 +492,8 @@ const HOOK_PERMISSIONS: Record<string, Permission> = {
|
||||
onSidebarAppChange: 'ui:observe',
|
||||
// Avatar
|
||||
onAvatarResolve: 'email:read',
|
||||
// Router
|
||||
onNavigate: 'ui:observe', onRouteEnter: 'ui:observe', onRouteLeave: 'ui:observe',
|
||||
};
|
||||
|
||||
// Map hook names → actual HookBus instances
|
||||
@@ -494,6 +544,8 @@ const HOOK_BUSES: Record<string, { register: (pluginId: string, handler: (...arg
|
||||
...Object.fromEntries(Object.entries(avatarHooks)),
|
||||
// Render
|
||||
...Object.fromEntries(Object.entries(renderHooks)),
|
||||
// Router
|
||||
...Object.fromEntries(Object.entries(routerHooks)),
|
||||
};
|
||||
|
||||
// --- Slot registration bridge --------------------------------
|
||||
|
||||
+70
-1
@@ -207,6 +207,38 @@ export const emailHooks = {
|
||||
// Intercept hook - fired when a mailto: link is clicked.
|
||||
// Return false to prevent the browser from opening the system mail client.
|
||||
onMailtoIntercept: new HookBus(),
|
||||
// Transform hook - fires after onBeforeEmailSend has not cancelled,
|
||||
// immediately before the message is handed to the JMAP submission. Handlers
|
||||
// receive an OutgoingEmail and return a modified copy (or undefined to pass
|
||||
// through). Use to inject signatures, scrub tracking pixels from forwarded
|
||||
// bodies, encrypt content, or rewrite links.
|
||||
onTransformOutgoingEmail: new HookBus(),
|
||||
// Intercept hooks fired when the user clicks Reply / Reply-All / Forward.
|
||||
// Handler receives a ReplyContext; return false to cancel.
|
||||
onBeforeReply: new HookBus(),
|
||||
onBeforeReplyAll: new HookBus(),
|
||||
onBeforeForward: new HookBus(),
|
||||
// Intercept hook fired before a file is added to the composer as an
|
||||
// attachment. Handler receives AttachmentInfo (size/type/name only - the
|
||||
// raw file is not exposed). Return false to refuse the upload.
|
||||
onBeforeAttachmentUpload: new HookBus(),
|
||||
// Observer fired after an attachment has been uploaded and its blobId is
|
||||
// available. Handler receives AttachmentInfo with `blobId` populated.
|
||||
onAfterAttachmentUpload: new HookBus(),
|
||||
// Observer fired when the user downloads an attachment from a message.
|
||||
onAttachmentDownload: new HookBus(),
|
||||
// Transform hook - lets plugins replace the preview URL or supply a custom
|
||||
// renderer for an attachment. Initial value: AttachmentPreview, second
|
||||
// argument: AttachmentInfo.
|
||||
onAttachmentPreview: new HookBus(),
|
||||
// Transform hook - lets plugins contribute additional results to the global
|
||||
// search panel. Initial value: ExternalSearchResult[]. Second argument:
|
||||
// { query: string, filters: SearchFilters }.
|
||||
onProvideSearchResults: new HookBus(),
|
||||
// Observer fired (debounced) when the composer draft body, subject, or
|
||||
// recipients change. Handler receives a DraftView snapshot. Use for AI
|
||||
// assistants, grammar checkers, etc.
|
||||
onDraftChange: new HookBus(),
|
||||
};
|
||||
|
||||
// §7.2 Calendar Hooks
|
||||
@@ -227,6 +259,10 @@ export const calendarHooks = {
|
||||
onICalSubscriptionChange: new HookBus(),
|
||||
onCalendarAlert: new HookBus(),
|
||||
onCalendarAlertAcknowledge: new HookBus(),
|
||||
// Transform hook - fires when the event form is open and start/end change.
|
||||
// Initial value: ConflictWarning[], second argument: { event: CalendarEventFormView }.
|
||||
// Plugins return an extended array; the form renders each warning inline.
|
||||
onCheckEventConflicts: new HookBus(),
|
||||
};
|
||||
|
||||
// §7.2b Calendar Form Hooks (UI integration)
|
||||
@@ -249,6 +285,10 @@ export const contactHooks = {
|
||||
onContactGroupChange: new HookBus(),
|
||||
onContactGroupMemberChange: new HookBus(),
|
||||
onContactMove: new HookBus(),
|
||||
// Transform hook - lets plugins contribute extra recipient suggestions to
|
||||
// the composer's autocomplete. Initial value: RecipientSuggestion[],
|
||||
// second argument: { query: string }.
|
||||
onProvideRecipientSuggestions: new HookBus(),
|
||||
};
|
||||
|
||||
// §7.4 File Hooks
|
||||
@@ -358,6 +398,14 @@ export const uiHooks = {
|
||||
onColumnResize: new HookBus(),
|
||||
onMobileBack: new HookBus(),
|
||||
onMobileViewSwitch: new HookBus(),
|
||||
// Intercept hook - fires when the user clicks an external link inside the
|
||||
// app (typically inside an email body iframe). Handler receives
|
||||
// ExternalLinkContext; return false to cancel the navigation. Mutate
|
||||
// `href` in place to rewrite (e.g. strip UTM params, route via a proxy).
|
||||
onBeforeExternalLink: new HookBus(),
|
||||
// Observer (debounced) fired when the user changes the active text
|
||||
// selection inside an app surface. Receives SelectionContext.
|
||||
onTextSelectionChange: new HookBus(),
|
||||
};
|
||||
|
||||
// §7.14 Theme Hooks
|
||||
@@ -384,6 +432,10 @@ export const toastHooks = {
|
||||
onToastShow: new HookBus(),
|
||||
onToastDismiss: new HookBus(),
|
||||
onBrowserNotification: new HookBus(),
|
||||
// Observer fired when the user clicks an OS-level browser notification
|
||||
// dispatched by the host. Handler receives { tag: string, data?: unknown }
|
||||
// matching the original notification options.
|
||||
onNotificationClick: new HookBus(),
|
||||
};
|
||||
|
||||
// §7.16 Drag & Drop Hooks
|
||||
@@ -408,6 +460,14 @@ export const appLifecycleHooks = {
|
||||
onBeforeUnload: new HookBus(),
|
||||
onAppError: new HookBus(),
|
||||
onInterval: new HookBus(),
|
||||
// Observer fired when the browser window receives focus / blur. Useful for
|
||||
// refresh-on-focus behaviour (re-poll, recheck staleness, pause timers).
|
||||
onWindowFocus: new HookBus(),
|
||||
onWindowBlur: new HookBus(),
|
||||
// Observer fired when network connectivity transitions. Mirrors the
|
||||
// navigator online / offline events.
|
||||
onOnline: new HookBus(),
|
||||
onOffline: new HookBus(),
|
||||
};
|
||||
|
||||
// §7.19 Account Security Hooks
|
||||
@@ -433,6 +493,15 @@ export const avatarHooks = {
|
||||
onAvatarResolve: new HookBus(),
|
||||
};
|
||||
|
||||
// §7.23 Router Hooks
|
||||
// Observers fired by the app router. Handlers receive a RouteContext; on
|
||||
// onNavigate the previous path is exposed via `from`.
|
||||
export const routerHooks = {
|
||||
onNavigate: new HookBus(),
|
||||
onRouteEnter: new HookBus(),
|
||||
onRouteLeave: new HookBus(),
|
||||
};
|
||||
|
||||
// §7.22 Render Hooks
|
||||
export const renderHooks = {
|
||||
// Transform hook - runs for each visible email list row.
|
||||
@@ -451,7 +520,7 @@ const allHookGroups = [
|
||||
taskHooks, templateHooks, smimeHooks, vacationHooks,
|
||||
uiHooks, themeHooks, toastHooks, dragDropHooks,
|
||||
keyboardHooks, appLifecycleHooks, accountSecurityHooks, sidebarAppHooks,
|
||||
avatarHooks, renderHooks,
|
||||
avatarHooks, renderHooks, routerHooks,
|
||||
];
|
||||
|
||||
export function removeAllPluginHooks(pluginId: string): void {
|
||||
|
||||
@@ -522,6 +522,137 @@ export interface MailtoContext {
|
||||
body?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed to onTransformOutgoingEmail handlers as a transform value.
|
||||
* Handlers receive the email about to be sent and return a (possibly mutated)
|
||||
* copy. Use to inject signatures, rewrite links, strip tracking pixels from
|
||||
* forwards, encrypt the body, etc. Return undefined to pass through unchanged.
|
||||
*/
|
||||
export interface OutgoingEmail {
|
||||
to: string[];
|
||||
cc: string[];
|
||||
bcc: string[];
|
||||
subject: string;
|
||||
htmlBody: string;
|
||||
textBody: string;
|
||||
identityId: string;
|
||||
attachments: { name: string; type: string; size: number }[];
|
||||
/** Original message id when this is a reply or forward */
|
||||
inReplyTo?: string;
|
||||
/** Free-form custom headers added by the composer or earlier handlers */
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed to onBeforeReply / onBeforeReplyAll / onBeforeForward intercept hooks.
|
||||
* Return false to cancel the operation before the composer opens.
|
||||
*/
|
||||
export interface ReplyContext {
|
||||
originalEmailId: string;
|
||||
originalEmail: EmailReadView;
|
||||
mode: 'reply' | 'reply-all' | 'forward';
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes an attachment crossing an attachment hook (upload, download, preview).
|
||||
*/
|
||||
export interface AttachmentInfo {
|
||||
name: string;
|
||||
type: string;
|
||||
size: number;
|
||||
/** JMAP blob id, when known (download / preview / after-upload) */
|
||||
blobId?: string;
|
||||
/** The email this attachment belongs to (download / preview) */
|
||||
emailId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial value passed to the onAttachmentPreview transform hook. A handler
|
||||
* may return a different `previewUrl` (e.g. a proxied/sanitised URL) or a
|
||||
* React component descriptor identified by `customRenderer`. Return undefined
|
||||
* to pass through.
|
||||
*/
|
||||
export interface AttachmentPreview {
|
||||
previewUrl?: string;
|
||||
/** Optional plugin-supplied renderer key. The host resolves the renderer. */
|
||||
customRenderer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed to onBeforeExternalLink intercept handlers when the user clicks a
|
||||
* link that would navigate away from the app (typically inside an email body).
|
||||
* Return false to cancel the navigation. Mutate `href` to rewrite it.
|
||||
*/
|
||||
export interface ExternalLinkContext {
|
||||
href: string;
|
||||
/** Anchor target ('_blank', '_self', etc.) when set */
|
||||
target?: string;
|
||||
/** Email currently in view, when the click came from an email body */
|
||||
emailId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed to onTextSelectionChange observer when the user selects text inside
|
||||
* the app. Source identifies which surface produced the selection so plugins
|
||||
* can scope themselves (e.g. translate-on-select only inside emails).
|
||||
*/
|
||||
export interface SelectionContext {
|
||||
text: string;
|
||||
source: 'email-body' | 'composer' | 'task-detail' | 'event-detail' | 'other';
|
||||
emailId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by onCheckEventConflicts transform handlers. The form UI renders
|
||||
* each warning as an inline notice next to the event time fields.
|
||||
*/
|
||||
export interface ConflictWarning {
|
||||
/** Stable unique key per warning, used as React key */
|
||||
key: string;
|
||||
/** Short message — e.g. "Conflicts with: Team Standup" */
|
||||
message: string;
|
||||
severity?: 'info' | 'warning' | 'error';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by onProvideSearchResults transform handlers. Plugins extend the
|
||||
* initial array with their own results (CRM hits, Slack messages, etc.).
|
||||
* The host renders these in a grouped section below native email results.
|
||||
*/
|
||||
export interface ExternalSearchResult {
|
||||
/** Stable unique key */
|
||||
key: string;
|
||||
title: string;
|
||||
snippet: string;
|
||||
/** Plugin-handled action when the result row is clicked */
|
||||
onClick: () => void;
|
||||
/** Optional source label, e.g. "Slack", "Notion" */
|
||||
source?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by onProvideRecipientSuggestions transform handlers. Lets plugins
|
||||
* contribute non-contact suggestions (Slack handles, GitHub usernames, etc.)
|
||||
* to the recipient autocomplete in the composer.
|
||||
*/
|
||||
export interface RecipientSuggestion {
|
||||
name: string;
|
||||
email: string;
|
||||
/** Optional source label rendered as a small tag */
|
||||
source?: string;
|
||||
avatarUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed to router hooks (onNavigate, onRouteEnter, onRouteLeave).
|
||||
* Paths are app-internal, e.g. "/mail/inbox", "/calendar".
|
||||
*/
|
||||
export interface RouteContext {
|
||||
path: string;
|
||||
/** Previous path (only on onNavigate) */
|
||||
from?: string;
|
||||
}
|
||||
|
||||
// ─── Plugin i18n API ─────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user