Merge branch '422-add-room-member-management-kick-add-moderator-etc' into 'dev'

Add kick,ban,make admin and make moderator operations

See merge request keanuapp/keanuapp-weblite!130
This commit is contained in:
N Pex 2023-01-22 13:45:13 +00:00
commit f5c75e3232
23 changed files with 281 additions and 27 deletions

View file

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

View file

@ -26,7 +26,12 @@
"undo": "Undo",
"join": "Join",
"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": {
"you": "You",
@ -37,6 +42,12 @@
"user_changed_room_avatar": "{user} changed the room avatar",
"user_encrypted_room": "{user} made the room encrypted",
"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_left": "{user} left the chat",
"user_said": "{user} said:",
@ -229,7 +240,9 @@
"leave_room": "Leave",
"version_info": "Powered by Guardian Project. Version: {version}",
"scan_code": "Scan to join the room",
"export_room": "Export chat"
"export_room": "Export chat",
"user_admin": "Administrator",
"user_moderator": "Moderator"
},
"room_info_sheet": {
"this_room": "This room",

View file

@ -195,7 +195,22 @@
})
}}
</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="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
v-if="expandedMembers.includes(member)"
:member="member"
@ -528,6 +543,91 @@ export default {
if (this.room) {
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 ContactLeave from "./messages/ContactLeave.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 RoomCreated from "./messages/RoomCreated.vue";
import RoomAliased from "./messages/RoomAliased.vue";
@ -66,6 +68,8 @@ export default {
ContactJoin,
ContactLeave,
ContactInvited,
ContactKicked,
ContactBanned,
ContactChanged,
RoomCreated,
RoomAliased,
@ -123,9 +127,15 @@ export default {
return ContactJoin;
}
} else if (event.getContent().membership == "leave") {
if ((event.getPrevContent() || {}).membership == "join" &&
event.getStateKey() != event.getSender()) {
return ContactKicked;
}
return ContactLeave;
} else if (event.getContent().membership == "invite") {
return ContactInvited;
} else if (event.getContent().membership == "ban") {
return ContactBanned;
}
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) {
return this.event.getPrevContent().displayname;
}
return this.stateEventDisplayName(this.event);
return this.eventStateKeyDisplayName(this.event);
},
}
};

View file

@ -1,7 +1,7 @@
<template>
<!-- Contact invited to the chat -->
<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>
</template>

View file

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

View file

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

View file

@ -1,6 +1,6 @@
<template>
<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>
</template>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
<template>
<!-- ROOM AVATAR CHANGED -->
<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>
</template>

View file

@ -1,7 +1,7 @@
<template>
<!-- ROOM JOIN RULES CHANGED -->
<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>
</template>

View file

@ -1,7 +1,7 @@
<template>
<!-- ROOM NAME CHANGED -->
<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>
</template>

View file

@ -1,7 +1,7 @@
<template>
<!-- ROOM TOPIC CHANGED -->
<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>
</template>

View file

@ -50,7 +50,7 @@ export default {
const originalEvent = this.timelineSet.findEventById(originalEventId);
if (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.
*/
stateEventDisplayName(event) {
eventSenderDisplayName(event) {
if (event.getSender() == this.$matrix.currentUserId) {
return this.$t('message.you');
}
@ -167,11 +167,26 @@ export default {
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) {

View file

@ -356,19 +356,22 @@ export default {
event.getRoomId() == this.currentRoom.roomId
) {
// Don't use this.currentRoomId, may be an alias. We need the real id!
if (
if ((
event.getContent().membership == "leave" &&
(event.getPrevContent() || {}).membership == "join" &&
event.getStateKey() == this.currentUserId &&
event.getSender() != this.currentUserId
) {
// We were kicked
) || (event.getContent().membership == "ban" && event.getStateKey() == this.currentUserId)) {
// We were kicked or banned
// If this is a live event (not just backpaging) then redirect to goodbye!
if (this.matrixClientReady) {
const wasPurged =
event.getContent().reason == "Room Deleted";
this.$navigation.push(
{ name: "Goodbye", params: { roomWasPurged: wasPurged } },
-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:
* - Make room invite only