Merge branches 'develop' and 't3chguy/community_member_invite_IS_text' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/community_member_invite_IS_text
This commit is contained in:
commit
6dc69afe67
38 changed files with 975 additions and 393 deletions
|
@ -31,6 +31,7 @@ export default class PasswordLogin extends React.Component {
|
|||
static propTypes = {
|
||||
onSubmit: PropTypes.func.isRequired, // fn(username, password)
|
||||
onError: PropTypes.func,
|
||||
onEditServerDetailsClick: PropTypes.func,
|
||||
onForgotPasswordClick: PropTypes.func, // fn()
|
||||
initialUsername: PropTypes.string,
|
||||
initialPhoneCountry: PropTypes.string,
|
||||
|
@ -257,6 +258,7 @@ export default class PasswordLogin extends React.Component {
|
|||
|
||||
render() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
const SignInToText = sdk.getComponent('views.auth.SignInToText');
|
||||
|
||||
let forgotPasswordJsx;
|
||||
|
||||
|
@ -273,33 +275,6 @@ export default class PasswordLogin extends React.Component {
|
|||
</span>;
|
||||
}
|
||||
|
||||
let signInToText = _t('Sign in to your Matrix account on %(serverName)s', {
|
||||
serverName: this.props.serverConfig.hsName,
|
||||
});
|
||||
if (this.props.serverConfig.hsNameIsDifferent) {
|
||||
const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip");
|
||||
|
||||
signInToText = _t('Sign in to your Matrix account on <underlinedServerName />', {}, {
|
||||
'underlinedServerName': () => {
|
||||
return <TextWithTooltip
|
||||
class="mx_Login_underlinedServerName"
|
||||
tooltip={this.props.serverConfig.hsUrl}
|
||||
>
|
||||
{this.props.serverConfig.hsName}
|
||||
</TextWithTooltip>;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let editLink = null;
|
||||
if (this.props.onEditServerDetailsClick) {
|
||||
editLink = <a className="mx_AuthBody_editServerDetails"
|
||||
href="#" onClick={this.props.onEditServerDetailsClick}
|
||||
>
|
||||
{_t('Change')}
|
||||
</a>;
|
||||
}
|
||||
|
||||
const pwFieldClass = classNames({
|
||||
error: this.props.loginIncorrect && !this.isLoginEmpty(), // only error password if error isn't top field
|
||||
});
|
||||
|
@ -342,10 +317,8 @@ export default class PasswordLogin extends React.Component {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<h3>
|
||||
{signInToText}
|
||||
{editLink}
|
||||
</h3>
|
||||
<SignInToText serverConfig={this.props.serverConfig}
|
||||
onEditServerDetailsClick={this.props.onEditServerDetailsClick} />
|
||||
<form onSubmit={this.onSubmitForm}>
|
||||
{loginType}
|
||||
{loginField}
|
||||
|
|
62
src/components/views/auth/SignInToText.js
Normal file
62
src/components/views/auth/SignInToText.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2019 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 from 'react';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import sdk from "../../../index";
|
||||
import PropTypes from "prop-types";
|
||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
|
||||
export default class SignInToText extends React.PureComponent {
|
||||
static propTypes = {
|
||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
||||
onEditServerDetailsClick: PropTypes.func,
|
||||
};
|
||||
|
||||
render() {
|
||||
let signInToText = _t('Sign in to your Matrix account on %(serverName)s', {
|
||||
serverName: this.props.serverConfig.hsName,
|
||||
});
|
||||
if (this.props.serverConfig.hsNameIsDifferent) {
|
||||
const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip");
|
||||
|
||||
signInToText = _t('Sign in to your Matrix account on <underlinedServerName />', {}, {
|
||||
'underlinedServerName': () => {
|
||||
return <TextWithTooltip
|
||||
class="mx_Login_underlinedServerName"
|
||||
tooltip={this.props.serverConfig.hsUrl}
|
||||
>
|
||||
{this.props.serverConfig.hsName}
|
||||
</TextWithTooltip>;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let editLink = null;
|
||||
if (this.props.onEditServerDetailsClick) {
|
||||
editLink = <a className="mx_AuthBody_editServerDetails"
|
||||
href="#" onClick={this.props.onEditServerDetailsClick}
|
||||
>
|
||||
{_t('Change')}
|
||||
</a>;
|
||||
}
|
||||
|
||||
return <h3>
|
||||
{signInToText}
|
||||
{editLink}
|
||||
</h3>;
|
||||
}
|
||||
}
|
|
@ -112,6 +112,10 @@ export default class EditMessageComposer extends React.Component {
|
|||
super(props, context);
|
||||
this.model = null;
|
||||
this._editorRef = null;
|
||||
|
||||
this.state = {
|
||||
saveDisabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
_setEditorRef = ref => {
|
||||
|
@ -160,7 +164,7 @@ export default class EditMessageComposer extends React.Component {
|
|||
dis.dispatch({action: 'focus_composer'});
|
||||
}
|
||||
|
||||
_isModifiedOrSameAsOld(newContent) {
|
||||
_isContentModified(newContent) {
|
||||
// if nothing has changed then bail
|
||||
const oldContent = this.props.editState.getEvent().getContent();
|
||||
if (!this._editorRef.isModified() ||
|
||||
|
@ -176,16 +180,18 @@ export default class EditMessageComposer extends React.Component {
|
|||
const editedEvent = this.props.editState.getEvent();
|
||||
const editContent = createEditContent(this.model, editedEvent);
|
||||
const newContent = editContent["m.new_content"];
|
||||
if (!this._isModifiedOrSameAsOld(newContent)) {
|
||||
return;
|
||||
}
|
||||
const roomId = editedEvent.getRoomId();
|
||||
this._cancelPreviousPendingEdit();
|
||||
this.context.matrixClient.sendMessage(roomId, editContent);
|
||||
|
||||
// If content is modified then send an updated event into the room
|
||||
if (this._isContentModified(newContent)) {
|
||||
const roomId = editedEvent.getRoomId();
|
||||
this._cancelPreviousPendingEdit();
|
||||
this.context.matrixClient.sendMessage(roomId, editContent);
|
||||
}
|
||||
|
||||
// close the event editing and focus composer
|
||||
dis.dispatch({action: "edit_event", event: null});
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
}
|
||||
};
|
||||
|
||||
_cancelPreviousPendingEdit() {
|
||||
const originalEvent = this.props.editState.getEvent();
|
||||
|
@ -240,6 +246,16 @@ export default class EditMessageComposer extends React.Component {
|
|||
return caretPosition;
|
||||
}
|
||||
|
||||
_onChange = () => {
|
||||
if (!this.state.saveDisabled || !this._editorRef || !this._editorRef.isModified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
saveDisabled: false,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
return (<div className={classNames("mx_EditMessageComposer", this.props.className)} onKeyDown={this._onKeyDown}>
|
||||
|
@ -249,10 +265,13 @@ export default class EditMessageComposer extends React.Component {
|
|||
room={this._getRoom()}
|
||||
initialCaret={this.props.editState.getCaret()}
|
||||
label={_t("Edit message")}
|
||||
onChange={this._onChange}
|
||||
/>
|
||||
<div className="mx_EditMessageComposer_buttons">
|
||||
<AccessibleButton kind="secondary" onClick={this._cancelEdit}>{_t("Cancel")}</AccessibleButton>
|
||||
<AccessibleButton kind="primary" onClick={this._sendEdit}>{_t("Save")}</AccessibleButton>
|
||||
<AccessibleButton kind="primary" onClick={this._sendEdit} disabled={this.state.saveDisabled}>
|
||||
{_t("Save")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import E2EIcon from "./E2EIcon";
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import {EventTimeline} from "matrix-js-sdk";
|
||||
|
||||
module.exports = createReactClass({
|
||||
displayName: 'MemberInfo',
|
||||
|
@ -64,6 +65,7 @@ module.exports = createReactClass({
|
|||
mute: false,
|
||||
modifyLevel: false,
|
||||
synapseDeactivate: false,
|
||||
redactMessages: false,
|
||||
},
|
||||
muted: false,
|
||||
isTargetMod: false,
|
||||
|
@ -356,6 +358,74 @@ module.exports = createReactClass({
|
|||
});
|
||||
},
|
||||
|
||||
onRedactAllMessages: async function() {
|
||||
const {roomId, userId} = this.props.member;
|
||||
const room = this.context.matrixClient.getRoom(roomId);
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
let timeline = room.getLiveTimeline();
|
||||
let eventsToRedact = [];
|
||||
while (timeline) {
|
||||
eventsToRedact = timeline.getEvents().reduce((events, event) => {
|
||||
if (event.getSender() === userId && !event.isRedacted()) {
|
||||
return events.concat(event);
|
||||
} else {
|
||||
return events;
|
||||
}
|
||||
}, eventsToRedact);
|
||||
timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS);
|
||||
}
|
||||
|
||||
const count = eventsToRedact.length;
|
||||
const user = this.props.member.name;
|
||||
|
||||
if (count === 0) {
|
||||
const InfoDialog = sdk.getComponent("dialogs.InfoDialog");
|
||||
Modal.createTrackedDialog('No user messages found to remove', '', InfoDialog, {
|
||||
title: _t("No recent messages by %(user)s found", {user}),
|
||||
description:
|
||||
<div>
|
||||
<p>{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }</p>
|
||||
</div>,
|
||||
});
|
||||
} else {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const confirmed = await new Promise((resolve) => {
|
||||
Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, {
|
||||
title: _t("Remove recent messages by %(user)s", {user}),
|
||||
description:
|
||||
<div>
|
||||
<p>{ _t("You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", {count, user}) }</p>
|
||||
<p>{ _t("For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.") }</p>
|
||||
</div>,
|
||||
button: _t("Remove %(count)s messages", {count}),
|
||||
onFinished: resolve,
|
||||
});
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Submitting a large number of redactions freezes the UI,
|
||||
// so first yield to allow to rerender after closing the dialog.
|
||||
await Promise.resolve();
|
||||
|
||||
console.info(`Started redacting recent ${count} messages for ${user} in ${roomId}`);
|
||||
await Promise.all(eventsToRedact.map(async event => {
|
||||
try {
|
||||
await this.context.matrixClient.redactEvent(roomId, event.getId());
|
||||
} catch (err) {
|
||||
// log and swallow errors
|
||||
console.error("Could not redact", event.getId());
|
||||
console.error(err);
|
||||
}
|
||||
}));
|
||||
console.info(`Finished redacting recent ${count} messages for ${user} in ${roomId}`);
|
||||
}
|
||||
},
|
||||
|
||||
_warnSelfDemote: function() {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
return new Promise((resolve) => {
|
||||
|
@ -572,7 +642,10 @@ module.exports = createReactClass({
|
|||
|
||||
_calculateOpsPermissions: async function(member) {
|
||||
const defaultPerms = {
|
||||
can: {},
|
||||
can: {
|
||||
// Calculate permissions for Synapse before doing the PL checks
|
||||
synapseDeactivate: await this.context.matrixClient.isSynapseAdministrator(),
|
||||
},
|
||||
muted: false,
|
||||
};
|
||||
const room = this.context.matrixClient.getRoom(member.roomId);
|
||||
|
@ -586,9 +659,10 @@ module.exports = createReactClass({
|
|||
|
||||
const them = member;
|
||||
return {
|
||||
can: await this._calculateCanPermissions(
|
||||
me, them, powerLevels.getContent(),
|
||||
),
|
||||
can: {
|
||||
...defaultPerms.can,
|
||||
...await this._calculateCanPermissions(me, them, powerLevels.getContent()),
|
||||
},
|
||||
muted: this._isMuted(them, powerLevels.getContent()),
|
||||
isTargetMod: them.powerLevel > powerLevels.getContent().users_default,
|
||||
};
|
||||
|
@ -602,11 +676,9 @@ module.exports = createReactClass({
|
|||
mute: false,
|
||||
modifyLevel: false,
|
||||
modifyLevelMax: 0,
|
||||
redactMessages: false,
|
||||
};
|
||||
|
||||
// Calculate permissions for Synapse before doing the PL checks
|
||||
can.synapseDeactivate = await this.context.matrixClient.isSynapseAdministrator();
|
||||
|
||||
const canAffectUser = them.powerLevel < me.powerLevel || isMe;
|
||||
if (!canAffectUser) {
|
||||
//console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
|
||||
|
@ -623,6 +695,7 @@ module.exports = createReactClass({
|
|||
can.mute = me.powerLevel >= editPowerLevel;
|
||||
can.modifyLevel = me.powerLevel >= editPowerLevel && (isMe || me.powerLevel > them.powerLevel);
|
||||
can.modifyLevelMax = me.powerLevel;
|
||||
can.redactMessages = me.powerLevel >= powerLevels.redact;
|
||||
|
||||
return can;
|
||||
},
|
||||
|
@ -812,6 +885,7 @@ module.exports = createReactClass({
|
|||
let banButton;
|
||||
let muteButton;
|
||||
let giveModButton;
|
||||
let redactButton;
|
||||
let synapseDeactivateButton;
|
||||
let spinner;
|
||||
|
||||
|
@ -892,6 +966,15 @@ module.exports = createReactClass({
|
|||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.can.redactMessages) {
|
||||
redactButton = (
|
||||
<AccessibleButton className="mx_MemberInfo_field" onClick={this.onRedactAllMessages}>
|
||||
{ _t("Remove recent messages") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.can.ban) {
|
||||
let label = _t("Ban");
|
||||
if (this.props.member.membership === 'ban') {
|
||||
|
@ -932,7 +1015,7 @@ module.exports = createReactClass({
|
|||
}
|
||||
|
||||
let adminTools;
|
||||
if (kickButton || banButton || muteButton || giveModButton || synapseDeactivateButton) {
|
||||
if (kickButton || banButton || muteButton || giveModButton || synapseDeactivateButton || redactButton) {
|
||||
adminTools =
|
||||
<div>
|
||||
<h3>{ _t("Admin Tools") }</h3>
|
||||
|
@ -941,6 +1024,7 @@ module.exports = createReactClass({
|
|||
{ muteButton }
|
||||
{ kickButton }
|
||||
{ banButton }
|
||||
{ redactButton }
|
||||
{ giveModButton }
|
||||
{ synapseDeactivateButton }
|
||||
</div>
|
||||
|
|
|
@ -37,18 +37,19 @@ export default class ReplyPreview extends React.Component {
|
|||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.unmounted = false;
|
||||
|
||||
this.state = {
|
||||
event: null,
|
||||
event: RoomViewStore.getQuotingEvent(),
|
||||
};
|
||||
|
||||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this._onRoomViewStoreUpdate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unmounted = true;
|
||||
|
||||
// Remove RoomStore listener
|
||||
if (this._roomStoreToken) {
|
||||
this._roomStoreToken.remove();
|
||||
|
@ -56,6 +57,8 @@ export default class ReplyPreview extends React.Component {
|
|||
}
|
||||
|
||||
_onRoomViewStoreUpdate() {
|
||||
if (this.unmounted) return;
|
||||
|
||||
const event = RoomViewStore.getQuotingEvent();
|
||||
if (this.state.event !== event) {
|
||||
this.setState({ event });
|
||||
|
|
|
@ -758,7 +758,7 @@ module.exports = createReactClass({
|
|||
headerItems: this._getHeaderItems('im.vector.fake.recent'),
|
||||
order: "recent",
|
||||
incomingCall: incomingCallIfTaggedAs('im.vector.fake.recent'),
|
||||
onAddRoom: () => {dis.dispatch({action: 'view_room_directory'})},
|
||||
onAddRoom: () => {dis.dispatch({action: 'view_create_room'});},
|
||||
},
|
||||
];
|
||||
const tagSubLists = Object.keys(this.state.lists)
|
||||
|
|
|
@ -22,7 +22,7 @@ import sdk from '../../../index';
|
|||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import Modal from '../../../Modal';
|
||||
import dis from "../../../dispatcher";
|
||||
import { getThreepidBindStatus } from '../../../boundThreepids';
|
||||
import { getThreepidsWithBindStatus } from '../../../boundThreepids';
|
||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||
import {SERVICE_TYPES} from "matrix-js-sdk";
|
||||
import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils";
|
||||
|
@ -93,18 +93,11 @@ export default class SetIdServer extends React.Component {
|
|||
|
||||
onAction = (payload) => {
|
||||
// We react to changes in the ID server in the event the user is staring at this form
|
||||
// when changing their identity server on another device. If the user is trying to change
|
||||
// it in two places, we'll end up stomping all over their input, but at that point we
|
||||
// should question our UX which led to them doing that.
|
||||
// when changing their identity server on another device.
|
||||
if (payload.action !== "id_server_changed") return;
|
||||
|
||||
const fullUrl = MatrixClientPeg.get().getIdentityServerUrl();
|
||||
let abbr = '';
|
||||
if (fullUrl) abbr = abbreviateUrl(fullUrl);
|
||||
|
||||
this.setState({
|
||||
currentClientIdServer: fullUrl,
|
||||
idServer: abbr,
|
||||
currentClientIdServer: MatrixClientPeg.get().getIdentityServerUrl(),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -137,15 +130,21 @@ export default class SetIdServer extends React.Component {
|
|||
MatrixClientPeg.get().setAccountData("m.identity_server", {
|
||||
base_url: fullUrl,
|
||||
});
|
||||
this.setState({idServer: '', busy: false, error: null});
|
||||
this.setState({
|
||||
busy: false,
|
||||
error: null,
|
||||
currentClientIdServer: fullUrl,
|
||||
idServer: '',
|
||||
});
|
||||
};
|
||||
|
||||
_checkIdServer = async (e) => {
|
||||
e.preventDefault();
|
||||
const { idServer, currentClientIdServer } = this.state;
|
||||
|
||||
this.setState({busy: true, checking: true, error: null});
|
||||
|
||||
const fullUrl = unabbreviateUrl(this.state.idServer);
|
||||
const fullUrl = unabbreviateUrl(idServer);
|
||||
|
||||
let errStr = await checkIdentityServerUrl(fullUrl);
|
||||
if (!errStr) {
|
||||
|
@ -157,20 +156,49 @@ export default class SetIdServer extends React.Component {
|
|||
const authClient = new IdentityAuthClient(fullUrl);
|
||||
await authClient.getAccessToken();
|
||||
|
||||
let save = true;
|
||||
|
||||
// Double check that the identity server even has terms of service.
|
||||
const terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
|
||||
if (!terms || !terms["policies"] || Object.keys(terms["policies"]).length <= 0) {
|
||||
this._showNoTermsWarning(fullUrl);
|
||||
return;
|
||||
let terms;
|
||||
try {
|
||||
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (e.cors === "rejected" || e.httpStatus === 404) {
|
||||
terms = null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
this._saveIdServer(fullUrl);
|
||||
if (!terms || !terms["policies"] || Object.keys(terms["policies"]).length <= 0) {
|
||||
const [confirmed] = await this._showNoTermsWarning(fullUrl);
|
||||
save = confirmed;
|
||||
}
|
||||
|
||||
// Show a general warning, possibly with details about any bound
|
||||
// 3PIDs that would be left behind.
|
||||
if (save && currentClientIdServer && fullUrl !== currentClientIdServer) {
|
||||
const [confirmed] = await this._showServerChangeWarning({
|
||||
title: _t("Change identity server"),
|
||||
unboundMessage: _t(
|
||||
"Disconnect from the identity server <current /> and " +
|
||||
"connect to <new /> instead?", {},
|
||||
{
|
||||
current: sub => <b>{abbreviateUrl(currentClientIdServer)}</b>,
|
||||
new: sub => <b>{abbreviateUrl(idServer)}</b>,
|
||||
},
|
||||
),
|
||||
button: _t("Continue"),
|
||||
});
|
||||
save = confirmed;
|
||||
}
|
||||
|
||||
if (save) {
|
||||
this._saveIdServer(fullUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (e.cors === "rejected" || e.httpStatus === 404) {
|
||||
this._showNoTermsWarning(fullUrl);
|
||||
return;
|
||||
}
|
||||
errStr = _t("Terms of service not accepted or the identity server is invalid.");
|
||||
}
|
||||
}
|
||||
|
@ -179,13 +207,12 @@ export default class SetIdServer extends React.Component {
|
|||
checking: false,
|
||||
error: errStr,
|
||||
currentClientIdServer: MatrixClientPeg.get().getIdentityServerUrl(),
|
||||
idServer: this.state.idServer,
|
||||
});
|
||||
};
|
||||
|
||||
_showNoTermsWarning(fullUrl) {
|
||||
const QuestionDialog = sdk.getComponent("views.dialogs.QuestionDialog");
|
||||
Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, {
|
||||
const { finished } = Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, {
|
||||
title: _t("Identity server has no terms of service"),
|
||||
description: (
|
||||
<div>
|
||||
|
@ -198,54 +225,67 @@ export default class SetIdServer extends React.Component {
|
|||
</div>
|
||||
),
|
||||
button: _t("Continue"),
|
||||
onFinished: async (confirmed) => {
|
||||
if (!confirmed) return;
|
||||
this._saveIdServer(fullUrl);
|
||||
},
|
||||
});
|
||||
return finished;
|
||||
}
|
||||
|
||||
_onDisconnectClicked = async () => {
|
||||
this.setState({disconnectBusy: true});
|
||||
try {
|
||||
const threepids = await getThreepidBindStatus(MatrixClientPeg.get());
|
||||
|
||||
const boundThreepids = threepids.filter(tp => tp.bound);
|
||||
let message;
|
||||
if (boundThreepids.length) {
|
||||
message = _t(
|
||||
"You are currently sharing email addresses or phone numbers on the identity " +
|
||||
"server <idserver />. You will need to reconnect to <idserver2 /> to stop " +
|
||||
"sharing them.", {},
|
||||
{
|
||||
idserver: sub => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b>,
|
||||
// XXX: https://github.com/vector-im/riot-web/issues/9086
|
||||
idserver2: sub => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b>,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
message = _t(
|
||||
const [confirmed] = await this._showServerChangeWarning({
|
||||
title: _t("Disconnect identity server"),
|
||||
unboundMessage: _t(
|
||||
"Disconnect from the identity server <idserver />?", {},
|
||||
{idserver: sub => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b>},
|
||||
);
|
||||
}
|
||||
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
Modal.createTrackedDialog('Identity Server Disconnect Warning', '', QuestionDialog, {
|
||||
title: _t("Disconnect Identity Server"),
|
||||
description: message,
|
||||
),
|
||||
button: _t("Disconnect"),
|
||||
onFinished: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this._disconnectIdServer();
|
||||
}
|
||||
},
|
||||
});
|
||||
if (confirmed) {
|
||||
this._disconnectIdServer();
|
||||
}
|
||||
} finally {
|
||||
this.setState({disconnectBusy: false});
|
||||
}
|
||||
};
|
||||
|
||||
async _showServerChangeWarning({ title, unboundMessage, button }) {
|
||||
const threepids = await getThreepidsWithBindStatus(MatrixClientPeg.get());
|
||||
|
||||
const boundThreepids = threepids.filter(tp => tp.bound);
|
||||
let message;
|
||||
let danger = false;
|
||||
if (boundThreepids.length) {
|
||||
message = <div>
|
||||
<p>{_t(
|
||||
"You are still <b>sharing your personal data</b> on the identity " +
|
||||
"server <idserver />.", {},
|
||||
{
|
||||
idserver: sub => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b>,
|
||||
b: sub => <b>{sub}</b>,
|
||||
},
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"We recommend that you remove your email addresses and phone numbers " +
|
||||
"from the identity server before disconnecting.",
|
||||
)}</p>
|
||||
</div>;
|
||||
danger = true;
|
||||
button = _t("Disconnect anyway");
|
||||
} else {
|
||||
message = unboundMessage;
|
||||
}
|
||||
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const { finished } = Modal.createTrackedDialog('Identity Server Bound Warning', '', QuestionDialog, {
|
||||
title,
|
||||
description: message,
|
||||
button,
|
||||
cancelButton: _t("Go back"),
|
||||
danger,
|
||||
});
|
||||
return finished;
|
||||
}
|
||||
|
||||
_disconnectIdServer = () => {
|
||||
// Account data change will update localstorage, client, etc through dispatcher
|
||||
MatrixClientPeg.get().setAccountData("m.identity_server", {
|
||||
|
|
|
@ -23,8 +23,8 @@ import Field from "../../elements/Field";
|
|||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import * as Email from "../../../../email";
|
||||
import AddThreepid from "../../../../AddThreepid";
|
||||
const sdk = require('../../../../index');
|
||||
const Modal = require("../../../../Modal");
|
||||
import sdk from '../../../../index';
|
||||
import Modal from '../../../../Modal';
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
|
@ -113,11 +113,15 @@ export class ExistingEmailAddress extends React.Component {
|
|||
}
|
||||
|
||||
export default class EmailAddresses extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
static propTypes = {
|
||||
emails: PropTypes.array.isRequired,
|
||||
onEmailsChange: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
emails: [],
|
||||
verifying: false,
|
||||
addTask: null,
|
||||
continueDisabled: false,
|
||||
|
@ -125,16 +129,9 @@ export default class EmailAddresses extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
client.getThreePids().then((addresses) => {
|
||||
this.setState({emails: addresses.threepids.filter((a) => a.medium === 'email')});
|
||||
});
|
||||
}
|
||||
|
||||
_onRemoved = (address) => {
|
||||
this.setState({emails: this.state.emails.filter((e) => e !== address)});
|
||||
const emails = this.props.emails.filter((e) => e !== address);
|
||||
this.props.onEmailsChange(emails);
|
||||
};
|
||||
|
||||
_onChangeNewEmailAddress = (e) => {
|
||||
|
@ -184,12 +181,16 @@ export default class EmailAddresses extends React.Component {
|
|||
this.state.addTask.checkEmailLinkClicked().then(() => {
|
||||
const email = this.state.newEmailAddress;
|
||||
this.setState({
|
||||
emails: [...this.state.emails, {address: email, medium: "email"}],
|
||||
addTask: null,
|
||||
continueDisabled: false,
|
||||
verifying: false,
|
||||
newEmailAddress: "",
|
||||
});
|
||||
const emails = [
|
||||
...this.props.emails,
|
||||
{ address: email, medium: "email" },
|
||||
];
|
||||
this.props.onEmailsChange(emails);
|
||||
}).catch((err) => {
|
||||
this.setState({continueDisabled: false});
|
||||
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
||||
|
@ -204,7 +205,7 @@ export default class EmailAddresses extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const existingEmailElements = this.state.emails.map((e) => {
|
||||
const existingEmailElements = this.props.emails.map((e) => {
|
||||
return <ExistingEmailAddress email={e} onRemoved={this._onRemoved} key={e.address} />;
|
||||
});
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ import Field from "../../elements/Field";
|
|||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import AddThreepid from "../../../../AddThreepid";
|
||||
import CountryDropdown from "../../auth/CountryDropdown";
|
||||
const sdk = require('../../../../index');
|
||||
const Modal = require("../../../../Modal");
|
||||
import sdk from '../../../../index';
|
||||
import Modal from '../../../../Modal';
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
|
@ -108,11 +108,15 @@ export class ExistingPhoneNumber extends React.Component {
|
|||
}
|
||||
|
||||
export default class PhoneNumbers extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
static propTypes = {
|
||||
msisdns: PropTypes.array.isRequired,
|
||||
onMsisdnsChange: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
msisdns: [],
|
||||
verifying: false,
|
||||
verifyError: false,
|
||||
verifyMsisdn: "",
|
||||
|
@ -124,16 +128,9 @@ export default class PhoneNumbers extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillMount(): void {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
client.getThreePids().then((addresses) => {
|
||||
this.setState({msisdns: addresses.threepids.filter((a) => a.medium === 'msisdn')});
|
||||
});
|
||||
}
|
||||
|
||||
_onRemoved = (address) => {
|
||||
this.setState({msisdns: this.state.msisdns.filter((e) => e !== address)});
|
||||
const msisdns = this.props.msisdns.filter((e) => e !== address);
|
||||
this.props.onMsisdnsChange(msisdns);
|
||||
};
|
||||
|
||||
_onChangeNewPhoneNumber = (e) => {
|
||||
|
@ -181,7 +178,6 @@ export default class PhoneNumbers extends React.Component {
|
|||
const token = this.state.newPhoneNumberCode;
|
||||
this.state.addTask.haveMsisdnToken(token).then(() => {
|
||||
this.setState({
|
||||
msisdns: [...this.state.msisdns, {address: this.state.verifyMsisdn, medium: "msisdn"}],
|
||||
addTask: null,
|
||||
continueDisabled: false,
|
||||
verifying: false,
|
||||
|
@ -190,6 +186,11 @@ export default class PhoneNumbers extends React.Component {
|
|||
newPhoneNumber: "",
|
||||
newPhoneNumberCode: "",
|
||||
});
|
||||
const msisdns = [
|
||||
...this.props.msisdns,
|
||||
{ address: this.state.verifyMsisdn, medium: "msisdn" },
|
||||
];
|
||||
this.props.onMsisdnsChange(msisdns);
|
||||
}).catch((err) => {
|
||||
this.setState({continueDisabled: false});
|
||||
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
||||
|
@ -210,7 +211,7 @@ export default class PhoneNumbers extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const existingPhoneElements = this.state.msisdns.map((p) => {
|
||||
const existingPhoneElements = this.props.msisdns.map((p) => {
|
||||
return <ExistingPhoneNumber msisdn={p} onRemoved={this._onRemoved} key={p.address} />;
|
||||
});
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import MatrixClientPeg from "../../../../MatrixClientPeg";
|
|||
import sdk from '../../../../index';
|
||||
import Modal from '../../../../Modal';
|
||||
import AddThreepid from '../../../../AddThreepid';
|
||||
import { getThreepidBindStatus } from '../../../../boundThreepids';
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
|
@ -59,6 +58,11 @@ export class EmailAddress extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { bound } = nextProps.email;
|
||||
this.setState({ bound });
|
||||
}
|
||||
|
||||
async changeBinding({ bind, label, errorTitle }) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const { medium, address } = this.props.email;
|
||||
|
@ -187,27 +191,14 @@ export class EmailAddress extends React.Component {
|
|||
}
|
||||
|
||||
export default class EmailAddresses extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
loaded: false,
|
||||
emails: [],
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
const emails = await getThreepidBindStatus(client, 'email');
|
||||
|
||||
this.setState({ emails });
|
||||
static propTypes = {
|
||||
emails: PropTypes.array.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
if (this.state.emails.length > 0) {
|
||||
content = this.state.emails.map((e) => {
|
||||
if (this.props.emails.length > 0) {
|
||||
content = this.props.emails.map((e) => {
|
||||
return <EmailAddress email={e} key={e.address} />;
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -23,7 +23,6 @@ import MatrixClientPeg from "../../../../MatrixClientPeg";
|
|||
import sdk from '../../../../index';
|
||||
import Modal from '../../../../Modal';
|
||||
import AddThreepid from '../../../../AddThreepid';
|
||||
import { getThreepidBindStatus } from '../../../../boundThreepids';
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
|
@ -51,6 +50,11 @@ export class PhoneNumber extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { bound } = nextProps.msisdn;
|
||||
this.setState({ bound });
|
||||
}
|
||||
|
||||
async changeBinding({ bind, label, errorTitle }) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const { medium, address } = this.props.msisdn;
|
||||
|
@ -206,27 +210,14 @@ export class PhoneNumber extends React.Component {
|
|||
}
|
||||
|
||||
export default class PhoneNumbers extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
loaded: false,
|
||||
msisdns: [],
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
const msisdns = await getThreepidBindStatus(client, 'msisdn');
|
||||
|
||||
this.setState({ msisdns });
|
||||
static propTypes = {
|
||||
msisdns: PropTypes.array.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
if (this.state.msisdns.length > 0) {
|
||||
content = this.state.msisdns.map((e) => {
|
||||
if (this.props.msisdns.length > 0) {
|
||||
content = this.props.msisdns.map((e) => {
|
||||
return <PhoneNumber msisdn={e} key={e.address} />;
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -148,7 +148,18 @@ export default class RolesRoomSettingsTab extends React.Component {
|
|||
parentObj[keyPath[keyPath.length - 1]] = value;
|
||||
}
|
||||
|
||||
client.sendStateEvent(this.props.roomId, "m.room.power_levels", plContent);
|
||||
client.sendStateEvent(this.props.roomId, "m.room.power_levels", plContent).catch(e => {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Power level requirement change failed', '', ErrorDialog, {
|
||||
title: _t('Error changing power level requirement'),
|
||||
description: _t(
|
||||
"An error occurred changing the room's power level requirements. Ensure you have sufficient " +
|
||||
"permissions and try again.",
|
||||
),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
_onUserPowerLevelChanged = (value, powerLevelKey) => {
|
||||
|
|
|
@ -37,6 +37,7 @@ import {Service, startTermsFlow} from "../../../../../Terms";
|
|||
import {SERVICE_TYPES} from "matrix-js-sdk";
|
||||
import IdentityAuthClient from "../../../../../IdentityAuthClient";
|
||||
import {abbreviateUrl} from "../../../../../utils/UrlUtils";
|
||||
import { getThreepidsWithBindStatus } from '../../../../../boundThreepids';
|
||||
|
||||
export default class GeneralUserSettingsTab extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -58,17 +59,20 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
// agreedUrls, // From the startTermsFlow callback
|
||||
// resolve, // Promise resolve function for startTermsFlow callback
|
||||
},
|
||||
emails: [],
|
||||
msisdns: [],
|
||||
};
|
||||
|
||||
this.dispatcherRef = dis.register(this._onAction);
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
const serverRequiresIdServer = await MatrixClientPeg.get().doesServerRequireIdServerParam();
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
const serverRequiresIdServer = await cli.doesServerRequireIdServerParam();
|
||||
this.setState({serverRequiresIdServer});
|
||||
|
||||
// Check to see if terms need accepting
|
||||
this._checkTerms();
|
||||
this._getThreepidState();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -78,10 +82,31 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
_onAction = (payload) => {
|
||||
if (payload.action === 'id_server_changed') {
|
||||
this.setState({haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl())});
|
||||
this._checkTerms();
|
||||
this._getThreepidState();
|
||||
}
|
||||
};
|
||||
|
||||
_onEmailsChange = (emails) => {
|
||||
this.setState({ emails });
|
||||
}
|
||||
|
||||
_onMsisdnsChange = (msisdns) => {
|
||||
this.setState({ msisdns });
|
||||
}
|
||||
|
||||
async _getThreepidState() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
// Check to see if terms need accepting
|
||||
this._checkTerms();
|
||||
|
||||
// Need to get 3PIDs generally for Account section and possibly also for
|
||||
// Discovery (assuming we have an IS and terms are agreed).
|
||||
const threepids = await getThreepidsWithBindStatus(cli);
|
||||
this.setState({ emails: threepids.filter((a) => a.medium === 'email') });
|
||||
this.setState({ msisdns: threepids.filter((a) => a.medium === 'msisdn') });
|
||||
}
|
||||
|
||||
async _checkTerms() {
|
||||
if (!this.state.haveIdServer) {
|
||||
this.setState({idServerHasUnsignedTerms: false});
|
||||
|
@ -91,7 +116,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
// By starting the terms flow we get the logic for checking which terms the user has signed
|
||||
// for free. So we might as well use that for our own purposes.
|
||||
const authClient = new IdentityAuthClient();
|
||||
const idAccessToken = await authClient.getAccessToken(/*check=*/false);
|
||||
const idAccessToken = await authClient.getAccessToken({ check: false });
|
||||
startTermsFlow([new Service(
|
||||
SERVICE_TYPES.IS,
|
||||
MatrixClientPeg.get().getIdentityServerUrl(),
|
||||
|
@ -200,10 +225,16 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
if (this.state.haveIdServer || this.state.serverRequiresIdServer === false) {
|
||||
threepidSection = <div>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>
|
||||
<EmailAddresses />
|
||||
<EmailAddresses
|
||||
emails={this.state.emails}
|
||||
onEmailsChange={this._onEmailsChange}
|
||||
/>
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Phone numbers")}</span>
|
||||
<PhoneNumbers />
|
||||
<PhoneNumbers
|
||||
msisdns={this.state.msisdns}
|
||||
onMsisdnsChange={this._onMsisdnsChange}
|
||||
/>
|
||||
</div>;
|
||||
} else if (this.state.serverRequiresIdServer === null) {
|
||||
threepidSection = <Spinner />;
|
||||
|
@ -279,10 +310,10 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
|
||||
const threepidSection = this.state.haveIdServer ? <div className='mx_GeneralUserSettingsTab_discovery'>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Email addresses")}</span>
|
||||
<EmailAddresses />
|
||||
<EmailAddresses emails={this.state.emails} />
|
||||
|
||||
<span className="mx_SettingsTab_subheading">{_t("Phone numbers")}</span>
|
||||
<PhoneNumbers />
|
||||
<PhoneNumbers msisdns={this.state.msisdns} />
|
||||
</div> : null;
|
||||
|
||||
return (
|
||||
|
|
|
@ -54,6 +54,7 @@ export default class LabsUserSettingsTab extends React.Component {
|
|||
<SettingsFlag name={"enableWidgetScreenshots"} level={SettingLevel.ACCOUNT} />
|
||||
<SettingsFlag name={"showHiddenEventsInTimeline"} level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name={"lowBandwidth"} level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name={"sendReadReceipts"} level={SettingLevel.ACCOUNT} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue