Skip to content

Commit 809c73a

Browse files
committed
feat: use YHub activity timeline for yjs versioning
Wire the 13-versioning-yjs14 example to the YHub versioning adapter and make YHub's /activity timeline the source of truth for versions. - yhub.ts: fix v1/v2 update encoding mismatch (getContent converts v1->v2, create writes v1); list/create/restore now derive version identity from YHub activity timestamps; drop unused metaStore concept and updateSnapshotName - Versioning.ts: make Yjs adapter applyRestore a no-op since YHub /rollback publishes the reverting update over live sync - Snapshot.tsx: render version name as static text when updateSnapshotName is unsupported instead of a read-only input - example: point websocket + versioning at YHub, load versions on mount, remove localStorage/named-filter scaffolding
1 parent 1ab97a8 commit 809c73a

7 files changed

Lines changed: 99 additions & 263 deletions

File tree

examples/07-collaboration/13-versioning-yjs14/src/App.tsx

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,48 @@
11
import "@blocknote/core/fonts/inter.css";
2-
import { withCollaboration } from "@blocknote/core/y";
2+
import {
3+
createYHubVersioningEndpoints,
4+
withCollaboration,
5+
} from "@blocknote/core/y";
36
import { VersioningExtension } from "@blocknote/core/extensions";
4-
import { localStorageEndpoints } from "./localStorageEndpoints.js";
57
import {
68
BlockNoteViewEditor,
79
useCreateBlockNote,
10+
useExtension,
811
useExtensionState,
912
} from "@blocknote/react";
13+
import { useEffect } from "react";
1014
import { BlockNoteView } from "@blocknote/mantine";
1115
import "@blocknote/mantine/style.css";
1216

1317
import * as Y from "@y/y";
1418
import { WebsocketProvider } from "@y/websocket";
1519

16-
import { VersionHistorySidebar } from "./VersionHistorySidebar";
20+
import { VersionHistorySidebar } from "./VersionHistorySidebar.js";
1721
import "./style.css";
1822

19-
const roomName = "blocknote-versioning-y-example";
23+
// YHub serves both real-time sync (over WebSocket) and version history (over
24+
// HTTP) for the same document, so the backend URL, org, and docId are shared.
25+
const yhubHost = "yhub-standalone-x9kss.ondigitalocean.app";
26+
const org = "blocknote-versioning-yjs14";
27+
const docId = "blocknote-versioning-y-example-4";
28+
2029
const doc = new Y.Doc();
30+
// YHub expects clients to connect to `/ws/{org}/{docId}`. WebsocketProvider
31+
// joins its base URL and room with a slash, so the room is `{org}/{docId}`.
2132
const provider = new WebsocketProvider(
22-
"wss://demos.yjs.dev/ws",
23-
roomName,
33+
`wss://${yhubHost}/ws`,
34+
`${org}/${docId}`,
2435
doc,
25-
{ connect: false },
2636
);
27-
provider.connectBc();
37+
// provider.connectBc();
38+
39+
// YHub-backed versioning endpoints. YHub stores continuous edit history and
40+
// exposes its activity timeline as versions through BlockNote's versioning UI.
41+
const versioningEndpoints = createYHubVersioningEndpoints({
42+
baseUrl: `https://${yhubHost}`,
43+
org,
44+
docId,
45+
});
2846

2947
export default function App() {
3048
const editor = useCreateBlockNote(
@@ -35,7 +53,7 @@ export default function App() {
3553
user: { color: "#ff0000", name: "User" },
3654
// Pass versioningEndpoints to the v14 CollaborationExtension which
3755
// automatically wires up the VersioningExtension with the Yjs adapter.
38-
versioningEndpoints: localStorageEndpoints,
56+
versioningEndpoints,
3957
},
4058
}),
4159
);
@@ -44,6 +62,14 @@ export default function App() {
4462
editor,
4563
});
4664

65+
// The versioning store starts empty on every page load, so fetch the list of
66+
// versions from YHub once when the editor mounts. Without this, the sidebar
67+
// only shows versions created during the current session.
68+
const versioning = useExtension(VersioningExtension, { editor });
69+
useEffect(() => {
70+
void versioning.listSnapshots();
71+
}, [versioning]);
72+
4773
return (
4874
<div className="wrapper">
4975
<BlockNoteView

examples/07-collaboration/13-versioning-yjs14/src/SettingsSelect.tsx

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,11 @@
11
import { VersioningSidebar } from "@blocknote/react";
2-
import { useState } from "react";
3-
4-
import { SettingsSelect } from "./SettingsSelect";
52

63
export const VersionHistorySidebar = () => {
7-
const [filter, setFilter] = useState<"named" | "all">("all");
8-
4+
// YHub's activity timeline is the source of truth for versions, and YHub has
5+
// no concept of a custom/pinned name, so every version is shown ("all").
96
return (
107
<div className={"sidebar-section"}>
11-
<div className={"settings"}>
12-
<SettingsSelect
13-
label={"Filter"}
14-
items={[
15-
{
16-
text: "All",
17-
icon: null,
18-
onClick: () => setFilter("all"),
19-
isSelected: filter === "all",
20-
},
21-
{
22-
text: "Named",
23-
icon: null,
24-
onClick: () => setFilter("named"),
25-
isSelected: filter === "named",
26-
},
27-
]}
28-
/>
29-
</div>
30-
<VersioningSidebar filter={filter} />
8+
<VersioningSidebar filter={"all"} />
319
</div>
3210
);
3311
};

examples/07-collaboration/13-versioning-yjs14/src/localStorageEndpoints.ts

Lines changed: 0 additions & 124 deletions
This file was deleted.

packages/core/src/y/extensions/Versioning.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,17 @@ export function createYjsVersioningAdapter(
5555
editor.exec(configureYProsemirror({ ytype: fragment }));
5656
},
5757
applyRestore: (_snapshotContent: Uint8Array) => {
58-
throw new Error(
59-
"Restore is not yet implemented for Yjs versioning adapter.",
60-
);
58+
// For Yjs-backed versioning, restoration happens on the server (e.g.
59+
// YHub's `/rollback` endpoint) which publishes a reverting update to
60+
// the document's room. That update propagates back to this client over
61+
// the live sync connection and updates `fragment` automatically, so
62+
// there is nothing to apply locally — we only need to leave preview
63+
// mode. `exitPreview` is already called by the base extension before
64+
// this runs, so this is a no-op.
65+
//
66+
// Note: this assumes `endpoints.restore` performs the server-side
67+
// restore. The default in-memory adapter has no server, which is why
68+
// this is specific to the Yjs collaboration setup.
6169
},
6270
},
6371
};

0 commit comments

Comments
 (0)