diff --git a/src/Keyboard.js b/src/Keyboard.js index 9c872e1c66..bf83a1a05f 100644 --- a/src/Keyboard.js +++ b/src/Keyboard.js @@ -68,3 +68,12 @@ export function isOnlyCtrlOrCmdKeyEvent(ev) { return ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey; } } + +export function isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) { + const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; + if (isMac) { + return ev.metaKey && !ev.altKey && !ev.ctrlKey; + } else { + return ev.ctrlKey && !ev.altKey && !ev.metaKey; + } +} diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 38b7634edb..6f40aa559a 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -333,7 +333,6 @@ const LoggedInView = React.createClass({
{ SettingsStore.isFeatureEnabled("feature_tag_panel") ? :
} diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js index aa35101197..531c247ea6 100644 --- a/src/components/structures/TagPanel.js +++ b/src/components/structures/TagPanel.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import { MatrixClient } from 'matrix-js-sdk'; -import FilterStore from '../../stores/FilterStore'; import TagOrderStore from '../../stores/TagOrderStore'; import GroupActions from '../../actions/GroupActions'; @@ -44,20 +43,13 @@ const TagPanel = React.createClass({ this.unmounted = false; this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership); - this._filterStoreToken = FilterStore.addListener(() => { - if (this.unmounted) { - return; - } - this.setState({ - selectedTags: FilterStore.getSelectedTags(), - }); - }); this._tagOrderStoreToken = TagOrderStore.addListener(() => { if (this.unmounted) { return; } this.setState({ orderedTags: TagOrderStore.getOrderedTags() || [], + selectedTags: TagOrderStore.getSelectedTags(), }); }); // This could be done by anything with a matrix client diff --git a/src/components/views/elements/TagTile.js b/src/components/views/elements/TagTile.js index eb9a5cbdd9..a98cca9609 100644 --- a/src/components/views/elements/TagTile.js +++ b/src/components/views/elements/TagTile.js @@ -20,7 +20,7 @@ import classNames from 'classnames'; import { MatrixClient } from 'matrix-js-sdk'; import sdk from '../../../index'; import dis from '../../../dispatcher'; -import { isOnlyCtrlOrCmdKeyEvent } from '../../../Keyboard'; +import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard'; import FlairStore from '../../../stores/FlairStore'; @@ -76,7 +76,7 @@ export default React.createClass({ dis.dispatch({ action: 'select_tag', tag: this.props.tag, - ctrlOrCmdKey: isOnlyCtrlOrCmdKeyEvent(e), + ctrlOrCmdKey: isOnlyCtrlOrCmdIgnoreShiftKeyEvent(e), shiftKey: e.shiftKey, }); }, diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index cb6cb6c0f3..119355bc82 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -41,7 +41,6 @@ import AccessibleButton from '../elements/AccessibleButton'; import GeminiScrollbar from 'react-gemini-scrollbar'; import RoomViewStore from '../../../stores/RoomViewStore'; - module.exports = withMatrixClient(React.createClass({ displayName: 'MemberInfo', @@ -713,6 +712,10 @@ module.exports = withMatrixClient(React.createClass({ if (this.props.member.userId !== this.props.matrixClient.credentials.userId) { const dmRoomMap = new DMRoomMap(this.props.matrixClient); + // dmRooms will not include dmRooms that we have been invited into but did not join. + // Because DMRoomMap runs off account_data[m.direct] which is only set on join of dm room. + // XXX: we potentially want DMs we have been invited to, to also show up here :L + // especially as logic below concerns specially if we haven't joined but have been invited const dmRooms = dmRoomMap.getDMRoomsForUserId(this.props.member.userId); const RoomTile = sdk.getComponent("rooms.RoomTile"); @@ -722,10 +725,15 @@ module.exports = withMatrixClient(React.createClass({ const room = this.props.matrixClient.getRoom(roomId); if (room) { const me = room.getMember(this.props.matrixClient.credentials.userId); - const highlight = ( - room.getUnreadNotificationCount('highlight') > 0 || - me.membership === "invite" - ); + + // not a DM room if we have are not joined + if (!me.membership || me.membership !== 'join') continue; + // not a DM room if they are not joined + const them = this.props.member; + if (!them.membership || them.membership !== 'join') continue; + + const highlight = room.getUnreadNotificationCount('highlight') > 0 || me.membership === 'invite'; + tiles.push( { - FilterStore.getSelectedTags().forEach((tag) => { + this._tagStoreToken = TagOrderStore.addListener(() => { + TagOrderStore.getSelectedTags().forEach((tag) => { if (tag[0] !== '+' || this._groupStores[tag]) { return; } @@ -183,8 +183,8 @@ module.exports = React.createClass({ MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership); } - if (this._filterStoreToken) { - this._filterStoreToken.remove(); + if (this._tagStoreToken) { + this._tagStoreToken.remove(); } if (this._groupStoreTokens.length > 0) { @@ -298,12 +298,11 @@ module.exports = React.createClass({ // Update which rooms and users should appear according to which tags are selected updateVisibleRooms: function() { - const selectedTags = FilterStore.getSelectedTags(); - - let visibleGroupRooms = []; + const selectedTags = TagOrderStore.getSelectedTags(); + const visibleGroupRooms = []; selectedTags.forEach((tag) => { - visibleGroupRooms = visibleGroupRooms.concat( - this._visibleRoomsForGroup[tag] || [], + (this._visibleRoomsForGroup[tag] || []).forEach( + (roomId) => visibleGroupRooms.push(roomId), ); }); @@ -378,7 +377,6 @@ module.exports = React.createClass({ (me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey())) { // Used to split rooms via tags const tagNames = Object.keys(room.tags); - if (tagNames.length) { for (let i = 0; i < tagNames.length; i++) { const tagName = tagNames[i]; @@ -672,7 +670,6 @@ module.exports = React.createClass({ editable={false} order="recent" isInvite={true} - selectedRoom={self.props.selectedRoom} incomingCall={self.state.incomingCall} collapsed={self.props.collapsed} searchFilter={self.props.searchFilter} @@ -686,7 +683,6 @@ module.exports = React.createClass({ emptyContent={this._getEmptyContent('m.favourite')} editable={true} order="manual" - selectedRoom={self.props.selectedRoom} incomingCall={self.state.incomingCall} collapsed={self.props.collapsed} searchFilter={self.props.searchFilter} @@ -700,7 +696,6 @@ module.exports = React.createClass({ headerItems={this._getHeaderItems('im.vector.fake.direct')} editable={true} order="recent" - selectedRoom={self.props.selectedRoom} incomingCall={self.state.incomingCall} collapsed={self.props.collapsed} alwaysShowHeader={true} @@ -714,7 +709,6 @@ module.exports = React.createClass({ emptyContent={this._getEmptyContent('im.vector.fake.recent')} headerItems={this._getHeaderItems('im.vector.fake.recent')} order="recent" - selectedRoom={self.props.selectedRoom} incomingCall={self.state.incomingCall} collapsed={self.props.collapsed} searchFilter={self.props.searchFilter} @@ -730,7 +724,6 @@ module.exports = React.createClass({ emptyContent={this._getEmptyContent(tagName)} editable={true} order="manual" - selectedRoom={self.props.selectedRoom} incomingCall={self.state.incomingCall} collapsed={self.props.collapsed} searchFilter={self.props.searchFilter} @@ -745,7 +738,6 @@ module.exports = React.createClass({ emptyContent={this._getEmptyContent('m.lowpriority')} editable={true} order="recent" - selectedRoom={self.props.selectedRoom} incomingCall={self.state.incomingCall} collapsed={self.props.collapsed} searchFilter={self.props.searchFilter} @@ -756,7 +748,6 @@ module.exports = React.createClass({ label={_t('Historical')} editable={false} order="recent" - selectedRoom={self.props.selectedRoom} collapsed={self.props.collapsed} alwaysShowHeader={true} startAsHidden={true} diff --git a/src/stores/FilterStore.js b/src/stores/FilterStore.js deleted file mode 100644 index 8078a13ffd..0000000000 --- a/src/stores/FilterStore.js +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2017 Vector Creations Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -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. -*/ -import {Store} from 'flux/utils'; -import dis from '../dispatcher'; -import Analytics from '../Analytics'; - -const INITIAL_STATE = { - allTags: [], - selectedTags: [], - // Last selected tag when shift was not being pressed - anchorTag: null, -}; - -/** - * A class for storing application state for filtering via TagPanel. - */ -class FilterStore extends Store { - constructor() { - super(dis); - - // Initialise state - this._state = INITIAL_STATE; - } - - _setState(newState) { - this._state = Object.assign(this._state, newState); - this.__emitChange(); - } - - __onDispatch(payload) { - switch (payload.action) { - case 'all_tags' : - this._setState({ - allTags: payload.tags, - }); - break; - case 'select_tag': { - let newTags = []; - // Shift-click semantics - if (payload.shiftKey) { - // Select range of tags - let start = this._state.allTags.indexOf(this._state.anchorTag); - let end = this._state.allTags.indexOf(payload.tag); - - if (start === -1) { - start = end; - } - if (start > end) { - const temp = start; - start = end; - end = temp; - } - newTags = payload.ctrlOrCmdKey ? this._state.selectedTags : []; - newTags = [...new Set( - this._state.allTags.slice(start, end + 1).concat(newTags), - )]; - } else { - if (payload.ctrlOrCmdKey) { - // Toggle individual tag - if (this._state.selectedTags.includes(payload.tag)) { - newTags = this._state.selectedTags.filter((t) => t !== payload.tag); - } else { - newTags = [...this._state.selectedTags, payload.tag]; - } - } else { - // Select individual tag - newTags = [payload.tag]; - } - // Only set the anchor tag if the tag was previously unselected, otherwise - // the next range starts with an unselected tag. - if (!this._state.selectedTags.includes(payload.tag)) { - this._setState({ - anchorTag: payload.tag, - }); - } - } - - this._setState({ - selectedTags: newTags, - }); - - Analytics.trackEvent('FilterStore', 'select_tag'); - } - break; - case 'deselect_tags': - this._setState({ - selectedTags: [], - }); - Analytics.trackEvent('FilterStore', 'deselect_tags'); - break; - } - } - - getSelectedTags() { - return this._state.selectedTags; - } -} - -if (global.singletonFilterStore === undefined) { - global.singletonFilterStore = new FilterStore(); -} -export default global.singletonFilterStore; diff --git a/src/stores/TagOrderStore.js b/src/stores/TagOrderStore.js index 633ffc7e9c..9c9427284e 100644 --- a/src/stores/TagOrderStore.js +++ b/src/stores/TagOrderStore.js @@ -15,12 +15,17 @@ limitations under the License. */ import {Store} from 'flux/utils'; import dis from '../dispatcher'; +import Analytics from '../Analytics'; const INITIAL_STATE = { orderedTags: null, orderedTagsAccountData: null, hasSynced: false, joinedGroupIds: null, + + selectedTags: [], + // Last selected tag when shift was not being pressed + anchorTag: null, }; /** @@ -93,6 +98,60 @@ class TagOrderStore extends Store { this._setState({orderedTags}); break; } + case 'select_tag': { + let newTags = []; + // Shift-click semantics + if (payload.shiftKey) { + // Select range of tags + let start = this._state.orderedTags.indexOf(this._state.anchorTag); + let end = this._state.orderedTags.indexOf(payload.tag); + + if (start === -1) { + start = end; + } + if (start > end) { + const temp = start; + start = end; + end = temp; + } + newTags = payload.ctrlOrCmdKey ? this._state.selectedTags : []; + newTags = [...new Set( + this._state.orderedTags.slice(start, end + 1).concat(newTags), + )]; + } else { + if (payload.ctrlOrCmdKey) { + // Toggle individual tag + if (this._state.selectedTags.includes(payload.tag)) { + newTags = this._state.selectedTags.filter((t) => t !== payload.tag); + } else { + newTags = [...this._state.selectedTags, payload.tag]; + } + } else { + // Select individual tag + newTags = [payload.tag]; + } + // Only set the anchor tag if the tag was previously unselected, otherwise + // the next range starts with an unselected tag. + if (!this._state.selectedTags.includes(payload.tag)) { + this._setState({ + anchorTag: payload.tag, + }); + } + } + + this._setState({ + selectedTags: newTags, + }); + + Analytics.trackEvent('FilterStore', 'select_tag'); + } + break; + case 'deselect_tags': + this._setState({ + selectedTags: [], + }); + Analytics.trackEvent('FilterStore', 'deselect_tags'); + break; case 'on_logged_out': { // Reset state without pushing an update to the view, which generally assumes that // the matrix client isn't `null` and so causing a re-render will cause NPEs. @@ -129,6 +188,10 @@ class TagOrderStore extends Store { getOrderedTags() { return this._state.orderedTags; } + + getSelectedTags() { + return this._state.selectedTags; + } } if (global.singletonTagOrderStore === undefined) {