mirror of
https://github.com/element-hq/element-web
synced 2024-11-23 01:35:49 +03:00
Merge pull request #2748 from matrix-org/dbkr/shameless_plugging
Support linking to hosting providers
This commit is contained in:
commit
c1056025ed
10 changed files with 167 additions and 18 deletions
|
@ -62,6 +62,10 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/icons-share.svg');
|
mask-image: url('$(res)/img/icons-share.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_GroupView_hostingSignup img {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_GroupView_editable {
|
.mx_GroupView_editable {
|
||||||
border-bottom: 1px solid $strong-input-border-color ! important;
|
border-bottom: 1px solid $strong-input-border-color ! important;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
|
|
|
@ -15,17 +15,39 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_TopLeftMenu {
|
.mx_TopLeftMenu {
|
||||||
min-width: 180px;
|
min-width: 210px;
|
||||||
border-radius: 4px;
|
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) {
|
.mx_TopLeftMenu_section:not(:last-child) {
|
||||||
border-bottom: 1px solid $menu-border-color;
|
border-bottom: 1px solid $menu-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TopLeftMenu_section {
|
.mx_TopLeftMenu_section_noIcon {
|
||||||
list-style: none;
|
margin: 5px 0;
|
||||||
|
padding: 5px 20px 5px 15px;
|
||||||
|
|
||||||
|
div:not(:first-child) {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TopLeftMenu_section_withIcon {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
li.mx_TopLeftMenu_icon_home::after {
|
li.mx_TopLeftMenu_icon_home::after {
|
||||||
mask-image: url('$(res)/img/feather-customised/home.svg');
|
mask-image: url('$(res)/img/feather-customised/home.svg');
|
||||||
|
|
|
@ -35,6 +35,14 @@ limitations under the License.
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_ProfileSettings_hostingSignup {
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ProfileSettings_avatar {
|
.mx_ProfileSettings_avatar {
|
||||||
width: 88px;
|
width: 88px;
|
||||||
height: 88px;
|
height: 88px;
|
||||||
|
|
5
res/img/external-link.svg
Normal file
5
res/img/external-link.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="10" viewBox="0 0 11 10">
|
||||||
|
<g fill="none" fill-rule="evenodd" stroke="#9E9E9E" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M8.5 5.5v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h3M7 .5h3v3M4.5 6L10 .5"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 304 B |
|
@ -21,6 +21,7 @@ import Promise from 'bluebird';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
|
import { getHostingLink } from '../../utils/HostingLink';
|
||||||
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
||||||
import { _t, _td } from '../../languageHandler';
|
import { _t, _td } from '../../languageHandler';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
|
@ -816,6 +817,23 @@ export default React.createClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
const header = this.state.editing ? <h2> { _t('Community Settings') } </h2> : <div />;
|
const header = this.state.editing ? <h2> { _t('Community Settings') } </h2> : <div />;
|
||||||
|
|
||||||
|
const hostingSignupLink = getHostingLink('community-settings');
|
||||||
|
let hostingSignup = null;
|
||||||
|
if (hostingSignupLink) {
|
||||||
|
hostingSignup = <div className="mx_GroupView_hostingSignup">
|
||||||
|
{_t(
|
||||||
|
"Want more than a community? <a>Get your own server</a>", {},
|
||||||
|
{
|
||||||
|
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener">{sub}</a>,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
<a href={hostingSignupLink} target="_blank" rel="noopener">
|
||||||
|
<img src={require("../../../res/img/external-link.svg")} width="11" height="10" alt='' />
|
||||||
|
</a>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
const changeDelayWarning = this.state.editing && this.state.isUserPrivileged ?
|
const changeDelayWarning = this.state.editing && this.state.isUserPrivileged ?
|
||||||
<div className="mx_GroupView_changeDelayWarning">
|
<div className="mx_GroupView_changeDelayWarning">
|
||||||
{ _t(
|
{ _t(
|
||||||
|
@ -830,6 +848,7 @@ export default React.createClass({
|
||||||
</div> : <div />;
|
</div> : <div />;
|
||||||
return <div className={groupSettingsSectionClasses}>
|
return <div className={groupSettingsSectionClasses}>
|
||||||
{ header }
|
{ header }
|
||||||
|
{ hostingSignup }
|
||||||
{ changeDelayWarning }
|
{ changeDelayWarning }
|
||||||
{ this._getJoinableNode() }
|
{ this._getJoinableNode() }
|
||||||
{ this._getLongDescriptionNode() }
|
{ this._getLongDescriptionNode() }
|
||||||
|
|
|
@ -68,17 +68,18 @@ export default class TopLeftMenuButton extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
_getDisplayName() {
|
||||||
const fallbackUserId = MatrixClientPeg.get().getUserId();
|
|
||||||
const profileInfo = this.state.profileInfo;
|
|
||||||
let name;
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
name = _t("Guest");
|
return _t("Guest");
|
||||||
} else if (profileInfo) {
|
} else if (this.state.profileInfo) {
|
||||||
name = profileInfo.name;
|
return this.state.profileInfo.name;
|
||||||
} else {
|
} else {
|
||||||
name = fallbackUserId;
|
return MatrixClientPeg.get().getUserId();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const name = this._getDisplayName();
|
||||||
let nameElement;
|
let nameElement;
|
||||||
if (!this.props.collapsed) {
|
if (!this.props.collapsed) {
|
||||||
nameElement = <div className="mx_TopLeftMenuButton_name">
|
nameElement = <div className="mx_TopLeftMenuButton_name">
|
||||||
|
@ -89,9 +90,9 @@ export default class TopLeftMenuButton extends React.Component {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className="mx_TopLeftMenuButton" onClick={this.onToggleMenu}>
|
<AccessibleButton className="mx_TopLeftMenuButton" onClick={this.onToggleMenu}>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
idName={fallbackUserId}
|
idName={MatrixClientPeg.get().getUserId()}
|
||||||
name={name}
|
name={name}
|
||||||
url={profileInfo && profileInfo.avatarUrl}
|
url={this.state.profileInfo && this.state.profileInfo.avatarUrl}
|
||||||
width={AVATAR_SIZE}
|
width={AVATAR_SIZE}
|
||||||
height={AVATAR_SIZE}
|
height={AVATAR_SIZE}
|
||||||
resizeMethod="crop"
|
resizeMethod="crop"
|
||||||
|
@ -114,6 +115,8 @@ export default class TopLeftMenuButton extends React.Component {
|
||||||
chevronFace: "none",
|
chevronFace: "none",
|
||||||
left: x,
|
left: x,
|
||||||
top: y,
|
top: y,
|
||||||
|
userId: MatrixClientPeg.get().getUserId(),
|
||||||
|
displayName: this._getDisplayName(),
|
||||||
onFinished: () => {
|
onFinished: () => {
|
||||||
this.setState({ menuDisplayed: false });
|
this.setState({ menuDisplayed: false });
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,14 +15,22 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import LogoutDialog from "../dialogs/LogoutDialog";
|
import LogoutDialog from "../dialogs/LogoutDialog";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
|
import { getHostingLink } from '../../../utils/HostingLink';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
export class TopLeftMenu extends React.Component {
|
export class TopLeftMenu extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
displayName: PropTypes.string.isRequired,
|
||||||
|
userId: PropTypes.string.isRequired,
|
||||||
|
onFinished: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.viewHomePage = this.viewHomePage.bind(this);
|
this.viewHomePage = this.viewHomePage.bind(this);
|
||||||
|
@ -46,27 +54,48 @@ export class TopLeftMenu extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const isGuest = MatrixClientPeg.get().isGuest();
|
const isGuest = MatrixClientPeg.get().isGuest();
|
||||||
|
|
||||||
|
const hostingSignupLink = getHostingLink('user-context-menu');
|
||||||
|
let hostingSignup = null;
|
||||||
|
if (hostingSignupLink) {
|
||||||
|
hostingSignup = <div className="mx_TopLeftMenu_upgradeLink">
|
||||||
|
{_t(
|
||||||
|
"<a>Upgrade</a> to your own domain", {},
|
||||||
|
{
|
||||||
|
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener">{sub}</a>,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
<a href={hostingSignupLink} target="_blank" rel="noopener">
|
||||||
|
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
|
||||||
|
</a>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
let homePageSection = null;
|
let homePageSection = null;
|
||||||
if (this.hasHomePage()) {
|
if (this.hasHomePage()) {
|
||||||
homePageSection = <ul className="mx_TopLeftMenu_section">
|
homePageSection = <ul className="mx_TopLeftMenu_section_withIcon">
|
||||||
<li className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>{_t("Home")}</li>
|
<li className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>{_t("Home")}</li>
|
||||||
</ul>;
|
</ul>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let signInOutSection;
|
let signInOutSection;
|
||||||
if (isGuest) {
|
if (isGuest) {
|
||||||
signInOutSection = <ul className="mx_TopLeftMenu_section">
|
signInOutSection = <ul className="mx_TopLeftMenu_section_withIcon">
|
||||||
<li className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>{_t("Sign in")}</li>
|
<li className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>{_t("Sign in")}</li>
|
||||||
</ul>;
|
</ul>;
|
||||||
} else {
|
} else {
|
||||||
signInOutSection = <ul className="mx_TopLeftMenu_section">
|
signInOutSection = <ul className="mx_TopLeftMenu_section_withIcon">
|
||||||
<li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>{_t("Sign out")}</li>
|
<li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>{_t("Sign out")}</li>
|
||||||
</ul>;
|
</ul>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_TopLeftMenu">
|
return <div className="mx_TopLeftMenu">
|
||||||
|
<div className="mx_TopLeftMenu_section_noIcon">
|
||||||
|
<div>{this.props.displayName}</div>
|
||||||
|
<div className="mx_TopLeftMenu_greyedText">{this.props.userId}</div>
|
||||||
|
{hostingSignup}
|
||||||
|
</div>
|
||||||
{homePageSection}
|
{homePageSection}
|
||||||
<ul className="mx_TopLeftMenu_section">
|
<ul className="mx_TopLeftMenu_section_withIcon">
|
||||||
<li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>{_t("Settings")}</li>
|
<li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>{_t("Settings")}</li>
|
||||||
</ul>
|
</ul>
|
||||||
{signInOutSection}
|
{signInOutSection}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import Field from "../elements/Field";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {User} from "matrix-js-sdk";
|
import {User} from "matrix-js-sdk";
|
||||||
|
import { getHostingLink } from '../../../utils/HostingLink';
|
||||||
|
|
||||||
export default class ProfileSettings extends React.Component {
|
export default class ProfileSettings extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -137,13 +138,32 @@ export default class ProfileSettings extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hostingSignupLink = getHostingLink('user-settings');
|
||||||
|
let hostingSignup = null;
|
||||||
|
if (hostingSignupLink) {
|
||||||
|
hostingSignup = <span className="mx_ProfileSettings_hostingSignup">
|
||||||
|
{_t(
|
||||||
|
"<a>Upgrade</a> to your own domain", {},
|
||||||
|
{
|
||||||
|
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener">{sub}</a>,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
<a href={hostingSignupLink} target="_blank" rel="noopener">
|
||||||
|
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
|
||||||
|
</a>
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this._saveProfile} autoComplete={false} noValidate={true}>
|
<form onSubmit={this._saveProfile} autoComplete={false} noValidate={true}>
|
||||||
<input type="file" ref="avatarUpload" className="mx_ProfileSettings_avatarUpload"
|
<input type="file" ref="avatarUpload" className="mx_ProfileSettings_avatarUpload"
|
||||||
onChange={this._onAvatarChanged} accept="image/*" />
|
onChange={this._onAvatarChanged} accept="image/*" />
|
||||||
<div className="mx_ProfileSettings_profile">
|
<div className="mx_ProfileSettings_profile">
|
||||||
<div className="mx_ProfileSettings_controls">
|
<div className="mx_ProfileSettings_controls">
|
||||||
<p>{this.state.userId}</p>
|
<p>
|
||||||
|
{this.state.userId}
|
||||||
|
{hostingSignup}
|
||||||
|
</p>
|
||||||
<Field id="profileDisplayName" label={_t("Display Name")}
|
<Field id="profileDisplayName" label={_t("Display Name")}
|
||||||
type="text" value={this.state.displayName} autoComplete="off"
|
type="text" value={this.state.displayName} autoComplete="off"
|
||||||
onChange={this._onDisplayNameChanged} />
|
onChange={this._onDisplayNameChanged} />
|
||||||
|
|
|
@ -502,6 +502,7 @@
|
||||||
"Phone Number": "Phone Number",
|
"Phone Number": "Phone Number",
|
||||||
"Profile picture": "Profile picture",
|
"Profile picture": "Profile picture",
|
||||||
"Upload profile picture": "Upload profile picture",
|
"Upload profile picture": "Upload profile picture",
|
||||||
|
"<a>Upgrade</a> to your own domain": "<a>Upgrade</a> to your own domain",
|
||||||
"Display Name": "Display Name",
|
"Display Name": "Display Name",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Flair": "Flair",
|
"Flair": "Flair",
|
||||||
|
@ -1317,6 +1318,7 @@
|
||||||
"Leave %(groupName)s?": "Leave %(groupName)s?",
|
"Leave %(groupName)s?": "Leave %(groupName)s?",
|
||||||
"Unable to leave community": "Unable to leave community",
|
"Unable to leave community": "Unable to leave community",
|
||||||
"Community Settings": "Community Settings",
|
"Community Settings": "Community Settings",
|
||||||
|
"Want more than a community? <a>Get your own server</a>": "Want more than a community? <a>Get your own server</a>",
|
||||||
"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.",
|
"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.",
|
||||||
"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.",
|
"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:",
|
"Featured Rooms:": "Featured Rooms:",
|
||||||
|
|
37
src/utils/HostingLink.js
Normal file
37
src/utils/HostingLink.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue