From e541ddb060c1f47fced0ca6b25b8fead58e339b7 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 22 Dec 2015 11:14:36 +0000 Subject: [PATCH] Auto-complete clicked suggestions --- src/TabComplete.js | 40 ++++++++++++++----- src/TabCompleteEntries.js | 7 ++++ src/components/structures/RoomView.js | 1 + src/components/views/rooms/MessageComposer.js | 6 +-- src/components/views/rooms/TabCompleteBar.js | 5 ++- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/TabComplete.js b/src/TabComplete.js index 7e5a02c7ea..fc3478b8f7 100644 --- a/src/TabComplete.js +++ b/src/TabComplete.js @@ -29,6 +29,7 @@ class TabComplete { opts.wordSuffix = opts.wordSuffix || ""; opts.allowLooping = opts.allowLooping || false; opts.autoEnterTabComplete = opts.autoEnterTabComplete || false; + opts.onClickCompletes = opts.onClickCompletes || false; this.opts = opts; this.completing = false; this.list = []; // full set of tab-completable things @@ -45,6 +46,14 @@ class TabComplete { */ setCompletionList(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(); } + /** + * 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. * @return {Entry[]} @@ -184,15 +204,10 @@ class TabComplete { } 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 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 @@ -211,8 +226,15 @@ class TabComplete { } } - _replaceWith(newVal) { - return this.originalText.replace(MATCH_REGEX, newVal); + _replaceWith(newVal, includeSuffix) { + var replacementText = ( + newVal + ( + includeSuffix ? + (this.isFirstWord ? this.opts.startingWordSuffix : this.opts.wordSuffix) : + "" + ) + ); + return this.originalText.replace(MATCH_REGEX, replacementText); } _calculateCompletions() { diff --git a/src/TabCompleteEntries.js b/src/TabCompleteEntries.js index 00af3473c8..81a51c10bd 100644 --- a/src/TabCompleteEntries.js +++ b/src/TabCompleteEntries.js @@ -41,6 +41,13 @@ class Entry { getKey() { return null; } + + /** + * Called when this entry is clicked. + */ + onClick() { + // NOP + } } class MemberEntry extends Entry { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index d34cb28dca..a7520de94f 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -93,6 +93,7 @@ module.exports = React.createClass({ wordSuffix: " ", allowLooping: false, autoEnterTabComplete: true, + onClickCompletes: true, onStateChange: (isCompleting) => { this.forceUpdate(); } diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 2e35614f9c..95b3616b40 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -201,8 +201,8 @@ module.exports = React.createClass({ this.onEnter(ev); } else if (ev.keyCode === KeyCode.TAB) { - var memberList = []; - if (this.props.room) { + if (this.props.tabComplete && this.props.room) { + var memberList = []; // TODO: We should cache this list and only update it when the // member list changes. It's also horrendous that this is done here. memberList = this.props.room.getJoinedMembers().sort(function(a, b) { @@ -231,8 +231,6 @@ module.exports = React.createClass({ }).map(function(m) { return new MemberEntry(m); }); - } - if (this.props.tabComplete) { this.props.tabComplete.setCompletionList(memberList); } } diff --git a/src/components/views/rooms/TabCompleteBar.js b/src/components/views/rooms/TabCompleteBar.js index 413aacbb3b..9668c4b3ac 100644 --- a/src/components/views/rooms/TabCompleteBar.js +++ b/src/components/views/rooms/TabCompleteBar.js @@ -31,10 +31,11 @@ module.exports = React.createClass({
{this.props.entries.map(function(entry, i) { return ( -
+
{entry.getImageJsx()} - {entry.text} + {entry.getText()}
);