mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
68 lines
2.9 KiB
TypeScript
68 lines
2.9 KiB
TypeScript
// Plugins opening and closing the panel their slot sits in
|
|
// (api.ui.openPanel / closePanel / togglePanel): the composer's side panels
|
|
// and the reader's detail panel. A toolbar button, a shortcut or a
|
|
// right-click entry can then bring the plugin's panel up instead of a
|
|
// dialog. The panels subscribe here while they are on screen; a request
|
|
// nobody answers (no composer open, no message open, a phone) resolves to
|
|
// null, so the plugin can fall back to a dialog.
|
|
//
|
|
// When a panel opens on request, its slot gets a new `focusRequest` number
|
|
// in its props, so the plugin can put the focus where it belongs.
|
|
|
|
export const PANEL_SLOTS = ['composer-sidebar', 'composer-sidebar-right', 'email-detail-sidebar'] as const;
|
|
export type PanelSlot = typeof PANEL_SLOTS[number];
|
|
export type PanelMode = 'open' | 'close' | 'toggle';
|
|
|
|
export interface PanelRequest {
|
|
pluginId: string;
|
|
slot: PanelSlot;
|
|
mode: PanelMode;
|
|
/** For a composer panel: the composer it belongs to (from the plugin's click context or slot props). */
|
|
composerId?: string;
|
|
}
|
|
|
|
/** Answers with the panel's state after the request (true = open), or null when the request is not for it. */
|
|
export type PanelRequestHandler = (request: PanelRequest) => boolean | null;
|
|
|
|
const handlers = new Set<PanelRequestHandler>();
|
|
|
|
export function onPanelRequest(handler: PanelRequestHandler): () => void {
|
|
handlers.add(handler);
|
|
return () => { handlers.delete(handler); };
|
|
}
|
|
|
|
export function isPanelSlot(value: unknown): value is PanelSlot {
|
|
return typeof value === 'string' && (PANEL_SLOTS as readonly string[]).includes(value);
|
|
}
|
|
|
|
/**
|
|
* Check a plugin's request and deliver it: the panel's state afterwards, or
|
|
* null when no such panel is on screen. Throws with a message the plugin sees.
|
|
*/
|
|
export function requestPanel(pluginId: string, slot: unknown, mode: unknown, opts: unknown): boolean | null {
|
|
const method = `ui.${mode === 'open' ? 'openPanel' : mode === 'close' ? 'closePanel' : 'togglePanel'}`;
|
|
if (!isPanelSlot(slot)) throw new Error(`${method}: "${String(slot)}" is not a panel (${PANEL_SLOTS.join(', ')})`);
|
|
if (mode !== 'open' && mode !== 'close' && mode !== 'toggle') throw new Error(`${method}: unknown mode`);
|
|
let composerId: string | undefined;
|
|
if (opts !== undefined && opts !== null) {
|
|
if (typeof opts !== 'object') throw new Error(`${method}: options must be an object`);
|
|
const value = (opts as { composerId?: unknown }).composerId;
|
|
if (value !== undefined) {
|
|
if (typeof value !== 'string' || !value) throw new Error(`${method}: options.composerId must be a string`);
|
|
composerId = value;
|
|
}
|
|
}
|
|
const request: PanelRequest = { pluginId, slot, mode, ...(composerId ? { composerId } : {}) };
|
|
for (const handler of handlers) {
|
|
let answer: boolean | null = null;
|
|
try { answer = handler(request); } catch { answer = null; }
|
|
if (answer !== null) return answer;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Test helper. */
|
|
export function resetPanelRequests(): void {
|
|
handlers.clear();
|
|
}
|