feat(plugins): resizable collapsible detail sidebar, auto-init on refresh
- Add email-detail-sidebar slot type for right-side plugin panels - Plugin sidebar inside EmailViewer with ResizeHandle (200-500px, drag/dbl-click reset) - Collapse/expand toggle with PanelRightClose/PanelRightOpen icons - Call initializePlugins() on mount so plugins survive page refresh - Fix plugin loader: call exposePluginExternals() before loading - Fix CSP: add blob: to script-src for plugin bundle loading - Fix enablePlugin: wire slot registration bridge before loading - Fix email-banner PluginSlot: pass email via extraProps - Fix sidebar-widget PluginSlot: move inside scrollable area - Emit emailHooks.onEmailOpen/onEmailClose from selectEmail()
This commit is contained in:
+11
-1
@@ -4,6 +4,7 @@ import type { IJMAPClient } from "@/lib/jmap/client-interface";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { useCalendarStore } from "@/stores/calendar-store";
|
||||
import { SearchFilters, DEFAULT_SEARCH_FILTERS, buildJMAPFilter, isFilterEmpty } from "@/lib/jmap/search-utils";
|
||||
import { emailHooks } from "@/lib/plugin-hooks";
|
||||
|
||||
interface EmailStore {
|
||||
emails: Email[];
|
||||
@@ -159,7 +160,16 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
|
||||
setEmails: (emails) => set({ emails }),
|
||||
setMailboxes: (mailboxes) => set({ mailboxes }),
|
||||
selectEmail: (email) => set({ selectedEmail: email, lastSelectedEmailId: email?.id ?? get().lastSelectedEmailId }),
|
||||
selectEmail: (email) => {
|
||||
const prev = get().selectedEmail;
|
||||
set({ selectedEmail: email, lastSelectedEmailId: email?.id ?? get().lastSelectedEmailId });
|
||||
if (prev && (!email || email.id !== prev.id)) {
|
||||
emailHooks.onEmailClose.emitSync(prev);
|
||||
}
|
||||
if (email && (!prev || email.id !== prev.id)) {
|
||||
emailHooks.onEmailOpen.emitSync(email);
|
||||
}
|
||||
},
|
||||
selectKeyword: (keyword) => set({
|
||||
selectedKeyword: keyword,
|
||||
selectedEmail: null,
|
||||
|
||||
@@ -19,7 +19,7 @@ import { removeAllPluginHooks } from '@/lib/plugin-hooks';
|
||||
|
||||
const SLOT_NAMES: SlotName[] = [
|
||||
'toolbar-actions', 'email-banner', 'email-footer', 'composer-toolbar',
|
||||
'sidebar-widget', 'settings-section', 'context-menu-email', 'navigation-rail-bottom',
|
||||
'sidebar-widget', 'email-detail-sidebar', 'settings-section', 'context-menu-email', 'navigation-rail-bottom',
|
||||
];
|
||||
|
||||
function emptySlots(): Record<SlotName, SlotRegistration[]> {
|
||||
@@ -136,6 +136,10 @@ export const usePluginStore = create<PluginStoreState>()(
|
||||
const plugin = plugins.find(p => p.id === id);
|
||||
if (!plugin) return;
|
||||
|
||||
// Ensure bridges are wired before loading (may not have run initializePlugins yet)
|
||||
setPluginStoreAccessor({ setPluginStatus: get().setPluginStatus });
|
||||
setSlotRegistrationBridge(get().registerSlot);
|
||||
|
||||
set({
|
||||
plugins: plugins.map(p =>
|
||||
p.id === id ? { ...p, enabled: true, status: 'enabled' as PluginStatus, error: undefined } : p
|
||||
|
||||
Reference in New Issue
Block a user