Merge pull request #1486 from turt2live/travis/pinned-notice
Add a small indicator for when a new event is pinned
This commit is contained in:
commit
e14e0bf200
3 changed files with 102 additions and 8 deletions
|
@ -19,6 +19,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import PinnedEventTile from "./PinnedEventTile";
|
import PinnedEventTile from "./PinnedEventTile";
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import PinningUtils from "../../../utils/PinningUtils";
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'PinnedEventsPanel',
|
displayName: 'PinnedEventsPanel',
|
||||||
|
@ -61,20 +62,39 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
Promise.all(promises).then((contexts) => {
|
Promise.all(promises).then((contexts) => {
|
||||||
// Filter out the messages before we try to render them
|
// Filter out the messages before we try to render them
|
||||||
const pinned = contexts.filter((context) => {
|
const pinned = contexts.filter((context) => PinningUtils.isPinnable(context.event));
|
||||||
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 });
|
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 readStateEvents = null;
|
||||||
|
const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins");
|
||||||
|
if (readPinsEvent && readPinsEvent.getContent()) {
|
||||||
|
readStateEvents = readPinsEvent.getContent().event_ids || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_getPinnedTiles: function() {
|
_getPinnedTiles: function() {
|
||||||
if (this.state.pinned.length == 0) {
|
if (this.state.pinned.length === 0) {
|
||||||
return (<div>{ _t("No pinned messages.") }</div>);
|
return (<div>{ _t("No pinned messages.") }</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,7 @@ module.exports = React.createClass({
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
cli.on("RoomState.events", this._onRoomStateEvents);
|
cli.on("RoomState.events", this._onRoomStateEvents);
|
||||||
|
cli.on("Room.accountData", this._onRoomAccountData);
|
||||||
|
|
||||||
// When a room name occurs, RoomState.events is fired *before*
|
// 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
|
// 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();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.removeListener("RoomState.events", this._onRoomStateEvents);
|
cli.removeListener("RoomState.events", this._onRoomStateEvents);
|
||||||
|
cli.removeListener("Room.accountData", this._onRoomAccountData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -99,6 +101,13 @@ module.exports = React.createClass({
|
||||||
this._rateLimitedUpdate();
|
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() {
|
_rateLimitedUpdate: new RateLimitedFunc(function() {
|
||||||
/* eslint-disable babel/no-invalid-this */
|
/* eslint-disable babel/no-invalid-this */
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
@ -139,6 +148,32 @@ module.exports = React.createClass({
|
||||||
dis.dispatch({ action: 'show_right_panel' });
|
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 && readPinsEvent.getContent()) {
|
||||||
|
const readStateEvents = readPinsEvent.getContent().event_ids || [];
|
||||||
|
if (readStateEvents) {
|
||||||
|
return !readStateEvents.includes(currentPinEvent.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's pins, and we haven't read any of them
|
||||||
|
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
|
* After editing the settings, get the new name for the room
|
||||||
*
|
*
|
||||||
|
@ -305,8 +340,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) {
|
if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) {
|
||||||
|
let pinsIndicator = null;
|
||||||
|
if (this._hasUnreadPins()) {
|
||||||
|
pinsIndicator = (<div className="mx_RoomHeader_pinsIndicator mx_RoomHeader_pinsIndicatorUnread" />);
|
||||||
|
} else if (this._hasPins()) {
|
||||||
|
pinsIndicator = (<div className="mx_RoomHeader_pinsIndicator" />);
|
||||||
|
}
|
||||||
|
|
||||||
pinnedEventsButton =
|
pinnedEventsButton =
|
||||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}>
|
<AccessibleButton className="mx_RoomHeader_button mx_RoomHeader_pinnedButton"
|
||||||
|
onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}>
|
||||||
|
{ pinsIndicator }
|
||||||
<TintableSvg src="img/icons-pin.svg" width="16" height="16" />
|
<TintableSvg src="img/icons-pin.svg" width="16" height="16" />
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
30
src/utils/PinningUtils.js
Normal file
30
src/utils/PinningUtils.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue