Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit ff4963e

Browse files
authored
Merge pull request #2750 from matrix-org/dbkr/shameless_plugging_2
Support linking to hosting providers
2 parents 133dfe0 + b657913 commit ff4963e

File tree

10 files changed

+168
-18
lines changed

10 files changed

+168
-18
lines changed

res/css/structures/_GroupView.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ limitations under the License.
6262
mask-image: url('$(res)/img/icons-share.svg');
6363
}
6464

65+
.mx_GroupView_hostingSignup img {
66+
margin-left: 5px;
67+
}
68+
6569
.mx_GroupView_editable {
6670
border-bottom: 1px solid $strong-input-border-color ! important;
6771
min-width: 150px;

res/css/views/context_menus/_TopLeftMenu.scss

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,39 @@ limitations under the License.
1515
*/
1616

1717
.mx_TopLeftMenu {
18-
min-width: 180px;
18+
min-width: 210px;
1919
border-radius: 4px;
2020

21+
.mx_TopLeftMenu_greyedText {
22+
font-size: 12px;
23+
opacity: 0.5;
24+
}
25+
26+
.mx_TopLeftMenu_upgradeLink {
27+
font-size: 12px;
28+
29+
img {
30+
margin-left: 5px;
31+
}
32+
}
33+
2134
.mx_TopLeftMenu_section:not(:last-child) {
2235
border-bottom: 1px solid $menu-border-color;
2336
}
2437

25-
.mx_TopLeftMenu_section {
26-
list-style: none;
38+
.mx_TopLeftMenu_section_noIcon {
39+
margin: 5px 0;
40+
padding: 5px 20px 5px 15px;
41+
42+
div:not(:first-child) {
43+
margin-top: 5px;
44+
}
45+
}
46+
47+
.mx_TopLeftMenu_section_withIcon {
2748
margin: 5px 0;
2849
padding: 0;
50+
list-style: none;
2951

3052
li.mx_TopLeftMenu_icon_home::after {
3153
mask-image: url('$(res)/img/feather-customised/home.svg');

res/css/views/settings/_ProfileSettings.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ limitations under the License.
3535
margin-top: 0;
3636
}
3737

38+
.mx_ProfileSettings_hostingSignup {
39+
margin-left: 20px;
40+
41+
img {
42+
margin-left: 5px;
43+
}
44+
}
45+
3846
.mx_ProfileSettings_avatar {
3947
width: 88px;
4048
height: 88px;

res/img/external-link.svg

Lines changed: 5 additions & 0 deletions
Loading

src/components/structures/GroupView.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Promise from 'bluebird';
2121
import MatrixClientPeg from '../../MatrixClientPeg';
2222
import sdk from '../../index';
2323
import dis from '../../dispatcher';
24+
import { getHostingLink } from '../../utils/HostingLink';
2425
import { sanitizedHtmlNode } from '../../HtmlUtils';
2526
import { _t, _td } from '../../languageHandler';
2627
import AccessibleButton from '../views/elements/AccessibleButton';
@@ -816,6 +817,23 @@ export default React.createClass({
816817
});
817818

818819
const header = this.state.editing ? <h2> { _t('Community Settings') } </h2> : <div />;
820+
821+
const hostingSignupLink = getHostingLink('community-settings');
822+
let hostingSignup = null;
823+
if (hostingSignupLink) {
824+
hostingSignup = <div className="mx_GroupView_hostingSignup">
825+
{_t(
826+
"Want more than a community? <a>Get your own server</a>", {},
827+
{
828+
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener">{sub}</a>,
829+
},
830+
)}
831+
<a href={hostingSignupLink} target="_blank" rel="noopener">
832+
<img src={require("../../../res/img/external-link.svg")} width="11" height="10" alt='' />
833+
</a>
834+
</div>;
835+
}
836+
819837
const changeDelayWarning = this.state.editing && this.state.isUserPrivileged ?
820838
<div className="mx_GroupView_changeDelayWarning">
821839
{ _t(
@@ -830,6 +848,7 @@ export default React.createClass({
830848
</div> : <div />;
831849
return <div className={groupSettingsSectionClasses}>
832850
{ header }
851+
{ hostingSignup }
833852
{ changeDelayWarning }
834853
{ this._getJoinableNode() }
835854
{ this._getLongDescriptionNode() }

src/components/structures/TopLeftMenuButton.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,18 @@ export default class TopLeftMenuButton extends React.Component {
6868
}
6969
}
7070

71-
render() {
72-
const fallbackUserId = MatrixClientPeg.get().getUserId();
73-
const profileInfo = this.state.profileInfo;
74-
let name;
71+
_getDisplayName() {
7572
if (MatrixClientPeg.get().isGuest()) {
76-
name = _t("Guest");
77-
} else if (profileInfo) {
78-
name = profileInfo.name;
73+
return _t("Guest");
74+
} else if (this.state.profileInfo) {
75+
return this.state.profileInfo.name;
7976
} else {
80-
name = fallbackUserId;
77+
return MatrixClientPeg.get().getUserId();
8178
}
79+
}
80+
81+
render() {
82+
const name = this._getDisplayName();
8283
let nameElement;
8384
if (!this.props.collapsed) {
8485
nameElement = <div className="mx_TopLeftMenuButton_name">
@@ -89,9 +90,9 @@ export default class TopLeftMenuButton extends React.Component {
8990
return (
9091
<AccessibleButton className="mx_TopLeftMenuButton" onClick={this.onToggleMenu}>
9192
<BaseAvatar
92-
idName={fallbackUserId}
93+
idName={MatrixClientPeg.get().getUserId()}
9394
name={name}
94-
url={profileInfo && profileInfo.avatarUrl}
95+
url={this.state.profileInfo && this.state.profileInfo.avatarUrl}
9596
width={AVATAR_SIZE}
9697
height={AVATAR_SIZE}
9798
resizeMethod="crop"
@@ -114,6 +115,8 @@ export default class TopLeftMenuButton extends React.Component {
114115
chevronFace: "none",
115116
left: x,
116117
top: y,
118+
userId: MatrixClientPeg.get().getUserId(),
119+
displayName: this._getDisplayName(),
117120
onFinished: () => {
118121
this.setState({ menuDisplayed: false });
119122
},

src/components/views/context_menus/TopLeftMenu.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,22 @@ limitations under the License.
1515
*/
1616

1717
import React from 'react';
18+
import PropTypes from 'prop-types';
1819
import dis from '../../../dispatcher';
1920
import { _t } from '../../../languageHandler';
2021
import LogoutDialog from "../dialogs/LogoutDialog";
2122
import Modal from "../../../Modal";
2223
import SdkConfig from '../../../SdkConfig';
24+
import { getHostingLink } from '../../../utils/HostingLink';
2325
import MatrixClientPeg from '../../../MatrixClientPeg';
2426

2527
export class TopLeftMenu extends React.Component {
28+
static propTypes = {
29+
displayName: PropTypes.string.isRequired,
30+
userId: PropTypes.string.isRequired,
31+
onFinished: PropTypes.func,
32+
};
33+
2634
constructor() {
2735
super();
2836
this.viewHomePage = this.viewHomePage.bind(this);
@@ -46,27 +54,48 @@ export class TopLeftMenu extends React.Component {
4654
render() {
4755
const isGuest = MatrixClientPeg.get().isGuest();
4856

57+
const hostingSignupLink = getHostingLink('user-context-menu');
58+
let hostingSignup = null;
59+
if (hostingSignupLink) {
60+
hostingSignup = <div className="mx_TopLeftMenu_upgradeLink">
61+
{_t(
62+
"<a>Upgrade</a> to your own domain", {},
63+
{
64+
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener">{sub}</a>,
65+
},
66+
)}
67+
<a href={hostingSignupLink} target="_blank" rel="noopener">
68+
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
69+
</a>
70+
</div>;
71+
}
72+
4973
let homePageSection = null;
5074
if (this.hasHomePage()) {
51-
homePageSection = <ul className="mx_TopLeftMenu_section">
75+
homePageSection = <ul className="mx_TopLeftMenu_section_withIcon">
5276
<li className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>{_t("Home")}</li>
5377
</ul>;
5478
}
5579

5680
let signInOutSection;
5781
if (isGuest) {
58-
signInOutSection = <ul className="mx_TopLeftMenu_section">
82+
signInOutSection = <ul className="mx_TopLeftMenu_section_withIcon">
5983
<li className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>{_t("Sign in")}</li>
6084
</ul>;
6185
} else {
62-
signInOutSection = <ul className="mx_TopLeftMenu_section">
86+
signInOutSection = <ul className="mx_TopLeftMenu_section_withIcon">
6387
<li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>{_t("Sign out")}</li>
6488
</ul>;
6589
}
6690

6791
return <div className="mx_TopLeftMenu">
92+
<div className="mx_TopLeftMenu_section_noIcon">
93+
<div>{this.props.displayName}</div>
94+
<div className="mx_TopLeftMenu_greyedText">{this.props.userId}</div>
95+
{hostingSignup}
96+
</div>
6897
{homePageSection}
69-
<ul className="mx_TopLeftMenu_section">
98+
<ul className="mx_TopLeftMenu_section_withIcon">
7099
<li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>{_t("Settings")}</li>
71100
</ul>
72101
{signInOutSection}

src/components/views/settings/ProfileSettings.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import MatrixClientPeg from "../../../MatrixClientPeg";
2020
import Field from "../elements/Field";
2121
import AccessibleButton from "../elements/AccessibleButton";
2222
import classNames from 'classnames';
23+
import {User} from "matrix-js-sdk";
24+
import { getHostingLink } from '../../../utils/HostingLink';
2325

2426
export default class ProfileSettings extends React.Component {
2527
constructor() {
@@ -129,13 +131,32 @@ export default class ProfileSettings extends React.Component {
129131
</div>
130132
);
131133

134+
const hostingSignupLink = getHostingLink('user-settings');
135+
let hostingSignup = null;
136+
if (hostingSignupLink) {
137+
hostingSignup = <span className="mx_ProfileSettings_hostingSignup">
138+
{_t(
139+
"<a>Upgrade</a> to your own domain", {},
140+
{
141+
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener">{sub}</a>,
142+
},
143+
)}
144+
<a href={hostingSignupLink} target="_blank" rel="noopener">
145+
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
146+
</a>
147+
</span>;
148+
}
149+
132150
return (
133151
<form onSubmit={this._saveProfile} autoComplete={false} noValidate={true}>
134152
<input type="file" ref="avatarUpload" className="mx_ProfileSettings_avatarUpload"
135153
onChange={this._onAvatarChanged} accept="image/*" />
136154
<div className="mx_ProfileSettings_profile">
137155
<div className="mx_ProfileSettings_controls">
138-
<p>{this.state.userId}</p>
156+
<p>
157+
{this.state.userId}
158+
{hostingSignup}
159+
</p>
139160
<Field id="profileDisplayName" label={_t("Display Name")}
140161
type="text" value={this.state.displayName} autoComplete="off"
141162
onChange={this._onDisplayNameChanged} />

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@
500500
"Phone Number": "Phone Number",
501501
"Profile picture": "Profile picture",
502502
"Upload profile picture": "Upload profile picture",
503+
"<a>Upgrade</a> to your own domain": "<a>Upgrade</a> to your own domain",
503504
"Display Name": "Display Name",
504505
"Save": "Save",
505506
"Flair": "Flair",
@@ -1314,6 +1315,7 @@
13141315
"Leave %(groupName)s?": "Leave %(groupName)s?",
13151316
"Unable to leave community": "Unable to leave community",
13161317
"Community Settings": "Community Settings",
1318+
"Want more than a community? <a>Get your own server</a>": "Want more than a community? <a>Get your own server</a>",
13171319
"Changes made to your community <bold1>name</bold1> and <bold2>avatar</bold2> might not be seen by other users for up to 30 minutes.": "Changes made to your community <bold1>name</bold1> and <bold2>avatar</bold2> might not be seen by other users for up to 30 minutes.",
13181320
"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.",
13191321
"Featured Rooms:": "Featured Rooms:",

src/utils/HostingLink.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright 2019 New Vector Ltd.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import url from 'url';
18+
import qs from 'qs';
19+
20+
import SdkConfig from '../SdkConfig';
21+
22+
export function getHostingLink(campaign) {
23+
const hostingLink = SdkConfig.get().hosting_signup_link;
24+
if (!hostingLink) return null;
25+
if (!campaign) return hostingLink;
26+
27+
try {
28+
const hostingUrl = url.parse(hostingLink);
29+
const params = qs.parse(hostingUrl.query);
30+
params.utm_campaign = campaign;
31+
hostingUrl.search = undefined;
32+
hostingUrl.query = params;
33+
return hostingUrl.format();
34+
} catch (e) {
35+
return hostingLink;
36+
}
37+
}

0 commit comments

Comments
 (0)