feat(docs): bootstrapping using blocksuite (#2859)
This commit is contained in:
@@ -1,21 +1,17 @@
|
|||||||
import { defineEntries } from 'waku/server';
|
import { defineRouter } from 'waku/router/server';
|
||||||
|
|
||||||
export default defineEntries(
|
export default defineRouter(
|
||||||
// getEntry
|
|
||||||
async id => {
|
async id => {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case 'App':
|
case 'index': {
|
||||||
return import('./src/app.js') as any;
|
const { default: AppCreator } = await import('./src/app.js');
|
||||||
|
return AppCreator(id);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// getBuildConfig
|
|
||||||
async () => {
|
async () => {
|
||||||
return {
|
return ['index'];
|
||||||
'/': {
|
|
||||||
elements: [['App', {}]],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,36 +1,44 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
'use server';
|
'use server';
|
||||||
|
import { existsSync, readFileSync } from 'node:fs';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
|
|
||||||
import { Sidebar } from './components/sidebar.js';
|
import { Sidebar } from './components/sidebar/index.js';
|
||||||
|
import { saveFile } from './server-fns.js';
|
||||||
|
|
||||||
const Editor = lazy(() =>
|
const Editor = lazy(() =>
|
||||||
import('./components/editor.js').then(({ Editor }) => ({ default: Editor }))
|
import('./components/editor.js').then(({ Editor }) => ({ default: Editor }))
|
||||||
);
|
);
|
||||||
|
|
||||||
const markdown = `---
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||||
title: AFFiNE Developer Documentation
|
|
||||||
---
|
|
||||||
|
|
||||||
## To Shape, not to adapt
|
const AppCreator = (pathname: string) =>
|
||||||
|
function App(): ReactElement {
|
||||||
|
let path = resolve(__dirname, 'pages', 'binary');
|
||||||
|
if (!existsSync(path)) {
|
||||||
|
path = resolve(__dirname, '..', '..', 'src', 'pages', 'binary');
|
||||||
|
}
|
||||||
|
const buffer = [...readFileSync(path)];
|
||||||
|
|
||||||
---
|
return (
|
||||||
|
<div className="flex flex-col-reverse sm:flex-row">
|
||||||
|
<nav className="w-full sm:w-64">
|
||||||
|
<Sidebar />
|
||||||
|
</nav>
|
||||||
|
<main className="flex-1 p-6 w-full sm:w-[calc(100%-16rem)]">
|
||||||
|
<Editor
|
||||||
|
workspaceId={pathname}
|
||||||
|
pageId="1"
|
||||||
|
onSave={saveFile}
|
||||||
|
binary={buffer}
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
**Powered by BlockSuite**
|
export default AppCreator;
|
||||||
`;
|
|
||||||
|
|
||||||
const App = (): ReactElement => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col-reverse sm:flex-row">
|
|
||||||
<nav className="w-full sm:w-64">
|
|
||||||
<Sidebar />
|
|
||||||
</nav>
|
|
||||||
<main className="flex-1 p-6 w-full sm:w-[calc(100%-16rem)]">
|
|
||||||
<Editor text={markdown} />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|||||||
11
apps/docs/src/atom.ts
Normal file
11
apps/docs/src/atom.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||||
|
import { atom } from 'jotai/vanilla';
|
||||||
|
|
||||||
|
export const workspaceAtom = atom(async () => {
|
||||||
|
const { Workspace } = await import('@blocksuite/store');
|
||||||
|
return new Workspace({
|
||||||
|
id: 'test-workspace',
|
||||||
|
})
|
||||||
|
.register(AffineSchemas)
|
||||||
|
.register(__unstableSchemas);
|
||||||
|
});
|
||||||
@@ -2,54 +2,52 @@
|
|||||||
import '@blocksuite/editor/themes/affine.css';
|
import '@blocksuite/editor/themes/affine.css';
|
||||||
|
|
||||||
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
|
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
|
||||||
import { ContentParser } from '@blocksuite/blocks/content-parser';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
import { useAtomValue } from 'jotai/react';
|
||||||
import { assertExists, Workspace } from '@blocksuite/store';
|
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { use } from 'react';
|
||||||
|
import { applyUpdate } from 'yjs';
|
||||||
|
|
||||||
const workspace = new Workspace({
|
import { workspaceAtom } from '../atom.js';
|
||||||
id: 'local-workspace',
|
|
||||||
})
|
|
||||||
.register(AffineSchemas)
|
|
||||||
.register(__unstableSchemas);
|
|
||||||
|
|
||||||
const page = workspace.createPage({
|
|
||||||
id: 'example-page',
|
|
||||||
});
|
|
||||||
|
|
||||||
export type EditorProps = {
|
export type EditorProps = {
|
||||||
text: string;
|
workspaceId: string;
|
||||||
|
pageId: string;
|
||||||
|
binary?: number[];
|
||||||
|
onSave: (binary: any) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Editor = (props: EditorProps): ReactElement => {
|
export const Editor = (props: EditorProps): ReactElement => {
|
||||||
return (
|
const workspace = useAtomValue(workspaceAtom);
|
||||||
<BlockSuiteEditor
|
let page = workspace.getPage('page0') as Page;
|
||||||
page={page}
|
if (!page) {
|
||||||
mode="page"
|
page = workspace.createPage({
|
||||||
onInit={useCallback(
|
id: 'page0',
|
||||||
async page => {
|
});
|
||||||
const text = props.text;
|
}
|
||||||
await page.waitForLoaded();
|
|
||||||
const metadata = text.split('---\n')[1];
|
|
||||||
assertExists(metadata);
|
|
||||||
|
|
||||||
// find title
|
if (props.binary && !page.root) {
|
||||||
const title = metadata.split('title: ')[1]?.split('\n')[0];
|
use(
|
||||||
const pageBlockId = page.addBlock('affine:page', {
|
page.waitForLoaded().then(() => {
|
||||||
title: new page.Text(title),
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
});
|
// @ts-ignore
|
||||||
page.addBlock('affine:surface', {}, pageBlockId);
|
applyUpdate(page._ySpaceDoc, new Uint8Array(props.binary as number[]));
|
||||||
const noteBlockId = page.addBlock('affine:note', {}, pageBlockId);
|
})
|
||||||
const contentParser = new ContentParser(page);
|
);
|
||||||
const content = text.split('---\n').splice(2).join('---\n');
|
if (import.meta.env.MODE !== 'development') {
|
||||||
assertExists(content);
|
page.awarenessStore.setReadonly(page, true);
|
||||||
await contentParser.importMarkdown(content, noteBlockId);
|
}
|
||||||
page.awarenessStore.setReadonly(page, true);
|
} else if (!page.root) {
|
||||||
page.awarenessStore.setFlag('enable_drag_handle', false);
|
use(
|
||||||
},
|
page.waitForLoaded().then(() => {
|
||||||
[props.text]
|
const pageBlockId = page.addBlock('affine:page', {
|
||||||
)}
|
title: new page.Text(''),
|
||||||
/>
|
});
|
||||||
);
|
page.addBlock('affine:surface', {}, pageBlockId);
|
||||||
|
const noteBlockId = page.addBlock('affine:note', {}, pageBlockId);
|
||||||
|
page.addBlock('affine:paragraph', {}, noteBlockId);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <BlockSuiteEditor page={page} mode="page" onInit={() => {}} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
import { saveFile } from '../../server-fns.js';
|
||||||
|
|
||||||
|
const SaveToLocal = lazy(() =>
|
||||||
|
import('./save-to-local.js').then(({ SaveToLocal }) => ({
|
||||||
|
default: SaveToLocal,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
export const Sidebar = () => {
|
export const Sidebar = () => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -11,6 +23,9 @@ export const Sidebar = () => {
|
|||||||
AFFiNE
|
AFFiNE
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
{import.meta.env.MODE === 'development' && (
|
||||||
|
<SaveToLocal saveFile={saveFile} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
28
apps/docs/src/components/sidebar/save-to-local.tsx
Normal file
28
apps/docs/src/components/sidebar/save-to-local.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use client';
|
||||||
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
|
import { useAtomValue } from 'jotai/react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { encodeStateAsUpdate } from 'yjs';
|
||||||
|
|
||||||
|
import { workspaceAtom } from '../../atom.js';
|
||||||
|
|
||||||
|
type SaveToLocalProps = {
|
||||||
|
saveFile: (update: number[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SaveToLocal = (props: SaveToLocalProps) => {
|
||||||
|
const workspace = useAtomValue(workspaceAtom);
|
||||||
|
const saveFile = props.saveFile;
|
||||||
|
const onSave = useCallback(() => {
|
||||||
|
const page = workspace.getPage('page0');
|
||||||
|
assertExists(page);
|
||||||
|
saveFile([...encodeStateAsUpdate(page.spaceDoc)]);
|
||||||
|
}, [saveFile, workspace]);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-center h-16 font-bold">
|
||||||
|
<button onClick={onSave}>Save to Local</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
|
import '@blocksuite/editor/themes/affine.css';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { serve } from 'waku/client';
|
import { Router } from 'waku/router/client';
|
||||||
|
|
||||||
const App = serve('App');
|
const root = createRoot(document.getElementById('root') as HTMLElement);
|
||||||
const rootElement = (
|
|
||||||
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<Router />
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
createRoot(document.getElementById('root') as HTMLElement).render(rootElement);
|
|
||||||
|
|||||||
BIN
apps/docs/src/pages/binary
Normal file
BIN
apps/docs/src/pages/binary
Normal file
Binary file not shown.
10
apps/docs/src/server-fns.ts
Normal file
10
apps/docs/src/server-fns.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
'use server';
|
||||||
|
import { writeFile } from 'node:fs/promises';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||||
|
|
||||||
|
export async function saveFile(binary: any) {
|
||||||
|
const data = new Uint8Array(binary);
|
||||||
|
await writeFile(__dirname + 'pages' + '/binary', data);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user