Skip to content

Commit

Permalink
Bindings & fix corner cases in block nodes (code block, quotes) with …
Browse files Browse the repository at this point in the history
…new lines (#442)

* Bindings and fixes for mobile platforms

* Fix inserting characters between 2 line breaks/ZWSP nodes.

* Improve the way line breaks are added/removed for quotes and code blocks.

* Fix double enter press in empty code block.

* Simplify adding line breaks to code blocks
  • Loading branch information
jmartinesp authored Jan 4, 2023
1 parent 6c01278 commit 87a53e3
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 85 deletions.
10 changes: 10 additions & 0 deletions bindings/wysiwyg-ffi/src/ffi_composer_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ impl ComposerModel {
))
}

pub fn code_block(self: &Arc<Self>) -> Arc<ComposerUpdate> {
Arc::new(ComposerUpdate::from(
self.inner.lock().unwrap().code_block(),
))
}

pub fn quote(self: &Arc<Self>) -> Arc<ComposerUpdate> {
Arc::new(ComposerUpdate::from(self.inner.lock().unwrap().quote()))
}

pub fn ordered_list(self: &Arc<Self>) -> Arc<ComposerUpdate> {
Arc::new(ComposerUpdate::from(
self.inner.lock().unwrap().ordered_list(),
Expand Down
2 changes: 2 additions & 0 deletions bindings/wysiwyg-ffi/src/wysiwyg_composer.udl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ interface ComposerModel {
ComposerUpdate set_link(string link);
ComposerUpdate set_link_with_text(string link, string text);
ComposerUpdate remove_links();
ComposerUpdate code_block();
ComposerUpdate quote();
string to_tree();
ComposerState get_current_dom_state();
record<ComposerAction, ActionState> action_states();
Expand Down
70 changes: 63 additions & 7 deletions crates/wysiwyg/src/composer_model/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,20 @@ where
fn add_code_block(&mut self) -> ComposerUpdate<S> {
let (s, e) = self.safe_selection();
let Some(wrap_result) = self.state.dom.find_nodes_to_wrap_in_block(s, e) else {
// No suitable nodes found to be wrapped inside the code block. The Dom should be empty
self.state.dom.append_at_end_of_document(DomNode::new_code_block(vec![DomNode::new_zwsp()]));
// No suitable nodes found to be wrapped inside the code block. Add an empty block.
let range = self.state.dom.find_range(s, e);
let leaves: Vec<&DomLocation> = range.leaves().collect();
if leaves.is_empty() {
self.state.dom.append_at_end_of_document(DomNode::new_code_block(vec![DomNode::new_zwsp()]));
} else {
let first_leaf_loc = leaves.first().unwrap();
let insert_at = if first_leaf_loc.is_start() {
first_leaf_loc.node_handle.next_sibling()
} else {
first_leaf_loc.node_handle.clone()
};
self.state.dom.insert_at(&insert_at, DomNode::new_code_block(vec![DomNode::new_zwsp()]));
}
self.state.start += 1;
self.state.end += 1;
return self.create_update_replace_all();
Expand Down Expand Up @@ -168,11 +180,23 @@ where
let end_in_block = code_block_location.end_offset;
let mut selection_offset_start = 0;
let mut selection_offset_end = 0;
self.state.start += 1;
self.state.end += 1;

// If we remove the whole code block, we should start by adding a line break
let mut nodes_to_add = vec![DomNode::new_line_break()];
// If we remove the whole code block and it's not the first child, we should start by adding a line break
let has_previous_line_break = self
.state
.dom
.prev_leaf(&code_block_location.node_handle)
.map_or(false, |n| n.is_line_break());
let mut nodes_to_add =
if code_block_location.node_handle.index_in_parent() > 0
&& !has_previous_line_break
{
self.state.start += 1;
self.state.end += 1;
vec![DomNode::new_line_break()]
} else {
Vec::new()
};
// Just remove everything
let DomNode::Container(block) =
self.state.dom.lookup_node(&code_block_location.node_handle) else
Expand Down Expand Up @@ -219,9 +243,14 @@ where
}
}

let has_next_line_break = self
.state
.dom
.next_leaf(&code_block_location.node_handle)
.map_or(false, |n| n.is_line_break());
// Add a final line break if needed
if let Some(last_node) = nodes_to_add.last() {
if last_node.kind() != LineBreak {
if last_node.kind() != LineBreak && !has_next_line_break {
nodes_to_add.push(DomNode::new_line_break());
}
}
Expand Down Expand Up @@ -578,4 +607,31 @@ mod test {
model.code_block();
assert_eq!(tx(&model), "Test<br /><pre>~|</pre>");
}

#[test]
fn creating_and_removing_code_block_works() {
let mut model = cm("|");
model.code_block();
assert_eq!(tx(&model), "<pre>~|</pre>");
model.code_block();
assert_eq!(tx(&model), "|");
}

#[test]
fn removing_code_block_doesnt_add_extra_line_break_at_start() {
let mut model = cm("<br />|");
model.code_block();
assert_eq!(tx(&model), "<br /><pre>~|</pre>");
model.code_block();
assert_eq!(tx(&model), "<br />|");
}

#[test]
fn removing_code_block_doesnt_add_extra_line_break_at_end() {
let mut model = cm("|<br />");
model.code_block();
assert_eq!(tx(&model), "<pre>~|</pre><br />");
model.code_block();
assert_eq!(tx(&model), "|<br />");
}
}
24 changes: 23 additions & 1 deletion crates/wysiwyg/src/composer_model/quotes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

use crate::dom::nodes::dom_node::DomNodeKind::{Generic, Quote};
use crate::dom::DomLocation;
use crate::{
ComposerAction, ComposerModel, ComposerUpdate, DomHandle, DomNode,
UnicodeString,
Expand All @@ -35,7 +36,19 @@ where
let Some(wrap_result) = self.state.dom.find_nodes_to_wrap_in_block(s, e) else {
// No nodes to be wrapped found.
// Adding an empty Quote block with an a single ZWSP
self.state.dom.append_at_end_of_document(DomNode::new_quote(vec![DomNode::new_text(S::zwsp())]));
let range = self.state.dom.find_range(s, e);
let leaves: Vec<&DomLocation> = range.leaves().collect();
if leaves.is_empty() {
self.state.dom.append_at_end_of_document(DomNode::new_quote(vec![DomNode::new_zwsp()]));
} else {
let first_leaf_loc = leaves.first().unwrap();
let insert_at = if first_leaf_loc.is_start() {
first_leaf_loc.node_handle.next_sibling()
} else {
first_leaf_loc.node_handle.clone()
};
self.state.dom.insert_at(&insert_at, DomNode::new_quote(vec![DomNode::new_zwsp()]));
}
self.state.start += 1;
self.state.end += 1;
return self.create_update_replace_all();
Expand Down Expand Up @@ -304,4 +317,13 @@ mod test {
model.quote();
assert_eq!(tx(&model), "<pre>~Some| code</pre>");
}

#[test]
fn create_and_remove_quote() {
let mut model = cm("|");
model.quote();
assert_eq!(tx(&model), "<blockquote>~|</blockquote>");
model.quote();
assert_eq!(tx(&model), "|");
}
}
63 changes: 34 additions & 29 deletions crates/wysiwyg/src/composer_model/replace_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ where

let mut has_previous_line_break = false;
let mut selection_offset = 0;
let mut code_block_is_empty = false;
if leaf.start_offset > 0 {
if let DomNode::Text(text_node) =
self.state.dom.lookup_node_mut(&leaf.node_handle)
Expand All @@ -198,10 +199,25 @@ where
}
}
}
if has_previous_line_break {
let block =
self.state.dom.lookup_node(&block_location.node_handle);
let block_len = block.text_len();
if block_len == 0
|| (block_len == 1 && block.has_leading_zwsp())
{
code_block_is_empty = true;
}
}
}

// If there was a previous line break, we need to split the code block and add the line break
if has_previous_line_break {
if code_block_is_empty {
self.state.dom.replace(
&block_location.node_handle,
vec![DomNode::new_line_break()],
);
self.create_update_replace_all()
} else if has_previous_line_break {
let mut sub_tree = self.state.dom.split_sub_tree_from(
&leaf.node_handle,
leaf.start_offset - selection_offset,
Expand All @@ -213,7 +229,10 @@ where
};

let new_line_at_end = leaf.is_start();
if !sub_tree_container.has_leading_zwsp() {

if !sub_tree_container.is_empty()
&& !sub_tree_container.has_leading_zwsp()
{
sub_tree_container.insert_child(0, DomNode::new_zwsp());
}

Expand All @@ -235,7 +254,9 @@ where
} else {
let next_handle =
block_location.node_handle.next_sibling();
if !sub_tree_container.children().is_empty() {
if !sub_tree_container.is_empty()
&& !sub_tree_container.only_contains_zwsp()
{
self.state.dom.insert_at(
&next_handle,
DomNode::new_code_block(
Expand Down Expand Up @@ -279,35 +300,19 @@ where
if block.children().is_empty() {
// If it's the first character, we need to add 2 line breaks instead, since the
// first one will be automatically removed by any HTML parser
self.state.dom.insert_at(
&leaf.node_handle,
DomNode::new_text("\n\n".into()),
self.replace_text("\n\n".into())
} else {
let cursor_pos = leaf.position + leaf.start_offset;
// I'd call `self.state.replace_text` here too, but we'd end up calling
// this same code recursively
self.state.dom.replace_text_in(
"\n".into(),
cursor_pos,
cursor_pos,
);
self.state.start += 2;
self.state.end = self.state.start;
self.create_update_replace_all()
} else if let DomNode::Text(text_node) =
self.state.dom.lookup_node_mut(&leaf.node_handle)
{
// Just add the line break and move the selection
let mut new_data = text_node.data().to_owned();
new_data.insert(leaf.start_offset, &S::from("\n"));
text_node.set_data(new_data);
self.state.start += 1;
self.state.end = self.state.start;
self.create_update_replace_all()
} else if let DomNode::Zwsp(_) =
self.state.dom.lookup_node(&leaf.node_handle)
{
let text_node = DomNode::new_text("\n".into());
self.state
.dom
.insert_at(&leaf.node_handle.next_sibling(), text_node);
self.state.start += 1;
self.state.end = self.state.start;
self.create_update_replace_all()
} else {
ComposerUpdate::keep()
}
}
}
Expand Down
Loading

0 comments on commit 87a53e3

Please # to comment.