Collaborative Editing with Liveblocks
In this example, we use the Liveblocks + BlockNote setup guide to create a collaborative BlockNote editor, where multiple people can work on the same document in real-time.
Users can also add comments to the documents to start threads, which are displayed next to the editor. As well as that, they can react to, reply to, and resolve existing comments.
Try it out: Open this page in a new browser tab or window to see it in action!
Relevant Docs:
From Liveblocks Website:
// See https://liveblocks.io/docs/get-started/react-blocknote to see how this
// example was created, and an explanation for all the code.
import {
ClientSideSuspense,
LiveblocksProvider,
RoomProvider,
} from "@liveblocks/react/suspense";
import "@liveblocks/react-ui/styles.css";
import "@liveblocks/react-ui/styles/dark/media-query.css";
import "@liveblocks/react-tiptap/styles.css";
import { Editor } from "./Editor";
import "./globals.css";
import "./styles.css";
export default function App() {
return (
<LiveblocksProvider
publicApiKey={
"pk_prod_6iVYNrHvG98GvWioAutXrhTkpG0iQLrzUK3nfWT4_VKWl6NIrlt112YD29to9gQH"
}
>
<RoomProvider id="my-room">
<ClientSideSuspense fallback={<div>Loadingā¦</div>}>
<Editor />
</ClientSideSuspense>
</RoomProvider>
</LiveblocksProvider>
);
}
import "@blocknote/core/fonts/inter.css";
import { BlockNoteEditor } from "@blocknote/core";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { useCreateBlockNoteWithLiveblocks } from "@liveblocks/react-blocknote";
import { Threads } from "./Threads";
export function Editor() {
const editor = useCreateBlockNoteWithLiveblocks(
{},
{ mentions: true },
) as BlockNoteEditor;
return (
<div>
<BlockNoteView editor={editor} className="editor" />
<Threads editor={editor} />
</div>
);
}
import { BlockNoteEditor } from "@blocknote/core";
import { useThreads } from "@liveblocks/react/suspense";
import {
AnchoredThreads,
FloatingComposer,
FloatingThreads,
} from "@liveblocks/react-blocknote";
export function Threads({ editor }: { editor: BlockNoteEditor | null }) {
const { threads } = useThreads({ query: { resolved: false } });
if (!editor) {
return null;
}
return (
<>
<div className="anchored-threads">
<AnchoredThreads editor={editor} threads={threads} />
</div>
<FloatingThreads
editor={editor}
threads={threads}
className="floating-threads"
/>
<FloatingComposer editor={editor} className="floating-composer" />
</>
);
}
html {
font-family: Inter, sans-serif;
background: #f9f9f9;
}
@media (prefers-color-scheme: dark) {
html {
background: #0c0c0c;
}
}
.editor {
position: absolute;
inset: 0;
max-width: 1024px;
margin: 0 auto;
padding: 48px 0;
}
.bn-editor {
padding: 36px 52px;
min-height: 100%;
}
/* For mobile */
.floating-threads {
display: none;
}
/* For desktop */
.anchored-threads {
display: block;
max-width: 300px;
width: 100%;
position: absolute;
right: 12px;
}
@media (max-width: 640px) {
.floating-threads {
display: block;
}
.anchored-threads {
display: none;
}
}
.editor {
position: relative;
height: 100%;
}
div:has(> .editor) {
height: 100%;
}