;
},
@@ -851,6 +884,18 @@ export default React.createClass({
let description = null;
if (summary.profile && summary.profile.long_description) {
description = sanitizedHtmlNode(summary.profile.long_description);
+ } else if (this.state.isUserPrivileged) {
+ description =
+ { _tJsx(
+ 'Your community hasn\'t got a Long Description, a HTML page to show to community members. ' +
+ 'Click here to open settings and give it one!',
+ [/ /],
+ [(sub) => ])
+ }
+
;
},
diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js
index fbfe7ebe18..a44673c879 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,32 @@ 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 && 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
*
@@ -305,8 +340,17 @@ module.exports = React.createClass({
}
if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) {
+ let pinsIndicator = null;
+ if (this._hasUnreadPins()) {
+ pinsIndicator = ();
+ } else if (this._hasPins()) {
+ pinsIndicator = ();
+ }
+
pinnedEventsButton =
-
+
+ { pinsIndicator }
;
}
diff --git a/src/groups.js b/src/groups.js
index 6c266e0fb6..957db1d85b 100644
--- a/src/groups.js
+++ b/src/groups.js
@@ -36,6 +36,7 @@ export function groupMemberFromApiObject(apiObject) {
userId: apiObject.user_id,
displayname: apiObject.displayname,
avatarUrl: apiObject.avatar_url,
+ isAdmin: apiObject.is_admin,
};
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 42bf74fb0e..b94e89a113 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -669,6 +669,7 @@
"You must register to use this functionality": "You must register to use this functionality",
"You must join the room to see its files": "You must join the room to see its files",
"There are no visible files in this room": "There are no visible files in this room",
+ "
HTML for your community's page
\n
\n Use the long description to introduce new members to the community, or distribute\n some important links\n
\n
\n You can even use 'img' tags\n
\n": "
HTML for your community's page
\n
\n Use the long description to introduce new members to the community, or distribute\n some important links\n
\n
\n You can even use 'img' tags\n
\n",
"Add rooms to the community summary": "Add rooms to the community summary",
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",
"Add to summary": "Add to summary",
@@ -691,6 +692,7 @@
"Leave": "Leave",
"Unable to leave room": "Unable to leave room",
"Community Settings": "Community Settings",
+ "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.",
"Add rooms to this community": "Add rooms to this community",
"Featured Rooms:": "Featured Rooms:",
"Featured Users:": "Featured Users:",
@@ -699,6 +701,7 @@
"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",
+ "Your community hasn't got a Long Description, a HTML page to show to community members. Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members. Click here to open settings and give it one!",
"Long Description (HTML)": "Long Description (HTML)",
"Description": "Description",
"Community %(groupId)s not found": "Community %(groupId)s not found",
@@ -889,6 +892,8 @@
"Commands": "Commands",
"Results from DuckDuckGo": "Results from DuckDuckGo",
"Emoji": "Emoji",
+ "Notify the whole room": "Notify the whole room",
+ "Room Notification": "Room Notification",
"Users": "Users",
"unknown device": "unknown device",
"NOT verified": "NOT verified",
diff --git a/src/stores/GroupStore.js b/src/stores/GroupStore.js
index 2578d373a7..11dd664053 100644
--- a/src/stores/GroupStore.js
+++ b/src/stores/GroupStore.js
@@ -33,6 +33,9 @@ export default class GroupStore extends EventEmitter {
constructor(matrixClient, groupId) {
super();
+ if (!groupId) {
+ throw new Error('GroupStore needs a valid groupId to be created');
+ }
this.groupId = groupId;
this._matrixClient = matrixClient;
this._summary = {};
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;
+ }
+}