From d888445ec72c858178f42a81068864e3b863e8c5 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 | 23 +++++++++++- src/i18n/strings/en_EN.json | 2 ++ 8 files changed, 125 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 649128e4f3..0764fc0d50 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -20,6 +20,8 @@ import MatrixClientPeg from "../../../MatrixClientPeg"; 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() { @@ -129,13 +131,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 696bd8152c..99ffd71c84 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -500,6 +500,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", @@ -1314,6 +1315,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 dc0eff38fdf8e42df3b0c67fd70d54e2a49c2f63 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 0764fc0d50..9f4b663bcb 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -144,7 +144,7 @@ export default class ProfileSettings extends React.Component { - + ; } return ( From 7aa451067e703050f18b54ea8429b0d519b98b32 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 9f4b663bcb..7765978931 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() { @@ -131,7 +131,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 b6579131b856de9d8ba1b17b28ec993cb4416449 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 @@ + + + + +