Skip to content

Commit d882431

Browse files
authored
feat: add upgradeToCollaboration api to promose local documents into collab mode (#2508)
* feat: add upgradeToCollaboration() to promote local documents into collaborative rooms * chore: update docs for collab upgrade
1 parent 4437680 commit d882431

20 files changed

Lines changed: 2464 additions & 84 deletions

File tree

apps/docs/core/superdoc/methods.mdx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,77 @@ const superdoc = new SuperDoc({
130130

131131
</CodeGroup>
132132

133+
## Collaboration methods
134+
135+
### `upgradeToCollaboration`
136+
137+
Promote a local SuperDoc instance into collaboration without creating a new instance.
138+
139+
<Warning>
140+
This is a promotion flow, not a passive join flow. SuperDoc seeds the target
141+
room with the current local document state, comments, and lock state.
142+
</Warning>
143+
144+
<ParamField path="options" type="Object" required>
145+
Upgrade configuration
146+
147+
<Expandable title="properties" defaultOpen>
148+
<ParamField path="ydoc" type="Y.Doc" required>
149+
The Yjs document for the target room
150+
</ParamField>
151+
<ParamField path="provider" type="Object" required>
152+
The collaboration provider connected to the same room as `ydoc`
153+
</ParamField>
154+
</Expandable>
155+
</ParamField>
156+
157+
**Returns:** `Promise<void>`
158+
159+
**Requirements:**
160+
161+
- The SuperDoc instance must already be ready
162+
- The instance must not already be collaborative
163+
- The current instance must contain a single DOCX document
164+
- You must provide a matching `{ ydoc, provider }` pair
165+
166+
<Note>
167+
SuperDoc waits for provider sync internally. You do not need to wait for the
168+
provider's `sync` event before calling this method.
169+
</Note>
170+
171+
<CodeGroup>
172+
173+
```javascript Usage
174+
await superdoc.upgradeToCollaboration({ ydoc, provider });
175+
```
176+
177+
```javascript Full Example
178+
import * as Y from 'yjs';
179+
import { WebsocketProvider } from 'y-websocket';
180+
import { SuperDoc } from 'superdoc';
181+
import 'superdoc/style.css';
182+
183+
const superdoc = new SuperDoc({
184+
selector: '#editor',
185+
document: file,
186+
user: {
187+
name: 'John Smith',
188+
email: 'john@example.com',
189+
},
190+
});
191+
192+
superdoc.on('ready', async () => {
193+
const ydoc = new Y.Doc();
194+
const provider = new WebsocketProvider('wss://collab.example.com', 'document-123', ydoc);
195+
196+
await superdoc.upgradeToCollaboration({ ydoc, provider });
197+
});
198+
```
199+
200+
</CodeGroup>
201+
202+
See [Upgrade to Collaboration](/modules/collaboration/upgrade-to-collaboration) for the full workflow guide.
203+
133204
## Mode control
134205

135206
### `setDocumentMode`

apps/docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
"pages": [
136136
"modules/collaboration/overview",
137137
"modules/collaboration/quickstart",
138+
"modules/collaboration/upgrade-to-collaboration",
138139
"modules/collaboration/configuration"
139140
]
140141
},

apps/docs/modules/collaboration/configuration.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ new SuperDoc({
2727
});
2828
```
2929

30+
<Note>
31+
Use `modules.collaboration = { ydoc, provider }` when the editor should start
32+
in collaboration mode. If you want to open the document locally first and
33+
create a shared room later, use
34+
[`superdoc.upgradeToCollaboration()`](/modules/collaboration/upgrade-to-collaboration)
35+
instead.
36+
</Note>
37+
3038
<ParamField path="modules.collaboration.ydoc" type="Y.Doc" required>
3139
Your Yjs document instance
3240
</ParamField>

apps/docs/modules/collaboration/overview.mdx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,19 @@ provider.on("sync", (synced) => {
7070

7171
See the [full quickstart guide](/modules/collaboration/quickstart) for framework-specific examples (React, Vue, Vanilla JS).
7272

73+
## Start collaborative immediately or later
74+
75+
<CardGroup cols={2}>
76+
<Card title="Start in collaboration mode" href="/modules/collaboration/quickstart">
77+
Create your Yjs provider first, then pass `{ ydoc, provider }` in
78+
`modules.collaboration` when you construct `SuperDoc`.
79+
</Card>
80+
81+
<Card title="Upgrade later" href="/modules/collaboration/upgrade-to-collaboration">
82+
Mount a local `SuperDoc` first, then call
83+
`superdoc.upgradeToCollaboration({ ydoc, provider })` when the user creates
84+
a shared room.
85+
</Card>
86+
</CardGroup>
87+
7388
If you are also using the SDK for backend automation, see [SDK collaboration sessions](/document-engine/sdks#collaboration-sessions).

apps/docs/modules/collaboration/quickstart.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ Get real-time collaboration working in 5 minutes using [Liveblocks](https://live
1010
This guide uses Liveblocks because it's the fastest way to get started. See [all providers](/modules/collaboration/overview#choose-your-approach) for alternatives.
1111
</Note>
1212

13+
<Note>
14+
Need to start local and create the room later? See
15+
[Upgrade to Collaboration](/modules/collaboration/upgrade-to-collaboration).
16+
</Note>
17+
1318
## Prerequisites
1419

1520
- SuperDoc installed in your project
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
title: Upgrade to Collaboration
3+
sidebarTitle: Upgrade Later
4+
keywords: "upgrade to collaboration, runtime collaboration, late collaboration, yjs room promotion, superdoc upgradeToCollaboration"
5+
---
6+
7+
Use `superdoc.upgradeToCollaboration()` when you want to render a document locally first and turn that same SuperDoc instance into a collaborative room later.
8+
9+
This is useful for flows like:
10+
11+
- A user opens a document privately, then clicks `Share`
12+
- You only provision collaboration infrastructure when someone requests it
13+
- You want the document visible immediately, then enable real-time editing on demand
14+
15+
<Warning>
16+
`upgradeToCollaboration()` promotes the current local document into the target
17+
room. It seeds the room with the current document state, comments, and lock
18+
state. Use it to create a new shared room from local state, not to join an
19+
existing room you need to preserve as-is.
20+
</Warning>
21+
22+
## Before you start
23+
24+
- Wait until the SuperDoc instance is ready
25+
- Call it only on a local instance that is not already collaborative
26+
- The current implementation supports a single DOCX document
27+
- Provide a matching `{ ydoc, provider }` pair for the room you want to create
28+
- You do not need to wait for provider sync yourself; SuperDoc waits internally
29+
30+
## Example
31+
32+
This example starts with a local editor and upgrades it when the user clicks a button.
33+
34+
```javascript
35+
import * as Y from 'yjs';
36+
import { WebsocketProvider } from 'y-websocket';
37+
import { SuperDoc } from 'superdoc';
38+
import 'superdoc/style.css';
39+
40+
const superdoc = new SuperDoc({
41+
selector: '#editor',
42+
document: file,
43+
user: {
44+
name: 'John Smith',
45+
email: 'john@example.com',
46+
},
47+
});
48+
49+
superdoc.on('ready', () => {
50+
document.querySelector('#share-button').addEventListener('click', async () => {
51+
const roomId = `document-${crypto.randomUUID()}`;
52+
const ydoc = new Y.Doc();
53+
const provider = new WebsocketProvider('wss://collab.example.com', roomId, ydoc);
54+
55+
try {
56+
await superdoc.upgradeToCollaboration({ ydoc, provider });
57+
console.log('Collaboration enabled:', roomId);
58+
} catch (error) {
59+
provider.destroy();
60+
ydoc.destroy();
61+
throw error;
62+
}
63+
});
64+
});
65+
```
66+
67+
## What happens during the upgrade
68+
69+
1. SuperDoc waits for the collaboration provider to connect and sync
70+
2. The current local document state is written into the target room
71+
3. Comments and lock state are copied into the room
72+
4. The editor runtime is rebuilt in collaboration mode
73+
5. The same `superdoc` instance continues running, now backed by Yjs
74+
75+
## Constructor-time collaboration vs late upgrade
76+
77+
<CardGroup cols={2}>
78+
<Card title="Start collaborative immediately" href="/modules/collaboration/quickstart">
79+
Create the provider first, then pass `{ ydoc, provider }` in
80+
`modules.collaboration` when you construct `SuperDoc`.
81+
</Card>
82+
<Card title="Start local, upgrade later" href="/core/superdoc/methods">
83+
Create a local `SuperDoc` first, then call
84+
`superdoc.upgradeToCollaboration({ ydoc, provider })` when you want to
85+
create a shared room.
86+
</Card>
87+
</CardGroup>
88+
89+
## Related docs
90+
91+
- [Collaboration quickstart](/modules/collaboration/quickstart)
92+
- [Collaboration configuration](/modules/collaboration/configuration)
93+
- [SuperDoc methods](/core/superdoc/methods)
94+
- [Liveblocks guide](/guides/collaboration/liveblocks)
95+
- [SuperDoc Yjs guide](/guides/collaboration/superdoc-yjs)

packages/super-editor/src/components/SuperEditor.vue

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,51 @@ const initEditor = async ({ content, media = {}, mediaFiles = {}, fonts = {} } =
922922
presentationEditor: editor.value instanceof PresentationEditor ? editor.value : null,
923923
});
924924
925+
// Upgrade visual-readiness signal: during upgradeToCollaboration, SuperDoc
926+
// threads this callback so it knows when the rebuilt runtime has actually
927+
// painted AND collaboration is ready, not just when editors are created.
928+
// For collaborative remounts the provider is already synced so the
929+
// collaboration extension will emit collaborationReady after a 250ms delay.
930+
// We must wait for BOTH that event AND the first layout paint before
931+
// signalling that the upgrade transition can reveal the new runtime.
932+
const onUpgradeVisualReady = props.options?.onUpgradeVisualReady;
933+
if (typeof onUpgradeVisualReady === 'function') {
934+
const hasCollabProvider = Boolean(props.options?.collaborationProvider);
935+
const isPresentationEditor = editor.value instanceof PresentationEditor;
936+
937+
let collabReady = !hasCollabProvider; // no provider → already satisfied
938+
let layoutReady = !isPresentationEditor; // no layout engine → already satisfied
939+
940+
const tryFire = () => {
941+
if (collabReady && layoutReady) {
942+
nextTick(() => onUpgradeVisualReady());
943+
}
944+
};
945+
946+
if (!collabReady) {
947+
editor.value.once('collaborationReady', () => {
948+
collabReady = true;
949+
tryFire();
950+
});
951+
}
952+
953+
if (!layoutReady) {
954+
const pe = editor.value;
955+
if (pe.getPages().length > 0) {
956+
layoutReady = true;
957+
} else {
958+
const onFirstLayout = () => {
959+
pe.off('layoutUpdated', onFirstLayout);
960+
layoutReady = true;
961+
tryFire();
962+
};
963+
pe.on('layoutUpdated', onFirstLayout);
964+
}
965+
}
966+
967+
tryFire();
968+
}
969+
925970
// Attach layout-engine specific image selection listeners
926971
if (editor.value instanceof PresentationEditor) {
927972
const presentationEditor = editor.value;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './collaboration.js';
2+
export { seedEditorStateToYDoc } from './seed-editor-to-ydoc.js';

0 commit comments

Comments
 (0)