From 5514d816833cf860bb76260918b87a57b4bccd4a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 24 Oct 2017 23:01:40 +0100 Subject: [PATCH 01/16] Add Mention button to MemberInfo Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/MemberInfo.js | 17 ++++++++++++++++- src/i18n/strings/en_EN.json | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 2f8bd30a72..e281a93558 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -629,6 +629,7 @@ module.exports = withMatrixClient(React.createClass({ const member = this.props.member; let ignoreButton = null; + let insertPillButton = null; let readReceiptButton = null; // Only allow the user to ignore the user if its not ourselves @@ -653,21 +654,35 @@ module.exports = withMatrixClient(React.createClass({ }); }; + const onInsertPillButton = function() { + dis.dispatch({ + action: 'insert_mention', + user_id: member.userId, + }); + }; + readReceiptButton = ( { _t('Jump to read receipt') } ); + + insertPillButton = ( + + { _t('Mention') } + + ); } } - if (!ignoreButton && !readReceiptButton) return null; + if (!ignoreButton && !readReceiptButton && !insertPillButton) return null; return (

{ _t("User Options") }

{ readReceiptButton } + { insertPillButton } { ignoreButton }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 492113989b..ea5c12d86c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -152,6 +152,7 @@ "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s", "Communities": "Communities", "Message Pinning": "Message Pinning", + "Mention": "Mention", "%(displayName)s is typing": "%(displayName)s is typing", "%(names)s and one other are typing": "%(names)s and one other are typing", "%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing", From b3463146baf0d1468e53b10b5ae6ce90b66cbb77 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 25 Oct 2017 00:58:16 +0100 Subject: [PATCH 02/16] Add invite button to MemberInfo if user has left or wasn't in room Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/MemberInfo.js | 68 +++++++++++++++++++----- src/i18n/strings/en_EN.json | 1 + 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 2f8bd30a72..7d64b22cb0 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -39,6 +39,7 @@ import { findReadReceiptFromUserId } from '../../../utils/Receipt'; import withMatrixClient from '../../../wrappers/withMatrixClient'; import AccessibleButton from '../elements/AccessibleButton'; import GeminiScrollbar from 'react-gemini-scrollbar'; +import RoomViewStore from '../../../stores/RoomViewStore'; module.exports = withMatrixClient(React.createClass({ @@ -81,6 +82,7 @@ module.exports = withMatrixClient(React.createClass({ cli.on("Room.receipt", this.onRoomReceipt); cli.on("RoomState.events", this.onRoomStateEvents); cli.on("RoomMember.name", this.onRoomMemberName); + cli.on("RoomMember.membership", this.onRoomMemberMembership); cli.on("accountData", this.onAccountData); this._checkIgnoreState(); @@ -107,6 +109,7 @@ module.exports = withMatrixClient(React.createClass({ client.removeListener("Room.receipt", this.onRoomReceipt); client.removeListener("RoomState.events", this.onRoomStateEvents); client.removeListener("RoomMember.name", this.onRoomMemberName); + client.removeListener("RoomMember.membership", this.onRoomMemberMembership); client.removeListener("accountData", this.onAccountData); } if (this._cancelDeviceList) { @@ -186,6 +189,10 @@ module.exports = withMatrixClient(React.createClass({ this.forceUpdate(); }, + onRoomMemberMembership: function(ev, member) { + if (this.props.member.userId === member.userId) this.forceUpdate(); + }, + onAccountData: function(ev) { if (ev.getType() == 'm.direct') { this.forceUpdate(); @@ -629,6 +636,7 @@ module.exports = withMatrixClient(React.createClass({ const member = this.props.member; let ignoreButton = null; + let inviteUserButton = null; let readReceiptButton = null; // Only allow the user to ignore the user if its not ourselves @@ -659,9 +667,30 @@ module.exports = withMatrixClient(React.createClass({ ); } + + if (!member || !member.membership || member.membership === 'leave') { + const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId(); + const onInviteUserButton = async () => { + try { + await cli.invite(roomId, member.userId); + } catch (err) { + const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); + Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, { + title: _t('Failed to invite'), + description: ((err && err.message) ? err.message : "Operation failed"), + }); + } + }; + + inviteUserButton = ( + + { _t('Invite') } + + ); + } } - if (!ignoreButton && !readReceiptButton) return null; + if (!ignoreButton && !readReceiptButton && !inviteUserButton) return null; return (
@@ -669,6 +698,7 @@ module.exports = withMatrixClient(React.createClass({
{ readReceiptButton } { ignoreButton } + { inviteUserButton }
); @@ -768,9 +798,6 @@ module.exports = withMatrixClient(React.createClass({ ; } - // TODO: we should have an invite button if this MemberInfo is showing a user who isn't actually in the current room yet - // e.g. clicking on a linkified userid in a room - let adminTools; if (kickButton || banButton || muteButton || giveModButton) { adminTools = @@ -795,9 +822,29 @@ module.exports = withMatrixClient(React.createClass({ var presenceCurrentlyActive = this.props.member.user.currentlyActive; } + let roomMemberDetails = null; + + if (this.props.member.roomId) { // is in room + const PowerSelector = sdk.getComponent('elements.PowerSelector'); + const PresenceLabel = sdk.getComponent('rooms.PresenceLabel'); + roomMemberDetails =
+
+ { _t("Level:") } + + +
+
+ +
+
; + } + const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); - const PowerSelector = sdk.getComponent('elements.PowerSelector'); - const PresenceLabel = sdk.getComponent('rooms.PresenceLabel'); const EmojiText = sdk.getComponent('elements.EmojiText'); return (
@@ -813,14 +860,7 @@ module.exports = withMatrixClient(React.createClass({
{ this.props.member.userId }
-
- { _t("Level:") } -
-
- -
+ { roomMemberDetails }
{ this._renderUserOptions() } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 492113989b..9d91c3a110 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -249,6 +249,7 @@ "Level:": "Level:", "and %(count)s others...|other": "and %(count)s others...", "and %(count)s others...|one": "and one other...", + "Invite": "Invite", "Invited": "Invited", "Filter room members": "Filter room members", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", From 5990e41bd74264c590ec8ccf2b0d4272134f8e24 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 25 Oct 2017 17:24:45 +0100 Subject: [PATCH 03/16] Use the correct userId when displaying who redacted a message --- src/components/views/messages/UnknownBody.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/UnknownBody.js b/src/components/views/messages/UnknownBody.js index 083d7ac12e..5a2bf7c54c 100644 --- a/src/components/views/messages/UnknownBody.js +++ b/src/components/views/messages/UnknownBody.js @@ -25,7 +25,9 @@ module.exports = React.createClass({ render: function() { let tooltip = _t("Removed or unknown message type"); if (this.props.mxEvent.isRedacted()) { - tooltip = _t("Message removed by %(userId)s", {userId: this.props.mxEvent.getSender()}); + tooltip = _t("Message removed by %(userId)s", { + userId: this.props.mxEvent.getUnsigned().redacted_because.sender, + }); } const text = this.props.mxEvent.getContent().body; From cedc0b27a939781bcfa65a2c52197cdb376c338c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 25 Oct 2017 17:37:20 +0100 Subject: [PATCH 04/16] Handle redaction with no sender --- src/components/views/messages/UnknownBody.js | 7 ++++--- src/i18n/strings/en_EN.json | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/views/messages/UnknownBody.js b/src/components/views/messages/UnknownBody.js index 5a2bf7c54c..9a172baf7c 100644 --- a/src/components/views/messages/UnknownBody.js +++ b/src/components/views/messages/UnknownBody.js @@ -25,9 +25,10 @@ module.exports = React.createClass({ render: function() { let tooltip = _t("Removed or unknown message type"); if (this.props.mxEvent.isRedacted()) { - tooltip = _t("Message removed by %(userId)s", { - userId: this.props.mxEvent.getUnsigned().redacted_because.sender, - }); + const redactedBecauseUserId = this.props.mxEvent.getUnsigned().redacted_because.sender; + tooltip = redactedBecauseUserId ? + _t("Message removed by %(userId)s", { userId: redactedBecauseUserId }) : + _t("Message removed"); } const text = this.props.mxEvent.getContent().body; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ef915f23cd..fbff51299a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -200,8 +200,6 @@ "Authentication": "Authentication", "Failed to delete device": "Failed to delete device", "Delete": "Delete", - "Delete Widget": "Delete Widget", - "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", "Disable Notifications": "Disable Notifications", "Enable Notifications": "Enable Notifications", "Cannot add any more widgets": "Cannot add any more widgets", @@ -452,6 +450,7 @@ "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", + "Message removed": "Message removed", "Robot check is currently unavailable on desktop - please use a web browser": "Robot check is currently unavailable on desktop - please use a web browser", "This Home Server would like to make sure you are not a robot": "This Home Server would like to make sure you are not a robot", "Sign in with CAS": "Sign in with CAS", @@ -499,6 +498,8 @@ "NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted", "Do you want to load widget from URL:": "Do you want to load widget from URL:", "Allow": "Allow", + "Delete Widget": "Delete Widget", + "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", "Delete widget": "Delete widget", "Revoke widget access": "Revoke widget access", "Edit": "Edit", @@ -678,17 +679,17 @@ "Leave %(groupName)s?": "Leave %(groupName)s?", "Leave": "Leave", "Unable to leave room": "Unable to leave room", + "Community Settings": "Community Settings", "Add rooms to this community": "Add rooms to this community", "Featured Rooms:": "Featured Rooms:", "Featured Users:": "Featured Users:", "%(inviter)s has invited you to join this community": "%(inviter)s has invited you to join this community", - "You are a member of this community": "You are a member of this community", "You are an administrator of this community": "You are an administrator of this community", + "You are a member of this community": "You are a member of this community", "Community Member Settings": "Community Member Settings", "Publish this community on your profile": "Publish this community on your profile", "Long Description (HTML)": "Long Description (HTML)", "Description": "Description", - "Community Settings": "Community Settings", "Community %(groupId)s not found": "Community %(groupId)s not found", "This Home server does not support communities": "This Home server does not support communities", "Failed to load %(groupId)s": "Failed to load %(groupId)s", From 3ae97348f81c54691061c0a8fcf843882c3500b8 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 25 Oct 2017 18:17:33 +0100 Subject: [PATCH 05/16] Add option to mirror local video feed --- src/components/structures/UserSettings.js | 4 ++++ src/components/views/voip/VideoView.js | 9 +++++++-- src/i18n/strings/en_EN.json | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 7704cce0c7..ac1c6b662b 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -118,6 +118,10 @@ const SETTINGS_LABELS = [ id: 'TextualBody.disableBigEmoji', label: _td('Disable big emoji in chat'), }, + { + id: 'VideoView.flipVideoHorizontally', + label: _td('Mirror local video feed'), + }, /* { id: 'useFixedWidthFont', diff --git a/src/components/views/voip/VideoView.js b/src/components/views/voip/VideoView.js index 8f062d27ae..94cc74960f 100644 --- a/src/components/views/voip/VideoView.js +++ b/src/components/views/voip/VideoView.js @@ -18,10 +18,13 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; +import classNames from 'classnames'; import sdk from '../../../index'; import dis from '../../../dispatcher'; +import UserSettingsStore from '../../../UserSettingsStore'; + module.exports = React.createClass({ displayName: 'VideoView', @@ -108,14 +111,16 @@ module.exports = React.createClass({ document.mozFullScreenElement || document.webkitFullscreenElement); const maxVideoHeight = fullscreenElement ? null : this.props.maxHeight; - + const localVideoFeedClasses = classNames("mx_VideoView_localVideoFeed", + {"mx_VideoView_localVideoFeed_flipped": UserSettingsStore.getSyncedSetting} + ); return (
-
+
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fbff51299a..b9bca1ca6c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -755,6 +755,7 @@ "Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing", "Hide avatars in user and room mentions": "Hide avatars in user and room mentions", "Disable big emoji in chat": "Disable big emoji in chat", + "Mirror local video feed": "Mirror local video feed", "Opt out of analytics": "Opt out of analytics", "Disable Peer-to-Peer for 1:1 calls": "Disable Peer-to-Peer for 1:1 calls", "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", From 97b9cf64023967e177724373204ad15f5b618b2a Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 26 Oct 2017 09:58:46 +0100 Subject: [PATCH 06/16] Actually use the synced setting --- src/components/views/voip/VideoView.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/voip/VideoView.js b/src/components/views/voip/VideoView.js index 94cc74960f..8fbc95697d 100644 --- a/src/components/views/voip/VideoView.js +++ b/src/components/views/voip/VideoView.js @@ -112,7 +112,9 @@ module.exports = React.createClass({ document.webkitFullscreenElement); const maxVideoHeight = fullscreenElement ? null : this.props.maxHeight; const localVideoFeedClasses = classNames("mx_VideoView_localVideoFeed", - {"mx_VideoView_localVideoFeed_flipped": UserSettingsStore.getSyncedSetting} + { "mx_VideoView_localVideoFeed_flipped": + UserSettingsStore.getSyncedSetting('VideoView.flipVideoHorizontally'), + }, ); return (
From 591a7f4b8616ee5d457642d3d2de34f47b9745c7 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 26 Oct 2017 10:06:04 +0100 Subject: [PATCH 07/16] Default to false --- src/components/views/voip/VideoView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/voip/VideoView.js b/src/components/views/voip/VideoView.js index 8fbc95697d..748673f1a5 100644 --- a/src/components/views/voip/VideoView.js +++ b/src/components/views/voip/VideoView.js @@ -113,7 +113,7 @@ module.exports = React.createClass({ const maxVideoHeight = fullscreenElement ? null : this.props.maxHeight; const localVideoFeedClasses = classNames("mx_VideoView_localVideoFeed", { "mx_VideoView_localVideoFeed_flipped": - UserSettingsStore.getSyncedSetting('VideoView.flipVideoHorizontally'), + UserSettingsStore.getSyncedSetting('VideoView.flipVideoHorizontally', false), }, ); return ( From eec6ed36cdf03ccaeaa712e33439ab668c3a63e3 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 26 Oct 2017 14:05:58 +0100 Subject: [PATCH 08/16] Use correct icon for group room deletion and make themeable Also fix cancel icons to have class mx_filterFlipColor --- src/components/structures/UserSettings.js | 7 +++++-- src/components/views/groups/GroupMemberInfo.js | 2 +- src/components/views/groups/GroupRoomTile.js | 7 +++++-- src/components/views/rooms/LinkPreviewWidget.js | 5 +++-- src/components/views/rooms/PinnedEventsPanel.js | 4 +++- src/components/views/rooms/RoomHeader.js | 7 +++++-- src/i18n/strings/en_EN.json | 3 ++- 7 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index ac1c6b662b..68ea932f93 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -1332,8 +1332,11 @@ module.exports = React.createClass({
- {_t("Remove + {_t("Remove
- +
{ avatar } diff --git a/src/components/views/groups/GroupRoomTile.js b/src/components/views/groups/GroupRoomTile.js index 23e53996e3..94dc8e593f 100644 --- a/src/components/views/groups/GroupRoomTile.js +++ b/src/components/views/groups/GroupRoomTile.js @@ -120,8 +120,11 @@ const GroupRoomTile = React.createClass({
{ this.state.name }
- - + + ); diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index 10c809e0f1..d69a8fbcf1 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -133,8 +133,9 @@ module.exports = React.createClass({ { p["og:description"] }
- +
); }, diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index deea03f030..5b1b8a4590 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -95,7 +95,9 @@ module.exports = React.createClass({ return (
- + + +

{ _t("Pinned Messages") }

{ tiles }
diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 4df0ff738c..4dfbdb3644 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -281,8 +281,11 @@ module.exports = React.createClass({
- {_t("Remove + {_t("Remove
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b1dbfcfa0c..5ea42021dd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -239,6 +239,7 @@ "Unignore": "Unignore", "Ignore": "Ignore", "Jump to read receipt": "Jump to read receipt", + "Invite": "Invite", "User Options": "User Options", "Direct chats": "Direct chats", "Unmute": "Unmute", @@ -249,7 +250,6 @@ "Level:": "Level:", "and %(count)s others...|other": "and %(count)s others...", "and %(count)s others...|one": "and one other...", - "Invite": "Invite", "Invited": "Invited", "Filter room members": "Filter room members", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", @@ -495,6 +495,7 @@ "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?", "Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.", "Remove": "Remove", + "Remove this room from the community": "Remove this room from the community", "Unknown Address": "Unknown Address", "NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted", "Do you want to load widget from URL:": "Do you want to load widget from URL:", From 9643784f850daf642080de1768ce1eb99753ac9d Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 26 Oct 2017 14:31:27 +0100 Subject: [PATCH 09/16] Only show group settings cog to members Non-members have no settings to change. --- src/components/structures/GroupView.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 314a36e486..9d87f84aef 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -964,13 +964,15 @@ export default React.createClass({ , ); } else { - rightButtons.push( - - - , - ); + if (summary.user && summary.user.membership === 'join') { + rightButtons.push( + + + , + ); + } if (this.props.collapsedRhs) { rightButtons.push( Date: Fri, 27 Oct 2017 11:36:32 +0100 Subject: [PATCH 10/16] Simplify GroupStore listener registration --- src/components/structures/GroupView.js | 3 +-- src/components/views/groups/GroupMemberList.js | 5 +---- src/components/views/groups/GroupRoomList.js | 4 +--- src/stores/GroupStore.js | 14 +++++++++++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 9d87f84aef..1564efd8d3 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -447,7 +447,7 @@ export default React.createClass({ _initGroupStore: function(groupId) { this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId); - this._groupStore.on('update', () => { + this._groupStore.registerListener(() => { const summary = this._groupStore.getSummary(); if (summary.profile) { // Default profile fields should be "" for later sending to the server (which @@ -464,7 +464,6 @@ export default React.createClass({ }); }); this._groupStore.on('error', (err) => { - console.error(err); this.setState({ summary: null, error: err, diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js index a5ab22eb0e..8658ac19a5 100644 --- a/src/components/views/groups/GroupMemberList.js +++ b/src/components/views/groups/GroupMemberList.js @@ -50,12 +50,9 @@ export default withMatrixClient(React.createClass({ _initGroupStore: function(groupId) { this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId); - this._groupStore.on('update', () => { + this._groupStore.registerListener(() => { this._fetchMembers(); }); - this._groupStore.on('error', (err) => { - console.error(err); - }); }, _fetchMembers: function() { diff --git a/src/components/views/groups/GroupRoomList.js b/src/components/views/groups/GroupRoomList.js index 3fcfedd486..aeded2dfb0 100644 --- a/src/components/views/groups/GroupRoomList.js +++ b/src/components/views/groups/GroupRoomList.js @@ -47,16 +47,14 @@ export default React.createClass({ _initGroupStore: function(groupId) { this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId); - this._groupStore.on('update', () => { + this._groupStore.registerListener(() => { this._fetchRooms(); }); this._groupStore.on('error', (err) => { - console.error('Error in group store (listened to by GroupRoomList)', err); this.setState({ rooms: null, }); }); - this._fetchRooms(); }, _fetchRooms: function() { diff --git a/src/stores/GroupStore.js b/src/stores/GroupStore.js index 1da1c35a2b..1d3526ecd5 100644 --- a/src/stores/GroupStore.js +++ b/src/stores/GroupStore.js @@ -29,9 +29,10 @@ export default class GroupStore extends EventEmitter { this._matrixClient = matrixClient; this._summary = {}; this._rooms = []; - this._fetchSummary(); - this._fetchRooms(); - this._fetchMembers(); + + this.on('error', (err) => { + console.error(`GroupStore for ${this.groupId} encountered error`, err); + }); } _fetchMembers() { @@ -80,6 +81,13 @@ export default class GroupStore extends EventEmitter { this.emit('update'); } + registerListener(fn) { + this.on('update', fn); + this._fetchSummary(); + this._fetchRooms(); + this._fetchMembers(); + } + getSummary() { return this._summary; } From 5d0aa8d7f75eabaf2694f00b2fb51a0f1bead43c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 27 Oct 2017 11:37:45 +0100 Subject: [PATCH 11/16] Handle 403 when inspecting invited users as non-member --- src/stores/GroupStore.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stores/GroupStore.js b/src/stores/GroupStore.js index 1d3526ecd5..e169e13ddc 100644 --- a/src/stores/GroupStore.js +++ b/src/stores/GroupStore.js @@ -52,6 +52,10 @@ export default class GroupStore extends EventEmitter { }); this._notifyListeners(); }).catch((err) => { + // Invited users not visible to non-members + if (err.httpStatus === 403) { + return; + } console.error("Failed to get group invited member list: " + err); this.emit('error', err); }); From 175fadbb57a4c8330aff6482f347844258b58a34 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 27 Oct 2017 14:33:10 +0100 Subject: [PATCH 12/16] Add unregiseterListener to GroupStore --- src/stores/GroupStore.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stores/GroupStore.js b/src/stores/GroupStore.js index e169e13ddc..66bc293b44 100644 --- a/src/stores/GroupStore.js +++ b/src/stores/GroupStore.js @@ -92,6 +92,10 @@ export default class GroupStore extends EventEmitter { this._fetchMembers(); } + unregisterListener(fn) { + this.removeListener('update', fn); + } + getSummary() { return this._summary; } From 5d0b9d73b4f318ef980c001719fd0d990cffe3b0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 27 Oct 2017 16:20:17 +0100 Subject: [PATCH 13/16] Fix prompt to re-use chat room I managed to lose this when refactoring ChatInviteDialog in https://github.com/matrix-org/matrix-react-sdk/pull/1300 Fixes https://github.com/vector-im/riot-web/issues/5119 --- src/RoomInvite.js | 59 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 427f549eb0..42cff3f5d0 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -21,6 +21,8 @@ import Modal from './Modal'; import { getAddressType } from './UserAddress'; import createRoom from './createRoom'; import sdk from './'; +import dis from './dispatcher'; +import DMRoomMap from './utils/DMRoomMap'; import { _t } from './languageHandler'; export function inviteToRoom(roomId, addr) { @@ -79,15 +81,40 @@ function _onStartChatFinished(shouldInvite, addrs) { const addrTexts = addrs.map((addr) => addr.address); if (_isDmChat(addrTexts)) { - // Start a new DM chat - createRoom({dmUserId: addrTexts[0]}).catch((err) => { - console.error(err.stack); - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, { - title: _t("Failed to invite user"), - description: ((err && err.message) ? err.message : _t("Operation failed")), + const rooms = _getDirectMessageRooms(addrTexts[0]); + if (rooms.length > 0) { + // A Direct Message room already exists for this user, so select a + // room from a list that is similar to the one in MemberInfo panel + const ChatCreateOrReuseDialog = sdk.getComponent( + "views.dialogs.ChatCreateOrReuseDialog", + ); + const close = Modal.createTrackedDialog('Create or Reuse', '', ChatCreateOrReuseDialog, { + userId: addrTexts[0], + onNewDMClick: () => { + dis.dispatch({ + action: 'start_chat', + user_id: addrTexts[0], + }); + close(true); + }, + onExistingRoomSelected: (roomId) => { + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); + close(true); + }, + }).close; + } else { + // Start a new DM chat + createRoom({dmUserId: addrTexts[0]}).catch((err) => { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createTrackedDialog('Failed to invite user', '', ErrorDialog, { + title: _t("Failed to invite user"), + description: ((err && err.message) ? err.message : _t("Operation failed")), + }); }); - }); + } } else { // Start multi user chat let room; @@ -153,3 +180,19 @@ function _showAnyInviteErrors(addrs, room) { return addrs; } +function _getDirectMessageRooms(addr) { + const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); + const dmRooms = dmRoomMap.getDMRoomsForUserId(addr); + const rooms = []; + dmRooms.forEach(dmRoom => { + let room = MatrixClientPeg.get().getRoom(dmRoom); + if (room) { + const me = room.getMember(MatrixClientPeg.get().credentials.userId); + if (me.membership == 'join') { + rooms.push(room); + } + } + }); + return rooms; +} + From 2f3e0fb04932fc39f42122694416effcf6c15204 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 27 Oct 2017 16:29:56 +0100 Subject: [PATCH 14/16] Lint --- src/RoomInvite.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 42cff3f5d0..1979c6d111 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -184,8 +184,8 @@ function _getDirectMessageRooms(addr) { const dmRoomMap = new DMRoomMap(MatrixClientPeg.get()); const dmRooms = dmRoomMap.getDMRoomsForUserId(addr); const rooms = []; - dmRooms.forEach(dmRoom => { - let room = MatrixClientPeg.get().getRoom(dmRoom); + dmRooms.forEach((dmRoom) => { + const room = MatrixClientPeg.get().getRoom(dmRoom); if (room) { const me = room.getMember(MatrixClientPeg.get().credentials.userId); if (me.membership == 'join') { From 8a64123ab8175a67e4974a8cf13f660b9905eeba Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 27 Oct 2017 16:55:04 +0100 Subject: [PATCH 15/16] Add sensible missing entry generator for MELS tests Fixes vector-im/riot-web#5426 (because we don't test plurals anywhere else) --- test/components/views/elements/MemberEventListSummary-test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js index 1618fe4cfe..436133c717 100644 --- a/test/components/views/elements/MemberEventListSummary-test.js +++ b/test/components/views/elements/MemberEventListSummary-test.js @@ -88,6 +88,9 @@ describe('MemberEventListSummary', function() { sandbox = testUtils.stubClient(); languageHandler.setLanguage('en').done(done); + languageHandler.setMissingEntryGenerator(function(key) { + return key.split('|', 2)[1]; + }); }); afterEach(function() { From 4eb8fe3e6ab23ef6493f0b35f44a12bf973a22e2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 27 Oct 2017 17:49:44 +0100 Subject: [PATCH 16/16] Lowercase all usernames As synapse doesn't accept usernames with capitals in them now Fixes https://github.com/vector-im/riot-web/issues/5445 --- src/components/structures/login/Registration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index db488ea237..2ee11f8386 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -302,7 +302,7 @@ module.exports = React.createClass({ } : {}; return this._matrixClient.register( - this.state.formVals.username, + this.state.formVals.username.toLowerCase(), this.state.formVals.password, undefined, // session id: included in the auth dict already auth,