瀏覽代碼

feat: split create document and open document (#2261)

* fix: add  method

* fix: update text block and doc title
qinluhe 2 年之前
父節點
當前提交
07947db98b
共有 19 個文件被更改,包括 240 次插入139 次删除
  1. 5 5
      frontend/appflowy_tauri/src-tauri/Cargo.lock
  2. 6 10
      frontend/appflowy_tauri/src/appflowy_app/components/document/DocumentTitle/index.tsx
  3. 12 22
      frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx
  4. 1 0
      frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx
  5. 4 4
      frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx
  6. 2 1
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SubscribeNode.hooks.ts
  7. 17 9
      frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextInput.hooks.ts
  8. 19 12
      frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts
  9. 8 0
      frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts
  10. 10 9
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_bd_svc.ts
  11. 32 21
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_controller.ts
  12. 4 3
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_observer.ts
  13. 10 6
      frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts
  14. 25 25
      frontend/rust-lib/Cargo.lock
  15. 2 2
      frontend/rust-lib/flowy-document2/src/document.rs
  16. 25 0
      frontend/rust-lib/flowy-document2/src/entities.rs
  17. 23 2
      frontend/rust-lib/flowy-document2/src/event_handler.rs
  18. 5 1
      frontend/rust-lib/flowy-document2/src/event_map.rs
  19. 30 7
      frontend/rust-lib/flowy-document2/src/manager.rs

+ 5 - 5
frontend/appflowy_tauri/src-tauri/Cargo.lock

@@ -576,7 +576,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab"
 name = "collab"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#dd49b08fae2ad008844409c7ce6b8c754446955c"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#986737b4125162ae94dbeb6ece8267714f67b1dd"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "bytes",
  "bytes",
@@ -594,7 +594,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-derive"
 name = "collab-derive"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#dd49b08fae2ad008844409c7ce6b8c754446955c"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#986737b4125162ae94dbeb6ece8267714f67b1dd"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
@@ -606,7 +606,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-document"
 name = "collab-document"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#dd49b08fae2ad008844409c7ce6b8c754446955c"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#986737b4125162ae94dbeb6ece8267714f67b1dd"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "collab",
  "collab",
@@ -622,7 +622,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-folder"
 name = "collab-folder"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#dd49b08fae2ad008844409c7ce6b8c754446955c"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#986737b4125162ae94dbeb6ece8267714f67b1dd"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "collab",
  "collab",
@@ -640,7 +640,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-persistence"
 name = "collab-persistence"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#dd49b08fae2ad008844409c7ce6b8c754446955c"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#986737b4125162ae94dbeb6ece8267714f67b1dd"
 dependencies = [
 dependencies = [
  "bincode",
  "bincode",
  "chrono",
  "chrono",

+ 6 - 10
frontend/appflowy_tauri/src/appflowy_app/components/document/DocumentTitle/index.tsx

@@ -1,20 +1,16 @@
 import React from 'react';
 import React from 'react';
 import { useDocumentTitle } from './DocumentTitle.hooks';
 import { useDocumentTitle } from './DocumentTitle.hooks';
 import TextBlock from '../TextBlock';
 import TextBlock from '../TextBlock';
+import { NodeContext } from '../_shared/SubscribeNode.hooks';
 
 
 export default function DocumentTitle({ id }: { id: string }) {
 export default function DocumentTitle({ id }: { id: string }) {
   const { node } = useDocumentTitle(id);
   const { node } = useDocumentTitle(id);
   if (!node) return null;
   if (!node) return null;
   return (
   return (
-    <div data-block-id={node.id} className='doc-title relative pt-[50px] text-4xl font-bold'>
-      <TextBlock placeholder='Untitled' childIds={[]} node={{
-        ...node,
-        data: {
-          ...node.data,
-          delta: node.data.delta || [],
-        }
-      }} />
-
-    </div>
+    <NodeContext.Provider value={node}>
+      <div data-block-id={node.id} className='doc-title relative pt-[50px] text-4xl font-bold'>
+        <TextBlock placeholder='Untitled' childIds={[]} node={node} />
+      </div>
+    </NodeContext.Provider>
   );
   );
 }
 }

+ 12 - 22
frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx

@@ -4,7 +4,7 @@ import { withErrorBoundary } from 'react-error-boundary';
 import { ErrorBoundaryFallbackComponent } from '../_shared/ErrorBoundaryFallbackComponent';
 import { ErrorBoundaryFallbackComponent } from '../_shared/ErrorBoundaryFallbackComponent';
 import { Node } from '@/appflowy_app/stores/reducers/document/slice';
 import { Node } from '@/appflowy_app/stores/reducers/document/slice';
 import TextBlock from '../TextBlock';
 import TextBlock from '../TextBlock';
-import { TextDelta } from '@/appflowy_app/interfaces/document';
+import { NodeContext } from '../_shared/SubscribeNode.hooks';
 
 
 function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<HTMLDivElement>) {
 function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<HTMLDivElement>) {
   const { node, childIds, isSelected, ref } = useNode(id);
   const { node, childIds, isSelected, ref } = useNode(id);
@@ -13,19 +13,7 @@ function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<H
   const renderBlock = useCallback((_props: { node: Node; childIds?: string[] }) => {
   const renderBlock = useCallback((_props: { node: Node; childIds?: string[] }) => {
     switch (_props.node.type) {
     switch (_props.node.type) {
       case 'text': {
       case 'text': {
-        const delta = _props.node.data.delta;
-        if (!delta) return null;
-        return (
-          <TextBlock
-            node={{
-              ..._props.node,
-              data: {
-                delta,
-              },
-            }}
-            childIds={childIds}
-          />
-        );
+        return <TextBlock node={node} childIds={childIds} />;
       }
       }
       default:
       default:
         break;
         break;
@@ -35,14 +23,16 @@ function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<H
   if (!node) return null;
   if (!node) return null;
 
 
   return (
   return (
-    <div {...props} ref={ref} data-block-id={node.id} className={`relative my-[2px] px-[2px] ${props.className}`}>
-      {renderBlock({
-        node,
-        childIds,
-      })}
-      <div className='block-overlay' />
-      {isSelected ? <div className='pointer-events-none absolute inset-0 z-[-1] rounded-[4px] bg-[#E0F8FF]' /> : null}
-    </div>
+    <NodeContext.Provider value={node}>
+      <div {...props} ref={ref} data-block-id={node.id} className={`relative my-[2px] px-[2px] ${props.className}`}>
+        {renderBlock({
+          node,
+          childIds,
+        })}
+        <div className='block-overlay' />
+        {isSelected ? <div className='pointer-events-none absolute inset-0 z-[-1] rounded-[4px] bg-[#E0F8FF]' /> : null}
+      </div>
+    </NodeContext.Provider>
   );
   );
 }
 }
 
 

+ 1 - 0
frontend/appflowy_tauri/src/appflowy_app/components/document/Root/index.tsx

@@ -18,6 +18,7 @@ function Root({ documentData }: { documentData: DocumentData }) {
     return <Skeleton />;
     return <Skeleton />;
   }
   }
 
 
+
   return (
   return (
     <div id='appflowy-block-doc' className='h-[100%] overflow-hidden'>
     <div id='appflowy-block-doc' className='h-[100%] overflow-hidden'>
       <VirtualizedList node={node} childIds={childIds} renderNode={renderNode} />
       <VirtualizedList node={node} childIds={childIds} renderNode={renderNode} />

+ 4 - 4
frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx

@@ -4,8 +4,7 @@ import { useTextBlock } from './TextBlock.hooks';
 import { Node } from '@/appflowy_app/stores/reducers/document/slice';
 import { Node } from '@/appflowy_app/stores/reducers/document/slice';
 import NodeComponent from '../Node';
 import NodeComponent from '../Node';
 import HoveringToolbar from '../_shared/HoveringToolbar';
 import HoveringToolbar from '../_shared/HoveringToolbar';
-import React from 'react';
-import { TextDelta } from '@/appflowy_app/interfaces/document';
+import React, { useMemo } from 'react';
 
 
 function TextBlock({
 function TextBlock({
   node,
   node,
@@ -13,11 +12,12 @@ function TextBlock({
   placeholder,
   placeholder,
   ...props
   ...props
 }: {
 }: {
-  node: Node & { data: { delta: TextDelta[] } };
+  node: Node;
   childIds?: string[];
   childIds?: string[];
   placeholder?: string;
   placeholder?: string;
 } & React.HTMLAttributes<HTMLDivElement>) {
 } & React.HTMLAttributes<HTMLDivElement>) {
-  const { editor, value, onChange, onKeyDownCapture, onDOMBeforeInput } = useTextBlock(node.data.delta);
+  const delta = useMemo(() => node.data.delta || [], [node.data.delta]);
+  const { editor, value, onChange, onKeyDownCapture, onDOMBeforeInput } = useTextBlock(delta);
 
 
   return (
   return (
     <div {...props} className={`py-[2px] ${props.className}`}>
     <div {...props} className={`py-[2px] ${props.className}`}>

+ 2 - 1
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SubscribeNode.hooks.ts

@@ -1,6 +1,7 @@
 import { Node } from '@/appflowy_app/stores/reducers/document/slice';
 import { Node } from '@/appflowy_app/stores/reducers/document/slice';
 import { useAppSelector } from '@/appflowy_app/stores/store';
 import { useAppSelector } from '@/appflowy_app/stores/store';
-import { useMemo } from 'react';
+import { useMemo, createContext } from 'react';
+export const NodeContext = createContext<Node | null>(null);
 
 
 /**
 /**
  * Subscribe to a node and its children
  * Subscribe to a node and its children

+ 17 - 9
frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextInput.hooks.ts

@@ -1,12 +1,14 @@
 import { useCallback, useContext, useMemo, useRef, useEffect } from 'react';
 import { useCallback, useContext, useMemo, useRef, useEffect } from 'react';
 import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
 import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
-import { TextDelta } from '$app/interfaces/document';
+import { TextDelta, BlockActionType } from '$app/interfaces/document';
 import { debounce } from '@/appflowy_app/utils/tool';
 import { debounce } from '@/appflowy_app/utils/tool';
 import { createEditor } from 'slate';
 import { createEditor } from 'slate';
 import { withReact } from 'slate-react';
 import { withReact } from 'slate-react';
 
 
 import * as Y from 'yjs';
 import * as Y from 'yjs';
 import { withYjs, YjsEditor, slateNodesToInsertDelta } from '@slate-yjs/core';
 import { withYjs, YjsEditor, slateNodesToInsertDelta } from '@slate-yjs/core';
+import { NodeContext } from './SubscribeNode.hooks';
+import { BlockActionTypePB } from '@/services/backend/models/flowy-document2';
 
 
 export function useTextInput(delta: TextDelta[]) {
 export function useTextInput(delta: TextDelta[]) {
   const { sendDelta } = useTransact();
   const { sendDelta } = useTransact();
@@ -19,23 +21,30 @@ export function useTextInput(delta: TextDelta[]) {
 
 
 function useController() {
 function useController() {
   const docController = useContext(DocumentControllerContext);
   const docController = useContext(DocumentControllerContext);
+  const node = useContext(NodeContext);
 
 
   const update = useCallback(
   const update = useCallback(
-    (delta: TextDelta[]) => {
-      docController?.applyActions([
+    async (delta: TextDelta[]) => {
+      if (!docController || !node) return;
+      await docController.applyActions([
         {
         {
-          type: 'update',
+          action: BlockActionTypePB.Update,
           payload: {
           payload: {
             block: {
             block: {
-              data: {
+              id: node.id,
+              ty: node.type,
+              parent_id: node.parent || '',
+              children_id: node.children,
+              data: JSON.stringify({
+                ...node.data,
                 delta,
                 delta,
-              },
+              }),
             },
             },
           },
           },
         },
         },
       ]);
       ]);
     },
     },
-    [docController]
+    [docController, node]
   );
   );
 
 
   return {
   return {
@@ -48,7 +57,7 @@ function useTransact() {
 
 
   const sendDelta = useCallback(
   const sendDelta = useCallback(
     (delta: TextDelta[]) => {
     (delta: TextDelta[]) => {
-      update(delta);
+      void update(delta);
     },
     },
     [update]
     [update]
   );
   );
@@ -99,7 +108,6 @@ function useBindYjs(delta: TextDelta[], update: (_delta: TextDelta[]) => void) {
 
 
     const textEventHandler = (event: Y.YTextEvent) => {
     const textEventHandler = (event: Y.YTextEvent) => {
       const textDelta = event.target.toDelta();
       const textDelta = event.target.toDelta();
-      console.log('delta', textDelta);
       update(textDelta);
       update(textDelta);
     };
     };
     yText.applyDelta(delta);
     yText.applyDelta(delta);

+ 19 - 12
frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/FolderItem.hooks.ts

@@ -10,6 +10,8 @@ import { AppObserver } from '../../../stores/effects/folder/app/app_observer';
 import { useNavigate } from 'react-router-dom';
 import { useNavigate } from 'react-router-dom';
 import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
 import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants';
 
 
+import { DocumentController } from '$app/stores/effects/document/document_controller';
+
 export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
 export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
   const appDispatch = useAppDispatch();
   const appDispatch = useAppDispatch();
   const workspace = useAppSelector((state) => state.workspace);
   const workspace = useAppSelector((state) => state.workspace);
@@ -115,19 +117,24 @@ export const useFolderEvents = (folder: IFolder, pages: IPage[]) => {
       name: 'New Document 1',
       name: 'New Document 1',
       layoutType: ViewLayoutPB.Document,
       layoutType: ViewLayoutPB.Document,
     });
     });
+    try {
+      await new DocumentController(newView.id).create();
+      appDispatch(
+          pagesActions.addPage({
+            folderId: folder.id,
+            pageType: ViewLayoutPB.Document,
+            title: newView.name,
+            id: newView.id,
+          })
+      );
+
+      setShowPages(true);
+
+      navigate(`/page/document/${newView.id}`);
+    } catch (e) {
+      console.error(e);
+    }
 
 
-    appDispatch(
-      pagesActions.addPage({
-        folderId: folder.id,
-        pageType: ViewLayoutPB.Document,
-        title: newView.name,
-        id: newView.id,
-      })
-    );
-
-    setShowPages(true);
-
-    navigate(`/page/document/${newView.id}`);
   };
   };
 
 
   const onAddNewBoardPage = async () => {
   const onAddNewBoardPage = async () => {

+ 8 - 0
frontend/appflowy_tauri/src/appflowy_app/interfaces/document.ts

@@ -41,3 +41,11 @@ export interface DocumentData {
     childrenMap: Record<string, string[]>;
     childrenMap: Record<string, string[]>;
   };
   };
 }
 }
+
+// eslint-disable-next-line no-shadow
+export enum BlockActionType {
+  Insert = 0,
+  Update = 1,
+  Delete = 2,
+  Move = 3
+}

+ 10 - 9
frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_bd_svc.ts

@@ -1,29 +1,30 @@
 import {
 import {
-  DocumentDataPB,
-  DocumentVersionPB,
-  EditPayloadPB,
   FlowyError,
   FlowyError,
-  OpenDocumentPayloadPB,
   DocumentDataPB2,
   DocumentDataPB2,
-  ViewIdPB,
   OpenDocumentPayloadPBV2,
   OpenDocumentPayloadPBV2,
+  CreateDocumentPayloadPBV2,
   ApplyActionPayloadPBV2,
   ApplyActionPayloadPBV2,
-  BlockActionTypePB,
   BlockActionPB,
   BlockActionPB,
   CloseDocumentPayloadPBV2,
   CloseDocumentPayloadPBV2,
 } from '@/services/backend';
 } from '@/services/backend';
-import { DocumentEventApplyEdit, DocumentEventGetDocument } from '@/services/backend/events/flowy-document';
 import { Result } from 'ts-results';
 import { Result } from 'ts-results';
-import { FolderEventCloseView } from '@/services/backend/events/flowy-folder2';
 import {
 import {
   DocumentEvent2ApplyAction,
   DocumentEvent2ApplyAction,
   DocumentEvent2CloseDocument,
   DocumentEvent2CloseDocument,
   DocumentEvent2OpenDocument,
   DocumentEvent2OpenDocument,
+  DocumentEvent2CreateDocument,
 } from '@/services/backend/events/flowy-document2';
 } from '@/services/backend/events/flowy-document2';
 
 
 export class DocumentBackendService {
 export class DocumentBackendService {
   constructor(public readonly viewId: string) {}
   constructor(public readonly viewId: string) {}
 
 
+  create = (): Promise<Result<void, FlowyError>> => {
+    const payload = CreateDocumentPayloadPBV2.fromObject({
+      document_id: this.viewId,
+    });
+    return DocumentEvent2CreateDocument(payload);
+  };
+
   open = (): Promise<Result<DocumentDataPB2, FlowyError>> => {
   open = (): Promise<Result<DocumentDataPB2, FlowyError>> => {
     const payload = OpenDocumentPayloadPBV2.fromObject({
     const payload = OpenDocumentPayloadPBV2.fromObject({
       document_id: this.viewId,
       document_id: this.viewId,
@@ -31,7 +32,7 @@ export class DocumentBackendService {
     return DocumentEvent2OpenDocument(payload);
     return DocumentEvent2OpenDocument(payload);
   };
   };
 
 
-  applyActions = (actions: [BlockActionPB]): Promise<Result<void, FlowyError>> => {
+  applyActions = (actions: ReturnType<typeof BlockActionPB.prototype.toObject>[]): Promise<Result<void, FlowyError>> => {
     const payload = ApplyActionPayloadPBV2.fromObject({
     const payload = ApplyActionPayloadPBV2.fromObject({
       document_id: this.viewId,
       document_id: this.viewId,
       actions: actions,
       actions: actions,

+ 32 - 21
frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_controller.ts

@@ -1,7 +1,7 @@
 import { DocumentData, BlockType } from '@/appflowy_app/interfaces/document';
 import { DocumentData, BlockType } from '@/appflowy_app/interfaces/document';
 import { createContext } from 'react';
 import { createContext } from 'react';
 import { DocumentBackendService } from './document_bd_svc';
 import { DocumentBackendService } from './document_bd_svc';
-import { FlowyError } from '@/services/backend';
+import { FlowyError, BlockActionPB } from '@/services/backend';
 import { DocumentObserver } from './document_observer';
 import { DocumentObserver } from './document_observer';
 
 
 export const DocumentControllerContext = createContext<DocumentController | null>(null);
 export const DocumentControllerContext = createContext<DocumentController | null>(null);
@@ -15,51 +15,62 @@ export class DocumentController {
     this.observer = new DocumentObserver(viewId);
     this.observer = new DocumentObserver(viewId);
   }
   }
 
 
-  open = async (): Promise<DocumentData | FlowyError> => {
-    // example:
+  create = async (): Promise<FlowyError | void> => {
+    const result = await this.backendService.create();
+    if (result.ok) {
+      return;
+    }
+    return result.val;
+  };
+  open = async (): Promise<DocumentData> => {
     await this.observer.subscribe({
     await this.observer.subscribe({
-      didReceiveUpdate: () => {
-        console.log('didReceiveUpdate');
-      },
+      didReceiveUpdate: this.updated,
     });
     });
 
 
     const document = await this.backendService.open();
     const document = await this.backendService.open();
     if (document.ok) {
     if (document.ok) {
-      console.log(document.val);
-      const blocks: DocumentData["blocks"] = {};
+      const blocks: DocumentData['blocks'] = {};
       document.val.blocks.forEach((block) => {
       document.val.blocks.forEach((block) => {
+        let data = {};
+        try {
+          data = JSON.parse(block.data);
+        } catch {
+          console.log('json parse error', block.data);
+        }
+
         blocks[block.id] = {
         blocks[block.id] = {
           id: block.id,
           id: block.id,
           type: block.ty as BlockType,
           type: block.ty as BlockType,
           parent: block.parent_id,
           parent: block.parent_id,
           children: block.children_id,
           children: block.children_id,
-          data: JSON.parse(block.data),
+          data,
         };
         };
       });
       });
       const childrenMap: Record<string, string[]> = {};
       const childrenMap: Record<string, string[]> = {};
-      document.val.meta.children_map.forEach((child, key) => { childrenMap[key] = child.children; });
+      document.val.meta.children_map.forEach((child, key) => {
+        childrenMap[key] = child.children;
+      });
       return {
       return {
         rootId: document.val.page_id,
         rootId: document.val.page_id,
         blocks,
         blocks,
         meta: {
         meta: {
-          childrenMap
-        }
-      }
+          childrenMap,
+        },
+      };
     }
     }
-    return document.val;
 
 
+    return Promise.reject(document.val);
   };
   };
 
 
-  applyActions = (
-    actions: {
-      type: string;
-      payload: any;
-    }[]
-  ) => {
-    //
+  applyActions = async (actions: ReturnType<typeof BlockActionPB.prototype.toObject>[]) => {
+    await this.backendService.applyActions(actions);
   };
   };
 
 
   dispose = async () => {
   dispose = async () => {
     await this.backendService.close();
     await this.backendService.close();
   };
   };
+
+  private updated = (payload: Uint8Array) => {
+    console.log('didReceiveUpdate', payload);
+  };
 }
 }

+ 4 - 3
frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_observer.ts

@@ -4,7 +4,7 @@ import { FolderNotificationObserver } from '../folder/notifications/observer';
 import { DocumentNotification } from '@/services/backend';
 import { DocumentNotification } from '@/services/backend';
 import { DocumentNotificationObserver } from './notifications/observer';
 import { DocumentNotificationObserver } from './notifications/observer';
 
 
-export type DidReceiveUpdateCallback = () => void; // todo: add params
+export type DidReceiveUpdateCallback = (payload: Uint8Array) => void; // todo: add params
 
 
 export class DocumentObserver {
 export class DocumentObserver {
   private listener?: DocumentNotificationObserver;
   private listener?: DocumentNotificationObserver;
@@ -17,8 +17,9 @@ export class DocumentObserver {
       parserHandler: (notification, result) => {
       parserHandler: (notification, result) => {
         switch (notification) {
         switch (notification) {
           case DocumentNotification.DidReceiveUpdate:
           case DocumentNotification.DidReceiveUpdate:
-            callbacks.didReceiveUpdate();
-            // Fixme: ...
+            if (!result.ok) break;
+            callbacks.didReceiveUpdate(result.val);
+
             break;
             break;
           default:
           default:
             break;
             break;

+ 10 - 6
frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.hooks.ts

@@ -20,12 +20,16 @@ export const useDocument = () => {
       if (!params?.id) return;
       if (!params?.id) return;
       const c = new DocumentController(params.id);
       const c = new DocumentController(params.id);
       setController(c);
       setController(c);
-      const res = await c.open();
-      console.log(res)
-      if (!res) return;
-      // setDocumentData(res)
-      setDocumentId(params.id)
-      
+      try {
+        const res = await c.open();
+        console.log(res)
+        if (!res) return;
+        setDocumentData(res);
+        setDocumentId(params.id);
+      } catch (e) {
+        console.log(e)
+      }
+
     })();
     })();
     return () => {
     return () => {
       console.log('==== leave ====', params?.id)
       console.log('==== leave ====', params?.id)

+ 25 - 25
frontend/rust-lib/Cargo.lock

@@ -113,7 +113,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -124,7 +124,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -817,7 +817,7 @@ dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
  "scratch",
  "scratch",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -834,7 +834,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -1792,7 +1792,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -1916,9 +1916,9 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "h2"
 name = "h2"
-version = "0.3.16"
+version = "0.3.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
+checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f"
 dependencies = [
 dependencies = [
  "bytes",
  "bytes",
  "fnv",
  "fnv",
@@ -2064,9 +2064,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
 
 [[package]]
 [[package]]
 name = "hyper"
 name = "hyper"
-version = "0.14.25"
+version = "0.14.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899"
+checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
 dependencies = [
 dependencies = [
  "bytes",
  "bytes",
  "futures-channel",
  "futures-channel",
@@ -2668,7 +2668,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -2808,7 +2808,7 @@ dependencies = [
  "pest_meta",
  "pest_meta",
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -3027,9 +3027,9 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "prost"
 name = "prost"
-version = "0.11.8"
+version = "0.11.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e48e50df39172a3e7eb17e14642445da64996989bc212b583015435d39a58537"
+checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd"
 dependencies = [
 dependencies = [
  "bytes",
  "bytes",
  "prost-derive",
  "prost-derive",
@@ -3037,9 +3037,9 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "prost-derive"
 name = "prost-derive"
-version = "0.11.8"
+version = "0.11.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ea9b0f8cbe5e15a8a042d030bd96668db28ecb567ec37d691971ff5731d2b1b"
+checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "itertools",
  "itertools",
@@ -3050,9 +3050,9 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "prost-types"
 name = "prost-types"
-version = "0.11.8"
+version = "0.11.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "379119666929a1afd7a043aa6cf96fa67a6dce9af60c88095a4686dbce4c9c88"
+checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13"
 dependencies = [
 dependencies = [
  "prost",
  "prost",
 ]
 ]
@@ -3631,14 +3631,14 @@ checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
 name = "serde_json"
 name = "serde_json"
-version = "1.0.95"
+version = "1.0.96"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
 dependencies = [
 dependencies = [
  "itoa",
  "itoa",
  "ryu",
  "ryu",
@@ -3653,7 +3653,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -3846,9 +3846,9 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "syn"
 name = "syn"
-version = "2.0.14"
+version = "2.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5"
+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
@@ -3942,7 +3942,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]
@@ -4039,7 +4039,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
- "syn 2.0.14",
+ "syn 2.0.15",
 ]
 ]
 
 
 [[package]]
 [[package]]

+ 2 - 2
frontend/rust-lib/flowy-document2/src/document.rs

@@ -20,8 +20,8 @@ use crate::entities::{BlockPB, ChildrenPB, DocumentDataPB2, MetaPB};
 pub struct Document(Arc<Mutex<InnerDocument>>);
 pub struct Document(Arc<Mutex<InnerDocument>>);
 
 
 impl Document {
 impl Document {
-  pub fn new(collab: Collab, data: DocumentDataWrapper) -> FlowyResult<Self> {
-    let inner = InnerDocument::create(collab, data.0)
+  pub fn new(collab: Collab) -> FlowyResult<Self> {
+    let inner = InnerDocument::create(collab)
       .map_err(|_| FlowyError::from(ErrorCode::DocumentDataInvalid))?;
       .map_err(|_| FlowyError::from(ErrorCode::DocumentDataInvalid))?;
     Ok(Self(Arc::new(Mutex::new(inner))))
     Ok(Self(Arc::new(Mutex::new(inner))))
   }
   }

+ 25 - 0
frontend/rust-lib/flowy-document2/src/entities.rs

@@ -9,6 +9,13 @@ pub struct OpenDocumentPayloadPBV2 {
   // Support customize initial data
   // Support customize initial data
 }
 }
 
 
+#[derive(Default, ProtoBuf)]
+pub struct CreateDocumentPayloadPBV2 {
+  #[pb(index = 1)]
+  pub document_id: String,
+  // Support customize initial data
+}
+
 #[derive(Default, ProtoBuf)]
 #[derive(Default, ProtoBuf)]
 pub struct CloseDocumentPayloadPBV2 {
 pub struct CloseDocumentPayloadPBV2 {
   #[pb(index = 1)]
   #[pb(index = 1)]
@@ -102,3 +109,21 @@ impl Default for BlockActionTypePB {
     Self::Insert
     Self::Insert
   }
   }
 }
 }
+
+#[derive(Default, ProtoBuf)]
+pub struct DocEventPB {
+  #[pb(index = 1)]
+  pub events: Vec<BlockEventPB>,
+
+  #[pb(index = 2)]
+  pub is_remote: bool,
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct BlockEventPB {
+  #[pb(index = 1)]
+  pub path: Vec<String>,
+
+  #[pb(index = 2)]
+  pub delta: String,
+}

+ 23 - 2
frontend/rust-lib/flowy-document2/src/event_handler.rs

@@ -4,13 +4,14 @@ use crate::{
   document::DocumentDataWrapper,
   document::DocumentDataWrapper,
   entities::{
   entities::{
     ApplyActionPayloadPBV2, BlockActionPB, BlockActionPayloadPB, BlockActionTypePB,
     ApplyActionPayloadPBV2, BlockActionPB, BlockActionPayloadPB, BlockActionTypePB,
-    BlockPB, CloseDocumentPayloadPBV2, DocumentDataPB2, OpenDocumentPayloadPBV2,
+    BlockPB, CloseDocumentPayloadPBV2, DocumentDataPB2, OpenDocumentPayloadPBV2, CreateDocumentPayloadPBV2,
+    BlockEventPB
   },
   },
   manager::DocumentManager,
   manager::DocumentManager,
 };
 };
 
 
 use collab_document::blocks::{
 use collab_document::blocks::{
-  json_str_to_hashmap, Block, BlockAction, BlockActionPayload, BlockActionType,
+  json_str_to_hashmap, Block, BlockAction, BlockActionPayload, BlockActionType, BlockEvent
 };
 };
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_error::{FlowyError, FlowyResult};
 use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
 use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
@@ -27,6 +28,16 @@ pub(crate) async fn open_document_handler(
   data_result_ok(DocumentDataPB2::from(DocumentDataWrapper(document_data)))
   data_result_ok(DocumentDataPB2::from(DocumentDataWrapper(document_data)))
 }
 }
 
 
+pub(crate) async fn create_document_handler(
+  data: AFPluginData<CreateDocumentPayloadPBV2>,
+  manager: AFPluginState<Arc<DocumentManager>>,
+) -> FlowyResult<()> {
+  let context = data.into_inner();
+  let data = DocumentDataWrapper::default();
+  manager.create_document(context.document_id, data)?;
+  Ok(())
+}
+
 pub(crate) async fn close_document_handler(
 pub(crate) async fn close_document_handler(
   data: AFPluginData<CloseDocumentPayloadPBV2>,
   data: AFPluginData<CloseDocumentPayloadPBV2>,
   manager: AFPluginState<Arc<DocumentManager>>,
   manager: AFPluginState<Arc<DocumentManager>>,
@@ -96,3 +107,13 @@ impl From<BlockPB> for Block {
     }
     }
   }
   }
 }
 }
+
+impl From<BlockEvent> for BlockEventPB {
+  fn from(block_event: BlockEvent) -> Self {
+    let delta = serde_json::to_value(&block_event.delta).unwrap();
+    Self {
+      path: block_event.path.into(),
+      delta: delta.to_string(),
+    }
+  }
+}

+ 5 - 1
frontend/rust-lib/flowy-document2/src/event_map.rs

@@ -5,7 +5,7 @@ use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
 use lib_dispatch::prelude::AFPlugin;
 use lib_dispatch::prelude::AFPlugin;
 
 
 use crate::{
 use crate::{
-  event_handler::{apply_action_handler, close_document_handler, open_document_handler},
+  event_handler::{apply_action_handler, close_document_handler, open_document_handler, create_document_handler },
   manager::DocumentManager,
   manager::DocumentManager,
 };
 };
 
 
@@ -17,6 +17,7 @@ pub fn init(document_manager: Arc<DocumentManager>) -> AFPlugin {
   plugin = plugin.event(DocumentEvent2::OpenDocument, open_document_handler);
   plugin = plugin.event(DocumentEvent2::OpenDocument, open_document_handler);
   plugin = plugin.event(DocumentEvent2::CloseDocument, close_document_handler);
   plugin = plugin.event(DocumentEvent2::CloseDocument, close_document_handler);
   plugin = plugin.event(DocumentEvent2::ApplyAction, apply_action_handler);
   plugin = plugin.event(DocumentEvent2::ApplyAction, apply_action_handler);
+  plugin = plugin.event(DocumentEvent2::CreateDocument, create_document_handler);
 
 
   plugin
   plugin
 }
 }
@@ -32,4 +33,7 @@ pub enum DocumentEvent2 {
 
 
   #[event(input = "ApplyActionPayloadPBV2")]
   #[event(input = "ApplyActionPayloadPBV2")]
   ApplyAction = 2,
   ApplyAction = 2,
+
+  #[event(input = "CreateDocumentPayloadPBV2")]
+  CreateDocument = 3,
 }
 }

+ 30 - 7
frontend/rust-lib/flowy-document2/src/manager.rs

@@ -8,6 +8,7 @@ use parking_lot::RwLock;
 use crate::{
 use crate::{
   document::{Document, DocumentDataWrapper},
   document::{Document, DocumentDataWrapper},
   notification::{send_notification, DocumentNotification},
   notification::{send_notification, DocumentNotification},
+  entities::{DocEventPB, BlockEventPB},
 };
 };
 
 
 pub trait DocumentUser: Send + Sync {
 pub trait DocumentUser: Send + Sync {
@@ -32,23 +33,45 @@ impl DocumentManager {
     }
     }
   }
   }
 
 
+  pub fn create_document(&self, doc_id: String, data: DocumentDataWrapper) -> FlowyResult<Arc<Document>> {
+    self.get_document(doc_id, Some(data))
+  }
+
+  fn get_document(&self, doc_id: String, data: Option<DocumentDataWrapper>) -> FlowyResult<Arc<Document>> {
+    let collab = self.get_collab_for_doc_id(&doc_id)?;
+    let document = Arc::new(Document::new(collab)?);
+    self.documents.write().insert(doc_id, document.clone());
+    if data.is_some() {
+      // Here use unwrap() is safe, because we have checked data.is_some() before.
+      document.lock().create_with_data(data.unwrap().0).map_err(|err| FlowyError::internal().context(err))?;
+    }
+    Ok(document)
+  }
+
   pub fn open_document(&self, doc_id: String) -> FlowyResult<Arc<Document>> {
   pub fn open_document(&self, doc_id: String) -> FlowyResult<Arc<Document>> {
     if let Some(doc) = self.documents.read().get(&doc_id) {
     if let Some(doc) = self.documents.read().get(&doc_id) {
       return Ok(doc.clone());
       return Ok(doc.clone());
     }
     }
-    let collab = self.get_collab_for_doc_id(&doc_id)?;
-    let data = DocumentDataWrapper::default();
-    let document = Arc::new(Document::new(collab, data)?);
 
 
+    let document = self.get_document(doc_id.clone(), None)?;
     let clone_doc_id = doc_id.clone();
     let clone_doc_id = doc_id.clone();
     let _document_data = document
     let _document_data = document
       .lock()
       .lock()
-      .open(move |_, _| {
-        // TODO: add payload data.
-        send_notification(&clone_doc_id, DocumentNotification::DidReceiveUpdate).send();
+      .open(move |events, is_remote| {
+        println!("events: {:?}", events);
+        println!("is_remote: {:?}", is_remote);
+        send_notification(&clone_doc_id, DocumentNotification::DidReceiveUpdate)
+            .payload(DocEventPB {
+              events: events
+                  .iter()
+                  .map(|event| event.to_owned().into())
+                  .collect::<Vec<BlockEventPB>>(),
+              is_remote: is_remote.to_owned(),
+            })
+            .send();
+
       })
       })
       .map_err(|err| FlowyError::internal().context(err))?;
       .map_err(|err| FlowyError::internal().context(err))?;
-    self.documents.write().insert(doc_id, document.clone());
     Ok(document)
     Ok(document)
   }
   }