Merge branch '446-read-only-rooms-limit-non-moderators-ability-to-send-post-write' into 'dev'
Experimental "read only" room support See merge request keanuapp/keanuapp-weblite!149
This commit is contained in:
commit
56ecca1add
8 changed files with 169 additions and 17 deletions
|
|
@ -40,5 +40,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"experimental_voice_mode": true
|
"experimental_voice_mode": true,
|
||||||
|
"experimental_read_only_room": true
|
||||||
}
|
}
|
||||||
|
|
@ -295,6 +295,19 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-area-read-only {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(white, 0.8);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
@media #{map-get($display-breakpoints, 'sm-and-down')} {
|
@media #{map-get($display-breakpoints, 'sm-and-down')} {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
|
|
@ -1466,6 +1479,21 @@ body {
|
||||||
.mic-button {
|
.mic-button {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
.mic-button.dimmed {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.toast-read-only {
|
||||||
|
position: fixed;
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 10px;
|
||||||
|
background-color: rgba(black, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 40;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.audio-layout.voice-recorder {
|
.audio-layout.voice-recorder {
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,8 @@
|
||||||
"reply_poll": "Poll",
|
"reply_poll": "Poll",
|
||||||
"time_ago": "Today | Yesterday | {count} days ago",
|
"time_ago": "Today | Yesterday | {count} days ago",
|
||||||
"outgoing_message_deleted_text": "You deleted this message.",
|
"outgoing_message_deleted_text": "You deleted this message.",
|
||||||
"incoming_message_deleted_text": "This message was deleted."
|
"incoming_message_deleted_text": "This message was deleted.",
|
||||||
|
"not_allowed_to_send": "Only admins and moderators are allowed to send to the room"
|
||||||
},
|
},
|
||||||
"room": {
|
"room": {
|
||||||
"invitations": "You have no invitations | You have 1 invitation | You have {count} invitations",
|
"invitations": "You have no invitations | You have 1 invitation | You have {count} invitations",
|
||||||
|
|
@ -249,7 +250,9 @@
|
||||||
"experimental_features": "Experimental Features",
|
"experimental_features": "Experimental Features",
|
||||||
"voice_mode": "Voice mode",
|
"voice_mode": "Voice mode",
|
||||||
"voice_mode_info": "Switches the chat interface to a 'listen and record' mode",
|
"voice_mode_info": "Switches the chat interface to a 'listen and record' mode",
|
||||||
"download_chat": "Download chat"
|
"download_chat": "Download chat",
|
||||||
|
"read_only_room": "Read only room",
|
||||||
|
"read_only_room_info": "Only admins and moderators are allowed to send to the room"
|
||||||
},
|
},
|
||||||
"room_info_sheet": {
|
"room_info_sheet": {
|
||||||
"this_room": "This room",
|
"this_room": "This room",
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="load-later">
|
<div class="load-later">
|
||||||
<v-btn v-if="canRecordAudio" class="mic-button" ref="mic_button" fab small elevation="0" v-blur
|
<v-btn :class="{'mic-button': true, 'dimmed': !canRecordAudio}" ref="mic_button" fab small elevation="0" v-blur
|
||||||
@click.stop="$emit('start-recording')">
|
@click.stop="micButtonClicked()">
|
||||||
<v-icon color="white">mic</v-icon>
|
<v-icon color="white">mic</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-icon class="clickable" @click="loadNext" color="white" size="28">expand_more</v-icon>
|
<v-icon class="clickable" @click="loadNext" color="white" size="28">expand_more</v-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showReadOnlyToast" class="toast-read-only">{{ $t("message.not_allowed_to_send") }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -111,6 +113,7 @@ export default {
|
||||||
playing: false,
|
playing: false,
|
||||||
analyzer: null,
|
analyzer: null,
|
||||||
analyzerDataArray: null,
|
analyzerDataArray: null,
|
||||||
|
showReadOnlyToast: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
@ -162,12 +165,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
canRecordAudio() {
|
canRecordAudio() {
|
||||||
if (this.room) {
|
return !this.$matrix.currentRoomIsReadOnlyForUser && util.browserCanRecordAudio();
|
||||||
const myUserId = this.$matrix.currentUserId;
|
|
||||||
const me = this.room.getMember(myUserId);
|
|
||||||
return me && me.powerLevelNorm > 0 && util.browserCanRecordAudio();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
currentTime() {
|
currentTime() {
|
||||||
return util.formatDuration(this.playTime);
|
return util.formatDuration(this.playTime);
|
||||||
|
|
@ -448,6 +446,17 @@ export default {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
micButtonClicked() {
|
||||||
|
if (this.$matrix.currentRoomIsReadOnlyForUser) {
|
||||||
|
this.showReadOnlyToast = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showReadOnlyToast = false;
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
this.$emit('start-recording');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
:timelineSet="timelineSet"
|
:timelineSet="timelineSet"
|
||||||
:readMarker="readMarker"
|
:readMarker="readMarker"
|
||||||
:recordingMembers="typingMembers"
|
:recordingMembers="typingMembers"
|
||||||
v-on:start-recording="showRecorder = true"
|
v-on:start-recording="setShowRecorder()"
|
||||||
v-on:loadnext="handleScrolledToBottom(false)"
|
v-on:loadnext="handleScrolledToBottom(false)"
|
||||||
v-on:loadprevious="handleScrolledToTop()"
|
v-on:loadprevious="handleScrolledToTop()"
|
||||||
v-on:mark-read="sendRR"
|
v-on:mark-read="sendRR"
|
||||||
|
|
@ -177,6 +177,7 @@
|
||||||
<VoiceRecorder :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
|
<VoiceRecorder :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
|
||||||
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!useVoiceMode && room && $matrix.currentRoomIsReadOnlyForUser" class="input-area-read-only">{{ $t("message.not_allowed_to_send") }}</div>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
|
||||||
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
||||||
|
|
@ -1450,7 +1451,15 @@ export default {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.chatContainer.parentElement.removeChild(div);
|
this.chatContainer.parentElement.removeChild(div);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
},
|
||||||
|
setShowRecorder() {
|
||||||
|
if (this.canRecordAudio) {
|
||||||
|
this.showRecorder = true;
|
||||||
|
} else {
|
||||||
|
this.showNoRecordingAvailableDialog = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,13 @@
|
||||||
v-on:keyup.enter="$refs.create.focus()" :disabled="step > steps.INITIAL" solo></v-text-field>
|
v-on:keyup.enter="$refs.create.focus()" :disabled="step > steps.INITIAL" solo></v-text-field>
|
||||||
|
|
||||||
<!-- Our only option right now is voice mode, so if not enabled, hide the 'options' drop down as well -->
|
<!-- Our only option right now is voice mode, so if not enabled, hide the 'options' drop down as well -->
|
||||||
<template v-if="$config.experimental_voice_mode">
|
<template v-if="$config.experimental_voice_mode || $config.experimental_read_only_room">
|
||||||
<div @click.stop="showOptions = !showOptions" v-show="roomName.length > 0" class="options clickable">
|
<div @click.stop="showOptions = !showOptions" v-show="roomName.length > 0" class="options clickable">
|
||||||
<div>{{ $t("new_room.options") }}</div>
|
<div>{{ $t("new_room.options") }}</div>
|
||||||
<v-icon v-if="!showOptions">expand_more</v-icon>
|
<v-icon v-if="!showOptions">expand_more</v-icon>
|
||||||
<v-icon v-else>expand_less</v-icon>
|
<v-icon v-else>expand_less</v-icon>
|
||||||
</div>
|
</div>
|
||||||
<v-card v-show="showOptions" class="account ma-3" flat>
|
<v-card v-if="$config.experimental_voice_mode" v-show="showOptions" class="account ma-3" flat>
|
||||||
<v-card-text class="with-right-label">
|
<v-card-text class="with-right-label">
|
||||||
<div>
|
<div>
|
||||||
<div class="option-title">{{ $t('room_info.voice_mode') }}</div>
|
<div class="option-title">{{ $t('room_info.voice_mode') }}</div>
|
||||||
|
|
@ -58,6 +58,15 @@
|
||||||
<v-switch v-model="useVoiceMode"></v-switch>
|
<v-switch v-model="useVoiceMode"></v-switch>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
<v-card v-if="$config.experimental_read_only_room" v-show="showOptions" class="account ma-3" flat>
|
||||||
|
<v-card-text class="with-right-label">
|
||||||
|
<div>
|
||||||
|
<div class="option-title">{{ $t('room_info.read_only_room') }}</div>
|
||||||
|
<div class="option-text">{{ $t('room_info.read_only_room_info') }}</div>
|
||||||
|
</div>
|
||||||
|
<v-switch v-model="readOnlyRoom"></v-switch>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="error--text" v-if="roomCreationErrorMsg"> {{ roomCreationErrorMsg }}</div>
|
<div class="error--text" v-if="roomCreationErrorMsg"> {{ roomCreationErrorMsg }}</div>
|
||||||
|
|
@ -259,6 +268,7 @@ export default {
|
||||||
roomCreationErrorMsg: "",
|
roomCreationErrorMsg: "",
|
||||||
showOptions: false,
|
showOptions: false,
|
||||||
useVoiceMode: false,
|
useVoiceMode: false,
|
||||||
|
readOnlyRoom: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -401,7 +411,7 @@ export default {
|
||||||
content: {
|
content: {
|
||||||
history_visibility: "joined"
|
history_visibility: "joined"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -516,6 +526,20 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
||||||
|
// Set power level event. Need to do that here, because we might not have the userId when the options object is created.
|
||||||
|
const powerLevels = {};
|
||||||
|
powerLevels[this.$matrix.currentUserId] = 100;
|
||||||
|
createRoomOptions.initial_state.push(
|
||||||
|
{
|
||||||
|
type: "m.room.power_levels",
|
||||||
|
state_key: "",
|
||||||
|
content: {
|
||||||
|
users: powerLevels,
|
||||||
|
events_default: this.readOnlyRoom ? 50 : 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return this.$matrix.matrixClient
|
return this.$matrix.matrixClient
|
||||||
.createRoom(createRoomOptions)
|
.createRoom(createRoomOptions)
|
||||||
.then(({ room_id, room_alias }) => {
|
.then(({ room_id, room_alias }) => {
|
||||||
|
|
|
||||||
|
|
@ -165,9 +165,9 @@
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<v-card class="account ma-3" flat v-if="$config.experimental_voice_mode">
|
<v-card class="account ma-3" flat v-if="$config.experimental_voice_mode || canChangeReadOnly()">
|
||||||
<v-card-title class="h2 with-right-label"><div>{{ $t("room_info.experimental_features") }}</div><div></div></v-card-title>
|
<v-card-title class="h2 with-right-label"><div>{{ $t("room_info.experimental_features") }}</div><div></div></v-card-title>
|
||||||
<v-card-text class="with-right-label">
|
<v-card-text class="with-right-label" v-if="$config.experimental_voice_mode">
|
||||||
<div>
|
<div>
|
||||||
<div class="option-title">{{ $t('room_info.voice_mode') }}</div>
|
<div class="option-title">{{ $t('room_info.voice_mode') }}</div>
|
||||||
<div class="option-text">{{ $t('room_info.voice_mode_info') }}</div>
|
<div class="option-text">{{ $t('room_info.voice_mode_info') }}</div>
|
||||||
|
|
@ -176,6 +176,15 @@
|
||||||
v-model="useVoiceMode"
|
v-model="useVoiceMode"
|
||||||
></v-switch>
|
></v-switch>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
<v-card-text class="with-right-label" v-if="canChangeReadOnly()">
|
||||||
|
<div>
|
||||||
|
<div class="option-title">{{ $t('room_info.read_only_room') }}</div>
|
||||||
|
<div class="option-text">{{ $t('room_info.read_only_room_info') }}</div>
|
||||||
|
</div>
|
||||||
|
<v-switch
|
||||||
|
v-model="readOnlyRoom"
|
||||||
|
></v-switch>
|
||||||
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
||||||
<v-card class="members ma-3" flat>
|
<v-card class="members ma-3" flat>
|
||||||
|
|
@ -389,6 +398,14 @@ export default {
|
||||||
this.$matrix.matrixClient.setRoomTag(this.room.roomId, "ui_options", options);
|
this.$matrix.matrixClient.setRoomTag(this.room.roomId, "ui_options", options);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
readOnlyRoom: {
|
||||||
|
get: function () {
|
||||||
|
return this.$matrix.isReadOnlyRoom(this.room.roomId);
|
||||||
|
},
|
||||||
|
set: function (readOnly) {
|
||||||
|
this.$matrix.setReadOnlyRoom(this.room.roomId, readOnly);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -591,6 +608,16 @@ export default {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Return true if we can change power levels in the room, i.e. make read only room
|
||||||
|
*/
|
||||||
|
canChangeReadOnly() {
|
||||||
|
if (!this.$config.experimental_read_only_room) { return false; }
|
||||||
|
if (this.room) {
|
||||||
|
return this.room.currentState && this.room.currentState.maySendStateEvent("m.room.power_levels", this.$matrix.currentUserId);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
// TODO - following power level comparisons assume that default power levels are used in the room!
|
// TODO - following power level comparisons assume that default power levels are used in the room!
|
||||||
isAdmin(member) {
|
isAdmin(member) {
|
||||||
return member.powerLevelNorm > 50;
|
return member.powerLevelNorm > 50;
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ export default {
|
||||||
userDisplayName: null,
|
userDisplayName: null,
|
||||||
userAvatar: null,
|
userAvatar: null,
|
||||||
currentRoom: null,
|
currentRoom: null,
|
||||||
|
currentRoomIsReadOnlyForUser: false,
|
||||||
currentRoomBeingPurged: false,
|
currentRoomBeingPurged: false,
|
||||||
notificationCount: 0,
|
notificationCount: 0,
|
||||||
};
|
};
|
||||||
|
|
@ -93,6 +94,7 @@ export default {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(roomId) {
|
handler(roomId) {
|
||||||
this.currentRoom = this.getRoom(roomId);
|
this.currentRoom = this.getRoom(roomId);
|
||||||
|
this.currentRoomIsReadOnlyForUser = this.isReadOnlyRoomForUser(roomId, this.currentUserId);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -355,6 +357,14 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "m.room.power_levels":
|
||||||
|
{
|
||||||
|
if (this.currentRoom && event.getRoomId() == this.currentRoom.roomId) {
|
||||||
|
this.currentRoomIsReadOnlyForUser = this.isReadOnlyRoomForUser(event.getRoomId(), this.currentUserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
this.updateNotificationCount();
|
this.updateNotificationCount();
|
||||||
},
|
},
|
||||||
|
|
@ -550,6 +560,47 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isReadOnlyRoom(roomId) {
|
||||||
|
if (this.matrixClient && roomId) {
|
||||||
|
const room = this.getRoom(roomId);
|
||||||
|
if (room && room.currentState) {
|
||||||
|
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||||
|
if (powerLevelEvent) {
|
||||||
|
return powerLevelEvent.getContent().events_default > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
isReadOnlyRoomForUser(roomId, userId) {
|
||||||
|
if (this.matrixClient && roomId && userId) {
|
||||||
|
const room = this.getRoom(roomId);
|
||||||
|
if (room && room.currentState) {
|
||||||
|
return !room.currentState.maySendMessage(userId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
setReadOnlyRoom(roomId, readOnly) {
|
||||||
|
if (this.matrixClient && roomId) {
|
||||||
|
const room = this.getRoom(roomId);
|
||||||
|
if (room && room.currentState) {
|
||||||
|
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||||
|
if (powerLevelEvent) {
|
||||||
|
let content = powerLevelEvent.getContent();
|
||||||
|
content.events_default = readOnly ? 50 : 0;
|
||||||
|
this.matrixClient.sendStateEvent(
|
||||||
|
room.roomId,
|
||||||
|
"m.room.power_levels",
|
||||||
|
content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue