mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: implement document editor trait (#1321)
This commit is contained in:
@ -2,7 +2,9 @@ use bytes::Bytes;
|
|||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use flowy_revision::{RevisionObjectDeserializer, RevisionObjectSerializer};
|
use flowy_revision::{RevisionObjectDeserializer, RevisionObjectSerializer};
|
||||||
use flowy_sync::entities::revision::Revision;
|
use flowy_sync::entities::revision::Revision;
|
||||||
use lib_ot::core::{Body, Extension, Interval, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Transaction};
|
use lib_ot::core::{
|
||||||
|
Body, Extension, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Selection, Transaction,
|
||||||
|
};
|
||||||
use lib_ot::text_delta::TextOperationBuilder;
|
use lib_ot::text_delta::TextOperationBuilder;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -46,8 +48,8 @@ pub fn initial_document_content() -> String {
|
|||||||
nodes: vec![editor_node],
|
nodes: vec![editor_node],
|
||||||
};
|
};
|
||||||
let extension = Extension::TextSelection {
|
let extension = Extension::TextSelection {
|
||||||
before_selection: Interval::default(),
|
before_selection: Selection::default(),
|
||||||
after_selection: Interval::default(),
|
after_selection: Selection::default(),
|
||||||
};
|
};
|
||||||
let transaction = Transaction {
|
let transaction = Transaction {
|
||||||
operations: vec![node_operation].into(),
|
operations: vec![node_operation].into(),
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
use crate::editor::document::Document;
|
use crate::editor::document::Document;
|
||||||
|
|
||||||
use lib_ot::core::{AttributeHashMap, Body, NodeData, NodeId, NodeTree};
|
use bytes::Bytes;
|
||||||
|
use flowy_error::FlowyResult;
|
||||||
|
use lib_ot::core::{
|
||||||
|
AttributeHashMap, Body, Changeset, Extension, NodeData, NodeId, NodeOperation, NodeTree, Path, Selection,
|
||||||
|
Transaction,
|
||||||
|
};
|
||||||
use lib_ot::text_delta::TextOperations;
|
use lib_ot::text_delta::TextOperations;
|
||||||
use serde::de::{self, MapAccess, Visitor};
|
use serde::de::{self, MapAccess, Visitor};
|
||||||
use serde::ser::{SerializeMap, SerializeSeq};
|
use serde::ser::{SerializeMap, SerializeSeq};
|
||||||
@ -67,8 +72,157 @@ impl<'de> Deserialize<'de> for Document {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct DocumentContentSerializer<'a>(pub &'a Document);
|
struct DocumentContentSerializer<'a>(pub &'a Document);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DocumentTransaction {
|
||||||
|
#[serde(default)]
|
||||||
|
operations: Vec<DocumentOperation>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
before_selection: Selection,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
after_selection: Selection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocumentTransaction {
|
||||||
|
pub fn to_json(&self) -> FlowyResult<String> {
|
||||||
|
let json = serde_json::to_string(self)?;
|
||||||
|
Ok(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> FlowyResult<Bytes> {
|
||||||
|
let data = serde_json::to_vec(&self)?;
|
||||||
|
Ok(Bytes::from(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: Bytes) -> FlowyResult<Self> {
|
||||||
|
let transaction = serde_json::from_slice(&bytes)?;
|
||||||
|
Ok(transaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<Transaction> for DocumentTransaction {
|
||||||
|
fn from(transaction: Transaction) -> Self {
|
||||||
|
let (before_selection, after_selection) = match transaction.extension {
|
||||||
|
Extension::Empty => (Selection::default(), Selection::default()),
|
||||||
|
Extension::TextSelection {
|
||||||
|
before_selection,
|
||||||
|
after_selection,
|
||||||
|
} => (before_selection, after_selection),
|
||||||
|
};
|
||||||
|
|
||||||
|
DocumentTransaction {
|
||||||
|
operations: transaction
|
||||||
|
.operations
|
||||||
|
.into_inner()
|
||||||
|
.into_iter()
|
||||||
|
.map(|operation| operation.as_ref().into())
|
||||||
|
.collect(),
|
||||||
|
before_selection,
|
||||||
|
after_selection,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<DocumentTransaction> for Transaction {
|
||||||
|
fn from(transaction: DocumentTransaction) -> Self {
|
||||||
|
Transaction {
|
||||||
|
operations: transaction
|
||||||
|
.operations
|
||||||
|
.into_iter()
|
||||||
|
.map(|operation| operation.into())
|
||||||
|
.collect::<Vec<NodeOperation>>()
|
||||||
|
.into(),
|
||||||
|
extension: Extension::TextSelection {
|
||||||
|
before_selection: transaction.before_selection,
|
||||||
|
after_selection: transaction.after_selection,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "op")]
|
||||||
|
pub enum DocumentOperation {
|
||||||
|
#[serde(rename = "insert")]
|
||||||
|
Insert { path: Path, nodes: Vec<DocumentNode> },
|
||||||
|
|
||||||
|
#[serde(rename = "delete")]
|
||||||
|
Delete { path: Path, nodes: Vec<DocumentNode> },
|
||||||
|
|
||||||
|
#[serde(rename = "update")]
|
||||||
|
Update {
|
||||||
|
path: Path,
|
||||||
|
attributes: AttributeHashMap,
|
||||||
|
#[serde(rename = "oldAttributes")]
|
||||||
|
old_attributes: AttributeHashMap,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[serde(rename = "update_text")]
|
||||||
|
UpdateText {
|
||||||
|
path: Path,
|
||||||
|
delta: TextOperations,
|
||||||
|
inverted: TextOperations,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<DocumentOperation> for NodeOperation {
|
||||||
|
fn from(document_operation: DocumentOperation) -> Self {
|
||||||
|
match document_operation {
|
||||||
|
DocumentOperation::Insert { path, nodes } => NodeOperation::Insert {
|
||||||
|
path,
|
||||||
|
nodes: nodes.into_iter().map(|node| node.into()).collect(),
|
||||||
|
},
|
||||||
|
DocumentOperation::Delete { path, nodes } => NodeOperation::Delete {
|
||||||
|
path,
|
||||||
|
|
||||||
|
nodes: nodes.into_iter().map(|node| node.into()).collect(),
|
||||||
|
},
|
||||||
|
DocumentOperation::Update {
|
||||||
|
path,
|
||||||
|
attributes,
|
||||||
|
old_attributes,
|
||||||
|
} => NodeOperation::Update {
|
||||||
|
path,
|
||||||
|
changeset: Changeset::Attributes {
|
||||||
|
new: attributes,
|
||||||
|
old: old_attributes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DocumentOperation::UpdateText { path, delta, inverted } => NodeOperation::Update {
|
||||||
|
path,
|
||||||
|
changeset: Changeset::Delta { delta, inverted },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<&NodeOperation> for DocumentOperation {
|
||||||
|
fn from(node_operation: &NodeOperation) -> Self {
|
||||||
|
let node_operation = node_operation.clone();
|
||||||
|
match node_operation {
|
||||||
|
NodeOperation::Insert { path, nodes } => DocumentOperation::Insert {
|
||||||
|
path,
|
||||||
|
nodes: nodes.into_iter().map(|node| node.into()).collect(),
|
||||||
|
},
|
||||||
|
NodeOperation::Update { path, changeset } => match changeset {
|
||||||
|
Changeset::Delta { delta, inverted } => DocumentOperation::UpdateText { path, delta, inverted },
|
||||||
|
Changeset::Attributes { new, old } => DocumentOperation::Update {
|
||||||
|
path,
|
||||||
|
attributes: new,
|
||||||
|
old_attributes: old,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NodeOperation::Delete { path, nodes } => DocumentOperation::Delete {
|
||||||
|
path,
|
||||||
|
nodes: nodes.into_iter().map(|node| node.into()).collect(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||||
pub struct DocumentNodeData {
|
pub struct DocumentNode {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub node_type: String,
|
pub node_type: String,
|
||||||
|
|
||||||
@ -81,21 +235,32 @@ pub struct DocumentNodeData {
|
|||||||
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub children: Vec<DocumentNodeData>,
|
pub children: Vec<DocumentNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<NodeData> for DocumentNodeData {
|
impl std::convert::From<NodeData> for DocumentNode {
|
||||||
fn from(node_data: NodeData) -> Self {
|
fn from(node_data: NodeData) -> Self {
|
||||||
let delta = if let Body::Delta(operations) = node_data.body {
|
let delta = if let Body::Delta(operations) = node_data.body {
|
||||||
operations
|
operations
|
||||||
} else {
|
} else {
|
||||||
TextOperations::default()
|
TextOperations::default()
|
||||||
};
|
};
|
||||||
DocumentNodeData {
|
DocumentNode {
|
||||||
node_type: node_data.node_type,
|
node_type: node_data.node_type,
|
||||||
attributes: node_data.attributes,
|
attributes: node_data.attributes,
|
||||||
delta,
|
delta,
|
||||||
children: node_data.children.into_iter().map(DocumentNodeData::from).collect(),
|
children: node_data.children.into_iter().map(DocumentNode::from).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::convert::From<DocumentNode> for NodeData {
|
||||||
|
fn from(document_node: DocumentNode) -> Self {
|
||||||
|
NodeData {
|
||||||
|
node_type: document_node.node_type,
|
||||||
|
attributes: document_node.attributes,
|
||||||
|
body: Body::Delta(document_node.delta),
|
||||||
|
children: document_node.children.into_iter().map(|child| child.into()).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +274,7 @@ impl<'a> Serialize for DocumentContentSerializer<'a> {
|
|||||||
let root_node_id = tree.root_node_id();
|
let root_node_id = tree.root_node_id();
|
||||||
|
|
||||||
// transform the NodeData to DocumentNodeData
|
// transform the NodeData to DocumentNodeData
|
||||||
let get_document_node_data = |node_id: NodeId| tree.get_node_data(node_id).map(DocumentNodeData::from);
|
let get_document_node_data = |node_id: NodeId| tree.get_node_data(node_id).map(DocumentNode::from);
|
||||||
|
|
||||||
let mut children = tree.get_children_ids(root_node_id);
|
let mut children = tree.get_children_ids(root_node_id);
|
||||||
if children.len() == 1 {
|
if children.len() == 1 {
|
||||||
@ -133,6 +298,40 @@ impl<'a> Serialize for DocumentContentSerializer<'a> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::editor::document::Document;
|
use crate::editor::document::Document;
|
||||||
|
use crate::editor::document_serde::DocumentTransaction;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_deserialize_update_text_operation_test() {
|
||||||
|
// bold
|
||||||
|
let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":3,"attributes":{"bold":true}}],"inverted":[{"retain":3,"attributes":{"bold":null}}]}],"after_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}}}"#;
|
||||||
|
let _ = serde_json::from_str::<DocumentTransaction>(json).unwrap();
|
||||||
|
|
||||||
|
// delete character
|
||||||
|
let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":2},{"delete":1}],"inverted":[{"retain":2},{"insert":"C","attributes":{"bold":true}}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[0],"offset":3},"end":{"path":[0],"offset":3}}}"#;
|
||||||
|
let _ = serde_json::from_str::<DocumentTransaction>(json).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_deserialize_insert_operation_test() {
|
||||||
|
let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"insert":"a"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0],"offset":1},"end":{"path":[0],"offset":1}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":0}}}"#;
|
||||||
|
let _ = serde_json::from_str::<DocumentTransaction>(json).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_deserialize_delete_operation_test() {
|
||||||
|
let json = r#"{"operations": [{"op":"delete","path":[1],"nodes":[{"type":"text","delta":[]}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[1],"offset":0},"end":{"path":[1],"offset":0}}}"#;
|
||||||
|
let _transaction = serde_json::from_str::<DocumentTransaction>(json).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_deserialize_update_attribute_operation_test() {
|
||||||
|
// let json = r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3,"attributes":{"bold":true}},"oldAttributes":{"retain":3,"attributes":{"bold":null}}}]}"#;
|
||||||
|
// let transaction = serde_json::from_str::<DocumentTransaction>(&json).unwrap();
|
||||||
|
|
||||||
|
let json =
|
||||||
|
r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3},"oldAttributes":{"retain":3}}]}"#;
|
||||||
|
let _ = serde_json::from_str::<DocumentTransaction>(json).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn document_serde_test() {
|
fn document_serde_test() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::editor::document::{Document, DocumentRevisionSerde};
|
use crate::editor::document::{Document, DocumentRevisionSerde};
|
||||||
|
use crate::editor::document_serde::DocumentTransaction;
|
||||||
use crate::editor::queue::{Command, CommandSender, DocumentQueue};
|
use crate::editor::queue::{Command, CommandSender, DocumentQueue};
|
||||||
use crate::{DocumentEditor, DocumentUser};
|
use crate::{DocumentEditor, DocumentUser};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@ -66,25 +67,27 @@ fn spawn_edit_queue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentEditor for Arc<AppFlowyDocumentEditor> {
|
impl DocumentEditor for Arc<AppFlowyDocumentEditor> {
|
||||||
fn get_operations_str(&self) -> FutureResult<String, FlowyError> {
|
fn export(&self) -> FutureResult<String, FlowyError> {
|
||||||
todo!()
|
let this = self.clone();
|
||||||
|
FutureResult::new(async move { this.get_content(false).await })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compose_local_operations(&self, _data: Bytes) -> FutureResult<(), FlowyError> {
|
fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> {
|
||||||
todo!()
|
let this = self.clone();
|
||||||
|
FutureResult::new(async move {
|
||||||
|
let transaction = DocumentTransaction::from_bytes(data)?;
|
||||||
|
let _ = this.apply_transaction(transaction.into()).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close(&self) {
|
fn close(&self) {}
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FutureResult<(), FlowyError> {
|
fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FutureResult<(), FlowyError> {
|
||||||
todo!()
|
FutureResult::new(async move { Ok(()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_ws_state(&self, _state: &WSConnectState) {
|
fn receive_ws_state(&self, _state: &WSConnectState) {}
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
|
@ -12,7 +12,7 @@ pub(crate) async fn get_document_handler(
|
|||||||
) -> DataResult<DocumentSnapshotPB, FlowyError> {
|
) -> DataResult<DocumentSnapshotPB, FlowyError> {
|
||||||
let document_id: DocumentIdPB = data.into_inner();
|
let document_id: DocumentIdPB = data.into_inner();
|
||||||
let editor = manager.open_document_editor(&document_id).await?;
|
let editor = manager.open_document_editor(&document_id).await?;
|
||||||
let operations_str = editor.get_operations_str().await?;
|
let operations_str = editor.export().await?;
|
||||||
data_result(DocumentSnapshotPB {
|
data_result(DocumentSnapshotPB {
|
||||||
doc_id: document_id.into(),
|
doc_id: document_id.into(),
|
||||||
snapshot: operations_str,
|
snapshot: operations_str,
|
||||||
@ -35,9 +35,9 @@ pub(crate) async fn export_handler(
|
|||||||
) -> DataResult<ExportDataPB, FlowyError> {
|
) -> DataResult<ExportDataPB, FlowyError> {
|
||||||
let params: ExportParams = data.into_inner().try_into()?;
|
let params: ExportParams = data.into_inner().try_into()?;
|
||||||
let editor = manager.open_document_editor(¶ms.view_id).await?;
|
let editor = manager.open_document_editor(¶ms.view_id).await?;
|
||||||
let operations_str = editor.get_operations_str().await?;
|
let document_data = editor.export().await?;
|
||||||
data_result(ExportDataPB {
|
data_result(ExportDataPB {
|
||||||
data: operations_str,
|
data: document_data,
|
||||||
export_type: params.export_type,
|
export_type: params.export_type,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::editor::{initial_document_content, AppFlowyDocumentEditor};
|
use crate::editor::{initial_document_content, AppFlowyDocumentEditor};
|
||||||
use crate::entities::EditParams;
|
use crate::entities::EditParams;
|
||||||
use crate::old_editor::editor::{DocumentRevisionCompress, OldDocumentEditor};
|
use crate::old_editor::editor::{DeltaDocumentEditor, DocumentRevisionCompress};
|
||||||
use crate::{errors::FlowyError, DocumentCloudService};
|
use crate::{errors::FlowyError, DocumentCloudService};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
@ -30,7 +30,7 @@ pub trait DocumentUser: Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait DocumentEditor: Send + Sync {
|
pub trait DocumentEditor: Send + Sync {
|
||||||
fn get_operations_str(&self) -> FutureResult<String, FlowyError>;
|
fn export(&self) -> FutureResult<String, FlowyError>;
|
||||||
fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError>;
|
fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError>;
|
||||||
fn close(&self);
|
fn close(&self);
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ impl DocumentManager {
|
|||||||
let _ = editor
|
let _ = editor
|
||||||
.compose_local_operations(Bytes::from(payload.operations_str))
|
.compose_local_operations(Bytes::from(payload.operations_str))
|
||||||
.await?;
|
.await?;
|
||||||
let operations_str = editor.get_operations_str().await?;
|
let operations_str = editor.export().await?;
|
||||||
Ok(DocumentOperationsPB {
|
Ok(DocumentOperationsPB {
|
||||||
doc_id: payload.doc_id.clone(),
|
doc_id: payload.doc_id.clone(),
|
||||||
operations_str,
|
operations_str,
|
||||||
@ -198,7 +198,7 @@ impl DocumentManager {
|
|||||||
Arc::new(editor)
|
Arc::new(editor)
|
||||||
} else {
|
} else {
|
||||||
let editor =
|
let editor =
|
||||||
OldDocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?;
|
DeltaDocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?;
|
||||||
Arc::new(editor)
|
Arc::new(editor)
|
||||||
};
|
};
|
||||||
self.editor_map.insert(doc_id, editor.clone());
|
self.editor_map.insert(doc_id, editor.clone());
|
||||||
|
@ -25,7 +25,7 @@ use std::any::Any;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
pub struct OldDocumentEditor {
|
pub struct DeltaDocumentEditor {
|
||||||
pub doc_id: String,
|
pub doc_id: String,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
rev_manager: Arc<RevisionManager>,
|
rev_manager: Arc<RevisionManager>,
|
||||||
@ -34,7 +34,7 @@ pub struct OldDocumentEditor {
|
|||||||
edit_cmd_tx: EditorCommandSender,
|
edit_cmd_tx: EditorCommandSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OldDocumentEditor {
|
impl DeltaDocumentEditor {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub(crate) async fn new(
|
pub(crate) async fn new(
|
||||||
doc_id: &str,
|
doc_id: &str,
|
||||||
@ -146,8 +146,8 @@ impl OldDocumentEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentEditor for Arc<OldDocumentEditor> {
|
impl DocumentEditor for Arc<DeltaDocumentEditor> {
|
||||||
fn get_operations_str(&self) -> FutureResult<String, FlowyError> {
|
fn export(&self) -> FutureResult<String, FlowyError> {
|
||||||
let (ret, rx) = oneshot::channel::<CollaborateResult<String>>();
|
let (ret, rx) = oneshot::channel::<CollaborateResult<String>>();
|
||||||
let msg = EditorCommand::GetOperationsString { ret };
|
let msg = EditorCommand::GetOperationsString { ret };
|
||||||
let edit_cmd_tx = self.edit_cmd_tx.clone();
|
let edit_cmd_tx = self.edit_cmd_tx.clone();
|
||||||
@ -197,7 +197,7 @@ impl DocumentEditor for Arc<OldDocumentEditor> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl std::ops::Drop for OldDocumentEditor {
|
impl std::ops::Drop for DeltaDocumentEditor {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
tracing::trace!("{} DocumentEditor was dropped", self.doc_id)
|
tracing::trace!("{} DocumentEditor was dropped", self.doc_id)
|
||||||
}
|
}
|
||||||
@ -225,7 +225,7 @@ fn spawn_edit_queue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "flowy_unit_test")]
|
#[cfg(feature = "flowy_unit_test")]
|
||||||
impl OldDocumentEditor {
|
impl DeltaDocumentEditor {
|
||||||
pub async fn document_operations(&self) -> FlowyResult<TextOperations> {
|
pub async fn document_operations(&self) -> FlowyResult<TextOperations> {
|
||||||
let (ret, rx) = oneshot::channel::<CollaborateResult<TextOperations>>();
|
let (ret, rx) = oneshot::channel::<CollaborateResult<TextOperations>>();
|
||||||
let msg = EditorCommand::GetOperations { ret };
|
let msg = EditorCommand::GetOperations { ret };
|
||||||
|
@ -14,7 +14,7 @@ async fn text_block_sync_current_rev_id_check() {
|
|||||||
AssertNextSyncRevId(None),
|
AssertNextSyncRevId(None),
|
||||||
AssertJson(r#"[{"insert":"123\n"}]"#),
|
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||||
];
|
];
|
||||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -28,7 +28,7 @@ async fn text_block_sync_state_check() {
|
|||||||
AssertRevisionState(3, RevisionState::Ack),
|
AssertRevisionState(3, RevisionState::Ack),
|
||||||
AssertJson(r#"[{"insert":"123\n"}]"#),
|
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||||
];
|
];
|
||||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -40,7 +40,7 @@ async fn text_block_sync_insert_test() {
|
|||||||
AssertJson(r#"[{"insert":"123\n"}]"#),
|
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||||
AssertNextSyncRevId(None),
|
AssertNextSyncRevId(None),
|
||||||
];
|
];
|
||||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -52,7 +52,7 @@ async fn text_block_sync_insert_in_chinese() {
|
|||||||
InsertText("好", offset),
|
InsertText("好", offset),
|
||||||
AssertJson(r#"[{"insert":"你好\n"}]"#),
|
AssertJson(r#"[{"insert":"你好\n"}]"#),
|
||||||
];
|
];
|
||||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -64,7 +64,7 @@ async fn text_block_sync_insert_with_emoji() {
|
|||||||
InsertText("☺️", offset),
|
InsertText("☺️", offset),
|
||||||
AssertJson(r#"[{"insert":"😁☺️\n"}]"#),
|
AssertJson(r#"[{"insert":"😁☺️\n"}]"#),
|
||||||
];
|
];
|
||||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -76,7 +76,7 @@ async fn text_block_sync_delete_in_english() {
|
|||||||
Delete(Interval::new(0, 2)),
|
Delete(Interval::new(0, 2)),
|
||||||
AssertJson(r#"[{"insert":"3\n"}]"#),
|
AssertJson(r#"[{"insert":"3\n"}]"#),
|
||||||
];
|
];
|
||||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -89,7 +89,7 @@ async fn text_block_sync_delete_in_chinese() {
|
|||||||
Delete(Interval::new(0, offset)),
|
Delete(Interval::new(0, offset)),
|
||||||
AssertJson(r#"[{"insert":"好\n"}]"#),
|
AssertJson(r#"[{"insert":"好\n"}]"#),
|
||||||
];
|
];
|
||||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -101,5 +101,5 @@ async fn text_block_sync_replace_test() {
|
|||||||
Replace(Interval::new(0, 3), "abc"),
|
Replace(Interval::new(0, 3), "abc"),
|
||||||
AssertJson(r#"[{"insert":"abc\n"}]"#),
|
AssertJson(r#"[{"insert":"abc\n"}]"#),
|
||||||
];
|
];
|
||||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use flowy_document::old_editor::editor::OldDocumentEditor;
|
use flowy_document::old_editor::editor::DeltaDocumentEditor;
|
||||||
use flowy_document::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS;
|
use flowy_document::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS;
|
||||||
use flowy_revision::disk::RevisionState;
|
use flowy_revision::disk::RevisionState;
|
||||||
use flowy_test::{helper::ViewTest, FlowySDKTest};
|
use flowy_test::{helper::ViewTest, FlowySDKTest};
|
||||||
@ -17,18 +17,18 @@ pub enum EditorScript {
|
|||||||
AssertJson(&'static str),
|
AssertJson(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OldDocumentEditorTest {
|
pub struct DeltaDocumentEditorTest {
|
||||||
pub sdk: FlowySDKTest,
|
pub sdk: FlowySDKTest,
|
||||||
pub editor: Arc<OldDocumentEditor>,
|
pub editor: Arc<DeltaDocumentEditor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OldDocumentEditorTest {
|
impl DeltaDocumentEditorTest {
|
||||||
pub async fn new() -> Self {
|
pub async fn new() -> Self {
|
||||||
let sdk = FlowySDKTest::default();
|
let sdk = FlowySDKTest::default();
|
||||||
let _ = sdk.init_user().await;
|
let _ = sdk.init_user().await;
|
||||||
let test = ViewTest::new_document_view(&sdk).await;
|
let test = ViewTest::new_document_view(&sdk).await;
|
||||||
let document_editor = sdk.document_manager.open_document_editor(&test.view.id).await.unwrap();
|
let document_editor = sdk.document_manager.open_document_editor(&test.view.id).await.unwrap();
|
||||||
let editor = match document_editor.as_any().downcast_ref::<Arc<OldDocumentEditor>>() {
|
let editor = match document_editor.as_any().downcast_ref::<Arc<DeltaDocumentEditor>>() {
|
||||||
None => panic!(),
|
None => panic!(),
|
||||||
Some(editor) => editor.clone(),
|
Some(editor) => editor.clone(),
|
||||||
};
|
};
|
||||||
|
@ -174,7 +174,7 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
|||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
let editor = manager.open_document_editor(view_id).await?;
|
let editor = manager.open_document_editor(view_id).await?;
|
||||||
let delta_bytes = Bytes::from(editor.get_operations_str().await?);
|
let delta_bytes = Bytes::from(editor.export().await?);
|
||||||
Ok(delta_bytes)
|
Ok(delta_bytes)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// The path of Node A-1 will be [0,0]
|
/// The path of Node A-1 will be [0,0]
|
||||||
/// The path of Node A-2 will be [0,1]
|
/// The path of Node A-2 will be [0,1]
|
||||||
/// The path of Node B-2 will be [1,1]
|
/// The path of Node B-2 will be [1,1]
|
||||||
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)]
|
#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug, Default)]
|
||||||
pub struct Path(pub Vec<usize>);
|
pub struct Path(pub Vec<usize>);
|
||||||
|
|
||||||
impl Path {
|
impl Path {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::{Changeset, NodeOperations};
|
use super::{Changeset, NodeOperations};
|
||||||
use crate::core::attributes::AttributeHashMap;
|
use crate::core::attributes::AttributeHashMap;
|
||||||
use crate::core::{Interval, NodeData, NodeOperation, NodeTree, Path};
|
use crate::core::{NodeData, NodeOperation, NodeTree, Path};
|
||||||
use crate::errors::OTError;
|
use crate::errors::OTError;
|
||||||
|
|
||||||
use indextree::NodeId;
|
use indextree::NodeId;
|
||||||
@ -93,8 +93,8 @@ impl std::ops::DerefMut for Transaction {
|
|||||||
pub enum Extension {
|
pub enum Extension {
|
||||||
Empty,
|
Empty,
|
||||||
TextSelection {
|
TextSelection {
|
||||||
before_selection: Interval,
|
before_selection: Selection,
|
||||||
after_selection: Interval,
|
after_selection: Selection,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +110,18 @@ impl Extension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Selection {
|
||||||
|
start: Position,
|
||||||
|
end: Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Position {
|
||||||
|
path: Path,
|
||||||
|
offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TransactionBuilder<'a> {
|
pub struct TransactionBuilder<'a> {
|
||||||
node_tree: &'a NodeTree,
|
node_tree: &'a NodeTree,
|
||||||
operations: NodeOperations,
|
operations: NodeOperations,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use lib_ot::core::{
|
use lib_ot::core::{
|
||||||
AttributeBuilder, Changeset, Extension, Interval, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path,
|
AttributeBuilder, Changeset, Extension, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path, Selection,
|
||||||
Transaction,
|
Transaction,
|
||||||
};
|
};
|
||||||
use lib_ot::text_delta::TextOperationBuilder;
|
use lib_ot::text_delta::TextOperationBuilder;
|
||||||
@ -78,19 +78,20 @@ fn transaction_serialize_test() {
|
|||||||
};
|
};
|
||||||
let mut transaction = Transaction::from_operations(vec![insert]);
|
let mut transaction = Transaction::from_operations(vec![insert]);
|
||||||
transaction.extension = Extension::TextSelection {
|
transaction.extension = Extension::TextSelection {
|
||||||
before_selection: Interval::new(0, 1),
|
before_selection: Selection::default(),
|
||||||
after_selection: Interval::new(1, 2),
|
after_selection: Selection::default(),
|
||||||
};
|
};
|
||||||
let json = serde_json::to_string(&transaction).unwrap();
|
let json = serde_json::to_string(&transaction).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json,
|
json,
|
||||||
r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":0,"end":1},"after_selection":{"start":1,"end":2}}}"#
|
r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}},"after_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}}}}"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_deserialize_test() {
|
fn transaction_deserialize_test() {
|
||||||
let json = r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":0,"end":1},"after_selection":{"start":1,"end":2}}}"#;
|
let json = r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}},"after_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}}}}"#;
|
||||||
|
|
||||||
let transaction: Transaction = serde_json::from_str(json).unwrap();
|
let transaction: Transaction = serde_json::from_str(json).unwrap();
|
||||||
assert_eq!(transaction.operations.len(), 1);
|
assert_eq!(transaction.operations.len(), 1);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user