diff --git a/res/css/_common.scss b/res/css/_common.scss index 70ab2457f1..5987275f7f 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -550,6 +550,22 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { color: $username-variant8-color; } +@define-mixin mx_Tooltip_dark { + box-shadow: none; + background-color: $tooltip-timeline-bg-color; + color: $tooltip-timeline-fg-color; + border: none; + border-radius: 3px; + padding: 6px 8px; +} + +// This is a workaround for our mixins not supporting child selectors +.mx_Tooltip_dark { + .mx_Tooltip_chevron::after { + border-right-color: $tooltip-timeline-bg-color; + } +} + @define-mixin mx_Settings_fullWidthField { margin-right: 100px; } diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 9ca6954af7..6f5e3abade 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -294,49 +294,61 @@ form.mx_Custom_Widget_Form div { .mx_AppPermissionWarning { text-align: center; - background-color: $primary-bg-color; + background-color: $widget-menu-bar-bg-color; display: flex; height: 100%; flex-direction: column; justify-content: center; align-items: center; + font-size: 16px; } -.mx_AppPermissionWarningImage { - margin: 10px 0; +.mx_AppPermissionWarning_row { + margin-bottom: 12px; } -.mx_AppPermissionWarningImage img { - width: 100px; +.mx_AppPermissionWarning_smallText { + font-size: 12px; } -.mx_AppPermissionWarningText { - max-width: 90%; - margin: 10px auto 10px auto; - color: $primary-fg-color; +.mx_AppPermissionWarning_bolder { + font-weight: 600; } -.mx_AppPermissionWarningTextLabel { - font-weight: bold; - display: block; +.mx_AppPermissionWarning h4 { + margin: 0; + padding: 0; } -.mx_AppPermissionWarningTextURL { +.mx_AppPermissionWarning_helpIcon { + margin-top: 1px; + margin-right: 2px; + width: 10px; + height: 10px; display: inline-block; - max-width: 100%; - color: $accent-color; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; } -.mx_AppPermissionButton { - border: none; - padding: 5px 20px; - border-radius: 5px; - background-color: $button-bg-color; - color: $button-fg-color; - cursor: pointer; +.mx_AppPermissionWarning_helpIcon::before { + display: inline-block; + background-color: $accent-color; + mask-repeat: no-repeat; + mask-size: 12px; + width: 12px; + height: 12px; + mask-position: center; + content: ''; + vertical-align: middle; + mask-image: url('$(res)/img/feather-customised/help-circle.svg'); +} + +.mx_AppPermissionWarning_tooltip { + @mixin mx_Tooltip_dark; + + ul { + list-style-position: inside; + padding-left: 2px; + margin-left: 0; + } } .mx_AppLoading { diff --git a/src/components/views/elements/AppPermission.js b/src/components/views/elements/AppPermission.js index 1e019c0287..422427d4c4 100644 --- a/src/components/views/elements/AppPermission.js +++ b/src/components/views/elements/AppPermission.js @@ -19,79 +19,123 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import url from 'url'; +import sdk from '../../../index'; import { _t } from '../../../languageHandler'; import WidgetUtils from "../../../utils/WidgetUtils"; +import MatrixClientPeg from "../../../MatrixClientPeg"; export default class AppPermission extends React.Component { + static propTypes = { + url: PropTypes.string.isRequired, + creatorUserId: PropTypes.string.isRequired, + roomId: PropTypes.string.isRequired, + onPermissionGranted: PropTypes.func.isRequired, + }; + + static defaultProps = { + onPermissionGranted: () => {}, + }; + constructor(props) { super(props); - const curlBase = this.getCurlBase(); - this.state = { curlBase: curlBase}; + // The first step is to pick apart the widget so we can render information about it + const urlInfo = this.parseWidgetUrl(); + + // The second step is to find the user's profile so we can show it on the prompt + const room = MatrixClientPeg.get().getRoom(this.props.roomId); + let roomMember; + if (room) roomMember = room.getMember(this.props.creatorUserId); + + // Set all this into the initial state + this.state = { + ...urlInfo, + roomMember, + }; } - // Return string representation of content URL without query parameters - getCurlBase() { - const wurl = url.parse(this.props.url); - let curl; - let curlString; + parseWidgetUrl() { + const widgetUrl = url.parse(this.props.url); + const params = new URLSearchParams(widgetUrl.search); - const searchParams = new URLSearchParams(wurl.search); - - if (WidgetUtils.isScalarUrl(wurl) && searchParams && searchParams.get('url')) { - curl = url.parse(searchParams.get('url')); - if (curl) { - curl.search = curl.query = ""; - curlString = curl.format(); - } + // HACK: We're relying on the query params when we should be relying on the widget's `data`. + // This is a workaround for Scalar. + if (WidgetUtils.isScalarUrl(widgetUrl) && params && params.get('url')) { + const unwrappedUrl = url.parse(params.get('url')); + return { + widgetDomain: unwrappedUrl.host || unwrappedUrl.hostname, + isWrapped: true, + }; + } else { + return { + widgetDomain: widgetUrl.host || widgetUrl.hostname, + isWrapped: false, + }; } - if (!curl && wurl) { - wurl.search = wurl.query = ""; - curlString = wurl.format(); - } - return curlString; } render() { - let e2eWarningText; - if (this.props.isRoomEncrypted) { - e2eWarningText = - { _t('NOTE: Apps are not end-to-end encrypted') }; - } - const cookieWarning = - - { _t('Warning: This widget might use cookies.') } - ; + const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton"); + const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); + const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar"); + const TextWithTooltip = sdk.getComponent("views.elements.TextWithTooltip"); + + const displayName = this.state.roomMember ? this.state.roomMember.name : this.props.creatorUserId; + const userId = displayName === this.props.creatorUserId ? null : this.props.creatorUserId; + + const avatar = this.state.roomMember + ? + : ; + + const warningTooltipText = ( +
+ {_t("Any of the following data may be shared:")} +
    +
  • {_t("Your display name")}
  • +
  • {_t("Your avatar URL")}
  • +
  • {_t("Your user ID")}
  • +
  • {_t("Your theme")}
  • +
  • {_t("Riot URL")}
  • +
  • {_t("Room ID")}
  • +
  • {_t("Widget ID")}
  • +
+
+ ); + const warningTooltip = ( + + + + ); + + // Due to i18n limitations, we can't dedupe the code for variables in these two messages. + const warning = this.state.isWrapped + ? _t("Using this widget may share data with %(widgetDomain)s & your Integration Manager.", + {widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip}) + : _t("Using this widget may share data with %(widgetDomain)s.", + {widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip}); + return (
-
- {_t('Warning!')} +
+ {_t("Widget added by")}
-
- {_t('Do you want to load widget from URL:')} - {this.state.curlBase} - { e2eWarningText } - { cookieWarning } +
+ {avatar} +

{displayName}

+
{userId}
+
+
+ {warning} +
+
+ {_t("This widget may use cookies.")} +
+
+ + {_t("Continue")} +
-
); } } - -AppPermission.propTypes = { - isRoomEncrypted: PropTypes.bool, - url: PropTypes.string.isRequired, - onPermissionGranted: PropTypes.func.isRequired, -}; -AppPermission.defaultProps = { - isRoomEncrypted: false, - onPermissionGranted: function() {}, -}; diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 260b63dfd4..ffd9d73cca 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -569,11 +569,11 @@ export default class AppTile extends React.Component {
); if (!this.state.hasPermissionToLoad) { - const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); appTileBody = (
diff --git a/src/components/views/elements/TextWithTooltip.js b/src/components/views/elements/TextWithTooltip.js index 61c3a2125a..f6cef47117 100644 --- a/src/components/views/elements/TextWithTooltip.js +++ b/src/components/views/elements/TextWithTooltip.js @@ -21,7 +21,8 @@ import sdk from '../../../index'; export default class TextWithTooltip extends React.Component { static propTypes = { class: PropTypes.string, - tooltip: PropTypes.string.isRequired, + tooltipClass: PropTypes.string, + tooltip: PropTypes.node.isRequired, }; constructor() { @@ -49,6 +50,7 @@ export default class TextWithTooltip extends React.Component { ); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 655c7030c4..37383b7e4e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1183,10 +1183,18 @@ "Quick Reactions": "Quick Reactions", "Cancel search": "Cancel search", "Unknown Address": "Unknown Address", - "NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted", - "Warning: This widget might use cookies.": "Warning: This widget might use cookies.", - "Do you want to load widget from URL:": "Do you want to load widget from URL:", - "Allow": "Allow", + "Any of the following data may be shared:": "Any of the following data may be shared:", + "Your display name": "Your display name", + "Your avatar URL": "Your avatar URL", + "Your user ID": "Your user ID", + "Your theme": "Your theme", + "Riot URL": "Riot URL", + "Room ID": "Room ID", + "Widget ID": "Widget ID", + "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data with %(widgetDomain)s & your Integration Manager.", + "Using this widget may share data with %(widgetDomain)s.": "Using this widget may share data with %(widgetDomain)s.", + "Widget added by": "Widget added by", + "This widget may use cookies.": "This widget may use cookies.", "Delete Widget": "Delete Widget", "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", "Delete widget": "Delete widget", @@ -1494,6 +1502,7 @@ "A widget would like to verify your identity": "A widget would like to verify your identity", "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.", "Remember my selection for this widget": "Remember my selection for this widget", + "Allow": "Allow", "Deny": "Deny", "Unable to load backup status": "Unable to load backup status", "Recovery Key Mismatch": "Recovery Key Mismatch",