appflowy преди 3 години
родител
ревизия
8a9a23ddbe
променени са 44 файла, в които са добавени 296 реда и са изтрити 280 реда
  1. 1 1
      backend/src/services/doc/edit/editor.rs
  2. 1 1
      backend/tests/document/edit.rs
  3. 2 1
      backend/tests/document/helper.rs
  4. 4 1
      frontend/rust-lib/flowy-document/src/services/doc/edit/editor.rs
  5. 4 1
      frontend/rust-lib/flowy-document/src/services/doc/edit/queue.rs
  6. 1 1
      frontend/rust-lib/flowy-document/src/services/doc/revision/manager.rs
  7. 4 1
      frontend/rust-lib/flowy-document/src/services/doc/revision/persistence.rs
  8. 2 1
      frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs
  9. 4 1
      frontend/rust-lib/flowy-document/tests/editor/mod.rs
  10. 4 1
      frontend/rust-lib/flowy-document/tests/editor/op_test.rs
  11. 4 1
      frontend/rust-lib/flowy-document/tests/editor/serde_test.rs
  12. 4 1
      shared-lib/flowy-document-infra/src/core/document.rs
  13. 4 1
      shared-lib/flowy-document-infra/src/core/extensions/delete/default_delete.rs
  14. 3 9
      shared-lib/flowy-document-infra/src/core/extensions/delete/preserve_line_format_merge.rs
  15. 4 1
      shared-lib/flowy-document-infra/src/core/extensions/format/helper.rs
  16. 3 8
      shared-lib/flowy-document-infra/src/core/extensions/format/resolve_block_format.rs
  17. 4 1
      shared-lib/flowy-document-infra/src/core/extensions/format/resolve_inline_format.rs
  18. 3 7
      shared-lib/flowy-document-infra/src/core/extensions/insert/auto_exit_block.rs
  19. 3 8
      shared-lib/flowy-document-infra/src/core/extensions/insert/auto_format.rs
  20. 3 8
      shared-lib/flowy-document-infra/src/core/extensions/insert/default_insert.rs
  21. 1 1
      shared-lib/flowy-document-infra/src/core/extensions/insert/mod.rs
  22. 10 10
      shared-lib/flowy-document-infra/src/core/extensions/insert/preserve_block_format.rs
  23. 3 8
      shared-lib/flowy-document-infra/src/core/extensions/insert/preserve_inline_format.rs
  24. 3 8
      shared-lib/flowy-document-infra/src/core/extensions/insert/reset_format_on_new_line.rs
  25. 4 1
      shared-lib/flowy-document-infra/src/core/extensions/mod.rs
  26. 1 1
      shared-lib/flowy-document-infra/src/core/history.rs
  27. 2 1
      shared-lib/flowy-document-infra/src/core/view.rs
  28. 1 1
      shared-lib/flowy-document-infra/src/entities/doc/doc.rs
  29. 1 1
      shared-lib/flowy-document-infra/src/entities/doc/revision.rs
  30. 1 1
      shared-lib/flowy-document-infra/src/user_default.rs
  31. 0 170
      shared-lib/lib-ot/src/core/attributes/attributes.rs
  32. 14 7
      shared-lib/lib-ot/src/core/delta/delta.rs
  33. 4 1
      shared-lib/lib-ot/src/core/delta/iterator.rs
  34. 0 2
      shared-lib/lib-ot/src/core/mod.rs
  35. 4 1
      shared-lib/lib-ot/src/core/operation/builder.rs
  36. 4 1
      shared-lib/lib-ot/src/core/operation/operation.rs
  37. 1 0
      shared-lib/lib-ot/src/lib.rs
  38. 172 4
      shared-lib/lib-ot/src/rich_text/attributes.rs
  39. 1 2
      shared-lib/lib-ot/src/rich_text/attributes_serde.rs
  40. 2 1
      shared-lib/lib-ot/src/rich_text/builder.rs
  41. 3 0
      shared-lib/lib-ot/src/rich_text/delta.rs
  42. 0 0
      shared-lib/lib-ot/src/rich_text/macros.rs
  43. 2 3
      shared-lib/lib-ot/src/rich_text/mod.rs
  44. 0 0
      shared-lib/lib-ot/tests/main.rs

+ 1 - 1
backend/src/services/doc/edit/editor.rs

@@ -13,7 +13,7 @@ use flowy_document_infra::{
     entities::ws::{WsDataType, WsDocumentData},
     protobuf::{Doc, RevId, RevType, Revision, RevisionRange, UpdateDocParams},
 };
-use lib_ot::core::{OperationTransformable, RichTextDelta};
+use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
 use parking_lot::RwLock;
 use protobuf::Message;
 use sqlx::PgPool;

+ 1 - 1
backend/tests/document/edit.rs

@@ -1,6 +1,6 @@
 use crate::document::helper::{DocScript, DocumentTest};
 use flowy_document_infra::core::{Document, FlowyDoc};
-use lib_ot::core::{Interval, RichTextAttribute};
+use lib_ot::{core::Interval, rich_text::RichTextAttribute};
 
 #[rustfmt::skip]
 //                         ┌─────────┐       ┌─────────┐

+ 2 - 1
backend/tests/document/helper.rs

@@ -12,8 +12,9 @@ use tokio::time::{sleep, Duration};
 // use crate::helper::*;
 use crate::util::helper::{spawn_server, TestServer};
 use flowy_document_infra::{entities::doc::DocIdentifier, protobuf::UpdateDocParams};
-use lib_ot::core::{RichTextAttribute, RichTextDelta, Interval};
+use lib_ot::rich_text::{RichTextAttribute, RichTextDelta};
 use parking_lot::RwLock;
+use lib_ot::core::Interval;
 
 pub struct DocumentTest {
     server: TestServer,

+ 4 - 1
frontend/rust-lib/flowy-document/src/services/doc/edit/editor.rs

@@ -17,7 +17,10 @@ use flowy_document_infra::{
     errors::DocumentResult,
 };
 use lib_infra::retry::{ExponentialBackoff, Retry};
-use lib_ot::core::{Interval, RichTextAttribute, RichTextDelta};
+use lib_ot::{
+    core::Interval,
+    rich_text::{RichTextAttribute, RichTextDelta},
+};
 use lib_ws::WsConnectState;
 use std::{convert::TryFrom, sync::Arc};
 use tokio::sync::{mpsc, mpsc::UnboundedSender, oneshot};

+ 4 - 1
frontend/rust-lib/flowy-document/src/services/doc/edit/queue.rs

@@ -6,7 +6,10 @@ use flowy_document_infra::{
     errors::DocumentError,
 };
 use futures::stream::StreamExt;
-use lib_ot::core::{Interval, OperationTransformable, RichTextAttribute, RichTextDelta};
+use lib_ot::{
+    core::{Interval, OperationTransformable},
+    rich_text::{RichTextAttribute, RichTextDelta},
+};
 use std::{convert::TryFrom, sync::Arc};
 use tokio::sync::{mpsc, oneshot, RwLock};
 

+ 1 - 1
frontend/rust-lib/flowy-document/src/services/doc/revision/manager.rs

@@ -8,7 +8,7 @@ use flowy_document_infra::{
     util::RevIdCounter,
 };
 use lib_infra::future::ResultFuture;
-use lib_ot::core::{OperationTransformable, RichTextDelta};
+use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
 use std::sync::Arc;
 use tokio::sync::mpsc;
 

+ 4 - 1
frontend/rust-lib/flowy-document/src/services/doc/revision/persistence.rs

@@ -9,7 +9,10 @@ use flowy_database::{ConnectionPool, SqliteConnection};
 use flowy_document_infra::entities::doc::{revision_from_doc, Doc, RevId, RevType, Revision, RevisionRange};
 use futures::stream::StreamExt;
 use lib_infra::future::ResultFuture;
-use lib_ot::core::{Operation, OperationTransformable, RichTextDelta};
+use lib_ot::{
+    core::{Operation, OperationTransformable},
+    rich_text::RichTextDelta,
+};
 use std::{collections::VecDeque, sync::Arc, time::Duration};
 use tokio::{
     sync::{broadcast, mpsc, RwLock},

+ 2 - 1
frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs

@@ -1,8 +1,9 @@
 #![cfg_attr(rustfmt, rustfmt::skip)]
 use crate::editor::{TestBuilder, TestOp::*};
 use flowy_document_infra::core::{FlowyDoc, PlainDoc};
-use lib_ot::core::{Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr, RichTextDelta};
+use lib_ot::core::{Interval, OperationTransformable, NEW_LINE, WHITESPACE, FlowyStr};
 use unicode_segmentation::UnicodeSegmentation;
+use lib_ot::rich_text::RichTextDelta;
 
 #[test]
 fn attributes_bold_added() {

+ 4 - 1
frontend/rust-lib/flowy-document/tests/editor/mod.rs

@@ -6,7 +6,10 @@ mod undo_redo_test;
 
 use derive_more::Display;
 use flowy_document_infra::core::{CustomDocument, Document};
-use lib_ot::core::*;
+use lib_ot::{
+    core::*,
+    rich_text::{RichTextAttribute, RichTextAttributes, RichTextDelta},
+};
 use rand::{prelude::*, Rng as WrappedRng};
 use std::{sync::Once, time::Duration};
 

+ 4 - 1
frontend/rust-lib/flowy-document/tests/editor/op_test.rs

@@ -1,7 +1,10 @@
 #![allow(clippy::all)]
 use crate::editor::{Rng, TestBuilder, TestOp::*};
 use flowy_document_infra::core::{FlowyDoc, PlainDoc};
-use lib_ot::core::*;
+use lib_ot::{
+    core::*,
+    rich_text::{AttributeBuilder, RichTextAttribute, RichTextAttributes, RichTextDelta},
+};
 
 #[test]
 fn attributes_insert_text() {

+ 4 - 1
frontend/rust-lib/flowy-document/tests/editor/serde_test.rs

@@ -1,5 +1,8 @@
 use flowy_document_infra::core::{Document, PlainDoc};
-use lib_ot::core::*;
+use lib_ot::{
+    core::*,
+    rich_text::{AttributeBuilder, RichTextAttribute, RichTextAttributeValue, RichTextDelta},
+};
 
 #[test]
 fn operation_insert_serialize_test() {

+ 4 - 1
shared-lib/flowy-document-infra/src/core/document.rs

@@ -6,7 +6,10 @@ use crate::{
     errors::DocumentError,
     user_default::doc_initial_delta,
 };
-use lib_ot::core::*;
+use lib_ot::{
+    core::*,
+    rich_text::{RichTextAttribute, RichTextDelta},
+};
 use tokio::sync::mpsc;
 
 pub trait CustomDocument {

+ 4 - 1
shared-lib/flowy-document-infra/src/core/extensions/delete/default_delete.rs

@@ -1,5 +1,8 @@
 use crate::core::extensions::DeleteExt;
-use lib_ot::core::{DeltaBuilder, Interval, RichTextDelta};
+use lib_ot::{
+    core::{DeltaBuilder, Interval},
+    rich_text::RichTextDelta,
+};
 
 pub struct DefaultDelete {}
 impl DeleteExt for DefaultDelete {

+ 3 - 9
shared-lib/flowy-document-infra/src/core/extensions/delete/preserve_line_format_merge.rs

@@ -1,13 +1,7 @@
 use crate::{core::extensions::DeleteExt, util::is_newline};
-use lib_ot::core::{
-    plain_attributes,
-    Attributes,
-    CharMetric,
-    DeltaBuilder,
-    DeltaIter,
-    Interval,
-    RichTextDelta,
-    NEW_LINE,
+use lib_ot::{
+    core::{Attributes, CharMetric, DeltaBuilder, DeltaIter, Interval, NEW_LINE},
+    rich_text::{plain_attributes, RichTextDelta},
 };
 
 pub struct PreserveLineFormatOnMerge {}

+ 4 - 1
shared-lib/flowy-document-infra/src/core/extensions/format/helper.rs

@@ -1,5 +1,8 @@
 use crate::util::find_newline;
-use lib_ot::core::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta, RichTextOperation};
+use lib_ot::{
+    core::RichTextOperation,
+    rich_text::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta},
+};
 
 pub(crate) fn line_break(
     op: &RichTextOperation,

+ 3 - 8
shared-lib/flowy-document-infra/src/core/extensions/format/resolve_block_format.rs

@@ -2,14 +2,9 @@ use crate::{
     core::extensions::{format::helper::line_break, FormatExt},
     util::find_newline,
 };
-use lib_ot::core::{
-    plain_attributes,
-    AttributeScope,
-    DeltaBuilder,
-    DeltaIter,
-    Interval,
-    RichTextAttribute,
-    RichTextDelta,
+use lib_ot::{
+    core::{DeltaBuilder, DeltaIter, Interval},
+    rich_text::{plain_attributes, AttributeScope, RichTextAttribute, RichTextDelta},
 };
 
 pub struct ResolveBlockFormat {}

+ 4 - 1
shared-lib/flowy-document-infra/src/core/extensions/format/resolve_inline_format.rs

@@ -2,7 +2,10 @@ use crate::{
     core::extensions::{format::helper::line_break, FormatExt},
     util::find_newline,
 };
-use lib_ot::core::{AttributeScope, DeltaBuilder, DeltaIter, Interval, RichTextAttribute, RichTextDelta};
+use lib_ot::{
+    core::{DeltaBuilder, DeltaIter, Interval},
+    rich_text::{AttributeScope, RichTextAttribute, RichTextDelta},
+};
 
 pub struct ResolveInlineFormat {}
 impl FormatExt for ResolveInlineFormat {

+ 3 - 7
shared-lib/flowy-document-infra/src/core/extensions/insert/auto_exit_block.rs

@@ -1,11 +1,7 @@
 use crate::{core::extensions::InsertExt, util::is_newline};
-use lib_ot::core::{
-    attributes_except_header,
-    is_empty_line_at_index,
-    DeltaBuilder,
-    DeltaIter,
-    RichTextAttributeKey,
-    RichTextDelta,
+use lib_ot::{
+    core::{is_empty_line_at_index, DeltaBuilder, DeltaIter},
+    rich_text::{attributes_except_header, RichTextAttributeKey, RichTextDelta},
 };
 
 pub struct AutoExitBlock {}

+ 3 - 8
shared-lib/flowy-document-infra/src/core/extensions/insert/auto_format.rs

@@ -1,12 +1,7 @@
 use crate::{core::extensions::InsertExt, util::is_whitespace};
-use lib_ot::core::{
-    count_utf16_code_units,
-    plain_attributes,
-    DeltaBuilder,
-    DeltaIter,
-    RichTextAttribute,
-    RichTextAttributes,
-    RichTextDelta,
+use lib_ot::{
+    core::{count_utf16_code_units, DeltaBuilder, DeltaIter},
+    rich_text::{plain_attributes, RichTextAttribute, RichTextAttributes, RichTextDelta},
 };
 use std::cmp::min;
 use url::Url;

+ 3 - 8
shared-lib/flowy-document-infra/src/core/extensions/insert/default_insert.rs

@@ -1,12 +1,7 @@
 use crate::core::extensions::InsertExt;
-use lib_ot::core::{
-    Attributes,
-    DeltaBuilder,
-    DeltaIter,
-    RichTextAttributeKey,
-    RichTextAttributes,
-    RichTextDelta,
-    NEW_LINE,
+use lib_ot::{
+    core::{Attributes, DeltaBuilder, DeltaIter, NEW_LINE},
+    rich_text::{RichTextAttributeKey, RichTextAttributes, RichTextDelta},
 };
 
 pub struct DefaultInsertAttribute {}

+ 1 - 1
shared-lib/flowy-document-infra/src/core/extensions/insert/mod.rs

@@ -2,7 +2,7 @@ use crate::core::extensions::InsertExt;
 pub use auto_exit_block::*;
 pub use auto_format::*;
 pub use default_insert::*;
-use lib_ot::core::RichTextDelta;
+use lib_ot::rich_text::RichTextDelta;
 pub use preserve_block_format::*;
 pub use preserve_inline_format::*;
 pub use reset_format_on_new_line::*;

+ 10 - 10
shared-lib/flowy-document-infra/src/core/extensions/insert/preserve_block_format.rs

@@ -1,14 +1,14 @@
 use crate::{core::extensions::InsertExt, util::is_newline};
-use lib_ot::core::{
-    attributes_except_header,
-    plain_attributes,
-    DeltaBuilder,
-    DeltaIter,
-    RichTextAttribute,
-    RichTextAttributeKey,
-    RichTextAttributes,
-    RichTextDelta,
-    NEW_LINE,
+use lib_ot::{
+    core::{DeltaBuilder, DeltaIter, NEW_LINE},
+    rich_text::{
+        attributes_except_header,
+        plain_attributes,
+        RichTextAttribute,
+        RichTextAttributeKey,
+        RichTextAttributes,
+        RichTextDelta,
+    },
 };
 
 pub struct PreserveBlockFormatOnInsert {}

+ 3 - 8
shared-lib/flowy-document-infra/src/core/extensions/insert/preserve_inline_format.rs

@@ -2,14 +2,9 @@ use crate::{
     core::extensions::InsertExt,
     util::{contain_newline, is_newline},
 };
-use lib_ot::core::{
-    plain_attributes,
-    DeltaBuilder,
-    DeltaIter,
-    OpNewline,
-    RichTextAttributeKey,
-    RichTextDelta,
-    NEW_LINE,
+use lib_ot::{
+    core::{DeltaBuilder, DeltaIter, OpNewline, NEW_LINE},
+    rich_text::{plain_attributes, RichTextAttributeKey, RichTextDelta},
 };
 
 pub struct PreserveInlineFormat {}

+ 3 - 8
shared-lib/flowy-document-infra/src/core/extensions/insert/reset_format_on_new_line.rs

@@ -1,12 +1,7 @@
 use crate::{core::extensions::InsertExt, util::is_newline};
-use lib_ot::core::{
-    CharMetric,
-    DeltaBuilder,
-    DeltaIter,
-    RichTextAttributeKey,
-    RichTextAttributes,
-    RichTextDelta,
-    NEW_LINE,
+use lib_ot::{
+    core::{CharMetric, DeltaBuilder, DeltaIter, NEW_LINE},
+    rich_text::{RichTextAttributeKey, RichTextAttributes, RichTextDelta},
 };
 
 pub struct ResetLineFormatOnNewLine {}

+ 4 - 1
shared-lib/flowy-document-infra/src/core/extensions/mod.rs

@@ -2,7 +2,10 @@ pub use delete::*;
 pub use format::*;
 pub use insert::*;
 
-use lib_ot::core::{Interval, RichTextAttribute, RichTextDelta};
+use lib_ot::{
+    core::Interval,
+    rich_text::{RichTextAttribute, RichTextDelta},
+};
 
 mod delete;
 mod format;

+ 1 - 1
shared-lib/flowy-document-infra/src/core/history.rs

@@ -1,4 +1,4 @@
-use lib_ot::core::RichTextDelta;
+use lib_ot::rich_text::RichTextDelta;
 
 const MAX_UNDOS: usize = 20;
 

+ 2 - 1
shared-lib/flowy-document-infra/src/core/view.rs

@@ -1,7 +1,8 @@
 use crate::core::extensions::*;
 use lib_ot::{
-    core::{trim, Interval, RichTextAttribute, RichTextDelta},
+    core::{trim, Interval},
     errors::{ErrorBuilder, OTError, OTErrorCode},
+    rich_text::{RichTextAttribute, RichTextDelta},
 };
 
 pub const RECORD_THRESHOLD: usize = 400; // in milliseconds

+ 1 - 1
shared-lib/flowy-document-infra/src/entities/doc/doc.rs

@@ -1,5 +1,5 @@
 use flowy_derive::ProtoBuf;
-use lib_ot::{core::RichTextDelta, errors::OTError};
+use lib_ot::{errors::OTError, rich_text::RichTextDelta};
 
 #[derive(ProtoBuf, Default, Debug, Clone)]
 pub struct CreateDocParams {

+ 1 - 1
shared-lib/flowy-document-infra/src/entities/doc/revision.rs

@@ -1,6 +1,6 @@
 use crate::{entities::doc::Doc, util::md5};
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use lib_ot::core::RichTextDelta;
+use lib_ot::rich_text::RichTextDelta;
 use std::{fmt::Formatter, ops::RangeInclusive};
 
 #[derive(Debug, ProtoBuf_Enum, Clone, Eq, PartialEq)]

+ 1 - 1
shared-lib/flowy-document-infra/src/user_default.rs

@@ -1,4 +1,4 @@
-use lib_ot::core::{DeltaBuilder, RichTextDelta};
+use lib_ot::{core::DeltaBuilder, rich_text::RichTextDelta};
 
 #[inline]
 pub fn doc_initial_delta() -> RichTextDelta { DeltaBuilder::new().insert("\n").build() }

+ 0 - 170
shared-lib/lib-ot/src/core/attributes/attributes.rs

@@ -1,170 +0,0 @@
-use crate::{
-    core::{
-        Attributes,
-        OperationTransformable,
-        RichTextAttribute,
-        RichTextAttributeKey,
-        RichTextAttributeValue,
-        RichTextOperation,
-    },
-    errors::OTError,
-};
-use std::{collections::HashMap, fmt};
-
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct RichTextAttributes {
-    pub(crate) inner: HashMap<RichTextAttributeKey, RichTextAttributeValue>,
-}
-
-impl std::default::Default for RichTextAttributes {
-    fn default() -> Self {
-        Self {
-            inner: HashMap::with_capacity(0),
-        }
-    }
-}
-
-impl fmt::Display for RichTextAttributes {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("{:?}", self.inner)) }
-}
-
-pub fn plain_attributes() -> RichTextAttributes { RichTextAttributes::default() }
-
-impl RichTextAttributes {
-    pub fn new() -> Self { RichTextAttributes { inner: HashMap::new() } }
-
-    pub fn is_empty(&self) -> bool { self.inner.is_empty() }
-
-    pub fn add(&mut self, attribute: RichTextAttribute) {
-        let RichTextAttribute { key, value, scope: _ } = attribute;
-        self.inner.insert(key, value);
-    }
-
-    pub fn add_kv(&mut self, key: RichTextAttributeKey, value: RichTextAttributeValue) {
-        self.inner.insert(key, value);
-    }
-
-    pub fn delete(&mut self, key: &RichTextAttributeKey) {
-        self.inner.insert(key.clone(), RichTextAttributeValue(None));
-    }
-
-    pub fn mark_all_as_removed_except(&mut self, attribute: Option<RichTextAttributeKey>) {
-        match attribute {
-            None => {
-                self.inner.iter_mut().for_each(|(_k, v)| v.0 = None);
-            },
-            Some(attribute) => {
-                self.inner.iter_mut().for_each(|(k, v)| {
-                    if k != &attribute {
-                        v.0 = None;
-                    }
-                });
-            },
-        }
-    }
-
-    pub fn remove(&mut self, key: RichTextAttributeKey) { self.inner.retain(|k, _| k != &key); }
-
-    // pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes
-    // {     let mut new_attributes = Attributes::new();
-    //     attributes.iter().for_each(|(k, v)| {
-    //         if k != &AttributeKey::Header {
-    //             new_attributes.insert(k.clone(), v.clone());
-    //         }
-    //     });
-    //
-    //     new_attributes
-    // }
-
-    // Update inner by constructing new attributes from the other if it's
-    // not None and replace the key/value with self key/value.
-    pub fn merge(&mut self, other: Option<RichTextAttributes>) {
-        if other.is_none() {
-            return;
-        }
-
-        let mut new_attributes = other.unwrap().inner;
-        self.inner.iter().for_each(|(k, v)| {
-            new_attributes.insert(k.clone(), v.clone());
-        });
-        self.inner = new_attributes;
-    }
-}
-
-impl Attributes for RichTextAttributes {
-    fn is_empty(&self) -> bool { self.inner.is_empty() }
-
-    fn remove_empty(&mut self) { self.inner.retain(|_, v| v.0.is_some()); }
-
-    fn extend_other(&mut self, other: Self) { self.inner.extend(other.inner); }
-}
-
-impl OperationTransformable for RichTextAttributes {
-    fn compose(&self, other: &Self) -> Result<Self, OTError>
-    where
-        Self: Sized,
-    {
-        let mut attributes = self.clone();
-        attributes.extend_other(other.clone());
-        Ok(attributes)
-    }
-
-    fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
-    where
-        Self: Sized,
-    {
-        let a = self
-            .iter()
-            .fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
-                if !other.contains_key(k) {
-                    new_attributes.insert(k.clone(), v.clone());
-                }
-                new_attributes
-            });
-
-        let b = other
-            .iter()
-            .fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
-                if !self.contains_key(k) {
-                    new_attributes.insert(k.clone(), v.clone());
-                }
-                new_attributes
-            });
-
-        Ok((a, b))
-    }
-
-    fn invert(&self, other: &Self) -> Self {
-        let base_inverted = other.iter().fold(RichTextAttributes::new(), |mut attributes, (k, v)| {
-            if other.get(k) != self.get(k) && self.contains_key(k) {
-                attributes.insert(k.clone(), v.clone());
-            }
-            attributes
-        });
-
-        let inverted = self.iter().fold(base_inverted, |mut attributes, (k, _)| {
-            if other.get(k) != self.get(k) && !other.contains_key(k) {
-                attributes.delete(k);
-            }
-            attributes
-        });
-
-        inverted
-    }
-}
-
-impl std::ops::Deref for RichTextAttributes {
-    type Target = HashMap<RichTextAttributeKey, RichTextAttributeValue>;
-
-    fn deref(&self) -> &Self::Target { &self.inner }
-}
-
-impl std::ops::DerefMut for RichTextAttributes {
-    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
-}
-
-pub fn attributes_except_header(op: &RichTextOperation) -> RichTextAttributes {
-    let mut attributes = op.get_attributes();
-    attributes.remove(RichTextAttributeKey::Header);
-    attributes
-}

+ 14 - 7
shared-lib/lib-ot/src/core/delta/delta.rs

@@ -1,5 +1,5 @@
 use crate::{
-    core::{attributes::*, operation::*, DeltaIter, FlowyStr, Interval, OperationTransformable, MAX_IV_LEN},
+    core::{operation::*, DeltaIter, FlowyStr, Interval, OperationTransformable, MAX_IV_LEN},
     errors::{ErrorBuilder, OTError, OTErrorCode},
 };
 use bytes::Bytes;
@@ -469,8 +469,6 @@ fn transform_op_attribute<T: Attributes>(left: &Option<Operation<T>>, right: &Op
     left.transform(&right).unwrap().0
 }
 
-pub type RichTextDelta = Delta<RichTextAttributes>;
-
 impl<T> Delta<T>
 where
     T: Attributes + DeserializeOwned,
@@ -503,22 +501,31 @@ where
     }
 }
 
-impl FromStr for RichTextDelta {
+impl<T> FromStr for Delta<T>
+where
+    T: Attributes,
+{
     type Err = ();
 
-    fn from_str(s: &str) -> Result<RichTextDelta, Self::Err> {
+    fn from_str(s: &str) -> Result<Delta<T>, Self::Err> {
         let mut delta = Delta::with_capacity(1);
         delta.add(Operation::Insert(s.into()));
         Ok(delta)
     }
 }
 
-impl std::convert::TryFrom<Vec<u8>> for RichTextDelta {
+impl<T> std::convert::TryFrom<Vec<u8>> for Delta<T>
+where
+    T: Attributes + DeserializeOwned,
+{
     type Error = OTError;
     fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> { Delta::from_bytes(bytes) }
 }
 
-impl std::convert::TryFrom<Bytes> for RichTextDelta {
+impl<T> std::convert::TryFrom<Bytes> for Delta<T>
+where
+    T: Attributes + DeserializeOwned,
+{
     type Error = OTError;
 
     fn try_from(bytes: Bytes) -> Result<Self, Self::Error> { Delta::from_bytes(&bytes) }

+ 4 - 1
shared-lib/lib-ot/src/core/delta/iterator.rs

@@ -1,5 +1,8 @@
 use super::cursor::*;
-use crate::core::{Attributes, Delta, Interval, Operation, RichTextAttributes, NEW_LINE};
+use crate::{
+    core::{Attributes, Delta, Interval, Operation, NEW_LINE},
+    rich_text::RichTextAttributes,
+};
 use std::ops::{Deref, DerefMut};
 
 pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize;

+ 0 - 2
shared-lib/lib-ot/src/core/mod.rs

@@ -1,11 +1,9 @@
-mod attributes;
 mod delta;
 mod flowy_str;
 mod interval;
 mod operation;
 
 use crate::errors::OTError;
-pub use attributes::*;
 pub use delta::*;
 pub use flowy_str::*;
 pub use interval::*;

+ 4 - 1
shared-lib/lib-ot/src/core/operation/builder.rs

@@ -1,4 +1,7 @@
-use crate::core::{Attributes, Operation, RichTextAttributes};
+use crate::{
+    core::{Attributes, Operation},
+    rich_text::RichTextAttributes,
+};
 
 pub type RichTextOpBuilder = OpBuilder<RichTextAttributes>;
 

+ 4 - 1
shared-lib/lib-ot/src/core/operation/operation.rs

@@ -1,4 +1,7 @@
-use crate::core::{FlowyStr, Interval, OpBuilder, OperationTransformable, RichTextAttribute, RichTextAttributes};
+use crate::{
+    core::{FlowyStr, Interval, OpBuilder, OperationTransformable},
+    rich_text::{RichTextAttribute, RichTextAttributes},
+};
 use serde::__private::Formatter;
 use std::{
     cmp::min,

+ 1 - 0
shared-lib/lib-ot/src/lib.rs

@@ -1,2 +1,3 @@
 pub mod core;
 pub mod errors;
+pub mod rich_text;

+ 172 - 4
shared-lib/lib-ot/src/core/attributes/attribute.rs → shared-lib/lib-ot/src/rich_text/attributes.rs

@@ -1,11 +1,179 @@
 #![allow(non_snake_case)]
-
-use crate::{block_attribute, core::RichTextAttributes, ignore_attribute, inline_attribute, list_attribute};
+use crate::{
+    block_attribute,
+    core::{Attributes, OperationTransformable, RichTextOperation},
+    errors::OTError,
+    ignore_attribute,
+    inline_attribute,
+    list_attribute,
+};
 use lazy_static::lazy_static;
-
-use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator};
+use std::{
+    collections::{HashMap, HashSet},
+    fmt,
+    fmt::Formatter,
+    iter::FromIterator,
+};
 use strum_macros::Display;
 
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct RichTextAttributes {
+    pub(crate) inner: HashMap<RichTextAttributeKey, RichTextAttributeValue>,
+}
+
+impl std::default::Default for RichTextAttributes {
+    fn default() -> Self {
+        Self {
+            inner: HashMap::with_capacity(0),
+        }
+    }
+}
+
+impl fmt::Display for RichTextAttributes {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_fmt(format_args!("{:?}", self.inner)) }
+}
+
+pub fn plain_attributes() -> RichTextAttributes { RichTextAttributes::default() }
+
+impl RichTextAttributes {
+    pub fn new() -> Self { RichTextAttributes { inner: HashMap::new() } }
+
+    pub fn is_empty(&self) -> bool { self.inner.is_empty() }
+
+    pub fn add(&mut self, attribute: RichTextAttribute) {
+        let RichTextAttribute { key, value, scope: _ } = attribute;
+        self.inner.insert(key, value);
+    }
+
+    pub fn add_kv(&mut self, key: RichTextAttributeKey, value: RichTextAttributeValue) {
+        self.inner.insert(key, value);
+    }
+
+    pub fn delete(&mut self, key: &RichTextAttributeKey) {
+        self.inner.insert(key.clone(), RichTextAttributeValue(None));
+    }
+
+    pub fn mark_all_as_removed_except(&mut self, attribute: Option<RichTextAttributeKey>) {
+        match attribute {
+            None => {
+                self.inner.iter_mut().for_each(|(_k, v)| v.0 = None);
+            },
+            Some(attribute) => {
+                self.inner.iter_mut().for_each(|(k, v)| {
+                    if k != &attribute {
+                        v.0 = None;
+                    }
+                });
+            },
+        }
+    }
+
+    pub fn remove(&mut self, key: RichTextAttributeKey) { self.inner.retain(|k, _| k != &key); }
+
+    // pub fn block_attributes_except_header(attributes: &Attributes) -> Attributes
+    // {     let mut new_attributes = Attributes::new();
+    //     attributes.iter().for_each(|(k, v)| {
+    //         if k != &AttributeKey::Header {
+    //             new_attributes.insert(k.clone(), v.clone());
+    //         }
+    //     });
+    //
+    //     new_attributes
+    // }
+
+    // Update inner by constructing new attributes from the other if it's
+    // not None and replace the key/value with self key/value.
+    pub fn merge(&mut self, other: Option<RichTextAttributes>) {
+        if other.is_none() {
+            return;
+        }
+
+        let mut new_attributes = other.unwrap().inner;
+        self.inner.iter().for_each(|(k, v)| {
+            new_attributes.insert(k.clone(), v.clone());
+        });
+        self.inner = new_attributes;
+    }
+}
+
+impl Attributes for RichTextAttributes {
+    fn is_empty(&self) -> bool { self.inner.is_empty() }
+
+    fn remove_empty(&mut self) { self.inner.retain(|_, v| v.0.is_some()); }
+
+    fn extend_other(&mut self, other: Self) { self.inner.extend(other.inner); }
+}
+
+impl OperationTransformable for RichTextAttributes {
+    fn compose(&self, other: &Self) -> Result<Self, OTError>
+    where
+        Self: Sized,
+    {
+        let mut attributes = self.clone();
+        attributes.extend_other(other.clone());
+        Ok(attributes)
+    }
+
+    fn transform(&self, other: &Self) -> Result<(Self, Self), OTError>
+    where
+        Self: Sized,
+    {
+        let a = self
+            .iter()
+            .fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
+                if !other.contains_key(k) {
+                    new_attributes.insert(k.clone(), v.clone());
+                }
+                new_attributes
+            });
+
+        let b = other
+            .iter()
+            .fold(RichTextAttributes::new(), |mut new_attributes, (k, v)| {
+                if !self.contains_key(k) {
+                    new_attributes.insert(k.clone(), v.clone());
+                }
+                new_attributes
+            });
+
+        Ok((a, b))
+    }
+
+    fn invert(&self, other: &Self) -> Self {
+        let base_inverted = other.iter().fold(RichTextAttributes::new(), |mut attributes, (k, v)| {
+            if other.get(k) != self.get(k) && self.contains_key(k) {
+                attributes.insert(k.clone(), v.clone());
+            }
+            attributes
+        });
+
+        let inverted = self.iter().fold(base_inverted, |mut attributes, (k, _)| {
+            if other.get(k) != self.get(k) && !other.contains_key(k) {
+                attributes.delete(k);
+            }
+            attributes
+        });
+
+        inverted
+    }
+}
+
+impl std::ops::Deref for RichTextAttributes {
+    type Target = HashMap<RichTextAttributeKey, RichTextAttributeValue>;
+
+    fn deref(&self) -> &Self::Target { &self.inner }
+}
+
+impl std::ops::DerefMut for RichTextAttributes {
+    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
+}
+
+pub fn attributes_except_header(op: &RichTextOperation) -> RichTextAttributes {
+    let mut attributes = op.get_attributes();
+    attributes.remove(RichTextAttributeKey::Header);
+    attributes
+}
+
 #[derive(Debug, Clone)]
 pub struct RichTextAttribute {
     pub key: RichTextAttributeKey,

+ 1 - 2
shared-lib/lib-ot/src/core/attributes/attributes_serde.rs → shared-lib/lib-ot/src/rich_text/attributes_serde.rs

@@ -1,6 +1,5 @@
 #[rustfmt::skip]
-use crate::core::RichTextAttributeValue;
-use crate::core::{RichTextAttribute, RichTextAttributeKey, RichTextAttributes};
+use crate::rich_text::{RichTextAttribute, RichTextAttributeKey, RichTextAttributes, RichTextAttributeValue};
 use serde::{
     de,
     de::{MapAccess, Visitor},

+ 2 - 1
shared-lib/lib-ot/src/core/attributes/builder.rs → shared-lib/lib-ot/src/rich_text/builder.rs

@@ -1,5 +1,6 @@
 #![allow(non_snake_case)]
-use crate::core::{RichTextAttribute, RichTextAttributes};
+use crate::rich_text::{RichTextAttribute, RichTextAttributes};
+
 pub struct AttributeBuilder {
     inner: RichTextAttributes,
 }

+ 3 - 0
shared-lib/lib-ot/src/rich_text/delta.rs

@@ -0,0 +1,3 @@
+use crate::{core::Delta, rich_text::RichTextAttributes};
+
+pub type RichTextDelta = Delta<RichTextAttributes>;

+ 0 - 0
shared-lib/lib-ot/src/core/attributes/macros.rs → shared-lib/lib-ot/src/rich_text/macros.rs


+ 2 - 3
shared-lib/lib-ot/src/core/attributes/mod.rs → shared-lib/lib-ot/src/rich_text/mod.rs

@@ -1,12 +1,11 @@
-#![allow(clippy::module_inception)]
-mod attribute;
 mod attributes;
 mod attributes_serde;
 mod builder;
 
 #[macro_use]
 mod macros;
+mod delta;
 
-pub use attribute::*;
 pub use attributes::*;
 pub use builder::*;
+pub use delta::*;

+ 0 - 0
shared-lib/lib-ot/tests/main.rs