add auto format extension
@ -1,4 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="15.7124" y="7.22705" width="1.5" height="12" rx="0.75" transform="rotate(45 15.7124 7.22705)" fill="#333333"/>
|
||||
<rect x="16.7729" y="15.7124" width="1.5" height="12" rx="0.75" transform="rotate(135 16.7729 15.7124)" fill="#333333"/>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="20.9497" y="9.63599" width="2" height="16" rx="1" transform="rotate(45 20.9497 9.63599)" fill="#333333"/>
|
||||
<rect x="22.364" y="20.95" width="2" height="16" rx="1" transform="rotate(135 22.364 20.95)" fill="#333333"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 328 B |
3
app_flowy/assets/images/home/Favorite/Active.svg
Executable file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 6L18.781 11.9243L25 12.8801L20.5 17.489L21.562 24L16 20.9243L10.438 24L11.5 17.489L7 12.8801L13.219 11.9243L16 6Z" fill="#FFD667" stroke="#FFD667" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 329 B |
3
app_flowy/assets/images/home/Favorite/Inactive.svg
Executable file
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 6L18.781 11.9243L25 12.8801L20.5 17.489L21.562 24L16 20.9243L10.438 24L11.5 17.489L7 12.8801L13.219 11.9243L16 6Z" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 314 B |
5
app_flowy/assets/images/home/Image.svg
Executable file
@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="6" width="26" height="20" rx="3" stroke="#333333" stroke-width="2"/>
|
||||
<circle cx="11" cy="13" r="2" stroke="#333333" stroke-width="2"/>
|
||||
<path d="M10 26L20.2239 16.9121C20.8422 16.3625 21.7349 16.2503 22.4699 16.6296L29 20" stroke="#333333" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 376 B |
@ -1,4 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.5 3.75V9C13.5 9.82843 14.1716 10.5 15 10.5H18" stroke="#333333" stroke-width="1.5"/>
|
||||
<path d="M5.25 5.25C5.25 4.42157 5.92157 3.75 6.75 3.75H12H12.75C13.6943 3.75 14.5834 4.19458 15.15 4.95L18.15 8.95C18.5395 9.46929 18.75 10.1009 18.75 10.75V12V18.75C18.75 19.5784 18.0784 20.25 17.25 20.25H6.75C5.92157 20.25 5.25 19.5784 5.25 18.75V5.25Z" stroke="#333333" stroke-width="1.5"/>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 5V12C18 13.1046 18.8954 14 20 14H24" stroke="#333333" stroke-width="2"/>
|
||||
<path d="M7 7C7 5.89543 7.89543 5 9 5H16H17C18.259 5 19.4446 5.59278 20.2 6.6L24.2 11.9333C24.7193 12.6257 25 13.4679 25 14.3333V16V25C25 26.1046 24.1046 27 23 27H9C7.89543 27 7 26.1046 7 25V7Z" stroke="#333333" stroke-width="2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 420 B |
@ -49,7 +49,7 @@ class Rules {
|
||||
// const PreserveBlockStyleOnInsertRule(),
|
||||
// const PreserveLineStyleOnSplitRule(),
|
||||
const ResetLineFormatOnNewLineRule(),
|
||||
// const AutoFormatLinksRule(),
|
||||
const AutoFormatLinksRule(),
|
||||
const PreserveInlineStylesRule(),
|
||||
const CatchAllInsertRule(),
|
||||
// const EnsureEmbedLineRule(),
|
||||
|
@ -93,7 +93,8 @@ class EditorController extends ChangeNotifier {
|
||||
toggledStyle = toggledStyle.put(attribute);
|
||||
}
|
||||
|
||||
final change = document.format(index, length, attribute);
|
||||
final change =
|
||||
document.format(index, length, LinkAttribute("www.baidu.com"));
|
||||
final adjustedSelection = selection.copyWith(
|
||||
baseOffset: change.transformPosition(selection.baseOffset),
|
||||
extentOffset: change.transformPosition(selection.extentOffset),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
client::view::DeleteExt,
|
||||
client::extensions::DeleteExt,
|
||||
core::{Delta, DeltaBuilder, Interval},
|
||||
};
|
||||
|
3
rust-lib/flowy-ot/src/client/extensions/delete/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod default_delete;
|
||||
|
||||
pub use default_delete::*;
|
@ -0,0 +1,49 @@
|
||||
use crate::{
|
||||
client::extensions::FormatExt,
|
||||
core::{Attribute, AttributeKey, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval},
|
||||
};
|
||||
|
||||
pub struct FormatLinkAtCaretPositionExt {}
|
||||
|
||||
impl FormatExt for FormatLinkAtCaretPositionExt {
|
||||
fn ext_name(&self) -> &str { "FormatLinkAtCaretPositionExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||
if attribute.key != AttributeKey::Link || interval.size() != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
|
||||
let (before, after) = (iter.next_op_before(interval.size()), iter.next());
|
||||
let mut start = interval.end;
|
||||
let mut retain = 0;
|
||||
|
||||
if let Some(before) = before {
|
||||
if before.contain_attribute(attribute) {
|
||||
start -= before.len();
|
||||
retain += before.len();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(after) = after {
|
||||
if after.contain_attribute(attribute) {
|
||||
if retain != 0 {
|
||||
retain += after.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if retain == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(start)
|
||||
.retain_with_attributes(retain, (attribute.clone()).into())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
39
rust-lib/flowy-ot/src/client/extensions/format/helper.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::{
|
||||
client::util::find_newline,
|
||||
core::{Attribute, AttributeScope, Attributes, Delta, Operation},
|
||||
};
|
||||
|
||||
pub(crate) fn line_break(op: &Operation, attribute: &Attribute, scope: AttributeScope) -> Delta {
|
||||
let mut new_delta = Delta::new();
|
||||
let mut start = 0;
|
||||
let end = op.len();
|
||||
let mut s = op.get_data();
|
||||
|
||||
while let Some(line_break) = find_newline(s) {
|
||||
match scope {
|
||||
AttributeScope::Inline => {
|
||||
new_delta.retain(line_break - start, attribute.clone().into());
|
||||
new_delta.retain(1, Attributes::empty());
|
||||
},
|
||||
AttributeScope::Block => {
|
||||
new_delta.retain(line_break - start, Attributes::empty());
|
||||
new_delta.retain(1, attribute.clone().into());
|
||||
},
|
||||
_ => {
|
||||
log::error!("Unsupported parser line break for {:?}", scope);
|
||||
},
|
||||
}
|
||||
|
||||
start = line_break + 1;
|
||||
s = &s[start..s.len()];
|
||||
}
|
||||
|
||||
if start < end {
|
||||
match scope {
|
||||
AttributeScope::Inline => new_delta.retain(end - start, attribute.clone().into()),
|
||||
AttributeScope::Block => new_delta.retain(end - start, Attributes::empty()),
|
||||
_ => log::error!("Unsupported parser line break for {:?}", scope),
|
||||
}
|
||||
}
|
||||
new_delta
|
||||
}
|
8
rust-lib/flowy-ot/src/client/extensions/format/mod.rs
Normal file
@ -0,0 +1,8 @@
|
||||
mod format_at_position;
|
||||
mod helper;
|
||||
mod resolve_block_format;
|
||||
mod resolve_inline_format;
|
||||
|
||||
pub use format_at_position::*;
|
||||
pub use resolve_block_format::*;
|
||||
pub use resolve_inline_format::*;
|
@ -0,0 +1,62 @@
|
||||
use crate::{
|
||||
client::{
|
||||
extensions::{format::helper::line_break, FormatExt},
|
||||
util::find_newline,
|
||||
},
|
||||
core::{
|
||||
Attribute,
|
||||
AttributeScope,
|
||||
Attributes,
|
||||
CharMetric,
|
||||
Delta,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
Interval,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ResolveBlockFormatExt {}
|
||||
impl FormatExt for ResolveBlockFormatExt {
|
||||
fn ext_name(&self) -> &str { "ResolveBlockFormatExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||
if attribute.scope != AttributeScope::Block {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
let mut start = 0;
|
||||
let end = interval.size();
|
||||
while start < end && iter.has_next() {
|
||||
let next_op = iter.next_op_before(end - start).unwrap();
|
||||
match find_newline(next_op.get_data()) {
|
||||
None => new_delta.retain(next_op.len(), Attributes::empty()),
|
||||
Some(_) => {
|
||||
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Block);
|
||||
new_delta.extend(tmp_delta);
|
||||
},
|
||||
}
|
||||
|
||||
start += next_op.len();
|
||||
}
|
||||
|
||||
while iter.has_next() {
|
||||
let op = iter
|
||||
.next_op()
|
||||
.expect("Unexpected None, iter.has_next() must return op");
|
||||
|
||||
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(1, attribute.clone().into());
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Some(new_delta)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
use crate::{
|
||||
client::{
|
||||
extensions::{format::helper::line_break, FormatExt},
|
||||
util::find_newline,
|
||||
},
|
||||
core::{Attribute, AttributeScope, CharMetric, Delta, DeltaBuilder, DeltaIter, Interval},
|
||||
};
|
||||
|
||||
pub struct ResolveInlineFormatExt {}
|
||||
impl FormatExt for ResolveInlineFormatExt {
|
||||
fn ext_name(&self) -> &str { "ResolveInlineFormatExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||
if attribute.scope != AttributeScope::Inline {
|
||||
return None;
|
||||
}
|
||||
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
|
||||
let mut start = 0;
|
||||
let end = interval.size();
|
||||
|
||||
while start < end && iter.has_next() {
|
||||
let next_op = iter.next_op_before(end - start).unwrap();
|
||||
match find_newline(next_op.get_data()) {
|
||||
None => new_delta.retain(next_op.len(), attribute.clone().into()),
|
||||
Some(_) => {
|
||||
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline);
|
||||
new_delta.extend(tmp_delta);
|
||||
},
|
||||
}
|
||||
|
||||
start += next_op.len();
|
||||
}
|
||||
|
||||
Some(new_delta)
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
use crate::{
|
||||
client::{extensions::InsertExt, util::is_whitespace},
|
||||
core::{CharMetric, Delta, DeltaIter},
|
||||
};
|
||||
|
||||
pub struct AutoFormatExt {}
|
||||
impl InsertExt for AutoFormatExt {
|
||||
fn ext_name(&self) -> &str { "AutoFormatExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, _replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
// enter whitespace to trigger auto format
|
||||
if !is_whitespace(text) {
|
||||
return None;
|
||||
}
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(index);
|
||||
let prev = iter.next_op();
|
||||
if prev.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let _prev = prev.unwrap();
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
use crate::{
|
||||
client::extensions::{InsertExt, NEW_LINE},
|
||||
core::{AttributeKey, Attributes, Delta, DeltaBuilder, DeltaIter},
|
||||
};
|
||||
|
||||
pub struct DefaultInsertExt {}
|
||||
impl InsertExt for DefaultInsertExt {
|
||||
fn ext_name(&self) -> &str { "DefaultInsertExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
let iter = DeltaIter::new(delta);
|
||||
let mut attributes = Attributes::new();
|
||||
|
||||
// Enable each line split by "\n" remains the block attributes. for example:
|
||||
// insert "\n" to "123456" at index 3
|
||||
//
|
||||
// [{"insert":"123"},{"insert":"\n","attributes":{"header":"1"}},
|
||||
// {"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]
|
||||
if text.ends_with(NEW_LINE) {
|
||||
match iter.last() {
|
||||
None => {},
|
||||
Some(op) => {
|
||||
if op.get_attributes().contains_key(&AttributeKey::Header) {
|
||||
attributes.extend(op.get_attributes());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
90
rust-lib/flowy-ot/src/client/extensions/insert/mod.rs
Normal file
@ -0,0 +1,90 @@
|
||||
pub use auto_format::*;
|
||||
pub use default_insert::*;
|
||||
pub use preserve_inline_style::*;
|
||||
pub use reset_format_on_new_line::*;
|
||||
|
||||
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},
|
||||
};
|
||||
|
||||
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" }
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &Delta,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<Delta> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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" }
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &Delta,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<Delta> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ForceNewlineForInsertsAroundEmbedExt {}
|
||||
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
|
||||
fn ext_name(&self) -> &str { "ForceNewlineForInsertsAroundEmbedExt" }
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &Delta,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<Delta> {
|
||||
None
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
use crate::{
|
||||
client::{
|
||||
extensions::InsertExt,
|
||||
util::{contain_newline, OpNewline},
|
||||
},
|
||||
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
|
||||
};
|
||||
|
||||
pub struct PreserveInlineStylesExt {}
|
||||
impl InsertExt for PreserveInlineStylesExt {
|
||||
fn ext_name(&self) -> &str { "PreserveInlineStylesExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
if contain_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
let prev = iter.next_op_before(index)?;
|
||||
if OpNewline::parse(&prev).is_contain() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut attributes = prev.get_attributes();
|
||||
if attributes.is_empty() || !attributes.contains_key(&AttributeKey::Link) {
|
||||
return Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
let next = iter.next_op();
|
||||
match &next {
|
||||
None => attributes = Attributes::empty(),
|
||||
Some(next) => {
|
||||
if OpNewline::parse(&next).is_equal() {
|
||||
attributes = Attributes::empty();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let new_delta = DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build();
|
||||
|
||||
return Some(new_delta);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
use crate::{
|
||||
client::{
|
||||
extensions::{InsertExt, NEW_LINE},
|
||||
util::is_newline,
|
||||
},
|
||||
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
|
||||
};
|
||||
|
||||
pub struct ResetLineFormatOnNewLineExt {}
|
||||
impl InsertExt for ResetLineFormatOnNewLineExt {
|
||||
fn ext_name(&self) -> &str { "ResetLineFormatOnNewLineExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
if !is_newline(text) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(index);
|
||||
let next_op = iter.next()?;
|
||||
if !next_op.get_data().starts_with(NEW_LINE) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut reset_attribute = Attributes::new();
|
||||
if next_op.get_attributes().contains_key(&AttributeKey::Header) {
|
||||
reset_attribute.add(AttributeKey::Header.value(""));
|
||||
}
|
||||
|
||||
let len = index + replace_len;
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(len)
|
||||
.insert_with_attributes(NEW_LINE, next_op.get_attributes())
|
||||
.retain_with_attributes(1, reset_attribute)
|
||||
.trim()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,5 +1,15 @@
|
||||
pub use delete::*;
|
||||
pub use format::*;
|
||||
pub use insert::*;
|
||||
|
||||
use crate::core::{Attribute, Delta, Interval};
|
||||
|
||||
mod delete;
|
||||
mod format;
|
||||
mod insert;
|
||||
|
||||
pub const NEW_LINE: &'static str = "\n";
|
||||
|
||||
pub type InsertExtension = Box<dyn InsertExt>;
|
||||
pub type FormatExtension = Box<dyn FormatExt>;
|
||||
pub type DeleteExtension = Box<dyn DeleteExt>;
|
@ -2,5 +2,9 @@ mod document;
|
||||
mod history;
|
||||
mod view;
|
||||
|
||||
pub mod extensions;
|
||||
mod util;
|
||||
|
||||
pub use document::*;
|
||||
pub use history::*;
|
||||
pub use view::*;
|
||||
|
66
rust-lib/flowy-ot/src/client/util.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use crate::{client::extensions::NEW_LINE, core::Operation};
|
||||
|
||||
#[inline]
|
||||
pub fn find_newline(s: &str) -> Option<usize> {
|
||||
match s.find(NEW_LINE) {
|
||||
None => None,
|
||||
Some(line_break) => Some(line_break),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum OpNewline {
|
||||
Start,
|
||||
End,
|
||||
Contain,
|
||||
Equal,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl OpNewline {
|
||||
pub fn parse(op: &Operation) -> OpNewline {
|
||||
let s = op.get_data();
|
||||
|
||||
if s == NEW_LINE {
|
||||
return OpNewline::Equal;
|
||||
}
|
||||
|
||||
if s.starts_with(NEW_LINE) {
|
||||
return OpNewline::Start;
|
||||
}
|
||||
|
||||
if s.ends_with(NEW_LINE) {
|
||||
return OpNewline::End;
|
||||
}
|
||||
|
||||
if s.contains(NEW_LINE) {
|
||||
return OpNewline::Contain;
|
||||
}
|
||||
|
||||
OpNewline::NotFound
|
||||
}
|
||||
|
||||
pub fn is_start(&self) -> bool { self == &OpNewline::Start }
|
||||
|
||||
pub fn is_end(&self) -> bool { self == &OpNewline::End }
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn is_equal(&self) -> bool { self == &OpNewline::Equal }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_op_contains_newline(op: &Operation) -> bool { contain_newline(op.get_data()) }
|
||||
|
||||
#[inline]
|
||||
pub fn is_newline(s: &str) -> bool { s == NEW_LINE }
|
||||
|
||||
#[inline]
|
||||
pub fn is_whitespace(s: &str) -> bool { s == " " }
|
||||
|
||||
#[inline]
|
||||
pub fn contain_newline(s: &str) -> bool { s.contains(NEW_LINE) }
|
@ -1,5 +1,5 @@
|
||||
use super::extensions::*;
|
||||
use crate::{
|
||||
client::view::*,
|
||||
core::{Attribute, Delta, Interval},
|
||||
errors::{ErrorBuilder, OTError, OTErrorCode},
|
||||
};
|
||||
@ -86,7 +86,7 @@ fn construct_insert_exts() -> Vec<InsertExtension> {
|
||||
Box::new(PreserveBlockStyleOnInsertExt {}),
|
||||
Box::new(PreserveLineStyleOnSplitExt {}),
|
||||
Box::new(ResetLineFormatOnNewLineExt {}),
|
||||
Box::new(AutoFormatLinksExt {}),
|
||||
Box::new(AutoFormatExt {}),
|
||||
Box::new(PreserveInlineStylesExt {}),
|
||||
Box::new(DefaultInsertExt {}),
|
||||
]
|
@ -1,173 +0,0 @@
|
||||
use crate::{
|
||||
client::view::{util::find_newline, FormatExt},
|
||||
core::{
|
||||
Attribute,
|
||||
AttributeKey,
|
||||
AttributeScope,
|
||||
Attributes,
|
||||
CharMetric,
|
||||
Delta,
|
||||
DeltaBuilder,
|
||||
DeltaIter,
|
||||
Interval,
|
||||
Operation,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct FormatLinkAtCaretPositionExt {}
|
||||
|
||||
impl FormatExt for FormatLinkAtCaretPositionExt {
|
||||
fn ext_name(&self) -> &str { "FormatLinkAtCaretPositionExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||
if attribute.key != AttributeKey::Link || interval.size() != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
|
||||
let (before, after) = (iter.next_op_before(interval.size()), iter.next());
|
||||
let mut start = interval.end;
|
||||
let mut retain = 0;
|
||||
|
||||
if let Some(before) = before {
|
||||
if before.contain_attribute(attribute) {
|
||||
start -= before.len();
|
||||
retain += before.len();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(after) = after {
|
||||
if after.contain_attribute(attribute) {
|
||||
if retain != 0 {
|
||||
retain += after.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if retain == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(start)
|
||||
.retain_with_attributes(retain, (attribute.clone()).into())
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResolveBlockFormatExt {}
|
||||
impl FormatExt for ResolveBlockFormatExt {
|
||||
fn ext_name(&self) -> &str { "ResolveBlockFormatExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||
if attribute.scope != AttributeScope::Block {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
let mut start = 0;
|
||||
let end = interval.size();
|
||||
while start < end && iter.has_next() {
|
||||
let next_op = iter.next_op_before(end - start).unwrap();
|
||||
match find_newline(next_op.get_data()) {
|
||||
None => new_delta.retain(next_op.len(), Attributes::empty()),
|
||||
Some(_) => {
|
||||
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Block);
|
||||
new_delta.extend(tmp_delta);
|
||||
},
|
||||
}
|
||||
|
||||
start += next_op.len();
|
||||
}
|
||||
|
||||
while iter.has_next() {
|
||||
let op = iter
|
||||
.next_op()
|
||||
.expect("Unexpected None, iter.has_next() must return op");
|
||||
|
||||
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(1, attribute.clone().into());
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Some(new_delta)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResolveInlineFormatExt {}
|
||||
impl FormatExt for ResolveInlineFormatExt {
|
||||
fn ext_name(&self) -> &str { "ResolveInlineFormatExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, interval: Interval, attribute: &Attribute) -> Option<Delta> {
|
||||
if attribute.scope != AttributeScope::Inline {
|
||||
return None;
|
||||
}
|
||||
let mut new_delta = DeltaBuilder::new().retain(interval.start).build();
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(interval.start);
|
||||
|
||||
let mut start = 0;
|
||||
let end = interval.size();
|
||||
|
||||
while start < end && iter.has_next() {
|
||||
let next_op = iter.next_op_before(end - start).unwrap();
|
||||
match find_newline(next_op.get_data()) {
|
||||
None => new_delta.retain(next_op.len(), attribute.clone().into()),
|
||||
Some(_) => {
|
||||
let tmp_delta = line_break(&next_op, attribute, AttributeScope::Inline);
|
||||
new_delta.extend(tmp_delta);
|
||||
},
|
||||
}
|
||||
|
||||
start += next_op.len();
|
||||
}
|
||||
|
||||
Some(new_delta)
|
||||
}
|
||||
}
|
||||
|
||||
fn line_break(op: &Operation, attribute: &Attribute, scope: AttributeScope) -> Delta {
|
||||
let mut new_delta = Delta::new();
|
||||
let mut start = 0;
|
||||
let end = op.len();
|
||||
let mut s = op.get_data();
|
||||
|
||||
while let Some(line_break) = find_newline(s) {
|
||||
match scope {
|
||||
AttributeScope::Inline => {
|
||||
new_delta.retain(line_break - start, attribute.clone().into());
|
||||
new_delta.retain(1, Attributes::empty());
|
||||
},
|
||||
AttributeScope::Block => {
|
||||
new_delta.retain(line_break - start, Attributes::empty());
|
||||
new_delta.retain(1, attribute.clone().into());
|
||||
},
|
||||
_ => {
|
||||
log::error!("Unsupported parser line break for {:?}", scope);
|
||||
},
|
||||
}
|
||||
|
||||
start = line_break + 1;
|
||||
s = &s[start..s.len()];
|
||||
}
|
||||
|
||||
if start < end {
|
||||
match scope {
|
||||
AttributeScope::Inline => new_delta.retain(end - start, attribute.clone().into()),
|
||||
AttributeScope::Block => new_delta.retain(end - start, Attributes::empty()),
|
||||
_ => log::error!("Unsupported parser line break for {:?}", scope),
|
||||
}
|
||||
}
|
||||
new_delta
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
use crate::{
|
||||
client::view::InsertExt,
|
||||
core::{AttributeKey, Attributes, CharMetric, Delta, DeltaBuilder, DeltaIter},
|
||||
};
|
||||
|
||||
pub const NEW_LINE: &'static str = "\n";
|
||||
|
||||
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" }
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &Delta,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<Delta> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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" }
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &Delta,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<Delta> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ForceNewlineForInsertsAroundEmbedExt {}
|
||||
impl InsertExt for ForceNewlineForInsertsAroundEmbedExt {
|
||||
fn ext_name(&self) -> &str { "ForceNewlineForInsertsAroundEmbedExt" }
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &Delta,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<Delta> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AutoFormatLinksExt {}
|
||||
impl InsertExt for AutoFormatLinksExt {
|
||||
fn ext_name(&self) -> &str { "AutoFormatLinksExt" }
|
||||
|
||||
fn apply(
|
||||
&self,
|
||||
_delta: &Delta,
|
||||
_replace_len: usize,
|
||||
_text: &str,
|
||||
_index: usize,
|
||||
) -> Option<Delta> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PreserveInlineStylesExt {}
|
||||
impl InsertExt for PreserveInlineStylesExt {
|
||||
fn ext_name(&self) -> &str { "PreserveInlineStylesExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
if text.contains(NEW_LINE) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
let prev = iter.next_op_before(index)?;
|
||||
if prev.get_data().contains(NEW_LINE) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut attributes = prev.get_attributes();
|
||||
if attributes.is_empty() || !attributes.contains_key(&AttributeKey::Link) {
|
||||
return Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
|
||||
attributes.remove(&AttributeKey::Link);
|
||||
let new_delta = DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build();
|
||||
|
||||
return Some(new_delta);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResetLineFormatOnNewLineExt {}
|
||||
impl InsertExt for ResetLineFormatOnNewLineExt {
|
||||
fn ext_name(&self) -> &str { "ResetLineFormatOnNewLineExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
if text != NEW_LINE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut iter = DeltaIter::new(delta);
|
||||
iter.seek::<CharMetric>(index);
|
||||
let next_op = iter.next()?;
|
||||
if !next_op.get_data().starts_with(NEW_LINE) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut reset_attribute = Attributes::new();
|
||||
if next_op.get_attributes().contains_key(&AttributeKey::Header) {
|
||||
reset_attribute.add(AttributeKey::Header.value(""));
|
||||
}
|
||||
|
||||
let len = index + replace_len;
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(len)
|
||||
.insert_with_attributes(NEW_LINE, next_op.get_attributes())
|
||||
.retain_with_attributes(1, reset_attribute)
|
||||
.trim()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultInsertExt {}
|
||||
impl InsertExt for DefaultInsertExt {
|
||||
fn ext_name(&self) -> &str { "DefaultInsertExt" }
|
||||
|
||||
fn apply(&self, delta: &Delta, replace_len: usize, text: &str, index: usize) -> Option<Delta> {
|
||||
let iter = DeltaIter::new(delta);
|
||||
let mut attributes = Attributes::new();
|
||||
|
||||
// Enable each line split by "\n" remains the block attributes. for example:
|
||||
// insert "\n" to "123456" at index 3
|
||||
//
|
||||
// [{"insert":"123"},{"insert":"\n","attributes":{"header":"1"}},
|
||||
// {"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]
|
||||
if text.ends_with(NEW_LINE) {
|
||||
match iter.last() {
|
||||
None => {},
|
||||
Some(op) => {
|
||||
if op.get_attributes().contains_key(&AttributeKey::Header) {
|
||||
attributes.extend(op.get_attributes());
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Some(
|
||||
DeltaBuilder::new()
|
||||
.retain(index + replace_len)
|
||||
.insert_with_attributes(text, attributes)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
mod delete_ext;
|
||||
mod extension;
|
||||
mod format_ext;
|
||||
mod insert_ext;
|
||||
mod util;
|
||||
mod view;
|
||||
|
||||
pub use delete_ext::*;
|
||||
pub use extension::*;
|
||||
pub use format_ext::*;
|
||||
pub use insert_ext::*;
|
||||
pub use view::*;
|
@ -1,8 +0,0 @@
|
||||
use crate::client::view::NEW_LINE;
|
||||
|
||||
pub fn find_newline(s: &str) -> Option<usize> {
|
||||
match s.find(NEW_LINE) {
|
||||
None => None,
|
||||
Some(line_break) => Some(line_break),
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ pub mod helper;
|
||||
use crate::helper::{TestOp::*, *};
|
||||
use flowy_ot::core::Interval;
|
||||
|
||||
use flowy_ot::client::extensions::NEW_LINE;
|
||||
|
||||
#[test]
|
||||
fn attributes_insert_text() {
|
||||
let ops = vec![
|
||||
@ -446,7 +448,7 @@ fn attributes_replace_with_text() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_add_header() {
|
||||
fn attributes_header_insert_newline_at_middle() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Header(0, Interval::new(0, 6), 1, true),
|
||||
@ -465,22 +467,7 @@ fn attributes_add_header() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_header_add_newline() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Header(0, Interval::new(0, 6), 1, true),
|
||||
Insert(0, "\n", 6),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":"1"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_header_add_newline_2() {
|
||||
fn attributes_header_insert_newline_at_middle2() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Header(0, Interval::new(0, 6), 1, true),
|
||||
@ -503,3 +490,100 @@ fn attributes_header_add_newline_2() {
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_header_insert_newline_at_trailing() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Header(0, Interval::new(0, 6), 1, true),
|
||||
Insert(0, "\n", 6),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":"1"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_add_link() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Link(0, Interval::new(0, 6), "https://appflowy.io", true),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_link_insert_char_at_head() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Link(0, Interval::new(0, 6), "https://appflowy.io", true),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
Insert(0, "a", 0),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"a"},{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_link_insert_char_at_middle() {
|
||||
let ops = vec![
|
||||
Insert(0, "1256", 0),
|
||||
Link(0, Interval::new(0, 4), "https://appflowy.io", true),
|
||||
Insert(0, "34", 2),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_link_insert_char_at_trailing() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Link(0, Interval::new(0, 6), "https://appflowy.io", true),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
Insert(0, "a", 6),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123456","attributes":{"link":"https://appflowy.io"}},{"insert":"a\n"}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_link_insert_newline_at_middle() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Link(0, Interval::new(0, 6), "https://appflowy.io", true),
|
||||
Insert(0, NEW_LINE, 3),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"},{"insert":"456","attributes":{"link":"https://appflowy.io"}},{"insert":"\n"}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ pub enum TestOp {
|
||||
#[display(fmt = "Header")]
|
||||
Header(usize, Interval, usize, bool),
|
||||
|
||||
#[display(fmt = "Link")]
|
||||
Link(usize, Interval, &'static str, bool),
|
||||
|
||||
#[display(fmt = "Transform")]
|
||||
Transform(usize, usize),
|
||||
|
||||
@ -74,44 +77,52 @@ impl OpTester {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
document.insert(*index, s).unwrap();
|
||||
},
|
||||
TestOp::Delete(delta_i, interval) => {
|
||||
TestOp::Delete(delta_i, iv) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
document.replace(*interval, "").unwrap();
|
||||
document.replace(*iv, "").unwrap();
|
||||
},
|
||||
TestOp::Replace(delta_i, interval, s) => {
|
||||
TestOp::Replace(delta_i, iv, s) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
document.replace(*interval, s).unwrap();
|
||||
document.replace(*iv, s).unwrap();
|
||||
},
|
||||
TestOp::InsertBold(delta_i, s, interval) => {
|
||||
TestOp::InsertBold(delta_i, s, iv) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
document.insert(interval.start, s).unwrap();
|
||||
document.insert(iv.start, s).unwrap();
|
||||
document
|
||||
.format(*interval, AttributeKey::Bold.value(true))
|
||||
.format(*iv, AttributeKey::Bold.value(true))
|
||||
.unwrap();
|
||||
},
|
||||
TestOp::Bold(delta_i, interval, enable) => {
|
||||
TestOp::Bold(delta_i, iv, enable) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let attribute = match *enable {
|
||||
true => AttributeKey::Bold.value(true),
|
||||
false => AttributeKey::Bold.remove(),
|
||||
};
|
||||
document.format(*interval, attribute).unwrap();
|
||||
document.format(*iv, attribute).unwrap();
|
||||
},
|
||||
TestOp::Italic(delta_i, interval, enable) => {
|
||||
TestOp::Italic(delta_i, iv, enable) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let attribute = match *enable {
|
||||
true => AttributeKey::Italic.value("true"),
|
||||
false => AttributeKey::Italic.remove(),
|
||||
};
|
||||
document.format(*interval, attribute).unwrap();
|
||||
document.format(*iv, attribute).unwrap();
|
||||
},
|
||||
TestOp::Header(delta_i, interval, level, enable) => {
|
||||
TestOp::Header(delta_i, iv, level, enable) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let attribute = match *enable {
|
||||
true => AttributeKey::Header.value(level),
|
||||
false => AttributeKey::Header.remove(),
|
||||
};
|
||||
document.format(*interval, attribute).unwrap();
|
||||
document.format(*iv, attribute).unwrap();
|
||||
},
|
||||
TestOp::Link(delta_i, iv, link, enable) => {
|
||||
let document = &mut self.documents[*delta_i];
|
||||
let attribute = match *enable {
|
||||
true => AttributeKey::Link.value(link.to_owned()),
|
||||
false => AttributeKey::Link.remove(),
|
||||
};
|
||||
document.format(*iv, attribute).unwrap();
|
||||
},
|
||||
TestOp::Transform(delta_a_i, delta_b_i) => {
|
||||
let (a_prime, b_prime) = self.documents[*delta_a_i]
|
||||
|
@ -4,7 +4,7 @@ use crate::helper::{TestOp::*, *};
|
||||
use flowy_ot::{client::RECORD_THRESHOLD, core::Interval};
|
||||
|
||||
#[test]
|
||||
fn delta_undo_insert() {
|
||||
fn history_undo_insert() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Undo(0),
|
||||
@ -14,7 +14,7 @@ fn delta_undo_insert() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_undo_insert2() {
|
||||
fn history_undo_insert2() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Wait(RECORD_THRESHOLD),
|
||||
@ -28,7 +28,7 @@ fn delta_undo_insert2() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_redo_insert() {
|
||||
fn history_redo_insert() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
AssertOpsJson(0, r#"[{"insert":"123\n"}]"#),
|
||||
@ -41,7 +41,7 @@ fn delta_redo_insert() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_redo_insert_with_lagging() {
|
||||
fn history_redo_insert_with_lagging() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Wait(RECORD_THRESHOLD),
|
||||
@ -60,7 +60,7 @@ fn delta_redo_insert_with_lagging() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_undo_attributes() {
|
||||
fn history_undo_attributes() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Bold(0, Interval::new(0, 3), true),
|
||||
@ -71,7 +71,7 @@ fn delta_undo_attributes() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_undo_attributes_with_lagging() {
|
||||
fn history_undo_attributes_with_lagging() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Wait(RECORD_THRESHOLD),
|
||||
@ -83,7 +83,7 @@ fn delta_undo_attributes_with_lagging() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_redo_attributes() {
|
||||
fn history_redo_attributes() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Bold(0, Interval::new(0, 3), true),
|
||||
@ -99,7 +99,7 @@ fn delta_redo_attributes() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_redo_attributes_with_lagging() {
|
||||
fn history_redo_attributes_with_lagging() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Wait(RECORD_THRESHOLD),
|
||||
@ -116,7 +116,7 @@ fn delta_redo_attributes_with_lagging() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_undo_delete() {
|
||||
fn history_undo_delete() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
AssertOpsJson(0, r#"[{"insert":"123"}]"#),
|
||||
@ -129,7 +129,7 @@ fn delta_undo_delete() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_undo_delete2() {
|
||||
fn history_undo_delete2() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Bold(0, Interval::new(0, 3), true),
|
||||
@ -148,7 +148,7 @@ fn delta_undo_delete2() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_undo_delete2_with_lagging() {
|
||||
fn history_undo_delete2_with_lagging() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Wait(RECORD_THRESHOLD),
|
||||
@ -175,7 +175,7 @@ fn delta_undo_delete2_with_lagging() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_redo_delete() {
|
||||
fn history_redo_delete() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Wait(RECORD_THRESHOLD),
|
||||
@ -189,7 +189,7 @@ fn delta_redo_delete() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_undo_replace() {
|
||||
fn history_undo_replace() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Bold(0, Interval::new(0, 3), true),
|
||||
@ -208,7 +208,7 @@ fn delta_undo_replace() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_undo_replace_with_lagging() {
|
||||
fn history_undo_replace_with_lagging() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Wait(RECORD_THRESHOLD),
|
||||
@ -232,7 +232,7 @@ fn delta_undo_replace_with_lagging() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delta_redo_replace() {
|
||||
fn history_redo_replace() {
|
||||
let ops = vec![
|
||||
Insert(0, "123", 0),
|
||||
Bold(0, Interval::new(0, 3), true),
|
||||
@ -249,3 +249,21 @@ fn delta_redo_replace() {
|
||||
];
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn history_undo_add_header() {
|
||||
let ops = vec![
|
||||
Insert(0, "123456", 0),
|
||||
Header(0, Interval::new(0, 6), 1, true),
|
||||
Insert(0, "\n", 3),
|
||||
Insert(0, "\n", 4),
|
||||
Undo(0),
|
||||
Redo(0),
|
||||
AssertOpsJson(
|
||||
0,
|
||||
r#"[{"insert":"123"},{"insert":"\n\n","attributes":{"header":"1"}},{"insert":"456"},{"insert":"\n","attributes":{"header":"1"}}]"#,
|
||||
),
|
||||
];
|
||||
|
||||
OpTester::new().run_script_with_newline(ops);
|
||||
}
|
||||
|