Преглед изворни кода

preserve block attribute when insert inside

appflowy пре 3 година
родитељ
комит
7c4b2d74a5

+ 1 - 1
app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart

@@ -46,7 +46,7 @@ class Rules {
     // const InsertEmbedsRule(),
     // const ForceNewlineForInsertsAroundEmbedRule(),
     const AutoExitBlockRule(),
-    // const PreserveBlockStyleOnInsertRule(),
+    const PreserveBlockStyleOnInsertRule(),
     // const PreserveLineStyleOnSplitRule(),
     const ResetLineFormatOnNewLineRule(),
     const AutoFormatLinksRule(),

+ 1 - 1
rust-lib/flowy-ot/src/client/extensions/format/resolve_block_format.rs

@@ -49,7 +49,7 @@ impl FormatExt for ResolveBlockFormatExt {
             match find_newline(op.get_data()) {
                 None => new_delta.retain(op.len(), Attributes::empty()),
                 Some(line_break) => {
-                    debug_assert_eq!(line_break, 0);
+                    new_delta.retain(line_break, Attributes::empty());
                     new_delta.retain(1, attribute.clone().into());
                     break;
                 },

+ 2 - 8
rust-lib/flowy-ot/src/client/extensions/insert/auto_exit_block.rs

@@ -12,7 +12,7 @@ use crate::{
     },
 };
 
-use crate::core::is_empty_line_at_index;
+use crate::core::{attributes_except_header, is_empty_line_at_index};
 
 pub struct AutoExitBlockExt {}
 
@@ -42,7 +42,7 @@ impl InsertExt for AutoExitBlockExt {
             return None;
         }
 
-        match iter.first_op_contains_newline() {
+        match iter.first_newline_op() {
             None => {},
             Some((newline_op, _)) => {
                 let newline_attributes = attributes_except_header(&newline_op);
@@ -62,9 +62,3 @@ impl InsertExt for AutoExitBlockExt {
         )
     }
 }
-
-fn attributes_except_header(op: &Operation) -> Attributes {
-    let mut attributes = op.get_attributes();
-    attributes.remove(AttributeKey::Header);
-    attributes
-}

+ 2 - 15
rust-lib/flowy-ot/src/client/extensions/insert/mod.rs

@@ -1,32 +1,19 @@
 pub use auto_exit_block::*;
 pub use auto_format::*;
 pub use default_insert::*;
+pub use preserve_block_style::*;
 pub use preserve_inline_style::*;
 pub use reset_format_on_new_line::*;
 
 mod auto_exit_block;
 mod auto_format;
 mod default_insert;
+mod preserve_block_style;
 mod preserve_inline_style;
 mod reset_format_on_new_line;
 
 use crate::{client::extensions::InsertExt, core::Delta};
 
-pub struct PreserveBlockStyleOnInsertExt {}
-impl InsertExt for PreserveBlockStyleOnInsertExt {
-    fn ext_name(&self) -> &str { "PreserveBlockStyleOnInsertExt" }
-
-    fn apply(
-        &self,
-        _delta: &Delta,
-        _replace_len: usize,
-        _text: &str,
-        _index: usize,
-    ) -> Option<Delta> {
-        None
-    }
-}
-
 pub struct PreserveLineStyleOnSplitExt {}
 impl InsertExt for PreserveLineStyleOnSplitExt {
     fn ext_name(&self) -> &str { "PreserveLineStyleOnSplitExt" }

+ 71 - 0
rust-lib/flowy-ot/src/client/extensions/insert/preserve_block_style.rs

@@ -0,0 +1,71 @@
+use crate::{
+    client::{extensions::InsertExt, util::is_newline},
+    core::{
+        attributes_except_header,
+        AttributeBuilder,
+        AttributeKey,
+        Attributes,
+        Delta,
+        DeltaBuilder,
+        DeltaIter,
+        Operation,
+        NEW_LINE,
+    },
+};
+
+pub struct PreserveBlockStyleOnInsertExt {}
+impl InsertExt for PreserveBlockStyleOnInsertExt {
+    fn ext_name(&self) -> &str { "PreserveBlockStyleOnInsertExt" }
+
+    fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
+        if !is_newline(text) {
+            return None;
+        }
+
+        let mut iter = DeltaIter::from_offset(delta, index);
+        match iter.first_newline_op() {
+            None => {},
+            Some((newline_op, offset)) => {
+                let newline_attributes = newline_op.get_attributes();
+                let block_attributes = attributes_except_header(&newline_op);
+                if block_attributes.is_empty() {
+                    return None;
+                }
+
+                let mut reset_attribute = Attributes::new();
+                if newline_attributes.contains_key(&AttributeKey::Header) {
+                    reset_attribute.add(AttributeKey::Header.value(""));
+                }
+
+                let lines: Vec<_> = text.split(NEW_LINE).collect();
+                let line_count = lines.len();
+                let mut new_delta = DeltaBuilder::new().retain(index + replace_len).build();
+                lines.iter().enumerate().for_each(|(i, line)| {
+                    if !line.is_empty() {
+                        new_delta.insert(line, Attributes::empty());
+                    }
+
+                    if i == 0 {
+                        new_delta.insert(NEW_LINE, newline_attributes.clone());
+                    } else if i < lines.len() - 1 {
+                        new_delta.insert(NEW_LINE, block_attributes.clone());
+                    } else {
+                        // do nothing
+                    }
+
+                    log::info!("{}", new_delta);
+                });
+                if !reset_attribute.is_empty() {
+                    new_delta.retain(offset, Attributes::empty());
+                    let len = newline_op.get_data().find(NEW_LINE).unwrap();
+                    new_delta.retain(len, Attributes::empty());
+                    new_delta.retain(1, reset_attribute.clone());
+                }
+
+                return Some(new_delta);
+            },
+        }
+
+        None
+    }
+}

+ 57 - 7
rust-lib/flowy-ot/src/core/attributes/attribute.rs

@@ -31,11 +31,8 @@ lazy_static! {
         AttributeKey::Size,
         AttributeKey::Background,
     ]);
-    static ref INGORE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
-        AttributeKey::Width,
-        AttributeKey::Height,
-        AttributeKey::Style,
-    ]);
+    static ref INGORE_KEYS: HashSet<AttributeKey> =
+        HashSet::from_iter(vec![AttributeKey::Width, AttributeKey::Height,]);
 }
 
 #[derive(Debug, PartialEq, Eq, Clone)]
@@ -103,8 +100,6 @@ pub enum AttributeKey {
     Width,
     #[display(fmt = "height")]
     Height,
-    #[display(fmt = "style")]
-    Style,
     #[display(fmt = "header")]
     Header,
     #[display(fmt = "left")]
@@ -131,6 +126,8 @@ impl AttributeKey {
     pub fn value<T: Into<AttributeValue>>(&self, value: T) -> Attribute {
         let key = self.clone();
         let value: AttributeValue = value.into();
+        debug_assert_eq!(self.check_value(&value), true);
+
         if INLINE_KEYS.contains(self) {
             return Attribute {
                 key,
@@ -153,6 +150,59 @@ impl AttributeKey {
             scope: AttributeScope::Ignore,
         }
     }
+
+    fn check_value(&self, value: &AttributeValue) -> bool {
+        if value.0.is_empty() {
+            return true;
+        }
+
+        match self {
+            AttributeKey::Bold
+            | AttributeKey::Italic
+            | AttributeKey::Underline
+            | AttributeKey::StrikeThrough
+            | AttributeKey::Indent
+            | AttributeKey::Align
+            | AttributeKey::CodeBlock
+            | AttributeKey::List
+            | AttributeKey::QuoteBlock
+            | AttributeKey::JustifyAlignment
+            | AttributeKey::Bullet
+            | AttributeKey::Ordered
+            | AttributeKey::Checked
+            | AttributeKey::UnChecked => {
+                if let Err(e) = value.0.parse::<bool>() {
+                    log::error!(
+                        "Parser failed: {:?}. expected bool, but receive {}",
+                        e,
+                        value.0
+                    );
+                    return false;
+                }
+            },
+
+            AttributeKey::Link | AttributeKey::Color | AttributeKey::Background => {},
+
+            AttributeKey::Header
+            | AttributeKey::Width
+            | AttributeKey::Height
+            | AttributeKey::Font
+            | AttributeKey::Size
+            | AttributeKey::LeftAlignment
+            | AttributeKey::CenterAlignment
+            | AttributeKey::RightAlignment => {
+                if let Err(e) = value.0.parse::<usize>() {
+                    log::error!(
+                        "Parser failed: {:?}. expected usize, but receive {}",
+                        e,
+                        value.0
+                    );
+                    return false;
+                }
+            },
+        }
+        true
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]

+ 6 - 0
rust-lib/flowy-ot/src/core/attributes/attributes.rs

@@ -178,3 +178,9 @@ pub fn merge_attributes(mut attributes: Attributes, other: Attributes) -> Attrib
     attributes.extend(other);
     attributes
 }
+
+pub fn attributes_except_header(op: &Operation) -> Attributes {
+    let mut attributes = op.get_attributes();
+    attributes.remove(AttributeKey::Header);
+    attributes
+}

+ 1 - 0
rust-lib/flowy-ot/src/core/attributes/builder.rs

@@ -56,6 +56,7 @@ impl AttributeBuilder {
     impl_bool_attribute!(underline, AttributeKey::Underline);
     impl_bool_attribute!(strike_through, AttributeKey::StrikeThrough);
     impl_str_attribute!(link, AttributeKey::Link);
+    // impl_str_attribute!(header, AttributeKey::Header);
 
     pub fn build(self) -> Attributes { self.inner }
 }

+ 1 - 1
rust-lib/flowy-ot/src/core/delta/iterator.rs

@@ -38,7 +38,7 @@ impl<'a> DeltaIter<'a> {
     }
 
     // find next op contains NEW_LINE
-    pub fn first_op_contains_newline(&mut self) -> Option<(Operation, usize)> {
+    pub fn first_newline_op(&mut self) -> Option<(Operation, usize)> {
         let mut offset = 0;
         while self.has_next() {
             if let Some(op) = self.next_op() {

+ 70 - 0
rust-lib/flowy-ot/tests/attribute_test.rs

@@ -506,6 +506,22 @@ fn attributes_header_insert_newline_at_trailing() {
     OpTester::new().run_script_with_newline(ops);
 }
 
+#[test]
+fn attributes_header_insert_double_newline_at_trailing() {
+    let ops = vec![
+        Insert(0, "123456", 0),
+        Header(0, Interval::new(0, 6), 1, true),
+        Insert(0, "\n", 6),
+        Insert(0, "\n", 7),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":"1"}},{"insert":"\n\n"}]"#,
+        ),
+    ];
+
+    OpTester::new().run_script_with_newline(ops);
+}
+
 #[test]
 fn attributes_add_link() {
     let ops = vec![
@@ -638,6 +654,20 @@ fn attributes_auto_format_exist_link2() {
 
 #[test]
 fn attributes_add_bullet() {
+    let ops = vec![
+        Insert(0, "12", 0),
+        Bullet(0, Interval::new(0, 1), true),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12"},{"insert":"\n","attributes":{"bullet":"true"}}]"#,
+        ),
+    ];
+
+    OpTester::new().run_script_with_newline(ops);
+}
+
+#[test]
+fn attributes_add_bullet2() {
     let ops = vec![
         Insert(0, "1", 0),
         Bullet(0, Interval::new(0, 1), true),
@@ -692,3 +722,43 @@ fn attributes_auto_exit_block() {
 
     OpTester::new().run_script_with_newline(ops);
 }
+
+#[test]
+fn attributes_preserve_block_when_insert_newline_inside() {
+    let ops = vec![
+        Insert(0, "12", 0),
+        Bullet(0, Interval::new(0, 2), true),
+        Insert(0, NEW_LINE, 2),
+        AssertOpsJson(
+            0,
+            r#"[{"insert":"12"},{"insert":"\n\n","attributes":{"bullet":"true"}}]"#,
+        ),
+        Insert(0, "34", 3),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"12"},{"insert":"\n","attributes":{"bullet":"true"}},
+            {"insert":"34"},{"insert":"\n","attributes":{"bullet":"true"}}
+            ]"#,
+        ),
+        Insert(0, NEW_LINE, 3),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"12"},{"insert":"\n\n","attributes":{"bullet":"true"}},
+            {"insert":"34"},{"insert":"\n","attributes":{"bullet":"true"}}
+            ]"#,
+        ),
+        Insert(0, "ab", 3),
+        AssertOpsJson(
+            0,
+            r#"[
+            {"insert":"12"},{"insert":"\n","attributes":{"bullet":"true"}},
+            {"insert":"ab"},{"insert":"\n","attributes":{"bullet":"true"}},
+            {"insert":"34"},{"insert":"\n","attributes":{"bullet":"true"}}
+            ]"#,
+        ),
+    ];
+
+    OpTester::new().run_script_with_newline(ops);
+}