Merge branch 'dev'

This commit is contained in:
N-Pex 2024-08-23 09:45:13 +02:00
commit cbad68ecec
21 changed files with 194 additions and 50 deletions

View file

@ -54,7 +54,6 @@ The app loads runtime configutation from the server at "./config.json" and merge
* **show_status_messages** - Whether to show only user joins/leaves and display name updates, or the full range of room status updates. Possible values are "never" (only the above), "moderators" (moderators will see all status updates) or "always" (everyone will see all status updates). Defaults to "always". * **show_status_messages** - Whether to show only user joins/leaves and display name updates, or the full range of room status updates. Possible values are "never" (only the above), "moderators" (moderators will see all status updates) or "always" (everyone will see all status updates). Defaults to "always".
* **maxSizeAutoDownloads** - Attachments smaller than this will be auto downloaded. Default is 10Mb. * **maxSizeAutoDownloads** - Attachments smaller than this will be auto downloaded. Default is 10Mb.
### Sticker short codes - To enable sticker short codes, follow these steps: ### Sticker short codes - To enable sticker short codes, follow these steps:
* Run the "create sticker config" script using "npm run create-sticker-config <path-to-sticker-packs>" * Run the "create sticker config" script using "npm run create-sticker-config <path-to-sticker-packs>"
* Insert the resulting config blob into the "shortCodeStickers" value of the config file (assets/config.json) * Insert the resulting config blob into the "shortCodeStickers" value of the config file (assets/config.json)

View file

@ -1,6 +1,6 @@
{ {
"name": "keanuapp-weblite", "name": "keanuapp-weblite",
"version": "0.1.39", "version": "0.1.40",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",

View file

@ -1,6 +1,6 @@
{ {
"name": "keanuapp-weblite", "name": "keanuapp-weblite",
"version": "0.1.38", "version": "0.1.39",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",

View file

@ -25,6 +25,7 @@
type="list-item-avatar-two-line, divider, list-item-three-line, card-heading" type="list-item-avatar-two-line, divider, list-item-three-line, card-heading"
v-if="showLoadingScreen" v-if="showLoadingScreen"
></v-skeleton-loader> ></v-skeleton-loader>
<unsupported-browser-alert />
</v-main> </v-main>
</v-app> </v-app>
</template> </template>
@ -34,10 +35,14 @@ import stickers from "./plugins/stickers";
import { registerServiceWorker, notificationCount, windowNotificationPermission } from "./plugins/notificationAndServiceWorker.js" import { registerServiceWorker, notificationCount, windowNotificationPermission } from "./plugins/notificationAndServiceWorker.js"
import logoMixin from "./components/logoMixin"; import logoMixin from "./components/logoMixin";
import { mapState } from 'vuex' import { mapState } from 'vuex'
import UnsupportedBrowserAlert from "./components/UnsupportedBrowserAlert.vue";
export default { export default {
name: "App", name: "App",
mixins: [logoMixin], mixins: [logoMixin],
components: {
UnsupportedBrowserAlert
},
data() { data() {
return { return {
loading: true, loading: true,

View file

@ -19,7 +19,9 @@
"days": "1 day ago | {n} days ago" "days": "1 day ago | {n} days ago"
}, },
"close": "close", "close": "close",
"notify": "Notify" "notify": "Notify",
"different_browser_title": "Try different browser",
"different_browser_content": "Some features may break. Copy and open link in a different browser."
}, },
"menu": { "menu": {
"start_private_chat": "Direct Message with this user", "start_private_chat": "Direct Message with this user",
@ -142,6 +144,7 @@
"join_channel": "All set! Invite people to join you: {link}", "join_channel": "All set! Invite people to join you: {link}",
"info_retention": "🕓 Messages sent within {time} are viewable by anyone with the link.", "info_retention": "🕓 Messages sent within {time} are viewable by anyone with the link.",
"info_retention_user": "🕓 Messages older than {time} will be deleted from the history.", "info_retention_user": "🕓 Messages older than {time} will be deleted from the history.",
"info_auto_join": "Welcome to {room}.\nYou are joining as {you}.",
"change": "Change" "change": "Change"
}, },
"new_room": { "new_room": {

View file

@ -174,7 +174,8 @@
"message_retention_1_day": "1 päivä", "message_retention_1_day": "1 päivä",
"message_retention_8_hours": "8 tuntia", "message_retention_8_hours": "8 tuntia",
"message_retention_1_hour": "1 tunti", "message_retention_1_hour": "1 tunti",
"read_only_room": "Lue Ainoastaan" "read_only_room": "Lue Ainoastaan",
"moderation": "Moderointi"
}, },
"power_level": { "power_level": {
"restricted": "rajoitettu", "restricted": "rajoitettu",

View file

@ -18,7 +18,8 @@
"delete": "Supprimer", "delete": "Supprimer",
"done": "Terminé", "done": "Terminé",
"user_kick_and_ban": "Exclure", "user_kick_and_ban": "Exclure",
"user_make_admin": "Rendre administrateur" "user_make_admin": "Rendre administrateur",
"direct_chat": "Discussion directe"
}, },
"language_display_name": "français", "language_display_name": "français",
"message": { "message": {
@ -222,7 +223,8 @@
"message_retention_1_day": "1 jour", "message_retention_1_day": "1 jour",
"message_retention_8_hours": "8 heures", "message_retention_8_hours": "8 heures",
"message_retention_1_hour": "1 heure", "message_retention_1_hour": "1 heure",
"read_only_room": "Lecture seule" "read_only_room": "Lecture seule",
"moderation": "Modération"
}, },
"room_info_sheet": { "room_info_sheet": {
"this_room": "Ce salon", "this_room": "Ce salon",

View file

@ -125,7 +125,8 @@
"send_verification": "Invia email di verifica", "send_verification": "Invia email di verifica",
"invalid_message": "Nome utente o password non validi", "invalid_message": "Nome utente o password non validi",
"accept_terms": "Accetto", "accept_terms": "Accetto",
"resend_verification": "Rispedisci email di verifica" "resend_verification": "Rispedisci email di verifica",
"token_not_valid": "Token non valido"
}, },
"profile": { "profile": {
"title": "Il mio profilo", "title": "Il mio profilo",
@ -218,7 +219,8 @@
"message_retention_1_day": "1 giorno", "message_retention_1_day": "1 giorno",
"message_retention_8_hours": "8 ore", "message_retention_8_hours": "8 ore",
"message_retention_1_hour": "1 ora", "message_retention_1_hour": "1 ora",
"read_only_room": "Sola lettura" "read_only_room": "Sola lettura",
"moderation": "Moderazione"
}, },
"voice_recorder": { "voice_recorder": {
"failed_to_record": "Impossibile registrare laudio", "failed_to_record": "Impossibile registrare laudio",

View file

@ -155,7 +155,8 @@
"change": "Mudança", "change": "Mudança",
"join_channel": "Tudo pronto! Convide pessoas para se juntarem a você: {link}", "join_channel": "Tudo pronto! Convide pessoas para se juntarem a você: {link}",
"info_retention": "🕓 As mensagens enviadas dentro de {time} podem ser visualizadas por qualquer pessoa com o link.", "info_retention": "🕓 As mensagens enviadas dentro de {time} podem ser visualizadas por qualquer pessoa com o link.",
"info_retention_user": "As mensagens mais antigas que {time} serão excluídas do histórico." "info_retention_user": "As mensagens mais antigas que {time} serão excluídas do histórico.",
"info_auto_join": "Bem-vindo à {room}.\nVocê está entrando como {you}."
}, },
"new_room": { "new_room": {
"new_room": "Nova sala", "new_room": "Nova sala",

View file

@ -268,7 +268,8 @@
"info_permissions": "Вы можете в любой момент изменить \"разрешение на присоединение\" в настройках комнаты.", "info_permissions": "Вы можете в любой момент изменить \"разрешение на присоединение\" в настройках комнаты.",
"info": "Добро пожаловать! Вот несколько вещей, которые нужно знать о вашей комнате:", "info": "Добро пожаловать! Вот несколько вещей, которые нужно знать о вашей комнате:",
"direct_private_chat": "Личное сообщение", "direct_private_chat": "Личное сообщение",
"info_retention_user": "🕓 Сообщения старше {time} будут удалены из истории." "info_retention_user": "🕓 Сообщения старше {time} будут удалены из истории.",
"info_auto_join": "Добро пожаловать в {room}.\nВы присоединяетесь как {you}."
}, },
"device_list": { "device_list": {
"blocked": "Заблокированный", "blocked": "Заблокированный",

View file

@ -49,7 +49,8 @@
"message_retention_8_hours": "8 小时", "message_retention_8_hours": "8 小时",
"message_retention_1_hour": "1 小时", "message_retention_1_hour": "1 小时",
"message_retention_2_week": "2周", "message_retention_2_week": "2周",
"message_retention_1_week": "1周" "message_retention_1_week": "1周",
"moderation": "调节面板"
}, },
"leave": { "leave": {
"leave": "离开", "leave": "离开",

View file

@ -37,7 +37,6 @@
<div ref="messageOperationsStrut" class="message-operations-strut"> <div ref="messageOperationsStrut" class="message-operations-strut">
<message-operations ref="messageOperations" :style="opStyle" :emojis="recentEmojis" v-on:close=" <message-operations ref="messageOperations" :style="opStyle" :emojis="recentEmojis" v-on:close="
showContextMenu = false; showContextMenu = false;
showContextMenuAnchor = null;
" v-if="showMessageOperations" v-on:addreaction="addReaction" v-on:addquickreaction="addQuickReaction" " v-if="showMessageOperations" v-on:addreaction="addReaction" v-on:addquickreaction="addQuickReaction"
v-on:addreply="addReply(selectedEvent)" v-on:edit="edit(selectedEvent)" v-on:redact="redact(selectedEvent)" v-on:addreply="addReply(selectedEvent)" v-on:edit="edit(selectedEvent)" v-on:redact="redact(selectedEvent)"
v-on:download="download(selectedEvent)" v-on:more=" v-on:download="download(selectedEvent)" v-on:more="
@ -55,7 +54,7 @@
<component :is="roomWelcomeHeader" v-on:close="closeRoomWelcomeHeader"></component> <component :is="roomWelcomeHeader" v-on:close="closeRoomWelcomeHeader"></component>
<!-- If we have a retention timer, it means we have active message retention. Show header. --> <!-- If we have a retention timer, it means we have active message retention. Show header. -->
<WelcomeHeaderChannelUser v-if="retentionTimer && !roomWelcomeHeader" /> <WelcomeHeaderChannelUser v-if="retentionTimer && !roomWelcomeHeader && newlyJoinedRoom" />
<div v-for="(event, index) in filteredEvents" :key="event.getId()" :eventId="event.getId()"> <div v-for="(event, index) in filteredEvents" :key="event.getId()" :eventId="event.getId()">
<!-- DAY Marker, shown for every new day in the timeline --> <!-- DAY Marker, shown for every new day in the timeline -->
@ -479,6 +478,7 @@ export default {
/** If we just created this room, show a small welcome header with info */ /** If we just created this room, show a small welcome header with info */
hideRoomWelcomeHeader: false, hideRoomWelcomeHeader: false,
newlyJoinedRoom: false,
/** An array of recent emojis. Used in the "message operations" popup. */ /** An array of recent emojis. Used in the "message operations" popup. */
recentEmojis: [], recentEmojis: [],
@ -831,6 +831,7 @@ export default {
this.typingMembers = []; this.typingMembers = [];
this.initialLoadDone = false; this.initialLoadDone = false;
this.hideRoomWelcomeHeader = false; this.hideRoomWelcomeHeader = false;
this.newlyJoinedRoom = false;
// Stop RR timer // Stop RR timer
this.stopRRTimer(); this.stopRRTimer();
@ -862,8 +863,8 @@ export default {
this.onRoomJoined(this.readMarker); this.onRoomJoined(this.readMarker);
} }
}, },
showMessageOperations() { showMessageOperations(show) {
if (this.showMessageOperations) { if (show) {
this.$nextTick(() => { this.$nextTick(() => {
// Calculate where to show the context menu. // Calculate where to show the context menu.
// //
@ -958,6 +959,16 @@ export default {
}, },
onRoomJoined(initialEventId) { onRoomJoined(initialEventId) {
// If our own join event is less than a minute old, consider this a "newly joined" room.
//
// Previously tried to look at initialEventId, but it seems like "this.room.getEventReadUpTo(this.$matrix.currentUserId, false)"
// always returns an event id? Strange. I would expect it to be null on a fresh room.
//
const joinEvent = this.room && this.room.currentState.getStateEvents("m.room.member", this.$matrix.currentUserId);
if (joinEvent) {
this.newlyJoinedRoom = joinEvent.getLocalAge() < 1 * 60000 /* 1 minute */;
}
// Listen to events // Listen to events
this.$matrix.on("Room.timeline", this.onEvent); this.$matrix.on("Room.timeline", this.onEvent);
this.$matrix.on("RoomMember.typing", this.onUserTyping); this.$matrix.on("RoomMember.typing", this.onUserTyping);
@ -1044,7 +1055,7 @@ export default {
this.$navigation.push( this.$navigation.push(
{ {
name: "Join", name: "Join",
params: { roomId: util.sanitizeRoomId(this.roomAliasOrId) }, params: { roomId: util.sanitizeRoomId(this.roomAliasOrId), join: this.$route.params.join },
}, },
0 0
); );
@ -1740,10 +1751,17 @@ export default {
showContextMenuForEvent(e) { showContextMenuForEvent(e) {
const event = e.event; const event = e.event;
this.selectedEvent = event; if (this.selectedEvent == event) {
this.updateRecentEmojis(); this.showContextMenu = !this.showContextMenu;
this.showContextMenu = !this.showContextMenu; } else {
this.showContextMenuAnchor = e.anchor; this.showContextMenu = false;
this.$nextTick(() => {
this.selectedEvent = event;
this.updateRecentEmojis();
this.showContextMenu = true;
this.showContextMenuAnchor = e.anchor;
})
}
}, },
showAvatarMenuForEvent(e) { showAvatarMenuForEvent(e) {
@ -1761,6 +1779,7 @@ export default {
if (this.showContextMenu) { if (this.showContextMenu) {
this.showContextMenu = false; this.showContextMenu = false;
this.showContextMenuAnchor = null; this.showContextMenuAnchor = null;
this.selectedEvent = null;
e.preventDefault(); e.preventDefault();
} }
}, },

View file

@ -105,6 +105,9 @@
<input id="room-avatar-picker" ref="avatar" type="file" name="avatar" @change="handlePickedAvatar($event)" <input id="room-avatar-picker" ref="avatar" type="file" name="avatar" @change="handlePickedAvatar($event)"
accept="image/*" class="d-none" /> accept="image/*" class="d-none" />
<input id="user-avatar-picker" ref="useravatar" type="file" name="user-avatar" @change="handlePickedUserAvatar($event)" accept="image/*" class="d-none" />
<v-dialog v-model="enterRoomDialog" :width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'"> <v-dialog v-model="enterRoomDialog" :width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'">
<v-card> <v-card>
<v-container v-if="canEditProfile" class="pa-10"> <v-container v-if="canEditProfile" class="pa-10">
@ -112,9 +115,11 @@
<v-col class="py-0"> <v-col class="py-0">
<div class="text-start font-weight-bold">{{ $t("join.choose_name") }}</div> <div class="text-start font-weight-bold">{{ $t("join.choose_name") }}</div>
<v-select ref="avatar" :items="availableAvatars" cache-items outlined dense @change="selectAvatar" <v-select ref="avatar" :items="availableAvatars" cache-items outlined dense @change="selectAvatar"
:value="availableAvatars[0]" single-line autofocus> :value="selectedProfile">
<template v-slot:selection> <template v-slot:selection>
<v-text-field background-color="transparent" solo flat hide-details @click.native.stop="{}" <v-text-field background-color="transparent" solo flat hide-details
@click.native.stop="(event) => event.target.focus()"
@focus="$event.target.select()"
v-model="selectedProfile.name"></v-text-field> v-model="selectedProfile.name"></v-text-field>
</template> </template>
<template v-slot:item="data"> <template v-slot:item="data">
@ -126,7 +131,7 @@
</v-select> </v-select>
</v-col> </v-col>
<v-col cols="2" class="py-0"> <v-col cols="2" class="py-0">
<v-avatar @click="showAvatarPickerList"> <v-avatar @click="showUserAvatarPicker">
<v-img v-if="selectedProfile" :src="selectedProfile.image" /> <v-img v-if="selectedProfile" :src="selectedProfile.image" />
</v-avatar> </v-avatar>
</v-col> </v-col>
@ -586,6 +591,21 @@ export default {
showAvatarPickerList() { showAvatarPickerList() {
this.$refs.avatar.$refs.input.click(); this.$refs.avatar.$refs.input.click();
}, },
/**
* Show picker to select user avatar file
*/
showUserAvatarPicker() {
if (this.step == steps.INITIAL) {
this.$refs.useravatar.click();
}
},
handlePickedUserAvatar(event) {
util.loadAvatarFromFile(event, (image) => {
this.selectedProfile.image = image;
});
},
} }
}; };
</script> </script>

View file

@ -18,7 +18,7 @@
</div> </div>
<hr class="my-10 join-line" /> <hr class="my-10 join-line" />
<div class="font-weight-black mb-4" v-if="!currentUser">Choose a name to use.</div> <div class="font-weight-black mb-4" v-if="!currentUser">{{ $t("join.choose_name") }}</div>
<v-row v-if="canEditProfile"> <v-row v-if="canEditProfile">
<v-col cols="10" sm="7" class="py-0"> <v-col cols="10" sm="7" class="py-0">
@ -29,7 +29,7 @@
outlined outlined
dense dense
@change="selectAvatar" @change="selectAvatar"
:value="availableAvatars[0]" :value="selectedProfile"
single-line single-line
autofocus autofocus
> >
@ -39,10 +39,8 @@
solo solo
flat flat
hide-details hide-details
@click.native.stop=" @click.native.stop="(event) => event.target.focus()"
{ @focus="$event.target.select()"
}
"
v-model="selectedProfile.name" v-model="selectedProfile.name"
></v-text-field> ></v-text-field>
</template> </template>
@ -308,7 +306,14 @@ export default {
return roomName ? roomName : ""; return roomName ? roomName : "";
}, },
getRoomInfo() { getRoomInfo() {
if (this.roomId.startsWith("#")) { if (this.$route.params.join) {
// Auto-join room
this.waitingForRoomCreation = true;
this.$nextTick(() => {
this.handleJoin();
});
}
else if (this.roomId.startsWith("#")) {
this.$matrix this.$matrix
.getPublicRoomInfo(this.roomId) .getPublicRoomInfo(this.roomId)
.then((room) => { .then((room) => {

View file

@ -0,0 +1,53 @@
<template>
<v-dialog
class="ma-0 pa-0"
v-model="isUnSupportedBrowser"
persistent
:width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'"
>
<div class="dialog-content text-center">
<h2 class="dialog-title">{{ $t("global.different_browser_title") }}</h2>
<div class="dialog-text">{{ $t("global.different_browser_content") }}</div>
<v-card-actions class="pb-0">
<v-spacer></v-spacer>
<v-btn
:color="locationUrlCopied ? '#DEE6FF' : 'black'"
depressed
@click.stop="copyRoomLink1"
:class="{'filled-button' : true, 'link-copied-in-place' : locationUrlCopied}"
>{{ $t(`room_info.${locationUrlCopied ? 'link_copied' : 'copy_link'}`) }}</v-btn
>
</v-card-actions>
</div>
</v-dialog>
</template>
<script>
const UNSUPPORTED_USER_AGENT = [
'MicroMessenger' // WeChat
]
export default {
data () {
return {
locationUrlCopied: false,
locationUrl: window.location.href
}
},
computed: {
isUnSupportedBrowser() {
return UNSUPPORTED_USER_AGENT.some((userAgent) => window.navigator.userAgent.includes(userAgent));
}
},
methods: {
copyRoomLink1() {
if(this.locationUrlCopied) return
navigator.clipboard.writeText(this.locationUrl)
this.locationUrlCopied = true;
setInterval(() => {
this.locationUrlCopied = false;
}, 3000);
}
}
}
</script>

View file

@ -1,4 +1,4 @@
import utils from "../plugins/utils"; import utils, { ROOM_TYPE_CHANNEL } from "../plugins/utils";
import roomTypeMixin from "./roomTypeMixin"; import roomTypeMixin from "./roomTypeMixin";
export default { export default {
@ -96,7 +96,8 @@ export default {
this.room.getCanonicalAlias(), this.room.getCanonicalAlias(),
this.room.roomId, this.room.roomId,
this.room.name, this.room.name,
utils.roomDisplayTypeToQueryParam(this.room, this.roomDisplayType) utils.roomDisplayTypeToQueryParam(this.room, this.roomDisplayType),
this.roomDisplayType == ROOM_TYPE_CHANNEL /* Auto join for channels */
); );
} }
return null; return null;

View file

@ -20,12 +20,8 @@
<template v-slot:time> <template v-slot:time>
<b>{{ messageRetentionDisplay }}</b> <b>{{ messageRetentionDisplay }}</b>
</template> </template>
</i18n> </i18n>&nbsp;
</div> <a href="#" text @click.prevent="showMessageRetentionDialog = true" style="white-space: pre-line;">{{ $t("room_welcome.change") }}</a>
<div class="mt-2" v-if="roomMessageRetention() > 0">
<a href="#" text @click.prevent="showMessageRetentionDialog = true">
{{ $t("room_welcome.change") }}
</a>
</div> </div>
<div class="text-end"> <div class="text-end">
<v-btn id="btn-got-it" text @click.stop="$emit('close')" class="text-transform-0"> <v-btn id="btn-got-it" text @click.stop="$emit('close')" class="text-transform-0">

View file

@ -1,5 +1,11 @@
<template> <template>
<div class="created-room-welcome-header"> <div class="created-room-welcome-header">
<div style="white-space: pre-line;">
{{ $t('room_welcome.info_auto_join',{room: room ? room.name : "",you: $matrix.currentUserDisplayName}) }}
<a href="#" text @click.prevent="viewProfile">
{{ $t("room_welcome.change") }}
</a>
</div>
<div class="mt-2" v-if="roomMessageRetention() > 0"> <div class="mt-2" v-if="roomMessageRetention() > 0">
<i18n path="room_welcome.info_retention_user" tag="span"> <i18n path="room_welcome.info_retention_user" tag="span">
<template v-slot:time> <template v-slot:time>
@ -25,6 +31,9 @@ export default {
} }
}, },
methods: { methods: {
viewProfile() {
this.$navigation.push({ name: "Profile" }, 1);
},
onMessageRetention(ignoredretention) { onMessageRetention(ignoredretention) {
this.updateMessageRetention(); this.updateMessageRetention();
}, },

View file

@ -920,7 +920,10 @@ class Util {
const link = document.createElement("a"); const link = document.createElement("a");
link.href = url; link.href = url;
link.target = "_blank"; link.target = "_blank";
link.download = event.getContent().body || this.$t("fallbacks.download_name"); if (!this.isFileTypePDF(event)) {
// PDFs are shown inline, not downloaded
link.download = event.getContent().body || this.$t("fallbacks.download_name");
}
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
setTimeout(function () { setTimeout(function () {

View file

@ -20,13 +20,13 @@ const routes = [
component: Home component: Home
}, },
{ {
path: '/room/:roomId?', path: '/room/:join(join/)?:roomId?',
name: 'Chat', name: 'Chat',
component: Chat, component: Chat,
meta: { meta: {
includeRoom: true, includeRoom: true,
includeFavicon: true includeFavicon: true
} },
}, },
{ {
path: '/info', path: '/info',
@ -73,7 +73,7 @@ const routes = [
props: true props: true
}, },
{ {
path: '/join/:roomId?', path: '/join/:join(join/)?:roomId?',
name: 'Join', name: 'Join',
component: Join component: Join
}, },
@ -170,7 +170,7 @@ router.beforeEach((to, from, next) => {
} }
}); });
router.getRoomLink = function (alias, roomId, roomName, mode) { router.getRoomLink = function (alias, roomId, roomName, mode, autojoin) {
let params = {}; let params = {};
if ((!alias || roomName.replace(/\s/g, "").toLowerCase() !== util.getRoomNameFromAlias(alias)) && roomName) { if ((!alias || roomName.replace(/\s/g, "").toLowerCase() !== util.getRoomNameFromAlias(alias)) && roomName) {
// There is no longer a correlation between alias and room name, probably because room name has // There is no longer a correlation between alias and room name, probably because room name has
@ -181,13 +181,14 @@ router.getRoomLink = function (alias, roomId, roomName, mode) {
// Optional mode given, append as "m" query param // Optional mode given, append as "m" query param
params["m"] = mode; params["m"] = mode;
} }
const autoJoinSegment = autojoin ? "join/" : "";
if (Object.entries(params).length > 0) { if (Object.entries(params).length > 0) {
const queryString = Object.entries(params) const queryString = Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&') .join('&')
return window.location.origin + window.location.pathname + "?" + queryString + "#/room/" + encodeURIComponent(util.sanitizeRoomId(alias || roomId)); return window.location.origin + window.location.pathname + "?" + queryString + "#/room/" + autoJoinSegment + encodeURIComponent(util.sanitizeRoomId(alias || roomId));
} }
return window.location.origin + window.location.pathname + "#/room/" + encodeURIComponent(util.sanitizeRoomId(alias || roomId)); return window.location.origin + window.location.pathname + "#/room/" + autoJoinSegment + encodeURIComponent(util.sanitizeRoomId(alias || roomId));
} }
router.getDMLink = function (user, config) { router.getDMLink = function (user, config) {

View file

@ -406,6 +406,17 @@ export default {
} }
break; break;
case "m.room.join_rules":
{
const room = this.matrixClient.getRoom(event.getRoomId());
if (room && room.getJoinRule() == "private" && room.selfMembership == "invite") {
// We have an invite to a room that's now "private"? This is most probably a deleted DM room.
// Reject the invite, i.e. call "leave" on it.
this.matrixClient.leave(room.roomId);
}
}
break;
case STATE_EVENT_ROOM_DELETED: case STATE_EVENT_ROOM_DELETED:
{ {
const room = this.matrixClient.getRoom(event.getRoomId()); const room = this.matrixClient.getRoom(event.getRoomId());
@ -494,7 +505,7 @@ export default {
// each time! // each time!
var updatedRooms = this.matrixClient.getVisibleRooms(); var updatedRooms = this.matrixClient.getVisibleRooms();
updatedRooms = updatedRooms.filter((room) => { updatedRooms = updatedRooms.filter((room) => {
return room.selfMembership && (room.selfMembership == "invite" || room.selfMembership == "join"); return room.selfMembership && (room.selfMembership == "invite" || room.selfMembership == "join") && room.currentState.getStateEvents(STATE_EVENT_ROOM_DELETED).length == 0;
}); });
updatedRooms.forEach((room) => { updatedRooms.forEach((room) => {
if (!room.avatar) { if (!room.avatar) {
@ -580,7 +591,7 @@ export default {
}, },
leaveRoom(roomId) { leaveRoom(roomId) {
return this.matrixClient.leave(roomId, undefined).then(() => { return this.matrixClient.leave(roomId).then(() => {
this.$store.commit("setCurrentRoomId", null); this.$store.commit("setCurrentRoomId", null);
this.rooms = this.rooms.filter((room) => { this.rooms = this.rooms.filter((room) => {
room.roomId != roomId; room.roomId != roomId;
@ -805,7 +816,7 @@ export default {
//console.log("Purge: set invite only"); //console.log("Purge: set invite only");
statusCallback(this.$t("room.purge_set_room_state")); statusCallback(this.$t("room.purge_set_room_state"));
withRetry(() => this.matrixClient.sendStateEvent(roomId, "m.room.join_rules", { join_rule: "invite" }, "")) withRetry(() => this.matrixClient.sendStateEvent(roomId, "m.room.join_rules", { join_rule: "private" }, ""))
.then(() => { .then(() => {
//console.log("Purge: forbid guest access"); //console.log("Purge: forbid guest access");
return withRetry(() => this.matrixClient.sendStateEvent( return withRetry(() => this.matrixClient.sendStateEvent(
@ -887,6 +898,8 @@ export default {
var invited = room.getMembersWithMembership("invite"); var invited = room.getMembersWithMembership("invite");
var allMembers = joined.concat(invited); var allMembers = joined.concat(invited);
const me = allMembers.find((m) => m.userId == self.currentUserId);
const kickFirstMember = (members) => { const kickFirstMember = (members) => {
//console.log(`Kicking ${members.length} members`); //console.log(`Kicking ${members.length} members`);
statusCallback( statusCallback(
@ -904,7 +917,16 @@ export default {
} else { } else {
// Slight pause to avoid rate limiting. // Slight pause to avoid rate limiting.
return sleep(0.1) return sleep(0.1)
.then(() => withRetry(() => this.matrixClient.kick(roomId, member.userId, "Room Deleted"))) .then(() => withRetry(() => {
if (member.membership == "invite" && me && me.powerLevel <= member.powerLevel) {
// The user is invited, but we can't kick them because of power levels.
// Send a new invite with reason set to "Room Deleted".
// The client will be sent stripped room state, and can from that see the
// join_rule of "private". It will then "leave", i.e. reject the invite.
return this.matrixClient.invite(roomId, member.userId, "Room Deleted");
}
return this.matrixClient.kick(roomId, member.userId, "Room Deleted")
}))
.then(() => kickFirstMember(members.slice(1))); .then(() => kickFirstMember(members.slice(1)));
} }
}; };