From 4f6cd6b23a8474550cdd745feafeb69dbc88bbf7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 15 Oct 2017 21:17:43 -0600 Subject: [PATCH 1/5] Add a small indicator for when a new event is pinned Signed-off-by: Travis Ralston --- .../views/rooms/PinnedEventsPanel.js | 19 ++++++++++ src/components/views/rooms/RoomHeader.js | 36 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index deea03f030..5a99d9ab2d 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -71,6 +71,25 @@ module.exports = React.createClass({ this.setState({ loading: false, pinned }); }); } + + this._updateReadState(); + }, + + _updateReadState: function() { + const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); + if (!pinnedEvents) return; // nothing to read + + let lastReadEvent = null; + const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); + if (readPinsEvent) { + lastReadEvent = readPinsEvent.getContent().last_read_id; + } + + if (lastReadEvent !== pinnedEvents.getId()) { + MatrixClientPeg.get().setRoomAccountData(this.props.room.roomId, "im.vector.room.read_pins", { + last_read_id: pinnedEvents.getId(), + }); + } }, _getPinnedTiles: function() { diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 4df0ff738c..ea5748db60 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -65,6 +65,7 @@ module.exports = React.createClass({ componentDidMount: function() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._onRoomStateEvents); + cli.on("Room.accountData", this._onRoomAccountData); // When a room name occurs, RoomState.events is fired *before* // room.name is updated. So we have to listen to Room.name as well as @@ -87,6 +88,7 @@ module.exports = React.createClass({ const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener("RoomState.events", this._onRoomStateEvents); + cli.removeListener("Room.accountData", this._onRoomAccountData); } }, @@ -99,6 +101,13 @@ module.exports = React.createClass({ this._rateLimitedUpdate(); }, + _onRoomAccountData: function(event, room) { + if (!this.props.room || room.roomId !== this.props.room.roomId) return; + if (event.getType() !== "im.vector.room.read_pins") return; + + this._rateLimitedUpdate(); + }, + _rateLimitedUpdate: new RateLimitedFunc(function() { /* eslint-disable babel/no-invalid-this */ this.forceUpdate(); @@ -139,6 +148,25 @@ module.exports = React.createClass({ dis.dispatch({ action: 'show_right_panel' }); }, + _hasUnreadPins: function() { + const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); + if (!currentPinEvent) return false; + if (currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0) { + return false; // no pins == nothing to read + } + + const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); + if (readPinsEvent) { + const lastReadEvent = readPinsEvent.getContent().last_read_id; + if (lastReadEvent) { + return currentPinEvent.getId() !== lastReadEvent; + } + } + + // There's pins, and we haven't read any of them + return true; + }, + /** * After editing the settings, get the new name for the room * @@ -302,8 +330,14 @@ module.exports = React.createClass({ } if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) { + let newPinsNotification = null; + if (this._hasUnreadPins()) { + newPinsNotification = (
); + } pinnedEventsButton = - + + { newPinsNotification } ; } From 3656fdb571a63b9686856386cb8d78c5c5a572c1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 3 Nov 2017 18:12:57 -0600 Subject: [PATCH 2/5] Store read pinned events as an array to avoid racing saves. Signed-off-by: Travis Ralston --- src/components/views/rooms/PinnedEventsPanel.js | 13 +++++++------ src/components/views/rooms/RoomHeader.js | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index 5a99d9ab2d..ddadc30258 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -79,21 +79,22 @@ module.exports = React.createClass({ const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); if (!pinnedEvents) return; // nothing to read - let lastReadEvent = null; + let readStateEvents = null; const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); - if (readPinsEvent) { - lastReadEvent = readPinsEvent.getContent().last_read_id; + if (readPinsEvent && readPinsEvent.getContent()) { + readStateEvents = readPinsEvent.getContent().event_ids || []; } - if (lastReadEvent !== pinnedEvents.getId()) { + if (!readStateEvents.includes(pinnedEvents.getId())) { + readStateEvents.push(pinnedEvents.getId()); MatrixClientPeg.get().setRoomAccountData(this.props.room.roomId, "im.vector.room.read_pins", { - last_read_id: pinnedEvents.getId(), + event_ids: readStateEvents, }); } }, _getPinnedTiles: function() { - if (this.state.pinned.length == 0) { + if (this.state.pinned.length === 0) { return (
{ _t("No pinned messages.") }
); } diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index ea5748db60..2a06a90391 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -157,9 +157,9 @@ module.exports = React.createClass({ const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); if (readPinsEvent) { - const lastReadEvent = readPinsEvent.getContent().last_read_id; - if (lastReadEvent) { - return currentPinEvent.getId() !== lastReadEvent; + const readStateEvents = readPinsEvent.getContent().event_ids; + if (readStateEvents) { + return !readStateEvents.includes(currentPinEvent.getId()); } } From 5c37155730d343dc5e5f53ad2b12cb57d28aa9d7 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 3 Nov 2017 18:18:09 -0600 Subject: [PATCH 3/5] Don't assume we have a valid event. Signed-off-by: Travis Ralston --- src/components/views/rooms/RoomHeader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 2a06a90391..dcfb2e5a7c 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -156,8 +156,8 @@ module.exports = React.createClass({ } const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); - if (readPinsEvent) { - const readStateEvents = readPinsEvent.getContent().event_ids; + if (readPinsEvent && readPinsEvent.getContent()) { + const readStateEvents = readPinsEvent.getContent().event_ids || []; if (readStateEvents) { return !readStateEvents.includes(currentPinEvent.getId()); } From de6fc32a87d44bf63270366ecffe5e058949fc47 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 3 Nov 2017 20:00:48 -0600 Subject: [PATCH 4/5] Show an indicator when there are any pins in the room Signed-off-by: Travis Ralston --- .../views/rooms/PinnedEventsPanel.js | 8 ++--- src/components/views/rooms/RoomHeader.js | 16 ++++++++-- src/utils/PinningUtils.js | 30 +++++++++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 src/utils/PinningUtils.js diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index d72e9a1b3f..5325768399 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -19,6 +19,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg"; import AccessibleButton from "../elements/AccessibleButton"; import PinnedEventTile from "./PinnedEventTile"; import { _t } from '../../../languageHandler'; +import PinningUtils from "../../../utils/PinningUtils"; module.exports = React.createClass({ displayName: 'PinnedEventsPanel', @@ -61,12 +62,7 @@ module.exports = React.createClass({ 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; - }); + const pinned = contexts.filter((context) => PinningUtils.isPinnable(context.event)); this.setState({ loading: false, pinned }); }); diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 8374defe21..f558f44b4e 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -167,6 +167,13 @@ module.exports = React.createClass({ return true; }, + _hasPins: function() { + const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); + if (!currentPinEvent) return false; + + return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0); + }, + /** * After editing the settings, get the new name for the room * @@ -333,14 +340,17 @@ module.exports = React.createClass({ } if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) { - let newPinsNotification = null; + let pinsIndicator = null; if (this._hasUnreadPins()) { - newPinsNotification = (
); + pinsIndicator = (
); + } else if (this._hasPins()) { + pinsIndicator = (
); } + pinnedEventsButton = - { newPinsNotification } + { pinsIndicator } ; } diff --git a/src/utils/PinningUtils.js b/src/utils/PinningUtils.js new file mode 100644 index 0000000000..90d26cc988 --- /dev/null +++ b/src/utils/PinningUtils.js @@ -0,0 +1,30 @@ +/* +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. +*/ + +export default class PinningUtils { + /** + * Determines if the given event may be pinned. + * @param {MatrixEvent} event The event to check. + * @return {boolean} True if the event may be pinned, false otherwise. + */ + static isPinnable(event) { + if (!event) return false; + if (event.getType() !== "m.room.message") return false; + if (event.isRedacted()) return false; + + return true; + } +} From 142d23a0995890716c943dbb97e8c2f6db1261cc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 7 Nov 2017 09:16:19 -0700 Subject: [PATCH 5/5] Cap the read pin event IDs to 10 Signed-off-by: Travis Ralston --- src/components/views/rooms/PinnedEventsPanel.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index 5325768399..1e6de43ab5 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -83,6 +83,10 @@ module.exports = React.createClass({ if (!readStateEvents.includes(pinnedEvents.getId())) { readStateEvents.push(pinnedEvents.getId()); + + // Only keep the last 10 event IDs to avoid infinite growth + readStateEvents = readStateEvents.reverse().splice(0, 10).reverse(); + MatrixClientPeg.get().setRoomAccountData(this.props.room.roomId, "im.vector.room.read_pins", { event_ids: readStateEvents, });