Merge branch 'dev'
This commit is contained in:
commit
69e8afd5be
82 changed files with 2841 additions and 815 deletions
|
|
@ -52,6 +52,7 @@ The app loads runtime configutation from the server at "./config.json" and merge
|
|||
* **logo** - An url or base64-encoded image data url that represents the app logotype.
|
||||
* **accentColor** - The accent color of the app UI. Use a HTML-style color value string, like "#ff0080".
|
||||
* **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.
|
||||
|
||||
|
||||
### Sticker short codes - To enable sticker short codes, follow these steps:
|
||||
|
|
|
|||
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -28,7 +28,7 @@
|
|||
"jszip": "^3.9.1",
|
||||
"linkify-html": "^4.1.0",
|
||||
"linkifyjs": "^4.1.0",
|
||||
"material-design-icons-iconfont": "^6.1",
|
||||
"material-design-icons-iconfont": "^6.7.0",
|
||||
"matrix-js-sdk": "^23.4.0",
|
||||
"md-gum-polyfill": "^1.0.0",
|
||||
"mic-recorder-to-mp3": "^2.2.2",
|
||||
|
|
@ -9936,9 +9936,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/material-design-icons-iconfont": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.1.0.tgz",
|
||||
"integrity": "sha512-wRJtOo1v1ch+gN8PRsj0IGJznk+kQ8mz13ds/nuhLI+Qyf/931ZlRpd92oq0IRPpZIb+bhX8pRjzIVdcPDKmiQ=="
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz",
|
||||
"integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA=="
|
||||
},
|
||||
"node_modules/matrix-events-sdk": {
|
||||
"version": "0.0.1",
|
||||
|
|
@ -24023,9 +24023,9 @@
|
|||
}
|
||||
},
|
||||
"material-design-icons-iconfont": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.1.0.tgz",
|
||||
"integrity": "sha512-wRJtOo1v1ch+gN8PRsj0IGJznk+kQ8mz13ds/nuhLI+Qyf/931ZlRpd92oq0IRPpZIb+bhX8pRjzIVdcPDKmiQ=="
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz",
|
||||
"integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA=="
|
||||
},
|
||||
"matrix-events-sdk": {
|
||||
"version": "0.0.1",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
"jszip": "^3.9.1",
|
||||
"linkify-html": "^4.1.0",
|
||||
"linkifyjs": "^4.1.0",
|
||||
"material-design-icons-iconfont": "^6.1",
|
||||
"material-design-icons-iconfont": "^6.7.0",
|
||||
"matrix-js-sdk": "^23.4.0",
|
||||
"md-gum-polyfill": "^1.0.0",
|
||||
"mic-recorder-to-mp3": "^2.2.2",
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@
|
|||
<link rel="icon" id="favicon" href="<%= BASE_URL %>favicon.ico" />
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon" href="<%= BASE_URL %>icons/icon-72x72.png" sizes="72x72" />
|
||||
<link rel="apple-touch-icon" href="<%= BASE_URL %>icons/icon-96x96.png" sizes="96x96" />
|
||||
<link rel="apple-touch-icon" href="<%= BASE_URL %>icons/icon-128x128.png" sizes="128x128" />
|
||||
<link rel="apple-touch-icon" href="<%= BASE_URL %>icons/icon-144x144.png" sizes="144x144" />
|
||||
<link rel="apple-touch-icon" href="<%= BASE_URL %>icons/icon-152x152.png" sizes="152x152" />
|
||||
<link rel="apple-touch-icon" href="<%= BASE_URL %>icons/icon-192x192.png" sizes="192x192" />
|
||||
<link rel="apple-touch-icon" href="<%= BASE_URL %>icons/icon-384x384.png" sizes="384x384" />
|
||||
<link rel="apple-touch-icon" href="<%= BASE_URL %>icons/icon-512x512.png" sizes="512x512" />
|
||||
<link rel="manifest" href="<%= BASE_URL %>manifest.json" />
|
||||
<link rel="apple-touch-icon" href="./icons/icon-72x72.png" sizes="72x72" />
|
||||
<link rel="apple-touch-icon" href="./icons/icon-96x96.png" sizes="96x96" />
|
||||
<link rel="apple-touch-icon" href="./icons/icon-128x128.png" sizes="128x128" />
|
||||
<link rel="apple-touch-icon" href="./icons/icon-144x144.png" sizes="144x144" />
|
||||
<link rel="apple-touch-icon" href="./icons/icon-152x152.png" sizes="152x152" />
|
||||
<link rel="apple-touch-icon" href="./icons/icon-192x192.png" sizes="192x192" />
|
||||
<link rel="apple-touch-icon" href="./icons/icon-384x384.png" sizes="384x384" />
|
||||
<link rel="apple-touch-icon" href="./icons/icon-512x512.png" sizes="512x512" />
|
||||
<link rel="manifest" href="./manifest.json" />
|
||||
<script src="./lottie-player.js"></script>
|
||||
<style>
|
||||
#loader {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"start_url": ".",
|
||||
"name": "Convene - Chat for everyone ",
|
||||
"short_name": "Convene",
|
||||
"theme_color": "#FFFFFF",
|
||||
|
|
@ -8,42 +7,42 @@
|
|||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/icon-72x72.png",
|
||||
"src": "./icons/icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-96x96.png",
|
||||
"src": "./icons/icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-128x128.png",
|
||||
"src": "./icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-144x144.png",
|
||||
"src": "./icons/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-152x152.png",
|
||||
"src": "./icons/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-192x192.png",
|
||||
"src": "./icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-384x384.png",
|
||||
"src": "./icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icons/icon-512x512.png",
|
||||
"src": "./icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
|
|
|
|||
19
public/sw.js
19
public/sw.js
|
|
@ -1,8 +1,6 @@
|
|||
// Notification click event listener
|
||||
self.addEventListener("notificationclick", (e) => {
|
||||
// Close the notification popout
|
||||
e.notification.close();
|
||||
// Get all the Window clients
|
||||
e.waitUntil(
|
||||
clients.matchAll({ type: "window" }).then((clientsArr) => {
|
||||
// If a Window tab matching the targeted URL already exists, focus that;
|
||||
|
|
@ -19,4 +17,19 @@ self.addEventListener("notificationclick", (e) => {
|
|||
.then((windowClient) => (windowClient ? windowClient.focus() : null));
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function checkNewMessages() {
|
||||
const cachedCredentials = await caches.open('cachedCredentials');
|
||||
// Todo...
|
||||
}
|
||||
|
||||
// Install PWA in mobile or web to test if periodicSync notification works
|
||||
// see browser compatibility: https://developer.mozilla.org/en-US/docs/Web/API/Web_Periodic_Background_Synchronization_API#browser_compatibility
|
||||
self.addEventListener('periodicsync', (event) => {
|
||||
if (event.tag === 'check-new-messages') {
|
||||
self.registration.showNotification("Notification via periodicSync");
|
||||
|
||||
event.waitUntil(checkNewMessages());
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
import stickers from "./plugins/stickers";
|
||||
import { registerServiceWorker, notificationCount, windowNotificationPermission } from "./plugins/notificationAndServiceWorker.js"
|
||||
import logoMixin from "./components/logoMixin";
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
|
|
@ -175,13 +176,16 @@ export default {
|
|||
}
|
||||
return favicon;
|
||||
},
|
||||
...mapState([
|
||||
'globalNotification'
|
||||
])
|
||||
},
|
||||
watch: {
|
||||
notificationCount: {
|
||||
handler(nCount) {
|
||||
// windowNotificationPermission
|
||||
// return value: 'granted', 'default', 'denied'
|
||||
if (nCount > 0 && this.windowNotificationPermission() === "granted") {
|
||||
if (this.globalNotification && nCount > 0 && this.windowNotificationPermission() === "granted") {
|
||||
this.showNotification()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ $app-background: #f6f6f6;
|
|||
$main-desktop-width: 900px;
|
||||
$dialog-desktop-width: 940px;
|
||||
$very-very-purple: #536dfe;
|
||||
$lighter-gray: #FDFBF9;
|
||||
|
||||
$chat-background: $background;
|
||||
$chat-standard-padding: 32px;
|
||||
|
|
|
|||
|
|
@ -740,8 +740,7 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.message-operations-strut,
|
||||
.avatar-operations-strut {
|
||||
.message-operations-strut {
|
||||
position: relative;
|
||||
height: 0px;
|
||||
z-index: 10;
|
||||
|
|
@ -757,16 +756,6 @@ body {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.avatar-operations {
|
||||
position: absolute;
|
||||
width: fit-content;
|
||||
background-color: white;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
padding: 0px 20px;
|
||||
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.send-options {
|
||||
z-index: 11; // Above mic button
|
||||
}
|
||||
|
|
@ -783,12 +772,23 @@ body {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.download-text {
|
||||
width: 100%;
|
||||
color: white;
|
||||
}
|
||||
.download-size {
|
||||
font-size: 70%;
|
||||
color: white;
|
||||
}
|
||||
.download-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.room-name,
|
||||
|
|
@ -911,8 +911,9 @@ body {
|
|||
}
|
||||
|
||||
.room-info {
|
||||
background-color: #e8e8e8;
|
||||
background-color: $lighter-gray;
|
||||
height: 100%;
|
||||
|
||||
.chat-header {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
|
@ -967,6 +968,7 @@ body {
|
|||
.v-card {
|
||||
background-color: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
.member .user-name {
|
||||
|
|
@ -983,14 +985,25 @@ body {
|
|||
margin-left: 38px;
|
||||
}
|
||||
|
||||
.member::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
margin: 10px 0px;
|
||||
bottom: 0px;
|
||||
height: 1px;
|
||||
background-color: #e1e1e1;
|
||||
width: 100%;
|
||||
.member.v-list-item {
|
||||
height: 56px;
|
||||
|
||||
&::before {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.user-icon-with-badge {
|
||||
display: inline;
|
||||
|
||||
.user-badge {
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.1);
|
||||
position: absolute;
|
||||
left: 35px;
|
||||
top: 30px;
|
||||
background: #FFFFFF;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.show-all {
|
||||
|
|
@ -998,6 +1011,7 @@ body {
|
|||
font-size: 14 * $chat-text-size;
|
||||
font-weight: bold;
|
||||
margin-left: 10px;
|
||||
padding: 10px;
|
||||
[dir="rtl"] & {
|
||||
margin-left: initial;
|
||||
margin-right: 10px;
|
||||
|
|
@ -1007,6 +1021,22 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
.member-action-dialog {
|
||||
.user-name {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.v-list-item__title {
|
||||
color:#074EE8;
|
||||
font-size: 16px !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.v-list-item__subtitle {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.with-right-label {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
|
|
@ -1022,6 +1052,8 @@ body {
|
|||
|
||||
.option-text {
|
||||
font-size: 13 * $chat-text-size;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.option-title {
|
||||
|
|
@ -1060,7 +1092,7 @@ body {
|
|||
}
|
||||
|
||||
.profile {
|
||||
background-color: #e8e8e8;
|
||||
background-color: $lighter-gray;
|
||||
height: 100%;
|
||||
.chat-header {
|
||||
background-color: transparent;
|
||||
|
|
@ -1070,6 +1102,7 @@ body {
|
|||
.v-card {
|
||||
background-color: white;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
@import "@/assets/css/main.scss";
|
||||
|
||||
.getlink-root {
|
||||
.create-root {
|
||||
background-color: $background;
|
||||
|
||||
.getlink-loggedin {
|
||||
.create-loggedin {
|
||||
text-align: center;
|
||||
button {
|
||||
min-width: 200px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.getlink-image {
|
||||
.create-image {
|
||||
text-align: center;
|
||||
max-width: 325px;
|
||||
max-height: 257px;
|
||||
|
|
@ -21,7 +21,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.getlink-title {
|
||||
.create-image-small {
|
||||
text-align: center;
|
||||
max-width: 72px;
|
||||
max-height: 72px;
|
||||
width: 100%;
|
||||
.v-icon__component {
|
||||
width: unset;
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.create-title {
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-family: "Poppins";
|
||||
|
|
@ -34,7 +45,7 @@
|
|||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.getlink-info {
|
||||
.create-info {
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-feature-settings: "clig" off, "liga" off;
|
||||
|
|
@ -47,7 +58,7 @@
|
|||
margin: 15px 9px 40px 9px;
|
||||
}
|
||||
|
||||
.getlink-subtitle {
|
||||
.create-subtitle {
|
||||
color: #000;
|
||||
text-align: center;
|
||||
font-feature-settings: "clig" off, "liga" off;
|
||||
|
|
@ -95,7 +106,7 @@
|
|||
color: black;
|
||||
}
|
||||
|
||||
.getlink-share {
|
||||
.create-share {
|
||||
color: #161616;
|
||||
text-align: center;
|
||||
font-family: "Inter";
|
||||
|
|
@ -118,7 +129,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.getlink-buttons {
|
||||
.create-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
5
src/assets/icons/direct_chat.vue
Normal file
5
src/assets/icons/direct_chat.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.455 1.15172C17.5599 0.837267 17.478 0.490627 17.2437 0.256285C17.0093 0.0219423 16.6627 -0.0598701 16.3483 0.0449199L0.598305 5.29492C0.247955 5.41168 0.00864239 5.7359 0.000224888 6.10512C-0.00811491 6.4744 0.216175 6.80909 0.560907 6.94171L7.78123 9.71878L10.5583 16.9391C10.6909 17.2838 11.0255 17.5081 11.3948 17.4997C11.764 17.4914 12.0883 17.2521 12.205 16.9016L17.455 1.15172Z" fill="#242424"/>
|
||||
</svg>
|
||||
</template>
|
||||
8
src/assets/icons/ic_apk.vue
Normal file
8
src/assets/icons/ic_apk.vue
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="56" height="56" rx="13" fill="#FCFCFC" />
|
||||
<path
|
||||
d="M19.8333 24.3817C19.8333 23.4434 19.0217 22.665 18.0833 22.6667C17.1433 22.665 16.3333 23.4434 16.3333 24.3834V31.5067C16.3333 32.4467 17.1433 33.1667 18.0833 33.1667C19.0217 33.1667 19.8333 32.4484 19.8333 31.5067V24.3817ZM35 22.6667H21L21.0017 33.685C21.0017 34.69 21.8133 35.5 22.8183 35.5H23.3333V39.6317C23.3333 40.57 24.1567 41.3334 25.095 41.3334C26.035 41.3334 26.8333 40.57 26.8333 39.6317V35.5H29.1667V39.6317C29.1667 40.57 29.9933 41.3334 30.93 41.3334C31.87 41.3334 32.6667 40.57 32.6667 39.6317V35.5H33.1867C34.1867 35.5 35.0017 34.6884 35.0017 33.6834L35 22.6667ZM35 21.4967C35 18.865 33.595 17.0567 31.4433 15.8517L32.54 13.6834C32.5967 13.57 32.56 13.4267 32.4517 13.3617C32.345 13.3 32.2133 13.3417 32.155 13.4567L31.05 15.6467C29.265 14.7867 26.9167 14.6984 24.9483 15.6467L23.8417 13.455C23.785 13.3417 23.6533 13.2984 23.5467 13.36C23.44 13.425 23.4017 13.5684 23.46 13.6834L24.5567 15.8517C22.405 17.0567 21 18.8667 21 21.4967H35ZM39.6667 24.3817C39.6667 23.4434 38.8567 22.665 37.9167 22.6667C36.9783 22.665 36.1667 23.4434 36.1667 24.3834V31.5067C36.1667 32.4467 36.9767 33.1667 37.9167 33.1667C38.8567 33.1667 39.6667 32.4484 39.6667 31.5067V24.3817Z"
|
||||
fill="#1D1D1D" />
|
||||
</svg>
|
||||
</template>
|
||||
13
src/assets/icons/ic_eye.vue
Normal file
13
src/assets/icons/ic_eye.vue
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<template>
|
||||
<svg width="21" height="15" viewBox="0 0 21 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M10.592 14.2365H10.5916L10.492 14.2358L10.4083 14.2365C7.13997 14.2365 3.92668 12.2232 0.296132 7.90096C0.101322 7.66909 0.101322 7.33083 0.296132 7.09895C3.92687 2.77668 7.13997 0.763603 10.4081 0.763603L10.5081 0.764277L10.592 0.763603C13.8601 0.763603 17.0734 2.77687 20.704 7.09895C20.8988 7.33083 20.8988 7.66909 20.704 7.90096C17.0732 12.2234 13.8601 14.2365 10.5922 14.2365H10.592ZM10.4919 12.9889L10.5917 12.9896C13.3659 12.9896 16.1761 11.2425 19.4076 7.5001C16.1761 3.7578 13.3663 2.01076 10.5919 2.01076L10.508 2.01144L10.4079 2.01076C7.63368 2.01076 4.82366 3.75784 1.59216 7.5001C4.82366 11.2428 7.63349 12.9896 10.4081 12.9896L10.4919 12.9889Z"
|
||||
fill="black" />
|
||||
<path
|
||||
d="M10.5 11.4495C8.32223 11.4495 6.55066 9.67775 6.55066 7.5C6.55066 5.32209 8.32231 3.55051 10.5 3.55051C12.6779 3.55051 14.4494 5.32224 14.4494 7.5C14.4494 9.67791 12.6776 11.4495 10.5 11.4495ZM10.5 4.7975C9.00982 4.7975 7.79745 6.00987 7.79745 7.5C7.79745 8.99013 9.00982 10.2025 10.5 10.2025C11.9901 10.2025 13.2025 8.99013 13.2025 7.5C13.2025 6.00987 11.9901 4.7975 10.5 4.7975Z"
|
||||
fill="black" />
|
||||
<path
|
||||
d="M8.61532 7.91553C8.38576 7.91553 8.19971 7.72948 8.19971 7.49992C8.19971 6.23147 9.23149 5.19954 10.4999 5.19954C10.7295 5.19954 10.9155 5.38559 10.9155 5.61515C10.9155 5.84471 10.7295 6.03076 10.4999 6.03076C9.6898 6.03076 9.03087 6.68969 9.03087 7.49979C9.03087 7.72956 8.84482 7.91561 8.61526 7.91561L8.61532 7.91553Z"
|
||||
fill="black" />
|
||||
</svg>
|
||||
</template>
|
||||
8
src/assets/icons/ic_ipa.vue
Normal file
8
src/assets/icons/ic_ipa.vue
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="56" height="56" rx="13" fill="#FCFCFC" />
|
||||
<path
|
||||
d="M33.31 13C33.6517 16.1183 30.865 19.3717 27.965 19.145C27.6067 16.4217 30.1683 13.1267 33.31 13ZM32.9283 39.6317C31.1417 39.6667 30.5683 38.5733 28.5267 38.5733C26.485 38.5733 25.8483 39.6 24.1583 39.6667C21.3 39.775 16.89 33.19 16.89 27.4483C16.89 22.1733 20.565 19.5583 23.7767 19.5117C25.4983 19.48 27.125 20.6717 28.1767 20.6717C29.2317 20.6717 31.2083 19.2383 33.285 19.45C34.1517 19.485 36.5917 19.8 38.1617 22.0917C34.0017 24.8033 34.65 30.4783 39.1117 32.565C38.2383 35.0967 35.6233 39.5833 32.9283 39.6317Z"
|
||||
fill="#1D1D1D" />
|
||||
</svg>
|
||||
</template>
|
||||
8
src/assets/icons/ic_pdf.vue
Normal file
8
src/assets/icons/ic_pdf.vue
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="56" height="56" rx="13" fill="#FCFCFC" />
|
||||
<path
|
||||
d="M20.7408 23.42H18.0965C17.8358 23.42 17.5858 23.5235 17.4014 23.7078C17.217 23.8923 17.1135 24.1422 17.1135 24.403V31.4257C17.1135 31.7769 17.3008 32.1014 17.605 32.2769C17.9091 32.4526 18.2838 32.4526 18.588 32.2769C18.8921 32.1014 19.0795 31.7769 19.0795 31.4257V28.9033H20.7408C21.4683 28.8996 22.1649 28.6081 22.678 28.0922C23.1913 27.5764 23.4794 26.8785 23.4794 26.1509C23.4852 25.4234 23.1983 24.7242 22.6832 24.2107C22.1681 23.6971 21.4682 23.4121 20.7408 23.4201L20.7408 23.42ZM20.7408 26.9312H19.0795V25.386H20.7408C20.9471 25.381 21.1466 25.4609 21.2925 25.6068C21.4384 25.7529 21.5182 25.9522 21.5134 26.1586C21.515 26.3645 21.4343 26.5626 21.2891 26.7088C21.1442 26.8549 20.9467 26.9372 20.7408 26.9372L20.7408 26.9312ZM28.4947 23.4081H25.8405C25.5797 23.4081 25.3296 23.5117 25.1453 23.6961C24.961 23.8804 24.8575 24.1305 24.8575 24.3912V31.4352C24.8575 31.6958 24.961 31.9459 25.1453 32.1302C25.3296 32.3147 25.5797 32.4182 25.8405 32.4182H28.4947H28.4945C29.2246 32.4182 29.9247 32.1281 30.4409 31.612C30.957 31.0958 31.2471 30.3957 31.2471 29.6658V26.1506C31.2444 25.4223 30.9533 24.7247 30.4373 24.2107C29.9215 23.6966 29.2228 23.408 28.4945 23.408L28.4947 23.4081ZM29.281 29.674C29.281 29.8824 29.1982 30.0826 29.0506 30.2299C28.9031 30.3774 28.7031 30.4603 28.4945 30.4603H26.8234L26.8235 25.3741H28.4947H28.4945C28.7031 25.3741 28.9031 25.457 29.0506 25.6045C29.1982 25.7519 29.281 25.952 29.281 26.1605V29.674ZM39.0381 24.3911C39.0381 24.6519 38.9346 24.9019 38.7502 25.0863C38.5658 25.2706 38.3158 25.3742 38.0551 25.3742H34.6166V26.9371H36.8952C37.2464 26.9371 37.5708 27.1245 37.7465 27.4286C37.922 27.7328 37.922 28.1075 37.7465 28.4116C37.5708 28.7158 37.2464 28.9031 36.8952 28.9031H34.6166V31.4413C34.6166 31.7925 34.4292 32.1169 34.125 32.2926C33.8209 32.4681 33.4462 32.4681 33.142 32.2926C32.8379 32.1169 32.6505 31.7925 32.6505 31.4413V24.3911C32.6505 24.1305 32.7542 23.8804 32.9385 23.6961C33.1228 23.5116 33.3729 23.4081 33.6336 23.4081H38.0472C38.3079 23.4081 38.5579 23.5116 38.7423 23.6961C38.9266 23.8804 39.0302 24.1305 39.0302 24.3911L39.0381 24.3911ZM39.6162 10H16.4882C15.23 10.0005 14.0236 10.5012 13.1349 11.3918C12.2461 12.2823 11.7479 13.4898 11.75 14.7478V41.0923C11.75 42.3506 12.2487 43.5578 13.1367 44.4494C14.0246 45.3412 15.2297 45.8447 16.4882 45.85H34.1626C34.4912 45.8466 34.7984 45.6857 34.9883 45.4174L44.1106 36.2951L44.1104 36.2953C44.1808 36.2225 44.2345 36.1353 44.2677 36.0396C44.3217 35.9227 44.3488 35.795 44.3464 35.6662V14.7479C44.3485 13.4899 43.8503 12.2824 42.9615 11.3919C42.0727 10.5014 40.8664 10.0006 39.6082 10.0001L39.6162 10ZM33.1874 39.4114V43.8742H16.4882C15.7524 43.8706 15.0479 43.576 14.5286 43.0549C14.0091 42.5336 13.7171 41.8281 13.716 41.0924V14.748C13.715 14.0125 14.0067 13.3067 14.5268 12.7868C15.047 12.2666 15.7525 11.975 16.4882 11.9759H39.6084C40.3441 11.975 41.0497 12.2666 41.5698 12.7868C42.0899 13.3067 42.3816 14.0125 42.3806 14.748V34.6833L37.9375 34.6834C36.6796 34.6797 35.4718 35.1757 34.5795 36.0624C33.6874 36.9491 33.1839 38.1538 33.1797 39.4116L33.1874 39.4114Z"
|
||||
fill="#1D1D1D" />
|
||||
</svg>
|
||||
</template>
|
||||
8
src/assets/icons/ic_zip.vue
Normal file
8
src/assets/icons/ic_zip.vue
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="56" height="56" rx="13" fill="#FCFCFC" />
|
||||
<path
|
||||
d="M30.5667 36.6733C30.5667 38.8627 25.4307 38.8453 25.4307 36.6773C25.4307 34.4267 30.5667 34.3747 30.5667 36.6733ZM44 18.6667V37.3333C44 41.0147 41.016 44 37.3333 44H18.6667C14.984 44 12 41.0147 12 37.3333V18.6667C12 14.9853 14.984 12 18.6667 12H37.3333C41.016 12 44 14.9853 44 18.6667ZM25.3333 21.3333H30.6667V20H25.3333V21.3333ZM25.3333 18.6667H30.6667V17.3333H25.3333V18.6667ZM25.3333 16H30.6667V14.6667H25.3333V16ZM25.3333 24H30.6667V22.6667H25.3333V24ZM25.3333 26.6667H30.6667V25.3333H25.3333V26.6667ZM32.1573 35.7147C31.64 33.056 30.668 28 30.668 28H25.3347C25.3347 28 24.3907 32.944 23.8453 35.7147C23.3053 38.4493 25.2173 40 28.0013 40C30.788 40 32.6893 38.4467 32.1573 35.7147Z"
|
||||
fill="#1D1D1D" />
|
||||
</svg>
|
||||
</template>
|
||||
8
src/assets/icons/kickout.vue
Normal file
8
src/assets/icons/kickout.vue
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.89384 5.65641C6.55215 5.31471 5.9981 5.31471 5.65641 5.65641C5.31471 5.9981 5.31471 6.55215 5.65641 6.89384L10.6062 11.8436C10.9478 12.1853 11.5019 12.1853 11.8436 11.8436C12.1853 11.5019 12.1853 10.9478 11.8436 10.6062L6.89384 5.65641Z" fill="black"/>
|
||||
<path d="M6.89384 5.65641C6.55215 5.31471 5.9981 5.31471 5.65641 5.65641C5.31471 5.9981 5.31471 6.55215 5.65641 6.89384L10.6062 11.8436C10.9478 12.1853 11.5019 12.1853 11.8436 11.8436C12.1853 11.5019 12.1853 10.9478 11.8436 10.6062L6.89384 5.65641Z" fill="black"/>
|
||||
<path d="M5.65641 10.6062C5.31471 10.9479 5.31471 11.5019 5.65641 11.8436C5.9981 12.1853 6.55215 12.1853 6.89384 11.8436L11.8436 6.89386C12.1853 6.55216 12.1853 5.99811 11.8436 5.65642C11.5019 5.31472 10.9478 5.31472 10.6062 5.65642L5.65641 10.6062Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 0C3.91755 0 0 3.91755 0 8.75C0 13.5824 3.91755 17.5 8.75 17.5C13.5824 17.5 17.5 13.5824 17.5 8.75C17.5 3.91755 13.5824 0 8.75 0ZM1.75 8.75C1.75 4.88408 4.88408 1.75 8.75 1.75C12.6159 1.75 15.75 4.88408 15.75 8.75C15.75 12.6159 12.6159 15.75 8.75 15.75C4.88408 15.75 1.75 12.6159 1.75 8.75Z" fill="black"/>
|
||||
</svg>
|
||||
</template>
|
||||
7
src/assets/icons/make_admin.vue
Normal file
7
src/assets/icons/make_admin.vue
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<template>
|
||||
<svg width="21" height="18" viewBox="0 0 21 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.60303 0C8.39234 0 8.18549 0.0563288 8.00393 0.163173C7.82244 0.27002 7.67273 0.423416 7.5704 0.607586L5.39603 4.52146L1.661 2.8615C1.46003 2.77215 1.23813 2.74071 1.02028 2.77072C0.802418 2.80066 0.597196 2.89083 0.427813 3.0311C0.258487 3.17137 0.131608 3.35614 0.0615383 3.56457C-0.00852994 3.77306 -0.0189897 3.99694 0.0313228 4.21104L1.90995 12.2092L1.91022 12.2105C1.947 12.3656 2.01481 12.5117 2.10949 12.64C2.20431 12.7683 2.32401 12.876 2.46147 12.9568C2.59887 13.0376 2.75131 13.0898 2.90944 13.1101C3.06694 13.1304 3.22683 13.1188 3.37968 13.0759C6.7969 12.1327 10.4056 12.1323 13.823 13.0749C13.9758 13.1178 14.1357 13.1294 14.2931 13.1091C14.4512 13.0887 14.6035 13.0366 14.741 12.9558C14.8784 12.8751 14.9981 12.7674 15.0928 12.6392C15.1875 12.511 15.2554 12.3649 15.2922 12.2098L17.1744 4.21182C17.2247 3.99772 17.2143 3.77378 17.1443 3.56528C17.0743 3.35679 16.9475 3.17193 16.7781 3.0316C16.6087 2.89126 16.4035 2.80102 16.1856 2.77108C15.9677 2.74107 15.7457 2.77245 15.5447 2.86179L11.8102 4.52156L9.63586 0.607686C9.53353 0.423534 9.3839 0.270129 9.20234 0.163274C9.02077 0.0564274 8.81364 0 8.60303 0Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0843 15.9802C17.2175 16.0455 17.3733 15.94 17.3478 15.8016L16.8547 13.1173L18.945 11.2169C19.0528 11.1189 18.9933 10.9481 18.8443 10.9279L15.9559 10.538L14.6628 8.09441C14.5962 7.96853 14.4038 7.96853 14.3371 8.09441L13.043 10.5366L10.1557 10.9279C10.0067 10.9481 9.94725 11.1189 10.055 11.2169L12.1459 13.1171L11.6522 15.8016C11.6268 15.94 11.7825 16.0455 11.9158 15.9802L14.5012 14.7124L17.0843 15.9802Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4234 17.3267L14.5009 16.3831L12.5763 17.3269C11.9916 17.6137 11.3479 17.524 10.8831 17.209C10.4121 16.8899 10.0413 16.2684 10.177 15.5304L10.5198 13.6662L9.04619 12.3269C7.87843 11.2654 8.70182 9.61127 9.95426 9.44153C9.95423 9.44153 9.95429 9.44152 9.95426 9.44153L12.0783 9.15366L13.0113 7.39281C13.6416 6.20313 15.3588 6.20263 15.9887 7.39282L16.9209 9.15466L19.045 9.44143C20.2974 9.6111 21.1217 11.2651 19.954 12.3267C19.954 12.3267 19.9541 12.3267 19.954 12.3267L18.4807 13.6663L18.8231 15.5305C18.9587 16.2684 18.5879 16.8899 18.117 17.209C17.6522 17.524 17.0081 17.6135 16.4234 17.3267ZM16.8547 13.1173L18.945 11.2169C19.0528 11.1189 18.9933 10.9481 18.8443 10.9279L18.6597 10.903L15.9559 10.538L14.6628 8.09442C14.5962 7.96853 14.4038 7.96853 14.3371 8.09442L13.043 10.5366L10.1557 10.9279C10.0067 10.9481 9.94725 11.1189 10.055 11.2169L10.2145 11.3618L12.1459 13.1171L11.6522 15.8016C11.6268 15.94 11.7825 16.0455 11.9158 15.9802L12.1087 15.8856L14.5012 14.7124L16.8914 15.8855L17.0843 15.9802C17.2175 16.0455 17.3733 15.94 17.3478 15.8016L16.8547 13.1173Z" fill="white"/>
|
||||
</svg>
|
||||
</template>
|
||||
5
src/assets/icons/make_moderator.vue
Normal file
5
src/assets/icons/make_moderator.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<svg width="18" height="14" viewBox="0 0 18 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.60303 0C8.39234 0 8.18549 0.0563288 8.00393 0.163173C7.82244 0.27002 7.67273 0.423416 7.5704 0.607586L5.39603 4.52146L1.661 2.8615C1.46003 2.77215 1.23813 2.74071 1.02028 2.77072C0.802418 2.80066 0.597196 2.89083 0.427813 3.0311C0.258487 3.17137 0.131608 3.35614 0.0615383 3.56457C-0.00852994 3.77306 -0.0189897 3.99694 0.0313228 4.21104L1.90995 12.2092L1.91022 12.2105C1.947 12.3656 2.01481 12.5117 2.10949 12.64C2.20431 12.7683 2.32401 12.876 2.46147 12.9568C2.59887 13.0376 2.75131 13.0898 2.90944 13.1101C3.06694 13.1304 3.22683 13.1188 3.37968 13.0759C6.7969 12.1327 10.4056 12.1323 13.823 13.0749C13.9758 13.1178 14.1357 13.1294 14.2931 13.1091C14.4512 13.0887 14.6035 13.0366 14.741 12.9558C14.8784 12.8751 14.9981 12.7674 15.0928 12.6392C15.1875 12.511 15.2554 12.3649 15.2922 12.2098L17.1744 4.21182C17.2247 3.99772 17.2143 3.77378 17.1443 3.56528C17.0743 3.35679 16.9475 3.17193 16.7781 3.0316C16.6087 2.89126 16.4035 2.80102 16.1856 2.77108C15.9677 2.74107 15.7457 2.77245 15.5447 2.86179L11.8102 4.52156L9.63586 0.607686C9.53353 0.423534 9.3839 0.270129 9.20234 0.163274C9.02077 0.0564274 8.81364 0 8.60303 0Z" fill="black"/>
|
||||
</svg>
|
||||
</template>
|
||||
6
src/assets/icons/revoke.vue
Normal file
6
src/assets/icons/revoke.vue
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.25 7.875C4.76677 7.875 4.375 8.26677 4.375 8.75C4.375 9.23323 4.76677 9.625 5.25 9.625H12.25C12.7332 9.625 13.125 9.23323 13.125 8.75C13.125 8.26677 12.7332 7.875 12.25 7.875H5.25Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 0C3.91755 0 0 3.91755 0 8.75C0 13.5824 3.91755 17.5 8.75 17.5C13.5824 17.5 17.5 13.5824 17.5 8.75C17.5 3.91755 13.5824 0 8.75 0ZM1.75 8.75C1.75 4.88408 4.88408 1.75 8.75 1.75C12.6159 1.75 15.75 4.88408 15.75 8.75C15.75 12.6159 12.6159 15.75 8.75 15.75C4.88408 15.75 1.75 12.6159 1.75 8.75Z" fill="black"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
|
@ -34,7 +34,17 @@
|
|||
"voice_mode": "སྐད་སྒྲའི་རྣམ་པ།",
|
||||
"voice_mode_info": "ཁ་བརྡའི་འབྲེལ་མཐུད་དེ་ཉན་པ་དང་སྒྲ་ཕབ་ཀྱི་རྣམ་པའི་ནང་དུ་བསྒྱུར།",
|
||||
"download_chat": "ཁ་བརྡ་ཕབ་ལེན།",
|
||||
"read_only_room": "ཁ་བརྡ་ཁང་དུ་ཀློག་མ་གཏོགས་མི་ཆོག"
|
||||
"read_only_room": "ཁ་བརྡ་ཁང་དུ་ཀློག་མ་གཏོགས་མི་ཆོག",
|
||||
"direct_link": "ང་དང་ཐད་ཀར་ཁ་བརྡ་བྱེད་སའི་འབྲེལ་ཐག",
|
||||
"make_public_warning": "ཉེན་བརྡ།: ཆ་འཕྲིན་གྱི་ལོ་རྒྱུས་ཡོངས་རྫོགས་འཛུལ་ཞུགས་པ་གསར་པ་ཚོས་མཐོང་ཐུབ།",
|
||||
"room_type": "ཁ་བརྡ་ཁང་གི་དབྱེ་བ།",
|
||||
"file_mode_info": "གླེང་མོལ་གྱི་མཐུད་ངོས་ཡིག་ཆ་བརྒྱུད་གཏོང་གི་རྣམ་པར་བསྒྱུར།",
|
||||
"room_type_default": "སྔར་ཡོད།",
|
||||
"file_mode": "ཡིག་ཆའི་རྣམ་པ།",
|
||||
"direct_link_desc": "བརྒྱུད་སྐུར་བྱེད་པར་གྲ་སྒྲིག་ཡིན། མི་ཞིག་གིས་འབྲེལ་ཐག་དེ་ཁ་འབྱེད་ཐེངས་རེར། ང་དང་ཐད་ཀར་གླེང་མོལ་བྱ་སའི་ཁ་བརྡ་ཁང་ཞིག་གི་སྒོ་འབྱེད་ངེས།",
|
||||
"copy_link": "འབྲེལ་ཐག་པར་བཤུས་རྒྱོབས།",
|
||||
"make_public": "ཡོངས་ཁྱབ་བཟོས།",
|
||||
"message_retention_1_hour": "ཆུ་ཚོད་ ༡"
|
||||
},
|
||||
"invite": {
|
||||
"done": "ལེགས་འགྲུབ།",
|
||||
|
|
@ -60,7 +70,10 @@
|
|||
"send_verification": "ར་སྤྲོད་ཡིག་ཟམ་ཐོངས།",
|
||||
"no_supported_flow": "བཀོལ་ཆས་འདི་སྤྲད་ཡོད་པའི་དྲ་བའི་ཞབས་ཞུ་ཆས་ནང་དུ་འཛུལ་ཐུབ་ཀྱི་མི་འདུག",
|
||||
"sent_verification": "{email}ཡིག་ཟམ་ཁ་བྱང་འདིའི་ཐོག་ཏུ་འཕྲིན་པ་ཞིག་བཏང་ཡོད། ཡིག་ཟམ་ནང་འཛུལ་ཏེ་སོ་སོའི་ཡིག་ཟམ་ཁ་བྱང་ར་སྤྲོད་བྱེད་རོགས།",
|
||||
"email_not_valid": "ཡིག་ཟམ་ཁ་བྱང་བེད་མེད་རེད་འདུག"
|
||||
"email_not_valid": "ཡིག་ཟམ་ཁ་བྱང་བེད་མེད་རེད་འདུག",
|
||||
"registration_token": "ཐོ་འགོད་མཚོན་རྟགས་གཏགས་རོགས།",
|
||||
"token_not_valid": "མཚོན་རྟགས་རྩིས་མེད།",
|
||||
"send_token": "མཚོན་རྟགས་ཐོངས།"
|
||||
},
|
||||
"new_room": {
|
||||
"next": "རྗེས་མ།",
|
||||
|
|
@ -103,11 +116,9 @@
|
|||
"ignore": "སྣང་མེད་ཐོངས།",
|
||||
"join": "ཞུགས།",
|
||||
"undo": "ཕྱིར་བསྡུ།",
|
||||
"user_kick_and_ban": "སྤྱོད་མཁན་འདི་ཕྱིར་འབུད་དང་བཀག་སྡོམ་བྱོས།",
|
||||
"user_make_admin": "འཛིན་སྐྱོང་པ་བཟོས།",
|
||||
"user_make_moderator": "གཙོ་སྐྱོང་བ་བཟོས།",
|
||||
"user_revoke_moderator": "གཙོ་སྐྱོང་བའི་དབང་ཚད་མེད་པར་བཟོས།",
|
||||
"user_kick": "སྤྱོད་མཁན་འདི་སྒོར་ཕུད།"
|
||||
"user_revoke_moderator": "གཙོ་སྐྱོང་བའི་དབང་ཚད་མེད་པར་བཟོས།"
|
||||
},
|
||||
"profile": {
|
||||
"change_password": "གསང་ཚིག་རྗེས།",
|
||||
|
|
@ -129,8 +140,7 @@
|
|||
"device_list": {
|
||||
"not_verified": "ར་སྤྲོད་བྱས་མི་འདུག",
|
||||
"verified": "ར་སྤྲོད་བྱས་ཟིན།",
|
||||
"blocked": "བཀག་ཚར།",
|
||||
"title": "ཡོ་ཆས་ཁག"
|
||||
"blocked": "བཀག་ཚར།"
|
||||
},
|
||||
"room_welcome": {
|
||||
"info_permissions": "ཁྱེད་ཀྱིས་ག་དུས་ཡིན་ཡང་སྒྲིག་བཀོད་ཀྱི་ཁོངས་ནས་ཁ་བརྡ་ཁང་གི་'ནང་འཛུལ་གྱི་ཆོག་མཆན'ལ་འགྱུར་བ་གཏོང་ཆོག",
|
||||
|
|
@ -140,7 +150,10 @@
|
|||
"got_it": "ཧ་གོ་སོང་།",
|
||||
"room_history_joined": "ཚོགས་མི་ཁག་ཁ་བརྡ་ཁང་དུ་ཞུགས་པའི་རྗེས་སུ། ད་གཟོད་དེའི་ནང་དུ་བཏང་ཡོད་པའི་འཕྲིན་ཐུང་ཁག་མཐོང་ཐུབ།",
|
||||
"room_history_is": "ཁ་བརྡ་ཁང་གི་ཟིན་ཐོ་ཁག {type}.",
|
||||
"encrypted": "འཕྲིན་ཐུང་ཁག་ལ་སྣེ་གཉིས་བར་གྱི་གསང་སྡོམ་བྱས་ཡོད།"
|
||||
"encrypted": "འཕྲིན་ཐུང་ཁག་ལ་སྣེ་གཉིས་བར་གྱི་གསང་སྡོམ་བྱས་ཡོད།",
|
||||
"no_past_messages": "དགའ་བསུ་ཞུ། ཁྱེད་ཀྱི་བདེ་འཇགས་ཀྱི་ཆེད་དུ། སྔོན་གྱི་ཆ་འཕྲིན་ཁག་ལྟ་ཀློག་མི་ཐུབ།",
|
||||
"direct_info": "བཀྲ་ཤིས་བདེ་ལེགས། {you} ཁྱེད་རང་{user}་དང་སྒེར་གྱི་ཁ་བརྡ་བྱེད་བཞིན་ཡོད།",
|
||||
"direct_private_chat": "ཐད་ཀའི་འཕྲིན་ཐུང་།"
|
||||
},
|
||||
"room": {
|
||||
"leave": "ཕྱིར་ཐོན།",
|
||||
|
|
@ -148,7 +161,7 @@
|
|||
"room_list_rooms": "ཁ་བརྡ་ཁང་།",
|
||||
"room_list_invites": "གདན་ཞུ་ཁག",
|
||||
"purge_failed": "ཁ་བརྡ་ཁང་བཤིག་ཐུབ་མ་སོང་།",
|
||||
"purge_removing_members": "ཚོགས་མི་ཁག་ཕྱིར་འདོན། ({total}་ཀྱི་({count})",
|
||||
"purge_removing_members": "ཚོགས་མི་ཁག་ཕྱིར་འདོན། {total})་ཀྱི་({members}",
|
||||
"purge_redacting_events": "ཁ་བརྡ་གཙང་གསུབ། {total})་ཀྱི་({count}",
|
||||
"purge_set_room_state": "ཁ་བརྡ་ཁང་གི་རྣམ་པ་སྒྲིག་འགོད།",
|
||||
"room_list_new_messages": "{count} ཆ་འཕྲིན་གསར་པ།",
|
||||
|
|
@ -208,7 +221,17 @@
|
|||
"user_was_banned_by_you": "ཁྱེད་ཀྱིས་{user} ་འདི་ཁ་བརྡའི་ཁོངས་ནས་བཀག་སྡོམ་བྱས་ཏེ་སྒོར་ཕུད་ཟིན།",
|
||||
"time_ago": "དེ་རིང་། | ཁ་སང་། | ཉིན་གྲངས་{count} གོང་།",
|
||||
"outgoing_message_deleted_text": "ཁྱེད་ཀྱིས་ཆ་འཕྲིན་འདི་བསུབས་སོང་།",
|
||||
"reaction_count_more": "{reactionCount} མང་བ།"
|
||||
"reaction_count_more": "{reactionCount} མང་བ།",
|
||||
"images": "པར་རིས།",
|
||||
"send_attachements_dialog_title": "ཁྱེད་ཀྱིས་གཤམ་གྱི་ཟུར་སྣོན་འདི་དག་གཏོང་འདོད་དམ།",
|
||||
"preparing_to_upload": "ཡར་འཇུག་བྱེད་པར་གྲ་སྒྲིག་བྱེད་བཞིན་པ...",
|
||||
"seen_by": "མཐོང་མཁན།",
|
||||
"seen_by_count": "ཚོགས་མི་སུས་ཀྱང་མཐོང་མི་འདུག| ཚོགས་མི་གཅིག་གིས་མཐོང་འདུག ཚོགས་མི་{count}་མཐོང་འདུག",
|
||||
"files": "ཡིག་ཆ་ཁག",
|
||||
"download_all": "ཆ་ཚང་ཕབ་ལེན།",
|
||||
"file": "ཡིག་ཆ།",
|
||||
"upload_file_too_large": "ཡར་འཇུག་བྱ་འདོད་ཀྱི་ཡིག་ཆ་ཆེ་དྲགས་འདུག",
|
||||
"upload_exceeded_file_limit": "({configFormattedUploadSize}) ་ཡི་ཡིག་ཆ་ཆེ་ཤོས་ཀྱི་ཆེ་ཆུང་གི་ཚད་གཞི་ལས་བརྒལ་འདུག "
|
||||
},
|
||||
"power_level": {
|
||||
"moderator": "མདོ་འཛིན་པ།",
|
||||
|
|
@ -261,7 +284,8 @@
|
|||
"join_user": "ཁ་བརྡ་འགོ་རྩོམ།",
|
||||
"enter_room_user": "ཁ་བརྡ་འགོ་རྩོམ།",
|
||||
"choose_name": "མིང་ཞིག་འདེམས་ཏེ་བཀོལ།",
|
||||
"title_user": "དགའ་བསུ་ཞུ། ཁྱེད་རང་ཁ་བརྡ་བྱེད་པར་གདན་ཞུ་གནང་སོང་།"
|
||||
"title_user": "དགའ་བསུ་ཞུ། ཁྱེད་རང་ཁ་བརྡ་བྱེད་པར་གདན་ཞུ་གནང་སོང་།",
|
||||
"you_have_been_banned": "ཁྱེད་རང་ཚོགས་པ་འདིའི་ནང་ནས་བཀག་འདུག"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"powered_by": "ཁ་བརྡ་ཁང་འདི་{product} ནུས་ཤུགས་བསྩལ་ཡོད། {productLink} ནས་དེ་ལས་མང་བ་སྦྱོང་ཆོག་ལ། མདུན་དུ་བསྐྱོད་དེ་ཁ་བརྡ་ཁང་གཞན་ཞིག་བསྐྲུན་ཆོག",
|
||||
|
|
@ -282,11 +306,19 @@
|
|||
"global": {
|
||||
"save": "ཉར་ཚགས།",
|
||||
"password_didnot_match": "གསང་ཚིག་མཐུན་གྱི་མི་འདུག",
|
||||
"password_hint": "ཉུང་མཐར་ཡང་ཡིག་འབྲུ་༡༢་དགོས་ལ། དེའི་ནང་དུ་ཨང་གྲངས་གཅིག་དང་། ཡིག་ཆེན་གཅིག ཡིག་ཆུང་གཅིག་ངེས་པར་དུ་ཚང་དགོས།",
|
||||
"password_hint": "ཉུང་མཐར་ཡང་ཡིག་འབྲུ་༡༢་ཀྱི་ཁོངས་སུ། ཨང་གྲངས་གཅིག་དང་། ཡིག་ཆེན་གཅིག ཡིག་ཆུང་གཅིག་ངེས་པར་དུ་ཚང་དགོས།",
|
||||
"add_reaction": "ཡ་ལན་ཁ་སྣོན།",
|
||||
"click_to_remove": "བསྣུན་ཏེ་མེད་པར་བཟོས།",
|
||||
"show_less": "ཉུང་བ་སྟོན།",
|
||||
"show_more": "མང་བ་སྟོན།"
|
||||
"show_more": "མང་བ་སྟོན།",
|
||||
"time": {
|
||||
"hours": "ཆུ་ཚོད་གཅིག་གི་སྔོན། | ཆུ་ཚོད {n} སྔོན་ལ།",
|
||||
"minutes": "སྐར་མ་གཅིག་གི་སྔོན། | སྐར་མ {n} སྔོན་ལ།",
|
||||
"days": "ཉིན་གཅིག་གི་གོང་། | ཉིན{n}གོང་ལ།",
|
||||
"recently": "ད་ལྟ་རང་།"
|
||||
},
|
||||
"notify": "བརྡ་ཁྱབ་གཏོང་བ།",
|
||||
"close": "སྒོ་རྒྱོབ།"
|
||||
},
|
||||
"logout": {
|
||||
"confirm_text": "ཁྱེད་རང་ཁ་བརྡ་ཁང་ནས་ཕྱི་རུ་ཐོན་རྒྱུ་ཡིན་ནམ།"
|
||||
|
|
@ -321,6 +353,57 @@
|
|||
"export": {
|
||||
"exported_date": "{date} ཉིན་ལ་ཕྱིར་འདྲེན་བྱས།",
|
||||
"export_filename": "{date} ཉིན་ཕྱིར་འདྲེན་བྱས་པའི་ཁ་བརྡ།",
|
||||
"processed_n_of_total_events": "བྱུང་བ་{total}ཁོངས་ནས་ལས་སྣོན་བྱས་ཟིན་པའི་སྨྱན་སྦྱོར་གྱི་གྲངས {count}"
|
||||
"processed_n_of_total_events": "བྱུང་བ་{total}ཁོངས་ནས་ལས་སྣོན་བྱས་ཟིན་པའི་སྨྱན་སྦྱོར་གྱི་གྲངས {count}",
|
||||
"fetched_n_of_total_events": "གནས་ཚུལ་{total} ནང་ནས་{count} བཏོན་གནང་།",
|
||||
"fetched_n_events": "བཏོན་གནང་བའི་གནས་ཚུལ{count}"
|
||||
},
|
||||
"file_mode": {
|
||||
"add_a_message": "འཕྲིན་ཐུང་ཞིག་ཁ་སྣོན་བྱོས།",
|
||||
"send_more_files": "ཡིག་ཆ་དེ་ལས་མང་བ་ཐོངས།",
|
||||
"choose_files": "ཡིག་ཆ་འདེམས།",
|
||||
"secure_file_send": "བདེ་འཇགས་ཡིག་ཆ་བརྒྱུད་གཏོང་།",
|
||||
"sending": "གཏོང་བཞིན་པ།",
|
||||
"files": "ཡིག་ཆ་ཁག",
|
||||
"sending_progress": "གཏོང་བཞིན་པ...",
|
||||
"any_file_format_accepted": "ཡིག་ཆའི་རྣམ་བཞག་ཆ་ཚང་ངོས་ལེན་བྱེད།",
|
||||
"files_sent_with_note": "མཆན་ཞིག་དང་མཉམ་དུ་ཡིག་ཆ་1་བཏང་ཟིན། མཆན་ཞིག་དང་མཉམ་དུ་ཡིག་ཆ {count} བཏང་ཟིན།",
|
||||
"close": "སྒོ་རྒྱོབ།",
|
||||
"files_sent": "ཡིག་ཆ1 བཏང་ཟིན། |ཡིག་ཆ{count}བཏང་ཟིན།"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"body": "འཕྲིན་ཐུང་དང་ཁ་བརྡ་གལ་ཆེན་གཅིག་ཀྱང་མ་ཤོར་བ་བྱོས། མི་ཞིག་གིས་ཁྱེད་ལ་འཕྲིན་ཐུང་གཏོང་སྐབས་སམ། ཁྱེད་ཀྱི་ཁ་བརྡ་ལ་ལན་འདེབས་གནང་སྐབས་བརྡ་ཁྱབ་གཏོང་ངེས།",
|
||||
"title": "གླེང་མོལ་བརྡ་ཁྱབ་ལ་འབྲེལ་མཐུད་རྒྱུན་འཁྱོངས་བྱོས།",
|
||||
"enable": "ནུས་ཡོད་བཟོ་བ།"
|
||||
},
|
||||
"title": "འཕྲིན་ཐུང་གསར་པ་འབྱོར་སོང་།",
|
||||
"blocked_message": "བརྡ་ཁྱབ་བཀག་འདུག ཆོག་མཆན་ཁག་བསྐྱར་སྒྲིག་བྱེད་རོགས།"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"nature": "རང་བྱུང་།",
|
||||
"places": "ས་ཆ།",
|
||||
"activity": "བྱེད་སྒོ།",
|
||||
"flags": "དར་ཆ་ཁག",
|
||||
"foods": "ཞལ་ཟས།",
|
||||
"objects": "བྱ་དངོས།",
|
||||
"peoples": "མི་མང་།",
|
||||
"symbols": "མཚོན་རྟགས།",
|
||||
"frequently": "རྒྱུན་དུ་སྤྱོད་བཞིན་པ།"
|
||||
},
|
||||
"search": "འཚོལ..."
|
||||
},
|
||||
"getlink": {
|
||||
"title": "ཐད་ཀར་འབྲེལ་ཐག་ཐོབ་པར་བྱོས།",
|
||||
"info": "ཐད་ཀར་ཁ་བརྡ་བྱེད་སྤྱད་ཀྱི་འབྲེལ་ཐག་གིས་ཁྱེད་དང་མི་གཞན་གྱི་བར་ལ་བདེ་འཇགས་ཀྱི་འཕྲིན་གཏོང་སྐུད་ལམ་ཞིག་མཁོ་སྤྲོད་བྱེད། འགོ་འཛུགས་བྱེད་པར། མི་ཚོས་ཁྱེད་ལ་ཁ་བརྡ་བྱེད་པར་སླེབས་དུས། འཆར་མིང་ཞིག་འདེམས་ཏེ་སྟོན།",
|
||||
"share_qr": "བཤེར་རིས་བརྒྱུད་སྐུར་བྱོས།",
|
||||
"next": "རྗེས་མ།",
|
||||
"scan_title": "བཤེར་རིས་འདི་བཤེར་ཏེ་ཐད་ཀར་ཁ་བརྡ་ཞིག་འགོ་རྩོམ།",
|
||||
"continue": "མུ་མཐུད།",
|
||||
"ready_to_share": "བརྒྱུད་སྐུར་བྱེད་པར་གྲ་སྒྲིག་ཡིན། མི་ཞིག་གིས་འབྲེལ་ཐག་དེ་ཁ་འབྱེད་ཐེངས་རེར། ཐད་ཀར་གླེང་མོལ་བྱ་སའི་ཁ་བརྡ་ཁང་ཞིག་ཁ་འབྱེད་ངེས།",
|
||||
"qr_image_copied": "འབྲེག་པང་གི་སྟེང་དུ་པར་རིས་འདྲ་བཤུས་བྱས་ཚར།",
|
||||
"hello": "ཁམས་བཟང་། {user},\nའདི་ཐད་ཀར་ཁ་བརྡ་བྱེད་སའི་ཁྱེད་ཀྱི་འབྲེལ་ཐག་ཡིན།",
|
||||
"username": "འཆར་མིང་ཞིག་གཏགས། (དཔེར་ན།: waku)",
|
||||
"different_link": "འབྲེལ་ཐག་གཞན་པ་ཞིག་རག་པར་བྱོས།"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
"join": "Beitreten",
|
||||
"ignore": "Ignorieren",
|
||||
"loading": "{appName} wird geladen",
|
||||
"done": "Fertig"
|
||||
"done": "Fertig",
|
||||
"user_kick_and_ban": "Hinauswerfen",
|
||||
"direct_chat": "Privater Chat"
|
||||
},
|
||||
"message": {
|
||||
"you": "Du",
|
||||
|
|
@ -59,7 +61,14 @@
|
|||
"reply_image": "Bild",
|
||||
"reply_audio_message": "Sprachnachricht",
|
||||
"reply_video": "Video",
|
||||
"time_ago": "Heute | Gestern | Vor {count} Tagen"
|
||||
"time_ago": "Heute | Gestern | Vor {count} Tagen",
|
||||
"reply_poll": "Umfrage",
|
||||
"seen_by": "Gesehen von",
|
||||
"images": "Bilder",
|
||||
"someone": "Jemand",
|
||||
"file": "Datei",
|
||||
"files": "Dateien",
|
||||
"download_all": "Alle herunterladen"
|
||||
},
|
||||
"room": {
|
||||
"leave": "Verlassen",
|
||||
|
|
@ -101,10 +110,10 @@
|
|||
"next": "Nächste",
|
||||
"name_room": "Raum benennen",
|
||||
"room_topic": "Füge eine Beschreibung hinzu, wenn du möchtest",
|
||||
"room_name_limit_error_msg": "Maximal 50 Zeichen erlaubt"
|
||||
"room_name_limit_error_msg": "Maximal 50 Zeichen erlaubt",
|
||||
"options": "Optionen"
|
||||
},
|
||||
"device_list": {
|
||||
"title": "GERÄTE",
|
||||
"blocked": "Blockiert",
|
||||
"verified": "Verifiziert",
|
||||
"not_verified": "Nicht verifiziert"
|
||||
|
|
@ -118,7 +127,10 @@
|
|||
"login": "Anmelden",
|
||||
"create_room": "Registrieren und Raum erstellen",
|
||||
"or": "ODER",
|
||||
"invalid_message": "Benutzername oder Passwort falsch"
|
||||
"invalid_message": "Benutzername oder Passwort falsch",
|
||||
"send_verification": "Sende Bestätigungs-E-Mail",
|
||||
"resend_verification": "Bestätigungsmail erneut senden",
|
||||
"accept_terms": "Akzeptiere."
|
||||
},
|
||||
"profile": {
|
||||
"title": "Mein Profil",
|
||||
|
|
@ -133,7 +145,8 @@
|
|||
"display_name": "Anzeigename",
|
||||
"set_language": "Stelle deine Sprache ein",
|
||||
"language_description": "Convene ist in vielen Sprachen verfügbar.",
|
||||
"tell_us": "Teile uns mit."
|
||||
"tell_us": "Teile uns mit.",
|
||||
"notification_label": "Benachrichtigung"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"you_are": "Du bist",
|
||||
|
|
@ -206,7 +219,16 @@
|
|||
"leave_room": "Verlassen",
|
||||
"version_info": "Angetrieben von Guardian Project. Version: {version}",
|
||||
"scan_code": "Scannen, um den Raum zu betreten",
|
||||
"export_room": "Chat exportieren"
|
||||
"export_room": "Chat exportieren",
|
||||
"user_moderator": "Moderator*innen",
|
||||
"room_type_default": "Vorgabe",
|
||||
"user_admin": "Administrator",
|
||||
"copy_link": "Link kopieren",
|
||||
"message_retention_2_week": "2 Wochen",
|
||||
"message_retention_1_week": "1 Woche",
|
||||
"message_retention_1_day": "1 Tag",
|
||||
"message_retention_8_hours": "8 Stunden",
|
||||
"message_retention_1_hour": "1 Stunde"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "Dieser Raum",
|
||||
|
|
@ -254,7 +276,9 @@
|
|||
"poll_status_open": "Umfrage ist offen",
|
||||
"poll_status_disclosed": "Ergebnisse werden angezeigt, sobald die Umfrage geschlossen wurde.",
|
||||
"poll_status_open_not_voted": "Umfrage ist offen – stimme ab, um die Ergebnisse zu sehen",
|
||||
"close_poll": "Umfrage schließen"
|
||||
"close_poll": "Umfrage schließen",
|
||||
"answer_label_n": "Antwort",
|
||||
"view_results": "Ergebnisse ansehen"
|
||||
},
|
||||
"export": {
|
||||
"fetched_n_of_total_events": "{count} von {total} Ereignissen geladen",
|
||||
|
|
@ -262,5 +286,43 @@
|
|||
"processed_n_of_total_events": "Medien für {count} von {total} Ereignissen verarbeitet",
|
||||
"fetched_n_events": "{count} Ereignisse geladen",
|
||||
"export_filename": "Chat exportiert: {date}"
|
||||
},
|
||||
"global": {
|
||||
"save": "Speichern",
|
||||
"show_more": "Mehr anzeigen",
|
||||
"password_didnot_match": "Passwort stimmt nicht überein",
|
||||
"add_reaction": "Reaktion hinzufügen",
|
||||
"show_less": "Weniger anzeigen",
|
||||
"time": {
|
||||
"recently": "im Moment"
|
||||
},
|
||||
"notify": "Benachrichtigung",
|
||||
"close": "schlieen"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"nature": "Natur",
|
||||
"activity": "Aktivität",
|
||||
"flags": "Kennzeichnungen",
|
||||
"objects": "Objekte",
|
||||
"symbols": "Symbole",
|
||||
"places": "Orte"
|
||||
},
|
||||
"search": "Suche ..."
|
||||
},
|
||||
"file_mode": {
|
||||
"sending": "Sende",
|
||||
"sending_progress": "Senden…",
|
||||
"close": "Schließen",
|
||||
"files": "Dateien"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "Aktiviere"
|
||||
}
|
||||
},
|
||||
"getlink": {
|
||||
"next": "Nächster",
|
||||
"continue": "Fortfahren"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
},
|
||||
"menu": {
|
||||
"start_private_chat": "Direct Message with this user",
|
||||
"direct_chat": "Direct chat",
|
||||
"reply": "Reply",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
|
|
@ -40,9 +41,8 @@
|
|||
"join": "Join",
|
||||
"ignore": "Ignore",
|
||||
"loading": "Loading {appName}",
|
||||
"user_kick": "Kick this user",
|
||||
"user_kick_and_ban": "Kick and ban this user",
|
||||
"user_make_admin": "Make administrator",
|
||||
"user_kick_and_ban": "Kick out",
|
||||
"user_make_admin": "Make admin",
|
||||
"user_make_moderator": "Make moderator",
|
||||
"user_revoke_moderator": "Revoke moderator"
|
||||
},
|
||||
|
|
@ -137,7 +137,10 @@
|
|||
"got_it": "Got it",
|
||||
"no_past_messages": "Welcome! For your security, past messages are not available.",
|
||||
"direct_info": "Hi, {you}. You’re in a private chat with {user}.",
|
||||
"direct_private_chat": "Direct Message"
|
||||
"direct_private_chat": "Direct Message",
|
||||
"join_channel": "All set! Invite people to join you: {link}",
|
||||
"info_retention": "🕓 Messages sent within {time} are viewable by anyone with the link.",
|
||||
"change": "Change"
|
||||
},
|
||||
"new_room": {
|
||||
"new_room": "New Room",
|
||||
|
|
@ -163,7 +166,6 @@
|
|||
"options": "Options"
|
||||
},
|
||||
"device_list": {
|
||||
"title": "DEVICES",
|
||||
"blocked": "Blocked",
|
||||
"verified": "Verified",
|
||||
"not_verified": "Not verified"
|
||||
|
|
@ -203,6 +205,14 @@
|
|||
"share_qr": "Share QR",
|
||||
"qr_image_copied": "Image copied to clipboard"
|
||||
},
|
||||
"createchannel": {
|
||||
"title": "Create a Channel",
|
||||
"info": "Broadcast news or knowledge in any format—video, podcast, text, pictures or PDFs.",
|
||||
"channel_name": "Name your channel",
|
||||
"channel_topic": "Describe it",
|
||||
"name_required": "Channel name is required",
|
||||
"error_channel": "Failed to create channel"
|
||||
},
|
||||
"profile": {
|
||||
"title": "My Profile",
|
||||
"temporary_identity": "This identity is temporary. Set a password to use it again",
|
||||
|
|
@ -218,7 +228,8 @@
|
|||
"password_new": "New password",
|
||||
"password_repeat": "Repeat new password",
|
||||
"display_name": "Display name",
|
||||
"display_name_required": "Display name is required"
|
||||
"display_name_required": "Display name is required",
|
||||
"notification_label": "Notification"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"you_are": "You are",
|
||||
|
|
@ -302,6 +313,7 @@
|
|||
"export_room": "Export chat",
|
||||
"user_admin": "Administrator",
|
||||
"user_moderator": "Moderator",
|
||||
"moderation": "Moderation",
|
||||
"experimental_features": "Experimental Features",
|
||||
"room_type": "Room type",
|
||||
"room_type_default": "Default",
|
||||
|
|
@ -310,12 +322,23 @@
|
|||
"file_mode": "File mode",
|
||||
"file_mode_info": "Switches the chat interface to a 'file drop' mode",
|
||||
"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",
|
||||
"read_only_room": "Read Only",
|
||||
"read_only_room_info": "Only admins and moderators are allowed to send to the room.",
|
||||
"message_retention": "Message History",
|
||||
"message_retention_info": "Messages sent within this time frame are viewable by anyone with the link.",
|
||||
"message_retention_none": "Off",
|
||||
"message_retention_4_week": "4 weeks",
|
||||
"message_retention_2_week": "2 weeks",
|
||||
"message_retention_1_week": "1 week",
|
||||
"message_retention_1_day": "1 day",
|
||||
"message_retention_8_hours": "8 hours",
|
||||
"message_retention_1_hour": "1 Hour",
|
||||
"make_public": "Make Public",
|
||||
"make_public_warning": "warning: Full message history will be visible to new participants",
|
||||
"direct_link": "My Direct Link",
|
||||
"direct_link_desc": "It's ready to share! A new direct room will open each time someone opens the link."
|
||||
"direct_link_desc": "It's ready to share! A new direct room will open each time someone opens the link.",
|
||||
"shared_room_number": "You share {count} rooms with {name}",
|
||||
"shared_room_number_more": "You share more than {count} rooms with {name}"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "This room",
|
||||
|
|
@ -382,7 +405,8 @@
|
|||
"body": "Never miss a message or important conversation again! Be notified whenever someone sends you a message or replies to your chat.",
|
||||
"enable": "Enable"
|
||||
},
|
||||
"blocked_message": "Notifications blocked. Please reset the permissions"
|
||||
"blocked_message": "Notification is blocked. Go to your device or browser settings to enable Notification",
|
||||
"not_supported": "Notification is not yet supported in Mobile"
|
||||
},
|
||||
"emoji": {
|
||||
"search": "Search...",
|
||||
|
|
|
|||
|
|
@ -38,14 +38,25 @@
|
|||
"download_chat": "Descargar el chat",
|
||||
"make_public": "Hacer público",
|
||||
"voice_mode_info": "Cambia la interfaz del chat al modo \"escuchar y grabar\"",
|
||||
"export_room": "Exportar el chat"
|
||||
"export_room": "Exportar el chat",
|
||||
"message_retention_none": "Apagado",
|
||||
"message_retention_2_week": "2 semanas",
|
||||
"message_retention_1_week": "1 semana",
|
||||
"message_retention_1_day": "1 día",
|
||||
"message_retention_8_hours": "8 horas",
|
||||
"message_retention_1_hour": "Una hora",
|
||||
"message_retention_4_week": "4 semanas",
|
||||
"shared_room_number": "Compartes {count} salas con {name}",
|
||||
"shared_room_number_more": "Compartes más de {count} y salas con {name}",
|
||||
"message_retention": "Historial de mensajes",
|
||||
"message_retention_info": "Los mensajes enviados dentro de este plazo pueden ser vistos por cualquiera que tenga el enlace."
|
||||
},
|
||||
"purge_room": {
|
||||
"button": "Borrar",
|
||||
"info": "Todos los miembros y mensajes serán eliminados. Esta acción no se puede deshacer.",
|
||||
"title": "¿Borrar la sala?",
|
||||
"n_seconds": "{seconds} segundos",
|
||||
"self_destruct": "La habitación se autodestruirá en segundos.",
|
||||
"self_destruct": "Tu sala se autodestruirá en segundos.",
|
||||
"deleting": "Borrando la sala:",
|
||||
"notified": "Hemos avisado a los miembros.",
|
||||
"room_deletion_notice": "¡Es hora de decir adiós! Esta sala ha sido eliminada por {user}. Se autodestruirá en segundos."
|
||||
|
|
@ -98,7 +109,8 @@
|
|||
"language_description": "Convine esta disponible en varios Idiomas.",
|
||||
"dont_see_yours": "¿No ves el tuyo?",
|
||||
"tell_us": "Dinos.",
|
||||
"display_name_required": "El nombre para mostrar es obligatorio"
|
||||
"display_name_required": "El nombre para mostrar es obligatorio",
|
||||
"notification_label": "Notificación"
|
||||
},
|
||||
"login": {
|
||||
"login": "Iniciar sesión",
|
||||
|
|
@ -125,8 +137,7 @@
|
|||
"device_list": {
|
||||
"not_verified": "No ha sido Verificado",
|
||||
"verified": "Verificado",
|
||||
"blocked": "Bloqueado",
|
||||
"title": "DISPOSITIVOS"
|
||||
"blocked": "Bloqueado"
|
||||
},
|
||||
"new_room": {
|
||||
"status_avatar": "Subiendo avatar: {count}",
|
||||
|
|
@ -148,7 +159,8 @@
|
|||
"create": "Crear",
|
||||
"room_topic": "Añade una descripción si quieres",
|
||||
"options": "Opciones",
|
||||
"room_name_limit_error_msg": "50 caracteres como máximo"
|
||||
"room_name_limit_error_msg": "50 caracteres como máximo",
|
||||
"colon_not_allowed": "Colon no está permitido"
|
||||
},
|
||||
"room_welcome": {
|
||||
"join_public": "Cualquiera puede unirse abriendo este vínculo: {link}.",
|
||||
|
|
@ -239,7 +251,9 @@
|
|||
"user_was_kicked_by_you": "Has expulsado a {user} del chat.",
|
||||
"upload_file_too_large": "¡El archivo es demasiado grande para subirlo!",
|
||||
"time_ago": "Hoy | Ayer | Hace {count} días",
|
||||
"upload_exceeded_file_limit": "Se ha superado el tamaño máximo para el archivo ({configFormattedUploadSize}). "
|
||||
"upload_exceeded_file_limit": "Se ha superado el tamaño máximo para el archivo ({configFormattedUploadSize}). ",
|
||||
"someone": "Alguien",
|
||||
"sent_media": "Enviados {count} elementos multimedia."
|
||||
},
|
||||
"menu": {
|
||||
"login": "Iniciar sesión",
|
||||
|
|
@ -259,11 +273,12 @@
|
|||
"loading": "Cargando {appName}",
|
||||
"undo": "Deshacer",
|
||||
"join": "Unirse",
|
||||
"user_kick": "Expulsa a este usuario",
|
||||
"user_kick_and_ban": "Expulsar y banear a este usuario",
|
||||
"user_make_moderator": "Hacer moderador",
|
||||
"user_make_admin": "Hacer administrador",
|
||||
"user_revoke_moderator": "Revocar al moderador"
|
||||
"user_revoke_moderator": "Revocar al moderador",
|
||||
"delete_now": "Elimina ahora",
|
||||
"user_kick_and_ban": "Expulsar",
|
||||
"direct_chat": "Chat directo"
|
||||
},
|
||||
"fallbacks": {
|
||||
"download_name": "Descargar",
|
||||
|
|
@ -395,7 +410,8 @@
|
|||
"title": "¡Manténgase conectado con las notificaciones para el chat!"
|
||||
},
|
||||
"title": "Nuevo mensaje recibido",
|
||||
"blocked_message": "Notificaciones bloqueadas. Por favor, restablezca los permisos"
|
||||
"blocked_message": "La notificación está bloqueada. Vaya a la configuración de su dispositivo o navegador para habilitar la Notificación",
|
||||
"not_supported": "La notificación aún no es compatible con dispositivos móviles"
|
||||
},
|
||||
"export": {
|
||||
"fetched_n_of_total_events": "{count} de {total} eventos recuperados",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@
|
|||
"new_room": "Uusi huone",
|
||||
"undo": "Kumoa",
|
||||
"join": "Liity",
|
||||
"loading": "{appName} ladataan"
|
||||
"loading": "{appName} ladataan",
|
||||
"done": "Valmis",
|
||||
"direct_chat": "Yksityiskeskustelu",
|
||||
"user_kick_and_ban": "Poista"
|
||||
},
|
||||
"new_room": {
|
||||
"create": "Luo",
|
||||
|
|
@ -27,7 +30,8 @@
|
|||
"room_topic": "Lisää kuvaus, jos haluat",
|
||||
"add_people": "Lisää ihmisiä",
|
||||
"link_copied": "Linkki kopioitu!",
|
||||
"public_info": "Kuka tahansa, jolla on linkki"
|
||||
"public_info": "Kuka tahansa, jolla on linkki",
|
||||
"options": "Asetukset"
|
||||
},
|
||||
"purge_room": {
|
||||
"n_seconds": "{seconds} sekuntia",
|
||||
|
|
@ -48,7 +52,6 @@
|
|||
"language_display_name": "suomi",
|
||||
"device_list": {
|
||||
"verified": "Vahvistettu",
|
||||
"title": "LAITTEET",
|
||||
"blocked": "Estetty",
|
||||
"not_verified": "Ei vahvistettu"
|
||||
},
|
||||
|
|
@ -60,7 +63,8 @@
|
|||
"login": "Kirjaudu sisään",
|
||||
"password": "Anna salasana",
|
||||
"password_required": "Salasana vaaditaan",
|
||||
"create_room": "Rekisteröidy ja luo huone"
|
||||
"create_room": "Rekisteröidy ja luo huone",
|
||||
"accept_terms": "Hyväksy"
|
||||
},
|
||||
"join": {
|
||||
"title": "Tervetuloa huoneen {roomName}",
|
||||
|
|
@ -69,7 +73,9 @@
|
|||
"join": "Liity huoneeseen",
|
||||
"status_logging_in": "Kirjautuminen sisään…",
|
||||
"status_joining": "Liittyminen huoneeseen…",
|
||||
"join_failed": "Huoneeseen liittyminen epäonnistui."
|
||||
"join_failed": "Huoneeseen liittyminen epäonnistui.",
|
||||
"remember_me": "Muista minut",
|
||||
"enter_room": "Siirry huoneeseen"
|
||||
},
|
||||
"leave": {
|
||||
"title_public": "Näkemiin, {user}",
|
||||
|
|
@ -92,11 +98,20 @@
|
|||
"user_joined": "{user} liittyi keskusteluun",
|
||||
"file_prefix": "Tiedosto: ",
|
||||
"edited": "(muokattu)",
|
||||
"users_are_typing": "{count} jäsentä kirjoitavat"
|
||||
"users_are_typing": "{count} jäsentä kirjoitavat",
|
||||
"reply_image": "Kuva",
|
||||
"reply_video": "Video",
|
||||
"files": "Tiedostot",
|
||||
"download_all": "Lataa kaikki",
|
||||
"reply_poll": "Kysely",
|
||||
"file": "Tiedosto",
|
||||
"someone": "Joku",
|
||||
"images": "Kuvat"
|
||||
},
|
||||
"room": {
|
||||
"leave": "Poistu",
|
||||
"room_list_rooms": "Huoneet"
|
||||
"room_list_rooms": "Huoneet",
|
||||
"room_list_invites": "Kutsut"
|
||||
},
|
||||
"room_welcome": {
|
||||
"room_history_is": "Huoneen historia on {type}.",
|
||||
|
|
@ -115,7 +130,8 @@
|
|||
"set_password": "Aseta salasana",
|
||||
"select_language": "Kieli",
|
||||
"password_old": "Vanha salasana",
|
||||
"display_name": "Näyttönimi"
|
||||
"display_name": "Näyttönimi",
|
||||
"notification_label": "Ilmoitus"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"want_more": "Haluatko lisää?",
|
||||
|
|
@ -144,10 +160,21 @@
|
|||
"user": "{user}",
|
||||
"user_you": "{user} (sinä)",
|
||||
"hide_all": "Piilota",
|
||||
"show_all": "Näytä kaikki >"
|
||||
"show_all": "Näytä kaikki >",
|
||||
"link_copied": "Linkki kopioitu!",
|
||||
"room_type_default": "Oletusasetus",
|
||||
"join_public": "Kuka tahansa, jolla on linkki",
|
||||
"copy_link": "Kopioi linkki",
|
||||
"user_admin": "Ylläpitäjä",
|
||||
"user_moderator": "Moderaattori",
|
||||
"message_retention_none": "Pois päältä",
|
||||
"message_retention_1_day": "1 päivä",
|
||||
"message_retention_8_hours": "8 tuntia",
|
||||
"message_retention_1_hour": "1 tunti"
|
||||
},
|
||||
"power_level": {
|
||||
"restricted": "rajoitettu"
|
||||
"restricted": "rajoitettu",
|
||||
"default": "oletus"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "Tämä huone",
|
||||
|
|
@ -156,5 +183,44 @@
|
|||
"voice_recorder": {
|
||||
"swipe_to_cancel": "Peruuta pyyhkäisemällä",
|
||||
"not_supported_title": "Ei tuettu"
|
||||
},
|
||||
"global": {
|
||||
"save": "Tallenna",
|
||||
"time": {
|
||||
"recently": "juuri nyt"
|
||||
},
|
||||
"notify": "Ilmoitus",
|
||||
"show_less": "Näytä vähemmän",
|
||||
"show_more": "Näytä lisää"
|
||||
},
|
||||
"poll_create": {
|
||||
"create": "Julkaise",
|
||||
"answer_label_n": "Vastaus",
|
||||
"poll_submit": "Lähetä"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"objects": "Kohteet",
|
||||
"nature": "Tyyppi",
|
||||
"symbols": "Symbolit",
|
||||
"activity": "Aktiviteetti",
|
||||
"flags": "Liput",
|
||||
"places": "Sijainnit"
|
||||
},
|
||||
"search": "Etsi..."
|
||||
},
|
||||
"getlink": {
|
||||
"next": "Seuraava",
|
||||
"continue": "Jatka"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "Ota käyttöön"
|
||||
}
|
||||
},
|
||||
"file_mode": {
|
||||
"sending_progress": "Lähetetään…",
|
||||
"close": "Sulje",
|
||||
"files": "Tiedostot"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@
|
|||
"loading": "Chargement de {appName}",
|
||||
"reply": "Répondre",
|
||||
"download": "Télécharger",
|
||||
"delete": "Supprimer"
|
||||
"delete": "Supprimer",
|
||||
"done": "Terminé",
|
||||
"user_kick_and_ban": "Exclure",
|
||||
"user_make_admin": "Rendre administrateur"
|
||||
},
|
||||
"language_display_name": "français",
|
||||
"message": {
|
||||
|
|
@ -53,7 +56,16 @@
|
|||
"room_joinrule_public": "public",
|
||||
"unread_messages": "Messages non lus",
|
||||
"users_are_typing": "{count} membres écrivent",
|
||||
"room_powerlevel_change": "{user} a changé le statut de {changes}"
|
||||
"room_powerlevel_change": "{user} a changé le statut de {changes}",
|
||||
"reply_video": "Vidéo",
|
||||
"reply_poll": "Sondage",
|
||||
"reply_image": "Image",
|
||||
"file": "Fichier",
|
||||
"seen_by": "Vu par",
|
||||
"download_all": "Télécharger tout",
|
||||
"someone": "Quelqu'un",
|
||||
"files": "Fichier",
|
||||
"images": "Images"
|
||||
},
|
||||
"room": {
|
||||
"members": "aucun membre | 1 membre | {count} membres",
|
||||
|
|
@ -93,10 +105,10 @@
|
|||
"get_link": "Obtenir le lien",
|
||||
"public_info": "Quiconque avec un lien",
|
||||
"join_permissions_info": "Ces autorisations déterminent comment les personnes peuvent rejoindre le salon et avec quelle facilité d’autres personnes peuvent être invitées. Elles peuvent être modifiées à tout moment.",
|
||||
"status_creating": "Création du salon"
|
||||
"status_creating": "Création du salon",
|
||||
"options": "Options"
|
||||
},
|
||||
"device_list": {
|
||||
"title": "APPAREILS",
|
||||
"not_verified": "Non vérifié",
|
||||
"blocked": "Bloqué",
|
||||
"verified": "Vérifié"
|
||||
|
|
@ -109,7 +121,12 @@
|
|||
"password_required": "Le mot de passe est obligatoire",
|
||||
"create_room": "S’inscrire et créer un salon",
|
||||
"or": "OU",
|
||||
"login": "Se connecter"
|
||||
"login": "Se connecter",
|
||||
"invalid_message": "Nom d'utilisateur ou mot de passe invalide",
|
||||
"token_not_valid": "Token invalide",
|
||||
"accept_terms": "Accept.",
|
||||
"send_verification": "Envoyer un email de vérification",
|
||||
"resend_verification": "Renvoyer le courriel de vérification"
|
||||
},
|
||||
"profile": {
|
||||
"temporary_identity": "Cette identité est temporaire. Définissez un mot de passe pour l’utiliser à nouveau",
|
||||
|
|
@ -121,7 +138,8 @@
|
|||
"display_name": "Nom d’affichage",
|
||||
"title": "Mon profil",
|
||||
"set_password": "Définir un mot de passe",
|
||||
"password_new": "Nouveau mot de passe"
|
||||
"password_new": "Nouveau mot de passe",
|
||||
"notification_label": "Notification"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"you_are": "Vous êtes",
|
||||
|
|
@ -140,7 +158,9 @@
|
|||
"join": "Rejoindre le salon",
|
||||
"status_logging_in": "Connexion en cours…",
|
||||
"status_joining": "Adhésion au salon…",
|
||||
"join_failed": "Impossible de rejoindre le salon."
|
||||
"join_failed": "Impossible de rejoindre le salon.",
|
||||
"enter_room": "Entrer dans le salon",
|
||||
"remember_me": "Rester connecté·e"
|
||||
},
|
||||
"invite": {
|
||||
"title": "Ajouter des amis",
|
||||
|
|
@ -190,7 +210,17 @@
|
|||
"scan_code": "Scanner pour rejoindre le salon",
|
||||
"user": "{user}",
|
||||
"title": "Détails du salon",
|
||||
"members": "Membres"
|
||||
"members": "Membres",
|
||||
"room_type_default": "Défaut",
|
||||
"user_admin": "Administrator",
|
||||
"copy_link": "Copy link",
|
||||
"user_moderator": "Modérateur⋅ice",
|
||||
"message_retention_none": "Inactif",
|
||||
"message_retention_2_week": "2 semaines",
|
||||
"message_retention_1_week": "1 semaine",
|
||||
"message_retention_1_day": "1 jour",
|
||||
"message_retention_8_hours": "8 heures",
|
||||
"message_retention_1_hour": "1 heure"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "Ce salon",
|
||||
|
|
@ -215,5 +245,49 @@
|
|||
"video_file": "Fichier vidéo",
|
||||
"download_name": "Téléchargement",
|
||||
"original_text": "<texte original>"
|
||||
},
|
||||
"global": {
|
||||
"time": {
|
||||
"recently": "à l’instant"
|
||||
},
|
||||
"close": "fermer",
|
||||
"notify": "Notifier",
|
||||
"show_less": "Afficher moins",
|
||||
"show_more": "Afficher plus",
|
||||
"save": "Sauvegarder"
|
||||
},
|
||||
"poll_create": {
|
||||
"create_poll_menu_option": "Créer le sondage",
|
||||
"create": "Publier",
|
||||
"answer_label_n": "Réponse",
|
||||
"view_results": "Voir les résultats",
|
||||
"poll_submit": "Envoyer"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"objects": "Objets",
|
||||
"symbols": "Symboles",
|
||||
"places": "Places",
|
||||
"activity": "Activité",
|
||||
"flags": "Fanions",
|
||||
"frequently": "Fréquemment utilisé",
|
||||
"nature": "Nature"
|
||||
},
|
||||
"search": "Recherche..."
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "Activer"
|
||||
}
|
||||
},
|
||||
"getlink": {
|
||||
"continue": "Continuer",
|
||||
"next": "Suivant"
|
||||
},
|
||||
"file_mode": {
|
||||
"sending_progress": "Envoi...",
|
||||
"sending": "Envoi",
|
||||
"close": "Fermer",
|
||||
"files": "Fichier"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,16 @@
|
|||
"room_history_world_readable": "leggibile da chiunque",
|
||||
"room_history_shared": "leggibile da tutti i membri nella stanza",
|
||||
"user_is_typing": "{user} sta scrivendo",
|
||||
"users_are_typing": "{count} membri stanno scrivendo"
|
||||
"users_are_typing": "{count} membri stanno scrivendo",
|
||||
"reply_image": "Immagine",
|
||||
"reply_poll": "Sondaggio",
|
||||
"file": "File",
|
||||
"files": "Files",
|
||||
"images": "Immagini",
|
||||
"seen_by": "Visto da",
|
||||
"download_all": "Scarica tutto",
|
||||
"someone": "Qualcuno",
|
||||
"reply_video": "Video"
|
||||
},
|
||||
"room": {
|
||||
"purge_removing_members": "Rimozione di membri",
|
||||
|
|
@ -62,7 +71,10 @@
|
|||
"cancel": "Annulla",
|
||||
"logout": "Esci",
|
||||
"new_room": "Nuova stanza",
|
||||
"loading": "Caricamento di {appName}"
|
||||
"loading": "Caricamento di {appName}",
|
||||
"done": "Fatto",
|
||||
"user_kick_and_ban": "Espelli",
|
||||
"direct_chat": "Chat diretta"
|
||||
},
|
||||
"room_welcome": {
|
||||
"info": "Benvenuto/a! Ecco alcune cose da sapere sulla tua stanza:",
|
||||
|
|
@ -92,10 +104,10 @@
|
|||
"new_room": "Nuova stanza",
|
||||
"invite_info": "Solo le persone aggiunte",
|
||||
"join_permissions_info": "Questi permessi determinano come le persone possono entrare nella stanza e quanto facilmente gli altri possono essere invitati. Possono essere cambiati in qualsiasi momento.",
|
||||
"public_info": "Chiunque abbia un collegamento"
|
||||
"public_info": "Chiunque abbia un collegamento",
|
||||
"options": "Opzioni"
|
||||
},
|
||||
"device_list": {
|
||||
"title": "DISPOSITIVI",
|
||||
"blocked": "Bloccato",
|
||||
"verified": "Verificato",
|
||||
"not_verified": "Non verificato"
|
||||
|
|
@ -108,7 +120,11 @@
|
|||
"login": "Accedi",
|
||||
"create_room": "Registrati e crea una stanza",
|
||||
"or": "O",
|
||||
"username_required": "Il nome utente è richiesto"
|
||||
"username_required": "Il nome utente è richiesto",
|
||||
"send_verification": "Invia email di verifica",
|
||||
"invalid_message": "Nome utente o password non validi",
|
||||
"accept_terms": "Accetto",
|
||||
"resend_verification": "Rispedisci email di verifica"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Il mio profilo",
|
||||
|
|
@ -120,7 +136,8 @@
|
|||
"display_name": "Nome visualizzato",
|
||||
"change_name": "Cambia il nome",
|
||||
"change_password": "Cambia la password",
|
||||
"password_new": "Nuova password"
|
||||
"password_new": "Nuova password",
|
||||
"notification_label": "Notifiche"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"you_are": "Sei",
|
||||
|
|
@ -139,7 +156,9 @@
|
|||
"join": "Unisciti alla stanza",
|
||||
"status_logging_in": "Accesso in corso…",
|
||||
"status_joining": "Unendosi alla stanza…",
|
||||
"join_failed": "Impossibile unirsi alla stanza."
|
||||
"join_failed": "Impossibile unirsi alla stanza.",
|
||||
"remember_me": "Ricordami",
|
||||
"enter_room": "Unirsi alla stanza"
|
||||
},
|
||||
"invite": {
|
||||
"title": "Aggiungi amici",
|
||||
|
|
@ -189,7 +208,15 @@
|
|||
"leave_room": "Lascia",
|
||||
"scan_code": "Scansiona per entrare nella stanza",
|
||||
"version_info": "Realizzato da Guardian Project. Versione: {version}",
|
||||
"copy_invite_link": "Copia il collegamento di invito"
|
||||
"copy_invite_link": "Copia il collegamento di invito",
|
||||
"user_admin": "Amministratore",
|
||||
"copy_link": "Copy link",
|
||||
"user_moderator": "Moderatore/Moderatrice",
|
||||
"room_type_default": "Predefinito",
|
||||
"message_retention_none": "Off",
|
||||
"message_retention_1_day": "1 giorno",
|
||||
"message_retention_8_hours": "8 ore",
|
||||
"message_retention_1_hour": "1 ora"
|
||||
},
|
||||
"voice_recorder": {
|
||||
"failed_to_record": "Impossibile registrare l’audio",
|
||||
|
|
@ -218,5 +245,46 @@
|
|||
},
|
||||
"project": {
|
||||
"name": "Convene"
|
||||
},
|
||||
"poll_create": {
|
||||
"create": "Pubblicare",
|
||||
"create_poll_menu_option": "Crea il sondaggio",
|
||||
"answer_label_n": "Rispondi",
|
||||
"poll_submit": "Invia"
|
||||
},
|
||||
"global": {
|
||||
"save": "Salva",
|
||||
"time": {
|
||||
"recently": "proprio ora"
|
||||
},
|
||||
"notify": "Notifica",
|
||||
"show_less": "Mostra meno",
|
||||
"show_more": "Mostra altro"
|
||||
},
|
||||
"emoji": {
|
||||
"search": "Cerca...",
|
||||
"categories": {
|
||||
"flags": "Flag",
|
||||
"symbols": "Simboli",
|
||||
"places": "Luoghi",
|
||||
"activity": "Attività",
|
||||
"objects": "Oggetti",
|
||||
"nature": "Natura"
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "Abilita"
|
||||
}
|
||||
},
|
||||
"file_mode": {
|
||||
"close": "Chiudi",
|
||||
"sending_progress": "Invio in corso...",
|
||||
"sending": "Invio",
|
||||
"files": "Files"
|
||||
},
|
||||
"getlink": {
|
||||
"next": "Prossimo",
|
||||
"continue": "Continua"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,16 @@
|
|||
"file_prefix": "Fil: ",
|
||||
"user_said": "{user} sa:",
|
||||
"user_created_room": "{user} opprettet rommet",
|
||||
"you": "Deg"
|
||||
"you": "Deg",
|
||||
"reply_video": "Video",
|
||||
"download_all": "Last ned alt",
|
||||
"reply_image": "Bilde",
|
||||
"reply_poll": "Avstemming",
|
||||
"file": "Fil",
|
||||
"files": "Filer",
|
||||
"images": "Bilder"
|
||||
},
|
||||
"device_list": {
|
||||
"title": "Enheter",
|
||||
"not_verified": "Ikke bekreftet",
|
||||
"verified": "Bekreftet",
|
||||
"blocked": "Blokkert"
|
||||
|
|
@ -38,7 +44,8 @@
|
|||
"fallbacks": {
|
||||
"original_text": "<opprinnelig tekst>",
|
||||
"video_file": "Videofil",
|
||||
"audio_file": "Lydfil"
|
||||
"audio_file": "Lydfil",
|
||||
"download_name": "Last ned"
|
||||
},
|
||||
"power_level": {
|
||||
"restricted": "begrenset",
|
||||
|
|
@ -47,7 +54,8 @@
|
|||
"admin": "administrator"
|
||||
},
|
||||
"voice_recorder": {
|
||||
"release_to_cancel": "Slipp for å avbryte"
|
||||
"release_to_cancel": "Slipp for å avbryte",
|
||||
"not_supported_title": "Ikke støttet"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "Denne gruppen",
|
||||
|
|
@ -61,7 +69,18 @@
|
|||
"copy_invite_link": "Kopier invitasjonslenke",
|
||||
"created_by": "Opprettet av {user}",
|
||||
"title": "Romdetaljer",
|
||||
"user": "{user}"
|
||||
"user": "{user}",
|
||||
"leave_room": "Forlat",
|
||||
"room_type_default": "Default",
|
||||
"user_admin": "administrator",
|
||||
"user_moderator": "Moderator",
|
||||
"copy_link": "Kopier link",
|
||||
"message_retention_none": "Av",
|
||||
"message_retention_2_week": "To uker",
|
||||
"message_retention_1_week": "1 uke",
|
||||
"message_retention_1_day": "1 dag",
|
||||
"message_retention_8_hours": "åtte timer",
|
||||
"message_retention_1_hour": "Én time"
|
||||
},
|
||||
"goodbye": {
|
||||
"view_other_rooms": "Vis andre rom",
|
||||
|
|
@ -71,7 +90,9 @@
|
|||
"title": "Velkommen til {roomName}",
|
||||
"join": "Ta del i rom",
|
||||
"status_logging_in": "Logger inn …",
|
||||
"status_joining": "Tar del i rom…"
|
||||
"status_joining": "Tar del i rom…",
|
||||
"user_name_label": "Brukernavn",
|
||||
"remember_me": "Husk meg"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"identity_temporary": "{displayName}",
|
||||
|
|
@ -89,20 +110,28 @@
|
|||
"set_password": "Sett passord",
|
||||
"title": "Min profil",
|
||||
"display_name": "Visningsnavn",
|
||||
"password_repeat": "Gjenta nytt passord"
|
||||
"password_repeat": "Gjenta nytt passord",
|
||||
"notification_label": "Merknad"
|
||||
},
|
||||
"login": {
|
||||
"password_required": "Passord kreves",
|
||||
"username_required": "Brukernavn kreves",
|
||||
"password": "Passord",
|
||||
"username": "Brukernavn"
|
||||
"password": "Skriv inn passord",
|
||||
"username": "Brukernavn",
|
||||
"login": "Logg inn",
|
||||
"title": "Logg inn",
|
||||
"token_not_valid": "Ugyldig symbol",
|
||||
"resend_verification": "Send e-post for bekreftelse på ny",
|
||||
"invalid_message": "Ugyldig brukernavn eller passord",
|
||||
"accept_terms": "Aksepter"
|
||||
},
|
||||
"new_room": {
|
||||
"add_people": "Legg til folk",
|
||||
"link_copied": "Lenke kopiert.",
|
||||
"next": "Neste",
|
||||
"create": "Opprett",
|
||||
"new_room": "Nytt rom"
|
||||
"new_room": "Nytt rom",
|
||||
"options": "Valg"
|
||||
},
|
||||
"room_welcome": {
|
||||
"room_history_is": "Romhistorikken er {type}.",
|
||||
|
|
@ -124,7 +153,9 @@
|
|||
"leave": {
|
||||
"go_back": "Gå tilbake",
|
||||
"create_account": "opprett en konto",
|
||||
"title_public": "Adjø, {user}"
|
||||
"title_public": "Adjø, {user}",
|
||||
"title_invite": "Er du sikker på at du vil forlate?",
|
||||
"leave": "Forlat"
|
||||
},
|
||||
"invite": {
|
||||
"status_inviting": "Inviterer venn {index} av {count}",
|
||||
|
|
@ -140,13 +171,58 @@
|
|||
"new_room": "Nytt rom",
|
||||
"login": "Logg inn",
|
||||
"send": "Send",
|
||||
"ok": "OK",
|
||||
"ok": "Ok",
|
||||
"cancel": "Avbryt",
|
||||
"download": "last ned",
|
||||
"delete": "Slett",
|
||||
"edit": "Rediger",
|
||||
"reply": "Svar",
|
||||
"start_private_chat": "Privat sludring med denne brukeren"
|
||||
"start_private_chat": "Privat sludring med denne brukeren",
|
||||
"join": "Ta del",
|
||||
"ignore": "Ignorer",
|
||||
"done": "Ferdig",
|
||||
"user_kick_and_ban": "Kast ut"
|
||||
},
|
||||
"language_display_name": "Norsk"
|
||||
"language_display_name": "Norsk",
|
||||
"global": {
|
||||
"save": "Lagre",
|
||||
"time": {
|
||||
"recently": "akkurat nå"
|
||||
},
|
||||
"notify": "Send merknad",
|
||||
"show_less": "Vis mindre",
|
||||
"show_more": "Vis mer"
|
||||
},
|
||||
"poll_create": {
|
||||
"create": "Publiser",
|
||||
"answer_label_n": "Svar",
|
||||
"poll_submit": "Send inn"
|
||||
},
|
||||
"file_mode": {
|
||||
"sending_progress": "Sender…",
|
||||
"sending": "Sender",
|
||||
"close": "Lukk",
|
||||
"files": "Filer"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"places": "Steder",
|
||||
"activity": "Aktivitet",
|
||||
"flags": "Flagg",
|
||||
"frequently": "Ofte brukt",
|
||||
"objects": "Objekter",
|
||||
"nature": "Natur",
|
||||
"symbols": "Symboler"
|
||||
},
|
||||
"search": "Søk …"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "Skru på"
|
||||
}
|
||||
},
|
||||
"getlink": {
|
||||
"next": "Neste",
|
||||
"continue": "Fortsett"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,11 +52,12 @@
|
|||
"join": "Entrar",
|
||||
"ignore": "Ignore",
|
||||
"loading": "Carregando {appName}",
|
||||
"user_kick": "Expulsar este usuário",
|
||||
"user_make_moderator": "Tornar moderador",
|
||||
"user_kick_and_ban": "Expulsar e banir este usuário",
|
||||
"user_make_admin": "Tornar administrador",
|
||||
"user_revoke_moderator": "Revogar moderador"
|
||||
"user_make_admin": "Tornar admin",
|
||||
"user_revoke_moderator": "Revogar moderador",
|
||||
"delete_now": "Excluir agora",
|
||||
"direct_chat": "Conversa direta",
|
||||
"user_kick_and_ban": "Expulsar"
|
||||
},
|
||||
"message": {
|
||||
"you": "Você",
|
||||
|
|
@ -119,7 +120,9 @@
|
|||
"files": "Arquivos",
|
||||
"download_all": "Baixar tudo",
|
||||
"upload_exceeded_file_limit": "O tamanho máximo do arquivo ({configFormattedUploadSize}) foi excedido. ",
|
||||
"preparing_to_upload": "Preparando para enviar..."
|
||||
"preparing_to_upload": "Preparando para enviar...",
|
||||
"someone": "Alguém",
|
||||
"sent_media": "Enviou {count} itens de mídia."
|
||||
},
|
||||
"room": {
|
||||
"members": "sem membros | 1 membro | {count} membros",
|
||||
|
|
@ -173,7 +176,6 @@
|
|||
"options": "Opções"
|
||||
},
|
||||
"device_list": {
|
||||
"title": "DISPOSITIVOS",
|
||||
"blocked": "Bloqueado",
|
||||
"verified": "Verificado",
|
||||
"not_verified": "Não verificado"
|
||||
|
|
@ -215,7 +217,8 @@
|
|||
"password_new": "Nova senha",
|
||||
"password_repeat": "Repita a nova senha",
|
||||
"display_name": "Nome de exibição",
|
||||
"display_name_required": "O nome de exibição é obrigatório"
|
||||
"display_name_required": "O nome de exibição é obrigatório",
|
||||
"notification_label": "Notificação"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"you_are": "Você é",
|
||||
|
|
@ -261,7 +264,7 @@
|
|||
"info": "Todos os membros e as mensagens serão excluídos. Essa ação não pode ser desfeita.",
|
||||
"button": "Excluir",
|
||||
"n_seconds": "{seconds} segundos",
|
||||
"self_destruct": "A sala se autodestruirá em segundos.",
|
||||
"self_destruct": "A sua sala se autodestruirá em segundos.",
|
||||
"deleting": "Excluindo a sala:",
|
||||
"notified": "Nós notificamos os membros.",
|
||||
"room_deletion_notice": "Hora de dizer adeus! Esta sala foi excluída pelo {user}. Ela se autodestruirá em segundos."
|
||||
|
|
@ -305,7 +308,18 @@
|
|||
"room_type_default": "Padrão",
|
||||
"file_mode": "Modo de arquivo",
|
||||
"direct_link": "Meu link direto",
|
||||
"direct_link_desc": "Ele está pronto para ser compartilhado! Uma nova sala com contato direto com você será aberta sempre que alguém abrir o link."
|
||||
"direct_link_desc": "Ele está pronto para ser compartilhado! Uma nova sala com contato direto com você será aberta sempre que alguém abrir o link.",
|
||||
"message_retention_1_day": "1 dia",
|
||||
"message_retention_none": "Não",
|
||||
"message_retention_2_week": "2 semanas",
|
||||
"message_retention_1_week": "1 semana",
|
||||
"message_retention_8_hours": "8 horas",
|
||||
"message_retention_1_hour": "1 hora",
|
||||
"message_retention": "Histórico da mensagem",
|
||||
"message_retention_info": "As mensagens enviadas dentro desse período podem ser visualizadas por qualquer pessoa que tenha o link.",
|
||||
"message_retention_4_week": "4 semanas",
|
||||
"shared_room_number": "Você compartilha {count} quartos com {name}",
|
||||
"shared_room_number_more": "Você compartilha mais de {count} quartos com {name}"
|
||||
},
|
||||
"room_info_sheet": {
|
||||
"this_room": "Esta sala",
|
||||
|
|
@ -393,7 +407,8 @@
|
|||
"title": "Manter-se conectado com as notificações do chat!",
|
||||
"enable": "Ativar"
|
||||
},
|
||||
"blocked_message": "Notificações bloqueadas. Redefina as permissões"
|
||||
"blocked_message": "A notificação está bloqueada. Vá para as configurações do dispositivo ou do navegador para ativar a notificação",
|
||||
"not_supported": "A notificação ainda não é suportada no mobile"
|
||||
},
|
||||
"getlink": {
|
||||
"title": "Obter um link direto",
|
||||
|
|
|
|||
|
|
@ -56,7 +56,14 @@
|
|||
"join_invite": "Numai persoane adăugate",
|
||||
"permissions": "Permisiuni de aderare",
|
||||
"created_by": "Creat de {user}",
|
||||
"title": "Detalii despre cameră"
|
||||
"title": "Detalii despre cameră",
|
||||
"user_admin": "Administrator",
|
||||
"copy_link": "Copiază link-ul",
|
||||
"user_moderator": "Moderator",
|
||||
"room_type_default": "Predefinit",
|
||||
"message_retention_none": "Dezactivat",
|
||||
"message_retention_1_day": "1 zi",
|
||||
"message_retention_1_hour": "O oră"
|
||||
},
|
||||
"goodbye": {
|
||||
"view_other_rooms": "Vezi alte camere",
|
||||
|
|
@ -125,7 +132,8 @@
|
|||
"change_name": "Schimbă numele",
|
||||
"set_password": "Setați parola",
|
||||
"temporary_identity": "Această identitate este temporară. Setați o parolă pentru a o utiliza din nou",
|
||||
"title": "Profilul meu"
|
||||
"title": "Profilul meu",
|
||||
"notification_label": "Notificare"
|
||||
},
|
||||
"login": {
|
||||
"login": "Autentificare",
|
||||
|
|
@ -136,13 +144,13 @@
|
|||
"title": "Autentificare",
|
||||
"create_room": "Înregistrare și creare cameră",
|
||||
"or": "SAU",
|
||||
"invalid_message": "Nume de utilizator sau parolă invalidă"
|
||||
"invalid_message": "Nume de utilizator sau parolă invalidă",
|
||||
"accept_terms": "Acceptă"
|
||||
},
|
||||
"device_list": {
|
||||
"not_verified": "Nu a fost verificat",
|
||||
"verified": "Verificat",
|
||||
"blocked": "Blocat",
|
||||
"title": "DEVICES"
|
||||
"blocked": "Blocat"
|
||||
},
|
||||
"new_room": {
|
||||
"status_avatar": "Încărcarea avatarului: {count}",
|
||||
|
|
@ -162,7 +170,8 @@
|
|||
"name_room": "Nume cameră",
|
||||
"next": "Următorul",
|
||||
"create": "Creați",
|
||||
"new_room": "Cameră nouă"
|
||||
"new_room": "Cameră nouă",
|
||||
"options": "Opțiuni"
|
||||
},
|
||||
"room_welcome": {
|
||||
"got_it": "L-am prins",
|
||||
|
|
@ -223,7 +232,12 @@
|
|||
"you": "Tu",
|
||||
"reply_image": "Imagine",
|
||||
"reply_audio_message": "Mesaj audio",
|
||||
"reply_video": "Videoclip"
|
||||
"reply_video": "Videoclip",
|
||||
"reply_poll": "Poll",
|
||||
"files": "Fișiere",
|
||||
"images": "Imagini",
|
||||
"file": "Fişier",
|
||||
"download_all": "Descărcați tot"
|
||||
},
|
||||
"language_display_name": "Engleză",
|
||||
"fallbacks": {
|
||||
|
|
@ -231,5 +245,43 @@
|
|||
"original_text": "<original text>",
|
||||
"video_file": "Fișier video",
|
||||
"audio_file": "Fișier audio"
|
||||
},
|
||||
"global": {
|
||||
"save": "Salvează",
|
||||
"notify": "Notifică",
|
||||
"show_less": "Arată mai puține",
|
||||
"show_more": "Arată mai multe",
|
||||
"time": {
|
||||
"recently": "chiar acum"
|
||||
}
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"symbols": "Simboluri",
|
||||
"places": "Locuri",
|
||||
"nature": "Natura",
|
||||
"activity": "Activitate",
|
||||
"flags": "Indicatoare",
|
||||
"objects": "Obiecte"
|
||||
}
|
||||
},
|
||||
"poll_create": {
|
||||
"answer_label_n": "Răspuns",
|
||||
"create": "Publica",
|
||||
"poll_submit": "Trimiteți"
|
||||
},
|
||||
"getlink": {
|
||||
"next": "Următor",
|
||||
"continue": "Continue"
|
||||
},
|
||||
"file_mode": {
|
||||
"sending_progress": "Se trimite...",
|
||||
"close": "Închideți",
|
||||
"files": "Fișiere"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "Activează"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,19 +10,108 @@
|
|||
"start_private_chat": "මෙම පරිශීලක සමඟ පුද්ගලික සංවාදය",
|
||||
"undo": "පෙරසේ",
|
||||
"new_room": "නව කාමරය",
|
||||
"logout": "නික්මෙන්න",
|
||||
"login": "පිවිසෙන්න",
|
||||
"logout": "ඉවත් වන්න",
|
||||
"login": "Login",
|
||||
"back": "ආපසු",
|
||||
"send": "යවන්න",
|
||||
"cancel": "අවලංගු කරන්න"
|
||||
"cancel": "අවලංගු කරන්න",
|
||||
"delete": "මකන්න",
|
||||
"done": "අහවරයි"
|
||||
},
|
||||
"language_display_name": "ඉංග්රීසි",
|
||||
"message": {
|
||||
"download_progress": "{percentage}% බාගත වී ඇත",
|
||||
"file_prefix": "ගොනුව: ",
|
||||
"you": "ඔබ"
|
||||
"you": "ඔබ",
|
||||
"reply_video": "දෘශ්යකය",
|
||||
"reply_poll": "මතවිමසුම",
|
||||
"file": "ගොනුව",
|
||||
"files": "ගොනු"
|
||||
},
|
||||
"room_info": {
|
||||
"user": "{user}"
|
||||
"user": "{user}",
|
||||
"leave_room": "හැරයන්න",
|
||||
"user_admin": "Administrator",
|
||||
"user_moderator": "Moderator",
|
||||
"room_type_default": "පෙරනිමිය",
|
||||
"hide_all": "සඟවන්න",
|
||||
"copy_link": "Copy link",
|
||||
"message_retention_none": "ඕෆ් කරන්න",
|
||||
"message_retention_1_day": "දවස් 1"
|
||||
},
|
||||
"leave": {
|
||||
"go_back": "Go back",
|
||||
"leave": "හැරයන්න",
|
||||
"title_invite": "ඔබට හැරයාමට වුවමනා ද?"
|
||||
},
|
||||
"join": {
|
||||
"join": "කාමරයට එක්වන්න",
|
||||
"user_name_label": "පරිශීලක නාමය"
|
||||
},
|
||||
"new_room": {
|
||||
"new_room": "නව කාමරය",
|
||||
"create": "සාදන්න",
|
||||
"next": "ඊලග",
|
||||
"options": "විකල්ප"
|
||||
},
|
||||
"device_list": {
|
||||
"blocked": "Blocked",
|
||||
"not_verified": "අසත්යාපිත",
|
||||
"verified": "සත්යාපිත"
|
||||
},
|
||||
"profile": {
|
||||
"select_language": "භාෂාව",
|
||||
"password_new": "නව මුරපදය",
|
||||
"display_name": "දර්ශන නාමය",
|
||||
"change_password": "මුරපදය වෙනස් කරන්න"
|
||||
},
|
||||
"global": {
|
||||
"save": "සුරකින්න",
|
||||
"notify": "දැනුම්දීම්"
|
||||
},
|
||||
"poll_create": {
|
||||
"create": "ප්රකාශයට පත්කරන්න",
|
||||
"poll_submit": "Submit",
|
||||
"answer_label_n": "පිළිතුර"
|
||||
},
|
||||
"login": {
|
||||
"login": "Login",
|
||||
"title": "Login",
|
||||
"accept_terms": "පිළිගන්න"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"edit_profile": "පැතිකඩ සංස්කරණය",
|
||||
"logout": "ඉවත් වන්න"
|
||||
},
|
||||
"invite": {
|
||||
"done": "අහවරයි"
|
||||
},
|
||||
"purge_room": {
|
||||
"button": "මකන්න"
|
||||
},
|
||||
"room": {
|
||||
"leave": "හැරයන්න"
|
||||
},
|
||||
"getlink": {
|
||||
"next": "ඊලග",
|
||||
"continue": "ඉදිරියට"
|
||||
},
|
||||
"fallbacks": {
|
||||
"download_name": "බාගන්න"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"flags": "කොඩි",
|
||||
"activity": "ක්රියාකාරිත්වය"
|
||||
}
|
||||
},
|
||||
"file_mode": {
|
||||
"close": "වසන්න",
|
||||
"files": "ගොනු"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "සක්රිය කරන්න"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
"send": "يوللاش",
|
||||
"cancel": "ئەمەلدىن قالدۇرۇڭ",
|
||||
"reply": "جاۋاب",
|
||||
"start_private_chat": "قوللانغۇچى بىلەن شەخسى ئۇچۇرلاشماق"
|
||||
"start_private_chat": "قوللانغۇچى بىلەن شەخسى ئۇچۇرلاشماق",
|
||||
"done": "تامام"
|
||||
},
|
||||
"message": {
|
||||
"upload_progress_with_total": "{count} نىڭ {total} يۈكلەندى",
|
||||
|
|
@ -40,7 +41,27 @@
|
|||
"room_history_joined": "ئەزالار قاتناشقاندىن باشلاپ ئوقۇغىلى بولىدۇ",
|
||||
"room_history_invited": "ئەزالار تەكلىپ قىلىنغان ۋاقىتتىن باشلاپ ئوقۇغىلى بولىدۇ",
|
||||
"room_history_shared": "مۇنازىرەخانىدىكى ھەركىم ئوقۇيالايدۇ",
|
||||
"room_history_world_readable": "ھەركىم ئوقۇيالايدۇ"
|
||||
"room_history_world_readable": "ھەركىم ئوقۇيالايدۇ",
|
||||
"user_powerlevel_change_from_to": "قوللانغۇچى بۇرۇنقى دەرىجىسىدىن يېڭى دەرىجىسىگە كۆتۈرىلدى",
|
||||
"user_aliased_room": "مۇنازىرە ئۆيىنىڭ ئىسمى ئۆزگەرتىلدى",
|
||||
"user_encrypted_room": "قوللانغۇچى مۇنازىرە-خانىنى سىفىرلاشتۇردى",
|
||||
"reply_image": "رەسىم",
|
||||
"user_changed_room_history": "قوللانغۇچى» مۇنازىرەخانىنىڭ تارىخىنى قۇردى»",
|
||||
"user_joined": "قوللانغۇچى مۇنازىرىغا قاتناشتى",
|
||||
"user_changed_guest_access_open": "قوللانغۇچى ئەزالارنىڭ مۇنازىرەخانىغا قوشۇلىشىغا رۇخسەت قىلدى",
|
||||
"user_changed_avatar": "قوللانغۇچى كۆرىنىشىنى ئۆزگەرتتى",
|
||||
"user_changed_room_avatar": "قوللانغۇچى مۇنازىرە-خانىدىكى كۆرىنىشىنى ئۆزگەرتتى",
|
||||
"user_was_invited": "قوللانغۇچى مۇنازىرە ئۆيىگە تەكلىپ قىلىندى...",
|
||||
"user_created_room": "يېڭى مۇنازىرە ئۆيى قۇرۇلدى",
|
||||
"user_left": "قوللانغۇچى مۇنازىرىدىن چېكىندى",
|
||||
"someone": "بىرەيلەن",
|
||||
"user_changed_join_rules": "قوللانغۇچى مۇنازىرەخانىنى مەلۇم تىپقا ئۆزگەرتتى",
|
||||
"user_changed_room_name": "قوللانغۇچى مۇنازىرەخانىنىڭ ئىسمىنى ئۆزگەرتتى",
|
||||
"user_changed_room_topic": "قوللانغۇچى مۇنازىرەخانىنىڭ تېمىسىنى ئۆزگەرتتى",
|
||||
"user_changed_guest_access_closed": "قوللانغۇچى ئەزالارنىڭ مۇنازىرەخانىغا قوشۇلۇشتىن رەت قىلىندى",
|
||||
"reply_video": "سىن",
|
||||
"file": "ھۆججەت",
|
||||
"files": "ھۆججەت"
|
||||
},
|
||||
"language_display_name": "ئۇيغۇرچە",
|
||||
"new_room": {
|
||||
|
|
@ -61,7 +82,8 @@
|
|||
"name_room": "مۇنازىرەخانىغا ئىسىم قويۇڭ",
|
||||
"next": "كېيىنكى",
|
||||
"create": "قۇرۇش",
|
||||
"new_room": "يېڭى مۇنازىرەخانا"
|
||||
"new_room": "يېڭى مۇنازىرەخانا",
|
||||
"options": "تاللانمىلار"
|
||||
},
|
||||
"room": {
|
||||
"purge_failed": "مۇنازىرەخانىنى يۇيۇش مەغلۇب بولدى!",
|
||||
|
|
@ -143,7 +165,9 @@
|
|||
"join_invite": "پەقەت كىشىلەر قوشۇلدى",
|
||||
"permissions": "ئىجازەتكە قوشۇلۇڭ",
|
||||
"created_by": "{ئىشلەتكۈچى قۇرغان",
|
||||
"title": "ياتاق تەپسىلاتلىرى"
|
||||
"title": "ياتاق تەپسىلاتلىرى",
|
||||
"room_type_default": "كۆڭۈلدىكى",
|
||||
"message_retention_none": "تاقاق"
|
||||
},
|
||||
"goodbye": {
|
||||
"view_other_rooms": "باشقا ئۆيلەرنى كۆرۈڭ",
|
||||
|
|
@ -187,7 +211,8 @@
|
|||
"change_name": "ئىسىم ئۆزگەرتىش",
|
||||
"set_password": "پارول بەلگىلەڭ",
|
||||
"temporary_identity": "بۇ كىملىك ۋاقىتلىق. قايتا ئىشلىتىش ئۈچۈن پارول بەلگىلەڭ",
|
||||
"title": "مېنىڭ ئارخىپىم"
|
||||
"title": "مېنىڭ ئارخىپىم",
|
||||
"notification_label": "ئۇقتۇرۇش"
|
||||
},
|
||||
"login": {
|
||||
"login": "كىرىش",
|
||||
|
|
@ -195,12 +220,36 @@
|
|||
"username_required": "قوللانغۇچى ئىسمى تەلەپ قىلىنىدۇ",
|
||||
"password": "پارول كىرگۈزۈڭ",
|
||||
"username": "قوللانغۇچى ئىسمى (مەسىلەن: marta)",
|
||||
"title": "كىرىش"
|
||||
"title": "كىرىش",
|
||||
"accept_terms": "قوشۇل"
|
||||
},
|
||||
"device_list": {
|
||||
"not_verified": "دەلىللەنمىدى",
|
||||
"verified": "دەلىللەندى",
|
||||
"blocked": "چەكلەنگەن",
|
||||
"title": "ئۈسكۈنىلەر"
|
||||
"blocked": "چەكلەنگەن"
|
||||
},
|
||||
"global": {
|
||||
"save": "ساقلاش",
|
||||
"close": "ياپماق"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"places": "ئورۇن تاللىغۇچ",
|
||||
"activity": "مەشغۇلات",
|
||||
"flags": "تاللانمىلار"
|
||||
}
|
||||
},
|
||||
"file_mode": {
|
||||
"close": "يېپىش",
|
||||
"files": "ھۆججەت"
|
||||
},
|
||||
"getlink": {
|
||||
"next": "كېيىنكى",
|
||||
"continue": "Continue"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"enable": "قوزغات"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,20 @@
|
|||
"read_only_room_info": "只允许管理员和版主发送到聊天室",
|
||||
"export_room": "导出聊天",
|
||||
"user_moderator": "版主",
|
||||
"experimental_features": "实验功能"
|
||||
"experimental_features": "实验功能",
|
||||
"direct_link": "我的直接链接",
|
||||
"make_public_warning": "警告:新参与者将可见完整的信息历史记录",
|
||||
"room_type": "聊天室种类",
|
||||
"file_mode_info": "将聊天界面切换到“文件投送”模式",
|
||||
"room_type_default": "默认",
|
||||
"file_mode": "文件模式",
|
||||
"direct_link_desc": "已准备好分享了! 每次有人打开链接时,会打开一个新的直接聊天室。",
|
||||
"copy_link": "复制链接",
|
||||
"make_public": "公开",
|
||||
"message_retention_none": "关闭",
|
||||
"message_retention_1_day": "1 天",
|
||||
"message_retention_8_hours": "8 小时",
|
||||
"message_retention_1_hour": "1 小时"
|
||||
},
|
||||
"leave": {
|
||||
"leave": "离开",
|
||||
|
|
@ -63,10 +76,12 @@
|
|||
"email_not_valid": "电子邮件地址无效",
|
||||
"terms": "主服务器要求您查看并接受以下政策:",
|
||||
"accept_terms": "接受",
|
||||
"email": "您需要验证您的电子邮件地址"
|
||||
"email": "您需要验证您的电子邮件地址",
|
||||
"registration_token": "请输入注册令牌",
|
||||
"token_not_valid": "令牌无效",
|
||||
"send_token": "发送令牌"
|
||||
},
|
||||
"device_list": {
|
||||
"title": "设备",
|
||||
"not_verified": "未验证",
|
||||
"verified": "已验证",
|
||||
"blocked": "被封锁"
|
||||
|
|
@ -137,7 +152,18 @@
|
|||
"outgoing_message_deleted_text": "你删除了这条信息。",
|
||||
"incoming_message_deleted_text": "这条信息已删除。",
|
||||
"not_allowed_to_send": "只允许管理员和版主发送到聊天室",
|
||||
"reaction_count_more": "{reactionCount} 更多"
|
||||
"reaction_count_more": "{reactionCount} 更多",
|
||||
"images": "图片",
|
||||
"send_attachements_dialog_title": "您想发送以下附件吗?",
|
||||
"preparing_to_upload": "准备上传...",
|
||||
"seen_by": "查看者",
|
||||
"seen_by_count": "没有被群成员看到 |已被 1 位成员查看 | 已被 {count} 位成员查看",
|
||||
"files": "文件",
|
||||
"download_all": "全部下载",
|
||||
"file": "文件",
|
||||
"upload_file_too_large": "文件太大, 无法上传!",
|
||||
"upload_exceeded_file_limit": "超出 ({configFormattedUploadSize}) 的最大文件大小。 ",
|
||||
"someone": "某人"
|
||||
},
|
||||
"menu": {
|
||||
"login": "登录",
|
||||
|
|
@ -157,11 +183,11 @@
|
|||
"ignore": "忽略",
|
||||
"join": "加入",
|
||||
"undo": "撤销",
|
||||
"user_kick_and_ban": "移出并禁止该用户",
|
||||
"user_kick": "移出该用户",
|
||||
"user_make_admin": "设为管理员",
|
||||
"user_make_moderator": "设为版主",
|
||||
"user_revoke_moderator": "撤销版主身份"
|
||||
"user_revoke_moderator": "撤销版主身份",
|
||||
"direct_chat": "私聊",
|
||||
"user_kick_and_ban": "退出"
|
||||
},
|
||||
"power_level": {
|
||||
"restricted": "被限制",
|
||||
|
|
@ -211,7 +237,8 @@
|
|||
"title_user": "欢迎您被邀请聊天",
|
||||
"join_user": "开始聊天",
|
||||
"enter_room_user": "开始聊天",
|
||||
"choose_name": "选择要使用的名称"
|
||||
"choose_name": "选择要使用的名称",
|
||||
"you_have_been_banned": "您已被禁止进入该聊天室。"
|
||||
},
|
||||
"profile": {
|
||||
"display_name": "显示名称",
|
||||
|
|
@ -228,7 +255,8 @@
|
|||
"language_description": "Convene 提供多种语言.",
|
||||
"dont_see_yours": "看不到你的?",
|
||||
"tell_us": "告诉我们。",
|
||||
"display_name_required": "显示名称是必需的"
|
||||
"display_name_required": "显示名称是必需的",
|
||||
"notification_label": "通知"
|
||||
},
|
||||
"new_room": {
|
||||
"status_avatar": "正在上传头像:{count}",
|
||||
|
|
@ -261,7 +289,10 @@
|
|||
"join_public": "任何人都可以加入通过打开此链接: {link}。",
|
||||
"room_history_joined": "只有加入后,人们才可以看到发送的信息。",
|
||||
"room_history_is": "聊天室纪录是{type}.",
|
||||
"encrypted": "信息是端到端加密的。"
|
||||
"encrypted": "信息是端到端加密的。",
|
||||
"no_past_messages": "欢迎! 为了您的安全,过去的信息不提供。",
|
||||
"direct_info": "你好,{you}.正在与 {user} 进行私人聊天。",
|
||||
"direct_private_chat": "私信"
|
||||
},
|
||||
"profile_info_popup": {
|
||||
"new_room": "新的聊天室",
|
||||
|
|
@ -282,15 +313,19 @@
|
|||
"global": {
|
||||
"save": "保存",
|
||||
"password_didnot_match": "密码不匹配",
|
||||
"password_hint": "至少 12 个字符,包含至少一个数字、一个大写字母和一个小写字母",
|
||||
"password_hint": "至少 12 个字符,包含一个数字、一个大写字母和一个小写字母",
|
||||
"show_less": "显示较少",
|
||||
"show_more": "展示更多",
|
||||
"add_reaction": "添加反应",
|
||||
"click_to_remove": "点击删除",
|
||||
"time": {
|
||||
"recently": "刚才"
|
||||
"recently": "刚才",
|
||||
"hours": "1小时前| {n}小时前",
|
||||
"minutes": "1 分钟前 | {n} 分钟前",
|
||||
"days": "1天前| {n}天前"
|
||||
},
|
||||
"close": "关闭"
|
||||
"close": "关闭",
|
||||
"notify": "通知"
|
||||
},
|
||||
"logout": {
|
||||
"confirm_text": "您确定要注销吗?"
|
||||
|
|
@ -328,5 +363,54 @@
|
|||
"fetched_n_of_total_events": "已获取 {count} 个事件,共 {total} 个事件",
|
||||
"processed_n_of_total_events": "已处理 {count} 个事件的媒体,共 {total} 个事件",
|
||||
"export_filename": "导出的聊天 {date}"
|
||||
},
|
||||
"file_mode": {
|
||||
"add_a_message": "添加留言",
|
||||
"send_more_files": "发送更多文件",
|
||||
"choose_files": "地方",
|
||||
"secure_file_send": "安全文件发送",
|
||||
"sending": "发送中",
|
||||
"files": "文件",
|
||||
"sending_progress": "正在发送...",
|
||||
"any_file_format_accepted": "接受任何文件格式",
|
||||
"files_sent_with_note": "发送 1 个文件并附有备注! | 已发送 {count} 个文件并附有备注!",
|
||||
"close": "关闭",
|
||||
"files_sent": "已发送 1 个文件! | 已发送 {count} 个文件!"
|
||||
},
|
||||
"notification": {
|
||||
"dialog": {
|
||||
"body": "再也不要错过任何信息或重要对话! 每当有人向您发送信息或回复您的聊天时,您会收到通知。",
|
||||
"title": "通过聊天通知保持联系!",
|
||||
"enable": "启用"
|
||||
},
|
||||
"title": "收到新信息",
|
||||
"blocked_message": "通知被屏蔽。 请重置权限"
|
||||
},
|
||||
"emoji": {
|
||||
"categories": {
|
||||
"nature": "自然",
|
||||
"places": "地方",
|
||||
"activity": "活动",
|
||||
"flags": "旗帜",
|
||||
"foods": "食品",
|
||||
"objects": "物体",
|
||||
"peoples": "人民",
|
||||
"symbols": "符号",
|
||||
"frequently": "常用的"
|
||||
},
|
||||
"search": "搜索..."
|
||||
},
|
||||
"getlink": {
|
||||
"title": "获取直接链接",
|
||||
"info": "直接链接为人们与您提供安全的沟通渠道。首先,选择当人们与您聊天时显示的屏幕名称。",
|
||||
"share_qr": "扫描二维码",
|
||||
"next": "下一步",
|
||||
"scan_title": "扫描此二维码即可直接聊天",
|
||||
"continue": "继续",
|
||||
"ready_to_share": "已准备好分享了! 每次有人打开链接时,会打开一个新的直接聊天室。",
|
||||
"qr_image_copied": "图片已复制到剪贴板",
|
||||
"hello": "你好{user},\n这是您的直接链接",
|
||||
"username": "输入屏幕名称(例如:waku)",
|
||||
"different_link": "获取其它链接"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,16 @@
|
|||
no-gutters
|
||||
align-content="center"
|
||||
v-on="$listeners"
|
||||
v-show="icon === 'notifications_active' ? this.windowNotificationPermission() !== 'granted' : true"
|
||||
>
|
||||
<v-col cols="auto" class="me-2">
|
||||
<v-col cols="auto" class="me-2 align-self-center">
|
||||
<v-icon :size="iconSize">{{ icon }}</v-icon>
|
||||
</v-col>
|
||||
<v-col>{{ text }}</v-col>
|
||||
<v-col class="align-self-center">{{ text }}</v-col>
|
||||
<v-col cols="auto" v-if="$slots.default"><slot/></v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { windowNotificationPermission } from "../plugins/notificationAndServiceWorker.js"
|
||||
|
||||
export default {
|
||||
name: "ActionRow",
|
||||
props: {
|
||||
|
|
@ -37,9 +35,6 @@ export default {
|
|||
return "";
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
windowNotificationPermission
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
canRecordAudio() {
|
||||
return !this.$matrix.currentRoomIsReadOnlyForUser && util.browserCanRecordAudio();
|
||||
return this.$matrix.userCanSendMessageInCurrentRoom && util.browserCanRecordAudio();
|
||||
},
|
||||
currentTime() {
|
||||
return util.formatDuration(this.info ? this.info.currentTime : 0);
|
||||
|
|
@ -414,7 +414,7 @@ export default {
|
|||
},
|
||||
|
||||
micButtonClicked() {
|
||||
if (this.$matrix.currentRoomIsReadOnlyForUser) {
|
||||
if (!this.$matrix.userCanSendMessageInCurrentRoom) {
|
||||
this.showReadOnlyToast = true;
|
||||
setTimeout(() => {
|
||||
this.showReadOnlyToast = false;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
<ChatHeaderPrivate class="chat-header flex-grow-0 flex-shrink-0"
|
||||
v-on:header-click="onHeaderClick"
|
||||
v-on:view-room-details="viewRoomDetails"
|
||||
v-on:notify="onNotificationDialog"
|
||||
v-on:purge="showPurgeConfirmation = true"
|
||||
v-if="!useFileModeNonAdmin && $matrix.isDirectRoom(room)" />
|
||||
<ChatHeader class="chat-header flex-grow-0 flex-shrink-0"
|
||||
v-on:header-click="onHeaderClick"
|
||||
v-on:view-room-details="viewRoomDetails"
|
||||
v-on:notify="onNotificationDialog"
|
||||
v-on:purge="showPurgeConfirmation = true"
|
||||
v-else-if="!useFileModeNonAdmin" />
|
||||
<AudioLayout ref="chatContainer" class="auto-audio-player-root" v-if="useVoiceMode" :room="room"
|
||||
:events="events" :autoplay="!showRecorder"
|
||||
|
|
@ -43,22 +43,16 @@
|
|||
v-on:download="download(selectedEvent)" v-on:more="
|
||||
isEmojiQuickReaction= true
|
||||
showMoreMessageOperations({event: selectedEvent, anchor: $event.anchor})
|
||||
" :originalEvent="selectedEvent" :timelineSet="timelineSet" />
|
||||
</div>
|
||||
|
||||
<div ref="avatarOperationsStrut" class="avatar-operations-strut">
|
||||
<avatar-operations ref="avatarOperations" :style="avatarOpStyle" v-on:close="
|
||||
showAvatarMenu = false;
|
||||
showAvatarMenuAnchor = null;
|
||||
" v-on:start-private-chat="startPrivateChat($event)" v-if="selectedEvent && showAvatarMenu" :room="room"
|
||||
:originalEvent="selectedEvent" />
|
||||
" :originalEvent="selectedEvent" :timelineSet="timelineSet"
|
||||
:userCanSendReactionAndAnswerPoll="$matrix.userCanSendReactionAndAnswerPollInCurrentRoom"
|
||||
:userCanSendMessage="$matrix.userCanSendMessageInCurrentRoom"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
|
||||
<resize-observer ref="chatContainerResizer" @notify="handleChatContainerResize" />
|
||||
|
||||
<CreatedRoomWelcomeHeader v-if="showCreatedRoomWelcomeHeader" v-on:close="closeCreateRoomWelcomeHeader" />
|
||||
<DirectChatWelcomeHeader v-if="showDirectChatWelcomeHeader" v-on:close="closeDirectChatWelcomeHeader" />
|
||||
<component :is="roomWelcomeHeader" v-on:close="closeRoomWelcomeHeader"></component>
|
||||
|
||||
<div v-for="(event, index) in filteredEvents" :key="event.getId()" :eventId="event.getId()">
|
||||
<!-- DAY Marker, shown for every new day in the timeline -->
|
||||
|
|
@ -78,9 +72,9 @@
|
|||
<component :is="componentForEvent(event)" :room="room" :originalEvent="event" :nextEvent="filteredEvents[index + 1]"
|
||||
:timelineSet="timelineSet" v-on:send-quick-reaction.stop="sendQuickReaction"
|
||||
:componentFn="componentForEvent"
|
||||
v-on:context-menu="showContextMenuForEvent({event: event, anchor: $event.anchor})"
|
||||
v-on:context-menu="showContextMenuForEvent({event: event, anchor: $event.anchor})"
|
||||
v-on:own-avatar-clicked="viewProfile"
|
||||
v-on:other-avatar-clicked="showAvatarMenuForEvent({event: event, anchor: $event.anchor})"
|
||||
v-on:other-avatar-clicked="showAvatarMenuForEvent({event: event, anchor: $event.anchor})"
|
||||
v-on:download="download(event)"
|
||||
v-on:poll-closed="pollWasClosed(event)"
|
||||
v-on:more="
|
||||
|
|
@ -140,7 +134,7 @@
|
|||
{{ typingMembersString }}
|
||||
</div>
|
||||
</v-row>
|
||||
<v-row class="input-area-inner align-center" v-show="!showRecorder" v-if="!$matrix.currentRoomIsReadOnlyForUser">
|
||||
<v-row class="input-area-inner align-center" v-show="!showRecorder" v-if="$matrix.userCanSendMessageInCurrentRoom">
|
||||
<v-col class="flex-grow-1 flex-shrink-1 ma-0 pa-0">
|
||||
<v-textarea height="undefined" ref="messageInput" full-width auto-grow rows="1" v-model="currentInput"
|
||||
no-resize class="input-area-text" :placeholder="$t('message.your_message')" hide-details
|
||||
|
|
@ -211,11 +205,11 @@
|
|||
<VoiceRecorder :micButtonRef="$refs.mic_button" :ptt="showRecorderPTT" :show="showRecorder"
|
||||
v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
||||
</div>
|
||||
<div v-if="!useVoiceMode && room && $matrix.currentRoomIsReadOnlyForUser" class="input-area-read-only">{{ $t("message.not_allowed_to_send") }}</div>
|
||||
<div v-if="!useVoiceMode && room && !$matrix.userCanSendMessageInCurrentRoom" class="input-area-read-only">{{ $t("message.not_allowed_to_send") }}</div>
|
||||
</v-container>
|
||||
|
||||
<input ref="attachment" type="file" name="attachment" @change="handlePickedAttachment($event)"
|
||||
accept="image/*, audio/*, video/*, .pdf" class="d-none" multiple/>
|
||||
accept="image/*,audio/*,video/*,.pdf,application/pdf,.apk,application/vnd.android.package-archive,.ipa,.zip,application/zip,application/x-zip-compressed,multipart/x-zip" class="d-none" multiple/>
|
||||
|
||||
<div v-if="currentFileInputsDialog && !useFileModeNonAdmin">
|
||||
<v-dialog v-model="currentFileInputsDialog" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'" persistent scrollable>
|
||||
|
|
@ -324,63 +318,34 @@
|
|||
|
||||
<CreatePollDialog :show="showCreatePollDialog" @close="showCreatePollDialog = false" />
|
||||
|
||||
<!-- Dialog for request Notification and register service worker-->
|
||||
<v-dialog
|
||||
v-model="notificationDialog"
|
||||
persistent
|
||||
class="ma-0 pa-0"
|
||||
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'"
|
||||
>
|
||||
<div class="dialog-content text-center">
|
||||
<v-icon size="30">notifications_active</v-icon>
|
||||
<h2 class="dialog-title">
|
||||
{{ $t("notification.dialog.title") }}
|
||||
</h2>
|
||||
<div class="dialog-text">{{ $t("notification.dialog.body") }}</div>
|
||||
<v-container fluid>
|
||||
<v-row cols="12">
|
||||
<v-col cols="6">
|
||||
<v-btn
|
||||
depressed
|
||||
text
|
||||
block
|
||||
class="text-button"
|
||||
@click="notificationDialog = false"
|
||||
>{{ $t("global.close") }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
<v-col cols="6" align="center">
|
||||
<v-btn
|
||||
color="primary"
|
||||
depressed
|
||||
block
|
||||
class="filled-button"
|
||||
@click.stop="onNotifyRequest"
|
||||
>{{ $t("notification.dialog.enable") }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</v-dialog>
|
||||
<UserProfileDialog
|
||||
:show="showProfileDialog"
|
||||
:activeMember="compActiveMember"
|
||||
:room="room"
|
||||
@close="showProfileDialog = false"
|
||||
/>
|
||||
|
||||
<!-- PURGE ROOM POPUP -->
|
||||
<PurgeRoomDialog :show="showPurgeConfirmation" :room="room" @close="showPurgeConfirmation = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import { TimelineWindow, EventTimeline } from "matrix-js-sdk";
|
||||
import util, { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE } from "../plugins/utils";
|
||||
import util, { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE, ROOM_TYPE_CHANNEL } from "../plugins/utils";
|
||||
import MessageOperations from "./messages/MessageOperations.vue";
|
||||
import AvatarOperations from "./messages/AvatarOperations.vue";
|
||||
import ChatHeader from "./ChatHeader";
|
||||
import ChatHeaderPrivate from "./ChatHeaderPrivate.vue";
|
||||
import VoiceRecorder from "./VoiceRecorder";
|
||||
import RoomInfoBottomSheet from "./RoomInfoBottomSheet";
|
||||
import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader";
|
||||
import DirectChatWelcomeHeader from "./DirectChatWelcomeHeader";
|
||||
import WelcomeHeaderRoom from "./welcome_headers/WelcomeHeaderRoom";
|
||||
import WelcomeHeaderDirectChat from "./welcome_headers/WelcomeHeaderDirectChat";
|
||||
import WelcomeHeaderChannel from "./welcome_headers/WelcomeHeaderChannel";
|
||||
import NoHistoryRoomWelcomeHeader from "./NoHistoryRoomWelcomeHeader.vue";
|
||||
import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet";
|
||||
import StickerPickerBottomSheet from "./StickerPickerBottomSheet";
|
||||
import UserProfileDialog from "./UserProfileDialog.vue"
|
||||
import BottomSheet from "./BottomSheet.vue";
|
||||
import ImageResize from "image-resize";
|
||||
import CreatePollDialog from "./CreatePollDialog.vue";
|
||||
|
|
@ -388,9 +353,9 @@ import chatMixin from "./chatMixin";
|
|||
import sendAttachmentsMixin from "./sendAttachmentsMixin";
|
||||
import AudioLayout from "./AudioLayout.vue";
|
||||
import FileDropLayout from "./file_mode/FileDropLayout";
|
||||
import { requestNotificationPermission, windowNotificationPermission } from "../plugins/notificationAndServiceWorker.js"
|
||||
import roomTypeMixin from "./roomTypeMixin";
|
||||
import roomMembersMixin from "./roomMembersMixin";
|
||||
import PurgeRoomDialog from "../components/PurgeRoomDialog";
|
||||
|
||||
const sizeOf = require("image-size");
|
||||
const dataUriToBuffer = require("data-uri-to-buffer");
|
||||
|
|
@ -433,16 +398,17 @@ export default {
|
|||
MessageOperations,
|
||||
VoiceRecorder,
|
||||
RoomInfoBottomSheet,
|
||||
CreatedRoomWelcomeHeader,
|
||||
DirectChatWelcomeHeader,
|
||||
WelcomeHeaderRoom,
|
||||
WelcomeHeaderDirectChat,
|
||||
NoHistoryRoomWelcomeHeader,
|
||||
MessageOperationsBottomSheet,
|
||||
StickerPickerBottomSheet,
|
||||
BottomSheet,
|
||||
AvatarOperations,
|
||||
CreatePollDialog,
|
||||
AudioLayout,
|
||||
FileDropLayout
|
||||
FileDropLayout,
|
||||
UserProfileDialog,
|
||||
PurgeRoomDialog,
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -472,8 +438,6 @@ export default {
|
|||
showNoRecordingAvailableDialog: false,
|
||||
showContextMenu: false,
|
||||
showContextMenuAnchor: null,
|
||||
showAvatarMenu: false,
|
||||
showAvatarMenuAnchor: null,
|
||||
initialLoadDone: false,
|
||||
loading: false, // Set this to true during long operations to show a "spinner" overlay
|
||||
showRecorder: false,
|
||||
|
|
@ -499,10 +463,7 @@ export default {
|
|||
lastRR: null,
|
||||
|
||||
/** If we just created this room, show a small welcome header with info */
|
||||
hideCreatedRoomWelcomeHeader: false,
|
||||
|
||||
/** For direct chats, show a small welcome header with info about the other party */
|
||||
hideDirectChatWelcomeHeader: false,
|
||||
hideRoomWelcomeHeader: false,
|
||||
|
||||
/** An array of recent emojis. Used in the "message operations" popup. */
|
||||
recentEmojis: [],
|
||||
|
|
@ -525,7 +486,13 @@ export default {
|
|||
Places: this.$t("emoji.categories.places")
|
||||
}
|
||||
},
|
||||
notificationDialog: false
|
||||
|
||||
/**
|
||||
* A timer to handle message retention/auto deletion
|
||||
*/
|
||||
retentionTimer: null,
|
||||
showProfileDialog: false,
|
||||
showPurgeConfirmation: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -554,6 +521,10 @@ export default {
|
|||
this.$root.$off('audio-playback-ended', this.audioPlaybackEnded);
|
||||
this.$audioPlayer.pause();
|
||||
this.stopRRTimer();
|
||||
if (this.retentionTimer) {
|
||||
clearInterval(this.retentionTimer);
|
||||
this.retentionTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
|
|
@ -562,6 +533,10 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
compActiveMember() {
|
||||
const currentUserId= this.selectedEvent?.sender.userId || this.$matrix.currentUserId
|
||||
return this.joinedAndInvitedMembers.find(({userId}) => userId === currentUserId)
|
||||
},
|
||||
nonImageFiles() {
|
||||
return this.isCurrentFileInputsAnArray && this.currentFileInputs.filter(file => !file?.type.includes("image/"))
|
||||
},
|
||||
|
|
@ -647,30 +622,6 @@ export default {
|
|||
showMessageOperations() {
|
||||
return this.selectedEvent && this.showContextMenu;
|
||||
},
|
||||
avatarOpStyle() {
|
||||
// Calculate where to show the context menu.
|
||||
//
|
||||
const ref = this.selectedEvent && this.$refs[this.selectedEvent.getId()];
|
||||
var top = 0;
|
||||
var left = "unset";
|
||||
var right = "unset";
|
||||
if (ref && ref[0]) {
|
||||
if (this.showAvatarMenuAnchor) {
|
||||
var rectAnchor = this.showAvatarMenuAnchor.getBoundingClientRect();
|
||||
var rectChat = this.$refs.avatarOperationsStrut.getBoundingClientRect();
|
||||
top = rectAnchor.top - rectChat.top;
|
||||
if (this.$vuetify.rtl) {
|
||||
right = (rectAnchor.right - rectChat.right)+ "px";
|
||||
} else {
|
||||
left = (rectAnchor.left - rectChat.left) + "px";
|
||||
}
|
||||
// if (left + 250 > rectChat.right) {
|
||||
// left = rectChat.right - 250; // Pretty ugly, but we want to make sure it does not escape the screen, and we don't have the exakt width of it (yet)!
|
||||
// }
|
||||
}
|
||||
}
|
||||
return "top:" + top + "px;left:" + left + ";right:" + right;
|
||||
},
|
||||
canRecordAudio() {
|
||||
return util.browserCanRecordAudio();
|
||||
},
|
||||
|
|
@ -743,14 +694,21 @@ export default {
|
|||
return this.room && this.room.getJoinRule() == "public";
|
||||
},
|
||||
|
||||
showCreatedRoomWelcomeHeader() {
|
||||
return !this.hideCreatedRoomWelcomeHeader && this.roomCreatedByUsRecently && !this.isDirectRoom;
|
||||
showRoomWelcomeHeader() {
|
||||
return this.roomWelcomeHeader != null;
|
||||
},
|
||||
|
||||
showDirectChatWelcomeHeader() {
|
||||
return !this.hideDirectChatWelcomeHeader && this.roomCreatedByUsRecently && this.isDirectRoom;
|
||||
roomWelcomeHeader() {
|
||||
if (!this.hideRoomWelcomeHeader && this.roomCreatedByUsRecently) {
|
||||
if (this.roomDisplayType == ROOM_TYPE_CHANNEL) {
|
||||
return WelcomeHeaderChannel;
|
||||
}
|
||||
if (this.isDirectRoom) {
|
||||
return WelcomeHeaderDirectChat;
|
||||
}
|
||||
return WelcomeHeaderRoom;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
chatContainerStyle() {
|
||||
if (this.$config.chat_backgrounds && this.room && this.roomId) {
|
||||
const roomType = this.isDirectRoom ? "direct" : this.isPublicRoom ? "public" : "invite";
|
||||
|
|
@ -817,6 +775,12 @@ export default {
|
|||
this.events.filter(event => (event.threadRootId && !event.parentThread)).forEach(event => this.setParentThread(event));
|
||||
this.events.filter(event => (event.replyEventId && !event.replyEvent)).forEach(event => this.setReplyToEvent(event));
|
||||
console.log("Loading finished!");
|
||||
this.updateRetentionTimer();
|
||||
} else if (!value) {
|
||||
if (this.retentionTimer) {
|
||||
clearInterval(this.retentionTimer);
|
||||
this.retentionTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -837,8 +801,7 @@ export default {
|
|||
this.timelineWindow = null;
|
||||
this.typingMembers = [];
|
||||
this.initialLoadDone = false;
|
||||
this.hideDirectChatWelcomeHeader = false;
|
||||
this.hideCreatedRoomWelcomeHeader = false;
|
||||
this.hideRoomWelcomeHeader = false;
|
||||
|
||||
// Stop RR timer
|
||||
this.stopRRTimer();
|
||||
|
|
@ -914,18 +877,60 @@ export default {
|
|||
this.initialLoadDone = true;
|
||||
console.log("Loading finished!");
|
||||
},
|
||||
windowNotificationPermission,
|
||||
onNotificationDialog() {
|
||||
if(this.windowNotificationPermission() === 'denied') {
|
||||
alert(this.$t("notification.blocked_message"));
|
||||
} else if(this.windowNotificationPermission() === 'default') {
|
||||
this.notificationDialog = true;
|
||||
|
||||
/**
|
||||
* Set events to display. At the same time, filter out messages that are past rentention period etc.
|
||||
*/
|
||||
setEvents(events) {
|
||||
this.events = this.filterOutOldAndInvisible(events);
|
||||
},
|
||||
|
||||
filterOutOldAndInvisible(events) {
|
||||
return this.removeTimedOutEvents(events.filter((e) => e.messageVisibility().visible));
|
||||
},
|
||||
|
||||
updateRetentionTimer(maybeEvent) {
|
||||
const retentionEvent = maybeEvent || (this.room && this.room.currentState.getStateEvents("m.room.retention", ""));
|
||||
if (retentionEvent) {
|
||||
const maxLifetime = parseInt(retentionEvent.getContent().max_lifetime);
|
||||
if (maxLifetime) {
|
||||
if (!this.retentionTimer) {
|
||||
this.retentionTimer = setInterval(this.onRetentionTimer, 60000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.retentionTimer) {
|
||||
clearInterval(this.retentionTimer);
|
||||
this.retentionTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
removeTimedOutEvents(events) {
|
||||
const retentionEvent = this.room && this.room.currentState.getStateEvents("m.room.retention", "");
|
||||
let maxLifetime = 0;
|
||||
if (retentionEvent) {
|
||||
maxLifetime = parseInt(retentionEvent.getContent().max_lifetime);
|
||||
}
|
||||
return events.filter((e) => {
|
||||
if (maxLifetime > 0 && !e.isState()) { // Keep all state events
|
||||
if (e.getLocalAge() < maxLifetime) {
|
||||
return true;
|
||||
}
|
||||
e.applyVisibilityEvent({ visible: false, eventId: e.getId(), reason: null});
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
onRetentionTimer() {
|
||||
const events = this.filterOutOldAndInvisible(this.events);
|
||||
if (events.length != this.events.length) {
|
||||
this.events = events; // Changed
|
||||
}
|
||||
},
|
||||
onNotifyRequest() {
|
||||
requestNotificationPermission()
|
||||
this.notificationDialog = false;
|
||||
},
|
||||
|
||||
onRoomJoined(initialEventId) {
|
||||
// Listen to events
|
||||
this.$matrix.on("Room.timeline", this.onEvent);
|
||||
|
|
@ -940,7 +945,7 @@ export default {
|
|||
this.timelineWindow
|
||||
.load(initialEventId, 20)
|
||||
.then(() => {
|
||||
self.events = self.timelineWindow.getEvents();
|
||||
self.setEvents(self.timelineWindow.getEvents());
|
||||
|
||||
const getMoreIfNeeded = function _getMoreIfNeeded() {
|
||||
const container = self.$refs.chatContainer;
|
||||
|
|
@ -951,7 +956,7 @@ export default {
|
|||
self.timelineWindow.canPaginate(EventTimeline.BACKWARDS)
|
||||
) {
|
||||
return self.timelineWindow.paginate(EventTimeline.BACKWARDS, 10, true, 5).then((success) => {
|
||||
self.events = self.timelineWindow.getEvents();
|
||||
self.setEvents(self.timelineWindow.getEvents());
|
||||
if (success) {
|
||||
return _getMoreIfNeeded.call(self);
|
||||
} else {
|
||||
|
|
@ -976,7 +981,7 @@ export default {
|
|||
// unknownRelations.forEach((event) => this.room.relations.aggregateChildEvent(event));
|
||||
|
||||
this.setInitialLoadDone();
|
||||
if (initialEventId && !this.showCreatedRoomWelcomeHeader) {
|
||||
if (initialEventId && !this.showRoomWelcomeHeader) {
|
||||
const event = this.room.findEventById(initialEventId);
|
||||
this.$nextTick(() => {
|
||||
if (event && event.parentThread) {
|
||||
|
|
@ -985,7 +990,7 @@ export default {
|
|||
self.scrollToEvent(initialEventId);
|
||||
}
|
||||
});
|
||||
} else if (this.showCreatedRoomWelcomeHeader || this.showDirectChatWelcomeHeader) {
|
||||
} else if (this.showRoomWelcomeHeader) {
|
||||
self.onScroll();
|
||||
}
|
||||
self.restartRRTimer();
|
||||
|
|
@ -998,7 +1003,7 @@ export default {
|
|||
this.onRoomJoined(null);
|
||||
} else {
|
||||
// Error. Done loading.
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
this.setEvents(this.timelineWindow.getEvents());
|
||||
this.setInitialLoadDone();
|
||||
}
|
||||
})
|
||||
|
|
@ -1031,7 +1036,7 @@ export default {
|
|||
.then(() => {
|
||||
self.timelineSet = timelineSet;
|
||||
self.timelineWindow = timelineWindow;
|
||||
self.events = self.timelineWindow.getEvents();
|
||||
self.setEvents(self.timelineWindow.getEvents());
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
|
|
@ -1145,7 +1150,7 @@ export default {
|
|||
if (tl) {
|
||||
const parentEvent = tl.getEvents().find((e) => e.getId() === event.threadRootId);
|
||||
if (parentEvent) {
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
this.setEvents(this.timelineWindow.getEvents());
|
||||
const fn = () => {
|
||||
Vue.set(parentEvent, "isMxThread", true);
|
||||
Vue.set(event, "parentThread", parentEvent);
|
||||
|
|
@ -1157,7 +1162,7 @@ export default {
|
|||
this.onLayoutChange(fn, element);
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
|
|
@ -1178,7 +1183,7 @@ export default {
|
|||
if (tl) {
|
||||
const parentEvent = tl.getEvents().find((e) => e.getId() === event.replyEventId);
|
||||
if (parentEvent) {
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
this.setEvents(this.timelineWindow.getEvents());
|
||||
const fn = () => {Vue.set(event, "replyEvent", parentEvent);};
|
||||
if (this.initialLoadDone) {
|
||||
const sel = "[eventId=\"" + parentEvent.getId() + "\"]";
|
||||
|
|
@ -1217,7 +1222,7 @@ export default {
|
|||
this.paginateBackIfNeeded();
|
||||
}
|
||||
|
||||
if (loadingDone && event.forwardLooking && (!event.isRelation() || event.isMxThread || event.threadRootId || event.parentThread )) {
|
||||
if (loadingDone && event.forwardLooking && (!event.isRelation() || event.isMxThread || event.threadRootId || event.parentThread)) {
|
||||
// If we are at bottom, scroll to see new events...
|
||||
var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll
|
||||
const container = this.chatContainer;
|
||||
|
|
@ -1234,13 +1239,17 @@ export default {
|
|||
(event.getPrevContent() || {}).membership == "join" &&
|
||||
(
|
||||
(event.getContent().membership == "leave" && event.getSender() != this.currentUserId) ||
|
||||
(event.getContent().membership == "ban" ))
|
||||
) {
|
||||
this.$store.commit("setCurrentRoomId", null);
|
||||
const wasPurged = event.getContent().reason == "Room Deleted";
|
||||
this.$navigation.push({ name: "Goodbye", params: { roomWasPurged: wasPurged } }, -1);
|
||||
}
|
||||
(event.getContent().membership == "ban"))
|
||||
) {
|
||||
this.$store.commit("setCurrentRoomId", null);
|
||||
const wasPurged = event.getContent().reason == "Room Deleted";
|
||||
this.$navigation.push({ name: "Goodbye", params: { roomWasPurged: wasPurged } }, -1);
|
||||
}
|
||||
|
||||
else if (event.getType() === "m.room.retention") {
|
||||
this.updateRetentionTimer(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onUserTyping(event, member) {
|
||||
|
|
@ -1459,7 +1468,7 @@ export default {
|
|||
.then((success) => {
|
||||
if (success && this.scrollPosition) {
|
||||
this.scrollPosition.prepareFor("up");
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
this.setEvents(this.timelineWindow.getEvents());
|
||||
this.$nextTick(() => {
|
||||
// restore scroll position!
|
||||
console.log("Restore scroll!");
|
||||
|
|
@ -1484,7 +1493,7 @@ export default {
|
|||
.paginate(EventTimeline.FORWARDS, 10, true)
|
||||
.then((success) => {
|
||||
if (success) {
|
||||
this.events = this.timelineWindow.getEvents();
|
||||
this.setEvents(this.timelineWindow.getEvents());
|
||||
if (!this.useVoiceMode && this.scrollPosition) {
|
||||
this.scrollPosition.prepareFor("down");
|
||||
this.$nextTick(() => {
|
||||
|
|
@ -1562,7 +1571,7 @@ export default {
|
|||
|
||||
setReplyToImage(event) {
|
||||
util
|
||||
.getThumbnail(this.$matrix.matrixClient, event)
|
||||
.getThumbnail(this.$matrix.matrixClient, event, this.$config)
|
||||
.then((url) => {
|
||||
this.replyToImg = url;
|
||||
})
|
||||
|
|
@ -1688,50 +1697,20 @@ export default {
|
|||
showAvatarMenuForEvent(e) {
|
||||
const event = e.event;
|
||||
this.selectedEvent = event;
|
||||
this.showAvatarMenu = true;
|
||||
this.showAvatarMenuAnchor = e.anchor;
|
||||
|
||||
this.showProfileDialog = true
|
||||
},
|
||||
|
||||
viewProfile() {
|
||||
this.$navigation.push({ name: "Profile" }, 1);
|
||||
},
|
||||
|
||||
startPrivateChat(e) {
|
||||
this.loading = true;
|
||||
this.$matrix
|
||||
.getOrCreatePrivateChat(e.event.getSender())
|
||||
.then((room) => {
|
||||
this.$nextTick(() => {
|
||||
this.$navigation.push(
|
||||
{
|
||||
name: "Chat",
|
||||
params: {
|
||||
roomId: util.sanitizeRoomId(room.getCanonicalAlias() || room.roomId),
|
||||
},
|
||||
},
|
||||
-1
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
closeContextMenusIfOpen(e) {
|
||||
if (this.showContextMenu) {
|
||||
this.showContextMenu = false;
|
||||
this.showContextMenuAnchor = null;
|
||||
e.preventDefault();
|
||||
}
|
||||
if (this.showAvatarMenu) {
|
||||
this.showAvatarMenu = false;
|
||||
this.showAvatarMenuAnchor = null;
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
/** Stop Read Receipt timer */
|
||||
|
|
@ -1847,18 +1826,8 @@ export default {
|
|||
this.$analytics.event("Audio", "Voice message sent");
|
||||
},
|
||||
|
||||
closeCreateRoomWelcomeHeader() {
|
||||
this.hideCreatedRoomWelcomeHeader = true;
|
||||
this.$nextTick(() => {
|
||||
// We change the layout when removing the welcome header, so call
|
||||
// onScroll here to handle updates (e.g. remove the "scroll to last" if we now
|
||||
// can see all messages).
|
||||
this.onScroll();
|
||||
});
|
||||
},
|
||||
|
||||
closeDirectChatWelcomeHeader() {
|
||||
this.hideDirectChatWelcomeHeader = true;
|
||||
closeRoomWelcomeHeader() {
|
||||
this.hideRoomWelcomeHeader = true;
|
||||
this.$nextTick(() => {
|
||||
// We change the layout when removing the welcome header, so call
|
||||
// onScroll here to handle updates (e.g. remove the "scroll to last" if we now
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
</v-col>
|
||||
<v-col cols="auto" class="text-end ma-0 pa-0 ms-1">
|
||||
<v-btn id="btn-purge-room" v-if="userCanPurgeRoom" class="mx-2 box-shadow-none" fab dark small color="red"
|
||||
@click.stop="showPurgeConfirmation = true">
|
||||
@click.stop="$emit('purge')">
|
||||
<v-icon light>$vuetify.icons.ic_moderator-delete</v-icon>
|
||||
</v-btn>
|
||||
<v-btn id="btn-leave-room" class="mx-2 box-shadow-none" fab dark small color="red" @click.stop="leaveRoom" v-else>
|
||||
|
|
@ -65,9 +65,6 @@
|
|||
<MoreMenuPopup :show="showMoreMenu" :menuItems="moreMenuItems" @close="showMoreMenu = false"
|
||||
v-on:leave="showLeaveConfirmation = true" />
|
||||
|
||||
<!-- PURGE ROOM POPUP -->
|
||||
<PurgeRoomDialog :show="showPurgeConfirmation" :room="room" @close="showPurgeConfirmation = false" />
|
||||
|
||||
<RoomExport :room="room" v-if="downloadingChat" v-on:close="downloadingChat = false" />
|
||||
|
||||
</v-container>
|
||||
|
|
@ -78,7 +75,6 @@ import LeaveRoomDialog from "../components/LeaveRoomDialog";
|
|||
import ProfileInfoPopup from "../components/ProfileInfoPopup";
|
||||
import MoreMenuPopup from "../components/MoreMenuPopup";
|
||||
import profileInfoMixin from "../components/profileInfoMixin";
|
||||
import PurgeRoomDialog from "../components/PurgeRoomDialog";
|
||||
import RoomExport from "../components/RoomExport";
|
||||
|
||||
import roomInfoMixin from "./roomInfoMixin";
|
||||
|
|
@ -90,7 +86,6 @@ export default {
|
|||
LeaveRoomDialog,
|
||||
ProfileInfoPopup,
|
||||
MoreMenuPopup,
|
||||
PurgeRoomDialog,
|
||||
RoomExport
|
||||
},
|
||||
data() {
|
||||
|
|
@ -98,7 +93,6 @@ export default {
|
|||
memberCount: null,
|
||||
showLeaveConfirmation: false,
|
||||
showProfileInfo: false,
|
||||
showPurgeConfirmation: false,
|
||||
showMoreMenu: false,
|
||||
downloadingChat: false,
|
||||
showMissedItemsInfo: false,
|
||||
|
|
@ -120,26 +114,6 @@ export default {
|
|||
room() {
|
||||
return this.$matrix.currentRoom;
|
||||
},
|
||||
memberAvatar() {
|
||||
let roomMember;
|
||||
if (this.room) {
|
||||
this.room.getMembers().forEach(member => {
|
||||
if (this.room.name === member.name) {
|
||||
roomMember = member;
|
||||
}
|
||||
});
|
||||
if (roomMember) {
|
||||
return roomMember.getAvatarUrl(
|
||||
this.$matrix.matrixClient.getHomeserverUrl(),
|
||||
40,
|
||||
40,
|
||||
"scale",
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
notifications() {
|
||||
return this.$matrix.joinedRooms.some(r => (r.roomId !== this.$matrix.currentRoomId && r.getCanonicalAlias() !== this.$matrix.currentRoomId) && r.getUnreadNotificationCount("total") > 0) ||
|
||||
this.$matrix.invites.length > 0;
|
||||
|
|
@ -177,11 +151,6 @@ export default {
|
|||
this.$emit("view-room-details", { event: this.event });
|
||||
}
|
||||
});
|
||||
items.push({
|
||||
icon: 'notifications_active', text: this.$t('global.notify'), handler: () => {
|
||||
this.$emit("notify");
|
||||
}
|
||||
});
|
||||
items.push({
|
||||
icon: '$vuetify.icons.ic_member-leave', text: this.$t('leave.leave'), handler: () => {
|
||||
this.leaveRoom();
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
<v-col cols="auto" class="text-end ma-0 pa-0 ms-1">
|
||||
<v-btn id="btn-purge-room" v-if="userCanPurgeRoom" class="mx-2 box-shadow-none" fab dark small color="red"
|
||||
@click.stop="showPurgeConfirmation = true">
|
||||
@click.stop="$emit('purge')">
|
||||
<v-icon light>$vuetify.icons.ic_moderator-delete</v-icon>
|
||||
</v-btn>
|
||||
<template v-else>
|
||||
|
|
@ -75,9 +75,6 @@
|
|||
<MoreMenuPopup :show="showMoreMenu" :menuItems="moreMenuItems" @close="showMoreMenu = false"
|
||||
v-on:leave="showLeaveConfirmation = true" />
|
||||
|
||||
<!-- PURGE ROOM POPUP -->
|
||||
<PurgeRoomDialog :show="showPurgeConfirmation" :room="room" @close="showPurgeConfirmation = false" />
|
||||
|
||||
<RoomExport :room="room" v-if="downloadingChat" v-on:close="downloadingChat = false" />
|
||||
|
||||
</v-container>
|
||||
|
|
@ -87,7 +84,6 @@
|
|||
import LeaveRoomDialog from "../components/LeaveRoomDialog";
|
||||
import ProfileInfoPopup from "../components/ProfileInfoPopup";
|
||||
import MoreMenuPopup from "../components/MoreMenuPopup";
|
||||
import PurgeRoomDialog from "../components/PurgeRoomDialog";
|
||||
import RoomExport from "../components/RoomExport";
|
||||
|
||||
import ChatHeader from "./ChatHeader.vue";
|
||||
|
|
@ -99,7 +95,6 @@ export default {
|
|||
LeaveRoomDialog,
|
||||
ProfileInfoPopup,
|
||||
MoreMenuPopup,
|
||||
PurgeRoomDialog,
|
||||
RoomExport
|
||||
},
|
||||
};
|
||||
|
|
|
|||
275
src/components/CreateChannel.vue
Normal file
275
src/components/CreateChannel.vue
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
<template>
|
||||
<div class="pa-4 create-root fill-height">
|
||||
<YouAre v-if="loggedIn" class="mt-4" />
|
||||
<div class="text-center">
|
||||
<v-avatar class="create-image-small" size="50" color="#ededed" @click.stop="showAvatarPicker">
|
||||
<v-img v-if="roomAvatar" :src="roomAvatar" />
|
||||
<v-icon v-else>camera_alt</v-icon>
|
||||
</v-avatar>
|
||||
<div class="create-title">{{ $t("createchannel.title") }}</div>
|
||||
<div class="create-info">{{ $t("createchannel.info") }}</div>
|
||||
<div color="rgba(255,255,255,0.1)" class="text-center">
|
||||
<v-form v-model="isValid" ref="form">
|
||||
<v-text-field v-model="roomName" :label="$t('createchannel.channel_name')" color="black"
|
||||
background-color="white" solo :rules="[(v) => !!v || $t('createchannel.name_required')]" required
|
||||
v-on:keydown="message = null"></v-text-field>
|
||||
<v-text-field v-model="roomTopic" :label="$t('createchannel.channel_topic')" color="black"
|
||||
background-color="#F5F5F5" filled v-on:keydown="message = null"></v-text-field>
|
||||
|
||||
<!-- <div class="error--text" v-if="loadingLoginFlows">Loading login flows...</div> -->
|
||||
|
||||
<div class="error--text" v-if="message != null">{{ this.message }}</div>
|
||||
|
||||
<interactive-auth ref="interactiveAuth" />
|
||||
|
||||
<v-btn :disabled="!isValid || loading" color="primary" depressed block @click.stop="handleNext"
|
||||
:loading="loading" class="filled-button mt-4">{{ $t("getlink.next") }}</v-btn>
|
||||
<v-btn v-if="!loggedIn" color="black" depressed text block @click.stop="goToLoginPage" class="text-button">{{
|
||||
$t("menu.login")
|
||||
}}</v-btn>
|
||||
</v-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input id="room-avatar-picker" ref="avatar" type="file" name="avatar" @change="handlePickedAvatar($event)"
|
||||
accept="image/*" class="d-none" />
|
||||
|
||||
<div :class="{ 'toast-at-bottom': true, 'visible': showQRCopiedToast }">{{ $t("getlink.qr_image_copied") }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import rememberMeMixin from "./rememberMeMixin";
|
||||
import * as sdk from "matrix-js-sdk";
|
||||
import logoMixin from "./logoMixin";
|
||||
import InteractiveAuth from './InteractiveAuth.vue';
|
||||
import util, { ROOM_TYPE_CHANNEL } from "../plugins/utils";
|
||||
import YouAre from "../components/YouAre.vue";
|
||||
import User from "../models/user";
|
||||
import { CHANNEL_POWER_LEVELS } from "../services/matrix.service";
|
||||
|
||||
export default {
|
||||
name: "CreateChannel",
|
||||
mixins: [rememberMeMixin, logoMixin],
|
||||
components: { InteractiveAuth, YouAre },
|
||||
data() {
|
||||
return this.defaultData();
|
||||
},
|
||||
computed: {
|
||||
loggedIn() {
|
||||
return this.$store.state.auth.status.loggedIn;
|
||||
},
|
||||
currentUser() {
|
||||
return this.$store.state.auth.user;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
},
|
||||
methods: {
|
||||
defaultData() {
|
||||
return {
|
||||
roomName: "",
|
||||
roomTopic: "",
|
||||
isValid: false,
|
||||
loading: false,
|
||||
message: null,
|
||||
currentLoginServer: "",
|
||||
loadingLoginFlows: false,
|
||||
loginFlows: null,
|
||||
showQRCopiedToast: false,
|
||||
roomAvatar: null,
|
||||
roomAvatarFile: null,
|
||||
};
|
||||
},
|
||||
goHome() {
|
||||
this.$navigation.push({ name: "Home" }, -1);
|
||||
},
|
||||
goToLoginPage() {
|
||||
this.$navigation.push({ name: "Login", params: { showCreateRoomOption: false, redirect: "GetLink" } }, 1);
|
||||
},
|
||||
handleNext() {
|
||||
const prefix = this.$config.userIdPrefix;
|
||||
const user = new User(util.randomUser(prefix), util.randomPass(), false);
|
||||
|
||||
// Reset errors
|
||||
this.message = null;
|
||||
this.loading = true;
|
||||
|
||||
const login = this.loggedIn ? Promise.resolve(this.currentUser) : this.loadLoginFlows().then(() => {
|
||||
return this.$store.dispatch("createUser", { user, registrationFlowHandler: this.$refs.interactiveAuth.registrationFlowHandler })
|
||||
});
|
||||
login
|
||||
.then(
|
||||
(ignoreduser) => {
|
||||
return this.createRoom();
|
||||
})
|
||||
.catch(
|
||||
(ignorederror) => {
|
||||
this.message = this.$t("createchannel.error_channel");
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
})
|
||||
},
|
||||
loadLoginFlows() {
|
||||
var user = Object.assign({}, this.user);
|
||||
return util.getMatrixBaseUrl(user, this.$config)
|
||||
.then((baseUrl) => {
|
||||
if (baseUrl !== this.currentLoginServer) {
|
||||
this.currentLoginServer = baseUrl;
|
||||
this.loadingLoginFlows = true;
|
||||
|
||||
const matrixClient = sdk.createClient({ baseUrl: baseUrl });
|
||||
return matrixClient.loginFlows().then((response) => {
|
||||
console.log("FLOWS", response.flows);
|
||||
this.loginFlows = response.flows.filter(this.supportedLoginFlow);
|
||||
this.loadingLoginFlows = false;
|
||||
if (this.loginFlows.length == 0) {
|
||||
this.message = this.$t('login.no_supported_flow')
|
||||
} else {
|
||||
this.message = null;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
},
|
||||
supportedLoginFlow(flow) {
|
||||
return ["m.login.password"].includes(flow.type);
|
||||
},
|
||||
createRoom() {
|
||||
const createRoomOptions = {
|
||||
visibility: "private", // Not listed!
|
||||
name: this.roomName,
|
||||
preset: "public_chat",
|
||||
initial_state:
|
||||
[
|
||||
{
|
||||
type: "m.room.encryption",
|
||||
state_key: "",
|
||||
content: {
|
||||
algorithm: "m.megolm.v1.aes-sha2",
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "m.room.history_visibility",
|
||||
state_key: "",
|
||||
content: {
|
||||
history_visibility: "joined"
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "m.room.retention",
|
||||
state_key: "",
|
||||
content: {
|
||||
max_lifetime: 3600 * 24 * 7 * 1000
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
if (this.roomTopic && this.roomTopic.length > 0) {
|
||||
// Add topic
|
||||
createRoomOptions.topic = this.roomTopic;
|
||||
}
|
||||
|
||||
createRoomOptions.creation_content = {
|
||||
type: ROOM_TYPE_CHANNEL
|
||||
}
|
||||
|
||||
let roomId = "";
|
||||
|
||||
return util
|
||||
.getUniqueAliasForRoomName(
|
||||
this.$matrix.matrixClient,
|
||||
this.roomName,
|
||||
this.$matrix.currentUserMXDomain
|
||||
)
|
||||
.then((alias) => {
|
||||
createRoomOptions.room_alias_name = alias;
|
||||
})
|
||||
.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;
|
||||
let powerLevelContent = {
|
||||
users: powerLevels,
|
||||
events_default: 50,
|
||||
state_default: 50,
|
||||
events: CHANNEL_POWER_LEVELS
|
||||
}
|
||||
createRoomOptions.initial_state.push(
|
||||
{
|
||||
type: "m.room.power_levels",
|
||||
state_key: "",
|
||||
content: powerLevelContent
|
||||
});
|
||||
|
||||
return this.$matrix.matrixClient
|
||||
.createRoom(createRoomOptions)
|
||||
.then(({ room_id, room_alias }) => {
|
||||
roomId = room_alias || room_id;
|
||||
if (!this.roomAvatarFile) {
|
||||
return true;
|
||||
}
|
||||
const self = this;
|
||||
return util.setRoomAvatar(
|
||||
this.$matrix.matrixClient,
|
||||
room_id,
|
||||
this.roomAvatarFile,
|
||||
(p) => {
|
||||
if (p.total) {
|
||||
self.status = this.$t("new_room.status_avatar_total", {
|
||||
count: p.loaded || 0,
|
||||
total: p.total,
|
||||
});
|
||||
} else {
|
||||
self.status = this.$t("new_room.status_avatar", {
|
||||
count: p.loaded || 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
this.$navigation.push(
|
||||
{
|
||||
name: "Chat",
|
||||
params: { roomId: util.sanitizeRoomId(roomId) },
|
||||
},
|
||||
-1
|
||||
);
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Show picker to select room avatar file
|
||||
*/
|
||||
showAvatarPicker() {
|
||||
this.$refs.avatar.click();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle picked avatar
|
||||
*/
|
||||
handlePickedAvatar(event) {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.roomAvatar = e.target.result;
|
||||
this.roomAvatarFile = event.target.files[0];
|
||||
};
|
||||
reader.readAsDataURL(event.target.files[0]);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/create.scss";
|
||||
</style>
|
||||
|
|
@ -477,14 +477,25 @@ export default {
|
|||
// 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;
|
||||
let powerLevelContent = {
|
||||
users: powerLevels,
|
||||
events_default: this.readOnlyRoom ? 50 : 0
|
||||
}
|
||||
if (this.readOnlyRoom) {
|
||||
powerLevelContent.events = {
|
||||
"m.room.encrypted": 0, // NOTE! Since practically all events in encrypted rooms get sent as "m.room.encrypted" we need to set
|
||||
// power to 0 here. Otherwise we would not be able to send quick reactions or poll responses...
|
||||
"m.poll.response": 0,
|
||||
"org.matrix.msc3381.poll.response": 0,
|
||||
"m.reaction": 0,
|
||||
"m.room.redaction": 0,
|
||||
};
|
||||
}
|
||||
createRoomOptions.initial_state.push(
|
||||
{
|
||||
type: "m.room.power_levels",
|
||||
state_key: "",
|
||||
content: {
|
||||
users: powerLevels,
|
||||
events_default: this.readOnlyRoom ? 50 : 0
|
||||
}
|
||||
content: powerLevelContent
|
||||
});
|
||||
|
||||
return this.$matrix.matrixClient
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<v-list dense @click.native.stop="nullEvent">
|
||||
<v-subheader>{{$t('device_list.title')}}</v-subheader>
|
||||
<v-list-item-group color="primary">
|
||||
<v-list-item
|
||||
v-for="device in devices"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="pa-4 getlink-root fill-height">
|
||||
<div class="pa-4 create-root fill-height">
|
||||
<div v-if="!loggedIn" class="text-center">
|
||||
<v-icon class="getlink-image">$vuetify.icons.getlink</v-icon>
|
||||
<div class="getlink-title">{{ $t("getlink.title") }}</div>
|
||||
<div class="getlink-info">{{ $t("getlink.info") }}</div>
|
||||
<v-icon class="create-image">$vuetify.icons.getlink</v-icon>
|
||||
<div class="create-title">{{ $t("getlink.title") }}</div>
|
||||
<div class="create-info">{{ $t("getlink.info") }}</div>
|
||||
<div color="rgba(255,255,255,0.1)" class="text-center">
|
||||
<v-form v-model="isValid" ref="form">
|
||||
<v-text-field v-model="user.user_id" :label="$t('getlink.username')" color="black" background-color="white" solo
|
||||
|
|
@ -23,20 +23,20 @@
|
|||
</v-form>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="position:relative" class="getlink-loggedin">
|
||||
<div v-else style="position:relative" class="create-loggedin">
|
||||
<!-- Logged in/account created -->
|
||||
<div class="getlink-title">{{ $t("getlink.hello", { user: $matrix.currentUserDisplayName }) }}</div>
|
||||
<div class="getlink-subtitle">{{ $t("getlink.ready_to_share") }}</div>
|
||||
<div class="create-title">{{ $t("getlink.hello", { user: $matrix.currentUserDisplayName }) }}</div>
|
||||
<div class="create-subtitle">{{ $t("getlink.ready_to_share") }}</div>
|
||||
<copy-link ref="qr" :locationLink="directMessageLink" i18nQrPopupTitleKey="getlink.scan_title"
|
||||
v-on:long-tap="copyQRImage">
|
||||
<template v-slot:share>
|
||||
<div v-if="shareSupported" class="clickable getlink-share" @click="shareLink">
|
||||
<div v-if="shareSupported" class="clickable create-share" @click="shareLink">
|
||||
<div>{{ $t("getlink.share_qr") }}</div>
|
||||
<v-img src="@/assets/icons/ic_share.svg" />
|
||||
</div>
|
||||
</template>
|
||||
</copy-link>
|
||||
<div class="getlink-buttons">
|
||||
<div class="create-buttons">
|
||||
<v-btn color="black" depressed @click.stop="goHome" class="outlined-button">{{ $t("getlink.continue") }}</v-btn>
|
||||
<v-btn color="black" depressed text block @click.stop="getDifferentLink" class="text-button">{{
|
||||
$t("getlink.different_link") }}</v-btn>
|
||||
|
|
@ -181,9 +181,6 @@ export default {
|
|||
})
|
||||
}
|
||||
},
|
||||
handleCreateRoom() {
|
||||
this.$navigation.push({ name: "CreateRoom" });
|
||||
},
|
||||
onUsernameEnter() {
|
||||
this.handleLogin();
|
||||
},
|
||||
|
|
@ -246,5 +243,5 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/getlink.scss";
|
||||
@import "@/assets/css/create.scss";
|
||||
</style>
|
||||
122
src/components/MessageRetentionDialog.vue
Normal file
122
src/components/MessageRetentionDialog.vue
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<v-dialog v-model="showDialog" v-show="room" class="ma-0 pa-0"
|
||||
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'">
|
||||
<div class="dialog-content text-center">
|
||||
<template>
|
||||
<h2>{{ $t("room_info.message_retention") }}</h2>
|
||||
</template>
|
||||
<v-container fluid>
|
||||
<v-row cols="12">
|
||||
<v-col cols="12">
|
||||
<v-form v-model="isValid">
|
||||
<v-radio-group v-model="retention" class="my-0 py-0">
|
||||
<v-radio active-class="radio-active" class="flex-row-reverse mb-0" v-for="p in retentionPeriods" :key="p.text" :label="p.text"
|
||||
:value="p.value" />
|
||||
</v-radio-group>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row cols="12" class="justify-center mt-0">
|
||||
<v-col cols="4" class="py-0">
|
||||
<v-btn color="primary" :disabled="!isValid" depressed block class="filled-button my-0"
|
||||
@click.stop="setRetention()">{{
|
||||
$t("menu.done") }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import roomInfoMixin from "./roomInfoMixin";
|
||||
|
||||
export default {
|
||||
name: "MessageRetentionDialog",
|
||||
mixins: [roomInfoMixin],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: function () {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDialog: false,
|
||||
isValid: true,
|
||||
retention: 0,
|
||||
retentionMinValue: 60,
|
||||
retentionMaxValue: 60 * 60 * 24 * 500
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(newVal, ignoredOldVal) {
|
||||
this.showDialog = newVal;
|
||||
},
|
||||
},
|
||||
showDialog(val, oldVal) {
|
||||
if (!val && oldVal) {
|
||||
this.$emit("close");
|
||||
} else if (val && !oldVal) {
|
||||
// Showing, reset
|
||||
this.setInitialValue();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setInitialValue();
|
||||
},
|
||||
methods: {
|
||||
setInitialValue() {
|
||||
this.isValid = true;
|
||||
this.retention = this.roomMessageRetention();
|
||||
},
|
||||
setRetention() {
|
||||
let ms = this.retention;
|
||||
if (ms === 0 || !ms) {
|
||||
// No expiry
|
||||
this.$matrix.matrixClient.sendStateEvent(
|
||||
this.room.roomId,
|
||||
"m.room.retention",
|
||||
{ max_lifetime: 0 }
|
||||
);
|
||||
} else if (ms >= 1000 * this.retentionMinValue && ms <= 1000 * this.retentionMaxValue) {
|
||||
this.$matrix.matrixClient.sendStateEvent(
|
||||
this.room.roomId,
|
||||
"m.room.retention",
|
||||
{ max_lifetime: ms }
|
||||
);
|
||||
}
|
||||
this.$emit('message-retention-update', ms)
|
||||
this.showDialog = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
|
||||
.v-radio .other {
|
||||
display: flex;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.v-radio .v-text-field {
|
||||
display: none;
|
||||
margin-left: 8px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.v-radio.radio-active {
|
||||
.v-text-field {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.v-radio.flex-row-reverse {
|
||||
height: 48px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -89,6 +89,16 @@
|
|||
:icon="'$vuetify.icons.globe'"
|
||||
:text="$t('profile.select_language')"
|
||||
/>
|
||||
<ActionRow
|
||||
@click="onUpdateGlobalNotification"
|
||||
:icon="notificationIcon"
|
||||
:text="$t('profile.notification_label')"
|
||||
>
|
||||
<v-switch
|
||||
v-model="globalNotification"
|
||||
readonly
|
||||
></v-switch>
|
||||
</ActionRow>
|
||||
</v-container>
|
||||
|
||||
<!-- edit password dialog -->
|
||||
|
|
@ -197,6 +207,45 @@
|
|||
v-model="showSelectLanguageDialog"
|
||||
v-on:close="showSelectLanguageDialog = false"
|
||||
/>
|
||||
<!-- Dialog for request Notification -->
|
||||
<v-dialog
|
||||
v-model="notificationDialog"
|
||||
persistent
|
||||
class="ma-0 pa-0"
|
||||
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'"
|
||||
>
|
||||
<div class="dialog-content text-center">
|
||||
<v-icon size="30">notifications_active</v-icon>
|
||||
<h2 class="dialog-title">
|
||||
{{ $t("notification.dialog.title") }}
|
||||
</h2>
|
||||
<div class="dialog-text">{{ $t("notification.dialog.body") }}</div>
|
||||
<v-container fluid>
|
||||
<v-row cols="12">
|
||||
<v-col cols="6">
|
||||
<v-btn
|
||||
depressed
|
||||
text
|
||||
block
|
||||
class="text-button"
|
||||
@click="onNotifyDialogClosed"
|
||||
>{{ $t("global.close") }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
<v-col cols="6" align="center">
|
||||
<v-btn
|
||||
color="primary"
|
||||
depressed
|
||||
block
|
||||
class="filled-button"
|
||||
@click.stop="onNotifyDialog"
|
||||
>{{ $t("notification.dialog.enable") }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -207,6 +256,8 @@ import util from "../plugins/utils";
|
|||
import profileInfoMixin from "./profileInfoMixin";
|
||||
import LogoutRoomDialog from './LogoutRoomDialog.vue';
|
||||
import CopyLink from "./CopyLink.vue"
|
||||
import { requestNotificationPermission, windowNotificationPermission } from "../plugins/notificationAndServiceWorker.js"
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: "Profile",
|
||||
|
|
@ -234,7 +285,8 @@ export default {
|
|||
passwordErrorMessage: null,
|
||||
isAvatarLoaded: true,
|
||||
loadValue: 0,
|
||||
newPasswordHasError: false
|
||||
newPasswordHasError: false,
|
||||
notificationDialog: false
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -252,7 +304,13 @@ export default {
|
|||
this.newPassword2 &&
|
||||
this.newPassword1 == this.newPassword2
|
||||
);
|
||||
}
|
||||
},
|
||||
notificationIcon() {
|
||||
return this.globalNotification ? 'notifications_active' : 'notifications_off';
|
||||
},
|
||||
...mapState([
|
||||
'globalNotification'
|
||||
])
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -306,7 +364,53 @@ export default {
|
|||
console.log("Progress: " + JSON.stringify(progress));
|
||||
});
|
||||
},
|
||||
updateGlobalNotificationStore(flag) {
|
||||
this.$store.commit('setGlobalNotification', flag);
|
||||
},
|
||||
windowNotificationPermission,
|
||||
onUpdateGlobalNotification(showAlertOrDialog = true) {
|
||||
const permission = this.windowNotificationPermission();
|
||||
|
||||
switch (permission) {
|
||||
case 'denied':
|
||||
this.updateGlobalNotificationStore(false);
|
||||
if (showAlertOrDialog) {
|
||||
alert(this.$t("notification.blocked_message"));
|
||||
}
|
||||
break;
|
||||
case 'granted':
|
||||
this.updateGlobalNotificationStore(!this.globalNotification);
|
||||
break;
|
||||
case 'default':
|
||||
if (showAlertOrDialog) {
|
||||
this.notificationDialog = true;
|
||||
}
|
||||
this.updateGlobalNotificationStore(!this.globalNotification);
|
||||
break;
|
||||
default:
|
||||
alert(this.$t("notification.not_supported"));
|
||||
}
|
||||
},
|
||||
async onNotifyDialog() {
|
||||
const permission = await requestNotificationPermission()
|
||||
if(permission === 'denied') {
|
||||
this.updateGlobalNotificationStore(false);
|
||||
alert(this.$t("notification.blocked_message"));
|
||||
} else {
|
||||
this.updateGlobalNotificationStore(true);
|
||||
}
|
||||
this.notificationDialog = false;
|
||||
},
|
||||
onNotifyDialogClosed() {
|
||||
this.updateGlobalNotificationStore(false);
|
||||
this.notificationDialog = false;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if(this.globalNotification && this.windowNotificationPermission() !== 'granted') {
|
||||
this.onUpdateGlobalNotification(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -84,11 +84,10 @@ import RoomEncrypted from "./messages/RoomEncrypted.vue";
|
|||
import RoomDeletionNotice from "./messages/RoomDeletionNotice.vue";
|
||||
import DebugEvent from "./messages/DebugEvent.vue";
|
||||
import MessageOperations from "./messages/MessageOperations.vue";
|
||||
import AvatarOperations from "./messages/AvatarOperations.vue";
|
||||
import ChatHeader from "./ChatHeader.vue";
|
||||
import VoiceRecorder from "./VoiceRecorder.vue";
|
||||
import RoomInfoBottomSheet from "./RoomInfoBottomSheet.vue";
|
||||
import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader.vue";
|
||||
import WelcomeHeaderRoom from "./welcome_headers/WelcomeHeaderRoom.vue";
|
||||
import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet.vue";
|
||||
import StickerPickerBottomSheet from "./StickerPickerBottomSheet.vue";
|
||||
import BottomSheet from "./BottomSheet.vue";
|
||||
|
|
@ -137,11 +136,10 @@ export default {
|
|||
MessageOperations,
|
||||
VoiceRecorder,
|
||||
RoomInfoBottomSheet,
|
||||
CreatedRoomWelcomeHeader,
|
||||
WelcomeHeaderRoom,
|
||||
MessageOperationsBottomSheet,
|
||||
StickerPickerBottomSheet,
|
||||
BottomSheet,
|
||||
AvatarOperations,
|
||||
CreatePollDialog,
|
||||
},
|
||||
props: {
|
||||
|
|
@ -264,7 +262,7 @@ export default {
|
|||
if (parentEvent) {
|
||||
Vue.set(parentEvent, "isMxThread", true);
|
||||
Vue.set(event, "parentThread", parentEvent);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.events.filter(event => (event.replyEventId && !event.replyEvent)).forEach(event => {
|
||||
const parentEvent = this.timelineSet.findEventById(event.replyEventId) || this.room.findEventById(event.replyEventId);
|
||||
|
|
@ -287,6 +285,7 @@ export default {
|
|||
var imageFolder = zip.folder("images");
|
||||
var audioFolder = zip.folder("audio");
|
||||
var videoFolder = zip.folder("video");
|
||||
var filesFolder = zip.folder("files");
|
||||
|
||||
var downloadPromises = [];
|
||||
let components = this.$refs.exportedEvent;
|
||||
|
|
@ -321,7 +320,8 @@ export default {
|
|||
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
|
||||
const img = images[imageIndex];
|
||||
img.onerror = undefined;
|
||||
img.src = './avatars/' + fileName;
|
||||
img.removeAttribute("src");
|
||||
img.setAttribute("data-exported-src", './avatars/' + fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -421,13 +421,15 @@ export default {
|
|||
var extension = ".mp3";
|
||||
let fileName = comp.event.getId() + extension;
|
||||
audioFolder.file(fileName, blob); // TODO calc bytes
|
||||
//this.$nextTick(() => {
|
||||
let elements = comp.$el.getElementsByTagName("audio");
|
||||
let element = elements && elements[0];
|
||||
if (element) {
|
||||
element.src = "./audio/" + fileName;
|
||||
element.setAttribute("data-exported-src", "./audio/" + fileName);
|
||||
}
|
||||
this.processedEvents += 1;
|
||||
resolve(true);
|
||||
//});
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
@ -449,13 +451,36 @@ export default {
|
|||
var extension = ".mp4";
|
||||
let fileName = comp.event.getId() + extension;
|
||||
videoFolder.file(fileName, blob); // TODO calc bytes
|
||||
// comp.src = "./video/" + fileName;
|
||||
let elements = comp.$el.getElementsByTagName("video");
|
||||
let element = elements && elements[0];
|
||||
if (element) {
|
||||
element.src = "./video/" + fileName;
|
||||
element.setAttribute("data-exported-src", "./video/" + fileName);
|
||||
}
|
||||
this.processedEvents += 1;
|
||||
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((ignoredErr) => {
|
||||
this.processedEvents += 1;
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "MessageIncomingFileExport":
|
||||
case "MessageOutgoingFileExport":
|
||||
downloadPromises.push(
|
||||
util
|
||||
.getAttachment(this.$matrix.matrixClient, comp.event, null, true)
|
||||
.then((blob) => {
|
||||
if (currentMediaSize + blob.size <= maxMediaSize) {
|
||||
currentMediaSize += blob.size;
|
||||
return new Promise((resolve, ignoredReject) => {
|
||||
var extension = util.getFileExtension(comp.event);
|
||||
let fileName = comp.event.getId() + extension;
|
||||
filesFolder.file(fileName, blob);
|
||||
comp.href="./files/" + fileName;
|
||||
this.processedEvents += 1;
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
|
|
@ -504,7 +529,8 @@ export default {
|
|||
getCssRules(root);
|
||||
|
||||
this.$nextTick(() => {
|
||||
doc += this.$refs.exportRoot.outerHTML;
|
||||
const contentHtml = this.$refs.exportRoot.outerHTML;
|
||||
doc += contentHtml.replaceAll("data-exported-src=", "src=");
|
||||
doc += "</div></body></html>";
|
||||
|
||||
zip.file("chat.html", doc);
|
||||
|
|
|
|||
|
|
@ -138,27 +138,45 @@
|
|||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card class="account ma-3" flat v-if="(iAmAdmin() && availableRoomTypes.length > 1) || canChangeReadOnly()">
|
||||
<v-card-title class="h2 with-right-label"><div>{{ $t("room_info.experimental_features") }}</div><div></div></v-card-title>
|
||||
<v-card-text v-if="iAmAdmin() && availableRoomTypes.length > 1">
|
||||
<div class="d-flex flex-wrap">
|
||||
<div class="col-12 col-md-6 mr-auto pa-0">
|
||||
<div class="option-title">{{ $t('room_info.room_type') }}</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 pa-0">
|
||||
<RoomTypeSelector v-model="roomType" />
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-text class="with-right-label" v-if="canChangeReadOnly()">
|
||||
<v-card class="account ma-3" flat>
|
||||
<v-card-title class="h2">{{ $t("room_info.message_retention") }}</v-card-title>
|
||||
<v-card-text v-if="canViewRetentionPolicy">
|
||||
<v-list v-if="canChangeRetentionPolicy">
|
||||
<v-list-item link v-on:click="showMessageRetentionDialog = true" class="px-0">
|
||||
<v-list-item-avatar class="mr-0 pb-0 mb-0">
|
||||
<v-img
|
||||
contain
|
||||
width="24"
|
||||
height="24"
|
||||
src="@/assets/icons/timer.svg"
|
||||
/>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content class="pb-0">
|
||||
{{ messageRetentionDisplay }}
|
||||
</v-list-item-content>
|
||||
|
||||
<v-list-item-action class="pb-0 mb-0">
|
||||
<v-icon>arrow_drop_down</v-icon>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
<hr />
|
||||
</v-list>
|
||||
<div>
|
||||
<div class="option-title">{{ $t('room_info.read_only_room') }}</div>
|
||||
<div class="option-text">{{ $t('room_info.read_only_room_info') }}</div>
|
||||
{{ $t("room_info.message_retention_info") }}
|
||||
</div>
|
||||
<v-switch
|
||||
v-model="readOnlyRoom"
|
||||
></v-switch>
|
||||
</v-card-text>
|
||||
|
||||
</v-card>
|
||||
|
||||
<v-card class="account ma-3" flat v-if="canChangeReadOnly()">
|
||||
<v-card-title class="h2">{{ $t("room_info.moderation") }}</v-card-title>
|
||||
<v-card-text class="" v-if="canChangeReadOnly()">
|
||||
<div class="with-right-label" style="align-items:center;height:36px">
|
||||
<div class="option-title"><v-icon>$vuetify.icons.ic_eye</v-icon> {{ $t('room_info.read_only_room') }}</div>
|
||||
<v-switch class="ma-0" v-model="readOnlyRoom"></v-switch>
|
||||
</div>
|
||||
<div class="option-text">{{ $t('room_info.read_only_room_info') }}</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card class="members ma-3" flat>
|
||||
|
|
@ -166,58 +184,56 @@
|
|||
>{{ $t("room_info.members") }}<v-spacer></v-spacer>
|
||||
<div>{{ members.length }}</div></v-card-title
|
||||
>
|
||||
<v-card-text>
|
||||
<div
|
||||
class="member ma-2"
|
||||
v-for="(member, index) in members"
|
||||
:key="member.userId"
|
||||
v-show="showAllMembers || index < SHOW_MEMBER_LIMIT"
|
||||
@click="toggleMemberExpanded(member)"
|
||||
>
|
||||
<v-avatar class="avatar" size="32" color="grey">
|
||||
<img v-if="memberAvatar(member)" :src="memberAvatar(member)" />
|
||||
<span v-else class="white--text headline">{{
|
||||
member.name.substring(0, 1).toUpperCase()
|
||||
}}</span>
|
||||
</v-avatar>
|
||||
<span class="user-name">
|
||||
{{
|
||||
member.userId == $matrix.currentUserId
|
||||
? $t("room_info.user_you", {
|
||||
user: member.user ? member.user.displayName : member.name,
|
||||
})
|
||||
: $t("room_info.user", {
|
||||
user: member.user ? member.user.displayName : member.name,
|
||||
})
|
||||
}}
|
||||
</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"
|
||||
/>
|
||||
</div>
|
||||
<div class="show-all" @click="showAllMembers = !showAllMembers" v-if="members.length > SHOW_MEMBER_LIMIT">
|
||||
{{
|
||||
showAllMembers ? $t("room_info.hide_all") : $t("room_info.show_all")
|
||||
}}
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-list>
|
||||
<v-list-item-group>
|
||||
<template v-for="(member, index) in members" >
|
||||
<v-list-item
|
||||
:key="member.userId"
|
||||
class="member"
|
||||
v-show="showAllMembers || index < SHOW_MEMBER_LIMIT"
|
||||
@click="onListItemClick(member)"
|
||||
>
|
||||
<div>
|
||||
<div class="user-icon-with-badge">
|
||||
<v-avatar class="avatar" size="32" color="grey">
|
||||
<img v-if="memberAvatar(member)" :src="memberAvatar(member)" />
|
||||
<span v-else class="white--text headline">{{
|
||||
member.name.substring(0, 1).toUpperCase()
|
||||
}}</span>
|
||||
</v-avatar>
|
||||
<v-avatar circle class="user-badge" size="15" v-if="isAdmin(member) || isModerator(member)">
|
||||
<v-icon>{{ `$vuetify.icons.${isAdmin(member)? 'make_admin' : 'make_moderator'}` }}</v-icon>
|
||||
</v-avatar>
|
||||
</div>
|
||||
<span class="user-name">
|
||||
{{
|
||||
member.userId == $matrix.currentUserId
|
||||
? $t("room_info.user_you", {
|
||||
user: member.user ? member.user.displayName : member.name,
|
||||
})
|
||||
: $t("room_info.user", {
|
||||
user: member.user ? member.user.displayName : member.name,
|
||||
})
|
||||
}}
|
||||
</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-list-item>
|
||||
<v-divider
|
||||
v-if="(showAllMembers || index < SHOW_MEMBER_LIMIT) && index < members.length - 1"
|
||||
:key="index"
|
||||
></v-divider>
|
||||
</template>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
<div class="show-all p-2" @click="showAllMembers = !showAllMembers" v-if="members.length > SHOW_MEMBER_LIMIT">
|
||||
{{ showAllMembers ? $t("room_info.hide_all") : $t("room_info.show_all") }}
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<!-- EXPORT CHAT -->
|
||||
|
|
@ -235,6 +251,13 @@
|
|||
{{ $t("room_info.version_info", { version: buildVersion }) }}
|
||||
</div>
|
||||
|
||||
<UserProfileDialog
|
||||
:show="showMemberActionConfirmation"
|
||||
:activeMember="activeMember || members[0]"
|
||||
:room="room"
|
||||
@close="showMemberActionConfirmation = false"
|
||||
/>
|
||||
|
||||
<LeaveRoomDialog
|
||||
:show="showLeaveConfirmation"
|
||||
:room="room"
|
||||
|
|
@ -247,6 +270,13 @@
|
|||
@close="showPurgeConfirmation = false"
|
||||
/>
|
||||
|
||||
<MessageRetentionDialog
|
||||
:show="showMessageRetentionDialog"
|
||||
:room="room"
|
||||
@close="showMessageRetentionDialog = false"
|
||||
v-on:message-retention-update="onMessageRetention"
|
||||
/>
|
||||
|
||||
<RoomExport :room="room" v-if="exporting" v-on:close="exporting = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -254,11 +284,11 @@
|
|||
<script>
|
||||
import LeaveRoomDialog from "../components/LeaveRoomDialog";
|
||||
import PurgeRoomDialog from "../components/PurgeRoomDialog";
|
||||
import DeviceList from "../components/DeviceList";
|
||||
import MessageRetentionDialog from "../components/MessageRetentionDialog";
|
||||
import RoomExport from "../components/RoomExport";
|
||||
import RoomAvatarPicker from "../components/RoomAvatarPicker";
|
||||
import CopyLink from "../components/CopyLink.vue"
|
||||
import RoomTypeSelector from "./RoomTypeSelector.vue";
|
||||
import UserProfileDialog from "./UserProfileDialog.vue"
|
||||
import roomInfoMixin from "./roomInfoMixin";
|
||||
import roomTypeMixin from "./roomTypeMixin";
|
||||
import util, { STATE_EVENT_ROOM_TYPE } from "../plugins/utils";
|
||||
|
|
@ -269,10 +299,10 @@ export default {
|
|||
components: {
|
||||
LeaveRoomDialog,
|
||||
PurgeRoomDialog,
|
||||
DeviceList,
|
||||
MessageRetentionDialog,
|
||||
UserProfileDialog,
|
||||
RoomExport,
|
||||
RoomAvatarPicker,
|
||||
RoomTypeSelector,
|
||||
CopyLink
|
||||
},
|
||||
data() {
|
||||
|
|
@ -280,9 +310,10 @@ export default {
|
|||
members: [],
|
||||
user: null,
|
||||
showAllMembers: false,
|
||||
showMemberActionConfirmation: false,
|
||||
showLeaveConfirmation: false,
|
||||
showPurgeConfirmation: false,
|
||||
expandedMembers: [],
|
||||
showMessageRetentionDialog: false,
|
||||
buildVersion: "",
|
||||
updatingJoinRule: false, // Flag if we are processing update curerntly
|
||||
joinRules: [
|
||||
|
|
@ -299,11 +330,13 @@ export default {
|
|||
],
|
||||
SHOW_MEMBER_LIMIT: 5,
|
||||
exporting: false,
|
||||
};
|
||||
activeMember: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$matrix.on("Room.timeline", this.onEvent);
|
||||
this.updateMembers();
|
||||
this.updateMessageRetention();
|
||||
this.user = this.$matrix.matrixClient.getUser(this.$matrix.currentUserId);
|
||||
|
||||
// Display build version
|
||||
|
|
@ -382,17 +415,26 @@ export default {
|
|||
handler() {
|
||||
console.log("RoomInfo: Current room changed");
|
||||
this.updateMembers();
|
||||
this.updateMessageRetention();
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onMessageRetention(retention){
|
||||
const retentionPeriodsFound = this.retentionPeriods.find(rp => rp.value===retention)
|
||||
this.messageRetentionDisplay = retentionPeriodsFound.text
|
||||
},
|
||||
onListItemClick(member) {
|
||||
this.activeMember = member
|
||||
this.showMemberActionConfirmation = true
|
||||
},
|
||||
onEvent(event) {
|
||||
if (this.room && this.room.roomId == event.getRoomId()) {
|
||||
// For this room
|
||||
if (event.getType() == "m.room.member") {
|
||||
this.updateMembers();
|
||||
}
|
||||
this.updateMembers();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -424,32 +466,10 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
memberAvatar(member) {
|
||||
if (member) {
|
||||
return member.getAvatarUrl(
|
||||
this.$matrix.matrixClient.getHomeserverUrl(),
|
||||
40,
|
||||
40,
|
||||
"scale",
|
||||
true
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
viewProfile() {
|
||||
this.$navigation.push({ name: "Profile" }, 1);
|
||||
},
|
||||
|
||||
toggleMemberExpanded(member) {
|
||||
const index = this.expandedMembers.indexOf(member);
|
||||
if (index > -1) {
|
||||
this.expandedMembers.splice(index, 1);
|
||||
} else {
|
||||
this.expandedMembers.push(member);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set room join rule.
|
||||
* @param joinRule One of "invite" or "public". Currently always disables guest access.
|
||||
|
|
@ -494,49 +514,11 @@ export default {
|
|||
this.updatingJoinRule = false;
|
||||
});
|
||||
},
|
||||
startPrivateChat(userId) {
|
||||
this.$matrix
|
||||
.getOrCreatePrivateChat(userId)
|
||||
.then((room) => {
|
||||
this.$nextTick(() => {
|
||||
this.$navigation.push(
|
||||
{
|
||||
name: "Chat",
|
||||
params: {
|
||||
roomId: util.sanitizeRoomId(
|
||||
room.getCanonicalAlias() || room.roomId
|
||||
),
|
||||
},
|
||||
},
|
||||
-1
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
exportRoom() {
|
||||
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;
|
||||
},
|
||||
/**
|
||||
* Return true if we can change power levels in the room, i.e. make read only room
|
||||
*/
|
||||
|
|
@ -553,76 +535,6 @@ export default {
|
|||
}
|
||||
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)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Go back to previous page, or if none on the stack, go back to current room view.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -90,7 +90,12 @@ export default {
|
|||
return this.sortItemsOnName(this.$matrix.invites);
|
||||
},
|
||||
joinedRooms() {
|
||||
return this.sortItemsOnName(this.$matrix.joinedRooms);
|
||||
// show room with notification on top, followed by room decending order by active Timestamp
|
||||
return [...this.$matrix.joinedRooms].sort((a, b) => {
|
||||
if (this.notificationCount(a)) return -1;
|
||||
if (this.notificationCount(b)) return 1;
|
||||
return b.getLastActiveTimestamp() - a.getLastActiveTimestamp()
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
255
src/components/UserProfileDialog.vue
Normal file
255
src/components/UserProfileDialog.vue
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
<template>
|
||||
<v-dialog
|
||||
v-if="activeMember"
|
||||
v-model="showDialog"
|
||||
v-show="room"
|
||||
class="ma-0 pa-0"
|
||||
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'"
|
||||
>
|
||||
<div class="dialog-content text-center member-action-dialog">
|
||||
<div class="pt-4">
|
||||
<v-avatar class="avatar" size="56" color="grey">
|
||||
<img v-if="memberAvatarComp" :src="memberAvatarComp" />
|
||||
<span v-else class="white--text headline">{{ firstLetterUserName }}</span>
|
||||
</v-avatar>
|
||||
<div>
|
||||
<span class="user-name">
|
||||
{{
|
||||
isCurrentUser
|
||||
? $t("room_info.user_you", {
|
||||
user: activeMemberNameComp
|
||||
})
|
||||
: $t("room_info.user", {
|
||||
user: activeMemberNameComp
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-if="isAdminComp" class="user-power">
|
||||
{{ $t("room_info.user_admin") }}
|
||||
</span>
|
||||
<span v-else-if="isModeratorComp" class="user-power">
|
||||
{{ $t("room_info.user_moderator") }}
|
||||
</span>
|
||||
</div>
|
||||
<template v-if="activeMember.userId != $matrix.currentUserId && sharedRooms.length">
|
||||
<span v-if="sharedRooms.length < 3">{{ $t("room_info.shared_room_number", {count: sharedRooms.length, name: activeMemberNameComp}) }}</span>
|
||||
<span v-else>{{ $t("room_info.shared_room_number_more", {count: sharedRooms.length, name: activeMemberNameComp}) }}</span>
|
||||
<p class="font-weight-light">{{ sharedRooms.slice(0, 3).join(", ") }}</p>
|
||||
</template>
|
||||
</div>
|
||||
<DeviceList :member="activeMember" />
|
||||
<div class="py-3" v-if="activeMember.userId != $matrix.currentUserId">
|
||||
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && !$matrix.isDirectRoomWith(room, activeMember.userId)" class="start-private-chat clickable d-block text-none justify-start" @click="startPrivateChat(activeMember.userId)">
|
||||
<v-icon left>$vuetify.icons.direct_chat</v-icon> {{ $t("menu.direct_chat") }}
|
||||
</v-btn>
|
||||
<div v-if="canBanUserComp">
|
||||
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && canBanUserComp" class="start-private-chat clickable d-block text-none justify-start" @click="banUser(activeMember)">
|
||||
<v-icon left>$vuetify.icons.kickout</v-icon> {{ $t("menu.user_kick_and_ban") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && !isAdminComp && canMakeAdminComp" class="start-private-chat clickable d-block text-none justify-start" @click="makeAdmin(activeMember)">
|
||||
<v-icon left>$vuetify.icons.make_admin</v-icon> {{ $t("menu.user_make_admin") }}
|
||||
</v-btn>
|
||||
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && !isModeratorComp && !isAdminComp && canMakeModeratorComp" class="start-private-chat clickable d-block text-none justify-start" @click="makeModerator(activeMember)">
|
||||
<v-icon left>$vuetify.icons.make_moderator</v-icon> {{ $t("menu.user_make_moderator") }}
|
||||
</v-btn>
|
||||
<v-btn text x-large block v-if="activeMember.userId != $matrix.currentUserId && isModeratorComp && canRevokeModeratorComp" class="start-private-chat clickable d-block text-none justify-start" @click="revokeModerator(activeMember)">
|
||||
<v-icon left>$vuetify.icons.revoke</v-icon> {{ $t("menu.user_revoke_moderator") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import roomInfoMixin from "./roomInfoMixin";
|
||||
import DeviceList from "../components/DeviceList";
|
||||
import util from "../plugins/utils";
|
||||
|
||||
export default {
|
||||
name: "UserProfileDialog",
|
||||
mixins: [roomInfoMixin],
|
||||
components: {
|
||||
DeviceList
|
||||
},
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: function () {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
activeMember: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDialog: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canRevokeModeratorComp () {
|
||||
return this.canRevokeModerator(this.activeMember)
|
||||
},
|
||||
isModeratorComp() {
|
||||
return this.isModerator(this.activeMember)
|
||||
},
|
||||
canMakeModeratorComp() {
|
||||
return this.canMakeModerator(this.activeMember)
|
||||
},
|
||||
canMakeAdminComp() {
|
||||
return this.canMakeAdmin(this.activeMember)
|
||||
},
|
||||
canBanUserComp() {
|
||||
return this.canBanUser(this.activeMember)
|
||||
},
|
||||
isAdminComp() {
|
||||
return this.isAdmin(this.activeMember)
|
||||
},
|
||||
activeMemberNameComp() {
|
||||
return this.activeMember.user ? this.activeMember.user.displayName : this.activeMember.name
|
||||
},
|
||||
isCurrentUser() {
|
||||
return this.activeMember.userId == this.$matrix.currentUserId
|
||||
},
|
||||
firstLetterUserName() {
|
||||
return this.activeMember.name.substring(0, 1).toUpperCase()
|
||||
},
|
||||
memberAvatarComp() {
|
||||
return this.memberAvatar(this.activeMember)
|
||||
},
|
||||
joinedMembersByRoomId() {
|
||||
const joinedRooms = this.$matrix.joinedRooms.filter(room => !this.$matrix.isDirectRoom(room)) || [];
|
||||
return joinedRooms.map(room => ({
|
||||
roomId: room.roomId,
|
||||
roomName: room.name,
|
||||
memberIds: room.getJoinedMembers().map(({ userId }) => userId)
|
||||
}));
|
||||
},
|
||||
sharedRooms() {
|
||||
return this.joinedMembersByRoomId.reduce((sharedRooms, room) => {
|
||||
if (room.roomId !== this.room.roomId && room.memberIds.includes(this.activeMember.userId)) {
|
||||
sharedRooms.push(room.roomName);
|
||||
}
|
||||
return sharedRooms;
|
||||
}, []);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(newVal, ignoredOldVal) {
|
||||
this.showDialog = newVal;
|
||||
}
|
||||
},
|
||||
showDialog() {
|
||||
if (!this.showDialog) {
|
||||
this.$emit("close");
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
startPrivateChat(userId) {
|
||||
this.$matrix
|
||||
.getOrCreatePrivateChat(userId)
|
||||
.then((room) => {
|
||||
this.$nextTick(() => {
|
||||
this.$navigation.push(
|
||||
{
|
||||
name: "Chat",
|
||||
params: {
|
||||
roomId: util.sanitizeRoomId(
|
||||
room.getCanonicalAlias() || room.roomId
|
||||
),
|
||||
},
|
||||
},
|
||||
-1
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
this.showDialog = 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;
|
||||
},
|
||||
/**
|
||||
* 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)
|
||||
this.showDialog = false;
|
||||
}
|
||||
},
|
||||
makeModerator(member) {
|
||||
if (this.room) {
|
||||
this.$matrix.makeModerator(this.room.roomId, member.userId)
|
||||
this.showDialog = false;
|
||||
}
|
||||
},
|
||||
revokeModerator(member) {
|
||||
if (this.room) {
|
||||
this.$matrix.revokeModerator(this.room.roomId, member.userId)
|
||||
this.showDialog = false;
|
||||
}
|
||||
},
|
||||
banUser(member) {
|
||||
if (this.room) {
|
||||
this.$matrix.banUser(this.room.roomId, member.userId)
|
||||
this.showDialog = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
|
|
@ -19,10 +19,12 @@ import MessageIncomingImageExport from "./messages/export/MessageIncomingImageEx
|
|||
import MessageIncomingAudioExport from "./messages/export/MessageIncomingAudioExport";
|
||||
import MessageIncomingVideoExport from "./messages/export/MessageIncomingVideoExport";
|
||||
import MessageIncomingThreadExport from "./messages/export/MessageIncomingThreadExport";
|
||||
import MessageIncomingFileExport from "./messages/export/MessageIncomingFileExport";
|
||||
import MessageOutgoingImageExport from "./messages/export/MessageOutgoingImageExport";
|
||||
import MessageOutgoingAudioExport from "./messages/export/MessageOutgoingAudioExport";
|
||||
import MessageOutgoingVideoExport from "./messages/export/MessageOutgoingVideoExport";
|
||||
import MessageOutgoingThreadExport from "./messages/export/MessageOutgoingThreadExport";
|
||||
import MessageOutgoingFileExport from "./messages/export/MessageOutgoingFileExport";
|
||||
import ContactJoin from "./messages/ContactJoin.vue";
|
||||
import ContactLeave from "./messages/ContactLeave.vue";
|
||||
import ContactInvited from "./messages/ContactInvited.vue";
|
||||
|
|
@ -36,11 +38,10 @@ import RoomTopicChanged from "./messages/RoomTopicChanged.vue";
|
|||
import RoomAvatarChanged from "./messages/RoomAvatarChanged.vue";
|
||||
import RoomHistoryVisibility from "./messages/RoomHistoryVisibility.vue";
|
||||
import MessageOperations from "./messages/MessageOperations.vue";
|
||||
import AvatarOperations from "./messages/AvatarOperations.vue";
|
||||
import ChatHeader from "./ChatHeader";
|
||||
import VoiceRecorder from "./VoiceRecorder";
|
||||
import RoomInfoBottomSheet from "./RoomInfoBottomSheet";
|
||||
import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader";
|
||||
import WelcomeHeaderRoom from "./welcome_headers/WelcomeHeaderRoom.vue";
|
||||
import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet";
|
||||
import stickers from "../plugins/stickers";
|
||||
import StickerPickerBottomSheet from "./StickerPickerBottomSheet";
|
||||
|
|
@ -94,11 +95,10 @@ export default {
|
|||
MessageOperations,
|
||||
VoiceRecorder,
|
||||
RoomInfoBottomSheet,
|
||||
CreatedRoomWelcomeHeader,
|
||||
WelcomeHeaderRoom,
|
||||
MessageOperationsBottomSheet,
|
||||
StickerPickerBottomSheet,
|
||||
BottomSheet,
|
||||
AvatarOperations,
|
||||
CreatePollDialog,
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -172,6 +172,9 @@ export default {
|
|||
event.getContent().info.mimetype &&
|
||||
event.getContent().info.mimetype.startsWith("image/svg")
|
||||
) {
|
||||
if (isForExport) {
|
||||
return MessageIncomingFileExport;
|
||||
}
|
||||
return MessageIncomingFile;
|
||||
}
|
||||
if (isForExport) {
|
||||
|
|
@ -189,6 +192,9 @@ export default {
|
|||
}
|
||||
return MessageIncomingVideo;
|
||||
} else if (event.getContent().msgtype == "m.file") {
|
||||
if (isForExport) {
|
||||
return MessageIncomingFileExport;
|
||||
}
|
||||
return MessageIncomingFile;
|
||||
} else if (stickers.isStickerShortcode(event.getContent().body)) {
|
||||
return MessageIncomingSticker;
|
||||
|
|
@ -223,6 +229,9 @@ export default {
|
|||
}
|
||||
return MessageOutgoingVideo;
|
||||
} else if (event.getContent().msgtype == "m.file") {
|
||||
if (isForExport) {
|
||||
return MessageOutgoingFileExport;
|
||||
}
|
||||
return MessageOutgoingFile;
|
||||
} else if (stickers.isStickerShortcode(event.getContent().body)) {
|
||||
return MessageOutgoingSticker;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
<template>
|
||||
<v-responsive v-if="item.event.getContent().msgtype == 'm.video'" :class="{'thumbnail-item': true, 'preview': previewOnly}"
|
||||
<v-responsive v-if="item.event.getContent().msgtype == 'm.video' && item.src" :class="{'thumbnail-item': true, 'preview': previewOnly}"
|
||||
@click.stop="$emit('itemclick', {item: item})">
|
||||
<video :src="item.src" :controls="!previewOnly" class="w-100 h-100">
|
||||
{{ $t('fallbacks.video_file') }}
|
||||
</video>
|
||||
</v-responsive>
|
||||
<v-img v-else-if="item.event.getContent().msgtype == 'm.image'" :aspect-ratio="previewOnly ? (16 / 9) : undefined" :class="{'thumbnail-item': true, 'preview': previewOnly}" :src="item.src" :contain="!previewOnly" :cover="previewOnly"
|
||||
<v-img v-else-if="item.event.getContent().msgtype == 'm.image' && item.src" :aspect-ratio="previewOnly ? (16 / 9) : undefined" :class="{'thumbnail-item': true, 'preview': previewOnly}" :src="item.src" :contain="!previewOnly" :cover="previewOnly"
|
||||
@click.stop="$emit('itemclick', {item: item})" />
|
||||
<div v-else :class="{'thumbnail-item': true, 'preview': previewOnly, 'file-item': true}" @click.stop="$emit('itemclick', {item: item})">
|
||||
<v-icon>description</v-icon>
|
||||
{{ $sanitize(item.event.getContent().body) }}
|
||||
<v-icon>{{ fileTypeIcon }}</v-icon>
|
||||
<b>{{ $sanitize(fileName) }}</b>
|
||||
<div>{{ fileSize }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import util from "../../plugins/utils";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
/**
|
||||
|
|
@ -32,6 +35,26 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
fileTypeIcon() {
|
||||
if (util.isFileTypeAPK(this.item.event)) {
|
||||
return "$vuetify.icons.ic_apk";
|
||||
} else if (util.isFileTypeIPA(this.item.event)) {
|
||||
return "$vuetify.icons.ic_ipa";
|
||||
} else if (util.isFileTypePDF(this.item.event)) {
|
||||
return "$vuetify.icons.ic_pdf";
|
||||
} else if (util.isFileTypeZip(this.item.event)) {
|
||||
return "$vuetify.icons.ic_zip";
|
||||
}
|
||||
return "description"
|
||||
},
|
||||
fileName() {
|
||||
return util.getFileName(this.item.event);
|
||||
},
|
||||
fileSize() {
|
||||
return util.getFileSizeFormatted(this.item.event);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'avatar-operations': true,
|
||||
incoming: incoming,
|
||||
outgoing: !incoming,
|
||||
}"
|
||||
>
|
||||
<v-btn id="btn-private-chat" v-if="incoming" text @click.stop="startPrivateChat" class="ma-0 pa-0"
|
||||
>{{ $t("menu.start_private_chat") }}</v-btn
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import messageMixin from "./messageMixin";
|
||||
|
||||
export default {
|
||||
mixins: [messageMixin],
|
||||
mounted() {
|
||||
// Any items to show?
|
||||
if (this.room && this.event && this.$matrix.isDirectRoomWith(this.room, this.event.getSender())) {
|
||||
this.$emit("close");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startPrivateChat() {
|
||||
this.$emit("close");
|
||||
this.$emit("start-private-chat", { event: this.event });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
</v-avatar>
|
||||
<!-- SLOT FOR CONTENT -->
|
||||
<slot></slot>
|
||||
<div class="op-button" ref="opbutton" v-if="!event.isRedacted() && !$matrix.currentRoomIsReadOnlyForUser">
|
||||
<div class="op-button" ref="opbutton" v-if="!event.isRedacted()">
|
||||
<v-btn id="btn-more" icon @click.stop="showContextMenu($refs.opbutton)">
|
||||
<v-icon>more_vert</v-icon>
|
||||
</v-btn>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,7 @@
|
|||
</div>
|
||||
|
||||
<div class="message">
|
||||
<span>{{ $t('message.file_prefix') }}</span>
|
||||
<span
|
||||
class="cursor-pointer"
|
||||
@click.stop="$emit('download')"
|
||||
v-html="linkify($sanitize(messageText))"
|
||||
/>
|
||||
<ThumbnailView class="clickable" v-on:itemclick="$emit('download')" :item="{ event: event, src: null }" />
|
||||
<span class="edit-marker" v-if="event.replacingEventId()"
|
||||
>{{ $t('message.edited') }}</span
|
||||
>
|
||||
|
|
@ -25,11 +20,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ThumbnailView from '../file_mode/ThumbnailView.vue';
|
||||
import MessageIncoming from "./MessageIncoming.vue";
|
||||
|
||||
export default {
|
||||
extends: MessageIncoming,
|
||||
components: { MessageIncoming }
|
||||
components: { MessageIncoming, ThumbnailView }
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export default {
|
|||
const width = this.$refs.image.$el.clientWidth;
|
||||
const height = (width * 9) / 16;
|
||||
util
|
||||
.getThumbnail(this.$matrix.matrixClient, this.event, width, height)
|
||||
.getThumbnail(this.$matrix.matrixClient, this.event, this.$config, width, height)
|
||||
.then((url) => {
|
||||
const info = this.event.getContent().info;
|
||||
// JPEGs use cover, PNG and GIF ect contain. This is because PNG and GIF are expected to
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default {
|
|||
};
|
||||
ret.promise =
|
||||
util
|
||||
.getThumbnail(this.$matrix.matrixClient, e, 100, 100)
|
||||
.getThumbnail(this.$matrix.matrixClient, e, this.$config, 100, 100)
|
||||
.then((url) => {
|
||||
ret.src = url;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,6 +10,15 @@
|
|||
{{ $t('message.download_progress',{percentage: downloadProgress}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="userInitiatedDownloadsOnly && !src" class="download-overlay">
|
||||
<div class="text-center download-text">
|
||||
{{ fileName }}
|
||||
</div>
|
||||
<div class="text-center download-size">
|
||||
{{ fileSize }}
|
||||
</div>
|
||||
<v-icon size="32" color="white" class="clickable" @click="loadAttachmentSource(event, true)">download</v-icon>
|
||||
</div>
|
||||
</v-responsive>
|
||||
</div>
|
||||
</message-incoming>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<div :class="{'message-operations':true,'incoming':incoming,'outgoing':!incoming}">
|
||||
<template v-for="(item,index) in getEmojis">
|
||||
<v-btn v-if="index < maxRecents" :key="item.data" id="btn-quick-reaction" icon @click.stop="addQuickReaction(item.data)" class="ma-0 pa-0" dense>
|
||||
<v-btn v-if="userCanSendReactionAndAnswerPoll && index < maxRecents" :key="item.data" id="btn-quick-reaction" icon @click.stop="addQuickReaction(item.data)" class="ma-0 pa-0" dense>
|
||||
<span class="recent-emoji">{{ item.data }}</span>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-btn id="btn-more" icon @click.stop="more" class="ma-0 pa-0">
|
||||
<v-btn v-if="userCanSendReactionAndAnswerPoll" id="btn-more" icon @click.stop="more" class="ma-0 pa-0">
|
||||
<v-icon small> $vuetify.icons.addReaction </v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-if="incoming" id="btn-reply" icon @click.stop="addReply" class="ma-0 pa-0">
|
||||
<v-btn v-if="userCanSendMessage" id="btn-reply" icon @click.stop="addReply" class="ma-0 pa-0">
|
||||
<v-icon>reply</v-icon>
|
||||
</v-btn>
|
||||
<v-btn id="btn-edit" icon @click.stop="edit" class="ma-0 pa-0" v-if="isEditable">
|
||||
|
|
@ -40,6 +40,18 @@ export default {
|
|||
default: function () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
userCanSendMessage: {
|
||||
type: Boolean,
|
||||
default: function () {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
userCanSendReactionAndAnswerPoll: {
|
||||
type: Boolean,
|
||||
default: function() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<div class="status">{{ event.status }}</div>
|
||||
</div>
|
||||
|
||||
<div class="op-button" ref="opbutton" v-if="!event.isRedacted() && !$matrix.currentRoomIsReadOnlyForUser">
|
||||
<div class="op-button" ref="opbutton" v-if="!event.isRedacted() && $matrix.userCanSendMessageInCurrentRoom">
|
||||
<v-btn id="btn-show-menu" icon @click.stop="showContextMenu($refs.opbutton)">
|
||||
<v-icon>more_vert</v-icon>
|
||||
</v-btn>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,7 @@
|
|||
|
||||
|
||||
<div class="message">
|
||||
<span>{{ $t('message.file_prefix') }}</span>
|
||||
<span
|
||||
class="cursor-pointer"
|
||||
@click.stop="$emit('download')"
|
||||
v-html="linkify($sanitize(messageText))"
|
||||
/>
|
||||
<ThumbnailView class="clickable" v-on:itemclick="$emit('download')" :item="{ event: event, src: null }" />
|
||||
<span class="edit-marker" v-if="event.replacingEventId()"
|
||||
>{{ $t('message.edited') }}</span
|
||||
>
|
||||
|
|
@ -26,11 +21,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ThumbnailView from '../file_mode/ThumbnailView.vue';
|
||||
import MessageOutgoing from "./MessageOutgoing.vue";
|
||||
|
||||
export default {
|
||||
extends: MessageOutgoing,
|
||||
components: { MessageOutgoing },
|
||||
components: { MessageOutgoing, ThumbnailView },
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export default {
|
|||
const width = this.$refs.image.$el.clientWidth;
|
||||
const height = (width * 9) / 16;
|
||||
util
|
||||
.getThumbnail(this.$matrix.matrixClient, this.event, width, height)
|
||||
.getThumbnail(this.$matrix.matrixClient, this.event, this.$config, width, height)
|
||||
.then((url) => {
|
||||
const info = this.event.getContent().info;
|
||||
// JPEGs use cover, PNG and GIF ect contain. This is because PNG and GIF are expected to
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export default {
|
|||
};
|
||||
ret.promise =
|
||||
util
|
||||
.getThumbnail(this.$matrix.matrixClient, e, 100, 100)
|
||||
.getThumbnail(this.$matrix.matrixClient, e, this.$config, 100, 100)
|
||||
.then((url) => {
|
||||
ret.src = url;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,20 @@
|
|||
<video :src="src" controls class="w-100 h-100">
|
||||
{{$t('fallbacks.video_file')}}
|
||||
</video>
|
||||
<div v-if="downloadProgress" class="download-overlay">
|
||||
<div class="text-center download-text">
|
||||
{{ $t('message.download_progress',{percentage: downloadProgress}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="userInitiatedDownloadsOnly && !src" class="download-overlay">
|
||||
<div class="text-center download-text">
|
||||
{{ fileName }}
|
||||
</div>
|
||||
<div class="text-center download-size">
|
||||
{{ fileSize }}
|
||||
</div>
|
||||
<v-icon size="32" color="white" class="clickable" @click="loadAttachmentSource(event, true)">download</v-icon>
|
||||
</div>
|
||||
</v-responsive>
|
||||
</div>
|
||||
</message-outgoing>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
src: null,
|
||||
downloadProgress: null
|
||||
downloadProgress: null,
|
||||
userInitiatedDownloadsOnly: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -21,14 +22,27 @@ export default {
|
|||
beforeDestroy() {
|
||||
this.loadAttachmentSource(null); // Release
|
||||
},
|
||||
computed: {
|
||||
fileName() {
|
||||
return util.getFileName(this.event);
|
||||
},
|
||||
fileSize() {
|
||||
return util.getFileSizeFormatted(this.event);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadAttachmentSource(event) {
|
||||
loadAttachmentSource(event, userInitiated = false) {
|
||||
if (this.src) {
|
||||
const objectUrl = this.src;
|
||||
this.src = null;
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
if (event) {
|
||||
const fileSize = util.getFileSize(event);
|
||||
if (!userInitiated && (fileSize == 0 || fileSize > this.$config.maxSizeAutoDownloads)) {
|
||||
this.userInitiatedDownloadsOnly = true;
|
||||
return;
|
||||
}
|
||||
util
|
||||
.getAttachment(this.$matrix.matrixClient, event, (progress) => {
|
||||
this.downloadProgress = progress;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<template>
|
||||
<message-incoming v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<audio controls :src="src">{{ $t("fallbacks.audio_file") }}</audio>
|
||||
<audio controls>{{ $t("fallbacks.audio_file") }}</audio>
|
||||
</message-incoming>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import attachmentMixin from "../attachmentMixin";
|
||||
import exportedAttachmentMixin from "./exportedAttachmentMixin";
|
||||
import MessageIncoming from "../MessageIncoming.vue";
|
||||
|
||||
export default {
|
||||
name: "MessageIncomingAudioExport",
|
||||
extends: MessageIncoming,
|
||||
mixins: [attachmentMixin],
|
||||
mixins: [exportedAttachmentMixin],
|
||||
components: { MessageIncoming },
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
51
src/components/messages/export/MessageIncomingFileExport.vue
Normal file
51
src/components/messages/export/MessageIncomingFileExport.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<message-incoming v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<a style="text-decoration: none;color: currentColor" class="bubble" target="_blank" :href="href" >
|
||||
<div :class="{ 'thumbnail-item': true, 'preview': true, 'file-item': true }">
|
||||
<b>{{ $sanitize(fileName) }}</b>
|
||||
<div>{{ fileSize }}</div>
|
||||
</div>
|
||||
</a>
|
||||
</message-incoming>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import exportedAttachmentMixin from "./exportedAttachmentMixin";
|
||||
import MessageIncoming from "../MessageIncoming.vue";
|
||||
|
||||
export default {
|
||||
name: "MessageIncomingFileExport",
|
||||
extends: MessageIncoming,
|
||||
mixins: [exportedAttachmentMixin],
|
||||
components: { MessageIncoming },
|
||||
data() {
|
||||
return {
|
||||
href: ""
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
|
||||
.thumbnail-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.6rem;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
.v-icon {
|
||||
margin-bottom: 10px;
|
||||
color: currentColor;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<message-incoming v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<div class="bubble image-bubble">
|
||||
<v-responsive :aspect-ratio="16 / 9" :src="src">
|
||||
<video :src="src" controls class="w-100 h-100">
|
||||
<video controls class="w-100 h-100">
|
||||
{{ $t("fallbacks.video_file") }}
|
||||
</video>
|
||||
</v-responsive>
|
||||
|
|
@ -11,14 +11,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import attachmentMixin from "../attachmentMixin";
|
||||
import exportedAttachmentMixin from "./exportedAttachmentMixin";
|
||||
import MessageIncoming from "../MessageIncoming.vue";
|
||||
|
||||
export default {
|
||||
name: "MessageIncomingVideoExport",
|
||||
extends: MessageIncoming,
|
||||
components: { MessageIncoming },
|
||||
mixins: [attachmentMixin],
|
||||
mixins: [exportedAttachmentMixin],
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
<template>
|
||||
<message-outgoing v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<audio controls :src="src">{{ $t("fallbacks.audio_file") }}</audio>
|
||||
<audio controls>{{ $t("fallbacks.audio_file") }}</audio>
|
||||
</message-outgoing>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import attachmentMixin from "../attachmentMixin";
|
||||
import exportedAttachmentMixin from "./exportedAttachmentMixin";
|
||||
import MessageOutgoing from "../MessageOutgoing.vue";
|
||||
|
||||
export default {
|
||||
name: "MessageOutgoingAudioExport",
|
||||
extends: MessageOutgoing,
|
||||
components: { MessageOutgoing },
|
||||
mixins: [attachmentMixin],
|
||||
mixins: [exportedAttachmentMixin],
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
51
src/components/messages/export/MessageOutgoingFileExport.vue
Normal file
51
src/components/messages/export/MessageOutgoingFileExport.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<message-outgoing v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<a style="text-decoration: none;color: currentColor" class="bubble" target="_blank" :href="href" >
|
||||
<div :class="{ 'thumbnail-item': true, 'preview': true, 'file-item': true }">
|
||||
<b>{{ $sanitize(fileName) }}</b>
|
||||
<div>{{ fileSize }}</div>
|
||||
</div>
|
||||
</a>
|
||||
</message-outgoing>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import exportedAttachmentMixin from "./exportedAttachmentMixin";
|
||||
import MessageOutgoing from "../MessageOutgoing.vue";
|
||||
|
||||
export default {
|
||||
name: "MessageOutgoingFileExport",
|
||||
extends: MessageOutgoing,
|
||||
mixins: [exportedAttachmentMixin],
|
||||
components: { MessageOutgoing },
|
||||
data() {
|
||||
return {
|
||||
href: ""
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
|
||||
.thumbnail-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.6rem;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
.v-icon {
|
||||
margin-bottom: 10px;
|
||||
color: currentColor;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<message-outgoing v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
|
||||
<div class="bubble image-bubble">
|
||||
<v-responsive :aspect-ratio="16 / 9" class="ma-0 pa-0">
|
||||
<video :src="src" controls class="w-100 h-100">
|
||||
<video controls class="w-100 h-100">
|
||||
{{ $t("fallbacks.video_file") }}
|
||||
</video>
|
||||
</v-responsive>
|
||||
|
|
@ -11,14 +11,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import attachmentMixin from "../attachmentMixin";
|
||||
import exportedAttachmentMixin from "./exportedAttachmentMixin";
|
||||
import MessageOutgoing from "../MessageOutgoing.vue";
|
||||
|
||||
export default {
|
||||
name: "MessageOutgoingVideoExport",
|
||||
extends: MessageOutgoing,
|
||||
components: { MessageOutgoing },
|
||||
mixins: [attachmentMixin],
|
||||
mixins: [exportedAttachmentMixin],
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
17
src/components/messages/export/exportedAttachmentMixin.js
Normal file
17
src/components/messages/export/exportedAttachmentMixin.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import util from "../../../plugins/utils";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
src: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fileName() {
|
||||
return util.getFileName(this.event);
|
||||
},
|
||||
fileSize() {
|
||||
return util.getFileSizeFormatted(this.event);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -14,11 +14,43 @@ export default {
|
|||
editedRoomTopic: "",
|
||||
isRoomTopicEditMode: false,
|
||||
roomTopicErrorMessage: null,
|
||||
}
|
||||
messageRetentionDisplay: "",
|
||||
retentionPeriods: [
|
||||
{
|
||||
text: this.$t("room_info.message_retention_none"),
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
text: this.$t("room_info.message_retention_4_week"),
|
||||
value: 3600 * 24 * 28 * 1000
|
||||
},
|
||||
{
|
||||
text: this.$t("room_info.message_retention_2_week"),
|
||||
value: 3600 * 24 * 14 * 1000
|
||||
},
|
||||
{
|
||||
text: this.$t("room_info.message_retention_1_week"),
|
||||
value: 3600 * 24 * 7 * 1000
|
||||
},
|
||||
{
|
||||
text: this.$t("room_info.message_retention_1_day"),
|
||||
value: 3600 * 24 * 1000
|
||||
},
|
||||
{
|
||||
text: this.$t("room_info.message_retention_8_hours"),
|
||||
value: 3600 * 8 * 1000
|
||||
},
|
||||
{
|
||||
text: this.$t("room_info.message_retention_1_hour"),
|
||||
value: 3600 * 1000
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$matrix.on("Room.timeline", this.roomInfoMixinOnEvent);
|
||||
this.updatePermissions();
|
||||
this.updateMessageRetention();
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
|
|
@ -61,7 +93,10 @@ export default {
|
|||
publicRoomLink() {
|
||||
if (this.room && this.roomJoinRule == "public") {
|
||||
return this.$router.getRoomLink(
|
||||
this.room.getCanonicalAlias(), this.room.roomId, this.room.name, utils.roomDisplayTypeToQueryParam(this.room, this.roomDisplayType)
|
||||
this.room.getCanonicalAlias(),
|
||||
this.room.roomId,
|
||||
this.room.name,
|
||||
utils.roomDisplayTypeToQueryParam(this.room, this.roomDisplayType)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -69,7 +104,7 @@ export default {
|
|||
|
||||
roomHistory() {
|
||||
if (this.room) {
|
||||
return this.room.shouldEncryptForInvitedMembers()
|
||||
return this.room.shouldEncryptForInvitedMembers();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
|
@ -92,13 +127,24 @@ export default {
|
|||
*/
|
||||
privateParty() {
|
||||
if (this.isPrivate) {
|
||||
const membersButMe = this.room.getMembers().filter(m => m.userId != this.$matrix.currentUserId);
|
||||
const membersButMe = this.room.getMembers().filter((m) => m.userId != this.$matrix.currentUserId);
|
||||
if (membersButMe.length == 1) {
|
||||
return membersButMe[0];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
canViewRetentionPolicy() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if we can set message retention policy in the room.
|
||||
*/
|
||||
canChangeRetentionPolicy() {
|
||||
return this.userCanExportChat;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
room: {
|
||||
|
|
@ -115,14 +161,57 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
memberAvatar(member) {
|
||||
if (member) {
|
||||
return member.getAvatarUrl(
|
||||
this.$matrix.matrixClient.getHomeserverUrl(),
|
||||
40,
|
||||
40,
|
||||
"scale",
|
||||
true
|
||||
);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// 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;
|
||||
},
|
||||
/**
|
||||
* Get a string describing current room retention setting.
|
||||
* Can be "None", "1 week", "1 hour" etc...
|
||||
*/
|
||||
roomMessageRetentionDisplay(maybeEvent) {
|
||||
const retention = this.roomMessageRetention(maybeEvent);
|
||||
const retentionPeriodsFound = this.retentionPeriods.find(rp=>rp.value===retention)
|
||||
if(retentionPeriodsFound) {
|
||||
return retentionPeriodsFound.text
|
||||
}
|
||||
},
|
||||
|
||||
roomMessageRetention(maybeEvent) {
|
||||
const retentionEvent = maybeEvent || (this.room && this.room.currentState.getStateEvents("m.room.retention", ""));
|
||||
if (retentionEvent) {
|
||||
console.log("Retention event found", JSON.stringify(retentionEvent));
|
||||
const maxLifetime = parseInt(retentionEvent.getContent().max_lifetime);
|
||||
if (maxLifetime) {
|
||||
return maxLifetime;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
onRoomNameClicked() {
|
||||
if(this.userCanPurgeRoom) {
|
||||
if (this.userCanPurgeRoom) {
|
||||
this.isRoomNameEditMode = !this.isRoomNameEditMode;
|
||||
this.editedRoomName = this.roomName;
|
||||
}
|
||||
},
|
||||
updateRoomName() {
|
||||
if(this.editedRoomName) {
|
||||
if (this.editedRoomName) {
|
||||
this.$matrix.matrixClient.setRoomName(this.room.roomId, this.editedRoomName);
|
||||
this.isRoomNameEditMode = !this.isRoomNameEditMode;
|
||||
} else {
|
||||
|
|
@ -130,13 +219,13 @@ export default {
|
|||
}
|
||||
},
|
||||
onRoomTopicClicked() {
|
||||
if(this.userCanPurgeRoom) {
|
||||
if (this.userCanPurgeRoom) {
|
||||
this.isRoomTopicEditMode = !this.isRoomTopicEditMode;
|
||||
this.editedRoomTopic = this.roomTopic;
|
||||
}
|
||||
},
|
||||
updateRoomTopic() {
|
||||
if(this.editedRoomTopic) {
|
||||
if (this.editedRoomTopic) {
|
||||
this.$matrix.matrixClient.setRoomTopic(this.room.roomId, this.editedRoomTopic);
|
||||
this.isRoomTopicEditMode = !this.isRoomTopicEditMode;
|
||||
} else {
|
||||
|
|
@ -154,14 +243,8 @@ export default {
|
|||
if (this.room) {
|
||||
this.roomJoinRule = this.getRoomJoinRule();
|
||||
const canChangeAccess =
|
||||
this.room.currentState.mayClientSendStateEvent(
|
||||
"m.room.join_rules",
|
||||
this.$matrix.matrixClient
|
||||
) &&
|
||||
this.room.currentState.mayClientSendStateEvent(
|
||||
"m.room.guest_access",
|
||||
this.$matrix.matrixClient
|
||||
);
|
||||
this.room.currentState.mayClientSendStateEvent("m.room.join_rules", this.$matrix.matrixClient) &&
|
||||
this.room.currentState.mayClientSendStateEvent("m.room.guest_access", this.$matrix.matrixClient);
|
||||
this.userCanChangeJoinRule = canChangeAccess;
|
||||
this.userCanPurgeRoom = canChangeAccess; //TODO - need different permissions here?
|
||||
} else {
|
||||
|
|
@ -172,29 +255,25 @@ export default {
|
|||
},
|
||||
|
||||
roomInfoMixinOnEvent(event) {
|
||||
if (event.getRoomId() !== this.roomId) {
|
||||
return; // Not for this room
|
||||
}
|
||||
if (
|
||||
event.getType() == "m.room.join_rules" ||
|
||||
event.getType() == "m.room.guest_access"
|
||||
) {
|
||||
this.updatePermissions();
|
||||
if (this.room && this.room.roomId == event.getRoomId()) {
|
||||
if (event.getType() == "m.room.join_rules" || event.getType() == "m.room.guest_access") {
|
||||
this.updatePermissions();
|
||||
} else if (event.getType() == "m.room.retention") {
|
||||
this.updateMessageRetention(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateMessageRetention(event) {
|
||||
this.messageRetentionDisplay = this.roomMessageRetentionDisplay(event);
|
||||
},
|
||||
|
||||
privatePartyAvatar(size) {
|
||||
const other = this.privateParty;
|
||||
if (other) {
|
||||
return other.getAvatarUrl(
|
||||
this.$matrix.matrixClient.getHomeserverUrl(),
|
||||
size,
|
||||
size,
|
||||
"scale",
|
||||
true
|
||||
);
|
||||
return other.getAvatarUrl(this.$matrix.matrixClient.getHomeserverUrl(), size, size, "scale", true);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE, ROOM_TYPE_DEFAULT, STATE_EVENT_ROOM_TYPE } from "../plugins/utils";
|
||||
import { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE, ROOM_TYPE_DEFAULT, STATE_EVENT_ROOM_TYPE, ROOM_TYPE_CHANNEL } from "../plugins/utils";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
|
@ -34,7 +34,7 @@ export default {
|
|||
if (e) {
|
||||
const roomType = e.getContent().type;
|
||||
// Validate value, or return default
|
||||
if ([ROOM_TYPE_FILE_MODE, ROOM_TYPE_VOICE_MODE].includes(roomType)) {
|
||||
if ([ROOM_TYPE_FILE_MODE, ROOM_TYPE_VOICE_MODE, ROOM_TYPE_CHANNEL].includes(roomType)) {
|
||||
this.roomDisplayType = roomType;
|
||||
} else {
|
||||
this.roomDisplayType = ROOM_TYPE_DEFAULT;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export default {
|
|||
if (item.status !== this.sendStatuses.INITIAL) {
|
||||
return getItemPromise(++index);
|
||||
}
|
||||
const itemPromise = util.sendImage(this.$matrix.matrixClient, this.room.roomId, item.attachment, ({ loaded, total }) => {
|
||||
const itemPromise = util.sendFile(this.$matrix.matrixClient, this.room.roomId, item.attachment, ({ loaded, total }) => {
|
||||
if (loaded == total) {
|
||||
item.progress = 100;
|
||||
} else if (total > 0) {
|
||||
|
|
|
|||
85
src/components/welcome_headers/WelcomeHeaderChannel.vue
Normal file
85
src/components/welcome_headers/WelcomeHeaderChannel.vue
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div class="created-room-welcome-header">
|
||||
<div class="mt-2" v-if="roomJoinRule == 'public'">
|
||||
<i18n path="room_welcome.join_channel" tag="span">
|
||||
<template v-slot:link>
|
||||
<div style="position:relative;display:inline-block">
|
||||
<a @click.stop="copyPublicLink" :href="publicRoomLink" class="text-break">{{ publicRoomLink }}</a>
|
||||
<v-btn v-if="publicRoomLinkCopied" id="btn-copy-room-link" color="#444444" depressed
|
||||
style="position:absolute;left:0;top:0" class="filled-button link-copied-in-place">{{
|
||||
$t("room_info.link_copied") }}</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</i18n>
|
||||
</div>
|
||||
<div class="mt-2" v-else-if="roomJoinRule == 'invite'">
|
||||
{{ $t("room_welcome.join_invite") }}
|
||||
</div>
|
||||
<div class="mt-2" v-if="roomMessageRetention() > 0">
|
||||
<i18n path="room_welcome.info_retention" tag="span">
|
||||
<template v-slot:time>
|
||||
<b>{{ messageRetentionDisplay }}</b>
|
||||
</template>
|
||||
</i18n>
|
||||
</div>
|
||||
<div class="mt-2" v-if="roomMessageRetention() > 0">
|
||||
<a href="#" text @click.prevent="showMessageRetentionDialog = true">
|
||||
{{ $t("room_welcome.change") }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<v-btn id="btn-got-it" text @click.stop="$emit('close')" class="text-transform-0">
|
||||
{{ $t("room_welcome.got_it") }}
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<MessageRetentionDialog :show="showMessageRetentionDialog" :room="room" @close="showMessageRetentionDialog = false"
|
||||
v-on:message-retention-update="onMessageRetention" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MessageRetentionDialog from '../MessageRetentionDialog.vue';
|
||||
import roomInfoMixin from "../roomInfoMixin";
|
||||
|
||||
export default {
|
||||
name: "WelcomeHeaderChannel",
|
||||
mixins: [roomInfoMixin],
|
||||
components: {
|
||||
MessageRetentionDialog
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
publicRoomLinkCopied: false,
|
||||
showMessageRetentionDialog: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMessageRetention(ignoredretention) {
|
||||
this.updateMessageRetention();
|
||||
},
|
||||
copyPublicLink() {
|
||||
const self = this;
|
||||
this.$copyText(this.publicRoomLink).then(
|
||||
function (ignored) {
|
||||
// Success!
|
||||
self.publicRoomLinkCopied = true;
|
||||
setInterval(() => {
|
||||
// Hide again
|
||||
self.publicRoomLinkCopied = false;
|
||||
}, 3000);
|
||||
},
|
||||
function (e) {
|
||||
console.log(e);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
|
|
@ -7,10 +7,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import roomInfoMixin from "./roomInfoMixin";
|
||||
import roomInfoMixin from "../roomInfoMixin";
|
||||
|
||||
export default {
|
||||
name: "CreatedRoomWelcomeHeader",
|
||||
name: "WelcomeHeaderDirectChat",
|
||||
mixins: [roomInfoMixin],
|
||||
computed: {
|
||||
roomHistoryDescription() {
|
||||
|
|
@ -38,10 +38,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import roomInfoMixin from "./roomInfoMixin";
|
||||
import roomInfoMixin from "../roomInfoMixin";
|
||||
|
||||
export default {
|
||||
name: "CreatedRoomWelcomeHeader",
|
||||
name: "WelcomeHeaderRoom",
|
||||
mixins: [roomInfoMixin],
|
||||
computed: {
|
||||
roomHistoryDescription() {
|
||||
|
|
@ -1,21 +1,63 @@
|
|||
// Installing your PWA is required for periodic syncs to work
|
||||
const registerPeriodicBackgroundSync = async (registration) => {
|
||||
if ('periodicSync' in registration) {
|
||||
const status = await navigator.permissions.query({
|
||||
name: 'periodic-background-sync',
|
||||
});
|
||||
if (status.state === 'granted') {
|
||||
console.log('Periodic background sync registered and granted')
|
||||
|
||||
try {
|
||||
await registration.periodicSync.register('check-new-messages', {
|
||||
// minInterval is one day
|
||||
minInterval: 24 * 60 * 60 * 1000,
|
||||
});
|
||||
console.log('Periodic background sync registered!');
|
||||
console.log(registration.periodicSync.getTags())
|
||||
|
||||
const tags = await registration.periodicSync.getTags();
|
||||
if (tags.length) {
|
||||
tags.forEach((tag) => {
|
||||
console.log('tag name')
|
||||
console.log(tag)
|
||||
});
|
||||
}
|
||||
} catch(e) {
|
||||
console.log(`Periodic background sync failed: ${e}`);
|
||||
}
|
||||
} else {
|
||||
console.log('Periodic background sync is not granted.');
|
||||
}
|
||||
} else {
|
||||
console.log('Periodic background sync is not supported.');
|
||||
}
|
||||
}
|
||||
|
||||
export function registerServiceWorker() {
|
||||
if("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("/sw.js");
|
||||
navigator.serviceWorker.register("./sw.js")
|
||||
.then(async registration => {
|
||||
console.log('Service Worker registered with scope:', registration.scope);
|
||||
await registerPeriodicBackgroundSync(registration);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Service Worker registration failed:', error);
|
||||
});
|
||||
} else {
|
||||
console.log("No Service Worker support!");
|
||||
}
|
||||
}
|
||||
|
||||
export function requestNotificationPermission() {
|
||||
export async function requestNotificationPermission() {
|
||||
if("PushManager" in window) {
|
||||
window.Notification.requestPermission();
|
||||
return Notification?.requestPermission().then((permission) => permission);
|
||||
} else {
|
||||
console.log("No Push API Support!");
|
||||
}
|
||||
}
|
||||
|
||||
export function windowNotificationPermission() {
|
||||
return window.Notification.permission
|
||||
return window?.Notification?.permission ?? 'Not_supported'
|
||||
}
|
||||
|
||||
export function notificationCount() {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import dataUriToBuffer from "data-uri-to-buffer";
|
|||
import ImageResize from "image-resize";
|
||||
import { AutoDiscovery } from 'matrix-js-sdk';
|
||||
import User from '../models/user';
|
||||
const prettyBytes = require("pretty-bytes");
|
||||
|
||||
export const STATE_EVENT_ROOM_DELETION_NOTICE = "im.keanu.room_deletion_notice";
|
||||
export const STATE_EVENT_ROOM_DELETED = "im.keanu.room_deleted";
|
||||
|
|
@ -11,6 +12,7 @@ export const STATE_EVENT_ROOM_DELETED = "im.keanu.room_deleted";
|
|||
export const ROOM_TYPE_DEFAULT = "im.keanu.room_type_default";
|
||||
export const ROOM_TYPE_VOICE_MODE = "im.keanu.room_type_voice";
|
||||
export const ROOM_TYPE_FILE_MODE = "im.keanu.room_type_file";
|
||||
export const ROOM_TYPE_CHANNEL = "im.keanu.room_type_channel";
|
||||
|
||||
export const STATE_EVENT_ROOM_TYPE = "im.keanu.room_type";
|
||||
|
||||
|
|
@ -129,7 +131,7 @@ class Util {
|
|||
});
|
||||
}
|
||||
|
||||
getThumbnail(matrixClient, event, ignoredw, ignoredh) {
|
||||
getThumbnail(matrixClient, event, config, ignoredw, ignoredh) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const content = event.getContent();
|
||||
if (content.url != null) {
|
||||
|
|
@ -159,7 +161,7 @@ class Util {
|
|||
// true
|
||||
// );
|
||||
url = matrixClient.mxcUrlToHttp(file.url);
|
||||
} else if (content.file && content.file.url) {
|
||||
} else if (content.file && content.file.url && this.getFileSize(event) > 0 && this.getFileSize(event) < config.maxSizeAutoDownloads) {
|
||||
// No thumb, use real url
|
||||
file = content.file;
|
||||
url = matrixClient.mxcUrlToHttp(file.url);
|
||||
|
|
@ -348,7 +350,7 @@ class Util {
|
|||
});
|
||||
}
|
||||
|
||||
sendImage(matrixClient, roomId, file, onUploadProgress, threadRoot) {
|
||||
sendFile(matrixClient, roomId, file, onUploadProgress, threadRoot) {
|
||||
const uploadPromise = new UploadPromise(undefined);
|
||||
uploadPromise.wrappedPromise = new Promise((resolve, reject) => {
|
||||
var reader = new FileReader();
|
||||
|
|
@ -371,13 +373,13 @@ class Util {
|
|||
}
|
||||
|
||||
var description = file.name;
|
||||
var msgtype = 'm.image';
|
||||
if (file.type.startsWith("audio/")) {
|
||||
var msgtype = 'm.file';
|
||||
if (file.type.startsWith("image/")) {
|
||||
msgtype = 'm.image';
|
||||
} else if (file.type.startsWith("audio/")) {
|
||||
msgtype = 'm.audio';
|
||||
} else if (file.type.startsWith("video/")) {
|
||||
msgtype = 'm.video';
|
||||
} else if (file.type.startsWith("application/pdf")) {
|
||||
msgtype = 'm.file';
|
||||
}
|
||||
|
||||
const opts = {
|
||||
|
|
@ -488,7 +490,7 @@ class Util {
|
|||
|
||||
/**
|
||||
* Return what "mode" to use for the given room.
|
||||
*
|
||||
*
|
||||
* The default value is given by the room itself (as state events, see roomTypeMixin).
|
||||
* This method just returns if the user has overridden this in room settings (this
|
||||
* fact will be persisted as a user specific tag on the room). Note: currently override
|
||||
|
|
@ -517,7 +519,7 @@ class Util {
|
|||
|
||||
/**
|
||||
* Return the room type for the current room
|
||||
* @param {*} roomOrNull
|
||||
* @param {*} roomOrNull
|
||||
*/
|
||||
roomDisplayTypeToQueryParam(roomOrNull, roomDisplayType) {
|
||||
const roomType = this.roomDisplayTypeOverride(roomOrNull) || roomDisplayType;
|
||||
|
|
@ -900,7 +902,7 @@ class Util {
|
|||
}
|
||||
|
||||
downloadableTypes() {
|
||||
return ['m.video','m.audio','m.image','m.file'];
|
||||
return ['m.video','m.audio','m.image','m.file'];
|
||||
}
|
||||
|
||||
download(matrixClient, event) {
|
||||
|
|
@ -913,7 +915,6 @@ class Util {
|
|||
link.download = event.getContent().body || this.$t("fallbacks.download_name");
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
|
@ -951,6 +952,68 @@ class Util {
|
|||
}
|
||||
return Promise.resolve(config.defaultBaseUrl);
|
||||
}
|
||||
|
||||
getMimeType(event) {
|
||||
const content = event.getContent();
|
||||
return (content.info && content.info.mimetype) ? content.info.mimetype : (content.file && content.file.mimetype) ? content.file.mimetype : "";
|
||||
}
|
||||
|
||||
getFileName(event) {
|
||||
const content = event.getContent();
|
||||
return (content.body || content.filename || "").toLowerCase();
|
||||
}
|
||||
|
||||
getFileExtension(event) {
|
||||
const fileName = this.getFileName(event);
|
||||
const parts = fileName.split(".");
|
||||
if (parts.length > 1) {
|
||||
return "." + parts[parts.length - 1].toLowerCase();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
getFileSize(event) {
|
||||
const content = event.getContent();
|
||||
if (content.info) {
|
||||
return content.info.size;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
getFileSizeFormatted(event) {
|
||||
return prettyBytes(this.getFileSize(event));
|
||||
}
|
||||
|
||||
isFileTypeAPK(event) {
|
||||
const mime = this.getMimeType(event);
|
||||
if (mime === "application/vnd.android.package-archive" || this.getFileName(event).endsWith(".apk")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isFileTypeIPA(event) {
|
||||
if (this.getFileName(event).endsWith(".ipa")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isFileTypePDF(event) {
|
||||
const mime = this.getMimeType(event);
|
||||
if (mime === "application/pdf" || this.getFileName(event).endsWith(".pdf")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isFileTypeZip(event) {
|
||||
const mime = this.getMimeType(event);
|
||||
if (["application/zip", "application/x-zip-compressed", "multipart/x-zip"].includes(mime) || this.getFileName(event).endsWith(".zip")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export default new Util();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Login from '../components/Login.vue'
|
|||
import Profile from '../components/Profile.vue'
|
||||
import CreateRoom from '../components/CreateRoom.vue'
|
||||
import GetLink from '../components/GetLink.vue'
|
||||
import CreateChannel from '../components/CreateChannel.vue'
|
||||
import User from '../models/user'
|
||||
import util from '../plugins/utils'
|
||||
|
||||
|
|
@ -60,6 +61,11 @@ const routes = [
|
|||
name: 'GetLink',
|
||||
component: GetLink,
|
||||
},
|
||||
{
|
||||
path: '/createchannel',
|
||||
name: 'CreateChannel',
|
||||
component: CreateChannel,
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
|
|
@ -97,7 +103,7 @@ const router = new VueRouter({
|
|||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const publicPages = ['/login', '/createroom', '/getlink'];
|
||||
const publicPages = ['/login', '/createroom', '/getlink', '/createchannel'];
|
||||
var authRequired = !publicPages.includes(to.path);
|
||||
const loggedIn = router.app.$store.state.auth.user;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ export default {
|
|||
if (json.useFullyQualifiedDMLinks == undefined) {
|
||||
Vue.set(config, "useFullyQualifiedDMLinks", true); // Default to true
|
||||
}
|
||||
if (!json.maxSizeAutoDownloads) {
|
||||
Vue.set(config, "maxSizeAutoDownloads", 10 * 1024 * 1024);
|
||||
}
|
||||
Vue.set(config, "loaded", true);
|
||||
|
||||
// Tell callback we are done loading runtime config
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
import olm from "@matrix-org/olm/olm";
|
||||
global.Olm = olm;
|
||||
import * as sdk from "matrix-js-sdk";
|
||||
import { TimelineWindow, EventTimeline } from "matrix-js-sdk";
|
||||
import util, { STATE_EVENT_ROOM_DELETED } from "../plugins/utils";
|
||||
import { TimelineWindow, EventTimeline, EventStatus } from "matrix-js-sdk";
|
||||
import util, { STATE_EVENT_ROOM_DELETED, STATE_EVENT_ROOM_TYPE, ROOM_TYPE_CHANNEL, ROOM_TYPE_FILE_MODE, ROOM_TYPE_VOICE_MODE, ROOM_TYPE_DEFAULT } from "../plugins/utils";
|
||||
import User from "../models/user";
|
||||
|
||||
const LocalStorageCryptoStore =
|
||||
require("matrix-js-sdk/lib/crypto/store/localStorage-crypto-store").LocalStorageCryptoStore;
|
||||
|
||||
export const CHANNEL_POWER_LEVELS = {
|
||||
"m.room.encrypted": 0, // NOTE! Since practically all events in encrypted rooms get sent as "m.room.encrypted" we need to set
|
||||
// power to 0 here. Otherwise we would not be able to send quick reactions or poll responses...
|
||||
"m.poll.response": 0,
|
||||
"org.matrix.msc3381.poll.response": 0,
|
||||
"m.reaction": 0,
|
||||
"m.room.redaction": 0,
|
||||
};
|
||||
|
||||
export default {
|
||||
install(Vue, options) {
|
||||
if (!options || !options.store) {
|
||||
|
|
@ -37,7 +46,8 @@ export default {
|
|||
userDisplayName: null,
|
||||
userAvatar: null,
|
||||
currentRoom: null,
|
||||
currentRoomIsReadOnlyForUser: false,
|
||||
userCanSendMessageInCurrentRoom: true,
|
||||
userCanSendReactionAndAnswerPollInCurrentRoom: true,
|
||||
currentRoomBeingPurged: false,
|
||||
notificationCount: 0,
|
||||
};
|
||||
|
|
@ -106,9 +116,11 @@ export default {
|
|||
immediate: true,
|
||||
handler(room) {
|
||||
if (room) {
|
||||
this.currentRoomIsReadOnlyForUser = this.isReadOnlyRoomForUser(room.roomId, this.currentUserId);
|
||||
this.userCanSendMessageInCurrentRoom = this.userCanSendMessageInRoom(room.roomId, this.currentUserId);
|
||||
this.userCanSendReactionAndAnswerPollInCurrentRoom = this.userCanSendReactionAndAnswerPollInRoom(room.roomId, this.currentUserId);
|
||||
} else {
|
||||
this.currentRoomIsReadOnlyForUser = false;
|
||||
this.userCanSendMessageInCurrentRoom = true;
|
||||
this.userCanSendReactionAndAnswerPollInCurrentRoom = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -388,7 +400,8 @@ export default {
|
|||
case "m.room.power_levels":
|
||||
{
|
||||
if (this.currentRoom && event.getRoomId() == this.currentRoom.roomId) {
|
||||
this.currentRoomIsReadOnlyForUser = this.isReadOnlyRoomForUser(event.getRoomId(), this.currentUserId);
|
||||
this.userCanSendMessageInCurrentRoom = this.userCanSendMessageInRoom(event.getRoomId(), this.currentUserId);
|
||||
this.userCanSendReactionAndAnswerPollInCurrentRoom = this.userCanSendReactionAndAnswerPollInRoom(event.getRoomId(), this.currentUserId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -403,7 +416,7 @@ export default {
|
|||
if (room.currentState.maySendStateEvent("m.room.power_levels", event.getSender())) {
|
||||
if (event.getSender() !== this.currentUserId) {
|
||||
this.leaveRoomAndNavigate(room.roomId).then(() => {
|
||||
this.matrixClient.store.removeRoom(room.roomId);
|
||||
this.matrixClient.forget(room.roomId, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -673,6 +686,9 @@ export default {
|
|||
if (room && room.currentState) {
|
||||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||
if (powerLevelEvent) {
|
||||
if (this.roomType(roomId) == ROOM_TYPE_CHANNEL) {
|
||||
return Object.keys(powerLevelEvent.getContent().events).length == 0;
|
||||
}
|
||||
return powerLevelEvent.getContent().events_default > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -680,16 +696,6 @@ export default {
|
|||
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);
|
||||
|
|
@ -697,13 +703,55 @@ export default {
|
|||
const powerLevelEvent = room.currentState.getStateEvents("m.room.power_levels", "");
|
||||
if (powerLevelEvent) {
|
||||
let content = powerLevelEvent.getContent();
|
||||
content.events_default = readOnly ? 50 : 0;
|
||||
if (this.roomType(roomId) == ROOM_TYPE_CHANNEL) {
|
||||
content.events = readOnly ? {} : CHANNEL_POWER_LEVELS
|
||||
} else {
|
||||
content.events_default = readOnly ? 50 : 0;
|
||||
}
|
||||
this.matrixClient.sendStateEvent(room.roomId, "m.room.power_levels", content);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
userCanSendMessageInRoom(roomId, userId) {
|
||||
if (this.matrixClient && roomId && userId) {
|
||||
const room = this.getRoom(roomId);
|
||||
if (room && room.currentState) {
|
||||
let isAdmin = room.currentState.maySendEvent("m.room.power_levels", this.currentUserId);
|
||||
return isAdmin || (this.roomType(roomId) != ROOM_TYPE_CHANNEL && !this.isReadOnlyRoom(roomId));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
userCanSendReactionAndAnswerPollInRoom(roomId, userId) {
|
||||
if (this.matrixClient && roomId && userId) {
|
||||
const room = this.getRoom(roomId);
|
||||
if (room && room.currentState) {
|
||||
let isAdmin = room.currentState.maySendEvent("m.room.power_levels", this.currentUserId);
|
||||
return isAdmin || !this.isReadOnlyRoom(roomId);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
roomType(roomId) {
|
||||
if (this.matrixClient && roomId) {
|
||||
const room = this.getRoom(roomId);
|
||||
if (room && room.currentState) {
|
||||
const roomTypeEvent = room.currentState.getStateEvents(STATE_EVENT_ROOM_TYPE, "") || room.currentState.getStateEvents("m.room.create", "");
|
||||
if (roomTypeEvent) {
|
||||
const roomType = roomTypeEvent.getContent().type;
|
||||
if ([ROOM_TYPE_FILE_MODE, ROOM_TYPE_VOICE_MODE, ROOM_TYPE_CHANNEL].includes(roomType)) {
|
||||
return roomType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ROOM_TYPE_DEFAULT;
|
||||
},
|
||||
|
||||
/**
|
||||
* Purge the room with the given id! This means:
|
||||
* - Make room invite only
|
||||
|
|
@ -716,6 +764,8 @@ export default {
|
|||
purgeRoom(roomId, statusCallback) {
|
||||
this.currentRoomBeingPurged = true;
|
||||
|
||||
//console.log("Purge room");
|
||||
|
||||
const sleep = (ms) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
|
@ -744,30 +794,39 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
// Remove any possible pending events
|
||||
room.getLiveTimeline().getEvents().filter((e) => [EventStatus.ENCRYPTING, sdk.EventStatus.QUEUED].includes(e.status)).forEach((e) => {
|
||||
//console.log("Cancel pending event!");
|
||||
this.matrixClient.cancelPendingEvent(e);
|
||||
});
|
||||
|
||||
const timelineWindow = new TimelineWindow(this.matrixClient, room.getUnfilteredTimelineSet(), {});
|
||||
const self = this;
|
||||
|
||||
//console.log("Purge: set invite only");
|
||||
statusCallback(this.$t("room.purge_set_room_state"));
|
||||
this.matrixClient
|
||||
.sendStateEvent(roomId, "m.room.join_rules", { join_rule: "invite" }, "")
|
||||
withRetry(() => this.matrixClient.sendStateEvent(roomId, "m.room.join_rules", { join_rule: "invite" }, ""))
|
||||
.then(() => {
|
||||
//console.log("Purge: forbid guest access");
|
||||
return this.matrixClient.sendStateEvent(
|
||||
return withRetry(() => this.matrixClient.sendStateEvent(
|
||||
roomId,
|
||||
"m.room.guest_access",
|
||||
{ guest_access: "forbidden" },
|
||||
""
|
||||
);
|
||||
));
|
||||
})
|
||||
.then(() => {
|
||||
//console.log("Purge: set history visibility to 'joined'");
|
||||
return this.matrixClient.sendStateEvent(roomId, "m.room.history_visibility", {
|
||||
return withRetry(() => this.matrixClient.sendStateEvent(roomId, "m.room.history_visibility", {
|
||||
history_visibility: "joined",
|
||||
});
|
||||
}));
|
||||
})
|
||||
.then(() => {
|
||||
return this.matrixClient.sendStateEvent(roomId, STATE_EVENT_ROOM_DELETED, { status: "deleted" });
|
||||
return withRetry(() => this.matrixClient.sendStateEvent(roomId, STATE_EVENT_ROOM_DELETED, { status: "deleted" }));
|
||||
})
|
||||
.then(() => {
|
||||
// Set message retention 1 minute (may be limited by server)
|
||||
return withRetry(() => this.matrixClient.sendStateEvent(roomId, "m.room.retention", { max_lifetime: 60000 }));
|
||||
})
|
||||
.then(() => {
|
||||
//console.log("Purge: create timeline");
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const vuexPersistLocalStorage = new VuexPersist({
|
|||
language: state.language,
|
||||
currentRoomId: state.currentRoomId,
|
||||
hasShownMissedItemsHint: state.hasShownMissedItemsHint,
|
||||
globalNotification: state.globalNotification,
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
|
|
@ -98,6 +99,9 @@ export default new Vuex.Store({
|
|||
},
|
||||
setHasShownMissedItemsHint(state, flag) {
|
||||
state.hasShownMissedItemsHint = flag;
|
||||
},
|
||||
setGlobalNotification(state, flag) {
|
||||
state.globalNotification = flag;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue