diff --git a/src/TextForEvent.js b/src/TextForEvent.js index de12cec502..f99cf7bde5 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -248,6 +248,28 @@ 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() || {}; + let widgetName = widgetName || name || type || previousContent.type || ''; + + // Apply sentence case + widgetName = widgetName ? widgetName[0].toUpperCase() + widgetName.slice(1).toLowerCase() + ' ' : ''; + + // 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 +282,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/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 1b8689847e..a493810c52 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -45,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() { @@ -60,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, }; diff --git a/src/components/views/login/CaptchaForm.js b/src/components/views/login/CaptchaForm.js index f154cc4f1d..8f9c0391ae 100644 --- a/src/components/views/login/CaptchaForm.js +++ b/src/components/views/login/CaptchaForm.js @@ -17,7 +17,7 @@ limitations under the License. 'use strict'; import React from 'react'; -import { _t } from '../../../languageHandler'; +import { _t, _tJsx } from '../../../languageHandler'; var DIV_ID = 'mx_recaptcha'; @@ -66,7 +66,11 @@ module.exports = React.createClass({ // * jumping straight to a hosted captcha page (but we don't support that yet) // * embedding the captcha in an iframe (if that works) // * using a better captcha lib - warning.innerHTML = "Robot check is currently unavailable on desktop - please use a web browser."; + warning.innerHTML = _tJsx( + "Robot check is currently unavailable on desktop - please use a web browser", + /(.*?)<\/a>/, + (sub) => { return "{ sub }"; } + ); this.refs.recaptchaContainer.appendChild(warning); } else { diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 07583dcc85..b98a18e9e6 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -94,7 +94,7 @@ module.exports = React.createClass({ return pathTemplate; }, - _initAppConfig: function(appId, app) { + _initAppConfig: function(appId, app, sender) { const user = MatrixClientPeg.get().getUser(this.props.userId); const params = { '$matrix_user_id': this.props.userId, @@ -112,6 +112,7 @@ module.exports = React.createClass({ app.id = appId; app.name = app.name || app.type; app.url = this.encodeUri(app.url, params); + app.creatorUserId = (sender && sender.userId) ? sender.userId : null; return app; }, @@ -132,7 +133,7 @@ module.exports = React.createClass({ return appsStateEvents.filter((ev) => { return ev.getContent().type && ev.getContent().url; }).map((ev) => { - return this._initAppConfig(ev.getStateKey(), ev.getContent()); + return this._initAppConfig(ev.getStateKey(), ev.getContent(), ev.sender); }); }, @@ -185,6 +186,7 @@ module.exports = React.createClass({ room={this.props.room} userId={this.props.userId} show={this.props.showApps} + creatorUserId={app.creatorUserId} />); }); diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index 65dedad61d..c59992f3f2 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -139,7 +139,7 @@ module.exports = React.createClass({ } return ( -