From 57d8f6d8a2f6e683fe5ec6c47f2dbaf0ca4c9e8a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 5 Mar 2019 16:12:02 +0000 Subject: [PATCH 1/4] Support linking to hosting providers From link in app config --- res/css/structures/_GroupView.scss | 4 +++ res/css/views/context_menus/_TopLeftMenu.scss | 28 +++++++++++++-- res/css/views/settings/_ProfileSettings.scss | 8 +++++ src/components/structures/GroupView.js | 19 ++++++++++ .../structures/TopLeftMenuButton.js | 23 ++++++------ .../views/context_menus/TopLeftMenu.js | 36 ++++++++++++++++--- .../views/settings/ProfileSettings.js | 22 +++++++++++- src/i18n/strings/en_EN.json | 2 ++ 8 files changed, 124 insertions(+), 18 deletions(-) diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index bfbc92ca05..4f33617344 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -62,6 +62,10 @@ limitations under the License. mask-image: url('$(res)/img/icons-share.svg'); } +.mx_GroupView_hostingSignup img { + margin-left: 5px; +} + .mx_GroupView_editable { border-bottom: 1px solid $strong-input-border-color ! important; min-width: 150px; diff --git a/res/css/views/context_menus/_TopLeftMenu.scss b/res/css/views/context_menus/_TopLeftMenu.scss index c15d12eb6a..113da004b8 100644 --- a/res/css/views/context_menus/_TopLeftMenu.scss +++ b/res/css/views/context_menus/_TopLeftMenu.scss @@ -15,17 +15,39 @@ limitations under the License. */ .mx_TopLeftMenu { - min-width: 180px; + min-width: 210px; border-radius: 4px; + .mx_TopLeftMenu_greyedText { + font-size: 12px; + opacity: 0.5; + } + + .mx_TopLeftMenu_upgradeLink { + font-size: 12px; + + img { + margin-left: 5px; + } + } + .mx_TopLeftMenu_section:not(:last-child) { border-bottom: 1px solid $menu-border-color; } - .mx_TopLeftMenu_section { - list-style: none; + .mx_TopLeftMenu_section_noIcon { + margin: 5px 0; + padding: 5px 20px 5px 15px; + + div:not(:first-child) { + margin-top: 5px; + } + } + + .mx_TopLeftMenu_section_withIcon { margin: 5px 0; padding: 0; + list-style: none; li.mx_TopLeftMenu_icon_home::after { mask-image: url('$(res)/img/feather-customised/home.svg'); diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index 5d85f80bfe..b2e449ac34 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -35,6 +35,14 @@ limitations under the License. margin-top: 0; } +.mx_ProfileSettings_hostingSignup { + margin-left: 20px; + + img { + margin-left: 5px; + } +} + .mx_ProfileSettings_avatar { width: 88px; height: 88px; diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index b80f49d051..dbca0fe5d1 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -21,6 +21,7 @@ import Promise from 'bluebird'; import MatrixClientPeg from '../../MatrixClientPeg'; import sdk from '../../index'; import dis from '../../dispatcher'; +import SdkConfig from '../../SdkConfig'; import { sanitizedHtmlNode } from '../../HtmlUtils'; import { _t, _td } from '../../languageHandler'; import AccessibleButton from '../views/elements/AccessibleButton'; @@ -816,6 +817,23 @@ export default React.createClass({ }); const header = this.state.editing ?

{ _t('Community Settings') }

:
; + + const hostingSignupLink = SdkConfig.get().hosting_signup_link; + let hostingSignup = null; + if (hostingSignupLink) { + hostingSignup =
+ {_t( + "Want more than a community? Get your own server", {}, + { + a: sub => {sub}, + }, + )} + + + +
+ } + const changeDelayWarning = this.state.editing && this.state.isUserPrivileged ?
{ _t( @@ -830,6 +848,7 @@ export default React.createClass({
:
; return
{ header } + { hostingSignup } { changeDelayWarning } { this._getJoinableNode() } { this._getLongDescriptionNode() } diff --git a/src/components/structures/TopLeftMenuButton.js b/src/components/structures/TopLeftMenuButton.js index 4e3f609c07..b68d3a95a0 100644 --- a/src/components/structures/TopLeftMenuButton.js +++ b/src/components/structures/TopLeftMenuButton.js @@ -68,17 +68,18 @@ export default class TopLeftMenuButton extends React.Component { } } - render() { - const fallbackUserId = MatrixClientPeg.get().getUserId(); - const profileInfo = this.state.profileInfo; - let name; + _getDisplayName() { if (MatrixClientPeg.get().isGuest()) { - name = _t("Guest"); - } else if (profileInfo) { - name = profileInfo.name; + return _t("Guest"); + } else if (this.state.profileInfo) { + return this.state.profileInfo.name; } else { - name = fallbackUserId; + return MatrixClientPeg.get().getUserId(); } + } + + render() { + const name = this._getDisplayName(); let nameElement; if (!this.props.collapsed) { nameElement =
@@ -89,9 +90,9 @@ export default class TopLeftMenuButton extends React.Component { return ( { this.setState({ menuDisplayed: false }); }, diff --git a/src/components/views/context_menus/TopLeftMenu.js b/src/components/views/context_menus/TopLeftMenu.js index e3d8ef8d91..a10c577f13 100644 --- a/src/components/views/context_menus/TopLeftMenu.js +++ b/src/components/views/context_menus/TopLeftMenu.js @@ -15,6 +15,7 @@ limitations under the License. */ import React from 'react'; +import PropTypes from 'prop-types'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; import LogoutDialog from "../dialogs/LogoutDialog"; @@ -23,6 +24,12 @@ import SdkConfig from '../../../SdkConfig'; import MatrixClientPeg from '../../../MatrixClientPeg'; export class TopLeftMenu extends React.Component { + static propTypes = { + displayName: PropTypes.string.isRequired, + userId: PropTypes.string.isRequired, + onFinished: PropTypes.func, + }; + constructor() { super(); this.viewHomePage = this.viewHomePage.bind(this); @@ -46,27 +53,48 @@ export class TopLeftMenu extends React.Component { render() { const isGuest = MatrixClientPeg.get().isGuest(); + const hostingSignupLink = SdkConfig.get().hosting_signup_link; + let hostingSignup = null; + if (hostingSignupLink) { + hostingSignup =
+ {_t( + "Upgrade to your own domain", {}, + { + a: sub => {sub}, + }, + )} + + + +
+ } + let homePageSection = null; if (this.hasHomePage()) { - homePageSection =
    + homePageSection =
    • {_t("Home")}
    ; } let signInOutSection; if (isGuest) { - signInOutSection =
      + signInOutSection =
      • {_t("Sign in")}
      ; } else { - signInOutSection =
        + signInOutSection =
        • {_t("Sign out")}
        ; } return
        +
        +
        {this.props.displayName}
        +
        {this.props.userId}
        + {hostingSignup} +
        {homePageSection} -
          +
          • {_t("Settings")}
          {signInOutSection} diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 1bb54d9361..3dc936d894 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -21,6 +21,7 @@ import Field from "../elements/Field"; import AccessibleButton from "../elements/AccessibleButton"; import classNames from 'classnames'; import {User} from "matrix-js-sdk"; +import SdkConfig from '../../../SdkConfig'; export default class ProfileSettings extends React.Component { constructor() { @@ -137,13 +138,32 @@ export default class ProfileSettings extends React.Component {
        ); + const hostingSignupLink = SdkConfig.get().hosting_signup_link; + let hostingSignup = null; + if (hostingSignupLink) { + hostingSignup = + {_t( + "Upgrade to your own domain", {}, + { + a: sub => {sub}, + }, + )} + + + + + } + return (
        -

        {this.state.userId}

        +

        + {this.state.userId} + {hostingSignup} +

        diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 19259c6341..7fa24c489d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -502,6 +502,7 @@ "Phone Number": "Phone Number", "Profile picture": "Profile picture", "Upload profile picture": "Upload profile picture", + "Upgrade to your own domain": "Upgrade to your own domain", "Display Name": "Display Name", "Save": "Save", "Flair": "Flair", @@ -1315,6 +1316,7 @@ "Leave %(groupName)s?": "Leave %(groupName)s?", "Unable to leave community": "Unable to leave community", "Community Settings": "Community Settings", + "Want more than a community? Get your own server": "Want more than a community? Get your own server", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.", "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.", "Featured Rooms:": "Featured Rooms:", From b68a71b2944737182c5d9eee019113ab394bc69a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 5 Mar 2019 16:20:18 +0000 Subject: [PATCH 2/4] Lint --- src/components/structures/GroupView.js | 2 +- src/components/views/context_menus/TopLeftMenu.js | 2 +- src/components/views/settings/ProfileSettings.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index dbca0fe5d1..b5870e793d 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -831,7 +831,7 @@ export default React.createClass({ -
        +
        ; } const changeDelayWarning = this.state.editing && this.state.isUserPrivileged ? diff --git a/src/components/views/context_menus/TopLeftMenu.js b/src/components/views/context_menus/TopLeftMenu.js index a10c577f13..2a5481ff1c 100644 --- a/src/components/views/context_menus/TopLeftMenu.js +++ b/src/components/views/context_menus/TopLeftMenu.js @@ -66,7 +66,7 @@ export class TopLeftMenu extends React.Component { -
+
; } let homePageSection = null; diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 3dc936d894..7264cbd048 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -151,7 +151,7 @@ export default class ProfileSettings extends React.Component { - + ; } return ( From 0244990731118f69a8e8fb8a4e494556da6bfbba Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 5 Mar 2019 16:42:22 +0000 Subject: [PATCH 3/4] Add utm_campaign to the hosting links According to where in the app the link was clicked --- src/components/structures/GroupView.js | 4 +- .../views/context_menus/TopLeftMenu.js | 3 +- .../views/settings/ProfileSettings.js | 4 +- src/utils/HostingLink.js | 37 +++++++++++++++++++ 4 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 src/utils/HostingLink.js diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index b5870e793d..6f1aeaf624 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -21,7 +21,7 @@ import Promise from 'bluebird'; import MatrixClientPeg from '../../MatrixClientPeg'; import sdk from '../../index'; import dis from '../../dispatcher'; -import SdkConfig from '../../SdkConfig'; +import { getHostingLink } from '../../utils/HostingLink'; import { sanitizedHtmlNode } from '../../HtmlUtils'; import { _t, _td } from '../../languageHandler'; import AccessibleButton from '../views/elements/AccessibleButton'; @@ -818,7 +818,7 @@ export default React.createClass({ const header = this.state.editing ?

{ _t('Community Settings') }

:
; - const hostingSignupLink = SdkConfig.get().hosting_signup_link; + const hostingSignupLink = getHostingLink('community-settings'); let hostingSignup = null; if (hostingSignupLink) { hostingSignup =
diff --git a/src/components/views/context_menus/TopLeftMenu.js b/src/components/views/context_menus/TopLeftMenu.js index 2a5481ff1c..278c879404 100644 --- a/src/components/views/context_menus/TopLeftMenu.js +++ b/src/components/views/context_menus/TopLeftMenu.js @@ -21,6 +21,7 @@ import { _t } from '../../../languageHandler'; import LogoutDialog from "../dialogs/LogoutDialog"; import Modal from "../../../Modal"; import SdkConfig from '../../../SdkConfig'; +import { getHostingLink } from '../../../utils/HostingLink'; import MatrixClientPeg from '../../../MatrixClientPeg'; export class TopLeftMenu extends React.Component { @@ -53,7 +54,7 @@ export class TopLeftMenu extends React.Component { render() { const isGuest = MatrixClientPeg.get().isGuest(); - const hostingSignupLink = SdkConfig.get().hosting_signup_link; + const hostingSignupLink = getHostingLink('user-context-menu'); let hostingSignup = null; if (hostingSignupLink) { hostingSignup =
diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 7264cbd048..d3522e7b23 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -21,7 +21,7 @@ import Field from "../elements/Field"; import AccessibleButton from "../elements/AccessibleButton"; import classNames from 'classnames'; import {User} from "matrix-js-sdk"; -import SdkConfig from '../../../SdkConfig'; +import { getHostingLink } from '../../../utils/HostingLink'; export default class ProfileSettings extends React.Component { constructor() { @@ -138,7 +138,7 @@ export default class ProfileSettings extends React.Component {
); - const hostingSignupLink = SdkConfig.get().hosting_signup_link; + const hostingSignupLink = getHostingLink('user-settings'); let hostingSignup = null; if (hostingSignupLink) { hostingSignup = diff --git a/src/utils/HostingLink.js b/src/utils/HostingLink.js new file mode 100644 index 0000000000..7efd8b6f96 --- /dev/null +++ b/src/utils/HostingLink.js @@ -0,0 +1,37 @@ +/* +Copyright 2019 New Vector Ltd. + +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. +*/ + +import url from 'url'; +import qs from 'qs'; + +import SdkConfig from '../SdkConfig'; + +export function getHostingLink(campaign) { + const hostingLink = SdkConfig.get().hosting_signup_link; + if (!hostingLink) return null; + if (!campaign) return hostingLink; + + try { + const hostingUrl = url.parse(hostingLink); + const params = qs.parse(hostingUrl.query); + params.utm_campaign = campaign; + hostingUrl.search = undefined; + hostingUrl.query = params; + return hostingUrl.format(); + } catch (e) { + return hostingLink; + } +} From 2055be36f1b13802c3f5b333a1fa9172129a7703 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 5 Mar 2019 16:49:12 +0000 Subject: [PATCH 4/4] Add the image --- res/img/external-link.svg | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 res/img/external-link.svg diff --git a/res/img/external-link.svg b/res/img/external-link.svg new file mode 100644 index 0000000000..459e790fe3 --- /dev/null +++ b/res/img/external-link.svg @@ -0,0 +1,5 @@ + + + + +