Skip to content
This repository has been archived by the owner on Sep 27, 2024. It is now read-only.

Commit

Permalink
[Rust] Edit link with text + url
Browse files Browse the repository at this point in the history
  • Loading branch information
aringenbach committed Oct 12, 2023
1 parent 450db53 commit 6c42f29
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 21 deletions.
25 changes: 25 additions & 0 deletions bindings/wysiwyg-ffi/src/ffi_composer_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,31 @@ impl ComposerModel {
))
}

pub fn edit_link_with_text(
self: &Arc<Self>,
url: String,
text: String,
attributes: Vec<Attribute>,
) -> Arc<ComposerUpdate> {
let url = Utf16String::from_str(&url);
let text = Utf16String::from_str(&text);
let attrs = attributes
.iter()
.map(|attr| {
(
Utf16String::from_str(&attr.key),
Utf16String::from_str(&attr.value),
)
})
.collect();
Arc::new(ComposerUpdate::from(
self.inner
.lock()
.unwrap()
.edit_link_with_text(url, text, attrs),
))
}

/// Creates an at-room mention node and inserts it into the composer at the current selection
pub fn insert_at_room_mention(self: &Arc<Self>) -> Arc<ComposerUpdate> {
Arc::new(ComposerUpdate::from(
Expand Down
5 changes: 3 additions & 2 deletions bindings/wysiwyg-ffi/src/ffi_link_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use widestring::Utf16String;
pub enum LinkAction {
CreateWithText,
Create,
Edit { url: String },
Edit { url: String, text: String },
Disabled,
}

Expand All @@ -13,8 +13,9 @@ impl From<wysiwyg::LinkAction<Utf16String>> for LinkAction {
match inner {
wysiwyg::LinkAction::CreateWithText => Self::CreateWithText,
wysiwyg::LinkAction::Create => Self::Create,
wysiwyg::LinkAction::Edit(url) => Self::Edit {
wysiwyg::LinkAction::Edit(url, text) => Self::Edit {
url: url.to_string(),
text: text.to_string(),
},
wysiwyg::LinkAction::Disabled => Self::Disabled,
}
Expand Down
19 changes: 17 additions & 2 deletions bindings/wysiwyg-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,19 @@ impl ComposerModel {
))
}

pub fn edit_link_with_text(
&mut self,
url: &str,
text: &str,
attributes: js_sys::Map,
) -> ComposerUpdate {
ComposerUpdate::from(self.inner.edit_link_with_text(
Utf16String::from_str(url),
Utf16String::from_str(text),
attributes.into_vec(),
))
}

/// Creates an at-room mention node and inserts it into the composer at the current selection
pub fn insert_at_room_mention(
&mut self,
Expand Down Expand Up @@ -831,6 +844,7 @@ pub struct Create;
#[wasm_bindgen(getter_with_clone)]
pub struct Edit {
pub url: String,
pub text: String,
}

#[derive(Clone)]
Expand Down Expand Up @@ -860,12 +874,13 @@ impl From<wysiwyg::LinkAction<Utf16String>> for LinkAction {
edit_link: None,
disabled: None,
},
wysiwyg::LinkAction::Edit(url) => {
wysiwyg::LinkAction::Edit(url, text) => {
let url = url.to_string();
let text = text.to_string();
Self {
create_with_text: None,
create: None,
edit_link: Some(Edit { url }),
edit_link: Some(Edit { url, text }),
disabled: None,
}
}
Expand Down
29 changes: 28 additions & 1 deletion crates/wysiwyg/src/composer_model/hyperlinks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::dom::nodes::dom_node::DomNodeKind;
use crate::dom::nodes::dom_node::DomNodeKind::{Link, List};
use crate::dom::nodes::ContainerNodeKind;
use crate::dom::nodes::DomNode;
use crate::dom::to_plain_text::ToPlainText;
use crate::dom::unicode_string::UnicodeStrExt;
use crate::dom::Range;
use crate::{
Expand Down Expand Up @@ -53,7 +54,10 @@ where
LinkAction::Disabled
} else {
// Otherwise we edit the first link of the selection.
LinkAction::Edit(first_link.get_link_url().unwrap())
LinkAction::Edit(
first_link.get_link_url().unwrap(),
first_link.to_plain_text(),
)
}
} else if s == e || self.is_blank_selection(range) {
LinkAction::CreateWithText
Expand Down Expand Up @@ -121,6 +125,29 @@ where
self.set_link_in_range(url, range, attributes)
}

pub fn edit_link_with_text(
&mut self,
url: S,
text: S,
attributes: Vec<(S, S)>,
) -> ComposerUpdate<S> {
self.push_state_to_history();
let (s, e) = self.safe_selection();
let range = self.state.dom.find_range(s, e);
let Some(link_loc) = range
.locations
.iter()
.find(|loc| loc.kind == DomNodeKind::Link) else {
panic!("Attempting to edit a link on a range that doesn't contain one")
};
let start = link_loc.position;
let end = start + link_loc.length;
let new_end = start + text.len();
self.do_replace_text_in(text, start, end);
let range = self.state.dom.find_range(start, new_end);
self.set_link_in_range(url, range, attributes)
}

fn set_link_in_range(
&mut self,
mut url: S,
Expand Down
2 changes: 1 addition & 1 deletion crates/wysiwyg/src/link_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ pub enum LinkActionUpdate<S: UnicodeString> {
pub enum LinkAction<S: UnicodeString> {
CreateWithText,
Create,
Edit(S),
Edit(S, S),
Disabled,
}
30 changes: 15 additions & 15 deletions crates/wysiwyg/src/tests/test_get_link_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fn get_link_action_from_highlighted_link() {
let model = cm("{<a href=\"https://element.io\">test</a>}|");
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16("test"))
)
}

Expand All @@ -63,7 +63,7 @@ fn get_link_action_from_cursor_at_the_end_of_a_link() {
let model = cm("<a href=\"https://element.io\">test</a>|");
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16("test"))
)
}

Expand All @@ -72,7 +72,7 @@ fn get_link_action_from_cursor_inside_a_link() {
let model = cm("<a href=\"https://element.io\">te|st</a>");
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16("test"))
)
}

Expand All @@ -81,7 +81,7 @@ fn get_link_action_from_cursor_at_the_start_of_a_link() {
let model = cm("|<a href=\"https://element.io\">test</a>");
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16("test"))
)
}

Expand All @@ -90,7 +90,7 @@ fn get_link_action_from_selection_that_contains_a_link_and_non_links() {
let model = cm("<b>{test_bold <a href=\"https://element.io\">test}|_link</a> test_bold</b>");
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16("test_link"))
)
}

Expand All @@ -99,7 +99,7 @@ fn get_link_action_from_selection_that_contains_multiple_links() {
let model = cm("{<a href=\"https://element.io\">test_element</a> <a href=\"https://matrix.org\">test_matrix</a>}|");
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16("test_element"))
)
}

Expand All @@ -108,7 +108,7 @@ fn get_link_action_from_selection_that_contains_multiple_links_partially() {
let model = cm("<a href=\"https://element.io\">test_{element</a> <a href=\"https://matrix.org\">test}|_matrix</a>");
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16("test_element"))
)
}

Expand All @@ -118,7 +118,7 @@ fn get_link_action_from_selection_that_contains_multiple_links_partially_in_diff
let model = cm("<a href=\"https://element.io\"> <b>test_{element</b></a> <i><a href=\"https://matrix.org\">test}|_matrix</a></i>");
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16(" test_element"))
)
}

Expand Down Expand Up @@ -176,7 +176,7 @@ fn get_link_action_on_blank_selection_after_a_link() {
// This is the correct behaviour because the end of a link should be considered part of the link itself
assert_eq!(
model.get_link_action(),
LinkAction::Edit(utf16("https://element.io"))
LinkAction::Edit(utf16("https://element.io"), utf16("test"))
)
}

Expand Down Expand Up @@ -224,7 +224,7 @@ fn get_link_action_on_multiple_link_with_first_immutable() {
model.select(Location::from(20), Location::from(20));
assert_eq!(
model.get_link_action(),
LinkAction::Edit("https://rust-lang.org".into()),
LinkAction::Edit("https://rust-lang.org".into(), utf16("Rust_mut")),
);
}

Expand All @@ -240,7 +240,7 @@ fn get_link_action_on_multiple_link_with_last_immutable() {
model.select(Location::from(0), Location::from(0));
assert_eq!(
model.get_link_action(),
LinkAction::Edit("https://rust-lang.org".into()),
LinkAction::Edit("https://rust-lang.org".into(), utf16("Rust_mut")),
);
}

Expand Down Expand Up @@ -281,13 +281,13 @@ fn get_link_action_on_multiple_link_with_first_is_mention() {
"#});
assert_eq!(
model.get_link_action(),
LinkAction::Edit("https://rust-lang.org".into()),
LinkAction::Edit("https://rust-lang.org".into(), utf16("Rust_mut")),
);
// Selecting the link afterwards works
model.select(Location::from(10), Location::from(10));
assert_eq!(
model.get_link_action(),
LinkAction::Edit("https://rust-lang.org".into()),
LinkAction::Edit("https://rust-lang.org".into(), utf16("Rust_mut")),
);
}

Expand All @@ -300,12 +300,12 @@ fn get_link_action_on_multiple_link_with_last_is_mention() {
"#});
assert_eq!(
model.get_link_action(),
LinkAction::Edit("https://rust-lang.org".into()),
LinkAction::Edit("https://rust-lang.org".into(), utf16("Rust_mut")),
);
// Selecting the mutable link afterwards works
model.select(Location::from(0), Location::from(0));
assert_eq!(
model.get_link_action(),
LinkAction::Edit("https://rust-lang.org".into()),
LinkAction::Edit("https://rust-lang.org".into(), utf16("Rust_mut")),
);
}
94 changes: 94 additions & 0 deletions crates/wysiwyg/src/tests/test_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -943,3 +943,97 @@ fn set_links_in_list_then_add_list_item() {
"<ul><li><a href=\"https://matrix.org\">test</a></li><li>|</li></ul>"
);
}

#[test]
fn edit_entirely_selected_link() {
let mut model = cm("<a href=\"https://mtrix.org\">{Mtrix}|</a>");
model.edit_link_with_text(
"https://matrix.org".into(),
"Matrix".into(),
vec![],
);
assert_eq!(tx(&model), "<a href=\"https://matrix.org\">Matrix|</a>");
}

#[test]
fn edit_partially_selected_link() {
let mut model = cm("<a href=\"https://mtrix.org\">Mtr{ix}|</a>");
model.edit_link_with_text(
"https://matrix.org".into(),
"Matrix".into(),
vec![],
);
assert_eq!(tx(&model), "<a href=\"https://matrix.org\">Matrix|</a>");
}

#[test]
fn edit_link_from_cursor_position() {
let mut model = cm("<a href=\"https://mtrix.org\">Mtrix|</a>");
model.edit_link_with_text(
"https://matrix.org".into(),
"Matrix".into(),
vec![],
);
assert_eq!(tx(&model), "<a href=\"https://matrix.org\">Matrix|</a>");
}

#[test]
fn edit_link_with_multiple_links_edits_first_occurence() {
// Note: this behaviour might change if we allow replacing
// text with the entire extended plain text from the selection.
let mut model = cm("<a href=\"https://mtrix.org\">{Mtrix</a> <a href=\"https://matrix.org\">Matrix}|</a>");
model.edit_link_with_text(
"https://matrix.org".into(),
"Matrix".into(),
vec![],
);
assert_eq!(
tx(&model),
"<a href=\"https://matrix.org\">Matrix|</a> <a href=\"https://matrix.org\">Matrix</a>",
);
}

#[test]
fn edit_link_with_multiple_partially_selected_links_edits_first_occurence() {
// Note: this behaviour might change if we allow replacing
// text with the entire extended plain text from the selection.
let mut model = cm("<a href=\"https://mtrix.org\">Mt{rix</a> <a href=\"https://matrix.org\">Mat}|rix</a>");
model.edit_link_with_text(
"https://matrix.org".into(),
"Matrix".into(),
vec![],
);
assert_eq!(
tx(&model),
"<a href=\"https://matrix.org\">Matrix|</a> <a href=\"https://matrix.org\">Matrix</a>",
);
}

#[test]
fn edit_entirely_formatted_link_keeps_formatting() {
let mut model =
cm("<a href=\"https://mtrix.org\">{<strong>Mtrix</strong>}|</a>");
model.edit_link_with_text(
"https://matrix.org".into(),
"Matrix".into(),
vec![],
);
assert_eq!(
tx(&model),
"<a href=\"https://matrix.org\"><strong>Matrix|</strong></a>",
);
}

#[test]
fn edit_partially_formatted_link_removes_formatting() {
// Note: replacing the text of the link makes the formatting position ambiguous
// it is better to remove it than provide unexpected content.
let mut model =
cm("<a href=\"https://mtrix.org\">{M<strong>tri</strong>x}|</a>");
model.edit_link_with_text(
"https://matrix.org".into(),
"Matrix".into(),
vec![],
);
assert_eq!(tx(&model), "<a href=\"https://matrix.org\">Matrix|</a>");
}

0 comments on commit 6c42f29

Please sign in to comment.