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

[MM-21494] Slash Command Autocomplete #5499

Merged
merged 4 commits into from
May 21, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions components/__snapshots__/textbox.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ exports[`components/TextBox should match snapshot with required props 1`] = `
"currentUserId": "currentUserId",
"data": null,
"disableDispatches": false,
"lastCompletedWord": "",
"lastPrefixWithNoResults": "",
"latestComplete": true,
"latestPrefix": "",
"profilesInChannel": Array [
Expand Down Expand Up @@ -137,6 +139,8 @@ exports[`components/TextBox should throw error when new property is too long 1`]
"currentUserId": "currentUserId",
"data": null,
"disableDispatches": false,
"lastCompletedWord": "",
"lastPrefixWithNoResults": "",
"latestComplete": true,
"latestPrefix": "",
"profilesInChannel": Array [
Expand Down Expand Up @@ -233,6 +237,8 @@ exports[`components/TextBox should throw error when value is too long 1`] = `
"currentUserId": "currentUserId",
"data": null,
"disableDispatches": false,
"lastCompletedWord": "",
"lastPrefixWithNoResults": "",
"latestComplete": true,
"latestPrefix": "",
"profilesInChannel": Array [
Expand Down
21 changes: 21 additions & 0 deletions components/suggestion/at_mention_provider/at_mention_provider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default class AtMentionProvider extends Provider {
this.setProps(props);

this.data = null;
this.lastCompletedWord = '';
this.lastPrefixWithNoResults = '';
}

// setProps gives the provider additional context for matching pretexts. Ideally this would
Expand Down Expand Up @@ -244,6 +246,11 @@ export default class AtMentionProvider extends Provider {

// updateMatches invokes the resultCallback with the metadata for rendering at mentions
updateMatches(resultCallback, items) {
if (items.length === 0) {
this.lastPrefixWithNoResults = this.latestPrefix;
} else if (this.lastPrefixWithNoResults === this.latestPrefix) {
this.lastPrefixWithNoResults = '';
}
const mentions = items.map((item) => {
if (item.username) {
return '@' + item.username;
Expand All @@ -267,7 +274,16 @@ export default class AtMentionProvider extends Provider {
return false;
}

if (this.lastCompletedWord && captured[0].trim().startsWith(this.lastCompletedWord.trim())) {
// It appears we're still matching a channel handle that we already completed
return false;
}

const prefix = captured[1];
if (this.lastPrefixWithNoResults && prefix.startsWith(this.lastPrefixWithNoResults)) {
// Just give up since we know it won't return any results
return false;
}

this.startNewRequest(prefix);
this.updateMatches(resultCallback, this.items());
Expand Down Expand Up @@ -309,6 +325,11 @@ export default class AtMentionProvider extends Provider {
return true;
}

handleCompleteWord(term) {
this.lastCompletedWord = term;
this.lastPrefixWithNoResults = '';
}

createFromProfile(profile, type) {
if (profile.id === this.currentUserId) {
return {
Expand Down
131 changes: 91 additions & 40 deletions components/suggestion/command_provider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ export class CommandSuggestion extends Suggestion {
if (isSelection) {
className += ' suggestion--selected';
}
let icon = <div className='slash-command__icon'><span>{'/'}</span></div>;
if (item.iconData !== '') {
icon = (
<div
className='slash-command__icon'
style={{backgroundColor: 'transparent'}}
>
<img src={item.iconData}/>
</div>);
}

return (
<div
Expand All @@ -29,9 +39,7 @@ export class CommandSuggestion extends Suggestion {
onMouseMove={this.handleMouseMove}
{...Suggestion.baseProps}
>
<div className='slash-command__icon'>
<span>{'/'}</span>
</div>
{icon}
<div className='slash-command__info'>
<div className='slash-command__title'>
{item.suggestion.substring(1) + ' ' + item.hint}
Expand All @@ -47,50 +55,93 @@ export class CommandSuggestion extends Suggestion {

export default class CommandProvider extends Provider {
handlePretextChanged(pretext, resultCallback) {
if (pretext.startsWith('/')) {
const command = pretext.toLowerCase();
Client4.getCommandsList(getCurrentTeamId(store.getState())).then(
(data) => {
let matches = [];
data.forEach((cmd) => {
if (!cmd.auto_complete) {
return;
}
if (!pretext.startsWith('/')) {
return false;
}
if (UserAgent.isMobile()) {
return this.handleMobile(pretext, resultCallback);
}
return this.handleWebapp(pretext, resultCallback);
}

handleCompleteWord(term, pretext, callback) {
callback(term + ' ');
}

handleMobile(pretext, resultCallback) {
const command = pretext.toLowerCase();
Client4.getCommandsList(getCurrentTeamId(store.getState())).then(
(data) => {
let matches = [];
data.forEach((cmd) => {
if (!cmd.auto_complete) {
return;
}

if (cmd.trigger !== 'shortcuts' || !UserAgent.isMobile()) {
if (('/' + cmd.trigger).indexOf(command) === 0) {
const s = '/' + cmd.trigger;
let hint = '';
if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
hint = cmd.auto_complete_hint;
}
matches.push({
suggestion: s,
hint,
description: cmd.auto_complete_desc,
});
if (cmd.trigger !== 'shortcuts') {
if (('/' + cmd.trigger).indexOf(command) === 0) {
const s = '/' + cmd.trigger;
let hint = '';
if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
hint = cmd.auto_complete_hint;
}
matches.push({
suggestion: s,
hint,
description: cmd.auto_complete_desc,
});
}
});
}
});

matches = matches.sort((a, b) => a.suggestion.localeCompare(b.suggestion));

matches = matches.sort((a, b) => a.suggestion.localeCompare(b.suggestion));
// pull out the suggested commands from the returned data
const terms = matches.map((suggestion) => suggestion.suggestion);

// pull out the suggested commands from the returned data
const terms = matches.map((suggestion) => suggestion.suggestion);
resultCallback({
matchedPretext: command,
terms,
items: matches,
component: CommandSuggestion,
});
}
).catch(
() => {} //eslint-disable-line no-empty-function
);

return true;
}

resultCallback({
matchedPretext: command,
terms,
items: matches,
component: CommandSuggestion,
handleWebapp(pretext, resultCallback) {
const command = pretext.toLowerCase();
Client4.getCommandAutocompleteSuggestionsList(command, getCurrentTeamId(store.getState())).then(
(data) => {
const matches = [];
data.forEach((sug) => {
matches.push({
complete: '/' + sug.Complete,
suggestion: '/' + sug.Suggestion,
hint: sug.Hint,
description: sug.Description,
iconData: sug.IconData,
});
}
).catch(
() => {} //eslint-disable-line no-empty-function
);
});

return true;
}
return false;
// pull out the suggested commands from the returned data
const terms = matches.map((suggestion) => suggestion.complete);

resultCallback({
matchedPretext: command,
terms,
items: matches,
component: CommandSuggestion,
});
}
).catch(
() => {} //eslint-disable-line no-empty-function
);

return true;
}
}
1 change: 1 addition & 0 deletions components/suggestion/command_provider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('CommandSuggestion', () => {
suggestion: '/invite',
hint: '@[username] ~[channel]',
description: 'Invite a user to a channel',
iconData: '',
},
isSelection: true,
term: '/',
Expand Down
2 changes: 1 addition & 1 deletion components/suggestion/suggestion_box.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ export default class SuggestionBox extends React.Component {

for (const provider of this.props.providers) {
if (provider.handleCompleteWord) {
provider.handleCompleteWord(term, matchedPretext);
provider.handleCompleteWord(term, matchedPretext, this.handlePretextChanged);
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.