Merge remote-tracking branch 'origin/develop' into jaywink/elementPro
This commit is contained in:
commit
5aa24b97cd
145 changed files with 7274 additions and 2269 deletions
|
@ -23,15 +23,17 @@ import { _t } from '../../../languageHandler';
|
|||
*/
|
||||
export default class ConfirmRedactDialog extends React.Component {
|
||||
render() {
|
||||
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
|
||||
const TextInputDialog = sdk.getComponent('views.dialogs.TextInputDialog');
|
||||
return (
|
||||
<QuestionDialog onFinished={this.props.onFinished}
|
||||
<TextInputDialog onFinished={this.props.onFinished}
|
||||
title={_t("Confirm Removal")}
|
||||
description={
|
||||
_t("Are you sure you wish to remove (delete) this event? " +
|
||||
"Note that if you delete a room name or topic change, it could undo the change.")}
|
||||
placeholder={_t("Reason (optional)")}
|
||||
focus
|
||||
button={_t("Remove")}>
|
||||
</QuestionDialog>
|
||||
</TextInputDialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ export default class InfoDialog extends React.Component {
|
|||
onFinished: PropTypes.func,
|
||||
hasCloseButton: PropTypes.bool,
|
||||
onKeyDown: PropTypes.func,
|
||||
fixedWidth: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
@ -54,6 +55,7 @@ export default class InfoDialog extends React.Component {
|
|||
contentId='mx_Dialog_content'
|
||||
hasCancel={this.props.hasCloseButton}
|
||||
onKeyDown={this.props.onKeyDown}
|
||||
fixedWidth={this.props.fixedWidth}
|
||||
>
|
||||
<div className={classNames("mx_Dialog_content", this.props.className)} id="mx_Dialog_content">
|
||||
{ this.props.description }
|
||||
|
|
|
@ -15,13 +15,12 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import * as sdk from "../../../index";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import {RoomMember} from "matrix-js-sdk/src/matrix";
|
||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||
import * as Email from "../../../email";
|
||||
|
@ -132,12 +131,12 @@ class ThreepidMember extends Member {
|
|||
}
|
||||
}
|
||||
|
||||
class DMUserTile extends React.PureComponent {
|
||||
static propTypes = {
|
||||
member: PropTypes.object.isRequired, // Should be a Member (see interface above)
|
||||
onRemove: PropTypes.func, // takes 1 argument, the member being removed
|
||||
};
|
||||
interface IDMUserTileProps {
|
||||
member: RoomMember;
|
||||
onRemove: (RoomMember) => any;
|
||||
}
|
||||
|
||||
class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||
_onRemove = (e) => {
|
||||
// Stop the browser from highlighting text
|
||||
e.preventDefault();
|
||||
|
@ -173,7 +172,9 @@ class DMUserTile extends React.PureComponent {
|
|||
className='mx_InviteDialog_userTile_remove'
|
||||
onClick={this._onRemove}
|
||||
>
|
||||
<img src={require("../../../../res/img/icon-pill-remove.svg")} alt={_t('Remove')} width={8} height={8} />
|
||||
<img src={require("../../../../res/img/icon-pill-remove.svg")}
|
||||
alt={_t('Remove')} width={8} height={8}
|
||||
/>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
@ -190,15 +191,15 @@ class DMUserTile extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
class DMRoomTile extends React.PureComponent {
|
||||
static propTypes = {
|
||||
member: PropTypes.object.isRequired, // Should be a Member (see interface above)
|
||||
lastActiveTs: PropTypes.number,
|
||||
onToggle: PropTypes.func.isRequired, // takes 1 argument, the member being toggled
|
||||
highlightWord: PropTypes.string,
|
||||
isSelected: PropTypes.bool,
|
||||
};
|
||||
interface IDMRoomTileProps {
|
||||
member: RoomMember;
|
||||
lastActiveTs: number;
|
||||
onToggle: (RoomMember) => any;
|
||||
highlightWord: string;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||
_onClick = (e) => {
|
||||
// Stop the browser from highlighting text
|
||||
e.preventDefault();
|
||||
|
@ -298,28 +299,45 @@ class DMRoomTile extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
export default class InviteDialog extends React.PureComponent {
|
||||
static propTypes = {
|
||||
// Takes an array of user IDs/emails to invite.
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
interface IInviteDialogProps {
|
||||
// Takes an array of user IDs/emails to invite.
|
||||
onFinished: (toInvite?: string[]) => any;
|
||||
|
||||
// The kind of invite being performed. Assumed to be KIND_DM if
|
||||
// not provided.
|
||||
kind: PropTypes.string,
|
||||
// The kind of invite being performed. Assumed to be KIND_DM if
|
||||
// not provided.
|
||||
kind: string,
|
||||
|
||||
// The room ID this dialog is for. Only required for KIND_INVITE.
|
||||
roomId: PropTypes.string,
|
||||
// The room ID this dialog is for. Only required for KIND_INVITE.
|
||||
roomId: string,
|
||||
|
||||
// Initial value to populate the filter with
|
||||
initialText: PropTypes.string,
|
||||
};
|
||||
// Initial value to populate the filter with
|
||||
initialText: string,
|
||||
}
|
||||
|
||||
interface IInviteDialogState {
|
||||
targets: RoomMember[]; // array of Member objects (see interface above)
|
||||
filterText: string;
|
||||
recents: { user: Member, userId: string }[];
|
||||
numRecentsShown: number;
|
||||
suggestions: { user: Member, userId: string }[];
|
||||
numSuggestionsShown: number;
|
||||
serverResultsMixin: { user: Member, userId: string }[];
|
||||
threepidResultsMixin: { user: Member, userId: string}[];
|
||||
canUseIdentityServer: boolean;
|
||||
tryingIdentityServer: boolean;
|
||||
|
||||
// These two flags are used for the 'Go' button to communicate what is going on.
|
||||
busy: boolean,
|
||||
errorText: string,
|
||||
}
|
||||
|
||||
export default class InviteDialog extends React.PureComponent<IInviteDialogProps, IInviteDialogState> {
|
||||
static defaultProps = {
|
||||
kind: KIND_DM,
|
||||
initialText: "",
|
||||
};
|
||||
|
||||
_debounceTimer: number = null;
|
||||
_debounceTimer: NodeJS.Timeout = null; // actually number because we're in the browser
|
||||
_editorRef: any = null;
|
||||
|
||||
constructor(props) {
|
||||
|
@ -348,8 +366,8 @@ export default class InviteDialog extends React.PureComponent {
|
|||
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
||||
suggestions: this._buildSuggestions(alreadyInvited),
|
||||
numSuggestionsShown: INITIAL_ROOMS_SHOWN,
|
||||
serverResultsMixin: [], // { user: DirectoryMember, userId: string }[], like recents and suggestions
|
||||
threepidResultsMixin: [], // { user: ThreepidMember, userId: string}[], like recents and suggestions
|
||||
serverResultsMixin: [],
|
||||
threepidResultsMixin: [],
|
||||
canUseIdentityServer: !!MatrixClientPeg.get().getIdentityServerUrl(),
|
||||
tryingIdentityServer: false,
|
||||
|
||||
|
@ -367,7 +385,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
static buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number} {
|
||||
static buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number}[] {
|
||||
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
||||
|
||||
// Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the
|
||||
|
@ -430,7 +448,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
return recents;
|
||||
}
|
||||
|
||||
_buildSuggestions(excludedTargetIds: Set<string>): {userId: string, user: RoomMember} {
|
||||
_buildSuggestions(excludedTargetIds: Set<string>): {userId: string, user: RoomMember}[] {
|
||||
const maxConsideredMembers = 200;
|
||||
const joinedRooms = MatrixClientPeg.get().getRooms()
|
||||
.filter(r => r.getMyMembership() === 'join' && r.getJoinedMemberCount() <= maxConsideredMembers);
|
||||
|
@ -470,7 +488,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
}, {});
|
||||
|
||||
// Generates { userId: {member, numRooms, score} }
|
||||
const memberScores = Object.values(memberRooms).reduce((scores, entry) => {
|
||||
const memberScores = Object.values(memberRooms).reduce((scores, entry: {member: RoomMember, rooms: Room[]}) => {
|
||||
const numMembersTotal = entry.rooms.reduce((c, r) => c + r.getJoinedMemberCount(), 0);
|
||||
const maxRange = maxConsideredMembers * entry.rooms.length;
|
||||
scores[entry.member.userId] = {
|
||||
|
@ -603,7 +621,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
const createRoomOptions = {inlineErrors: true};
|
||||
const createRoomOptions = {inlineErrors: true} as any; // XXX: Type out `createRoomOptions`
|
||||
|
||||
if (privateShouldBeEncrypted()) {
|
||||
// Check whether all users have uploaded device keys before.
|
||||
|
@ -620,7 +638,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
|
||||
// Check if it's a traditional DM and create the room if required.
|
||||
// TODO: [Canonical DMs] Remove this check and instead just create the multi-person DM
|
||||
let createRoomPromise = Promise.resolve();
|
||||
let createRoomPromise = Promise.resolve(null) as Promise<string | null | boolean>;
|
||||
const isSelf = targetIds.length === 1 && targetIds[0] === MatrixClientPeg.get().getUserId();
|
||||
if (targetIds.length === 1 && !isSelf) {
|
||||
createRoomOptions.dmUserId = targetIds[0];
|
||||
|
@ -990,7 +1008,8 @@ export default class InviteDialog extends React.PureComponent {
|
|||
const hasMixins = this.state.serverResultsMixin || this.state.threepidResultsMixin;
|
||||
if (this.state.filterText && hasMixins && kind === 'suggestions') {
|
||||
// We don't want to duplicate members though, so just exclude anyone we've already seen.
|
||||
const notAlreadyExists = (u: Member): boolean => {
|
||||
// The type of u is a pain to define but members of both mixins have the 'userId' property
|
||||
const notAlreadyExists = (u: any): boolean => {
|
||||
return !sourceMembers.some(m => m.userId === u.userId)
|
||||
&& !priorityAdditionalMembers.some(m => m.userId === u.userId)
|
||||
&& !otherAdditionalMembers.some(m => m.userId === u.userId);
|
||||
|
@ -1169,7 +1188,8 @@ export default class InviteDialog extends React.PureComponent {
|
|||
|
||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
||||
const inviteText = _t("This won't invite them to %(communityName)s. " +
|
||||
const inviteText = _t(
|
||||
"This won't invite them to %(communityName)s. " +
|
||||
"To invite someone to %(communityName)s, click <a>here</a>",
|
||||
{communityName}, {
|
||||
userId: () => {
|
||||
|
@ -1209,7 +1229,9 @@ export default class InviteDialog extends React.PureComponent {
|
|||
userId: () =>
|
||||
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
||||
a: (sub) =>
|
||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">
|
||||
{sub}
|
||||
</a>,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
|
@ -1220,7 +1242,9 @@ export default class InviteDialog extends React.PureComponent {
|
|||
userId: () =>
|
||||
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
||||
a: (sub) =>
|
||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">
|
||||
{sub}
|
||||
</a>,
|
||||
},
|
||||
);
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 * as React from "react";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import {useRef, useState} from "react";
|
||||
import Field from "../elements/Field";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import withValidation from "../elements/Validation";
|
||||
import * as Email from "../../../email";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
onFinished(continued: boolean, email?: string): void;
|
||||
}
|
||||
|
||||
const validation = withValidation({
|
||||
rules: [
|
||||
{
|
||||
key: "email",
|
||||
test: ({ value }) => !value || Email.looksValid(value),
|
||||
invalid: () => _t("Doesn't look like a valid email address"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const RegistrationEmailPromptDialog: React.FC<IProps> = ({onFinished}) => {
|
||||
const [email, setEmail] = useState("");
|
||||
const fieldRef = useRef<Field>();
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (email) {
|
||||
const valid = await fieldRef.current.validate({ allowEmpty: false });
|
||||
|
||||
if (!valid) {
|
||||
fieldRef.current.focus();
|
||||
fieldRef.current.validate({ allowEmpty: false, focused: true });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onFinished(true, email);
|
||||
};
|
||||
|
||||
return <BaseDialog
|
||||
title={_t("Continuing without email")}
|
||||
className="mx_RegistrationEmailPromptDialog"
|
||||
contentId="mx_RegistrationEmailPromptDialog"
|
||||
onFinished={() => onFinished(false)}
|
||||
fixedWidth={false}
|
||||
>
|
||||
<div className="mx_Dialog_content" id="mx_RegistrationEmailPromptDialog">
|
||||
<p>{_t("Just a heads up, if you don't add an email and forget your password, you could " +
|
||||
"<b>permanently lose access to your account</b>.", {}, {
|
||||
b: sub => <b>{sub}</b>,
|
||||
})}</p>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Field
|
||||
ref={fieldRef}
|
||||
type="text"
|
||||
label={_t("Email (optional)")}
|
||||
value={email}
|
||||
onChange={ev => {
|
||||
setEmail(ev.target.value);
|
||||
}}
|
||||
onValidate={async fieldState => await validation(fieldState)}
|
||||
onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_email2_focus")}
|
||||
onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_email2_blur")}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Continue")}
|
||||
onPrimaryButtonClick={onSubmit}
|
||||
hasCancel={false}
|
||||
/>
|
||||
</BaseDialog>;
|
||||
};
|
||||
|
||||
export default RegistrationEmailPromptDialog;
|
|
@ -53,9 +53,9 @@ export default class RoomSettingsDialog extends React.Component {
|
|||
}
|
||||
|
||||
_onAction = (payload) => {
|
||||
// When room changes below us, close the room settings
|
||||
// When view changes below us, close the room settings
|
||||
// whilst the modal is open this can only be triggered when someone hits Leave Room
|
||||
if (payload.action === 'view_next_room') {
|
||||
if (payload.action === 'view_home_page') {
|
||||
this.props.onFinished();
|
||||
}
|
||||
};
|
||||
|
|
235
src/components/views/dialogs/ServerPickerDialog.tsx
Normal file
235
src/components/views/dialogs/ServerPickerDialog.tsx
Normal file
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 React, {createRef} from "react";
|
||||
import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
|
||||
|
||||
import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import BaseDialog from './BaseDialog';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import Field from "../elements/Field";
|
||||
import StyledRadioButton from "../elements/StyledRadioButton";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import withValidation, {IFieldState} from "../elements/Validation";
|
||||
|
||||
interface IProps {
|
||||
title?: string;
|
||||
serverConfig: ValidatedServerConfig;
|
||||
onFinished(config?: ValidatedServerConfig): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
defaultChosen: boolean;
|
||||
otherHomeserver: string;
|
||||
}
|
||||
|
||||
export default class ServerPickerDialog extends React.PureComponent<IProps, IState> {
|
||||
private readonly defaultServer: ValidatedServerConfig;
|
||||
private readonly fieldRef = createRef<Field>();
|
||||
private validatedConf: ValidatedServerConfig;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const config = SdkConfig.get();
|
||||
this.defaultServer = config["validated_server_config"] as ValidatedServerConfig;
|
||||
const { serverConfig } = this.props;
|
||||
|
||||
let otherHomeserver = "";
|
||||
if (!serverConfig.isDefault) {
|
||||
if (serverConfig.isNameResolvable && serverConfig.hsName) {
|
||||
otherHomeserver = serverConfig.hsName;
|
||||
} else {
|
||||
otherHomeserver = serverConfig.hsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
defaultChosen: serverConfig.isDefault,
|
||||
otherHomeserver,
|
||||
};
|
||||
}
|
||||
|
||||
private onDefaultChosen = () => {
|
||||
this.setState({ defaultChosen: true });
|
||||
};
|
||||
|
||||
private onOtherChosen = () => {
|
||||
this.setState({ defaultChosen: false });
|
||||
};
|
||||
|
||||
private onHomeserverChange = (ev) => {
|
||||
this.setState({ otherHomeserver: ev.target.value });
|
||||
};
|
||||
|
||||
// TODO: Do we want to support .well-known lookups here?
|
||||
// If for some reason someone enters "matrix.org" for a URL, we could do a lookup to
|
||||
// find their homeserver without demanding they use "https://matrix.org"
|
||||
private validate = withValidation<this, { error?: string }>({
|
||||
deriveData: async ({ value }) => {
|
||||
let hsUrl = value.trim(); // trim to account for random whitespace
|
||||
|
||||
// if the URL has no protocol, try validate it as a serverName via well-known
|
||||
if (!hsUrl.includes("://")) {
|
||||
try {
|
||||
const discoveryResult = await AutoDiscovery.findClientConfig(hsUrl);
|
||||
this.validatedConf = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(hsUrl, discoveryResult);
|
||||
return {}; // we have a validated config, we don't need to try the other paths
|
||||
} catch (e) {
|
||||
console.error(`Attempted ${hsUrl} as a server_name but it failed`, e);
|
||||
}
|
||||
}
|
||||
|
||||
// if we got to this stage then either the well-known failed or the URL had a protocol specified,
|
||||
// so validate statically only. If the URL has no protocol, default to https.
|
||||
if (!hsUrl.includes("://")) {
|
||||
hsUrl = "https://" + hsUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl);
|
||||
return {};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
|
||||
if (stateForError.isFatalError) {
|
||||
let error = _t("Unable to validate homeserver");
|
||||
if (e.translatedMessage) {
|
||||
error = e.translatedMessage;
|
||||
}
|
||||
return { error };
|
||||
}
|
||||
|
||||
// try to carry on anyway
|
||||
try {
|
||||
this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, null, true);
|
||||
return {};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { error: _t("Invalid URL") };
|
||||
}
|
||||
}
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
key: "required",
|
||||
test: ({ value, allowEmpty }) => allowEmpty || !!value,
|
||||
invalid: () => _t("Specify a homeserver"),
|
||||
}, {
|
||||
key: "valid",
|
||||
test: async function({ value }, { error }) {
|
||||
if (!value) return true;
|
||||
return !error;
|
||||
},
|
||||
invalid: function({ error }) {
|
||||
return error;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
private onHomeserverValidate = (fieldState: IFieldState) => this.validate(fieldState);
|
||||
|
||||
private onSubmit = async (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
const valid = await this.fieldRef.current.validate({ allowEmpty: false });
|
||||
|
||||
if (!valid) {
|
||||
this.fieldRef.current.focus();
|
||||
this.fieldRef.current.validate({ allowEmpty: false, focused: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onFinished(this.validatedConf);
|
||||
};
|
||||
|
||||
public render() {
|
||||
let text;
|
||||
if (this.defaultServer.hsName === "matrix.org") {
|
||||
text = _t("Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.");
|
||||
}
|
||||
|
||||
let defaultServerName = this.defaultServer.hsName;
|
||||
if (this.defaultServer.hsNameIsDifferent) {
|
||||
defaultServerName = (
|
||||
<TextWithTooltip class="mx_Login_underlinedServerName" tooltip={this.defaultServer.hsUrl}>
|
||||
{this.defaultServer.hsName}
|
||||
</TextWithTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return <BaseDialog
|
||||
title={this.props.title || _t("Sign into your homeserver")}
|
||||
className="mx_ServerPickerDialog"
|
||||
contentId="mx_ServerPickerDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
fixedWidth={false}
|
||||
hasCancel={true}
|
||||
>
|
||||
<form className="mx_Dialog_content" id="mx_ServerPickerDialog" onSubmit={this.onSubmit}>
|
||||
<p>
|
||||
{_t("We call the places where you can host your account ‘homeservers’.")} {text}
|
||||
</p>
|
||||
|
||||
<StyledRadioButton
|
||||
name="defaultChosen"
|
||||
value="true"
|
||||
checked={this.state.defaultChosen}
|
||||
onChange={this.onDefaultChosen}
|
||||
>
|
||||
{defaultServerName}
|
||||
</StyledRadioButton>
|
||||
|
||||
<StyledRadioButton
|
||||
name="defaultChosen"
|
||||
value="false"
|
||||
className="mx_ServerPickerDialog_otherHomeserverRadio"
|
||||
checked={!this.state.defaultChosen}
|
||||
onChange={this.onOtherChosen}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
className="mx_ServerPickerDialog_otherHomeserver"
|
||||
label={_t("Other homeserver")}
|
||||
onChange={this.onHomeserverChange}
|
||||
onClick={this.onOtherChosen}
|
||||
ref={this.fieldRef}
|
||||
onValidate={this.onHomeserverValidate}
|
||||
value={this.state.otherHomeserver}
|
||||
validateOnChange={false}
|
||||
validateOnFocus={false}
|
||||
/>
|
||||
</StyledRadioButton>
|
||||
<p>
|
||||
{_t("Use your preferred Matrix homeserver if you have one, or host your own.")}
|
||||
</p>
|
||||
|
||||
<AccessibleButton className="mx_ServerPickerDialog_continue" kind="primary" onClick={this.onSubmit}>
|
||||
{_t("Continue")}
|
||||
</AccessibleButton>
|
||||
|
||||
<h4>{_t("Learn more")}</h4>
|
||||
<a href="https://matrix.org/faq/#what-is-a-homeserver%3F" target="_blank" rel="noreferrer noopener">
|
||||
{_t("About homeservers")}
|
||||
</a>
|
||||
</form>
|
||||
</BaseDialog>;
|
||||
}
|
||||
}
|
|
@ -146,7 +146,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
|||
const events = this.props.target.getLiveTimeline().getEvents();
|
||||
matrixToUrl = this.state.permalinkCreator.forEvent(events[events.length - 1].getId());
|
||||
} else {
|
||||
matrixToUrl = this.state.permalinkCreator.forRoom();
|
||||
matrixToUrl = this.state.permalinkCreator.forShareableRoom();
|
||||
}
|
||||
} else if (this.props.target instanceof User || this.props.target instanceof RoomMember) {
|
||||
matrixToUrl = makeUserPermalink(this.props.target.userId);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue