diff --git a/src/TextForEvent.js b/src/TextForEvent.js index de12cec502..55ea500092 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -248,6 +248,25 @@ function textForPowerEvent(event) { }); } +function textForWidgetEvent(event) { + const senderName = event.sender ? event.sender.name : event.getSender(); + const previousContent = event.getPrevContent() || {}; + const {name, type, url} = event.getContent() || {}; + const widgetName = widgetName || name || type || previousContent.type || ''; + + // If the widget was removed, its content should be {}, but this is sufficiently + // equivalent to that condition. + if (url) { + return _t('%(senderName)s added a %(widgetName)swidget', { + senderName, widgetName, + }); + } else { + return _t('%(senderName)s removed a %(widgetName)swidget', { + senderName, widgetName, + }); + } +} + var handlers = { 'm.room.message': textForMessageEvent, 'm.room.name': textForRoomNameEvent, @@ -260,6 +279,8 @@ var handlers = { 'm.room.history_visibility': textForHistoryVisibilityEvent, 'm.room.encryption': textForEncryptionEvent, 'm.room.power_levels': textForPowerEvent, + + 'im.vector.modular.widgets': textForWidgetEvent, }; module.exports = { diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 02f224e942..460ed43e82 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -339,6 +339,15 @@ module.exports = React.createClass({ for (;i + 1 < this.props.events.length; i++) { const collapsedMxEv = this.props.events[i + 1]; + // Ignore redacted/hidden member events + if (!this._shouldShowEvent(collapsedMxEv)) { + // If this hidden event is the RM and in or at end of a MELS put RM after MELS. + if (collapsedMxEv.getId() === this.props.readMarkerEventId) { + readMarkerInMels = true; + } + continue; + } + if (!isMembershipChange(collapsedMxEv) || this._wantsDateSeparator(this.props.events[i], collapsedMxEv.getDate())) { break; @@ -349,11 +358,6 @@ module.exports = React.createClass({ readMarkerInMels = true; } - // Ignore redacted/hidden member events - if (!this._shouldShowEvent(collapsedMxEv)) { - continue; - } - summarisedEvents.push(collapsedMxEv); } diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index a78b802ad7..a411e1d6f6 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -28,6 +28,7 @@ import AppPermission from './AppPermission'; import AppWarning from './AppWarning'; import MessageSpinner from './MessageSpinner'; import WidgetUtils from '../../../WidgetUtils'; +import dis from '../../../dispatcher'; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const betaHelpMsg = 'This feature is currently experimental and is intended for beta testing only'; @@ -44,6 +45,10 @@ export default React.createClass({ // Specifying 'fullWidth' as true will render the app tile to fill the width of the app drawer continer. // This should be set to true when there is only one widget in the app drawer, otherwise it should be false. fullWidth: React.PropTypes.bool, + // UserId of the current user + userId: React.PropTypes.string.isRequired, + // UserId of the entity that added / modified the widget + creatorUserId: React.PropTypes.string, }, getDefaultProps: function() { @@ -59,7 +64,8 @@ export default React.createClass({ loading: false, widgetUrl: this.props.url, widgetPermissionId: widgetPermissionId, - hasPermissionToLoad: Boolean(hasPermissionToLoad === 'true'), + // Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user + hasPermissionToLoad: hasPermissionToLoad === 'true' || this.props.userId === this.props.creatorUserId, error: null, deleting: false, }; @@ -177,11 +183,25 @@ export default React.createClass({ let appTileName = "No name"; if(this.props.name && this.props.name.trim()) { appTileName = this.props.name.trim(); - appTileName = appTileName[0].toUpperCase() + appTileName.slice(1).toLowerCase(); } return appTileName; }, + onClickMenuBar: function(ev) { + ev.preventDefault(); + + // Ignore clicks on menu bar children + if (ev.target !== this.refs.menu_bar) { + return; + } + + // Toggle the view state of the apps drawer + dis.dispatch({ + action: 'appsDrawer', + show: !this.props.show, + }); + }, + render: function() { let appTileBody; @@ -218,7 +238,7 @@ export default React.createClass({ /> ); - } else { + } else if (this.props.show) { appTileBody = (