Add kick,ban,make admin and make moderator operations

This commit is contained in:
N-Pex 2023-01-22 14:41:35 +01:00
parent e5bf7f4f0c
commit 11e544b1c5
23 changed files with 281 additions and 27 deletions

View file

@ -863,6 +863,12 @@ $admin-fg: white;
margin-left: 6px; margin-left: 6px;
} }
.member .user-power {
margin-left: 6px;
color: #aaa;
font-size: 0.8rem;
}
.member .start-private-chat { .member .start-private-chat {
margin-left: 38px; margin-left: 38px;
} }

View file

@ -26,7 +26,12 @@
"undo": "Undo", "undo": "Undo",
"join": "Join", "join": "Join",
"ignore": "Ignore", "ignore": "Ignore",
"loading": "Loading {appName}" "loading": "Loading {appName}",
"user_kick": "Kick this user",
"user_kick_and_ban": "Kick and ban this user",
"user_make_admin": "Make administrator",
"user_make_moderator": "Make moderator",
"user_revoke_moderator": "Revoke moderator"
}, },
"message": { "message": {
"you": "You", "you": "You",
@ -37,6 +42,12 @@
"user_changed_room_avatar": "{user} changed the room avatar", "user_changed_room_avatar": "{user} changed the room avatar",
"user_encrypted_room": "{user} made the room encrypted", "user_encrypted_room": "{user} made the room encrypted",
"user_was_invited": "{user} was invited to the chat...", "user_was_invited": "{user} was invited to the chat...",
"user_was_kicked": "{user} was kicked from the chat.",
"user_was_kicked_by_you": "You kicked {user} from the chat.",
"user_was_kicked_you": "You were kicked from the chat.",
"user_was_banned": "{user} was kicked and banned from the chat.",
"user_was_banned_by_you": "You kicked and banned {user} from the chat.",
"user_was_banned_you": "You were kicked and banned from the chat.",
"user_joined": "{user} joined the chat", "user_joined": "{user} joined the chat",
"user_left": "{user} left the chat", "user_left": "{user} left the chat",
"user_said": "{user} said:", "user_said": "{user} said:",
@ -229,7 +240,9 @@
"leave_room": "Leave", "leave_room": "Leave",
"version_info": "Powered by Guardian Project. Version: {version}", "version_info": "Powered by Guardian Project. Version: {version}",
"scan_code": "Scan to join the room", "scan_code": "Scan to join the room",
"export_room": "Export chat" "export_room": "Export chat",
"user_admin": "Administrator",
"user_moderator": "Moderator"
}, },
"room_info_sheet": { "room_info_sheet": {
"this_room": "This room", "this_room": "This room",

View file

@ -195,7 +195,22 @@
}) })
}} }}
</span> </span>
<span v-if="isAdmin(member)" class="user-power">
{{ $t("room_info.user_admin") }}
</span>
<span v-else-if="isModerator(member)" class="user-power">
{{ $t("room_info.user_moderator") }}
</span>
<div v-if="member.userId != $matrix.currentUserId && !$matrix.isDirectRoomWith(room, member.userId) && expandedMembers.includes(member)" class="start-private-chat clickable" @click="startPrivateChat(member.userId)">{{ $t("menu.start_private_chat") }}</div> <div v-if="member.userId != $matrix.currentUserId && !$matrix.isDirectRoomWith(room, member.userId) && expandedMembers.includes(member)" class="start-private-chat clickable" @click="startPrivateChat(member.userId)">{{ $t("menu.start_private_chat") }}</div>
<div v-if="canKickUser(member) || canBanUser(member)">
<div v-if="member.userId != $matrix.currentUserId && expandedMembers.includes(member)" class="mt-2">{{ String.fromCharCode(160) }}</div>
<div v-if="member.userId != $matrix.currentUserId && expandedMembers.includes(member) && canKickUser(member)" class="start-private-chat clickable" @click="kickUser(member)">{{ $t("menu.user_kick") }}</div>
<div v-if="member.userId != $matrix.currentUserId && expandedMembers.includes(member) && canBanUser(member)" class="start-private-chat clickable" @click="banUser(member)">{{ $t("menu.user_kick_and_ban") }}</div>
</div>
<div v-if="member.userId != $matrix.currentUserId && expandedMembers.includes(member)" class="mt-2">{{ String.fromCharCode(160) }}</div>
<div v-if="member.userId != $matrix.currentUserId && expandedMembers.includes(member) && !isAdmin(member) && canMakeAdmin(member)" class="start-private-chat clickable" @click="makeAdmin(member)">{{ $t("menu.user_make_admin") }}</div>
<div v-if="member.userId != $matrix.currentUserId && expandedMembers.includes(member) && !isModerator(member) && !isAdmin(member) && canMakeModerator(member)" class="start-private-chat clickable" @click="makeModerator(member)">{{ $t("menu.user_make_moderator") }}</div>
<div v-if="member.userId != $matrix.currentUserId && expandedMembers.includes(member) && isModerator(member) && canRevokeModerator(member)" class="start-private-chat clickable" @click="revokeModerator(member)">{{ $t("menu.user_revoke_moderator") }}</div>
<DeviceList <DeviceList
v-if="expandedMembers.includes(member)" v-if="expandedMembers.includes(member)"
:member="member" :member="member"
@ -528,6 +543,91 @@ export default {
if (this.room) { if (this.room) {
this.exporting = true; this.exporting = true;
} }
},
canKickUser(member) {
if (this.room) {
const myUserId = this.$matrix.currentUserId;
const me = this.room.getMember(myUserId);
return me && me.powerLevel > member.powerLevel && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("kick", me.powerLevel);
}
return false;
},
canBanUser(member) {
if (this.room) {
const myUserId = this.$matrix.currentUserId;
const me = this.room.getMember(myUserId);
return me && me.powerLevelNorm > member.powerLevelNorm && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("ban", me.powerLevel);
}
return false;
},
// TODO - following power level comparisons assume that default power levels are used in the room!
isAdmin(member) {
return member.powerLevelNorm > 50;
},
isModerator(member) {
return member.powerLevelNorm > 0 && member.powerLevelNorm <= 50;
},
/**
* Return true if WE can make the member an admin
* @param member
*/
canMakeAdmin(ignoredmember) {
if (this.room) {
const myUserId = this.$matrix.currentUserId;
const me = this.room.getMember(myUserId);
return me && this.isAdmin(me);
}
return false;
},
/**
* Return true if WE can make the member a moderator
* @param member
*/
canMakeModerator(ignoredmember) {
if (this.room) {
const myUserId = this.$matrix.currentUserId;
const me = this.room.getMember(myUserId);
return me && this.isAdmin(me);
}
return false;
},
/**
* Return true if WE can "unmake" the member a moderator
* @param member
*/
canRevokeModerator(member) {
if (this.room) {
const myUserId = this.$matrix.currentUserId;
const me = this.room.getMember(myUserId);
return me && this.isAdmin(me) && me.powerLevel > member.powerLevel;
}
return false;
},
makeAdmin(member) {
if (this.room) {
this.$matrix.makeAdmin(this.room.roomId, member.userId)
}
},
makeModerator(member) {
if (this.room) {
this.$matrix.makeModerator(this.room.roomId, member.userId)
}
},
revokeModerator(member) {
if (this.room) {
this.$matrix.revokeModerator(this.room.roomId, member.userId)
}
},
kickUser(member) {
if (this.room) {
this.$matrix.kickUser(this.room.roomId, member.userId)
}
},
banUser(member) {
if (this.room) {
this.$matrix.banUser(this.room.roomId, member.userId)
}
} }
}, },
}; };

View file

@ -22,6 +22,8 @@ import MessageOutgoingVideoExport from "./messages/export/MessageOutgoingVideoEx
import ContactJoin from "./messages/ContactJoin.vue"; import ContactJoin from "./messages/ContactJoin.vue";
import ContactLeave from "./messages/ContactLeave.vue"; import ContactLeave from "./messages/ContactLeave.vue";
import ContactInvited from "./messages/ContactInvited.vue"; import ContactInvited from "./messages/ContactInvited.vue";
import ContactKicked from "./messages/ContactKicked.vue";
import ContactBanned from "./messages/ContactBanned.vue";
import ContactChanged from "./messages/ContactChanged.vue"; import ContactChanged from "./messages/ContactChanged.vue";
import RoomCreated from "./messages/RoomCreated.vue"; import RoomCreated from "./messages/RoomCreated.vue";
import RoomAliased from "./messages/RoomAliased.vue"; import RoomAliased from "./messages/RoomAliased.vue";
@ -66,6 +68,8 @@ export default {
ContactJoin, ContactJoin,
ContactLeave, ContactLeave,
ContactInvited, ContactInvited,
ContactKicked,
ContactBanned,
ContactChanged, ContactChanged,
RoomCreated, RoomCreated,
RoomAliased, RoomAliased,
@ -123,9 +127,15 @@ export default {
return ContactJoin; return ContactJoin;
} }
} else if (event.getContent().membership == "leave") { } else if (event.getContent().membership == "leave") {
if ((event.getPrevContent() || {}).membership == "join" &&
event.getStateKey() != event.getSender()) {
return ContactKicked;
}
return ContactLeave; return ContactLeave;
} else if (event.getContent().membership == "invite") { } else if (event.getContent().membership == "invite") {
return ContactInvited; return ContactInvited;
} else if (event.getContent().membership == "ban") {
return ContactBanned;
} }
break; break;

View file

@ -0,0 +1,25 @@
<template>
<!-- Contact left the chat -->
<div class="messageJoin">
{{ (event.getStateKey() == this.$matrix.currentUserId) ?
$t('message.user_was_banned_you')
:
event.getSender() == this.$matrix.currentUserId ?
$t('message.user_was_banned_by_you',{user: eventStateKeyDisplayName(event)})
:
$t('message.user_was_banned',{user: eventStateKeyDisplayName(event)})
}}
</div>
</template>
<script>
import messageMixin from "./messageMixin";
export default {
mixins: [messageMixin],
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>

View file

@ -33,7 +33,7 @@ export default {
if (this.displayNameChange) { if (this.displayNameChange) {
return this.event.getPrevContent().displayname; return this.event.getPrevContent().displayname;
} }
return this.stateEventDisplayName(this.event); return this.eventStateKeyDisplayName(this.event);
}, },
} }
}; };

View file

@ -1,7 +1,7 @@
<template> <template>
<!-- Contact invited to the chat --> <!-- Contact invited to the chat -->
<div class="messageJoin"> <div class="messageJoin">
{{ $t('message.user_was_invited', {user: event.getContent().displayname || stateEventDisplayName(event)}) }} {{ $t('message.user_was_invited', {user: event.getContent().displayname || eventStateKeyDisplayName(event)}) }}
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<!-- Contact joined the chat --> <!-- Contact joined the chat -->
<div class="messageJoin"> <div class="messageJoin">
{{ $t('message.user_joined',{user: stateEventDisplayName(event)}) }} {{ $t('message.user_joined',{user: eventSenderDisplayName(event)}) }}
</div> </div>
</template> </template>

View file

@ -0,0 +1,25 @@
<template>
<!-- Contact left the chat -->
<div class="messageJoin">
{{ (event.getStateKey() == this.$matrix.currentUserId) ?
$t('message.user_was_kicked_you')
:
event.getSender() == this.$matrix.currentUserId ?
$t('message.user_was_kicked_by_you',{user: eventStateKeyDisplayName(event)})
:
$t('message.user_was_kicked',{user: eventStateKeyDisplayName(event)})
}}
</div>
</template>
<script>
import messageMixin from "./messageMixin";
export default {
mixins: [messageMixin],
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>

View file

@ -1,7 +1,7 @@
<template> <template>
<!-- Contact left the chat --> <!-- Contact left the chat -->
<div class="messageJoin"> <div class="messageJoin">
{{ $t('message.user_left',{user: stateEventDisplayName(event)}) }} {{ $t('message.user_left',{user: eventStateKeyDisplayName(event)}) }}
</div> </div>
</template> </template>

View file

@ -2,7 +2,7 @@
<!-- BASE CLASS FOR INCOMING MESSAGE --> <!-- BASE CLASS FOR INCOMING MESSAGE -->
<div :class="messageClasses"> <div :class="messageClasses">
<div v-if="showSenderAndTime" class="senderAndTime"> <div v-if="showSenderAndTime" class="senderAndTime">
<div class="sender">{{ messageEventDisplayName(event) }}</div> <div class="sender">{{ eventSenderDisplayName(event) }}</div>
<div class="time"> <div class="time">
{{ formatTime(event.event.origin_server_ts) }} {{ formatTime(event.event.origin_server_ts) }}
</div> </div>
@ -10,7 +10,7 @@
<v-avatar class="avatar" ref="avatar" size="32" color="#ededed" @click.stop="otherAvatarClicked($refs.avatar.$el)"> <v-avatar class="avatar" ref="avatar" size="32" color="#ededed" @click.stop="otherAvatarClicked($refs.avatar.$el)">
<img v-if="messageEventAvatar(event)" :src="messageEventAvatar(event)" /> <img v-if="messageEventAvatar(event)" :src="messageEventAvatar(event)" />
<span v-else class="white--text headline">{{ <span v-else class="white--text headline">{{
messageEventDisplayName(event).substring(0, 1).toUpperCase() eventSenderDisplayName(event).substring(0, 1).toUpperCase()
}}</span> }}</span>
</v-avatar> </v-avatar>
<!-- SLOT FOR CONTENT --> <!-- SLOT FOR CONTENT -->

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="statusEvent"> <div class="statusEvent">
{{ $t('message.user_aliased_room', {user: stateEventDisplayName(event), alias: event.getContent().alias}) }} {{ $t('message.user_aliased_room', {user: eventSenderDisplayName(event), alias: event.getContent().alias}) }}
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<!-- ROOM AVATAR CHANGED --> <!-- ROOM AVATAR CHANGED -->
<div class="statusEvent"> <div class="statusEvent">
{{ $t('message.user_changed_room_avatar',{user: stateEventDisplayName(event)}) }} {{ $t('message.user_changed_room_avatar',{user: eventSenderDisplayName(event)}) }}
</div> </div>
</template> </template>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="statusEvent"> <div class="statusEvent">
{{ $t('message.user_created_room', {user: stateEventDisplayName(event)}) }} {{ $t('message.user_created_room', {user: eventSenderDisplayName(event)}) }}
</div> </div>
</template> </template>

View file

@ -4,7 +4,7 @@
👋&nbsp; 👋&nbsp;
{{ {{
$t("purge_room.room_deletion_notice", { $t("purge_room.room_deletion_notice", {
user: stateEventDisplayName(event), user: eventSenderDisplayName(event),
}) })
}} }}
</div> </div>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="statusEvent"> <div class="statusEvent">
{{ $t('message.user_encrypted_room', {user: stateEventDisplayName(event)}) }} {{ $t('message.user_encrypted_room', {user: eventSenderDisplayName(event)}) }}
</div> </div>
</template> </template>

View file

@ -3,10 +3,10 @@
{{ {{
openToGuests openToGuests
? $t("message.user_changed_guest_access_open", { ? $t("message.user_changed_guest_access_open", {
user: stateEventDisplayName(event), user: eventSenderDisplayName(event),
}) })
: $t("message.user_changed_guest_access_closed", { : $t("message.user_changed_guest_access_closed", {
user: stateEventDisplayName(event), user: eventSenderDisplayName(event),
}) })
}} }}
</div> </div>

View file

@ -1,7 +1,7 @@
<template> <template>
<!-- ROOM AVATAR CHANGED --> <!-- ROOM AVATAR CHANGED -->
<div class="statusEvent"> <div class="statusEvent">
{{ $t('message.user_changed_room_history',{user: stateEventDisplayName(event), type: history(event)}) }} {{ $t('message.user_changed_room_history',{user: eventSenderDisplayName(event), type: history(event)}) }}
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<!-- ROOM JOIN RULES CHANGED --> <!-- ROOM JOIN RULES CHANGED -->
<div class="statusEvent"> <div class="statusEvent">
{{ $t('message.user_changed_join_rules', { user: stateEventDisplayName(event), type: joinRule(event)}) }} {{ $t('message.user_changed_join_rules', { user: eventSenderDisplayName(event), type: joinRule(event)}) }}
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<!-- ROOM NAME CHANGED --> <!-- ROOM NAME CHANGED -->
<div class="statusEvent"> <div class="statusEvent">
{{ $t('message.user_changed_room_name', {user: stateEventDisplayName(event), name: event.getContent().name}) }} {{ $t('message.user_changed_room_name', {user: eventSenderDisplayName(event), name: event.getContent().name}) }}
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<!-- ROOM TOPIC CHANGED --> <!-- ROOM TOPIC CHANGED -->
<div class="statusEvent"> <div class="statusEvent">
{{ $t('message.user_changed_room_topic', {user: stateEventDisplayName(event), topic: event.getContent().topic}) }} {{ $t('message.user_changed_room_topic', {user: eventSenderDisplayName(event), topic: event.getContent().topic}) }}
</div> </div>
</template> </template>

View file

@ -50,7 +50,7 @@ export default {
const originalEvent = this.timelineSet.findEventById(originalEventId); const originalEvent = this.timelineSet.findEventById(originalEventId);
if (originalEvent) { if (originalEvent) {
this.inReplyToEvent = originalEvent; this.inReplyToEvent = originalEvent;
this.inReplyToSender = this.messageEventDisplayName(originalEvent); this.inReplyToSender = this.eventSenderDisplayName(originalEvent);
} }
} }
} }
@ -157,7 +157,7 @@ export default {
/** /**
* Get a display name given an event. * Get a display name given an event.
*/ */
stateEventDisplayName(event) { eventSenderDisplayName(event) {
if (event.getSender() == this.$matrix.currentUserId) { if (event.getSender() == this.$matrix.currentUserId) {
return this.$t('message.you'); return this.$t('message.you');
} }
@ -167,11 +167,26 @@ export default {
return member.name; return member.name;
} }
} }
return event.getContent().displayname || event.event.state_key; return event.getContent().displayname || event.getSender();
}, },
messageEventDisplayName(event) { /**
return this.stateEventDisplayName(event); * In the case where the state_key points out a userId for an operation (e.g. membership events)
* return the display name of the affected user.
* @param event
* @returns
*/
eventStateKeyDisplayName(event) {
if (event.getStateKey() == this.$matrix.currentUserId) {
return this.$t('message.you');
}
if (this.room) {
const member = this.room.getMember(event.getStateKey());
if (member) {
return member.name;
}
}
return event.getStateKey();
}, },
messageEventAvatar(event) { messageEventAvatar(event) {

View file

@ -356,19 +356,22 @@ export default {
event.getRoomId() == this.currentRoom.roomId event.getRoomId() == this.currentRoom.roomId
) { ) {
// Don't use this.currentRoomId, may be an alias. We need the real id! // Don't use this.currentRoomId, may be an alias. We need the real id!
if ( if ((
event.getContent().membership == "leave" && event.getContent().membership == "leave" &&
(event.getPrevContent() || {}).membership == "join" && (event.getPrevContent() || {}).membership == "join" &&
event.getStateKey() == this.currentUserId && event.getStateKey() == this.currentUserId &&
event.getSender() != this.currentUserId event.getSender() != this.currentUserId
) { ) || (event.getContent().membership == "ban" && event.getStateKey() == this.currentUserId)) {
// We were kicked // We were kicked or banned
// If this is a live event (not just backpaging) then redirect to goodbye!
if (this.matrixClientReady) {
const wasPurged = const wasPurged =
event.getContent().reason == "Room Deleted"; event.getContent().reason == "Room Deleted";
this.$navigation.push( this.$navigation.push(
{ name: "Goodbye", params: { roomWasPurged: wasPurged } }, { name: "Goodbye", params: { roomWasPurged: wasPurged } },
-1 -1
); );
}
} }
} }
} }
@ -546,6 +549,63 @@ export default {
}); });
}, },
kickUser(roomId, userId) {
if (this.matrixClient && roomId && userId) {
this.matrixClient.kick(
roomId,
userId,
""
)
}
},
banUser(roomId, userId) {
if (this.matrixClient && roomId && userId) {
this.matrixClient.ban(
roomId,
userId,
""
)
}
},
makeAdmin(roomId, userId) {
if (this.matrixClient && roomId && userId) {
const room = this.getRoom(roomId);
if (room && room.currentState) {
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (powerLevelEvent) {
this.matrixClient.setPowerLevel(roomId, userId, 100, powerLevelEvent);
}
}
}
},
makeModerator(roomId, userId) {
if (this.matrixClient && roomId && userId) {
const room = this.getRoom(roomId);
console.log("Room", room);
if (room && room.currentState) {
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (powerLevelEvent) {
this.matrixClient.setPowerLevel(roomId, userId, 50, powerLevelEvent);
}
}
}
},
revokeModerator(roomId, userId) {
if (this.matrixClient && roomId && userId) {
const room = this.getRoom(roomId);
if (room && room.currentState) {
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
if (powerLevelEvent) {
this.matrixClient.setPowerLevel(roomId, userId, 0, powerLevelEvent);
}
}
}
},
/** /**
* Purge the room with the given id! This means: * Purge the room with the given id! This means:
* - Make room invite only * - Make room invite only