Skip to content

Commit d216f84

Browse files
authored
feat(workspace): complete editor output integrations (#8)
Wire supported exchange and print plugins across Workspace editors, align Plugin Mode assets, and remove embedded Base and Board chrome. Add regression coverage for plugin registration, status suppression, and editor shell layout.
1 parent 186ad10 commit d216f84

12 files changed

Lines changed: 552 additions & 32 deletions

apps/workspace/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@
3535
"@tanstack/react-query": "^5.101.4",
3636
"@tanstack/react-router": "^1.170.18",
3737
"@univerjs-pro/bases": "1.0.0-insiders.20260819-7b27824",
38+
"@univerjs-pro/bases-exchange-client": "1.0.0-insiders.20260819-7b27824",
3839
"@univerjs-pro/bases-ui": "1.0.0-insiders.20260819-7b27824",
3940
"@univerjs-pro/boards": "1.0.0-insiders.20260819-7b27824",
4041
"@univerjs-pro/boards-chart": "1.0.0-insiders.20260819-7b27824",
4142
"@univerjs-pro/boards-chart-ui": "1.0.0-insiders.20260819-7b27824",
4243
"@univerjs-pro/boards-mind": "1.0.0-insiders.20260819-7b27824",
4344
"@univerjs-pro/boards-mind-ui": "1.0.0-insiders.20260819-7b27824",
45+
"@univerjs-pro/boards-print": "1.0.0-insiders.20260819-7b27824",
4446
"@univerjs-pro/boards-table": "1.0.0-insiders.20260819-7b27824",
4547
"@univerjs-pro/boards-table-ui": "1.0.0-insiders.20260819-7b27824",
4648
"@univerjs-pro/boards-ui": "1.0.0-insiders.20260819-7b27824",
@@ -62,8 +64,10 @@
6264
"@univerjs-pro/docs-chart-ui": "1.0.0-insiders.20260819-7b27824",
6365
"@univerjs-pro/docs-code": "1.0.0-insiders.20260819-7b27824",
6466
"@univerjs-pro/docs-code-ui": "1.0.0-insiders.20260819-7b27824",
67+
"@univerjs-pro/docs-exchange-client": "1.0.0-insiders.20260819-7b27824",
6568
"@univerjs-pro/docs-latex": "1.0.0-insiders.20260819-7b27824",
6669
"@univerjs-pro/docs-latex-ui": "1.0.0-insiders.20260819-7b27824",
70+
"@univerjs-pro/docs-print": "1.0.0-insiders.20260819-7b27824",
6771
"@univerjs-pro/docs-shape": "1.0.0-insiders.20260819-7b27824",
6872
"@univerjs-pro/docs-shape-ui": "1.0.0-insiders.20260819-7b27824",
6973
"@univerjs-pro/docs-table": "1.0.0-insiders.20260819-7b27824",
@@ -81,6 +85,8 @@
8185
"@univerjs-pro/slides": "1.0.0-insiders.20260819-7b27824",
8286
"@univerjs-pro/slides-chart": "1.0.0-insiders.20260819-7b27824",
8387
"@univerjs-pro/slides-chart-ui": "1.0.0-insiders.20260819-7b27824",
88+
"@univerjs-pro/slides-exchange-client": "1.0.0-insiders.20260819-7b27824",
89+
"@univerjs-pro/slides-print": "1.0.0-insiders.20260819-7b27824",
8490
"@univerjs-pro/slides-table": "1.0.0-insiders.20260819-7b27824",
8591
"@univerjs-pro/slides-table-ui": "1.0.0-insiders.20260819-7b27824",
8692
"@univerjs-pro/slides-ui": "1.0.0-insiders.20260819-7b27824",

apps/workspace/test/unit/collaboration-status.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,30 @@ describe("collaborationStatusMessageKey", () => {
3232
collaborationStatusMessageKey(CollaborationStatus.PENDING, null)
3333
).toBe("collabSyncing");
3434
});
35+
36+
it("can hide collaboration status without restoring the SDK display", async () => {
37+
Object.defineProperty(globalThis, "Path2D", {
38+
configurable: true,
39+
value: class Path2D {},
40+
});
41+
const { resolveCollaborationStatusPresentation } = await import(
42+
"../../web/src/features/editor/collaboration-editor"
43+
);
44+
expect(resolveCollaborationStatusPresentation(true, undefined)).toEqual({
45+
suppressNative: true,
46+
showCustom: false,
47+
});
48+
expect(resolveCollaborationStatusPresentation(undefined, true)).toEqual({
49+
suppressNative: true,
50+
showCustom: true,
51+
});
52+
expect(resolveCollaborationStatusPresentation(undefined, undefined)).toEqual({
53+
suppressNative: false,
54+
showCustom: false,
55+
});
56+
expect(resolveCollaborationStatusPresentation(true, true)).toEqual({
57+
suppressNative: true,
58+
showCustom: false,
59+
});
60+
});
3561
});
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { readFileSync } from "node:fs";
2+
3+
import { describe, expect, it } from "vitest";
4+
5+
const editorSources = {
6+
base: readEditorSource("base"),
7+
board: readEditorSource("board"),
8+
doc: readEditorSource("doc"),
9+
sheet: readEditorSource("sheet"),
10+
slide: readEditorSource("slide"),
11+
};
12+
const globalStyles = readFileSync(
13+
new URL("../../web/src/app/styles/global.css", import.meta.url),
14+
"utf8"
15+
);
16+
17+
describe("editor integration assets", () => {
18+
it("loads shared styles before manual Plugin Mode product styles", () => {
19+
expectImportsInOrder(editorSources.base, [
20+
"@univerjs/design/lib/index.css",
21+
"@univerjs/ui/lib/index.css",
22+
"@univerjs-pro/bases-ui/lib/index.css",
23+
"@univerjs-pro/bases-exchange-client/lib/index.css",
24+
]);
25+
expectImportsInOrder(editorSources.slide, [
26+
"@univerjs/design/lib/index.css",
27+
"@univerjs/ui/lib/index.css",
28+
"@univerjs/docs-ui/lib/index.css",
29+
"@univerjs/drawing-ui/lib/index.css",
30+
"@univerjs-pro/chart-ui/lib/index.css",
31+
"@univerjs-pro/shape-editor-ui/lib/index.css",
32+
"@univerjs-pro/slides-ui/lib/index.css",
33+
"@univerjs-pro/slides-chart-ui/lib/index.css",
34+
"@univerjs-pro/slides-print/lib/index.css",
35+
"@univerjs-pro/slides-table-ui/lib/index.css",
36+
]);
37+
expectImportsInOrder(editorSources.board, [
38+
"@univerjs/design/lib/index.css",
39+
"@univerjs/ui/lib/index.css",
40+
"@univerjs/docs-ui/lib/index.css",
41+
"@univerjs/drawing-ui/lib/index.css",
42+
"@univerjs-pro/chart-ui/lib/index.css",
43+
"@univerjs-pro/shape-editor-ui/lib/index.css",
44+
"@univerjs-pro/ink-ui/lib/index.css",
45+
"@univerjs-pro/docs-latex-ui/lib/index.css",
46+
"@univerjs-pro/boards-ui/lib/index.css",
47+
"@univerjs-pro/boards-chart-ui/lib/index.css",
48+
"@univerjs-pro/boards-mind-ui/lib/index.css",
49+
"@univerjs-pro/boards-print/lib/index.css",
50+
"@univerjs-pro/boards-table-ui/lib/index.css",
51+
]);
52+
});
53+
54+
it("loads shared Pro styles before Doc feature styles", () => {
55+
expectImportsInOrder(editorSources.doc, [
56+
"@univerjs/preset-docs-core/lib/index.css",
57+
"@univerjs/preset-docs-drawing/lib/index.css",
58+
"@univerjs/preset-docs-hyper-link/lib/index.css",
59+
"@univerjs/preset-docs-thread-comment/lib/index.css",
60+
"@univerjs-pro/chart-ui/lib/index.css",
61+
"@univerjs-pro/shape-editor-ui/lib/index.css",
62+
"@univerjs-pro/docs-callout-ui/lib/index.css",
63+
"@univerjs-pro/docs-chart-ui/lib/index.css",
64+
"@univerjs-pro/docs-code-ui/lib/index.css",
65+
"@univerjs-pro/docs-latex-ui/lib/index.css",
66+
"@univerjs-pro/docs-print/lib/index.css",
67+
"@univerjs-pro/docs-shape-ui/lib/index.css",
68+
"@univerjs-pro/docs-table-ui/lib/index.css",
69+
]);
70+
});
71+
72+
it("loads the public Facade entries for every installed editor feature", () => {
73+
expectImports(editorSources.doc, [
74+
"@univerjs-pro/chart-ui/facade",
75+
"@univerjs-pro/docs-callout/facade",
76+
"@univerjs-pro/docs-chart/facade",
77+
"@univerjs-pro/docs-code/facade",
78+
"@univerjs-pro/docs-exchange-client/facade",
79+
"@univerjs-pro/docs-latex/facade",
80+
"@univerjs-pro/docs-shape/facade",
81+
"@univerjs-pro/docs-table/facade",
82+
"@univerjs-pro/engine-chart/facade",
83+
]);
84+
expectImports(editorSources.slide, [
85+
"@univerjs-pro/chart-ui/facade",
86+
"@univerjs-pro/engine-chart/facade",
87+
"@univerjs-pro/slides/facade",
88+
"@univerjs-pro/slides-chart/facade",
89+
"@univerjs-pro/slides-exchange-client/facade",
90+
"@univerjs-pro/slides-print/facade",
91+
"@univerjs-pro/slides-table/facade",
92+
]);
93+
expectImports(editorSources.base, [
94+
"@univerjs-pro/bases/facade",
95+
"@univerjs-pro/bases-exchange-client/facade",
96+
"@univerjs-pro/bases-ui/facade",
97+
]);
98+
expectImports(editorSources.board, [
99+
"@univerjs-pro/boards/facade",
100+
"@univerjs-pro/boards-chart/facade",
101+
"@univerjs-pro/boards-mind/facade",
102+
"@univerjs-pro/boards-table/facade",
103+
"@univerjs-pro/boards-ui/facade",
104+
"@univerjs-pro/chart-ui/facade",
105+
"@univerjs-pro/engine-chart/facade",
106+
]);
107+
});
108+
109+
it("keeps the Base-only status and footer out of the Workspace shell", () => {
110+
expect(editorSources.base).toContain("hideCollaborationStatus: true");
111+
expect(editorSources.base).toContain("toolbar: false");
112+
expect(editorSources.base).toContain("collaborationStatus: false");
113+
expect(editorSources.base).toContain("footer: false");
114+
expect(globalStyles).toContain(
115+
'[id^="univer-base-"] [data-u-comp="base-table-sidebar"] > :last-child'
116+
);
117+
expect(globalStyles).toContain(
118+
'[id^="univer-base-"] [data-u-comp="base-canvas-container"]'
119+
);
120+
});
121+
122+
it("registers the supported output plugins for each editor", () => {
123+
expect(editorSources.sheet).toContain("exchangeProvidedByPreset: true");
124+
expect(editorSources.doc).toContain(
125+
"exchangeFeaturePlugins: () => [UniverDocsExchangeClientPlugin]"
126+
);
127+
expect(editorSources.doc).toContain(
128+
"printFeaturePlugins: () => [UniverDocsPrintPlugin]"
129+
);
130+
expect(editorSources.slide).toContain(
131+
"exchangeFeaturePlugins: () => [UniverSlidesExchangeClientPlugin]"
132+
);
133+
expect(editorSources.slide).toContain(
134+
"printFeaturePlugins: () => [UniverSlidesPrintPlugin]"
135+
);
136+
expect(editorSources.base).toContain(
137+
"exchangeFeaturePlugins: () => [UniverBasesExchangeClientPlugin]"
138+
);
139+
expect(editorSources.board).toContain("exchangeEnabled: false");
140+
expect(editorSources.board).toContain(
141+
"printFeaturePlugins: () => [UniverBoardsPrintPlugin]"
142+
);
143+
});
144+
145+
it("keeps Board chrome out and contains viewport overscroll", () => {
146+
expect(editorSources.board).toContain("hideCollaborationStatus: true");
147+
expect(editorSources.board).toContain("header: false");
148+
expect(editorSources.board).toContain("toolbar: false");
149+
expect(editorSources.board).toContain("footer: false");
150+
expect(globalStyles).toContain("overscroll-behavior: none");
151+
expect(globalStyles).toContain(".univer-editor-shell");
152+
expect(globalStyles).toContain("overflow: hidden");
153+
});
154+
});
155+
156+
function readEditorSource(product: string): string {
157+
return readFileSync(
158+
new URL(`../../web/src/features/editor/${product}-editor.tsx`, import.meta.url),
159+
"utf8"
160+
);
161+
}
162+
163+
function expectImports(source: string, specifiers: readonly string[]): void {
164+
for (const specifier of specifiers) {
165+
expect(source, `missing side-effect import: ${specifier}`).toContain(
166+
`import "${specifier}";`
167+
);
168+
}
169+
}
170+
171+
function expectImportsInOrder(
172+
source: string,
173+
specifiers: readonly string[]
174+
): void {
175+
expectImports(source, specifiers);
176+
const indexes = specifiers.map((specifier) =>
177+
source.indexOf(`import "${specifier}";`)
178+
);
179+
expect(indexes, `incorrect import order: ${specifiers.join(" -> ")}`).toEqual(
180+
[...indexes].sort((left, right) => left - right)
181+
);
182+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import type { IPresetPlugin } from "@univerjs/presets";
2+
import { describe, expect, it } from "vitest";
3+
4+
describe("Workspace output plugins", () => {
5+
it("registers shared exchange before the host plugin and keeps print available", async () => {
6+
Object.defineProperty(globalThis, "Path2D", {
7+
configurable: true,
8+
value: class Path2D {},
9+
});
10+
const [
11+
{ UniverDocsExchangeClientPlugin },
12+
{ UniverDocsPrintPlugin },
13+
{ UniverExchangeClientPlugin },
14+
{ createWorkspaceOutputPlugins },
15+
] = await Promise.all([
16+
import("@univerjs-pro/docs-exchange-client"),
17+
import("@univerjs-pro/docs-print"),
18+
import("@univerjs-pro/exchange-client"),
19+
import("../../web/src/features/editor/exchange-plugins.js"),
20+
]);
21+
22+
const enabled = createWorkspaceOutputPlugins({
23+
origin: "https://workspace.example",
24+
exchangeEnabled: true,
25+
exchangeProvidedByPreset: false,
26+
exchangeFeaturePlugins: [UniverDocsExchangeClientPlugin],
27+
printFeaturePlugins: [UniverDocsPrintPlugin],
28+
});
29+
expect(enabled.map(pluginConstructor)).toEqual([
30+
UniverExchangeClientPlugin,
31+
UniverDocsExchangeClientPlugin,
32+
UniverDocsPrintPlugin,
33+
]);
34+
expect(Array.isArray(enabled[0]) ? enabled[0][1] : undefined).toMatchObject(
35+
{
36+
exportServerUrl:
37+
"https://workspace.example/universer-api/exchange/{type}/export",
38+
}
39+
);
40+
41+
const disabled = createWorkspaceOutputPlugins({
42+
origin: "https://workspace.example",
43+
exchangeEnabled: false,
44+
exchangeProvidedByPreset: false,
45+
exchangeFeaturePlugins: [UniverDocsExchangeClientPlugin],
46+
printFeaturePlugins: [UniverDocsPrintPlugin],
47+
});
48+
expect(disabled.map(pluginConstructor)).toEqual([
49+
UniverDocsPrintPlugin,
50+
]);
51+
52+
const presetExchange = createWorkspaceOutputPlugins({
53+
origin: "https://workspace.example",
54+
exchangeEnabled: true,
55+
exchangeProvidedByPreset: true,
56+
exchangeFeaturePlugins: [],
57+
printFeaturePlugins: [UniverDocsPrintPlugin],
58+
});
59+
expect(presetExchange.map(pluginConstructor)).toEqual([
60+
UniverDocsPrintPlugin,
61+
]);
62+
}, 15_000);
63+
});
64+
65+
function pluginConstructor(plugin: IPresetPlugin) {
66+
return Array.isArray(plugin) ? plugin[0] : plugin;
67+
}

apps/workspace/web/src/app/styles/global.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
body,
112112
#root {
113113
height: 100%;
114+
overscroll-behavior: none;
114115
}
115116

116117
body {
@@ -237,13 +238,24 @@
237238

238239
.univer-editor-shell {
239240
flex: 1;
241+
overflow: hidden;
242+
overscroll-behavior: none;
240243
}
241244

242245
.univer-editor-container {
243246
width: 100%;
244247
height: 100%;
245248
}
246249

250+
/* Base renders its own sidebar/canvas footer outside UniverUI's footer slot. */
251+
[id^="univer-base-"] [data-u-comp="base-table-sidebar"] > :last-child {
252+
display: none;
253+
}
254+
255+
[id^="univer-base-"] [data-u-comp="base-canvas-container"] {
256+
margin-bottom: -36px;
257+
}
258+
247259
/* ------------------------------------------------------------------ */
248260
/* Sonner toast alignment with the design language */
249261
/* ------------------------------------------------------------------ */

0 commit comments

Comments
 (0)