Auto-complete clicked suggestions

pull/21833/head
Kegan Dougal 2015-12-22 11:14:36 +00:00
parent 0dbb8d5294
commit e541ddb060
5 changed files with 44 additions and 15 deletions

View File

@ -29,6 +29,7 @@ class TabComplete {
opts.wordSuffix = opts.wordSuffix || ""; opts.wordSuffix = opts.wordSuffix || "";
opts.allowLooping = opts.allowLooping || false; opts.allowLooping = opts.allowLooping || false;
opts.autoEnterTabComplete = opts.autoEnterTabComplete || false; opts.autoEnterTabComplete = opts.autoEnterTabComplete || false;
opts.onClickCompletes = opts.onClickCompletes || false;
this.opts = opts; this.opts = opts;
this.completing = false; this.completing = false;
this.list = []; // full set of tab-completable things this.list = []; // full set of tab-completable things
@ -45,6 +46,14 @@ class TabComplete {
*/ */
setCompletionList(completeList) { setCompletionList(completeList) {
this.list = completeList; this.list = completeList;
if (this.opts.onClickCompletes) {
// assign onClick listeners for each entry to complete the text
this.list.forEach((l) => {
l.onClick = () => {
this.completeTo(l.getText());
}
});
}
} }
/** /**
@ -73,6 +82,17 @@ class TabComplete {
this._calculateCompletions(); this._calculateCompletions();
} }
/**
* Do an auto-complete with the given word. This terminates the tab-complete.
* @param {string} someVal
*/
completeTo(someVal) {
this.textArea.value = this._replaceWith(someVal, true);
this.stopTabCompleting();
// keep focus on the text area
this.textArea.focus();
}
/** /**
* @param {Number} numAheadToPeek Return *up to* this many elements. * @param {Number} numAheadToPeek Return *up to* this many elements.
* @return {Entry[]} * @return {Entry[]}
@ -184,15 +204,10 @@ class TabComplete {
} }
var looped = this.currentIndex === 0; // catch forward and backward looping var looped = this.currentIndex === 0; // catch forward and backward looping
var suffix = "";
if (this.currentIndex !== 0) { // don't suffix the original text!
suffix = this.isFirstWord ? this.opts.startingWordSuffix : this.opts.wordSuffix;
}
// set textarea to this new value // set textarea to this new value
this.textArea.value = this._replaceWith( this.textArea.value = this._replaceWith(
this.matchedList[this.currentIndex].text + suffix this.matchedList[this.currentIndex].text,
this.currentIndex !== 0 // don't suffix the original text!
); );
// visual display to the user that we looped - TODO: This should be configurable // visual display to the user that we looped - TODO: This should be configurable
@ -211,8 +226,15 @@ class TabComplete {
} }
} }
_replaceWith(newVal) { _replaceWith(newVal, includeSuffix) {
return this.originalText.replace(MATCH_REGEX, newVal); var replacementText = (
newVal + (
includeSuffix ?
(this.isFirstWord ? this.opts.startingWordSuffix : this.opts.wordSuffix) :
""
)
);
return this.originalText.replace(MATCH_REGEX, replacementText);
} }
_calculateCompletions() { _calculateCompletions() {

View File

@ -41,6 +41,13 @@ class Entry {
getKey() { getKey() {
return null; return null;
} }
/**
* Called when this entry is clicked.
*/
onClick() {
// NOP
}
} }
class MemberEntry extends Entry { class MemberEntry extends Entry {

View File

@ -93,6 +93,7 @@ module.exports = React.createClass({
wordSuffix: " ", wordSuffix: " ",
allowLooping: false, allowLooping: false,
autoEnterTabComplete: true, autoEnterTabComplete: true,
onClickCompletes: true,
onStateChange: (isCompleting) => { onStateChange: (isCompleting) => {
this.forceUpdate(); this.forceUpdate();
} }

View File

@ -201,8 +201,8 @@ module.exports = React.createClass({
this.onEnter(ev); this.onEnter(ev);
} }
else if (ev.keyCode === KeyCode.TAB) { else if (ev.keyCode === KeyCode.TAB) {
if (this.props.tabComplete && this.props.room) {
var memberList = []; var memberList = [];
if (this.props.room) {
// TODO: We should cache this list and only update it when the // TODO: We should cache this list and only update it when the
// member list changes. It's also horrendous that this is done here. // member list changes. It's also horrendous that this is done here.
memberList = this.props.room.getJoinedMembers().sort(function(a, b) { memberList = this.props.room.getJoinedMembers().sort(function(a, b) {
@ -231,8 +231,6 @@ module.exports = React.createClass({
}).map(function(m) { }).map(function(m) {
return new MemberEntry(m); return new MemberEntry(m);
}); });
}
if (this.props.tabComplete) {
this.props.tabComplete.setCompletionList(memberList); this.props.tabComplete.setCompletionList(memberList);
} }
} }

View File

@ -31,10 +31,11 @@ module.exports = React.createClass({
<div className="mx_TabCompleteBar"> <div className="mx_TabCompleteBar">
{this.props.entries.map(function(entry, i) { {this.props.entries.map(function(entry, i) {
return ( return (
<div key={entry.getKey() || i + ""} className="mx_TabCompleteBar_item"> <div key={entry.getKey() || i + ""} className="mx_TabCompleteBar_item"
onClick={entry.onClick.bind(entry)} >
{entry.getImageJsx()} {entry.getImageJsx()}
<span className="mx_TabCompleteBar_text"> <span className="mx_TabCompleteBar_text">
{entry.text} {entry.getText()}
</span> </span>
</div> </div>
); );