-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Editor: Add components for editor mentions #10142
Conversation
896d623
to
b6a176e
Compare
/** | ||
* Module variables | ||
*/ | ||
const VK = tinymce.util.VK; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be worth us adding a comment to explain what TinyMCE's rather cryptically-named VK
is for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is a partial mapping of keycodes: https://github.com/tinymce/tinymce/blob/master/js/tinymce/classes/util/VK.js
return suggestions | ||
.filter( ( { user_login, display_name } ) => { | ||
// Start of string or preceded by a space. | ||
const matcher = new RegExp( `^${ query }|\\s${ query }`, 'ig' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do usernames ever use reserved regex characters? Any reason for testing against a single whitespace vs many?
An alternative might just be to use lodash trim and startsWith
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re: whitespace. It's matching against ${ user_login } ${ display_name }
, so whether the regex uses \s
or \s+
, the result would be the same.
I also tried creating a user with a user name starting with ^ and it wasn't allowed.
|
||
constructor( props ) { | ||
super( props ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't look like we need a custom constructor here.
|
||
updatePosition( state, { left, top } = this.getPosition( state ) ) { | ||
const { editor, node } = this.props; | ||
const mceToolbar = tinymce.$( '.mce-toolbar-grp', editor.getContainer() )[ 0 ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unlikely to happen, but should we bulletproof this if we can't find the node?
return null; | ||
} | ||
|
||
getSelectedSuggestionIndex() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use findIndex here. If you pass through undefined or null as the first argument, this method will return -1.
if ( ! this.state.selectedSuggestionId ) {
return 0;
}
return findIndex( this.matchingSuggestions, ( suggestion ) => {
return suggestion.ID === this.state.selectedSuggestionId;
} );
I'd also suggest updating the no-match value from null to -1, which is common for these indexOf functions.
|
||
for ( let i = 0; i < this.matchingSuggestions.length; i++ ) { | ||
if ( this.matchingSuggestions[ i ].ID === this.state.selectedSuggestionId ) { | ||
return this.matchingSuggestions[ i ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about:
const index = this.getSelectedSuggestionIndex();
if ( index >= 0 ) {
return this.matchingSuggestions[ index ];
}
return null;
|
||
insertSuggestion = ( suggestion ) => { | ||
const { editor } = this.props; | ||
const re = /@\S*/; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this never changes, we can move this up out of the function as a constant;
insertSuggestion = ( suggestion ) => { | ||
const { editor } = this.props; | ||
const re = /@\S*/; | ||
const markup = <EditorMention username={ suggestion.user_login } />; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do an early return if suggestion is falsey?
return; | ||
} | ||
|
||
if ( keyCode === VK.DOWN ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe move the prevent default to above this line?
Also a switch might read a little nicer here, though that's optional.
ab28552
to
88c3e6e
Compare
2b059c0
to
1ee6862
Compare
1ee6862
to
c775c8f
Compare
c775c8f
to
f0d0f89
Compare
Let's give this another round of eyes? @gwwar most of your concerns have been addressed I think, with the exception of your regex question. Maybe @donnapep has more insights there? |
if ( query ) { | ||
suggestions = suggestions.filter( ( { user_login: userLogin, display_name: displayName } ) => { | ||
// Start of string or preceded by a space. | ||
const matcher = new RegExp( `^${ query }|\\s${ query }`, 'ig' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just want to verify that we intend to enable regex searches here. For example a user typing "foo.bar" for query will match the literal "foo.bar" along with any strings like "fooxbar" where the period can be replaced with any character.
If we intend to only do literal searches, we'd want to escape any reserved regex characters from the query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see what you're saying. Perhaps we can address this in #10321
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless folks have any objections, let's 🚢
Even though this change should be pretty safe. Let's also remember to do a bit of smoke testing on stage before deploying
w00t! 🎉 Just so I understand correctly, when you say smoke-testing, do you mean with #10321? |
Nice to see this being merged! |
@gwwar I poked around the editor a bit before deploying and didn't see any issues. Thanks for the review! |
} ); | ||
} | ||
|
||
return suggestions.slice( 0, 10 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use lodash slice
here for safety, or add a falsey check. soslice( suggestions, 0, 10 )
or suggestions && suggestions.slice( 0, 10 );
Also do we ever expect to have suggestions if query is falsey?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just looking at getUserSuggestions()
and I wonder if it would make sense to fall back to an empty array there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that looks like a better default.
To facilitate merging, this PR separates out the React components from PR #8028, which adds support for mentions to the editor. Still to come is a PR that leverages these components.
Dependent on PR #9969.
cc @mtias