From 774c3dbd3851b4b2d8ea9acc35db000c624f01d6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 28 Sep 2017 09:42:33 -0600 Subject: [PATCH 01/15] Pin/unpin message option in a message's context menu Signed-off-by: Travis Ralston --- .../views/context_menus/MessageContextMenu.js | 30 +++++++++++++++++++ src/i18n/strings/en_EN.json | 2 ++ src/i18n/strings/en_US.json | 2 ++ 3 files changed, 34 insertions(+) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index d6d9302fd..09503f99f 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -65,6 +65,13 @@ module.exports = React.createClass({ this.setState({canRedact}); }, + _isPinned: function() { + const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', ''); + if (!pinnedEvent) return false; + return pinnedEvent.getContent().pinned.includes(this.props.mxEvent.getId()); + }, + onResendClick: function() { Resend.resend(this.props.mxEvent); this.closeMenu(); @@ -122,6 +129,22 @@ module.exports = React.createClass({ this.closeMenu(); }, + onPinClick: function() { + MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '').then(event => { + const eventIds = (event ? event.pinned : []) || []; + if (!eventIds.includes(this.props.mxEvent.getId())) { + // Not pinned - add + eventIds.push(this.props.mxEvent.getId()); + } else { + // Pinned - remove + eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1); + } + + MatrixClientPeg.get().sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, ''); + }); + this.closeMenu(); + }, + closeMenu: function() { if (this.props.onFinished) this.props.onFinished(); }, @@ -147,6 +170,7 @@ module.exports = React.createClass({ let redactButton; let cancelButton; let forwardButton; + let pinButton; let viewSourceButton; let viewClearSourceButton; let unhidePreviewButton; @@ -186,6 +210,11 @@ module.exports = React.createClass({ { _t('Forward Message') } ); + pinButton = ( +
+ { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') } +
+ ); } } @@ -246,6 +275,7 @@ module.exports = React.createClass({ {redactButton} {cancelButton} {forwardButton} + {pinButton} {viewSourceButton} {viewClearSourceButton} {unhidePreviewButton} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 343377466..9b1780f78 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -213,6 +213,8 @@ "You have successfully set a password!": "You have successfully set a password!", "You can now return to your account after signing out, and sign in on other devices.": "You can now return to your account after signing out, and sign in on other devices.", "Continue": "Continue", + "Pin Message": "Pin Message", + "Unpin Message": "Unpin Message", "Please set a password!": "Please set a password!", "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.", "You have successfully set a password and an email address!": "You have successfully set a password and an email address!", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 4eded6160..1d2fc1c97 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -199,6 +199,8 @@ "You have successfully set a password and an email address!": "You have successfully set a password and an email address!", "Remember, you can always set an email address in user settings if you change your mind.": "Remember, you can always set an email address in user settings if you change your mind.", "Warning": "Warning", + "Pin Message": "Pin Message", + "Unpin Message": "Unpin Message", "Checking for an update...": "Checking for an update...", "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", "No update available.": "No update available.", From 5daa16ab53c685666b2ce2ac0ef3a55571406e32 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 28 Sep 2017 15:48:32 -0600 Subject: [PATCH 02/15] Add panel for pinned messages. Signed-off-by: Travis Ralston --- .../views/rooms/PinnedEventsPanel.js | 106 ++++++++++++++++++ src/skins/vector/css/_components.scss | 1 + .../views/rooms/_PinnedEventsPanel.scss | 67 +++++++++++ src/skins/vector/img/icons-pin.svg | 7 ++ 4 files changed, 181 insertions(+) create mode 100644 src/components/views/rooms/PinnedEventsPanel.js create mode 100644 src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss create mode 100644 src/skins/vector/img/icons-pin.svg diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js new file mode 100644 index 000000000..4e5efdd35 --- /dev/null +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -0,0 +1,106 @@ +/* +Copyright 2017 Travis Ralston + +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. +*/ + +'use strict'; + +var React = require('react'); +var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); +var sdk = require('matrix-react-sdk'); +var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); +import { _t } from "matrix-react-sdk/lib/languageHandler"; +import { EventTimeline } from "matrix-js-sdk"; + +module.exports = React.createClass({ + displayName: 'PinnedEventsPanel', + propTypes: { + // The Room from the js-sdk we're going to show pinned events for + room: React.PropTypes.object.isRequired, + + onCancelClick: React.PropTypes.func, + }, + + getInitialState: function() { + return { + loading: true, + }; + }, + + componentDidMount: function() { + const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); + if (!pinnedEvents || !pinnedEvents.getContent().pinned) { + this.setState({ loading: false, pinned: [] }); + } else { + const promises = []; + const cli = MatrixClientPeg.get(); + + pinnedEvents.getContent().pinned.map(eventId => { + promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then(timeline => { + return {eventId, timeline}; + })); + }); + + Promise.all(promises).then(contexts => { + this.setState({ loading: false, pinned: contexts }); + }); + } + }, + + _getPinnedTiles: function() { + const MessageEvent = sdk.getComponent("views.messages.MessageEvent"); + const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); + + if (this.state.pinned.length == 0) { + return
No pinned messages.
; + } + + return this.state.pinned.map(pinnedEvent => { + const event = pinnedEvent.timeline.getEvents().find(e => e.getId() === pinnedEvent.eventId); + const sender = this.props.room.getMember(event.getSender()); + const avatarSize = 40; + + // Don't show non-messages. Technically users can pin state/custom events, but we won't + // support those events. + if (event.getType() !== "m.room.message") return ''; + + return ( +
+ + + {sender.name} + + +
+ ); + }); + }, + + render: function() { + let tiles =
Loading...
; + if (this.state && !this.state.loading) { + tiles = this._getPinnedTiles(); + } + + return ( +
+
+ +

{_t("Pinned Messages")}

+ { tiles } +
+
+ ); + } +}); diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss index 61bfa104a..1d351c82d 100644 --- a/src/skins/vector/css/_components.scss +++ b/src/skins/vector/css/_components.scss @@ -88,4 +88,5 @@ @import "./vector-web/views/rooms/_RoomDropTarget.scss"; @import "./vector-web/views/rooms/_RoomTooltip.scss"; @import "./vector-web/views/rooms/_SearchBar.scss"; +@import "./vector-web/views/rooms/_PinnedEventsPanel.scss"; @import "./vector-web/views/settings/_Notifications.scss"; diff --git a/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss b/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss new file mode 100644 index 000000000..883eaa26e --- /dev/null +++ b/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss @@ -0,0 +1,67 @@ +/* +Copyright 2017 Travis Ralston + +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. +*/ + +.mx_PinnedEventsPanel { + border-top: 1px solid $primary-hairline-color; +} + +.mx_PinnedEventsPanel_body { + max-height: 300px; + overflow-y: scroll; +} + +.mx_PinnedEventsPanel_header { + margin: 0; + padding-top: 8px; + padding-bottom: 15px; +} + +.mx_PinnedEventsPanel_pinnedEvent { + min-height: 40px; + margin-bottom: 5px; + cursor: pointer; + width: 100%; + border-radius: 5px; // for the hover +} + +.mx_PinnedEventsPanel_pinnedEvent:hover { + background-color: $event-selected-color; +} + +.mx_PinnedEventsPanel_pinnedEvent .mx_PinnedEventsPanel_sender { + color: #868686; + font-size: 0.8em; + vertical-align: top; + display: block; +} + +.mx_PinnedEventsPanel_pinnedEvent .mx_EventTile_content { + margin-left: 50px; + position: relative; + top: 0; + left: 0; +} + +.mx_PinnedEventsPanel_pinnedEvent .mx_BaseAvatar { + float: left; + margin-right: 10px; +} + +.mx_PinnedEventsPanel_cancel { + margin: 12px; + float: right; + display: inline-block; +} diff --git a/src/skins/vector/img/icons-pin.svg b/src/skins/vector/img/icons-pin.svg new file mode 100644 index 000000000..a6fbf13ba --- /dev/null +++ b/src/skins/vector/img/icons-pin.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From fa5a23e0df3724ce0a3d80772503d0105b6c0af8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 28 Sep 2017 16:59:22 -0600 Subject: [PATCH 03/15] Permalink pins to their original events Signed-off-by: Travis Ralston --- .../views/rooms/PinnedEventsPanel.js | 50 +++++++++++++------ .../views/rooms/_PinnedEventsPanel.scss | 10 ++-- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index 4e5efdd35..a5d5d2ed5 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -20,9 +20,43 @@ var React = require('react'); var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); var sdk = require('matrix-react-sdk'); var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); +var dis = require('matrix-react-sdk/lib/dispatcher'); import { _t } from "matrix-react-sdk/lib/languageHandler"; import { EventTimeline } from "matrix-js-sdk"; +const PinnedEventTile = React.createClass({ + displayName: 'PinnedEventTile', + propTypes: { + mxRoom: React.PropTypes.object.isRequired, + mxEvent: React.PropTypes.object.isRequired, + }, + onTileClicked: function() { + dis.dispatch({ + action: 'view_room', + event_id: this.props.mxEvent.getId(), + highlighted: true, + room_id: this.props.mxEvent.getRoomId(), + }); + }, + render: function() { + const MessageEvent = sdk.getComponent("views.messages.MessageEvent"); + const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); + + const sender = this.props.mxRoom.getMember(this.props.mxEvent.getSender()); + const avatarSize = 40; + + return ( +
+ + + {sender.name} + + +
+ ); + } +}); + module.exports = React.createClass({ displayName: 'PinnedEventsPanel', propTypes: { @@ -59,31 +93,17 @@ module.exports = React.createClass({ }, _getPinnedTiles: function() { - const MessageEvent = sdk.getComponent("views.messages.MessageEvent"); - const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); - if (this.state.pinned.length == 0) { return
No pinned messages.
; } return this.state.pinned.map(pinnedEvent => { const event = pinnedEvent.timeline.getEvents().find(e => e.getId() === pinnedEvent.eventId); - const sender = this.props.room.getMember(event.getSender()); - const avatarSize = 40; // Don't show non-messages. Technically users can pin state/custom events, but we won't // support those events. if (event.getType() !== "m.room.message") return ''; - - return ( -
- - - {sender.name} - - -
- ); + return (); }); }, diff --git a/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss b/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss index 883eaa26e..133fa7032 100644 --- a/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss +++ b/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss @@ -29,7 +29,7 @@ limitations under the License. padding-bottom: 15px; } -.mx_PinnedEventsPanel_pinnedEvent { +.mx_PinnedEventTile { min-height: 40px; margin-bottom: 5px; cursor: pointer; @@ -37,25 +37,25 @@ limitations under the License. border-radius: 5px; // for the hover } -.mx_PinnedEventsPanel_pinnedEvent:hover { +.mx_PinnedEventTile:hover { background-color: $event-selected-color; } -.mx_PinnedEventsPanel_pinnedEvent .mx_PinnedEventsPanel_sender { +.mx_PinnedEventTile .mx_PinnedEventTile_sender { color: #868686; font-size: 0.8em; vertical-align: top; display: block; } -.mx_PinnedEventsPanel_pinnedEvent .mx_EventTile_content { +.mx_PinnedEventTile .mx_EventTile_content { margin-left: 50px; position: relative; top: 0; left: 0; } -.mx_PinnedEventsPanel_pinnedEvent .mx_BaseAvatar { +.mx_PinnedEventTile .mx_BaseAvatar { float: left; margin-right: 10px; } From 9b11f576fecc1721f77cdf19ce594e13eed36c59 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 10:41:23 -0600 Subject: [PATCH 04/15] Add action bar to pinned event tiles; support unpinning from the panel Signed-off-by: Travis Ralston --- .../views/rooms/PinnedEventsPanel.js | 33 +++++++++++++++++-- .../views/rooms/_PinnedEventsPanel.scss | 22 ++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index a5d5d2ed5..53bf3db65 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -29,6 +29,7 @@ const PinnedEventTile = React.createClass({ propTypes: { mxRoom: React.PropTypes.object.isRequired, mxEvent: React.PropTypes.object.isRequired, + onUnpinned: React.PropTypes.func, }, onTileClicked: function() { dis.dispatch({ @@ -38,6 +39,22 @@ const PinnedEventTile = React.createClass({ room_id: this.props.mxEvent.getRoomId(), }); }, + onUnpinClicked: function() { + const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", ""); + if (!pinnedEvents || !pinnedEvents.getContent().pinned) { + // Nothing to do: already unpinned + if (this.props.onUnpinned) this.props.onUnpinned(); + } else { + const pinned = pinnedEvents.getContent().pinned; + const index = pinned.indexOf(this.props.mxEvent.getId()); + if (index !== -1) { + pinned.splice(index, 1); + MatrixClientPeg.get().sendStateEvent(this.props.mxRoom.roomId, 'm.room.pinned_events', {pinned}, '').then(() => { + if (this.props.onUnpinned) this.props.onUnpinned(); + }); + } else if (this.props.onUnpinned) this.props.onUnpinned(); + } + }, render: function() { const MessageEvent = sdk.getComponent("views.messages.MessageEvent"); const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); @@ -46,7 +63,15 @@ const PinnedEventTile = React.createClass({ const avatarSize = 40; return ( -
+
+
+ + Jump to message + + {_t('Unpin +
+ {sender.name} @@ -73,6 +98,10 @@ module.exports = React.createClass({ }, componentDidMount: function() { + this._updatePinnedMessages(); + }, + + _updatePinnedMessages: function() { const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); if (!pinnedEvents || !pinnedEvents.getContent().pinned) { this.setState({ loading: false, pinned: [] }); @@ -103,7 +132,7 @@ module.exports = React.createClass({ // Don't show non-messages. Technically users can pin state/custom events, but we won't // support those events. if (event.getType() !== "m.room.message") return ''; - return (); + return (); }); }, diff --git a/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss b/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss index 133fa7032..a2041cfe4 100644 --- a/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss +++ b/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss @@ -32,7 +32,6 @@ limitations under the License. .mx_PinnedEventTile { min-height: 40px; margin-bottom: 5px; - cursor: pointer; width: 100%; border-radius: 5px; // for the hover } @@ -46,6 +45,7 @@ limitations under the License. font-size: 0.8em; vertical-align: top; display: block; + padding-bottom: 3px; } .mx_PinnedEventTile .mx_EventTile_content { @@ -65,3 +65,23 @@ limitations under the License. float: right; display: inline-block; } + +.mx_PinnedEventTile:hover .mx_PinnedEventTile_actions { + display: block; +} + +.mx_PinnedEventTile_actions { + float: right; + margin-right: 10px; + display: none; +} + +.mx_PinnedEventTile_unpinButton { + cursor: pointer; + margin-left: 10px; +} + +.mx_PinnedEventTile_gotoButton { + display: inline-block; + font-size: 0.8em; +} From 59a253af37dcfec7284d963f8122ccc2bd25191c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 10:53:44 -0600 Subject: [PATCH 05/15] Don't show the unpin button if the user can't unpin the message. Signed-off-by: Travis Ralston --- src/components/views/rooms/PinnedEventsPanel.js | 12 ++++++++++-- .../vector-web/views/rooms/_PinnedEventsPanel.scss | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index 53bf3db65..8f898b11e 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -55,6 +55,9 @@ const PinnedEventTile = React.createClass({ } else if (this.props.onUnpinned) this.props.onUnpinned(); } }, + _canUnpin: function() { + return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get()); + }, render: function() { const MessageEvent = sdk.getComponent("views.messages.MessageEvent"); const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); @@ -62,14 +65,19 @@ const PinnedEventTile = React.createClass({ const sender = this.props.mxRoom.getMember(this.props.mxEvent.getSender()); const avatarSize = 40; + let unpinButton = null; + if (this._canUnpin()) { + unpinButton = {_t('Unpin; + } + return (
Jump to message - {_t('Unpin + { unpinButton }
diff --git a/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss b/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss index a2041cfe4..44297cfae 100644 --- a/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss +++ b/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss @@ -20,7 +20,8 @@ limitations under the License. .mx_PinnedEventsPanel_body { max-height: 300px; - overflow-y: scroll; + overflow-y: auto; + padding-bottom: 15px; } .mx_PinnedEventsPanel_header { From efdb4b02c6dfaafc315ce3d6dd8f7435034f3c07 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 10:55:52 -0600 Subject: [PATCH 06/15] i18n for 'Jump to message' button Signed-off-by: Travis Ralston --- src/components/views/rooms/PinnedEventsPanel.js | 2 +- src/i18n/strings/en_EN.json | 1 + src/i18n/strings/en_US.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index 8f898b11e..fba0d8282 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -75,7 +75,7 @@ const PinnedEventTile = React.createClass({
- Jump to message + { _t("Jump to message") } { unpinButton }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9b1780f78..d403c7c1e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -215,6 +215,7 @@ "Continue": "Continue", "Pin Message": "Pin Message", "Unpin Message": "Unpin Message", + "Jump to message": "Jump to message", "Please set a password!": "Please set a password!", "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.", "You have successfully set a password and an email address!": "You have successfully set a password and an email address!", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 1d2fc1c97..cc42d7856 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -201,6 +201,7 @@ "Warning": "Warning", "Pin Message": "Pin Message", "Unpin Message": "Unpin Message", + "Jump to message": "Jump to message", "Checking for an update...": "Checking for an update...", "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", "No update available.": "No update available.", From f7389b70aac8c2d933c24b3cff7ad3324ff9db91 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 11:01:17 -0600 Subject: [PATCH 07/15] Hide the pin option in the context menu if the user can't pin messages Signed-off-by: Travis Ralston --- .../views/context_menus/MessageContextMenu.js | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 09503f99f..bc76fd255 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -43,26 +43,30 @@ module.exports = React.createClass({ getInitialState: function() { return { canRedact: false, + canPin: false, }; }, componentWillMount: function() { - MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkCanRedact); - this._checkCanRedact(); + MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions); + this._checkPermissions(); }, componentWillUnmount: function() { const cli = MatrixClientPeg.get(); if (cli) { - cli.removeListener('RoomMember.powerLevel', this._checkCanRedact); + cli.removeListener('RoomMember.powerLevel', this._checkPermissions); } }, - _checkCanRedact: function() { + _checkPermissions: function() { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); + const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId); - this.setState({canRedact}); + const canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli); + + this.setState({canRedact, canPin}); }, _isPinned: function() { @@ -210,11 +214,14 @@ module.exports = React.createClass({ { _t('Forward Message') }
); - pinButton = ( -
- { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') } -
- ); + + if (this.state.canPin) { + pinButton = ( +
+ {this._isPinned() ? _t('Unpin Message') : _t('Pin Message')} +
+ ); + } } } From 965a25ba84aaa4aed0b98dba0cbd7e60d1463e0c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 11:08:22 -0600 Subject: [PATCH 08/15] Fix bug where rooms missing m.room.pinned_events could not pin messages Signed-off-by: Travis Ralston --- .../views/context_menus/MessageContextMenu.js | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index bc76fd255..a04bf2745 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -134,18 +134,24 @@ module.exports = React.createClass({ }, onPinClick: function() { - MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '').then(event => { - const eventIds = (event ? event.pinned : []) || []; - if (!eventIds.includes(this.props.mxEvent.getId())) { - // Not pinned - add - eventIds.push(this.props.mxEvent.getId()); - } else { - // Pinned - remove - eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1); - } + MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '') + .then(null, e => { + // Intercept the Event Not Found error and fall through the promise chain with no event. + if (e.errcode === "M_NOT_FOUND") return null; + throw e; + }) + .then(event => { + const eventIds = (event ? event.pinned : []) || []; + if (!eventIds.includes(this.props.mxEvent.getId())) { + // Not pinned - add + eventIds.push(this.props.mxEvent.getId()); + } else { + // Pinned - remove + eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1); + } - MatrixClientPeg.get().sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, ''); - }); + MatrixClientPeg.get().sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, ''); + }); this.closeMenu(); }, From aff02885def8e0ccf8ae4e5118754dfd6de26bfd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 11:21:21 -0600 Subject: [PATCH 09/15] Don't fail if an event doesn't belong to a room. Signed-off-by: Travis Ralston --- src/components/views/rooms/PinnedEventsPanel.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index fba0d8282..7454e1044 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -120,11 +120,16 @@ module.exports = React.createClass({ pinnedEvents.getContent().pinned.map(eventId => { promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then(timeline => { return {eventId, timeline}; + }).catch(err => { + console.error("Error looking up pinned event " + eventId + " in room " + this.props.room.roomId); + console.error(err); + return null; // return lack of context to avoid unhandled errors })); }); Promise.all(promises).then(contexts => { - this.setState({ loading: false, pinned: contexts }); + // Filter out the contexts that may have failed early by doing a truthy test + this.setState({ loading: false, pinned: contexts.filter(c => c) }); }); } }, From 0a3bc1c6675e4cd9e1d9f58d6cd4516691ffc190 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 11:21:44 -0600 Subject: [PATCH 10/15] Don't try to show redacted messages Signed-off-by: Travis Ralston --- src/components/views/rooms/PinnedEventsPanel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index 7454e1044..d86d4b58d 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -145,6 +145,7 @@ module.exports = React.createClass({ // Don't show non-messages. Technically users can pin state/custom events, but we won't // support those events. if (event.getType() !== "m.room.message") return ''; + if (event.isRedacted()) return ''; // don't show redacted pins return (); }); }, From b0190f6a65a57c6d2f73bfdbc72aa77e045a1d1d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 11:22:06 -0600 Subject: [PATCH 11/15] i18n for remaining strings Signed-off-by: Travis Ralston --- src/components/views/rooms/PinnedEventsPanel.js | 4 ++-- src/i18n/strings/en_EN.json | 2 ++ src/i18n/strings/en_US.json | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index d86d4b58d..ec50ae079 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -136,7 +136,7 @@ module.exports = React.createClass({ _getPinnedTiles: function() { if (this.state.pinned.length == 0) { - return
No pinned messages.
; + return
{ _t("No pinned messages.") }
; } return this.state.pinned.map(pinnedEvent => { @@ -151,7 +151,7 @@ module.exports = React.createClass({ }, render: function() { - let tiles =
Loading...
; + let tiles =
{ _t("Loading...") }
; if (this.state && !this.state.loading) { tiles = this._getPinnedTiles(); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d403c7c1e..7f2d1185c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -216,6 +216,8 @@ "Pin Message": "Pin Message", "Unpin Message": "Unpin Message", "Jump to message": "Jump to message", + "No pinned messages.": "No pinned messages.", + "Loading...": "Loading...", "Please set a password!": "Please set a password!", "This will allow you to return to your account after signing out, and sign in on other devices.": "This will allow you to return to your account after signing out, and sign in on other devices.", "You have successfully set a password and an email address!": "You have successfully set a password and an email address!", diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index cc42d7856..66042713b 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -202,6 +202,8 @@ "Pin Message": "Pin Message", "Unpin Message": "Unpin Message", "Jump to message": "Jump to message", + "No pinned messages.": "No pinned messages.", + "Loading...": "Loading...", "Checking for an update...": "Checking for an update...", "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", "No update available.": "No update available.", From 2d5acfc0bc1337a0e816e37da5970e23272becfd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 11:30:52 -0600 Subject: [PATCH 12/15] Filter pinned events before rendering Signed-off-by: Travis Ralston --- .../views/rooms/PinnedEventsPanel.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index ec50ae079..e7da2ff2a 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -119,7 +119,8 @@ module.exports = React.createClass({ pinnedEvents.getContent().pinned.map(eventId => { promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then(timeline => { - return {eventId, timeline}; + const event = timeline.getEvents().find(e => e.getId() === eventId); + return {eventId, timeline, event}; }).catch(err => { console.error("Error looking up pinned event " + eventId + " in room " + this.props.room.roomId); console.error(err); @@ -128,8 +129,15 @@ module.exports = React.createClass({ }); Promise.all(promises).then(contexts => { - // Filter out the contexts that may have failed early by doing a truthy test - this.setState({ loading: false, pinned: contexts.filter(c => c) }); + // Filter out the messages before we try to render them + const pinned = contexts.filter(context => { + if (!context) return false; // no context == not applicable for the room + if (context.event.getType() !== "m.room.message") return false; + if (context.event.isRedacted()) return false; + return true; + }); + + this.setState({ loading: false, pinned }); }); } }, @@ -139,14 +147,8 @@ module.exports = React.createClass({ return
{ _t("No pinned messages.") }
; } - return this.state.pinned.map(pinnedEvent => { - const event = pinnedEvent.timeline.getEvents().find(e => e.getId() === pinnedEvent.eventId); - - // Don't show non-messages. Technically users can pin state/custom events, but we won't - // support those events. - if (event.getType() !== "m.room.message") return ''; - if (event.isRedacted()) return ''; // don't show redacted pins - return (); + return this.state.pinned.map(context => { + return (); }); }, From 078ee54edfffa05781ce65ce74a42678d7ce3b7d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 13:15:21 -0600 Subject: [PATCH 13/15] Move the PinnedEventsPanel to the react-sdk Signed-off-by: Travis Ralston --- .../views/rooms/PinnedEventsPanel.js | 171 ------------------ src/skins/vector/css/_components.scss | 2 +- .../views/rooms/_PinnedEventsPanel.scss | 0 3 files changed, 1 insertion(+), 172 deletions(-) delete mode 100644 src/components/views/rooms/PinnedEventsPanel.js rename src/skins/vector/css/{vector-web => matrix-react-sdk}/views/rooms/_PinnedEventsPanel.scss (100%) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js deleted file mode 100644 index e7da2ff2a..000000000 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ /dev/null @@ -1,171 +0,0 @@ -/* -Copyright 2017 Travis Ralston - -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. -*/ - -'use strict'; - -var React = require('react'); -var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); -var sdk = require('matrix-react-sdk'); -var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton'); -var dis = require('matrix-react-sdk/lib/dispatcher'); -import { _t } from "matrix-react-sdk/lib/languageHandler"; -import { EventTimeline } from "matrix-js-sdk"; - -const PinnedEventTile = React.createClass({ - displayName: 'PinnedEventTile', - propTypes: { - mxRoom: React.PropTypes.object.isRequired, - mxEvent: React.PropTypes.object.isRequired, - onUnpinned: React.PropTypes.func, - }, - onTileClicked: function() { - dis.dispatch({ - action: 'view_room', - event_id: this.props.mxEvent.getId(), - highlighted: true, - room_id: this.props.mxEvent.getRoomId(), - }); - }, - onUnpinClicked: function() { - const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", ""); - if (!pinnedEvents || !pinnedEvents.getContent().pinned) { - // Nothing to do: already unpinned - if (this.props.onUnpinned) this.props.onUnpinned(); - } else { - const pinned = pinnedEvents.getContent().pinned; - const index = pinned.indexOf(this.props.mxEvent.getId()); - if (index !== -1) { - pinned.splice(index, 1); - MatrixClientPeg.get().sendStateEvent(this.props.mxRoom.roomId, 'm.room.pinned_events', {pinned}, '').then(() => { - if (this.props.onUnpinned) this.props.onUnpinned(); - }); - } else if (this.props.onUnpinned) this.props.onUnpinned(); - } - }, - _canUnpin: function() { - return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get()); - }, - render: function() { - const MessageEvent = sdk.getComponent("views.messages.MessageEvent"); - const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); - - const sender = this.props.mxRoom.getMember(this.props.mxEvent.getSender()); - const avatarSize = 40; - - let unpinButton = null; - if (this._canUnpin()) { - unpinButton = {_t('Unpin; - } - - return ( -
-
- - { _t("Jump to message") } - - { unpinButton } -
- - - - {sender.name} - - -
- ); - } -}); - -module.exports = React.createClass({ - displayName: 'PinnedEventsPanel', - propTypes: { - // The Room from the js-sdk we're going to show pinned events for - room: React.PropTypes.object.isRequired, - - onCancelClick: React.PropTypes.func, - }, - - getInitialState: function() { - return { - loading: true, - }; - }, - - componentDidMount: function() { - this._updatePinnedMessages(); - }, - - _updatePinnedMessages: function() { - const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); - if (!pinnedEvents || !pinnedEvents.getContent().pinned) { - this.setState({ loading: false, pinned: [] }); - } else { - const promises = []; - const cli = MatrixClientPeg.get(); - - pinnedEvents.getContent().pinned.map(eventId => { - promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then(timeline => { - const event = timeline.getEvents().find(e => e.getId() === eventId); - return {eventId, timeline, event}; - }).catch(err => { - console.error("Error looking up pinned event " + eventId + " in room " + this.props.room.roomId); - console.error(err); - return null; // return lack of context to avoid unhandled errors - })); - }); - - Promise.all(promises).then(contexts => { - // Filter out the messages before we try to render them - const pinned = contexts.filter(context => { - if (!context) return false; // no context == not applicable for the room - if (context.event.getType() !== "m.room.message") return false; - if (context.event.isRedacted()) return false; - return true; - }); - - this.setState({ loading: false, pinned }); - }); - } - }, - - _getPinnedTiles: function() { - if (this.state.pinned.length == 0) { - return
{ _t("No pinned messages.") }
; - } - - return this.state.pinned.map(context => { - return (); - }); - }, - - render: function() { - let tiles =
{ _t("Loading...") }
; - if (this.state && !this.state.loading) { - tiles = this._getPinnedTiles(); - } - - return ( -
-
- -

{_t("Pinned Messages")}

- { tiles } -
-
- ); - } -}); diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss index 1d351c82d..837b675f3 100644 --- a/src/skins/vector/css/_components.scss +++ b/src/skins/vector/css/_components.scss @@ -64,6 +64,7 @@ @import "./matrix-react-sdk/views/voip/_CallView.scss"; @import "./matrix-react-sdk/views/voip/_IncomingCallbox.scss"; @import "./matrix-react-sdk/views/voip/_VideoView.scss"; +@import "./matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss"; @import "./vector-web/_fonts.scss"; @import "./vector-web/structures/_CompatibilityPage.scss"; @import "./vector-web/structures/_HomePage.scss"; @@ -88,5 +89,4 @@ @import "./vector-web/views/rooms/_RoomDropTarget.scss"; @import "./vector-web/views/rooms/_RoomTooltip.scss"; @import "./vector-web/views/rooms/_SearchBar.scss"; -@import "./vector-web/views/rooms/_PinnedEventsPanel.scss"; @import "./vector-web/views/settings/_Notifications.scss"; diff --git a/src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss similarity index 100% rename from src/skins/vector/css/vector-web/views/rooms/_PinnedEventsPanel.scss rename to src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss From 2d153a72c1351642d6826fe93437d4b4fc5c0273 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 29 Sep 2017 13:33:14 -0600 Subject: [PATCH 14/15] Split up CSS; use .catch instead of .then Signed-off-by: Travis Ralston --- .../views/context_menus/MessageContextMenu.js | 2 +- src/skins/vector/css/_components.scss | 1 + .../views/rooms/_PinnedEventTile.scss | 67 +++++++++++++++++++ .../views/rooms/_PinnedEventsPanel.scss | 51 -------------- 4 files changed, 69 insertions(+), 52 deletions(-) create mode 100644 src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventTile.scss diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index a04bf2745..7085e81d5 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -135,7 +135,7 @@ module.exports = React.createClass({ onPinClick: function() { MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '') - .then(null, e => { + .catch(e => { // Intercept the Event Not Found error and fall through the promise chain with no event. if (e.errcode === "M_NOT_FOUND") return null; throw e; diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss index 837b675f3..5c0ae4aa1 100644 --- a/src/skins/vector/css/_components.scss +++ b/src/skins/vector/css/_components.scss @@ -65,6 +65,7 @@ @import "./matrix-react-sdk/views/voip/_IncomingCallbox.scss"; @import "./matrix-react-sdk/views/voip/_VideoView.scss"; @import "./matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss"; +@import "./matrix-react-sdk/views/rooms/_PinnedEventTile.scss"; @import "./vector-web/_fonts.scss"; @import "./vector-web/structures/_CompatibilityPage.scss"; @import "./vector-web/structures/_HomePage.scss"; diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventTile.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventTile.scss new file mode 100644 index 000000000..ca790ef8f --- /dev/null +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventTile.scss @@ -0,0 +1,67 @@ +/* +Copyright 2017 Travis Ralston + +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. +*/ + +.mx_PinnedEventTile { + min-height: 40px; + margin-bottom: 5px; + width: 100%; + border-radius: 5px; // for the hover +} + +.mx_PinnedEventTile:hover { + background-color: $event-selected-color; +} + +.mx_PinnedEventTile .mx_PinnedEventTile_sender { + color: #868686; + font-size: 0.8em; + vertical-align: top; + display: block; + padding-bottom: 3px; +} + +.mx_PinnedEventTile .mx_EventTile_content { + margin-left: 50px; + position: relative; + top: 0; + left: 0; +} + +.mx_PinnedEventTile .mx_BaseAvatar { + float: left; + margin-right: 10px; +} + +.mx_PinnedEventTile:hover .mx_PinnedEventTile_actions { + display: block; +} + +.mx_PinnedEventTile_actions { + float: right; + margin-right: 10px; + display: none; +} + +.mx_PinnedEventTile_unpinButton { + display: inline-block; + cursor: pointer; + margin-left: 10px; +} + +.mx_PinnedEventTile_gotoButton { + display: inline-block; + font-size: 0.8em; +} diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss index 44297cfae..663d5bdf6 100644 --- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss @@ -30,59 +30,8 @@ limitations under the License. padding-bottom: 15px; } -.mx_PinnedEventTile { - min-height: 40px; - margin-bottom: 5px; - width: 100%; - border-radius: 5px; // for the hover -} - -.mx_PinnedEventTile:hover { - background-color: $event-selected-color; -} - -.mx_PinnedEventTile .mx_PinnedEventTile_sender { - color: #868686; - font-size: 0.8em; - vertical-align: top; - display: block; - padding-bottom: 3px; -} - -.mx_PinnedEventTile .mx_EventTile_content { - margin-left: 50px; - position: relative; - top: 0; - left: 0; -} - -.mx_PinnedEventTile .mx_BaseAvatar { - float: left; - margin-right: 10px; -} - .mx_PinnedEventsPanel_cancel { margin: 12px; float: right; display: inline-block; } - -.mx_PinnedEventTile:hover .mx_PinnedEventTile_actions { - display: block; -} - -.mx_PinnedEventTile_actions { - float: right; - margin-right: 10px; - display: none; -} - -.mx_PinnedEventTile_unpinButton { - cursor: pointer; - margin-left: 10px; -} - -.mx_PinnedEventTile_gotoButton { - display: inline-block; - font-size: 0.8em; -} From 6926c96f368b73f58fb3dad21f0d5be4aebd560c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 14 Oct 2017 16:41:44 -0600 Subject: [PATCH 15/15] Hide pinning messages behind a labs settings Signed-off-by: Travis Ralston --- src/components/views/context_menus/MessageContextMenu.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 7085e81d5..a07d11626 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -64,7 +64,10 @@ module.exports = React.createClass({ const room = cli.getRoom(this.props.mxEvent.getRoomId()); const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId); - const canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli); + let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli); + + // HACK: Intentionally say we can't pin if the user doesn't want to use the functionality + if (!UserSettingsStore.isFeatureEnabled("feature_pinning")) canPin = false; this.setState({canRedact, canPin}); },