Skip to content

Commit

Permalink
feat: Add collaborative initial content example
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Anselmo authored and Martin Anselmo committed Aug 7, 2024
1 parent 823f151 commit 74560b4
Show file tree
Hide file tree
Showing 13 changed files with 395 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"playground": true,
"docs": true,
"author": "mfanselmo",
"tags": ["Advanced", "Blocks", "yjs", "Collaboration"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Initial content with server-util and yjs

This example shows how to instantiate a collaborative editor with existing content, using the `@blocknote/server-util` API

## Relevant documentation
- https://docs.yjs.dev/api/document-updates
- https://www.blocknotejs.org/docs/editor-api/server-processing
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { BlockNoteSchema } from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/ariakit";
import "@blocknote/ariakit/style.css";
import * as Y from "yjs";
import { useEffect, useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import { toUint8Array } from "js-base64";

const schema = BlockNoteSchema.create();

export default function App() {
// Just fetches the initial contents
const { data: initialContentsUpdateVector, refetch } = useQuery({
queryKey: [1],
queryFn: async () =>
fetch("http://localhost:8080/")
.then((res) => res.text())
.then((res) => toUint8Array(res)), // API returns initial doc as a yjs update encoded as base64
});

const ydoc = useMemo(() => {
const doc = new Y.Doc();
if (initialContentsUpdateVector) {
// https://docs.yjs.dev/api/document-updates
Y.applyUpdateV2(doc, initialContentsUpdateVector);
}

return doc;
}, [initialContentsUpdateVector]);

// Cleanup of the ydoc it is re-initialized
useEffect(() => {
return () => {
if (ydoc) {
ydoc.destroy();
}
};
}, [ydoc]);

// Creates a new editor instance.
const editor = useCreateBlockNote(
{
collaboration: {
fragment: ydoc.getXmlFragment("document-store"),
provider: null,
user: {
name: "me",
color: "white",
},
},
schema,
},
[ydoc] // Since we are changing the doc we need to re-initialize the editor
);

return (
<>
<button onClick={() => refetch()}>refetch initial contents</button>
<BlockNoteView editor={editor}></BlockNoteView>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Alert Block

In this example, we create a custom `Alert` block which is used to emphasize text. In addition, we create a Slash Menu item which inserts an `Alert` block.

**Try it out:** Press the "/" key to open the Slash Menu and insert an `Alert` block!

**Relevant Docs:**

- [Custom Blocks](/docs/custom-schemas/custom-blocks)
- [Changing Slash Menu Items](/docs/ui-components/suggestion-menus#changing-slash-menu-items)
- [Editor Setup](/docs/editor-basics/setup)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html lang="en">
<head>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
</script>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Alert Block</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const root = createRoot(document.getElementById('root')!);
const queryClient = new QueryClient();

root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@blocknote/example-alert-block",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"private": true,
"version": "0.12.4",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --max-warnings 0"
},
"dependencies": {
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
"@mantine/core": "^7.10.1",
"@tanstack/react-query": "^5.51.21",
"js-base64": "^3.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.2.1"
},
"devDependencies": {
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^4.0.4",
"eslint": "^8.10.0",
"vite": "^4.4.8"
},
"eslintConfig": {
"extends": [
"../../../.eslintrc.js"
]
},
"eslintIgnore": [
"dist"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
.alert {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
border-radius: 4px;
min-height: 48px;
padding: 4px;
}

.alert[data-alert-type="warning"] {
background-color: #fff6e6;
}

.alert[data-alert-type="error"] {
background-color: #ffe6e6;
}

.alert[data-alert-type="info"] {
background-color: #e6ebff;
}

.alert[data-alert-type="success"] {
background-color: #e6ffe6;
}

[data-color-scheme="dark"] .alert[data-alert-type="warning"] {
background-color: #805d20;
}

[data-color-scheme="dark"] .alert[data-alert-type="error"] {
background-color: #802020;
}

[data-color-scheme="dark"] .alert[data-alert-type="info"] {
background-color: #203380;
}

[data-color-scheme="dark"] .alert[data-alert-type="success"] {
background-color: #208020;
}

.alert-icon-wrapper {
border-radius: 16px;
display: flex;
justify-content: center;
align-items: center;
margin-left: 12px;
margin-right: 12px;
height: 18px;
width: 18px;
user-select: none;
cursor: pointer;
}

.alert-icon[data-alert-icon-type="warning"] {
color: #e69819
}

.alert-icon[data-alert-icon-type="error"] {
color: #d80d0d
}

.alert-icon[data-alert-icon-type="info"] {
color: #507aff
}

.alert-icon[data-alert-icon-type="success"] {
color: #0bc10b
}

.inline-content {
flex-grow: 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true
},
"include": [
"."
],
"__ADD_FOR_LOCAL_DEV_references": [
{
"path": "../../../packages/core/"
},
{
"path": "../../../packages/react/"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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";
// import eslintPlugin from "vite-plugin-eslint";
// https://vitejs.dev/config/
export default defineConfig((conf) => ({
plugins: [react()],
optimizeDeps: {},
build: {
sourcemap: true,
},
resolve: {
alias:
conf.command === "build" ||
!fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
? {}
: ({
// 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),
},
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "github-a6kavu-x9g2cp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm-run-all --parallel start-server start-client",
"start-client": "cd client && npm install && npm run dev",
"start-server": "cd server && npm install && npm run start",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"npm-run-all": "^4.1.5"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import express from "express";
import { ServerBlockNoteEditor } from "@blocknote/server-util";
import { fromUint8Array } from "js-base64";
import * as Y from "yjs";
import { LoremIpsum } from "lorem-ipsum";

const lorem = new LoremIpsum({
sentencesPerParagraph: {
max: 8,
min: 4,
},
wordsPerSentence: {
max: 16,
min: 4,
},
});

const app = express();
const port = 8080;
const editor = ServerBlockNoteEditor.create();

app.get("/", async (req, res) => {
const doc = await editor.blocksToYDoc(
[
{
id: "7d181c92-fd43-405a-9760-d7feff142917",
type: "paragraph",
props: {
textColor: "default",
backgroundColor: "default",
textAlignment: "left",
},
content: [
{
type: "text",
text: lorem.generateSentences(2),
styles: {},
},
],
children: [],
},
],
"document-store"
);

// https://docs.yjs.dev/api/document-updates encode the state as a vector which we can later apply in the frontend
const state = Y.encodeStateAsUpdateV2(doc);
const initialContent = fromUint8Array(state);
res.send(initialContent);
});

app.listen(port, () => {
console.log(`App listening at http://localhost:${port}`);
});
Loading

0 comments on commit 74560b4

Please sign in to comment.