diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss index f949b52..ecb8ad3 100644 --- a/src/assets/css/chat.scss +++ b/src/assets/css/chat.scss @@ -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; } diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json index 1b0b494..08292f7 100644 --- a/src/assets/translations/en.json +++ b/src/assets/translations/en.json @@ -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", diff --git a/src/components/RoomInfo.vue b/src/components/RoomInfo.vue index 52fb29a..93e9557 100644 --- a/src/components/RoomInfo.vue +++ b/src/components/RoomInfo.vue @@ -195,7 +195,22 @@ }) }} + + {{ $t("room_info.user_admin") }} + + + {{ $t("room_info.user_moderator") }} +
{{ $t("menu.start_private_chat") }}
+
+
{{ String.fromCharCode(160) }}
+
{{ $t("menu.user_kick") }}
+
{{ $t("menu.user_kick_and_ban") }}
+
+
{{ String.fromCharCode(160) }}
+
{{ $t("menu.user_make_admin") }}
+
{{ $t("menu.user_make_moderator") }}
+
{{ $t("menu.user_revoke_moderator") }}
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) + } } }, }; diff --git a/src/components/chatMixin.js b/src/components/chatMixin.js index 970fe16..d87a548 100644 --- a/src/components/chatMixin.js +++ b/src/components/chatMixin.js @@ -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; diff --git a/src/components/messages/ContactBanned.vue b/src/components/messages/ContactBanned.vue new file mode 100644 index 0000000..37c2a45 --- /dev/null +++ b/src/components/messages/ContactBanned.vue @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/src/components/messages/ContactChanged.vue b/src/components/messages/ContactChanged.vue index 95622dc..e03d24a 100644 --- a/src/components/messages/ContactChanged.vue +++ b/src/components/messages/ContactChanged.vue @@ -33,7 +33,7 @@ export default { if (this.displayNameChange) { return this.event.getPrevContent().displayname; } - return this.stateEventDisplayName(this.event); + return this.eventStateKeyDisplayName(this.event); }, } }; diff --git a/src/components/messages/ContactInvited.vue b/src/components/messages/ContactInvited.vue index 85f96a8..110c43e 100644 --- a/src/components/messages/ContactInvited.vue +++ b/src/components/messages/ContactInvited.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/ContactJoin.vue b/src/components/messages/ContactJoin.vue index b8bce13..d8aa6d8 100644 --- a/src/components/messages/ContactJoin.vue +++ b/src/components/messages/ContactJoin.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/ContactKicked.vue b/src/components/messages/ContactKicked.vue new file mode 100644 index 0000000..b34da00 --- /dev/null +++ b/src/components/messages/ContactKicked.vue @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/src/components/messages/ContactLeave.vue b/src/components/messages/ContactLeave.vue index 0b77831..1e40799 100644 --- a/src/components/messages/ContactLeave.vue +++ b/src/components/messages/ContactLeave.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/MessageIncoming.vue b/src/components/messages/MessageIncoming.vue index 9c31eb3..e1f1727 100644 --- a/src/components/messages/MessageIncoming.vue +++ b/src/components/messages/MessageIncoming.vue @@ -2,7 +2,7 @@
-
{{ messageEventDisplayName(event) }}
+
{{ eventSenderDisplayName(event) }}
{{ formatTime(event.event.origin_server_ts) }}
@@ -10,7 +10,7 @@ {{ - messageEventDisplayName(event).substring(0, 1).toUpperCase() + eventSenderDisplayName(event).substring(0, 1).toUpperCase() }} diff --git a/src/components/messages/RoomAliased.vue b/src/components/messages/RoomAliased.vue index 5a908e6..9c87f29 100644 --- a/src/components/messages/RoomAliased.vue +++ b/src/components/messages/RoomAliased.vue @@ -1,6 +1,6 @@ diff --git a/src/components/messages/RoomAvatarChanged.vue b/src/components/messages/RoomAvatarChanged.vue index 7645e80..cc68bca 100644 --- a/src/components/messages/RoomAvatarChanged.vue +++ b/src/components/messages/RoomAvatarChanged.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/RoomCreated.vue b/src/components/messages/RoomCreated.vue index 9603e0f..c83ad15 100644 --- a/src/components/messages/RoomCreated.vue +++ b/src/components/messages/RoomCreated.vue @@ -1,6 +1,6 @@ diff --git a/src/components/messages/RoomDeletionNotice.vue b/src/components/messages/RoomDeletionNotice.vue index 9a2f476..f8b7524 100644 --- a/src/components/messages/RoomDeletionNotice.vue +++ b/src/components/messages/RoomDeletionNotice.vue @@ -4,7 +4,7 @@ 👋  {{ $t("purge_room.room_deletion_notice", { - user: stateEventDisplayName(event), + user: eventSenderDisplayName(event), }) }}
diff --git a/src/components/messages/RoomEncrypted.vue b/src/components/messages/RoomEncrypted.vue index 669b6e7..874d989 100644 --- a/src/components/messages/RoomEncrypted.vue +++ b/src/components/messages/RoomEncrypted.vue @@ -1,6 +1,6 @@ diff --git a/src/components/messages/RoomGuestAccessChanged.vue b/src/components/messages/RoomGuestAccessChanged.vue index 13d7f27..b89501c 100644 --- a/src/components/messages/RoomGuestAccessChanged.vue +++ b/src/components/messages/RoomGuestAccessChanged.vue @@ -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), }) }}
diff --git a/src/components/messages/RoomHistoryVisibility.vue b/src/components/messages/RoomHistoryVisibility.vue index 19892f4..a6e1592 100644 --- a/src/components/messages/RoomHistoryVisibility.vue +++ b/src/components/messages/RoomHistoryVisibility.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/RoomJoinRules.vue b/src/components/messages/RoomJoinRules.vue index f80dbff..0ee433b 100644 --- a/src/components/messages/RoomJoinRules.vue +++ b/src/components/messages/RoomJoinRules.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/RoomNameChanged.vue b/src/components/messages/RoomNameChanged.vue index ba152b8..1e844c5 100644 --- a/src/components/messages/RoomNameChanged.vue +++ b/src/components/messages/RoomNameChanged.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/RoomTopicChanged.vue b/src/components/messages/RoomTopicChanged.vue index 6ac9992..9268756 100644 --- a/src/components/messages/RoomTopicChanged.vue +++ b/src/components/messages/RoomTopicChanged.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/messageMixin.js b/src/components/messages/messageMixin.js index 1820781..7aea0ba 100644 --- a/src/components/messages/messageMixin.js +++ b/src/components/messages/messageMixin.js @@ -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) { diff --git a/src/services/matrix.service.js b/src/services/matrix.service.js index 158c971..ffe78fd 100644 --- a/src/services/matrix.service.js +++ b/src/services/matrix.service.js @@ -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