From 9bc72d3b9ea05f3eff9618084976bb78c24fedc8 Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 16 Aug 2021 23:07:40 +0800 Subject: [PATCH] config auto exit block extension --- .../lib/src/model/heuristic/rule.dart | 2 +- .../lib/src/service/controller.dart | 3 +- .../extensions/insert/auto_exit_block.rs | 23 +++ .../client/extensions/insert/auto_format.rs | 9 +- .../src/client/extensions/insert/mod.rs | 23 +-- .../insert/preserve_inline_style.rs | 2 +- rust-lib/flowy-ot/src/client/util.rs | 2 +- .../flowy-ot/src/core/attributes/attribute.rs | 185 ++++++++++++++++++ .../flowy-ot/src/core/attributes/builder.rs | 165 +--------------- rust-lib/flowy-ot/src/core/attributes/mod.rs | 2 + rust-lib/flowy-ot/src/core/delta/delta.rs | 2 +- rust-lib/flowy-ot/tests/attribute_test.rs | 37 ++++ rust-lib/flowy-ot/tests/helper/mod.rs | 11 ++ rust-lib/flowy-ot/tests/undo_redo_test.rs | 19 ++ 14 files changed, 289 insertions(+), 196 deletions(-) create mode 100644 rust-lib/flowy-ot/src/client/extensions/insert/auto_exit_block.rs create mode 100644 rust-lib/flowy-ot/src/core/attributes/attribute.rs diff --git a/app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart b/app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart index 6bac6ea792..8416aebe3f 100644 --- a/app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart +++ b/app_flowy/packages/flowy_editor/lib/src/model/heuristic/rule.dart @@ -45,7 +45,7 @@ class Rules { const ResolveInlineFormatRule(), // const InsertEmbedsRule(), // const ForceNewlineForInsertsAroundEmbedRule(), - // const AutoExitBlockRule(), + const AutoExitBlockRule(), // const PreserveBlockStyleOnInsertRule(), // const PreserveLineStyleOnSplitRule(), const ResetLineFormatOnNewLineRule(), diff --git a/app_flowy/packages/flowy_editor/lib/src/service/controller.dart b/app_flowy/packages/flowy_editor/lib/src/service/controller.dart index 08688a76ae..e9f9339fa6 100644 --- a/app_flowy/packages/flowy_editor/lib/src/service/controller.dart +++ b/app_flowy/packages/flowy_editor/lib/src/service/controller.dart @@ -93,8 +93,7 @@ class EditorController extends ChangeNotifier { toggledStyle = toggledStyle.put(attribute); } - final change = - document.format(index, length, LinkAttribute("www.baidu.com")); + final change = document.format(index, length, attribute); final adjustedSelection = selection.copyWith( baseOffset: change.transformPosition(selection.baseOffset), extentOffset: change.transformPosition(selection.extentOffset), diff --git a/rust-lib/flowy-ot/src/client/extensions/insert/auto_exit_block.rs b/rust-lib/flowy-ot/src/client/extensions/insert/auto_exit_block.rs new file mode 100644 index 0000000000..a2cac4f023 --- /dev/null +++ b/rust-lib/flowy-ot/src/client/extensions/insert/auto_exit_block.rs @@ -0,0 +1,23 @@ +use crate::{ + client::{extensions::InsertExt, util::is_newline}, + core::{Delta, DeltaIter}, +}; + +pub struct AutoExitBlockExt {} + +impl InsertExt for AutoExitBlockExt { + fn ext_name(&self) -> &str { "AutoExitBlockExt" } + + fn apply(&self, delta: &Delta, _replace_len: usize, text: &str, index: usize) -> Option { + // Auto exit block will be triggered by enter two new lines + if !is_newline(text) { + return None; + } + + let mut iter = DeltaIter::new(delta); + let _prev = iter.next_op_before(index); + let _next = iter.next_op(); + + None + } +} diff --git a/rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs b/rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs index 7c827f00d3..ccc6fd66df 100644 --- a/rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs +++ b/rust-lib/flowy-ot/src/client/extensions/insert/auto_format.rs @@ -1,6 +1,6 @@ use crate::{ client::{extensions::InsertExt, util::is_whitespace}, - core::{CharMetric, Delta, DeltaIter}, + core::{Delta, DeltaIter}, }; pub struct AutoFormatExt {} @@ -50,13 +50,10 @@ impl InsertExt for AutoFormatExt { } } -use crate::{ - client::extensions::NEW_LINE, - core::{Attribute, AttributeBuilder, Attributes, DeltaBuilder, Operation}, -}; +use crate::core::{Attribute, AttributeBuilder, Attributes, DeltaBuilder, Operation}; use bytecount::num_chars; use std::cmp::min; -use url::{ParseError, Url}; +use url::Url; pub enum AutoFormatter { Url(Url), diff --git a/rust-lib/flowy-ot/src/client/extensions/insert/mod.rs b/rust-lib/flowy-ot/src/client/extensions/insert/mod.rs index dba868f89c..11da10bd66 100644 --- a/rust-lib/flowy-ot/src/client/extensions/insert/mod.rs +++ b/rust-lib/flowy-ot/src/client/extensions/insert/mod.rs @@ -1,17 +1,16 @@ +pub use auto_exit_block::*; pub use auto_format::*; pub use default_insert::*; pub use preserve_inline_style::*; pub use reset_format_on_new_line::*; +mod auto_exit_block; mod auto_format; mod default_insert; mod preserve_inline_style; mod reset_format_on_new_line; -use crate::{ - client::extensions::InsertExt, - core::{Delta, DeltaBuilder, DeltaIter, Operation}, -}; +use crate::{client::extensions::InsertExt, core::Delta}; pub struct PreserveBlockStyleOnInsertExt {} impl InsertExt for PreserveBlockStyleOnInsertExt { @@ -43,22 +42,6 @@ impl InsertExt for PreserveLineStyleOnSplitExt { } } -pub struct AutoExitBlockExt {} - -impl InsertExt for AutoExitBlockExt { - fn ext_name(&self) -> &str { "AutoExitBlockExt" } - - fn apply( - &self, - _delta: &Delta, - _replace_len: usize, - _text: &str, - _index: usize, - ) -> Option { - None - } -} - pub struct InsertEmbedsExt {} impl InsertExt for InsertEmbedsExt { fn ext_name(&self) -> &str { "InsertEmbedsExt" } diff --git a/rust-lib/flowy-ot/src/client/extensions/insert/preserve_inline_style.rs b/rust-lib/flowy-ot/src/client/extensions/insert/preserve_inline_style.rs index 839924c5f0..4111218629 100644 --- a/rust-lib/flowy-ot/src/client/extensions/insert/preserve_inline_style.rs +++ b/rust-lib/flowy-ot/src/client/extensions/insert/preserve_inline_style.rs @@ -3,7 +3,7 @@ use crate::{ extensions::InsertExt, util::{contain_newline, OpNewline}, }, - core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter}, + core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter}, }; pub struct PreserveInlineStylesExt {} diff --git a/rust-lib/flowy-ot/src/client/util.rs b/rust-lib/flowy-ot/src/client/util.rs index 6e9566c54e..6a8a99f19b 100644 --- a/rust-lib/flowy-ot/src/client/util.rs +++ b/rust-lib/flowy-ot/src/client/util.rs @@ -50,7 +50,7 @@ impl OpNewline { pub fn is_not_found(&self) -> bool { self == &OpNewline::NotFound } pub fn is_contain(&self) -> bool { - self.is_start() || self.is_end() || self == &OpNewline::Contain + self.is_start() || self.is_end() || self.is_equal() || self == &OpNewline::Contain } pub fn is_equal(&self) -> bool { self == &OpNewline::Equal } diff --git a/rust-lib/flowy-ot/src/core/attributes/attribute.rs b/rust-lib/flowy-ot/src/core/attributes/attribute.rs new file mode 100644 index 0000000000..4d0bce3782 --- /dev/null +++ b/rust-lib/flowy-ot/src/core/attributes/attribute.rs @@ -0,0 +1,185 @@ +use crate::core::{Attributes, REMOVE_FLAG}; +use derive_more::Display; +use lazy_static::lazy_static; +use std::{collections::HashSet, fmt, fmt::Formatter, iter::FromIterator}; + +lazy_static! { + static ref BLOCK_KEYS: HashSet = HashSet::from_iter(vec![ + AttributeKey::Header, + AttributeKey::Align, + AttributeKey::List, + AttributeKey::CodeBlock, + AttributeKey::QuoteBlock, + AttributeKey::Indent, + ]); + static ref INLINE_KEYS: HashSet = HashSet::from_iter(vec![ + AttributeKey::Bold, + AttributeKey::Italic, + AttributeKey::Underline, + AttributeKey::StrikeThrough, + AttributeKey::Link, + AttributeKey::Color, + AttributeKey::Background, + ]); +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum AttributeScope { + Inline, + Block, + Embeds, + Ignore, +} + +#[derive(Debug, Clone)] +pub struct Attribute { + pub key: AttributeKey, + pub value: AttributeValue, + pub scope: AttributeScope, +} + +impl fmt::Display for Attribute { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let s = format!("{:?}:{} {:?}", self.key, self.value.as_ref(), self.scope); + f.write_str(&s) + } +} + +impl std::convert::Into for Attribute { + fn into(self) -> Attributes { + let mut attributes = Attributes::new(); + attributes.add(self); + attributes + } +} + +#[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum AttributeKey { + #[display(fmt = "bold")] + Bold, + #[display(fmt = "italic")] + Italic, + #[display(fmt = "underline")] + Underline, + #[display(fmt = "strike_through")] + StrikeThrough, + #[display(fmt = "font")] + Font, + #[display(fmt = "size")] + Size, + #[display(fmt = "link")] + Link, + #[display(fmt = "color")] + Color, + #[display(fmt = "background")] + Background, + #[display(fmt = "indent")] + Indent, + #[display(fmt = "align")] + Align, + #[display(fmt = "code_block")] + CodeBlock, + #[display(fmt = "list")] + List, + #[display(fmt = "quote_block")] + QuoteBlock, + #[display(fmt = "width")] + Width, + #[display(fmt = "height")] + Height, + #[display(fmt = "style")] + Style, + #[display(fmt = "header")] + Header, + #[display(fmt = "left")] + LeftAlignment, + #[display(fmt = "center")] + CenterAlignment, + #[display(fmt = "right")] + RightAlignment, + #[display(fmt = "justify")] + JustifyAlignment, + #[display(fmt = "bullet")] + Bullet, + #[display(fmt = "ordered")] + Ordered, + #[display(fmt = "checked")] + Checked, + #[display(fmt = "unchecked")] + UnChecked, +} + +impl AttributeKey { + pub fn remove(&self) -> Attribute { self.value(REMOVE_FLAG) } + + pub fn value>(&self, value: T) -> Attribute { + let key = self.clone(); + let value: AttributeValue = value.into(); + match self { + AttributeKey::Bold + | AttributeKey::Italic + | AttributeKey::Underline + | AttributeKey::StrikeThrough + | AttributeKey::Link + | AttributeKey::Color + | AttributeKey::Background + | AttributeKey::Font + | AttributeKey::Size => Attribute { + key, + value, + scope: AttributeScope::Inline, + }, + + AttributeKey::Header + | AttributeKey::LeftAlignment + | AttributeKey::CenterAlignment + | AttributeKey::RightAlignment + | AttributeKey::JustifyAlignment + | AttributeKey::Indent + | AttributeKey::Align + | AttributeKey::CodeBlock + | AttributeKey::List + | AttributeKey::Bullet + | AttributeKey::Ordered + | AttributeKey::Checked + | AttributeKey::UnChecked + | AttributeKey::QuoteBlock => Attribute { + key, + value, + scope: AttributeScope::Block, + }, + + AttributeKey::Width | AttributeKey::Height | AttributeKey::Style => Attribute { + key, + value, + scope: AttributeScope::Ignore, + }, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AttributeValue(pub(crate) String); + +impl AsRef for AttributeValue { + fn as_ref(&self) -> &str { &self.0 } +} + +impl std::convert::From<&usize> for AttributeValue { + fn from(val: &usize) -> Self { AttributeValue(format!("{}", val)) } +} + +impl std::convert::From<&str> for AttributeValue { + fn from(val: &str) -> Self { AttributeValue(val.to_owned()) } +} + +impl std::convert::From for AttributeValue { + fn from(val: bool) -> Self { + let val = match val { + true => "true", + false => "", + }; + AttributeValue(val.to_owned()) + } +} diff --git a/rust-lib/flowy-ot/src/core/attributes/builder.rs b/rust-lib/flowy-ot/src/core/attributes/builder.rs index 66ca5f2511..6e9b4aeadf 100644 --- a/rust-lib/flowy-ot/src/core/attributes/builder.rs +++ b/rust-lib/flowy-ot/src/core/attributes/builder.rs @@ -1,100 +1,4 @@ -use crate::core::{Attributes, REMOVE_FLAG}; -use derive_more::Display; -use std::{fmt, fmt::Formatter}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AttributeValue(pub(crate) String); - -impl AsRef for AttributeValue { - fn as_ref(&self) -> &str { &self.0 } -} - -#[derive(Clone, Debug, Display, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum AttributeKey { - #[display(fmt = "bold")] - Bold, - #[display(fmt = "italic")] - Italic, - #[display(fmt = "underline")] - Underline, - #[display(fmt = "strike_through")] - StrikeThrough, - #[display(fmt = "font")] - Font, - #[display(fmt = "size")] - Size, - #[display(fmt = "link")] - Link, - #[display(fmt = "color")] - Color, - #[display(fmt = "background")] - Background, - #[display(fmt = "ident")] - Ident, - #[display(fmt = "align")] - Align, - #[display(fmt = "code_block")] - CodeBlock, - #[display(fmt = "list")] - List, - #[display(fmt = "quote_block")] - QuoteBlock, - #[display(fmt = "width")] - Width, - #[display(fmt = "height")] - Height, - #[display(fmt = "style")] - Style, - #[display(fmt = "header")] - Header, - #[display(fmt = "left")] - LeftAlignment, - #[display(fmt = "center")] - CenterAlignment, - #[display(fmt = "right")] - RightAlignment, - #[display(fmt = "justify")] - JustifyAlignment, - #[display(fmt = "bullet")] - Bullet, - #[display(fmt = "ordered")] - Ordered, - #[display(fmt = "checked")] - Checked, - #[display(fmt = "unchecked")] - UnChecked, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum AttributeScope { - Inline, - Block, - Embeds, - Ignore, -} - -#[derive(Debug, Clone)] -pub struct Attribute { - pub key: AttributeKey, - pub value: AttributeValue, - pub scope: AttributeScope, -} - -impl fmt::Display for Attribute { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let s = format!("{:?}:{} {:?}", self.key, self.value.as_ref(), self.scope); - f.write_str(&s) - } -} - -impl std::convert::Into for Attribute { - fn into(self) -> Attributes { - let mut attributes = Attributes::new(); - attributes.add(self); - attributes - } -} +use crate::core::{Attribute, AttributeKey, AttributeValue, Attributes, REMOVE_FLAG}; pub struct AttributeBuilder { inner: Attributes, @@ -155,70 +59,3 @@ impl AttributeBuilder { pub fn build(self) -> Attributes { self.inner } } - -impl AttributeKey { - pub fn remove(&self) -> Attribute { self.value(REMOVE_FLAG) } - - pub fn value>(&self, value: T) -> Attribute { - let key = self.clone(); - let value: AttributeValue = value.into(); - match self { - AttributeKey::Bold - | AttributeKey::Italic - | AttributeKey::Underline - | AttributeKey::StrikeThrough - | AttributeKey::Link - | AttributeKey::Color - | AttributeKey::Background - | AttributeKey::Font - | AttributeKey::Size => Attribute { - key, - value, - scope: AttributeScope::Inline, - }, - - AttributeKey::Header - | AttributeKey::LeftAlignment - | AttributeKey::CenterAlignment - | AttributeKey::RightAlignment - | AttributeKey::JustifyAlignment - | AttributeKey::Ident - | AttributeKey::Align - | AttributeKey::CodeBlock - | AttributeKey::List - | AttributeKey::Bullet - | AttributeKey::Ordered - | AttributeKey::Checked - | AttributeKey::UnChecked - | AttributeKey::QuoteBlock => Attribute { - key, - value, - scope: AttributeScope::Block, - }, - - AttributeKey::Width | AttributeKey::Height | AttributeKey::Style => Attribute { - key, - value, - scope: AttributeScope::Ignore, - }, - } - } -} - -impl std::convert::From<&usize> for AttributeValue { - fn from(val: &usize) -> Self { AttributeValue(format!("{}", val)) } -} - -impl std::convert::From<&str> for AttributeValue { - fn from(val: &str) -> Self { AttributeValue(val.to_owned()) } -} - -impl std::convert::From for AttributeValue { - fn from(val: bool) -> Self { - let val = match val { - true => "true", - false => "", - }; - AttributeValue(val.to_owned()) - } -} diff --git a/rust-lib/flowy-ot/src/core/attributes/mod.rs b/rust-lib/flowy-ot/src/core/attributes/mod.rs index 1855d0e8be..e10bc7f85e 100644 --- a/rust-lib/flowy-ot/src/core/attributes/mod.rs +++ b/rust-lib/flowy-ot/src/core/attributes/mod.rs @@ -1,6 +1,8 @@ +mod attribute; mod attributes; mod attributes_serde; mod builder; +pub use attribute::*; pub use attributes::*; pub use builder::*; diff --git a/rust-lib/flowy-ot/src/core/delta/delta.rs b/rust-lib/flowy-ot/src/core/delta/delta.rs index 6dbb86d94e..45bee0e8bb 100644 --- a/rust-lib/flowy-ot/src/core/delta/delta.rs +++ b/rust-lib/flowy-ot/src/core/delta/delta.rs @@ -625,7 +625,7 @@ fn invert_from_other( ); let inverted_attrs = invert_attributes(operation.get_attributes(), other_op.get_attributes()); - log::debug!("invert result: {:?}", inverted_attrs); + log::debug!("invert attributes result: {:?}", inverted_attrs); log::debug!( "invert retain: {} by retain len: {}, {}", retain, diff --git a/rust-lib/flowy-ot/tests/attribute_test.rs b/rust-lib/flowy-ot/tests/attribute_test.rs index 9cbfc1e278..547bd5c2ba 100644 --- a/rust-lib/flowy-ot/tests/attribute_test.rs +++ b/rust-lib/flowy-ot/tests/attribute_test.rs @@ -635,3 +635,40 @@ fn attributes_auto_format_exist_link2() { OpTester::new().run_script_with_newline(ops); } + +#[test] +fn attributes_add_bullet() { + let ops = vec![ + Insert(0, "1", 0), + Bullet(0, Interval::new(0, 1), true), + AssertOpsJson( + 0, + r#"[{"insert":"1"},{"insert":"\n","attributes":{"bullet":"true"}}]"#, + ), + Insert(0, NEW_LINE, 1), + Insert(0, "2", 2), + AssertOpsJson( + 0, + r#"[{"insert":"1"},{"insert":"\n","attributes":{"bullet":"true"}},{"insert":"2"},{"insert":"\n","attributes":{"bullet":"true"}}]"#, + ), + ]; + + OpTester::new().run_script_with_newline(ops); +} + +#[test] +fn attributes_un_bullet_one() { + let ops = vec![ + Insert(0, "1", 0), + Bullet(0, Interval::new(0, 1), true), + Insert(0, NEW_LINE, 1), + Insert(0, "2", 2), + Bullet(0, Interval::new(2, 3), false), + AssertOpsJson( + 0, + r#"[{"insert":"1"},{"insert":"\n","attributes":{"bullet":"true"}},{"insert":"2\n"}]"#, + ), + ]; + + OpTester::new().run_script_with_newline(ops); +} diff --git a/rust-lib/flowy-ot/tests/helper/mod.rs b/rust-lib/flowy-ot/tests/helper/mod.rs index 933a3f8dff..045cb57c21 100644 --- a/rust-lib/flowy-ot/tests/helper/mod.rs +++ b/rust-lib/flowy-ot/tests/helper/mod.rs @@ -33,6 +33,9 @@ pub enum TestOp { #[display(fmt = "Link")] Link(usize, Interval, &'static str, bool), + #[display(fmt = "Bullet")] + Bullet(usize, Interval, bool), + #[display(fmt = "Transform")] Transform(usize, usize), @@ -126,6 +129,14 @@ impl OpTester { }; document.format(*iv, attribute).unwrap(); }, + TestOp::Bullet(delta_i, iv, enable) => { + let document = &mut self.documents[*delta_i]; + let attribute = match *enable { + true => AttributeKey::Bullet.value("true"), + false => AttributeKey::Bullet.remove(), + }; + document.format(*iv, attribute).unwrap(); + }, TestOp::Transform(delta_a_i, delta_b_i) => { let (a_prime, b_prime) = self.documents[*delta_a_i] .data() diff --git a/rust-lib/flowy-ot/tests/undo_redo_test.rs b/rust-lib/flowy-ot/tests/undo_redo_test.rs index e688aeb93c..2605356390 100644 --- a/rust-lib/flowy-ot/tests/undo_redo_test.rs +++ b/rust-lib/flowy-ot/tests/undo_redo_test.rs @@ -267,3 +267,22 @@ fn history_undo_add_header() { OpTester::new().run_script_with_newline(ops); } + +#[test] +fn history_undo_add_link() { + let site = "https://appflowy.io"; + let ops = vec![ + Insert(0, site, 0), + Wait(RECORD_THRESHOLD), + Link(0, Interval::new(0, site.len()), site, true), + Undo(0), + AssertOpsJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), + Redo(0), + AssertOpsJson( + 0, + r#"[{"insert":"https://appflowy.io","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#, + ), + ]; + + OpTester::new().run_script_with_newline(ops); +}