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:
parent
f1a5726fcb
commit
7dbd9fe8cd
@ -2,7 +2,9 @@ use bytes::Bytes;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_revision::{RevisionObjectDeserializer, RevisionObjectSerializer};
|
||||
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;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -46,8 +48,8 @@ pub fn initial_document_content() -> String {
|
||||
nodes: vec![editor_node],
|
||||
};
|
||||
let extension = Extension::TextSelection {
|
||||
before_selection: Interval::default(),
|
||||
after_selection: Interval::default(),
|
||||
before_selection: Selection::default(),
|
||||
after_selection: Selection::default(),
|
||||
};
|
||||
let transaction = Transaction {
|
||||
operations: vec![node_operation].into(),
|
||||
|
@ -1,6 +1,11 @@
|
||||
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 serde::de::{self, MapAccess, Visitor};
|
||||
use serde::ser::{SerializeMap, SerializeSeq};
|
||||
@ -67,8 +72,157 @@ impl<'de> Deserialize<'de> for Document {
|
||||
#[derive(Debug)]
|
||||
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)]
|
||||
pub struct DocumentNodeData {
|
||||
pub struct DocumentNode {
|
||||
#[serde(rename = "type")]
|
||||
pub node_type: String,
|
||||
|
||||
@ -81,21 +235,32 @@ pub struct DocumentNodeData {
|
||||
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[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 {
|
||||
let delta = if let Body::Delta(operations) = node_data.body {
|
||||
operations
|
||||
} else {
|
||||
TextOperations::default()
|
||||
};
|
||||
DocumentNodeData {
|
||||
DocumentNode {
|
||||
node_type: node_data.node_type,
|
||||
attributes: node_data.attributes,
|
||||
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();
|
||||
|
||||
// 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);
|
||||
if children.len() == 1 {
|
||||
@ -133,6 +298,40 @@ impl<'a> Serialize for DocumentContentSerializer<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
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]
|
||||
fn document_serde_test() {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::editor::document::{Document, DocumentRevisionSerde};
|
||||
use crate::editor::document_serde::DocumentTransaction;
|
||||
use crate::editor::queue::{Command, CommandSender, DocumentQueue};
|
||||
use crate::{DocumentEditor, DocumentUser};
|
||||
use bytes::Bytes;
|
||||
@ -66,25 +67,27 @@ fn spawn_edit_queue(
|
||||
}
|
||||
|
||||
impl DocumentEditor for Arc<AppFlowyDocumentEditor> {
|
||||
fn get_operations_str(&self) -> FutureResult<String, FlowyError> {
|
||||
todo!()
|
||||
fn export(&self) -> FutureResult<String, FlowyError> {
|
||||
let this = self.clone();
|
||||
FutureResult::new(async move { this.get_content(false).await })
|
||||
}
|
||||
|
||||
fn compose_local_operations(&self, _data: Bytes) -> FutureResult<(), FlowyError> {
|
||||
todo!()
|
||||
fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> {
|
||||
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) {
|
||||
todo!()
|
||||
}
|
||||
fn close(&self) {}
|
||||
|
||||
fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FutureResult<(), FlowyError> {
|
||||
todo!()
|
||||
FutureResult::new(async move { Ok(()) })
|
||||
}
|
||||
|
||||
fn receive_ws_state(&self, _state: &WSConnectState) {
|
||||
todo!()
|
||||
}
|
||||
fn receive_ws_state(&self, _state: &WSConnectState) {}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
|
@ -12,7 +12,7 @@ pub(crate) async fn get_document_handler(
|
||||
) -> DataResult<DocumentSnapshotPB, FlowyError> {
|
||||
let document_id: DocumentIdPB = data.into_inner();
|
||||
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 {
|
||||
doc_id: document_id.into(),
|
||||
snapshot: operations_str,
|
||||
@ -35,9 +35,9 @@ pub(crate) async fn export_handler(
|
||||
) -> DataResult<ExportDataPB, FlowyError> {
|
||||
let params: ExportParams = data.into_inner().try_into()?;
|
||||
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: operations_str,
|
||||
data: document_data,
|
||||
export_type: params.export_type,
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::editor::{initial_document_content, AppFlowyDocumentEditor};
|
||||
use crate::entities::EditParams;
|
||||
use crate::old_editor::editor::{DocumentRevisionCompress, OldDocumentEditor};
|
||||
use crate::old_editor::editor::{DeltaDocumentEditor, DocumentRevisionCompress};
|
||||
use crate::{errors::FlowyError, DocumentCloudService};
|
||||
use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
@ -30,7 +30,7 @@ pub trait DocumentUser: 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 close(&self);
|
||||
|
||||
@ -110,7 +110,7 @@ impl DocumentManager {
|
||||
let _ = editor
|
||||
.compose_local_operations(Bytes::from(payload.operations_str))
|
||||
.await?;
|
||||
let operations_str = editor.get_operations_str().await?;
|
||||
let operations_str = editor.export().await?;
|
||||
Ok(DocumentOperationsPB {
|
||||
doc_id: payload.doc_id.clone(),
|
||||
operations_str,
|
||||
@ -198,7 +198,7 @@ impl DocumentManager {
|
||||
Arc::new(editor)
|
||||
} else {
|
||||
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)
|
||||
};
|
||||
self.editor_map.insert(doc_id, editor.clone());
|
||||
|
@ -25,7 +25,7 @@ use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
pub struct OldDocumentEditor {
|
||||
pub struct DeltaDocumentEditor {
|
||||
pub doc_id: String,
|
||||
#[allow(dead_code)]
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
@ -34,7 +34,7 @@ pub struct OldDocumentEditor {
|
||||
edit_cmd_tx: EditorCommandSender,
|
||||
}
|
||||
|
||||
impl OldDocumentEditor {
|
||||
impl DeltaDocumentEditor {
|
||||
#[allow(unused_variables)]
|
||||
pub(crate) async fn new(
|
||||
doc_id: &str,
|
||||
@ -146,8 +146,8 @@ impl OldDocumentEditor {
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentEditor for Arc<OldDocumentEditor> {
|
||||
fn get_operations_str(&self) -> FutureResult<String, FlowyError> {
|
||||
impl DocumentEditor for Arc<DeltaDocumentEditor> {
|
||||
fn export(&self) -> FutureResult<String, FlowyError> {
|
||||
let (ret, rx) = oneshot::channel::<CollaborateResult<String>>();
|
||||
let msg = EditorCommand::GetOperationsString { ret };
|
||||
let edit_cmd_tx = self.edit_cmd_tx.clone();
|
||||
@ -197,7 +197,7 @@ impl DocumentEditor for Arc<OldDocumentEditor> {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl std::ops::Drop for OldDocumentEditor {
|
||||
impl std::ops::Drop for DeltaDocumentEditor {
|
||||
fn drop(&mut self) {
|
||||
tracing::trace!("{} DocumentEditor was dropped", self.doc_id)
|
||||
}
|
||||
@ -225,7 +225,7 @@ fn spawn_edit_queue(
|
||||
}
|
||||
|
||||
#[cfg(feature = "flowy_unit_test")]
|
||||
impl OldDocumentEditor {
|
||||
impl DeltaDocumentEditor {
|
||||
pub async fn document_operations(&self) -> FlowyResult<TextOperations> {
|
||||
let (ret, rx) = oneshot::channel::<CollaborateResult<TextOperations>>();
|
||||
let msg = EditorCommand::GetOperations { ret };
|
||||
|
@ -14,7 +14,7 @@ async fn text_block_sync_current_rev_id_check() {
|
||||
AssertNextSyncRevId(None),
|
||||
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||
];
|
||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -28,7 +28,7 @@ async fn text_block_sync_state_check() {
|
||||
AssertRevisionState(3, RevisionState::Ack),
|
||||
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||
];
|
||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -40,7 +40,7 @@ async fn text_block_sync_insert_test() {
|
||||
AssertJson(r#"[{"insert":"123\n"}]"#),
|
||||
AssertNextSyncRevId(None),
|
||||
];
|
||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -52,7 +52,7 @@ async fn text_block_sync_insert_in_chinese() {
|
||||
InsertText("好", offset),
|
||||
AssertJson(r#"[{"insert":"你好\n"}]"#),
|
||||
];
|
||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -64,7 +64,7 @@ async fn text_block_sync_insert_with_emoji() {
|
||||
InsertText("☺️", offset),
|
||||
AssertJson(r#"[{"insert":"😁☺️\n"}]"#),
|
||||
];
|
||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -76,7 +76,7 @@ async fn text_block_sync_delete_in_english() {
|
||||
Delete(Interval::new(0, 2)),
|
||||
AssertJson(r#"[{"insert":"3\n"}]"#),
|
||||
];
|
||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -89,7 +89,7 @@ async fn text_block_sync_delete_in_chinese() {
|
||||
Delete(Interval::new(0, offset)),
|
||||
AssertJson(r#"[{"insert":"好\n"}]"#),
|
||||
];
|
||||
OldDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
DeltaDocumentEditorTest::new().await.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@ -101,5 +101,5 @@ async fn text_block_sync_replace_test() {
|
||||
Replace(Interval::new(0, 3), "abc"),
|
||||
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_revision::disk::RevisionState;
|
||||
use flowy_test::{helper::ViewTest, FlowySDKTest};
|
||||
@ -17,18 +17,18 @@ pub enum EditorScript {
|
||||
AssertJson(&'static str),
|
||||
}
|
||||
|
||||
pub struct OldDocumentEditorTest {
|
||||
pub struct DeltaDocumentEditorTest {
|
||||
pub sdk: FlowySDKTest,
|
||||
pub editor: Arc<OldDocumentEditor>,
|
||||
pub editor: Arc<DeltaDocumentEditor>,
|
||||
}
|
||||
|
||||
impl OldDocumentEditorTest {
|
||||
impl DeltaDocumentEditorTest {
|
||||
pub async fn new() -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let _ = sdk.init_user().await;
|
||||
let test = ViewTest::new_document_view(&sdk).await;
|
||||
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!(),
|
||||
Some(editor) => editor.clone(),
|
||||
};
|
||||
|
@ -174,7 +174,7 @@ impl ViewDataProcessor for DocumentViewDataProcessor {
|
||||
let manager = self.0.clone();
|
||||
FutureResult::new(async move {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ use serde::{Deserialize, Serialize};
|
||||
/// The path of Node A-1 will be [0,0]
|
||||
/// The path of Node A-2 will be [0,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>);
|
||||
|
||||
impl Path {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::{Changeset, NodeOperations};
|
||||
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 indextree::NodeId;
|
||||
@ -93,8 +93,8 @@ impl std::ops::DerefMut for Transaction {
|
||||
pub enum Extension {
|
||||
Empty,
|
||||
TextSelection {
|
||||
before_selection: Interval,
|
||||
after_selection: Interval,
|
||||
before_selection: Selection,
|
||||
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> {
|
||||
node_tree: &'a NodeTree,
|
||||
operations: NodeOperations,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use lib_ot::core::{
|
||||
AttributeBuilder, Changeset, Extension, Interval, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path,
|
||||
AttributeBuilder, Changeset, Extension, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path, Selection,
|
||||
Transaction,
|
||||
};
|
||||
use lib_ot::text_delta::TextOperationBuilder;
|
||||
@ -78,19 +78,20 @@ fn transaction_serialize_test() {
|
||||
};
|
||||
let mut transaction = Transaction::from_operations(vec![insert]);
|
||||
transaction.extension = Extension::TextSelection {
|
||||
before_selection: Interval::new(0, 1),
|
||||
after_selection: Interval::new(1, 2),
|
||||
before_selection: Selection::default(),
|
||||
after_selection: Selection::default(),
|
||||
};
|
||||
let json = serde_json::to_string(&transaction).unwrap();
|
||||
assert_eq!(
|
||||
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]
|
||||
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();
|
||||
assert_eq!(transaction.operations.len(), 1);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user