diff --git a/src/TabComplete.js b/src/TabComplete.js index 7da8bde76b..0ec0b77802 100644 --- a/src/TabComplete.js +++ b/src/TabComplete.js @@ -13,7 +13,10 @@ 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. */ -var Entry = require("./TabCompleteEntries").Entry; + +import { Entry, MemberEntry, CommandEntry } from './TabCompleteEntries'; +import SlashCommands from './SlashCommands'; +import MatrixClientPeg from './MatrixClientPeg'; const DELAY_TIME_MS = 1000; const KEY_TAB = 9; @@ -45,23 +48,39 @@ class TabComplete { this.isFirstWord = false; // true if you tab-complete on the first word this.enterTabCompleteTimerId = null; this.inPassiveMode = false; + + // Map tracking ordering of the room members. + // userId: integer, highest comes first. + this.memberTabOrder = {}; + + // monotonically increasing counter used for tracking ordering of members + this.memberOrderSeq = 0; } /** - * @param {Entry[]} completeList + * Call this when a a UI element representing a tab complete entry has been clicked + * @param {entry} The entry that was clicked */ - setCompletionList(completeList) { - this.list = completeList; + onEntryClick(entry) { if (this.opts.onClickCompletes) { - // assign onClick listeners for each entry to complete the text - this.list.forEach((l) => { - l.onClick = () => { - this.completeTo(l); - } - }); + this.completeTo(entry); } } + loadEntries(room) { + this._makeEntries(room); + this._initSorting(room); + this._sortEntries(); + } + + onMemberSpoke(member) { + if (this.memberTabOrder[member.userId] === undefined) { + this.list.push(new MemberEntry(member)); + } + this.memberTabOrder[member.userId] = this.memberOrderSeq++; + this._sortEntries(); + } + /** * @param {DOMElement} */ @@ -307,6 +326,49 @@ class TabComplete { this.opts.onStateChange(this.completing); } } + + _sortEntries() { + // largest comes first + const KIND_ORDER = { + command: 1, + member: 2, + }; + + this.list.sort((a, b) => { + const kindOrderDifference = KIND_ORDER[b.kind] - KIND_ORDER[a.kind]; + if (kindOrderDifference != 0) { + return kindOrderDifference; + } + + if (a.kind == 'member') { + return this.memberTabOrder[b.member.userId] - this.memberTabOrder[a.member.userId]; + } + + // anything else we have no ordering for + return 0; + }); + } + + _makeEntries(room) { + const myUserId = MatrixClientPeg.get().credentials.userId; + + const members = room.getJoinedMembers().filter(function(member) { + if (member.userId !== myUserId) return true; + }); + + this.list = MemberEntry.fromMemberList(members).concat( + CommandEntry.fromCommands(SlashCommands.getCommandList()) + ); + } + + _initSorting(room) { + this.memberTabOrder = {}; + this.memberOrderSeq = 0; + + for (const ev of room.getLiveTimeline().getEvents()) { + this.memberTabOrder[ev.getSender()] = this.memberOrderSeq++; + } + } }; module.exports = TabComplete; diff --git a/src/TabCompleteEntries.js b/src/TabCompleteEntries.js index a23050063f..4a28103210 100644 --- a/src/TabCompleteEntries.js +++ b/src/TabCompleteEntries.js @@ -69,6 +69,7 @@ class Entry { class CommandEntry extends Entry { constructor(cmd, cmdWithArgs) { super(cmdWithArgs); + this.kind = 'command'; this.cmd = cmd; } @@ -95,6 +96,7 @@ class MemberEntry extends Entry { constructor(member) { super(member.name || member.userId); this.member = member; + this.kind = 'member'; } getImageJsx() { @@ -114,24 +116,7 @@ class MemberEntry extends Entry { } MemberEntry.fromMemberList = function(members) { - return members.sort(function(a, b) { - var userA = a.user; - var userB = b.user; - if (userA && !userB) { - return -1; // a comes first - } - else if (!userA && userB) { - return 1; // b comes first - } - else if (!userA && !userB) { - return 0; // don't care - } - else { // both User objects exist - var lastActiveAgoA = userA.lastActiveAgo || Number.MAX_SAFE_INTEGER; - var lastActiveAgoB = userB.lastActiveAgo || Number.MAX_SAFE_INTEGER; - return lastActiveAgoA - lastActiveAgoB; - } - }).map(function(m) { + return members.map(function(m) { return new MemberEntry(m); }); } diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 92f50dcb02..9a0d3dbbdd 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -26,9 +26,9 @@ module.exports = React.createClass({ propTypes: { // the room this statusbar is representing. room: React.PropTypes.object.isRequired, - - // a list of TabCompleteEntries.Entry objects - tabCompleteEntries: React.PropTypes.array, + + // a TabComplete object + tabComplete: React.PropTypes.object.isRequired, // the number of messages which have arrived since we've been scrolled up numUnreadMessages: React.PropTypes.number, @@ -208,11 +208,11 @@ module.exports = React.createClass({ ); } - if (this.props.tabCompleteEntries) { + if (this.props.tabComplete.isTabCompleting()) { return (