mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
preserve block attribute when insert inside
This commit is contained in:
parent
4ab4f744ba
commit
7c4b2d74a5
@ -46,7 +46,7 @@ class Rules {
|
||||
// const InsertEmbedsRule(),
|
||||
// const ForceNewlineForInsertsAroundEmbedRule(),
|
||||
const AutoExitBlockRule(),
|
||||
// const PreserveBlockStyleOnInsertRule(),
|
||||
const PreserveBlockStyleOnInsertRule(),
|
||||
// const PreserveLineStyleOnSplitRule(),
|
||||
const ResetLineFormatOnNewLineRule(),
|
||||
const AutoFormatLinksRule(),
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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" }
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)]
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user