feat(docs): bootstrapping using blocksuite (#2859)

This commit is contained in:
Alex Yang 2023-06-26 21:39:07 +08:00 committed by GitHub
parent bddcfe1b8b
commit e3ffd04804
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 148 additions and 82 deletions

View File

@ -1,21 +1,17 @@
import { defineEntries } from 'waku/server';
import { defineRouter } from 'waku/router/server';
export default defineEntries(
// getEntry
export default defineRouter(
async id => {
switch (id) {
case 'App':
return import('./src/app.js') as any;
case 'index': {
const { default: AppCreator } = await import('./src/app.js');
return AppCreator(id);
}
default:
return null;
}
},
// getBuildConfig
async () => {
return {
'/': {
elements: [['App', {}]],
},
};
return ['index'];
}
);

View File

@ -1,36 +1,44 @@
/// <reference types="vite/client" />
'use server';
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { ReactElement } 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(() =>
import('./components/editor.js').then(({ Editor }) => ({ default: Editor }))
);
const markdown = `---
title: AFFiNE Developer Documentation
---
const __dirname = fileURLToPath(new URL('.', import.meta.url));
## 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**
`;
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;
export default AppCreator;

11
apps/docs/src/atom.ts Normal file
View 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);
});

View File

@ -2,54 +2,52 @@
import '@blocksuite/editor/themes/affine.css';
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
import { ContentParser } from '@blocksuite/blocks/content-parser';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import { assertExists, Workspace } from '@blocksuite/store';
import type { Page } from '@blocksuite/store';
import { useAtomValue } from 'jotai/react';
import type { ReactElement } from 'react';
import { useCallback } from 'react';
import { use } from 'react';
import { applyUpdate } from 'yjs';
const workspace = new Workspace({
id: 'local-workspace',
})
.register(AffineSchemas)
.register(__unstableSchemas);
const page = workspace.createPage({
id: 'example-page',
});
import { workspaceAtom } from '../atom.js';
export type EditorProps = {
text: string;
workspaceId: string;
pageId: string;
binary?: number[];
onSave: (binary: any) => Promise<void>;
};
export const Editor = (props: EditorProps): ReactElement => {
return (
<BlockSuiteEditor
page={page}
mode="page"
onInit={useCallback(
async page => {
const text = props.text;
await page.waitForLoaded();
const metadata = text.split('---\n')[1];
assertExists(metadata);
const workspace = useAtomValue(workspaceAtom);
let page = workspace.getPage('page0') as Page;
if (!page) {
page = workspace.createPage({
id: 'page0',
});
}
// find title
const title = metadata.split('title: ')[1]?.split('\n')[0];
const pageBlockId = page.addBlock('affine:page', {
title: new page.Text(title),
});
page.addBlock('affine:surface', {}, pageBlockId);
const noteBlockId = page.addBlock('affine:note', {}, pageBlockId);
const contentParser = new ContentParser(page);
const content = text.split('---\n').splice(2).join('---\n');
assertExists(content);
await contentParser.importMarkdown(content, noteBlockId);
page.awarenessStore.setReadonly(page, true);
page.awarenessStore.setFlag('enable_drag_handle', false);
},
[props.text]
)}
/>
);
if (props.binary && !page.root) {
use(
page.waitForLoaded().then(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
applyUpdate(page._ySpaceDoc, new Uint8Array(props.binary as number[]));
})
);
if (import.meta.env.MODE !== 'development') {
page.awarenessStore.setReadonly(page, true);
}
} else if (!page.root) {
use(
page.waitForLoaded().then(() => {
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={() => {}} />;
};

View File

@ -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 = () => {
return (
<div
@ -11,6 +23,9 @@ export const Sidebar = () => {
AFFiNE
</div>
</a>
{import.meta.env.MODE === 'development' && (
<SaveToLocal saveFile={saveFile} />
)}
</div>
);
};

View 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>
);
};

View File

@ -1,14 +1,14 @@
import '@blocksuite/editor/themes/affine.css';
import './index.css';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { serve } from 'waku/client';
import { Router } from 'waku/router/client';
const App = serve('App');
const rootElement = (
const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(
<StrictMode>
<App />
<Router />
</StrictMode>
);
createRoot(document.getElementById('root') as HTMLElement).render(rootElement);

BIN
apps/docs/src/pages/binary Normal file

Binary file not shown.

View 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);
}