Merge branch 'main' of gitlab.com:keanuapp/keanuapp-weblite into main

This commit is contained in:
Nathan Freitas 2021-06-03 11:17:57 -04:00
commit b1530afa1e
No known key found for this signature in database
GPG key ID: A801183E69B37AA9
14 changed files with 221 additions and 47 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "keanuapp-weblite", "name": "keanuapp-weblite",
"version": "0.1.8", "version": "0.1.9",
"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.7", "version": "0.1.8",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",

View file

@ -11,7 +11,8 @@
"send": "Send", "send": "Send",
"back": "BACK", "back": "BACK",
"login": "Login", "login": "Login",
"logout": "Logout" "logout": "Logout",
"new_room": "New Room"
}, },
"message": { "message": {
"you": "You", "you": "You",
@ -47,25 +48,36 @@
"user_is_typing": "{user} is typing", "user_is_typing": "{user} is typing",
"users_are_typing": "{count} members are typing", "users_are_typing": "{count} members are typing",
"room_powerlevel_change": "{user} changed powerlevel of {changes}", "room_powerlevel_change": "{user} changed powerlevel of {changes}",
"user_powerlevel_change_from_to": "{user} from {powerOld} to {powerNew}" "user_powerlevel_change_from_to": "{user} from {powerOld} to {powerNew}",
"user_changed_guest_access_closed": "{user} disallowed guests to join the room",
"user_changed_guest_access_open": "{user} allowed guests to join the room"
}, },
"room": { "room": {
"members": "no members | 1 member | {count} members", "members": "no members | 1 member | {count} members",
"leave": "Leave" "leave": "Leave",
"purge_set_room_state": "Setting room state",
"purge_redacting_events": "Redacting events",
"purge_removing_members": "Removing members",
"purge_failed": "Failed to purge room!",
"room_list_invites": "Invites",
"room_list_rooms": "Rooms"
}, },
"room_welcome": { "room_welcome": {
"welcome": "Welcome!", "welcome": "Welcome!",
"info": "Here are a few things to know about your group:", "info": "Here are a few things to know about your group:",
"join_public": "Anyone can join by opening this link: {link}", "encrypted": "Messages are end-to-end encrypted.",
"room_history_is": "Room history is {type}.",
"join_public": "Anyone can join by opening this link: {link}.",
"join_invite": "Only people you invite can join.", "join_invite": "Only people you invite can join.",
"info_permissions": "You can change 'join permissions' and 'message history' at any time in the group settings.", "info_permissions": "You can change 'join permissions' and 'message history' at any time in the group settings.",
"got_it": "Got it" "got_it": "Got it"
}, },
"new_room": { "new_room": {
"new_room": "New Group", "new_room": "New Room",
"done": "Done", "create": "Create",
"next": "Next", "next": "Next",
"name_room": "Name group", "name_room": "Name group",
"room_topic": "Add a description if you like",
"join_permissions": "Join permissions", "join_permissions": "Join permissions",
"set_join_permissions": "Set Join Permissions", "set_join_permissions": "Set Join Permissions",
"join_permissions_info": "These permissions determine how people can join the group and how easily others can be invited. They can be changed anytime.", "join_permissions_info": "These permissions determine how people can join the group and how easily others can be invited. They can be changed anytime.",
@ -161,8 +173,7 @@
}, },
"room_info_sheet": { "room_info_sheet": {
"this_room": "This group", "this_room": "This group",
"view_details": "View details", "view_details": "View details"
"create_room": "Create group"
}, },
"voice_recorder": { "voice_recorder": {
"swipe_to_cancel": "Swipe to cancel", "swipe_to_cancel": "Swipe to cancel",

View file

@ -451,6 +451,7 @@ import RoomAvatarChanged from "./messages/RoomAvatarChanged.vue";
import RoomHistoryVisibility from "./messages/RoomHistoryVisibility.vue"; import RoomHistoryVisibility from "./messages/RoomHistoryVisibility.vue";
import RoomJoinRules from "./messages/RoomJoinRules.vue"; import RoomJoinRules from "./messages/RoomJoinRules.vue";
import RoomPowerLevelsChanged from "./messages/RoomPowerLevelsChanged.vue"; import RoomPowerLevelsChanged from "./messages/RoomPowerLevelsChanged.vue";
import RoomGuestAccessChanged from "./messages/RoomGuestAccessChanged.vue";
import RoomEncrypted from "./messages/RoomEncrypted.vue"; import RoomEncrypted from "./messages/RoomEncrypted.vue";
import DebugEvent from "./messages/DebugEvent.vue"; import DebugEvent from "./messages/DebugEvent.vue";
import util from "../plugins/utils"; import util from "../plugins/utils";
@ -530,6 +531,7 @@ export default {
RoomHistoryVisibility, RoomHistoryVisibility,
RoomJoinRules, RoomJoinRules,
RoomPowerLevelsChanged, RoomPowerLevelsChanged,
RoomGuestAccessChanged,
RoomEncrypted, RoomEncrypted,
DebugEvent, DebugEvent,
MessageOperations, MessageOperations,
@ -1049,6 +1051,9 @@ export default {
case "m.room.power_levels": case "m.room.power_levels":
return RoomPowerLevelsChanged; return RoomPowerLevelsChanged;
case "m.room.guest_access":
return RoomGuestAccessChanged;
case "m.room.encryption": case "m.room.encryption":
return RoomEncrypted; return RoomEncrypted;
} }

View file

@ -13,7 +13,7 @@
<v-icon>arrow_back</v-icon> <v-icon>arrow_back</v-icon>
<span>{{ $t("menu.back") }}</span> <span>{{ $t("menu.back") }}</span>
</v-btn> </v-btn>
<v-btn <!-- <v-btn
text text
:disabled=" :disabled="
!roomName || (step != steps.INITIAL && step != steps.CREATED) !roomName || (step != steps.INITIAL && step != steps.CREATED)
@ -24,7 +24,7 @@
<span>{{ <span>{{
step == steps.CREATED ? $t("new_room.done") : $t("new_room.next") step == steps.CREATED ? $t("new_room.done") : $t("new_room.next")
}}</span> }}</span>
</v-btn> </v-btn> -->
</v-container> </v-container>
</div> </div>
<v-container class="join-user-info"> <v-container class="join-user-info">
@ -75,14 +75,16 @@
</v-container> </v-container>
<v-container fluid style="margin-top: 40px"> <v-container fluid style="margin-top: 40px">
<v-row> <v-row align="center">
<v-col cols="auto"> <v-col align="center">
<v-avatar size="50" color="#ededed" @click.stop="showAvatarPicker"> <v-avatar size="50" color="#ededed" @click.stop="showAvatarPicker">
<v-img v-if="roomAvatar" :src="roomAvatar" /> <v-img v-if="roomAvatar" :src="roomAvatar" />
<v-icon v-else>camera_alt</v-icon> <v-icon v-else>camera_alt</v-icon>
</v-avatar> </v-avatar>
</v-col> </v-col>
<v-col> </v-row>
<v-row cols="12" align="center">
<v-col cols="8" offset="2" align="center">
<v-text-field <v-text-field
v-model="roomName" v-model="roomName"
:label="$t('new_room.name_room')" :label="$t('new_room.name_room')"
@ -91,13 +93,30 @@
v-on:keyup.enter="$refs.topic.focus()" v-on:keyup.enter="$refs.topic.focus()"
:disabled="step > steps.INITIAL" :disabled="step > steps.INITIAL"
></v-text-field> ></v-text-field>
<v-text-field
v-model="roomTopic"
v-show="roomName.length > 0"
:label="$t('new_room.room_topic')"
color="black"
background-color="white"
v-on:keyup.enter="$refs.create.focus()"
:disabled="step > steps.INITIAL"
></v-text-field>
<v-btn
color="black"
depressed
class="filled-button"
@click.stop="next"
:disabled="roomName.length == 0"
>{{$t('new_room.create')}}</v-btn
>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
<v-fade-transition> <v-fade-transition>
<div class="section ma-3" flat v-if="step > steps.INITIAL"> <!-- <div class="section ma-3" flat v-if="step > steps.INITIAL"> -->
<div class="h4 text-left">{{ $t("new_room.join_permissions") }}</div> <!-- <div class="h4 text-left">{{ $t("new_room.join_permissions") }}</div>
<div class="h2 text-left"> <div class="h2 text-left">
{{ $t("new_room.set_join_permissions") }} {{ $t("new_room.set_join_permissions") }}
</div> </div>
@ -168,9 +187,9 @@
<div v-if="publicRoomLinkCopied" class="link-copied"> <div v-if="publicRoomLinkCopied" class="link-copied">
{{ $t("new_room.link_copied") }} {{ $t("new_room.link_copied") }}
</div> </div>
-->
<div v-if="status">{{ status }}</div> <div v-if="status">{{ status }}</div>
</div> <!-- </div> -->
</v-fade-transition> </v-fade-transition>
<input <input
ref="avatar" ref="avatar"
@ -188,7 +207,7 @@ import util from "../plugins/utils";
const steps = Object.freeze({ const steps = Object.freeze({
INITIAL: 0, INITIAL: 0,
NAME_SET: 1, //NAME_SET: 1,
CREATING: 2, CREATING: 2,
CREATED: 3, CREATED: 3,
}); });
@ -273,8 +292,8 @@ export default {
if (this.step == steps.CREATED) { if (this.step == steps.CREATED) {
this.openRoom(); this.openRoom();
} else if (this.step == steps.INITIAL) { } else if (this.step == steps.INITIAL) {
this.step = steps.NAME_SET; // this.step = steps.NAME_SET;
} else if (this.step == steps.NAME_SET) { //} else if (this.step == steps.NAME_SET) {
// Create room with deafult setting // Create room with deafult setting
this.createRoom().then((roomId) => { this.createRoom().then((roomId) => {
this.roomId = roomId; this.roomId = roomId;
@ -476,7 +495,7 @@ export default {
(error.data && error.data.error) || (error.data && error.data.error) ||
error.message || error.message ||
error.toString(); error.toString();
this.step = steps.NAME_SET; // revert this.step = steps.INITIAL; // revert
return null; return null;
}); });
}, },

View file

@ -1,12 +1,28 @@
<template> <template>
<div class="created-room-welcome-header"> <div class="created-room-welcome-header">
<div class="h4">{{$t('room_welcome.welcome')}}</div> <div class="h4">{{ $t("room_welcome.welcome") }}</div>
<div class="mt-2">{{$t('room_welcome.info')}}</div> <div class="mt-2">{{ $t("room_welcome.info") }}</div>
<div class="mt-2" v-if="roomJoinRule == 'public'">{{$t('room_welcome.join_public', {link: publicRoomLink}) }}</div> <div class="mt-2" v-if="roomIsEncrypted">
<div class="mt-2" v-else-if="roomJoinRule == 'invite'">{{$t('room_welcome.join_invite')}}</div> {{ $t("room_welcome.encrypted") }}
<div class="mt-2">{{$t('room_welcome.info_permissions')}}</div> </div>
<div class="mt-2" v-if="roomHistoryDescription">
{{ roomHistoryDescription }}
</div>
<div class="mt-2" v-if="roomJoinRule == 'public'">
<i18n path="room_welcome.join_public" tag="span">
<template v-slot:link>
<a :href="publicRoomLink">{{ publicRoomLink }}</a>
</template>
</i18n>
</div>
<div class="mt-2" v-else-if="roomJoinRule == 'invite'">
{{ $t("room_welcome.join_invite") }}
</div>
<div class="mt-2">{{ $t("room_welcome.info_permissions") }}</div>
<div class="text-right"> <div class="text-right">
<v-btn text @click.stop="$emit('close')">{{$t('room_welcome.got_it')}}</v-btn> <v-btn text @click.stop="$emit('close')">{{
$t("room_welcome.got_it")
}}</v-btn>
</div> </div>
</div> </div>
</template> </template>
@ -17,6 +33,30 @@ import roomInfoMixin from "./roomInfoMixin";
export default { export default {
name: "CreatedRoomWelcomeHeader", name: "CreatedRoomWelcomeHeader",
mixins: [roomInfoMixin], mixins: [roomInfoMixin],
computed: {
roomHistoryDescription() {
const visibility = this.$matrix.getRoomHistoryVisibility(this.room);
switch (visibility) {
case "world_readable":
return this.$t("room_welcome.room_history_is", {
type: this.$t("message.room_history_world_readable"),
});
case "shared":
return this.$t("room_welcome.room_history_is", {
type: this.$t("message.room_history_shared"),
});
case "invited":
return this.$t("room_welcome.room_history_is", {
type: this.$t("message.room_history_invited"),
});
case "joined":
return this.$t("room_welcome.room_history_is", {
type: this.$t("message.room_history_joined"),
});
}
return null;
},
},
}; };
</script> </script>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="pa-4"> <div class="pa-4">
<RoomList showInvites /> <RoomList showInvites showCreate :title="$t('room.room_list_rooms')" :invitesTitle="$t('room.room_list_invites')" v-on:newroom="createRoom" />
<v-btn block depressed class="outlined-button" @click.stop="logout">{{$t('menu.logout')}}</v-btn> <v-btn block depressed class="outlined-button" @click.stop="logout">{{$t('menu.logout')}}</v-btn>
<!-- Loading indicator --> <!-- Loading indicator -->
@ -42,6 +42,10 @@ export default {
this.$navigation.push({path: "/login"}, -1); this.$navigation.push({path: "/login"}, -1);
}) })
}, },
createRoom() {
this.$navigation.push({ name: "CreateRoom" });
}
} }
}; };
</script> </script>

View file

@ -3,9 +3,12 @@
<div class="dialog-content text-center"> <div class="dialog-content text-center">
<template> <template>
<v-icon color="black" size="30">lock</v-icon> <v-icon color="black" size="30">lock</v-icon>
<h2 class="dialog-title">{{$t('purge_room.title')}}</h2> <h2 class="dialog-title">{{ $t("purge_room.title") }}</h2>
<div class="dialog-text"> <div class="dialog-text">
{{$t('purge_room.info')}} {{ $t("purge_room.info") }}
</div>
<div class="dialog-text">
{{ status }}
</div> </div>
</template> </template>
<v-container fluid> <v-container fluid>
@ -16,8 +19,9 @@
text text
block block
class="text-button" class="text-button"
:disabled="isPurging"
@click="showDialog = false" @click="showDialog = false"
>{{$t('menu.cancel')}}</v-btn >{{ $t("menu.cancel") }}</v-btn
> >
</v-col> </v-col>
<v-col cols="6" align="center"> <v-col cols="6" align="center">
@ -26,8 +30,9 @@
depressed depressed
block block
class="filled-button" class="filled-button"
:disabled="isPurging"
@click.stop="onPurgeRoom()" @click.stop="onPurgeRoom()"
>{{$t('purge_room.button')}}</v-btn >{{ $t("purge_room.button") }}</v-btn
> >
</v-col> </v-col>
</v-row> </v-row>
@ -52,6 +57,8 @@ export default {
data() { data() {
return { return {
showDialog: false, showDialog: false,
isPurging: false,
status: null,
}; };
}, },
watch: { watch: {
@ -69,8 +76,9 @@ export default {
methods: { methods: {
onPurgeRoom() { onPurgeRoom() {
this.isPurging = true;
this.$matrix this.$matrix
.purgeRoom(this.room.roomId) .purgeRoom(this.room.roomId, this.onPurgeStatus)
.then(() => { .then(() => {
this.showDialog = false; this.showDialog = false;
console.log("Purged room"); console.log("Purged room");
@ -78,8 +86,16 @@ export default {
}) })
.catch((err) => { .catch((err) => {
console.error("Error purging", err); console.error("Error purging", err);
this.status = this.$t("room.purge_failed");
})
.finally(() => {
this.isPurging = false;
}); });
}, },
onPurgeStatus(message) {
this.status = message;
},
}, },
}; };
</script> </script>

View file

@ -21,14 +21,7 @@
>{{$t('room_info_sheet.view_details')}}</v-btn >{{$t('room_info_sheet.view_details')}}</v-btn
> >
</div> </div>
<room-list :title="'Other groups'" v-on:close="close" /> <room-list :title="'Other groups'" v-on:close="close" v-on:newroom="createRoom" :showCreate="true" />
<v-btn
height="20px"
color="black"
class="outlined-button"
@click.stop="createRoom"
>{{$t('room_info_sheet.create_room')}}</v-btn
>
</div> </div>
</BottomSheet> </BottomSheet>
</template> </template>

View file

@ -1,6 +1,6 @@
<template> <template>
<v-list dense class="room-list"> <v-list dense class="room-list">
<div v-if="showInvites" class="h4">{{invitesTitle}}</div> <div v-if="showInvites && $matrix.invites.length > 0" class="h4">{{invitesTitle}}</div>
<v-list-item-group v-if="showInvites" v-model="currentRoomId" color="primary"> <v-list-item-group v-if="showInvites" v-model="currentRoomId" color="primary">
<v-slide-y-transition group> <v-slide-y-transition group>
<v-list-item :disabled="roomsProcessing[room.roomId]" v-for="room in $matrix.invites" :key="room.roomId" :value="room.roomId"> <v-list-item :disabled="roomsProcessing[room.roomId]" v-for="room in $matrix.invites" :key="room.roomId" :value="room.roomId">
@ -20,6 +20,15 @@
</v-list-item-group> </v-list-item-group>
<div class="h4">{{title}}</div> <div class="h4">{{title}}</div>
<v-list-item-group v-model="currentRoomId" color="primary"> <v-list-item-group v-model="currentRoomId" color="primary">
<v-list-item v-if="showCreate" @click.stop="$emit('newroom')">
<v-list-item-avatar size="40" color="#e0e0e0">
<v-img />
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{$t('menu.new_room')}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item v-for="room in $matrix.joinedRooms" :key="room.roomId" :value="room.roomId"> <v-list-item v-for="room in $matrix.joinedRooms" :key="room.roomId" :value="room.roomId">
<v-list-item-avatar size="40" color="#e0e0e0"> <v-list-item-avatar size="40" color="#e0e0e0">
<v-img :src="room.avatar" /> <v-img :src="room.avatar" />
@ -53,6 +62,10 @@ export default {
showInvites: { showInvites: {
type: Boolean, type: Boolean,
default: false default: false
},
showCreate: {
type: Boolean,
default: false
} }
}, },
@ -103,6 +116,10 @@ export default {
watch: { watch: {
currentRoomId() { currentRoomId() {
if (this.currentRoomId == null) {
// Ignore, this is caused by "new room" etc.
return;
}
this.$emit("close"); this.$emit("close");
this.$navigation.push({name: 'Chat', params: { roomId: util.sanitizeRoomId(this.currentRoomId) }}, -1); this.$navigation.push({name: 'Chat', params: { roomId: util.sanitizeRoomId(this.currentRoomId) }}, -1);
}, },

View file

@ -0,0 +1,34 @@
<template>
<div class="statusEvent">
{{
openToGuests
? $t("message.user_changed_guest_access_open", {
user: stateEventDisplayName(event),
})
: $t("message.user_changed_guest_access_closed", {
user: stateEventDisplayName(event),
})
}}
</div>
</template>
<script>
import messageMixin from "./messageMixin";
export default {
mixins: [messageMixin],
computed: {
openToGuests() {
const access = this.event.getContent().guest_access;
if (access && access == "forbidden") {
return false;
}
return true;
},
},
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>

View file

@ -41,6 +41,13 @@ export default {
return ""; return "";
}, },
roomIsEncrypted() {
if (this.room) {
return this.$matrix.matrixClient.isRoomEncrypted(this.room.roomId);
}
return false;
},
publicRoomLink() { publicRoomLink() {
if (this.room && this.roomJoinRule == "public") { if (this.room && this.roomJoinRule == "public") {
return this.$router.getRoomLink( return this.$router.getRoomLink(
@ -49,6 +56,13 @@ export default {
} }
return null; return null;
}, },
roomHistory() {
if (this.room) {
return this.room.shouldEncryptForInvitedMembers()
}
return false;
}
}, },
watch: { watch: {
room: { room: {

View file

@ -24,7 +24,7 @@ Vue.config.productionTip = false
Vue.use(VueResize); Vue.use(VueResize);
Vue.use(VEmojiPicker); Vue.use(VEmojiPicker);
Vue.use(matrix, { store: store }); Vue.use(matrix, { store: store, i18n: i18n });
Vue.use(cleaninsights); Vue.use(cleaninsights);
Vue.use(VueClipboard); Vue.use(VueClipboard);

View file

@ -15,10 +15,11 @@ export default {
} }
const store = options.store; const store = options.store;
const i18n = options.i18n;
const matrixService = new Vue({ const matrixService = new Vue({
store, store,
i18n,
data() { data() {
return { return {
matrixClient: null, matrixClient: null,
@ -440,6 +441,17 @@ export default {
return null; return null;
}, },
getRoomHistoryVisibility(room) {
if (room) {
const historyVisibility = room.currentState.getStateEvents(
"m.room.history_visibility",
""
);
return historyVisibility && historyVisibility.getContent().history_visibility;
}
return null;
},
leaveRoom(roomId) { leaveRoom(roomId) {
return this.matrixClient.leave(roomId, undefined) return this.matrixClient.leave(roomId, undefined)
.then(() => { .then(() => {
@ -460,7 +472,8 @@ export default {
* - Kick all members * - Kick all members
* @param roomId * @param roomId
*/ */
purgeRoom(roomId) { purgeRoom(roomId, statusCallback) {
const oldGlobalErrorSetting = this.matrixClient.getGlobalErrorOnUnknownDevices();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const room = this.getRoom(roomId); const room = this.getRoom(roomId);
if (!room) { if (!room) {
@ -476,6 +489,7 @@ export default {
const self = this; const self = this;
console.log("Purge: set invite only"); console.log("Purge: set invite only");
statusCallback(this.$t('room.purge_set_room_state'));
this.matrixClient.sendStateEvent( this.matrixClient.sendStateEvent(
roomId, roomId,
"m.room.join_rules", "m.room.join_rules",
@ -519,6 +533,9 @@ export default {
}) })
.then(() => { .then(() => {
console.log("Purge: redact events"); console.log("Purge: redact events");
statusCallback(this.$t('room.purge_redacting_events'));
// First ignore unknown device errors
this.matrixClient.setGlobalErrorOnUnknownDevices(false);
var redactionPromises = []; var redactionPromises = [];
timelineWindow.getEvents().forEach(event => { timelineWindow.getEvents().forEach(event => {
if (!event.isRedacted() && !event.isRedaction() && !event.isState()) { if (!event.isRedacted() && !event.isRedaction() && !event.isState()) {
@ -530,6 +547,7 @@ export default {
}) })
.then(() => { .then(() => {
console.log("Purge: kick members"); console.log("Purge: kick members");
statusCallback(this.$t('room.purge_removing_members'));
var joined = room.getMembersWithMembership("join"); var joined = room.getMembersWithMembership("join");
var invited = room.getMembersWithMembership("invite"); var invited = room.getMembersWithMembership("invite");
var members = joined.concat(invited); var members = joined.concat(invited);
@ -543,10 +561,13 @@ export default {
return Promise.all(kickPromises); return Promise.all(kickPromises);
}) })
.then(() => { .then(() => {
statusCallback(null);
this.matrixClient.setGlobalErrorOnUnknownDevices(oldGlobalErrorSetting);
resolve(true); // Done! resolve(true); // Done!
}) })
.catch((err) => { .catch((err) => {
console.error("Error purging room", err); console.error("Error purging room", err);
this.matrixClient.setGlobalErrorOnUnknownDevices(oldGlobalErrorSetting);
reject(err); reject(err);
}); });
}) })