fix(plugins): restore workbench refresh and bootstrap

This commit is contained in:
t8y2 committed 2026-09-14 23:01:04 +08:00
1 parent a5bb8cf1c1
commit 400ff6dd02
4 files changed
+48 -7

No files matched your search

+31 -1
View File
@@ -421,6 +421,8 @@ const activeTab = computed(() => queryStore.tabs.find((t) => t.id === queryStore
// iframe moved out of the DOM reloads from scratch, so KeepAlive/ContentArea
// remounts flash the whole webview and drop its live session state.
const mountedPluginWorkbenchTabs = computed(() => queryStore.tabs.filter((tab) => tab.mode === "plugin-workbench" && tab.pluginWorkbench));
type PluginWorkbenchTabHandle = { refresh: () => Promise<unknown> };
const pluginWorkbenchTabRefs = new Map<string, PluginWorkbenchTabHandle>();
const tabNavigationHistory = ref(createTabNavigationHistory());
let pendingTabHistoryNavigationId: string | null = null;
let detachedEventUnlisteners: Array<() => void> = [];
@@ -3144,6 +3146,23 @@ function handleNativeSelectAll(e: KeyboardEvent) {
if (shouldBlockAppNativeSelectAll(e)) e.preventDefault();
}
function setPluginWorkbenchTabRef(tabId: string, element: unknown) {
if (element && typeof element === "object" && "refresh" in element && typeof element.refresh === "function") {
pluginWorkbenchTabRefs.set(tabId, element as PluginWorkbenchTabHandle);
} else {
pluginWorkbenchTabRefs.delete(tabId);
}
}
function refreshActivePluginWorkbench(): boolean {
const tab = activeTab.value;
if (tab?.mode !== "plugin-workbench") return false;
const pluginWorkbench = pluginWorkbenchTabRefs.get(tab.id);
if (!pluginWorkbench) return false;
void pluginWorkbench.refresh();
return true;
}
async function closeActiveSurface() {
if (showSettingsPage.value) {
closeSettingsPage();
@@ -3303,6 +3322,11 @@ async function handleKeydown(e: KeyboardEvent) {
if (selectedSql.value.trim()) sendSelectionToAi(selectedSql.value);
return;
}
if (isModRShortcut(e) && refreshActivePluginWorkbench()) {
e.preventDefault();
e.stopPropagation();
return;
}
if (isModRShortcut(e) && e.target instanceof Element && contentAreaRef.value?.handleModRTarget(e.target)) {
e.preventDefault();
e.stopPropagation();
@@ -3876,7 +3900,13 @@ onUnmounted(() => {
yields the layout (display:none) while a plugin tab is
active, or both flex-1 siblings would split the column. -->
<div v-for="workbenchTab in mountedPluginWorkbenchTabs" :key="workbenchTab.id" v-show="activeTab && workbenchTab.id === activeTab.id" class="flex min-h-0 flex-1 flex-col">
<PluginWorkbenchTab :plugin-id="workbenchTab.pluginWorkbench!.pluginId" :contribution-id="workbenchTab.pluginWorkbench!.contributionId" :context="workbenchTab.pluginWorkbench!.context" @close-tab="queryStore.closeTab(workbenchTab.id)" />
<PluginWorkbenchTab
:ref="(element) => setPluginWorkbenchTabRef(workbenchTab.id, element)"
:plugin-id="workbenchTab.pluginWorkbench!.pluginId"
:contribution-id="workbenchTab.pluginWorkbench!.contributionId"
:context="workbenchTab.pluginWorkbench!.context"
@close-tab="queryStore.closeTab(workbenchTab.id)"
/>
</div>
</div>
</div>
@@ -205,9 +205,6 @@ function handleCancelClose() {
<div v-show="driverStoreActive || pluginCenterActive || settingsPageActive" data-special-page-workspace class="flex min-h-0 min-w-0 flex-1 overflow-hidden" :class="layoutClass">
<div data-special-page-navigation class="flex min-h-0 min-w-0 shrink-0 flex-col overflow-auto" :style="navigationStyle">
<div v-for="group in queryStore.groups" :key="group.id" :ref="(element) => setTabBarTarget(group.id, element)" :data-special-page-tab-target="group.id" class="flex min-h-0 min-w-0" :class="isVerticalLayout ? 'flex-1' : 'shrink-0'" />
<button v-if="pluginCenterOpen" type="button" data-plugin-center-tab class="shrink-0 rounded-md px-2 py-1 text-left text-sm" :class="pluginCenterActive ? 'bg-accent text-accent-foreground' : 'text-muted-foreground'" @click="emit('activate-plugin-center')">
{{ t("toolbar.pluginCenter") }}
</button>
</div>
<div data-special-page-content class="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
<slot />
+17 -2
View File
@@ -384,12 +384,19 @@ describe("plugin SDK source", () => {
dbxPlugin?: { invoke: (method: string, params?: unknown, options?: { timeoutMs?: number }) => Promise<unknown> };
}
function loadSdk(posted: unknown[]): SdkWindow {
function loadSdk(posted: unknown[], initialTheme?: { appearance: "dark" | "light"; tokens: Record<string, string> }): SdkWindow {
const sandbox = {} as SdkWindow;
const document = {
documentElement: {
dataset: {} as Record<string, string>,
style: { colorScheme: "", setProperty: vi.fn() },
},
dispatchEvent: vi.fn(),
} as unknown as Document;
// The SDK IIFE only touches window and the bare-global addEventListener at
// boot; parent.postMessage is captured for later request() calls, so a
// stub window/parent is enough here.
new Function("window", "parent", "addEventListener", pluginSdkSource())(sandbox, { postMessage: (message: unknown) => posted.push(message) }, () => {});
new Function("window", "parent", "addEventListener", "document", pluginSdkSource(initialTheme))(sandbox, { postMessage: (message: unknown) => posted.push(message) }, () => {}, document);
return sandbox;
}
@@ -421,6 +428,14 @@ describe("plugin SDK source", () => {
expect(JSON.stringify(message.params.params.sessionIds)).toBe(JSON.stringify(["session-a", "session-b"]));
});
it("boots successfully when the host provides the initial theme", () => {
const posted: unknown[] = [];
const { dbxPlugin } = loadSdk(posted, { appearance: "dark", tokens: { "--color-background": "#131416" } });
expect(dbxPlugin).toBeDefined();
expect(posted[0]).toMatchObject({ source: "dbx-plugin", type: "ready" });
});
it("keeps cloneable values intact and plain scalars by reference", () => {
const posted: unknown[] = [];
const { dbxPlugin } = loadSdk(posted);
@@ -349,7 +349,6 @@ export function pluginSdkSource(initialTheme?: PluginBridgeTheme): string {
const applyTheme = (value) => {
if (!value || typeof value !== 'object') return;
theme = value;
const theme = value;
const root = document.documentElement;
root.dataset.dbxTheme = theme.appearance === "dark" ? "dark" : "light";
root.style.colorScheme = theme.appearance === "dark" ? "dark" : "light";