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

Add mention insertion methods #698

Merged
merged 120 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from 109 commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
5ebaa7a
Add mention type (#689)
alunturner May 24, 2023
be47ece
Remove attributes from links (#690)
alunturner May 24, 2023
e10c98c
add parsing differentiation
May 24, 2023
3a42bc9
fix merge errors
May 24, 2023
6ac6e91
fix test error
May 24, 2023
d1fe1e5
create new file with rough placeholders
May 25, 2023
2134820
"export" new file
May 25, 2023
c83efeb
attempt to implement main body
May 25, 2023
e7ab088
attempt to implement toHtml
May 25, 2023
b44b8e5
attempt to implement toRawText
May 25, 2023
15e65f5
attempt to implement toPlainText
May 25, 2023
8d75502
attempt to implement toTree
May 25, 2023
351eaa3
attempt to implement toMarkdown
May 25, 2023
603f9bb
tidy unused function and variables away
May 25, 2023
cf8439e
tidy away unused imports
May 25, 2023
e3eae17
update license
May 25, 2023
9338af3
Convert to non-container node
jonnyandrew May 25, 2023
4b01334
WIP!
jonnyandrew May 26, 2023
c70a71b
Fix issue with mention example format renderer
jonnyandrew May 26, 2023
58a41a1
Remove logs
jonnyandrew May 26, 2023
017bfc1
Update link action tests
jonnyandrew May 26, 2023
cce8148
Remove link attributes
jonnyandrew May 26, 2023
5af177f
Revert "Remove link attributes"
jonnyandrew May 30, 2023
d1b11c3
Remove redundant clone
jonnyandrew May 30, 2023
e346446
Revert changes to existing links behaviour
jonnyandrew May 30, 2023
646decc
Tidy
jonnyandrew May 30, 2023
c3e29b7
Remove TODO
jonnyandrew May 30, 2023
47726ce
Remove unused container node method
jonnyandrew May 30, 2023
923106f
Clean up mention node
jonnyandrew May 30, 2023
c61787e
Merge branch 'main' of github.com:matrix-org/matrix-rich-text-editor …
jonnyandrew May 30, 2023
f743ab5
Add attributes to mention node
jonnyandrew May 31, 2023
7beeeb8
Share HTML tag rendering logic
jonnyandrew May 31, 2023
0d25965
Fix lint
jonnyandrew May 31, 2023
2a71a63
Update js parser implementation
jonnyandrew May 31, 2023
86a493c
Filter mention type attribute when parsing
jonnyandrew May 31, 2023
431b560
Fix wasm test
jonnyandrew May 31, 2023
ed48f07
Add JS mention parsing tests
jonnyandrew May 31, 2023
421779c
Don't parse attributes in sys feature
jonnyandrew May 31, 2023
d690b22
Parse @room mentions
jonnyandrew May 31, 2023
5daf120
Add test for mention parsing
jonnyandrew May 31, 2023
0cbeedf
Add to_markdown test
jonnyandrew May 31, 2023
15fd087
Add to_tree test
jonnyandrew May 31, 2023
d0cc42f
Add to_raw_text test
jonnyandrew May 31, 2023
6d5d594
Add to_plain_text test
jonnyandrew May 31, 2023
88d1c94
Refactor duplicated is_leaf logic
jonnyandrew May 31, 2023
f1d4d1e
Update documentation
jonnyandrew Jun 1, 2023
4519656
Fix list item error creation
jonnyandrew Jun 1, 2023
83fce36
Fix typo
jonnyandrew Jun 1, 2023
78e8fbf
Fix link behaviour
jonnyandrew Jun 1, 2023
338f925
Merge branch 'main' into jonny/distinct-mention-node-wip
jonnyandrew Jun 1, 2023
d4ef5c7
Add panic to unreachable match arm
jonnyandrew Jun 1, 2023
e81b109
Merge branch 'jonny/distinct-mention-node-wip' of github.com:matrix-o…
jonnyandrew Jun 1, 2023
a8e924a
ignore failing tests
May 30, 2023
284b933
remove blank line
May 30, 2023
269ee3c
setup and call new insert_at_cursor function
May 30, 2023
37e07f4
rejig to get access to composermodel
May 30, 2023
0a4aff8
unignore the ffi test, start working
May 30, 2023
76d0ba4
add new tests
May 30, 2023
3387042
get tests passing
May 30, 2023
8fe0b17
refactor
May 30, 2023
c8b9a55
fix visibility
May 30, 2023
3b1a618
split all functions out to new file
May 31, 2023
dc8c863
move to new file, refactor
May 31, 2023
ae4a3ad
add new tests file
May 31, 2023
9c93930
make insert_into_text return a DomHandle like other insert* methods
May 31, 2023
b8d7180
export from new file
May 31, 2023
1c01f58
move logic from mentions.rs to new dom file
May 31, 2023
7ab6e72
tidy up
May 31, 2023
c16d589
start writing new tests
May 31, 2023
ebfb120
expand tests (WIP)
May 31, 2023
9f7ddb5
complete rebase changes
May 31, 2023
445d935
ignore failing tests and move on
May 31, 2023
86b0688
add TODO
May 31, 2023
e11dde8
Fix mention selection writer
jonnyandrew Jun 1, 2023
974759f
tweak menu action to disallow mentions in links
Jun 1, 2023
c4e331b
smash out some tests
Jun 1, 2023
66c2e9f
finish testing mentions in all nodes
Jun 1, 2023
0cdb926
add comment
Jun 1, 2023
7ad28e5
consolidate tests
Jun 1, 2023
b239201
use helper fn to reduce repetition
Jun 1, 2023
99bb780
add insert_at_range tests
Jun 1, 2023
537d58f
add .assert_invariants calls
Jun 1, 2023
53debd4
separate out deletion/insertion functions
Jun 2, 2023
8e15970
rename function
Jun 2, 2023
085f1ef
rename function
Jun 2, 2023
e0e0177
expand tests to cover selection
Jun 2, 2023
044dea6
add panic to prevent incorrect calling
Jun 2, 2023
12f5c9f
add test for panic
Jun 2, 2023
6755468
add comment
Jun 2, 2023
cac737f
change binding function name
Jun 2, 2023
40329d3
change function name for consistency
Jun 2, 2023
2b6cf0b
stop web example app passing in contenteditable
Jun 2, 2023
a3349d0
remove contentEditable from call
Jun 2, 2023
95ca657
add new mention tests
Jun 2, 2023
706000c
get selection tests passing
Jun 2, 2023
81cbb11
add some formatting to the dom display (for testing)
Jun 2, 2023
0bd1927
update comment
Jun 2, 2023
416af99
add paragraph test
Jun 2, 2023
d052012
WIP paragraph testing
Jun 2, 2023
9a5153b
WIP paragraph testing
Jun 2, 2023
db197d5
fix inserting into an empty paragraph tag
Jun 5, 2023
ce4ca7c
add extra tests for ffi, improve handling for paragraphs
Jun 5, 2023
3187e78
add dom tests for nested mentions in paragraphs
Jun 5, 2023
031b979
tidy up dom methods
Jun 5, 2023
a4cafa0
use helper in ffi tests
Jun 5, 2023
ed92b73
tidy up TS
Jun 5, 2023
cb0cd27
shorten comment
Jun 5, 2023
441b39d
Merge remote-tracking branch 'origin/main' into alunturner/add-mentio…
Jun 5, 2023
ec46935
Merge branch 'main' into alunturner/add-mention-insertion-methods
aringenbach Jun 6, 2023
49b40e7
add comments
Jun 7, 2023
b9588fb
add comments and refactor to add util
Jun 8, 2023
65fbaf5
add bindings for insert_mention function
Jun 8, 2023
924b36a
add bindings for insert_mention function
Jun 8, 2023
31a7bcf
be consistent in using replace_text vs do_replace_text methods
Jun 8, 2023
3f6e1ba
remove unused import
alunturner Jun 8, 2023
55ca905
update udl file
alunturner Jun 8, 2023
2cb265c
use do_replace_text_in
alunturner Jun 8, 2023
9bd5a9c
use do_replace_text
alunturner Jun 8, 2023
cd8b839
Update Android to use new mention insertion APIs (#706)
jonnyandrew Jun 12, 2023
a3cadae
[iOS] Mention node updates (#711)
aringenbach Jun 12, 2023
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
9 changes: 4 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,9 @@ 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(
/// This function 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 +283,7 @@ impl ComposerModel {
self.inner
.lock()
.unwrap()
.set_link_suggestion(url, text, suggestion, attrs),
.insert_mention_at_suggestion(url, text, suggestion, attrs),
))
}

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update is unused now


let MenuAction::Suggestion { suggestion_pattern } =
update.menu_action() else
{
panic!("No suggestion found");
};
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",
)
}

model.set_link_suggestion(
#[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());

insert_mention_at_cursor(&mut model);

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>",
)
}

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(),
}],
alunturner marked this conversation as resolved.
Show resolved Hide resolved
);
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
2 changes: 1 addition & 1 deletion bindings/wysiwyg-ffi/src/wysiwyg_composer.udl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ 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 insert_mention_at_suggestion(string url, string text, SuggestionPattern suggestion, sequence<Attribute> attributes);
ComposerUpdate remove_links();
alunturner marked this conversation as resolved.
Show resolved Hide resolved
ComposerUpdate code_block();
ComposerUpdate quote();
Expand Down
9 changes: 4 additions & 5 deletions bindings/wysiwyg-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,17 +312,16 @@ 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(
/// This function 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
96 changes: 96 additions & 0 deletions crates/wysiwyg/src/composer_model/mentions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::{
dom::DomLocation, ComposerModel, ComposerUpdate, DomNode, Location,
SuggestionPattern, UnicodeString,
};

impl<S> ComposerModel<S>
where
S: UnicodeString,
{
/// Remove the suggestion text and then add a mention to the composer
pub fn insert_mention_at_suggestion(
&mut self,
url: S,
text: S,
suggestion: SuggestionPattern,
attributes: Vec<(S, S)>,
) -> ComposerUpdate<S> {
// This function removes the text between the suggestion start and end points, updates the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must self.push_state_to_history(); here, before we start making changes to the composer.

// cursor position and then calls insert_mention (equivalent to link insertion steps)
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.insert_mention(url, text, attributes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should really rather call the internal do_insert_mention. If the test around being inside a link is required, it should be done before everything else (and be extracted to an util function run before push_state_to_history)

}

/// Inserts a mention into the composer. It uses the following rules:
/// - If the selection or cursor contains/is inside a link, do nothing (see
/// https://github.com/matrix-org/matrix-rich-text-editor/issues/702)
/// - If the composer contains a selection, remove the contents of the selection
/// prior to inserting a mention at the cursor.
/// - If the composer contains a cursor, insert a mention at the cursor
pub fn insert_mention(
aringenbach marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
url: S,
text: S,
attributes: Vec<(S, S)>,
) -> ComposerUpdate<S> {
let (start, end) = self.safe_selection();
let range = self.state.dom.find_range(start, end);

if range.locations.iter().any(|l: &DomLocation| {
l.kind.is_link_kind() || l.kind.is_code_kind()
}) {
return ComposerUpdate::keep();
}

aringenbach marked this conversation as resolved.
Show resolved Hide resolved
if range.is_selection() {
self.replace_text(S::default());
}

self.do_insert_mention(url, text, attributes)
}

fn do_insert_mention(
&mut self,
url: S,
text: S,
attributes: Vec<(S, S)>,
) -> ComposerUpdate<S> {
let (start, end) = self.safe_selection();
let range = self.state.dom.find_range(start, end);

let new_node = DomNode::new_mention(url, text, attributes);
let new_cursor_index = start + new_node.text_len();

self.push_state_to_history();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably never push_state_to_history inside inner variant do_... in case of reuse somewhere else (since it creates an undo/redo state - it should always be called in the public API function)


let handle = self.state.dom.insert_node_at_cursor(&range, new_node);

// manually move the cursor to the end of the mention
self.state.start = Location::from(new_cursor_index);
self.state.end = self.state.start;

// add a trailing space in cases when we do not have a next sibling
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not completely sure if this is the correct logic or whether we want to simply always add a trailing space for the initial implementation

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think the behaviour you've implemented makes sense but could be worth checking with UX if you're not sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this was already discussed at some point, the conversation lies somewhere in our Room history, but yeah adding a space after the inserted trailing function only is the way to go.

if self.state.dom.is_last_in_parent(&handle) {
self.do_replace_text(" ".into())
} else {
self.create_update_replace_all()
}
}
}
7 changes: 6 additions & 1 deletion crates/wysiwyg/src/composer_model/menu_action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ where
pub(crate) fn compute_menu_action(&self) -> MenuAction {
let (s, e) = self.safe_selection();
let range = self.state.dom.find_range(s, e);
if range.locations.iter().any(|l| l.kind.is_code_kind()) {

if range
.locations
.iter()
.any(|l| l.kind.is_code_kind() || l.kind.is_link_kind())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See issue #702, initial implementation is to simply not allow suggestions when inside a link

{
return MenuAction::None;
}
let (raw_text, start, end) = self.extended_text(range);
Expand Down
1 change: 1 addition & 0 deletions crates/wysiwyg/src/dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod dom_struct;
pub mod find_extended_range;
pub mod find_range;
pub mod find_result;
pub mod insert_node_at_cursor;
pub mod insert_parent;
pub mod iter;
pub mod join_nodes;
Expand Down
Loading