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

Commit

Permalink
Add mention insertion methods (#698)
Browse files Browse the repository at this point in the history
* add mention insertion methods
* use new methods and implement in web
* use new methods and implement in iOS
* use new methods and implement in android
---------

Co-authored-by: jonnyandrew <[email protected]>
Co-authored-by: aringenbach <[email protected]>
Co-authored-by: aringenbach <[email protected]>
  • Loading branch information
4 people authored Jun 12, 2023
1 parent aec22de commit b431568
Show file tree
Hide file tree
Showing 70 changed files with 1,714 additions and 549 deletions.
32 changes: 27 additions & 5 deletions bindings/wysiwyg-ffi/src/ffi_composer_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,32 @@ impl ComposerModel {
))
}

/// This function creates a link with the first argument being the href, the second being the
/// display text, the third being the (rust model) suggestion that is being replaced and the
/// final argument being a list of attributes that will be added to the Link.
pub fn set_link_suggestion(
/// Creates a mention node and inserts it into the composer at the current selection
pub fn insert_mention(
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().insert_mention(url, text, attrs),
))
}

/// Creates a mention node and inserts it into the composer, replacing the
/// text content defined by the suggestion
pub fn insert_mention_at_suggestion(
self: &Arc<Self>,
url: String,
text: String,
Expand All @@ -284,7 +306,7 @@ impl ComposerModel {
self.inner
.lock()
.unwrap()
.set_link_suggestion(url, text, suggestion, attrs),
.insert_mention_at_suggestion(url, text, suggestion, attrs),
))
}

Expand Down
130 changes: 107 additions & 23 deletions bindings/wysiwyg-ffi/src/ffi_composer_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,35 +121,119 @@ mod test {
}

#[test]
fn test_set_link_suggestion_ffi() {
let model = Arc::new(ComposerModel::new());
let update = model.replace_text("@alic".into());
fn test_replace_whole_suggestion_with_mention_ffi() {
let mut model = Arc::new(ComposerModel::new());

insert_mention_at_cursor(&mut model);

assert_eq!(
model.get_content_as_html(),
"<a data-mention-type=\"user\" href=\"https://matrix.to/#/@alice:matrix.org\" contenteditable=\"false\">Alice</a>\u{a0}",
)
}

#[test]
fn test_replace_end_of_text_node_with_mention_ffi() {
let mut model = Arc::new(ComposerModel::new());
model.replace_text("hello ".into());

insert_mention_at_cursor(&mut model);

assert_eq!(
model.get_content_as_html(),
"hello <a data-mention-type=\"user\" href=\"https://matrix.to/#/@alice:matrix.org\" contenteditable=\"false\">Alice</a>\u{a0}",
)
}

#[test]
fn test_replace_start_of_text_node_with_mention_ffi() {
let mut model = Arc::new(ComposerModel::new());
model.replace_text(" says hello".into());
model.select(0, 0);

insert_mention_at_cursor(&mut model);

assert_eq!(
model.get_content_as_html(),
"<a data-mention-type=\"user\" href=\"https://matrix.to/#/@alice:matrix.org\" contenteditable=\"false\">Alice</a> says hello",
)
}

#[test]
fn test_replace_text_in_middle_of_node_with_mention_ffi() {
let mut model = Arc::new(ComposerModel::new());
model.replace_text("Like said".into());
model.select(5, 5); // "Like | said"

insert_mention_at_cursor(&mut model);

assert_eq!(
model.get_content_as_html(),
"Like <a data-mention-type=\"user\" href=\"https://matrix.to/#/@alice:matrix.org\" contenteditable=\"false\">Alice</a> said",
)
}

#[test]
fn test_replace_text_in_second_paragraph_node_with_mention_ffi() {
let mut model = Arc::new(ComposerModel::new());
model.replace_text("hello".into());
model.enter();
insert_mention_at_cursor(&mut model);

assert_eq!(
model.get_content_as_html(),
"<p>hello</p><p><a data-mention-type=\"user\" href=\"https://matrix.to/#/@alice:matrix.org\" contenteditable=\"false\">Alice</a>\u{a0}</p>",
)
}

#[test]
fn test_replace_text_in_second_list_item_start_with_mention_ffi() {
let mut model = Arc::new(ComposerModel::new());

model.ordered_list();
model.replace_text("hello".into());
model.enter();

insert_mention_at_cursor(&mut model);

assert_eq!(
model.get_content_as_html(),
"<ol><li>hello</li><li><a data-mention-type=\"user\" href=\"https://matrix.to/#/@alice:matrix.org\" contenteditable=\"false\">Alice</a>\u{a0}</li></ol>",
)
}

#[test]
fn test_replace_text_in_second_list_item_end_with_mention_ffi() {
let mut model = Arc::new(ComposerModel::new());
model.ordered_list();
model.replace_text("hello".into());
model.enter();
model.replace_text("there ".into());

let MenuAction::Suggestion { suggestion_pattern } =
update.menu_action() else
{
panic!("No suggestion found");
};
insert_mention_at_cursor(&mut model);

model.set_link_suggestion(
assert_eq!(
model.get_content_as_html(),
"<ol><li>hello</li><li>there <a data-mention-type=\"user\" href=\"https://matrix.to/#/@alice:matrix.org\" contenteditable=\"false\">Alice</a>\u{a0}</li></ol>",
)
}

// TODO remove attributes when Rust model can parse url directly
// https://github.com/matrix-org/matrix-rich-text-editor/issues/709
fn insert_mention_at_cursor(model: &mut Arc<ComposerModel>) {
let update = model.replace_text("@alic".into());
let MenuAction::Suggestion{suggestion_pattern} = update.menu_action() else {
panic!("No suggestion pattern found")
};
model.insert_mention_at_suggestion(
"https://matrix.to/#/@alice:matrix.org".into(),
"Alice".into(),
suggestion_pattern,
vec![
Attribute {
key: "contenteditable".into(),
value: "false".into(),
},
Attribute {
key: "data-mention-type".into(),
value: "user".into(),
},
],
vec![Attribute {
key: "data-mention-type".into(),
value: "user".into(),
}],
);
assert_eq!(
model.get_content_as_html(),
"<a contenteditable=\"false\" data-mention-type=\"user\" href=\"https://matrix.to/#/@alice:matrix.org\">Alice</a>\u{a0}",
)
}

fn redo_indent_unindent_disabled() -> HashMap<ComposerAction, ActionState> {
Expand Down
3 changes: 2 additions & 1 deletion bindings/wysiwyg-ffi/src/wysiwyg_composer.udl
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ interface ComposerModel {
ComposerUpdate unindent();
ComposerUpdate set_link(string url, sequence<Attribute> attributes);
ComposerUpdate set_link_with_text(string url, string text, sequence<Attribute> attributes);
ComposerUpdate set_link_suggestion(string url, string text, SuggestionPattern suggestion, sequence<Attribute> attributes);
ComposerUpdate remove_links();
ComposerUpdate insert_mention(string url, string text, sequence<Attribute> attributes);
ComposerUpdate insert_mention_at_suggestion(string url, string text, SuggestionPattern suggestion, sequence<Attribute> attributes);
ComposerUpdate code_block();
ComposerUpdate quote();
void debug_panic();
Expand Down
23 changes: 18 additions & 5 deletions bindings/wysiwyg-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,17 +312,30 @@ impl ComposerModel {
))
}

/// This function creates a link with the first argument being the href, the second being the
/// display text, the third being the (rust model) suggestion that is being replaced and the
/// final argument being a map of html attributes that will be added to the Link.
pub fn set_link_suggestion(
/// Creates a mention node and inserts it into the composer at the current selection
pub fn insert_mention(
&mut self,
url: &str,
text: &str,
attributes: js_sys::Map,
) -> ComposerUpdate {
ComposerUpdate::from(self.inner.insert_mention(
Utf16String::from_str(url),
Utf16String::from_str(text),
attributes.into_vec(),
))
}

/// Creates a mention node and inserts it into the composer, replacing the
/// text content defined by the suggestion
pub fn insert_mention_at_suggestion(
&mut self,
url: &str,
text: &str,
suggestion: &SuggestionPattern,
attributes: js_sys::Map,
) -> ComposerUpdate {
ComposerUpdate::from(self.inner.set_link_suggestion(
ComposerUpdate::from(self.inner.insert_mention_at_suggestion(
Utf16String::from_str(url),
Utf16String::from_str(text),
wysiwyg::SuggestionPattern::from(suggestion.clone()),
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 @@ -20,6 +20,7 @@ pub mod format;
mod format_inline_code;
pub mod hyperlinks;
pub mod lists;
pub mod mentions;
pub mod menu_action;
pub mod menu_state;
pub mod new_lines;
Expand Down
8 changes: 4 additions & 4 deletions crates/wysiwyg/src/composer_model/example_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,18 +350,18 @@ impl SelectionWriter {
/// after a mention node
///
/// * `buf` - the output buffer up to and including the given node
/// * `pos` - the buffer position immediately after the node
/// * `start_pos` - the buffer position immediately before the node
pub fn write_selection_mention_node<S: UnicodeString>(
&mut self,
buf: &mut S,
pos: usize,
start_pos: usize,
node: &MentionNode<S>,
) {
if let Some(loc) = self.locations.get(&node.handle()) {
let strings_to_add = self.state.advance(loc, 1);
for (str, i) in strings_to_add.into_iter().rev() {
let i = if i == 0 { 0 } else { buf.len() };
buf.insert(pos + i, &S::from(str));
let insert_pos = if i == 0 { start_pos } else { buf.len() };
buf.insert(insert_pos, &S::from(str));
}
}
}
Expand Down
21 changes: 1 addition & 20 deletions crates/wysiwyg/src/composer_model/hyperlinks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ use crate::dom::nodes::DomNode;
use crate::dom::unicode_string::UnicodeStrExt;
use crate::dom::Range;
use crate::{
ComposerModel, ComposerUpdate, DomHandle, LinkAction, Location,
SuggestionPattern, UnicodeString,
ComposerModel, ComposerUpdate, DomHandle, LinkAction, UnicodeString,
};
use email_address::*;
use url::{ParseError, Url};
Expand Down Expand Up @@ -63,24 +62,6 @@ where
}
}

pub fn set_link_suggestion(
&mut self,
url: S,
text: S,
suggestion: SuggestionPattern,
attributes: Vec<(S, S)>,
) -> ComposerUpdate<S> {
// TODO - this function allows us to accept a Vec of attributes to add to the Link we create,
// but these attributes will be present in the html of the message we output. We may need to
// add a step in the future that strips these attributes from the html before it is sent.

self.do_replace_text_in(S::default(), suggestion.start, suggestion.end);
self.state.start = Location::from(suggestion.start);
self.state.end = self.state.start;
self.set_link_with_text(url, text, attributes);
self.do_replace_text(" ".into())
}

fn is_blank_selection(&self, range: Range) -> bool {
for leaf in range.leaves() {
match leaf.kind {
Expand Down
Loading

0 comments on commit b431568

Please sign in to comment.