Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1de258e
feat!: improve version history authors and keyboard navigation
nperez0111 Oct 22, 2026
d231558
fix: hide named version filter when naming is unavailable
nperez0111 Oct 22, 2026
9745a60
fix: exclude version previews from change notifications
nperez0111 Oct 22, 2026
1d1bb1f
fix: focus current version when opening history
nperez0111 Oct 22, 2026
3b60c09
fix: keep active named version filter toggle visible
nperez0111 Oct 22, 2026
e5678e3
fix: preserve version history pagination after mutations
nperez0111 Oct 23, 2026
26d8be5
fix: preserve live renderer when closing version history
nperez0111 Oct 23, 2026
d875406
fix: preserve snapshot author ID boundaries in effect key
nperez0111 Oct 23, 2026
472f570
fix: avoid repeated version previews at navigation boundaries
nperez0111 Oct 23, 2026
8d3eba4
fix(core): clear removed version names before reconciling comparisons
nperez0111 Oct 23, 2026
5e8f560
test(react): align removed baseline expectations with named history
nperez0111 Oct 23, 2026
6a7384a
fix(core): guard collaborative editing until initial sync
nperez0111 Oct 23, 2026
96233a6
feat(examples): add minimal YHub collaboration example
nperez0111 Oct 23, 2026
67a512f
test: refresh block comparison baselines after toolchain upgrade
nperez0111 Oct 26, 2026
5e8aa6b
fix(examples): set YHub author for docs generation
nperez0111 Oct 26, 2026
2afee75
fix: remove deleted versions from cached history immediately
nperez0111 Oct 29, 2026
95657cf
fix(core): suppress change notifications throughout version previews
nperez0111 Oct 29, 2026
443ee9d
Update packages/core/src/extensions/Versioning/Versioning.ts
nperez0111 Oct 29, 2026
dc0356a
Fix pending version comparison highlights
nperez0111 Oct 29, 2026
e10706d
Wait for older history when navigating versions with arrow keys
nperez0111 Oct 29, 2026
a77d9d4
Reconcile deleted versions across history reads and cancellation
nperez0111 Oct 29, 2026
86ea7cb
fix(core): install nested extension plugins at runtime
nperez0111 Oct 22, 2026
2ff22d9
Clean up failed runtime extension registrations
nperez0111 Oct 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions examples/07-collaboration/15-yhub/.bnexample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"playground": true,
"docs": true,
"author": "nickthesick",
"tags": ["Advanced", "Saving/Loading", "Collaboration"],
"dependencies": {
"@y/prosemirror": "^2.0.0-15",
"@y/protocols": "^1.0.6-rc.1",
"@y/websocket": "^4.0.0-3",
"@y/y": "^14.0.0-rc.26"
}
}
11 changes: 11 additions & 0 deletions examples/07-collaboration/15-yhub/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Collaborative Editing with YHub

This example uses [YHub](https://github.com/yjs/yhub) and Yjs v14 to sync a BlockNote document and share user cursors over WebSocket. It connects to a public demo server, so don't enter private information.

**Try it out:** Open this page in another browser tab or window to edit the same document together.

**Relevant Docs:**

- [Editor Setup](/docs/getting-started/editor-setup)
- [Collaboration](/docs/features/collaboration)
- [YHub](https://github.com/yjs/yhub)
17 changes: 17 additions & 0 deletions examples/07-collaboration/15-yhub/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content"
/>
<title>Collaborative Editing with YHub</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions examples/07-collaboration/15-yhub/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./src/App.jsx";

const root = createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
34 changes: 34 additions & 0 deletions examples/07-collaboration/15-yhub/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@blocknote/example-collaboration-yhub",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"type": "module",
"private": true,
"version": "0.12.4",
"scripts": {
"start": "vite",
"dev": "vite",
"build:prod": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
"@mantine/core": "^9.0.2",
"@mantine/hooks": "^9.0.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"@y/prosemirror": "^2.0.0-15",
"@y/protocols": "^1.0.6-rc.1",
"@y/websocket": "^4.0.0-3",
"@y/y": "^14.0.0-rc.26"
},
"devDependencies": {
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"vite": "^8.0.0"
}
}
36 changes: 36 additions & 0 deletions examples/07-collaboration/15-yhub/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import "@blocknote/core/fonts/inter.css";
import { withCollaboration } from "@blocknote/core/y";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
import { WebsocketProvider } from "@y/websocket";
import * as Y from "@y/y";

const user = {
id: crypto.randomUUID(),
name: "My Username",
color: "#ff0000",
};

const doc = new Y.Doc();
const provider = new WebsocketProvider(
"wss://yhub.teleportal.tools/api/ws/v1",
// YHub rooms use "organization/document". Use your own names in production.
"blocknote/blocknote-yhub-example",
doc,
{ params: { userid: user.id } },
);

export default function App() {
const editor = useCreateBlockNote(
withCollaboration({
collaboration: {
provider,
fragment: doc.get("bn"),
user,
},
}),
);

return <BlockNoteView editor={editor} />;
}
32 changes: 32 additions & 0 deletions examples/07-collaboration/15-yhub/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true,
"paths": {
"@shared/*": ["../../../shared/*"]
}
},
"include": ["."],
"__ADD_FOR_LOCAL_DEV_references": [
{
"path": "../../../packages/core/"
},
{
"path": "../../../packages/react/"
}
]
}
1 change: 1 addition & 0 deletions examples/07-collaboration/15-yhub/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
35 changes: 35 additions & 0 deletions examples/07-collaboration/15-yhub/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import react from "@vitejs/plugin-react";
import * as fs from "fs";
import * as path from "path";
import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig(((conf: { command: string }) => ({
plugins: [react()],
optimizeDeps: {},
build: {
sourcemap: true,
},
resolve: {
alias:
conf.command === "build" ||
!fs.existsSync(path.resolve(__dirname, "../../../packages/core/src"))
? {}
: ({
// The repo-wide alias for the shared test-utils directory (private,
// so it only resolves inside the monorepo). Harmless for examples
// that don't use it.
"@shared": path.resolve(__dirname, "../../../shared/"),
// Comment out the lines below to load a built version of blocknote
// or, keep as is to load live from sources with live reload working
"@blocknote/core": path.resolve(
__dirname,
"../../../packages/core/src/",
),
"@blocknote/react": path.resolve(
__dirname,
"../../../packages/react/src/",
),
} as any),
},
})) as Parameters<typeof defineConfig>[0]);
37 changes: 31 additions & 6 deletions packages/ariakit/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -452,18 +452,16 @@

.bn-ariakit .bn-snapshot-date,
.bn-ariakit .bn-snapshot-original-date,
.bn-ariakit .bn-snapshot-secondary-label {
.bn-ariakit .bn-snapshot-users {
font-size: 13px;
line-height: 1.3;
}

.bn-ariakit .bn-snapshot-original-date,
.bn-ariakit .bn-snapshot-secondary-label {
.bn-ariakit .bn-snapshot-original-date {
color: hsl(204 4% 40%);
}

.bn-ariakit .bn-snapshot-original-date:where(.dark, .dark *),
.bn-ariakit .bn-snapshot-secondary-label:where(.dark, .dark *) {
.bn-ariakit .bn-snapshot-original-date:where(.dark, .dark *) {
color: hsl(204 8% 70%);
}

Expand All @@ -475,6 +473,34 @@
transition: opacity 0.12s ease;
}

.bn-ariakit .bn-snapshot-users {
display: flex;
flex-wrap: wrap;
gap: 4px;
overflow: visible;
white-space: normal;
}

.bn-ariakit .bn-snapshot-user {
display: inline-flex;
flex: none;
align-items: center;
gap: 4px;
white-space: nowrap;
}

.bn-ariakit .bn-snapshot-user-color {
background-color: currentColor;
border-radius: 50%;
flex: none;
height: 8px;
width: 8px;
}

.bn-ariakit .bn-snapshot-user-color:where(.dark, .dark *) {
background-color: var(--bn-user-color-light);
}

.bn-ariakit .bn-snapshot:hover .bn-snapshot-menu,
.bn-ariakit .bn-snapshot:focus-visible .bn-snapshot-menu,
.bn-ariakit .bn-snapshot:focus-within .bn-snapshot-menu {
Expand Down Expand Up @@ -512,7 +538,6 @@

.bn-ariakit .bn-snapshot.selected .bn-snapshot-date,
.bn-ariakit .bn-snapshot.selected .bn-snapshot-original-date,
.bn-ariakit .bn-snapshot.selected .bn-snapshot-secondary-label,
.bn-ariakit .bn-snapshot.selected .bn-snapshot-menu-trigger {
color: rgb(255 255 255 / 0.9);
}
Expand Down
27 changes: 23 additions & 4 deletions packages/ariakit/src/versioning/Versioning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const Snapshot = forwardRef<
name,
date,
restoredFrom,
secondaryLabel,
users,
comparingLabel,
comparingIcon,
...rest
Expand Down Expand Up @@ -131,9 +131,28 @@ export const Snapshot = forwardRef<
{restoredFrom && (
<div className="bn-snapshot-original-date">{restoredFrom}</div>
)}
{secondaryLabel && (
<div className="bn-snapshot-secondary-label">{secondaryLabel}</div>
)}
{users?.length ? (
<div className="bn-snapshot-users">
{users.map((user, index) => (
<span className="bn-snapshot-user" key={index}>
{index > 0 && <span aria-hidden="true">·</span>}
{typeof user !== "string" && user.color && (
<span
className="bn-snapshot-user-color"
aria-hidden="true"
style={{
color: user.color,
...{
"--bn-user-color-light": user.colorLight ?? user.color,
},
}}
/>
)}
<span>{typeof user === "string" ? user : user.username}</span>
</span>
))}
</div>
) : null}
</div>
{actions && (
<div
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/editor/managers/EventManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "../../api/getBlocksChangedByTransaction.js";
import { Transaction } from "prosemirror-state";
import { EventEmitter } from "../../util/EventEmitter.js";
import type { VersioningController } from "../../extensions/Versioning/Versioning.js";
import {
BlockSchema,
InlineContentSchema,
Expand Down Expand Up @@ -177,8 +178,13 @@ export class EventManager<
includeUpdatesFromRemote = true,
): Unsubscribe {
return this.on("onChange", ({ transaction, appendedTransactions }) => {
if (!includeUpdatesFromRemote && isRemoteTransaction(transaction)) {
// don't trigger the callback if the changes are caused by a remote user
if (
this.editor.getExtension<VersioningController>("versioning")
?.isPreviewOpen ||
transaction.getMeta("blocknoteVersionPreview") ||
(!includeUpdatesFromRemote && isRemoteTransaction(transaction))
) {
// Don't trigger the callback for previews or excluded remote changes.
return;
}
callback(this.editor, {
Expand Down

This file was deleted.

Loading
Loading