Skip to content

Commit

Permalink
feat(examples): add react-basic example (#6375)
Browse files Browse the repository at this point in the history
  • Loading branch information
doodlewind authored and pull[bot] committed Oct 25, 2024
1 parent 3253ee3 commit 1ae545a
Show file tree
Hide file tree
Showing 25 changed files with 401 additions and 9 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ You can consider BlockSuite as a [UI component library](https://blocksuite.io/co
- Reuse multiple first-party BlockSuite editors:
- [**`PageEditor`**](https://blocksuite.io/components/editors/page-editor.html): A comprehensive block-based document editor, offering extensive customization and flexibility.
- [**`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`.
- 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.
- 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.
- Or, build new editors from scratch based on the underlying vanilla framework.

> 🚧 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!
Expand Down Expand Up @@ -120,6 +120,7 @@ This can be illustrated as the diagram below:

- 🚚 Resources
- [Canary Playground](https://try-blocksuite.vercel.app/starter/?init)
- [Examples](./examples/)
- [BlockSuite in StackBlitz](https://stackblitz.com/github/toeverything/blocksuite)
- [Testing Real-Time Collaboration](https://github.com/toeverything/blocksuite/blob/master/BUILDING.md#test-collaboration)
- [BlockSuite Ecosystem CI](https://github.com/toeverything/blocksuite-ecosystem-ci)
Expand Down
25 changes: 24 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# BlockSuite Examples

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.
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.

## Install

```sh
pnpm install
```

## Run Example

```sh
pnpm dev example-name
```

## Example List

- [react-basic](./react-basic/)
- [react-sqlite](./react-sqlite/)
- [vue-basic](./vue-basic/)
- [angular-basic](./angular-basic/)

## Contribution

Contributions of more examples are welcome! If you are a new contributor, please sign the [CLA.md](../.github/CLA.md) in your pull request.
11 changes: 11 additions & 0 deletions examples/dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

if [ -z "$1" ]
then
echo "Please specify a project name."
exit 1
fi

PROJECT_NAME=$1

pnpm -C "./$PROJECT_NAME" dev
12 changes: 12 additions & 0 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "blocksuite-examples",
"version": "0.0.0",
"description": "",
"private": true,
"scripts": {
"dev": "./dev.sh"
},
"keywords": [],
"author": "toeverything",
"license": "MPL-2.0"
}
18 changes: 18 additions & 0 deletions examples/react-basic/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
};
24 changes: 24 additions & 0 deletions examples/react-basic/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
9 changes: 9 additions & 0 deletions examples/react-basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# React SQLite Example

This example encapsulates the BlockSuite editor and workspace in React, demonstrating basic document and workspace management.

## Development

```sh
pnpm dev
```
12 changes: 12 additions & 0 deletions examples/react-basic/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BlockSuite Example</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
32 changes: 32 additions & 0 deletions examples/react-basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "react-basic",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@blocksuite/blocks": "canary",
"@blocksuite/presets": "canary",
"@blocksuite/store": "canary",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.56",
"@types/react-dom": "^18.2.19",
"@types/sql.js": "^1.4.9",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.56.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"typescript": "^5.2.2",
"vite": "^5.1.4"
}
}
21 changes: 21 additions & 0 deletions examples/react-basic/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { EditorProvider } from './components/EditorProvider';
import Sidebar from './components/Sidebar';
import TopBar from './components/TopBar';
import EditorContainer from './components/EditorContainer';
import './index.css';

function App() {
return (
<EditorProvider>
<div className="app">
<Sidebar />
<div className="main-content">
<TopBar />
<EditorContainer />
</div>
</div>
</EditorProvider>
);
}

export default App;
19 changes: 19 additions & 0 deletions examples/react-basic/src/components/EditorContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useRef } from 'react';
import { useEditor } from '../editor/context';

const EditorContainer = () => {
const { editor } = useEditor()!;

const editorContainerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (editorContainerRef.current && editor) {
editorContainerRef.current.innerHTML = '';
editorContainerRef.current.appendChild(editor);
}
}, [editor]);

return <div className="editor-container" ref={editorContainerRef}></div>;
};

export default EditorContainer;
13 changes: 13 additions & 0 deletions examples/react-basic/src/components/EditorProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { initEditor } from '../editor/editor';
import { EditorContext } from '../editor/context';

export const EditorProvider = ({ children }: { children: React.ReactNode }) => {
const { editor, workspace } = initEditor();

return (
<EditorContext.Provider value={{ editor, workspace }}>
{children}
</EditorContext.Provider>
);
};
45 changes: 45 additions & 0 deletions examples/react-basic/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useEffect, useState } from 'react';
import { Doc } from '@blocksuite/store';
import { useEditor } from '../editor/context';

const Sidebar = () => {
const { workspace, editor } = useEditor()!;
const [docs, setDocs] = useState<Doc[]>([]);

useEffect(() => {
if (!workspace || !editor) return;
const updateDocs = () => {
setDocs([...workspace.docs.values()]);
};
updateDocs();

const disposable = [
workspace.slots.docUpdated.on(updateDocs),
editor.slots.docLinkClicked.on(updateDocs),
];

return () => disposable.forEach(d => d.dispose());
}, [workspace, editor]);

return (
<div className="sidebar">
<div className="header">All Docs</div>
<div className="doc-list">
{docs.map(doc => (
<div
className={`doc-item ${editor?.doc === doc ? 'active' : ''}`}
key={doc.id}
onClick={() => {
if (editor) editor.doc = doc;
setDocs([...workspace!.docs.values()]);
}}
>
{doc.meta?.title || 'Untitled'}
</div>
))}
</div>
</div>
);
};

export default Sidebar;
3 changes: 3 additions & 0 deletions examples/react-basic/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const TopBar = () => <div className="top-bar">React Basic</div>;

export default TopBar;
12 changes: 12 additions & 0 deletions examples/react-basic/src/editor/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AffineEditorContainer } from '@blocksuite/presets';
import { Workspace } from '@blocksuite/store';
import { createContext, useContext } from 'react';

export const EditorContext = createContext<{
editor: AffineEditorContainer;
workspace: Workspace;
} | null>(null);

export function useEditor() {
return useContext(EditorContext);
}
39 changes: 39 additions & 0 deletions examples/react-basic/src/editor/editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import '@blocksuite/presets/themes/affine.css';
import { AffineEditorContainer } from '@blocksuite/presets';
import { Doc, Schema } from '@blocksuite/store';
import { Workspace } from '@blocksuite/store';
import { createContext, useContext } from 'react';
import { AffineSchemas } from '@blocksuite/blocks';

export interface EditorContextType {
editor: AffineEditorContainer | null;
workspace: Workspace | null;
updateWorkspace: (newWorkspace: Workspace) => void;
}

export const EditorContext = createContext<EditorContextType | null>(null);

export function useEditor() {
return useContext(EditorContext);
}

export function initEditor() {
const schema = new Schema().register(AffineSchemas);
const workspace = new Workspace({ schema });
const doc = workspace.createDoc({ id: 'page1' });

doc.load(() => {
const pageBlockId = doc.addBlock('affine:page', {});
doc.addBlock('affine:surface', {}, pageBlockId);
const noteId = doc.addBlock('affine:note', {}, pageBlockId);
doc.addBlock('affine:paragraph', {}, noteId);
});

const editor = new AffineEditorContainer();
editor.doc = doc;
editor.slots.docLinkClicked.on(({ docId }) => {
const target = <Doc>workspace.getDoc(docId);
editor.doc = target;
});
return { editor, workspace };
}
35 changes: 35 additions & 0 deletions examples/react-basic/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.app {
display: flex;
font-family: sans-serif;
}

.sidebar {
padding: 10px;
width: 250px;
}

.sidebar .header {
margin-bottom: 10px;
}

.doc-item {
padding: 5px;
}

.doc-item.active,
.doc-item:hover {
background-color: #f0f0f0;
}

.top-bar {
display: flex;
flex-direction: row-reverse;
}

.top-bar button {
margin: auto 2px;
}

.main-content {
flex: 1;
}
9 changes: 9 additions & 0 deletions examples/react-basic/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
1 change: 1 addition & 0 deletions examples/react-basic/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
Loading

0 comments on commit 1ae545a

Please sign in to comment.