浏览代码

feat: init flowy document 2 (#2248)

* feat: init flowy document 2

* feat: convert inner document to document PB

* feat: integrate colla document into Flutter

* feat: integrate colla document into tauri

* fix: cargo clippy
Lucas.Xu 2 年之前
父节点
当前提交
ec89e9517b
共有 33 个文件被更改,包括 1274 次插入224 次删除
  1. 24 0
      frontend/appflowy_flutter/lib/core/document_notification.dart
  2. 47 1
      frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart
  3. 29 0
      frontend/appflowy_flutter/lib/plugins/document/application/doc_service.dart
  4. 54 0
      frontend/appflowy_flutter/lib/workspace/application/doc/doc_listener.dart
  5. 2 0
      frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart
  6. 44 4
      frontend/appflowy_tauri/src-tauri/Cargo.lock
  7. 4 0
      frontend/appflowy_tauri/src-tauri/Cargo.toml
  8. 33 0
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_bd_svc.ts
  9. 42 9
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_controller.ts
  10. 36 0
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_observer.ts
  11. 16 0
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/notifications/observer.ts
  12. 26 0
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/notifications/parser.ts
  13. 1 0
      frontend/appflowy_tauri/src/services/backend/index.ts
  14. 261 170
      frontend/rust-lib/Cargo.lock
  15. 10 6
      frontend/rust-lib/Cargo.toml
  16. 3 0
      frontend/rust-lib/flowy-core/Cargo.toml
  17. 40 0
      frontend/rust-lib/flowy-core/src/deps_resolve/document2_deps.rs
  18. 2 0
      frontend/rust-lib/flowy-core/src/deps_resolve/mod.rs
  19. 48 34
      frontend/rust-lib/flowy-core/src/lib.rs
  20. 4 0
      frontend/rust-lib/flowy-core/src/module.rs
  21. 33 0
      frontend/rust-lib/flowy-document2/Cargo.toml
  22. 3 0
      frontend/rust-lib/flowy-document2/Flowy.toml
  23. 10 0
      frontend/rust-lib/flowy-document2/build.rs
  24. 148 0
      frontend/rust-lib/flowy-document2/src/document.rs
  25. 110 0
      frontend/rust-lib/flowy-document2/src/entities.rs
  26. 98 0
      frontend/rust-lib/flowy-document2/src/event_handler.rs
  27. 35 0
      frontend/rust-lib/flowy-document2/src/event_map.rs
  28. 8 0
      frontend/rust-lib/flowy-document2/src/lib.rs
  29. 71 0
      frontend/rust-lib/flowy-document2/src/manager.rs
  30. 28 0
      frontend/rust-lib/flowy-document2/src/notification.rs
  31. 0 0
      frontend/rust-lib/flowy-document2/tests/document_test.rs
  32. 3 0
      frontend/rust-lib/flowy-error/src/code.rs
  33. 1 0
      frontend/rust-lib/flowy-net/Cargo.toml

+ 24 - 0
frontend/appflowy_flutter/lib/core/document_notification.dart

@@ -0,0 +1,24 @@
+import 'dart:typed_data';
+
+import 'package:appflowy/core/notification_helper.dart';
+import 'package:appflowy_backend/protobuf/flowy-document2/notification.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:dartz/dartz.dart';
+
+typedef DocumentNotificationCallback = void Function(
+  DocumentNotification,
+  Either<Uint8List, FlowyError>,
+);
+
+class DocumentNotificationParser
+    extends NotificationParser<DocumentNotification, FlowyError> {
+  DocumentNotificationParser({
+    String? id,
+    required DocumentNotificationCallback callback,
+  }) : super(
+          id: id,
+          callback: callback,
+          tyParser: (ty) => DocumentNotification.valueOf(ty),
+          errorParser: (bytes) => FlowyError.fromBuffer(bytes),
+        );
+}

+ 47 - 1
frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart

@@ -3,6 +3,7 @@ import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_
 import 'package:appflowy/plugins/trash/application/trash_service.dart';
 import 'package:appflowy/user/application/user_service.dart';
 import 'package:appflowy/workspace/application/view/view_listener.dart';
+import 'package:appflowy/workspace/application/doc/doc_listener.dart';
 import 'package:appflowy/plugins/document/application/doc_service.dart';
 import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
@@ -17,12 +18,13 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:dartz/dartz.dart';
 import 'dart:async';
 import 'package:appflowy/util/either_extension.dart';
-
+import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
 part 'doc_bloc.freezed.dart';
 
 class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   final ViewPB view;
   final DocumentService _documentService;
+  final DocumentListener _docListener;
 
   final ViewListener _listener;
   final TrashService _trashService;
@@ -32,12 +34,14 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   DocumentBloc({
     required this.view,
   })  : _documentService = DocumentService(),
+        _docListener = DocumentListener(id: view.id),
         _listener = ViewListener(view: view),
         _trashService = TrashService(),
         super(DocumentState.initial()) {
     on<DocumentEvent>((event, emit) async {
       await event.map(
         initial: (Initial value) async {
+          _listenOnDocChange();
           await _initial(value, emit);
           _listenOnViewChange();
         },
@@ -73,6 +77,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
     }
 
     await _documentService.closeDocument(docId: view.id);
+    await _documentService.closeDocumentV2(view: view);
     return super.close();
   }
 
@@ -88,6 +93,39 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
       );
     }
     final result = await _documentService.openDocument(view: view);
+    // test code
+    final document = await _documentService.openDocumentV2(view: view);
+    BlockPB? root;
+    document.fold((l) {
+      print('---------<open document v2>-----------');
+      print('page id = ${l.pageId}');
+      l.blocks.blocks.forEach((key, value) {
+        print('-----<block begin>-----');
+        print('block = $value');
+        if (value.ty == 'page') {
+          root = value;
+        }
+        print('-----<block end>-----');
+      });
+      print('---------<open document v2>-----------');
+    }, (r) {});
+    if (root != null) {
+      await _documentService.applyAction(
+        view: view,
+        actions: [
+          BlockActionPB(
+            action: BlockActionTypePB.Insert,
+            payload: BlockActionPayloadPB(
+              block: BlockPB()
+                ..id = 'id_0'
+                ..ty = 'text'
+                ..parentId = root!.id,
+            ),
+          ),
+        ],
+      );
+    }
+
     return result.fold(
       (documentData) async {
         await _initEditorState(documentData).whenComplete(() {
@@ -126,6 +164,14 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
     );
   }
 
+  void _listenOnDocChange() {
+    _docListener.start(
+      didReceiveUpdate: () {
+        print('---------<receive document update>-----------');
+      },
+    );
+  }
+
   Future<void> _initEditorState(DocumentDataPB documentData) async {
     final document = Document.fromJson(jsonDecode(documentData.content));
     final editorState = EditorState(document: document);

+ 29 - 0
frontend/appflowy_flutter/lib/plugins/document/application/doc_service.dart

@@ -4,6 +4,7 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
 
 class DocumentService {
   Future<Either<DocumentDataPB, FlowyError>> openDocument({
@@ -39,4 +40,32 @@ class DocumentService {
     final payload = ViewIdPB(value: docId);
     return FolderEventCloseView(payload).send();
   }
+
+  Future<Either<DocumentDataPB2, FlowyError>> openDocumentV2({
+    required ViewPB view,
+  }) async {
+    await FolderEventSetLatestView(ViewIdPB(value: view.id)).send();
+
+    final payload = OpenDocumentPayloadPBV2()..documentId = view.id;
+
+    return DocumentEvent2OpenDocument(payload).send();
+  }
+
+  Future<Either<Unit, FlowyError>> closeDocumentV2({
+    required ViewPB view,
+  }) async {
+    final payload = CloseDocumentPayloadPBV2()..documentId = view.id;
+    return DocumentEvent2CloseDocument(payload).send();
+  }
+
+  Future<Either<Unit, FlowyError>> applyAction({
+    required ViewPB view,
+    required List<BlockActionPB> actions,
+  }) async {
+    final payload = ApplyActionPayloadPBV2(
+      documentId: view.id,
+      actions: actions,
+    );
+    return DocumentEvent2ApplyAction(payload).send();
+  }
 }

+ 54 - 0
frontend/appflowy_flutter/lib/workspace/application/doc/doc_listener.dart

@@ -0,0 +1,54 @@
+import 'dart:async';
+import 'dart:typed_data';
+import 'package:appflowy/core/document_notification.dart';
+import 'package:dartz/dartz.dart';
+import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-document2/notification.pb.dart';
+import 'package:appflowy_backend/rust_stream.dart';
+import 'package:flowy_infra/notifier.dart';
+
+class DocumentListener {
+  DocumentListener({
+    required this.id,
+  });
+
+  final String id;
+
+  final _didReceiveUpdate = PublishNotifier();
+  StreamSubscription<SubscribeObject>? _subscription;
+  DocumentNotificationParser? _parser;
+
+  Function()? didReceiveUpdate;
+
+  void start({
+    void Function()? didReceiveUpdate,
+  }) {
+    this.didReceiveUpdate = didReceiveUpdate;
+
+    _parser = DocumentNotificationParser(
+      id: id,
+      callback: _callback,
+    );
+    _subscription = RustStreamReceiver.listen(
+      (observable) => _parser?.parse(observable),
+    );
+  }
+
+  void _callback(
+    DocumentNotification ty,
+    Either<Uint8List, FlowyError> result,
+  ) {
+    switch (ty) {
+      case DocumentNotification.DidReceiveUpdate:
+        didReceiveUpdate?.call();
+        break;
+      default:
+        break;
+    }
+  }
+
+  Future<void> stop() async {
+    await _subscription?.cancel();
+  }
+}

+ 2 - 0
frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart

@@ -17,6 +17,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
 import 'package:appflowy_backend/protobuf/dart-ffi/protobuf.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
 import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
 import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
 
 // ignore: unused_import
@@ -30,6 +31,7 @@ part 'dart_event/flowy-net/dart_event.dart';
 part 'dart_event/flowy-user/dart_event.dart';
 part 'dart_event/flowy-database/dart_event.dart';
 part 'dart_event/flowy-document/dart_event.dart';
+part 'dart_event/flowy-document2/dart_event.dart';
 
 enum FFIException {
   RequestIsEmpty,

+ 44 - 4
frontend/appflowy_tauri/src-tauri/Cargo.lock

@@ -564,7 +564,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#9b3f895bb6f8e92830acd90cfb68b69aece83095"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
 dependencies = [
  "anyhow",
  "bytes",
@@ -582,7 +582,7 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#9b3f895bb6f8e92830acd90cfb68b69aece83095"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -591,10 +591,26 @@ dependencies = [
  "yrs",
 ]
 
+[[package]]
+name = "collab-document"
+version = "0.1.0"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
+dependencies = [
+ "anyhow",
+ "collab",
+ "collab-derive",
+ "collab-persistence",
+ "nanoid",
+ "parking_lot 0.12.1",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#9b3f895bb6f8e92830acd90cfb68b69aece83095"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
 dependencies = [
  "anyhow",
  "collab",
@@ -612,7 +628,7 @@ dependencies = [
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#9b3f895bb6f8e92830acd90cfb68b69aece83095"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab#4ccf3e5cf88ee71f1ce257e587cca54a8173abae"
 dependencies = [
  "bincode",
  "chrono",
@@ -1284,6 +1300,7 @@ dependencies = [
  "flowy-client-ws",
  "flowy-database",
  "flowy-document",
+ "flowy-document2",
  "flowy-error",
  "flowy-folder",
  "flowy-folder2",
@@ -1407,6 +1424,28 @@ dependencies = [
  "ws-model",
 ]
 
+[[package]]
+name = "flowy-document2"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "collab",
+ "collab-document",
+ "collab-persistence",
+ "flowy-codegen",
+ "flowy-derive",
+ "flowy-error",
+ "flowy-notification",
+ "lib-dispatch",
+ "nanoid",
+ "parking_lot 0.12.1",
+ "protobuf",
+ "serde",
+ "serde_json",
+ "strum",
+ "strum_macros",
+]
+
 [[package]]
 name = "flowy-error"
 version = "0.1.0"
@@ -1511,6 +1550,7 @@ dependencies = [
  "flowy-codegen",
  "flowy-derive",
  "flowy-document",
+ "flowy-document2",
  "flowy-error",
  "flowy-folder2",
  "flowy-server-sync",

+ 4 - 0
frontend/appflowy_tauri/src-tauri/Cargo.toml

@@ -36,9 +36,13 @@ custom-protocol = ["tauri/custom-protocol"]
 collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
 collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
 collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
+
 #collab = { path = "../../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }
 #collab-persistence = { path = "../../AppFlowy-Collab/collab-persistence" }
+#collab-document = { path = "../../AppFlowy-Collab/collab-document" }
+
 
 
 

+ 33 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_bd_svc.ts

@@ -4,11 +4,22 @@ import {
   EditPayloadPB,
   FlowyError,
   OpenDocumentPayloadPB,
+  DocumentDataPB2,
   ViewIdPB,
+  OpenDocumentPayloadPBV2,
+  ApplyActionPayloadPBV2,
+  BlockActionTypePB,
+  BlockActionPB,
+  CloseDocumentPayloadPBV2,
 } from '@/services/backend';
 import { DocumentEventApplyEdit, DocumentEventGetDocument } from '@/services/backend/events/flowy-document';
 import { Result } from 'ts-results';
 import { FolderEventCloseView } from '@/services/backend/events/flowy-folder2';
+import {
+  DocumentEvent2ApplyAction,
+  DocumentEvent2CloseDocument,
+  DocumentEvent2OpenDocument,
+} from '@/services/backend/events/flowy-document2';
 
 export class DocumentBackendService {
   constructor(public readonly viewId: string) {}
@@ -27,4 +38,26 @@ export class DocumentBackendService {
     const payload = ViewIdPB.fromObject({ value: this.viewId });
     return FolderEventCloseView(payload);
   };
+
+  openV2 = (): Promise<Result<DocumentDataPB2, FlowyError>> => {
+    const payload = OpenDocumentPayloadPBV2.fromObject({
+      document_id: this.viewId,
+    });
+    return DocumentEvent2OpenDocument(payload);
+  };
+
+  applyActions = (actions: [BlockActionPB]): Promise<Result<void, FlowyError>> => {
+    const payload = ApplyActionPayloadPBV2.fromObject({
+      document_id: this.viewId,
+      actions: actions,
+    });
+    return DocumentEvent2ApplyAction(payload);
+  };
+
+  closeV2 = (): Promise<Result<void, FlowyError>> => {
+    const payload = CloseDocumentPayloadPBV2.fromObject({
+      document_id: this.viewId,
+    });
+    return DocumentEvent2CloseDocument(payload);
+  };
 }

+ 42 - 9
frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_controller.ts

@@ -2,37 +2,70 @@ import { DocumentData, BlockType, TextDelta } from '@/appflowy_app/interfaces/do
 import { createContext } from 'react';
 import { DocumentBackendService } from './document_bd_svc';
 import { Err } from 'ts-results';
-import { FlowyError } from '@/services/backend';
+import { BlockActionPB, BlockActionPayloadPB, BlockActionTypePB, BlockPB, FlowyError } from '@/services/backend';
+import { DocumentObserver } from './document_observer';
+import { nanoid } from 'nanoid';
 
 export const DocumentControllerContext = createContext<DocumentController | null>(null);
 
 export class DocumentController {
   private readonly backendService: DocumentBackendService;
+  private readonly observer: DocumentObserver;
 
   constructor(public readonly viewId: string) {
     this.backendService = new DocumentBackendService(viewId);
+    this.observer = new DocumentObserver(viewId);
   }
 
   open = async (): Promise<DocumentData | null> => {
+    // example:
+    await this.observer.subscribe({
+      didReceiveUpdate: () => {
+        console.log('didReceiveUpdate');
+      },
+    });
+
+    const document = await this.backendService.openV2();
+    let root_id = '';
+    if (document.ok) {
+      root_id = document.val.page_id;
+      console.log(document.val.blocks);
+    }
+    await this.backendService.applyActions([
+      BlockActionPB.fromObject({
+        action: BlockActionTypePB.Insert,
+        payload: BlockActionPayloadPB.fromObject({
+          block: BlockPB.fromObject({
+            id: nanoid(10),
+            ty: 'text',
+            parent_id: root_id,
+          }),
+        }),
+      }),
+    ]);
+
     const openDocumentResult = await this.backendService.open();
     if (openDocumentResult.ok) {
       return {
         rootId: '',
         blocks: {},
         ytexts: {},
-        yarrays: {}
+        yarrays: {},
       };
     } else {
       return null;
     }
   };
 
-
-  insert(node: {
-    id: string,
-    type: BlockType,
-    delta?: TextDelta[]
-  }, parentId: string, prevId: string) {
+  insert(
+    node: {
+      id: string;
+      type: BlockType;
+      delta?: TextDelta[];
+    },
+    parentId: string,
+    prevId: string
+  ) {
     //
   }
 
@@ -42,7 +75,7 @@ export class DocumentController {
 
   yTextApply = (yTextId: string, delta: TextDelta[]) => {
     //
-  }
+  };
 
   dispose = async () => {
     await this.backendService.close();

+ 36 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/document_observer.ts

@@ -0,0 +1,36 @@
+import { Ok, Result } from 'ts-results';
+import { ChangeNotifier } from '$app/utils/change_notifier';
+import { FolderNotificationObserver } from '../folder/notifications/observer';
+import { DocumentNotification } from '@/services/backend';
+import { DocumentNotificationObserver } from './notifications/observer';
+
+export type DidReceiveUpdateCallback = () => void; // todo: add params
+
+export class DocumentObserver {
+  private listener?: DocumentNotificationObserver;
+
+  constructor(public readonly workspaceId: string) {}
+
+  subscribe = async (callbacks: { didReceiveUpdate: DidReceiveUpdateCallback }) => {
+    this.listener = new DocumentNotificationObserver({
+      viewId: this.workspaceId,
+      parserHandler: (notification, result) => {
+        switch (notification) {
+          case DocumentNotification.DidReceiveUpdate:
+            callbacks.didReceiveUpdate();
+            // Fixme: ...
+            break;
+          default:
+            break;
+        }
+      },
+    });
+    await this.listener.start();
+  };
+
+  unsubscribe = async () => {
+    this.appListNotifier.unsubscribe();
+    this.workspaceNotifier.unsubscribe();
+    await this.listener?.stop();
+  };
+}

+ 16 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/notifications/observer.ts

@@ -0,0 +1,16 @@
+import { OnNotificationError, AFNotificationObserver } from '@/services/backend/notifications';
+import { DocumentNotificationParser } from './parser';
+import { FlowyError, DocumentNotification } from '@/services/backend';
+import { Result } from 'ts-results';
+
+export type ParserHandler = (notification: DocumentNotification, payload: Result<Uint8Array, FlowyError>) => void;
+
+export class DocumentNotificationObserver extends AFNotificationObserver<DocumentNotification> {
+  constructor(params: { viewId?: string; parserHandler: ParserHandler; onError?: OnNotificationError }) {
+    const parser = new DocumentNotificationParser({
+      callback: params.parserHandler,
+      id: params.viewId,
+    });
+    super(parser);
+  }
+}

+ 26 - 0
frontend/appflowy_tauri/src/appflowy_app/stores/effects/document/notifications/parser.ts

@@ -0,0 +1,26 @@
+import { NotificationParser, OnNotificationError } from '@/services/backend/notifications';
+import { FlowyError, DocumentNotification } from '@/services/backend';
+import { Result } from 'ts-results';
+
+declare type DocumentNotificationCallback = (ty: DocumentNotification, payload: Result<Uint8Array, FlowyError>) => void;
+
+export class DocumentNotificationParser extends NotificationParser<DocumentNotification> {
+  constructor(params: { id?: string; callback: DocumentNotificationCallback; onError?: OnNotificationError }) {
+    super(
+      params.callback,
+      (ty) => {
+        const notification = DocumentNotification[ty];
+        if (isDocumentNotification(notification)) {
+          return DocumentNotification[notification];
+        } else {
+          return DocumentNotification.Unknown;
+        }
+      },
+      params.id
+    );
+  }
+}
+
+const isDocumentNotification = (notification: string): notification is keyof typeof DocumentNotification => {
+  return Object.values(DocumentNotification).indexOf(notification) !== -1;
+};

+ 1 - 0
frontend/appflowy_tauri/src/services/backend/index.ts

@@ -2,5 +2,6 @@ export * from "./models/flowy-user";
 export * from "./models/flowy-document";
 export * from "./models/flowy-database";
 export * from "./models/flowy-folder2";
+export * from "./models/flowy-document2";
 export * from "./models/flowy-net";
 export * from "./models/flowy-error";

文件差异内容过多而无法显示
+ 261 - 170
frontend/rust-lib/Cargo.lock


+ 10 - 6
frontend/rust-lib/Cargo.toml

@@ -11,6 +11,7 @@ members = [
 #  "flowy-folder",r
   "flowy-folder2",
   "flowy-notification",
+  "flowy-document2",
   "flowy-document",
   "flowy-error",
   "flowy-revision",
@@ -38,9 +39,12 @@ opt-level = 3
 incremental = false
 
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab"  }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab"  }
-collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab" }
-#collab = { path = "../AppFlowy-Collab/collab" }
-#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
-#collab-persistence = { path = "../AppFlowy-Collab/collab-persistence" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", branch = "main"  }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", branch = "main"  }
+collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", branch = "main" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", branch = "main" }
+
+# collab = { path = "../AppFlowy-Collab/collab" }
+# collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
+# collab-persistence = { path = "../AppFlowy-Collab/collab-persistence" }
+# collab-document = { path = "../AppFlowy-Collab/collab-document" }

+ 3 - 0
frontend/rust-lib/flowy-core/Cargo.toml

@@ -18,6 +18,7 @@ user-model = { path = "../../../shared-lib/user-model" }
 flowy-client-ws = { path = "../../../shared-lib/flowy-client-ws" }
 flowy-sqlite = { path = "../flowy-sqlite", optional = true }
 flowy-document = { path = "../flowy-document" }
+flowy-document2 = { path = "../flowy-document2" }
 flowy-revision = { path = "../flowy-revision" }
 flowy-error = { path = "../flowy-error", features = ["adaptor_ws"] }
 flowy-task = { path = "../flowy-task" }
@@ -50,6 +51,7 @@ dart = [
     "flowy-folder2/dart",
     "flowy-database/dart",
     "flowy-document/dart",
+    "flowy-document2/dart",
 ]
 ts = [
     "flowy-user/ts",
@@ -58,6 +60,7 @@ ts = [
     "flowy-folder2/ts",
     "flowy-database/ts",
     "flowy-document/ts",
+    "flowy-document2/ts",
 ]
 rev-sqlite = [
     "flowy-sqlite",

+ 40 - 0
frontend/rust-lib/flowy-core/src/deps_resolve/document2_deps.rs

@@ -0,0 +1,40 @@
+use std::sync::Arc;
+
+use collab_persistence::CollabKV;
+use flowy_database::manager::DatabaseManager;
+use flowy_document2::manager::{DocumentManager as DocumentManager2, DocumentUser};
+use flowy_error::FlowyError;
+use flowy_user::services::UserSession;
+
+pub struct Document2DepsResolver();
+impl Document2DepsResolver {
+  pub fn resolve(
+    user_session: Arc<UserSession>,
+    database_manager: &Arc<DatabaseManager>,
+  ) -> Arc<DocumentManager2> {
+    let user: Arc<dyn DocumentUser> = Arc::new(DocumentUserImpl(user_session.clone()));
+
+    Arc::new(DocumentManager2::new(user.clone()))
+  }
+}
+
+struct DocumentUserImpl(Arc<UserSession>);
+impl DocumentUser for DocumentUserImpl {
+  fn user_id(&self) -> Result<i64, FlowyError> {
+    self
+      .0
+      .user_id()
+      .map_err(|e| FlowyError::internal().context(e))
+  }
+
+  fn token(&self) -> Result<String, FlowyError> {
+    self
+      .0
+      .token()
+      .map_err(|e| FlowyError::internal().context(e))
+  }
+
+  fn kv_db(&self) -> Result<Arc<CollabKV>, FlowyError> {
+    self.0.get_kv_db()
+  }
+}

+ 2 - 0
frontend/rust-lib/flowy-core/src/deps_resolve/mod.rs

@@ -1,9 +1,11 @@
+mod document2_deps;
 mod document_deps;
 mod folder2_deps;
 mod grid_deps;
 mod user_deps;
 mod util;
 
+pub use document2_deps::*;
 pub use document_deps::*;
 pub use folder2_deps::*;
 pub use grid_deps::*;

+ 48 - 34
frontend/rust-lib/flowy-core/src/lib.rs

@@ -7,6 +7,7 @@ use flowy_database::entities::DatabaseLayoutPB;
 use flowy_database::manager::DatabaseManager;
 use flowy_document::entities::DocumentVersionPB;
 use flowy_document::{DocumentConfig, DocumentManager};
+use flowy_document2::manager::DocumentManager as DocumentManager2;
 use flowy_error::FlowyResult;
 use flowy_folder::errors::FlowyError;
 use flowy_folder2::manager::Folder2Manager;
@@ -124,6 +125,7 @@ pub struct AppFlowyCore {
   pub config: AppFlowyCoreConfig,
   pub user_session: Arc<UserSession>,
   pub document_manager: Arc<DocumentManager>,
+  pub document_manager2: Arc<DocumentManager2>,
   pub folder_manager: Arc<Folder2Manager>,
   pub database_manager: Arc<DatabaseManager>,
   pub event_dispatcher: Arc<AFPluginDispatcher>,
@@ -146,40 +148,50 @@ impl AppFlowyCore {
     runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
 
     let (local_server, ws_conn) = mk_local_server(&config.server_config);
-    let (user_session, document_manager, folder_manager, local_server, database_manager) = runtime
-      .block_on(async {
-        let user_session = mk_user_session(&config, &local_server, &config.server_config);
-        let document_manager = DocumentDepsResolver::resolve(
-          local_server.clone(),
-          ws_conn.clone(),
-          user_session.clone(),
-          &config.server_config,
-          &config.document,
-        );
-
-        let database_manager = DatabaseDepsResolver::resolve(
-          ws_conn.clone(),
-          user_session.clone(),
-          task_dispatcher.clone(),
-        )
-        .await;
-
-        let folder_manager =
-          Folder2DepsResolver::resolve(user_session.clone(), &document_manager, &database_manager)
-            .await;
-
-        if let Some(local_server) = local_server.as_ref() {
-          local_server.run();
-        }
-        ws_conn.init().await;
-        (
-          user_session,
-          document_manager,
-          folder_manager,
-          local_server,
-          database_manager,
-        )
-      });
+    let (
+      user_session,
+      document_manager,
+      folder_manager,
+      local_server,
+      database_manager,
+      document_manager2,
+    ) = runtime.block_on(async {
+      let user_session = mk_user_session(&config, &local_server, &config.server_config);
+      let document_manager = DocumentDepsResolver::resolve(
+        local_server.clone(),
+        ws_conn.clone(),
+        user_session.clone(),
+        &config.server_config,
+        &config.document,
+      );
+
+      let database_manager = DatabaseDepsResolver::resolve(
+        ws_conn.clone(),
+        user_session.clone(),
+        task_dispatcher.clone(),
+      )
+      .await;
+
+      let folder_manager =
+        Folder2DepsResolver::resolve(user_session.clone(), &document_manager, &database_manager)
+          .await;
+
+      let document_manager2 =
+        Document2DepsResolver::resolve(user_session.clone(), &database_manager);
+
+      if let Some(local_server) = local_server.as_ref() {
+        local_server.run();
+      }
+      ws_conn.init().await;
+      (
+        user_session,
+        document_manager,
+        folder_manager,
+        local_server,
+        database_manager,
+        document_manager2,
+      )
+    });
 
     let user_status_listener = UserStatusListener {
       document_manager: document_manager.clone(),
@@ -203,6 +215,7 @@ impl AppFlowyCore {
         &database_manager,
         &user_session,
         &document_manager,
+        &document_manager2,
       )
     }));
     _start_listening(&event_dispatcher, &ws_conn, &folder_manager);
@@ -211,6 +224,7 @@ impl AppFlowyCore {
       config,
       user_session,
       document_manager,
+      document_manager2,
       folder_manager,
       database_manager,
       event_dispatcher,

+ 4 - 0
frontend/rust-lib/flowy-core/src/module.rs

@@ -1,6 +1,7 @@
 use flowy_client_ws::FlowyWebSocketConnect;
 use flowy_database::manager::DatabaseManager;
 use flowy_document::DocumentManager;
+use flowy_document2::manager::DocumentManager as DocumentManager2;
 
 use flowy_folder2::manager::Folder2Manager;
 use flowy_user::services::UserSession;
@@ -13,17 +14,20 @@ pub fn make_plugins(
   grid_manager: &Arc<DatabaseManager>,
   user_session: &Arc<UserSession>,
   document_manager: &Arc<DocumentManager>,
+  document_manager2: &Arc<DocumentManager2>,
 ) -> Vec<AFPlugin> {
   let user_plugin = flowy_user::event_map::init(user_session.clone());
   let folder_plugin = flowy_folder2::event_map::init(folder_manager.clone());
   let network_plugin = flowy_net::event_map::init(ws_conn.clone());
   let grid_plugin = flowy_database::event_map::init(grid_manager.clone());
   let document_plugin = flowy_document::event_map::init(document_manager.clone());
+  let document_plugin2 = flowy_document2::event_map::init(document_manager2.clone());
   vec![
     user_plugin,
     folder_plugin,
     network_plugin,
     grid_plugin,
     document_plugin,
+    document_plugin2,
   ]
 }

+ 33 - 0
frontend/rust-lib/flowy-document2/Cargo.toml

@@ -0,0 +1,33 @@
+[package]
+name = "flowy-document2"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+collab = { version = "0.1.0" }
+collab-persistence = { version = "0.1.0" }
+collab-document = { version = "0.1.0" }
+
+flowy-derive = { path = "../flowy-derive" }
+flowy-notification = { path = "../flowy-notification" }
+flowy-error = { path = "../flowy-error", features = ["adaptor_sync", "adaptor_ot", "adaptor_serde", "adaptor_database", "adaptor_dispatch"] }
+
+lib-dispatch = { path = "../lib-dispatch" }
+
+protobuf = {version = "2.28.0"}
+bytes = { version = "1.4" }
+nanoid = "0.4.0"
+parking_lot = "0.12.1"
+strum = "0.21"
+strum_macros = "0.21"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = {version = "1.0"}
+
+[build-dependencies]
+flowy-codegen = { path = "../flowy-codegen"}
+
+[features]
+dart = ["flowy-codegen/dart", "flowy-notification/dart"]
+ts = ["flowy-codegen/ts", "flowy-notification/ts"]

+ 3 - 0
frontend/rust-lib/flowy-document2/Flowy.toml

@@ -0,0 +1,3 @@
+# Check out the FlowyConfig (located in flowy_toml.rs) for more details.
+proto_input = ["src/event_map.rs", "src/entities.rs", "src/notification.rs"]
+event_files = ["src/event_map.rs"]

+ 10 - 0
frontend/rust-lib/flowy-document2/build.rs

@@ -0,0 +1,10 @@
+fn main() {
+  let crate_name = env!("CARGO_PKG_NAME");
+  flowy_codegen::protobuf_file::gen(crate_name);
+
+  #[cfg(feature = "dart")]
+  flowy_codegen::dart_event::gen(crate_name);
+
+  #[cfg(feature = "ts")]
+  flowy_codegen::ts_event::gen(crate_name);
+}

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

@@ -0,0 +1,148 @@
+use std::{
+  collections::HashMap,
+  ops::{Deref, DerefMut},
+  sync::Arc,
+  vec,
+};
+
+use collab::preclude::Collab;
+use collab_document::{
+  blocks::{Block, DocumentData, DocumentMeta},
+  document::Document as InnerDocument,
+};
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
+use nanoid::nanoid;
+use parking_lot::Mutex;
+
+use crate::entities::{BlockMapPB, BlockPB, ChildrenPB, DocumentDataPB2, MetaPB};
+
+#[derive(Clone)]
+pub struct Document(Arc<Mutex<InnerDocument>>);
+
+impl Document {
+  pub fn new(collab: Collab, data: DocumentDataWrapper) -> FlowyResult<Self> {
+    let inner = InnerDocument::create(collab, data.0)
+      .map_err(|_| FlowyError::from(ErrorCode::DocumentDataInvalid))?;
+    Ok(Self(Arc::new(Mutex::new(inner))))
+  }
+}
+
+unsafe impl Sync for Document {}
+unsafe impl Send for Document {}
+
+impl Deref for Document {
+  type Target = Arc<Mutex<InnerDocument>>;
+
+  fn deref(&self) -> &Self::Target {
+    &self.0
+  }
+}
+
+impl DerefMut for Document {
+  fn deref_mut(&mut self) -> &mut Self::Target {
+    &mut self.0
+  }
+}
+
+#[derive(Clone)]
+pub struct DocumentDataWrapper(pub DocumentData);
+
+impl Deref for DocumentDataWrapper {
+  type Target = DocumentData;
+
+  fn deref(&self) -> &Self::Target {
+    &self.0
+  }
+}
+
+impl DerefMut for DocumentDataWrapper {
+  fn deref_mut(&mut self) -> &mut Self::Target {
+    &mut self.0
+  }
+}
+
+impl From<DocumentDataWrapper> for DocumentDataPB2 {
+  fn from(data: DocumentDataWrapper) -> Self {
+    let blocks = data
+      .0
+      .blocks
+      .into_iter()
+      .map(|(id, block)| {
+        (
+          id,
+          BlockPB {
+            id: block.id,
+            ty: block.ty,
+            parent_id: block.parent,
+            children_id: block.children,
+            data: serde_json::to_string(&block.data).unwrap(),
+          },
+        )
+      })
+      .collect::<HashMap<String, BlockPB>>();
+    let children_map = data
+      .0
+      .meta
+      .children_map
+      .into_iter()
+      .map(|(id, children)| {
+        (
+          id,
+          ChildrenPB {
+            children: children.into_iter().collect(),
+          },
+        )
+      })
+      .collect::<HashMap<String, ChildrenPB>>();
+    Self {
+      page_id: data.0.page_id,
+      blocks: BlockMapPB { blocks },
+      meta: MetaPB { children_map },
+    }
+  }
+}
+
+impl Default for DocumentDataWrapper {
+  fn default() -> Self {
+    let mut blocks: HashMap<String, Block> = HashMap::new();
+    let mut meta: HashMap<String, Vec<String>> = HashMap::new();
+
+    // page block
+    let page_id = nanoid!(10);
+    let children_id = nanoid!(10);
+    let root = Block {
+      id: page_id.clone(),
+      ty: "page".to_string(),
+      parent: "".to_string(),
+      children: children_id.clone(),
+      external_id: None,
+      external_type: None,
+      data: HashMap::new(),
+    };
+    blocks.insert(page_id.clone(), root);
+
+    // text block
+    let text_block_id = nanoid!(10);
+    let text_0_children_id = nanoid!(10);
+    let text_block = Block {
+      id: text_block_id.clone(),
+      ty: "text".to_string(),
+      parent: page_id.clone(),
+      children: text_0_children_id.clone(),
+      external_id: None,
+      external_type: None,
+      data: HashMap::new(),
+    };
+    blocks.insert(text_block_id.clone(), text_block);
+
+    // meta
+    meta.insert(children_id, vec![text_block_id]);
+    meta.insert(text_0_children_id, vec![]);
+
+    Self(DocumentData {
+      page_id,
+      blocks,
+      meta: DocumentMeta { children_map: meta },
+    })
+  }
+}

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

@@ -0,0 +1,110 @@
+use std::collections::HashMap;
+
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+
+#[derive(Default, ProtoBuf)]
+pub struct OpenDocumentPayloadPBV2 {
+  #[pb(index = 1)]
+  pub document_id: String,
+  // Support customize initial data
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct CloseDocumentPayloadPBV2 {
+  #[pb(index = 1)]
+  pub document_id: String,
+  // Support customize initial data
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct ApplyActionPayloadPBV2 {
+  #[pb(index = 1)]
+  pub document_id: String,
+
+  #[pb(index = 2)]
+  pub actions: Vec<BlockActionPB>,
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct DocumentDataPB2 {
+  #[pb(index = 1)]
+  pub page_id: String,
+
+  #[pb(index = 2)]
+  pub blocks: BlockMapPB,
+
+  #[pb(index = 3)]
+  pub meta: MetaPB,
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct BlockMapPB {
+  #[pb(index = 1)]
+  pub blocks: HashMap<String, BlockPB>,
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct BlockPB {
+  #[pb(index = 1)]
+  pub id: String,
+
+  #[pb(index = 2)]
+  pub ty: String,
+
+  #[pb(index = 3)]
+  pub data: String,
+
+  #[pb(index = 4)]
+  pub parent_id: String,
+
+  #[pb(index = 5)]
+  pub children_id: String,
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct MetaPB {
+  #[pb(index = 1)]
+  pub children_map: HashMap<String, ChildrenPB>,
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct ChildrenPB {
+  #[pb(index = 1)]
+  pub children: Vec<String>,
+}
+
+// Actions
+#[derive(Default, ProtoBuf)]
+pub struct BlockActionPB {
+  #[pb(index = 1)]
+  pub action: BlockActionTypePB,
+
+  #[pb(index = 2)]
+  pub payload: BlockActionPayloadPB,
+}
+
+#[derive(Default, ProtoBuf)]
+pub struct BlockActionPayloadPB {
+  #[pb(index = 1)]
+  pub block: BlockPB,
+
+  #[pb(index = 2, one_of)]
+  pub prev_id: Option<String>,
+
+  #[pb(index = 3, one_of)]
+  pub parent_id: Option<String>,
+}
+
+#[derive(ProtoBuf_Enum)]
+pub enum BlockActionTypePB {
+  Insert = 0,
+  Update = 1,
+  Delete = 2,
+  Move = 3,
+}
+
+impl Default for BlockActionTypePB {
+  fn default() -> Self {
+    Self::Insert
+  }
+}

+ 98 - 0
frontend/rust-lib/flowy-document2/src/event_handler.rs

@@ -0,0 +1,98 @@
+use std::{sync::Arc};
+
+use crate::{
+  document::DocumentDataWrapper,
+  entities::{
+    ApplyActionPayloadPBV2, BlockActionPB, BlockActionPayloadPB, BlockActionTypePB,
+    BlockPB, CloseDocumentPayloadPBV2, DocumentDataPB2, OpenDocumentPayloadPBV2,
+  },
+  manager::DocumentManager,
+};
+
+use collab_document::blocks::{
+  json_str_to_hashmap, Block, BlockAction, BlockActionPayload, BlockActionType,
+};
+use flowy_error::{FlowyError, FlowyResult};
+use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
+pub(crate) async fn open_document_handler(
+  data: AFPluginData<OpenDocumentPayloadPBV2>,
+  manager: AFPluginState<Arc<DocumentManager>>,
+) -> DataResult<DocumentDataPB2, FlowyError> {
+  let context = data.into_inner();
+  let document = manager.open_document(context.document_id)?;
+  let document_data = document
+    .lock()
+    .get_document()
+    .map_err(|err| FlowyError::internal().context(err))?;
+  data_result_ok(DocumentDataPB2::from(DocumentDataWrapper(document_data)))
+}
+
+pub(crate) async fn close_document_handler(
+  data: AFPluginData<CloseDocumentPayloadPBV2>,
+  manager: AFPluginState<Arc<DocumentManager>>,
+) -> FlowyResult<()> {
+  let context = data.into_inner();
+  manager.close_document(context.document_id)?;
+  Ok(())
+}
+
+pub(crate) async fn apply_action_handler(
+  data: AFPluginData<ApplyActionPayloadPBV2>,
+  manager: AFPluginState<Arc<DocumentManager>>,
+) -> FlowyResult<()> {
+  let context = data.into_inner();
+  let doc_id = context.document_id;
+  let actions = context
+    .actions
+    .into_iter()
+    .map(|action| action.into())
+    .collect();
+  let document = manager.open_document(doc_id)?;
+  document.lock().apply_action(actions);
+  Ok(())
+}
+
+impl From<BlockActionPB> for BlockAction {
+  fn from(pb: BlockActionPB) -> Self {
+    Self {
+      action: pb.action.into(),
+      payload: pb.payload.into(),
+    }
+  }
+}
+
+impl From<BlockActionTypePB> for BlockActionType {
+  fn from(pb: BlockActionTypePB) -> Self {
+    match pb {
+      BlockActionTypePB::Insert => Self::Insert,
+      BlockActionTypePB::Update => Self::Update,
+      BlockActionTypePB::Delete => Self::Delete,
+      BlockActionTypePB::Move => Self::Move,
+    }
+  }
+}
+
+impl From<BlockActionPayloadPB> for BlockActionPayload {
+  fn from(pb: BlockActionPayloadPB) -> Self {
+    Self {
+      block: pb.block.into(),
+      parent_id: pb.parent_id,
+      prev_id: pb.prev_id,
+    }
+  }
+}
+
+impl From<BlockPB> for Block {
+  fn from(pb: BlockPB) -> Self {
+    let data = json_str_to_hashmap(&pb.data).unwrap_or_default();
+    Self {
+      id: pb.id,
+      ty: pb.ty,
+      children: pb.children_id,
+      parent: pb.parent_id,
+      data,
+      external_id: None,
+      external_type: None,
+    }
+  }
+}

+ 35 - 0
frontend/rust-lib/flowy-document2/src/event_map.rs

@@ -0,0 +1,35 @@
+use std::sync::Arc;
+use strum_macros::Display;
+
+use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
+use lib_dispatch::prelude::AFPlugin;
+
+use crate::{
+  event_handler::{apply_action_handler, close_document_handler, open_document_handler},
+  manager::DocumentManager,
+};
+
+pub fn init(document_manager: Arc<DocumentManager>) -> AFPlugin {
+  let mut plugin = AFPlugin::new()
+    .name(env!("CARGO_PKG_NAME"))
+    .state(document_manager);
+
+  plugin = plugin.event(DocumentEvent2::OpenDocument, open_document_handler);
+  plugin = plugin.event(DocumentEvent2::CloseDocument, close_document_handler);
+  plugin = plugin.event(DocumentEvent2::ApplyAction, apply_action_handler);
+
+  plugin
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, ProtoBuf_Enum, Flowy_Event)]
+#[event_err = "FlowyError"]
+pub enum DocumentEvent2 {
+  #[event(input = "OpenDocumentPayloadPBV2", output = "DocumentDataPB2")]
+  OpenDocument = 0,
+
+  #[event(input = "CloseDocumentPayloadPBV2")]
+  CloseDocument = 1,
+
+  #[event(input = "ApplyActionPayloadPBV2")]
+  ApplyAction = 2,
+}

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

@@ -0,0 +1,8 @@
+pub mod entities;
+pub mod event_map;
+pub mod manager;
+pub mod protobuf;
+
+mod document;
+mod event_handler;
+mod notification;

+ 71 - 0
frontend/rust-lib/flowy-document2/src/manager.rs

@@ -0,0 +1,71 @@
+use std::{collections::HashMap, sync::Arc};
+
+use collab::{plugin_impl::disk::CollabDiskPlugin, preclude::CollabBuilder};
+use collab_persistence::CollabKV;
+use flowy_error::{FlowyError, FlowyResult};
+use parking_lot::RwLock;
+
+use crate::{
+  document::{Document, DocumentDataWrapper},
+  notification::{send_notification, DocumentNotification},
+};
+
+pub trait DocumentUser: Send + Sync {
+  fn user_id(&self) -> Result<i64, FlowyError>;
+  fn token(&self) -> Result<String, FlowyError>; // unused now.
+  fn kv_db(&self) -> Result<Arc<CollabKV>, FlowyError>;
+}
+
+pub struct DocumentManager {
+  documents: Arc<RwLock<HashMap<String, Arc<Document>>>>,
+  user: Arc<dyn DocumentUser>,
+}
+
+// unsafe impl Send for DocumentManager {}
+// unsafe impl Sync for DocumentManager {}
+
+impl DocumentManager {
+  pub fn new(user: Arc<dyn DocumentUser>) -> Self {
+    Self {
+      documents: Default::default(),
+      user,
+    }
+  }
+
+  pub fn open_document(&self, doc_id: String) -> FlowyResult<Arc<Document>> {
+    if let Some(doc) = self.documents.read().get(&doc_id) {
+      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 clone_doc_id = doc_id.clone();
+    let _document_data = document
+      .lock()
+      .open(move |_, _| {
+        // TODO: add payload data.
+        send_notification(&clone_doc_id, DocumentNotification::DidReceiveUpdate).send();
+      })
+      .map_err(|err| FlowyError::internal().context(err))?;
+    self.documents.write().insert(doc_id, document.clone());
+    Ok(document)
+  }
+
+  pub fn close_document(&self, doc_id: String) -> FlowyResult<()> {
+    self.documents.write().remove(&doc_id);
+    Ok(())
+  }
+
+  fn get_collab_for_doc_id(&self, doc_id: &str) -> Result<collab::preclude::Collab, FlowyError> {
+    let uid = self.user.user_id()?;
+    let kv_db = self.user.kv_db()?;
+    let mut collab = CollabBuilder::new(uid, doc_id).build();
+    let disk_plugin = Arc::new(
+      CollabDiskPlugin::new(uid, kv_db).map_err(|err| FlowyError::internal().context(err))?,
+    );
+    collab.add_plugin(disk_plugin);
+    collab.initial();
+    Ok(collab)
+  }
+}

+ 28 - 0
frontend/rust-lib/flowy-document2/src/notification.rs

@@ -0,0 +1,28 @@
+use flowy_derive::ProtoBuf_Enum;
+use flowy_notification::NotificationBuilder;
+
+
+const OBSERVABLE_CATEGORY: &str = "Document";
+
+#[derive(ProtoBuf_Enum, Debug)]
+pub(crate) enum DocumentNotification {
+  Unknown = 0,
+
+  DidReceiveUpdate = 1,
+}
+
+impl std::default::Default for DocumentNotification {
+  fn default() -> Self {
+    DocumentNotification::Unknown
+  }
+}
+
+impl std::convert::From<DocumentNotification> for i32 {
+  fn from(notification: DocumentNotification) -> Self {
+    notification as i32
+  }
+}
+
+pub(crate) fn send_notification(id: &str, ty: DocumentNotification) -> NotificationBuilder {
+  NotificationBuilder::new(id, ty, OBSERVABLE_CATEGORY)
+}

+ 0 - 0
frontend/rust-lib/flowy-document2/tests/document_test.rs


+ 3 - 0
frontend/rust-lib/flowy-error/src/code.rs

@@ -189,6 +189,9 @@ pub enum ErrorCode {
 
   #[error("Only the date type can be used in calendar")]
   UnexpectedCalendarFieldType = 61,
+
+  #[error("Document Data Invalid")]
+  DocumentDataInvalid = 62,
 }
 
 impl ErrorCode {

+ 1 - 0
frontend/rust-lib/flowy-net/Cargo.toml

@@ -20,6 +20,7 @@ flowy-client-network-config= { path = "../../../shared-lib/flowy-client-network-
 flowy-sync = { path = "../../../shared-lib/flowy-sync"}
 user-model = { path = "../../../shared-lib/user-model"}
 flowy-folder2 = { path = "../flowy-folder2" }
+flowy-document2 = { path = "../flowy-document2" }
 flowy-user = { path = "../flowy-user" }
 flowy-document = { path = "../flowy-document" }
 lazy_static = "1.4.0"

部分文件因为文件数量过多而无法显示