preserve block attribute when insert inside

This commit is contained in:
appflowy 2021-08-17 14:14:09 +08:00
parent 4ab4f744ba
commit 7c4b2d74a5
10 changed files with 212 additions and 33 deletions

View File

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

View File

@ -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;
},

View File

@ -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
}

View File

@ -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" }

View File

@ -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
}
}

View File

@ -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)]

View File

@ -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
}

View File

@ -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 }
}

View File

@ -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() {

View File

@ -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);
}