From 2494af37c862144742c34780e85464366113090f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 19:08:40 +0100 Subject: [PATCH 1/3] Break UserInfo:RoomAdminToolsContainer into smaller components Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 452 ++++++++++--------- 1 file changed, 234 insertions(+), 218 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 59cd61a583..292cf87f47 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -374,6 +374,230 @@ const useRoomPowerLevels = (room) => { return powerLevels; }; +const RoomKickButton = withLegacyMatrixClient(({cli, member, startUpdating, stopUpdating}) => { + const onKick = async () => { + const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); + const {finished} = Modal.createTrackedDialog( + 'Confirm User Action Dialog', + 'onKick', + ConfirmUserActionDialog, + { + member, + action: member.membership === "invite" ? _t("Disinvite") : _t("Kick"), + title: member.membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"), + askReason: member.membership === "join", + danger: true, + }, + ); + + const [proceed, reason] = await finished; + if (!proceed) return; + + startUpdating(); + cli.kick(member.roomId, member.userId, reason || undefined).then(() => { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Kick success"); + }, function(err) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Kick error: " + err); + Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, { + title: _t("Failed to kick"), + description: ((err && err.message) ? err.message : "Operation failed"), + }); + }).finally(() => { + stopUpdating(); + }); + }; + + const kickLabel = member.membership === "invite" ? _t("Disinvite") : _t("Kick"); + return + { kickLabel } + ; +}); + +const RedactMessagesButton = withLegacyMatrixClient(({cli, member}) => { + const onRedactAllMessages = async () => { + const {roomId, userId} = member; + const room = cli.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 = 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: +
+

{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }

+
, + }); + } else { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + + const {finished} = Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, { + title: _t("Remove recent messages by %(user)s", {user}), + description: +
+

{ _t("You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", {count, user}) }

+

{ _t("For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.") }

+
, + button: _t("Remove %(count)s messages", {count}), + }); + + const [confirmed] = await finished; + 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 cli.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}`); + } + }; + + return + { _t("Remove recent messages") } + ; +}); + +const BanToggleButton = withLegacyMatrixClient(({cli, member, startUpdating, stopUpdating}) => { + const onBanOrUnban = async () => { + const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); + const {finished} = Modal.createTrackedDialog( + 'Confirm User Action Dialog', + 'onBanOrUnban', + ConfirmUserActionDialog, + { + member, + action: member.membership === 'ban' ? _t("Unban") : _t("Ban"), + title: member.membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"), + askReason: member.membership !== 'ban', + danger: member.membership !== 'ban', + }, + ); + + const [proceed, reason] = await finished; + if (!proceed) return; + + startUpdating(); + let promise; + if (member.membership === 'ban') { + promise = cli.unban(member.roomId, member.userId); + } else { + promise = cli.ban(member.roomId, member.userId, reason || undefined); + } + promise.then(() => { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Ban success"); + }, function(err) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Ban error: " + err); + Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, { + title: _t("Error"), + description: _t("Failed to ban user"), + }); + }).finally(() => { + stopUpdating(); + }); + }; + + let label = _t("Ban"); + if (member.membership === 'ban') { + label = _t("Unban"); + } + + return + { label } + ; +}); + +const MuteToggleButton = withLegacyMatrixClient(({cli, member, room, powerLevels, startUpdating, stopUpdating}) => { + const isMuted = _isMuted(member, powerLevels); + const onMuteToggle = async () => { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const roomId = member.roomId; + const target = member.userId; + + // if muting self, warn as it may be irreversible + if (target === cli.getUserId()) { + try { + if (!(await _warnSelfDemote())) return; + } catch (e) { + console.error("Failed to warn about self demotion: ", e); + return; + } + } + + const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); + if (!powerLevelEvent) return; + + const powerLevels = powerLevelEvent.getContent(); + const levelToSend = ( + (powerLevels.events ? powerLevels.events["m.room.message"] : null) || + powerLevels.events_default + ); + let level; + if (isMuted) { // unmute + level = levelToSend; + } else { // mute + level = levelToSend - 1; + } + level = parseInt(level); + + if (!isNaN(level)) { + startUpdating(); + cli.setPowerLevel(roomId, target, level, powerLevelEvent).then(() => { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Mute toggle success"); + }, function(err) { + console.error("Mute error: " + err); + Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { + title: _t("Error"), + description: _t("Failed to mute user"), + }); + }).finally(() => { + stopUpdating(); + }); + } + }; + + const muteLabel = isMuted ? _t("Unmute") : _t("Mute"); + return + { muteLabel } + ; +}); + const RoomAdminToolsContainer = withLegacyMatrixClient(({cli, room, children, member, startUpdating, stopUpdating}) => { let kickButton; let banButton; @@ -389,235 +613,27 @@ const RoomAdminToolsContainer = withLegacyMatrixClient(({cli, room, children, me const me = room.getMember(cli.getUserId()); const isMe = me.userId === member.userId; const canAffectUser = member.powerLevel < me.powerLevel || isMe; - const membership = member.membership; if (canAffectUser && me.powerLevel >= powerLevels.kick) { - const onKick = async () => { - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - const {finished} = Modal.createTrackedDialog( - 'Confirm User Action Dialog', - 'onKick', - ConfirmUserActionDialog, - { - member, - action: membership === "invite" ? _t("Disinvite") : _t("Kick"), - title: membership === "invite" ? _t("Disinvite this user?") : _t("Kick this user?"), - askReason: membership === "join", - danger: true, - }, - ); - - const [proceed, reason] = await finished; - if (!proceed) return; - - startUpdating(); - cli.kick(member.roomId, member.userId, reason || undefined).then(() => { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Kick success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Kick error: " + err); - Modal.createTrackedDialog('Failed to kick', '', ErrorDialog, { - title: _t("Failed to kick"), - description: ((err && err.message) ? err.message : "Operation failed"), - }); - }).finally(() => { - stopUpdating(); - }); - }; - - const kickLabel = membership === "invite" ? _t("Disinvite") : _t("Kick"); - kickButton = ( - - { kickLabel } - - ); + kickButton = ; } if (me.powerLevel >= powerLevels.redact) { - const onRedactAllMessages = async () => { - const {roomId, userId} = member; - const room = cli.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 = 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: -
-

{ _t("Try scrolling up in the timeline to see if there are any earlier ones.") }

-
, - }); - } else { - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - - const {finished} = Modal.createTrackedDialog('Remove recent messages by user', '', QuestionDialog, { - title: _t("Remove recent messages by %(user)s", {user}), - description: -
-

{ _t("You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?", {count, user}) }

-

{ _t("For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.") }

-
, - button: _t("Remove %(count)s messages", {count}), - }); - - const [confirmed] = await finished; - 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 cli.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}`); - } - }; - redactButton = ( - - { _t("Remove recent messages") } - + ); } if (canAffectUser && me.powerLevel >= powerLevels.ban) { - const onBanOrUnban = async () => { - const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); - const {finished} = Modal.createTrackedDialog( - 'Confirm User Action Dialog', - 'onBanOrUnban', - ConfirmUserActionDialog, - { - member, - action: membership === 'ban' ? _t("Unban") : _t("Ban"), - title: membership === 'ban' ? _t("Unban this user?") : _t("Ban this user?"), - askReason: membership !== 'ban', - danger: membership !== 'ban', - }, - ); - - const [proceed, reason] = await finished; - if (!proceed) return; - - startUpdating(); - let promise; - if (membership === 'ban') { - promise = cli.unban(member.roomId, member.userId); - } else { - promise = cli.ban(member.roomId, member.userId, reason || undefined); - } - promise.then(() => { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Ban success"); - }, function(err) { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Ban error: " + err); - Modal.createTrackedDialog('Failed to ban user', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to ban user"), - }); - }).finally(() => { - stopUpdating(); - }); - }; - - let label = _t("Ban"); - if (membership === 'ban') { - label = _t("Unban"); - } - banButton = ( - - { label } - - ); + banButton = ; } if (canAffectUser && me.powerLevel >= editPowerLevel) { - const isMuted = _isMuted(member, powerLevels); - const onMuteToggle = async () => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - const roomId = member.roomId; - const target = member.userId; - - // if muting self, warn as it may be irreversible - if (target === cli.getUserId()) { - try { - if (!(await _warnSelfDemote())) return; - } catch (e) { - console.error("Failed to warn about self demotion: ", e); - return; - } - } - - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; - - const powerLevels = powerLevelEvent.getContent(); - const levelToSend = ( - (powerLevels.events ? powerLevels.events["m.room.message"] : null) || - powerLevels.events_default - ); - let level; - if (isMuted) { // unmute - level = levelToSend; - } else { // mute - level = levelToSend - 1; - } - level = parseInt(level); - - if (!isNaN(level)) { - startUpdating(); - cli.setPowerLevel(roomId, target, level, powerLevelEvent).then(() => { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Mute toggle success"); - }, function(err) { - console.error("Mute error: " + err); - Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to mute user"), - }); - }).finally(() => { - stopUpdating(); - }); - } - }; - - const muteLabel = isMuted ? _t("Unmute") : _t("Mute"); muteButton = ( - - { muteLabel } - + ); } From 93429d7c2ee515872fc18c653c8cdae0636511cc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 19:13:37 +0100 Subject: [PATCH 2/3] Break withLegacyMatrixClient into a util module Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 35 ++++++++------------ src/utils/withLegacyMatrixClient.js | 31 +++++++++++++++++ 2 files changed, 44 insertions(+), 22 deletions(-) create mode 100644 src/utils/withLegacyMatrixClient.js diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 292cf87f47..e5ac7d4665 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -21,7 +21,7 @@ import React, {useCallback, useMemo, useState, useEffect} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import useEventListener from '@use-it/event-listener'; -import {Group, MatrixClient, RoomMember, User} from 'matrix-js-sdk'; +import {Group, RoomMember, User} from 'matrix-js-sdk'; import dis from '../../../dispatcher'; import Modal from '../../../Modal'; import sdk from '../../../index'; @@ -39,6 +39,7 @@ import MultiInviter from "../../../utils/MultiInviter"; import GroupStore from "../../../stores/GroupStore"; import MatrixClientPeg from "../../../MatrixClientPeg"; import E2EIcon from "../rooms/E2EIcon"; +import withLegacyMatrixClient from "../../../utils/withLegacyMatrixClient"; const _disambiguateDevices = (devices) => { const names = Object.create(null); @@ -57,22 +58,12 @@ const _disambiguateDevices = (devices) => { } }; -const withLegacyMatrixClient = (Component) => class extends React.PureComponent { - static contextTypes = { - matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, - }; - - render() { - return ; - } -}; - const _getE2EStatus = (devices) => { const hasUnverifiedDevice = devices.some((device) => device.isUnverified()); return hasUnverifiedDevice ? "warning" : "verified"; }; -const DevicesSection = withLegacyMatrixClient(({devices, userId, loading}) => { +const DevicesSection = ({devices, userId, loading}) => { const MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo'); const Spinner = sdk.getComponent("elements.Spinner"); @@ -95,7 +86,7 @@ const DevicesSection = withLegacyMatrixClient(({devices, userId, loading}) => { ); -}); +}; const onRoomTileClick = (roomId) => { dis.dispatch({ @@ -104,7 +95,7 @@ const onRoomTileClick = (roomId) => { }); }; -const DirectChatsSection = withLegacyMatrixClient(({cli, userId, startUpdating, stopUpdating}) => { +const DirectChatsSection = withLegacyMatrixClient(({matrixClient: cli, userId, startUpdating, stopUpdating}) => { const onNewDMClick = async () => { startUpdating(); await createRoom({dmUserId: userId}); @@ -195,7 +186,7 @@ const DirectChatsSection = withLegacyMatrixClient(({cli, userId, startUpdating, ); }); -const UserOptionsSection = withLegacyMatrixClient(({cli, member, isIgnored, canInvite}) => { +const UserOptionsSection = withLegacyMatrixClient(({matrixClient: cli, member, isIgnored, canInvite}) => { let ignoreButton = null; let insertPillButton = null; let inviteUserButton = null; @@ -374,7 +365,7 @@ const useRoomPowerLevels = (room) => { return powerLevels; }; -const RoomKickButton = withLegacyMatrixClient(({cli, member, startUpdating, stopUpdating}) => { +const RoomKickButton = withLegacyMatrixClient(({matrixClient: cli, member, startUpdating, stopUpdating}) => { const onKick = async () => { const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); const {finished} = Modal.createTrackedDialog( @@ -416,7 +407,7 @@ const RoomKickButton = withLegacyMatrixClient(({cli, member, startUpdating, stop ; }); -const RedactMessagesButton = withLegacyMatrixClient(({cli, member}) => { +const RedactMessagesButton = withLegacyMatrixClient(({matrixClient: cli, member}) => { const onRedactAllMessages = async () => { const {roomId, userId} = member; const room = cli.getRoom(roomId); @@ -489,7 +480,7 @@ const RedactMessagesButton = withLegacyMatrixClient(({cli, member}) => { ; }); -const BanToggleButton = withLegacyMatrixClient(({cli, member, startUpdating, stopUpdating}) => { +const BanToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, startUpdating, stopUpdating}) => { const onBanOrUnban = async () => { const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog"); const {finished} = Modal.createTrackedDialog( @@ -541,7 +532,7 @@ const BanToggleButton = withLegacyMatrixClient(({cli, member, startUpdating, sto ; }); -const MuteToggleButton = withLegacyMatrixClient(({cli, member, room, powerLevels, startUpdating, stopUpdating}) => { +const MuteToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, room, powerLevels, startUpdating, stopUpdating}) => { const isMuted = _isMuted(member, powerLevels); const onMuteToggle = async () => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -598,7 +589,7 @@ const MuteToggleButton = withLegacyMatrixClient(({cli, member, room, powerLevels ; }); -const RoomAdminToolsContainer = withLegacyMatrixClient(({cli, room, children, member, startUpdating, stopUpdating}) => { +const RoomAdminToolsContainer = withLegacyMatrixClient(({matrixClient: cli, room, children, member, startUpdating, stopUpdating}) => { let kickButton; let banButton; let muteButton; @@ -651,7 +642,7 @@ const RoomAdminToolsContainer = withLegacyMatrixClient(({cli, room, children, me }); const GroupAdminToolsSection = withLegacyMatrixClient( - ({cli, children, groupId, groupMember, startUpdating, stopUpdating}) => { + ({matrixClient: cli, children, groupId, groupMember, startUpdating, stopUpdating}) => { const [isPrivileged, setIsPrivileged] = useState(false); const [isInvited, setIsInvited] = useState(false); @@ -753,7 +744,7 @@ const useIsSynapseAdmin = (cli) => { }; // cli is injected by withLegacyMatrixClient -const UserInfo = withLegacyMatrixClient(({cli, user, groupId, roomId, onClose}) => { +const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, roomId, onClose}) => { // Load room if we are given a room id and memoize it const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]); diff --git a/src/utils/withLegacyMatrixClient.js b/src/utils/withLegacyMatrixClient.js new file mode 100644 index 0000000000..af6a930a88 --- /dev/null +++ b/src/utils/withLegacyMatrixClient.js @@ -0,0 +1,31 @@ +/* +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 PropTypes from "prop-types"; +import {MatrixClient} from "matrix-js-sdk"; + +// Higher Order Component to allow use of legacy MatrixClient React Context +// in Functional Components which do not otherwise support legacy React Contexts +export default (Component) => class extends React.PureComponent { + static contextTypes = { + matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, + }; + + render() { + return ; + } +}; From 7e4d429fa3b7918a0f40fd9e3379ebb9074315a4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 19:18:14 +0100 Subject: [PATCH 3/3] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/right_panel/UserInfo.js | 202 ++++++++++--------- 1 file changed, 103 insertions(+), 99 deletions(-) diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index e5ac7d4665..3abe97dd75 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -532,114 +532,118 @@ const BanToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, star ; }); -const MuteToggleButton = withLegacyMatrixClient(({matrixClient: cli, member, room, powerLevels, startUpdating, stopUpdating}) => { - const isMuted = _isMuted(member, powerLevels); - const onMuteToggle = async () => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - const roomId = member.roomId; - const target = member.userId; +const MuteToggleButton = withLegacyMatrixClient( + ({matrixClient: cli, member, room, powerLevels, startUpdating, stopUpdating}) => { + const isMuted = _isMuted(member, powerLevels); + const onMuteToggle = async () => { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const roomId = member.roomId; + const target = member.userId; - // if muting self, warn as it may be irreversible - if (target === cli.getUserId()) { - try { - if (!(await _warnSelfDemote())) return; - } catch (e) { - console.error("Failed to warn about self demotion: ", e); - return; + // if muting self, warn as it may be irreversible + if (target === cli.getUserId()) { + try { + if (!(await _warnSelfDemote())) return; + } catch (e) { + console.error("Failed to warn about self demotion: ", e); + return; + } } - } - const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); - if (!powerLevelEvent) return; + const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", ""); + if (!powerLevelEvent) return; - const powerLevels = powerLevelEvent.getContent(); - const levelToSend = ( - (powerLevels.events ? powerLevels.events["m.room.message"] : null) || - powerLevels.events_default - ); - let level; - if (isMuted) { // unmute - level = levelToSend; - } else { // mute - level = levelToSend - 1; - } - level = parseInt(level); + const powerLevels = powerLevelEvent.getContent(); + const levelToSend = ( + (powerLevels.events ? powerLevels.events["m.room.message"] : null) || + powerLevels.events_default + ); + let level; + if (isMuted) { // unmute + level = levelToSend; + } else { // mute + level = levelToSend - 1; + } + level = parseInt(level); - if (!isNaN(level)) { - startUpdating(); - cli.setPowerLevel(roomId, target, level, powerLevelEvent).then(() => { - // NO-OP; rely on the m.room.member event coming down else we could - // get out of sync if we force setState here! - console.log("Mute toggle success"); - }, function(err) { - console.error("Mute error: " + err); - Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { - title: _t("Error"), - description: _t("Failed to mute user"), + if (!isNaN(level)) { + startUpdating(); + cli.setPowerLevel(roomId, target, level, powerLevelEvent).then(() => { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Mute toggle success"); + }, function(err) { + console.error("Mute error: " + err); + Modal.createTrackedDialog('Failed to mute user', '', ErrorDialog, { + title: _t("Error"), + description: _t("Failed to mute user"), + }); + }).finally(() => { + stopUpdating(); }); - }).finally(() => { - stopUpdating(); - }); + } + }; + + const muteLabel = isMuted ? _t("Unmute") : _t("Mute"); + return + { muteLabel } + ; + }, +); + +const RoomAdminToolsContainer = withLegacyMatrixClient( + ({matrixClient: cli, room, children, member, startUpdating, stopUpdating}) => { + let kickButton; + let banButton; + let muteButton; + let redactButton; + + const powerLevels = useRoomPowerLevels(room); + const editPowerLevel = ( + (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || + powerLevels.state_default + ); + + const me = room.getMember(cli.getUserId()); + const isMe = me.userId === member.userId; + const canAffectUser = member.powerLevel < me.powerLevel || isMe; + + if (canAffectUser && me.powerLevel >= powerLevels.kick) { + kickButton = ; + } + if (me.powerLevel >= powerLevels.redact) { + redactButton = ( + + ); + } + if (canAffectUser && me.powerLevel >= powerLevels.ban) { + banButton = ; + } + if (canAffectUser && me.powerLevel >= editPowerLevel) { + muteButton = ( + + ); } - }; - const muteLabel = isMuted ? _t("Unmute") : _t("Mute"); - return - { muteLabel } - ; -}); + if (kickButton || banButton || muteButton || redactButton || children) { + return + { muteButton } + { kickButton } + { banButton } + { redactButton } + { children } + ; + } -const RoomAdminToolsContainer = withLegacyMatrixClient(({matrixClient: cli, room, children, member, startUpdating, stopUpdating}) => { - let kickButton; - let banButton; - let muteButton; - let redactButton; - - const powerLevels = useRoomPowerLevels(room); - const editPowerLevel = ( - (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || - powerLevels.state_default - ); - - const me = room.getMember(cli.getUserId()); - const isMe = me.userId === member.userId; - const canAffectUser = member.powerLevel < me.powerLevel || isMe; - - if (canAffectUser && me.powerLevel >= powerLevels.kick) { - kickButton = ; - } - if (me.powerLevel >= powerLevels.redact) { - redactButton = ( - - ); - } - if (canAffectUser && me.powerLevel >= powerLevels.ban) { - banButton = ; - } - if (canAffectUser && me.powerLevel >= editPowerLevel) { - muteButton = ( - - ); - } - - if (kickButton || banButton || muteButton || redactButton || children) { - return - { muteButton } - { kickButton } - { banButton } - { redactButton } - { children } - ; - } - - return
; -}); + return
; + }, +); const GroupAdminToolsSection = withLegacyMatrixClient( ({matrixClient: cli, children, groupId, groupMember, startUpdating, stopUpdating}) => {