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 } - + ); }