Browse Source

chore: add appflowy_document editor

appflowy 2 years ago
parent
commit
a562395eb1

+ 1 - 1
frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart

@@ -115,7 +115,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   void _composeDelta(Delta composedDelta, Delta documentDelta) async {
     final json = jsonEncode(composedDelta.toJson());
     Log.debug("doc_id: $view.id - Send json: $json");
-    final result = await service.applyEdit(docId: view.id, data: json);
+    final result = await service.applyEdit(docId: view.id, operations: json);
 
     result.fold(
       (_) {},

+ 2 - 4
frontend/app_flowy/lib/plugins/doc/application/doc_service.dart

@@ -18,13 +18,11 @@ class DocumentService {
 
   Future<Either<Unit, FlowyError>> applyEdit({
     required String docId,
-    required String data,
-    String operations = "",
+    required String operations,
   }) {
     final payload = EditPayloadPB.create()
       ..docId = docId
-      ..operations = operations
-      ..operationsStr = data;
+      ..operations = operations;
     return DocumentEventApplyEdit(payload).send();
   }
 

+ 47 - 0
frontend/rust-lib/flowy-document/src/editor/document.rs

@@ -0,0 +1,47 @@
+use bytes::Bytes;
+use flowy_error::{FlowyError, FlowyResult};
+use flowy_revision::{RevisionObjectDeserializer, RevisionObjectSerializer};
+use flowy_sync::entities::document::DocumentPayloadPB;
+use flowy_sync::entities::revision::Revision;
+use flowy_sync::util::make_operations_from_revisions;
+use lib_ot::core::{AttributeHashMap, Transaction};
+
+pub struct Document {
+    doc_id: String,
+}
+
+impl Document {
+    pub fn from_content(content: &str) -> FlowyResult<Self> {
+        todo!()
+    }
+}
+
+pub struct DocumentRevisionSerde();
+impl RevisionObjectDeserializer for DocumentRevisionSerde {
+    type Output = DocumentPayloadPB;
+
+    fn deserialize_revisions(object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Self::Output> {
+        let (base_rev_id, rev_id) = revisions.last().unwrap().pair_rev_id();
+
+        for revision in revisions {
+            let transaction = Transaction::from_bytes(&revision.bytes)?;
+        }
+
+        let mut delta = make_operations_from_revisions(revisions)?;
+        correct_delta(&mut delta);
+
+        Result::<DocumentPayloadPB, FlowyError>::Ok(DocumentPayloadPB {
+            doc_id: object_id.to_owned(),
+            content: delta.json_str(),
+            rev_id,
+            base_rev_id,
+        })
+    }
+}
+
+impl RevisionObjectSerializer for DocumentRevisionSerde {
+    fn serialize_revisions(revisions: Vec<Revision>) -> FlowyResult<Bytes> {
+        let operations = make_operations_from_revisions::<AttributeHashMap>(revisions)?;
+        Ok(operations.json_bytes())
+    }
+}

+ 50 - 0
frontend/rust-lib/flowy-document/src/editor/editor.rs

@@ -0,0 +1,50 @@
+use crate::{DocumentEditor, DocumentUser};
+use bytes::Bytes;
+use flowy_error::{FlowyError, FlowyResult};
+use flowy_revision::{RevisionCloudService, RevisionManager};
+use flowy_sync::entities::ws_data::ServerRevisionWSData;
+use lib_infra::future::FutureResult;
+use lib_ws::WSConnectState;
+use std::any::Any;
+use std::sync::Arc;
+
+pub struct AppFlowyDocumentEditor {
+    doc_id: String,
+}
+
+impl AppFlowyDocumentEditor {
+    pub async fn new(
+        doc_id: &str,
+        user: Arc<dyn DocumentUser>,
+        mut rev_manager: RevisionManager,
+        cloud_service: Arc<dyn RevisionCloudService>,
+    ) -> FlowyResult<Arc<Self>> {
+        todo!()
+    }
+}
+
+impl DocumentEditor for Arc<AppFlowyDocumentEditor> {
+    fn get_operations_str(&self) -> FutureResult<String, FlowyError> {
+        todo!()
+    }
+
+    fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> {
+        todo!()
+    }
+
+    fn close(&self) {
+        todo!()
+    }
+
+    fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError> {
+        todo!()
+    }
+
+    fn receive_ws_state(&self, state: &WSConnectState) {
+        todo!()
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+}

+ 4 - 0
frontend/rust-lib/flowy-document/src/editor/mod.rs

@@ -0,0 +1,4 @@
+mod document;
+mod editor;
+
+pub use editor::*;

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

@@ -37,10 +37,6 @@ pub struct EditPayloadPB {
     // Encode in JSON format
     #[pb(index = 2)]
     pub operations: String,
-
-    // Encode in JSON format
-    #[pb(index = 3)]
-    pub operations_str: String,
 }
 
 #[derive(Default)]
@@ -49,9 +45,6 @@ pub struct EditParams {
 
     // Encode in JSON format
     pub operations: String,
-
-    // Encode in JSON format
-    pub operations_str: String,
 }
 
 impl TryInto<EditParams> for EditPayloadPB {
@@ -60,7 +53,6 @@ impl TryInto<EditParams> for EditPayloadPB {
         Ok(EditParams {
             doc_id: self.doc_id,
             operations: self.operations,
-            operations_str: self.operations_str,
         })
     }
 }

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

@@ -3,6 +3,7 @@ mod event_handler;
 pub mod event_map;
 pub mod manager;
 
+pub mod editor;
 pub mod old_editor;
 pub mod protobuf;
 

+ 1 - 3
frontend/rust-lib/flowy-document/src/manager.rs

@@ -108,9 +108,7 @@ impl DocumentManager {
 
     pub async fn apply_edit(&self, params: EditParams) -> FlowyResult<()> {
         let editor = self.get_document_editor(&params.doc_id).await?;
-        let _ = editor
-            .compose_local_operations(Bytes::from(params.operations_str))
-            .await?;
+        let _ = editor.compose_local_operations(Bytes::from(params.operations)).await?;
         Ok(())
     }
 

+ 1 - 0
frontend/rust-lib/flowy-document/src/old_editor/conflict.rs

@@ -0,0 +1 @@
+

+ 8 - 6
frontend/rust-lib/flowy-document/src/old_editor/editor.rs

@@ -97,8 +97,10 @@ impl OldDocumentEditor {
         rev_web_socket: Arc<dyn RevisionWebSocket>,
         cloud_service: Arc<dyn RevisionCloudService>,
     ) -> FlowyResult<Arc<Self>> {
-        let document_info = rev_manager.load::<DocumentRevisionSerde>(Some(cloud_service)).await?;
-        let operations = TextOperations::from_bytes(&document_info.content)?;
+        let document = rev_manager
+            .load::<DeltaDocumentRevisionSerde>(Some(cloud_service))
+            .await?;
+        let operations = TextOperations::from_bytes(&document.content)?;
         let rev_manager = Arc::new(rev_manager);
         let doc_id = doc_id.to_string();
         let user_id = user.user_id()?;
@@ -240,8 +242,8 @@ impl OldDocumentEditor {
     }
 }
 
-pub struct DocumentRevisionSerde();
-impl RevisionObjectDeserializer for DocumentRevisionSerde {
+pub struct DeltaDocumentRevisionSerde();
+impl RevisionObjectDeserializer for DeltaDocumentRevisionSerde {
     type Output = DocumentPayloadPB;
 
     fn deserialize_revisions(object_id: &str, revisions: Vec<Revision>) -> FlowyResult<Self::Output> {
@@ -258,7 +260,7 @@ impl RevisionObjectDeserializer for DocumentRevisionSerde {
     }
 }
 
-impl RevisionObjectSerializer for DocumentRevisionSerde {
+impl RevisionObjectSerializer for DeltaDocumentRevisionSerde {
     fn serialize_revisions(revisions: Vec<Revision>) -> FlowyResult<Bytes> {
         let operations = make_operations_from_revisions::<AttributeHashMap>(revisions)?;
         Ok(operations.json_bytes())
@@ -268,7 +270,7 @@ impl RevisionObjectSerializer for DocumentRevisionSerde {
 pub(crate) struct DocumentRevisionCompactor();
 impl RevisionCompress for DocumentRevisionCompactor {
     fn serialize_revisions(&self, revisions: Vec<Revision>) -> FlowyResult<Bytes> {
-        DocumentRevisionSerde::serialize_revisions(revisions)
+        DeltaDocumentRevisionSerde::serialize_revisions(revisions)
     }
 }
 

+ 32 - 21
shared-lib/lib-ot/src/core/node_tree/transaction.rs

@@ -2,6 +2,7 @@ use super::{Changeset, NodeOperations};
 use crate::core::attributes::AttributeHashMap;
 use crate::core::{Interval, NodeData, NodeOperation, NodeTree, Path};
 use crate::errors::OTError;
+use bytes::Bytes;
 use indextree::NodeId;
 use serde::{Deserialize, Serialize};
 use std::rc::Rc;
@@ -17,27 +18,6 @@ pub struct Transaction {
     pub extension: Extension,
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub enum Extension {
-    Empty,
-    TextSelection {
-        before_selection: Interval,
-        after_selection: Interval,
-    },
-}
-
-impl std::default::Default for Extension {
-    fn default() -> Self {
-        Extension::Empty
-    }
-}
-
-impl Extension {
-    fn is_empty(&self) -> bool {
-        matches!(self, Extension::Empty)
-    }
-}
-
 impl Transaction {
     pub fn new() -> Self {
         Self::default()
@@ -50,6 +30,16 @@ impl Transaction {
         }
     }
 
+    pub fn from_bytes(bytes: &Vec<u8>) -> Result<Self, OTError> {
+        let transaction = serde_json::from_slice(bytes).map_err(|err| OTError::serde().context(err))?;
+        Ok(transaction)
+    }
+
+    pub fn to_bytes(&self) -> Result<Vec<u8>, OTError> {
+        let bytes = serde_json::to_vec(&self).map_err(|err| OTError::serde().context(err))?;
+        Ok(bytes)
+    }
+
     pub fn into_operations(self) -> Vec<Rc<NodeOperation>> {
         self.operations.into_inner()
     }
@@ -93,6 +83,27 @@ impl std::ops::DerefMut for Transaction {
     }
 }
 
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum Extension {
+    Empty,
+    TextSelection {
+        before_selection: Interval,
+        after_selection: Interval,
+    },
+}
+
+impl std::default::Default for Extension {
+    fn default() -> Self {
+        Extension::Empty
+    }
+}
+
+impl Extension {
+    fn is_empty(&self) -> bool {
+        matches!(self, Extension::Empty)
+    }
+}
+
 pub struct TransactionBuilder<'a> {
     node_tree: &'a NodeTree,
     operations: NodeOperations,

+ 1 - 55
shared-lib/lib-ot/tests/node/editor_test.rs

@@ -36,7 +36,7 @@ fn editor_deserialize_node_test() {
         },
         AssertNumberOfNodesAtPath {
             path: Some(0.into()),
-            len: 14,
+            len: 4,
         },
         AssertNodeDelta {
             path: vec![0, 1].into(),
@@ -104,60 +104,6 @@ const EXAMPLE_JSON: &str = r#"
             { "insert": "Flutter", "attributes": { "underline": true } }
         ]
       }
-    },
-    {
-      "type": "text",
-      "attributes": { "checkbox": true, "subtype": "checkbox" },
-      "body": {
-        "delta": [{ "insert": "Customizable" }]
-      }
-    },
-    {
-      "type": "text",
-      "attributes": { "checkbox": true, "subtype": "checkbox" },
-      "delta": [{ "insert": "Test-covered" }]
-    },
-    {
-      "type": "text",
-      "attributes": { "checkbox": false, "subtype": "checkbox" },
-      "delta": [{ "insert": "more to come!" }]
-    },
-    { "type": "text", "delta": [] },
-    {
-      "type": "text",
-      "attributes": { "subtype": "quote" },
-      "delta": [{ "insert": "Here is an exmaple you can give it a try" }]
-    },
-    { "type": "text", "delta": [] },
-    {
-      "type": "text",
-      "delta": [
-        { "insert": "You can also use " },
-        {
-          "insert": "AppFlowy Editor",
-          "attributes": {
-            "italic": true,
-            "bold": true,
-            "backgroundColor": "0x6000BCF0"
-          }
-        },
-        { "insert": " as a component to build your own app." }
-      ]
-    },
-    { "type": "text", "delta": [] },
-    {
-      "type": "text",
-      "attributes": { "subtype": "bulleted-list" },
-      "delta": [{ "insert": "Use / to insert blocks" }]
-    },
-    {
-      "type": "text",
-      "attributes": { "subtype": "bulleted-list" },
-      "delta": [
-        {
-          "insert": "Select text to trigger to the toolbar to format your notes."
-        }
-      ]
     }
   ]
 }