mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
feat: tell plugins when toasts are shown and dismissed
This commit is contained in:
@@ -47,7 +47,7 @@ function isFired(hookName: string, groupName: string): boolean {
|
||||
const NOT_YET_WIRED = new Set<HookGroup>([
|
||||
'mail-list', 'mail-read', 'mail-compose', 'mail-send', 'mail-organize', 'mail-mailboxes', 'mail-search',
|
||||
'mail-sync', 'calendar', 'contacts', 'tasks', 'files', 'settings', 'identities',
|
||||
'filters', 'vacation', 'templates', 'auth', 'security', 'notifications', 'ui', 'keyboard',
|
||||
'filters', 'vacation', 'templates', 'auth', 'security', 'ui', 'keyboard',
|
||||
]);
|
||||
|
||||
describe('hook catalogue', () => {
|
||||
|
||||
@@ -268,9 +268,8 @@ export const HOOK_CATALOG = {
|
||||
onLocaleChange: H('theme', 'observer', 'ui:observe', '{ locale }', 'after the app language changed'),
|
||||
|
||||
// ─── Notifications ─────────────────────────────────────────
|
||||
onToastShow: H('notifications', 'observer', 'ui:observe', '{ type, message }', 'when the app shows a toast'),
|
||||
onToastShow: H('notifications', 'observer', 'ui:observe', '{ id, type, title, message }', 'when the app shows a toast'),
|
||||
onToastDismiss: H('notifications', 'observer', 'ui:observe', '{ id }', 'when a toast is dismissed'),
|
||||
onBrowserNotification: H('notifications', 'transform', 'email:read', '{ title, body, tag } | false, { kind: "mail" | "calendar" | "plugin" }', 'before a system notification is shown; return false to suppress it'),
|
||||
onNotificationClick: H('notifications', 'observer', 'email:read', '{ tag, data }', 'when a system notification is clicked'),
|
||||
|
||||
// ─── App lifecycle ─────────────────────────────────────────
|
||||
|
||||
@@ -685,7 +685,6 @@ export const themeHooks = {
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { clearAllHooks, toastHooks } from '@/lib/plugin-hooks';
|
||||
import { useToastStore } from '../toast-store';
|
||||
|
||||
beforeEach(() => {
|
||||
clearAllHooks();
|
||||
useToastStore.setState({ toasts: [] });
|
||||
});
|
||||
|
||||
describe('toast hooks', () => {
|
||||
it('tells plugins about a toast when it is shown and when it goes away', () => {
|
||||
const shown = vi.fn();
|
||||
const dismissed = vi.fn();
|
||||
toastHooks.onToastShow.register('t', shown);
|
||||
toastHooks.onToastDismiss.register('t', dismissed);
|
||||
|
||||
const id = useToastStore.getState().addToast({ type: 'success', title: 'Moved', message: '3 messages' });
|
||||
useToastStore.getState().removeToast(id);
|
||||
|
||||
expect(shown).toHaveBeenCalledWith({ id, type: 'success', title: 'Moved', message: '3 messages' });
|
||||
expect(dismissed).toHaveBeenCalledWith({ id });
|
||||
});
|
||||
|
||||
it('does not report a dismissal for a toast that is not there', () => {
|
||||
const dismissed = vi.fn();
|
||||
toastHooks.onToastDismiss.register('t', dismissed);
|
||||
|
||||
useToastStore.getState().removeToast('gone');
|
||||
|
||||
expect(dismissed).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { create } from "zustand";
|
||||
import { Toast, ToastAction } from "@/components/ui/toast";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
import { toastHooks } from "@/lib/plugin-hooks";
|
||||
|
||||
interface ToastStore {
|
||||
toasts: Toast[];
|
||||
@@ -11,7 +12,7 @@ interface ToastStore {
|
||||
clearToasts: () => void;
|
||||
}
|
||||
|
||||
export const useToastStore = create<ToastStore>((set) => ({
|
||||
export const useToastStore = create<ToastStore>((set, get) => ({
|
||||
toasts: [],
|
||||
|
||||
addToast: (toast) => {
|
||||
@@ -29,6 +30,9 @@ export const useToastStore = create<ToastStore>((set) => ({
|
||||
set((state) => ({
|
||||
toasts: [...state.toasts, newToast],
|
||||
}));
|
||||
void toastHooks.onToastShow.emit({
|
||||
id, type: toast.type, title: toast.title, message: toast.message ?? "",
|
||||
});
|
||||
return id;
|
||||
},
|
||||
|
||||
@@ -39,9 +43,11 @@ export const useToastStore = create<ToastStore>((set) => ({
|
||||
},
|
||||
|
||||
removeToast: (id) => {
|
||||
if (!get().toasts.some((t) => t.id === id)) return;
|
||||
set((state) => ({
|
||||
toasts: state.toasts.filter((toast) => toast.id !== id),
|
||||
}));
|
||||
void toastHooks.onToastDismiss.emit({ id });
|
||||
},
|
||||
|
||||
clearToasts: () => {
|
||||
|
||||
Reference in New Issue
Block a user