mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
config auto exit block extension
This commit is contained in:
parent
0fb808ef4c
commit
9bc72d3b9e
@ -45,7 +45,7 @@ class Rules {
|
||||
const ResolveInlineFormatRule(),
|
||||
// const InsertEmbedsRule(),
|
||||
// const ForceNewlineForInsertsAroundEmbedRule(),
|
||||
// const AutoExitBlockRule(),
|
||||
const AutoExitBlockRule(),
|
||||
// const PreserveBlockStyleOnInsertRule(),
|
||||
// const PreserveLineStyleOnSplitRule(),
|
||||
const ResetLineFormatOnNewLineRule(),
|
||||
|
@ -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),
|
||||
|
@ -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<Delta> {
|
||||
// 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
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
@ -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<Delta> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InsertEmbedsExt {}
|
||||
impl InsertExt for InsertEmbedsExt {
|
||||
fn ext_name(&self) -> &str { "InsertEmbedsExt" }
|
||||
|
@ -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 {}
|
||||
|
@ -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 }
|
||||
|
185
rust-lib/flowy-ot/src/core/attributes/attribute.rs
Normal file
185
rust-lib/flowy-ot/src/core/attributes/attribute.rs
Normal file
@ -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<AttributeKey> = HashSet::from_iter(vec![
|
||||
AttributeKey::Header,
|
||||
AttributeKey::Align,
|
||||
AttributeKey::List,
|
||||
AttributeKey::CodeBlock,
|
||||
AttributeKey::QuoteBlock,
|
||||
AttributeKey::Indent,
|
||||
]);
|
||||
static ref INLINE_KEYS: HashSet<AttributeKey> = 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<Attributes> 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<T: Into<AttributeValue>>(&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<str> 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<bool> for AttributeValue {
|
||||
fn from(val: bool) -> Self {
|
||||
let val = match val {
|
||||
true => "true",
|
||||
false => "",
|
||||
};
|
||||
AttributeValue(val.to_owned())
|
||||
}
|
||||
}
|
@ -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<str> 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<Attributes> 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<T: Into<AttributeValue>>(&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<bool> for AttributeValue {
|
||||
fn from(val: bool) -> Self {
|
||||
let val = match val {
|
||||
true => "true",
|
||||
false => "",
|
||||
};
|
||||
AttributeValue(val.to_owned())
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
mod attribute;
|
||||
mod attributes;
|
||||
mod attributes_serde;
|
||||
mod builder;
|
||||
|
||||
pub use attribute::*;
|
||||
pub use attributes::*;
|
||||
pub use builder::*;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user