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 InsertEmbedsRule(),
|
||||||
// const ForceNewlineForInsertsAroundEmbedRule(),
|
// const ForceNewlineForInsertsAroundEmbedRule(),
|
||||||
const AutoExitBlockRule(),
|
const AutoExitBlockRule(),
|
||||||
// const PreserveBlockStyleOnInsertRule(),
|
const PreserveBlockStyleOnInsertRule(),
|
||||||
// const PreserveLineStyleOnSplitRule(),
|
// const PreserveLineStyleOnSplitRule(),
|
||||||
const ResetLineFormatOnNewLineRule(),
|
const ResetLineFormatOnNewLineRule(),
|
||||||
const AutoFormatLinksRule(),
|
const AutoFormatLinksRule(),
|
||||||
|
@ -49,7 +49,7 @@ impl FormatExt for ResolveBlockFormatExt {
|
|||||||
match find_newline(op.get_data()) {
|
match find_newline(op.get_data()) {
|
||||||
None => new_delta.retain(op.len(), Attributes::empty()),
|
None => new_delta.retain(op.len(), Attributes::empty()),
|
||||||
Some(line_break) => {
|
Some(line_break) => {
|
||||||
debug_assert_eq!(line_break, 0);
|
new_delta.retain(line_break, Attributes::empty());
|
||||||
new_delta.retain(1, attribute.clone().into());
|
new_delta.retain(1, attribute.clone().into());
|
||||||
break;
|
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 {}
|
pub struct AutoExitBlockExt {}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ impl InsertExt for AutoExitBlockExt {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match iter.first_op_contains_newline() {
|
match iter.first_newline_op() {
|
||||||
None => {},
|
None => {},
|
||||||
Some((newline_op, _)) => {
|
Some((newline_op, _)) => {
|
||||||
let newline_attributes = attributes_except_header(&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_exit_block::*;
|
||||||
pub use auto_format::*;
|
pub use auto_format::*;
|
||||||
pub use default_insert::*;
|
pub use default_insert::*;
|
||||||
|
pub use preserve_block_style::*;
|
||||||
pub use preserve_inline_style::*;
|
pub use preserve_inline_style::*;
|
||||||
pub use reset_format_on_new_line::*;
|
pub use reset_format_on_new_line::*;
|
||||||
|
|
||||||
mod auto_exit_block;
|
mod auto_exit_block;
|
||||||
mod auto_format;
|
mod auto_format;
|
||||||
mod default_insert;
|
mod default_insert;
|
||||||
|
mod preserve_block_style;
|
||||||
mod preserve_inline_style;
|
mod preserve_inline_style;
|
||||||
mod reset_format_on_new_line;
|
mod reset_format_on_new_line;
|
||||||
|
|
||||||
use crate::{client::extensions::InsertExt, core::Delta};
|
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 {}
|
pub struct PreserveLineStyleOnSplitExt {}
|
||||||
impl InsertExt for PreserveLineStyleOnSplitExt {
|
impl InsertExt for PreserveLineStyleOnSplitExt {
|
||||||
fn ext_name(&self) -> &str { "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::Size,
|
||||||
AttributeKey::Background,
|
AttributeKey::Background,
|
||||||
]);
|
]);
|
||||||
static ref INGORE_KEYS: HashSet<AttributeKey> = HashSet::from_iter(vec![
|
static ref INGORE_KEYS: HashSet<AttributeKey> =
|
||||||
AttributeKey::Width,
|
HashSet::from_iter(vec![AttributeKey::Width, AttributeKey::Height,]);
|
||||||
AttributeKey::Height,
|
|
||||||
AttributeKey::Style,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@ -103,8 +100,6 @@ pub enum AttributeKey {
|
|||||||
Width,
|
Width,
|
||||||
#[display(fmt = "height")]
|
#[display(fmt = "height")]
|
||||||
Height,
|
Height,
|
||||||
#[display(fmt = "style")]
|
|
||||||
Style,
|
|
||||||
#[display(fmt = "header")]
|
#[display(fmt = "header")]
|
||||||
Header,
|
Header,
|
||||||
#[display(fmt = "left")]
|
#[display(fmt = "left")]
|
||||||
@ -131,6 +126,8 @@ impl AttributeKey {
|
|||||||
pub fn value<T: Into<AttributeValue>>(&self, value: T) -> Attribute {
|
pub fn value<T: Into<AttributeValue>>(&self, value: T) -> Attribute {
|
||||||
let key = self.clone();
|
let key = self.clone();
|
||||||
let value: AttributeValue = value.into();
|
let value: AttributeValue = value.into();
|
||||||
|
debug_assert_eq!(self.check_value(&value), true);
|
||||||
|
|
||||||
if INLINE_KEYS.contains(self) {
|
if INLINE_KEYS.contains(self) {
|
||||||
return Attribute {
|
return Attribute {
|
||||||
key,
|
key,
|
||||||
@ -153,6 +150,59 @@ impl AttributeKey {
|
|||||||
scope: AttributeScope::Ignore,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
@ -178,3 +178,9 @@ pub fn merge_attributes(mut attributes: Attributes, other: Attributes) -> Attrib
|
|||||||
attributes.extend(other);
|
attributes.extend(other);
|
||||||
attributes
|
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!(underline, AttributeKey::Underline);
|
||||||
impl_bool_attribute!(strike_through, AttributeKey::StrikeThrough);
|
impl_bool_attribute!(strike_through, AttributeKey::StrikeThrough);
|
||||||
impl_str_attribute!(link, AttributeKey::Link);
|
impl_str_attribute!(link, AttributeKey::Link);
|
||||||
|
// impl_str_attribute!(header, AttributeKey::Header);
|
||||||
|
|
||||||
pub fn build(self) -> Attributes { self.inner }
|
pub fn build(self) -> Attributes { self.inner }
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ impl<'a> DeltaIter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// find next op contains NEW_LINE
|
// 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;
|
let mut offset = 0;
|
||||||
while self.has_next() {
|
while self.has_next() {
|
||||||
if let Some(op) = self.next_op() {
|
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);
|
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]
|
#[test]
|
||||||
fn attributes_add_link() {
|
fn attributes_add_link() {
|
||||||
let ops = vec![
|
let ops = vec![
|
||||||
@ -638,6 +654,20 @@ fn attributes_auto_format_exist_link2() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn attributes_add_bullet() {
|
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![
|
let ops = vec![
|
||||||
Insert(0, "1", 0),
|
Insert(0, "1", 0),
|
||||||
Bullet(0, Interval::new(0, 1), true),
|
Bullet(0, Interval::new(0, 1), true),
|
||||||
@ -692,3 +722,43 @@ fn attributes_auto_exit_block() {
|
|||||||
|
|
||||||
OpTester::new().run_script_with_newline(ops);
|
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…
x
Reference in New Issue
Block a user