Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions bindings/wysiwyg-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ impl ComposerModel {
)
}

pub fn replace_html(
&mut self,
new_html: &str,
external_source: HtmlSource,
) -> ComposerUpdate {
ComposerUpdate::from(self.inner.replace_html(
Utf16String::from_str(new_html),
external_source.into(),
))
}

pub fn replace_text_suggestion(
&mut self,
new_text: &str,
Expand Down Expand Up @@ -914,6 +925,24 @@ impl From<wysiwyg::LinkAction<Utf16String>> for LinkAction {
}
}

#[wasm_bindgen]
#[derive(Clone)]
pub enum HtmlSource {
Matrix,
GoogleDoc,
UnknownExternal,
}

impl From<HtmlSource> for wysiwyg::HtmlSource {
fn from(source: HtmlSource) -> Self {
match source {
HtmlSource::Matrix => Self::Matrix,
HtmlSource::GoogleDoc => Self::GoogleDoc,
HtmlSource::UnknownExternal => Self::UnknownExternal,
}
}
}

#[cfg(test)]
mod test {
use super::ComposerModel;
Expand Down
2 changes: 1 addition & 1 deletion crates/wysiwyg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ strum = "0.27"
strum_macros = "0.27"
unicode-segmentation = "1.7.1"
wasm-bindgen = { version = "0.2.83", default-features = false, optional = true }
web-sys = { version = "0.3.60", default-features = false, features = ["Document", "DomParser", "HtmlElement", "Node", "NodeList", "SupportedType"], optional = true }
web-sys = { version = "0.3.60", default-features = false, features = ["Document", "DomParser", "HtmlElement", "Node", "NodeList", "SupportedType", "CssStyleDeclaration"], optional = true }
widestring = "1.0.2"
indoc = "2.0"
url="2.3.1"
Expand Down
1 change: 1 addition & 0 deletions crates/wysiwyg/src/composer_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod menu_action;
pub mod menu_state;
pub mod new_lines;
pub mod quotes;
pub mod replace_html;
pub mod replace_text;
pub mod selection;
pub mod undo_redo;
Expand Down
3 changes: 2 additions & 1 deletion crates/wysiwyg/src/composer_model/example_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ mod test {
#[test]
fn selection_across_lists_roundtrips() {
assert_that!(
"<ol><li>1{1</li><li>22</li></ol><ol><li>33</li><li>4}|4</li></ol>"
"<ol><li>1{1</li><li>22</li></ol><p>a</p><ol><li>33</li><li>4}|4</li></ol>"
)
.roundtrips();
}
Expand All @@ -915,6 +915,7 @@ mod test {
<li>1{1</li>\
<li>22</li>\
</ol>\
<p>a</p>\
<ol>\
<li>33</li>\
<li>4}|4</li>\
Expand Down
293 changes: 293 additions & 0 deletions crates/wysiwyg/src/composer_model/replace_html.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/wysiwyg/src/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod dom_struct;
pub mod find_extended_range;
pub mod find_range;
pub mod find_result;
pub mod html_source;
pub mod insert_node_at_cursor;
pub mod insert_parent;
pub mod iter;
Expand All @@ -35,6 +36,7 @@ pub use dom_creation_error::MarkdownParseError;
pub use dom_handle::DomHandle;
pub use dom_struct::Dom;
pub use find_result::FindResult;
pub use html_source::HtmlSource;
pub use range::DomLocation;
pub use range::Range;
pub use to_html::ToHtml;
Expand Down
2 changes: 1 addition & 1 deletion crates/wysiwyg/src/dom/dom_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ where
}
}

fn merge_text_nodes_around(&mut self, handle: &DomHandle) {
pub fn merge_text_nodes_around(&mut self, handle: &DomHandle) {
// TODO: make this method not public because it is used to make
// the invariants true, instead of assuming they are true at the
// beginning!
Expand Down
26 changes: 20 additions & 6 deletions crates/wysiwyg/src/dom/dom_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::dom::nodes::{ContainerNode, DomNode};
use crate::dom::to_html::ToHtmlState;
use crate::dom::to_markdown::{MarkdownError, MarkdownOptions, ToMarkdown};
use crate::dom::unicode_string::UnicodeStrExt;
use crate::dom::DomLocation;
use crate::dom::{
find_range, to_raw_text::ToRawText, DomHandle, Range, ToTree, UnicodeString,
};
Expand Down Expand Up @@ -295,14 +296,24 @@ where
find_range::find_range(self, start, end)
}

pub fn find_range_by_node(&self, node_handle: &DomHandle) -> Range {
let result = find_range::find_pos(self, node_handle, 0, usize::MAX);
pub fn location_for_node(&self, node_handle: &DomHandle) -> DomLocation {
let locations = find_range::find_range(self, 0, usize::MAX);
locations.find_location(node_handle).unwrap().clone()
}

let locations = match result {
pub fn locations_for_node(
&self,
node_handle: &DomHandle,
) -> Vec<DomLocation> {
let result = find_range::find_pos(self, node_handle, 0, usize::MAX);
match result {
FindResult::Found(locations) => locations,
_ => panic!("Node does not exist"),
};
}
}

pub fn find_range_by_node(&self, node_handle: &DomHandle) -> Range {
let locations = self.locations_for_node(node_handle);
let leaves = locations.iter().filter(|l| l.is_leaf());

let s = leaves.clone().map(|l| l.position).min().unwrap();
Expand Down Expand Up @@ -924,12 +935,15 @@ mod test {
#[test]
fn find_parent_list_item_or_self_finds_our_grandparent() {
let d = cm("|<ul><li>b<strong>c</strong></li></ul>d").state.dom;
// The "|" at the start infers that when the dom is created, it be within a paragraph
// (as inline nodes and blocks are not allowed to be siblings).
// So the handle is [1, 0, 1, 0].
let res =
d.find_ancestor_list_item_or_self(&DomHandle::from_raw(vec![
0, 0, 1, 0,
1, 0, 1, 0,
]));
let res = res.expect("Should have found a list parent!");
assert_eq!(res.into_raw(), vec![0, 0]);
assert_eq!(res.into_raw(), vec![1, 0]);
}

#[test]
Expand Down
6 changes: 6 additions & 0 deletions crates/wysiwyg/src/dom/html_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum HtmlSource {
Matrix,
GoogleDoc,
UnknownExternal,
}
4 changes: 4 additions & 0 deletions crates/wysiwyg/src/dom/insert_node_at_cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ where
};
}

self.wrap_inline_nodes_into_paragraphs_if_needed(
&self.parent(&inserted_handle).handle(),
);

#[cfg(any(test, feature = "assert-invariants"))]
self.assert_invariants();

Expand Down
10 changes: 6 additions & 4 deletions crates/wysiwyg/src/dom/nodes/container_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1578,19 +1578,21 @@ mod test {

#[test]
fn paragraph_to_message_html() {
let model = cm("<p>&nbsp;</p><p>&nbsp;</p><p>Hello!</p><p>&nbsp;</p>|");
let model =
cm("<p>&nbsp;</p><p>&nbsp;</p><p>Hello!</p><p>&nbsp;</p><p>|</p>");
assert_eq!(
&model.state.dom.to_message_html(),
"<br /><br />Hello!<br />"
"<br /><br />Hello!<br /><br />"
);
}

#[test]
fn paragraph_to_html() {
let model = cm("<p>&nbsp;</p><p>&nbsp;</p><p>Hello!</p><p>&nbsp;</p>|");
let model =
cm("<p>&nbsp;</p><p>&nbsp;</p><p>Hello!</p><p>&nbsp;</p><p>|</p>");
assert_eq!(
&model.state.dom.to_html(),
"<p>\u{a0}</p><p>\u{a0}</p><p>Hello!</p><p>\u{a0}</p>"
"<p>\u{a0}</p><p>\u{a0}</p><p>Hello!</p><p>\u{a0}</p><p>\u{a0}</p>"
);
}

Expand Down
1 change: 1 addition & 0 deletions crates/wysiwyg/src/dom/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ mod sys {
use sys::*;

pub use parse::parse;
pub use parse::parse_from_source;
26 changes: 26 additions & 0 deletions crates/wysiwyg/src/dom/parser/panode_container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Please see LICENSE in the repository root for full details.

use html5ever::QualName;
use regex::Regex;

use super::PaDomHandle;

Expand All @@ -21,4 +22,29 @@ impl PaNodeContainer {
.find(|(n, _v)| n == name)
.map(|(_n, v)| v.as_str())
}

pub(crate) fn contains_style(&self, name: &str, value: &str) -> bool {
self.get_attr("style")
.map(|v| {
Regex::new(&format!(
r"(?i){}:\s*{};",
regex::escape(name),
regex::escape(value)
))
.map(|re| re.is_match(v))
.unwrap_or(false)
})
.unwrap_or(false)
}
}

#[test]
fn test_contains_style() {
let node = PaNodeContainer {
name: QualName::new(None, "div".into(), "div".into()),
attrs: vec![("style".into(), "font-weight:bold;".into())],
children: Vec::new(),
};
assert!(node.contains_style("font-weight", "bold"));
assert!(!node.contains_style("font-weight", "normal"));
}
1,302 changes: 984 additions & 318 deletions crates/wysiwyg/src/dom/parser/parse.rs

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions crates/wysiwyg/src/dom/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use crate::dom::dom_handle::DomHandle;
use crate::dom::nodes::dom_node::DomNodeKind;
use std::cmp::{min, Ordering};
use std::fmt;

/// Represents the relative position of a DomLocation towards
/// the range start and end.
Expand Down Expand Up @@ -202,6 +203,21 @@ impl DomLocation {
}
}

impl fmt::Display for DomLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"DomLocation[node_handle: {:?}, position: {}, start_offset: {}, end_offset: {}, length: {}, kind: {:?}]",
self.node_handle.raw(),
self.position,
self.start_offset,
self.end_offset,
self.length,
self.kind
)
}
}

impl PartialOrd<Self> for DomLocation {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
Expand Down
1 change: 1 addition & 0 deletions crates/wysiwyg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub use crate::dom::parser::parse;
pub use crate::dom::DomCreationError;
pub use crate::dom::DomHandle;
pub use crate::dom::HtmlParseError;
pub use crate::dom::HtmlSource;
pub use crate::dom::MarkdownParseError;
pub use crate::dom::ToHtml;
pub use crate::dom::ToRawText;
Expand Down
18 changes: 10 additions & 8 deletions crates/wysiwyg/src/tests/test_deleting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,12 @@ fn deleting_across_lists_joins_them() {
fn deleting_across_lists_joins_them_nested() {
let mut model = cm("<ol>\
<li>1{1</li>\
<li>22</li>\
<ol>\
<li>55</li>\
</ol>\
<li>
<p>22</p>
<ol>\
<li>55</li>\
</ol>\
</li>\
</ol>\
<ol>\
<li>33</li>\
Expand Down Expand Up @@ -876,7 +878,7 @@ fn backspace_immutable_link_from_inside_link() {
#[test]
fn backspace_immutable_link_multiple() {
let mut model = cm(
"<a contenteditable=\"false\" href=\"https://matrix.org\">first</a><a contenteditable=\"false\" href=\"https://matrix.org\">second|</a>",
"<a contenteditable=\"false\" href=\"https://matrix.org\">first</a><a contenteditable=\"false\" href=\"https://element.io.org\">second|</a>",
);
model.backspace();
assert_eq!(
Expand Down Expand Up @@ -954,12 +956,12 @@ fn delete_mention_from_start() {
#[test]
fn delete_first_immutable_link_of_multiple() {
let mut model = cm(
"<a contenteditable=\"false\" href=\"https://matrix.org\">|first</a><a contenteditable=\"false\" href=\"https://matrix.org\">second</a>",
"<a contenteditable=\"false\" href=\"https://matrix.org\">|first</a><a contenteditable=\"false\" href=\"https://element.io\">second</a>",
);
model.delete();
assert_eq!(
restore_whitespace(&tx(&model)),
"<a contenteditable=\"false\" href=\"https://matrix.org\">|second</a>"
"<a contenteditable=\"false\" href=\"https://element.io\">|second</a>"
);
model.delete();
assert_eq!(restore_whitespace(&tx(&model)), "|");
Expand All @@ -982,7 +984,7 @@ fn delete_first_mention_of_multiple() {
#[test]
fn delete_second_immutable_link_of_multiple() {
let mut model = cm(
"<a contenteditable=\"false\" href=\"https://matrix.org\">first</a><a contenteditable=\"false\" href=\"https://matrix.org\">second|</a>",
"<a contenteditable=\"false\" href=\"https://matrix.org\">first</a><a contenteditable=\"false\" href=\"https://element.io\">second|</a>",
);
model.backspace();
assert_eq!(
Expand Down
26 changes: 26 additions & 0 deletions platforms/web/cypress/e2e/clipboard/paste.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,30 @@ describe('Paste', () => {
// Note: we used to test it 'should convert pasted newlines into BRs' but
// the test was flakey, sometimes correctly showing text containing br tags,
// and sometimes mysteriously showing converted into two divs.

it(
'should display pasted richtext after we type',
{ browser: 'electron' },
() => {
cy.visit('/');
cy.get(editor).wait(500);
cy.get(editor).type('BEFORE');
cy.contains(editor, 'BEFORE');

cy.window().its('navigator.clipboard')
.then(async (clip) => {
const blob = new Blob(["<a href='https://matrix.org'>link</a>"], {type: 'text/html'});
const item = new ClipboardItem({'text/html': blob});
return await clip.write([item]);
})

cy.log("item");
cy.document().invoke('execCommand', 'paste');
cy.contains(editor, 'BEFORElink');

cy.get(editor).type('AFTER');
cy.contains(editor, /^BEFORElinkAFTER/);
},
);

});
Loading
Loading