Skip to content

Commit 1ae545a

Browse files
doodlewindpull[bot]
authored andcommitted
feat(examples): add react-basic example (#6375)
1 parent 3253ee3 commit 1ae545a

25 files changed

+401
-9
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ You can consider BlockSuite as a [UI component library](https://blocksuite.io/co
3232
- Reuse multiple first-party BlockSuite editors:
3333
- [**`PageEditor`**](https://blocksuite.io/components/editors/page-editor.html): A comprehensive block-based document editor, offering extensive customization and flexibility.
3434
- [**`EdgelessEditor`**](https://blocksuite.io/components/editors/edgeless-editor.html): A graphics editor with opt-in canvas rendering support, but also shares the same rich-text capabilities with the `PageEditor`.
35-
- Customize, extend and enhance these editors with a rich set of [BlockSuite components](https://blocksuite.io/components/overview.html). All BlockSuite components (including editors) are native web components, making them framework-agnostic and easy to interop with popular frameworks.
35+
- Customize, extend and enhance these editors with a rich set of [BlockSuite components](https://blocksuite.io/components/overview.html) and [examples](./examples/). All BlockSuite components (including editors) are native web components, making them framework-agnostic and easy to interop with popular frameworks.
3636
- Or, build new editors from scratch based on the underlying vanilla framework.
3737

3838
> 🚧 BlockSuite is currently in its early stage, with components and extension capabilities still under refinement. Hope you can stay tuned, try it out, or share your feedback!
@@ -120,6 +120,7 @@ This can be illustrated as the diagram below:
120120

121121
- 🚚 Resources
122122
- [Canary Playground](https://try-blocksuite.vercel.app/starter/?init)
123+
- [Examples](./examples/)
123124
- [BlockSuite in StackBlitz](https://stackblitz.com/github/toeverything/blocksuite)
124125
- [Testing Real-Time Collaboration](https://github.com/toeverything/blocksuite/blob/master/BUILDING.md#test-collaboration)
125126
- [BlockSuite Ecosystem CI](https://github.com/toeverything/blocksuite-ecosystem-ci)

examples/README.md

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
11
# BlockSuite Examples
22

3-
This collection showcases how to integrate, use, and configure BlockSuite across various common project setups. These examples are built and maintained independently from the packages in the monorepo. Contributions of more examples are welcome.
3+
This collection showcases how to integrate, use, and configure BlockSuite across various common project setups. These examples are built and maintained independently from the packages in the monorepo.
4+
5+
## Install
6+
7+
```sh
8+
pnpm install
9+
```
10+
11+
## Run Example
12+
13+
```sh
14+
pnpm dev example-name
15+
```
16+
17+
## Example List
18+
19+
- [react-basic](./react-basic/)
20+
- [react-sqlite](./react-sqlite/)
21+
- [vue-basic](./vue-basic/)
22+
- [angular-basic](./angular-basic/)
23+
24+
## Contribution
25+
26+
Contributions of more examples are welcome! If you are a new contributor, please sign the [CLA.md](../.github/CLA.md) in your pull request.

examples/dev.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
if [ -z "$1" ]
4+
then
5+
echo "Please specify a project name."
6+
exit 1
7+
fi
8+
9+
PROJECT_NAME=$1
10+
11+
pnpm -C "./$PROJECT_NAME" dev

examples/package.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "blocksuite-examples",
3+
"version": "0.0.0",
4+
"description": "",
5+
"private": true,
6+
"scripts": {
7+
"dev": "./dev.sh"
8+
},
9+
"keywords": [],
10+
"author": "toeverything",
11+
"license": "MPL-2.0"
12+
}

examples/react-basic/.eslintrc.cjs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module.exports = {
2+
root: true,
3+
env: { browser: true, es2020: true },
4+
extends: [
5+
'eslint:recommended',
6+
'plugin:@typescript-eslint/recommended',
7+
'plugin:react-hooks/recommended',
8+
],
9+
ignorePatterns: ['dist', '.eslintrc.cjs'],
10+
parser: '@typescript-eslint/parser',
11+
plugins: ['react-refresh'],
12+
rules: {
13+
'react-refresh/only-export-components': [
14+
'warn',
15+
{ allowConstantExport: true },
16+
],
17+
},
18+
};

examples/react-basic/.gitignore

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

examples/react-basic/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# React SQLite Example
2+
3+
This example encapsulates the BlockSuite editor and workspace in React, demonstrating basic document and workspace management.
4+
5+
## Development
6+
7+
```sh
8+
pnpm dev
9+
```

examples/react-basic/index.html

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>BlockSuite Example</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>

examples/react-basic/package.json

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "react-basic",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc && vite build",
9+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"@blocksuite/blocks": "canary",
14+
"@blocksuite/presets": "canary",
15+
"@blocksuite/store": "canary",
16+
"react": "^18.2.0",
17+
"react-dom": "^18.2.0"
18+
},
19+
"devDependencies": {
20+
"@types/react": "^18.2.56",
21+
"@types/react-dom": "^18.2.19",
22+
"@types/sql.js": "^1.4.9",
23+
"@typescript-eslint/eslint-plugin": "^7.0.2",
24+
"@typescript-eslint/parser": "^7.0.2",
25+
"@vitejs/plugin-react": "^4.2.1",
26+
"eslint": "^8.56.0",
27+
"eslint-plugin-react-hooks": "^4.6.0",
28+
"eslint-plugin-react-refresh": "^0.4.5",
29+
"typescript": "^5.2.2",
30+
"vite": "^5.1.4"
31+
}
32+
}

examples/react-basic/src/App.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { EditorProvider } from './components/EditorProvider';
2+
import Sidebar from './components/Sidebar';
3+
import TopBar from './components/TopBar';
4+
import EditorContainer from './components/EditorContainer';
5+
import './index.css';
6+
7+
function App() {
8+
return (
9+
<EditorProvider>
10+
<div className="app">
11+
<Sidebar />
12+
<div className="main-content">
13+
<TopBar />
14+
<EditorContainer />
15+
</div>
16+
</div>
17+
</EditorProvider>
18+
);
19+
}
20+
21+
export default App;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useEffect, useRef } from 'react';
2+
import { useEditor } from '../editor/context';
3+
4+
const EditorContainer = () => {
5+
const { editor } = useEditor()!;
6+
7+
const editorContainerRef = useRef<HTMLDivElement>(null);
8+
9+
useEffect(() => {
10+
if (editorContainerRef.current && editor) {
11+
editorContainerRef.current.innerHTML = '';
12+
editorContainerRef.current.appendChild(editor);
13+
}
14+
}, [editor]);
15+
16+
return <div className="editor-container" ref={editorContainerRef}></div>;
17+
};
18+
19+
export default EditorContainer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import { initEditor } from '../editor/editor';
3+
import { EditorContext } from '../editor/context';
4+
5+
export const EditorProvider = ({ children }: { children: React.ReactNode }) => {
6+
const { editor, workspace } = initEditor();
7+
8+
return (
9+
<EditorContext.Provider value={{ editor, workspace }}>
10+
{children}
11+
</EditorContext.Provider>
12+
);
13+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useEffect, useState } from 'react';
2+
import { Doc } from '@blocksuite/store';
3+
import { useEditor } from '../editor/context';
4+
5+
const Sidebar = () => {
6+
const { workspace, editor } = useEditor()!;
7+
const [docs, setDocs] = useState<Doc[]>([]);
8+
9+
useEffect(() => {
10+
if (!workspace || !editor) return;
11+
const updateDocs = () => {
12+
setDocs([...workspace.docs.values()]);
13+
};
14+
updateDocs();
15+
16+
const disposable = [
17+
workspace.slots.docUpdated.on(updateDocs),
18+
editor.slots.docLinkClicked.on(updateDocs),
19+
];
20+
21+
return () => disposable.forEach(d => d.dispose());
22+
}, [workspace, editor]);
23+
24+
return (
25+
<div className="sidebar">
26+
<div className="header">All Docs</div>
27+
<div className="doc-list">
28+
{docs.map(doc => (
29+
<div
30+
className={`doc-item ${editor?.doc === doc ? 'active' : ''}`}
31+
key={doc.id}
32+
onClick={() => {
33+
if (editor) editor.doc = doc;
34+
setDocs([...workspace!.docs.values()]);
35+
}}
36+
>
37+
{doc.meta?.title || 'Untitled'}
38+
</div>
39+
))}
40+
</div>
41+
</div>
42+
);
43+
};
44+
45+
export default Sidebar;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const TopBar = () => <div className="top-bar">React Basic</div>;
2+
3+
export default TopBar;
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { AffineEditorContainer } from '@blocksuite/presets';
2+
import { Workspace } from '@blocksuite/store';
3+
import { createContext, useContext } from 'react';
4+
5+
export const EditorContext = createContext<{
6+
editor: AffineEditorContainer;
7+
workspace: Workspace;
8+
} | null>(null);
9+
10+
export function useEditor() {
11+
return useContext(EditorContext);
12+
}
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import '@blocksuite/presets/themes/affine.css';
2+
import { AffineEditorContainer } from '@blocksuite/presets';
3+
import { Doc, Schema } from '@blocksuite/store';
4+
import { Workspace } from '@blocksuite/store';
5+
import { createContext, useContext } from 'react';
6+
import { AffineSchemas } from '@blocksuite/blocks';
7+
8+
export interface EditorContextType {
9+
editor: AffineEditorContainer | null;
10+
workspace: Workspace | null;
11+
updateWorkspace: (newWorkspace: Workspace) => void;
12+
}
13+
14+
export const EditorContext = createContext<EditorContextType | null>(null);
15+
16+
export function useEditor() {
17+
return useContext(EditorContext);
18+
}
19+
20+
export function initEditor() {
21+
const schema = new Schema().register(AffineSchemas);
22+
const workspace = new Workspace({ schema });
23+
const doc = workspace.createDoc({ id: 'page1' });
24+
25+
doc.load(() => {
26+
const pageBlockId = doc.addBlock('affine:page', {});
27+
doc.addBlock('affine:surface', {}, pageBlockId);
28+
const noteId = doc.addBlock('affine:note', {}, pageBlockId);
29+
doc.addBlock('affine:paragraph', {}, noteId);
30+
});
31+
32+
const editor = new AffineEditorContainer();
33+
editor.doc = doc;
34+
editor.slots.docLinkClicked.on(({ docId }) => {
35+
const target = <Doc>workspace.getDoc(docId);
36+
editor.doc = target;
37+
});
38+
return { editor, workspace };
39+
}

examples/react-basic/src/index.css

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.app {
2+
display: flex;
3+
font-family: sans-serif;
4+
}
5+
6+
.sidebar {
7+
padding: 10px;
8+
width: 250px;
9+
}
10+
11+
.sidebar .header {
12+
margin-bottom: 10px;
13+
}
14+
15+
.doc-item {
16+
padding: 5px;
17+
}
18+
19+
.doc-item.active,
20+
.doc-item:hover {
21+
background-color: #f0f0f0;
22+
}
23+
24+
.top-bar {
25+
display: flex;
26+
flex-direction: row-reverse;
27+
}
28+
29+
.top-bar button {
30+
margin: auto 2px;
31+
}
32+
33+
.main-content {
34+
flex: 1;
35+
}

examples/react-basic/src/main.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
import App from './App.tsx';
4+
5+
ReactDOM.createRoot(document.getElementById('root')!).render(
6+
<React.StrictMode>
7+
<App />
8+
</React.StrictMode>
9+
);
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />

0 commit comments

Comments
 (0)