Merge branch 'vite-vue3-upgrade' into 'dev'

Vue3 upgrade

See merge request keanuapp/keanuapp-weblite!348
This commit is contained in:
N Pex 2025-05-19 14:35:33 +00:00
commit 985dcfdfd2
197 changed files with 10244 additions and 25929 deletions

View file

@ -29,8 +29,8 @@ var packs = [];
fromDir(process.argv[2], /\.png$/, function (filename) {
let file = filename.substring(process.argv[2].length);
let parts = file.split("/");
let pack = parts[1];
let sticker = parts[2];
let pack = parts[parts.length - 2];
let sticker = parts[parts.length - 1];
if (!packs[pack]) {
packs[pack] = [];
}

View file

@ -4,9 +4,9 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<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="icon" id="favicon" href="/favicon.ico" />
<title></title>
<meta name="mobile-web-app-capable" content="yes" />
<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" />
@ -31,11 +31,12 @@
justify-content: center;
}
</style>
<script type="module" src="/src/main.js"></script>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please
>We're sorry but the app doesn't work properly without JavaScript enabled. Please
enable it to continue.</strong
>
</noscript>
@ -46,6 +47,7 @@
<lottie-player autoplay loop mode="normal" src="./loader.json" style="width: 128px"> </lottie-player>
</div>
</div>
<div id="app2"></div>
<!-- built files will be auto injected -->
</body>

27649
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,69 +1,73 @@
{
"name": "keanuapp-weblite",
"version": "0.1.44",
"version": "0.1.45",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"create-sticker-config": "node ./create_sticker_config.js $1"
},
"dependencies": {
"@matrix-org/olm": "^3.2.12",
"@vitejs/plugin-vue": "^5.2.3",
"aes-js": "^3.1.2",
"axios": "^1.4.0",
"browserify-fs": "^1.0.0",
"buffer": "^6.0.3",
"clean-insights-sdk": "^2.4",
"core-js": "^3.6.5",
"crypto-browserify": "^3.12.0",
"data-uri-to-buffer": "^3.0.1",
"crypto-browserify": "^3.12.1",
"dayjs": "^1.10.3",
"deepmerge": "^4.3.1",
"file-saver": "^2.0.5",
"fix-webm-duration": "^1.0.0",
"image-resize": "^1.1.5",
"image-size": "^1.0.0",
"image-resize": "^1.4.1",
"image-size": "^2.0.2",
"intersection-observer": "^0.12",
"js-sha256": "^0.9.0",
"json-web-key": "^0.4.0",
"jszip": "^3.9.1",
"linkify-html": "^4.1.0",
"linkifyjs": "^4.1.0",
"material-design-icons-iconfont": "^6.7.0",
"matrix-js-sdk": "^23.4.0",
"matrix-js-sdk": "^37.5.0",
"md-gum-polyfill": "^1.0.0",
"mic-recorder-to-mp3": "^2.2.2",
"path-browserify": "^1.0.1",
"pretty-bytes": "^5.6.0",
"qrcode": "^1.4.4",
"process": "^0.11.10",
"qrcode": "^1.5.4",
"raw-loader": "^4.0.2",
"recordrtc": "^5.6.2",
"roboto-fontface": "*",
"stream-browserify": "^3.0.0",
"v-emoji-picker": "^2.3.1",
"vue": "^2.6.11",
"vue-clipboard2": "^0.3.1",
"vue-i18n": "^8.24.4",
"vue-resize": "^1.0",
"vue-router": "^3.2.0",
"vue-sanitize": "^0.2.1",
"tiny-emitter": "^2.1.0",
"util": "^0.12.5",
"vue": "^3.5.13",
"vue-3-sanitize": "^0.1.4",
"vue-clipboard2": "^0.3.3",
"vue-i18n": "^11.1.3",
"vue-router": "^4.5.1",
"vue-swipeable-bottom-sheet": "^0.0.5",
"vuetify": "^2.2.11",
"vuex": "^3.5.1",
"vue3-emoji-picker": "^1.1.8",
"vue3-resize": "^0.2.0",
"vuetify": "^3.8.3",
"vuex": "^4.1.0",
"vuex-persist": "^3.1.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@rollup/plugin-commonjs": "^28.0.3",
"@types/jszip": "^3.4.0",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"@vue/compiler-sfc": "^3.5.13",
"babel-eslint": "^10.1.0",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^7.0",
"eslint-plugin-vue": "^7.0",
"sass": "^1.19.0",
"rollup-plugin-polyfill-node": "^0.13.0",
"sass": "^1.86.0",
"sass-loader": "^10",
"vue-cli-plugin-vuetify": "^2.4",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.3.0"
"unplugin-vue-components": "^28.4.1",
"vite": "^6.2.2",
"vite-plugin-static-copy": "^2.3.0",
"vite-plugin-vuetify": "^2.1.1",
"vue-cli-plugin-vuetify": "^2.5.8"
},
"eslintConfig": {
"root": true,

View file

@ -6,9 +6,8 @@
<!-- Loading indicator -->
<v-container
fluid
fill-height
v-if="showLoadingScreen"
class="loading-container"
class="loading-container fill-height"
>
<v-row align="center" justify="center">
<v-col class="text-center">
@ -22,7 +21,7 @@
</v-container>
<v-skeleton-loader
type="list-item-avatar-two-line, divider, list-item-three-line, card-heading"
type="list-item-avatar-two-line,divider,list-item-three-line,heading"
v-if="showLoadingScreen"
></v-skeleton-loader>
<unsupported-browser-alert />
@ -227,8 +226,9 @@ export default {
</script>
<style lang="scss">
@import '~vuetify/src/styles/settings/_variables.scss';
@import '@/assets/css/variables';
@use '@/assets/css/variables' as *;
@use "vuetify/settings" as *;
@use "sass:map";
.copyright {
font-size: 10px;
@ -239,7 +239,7 @@ export default {
}
.main {
@media #{map-get($display-breakpoints, 'lg-and-up')} {
@media #{map.get($display-breakpoints, 'lg-and-up')} {
margin: 0 auto;
width: $main-desktop-width;
}

View file

@ -1 +0,0 @@
// Sass Mixins

View file

@ -1,3 +1,5 @@
@use "@/assets/css/variables" as *;
.cursor-pointer {
cursor: pointer !important;
}

View file

@ -1,3 +1,5 @@
@forward "vuetify/settings";
$font-family: "Poppins";
$background: #ffffff;
$app-background: #f6f6f6;

View file

@ -1,3 +1,5 @@
@use "@/assets/css/variables" as *;
.chat-root.channel {
background-color: #f2f2f2;
.chat-content {
@ -111,6 +113,7 @@
flex: 0 0 100%;
margin: 24px 7px 0 7px;
padding: 0 8px 16px 8px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
.emoji {
font-size: 12 * $chat-text-size;
font-weight: 500;
@ -120,7 +123,6 @@
height: 21px;
}
}
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
/* Make all images 'cover' */

View file

@ -1,8 +1,11 @@
@import "~vuetify/src/styles/settings/_variables.scss";
@import "@/assets/css/main.scss";
@import "@/assets/css/vendors/v-emoji-picker";
@import "@/assets/css/filedrop.scss";
@import "@/assets/css/channel.scss";
@forward "@/assets/css/variables";
@use "vuetify/settings" as *;
@use "@/assets/css/variables" as *;
@use "@/assets/css/main.scss" as *;
@use "@/assets/css/vendors/v-emoji-picker" as *;
@use "@/assets/css/filedrop.scss" as *;
@use "@/assets/css/channel.scss" as *;
@use "sass:map";
$admin-bg: black;
$admin-fg: white;
@ -22,13 +25,22 @@ body {
}
}
.v-input.no-underline {
.v-field__outline {
/* Remove text underline */
color: transparent !important;
min-height: 20px;
overflow: hidden;
}
}
.home {
.v-card {
background-color: white;
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.25) !important;
border-radius: 18px;
padding-bottom: 10px;
.v-item-group > div:not(:last-of-type):after {
.v-list > .v-list-item:not(:last-of-type):before {
/* divider */
position: absolute;
content: " ";
@ -89,7 +101,7 @@ body {
font-size: 11 * $chat-text-size;
color: white;
background-color: red !important;
border-radius: $chat-standard-padding / 2;
border-radius: $chat-standard-padding * 0.5;
height: $chat-standard-padding;
margin-top: $chat-standard-padding-xs;
margin-bottom: $chat-standard-padding-xs;
@ -106,7 +118,7 @@ body {
line-height: 140%;
color: white !important;
background-color: #ff3300 !important;
border-radius: $small-button-height / 2;
border-radius: $small-button-height * 0.5;
min-height: 0;
height: $small-button-height !important;
margin-top: $chat-standard-padding-xs;
@ -183,7 +195,7 @@ body {
}
}
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
position: fixed;
z-index: 10;
}
@ -228,7 +240,7 @@ body {
background: #4d4d4d;
}
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
//margin-top: 72px;
margin-bottom: 70px;
}
@ -262,7 +274,7 @@ body {
margin: 0px 20px;
padding: 20px;
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
margin: 0px 5px;
padding: 15px;
}
@ -281,7 +293,7 @@ body {
border: 1px solid #d4d4d4;
border-radius: 32px;
margin-bottom: 10px;
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
margin-bottom: 12px;
}
}
@ -295,7 +307,7 @@ body {
fill: black;
}
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
display: none;
}
}
@ -312,7 +324,20 @@ body {
font-family: "Inter", sans-serif;
font-weight: 300;
font-size: 18 * $chat-text-size;
.v-input__slot {
.v-field__overlay {
display: none;
}
.v-field__input {
padding: 0;
min-height: 32px;
mask-image: none;
-webkit-mask-image: none;
font-family: "Inter", sans-serif;
font-weight: 300;
font-size: 18 * $chat-text-size;
}
.v-field__outline {
/* Remove text underline */
color: transparent !important;
min-height: 20px;
@ -333,7 +358,7 @@ body {
z-index: 40;
}
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
position: fixed;
bottom: 0px;
width: 100%;
@ -437,7 +462,7 @@ body {
position: relative;
max-width: 70%;
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
min-height: $min-touch-target;
}
}
@ -531,7 +556,7 @@ body {
position: relative;
max-width: 70%;
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
min-height: $min-touch-target;
}
}
@ -883,6 +908,7 @@ body {
.room-list {
.room-list-room {
color: white; // Used as selected item background
min-height: 58px;
.v-avatar:not(.round) {
// Make avatars rounded squares!
border-radius: 8px;
@ -974,7 +1000,7 @@ body {
}
.filled-button {
@media #{map-get($display-breakpoints, 'sm-and-up')} {
@media #{map.get($display-breakpoints, 'sm-and-up')} {
min-width: 180px !important;
}
}
@ -1566,7 +1592,7 @@ body {
line-height: 140%;
color: white !important;
background-color: #4642f1 !important;
border-radius: $small-button-height / 2;
border-radius: $small-button-height * 0.5;
min-height: 0;
height: $small-button-height !important;
margin-top: $chat-standard-padding-xs;

View file

@ -1,3 +1,5 @@
@use "../variables" as *;
.poll-bubble {
width: 70%;
text-align: start;

View file

@ -1,3 +1,5 @@
@use "../variables" as *;
.grow-enter-active,
.grow-leave-active {
transition-timing-function: ease-out;
@ -17,14 +19,21 @@
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
.voice-recorder-btn {
padding: 30px;
background-color: $voice-recorder-color;
&.recording {
background-color: $voice-recording-color;
.voice-recorder {
.v-btn {
background-color: black;
color: white;
}
&.recorded {
background-color: $voice-recorded-color;
.voice-recorder-btn {
background-color: $voice-recorder-color;
&.recording {
background-color: $voice-recording-color;
}
&.recorded {
background-color: $voice-recorded-color;
}
}
}
}

View file

@ -1,4 +1,4 @@
@import "@/assets/css/main.scss";
@use "@/assets/css/main.scss" as *;
.create-root {

View file

@ -1,3 +1,5 @@
@use "@/assets/css/variables" as *;
$large-button-height: $min-touch-target;
$small-button-height: 36px;
@ -65,7 +67,7 @@ $small-button-height: 36px;
line-height: 140%;
color: white;
background-color: $hiliteColor !important;
border-radius: $small-button-height / 2;
border-radius: $small-button-height * 0.5;
min-height: 0;
height: $small-button-height !important;
margin-top: $chat-standard-padding-xs;
@ -73,7 +75,7 @@ $small-button-height: 36px;
&.large {
padding: 16px 23px;
height: $large-button-height;
border-radius: $large-button-height / 2;
border-radius: $large-button-height * 0.5;
}
}

View file

@ -1,5 +1,6 @@
@import "~vuetify/src/styles/settings/_variables.scss";
@import "@/assets/css/main.scss";
@use "vuetify/settings" as *;
@use "@/assets/css/main.scss" as *;
@use "sass:map";
.v-btn.btn-light {
font-family: sans-serif;
@ -8,7 +9,7 @@
color: black;
background-color: white !important;
border: 1px solid black;
border-radius: $chat-button-height / 2;
border-radius: $chat-button-height * 0.5;
min-height: 0;
height: $chat-button-height !important;
margin-top: $chat-standard-padding-xs;
@ -22,7 +23,7 @@
color: white;
background-color: black !important;
border: 1px solid black;
border-radius: $chat-button-height / 2;
border-radius: $chat-button-height * 0.5;
min-height: 0;
height: $chat-button-height !important;
margin-top: $chat-standard-padding-xs;
@ -37,7 +38,7 @@
background: white;
box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.08);
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
padding: 20px;
}
}
@ -58,7 +59,7 @@
.inactive {
color: unset;
@media #{map-get($display-breakpoints, 'sm-and-up')} {
@media #{map.get($display-breakpoints, 'sm-and-up')} {
&:hover {
color: $very-very-purple;
}
@ -152,10 +153,10 @@
}
}
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
margin: 10px;
}
@media #{map-get($display-breakpoints, 'md-and-up')} {
@media #{map.get($display-breakpoints, 'md-and-up')} {
width: $main-desktop-width;
margin: 0 auto;
}

View file

@ -1,4 +1,5 @@
@import "@/assets/css/main.scss";
@use "@/assets/css/main.scss" as *;
@use "@/assets/css/variables" as *;
.login-root {
background-color: $background;

View file

@ -1,6 +1,7 @@
@import './variables';
@import './utilities';
@import './mixins';
@forward 'variables';
@forward 'utilities';
@use 'variables' as *;
@use 'utilities' as *;
@font-face {
font-family: "Inter";
@ -96,7 +97,7 @@ body { position:absolute; top:0; bottom:0; right:0; left:0; }
color: black;
background-color: white !important;
border: 1px solid black;
border-radius: $chat-standard-padding / 2;
border-radius: $chat-standard-padding * 0.5;
height: $chat-standard-padding;
margin-top: $chat-standard-padding-xs;
margin-bottom: $chat-standard-padding-xs;
@ -118,7 +119,7 @@ body { position:absolute; top:0; bottom:0; right:0; left:0; }
font-size: 11 * $chat-text-size;
color: white;
border: none;
border-radius: $chat-standard-padding / 2;
border-radius: $chat-standard-padding * 0.5;
height: $chat-standard-padding !important;
min-height: $chat-standard-padding !important;
margin-top: $chat-standard-padding-xs;
@ -131,21 +132,28 @@ body { position:absolute; top:0; bottom:0; right:0; left:0; }
}
}
.v-dialog {
.v-dialog-rounded > * {
border-radius: 20px !important;
}
.v-dialog {
.dialog-content {
border-radius: 20px !important;
padding: 20px;
background-color: white;
}
.dialog-title {
word-break: break-word;
text-align: center;
padding: 16px 24px 10px 24px;
margin-bottom: 20px;
}
.dialog-text {
text-align: left;
word-break: break-word;
a {
color: black;
text-decoration: underline;
@ -163,3 +171,15 @@ body { position:absolute; top:0; bottom:0; right:0; left:0; }
letter-spacing: 0.4px;
color: rgba(0, 0, 0, 0.6);
}
.v-card-title {
display: flex;
}
.fluid-radio {
flex-direction: row-reverse;
margin-bottom: 0;
.v-label {
width: 100%;
}
}

View file

@ -1,4 +1,4 @@
@import "@/assets/css/main.scss";
@use "@/assets/css/main.scss" as *;
.h3-bold {
font-family: "Poppins";

View file

@ -1,9 +1,22 @@
#EmojiPicker {
@use "vuetify/settings" as *;
@use "sass:map";
.v3-emoji-picker {
width: 100%;
background-color: #ffffff;
margin-top: 15px;
box-shadow: none;
height: 100%;
flex: 1 1 100%;
.container-emoji {
height: 60vh;
.v3-emojis button {
@media #{map.get($display-breakpoints, 'sm')} {
flex-basis: 10% !important;
max-width: 10% !important;
}
@media #{map.get($display-breakpoints, 'md-and-up')} {
flex-basis: 6.25% !important;
max-width: 6.25% !important;
}
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.3 KiB

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,13 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.7674 6.32558H8.55814V1.11628C8.55814 0.483721 8.07442 0 7.44186 0C6.8093 0 6.32558 0.483721 6.32558 1.11628V6.32558H1.11628C0.483721 6.32558 0 6.8093 0 7.44186C0 8.07442 0.483721 8.55814 1.11628 8.55814H6.32558V13.7674C6.32558 14.4 6.8093 14.8837 7.44186 14.8837C8.07442 14.8837 8.55814 14.4 8.55814 13.7674V8.55814H13.7674C14.4 8.55814 14.8837 8.07442 14.8837 7.44186C14.8837 6.8093 14.4 6.32558 13.7674 6.32558Z" fill="black" fill-opacity="0.5"/>
<path d="M13.7674 6.32558H8.55814V1.11628C8.55814 0.483721 8.07442 0 7.44186 0C6.8093 0 6.32558 0.483721 6.32558 1.11628V6.32558H1.11628C0.483721 6.32558 0 6.8093 0 7.44186C0 8.07442 0.483721 8.55814 1.11628 8.55814H6.32558V13.7674C6.32558 14.4 6.8093 14.8837 7.44186 14.8837C8.07442 14.8837 8.55814 14.4 8.55814 13.7674V8.55814H13.7674C14.4 8.55814 14.8837 8.07442 14.8837 7.44186C14.8837 6.8093 14.4 6.32558 13.7674 6.32558Z" fill="#242424"/>
</svg>
<template>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.7674 6.32558H8.55814V1.11628C8.55814 0.483721 8.07442 0 7.44186 0C6.8093 0 6.32558 0.483721 6.32558 1.11628V6.32558H1.11628C0.483721 6.32558 0 6.8093 0 7.44186C0 8.07442 0.483721 8.55814 1.11628 8.55814H6.32558V13.7674C6.32558 14.4 6.8093 14.8837 7.44186 14.8837C8.07442 14.8837 8.55814 14.4 8.55814 13.7674V8.55814H13.7674C14.4 8.55814 14.8837 8.07442 14.8837 7.44186C14.8837 6.8093 14.4 6.32558 13.7674 6.32558Z"
fill="black"
fill-opacity="0.5"
/>
<path
d="M13.7674 6.32558H8.55814V1.11628C8.55814 0.483721 8.07442 0 7.44186 0C6.8093 0 6.32558 0.483721 6.32558 1.11628V6.32558H1.11628C0.483721 6.32558 0 6.8093 0 7.44186C0 8.07442 0.483721 8.55814 1.11628 8.55814H6.32558V13.7674C6.32558 14.4 6.8093 14.8837 7.44186 14.8837C8.07442 14.8837 8.55814 14.4 8.55814 13.7674V8.55814H13.7674C14.4 8.55814 14.8837 8.07442 14.8837 7.44186C14.8837 6.8093 14.4 6.32558 13.7674 6.32558Z"
fill="#242424"
/>
</svg>
</template>

Before

Width:  |  Height:  |  Size: 1,010 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

@ -1,3 +1,8 @@
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.9265 0.278549C13.6041 -0.0706278 13.0413 -0.0953257 12.6944 0.225985L4.74201 7.59645C4.66894 7.6675 4.55947 7.67068 4.48341 7.6057L2.44807 5.93997C2.20767 5.74531 1.92476 5.64018 1.62665 5.64018C1.22503 5.64018 0.844759 5.83181 0.586146 6.15932L0.321439 6.49622C-0.153089 7.10185 -0.095373 7.992 0.449243 8.52339L3.64365 11.6229C3.89315 11.8671 4.2187 12 4.55335 12C4.93661 12 5.30168 11.827 5.5603 11.521L13.9508 1.5886C14.2672 1.21487 14.2581 0.64014 13.9265 0.278521L13.9265 0.278549Z" fill="black"/>
</svg>
<template>
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.9265 0.278549C13.6041 -0.0706278 13.0413 -0.0953257 12.6944 0.225985L4.74201 7.59645C4.66894 7.6675 4.55947 7.67068 4.48341 7.6057L2.44807 5.93997C2.20767 5.74531 1.92476 5.64018 1.62665 5.64018C1.22503 5.64018 0.844759 5.83181 0.586146 6.15932L0.321439 6.49622C-0.153089 7.10185 -0.095373 7.992 0.449243 8.52339L3.64365 11.6229C3.89315 11.8671 4.2187 12 4.55335 12C4.93661 12 5.30168 11.827 5.5603 11.521L13.9508 1.5886C14.2672 1.21487 14.2581 0.64014 13.9265 0.278521L13.9265 0.278549Z"
fill="black"
/>
</svg>
</template>

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 668 B

Before After
Before After

View file

@ -1,3 +1,8 @@
<svg width="9" height="8" viewBox="0 0 9 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.83799 0.185699C8.63338 -0.0470852 8.2762 -0.0635505 8.05605 0.150657L3.00935 5.0643C2.96298 5.11167 2.89351 5.11379 2.84524 5.07047L1.55358 3.95998C1.40102 3.83021 1.22148 3.76012 1.03229 3.76012C0.777422 3.76012 0.536097 3.88787 0.371977 4.10621L0.20399 4.33081C-0.0971529 4.73456 -0.0605251 5.328 0.285096 5.68226L2.31232 7.74863C2.47065 7.91143 2.67725 8 2.88963 8C3.13285 8 3.36453 7.88467 3.52865 7.68068L8.85337 1.05907C9.0542 0.809911 9.04842 0.42676 8.83794 0.185681L8.83799 0.185699Z" fill="black"/>
</svg>
<template>
<svg width="9" height="8" viewBox="0 0 9 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8.83799 0.185699C8.63338 -0.0470852 8.2762 -0.0635505 8.05605 0.150657L3.00935 5.0643C2.96298 5.11167 2.89351 5.11379 2.84524 5.07047L1.55358 3.95998C1.40102 3.83021 1.22148 3.76012 1.03229 3.76012C0.777422 3.76012 0.536097 3.88787 0.371977 4.10621L0.20399 4.33081C-0.0971529 4.73456 -0.0605251 5.328 0.285096 5.68226L2.31232 7.74863C2.47065 7.91143 2.67725 8 2.88963 8C3.13285 8 3.36453 7.88467 3.52865 7.68068L8.85337 1.05907C9.0542 0.809911 9.04842 0.42676 8.83794 0.185681L8.83799 0.185699Z"
fill="black"
/>
</svg>
</template>

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 668 B

Before After
Before After

View file

@ -1,4 +1,8 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.55 3H7.25C7.71944 3 8.1 3.38056 8.1 3.85C8.1 4.28591 7.77187 4.64518 7.34913 4.69428L7.25 4.7H5.55C5.11409 4.7 4.75482 5.02813 4.70572 5.45087L4.7 5.55V17.45C4.7 17.8859 5.02813 18.2452 5.45087 18.2943L5.55 18.3H17.45C17.8859 18.3 18.2452 17.9719 18.2943 17.5491L18.3 17.45V15.75C18.3 15.2806 18.6806 14.9 19.15 14.9C19.5859 14.9 19.9452 15.2281 19.9943 15.6509L20 15.75V17.45C20 18.808 18.9384 19.9181 17.5998 19.9957L17.45 20H5.55C4.19197 20 3.08189 18.9384 3.00433 17.5998L3 17.45V5.55C3 4.19197 4.06158 3.08189 5.40017 3.00433L5.55 3ZM19.15 3L19.2188 3.00255L19.3206 3.0172L19.4153 3.04228L19.5097 3.07962L19.5926 3.1241L19.6742 3.18087L19.751 3.24896L19.8331 3.34399L19.894 3.43855L19.9204 3.49037L19.9491 3.5596L19.9695 3.62391L19.9941 3.74962L20 3.85V8.95C20 9.41944 19.6194 9.8 19.15 9.8C18.6806 9.8 18.3 9.41944 18.3 8.95V5.9019L12.951 11.251C12.6446 11.5575 12.1625 11.581 11.829 11.3218L11.749 11.251C11.4425 10.9446 11.419 10.4625 11.6782 10.129L11.749 10.049L17.0964 4.7H14.05C13.5806 4.7 13.2 4.31944 13.2 3.85C13.2 3.38056 13.5806 3 14.05 3H19.15Z" fill="black"/>
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.55 3H7.25C7.71944 3 8.1 3.38056 8.1 3.85C8.1 4.28591 7.77187 4.64518 7.34913 4.69428L7.25 4.7H5.55C5.11409 4.7 4.75482 5.02813 4.70572 5.45087L4.7 5.55V17.45C4.7 17.8859 5.02813 18.2452 5.45087 18.2943L5.55 18.3H17.45C17.8859 18.3 18.2452 17.9719 18.2943 17.5491L18.3 17.45V15.75C18.3 15.2806 18.6806 14.9 19.15 14.9C19.5859 14.9 19.9452 15.2281 19.9943 15.6509L20 15.75V17.45C20 18.808 18.9384 19.9181 17.5998 19.9957L17.45 20H5.55C4.19197 20 3.08189 18.9384 3.00433 17.5998L3 17.45V5.55C3 4.19197 4.06158 3.08189 5.40017 3.00433L5.55 3ZM19.15 3L19.2188 3.00255L19.3206 3.0172L19.4153 3.04228L19.5097 3.07962L19.5926 3.1241L19.6742 3.18087L19.751 3.24896L19.8331 3.34399L19.894 3.43855L19.9204 3.49037L19.9491 3.5596L19.9695 3.62391L19.9941 3.74962L20 3.85V8.95C20 9.41944 19.6194 9.8 19.15 9.8C18.6806 9.8 18.3 9.41944 18.3 8.95V5.9019L12.951 11.251C12.6446 11.5575 12.1625 11.581 11.829 11.3218L11.749 11.251C11.4425 10.9446 11.419 10.4625 11.6782 10.129L11.749 10.049L17.0964 4.7H14.05C13.5806 4.7 13.2 4.31944 13.2 3.85C13.2 3.38056 13.5806 3 14.05 3H19.15Z"
fill="black"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

@ -1,6 +1,20 @@
<svg width="17" height="19" viewBox="0 0 17 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.31462 16.4718C3.31462 16.9496 3.70026 17.3368 4.17609 17.3368L16.1385 17.3368C16.6144 17.3368 17 16.9496 17 16.4718L16.9998 13.6229C16.9998 13.1452 16.6142 12.7579 16.1383 12.7579L4.1764 12.7579C3.70056 12.7579 3.31492 13.1452 3.31492 13.6229L3.31512 16.4718L3.31462 16.4718Z" fill="white"/>
<path d="M3.31462 10.4557C3.31462 10.9335 3.70026 11.3208 4.17609 11.3208L11.3428 11.3208C11.8186 11.3208 12.2043 10.9335 12.2043 10.4557L12.2043 7.60711C12.2043 7.12931 11.8186 6.74208 11.3428 6.74208L4.17609 6.74208C3.70026 6.74208 3.31462 7.12932 3.31462 7.60711L3.31462 10.4557Z" fill="white"/>
<path d="M3.31451 1.59127L3.31451 4.44011C3.31451 4.91791 3.70016 5.30514 4.17598 5.30514L6.99509 5.30514C7.47093 5.30514 7.85657 4.91791 7.85657 4.44011L7.85637 1.59127C7.85637 1.11347 7.47073 0.726242 6.9949 0.726242L4.17599 0.726242C3.70035 0.726242 3.31452 1.11348 3.31452 1.59127L3.31451 1.59127Z" fill="white"/>
<path d="M-2.00529e-05 0.587841L-2.0791e-05 17.4747C-2.08052e-05 17.7995 0.262306 18.0625 0.585404 18.0625L1.38198 18.0625C1.70528 18.0625 1.96741 17.7995 1.96741 17.4747L1.96741 0.587841C1.96741 0.263208 1.70508 -1.14667e-08 1.38198 -2.55897e-08L0.585405 -6.04092e-08C0.261911 -7.45496e-08 -2.00387e-05 0.263213 -2.00529e-05 0.587841Z" fill="white"/>
</svg>
<template>
<svg width="17" height="19" viewBox="0 0 17 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M3.31462 16.4718C3.31462 16.9496 3.70026 17.3368 4.17609 17.3368L16.1385 17.3368C16.6144 17.3368 17 16.9496 17 16.4718L16.9998 13.6229C16.9998 13.1452 16.6142 12.7579 16.1383 12.7579L4.1764 12.7579C3.70056 12.7579 3.31492 13.1452 3.31492 13.6229L3.31512 16.4718L3.31462 16.4718Z"
fill="white"
/>
<path
d="M3.31462 10.4557C3.31462 10.9335 3.70026 11.3208 4.17609 11.3208L11.3428 11.3208C11.8186 11.3208 12.2043 10.9335 12.2043 10.4557L12.2043 7.60711C12.2043 7.12931 11.8186 6.74208 11.3428 6.74208L4.17609 6.74208C3.70026 6.74208 3.31462 7.12932 3.31462 7.60711L3.31462 10.4557Z"
fill="white"
/>
<path
d="M3.31451 1.59127L3.31451 4.44011C3.31451 4.91791 3.70016 5.30514 4.17598 5.30514L6.99509 5.30514C7.47093 5.30514 7.85657 4.91791 7.85657 4.44011L7.85637 1.59127C7.85637 1.11347 7.47073 0.726242 6.9949 0.726242L4.17599 0.726242C3.70035 0.726242 3.31452 1.11348 3.31452 1.59127L3.31451 1.59127Z"
fill="white"
/>
<path
d="M-2.00529e-05 0.587841L-2.0791e-05 17.4747C-2.08052e-05 17.7995 0.262306 18.0625 0.585404 18.0625L1.38198 18.0625C1.70528 18.0625 1.96741 17.7995 1.96741 17.4747L1.96741 0.587841C1.96741 0.263208 1.70508 -1.14667e-08 1.38198 -2.55897e-08L0.585405 -6.04092e-08C0.261911 -7.45496e-08 -2.00387e-05 0.263213 -2.00529e-05 0.587841Z"
fill="white"
/>
</svg>
</template>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

@ -1,3 +0,0 @@
<svg width="24" height="17" viewBox="0 0 24 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.2477 0.000670823C16.1384 0.00531075 16.0311 0.0307591 15.9314 0.0754688C15.7815 0.142957 15.6546 0.25192 15.5656 0.389005C15.4766 0.526231 15.4291 0.685951 15.4291 0.849038V3.32107C14.7529 3.28142 14.0829 3.28494 13.4234 3.33598C6.62122 3.86253 0.885374 9.07191 0.00664931 16.0431H0.00679109C-0.0251098 16.2933 0.0564145 16.5444 0.229528 16.7288C0.402641 16.9133 0.649181 17.0122 0.902999 16.9988C1.15679 16.9853 1.39143 16.8609 1.54355 16.6591C4.61977 12.5918 9.44481 10.1994 14.572 10.1994H15.4292V12.7494C15.4295 12.9682 15.5147 13.1784 15.6674 13.3364C15.8199 13.4945 16.0281 13.5881 16.2484 13.5978C16.4687 13.6075 16.6845 13.5326 16.8505 13.3886L23.7079 7.43851C23.8935 7.2771 24 7.04413 24 6.79934C24 6.55455 23.8935 6.3216 23.7079 6.16016L16.8505 0.210072C16.6844 0.0659577 16.4685 -0.00898088 16.2478 0.000859754L16.2477 0.000670823Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1,022 B

View file

@ -0,0 +1,10 @@
<template>
<svg width="24" height="17" viewBox="0 0 24 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M16.2477 0.000670823C16.1384 0.00531075 16.0311 0.0307591 15.9314 0.0754688C15.7815 0.142957 15.6546 0.25192 15.5656 0.389005C15.4766 0.526231 15.4291 0.685951 15.4291 0.849038V3.32107C14.7529 3.28142 14.0829 3.28494 13.4234 3.33598C6.62122 3.86253 0.885374 9.07191 0.00664931 16.0431H0.00679109C-0.0251098 16.2933 0.0564145 16.5444 0.229528 16.7288C0.402641 16.9133 0.649181 17.0122 0.902999 16.9988C1.15679 16.9853 1.39143 16.8609 1.54355 16.6591C4.61977 12.5918 9.44481 10.1994 14.572 10.1994H15.4292V12.7494C15.4295 12.9682 15.5147 13.1784 15.6674 13.3364C15.8199 13.4945 16.0281 13.5881 16.2484 13.5978C16.4687 13.6075 16.6845 13.5326 16.8505 13.3886L23.7079 7.43851C23.8935 7.2771 24 7.04413 24 6.79934C24 6.55455 23.8935 6.3216 23.7079 6.16016L16.8505 0.210072C16.6844 0.0659577 16.4685 -0.00898088 16.2478 0.000859754L16.2477 0.000670823Z"
fill="currentColor"
/>
</svg>
</template>

View file

@ -1,10 +1,34 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.08333 5.77777H0.361111C0.161778 5.77777 0 5.93955 0 6.13888C0 6.33822 0.161778 6.49999 0.361111 6.49999H1.08333C1.28267 6.49999 1.44444 6.33822 1.44444 6.13888C1.44444 5.93955 1.28267 5.77777 1.08333 5.77777Z" fill="black"/>
<path d="M1.08333 5.77777H0.361111C0.161778 5.77777 0 5.93955 0 6.13888C0 6.33822 0.161778 6.49999 0.361111 6.49999H1.08333C1.28267 6.49999 1.44444 6.33822 1.44444 6.13888C1.44444 5.93955 1.28267 5.77777 1.08333 5.77777Z" fill="black"/>
<path d="M1.08333 8.66669H0.361111C0.161778 8.66669 0 8.82846 0 9.0278C0 9.22713 0.161778 9.38891 0.361111 9.38891H1.08333C1.28267 9.38891 1.44444 9.22713 1.44444 9.0278C1.44444 8.82846 1.28267 8.66669 1.08333 8.66669Z" fill="black"/>
<path d="M1.08333 8.66669H0.361111C0.161778 8.66669 0 8.82846 0 9.0278C0 9.22713 0.161778 9.38891 0.361111 9.38891H1.08333C1.28267 9.38891 1.44444 9.22713 1.44444 9.0278C1.44444 8.82846 1.28267 8.66669 1.08333 8.66669Z" fill="black"/>
<path d="M11.6553 4.02206L12.1724 3.50494C12.3132 3.36411 12.3132 3.13517 12.1724 2.99433C12.0316 2.8535 11.8026 2.8535 11.6618 2.99433L11.1447 3.51144C10.2766 2.75239 9.1658 2.26633 7.94453 2.18472V0.722222H9.02786C9.22719 0.722222 9.38897 0.560444 9.38897 0.361111C9.38897 0.161778 9.22719 0 9.02786 0H6.13897C5.93964 0 5.77786 0.161778 5.77786 0.361111C5.77786 0.560444 5.93964 0.722222 6.13897 0.722222H7.2223V2.18472C4.40419 2.37178 2.16675 4.71828 2.16675 7.58333C2.16675 10.5704 4.5963 13 7.58341 13C10.5705 13 13.0001 10.5704 13.0001 7.58333C13.0001 6.21978 12.4895 4.97611 11.6553 4.02206ZM7.58341 12.2778C4.99497 12.2778 2.88897 10.1718 2.88897 7.58333C2.88897 4.99489 4.99497 2.88889 7.58341 2.88889C10.1719 2.88889 12.2779 4.99489 12.2779 7.58333C12.2779 10.1718 10.1719 12.2778 7.58341 12.2778Z" fill="black"/>
<path d="M11.6553 4.02206L12.1724 3.50494C12.3132 3.36411 12.3132 3.13517 12.1724 2.99433C12.0316 2.8535 11.8026 2.8535 11.6618 2.99433L11.1447 3.51144C10.2766 2.75239 9.1658 2.26633 7.94453 2.18472V0.722222H9.02786C9.22719 0.722222 9.38897 0.560444 9.38897 0.361111C9.38897 0.161778 9.22719 0 9.02786 0H6.13897C5.93964 0 5.77786 0.161778 5.77786 0.361111C5.77786 0.560444 5.93964 0.722222 6.13897 0.722222H7.2223V2.18472C4.40419 2.37178 2.16675 4.71828 2.16675 7.58333C2.16675 10.5704 4.5963 13 7.58341 13C10.5705 13 13.0001 10.5704 13.0001 7.58333C13.0001 6.21978 12.4895 4.97611 11.6553 4.02206ZM7.58341 12.2778C4.99497 12.2778 2.88897 10.1718 2.88897 7.58333C2.88897 4.99489 4.99497 2.88889 7.58341 2.88889C10.1719 2.88889 12.2779 4.99489 12.2779 7.58333C12.2779 10.1718 10.1719 12.2778 7.58341 12.2778Z" fill="black"/>
<path d="M7.58328 4.33331C7.38395 4.33331 7.22217 4.49509 7.22217 4.69442V7.58331C7.22217 7.78265 7.38395 7.94442 7.58328 7.94442C7.78261 7.94442 7.94439 7.78265 7.94439 7.58331V4.69442C7.94439 4.49509 7.78261 4.33331 7.58328 4.33331Z" fill="black"/>
<path d="M7.58328 4.33331C7.38395 4.33331 7.22217 4.49509 7.22217 4.69442V7.58331C7.22217 7.78265 7.38395 7.94442 7.58328 7.94442C7.78261 7.94442 7.94439 7.78265 7.94439 7.58331V4.69442C7.94439 4.49509 7.78261 4.33331 7.58328 4.33331Z" fill="black"/>
</svg>
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.08333 5.77777H0.361111C0.161778 5.77777 0 5.93955 0 6.13888C0 6.33822 0.161778 6.49999 0.361111 6.49999H1.08333C1.28267 6.49999 1.44444 6.33822 1.44444 6.13888C1.44444 5.93955 1.28267 5.77777 1.08333 5.77777Z"
fill="black"
/>
<path
d="M1.08333 5.77777H0.361111C0.161778 5.77777 0 5.93955 0 6.13888C0 6.33822 0.161778 6.49999 0.361111 6.49999H1.08333C1.28267 6.49999 1.44444 6.33822 1.44444 6.13888C1.44444 5.93955 1.28267 5.77777 1.08333 5.77777Z"
fill="black"
/>
<path
d="M1.08333 8.66669H0.361111C0.161778 8.66669 0 8.82846 0 9.0278C0 9.22713 0.161778 9.38891 0.361111 9.38891H1.08333C1.28267 9.38891 1.44444 9.22713 1.44444 9.0278C1.44444 8.82846 1.28267 8.66669 1.08333 8.66669Z"
fill="black"
/>
<path
d="M1.08333 8.66669H0.361111C0.161778 8.66669 0 8.82846 0 9.0278C0 9.22713 0.161778 9.38891 0.361111 9.38891H1.08333C1.28267 9.38891 1.44444 9.22713 1.44444 9.0278C1.44444 8.82846 1.28267 8.66669 1.08333 8.66669Z"
fill="black"
/>
<path
d="M11.6553 4.02206L12.1724 3.50494C12.3132 3.36411 12.3132 3.13517 12.1724 2.99433C12.0316 2.8535 11.8026 2.8535 11.6618 2.99433L11.1447 3.51144C10.2766 2.75239 9.1658 2.26633 7.94453 2.18472V0.722222H9.02786C9.22719 0.722222 9.38897 0.560444 9.38897 0.361111C9.38897 0.161778 9.22719 0 9.02786 0H6.13897C5.93964 0 5.77786 0.161778 5.77786 0.361111C5.77786 0.560444 5.93964 0.722222 6.13897 0.722222H7.2223V2.18472C4.40419 2.37178 2.16675 4.71828 2.16675 7.58333C2.16675 10.5704 4.5963 13 7.58341 13C10.5705 13 13.0001 10.5704 13.0001 7.58333C13.0001 6.21978 12.4895 4.97611 11.6553 4.02206ZM7.58341 12.2778C4.99497 12.2778 2.88897 10.1718 2.88897 7.58333C2.88897 4.99489 4.99497 2.88889 7.58341 2.88889C10.1719 2.88889 12.2779 4.99489 12.2779 7.58333C12.2779 10.1718 10.1719 12.2778 7.58341 12.2778Z"
fill="black"
/>
<path
d="M11.6553 4.02206L12.1724 3.50494C12.3132 3.36411 12.3132 3.13517 12.1724 2.99433C12.0316 2.8535 11.8026 2.8535 11.6618 2.99433L11.1447 3.51144C10.2766 2.75239 9.1658 2.26633 7.94453 2.18472V0.722222H9.02786C9.22719 0.722222 9.38897 0.560444 9.38897 0.361111C9.38897 0.161778 9.22719 0 9.02786 0H6.13897C5.93964 0 5.77786 0.161778 5.77786 0.361111C5.77786 0.560444 5.93964 0.722222 6.13897 0.722222H7.2223V2.18472C4.40419 2.37178 2.16675 4.71828 2.16675 7.58333C2.16675 10.5704 4.5963 13 7.58341 13C10.5705 13 13.0001 10.5704 13.0001 7.58333C13.0001 6.21978 12.4895 4.97611 11.6553 4.02206ZM7.58341 12.2778C4.99497 12.2778 2.88897 10.1718 2.88897 7.58333C2.88897 4.99489 4.99497 2.88889 7.58341 2.88889C10.1719 2.88889 12.2779 4.99489 12.2779 7.58333C12.2779 10.1718 10.1719 12.2778 7.58341 12.2778Z"
fill="black"
/>
<path
d="M7.58328 4.33331C7.38395 4.33331 7.22217 4.49509 7.22217 4.69442V7.58331C7.22217 7.78265 7.38395 7.94442 7.58328 7.94442C7.78261 7.94442 7.94439 7.78265 7.94439 7.58331V4.69442C7.94439 4.49509 7.78261 4.33331 7.58328 4.33331Z"
fill="black"
/>
<path
d="M7.58328 4.33331C7.38395 4.33331 7.22217 4.49509 7.22217 4.69442V7.58331C7.22217 7.78265 7.38395 7.94442 7.58328 7.94442C7.78261 7.94442 7.94439 7.78265 7.94439 7.58331V4.69442C7.94439 4.49509 7.78261 4.33331 7.58328 4.33331Z"
fill="black"
/>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before After
Before After

View file

@ -0,0 +1,36 @@
<template>
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.08333 5.77777H0.361111C0.161778 5.77777 0 5.93955 0 6.13888C0 6.33822 0.161778 6.49999 0.361111 6.49999H1.08333C1.28267 6.49999 1.44444 6.33822 1.44444 6.13888C1.44444 5.93955 1.28267 5.77777 1.08333 5.77777Z"
fill="black"
/>
<path
d="M1.08333 5.77777H0.361111C0.161778 5.77777 0 5.93955 0 6.13888C0 6.33822 0.161778 6.49999 0.361111 6.49999H1.08333C1.28267 6.49999 1.44444 6.33822 1.44444 6.13888C1.44444 5.93955 1.28267 5.77777 1.08333 5.77777Z"
fill="black"
/>
<path
d="M1.08333 8.66669H0.361111C0.161778 8.66669 0 8.82846 0 9.0278C0 9.22713 0.161778 9.38891 0.361111 9.38891H1.08333C1.28267 9.38891 1.44444 9.22713 1.44444 9.0278C1.44444 8.82846 1.28267 8.66669 1.08333 8.66669Z"
fill="black"
/>
<path
d="M1.08333 8.66669H0.361111C0.161778 8.66669 0 8.82846 0 9.0278C0 9.22713 0.161778 9.38891 0.361111 9.38891H1.08333C1.28267 9.38891 1.44444 9.22713 1.44444 9.0278C1.44444 8.82846 1.28267 8.66669 1.08333 8.66669Z"
fill="black"
/>
<path
d="M11.6553 4.02206L12.1724 3.50494C12.3132 3.36411 12.3132 3.13517 12.1724 2.99433C12.0316 2.8535 11.8026 2.8535 11.6618 2.99433L11.1447 3.51144C10.2766 2.75239 9.1658 2.26633 7.94453 2.18472V0.722222H9.02786C9.22719 0.722222 9.38897 0.560444 9.38897 0.361111C9.38897 0.161778 9.22719 0 9.02786 0H6.13897C5.93964 0 5.77786 0.161778 5.77786 0.361111C5.77786 0.560444 5.93964 0.722222 6.13897 0.722222H7.2223V2.18472C4.40419 2.37178 2.16675 4.71828 2.16675 7.58333C2.16675 10.5704 4.5963 13 7.58341 13C10.5705 13 13.0001 10.5704 13.0001 7.58333C13.0001 6.21978 12.4895 4.97611 11.6553 4.02206ZM7.58341 12.2778C4.99497 12.2778 2.88897 10.1718 2.88897 7.58333C2.88897 4.99489 4.99497 2.88889 7.58341 2.88889C10.1719 2.88889 12.2779 4.99489 12.2779 7.58333C12.2779 10.1718 10.1719 12.2778 7.58341 12.2778Z"
fill="black"
/>
<path
d="M11.6553 4.02206L12.1724 3.50494C12.3132 3.36411 12.3132 3.13517 12.1724 2.99433C12.0316 2.8535 11.8026 2.8535 11.6618 2.99433L11.1447 3.51144C10.2766 2.75239 9.1658 2.26633 7.94453 2.18472V0.722222H9.02786C9.22719 0.722222 9.38897 0.560444 9.38897 0.361111C9.38897 0.161778 9.22719 0 9.02786 0H6.13897C5.93964 0 5.77786 0.161778 5.77786 0.361111C5.77786 0.560444 5.93964 0.722222 6.13897 0.722222H7.2223V2.18472C4.40419 2.37178 2.16675 4.71828 2.16675 7.58333C2.16675 10.5704 4.5963 13 7.58341 13C10.5705 13 13.0001 10.5704 13.0001 7.58333C13.0001 6.21978 12.4895 4.97611 11.6553 4.02206ZM7.58341 12.2778C4.99497 12.2778 2.88897 10.1718 2.88897 7.58333C2.88897 4.99489 4.99497 2.88889 7.58341 2.88889C10.1719 2.88889 12.2779 4.99489 12.2779 7.58333C12.2779 10.1718 10.1719 12.2778 7.58341 12.2778Z"
fill="black"
/>
<path
d="M7.58328 4.33331C7.38395 4.33331 7.22217 4.49509 7.22217 4.69442V7.58331C7.22217 7.78265 7.38395 7.94442 7.58328 7.94442C7.78261 7.94442 7.94439 7.78265 7.94439 7.58331V4.69442C7.94439 4.49509 7.78261 4.33331 7.58328 4.33331Z"
fill="black"
/>
<path
d="M7.58328 4.33331C7.38395 4.33331 7.22217 4.49509 7.22217 4.69442V7.58331C7.22217 7.78265 7.38395 7.94442 7.58328 7.94442C7.78261 7.94442 7.94439 7.78265 7.94439 7.58331V4.69442C7.94439 4.49509 7.78261 4.33331 7.58328 4.33331Z"
fill="black"
/>
</svg>
</template>

View file

@ -1,5 +1,16 @@
<svg width="28" height="33" viewBox="0 0 28 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.4989 0C9.22596 0 8.16477 1.0612 8.16477 2.33409V4.66819C5.83219 4.66819 3.49962 4.66819 1.16705 4.66819C0.52251 4.66819 0 5.1907 0 5.83524C0 6.47977 0.52251 7.00228 1.16705 7.00228H2.32953V29.1762C2.32953 31.0956 3.91123 32.6773 5.83067 32.6773H22.1693C24.0888 32.6773 25.6705 31.0956 25.6705 29.1762V7.00228H26.833C27.4775 7.00228 28 6.47977 28 5.83524C28 5.1907 27.4775 4.66819 26.833 4.66819C24.5004 4.66819 22.1678 4.66819 19.8352 4.66819V2.33409C19.8352 1.0612 18.774 0 17.5011 0H10.4989ZM10.4989 2.33409H17.5011V4.66819H10.4989V2.33409ZM4.66362 7.00228C10.8879 7.00228 17.1121 7.00228 23.3364 7.00228V29.1762C23.3364 29.8429 22.8361 30.3432 22.1693 30.3432H5.83067C5.16394 30.3432 4.66362 29.8429 4.66362 29.1762V7.00228Z" fill="white"/>
<path d="M10.4991 12.8374C10.1896 12.8374 9.89272 12.9604 9.67385 13.1792C9.45499 13.3981 9.33203 13.6949 9.33203 14.0044V23.3408C9.33203 23.6503 9.45499 23.9472 9.67385 24.1661C9.89272 24.3849 10.1896 24.5079 10.4991 24.5079C10.8086 24.5079 11.1054 24.3849 11.3243 24.1661C11.5432 23.9472 11.6661 23.6503 11.6661 23.3408V14.0044C11.6661 13.6949 11.5432 13.3981 11.3243 13.1792C11.1054 12.9604 10.8086 12.8374 10.4991 12.8374Z" fill="white"/>
<path d="M17.5015 12.8374C17.192 12.8374 16.8952 12.9604 16.6763 13.1792C16.4574 13.3981 16.3345 13.6949 16.3345 14.0044V23.3408C16.3345 23.6503 16.4574 23.9472 16.6763 24.1661C16.8952 24.3849 17.192 24.5079 17.5015 24.5079C17.811 24.5079 18.1079 24.3849 18.3267 24.1661C18.5456 23.9472 18.6686 23.6503 18.6686 23.3408V14.0044C18.6686 13.6949 18.5456 13.3981 18.3267 13.1792C18.1079 12.9604 17.811 12.8374 17.5015 12.8374Z" fill="white"/>
</svg>
<template>
<svg width="28" height="33" viewBox="0 0 28 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.4989 0C9.22596 0 8.16477 1.0612 8.16477 2.33409V4.66819C5.83219 4.66819 3.49962 4.66819 1.16705 4.66819C0.52251 4.66819 0 5.1907 0 5.83524C0 6.47977 0.52251 7.00228 1.16705 7.00228H2.32953V29.1762C2.32953 31.0956 3.91123 32.6773 5.83067 32.6773H22.1693C24.0888 32.6773 25.6705 31.0956 25.6705 29.1762V7.00228H26.833C27.4775 7.00228 28 6.47977 28 5.83524C28 5.1907 27.4775 4.66819 26.833 4.66819C24.5004 4.66819 22.1678 4.66819 19.8352 4.66819V2.33409C19.8352 1.0612 18.774 0 17.5011 0H10.4989ZM10.4989 2.33409H17.5011V4.66819H10.4989V2.33409ZM4.66362 7.00228C10.8879 7.00228 17.1121 7.00228 23.3364 7.00228V29.1762C23.3364 29.8429 22.8361 30.3432 22.1693 30.3432H5.83067C5.16394 30.3432 4.66362 29.8429 4.66362 29.1762V7.00228Z"
fill="white"
/>
<path
d="M10.4991 12.8374C10.1896 12.8374 9.89272 12.9604 9.67385 13.1792C9.45499 13.3981 9.33203 13.6949 9.33203 14.0044V23.3408C9.33203 23.6503 9.45499 23.9472 9.67385 24.1661C9.89272 24.3849 10.1896 24.5079 10.4991 24.5079C10.8086 24.5079 11.1054 24.3849 11.3243 24.1661C11.5432 23.9472 11.6661 23.6503 11.6661 23.3408V14.0044C11.6661 13.6949 11.5432 13.3981 11.3243 13.1792C11.1054 12.9604 10.8086 12.8374 10.4991 12.8374Z"
fill="white"
/>
<path
d="M17.5015 12.8374C17.192 12.8374 16.8952 12.9604 16.6763 13.1792C16.4574 13.3981 16.3345 13.6949 16.3345 14.0044V23.3408C16.3345 23.6503 16.4574 23.9472 16.6763 24.1661C16.8952 24.3849 17.192 24.5079 17.5015 24.5079C17.811 24.5079 18.1079 24.3849 18.3267 24.1661C18.5456 23.9472 18.6686 23.6503 18.6686 23.3408V14.0044C18.6686 13.6949 18.5456 13.3981 18.3267 13.1792C18.1079 12.9604 17.811 12.8374 17.5015 12.8374Z"
fill="white"
/>
</svg>
</template>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

@ -1,5 +1,16 @@
<svg width="28" height="33" viewBox="0 0 28 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.4989 0C9.22596 0 8.16477 1.0612 8.16477 2.33409V4.66819C5.83219 4.66819 3.49962 4.66819 1.16705 4.66819C0.52251 4.66819 0 5.1907 0 5.83524C0 6.47977 0.52251 7.00228 1.16705 7.00228H2.32953V29.1762C2.32953 31.0956 3.91123 32.6773 5.83067 32.6773H22.1693C24.0888 32.6773 25.6705 31.0956 25.6705 29.1762V7.00228H26.833C27.4775 7.00228 28 6.47977 28 5.83524C28 5.1907 27.4775 4.66819 26.833 4.66819C24.5004 4.66819 22.1678 4.66819 19.8352 4.66819V2.33409C19.8352 1.0612 18.774 0 17.5011 0H10.4989ZM10.4989 2.33409H17.5011V4.66819H10.4989V2.33409ZM4.66362 7.00228C10.8879 7.00228 17.1121 7.00228 23.3364 7.00228V29.1762C23.3364 29.8429 22.8361 30.3432 22.1693 30.3432H5.83067C5.16394 30.3432 4.66362 29.8429 4.66362 29.1762V7.00228Z" fill="currentColor"/>
<path d="M10.4991 12.8374C10.1896 12.8374 9.89272 12.9604 9.67385 13.1792C9.45499 13.3981 9.33203 13.6949 9.33203 14.0044V23.3408C9.33203 23.6503 9.45499 23.9472 9.67385 24.1661C9.89272 24.3849 10.1896 24.5079 10.4991 24.5079C10.8086 24.5079 11.1054 24.3849 11.3243 24.1661C11.5432 23.9472 11.6661 23.6503 11.6661 23.3408V14.0044C11.6661 13.6949 11.5432 13.3981 11.3243 13.1792C11.1054 12.9604 10.8086 12.8374 10.4991 12.8374Z" fill="currentColor"/>
<path d="M17.5015 12.8374C17.192 12.8374 16.8952 12.9604 16.6763 13.1792C16.4574 13.3981 16.3345 13.6949 16.3345 14.0044V23.3408C16.3345 23.6503 16.4574 23.9472 16.6763 24.1661C16.8952 24.3849 17.192 24.5079 17.5015 24.5079C17.811 24.5079 18.1079 24.3849 18.3267 24.1661C18.5456 23.9472 18.6686 23.6503 18.6686 23.3408V14.0044C18.6686 13.6949 18.5456 13.3981 18.3267 13.1792C18.1079 12.9604 17.811 12.8374 17.5015 12.8374Z" fill="currentColor"/>
</svg>
<template>
<svg width="28" height="33" viewBox="0 0 28 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.4989 0C9.22596 0 8.16477 1.0612 8.16477 2.33409V4.66819C5.83219 4.66819 3.49962 4.66819 1.16705 4.66819C0.52251 4.66819 0 5.1907 0 5.83524C0 6.47977 0.52251 7.00228 1.16705 7.00228H2.32953V29.1762C2.32953 31.0956 3.91123 32.6773 5.83067 32.6773H22.1693C24.0888 32.6773 25.6705 31.0956 25.6705 29.1762V7.00228H26.833C27.4775 7.00228 28 6.47977 28 5.83524C28 5.1907 27.4775 4.66819 26.833 4.66819C24.5004 4.66819 22.1678 4.66819 19.8352 4.66819V2.33409C19.8352 1.0612 18.774 0 17.5011 0H10.4989ZM10.4989 2.33409H17.5011V4.66819H10.4989V2.33409ZM4.66362 7.00228C10.8879 7.00228 17.1121 7.00228 23.3364 7.00228V29.1762C23.3364 29.8429 22.8361 30.3432 22.1693 30.3432H5.83067C5.16394 30.3432 4.66362 29.8429 4.66362 29.1762V7.00228Z"
fill="currentColor"
/>
<path
d="M10.4991 12.8374C10.1896 12.8374 9.89272 12.9604 9.67385 13.1792C9.45499 13.3981 9.33203 13.6949 9.33203 14.0044V23.3408C9.33203 23.6503 9.45499 23.9472 9.67385 24.1661C9.89272 24.3849 10.1896 24.5079 10.4991 24.5079C10.8086 24.5079 11.1054 24.3849 11.3243 24.1661C11.5432 23.9472 11.6661 23.6503 11.6661 23.3408V14.0044C11.6661 13.6949 11.5432 13.3981 11.3243 13.1792C11.1054 12.9604 10.8086 12.8374 10.4991 12.8374Z"
fill="currentColor"
/>
<path
d="M17.5015 12.8374C17.192 12.8374 16.8952 12.9604 16.6763 13.1792C16.4574 13.3981 16.3345 13.6949 16.3345 14.0044V23.3408C16.3345 23.6503 16.4574 23.9472 16.6763 24.1661C16.8952 24.3849 17.192 24.5079 17.5015 24.5079C17.811 24.5079 18.1079 24.3849 18.3267 24.1661C18.5456 23.9472 18.6686 23.6503 18.6686 23.3408V14.0044C18.6686 13.6949 18.5456 13.3981 18.3267 13.1792C18.1079 12.9604 17.811 12.8374 17.5015 12.8374Z"
fill="currentColor"
/>
</svg>
</template>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

@ -1,5 +0,0 @@
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="70" height="70" rx="12" fill="#242424"/>
<circle cx="35" cy="35" r="14" fill="white" fill-opacity="0.8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.6875 30.1891C32.2879 29.9393 31.7695 30.2266 31.7695 30.6979V39.302C31.7695 39.7733 32.2879 40.0606 32.6875 39.8108L39.5708 35.5088C39.9468 35.2738 39.9468 34.7262 39.5708 34.4912L32.6875 30.1891Z" fill="#242424"/>
</svg>

Before

Width:  |  Height:  |  Size: 490 B

View file

@ -0,0 +1,13 @@
<template>
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="70" height="70" rx="12" fill="#242424" />
<circle cx="35" cy="35" r="14" fill="white" fill-opacity="0.8" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M32.6875 30.1891C32.2879 29.9393 31.7695 30.2266 31.7695 30.6979V39.302C31.7695 39.7733 32.2879 40.0606 32.6875 39.8108L39.5708 35.5088C39.9468 35.2738 39.9468 34.7262 39.5708 34.4912L32.6875 30.1891Z"
fill="#242424"
/>
</svg>
<template></template>
</template>

View file

@ -0,0 +1,7 @@
{
"quotes": [
{
"quote":"Je suis un programmeur","author":"Mickey Mouse"}
]
}

View file

@ -115,7 +115,7 @@
"tip_text": "سيتمكن الأعضاء من رؤية نتائج الاستطلاع بعد الإجابة. أغلق الاستطلاع عندما تنتهي لعرض النتائج على الجميع في الغرفة.",
"create_poll_menu_option": "إنشاء استطلاع للرأي"
},
"export": {
"room_export": {
"exported_date": "‫تم التصدير بتاريخ {date}",
"fetched_n_events": "تم جلب {count} من الأحداث",
"fetched_n_of_total_events": "تم جلب {count} من أصل {total} من الأحداث",

View file

@ -395,7 +395,7 @@
"num_answered": "{count} টি উত্তর",
"results_shared": "ফলাফল রুমে প্রকাশ করা হয়েছে।"
},
"export": {
"room_export": {
"exported_date": "{date} এ এক্সপোর্ট করা হয়েছে",
"fetched_n_events": "{count} ইভেন্ট জড় করা হয়েছে",
"fetched_n_of_total_events": "{total} টির মধ্যে {count} টি ইভেন্ট জড় করা হয়েছে",

View file

@ -385,7 +385,7 @@
"view_results": "གྲུབ་འབྲས་ལ་གཟིགས།",
"results_shared": "གྲུབ་འབྲས་ཁ་བརྡ་ཁང་དུ་བརྒྱུད་སྤེལ་བྱེད་ངེས།"
},
"export": {
"room_export": {
"exported_date": "{date} ཉིན་ལ་ཕྱིར་འདྲེན་བྱས།",
"export_filename": "{date} ཉིན་ཕྱིར་འདྲེན་བྱས་པའི་ཁ་བརྡ།",
"processed_n_of_total_events": "བྱུང་བ་{total}ཁོངས་ནས་ལས་སྣོན་བྱས་ཟིན་པའི་སྨྱན་སྦྱོར་གྱི་གྲངས {count}",

View file

@ -363,7 +363,7 @@
"results_shared": "Die Ergebnisse werden dem Raum mitgeteilt.",
"tip_text": "Die Mitglieder sehen die Umfrageergebnisse, nachdem sie geantwortet haben. Schließe die Umfrage, wenn du fertig bist, um die Ergebnisse für alle im Raum anzuzeigen."
},
"export": {
"room_export": {
"fetched_n_of_total_events": "{count} von {total} Ereignissen geladen",
"exported_date": "Am {date} exportiert",
"processed_n_of_total_events": "Medien für {count} von {total} Ereignissen verarbeitet",

View file

@ -426,7 +426,7 @@
"num_answered": "{count} answers",
"results_shared": "Results shared to the room."
},
"export": {
"room_export": {
"exported_date": "Exported on {date}",
"fetched_n_events": "Fetched {count} events",
"fetched_n_of_total_events": "Fetched {count} of {total} events",

View file

@ -426,7 +426,7 @@
"not_supported": "La notificación aún no es compatible con dispositivos móviles",
"periodicSync_new_msg_reminder": "Es posible que tengas nuevos mensajes"
},
"export": {
"room_export": {
"fetched_n_of_total_events": "{count} de {total} eventos recuperados",
"export_filename": "Chat exportado el {date}",
"exported_date": "Exportado el {date}",

View file

@ -418,7 +418,7 @@
"num_answered": "{count} respuestas",
"results_shared": "Resultados compartidos a la sala."
},
"export": {
"room_export": {
"exported_date": "Exportado el {date}",
"fetched_n_events": "Se obtuvieron {count} eventos",
"processed_n_of_total_events": "Medios procesados para {count} de {total} eventos",

View file

@ -144,7 +144,7 @@
"message_history_warning": "Advertencia: el historial completo de mensajes será visible para los nuevos participantes.",
"message_history": "Historial de mensajes"
},
"export": {
"room_export": {
"exported_date": "Exportado en {date}",
"fetched_n_events": "Eventos{count} recuperados",
"fetched_n_of_total_events": "Se recuperaron {count} de {total} eventos",

View file

@ -423,7 +423,7 @@
"places": "مکان ها"
}
},
"export": {
"room_export": {
"exported_date": "خروجی گرفته شد در {date}",
"fetched_n_events": "{count} رویداد بازیابی شد",
"fetched_n_of_total_events": "{count} از {total} رویداد بازیابی شد",

View file

@ -195,7 +195,7 @@
"poll_submit": "تسلیم کردن",
"num_answered": "{count} جواب"
},
"export": {
"room_export": {
"exported_date": "خروجی گرفته شد در {date}",
"fetched_n_events": "{count} رویداد بازیابی شد",
"fetched_n_of_total_events": "{count} از {total} رویداد بازیابی شد",

View file

@ -445,7 +445,7 @@
"download_name": "Íoslódáil",
"original_text": "<buntéacs>"
},
"export": {
"room_export": {
"exported_date": "Onnmhairithe ar {date}",
"fetched_n_events": "Imeachtaí {count} faighte",
"fetched_n_of_total_events": "Fuarthas {count} de {total} imeacht",

View file

@ -56,7 +56,7 @@
"info_auto_join": "بەخێر بێیت بۆ {room}.\nتۆ وەکوو {you} بەشداری دەکەیت.",
"change": "گۆڕین"
},
"export": {
"room_export": {
"export_filename": "چەت لە {date} دەرهێنرا",
"exported_date": "لە {date} دەرهێنرا",
"fetched_n_events": "{count} ڕووداو هێنرا",

View file

@ -407,7 +407,7 @@
"poll_submit": "ສົ່ງ",
"num_answered": "{count} ຄຳຕອບ"
},
"export": {
"room_export": {
"exported_date": "ສົ່ງອອກໃນວັນທີ {date}",
"fetched_n_events": "ໄດ້ຮັບ {count} ກິດຈະກຳ",
"fetched_n_of_total_events": "ໄດ້ຮັບ {count}ຈາກ {total} ກິດຈະກຳ",

View file

@ -242,7 +242,7 @@
"num_answered": "အဖြေ {count}ခု",
"results_shared": "ရလဒ်များကို အခန်းသို့ မျှဝေခဲ့သည်။"
},
"export": {
"room_export": {
"fetched_n_events": "ပွဲအစီအစဉ် {count} ခု ရရှိခဲ့သည်",
"fetched_n_of_total_events": "ပွဲအစီအစဉ် {total} တွင် {count} ခု ရရှိခဲ့သည်",
"processed_n_of_total_events": "ပွဲအစီအစဉ် {total} တွင် {count} ခုအတွက် မီဒီယာကို စီမံဆောင်ရွက်ခဲ့သည်",

View file

@ -421,7 +421,7 @@
"original_text": "<original text>",
"download_name": "ډانلوډ"
},
"export": {
"room_export": {
"exported_date": "{date} نېټه خروجې اخیستل شوې ده",
"fetched_n_events": "{count} پېښې بېرته تر لاسه شوې دي",
"fetched_n_of_total_events": "له {total} پېښو څخه {count} یې بېرته تر لاسه شوې دي",

View file

@ -385,7 +385,7 @@
"view_results": "Ver os resultados",
"results_shared": "Resultados compartilhados com a sala."
},
"export": {
"room_export": {
"exported_date": "Foi exportado em {date}",
"fetched_n_events": "{count} eventos buscados",
"fetched_n_of_total_events": "Obteve {count} de {total} eventos",

View file

@ -434,7 +434,7 @@
"channel_topic": "Опишите его",
"error_channel": "Не удалось создать канал"
},
"export": {
"room_export": {
"fetched_n_events": "Найдено {count} событий",
"fetched_n_of_total_events": "Получено {count} из {total} событий",
"export_filename": "Экспортированный чат {date}",

View file

@ -434,7 +434,7 @@
"close_tab": "Tarayıcı sekmesini kapat",
"view_other_rooms": "Diğer odaları görüntüle"
},
"export": {
"room_export": {
"exported_date": "{date} tarihinde dışarı aktarıldı",
"fetched_n_events": "{count} eylem toplandı",
"fetched_n_of_total_events": "{total} eylemden {count}tanesi toplandı",

View file

@ -385,7 +385,7 @@
"answer_required": "答案不能为空。 请输入一些文本或删除此选项。",
"num_answered": "{count} 答案"
},
"export": {
"room_export": {
"fetched_n_events": "获取了 {count} 个事件",
"exported_date": "于 {date} 导出",
"fetched_n_of_total_events": "已获取 {count} 个事件,共 {total} 个事件",

View file

@ -3,7 +3,6 @@
class="action-row ma-0 pa-0"
no-gutters
align-content="center"
v-on="$listeners"
>
<v-col cols="auto" class="me-2 align-self-center">
<v-icon :size="iconSize">{{ icon }}</v-icon>
@ -40,5 +39,6 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "vuetify/settings";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -1,5 +1,5 @@
<template>
<div v-bind="{ ...$props, ...$attrs }" v-on="$listeners" class="messageIn">
<div v-bind="{ ...$props, ...$attrs }" class="messageIn">
<div class="load-earlier clickable" @click="loadPrevious">
<v-icon color="white" size="28">expand_less</v-icon>
</div>
@ -8,8 +8,8 @@
<div class="typing-users">
<transition-group name="list" tag="div">
<v-avatar v-for="(member) in recordingMembersExceptMe" :key="member.userId" class="typing-user" size="32" color="grey">
<img v-if="memberAvatar(member)" :src="memberAvatar(member)" />
<span v-else class="white--text headline">{{
<AuthedImage v-if="memberAvatar(member)" :src="memberAvatar(member)" />
<span v-else class="text-white headline">{{
member.name.substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
@ -23,7 +23,7 @@
<v-avatar v-if="currentAudioEvent" class="avatar" ref="avatar" size="32" color="#ededed"
@click.stop="otherAvatarClicked($refs.avatar.$el)">
<img v-if="messageEventAvatar(currentAudioEvent)" :src="messageEventAvatar(currentAudioEvent)" />
<span v-else class="white--text headline">{{
<span v-else class="text-white headline">{{
eventSenderDisplayName(currentAudioEvent).substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
@ -33,8 +33,8 @@
<div class="typing-users">
<transition-group name="list" tag="div">
<v-avatar v-for="reaction in reactions" :key="reaction.member.userId" class="typing-user" size="32" color="grey">
<img v-if="memberAvatar(reaction.member)" :src="memberAvatar(reaction.member)" />
<span v-else class="white--text headline">{{
<AuthedImage v-if="memberAvatar(reaction.member)" :src="memberAvatar(reaction.member)" />
<span v-else class="text-white headline">{{
reaction.member.name.substring(0, 1).toUpperCase()
}}</span>
<div class="reaction-emoji">{{ reaction.emoji }}</div>
@ -69,10 +69,9 @@
<div class="load-later">
<div style="align-self: flex-end;">
<v-btn class="clap-button clickable" text elevation="0" v-blur @click.stop="clapButtonClicked()">👏</v-btn>
<v-btn :class="{'mic-button': true, 'dimmed': !canRecordAudio}" ref="mic_button" fab small elevation="0" v-blur
<v-btn class="clap-button clickable" variant="text" elevation="0" v-blur @click.stop="clapButtonClicked()">👏</v-btn>
<v-btn icon="mic" :class="{'mic-button': true, 'dimmed': !canRecordAudio}" ref="mic_button" theme="dark" size="small" elevation="0" v-blur
@click.stop="micButtonClicked()">
<v-icon color="white">mic</v-icon>
</v-btn>
</div>
<v-icon class="clickable" @click="loadNext" color="white" size="28">expand_more</v-icon>
@ -85,10 +84,14 @@
<script>
import messageMixin from "./messages/messageMixin";
import util from "../plugins/utils";
import AuthedImage from "./AuthedImage.vue";
import clapping from "@/assets/sounds/clapping.mp3";
import emitter from 'tiny-emitter/instance';
export default {
mixins: [messageMixin],
components: {},
components: { AuthedImage },
emits: ['mark-read','loadprevious','loadnext','start-recording','sendclap'],
props: {
autoplay: {
type: Boolean,
@ -129,18 +132,18 @@ export default {
};
},
mounted() {
this.$root.$on('audio-playback-started', this.audioPlaybackStarted);
this.$root.$on('audio-playback-paused', this.audioPlaybackPaused);
this.$root.$on('audio-playback-ended', this.audioPlaybackEnded);
this.$root.$on('audio-playback-reaction', this.audioPlaybackReaction);
emitter.on('audio-playback-started', this.audioPlaybackStarted);
emitter.on('audio-playback-paused', this.audioPlaybackPaused);
emitter.on('audio-playback-ended', this.audioPlaybackEnded);
emitter.on('audio-playback-reaction', this.audioPlaybackReaction);
document.body.classList.add("dark");
this.$audioPlayer.setAutoplay(false);
},
beforeDestroy() {
this.$root.$off('audio-playback-started', this.audioPlaybackStarted);
this.$root.$off('audio-playback-paused', this.audioPlaybackPaused);
this.$root.$off('audio-playback-ended', this.audioPlaybackEnded);
this.$root.$off('audio-playback-reaction', this.audioPlaybackReaction);
beforeUnmount() {
emitter.off('audio-playback-started', this.audioPlaybackStarted);
emitter.off('audio-playback-paused', this.audioPlaybackPaused);
emitter.off('audio-playback-ended', this.audioPlaybackEnded);
emitter.off('audio-playback-reaction', this.audioPlaybackReaction);
document.body.classList.remove("dark");
this.$audioPlayer.removeListener(this._uid);
this.currentAudioEvent = null;
@ -270,7 +273,7 @@ export default {
},
audioPlaybackReaction(reaction) {
// Play sound!
const audio = new Audio(require("@/assets/sounds/clapping.mp3"));
const audio = new Audio(clapping);
audio.volume = 0.6;
audio.play();
@ -407,7 +410,9 @@ export default {
40,
40,
"scale",
true
true,
false,
this.$matrix.useAuthedMedia,
);
}
return null;
@ -440,5 +445,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -0,0 +1,69 @@
<template>
<img :src="imageSrc" style="width:100%;height:100%" />
</template>
<script>
import axios from 'axios';
export default {
name: "AuthedImage",
props: {
src: {
type: String,
default: function () {
return undefined;
},
},
},
unmounted() {
this.unloadSrc();
},
watch: {
src: {
immediate: true,
handler(newValue) {
this.unloadSrc();
this.loadSrc();
}
}
},
data() {
return {
imageSrc: null
}
},
methods: {
loadSrc() {
if (this.src) {
if (this.$matrix.useAuthedMedia) {
axios
.get(this.src, {
responseType: "blob", headers: {
Authorization: `Bearer ${this.$matrix.matrixClient.getAccessToken()}`,
}
})
.then((response) => {
this.imageSrc = URL.createObjectURL(response.data);
})
.catch((err) => {
console.log("Download error: ", err);
});
} else {
this.imageSrc = this.src;
}
}
},
unloadSrc() {
if (this.imageSrc && this.src != this.imageSrc) {
const url = this.imageSrc;
this.imageSrc = null;
URL.revokeObjectURL(url);
}
}
}
};
</script>
<style lang="scss">
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -18,15 +18,13 @@
:style="{ top: `${isMove ? y : calcY()}px` }"
>
<v-btn
fab
x-small
size="small"
elevation="0"
color="black"
@click.stop="onBackgroundClick"
class="bottom-sheet-close"
v-if="showCloseButton"
icon="cancel"
>
<v-icon color="white" >cancel</v-icon>
</v-btn>
<div class="bottom-sheet-handle"><div class="bottom-sheet-handle-decoration" /></div>
<div ref="sheetContent" class="sheetContent">
@ -116,7 +114,7 @@ export default {
}
});
},
beforeDestroy() {
beforeUnmount() {
this.mc.destroy();
window.onresize = null;
},
@ -163,8 +161,10 @@ export default {
</script>
<style lang="scss" scoped>
@import '~vuetify/src/styles/settings/_variables.scss';
@import '@/assets/css/variables';
@use 'vuetify/settings' as *;
@use '@/assets/css/variables' as *;
@use "sass:map";
.sheetContent {
position:absolute;
top:20px;left:0;
@ -220,12 +220,12 @@ export default {
.bottom-sheet-close {
position: absolute;
right: 0;
top: 0;
right: 4px;
top: 4px;
z-index: 1;
}
@media #{map-get($display-breakpoints, 'lg-and-up')} {
@media #{map.get($display-breakpoints, 'lg-and-up')} {
margin: 0 auto;
width: $dialog-desktop-width;
}
@ -238,7 +238,7 @@ export default {
}
.bottom-sheet-content[data-state="small"] {
@media #{map-get($display-breakpoints, 'lg-and-up')} {
@media #{map.get($display-breakpoints, 'lg-and-up')} {
top: 100px !important;
}
}

View file

@ -4,11 +4,13 @@
v-on:header-click="onHeaderClick"
v-on:view-room-details="viewRoomDetails"
v-on:purge="showPurgeConfirmation = true"
v-on:download="downloadingChat = 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:purge="showPurgeConfirmation = true"
v-on:download="downloadingChat = true"
v-else-if="!useFileModeNonAdmin" />
<AudioLayout ref="chatContainer" class="auto-audio-player-root" v-if="useVoiceMode" :room="room"
:events="events" :autoplay="!showRecorder"
@ -51,7 +53,7 @@
v-on:redact="redact(selectedEvent)"
v-on:download="download(selectedEvent)"
v-on:more="
isEmojiQuickReaction= true
isEmojiQuickReaction=true;
showMoreMessageOperations({event: selectedEvent, anchor: $event.anchor})
"
:userCanSendReactionAndAnswerPoll="$matrix.userCanSendReactionAndAnswerPollInCurrentRoom"
@ -95,7 +97,7 @@
v-on:download="download(event)"
v-on:poll-closed="pollWasClosed(event)"
v-on:more="
isEmojiQuickReaction = true
isEmojiQuickReaction = true;
showMoreMessageOperations({event: event, anchor: $event.anchor})
"
v-on:layout-change="onLayoutChange"
@ -115,9 +117,8 @@
<NoHistoryRoomWelcomeHeader v-if="showNoHistoryRoomWelcomeHeader" />
<!-- "Scroll to end"-button -->
<v-btn v-if="!useVoiceMode" :class="{'scroll-to-end': true, 'reversed': reverseOrder}" v-show="showScrollToEnd" fab x-small elevation="0" color="black"
<v-btn v-if="!useVoiceMode" icon="arrow_downward" :class="{'scroll-to-end': true, 'reversed': reverseOrder}" v-show="showScrollToEnd" theme="dark" size="x-small" elevation="0" color="black"
@click.stop="scrollToEndOfTimeline">
<v-icon color="white">arrow_downward</v-icon>
</v-btn>
@ -131,7 +132,7 @@
<div class="col">
<div class="font-weight-medium">{{ $t("message.replying_to", { user: senderDisplayName }) }}</div>
<div v-if="replyToContentType === 'm.text'" class="reply-text" :title="replyToEvent.getContent().body">
{{ replyToEvent.getContent().body | latestReply }}
{{ latestReply }}
</div>
<div v-if="replyToContentType === 'm.thread' || replyToContentType === 'io.element.thread'">{{ replyToThreadMessage }}</div>
<div v-if="replyToContentType === 'm.image'">{{ $t("message.reply_image") }}</div>
@ -142,13 +143,12 @@
<div class="col col-auto" v-if="replyToContentType !== 'm.text'">
<img v-if="replyToContentType === 'm.image'" width="50px" height="50px" :src="replyToImg"
class="rounded" />
<v-img v-if="replyToContentType === 'm.audio'" src="@/assets/icons/audio_message.svg" />
<v-img v-if="replyToContentType === 'm.video'" src="@/assets/icons/video_message.svg" />
<v-icon v-if="replyToContentType === 'm.poll'" light>$vuetify.icons.poll</v-icon>
<v-icon v-if="replyToContentType === 'm.audio'">$vuetify.icons.audio_message</v-icon>
<v-icon v-if="replyToContentType === 'm.video'">$vuetify.icons.video_message</v-icon>
<v-icon v-if="replyToContentType === 'm.poll'" theme="light">$vuetify.icons.poll</v-icon>
</div>
<div class="col col-auto">
<v-btn fab x-small elevation="0" color="black" @click.stop="cancelEditReply">
<v-icon color="white">cancel</v-icon>
<v-btn icon="cancel" size="default" elevation="0" color="black" @click.stop="cancelEditReply">
</v-btn>
</div>
</div>
@ -161,7 +161,7 @@
<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
no-resize class="input-area-text input-plain" :placeholder="$t('message.your_message')" hide-details
background-color="white" v-on:keydown.enter.prevent="
() => {
sendCurrentTextMessage();
@ -170,58 +170,52 @@
</v-col>
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1" v-if="editedEvent">
<v-btn fab small elevation="0" color="black" @click.stop="cancelEditReply">
<v-icon color="white">cancel</v-icon>
<v-btn icon="cancel" elevation="0" color="black" @click.stop="cancelEditReply">
</v-btn>
</v-col>
<v-col v-if="(!currentInput || currentInput.length == 0) && canCreatePoll && !replyToEvent"
class="input-area-button text-center flex-grow-0 flex-shrink-1">
<v-btn icon large color="black" @click="showCreatePollDialog = true">
<v-icon dark>$vuetify.icons.poll</v-icon>
<v-btn icon @click="showCreatePollDialog = true">
<v-icon size="24">$vuetify.icons.poll</v-icon>
</v-btn>
</v-col>
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1"
v-if="!$config.disableMediaSharing && (!currentInput || currentInput.length == 0 || showRecorder)">
<v-btn v-if="canRecordAudio" class="mic-button" ref="mic_button" fab small elevation="0" v-blur
<v-btn icon="mic" :color="showRecorder ? 'black' : 'white'" v-if="canRecordAudio" class="mic-button" ref="mic_button" elevation="0" v-blur
v-longTap:250="[showRecordingUI, startRecording]">
<v-icon :color="showRecorder ? 'white' : 'black'">mic</v-icon>
</v-btn>
<v-btn v-else class="mic-button" ref="mic_button" fab small elevation="0" v-blur
<v-btn icon="mic" :color="showRecorder ? 'black' : 'white'" v-else class="mic-button" ref="mic_button" elevation="0" v-blur
@click.stop="showNoRecordingAvailableDialog = true">
<v-icon :color="showRecorder ? 'white' : 'black'">mic</v-icon>
</v-btn>
</v-col>
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1" v-else-if="currentInput && currentInput.length > 0">
<v-btn fab small elevation="0" color="black" @click.stop="sendCurrentTextMessage"
<v-btn :icon="editedEvent ? 'save' : 'arrow_upward'" size="default" elevation="0" color="black" @click.stop="sendCurrentTextMessage"
:disabled="sendButtonDisabled">
<v-icon color="white">{{ editedEvent ? "save" : "arrow_upward" }}</v-icon>
</v-btn>
</v-col>
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1 input-more-icon">
<v-btn fab small elevation="0" v-blur @click.stop="
isEmojiQuickReaction = false
<v-btn size="small" elevation="0" v-blur @click.stop="
isEmojiQuickReaction = false;
showMoreMessageOperations($event)
">
<v-icon>$vuetify.icons.addReaction</v-icon>
" icon="$vuetify.icons.addReaction">
</v-btn>
</v-col>
<v-col v-if="$config.shortCodeStickers" class="input-area-button text-center flex-grow-0 flex-shrink-1">
<v-btn id="btn-attach" icon large color="black" @click="showStickerPicker"
<v-btn id="btn-stickers" icon="face" @click="showStickerPicker"
:disabled="attachButtonDisabled">
<v-icon large>face</v-icon>
</v-btn>
</v-col>
<v-col v-if="!$config.disableMediaSharing" class="input-area-button text-center flex-grow-0 flex-shrink-1">
<label icon flat ref="attachmentLabel">
<v-btn icon large color="black" @click="showAttachmentPicker"
<v-btn icon @click="showAttachmentPicker"
:disabled="attachButtonDisabled">
<v-icon x-large>add_circle_outline</v-icon>
<v-icon size="36">add_circle_outline</v-icon>
</v-btn>
</label>
</v-col>
@ -236,7 +230,7 @@
accept="image/*,audio/*,video/*,.mp3,.mp4,.wav,.m4a,.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>
<v-dialog v-model="currentFileInputsDialog" class="ma-0 pa-0" :width="$vuetify.display.smAndUp ? '50%' : '85%'" persistent scrollable>
<v-card class="ma-0 pa-0">
<v-card-text v-if="!currentFileInputs.length">
{{ this.$t("message.preparing_to_upload")}}
@ -247,7 +241,7 @@
</v-card-text>
<template v-else>
<v-card-title>
<div v-if="currentSendErrorExceededFile" class="red--text">{{ currentSendErrorExceededFile }}</div>
<div v-if="currentSendErrorExceededFile" class="text-red">{{ currentSendErrorExceededFile }}</div>
<span v-else> {{ $t('message.send_attachements_dialog_title') }} </span>
</v-card-title>
<v-divider></v-divider>
@ -294,15 +288,15 @@
</template>
<v-divider></v-divider>
<v-textarea v-if="showAttachmentCaptionInput" v-model="attachmentCaption" ref="attachmentCaption" color="black" background-color="transparent"
solo full-width auto-grow rows="1" no-resize hide-details :placeholder="$t('file_mode.add_a_message')"></v-textarea>
variant="solo" full-width auto-grow rows="1" no-resize hide-details :placeholder="$t('file_mode.add_a_message')"></v-textarea>
<v-card-actions>
<v-spacer>
<div v-if="currentSendError">{{ currentSendError }}</div>
</v-spacer>
<v-btn color="primary" text @click="cancelSendAttachment" id="btn-attachment-cancel" :disabled="sendingStatus != sendStatuses.SENDING && sendingStatus != sendStatuses.INITIAL">
<v-btn color="primary" variant="text" @click="cancelSendAttachment" id="btn-attachment-cancel" :disabled="sendingStatus != sendStatuses.SENDING && sendingStatus != sendStatuses.INITIAL">
{{ $t("menu.cancel") }}
</v-btn>
<v-btn id="btn-attachment-send" color="primary" text @click="sendAttachment(attachmentCaption)"
<v-btn id="btn-attachment-send" color="primary" variant="text" @click="sendAttachment(attachmentCaption)"
v-if="currentSendShowSendButton" :disabled="currentSendShowSendButton && sendingStatus != sendStatuses.INITIAL">{{ $t("menu.send") }}</v-btn>
</v-card-actions>
</template>
@ -311,13 +305,20 @@
</div>
<MessageOperationsBottomSheet ref="messageOperationsSheet">
<VEmojiPicker ref="emojiPicker" @select="emojiSelected" :i18n="i18nEmoji"/>
<EmojiPicker ref="emojiPicker"
@select="emojiSelected"
:additional-groups="additionalEmojiGroups"
:group-names="emojiGroupNames"
:group-icons="additionalEmojiGroupIcons"
:group-order="['recently_used']"
disable-skin-tones
:static-texts="{ placeholder: $t('emoji.search')}"/>
</MessageOperationsBottomSheet>
<StickerPickerBottomSheet ref="stickerPickerSheet" v-on:selectSticker="sendSticker" />
<!-- Loading indicator -->
<v-container fluid class="loading-indicator" fill-height v-if="!initialLoadDone || loading">
<v-container fluid class="loading-indicator fill-height" v-if="!initialLoadDone || loading">
<v-row align="center" justify="center">
<v-col class="text-center">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
@ -335,24 +336,25 @@
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn id="btn-ok" color="primary" text @click="showNoRecordingAvailableDialog = false">{{
<v-btn id="btn-ok" color="primary" variant="text" @click="showNoRecordingAvailableDialog = false">{{
$t("menu.ok")
}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<CreatePollDialog :show="showCreatePollDialog" @close="showCreatePollDialog = false" />
<CreatePollDialog v-model="showCreatePollDialog" :room="room" />
<UserProfileDialog
:show="showProfileDialog"
v-model="showProfileDialog"
:activeMember="compActiveMember"
:room="room"
@close="showProfileDialog = false"
/>
<!-- PURGE ROOM POPUP -->
<PurgeRoomDialog :show="showPurgeConfirmation" :room="room" @close="showPurgeConfirmation = false" />
<PurgeRoomDialog v-model="showPurgeConfirmation" :room="room" />
<RoomExport :room="room" v-if="downloadingChat" v-on:close="downloadingChat = false" />
<!-- Heart animation -->
<div :class="['heart-wrapper', { 'is-active': heartAnimation }]" :style="hearAnimationPosition">
@ -362,8 +364,8 @@
</template>
<script>
import Vue from "vue";
import { TimelineWindow, EventTimeline } from "matrix-js-sdk";
import { toRaw } from "vue";
import util, { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE, ROOM_TYPE_CHANNEL } from "../plugins/utils";
import MessageOperations from "./messages/MessageOperations.vue";
import ChatHeader from "./ChatHeader";
@ -379,7 +381,7 @@ import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet";
import StickerPickerBottomSheet from "./StickerPickerBottomSheet";
import UserProfileDialog from "./UserProfileDialog.vue"
import BottomSheet from "./BottomSheet.vue";
import ImageResize from "image-resize";
import imageResize from "image-resize";
import CreatePollDialog from "./CreatePollDialog.vue";
import chatMixin, { ROOM_READ_MARKER_EVENT_PLACEHOLDER } from "./chatMixin";
import sendAttachmentsMixin from "./sendAttachmentsMixin";
@ -390,10 +392,14 @@ import roomMembersMixin from "./roomMembersMixin";
import PurgeRoomDialog from "../components/PurgeRoomDialog";
import MessageErrorHandler from "./MessageErrorHandler";
import MessageOperationsChannel from './messages/channel/MessageOperationsChannel.vue';
const sizeOf = require("image-size");
const dataUriToBuffer = require("data-uri-to-buffer");
const prettyBytes = require("pretty-bytes");
import { imageSize } from "image-size";
import prettyBytes from "pretty-bytes";
import RoomExport from "./RoomExport.vue";
import EmojiPicker from 'vue3-emoji-picker';
import 'vue3-emoji-picker/css';
import emitter from 'tiny-emitter/instance';
import { markRaw } from "vue";
import timerIcon from '@/assets/icons/ic_timer.svg';
const READ_RECEIPT_TIMEOUT = 5000; /* How long a message must have been visible before the read marker is updated */
const WINDOW_BUFFER_SIZE = 0.3; /** Relative window height of when we start paginating. Always keep this much loaded before and after our scroll position! */
@ -445,7 +451,9 @@ export default {
PurgeRoomDialog,
WelcomeHeaderChannelUser,
MessageErrorHandler,
MessageOperationsChannel
MessageOperationsChannel,
RoomExport,
EmojiPicker
},
data() {
@ -514,19 +522,16 @@ export default {
opStyle: "",
isEmojiQuickReaction: true,
i18nEmoji: {
search: this.$t("emoji.search"),
categories: {
Activity: this.$t("emoji.categories.activity"),
Flags: this.$t("emoji.categories.flags"),
Foods: this.$t("emoji.categories.foods"),
Frequently: this.$t("emoji.categories.frequently"),
Objects: this.$t("emoji.categories.objects"),
Nature: this.$t("emoji.categories.nature"),
Peoples: this.$t("emoji.categories.peoples"),
Symbols: this.$t("emoji.categories.symbols"),
Places: this.$t("emoji.categories.places")
}
emojiGroupNames: {
"smileys_people": this.$t("emoji.categories.peoples"),
"animals_nature": this.$t("emoji.categories.nature"),
"food_drink": this.$t("emoji.categories.foods"),
"activities": this.$t("emoji.categories.activity"),
"travel_places": this.$t("emoji.categories.places"),
"objects": this.$t("emoji.categories.objects"),
"symbols": this.$t("emoji.categories.symbols"),
"flags": this.$t("emoji.categories.flags"),
"recently_used": this.$t("emoji.categories.frequently"),
},
/**
@ -540,22 +545,13 @@ export default {
top: 0,
left: 0
},
reverseOrder: false
reverseOrder: false,
downloadingChat: false
};
},
filters: {
latestReply(contents) {
const contentArr = contents.split("\n").reverse();
if (contentArr[0] === "") {
contentArr.shift();
}
return (contentArr && contentArr.length > 0) ? contentArr[0].replace(/^> (<.*> )?/g, "") : "";
},
},
mounted() {
this.$root.$on('audio-playback-ended', this.audioPlaybackEnded);
emitter.on('audio-playback-ended', this.audioPlaybackEnded);
const container = this.chatContainer;
if (container) {
this.scrollPosition = new ScrollPosition(container);
@ -565,8 +561,8 @@ export default {
}
},
beforeDestroy() {
this.$root.$off('audio-playback-ended', this.audioPlaybackEnded);
beforeUnmount() {
emitter.off('audio-playback-ended', this.audioPlaybackEnded);
this.$audioPlayer.pause();
this.stopRRTimer();
if (this.retentionTimer) {
@ -575,12 +571,26 @@ export default {
}
},
destroyed() {
unmounted() {
this.$matrix.off("Room.timeline", this.onEvent);
this.$matrix.off("RoomMember.typing", this.onUserTyping);
},
computed: {
additionalEmojiGroups() {
return { 'recently_used': this.recentEmojis }
},
additionalEmojiGroupIcons() {
return { 'recently_used': timerIcon }
},
latestReply() {
const contents = this.replyToEvent ? this.replyToEvent.getContent().body : "";
const contentArr = contents.split("\n").reverse();
if (contentArr[0] === "") {
contentArr.shift();
}
return (contentArr && contentArr.length > 0) ? contentArr[0].replace(/^> (<.*> )?/g, "") : "";
},
heartEmoji() {
return this.$refs.emojiPicker.mapEmojis["Symbols"].find(({ aliases }) => aliases.includes('heart')).data;
},
@ -682,9 +692,6 @@ export default {
canRecordAudio() {
return util.browserCanRecordAudio();
},
debugging() {
return false; //(window.location.host || "").startsWith("localhost");
},
canCreatePoll() {
// We say that if you can redact events, you are allowed to create polls.
const me = this.room && this.room.getMember(this.$matrix.currentUserId);
@ -747,17 +754,27 @@ export default {
let lastDisplayedEvent = undefined;
events = events.flatMap((e) => {
let result = [];
Vue.set(e, "component", this.componentForEvent(e, false));
if (e.isEncrypted()) {
if (e.getClearContent()) {
e.component = this.componentForEvent(e, false);
} else {
this.$matrix.matrixClient.decryptEventIfNeeded(e).then(() => {
e.component = this.componentForEvent(e, false);
});
}
} else {
e.component = this.componentForEvent(e, false);
}
if (e.getId() == this.readMarker && showReadMarker) {
const readMarkerEvent = ROOM_READ_MARKER_EVENT_PLACEHOLDER;
Vue.set(readMarkerEvent, "component", this.componentForEvent(readMarkerEvent, false));
readMarkerEvent["component"] = this.componentForEvent(readMarkerEvent, false);
if (readMarkerEvent.component) {
Vue.set(e, "nextDisplayedEvent", lastDisplayedEvent);
e["nextDisplayedEvent"] = lastDisplayedEvent;
}
result.push(readMarkerEvent);
}
if (e.component) {
Vue.set(e, "nextDisplayedEvent", lastDisplayedEvent);
e["nextDisplayedEvent"] = lastDisplayedEvent;
lastDisplayedEvent = e;
if (e.getSender() !== this.$matrix.currentUserId) {
showReadMarker = true;
@ -792,20 +809,20 @@ export default {
roomWelcomeHeader() {
if (!this.hideRoomWelcomeHeader && this.roomCreatedByUsRecently) {
if (this.roomDisplayType == ROOM_TYPE_CHANNEL) {
return WelcomeHeaderChannel;
return markRaw(WelcomeHeaderChannel);
}
if (this.isDirectRoom) {
return WelcomeHeaderDirectChat;
return markRaw(WelcomeHeaderDirectChat);
}
return WelcomeHeaderRoom;
return markRaw(WelcomeHeaderRoom);
}
return null;
},
messageOperationsComponent() {
if (this.room.displayType == ROOM_TYPE_CHANNEL) {
return MessageOperationsChannel;
return markRaw(MessageOperationsChannel);
}
return MessageOperations;
return markRaw(MessageOperations);
},
chatContainerStyle() {
if (this.$config.chat_backgrounds && this.room && this.roomId) {
@ -998,8 +1015,8 @@ export default {
if (this.room) {
const pinnedEvents = this.$matrix.getPinnedEvents(this.room);
updated.forEach((e) => {
Vue.set(e, "isPinned", pinnedEvents.includes(e.threadParent ? e.threadParent.getId() : e.getId()));
Vue.set(e, "isChannelMessage", (this.room && this.roomDisplayType == ROOM_TYPE_CHANNEL));
e["isPinned"] = pinnedEvents.includes(e.threadParent ? e.threadParent.getId() : e.getId());
e["isChannelMessage"] = (this.room && this.roomDisplayType == ROOM_TYPE_CHANNEL);
});
updated = updated.sort((e1, e2) => {
@ -1072,7 +1089,7 @@ export default {
}
this.reverseOrder = (this.room && this.roomDisplayType == ROOM_TYPE_CHANNEL);
Vue.set(this.room, "displayType", this.roomDisplayType);
this.room["displayType"] = this.roomDisplayType;
// Listen to events
this.$matrix.on("Room.timeline", this.onEvent);
@ -1232,7 +1249,6 @@ export default {
* Triggered when our "long tap" timer hits.
*/
touchTimerElapsed() {
this.updateRecentEmojis();
this.showContextMenu = true;
},
@ -1292,8 +1308,8 @@ export default {
setParentThread(event) {
const parentEvent = this.timelineSet.findEventById(event.threadRootId) || this.room.findEventById(event.threadRootId);
if (parentEvent) {
Vue.set(parentEvent, "isMxThread", true);
Vue.set(event, "parentThread", parentEvent);
parentEvent["isMxThread"] = true;
event["parentThread"] = parentEvent;
} else {
// Try to load from server.
this.$matrix.matrixClient.getEventTimeline(this.timelineSet, event.threadRootId)
@ -1303,8 +1319,8 @@ export default {
if (parentEvent) {
this.setEvents(this.timelineWindow.getEvents());
const fn = () => {
Vue.set(parentEvent, "isMxThread", true);
Vue.set(event, "parentThread", parentEvent);
parentEvent["isMxThread"] = true;
event["parentThread"] = parentEvent;
};
if (this.initialLoadDone) {
const sel = "[eventId=\"" + parentEvent.getId() + "\"]";
@ -1326,7 +1342,7 @@ export default {
setReplyToEvent(event) {
const parentEvent = this.timelineSet.findEventById(event.replyEventId) || this.room.findEventById(event.replyEventId);
if (parentEvent) {
Vue.set(event, "replyEvent", parentEvent);
event["replyEvent"] = parentEvent;
} else {
// Try to load from server.
this.$matrix.matrixClient.getEventTimeline(this.timelineSet, event.replyEventId)
@ -1335,7 +1351,7 @@ export default {
const parentEvent = tl.getEvents().find((e) => e.getId() === event.replyEventId);
if (parentEvent) {
this.setEvents(this.timelineWindow.getEvents());
const fn = () => {Vue.set(event, "replyEvent", parentEvent);};
const fn = () => {event["replyEvent"] = parentEvent;};
if (this.initialLoadDone) {
const sel = "[eventId=\"" + parentEvent.getId() + "\"]";
const element = document.querySelector(sel);
@ -1461,8 +1477,9 @@ export default {
fileObj.actualSize = file.size;
fileObj.actualFile = file
try {
fileObj.dimensions = sizeOf(dataUriToBuffer(evt.target.result));
const buffer = Uint8Array.from(window.atob(evt.target.result.replace(/^data[^,]+,/,'')), v => v.charCodeAt(0));
fileObj.dimensions = imageSize(buffer);
// Need to resize?
const w = fileObj.dimensions.width;
const h = fileObj.dimensions.height;
@ -1470,29 +1487,24 @@ export default {
var aspect = w / h;
var newWidth = parseInt((w > h ? 640 : 640 * aspect).toFixed());
var newHeight = parseInt((w > h ? 640 / aspect : 640).toFixed());
var imageResize = new ImageResize({
imageResize(evt.target.result, {
format: "png",
width: newWidth,
height: newHeight,
outputType: "blob",
});
imageResize
.play(evt.target.result)
})
.then((img) => {
Vue.set(
fileObj,
"scaled",
fileObj["scaled"] =
new File([img], file.name, {
type: img.type,
lastModified: Date.now(),
})
);
Vue.set(fileObj, "useScaled", true);
Vue.set(fileObj, "scaledSize", img.size);
Vue.set(fileObj, "scaledDimensions", {
});
fileObj["useScaled"] = true;
fileObj["scaledSize"] = img.size;
fileObj["scaledDimensions"] = {
width: newWidth,
height: newHeight,
});
};
})
.catch((err) => {
console.error("Resize failed:", err);
@ -1748,7 +1760,7 @@ export default {
setReplyToImage(event) {
util
.getThumbnail(this.$matrix.matrixClient, event, this.$config)
.getThumbnail(this.$matrix.matrixClient, this.$matrix.useAuthedMedia, event, this.$config)
.then((url) => {
this.replyToImg = url;
})
@ -1796,9 +1808,9 @@ export default {
download(event) {
if ((event.isThreadRoot || event.isMxThread) && this.timelineSet) {
const children = this.timelineSet.relations.getAllChildEventsForEvent(event.getId()).filter(e => util.downloadableTypes().includes(e.getContent().msgtype));
children.forEach(child => util.download(this.$matrix.matrixClient, child));
children.forEach(child => util.download(this.$matrix.matrixClient, this.$matrix.useAuthedMedia, child));
} else {
util.download(this.$matrix.matrixClient, event);
util.download(this.$matrix.matrixClient, this.$matrix.useAuthedMedia, event);
}
},
@ -1824,14 +1836,25 @@ export default {
if (this.selectedEvent) {
const event = this.selectedEvent;
this.selectedEvent = null;
this.sendQuickReaction({ reaction: e.data, event: event });
this.sendQuickReaction({ reaction: e.i, event: event });
}
} else {
// When text input emoji picker is clicked
this.currentInput = `${this.currentInput} ${e.data}`;
this.currentInput = `${this.currentInput} ${e.i}`;
this.$refs.messageInput.focus();
}
let emojis = this.recentEmojis;
const existingIdx = emojis.findIndex(emoji => emoji.u == e.u);
if (existingIdx >= 0) {
emojis.splice(existingIdx, 1);
}
emojis.splice(0, 0, markRaw(e));
if (emojis.length > 5) {
emojis.splice(5, emojis.length - 5);
}
this.recentEmojis = emojis;
this.showEmojiPicker = false;
this.$refs.messageOperationsSheet.close();
},
@ -1902,7 +1925,6 @@ export default {
this.showContextMenu = false;
this.$nextTick(() => {
this.selectedEvent = event;
this.updateRecentEmojis();
this.showContextMenuAnchor = e.anchor;
this.showContextMenu = true;
})
@ -2040,20 +2062,6 @@ export default {
});
},
updateRecentEmojis() {
if (this.$refs.emojiPicker) {
this.recentEmojis = this.$refs.emojiPicker.mapEmojis["Frequently"];
if (this.recentEmojis.length < 20) {
let peoples = this.$refs.emojiPicker.mapEmojis["Peoples"];
for (var p of peoples) {
this.recentEmojis.push(p);
}
}
return;
}
this.recentEmojis = [];
},
formatBytes(bytes) {
return prettyBytes(bytes);
},
@ -2128,7 +2136,7 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
.heart-wrapper {
position: fixed;
z-index: -1;

View file

@ -6,8 +6,8 @@
class="chat-header-members text-start ma-0 pa-0"
>
<v-avatar size="48" class="clickable me-2 chat-header-avatar" color="grey" @click.stop="onAvatarClicked">
<v-img v-if="roomAvatar" :src="roomAvatar" />
<span v-else class="white--text headline">{{
<AuthedImage v-if="roomAvatar" :src="roomAvatar" />
<span v-else class="text-white headline">{{
room.name.substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
@ -28,27 +28,25 @@
</div>
</div>
</div>
<div class="num-members">{{ $tc("room.members", memberCount) }}</div>
<div class="num-members">{{ $t("room.members", memberCount) }}</div>
</v-col>
<v-col cols="auto" class="text-end ma-0 pa-0 ms-1">
<v-avatar :class="{ 'avatar-32': true, 'clickable': true, 'popup-open': showProfileInfo }" size="26"
color="#e0e0e0" @click.stop="showProfileInfo = true">
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
<AuthedImage v-if="userAvatar" :src="userAvatar" />
<span v-else class="text-white">{{ userAvatarLetter }}</span>
</v-avatar>
</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"
<v-btn id="btn-purge-room" v-if="userCanPurgeRoom" icon="$vuetify.icons.ic_moderator-delete" class="mx-2 box-shadow-none" theme="light" size="small" color="red"
@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>
<v-icon color="white">$vuetify.icons.ic_member-leave</v-icon>
<v-btn id="btn-leave-room" v-else icon="$vuetify.icons.ic_member-leave" class="mx-2 box-shadow-none" theme="light" size="small" color="red" @click.stop="leaveRoom">
</v-btn>
</v-col>
<v-col cols="auto" class="text-end ma-0 pa-0 ms-1 clickable close-button more-menu-button">
<div :class="{ 'popup-open': showMoreMenu }">
<v-btn class="mx-2 box-shadow-none" fab dark small color="transparent" @click.stop="onShowMoreMenu">
<v-btn class="mx-2 box-shadow-none" icon theme="dark" size="small" color="transparent" @click.stop="onShowMoreMenu">
<v-icon size="15">$vuetify.icons.ic_more</v-icon>
</v-btn>
</div>
@ -56,7 +54,7 @@
</v-row>
<!-- "REALLY LEAVE?" dialog -->
<LeaveRoomDialog :show="showLeaveConfirmation" :room="room" @close="showLeaveConfirmation = false" />
<LeaveRoomDialog v-model="showLeaveConfirmation" :room="room" />
<!-- PROFILE INFO POPUP -->
<ProfileInfoPopup :show="showProfileInfo" @close="showProfileInfo = false" />
@ -65,8 +63,6 @@
<MoreMenuPopup :show="showMoreMenu" :menuItems="moreMenuItems" @close="showMoreMenu = false"
v-on:leave="showLeaveConfirmation = true" />
<RoomExport :room="room" v-if="downloadingChat" v-on:close="downloadingChat = false" />
</v-container>
</template>
@ -75,18 +71,18 @@ import LeaveRoomDialog from "../components/LeaveRoomDialog";
import ProfileInfoPopup from "../components/ProfileInfoPopup";
import MoreMenuPopup from "../components/MoreMenuPopup";
import profileInfoMixin from "../components/profileInfoMixin";
import RoomExport from "../components/RoomExport";
import AuthedImage from "./AuthedImage.vue";
import roomInfoMixin from "./roomInfoMixin";
export default {
name: "ChatHeader",
mixins: [profileInfoMixin, roomInfoMixin],
emits: ['download','view-room-details','header-click'],
components: {
LeaveRoomDialog,
ProfileInfoPopup,
MoreMenuPopup,
RoomExport
AuthedImage
},
data() {
return {
@ -94,7 +90,6 @@ export default {
showLeaveConfirmation: false,
showProfileInfo: false,
showMoreMenu: false,
downloadingChat: false,
showMissedItemsInfo: false,
/** Timer for showing the "missed items" hint */
@ -106,7 +101,7 @@ export default {
this.updateMemberCount();
},
destroyed() {
unmounted() {
this.$matrix.off("Room.timeline", this.onEvent);
},
@ -121,11 +116,11 @@ export default {
notificationsText() {
const invitationCount = this.$matrix.invites.length
if (invitationCount > 0) {
return this.$tc('room.invitations', invitationCount);
return this.$t('room.invitations', invitationCount);
}
const missedMessagesCount = this.$matrix.joinedRooms.reduce((value, r) => ((r.roomId !== this.$matrix.currentRoomId && r.getCanonicalAlias() !== this.$matrix.currentRoomId) ? (value + r.getUnreadNotificationCount("total")) : value), 0);
if (missedMessagesCount > 0) {
return this.$tc('room.unseen_messages', missedMessagesCount);
return this.$t('room.unseen_messages', missedMessagesCount);
}
return "";
},
@ -142,7 +137,7 @@ export default {
if (this.userCanExportChat) {
items.push({
icon: '$vuetify.icons.ic_download', text: this.$t('room_info.download_chat'), handler: () => {
this.downloadingChat = true;
this.$emit("download", { event: this.event });
}
});
}
@ -171,7 +166,9 @@ export default {
40,
40,
"scale",
true
true,
false,
this.$matrix.useAuthedMedia
);
}
}
@ -244,7 +241,7 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
.popup-open {
position: relative;

View file

@ -7,7 +7,7 @@
>
<v-avatar size="48" class="clickable me-2 chat-header-avatar rounded-circle" color="grey" @click.stop="onAvatarClicked">
<v-img v-if="privatePartyAvatar(40)" :src="privatePartyAvatar(40)" />
<span v-else class="white--text headline">{{
<span v-else class="text-white headline">{{
privateParty.name.substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
@ -37,28 +37,26 @@
<v-col v-if="$matrix.joinedRooms.length > 1" cols="auto" class="text-end ma-0 pa-0 ms-1">
<v-avatar :class="{ 'avatar-32': true, 'clickable': true, 'popup-open': showProfileInfo }" size="26"
color="#e0e0e0" @click.stop="showProfileInfo = true">
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
<AuthedImage v-if="userAvatar" :src="userAvatar" />
<span v-else class="text-white">{{ userAvatarLetter }}</span>
</v-avatar>
</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"
<v-btn id="btn-purge-room" v-if="userCanPurgeRoom" icon="$vuetify.icons.ic_moderator-delete" class="mx-2 box-shadow-none" theme="light" size="small" color="red"
@click.stop="$emit('purge')">
<v-icon light>$vuetify.icons.ic_moderator-delete</v-icon>
</v-btn>
<template v-else>
<v-btn v-if="$matrix.joinedRooms.length == 1" id="btn-leave-room" class="box-shadow-none leave-button" color="red" @click.stop="leaveRoom">
<v-icon color="white">$vuetify.icons.ic_member-leave</v-icon>{{ $t('room.leave') }}
<v-btn v-if="$matrix.joinedRooms.length == 1" id="btn-leave-room" class="box-shadow-none leave-button" theme="dark" color="red" @click.stop="leaveRoom">
<v-icon color="white"></v-icon>{{ $t('room.leave') }}
</v-btn>
<v-btn id="btn-leave-room" class="mx-2 box-shadow-none" fab dark small color="red" @click.stop="leaveRoom" v-else>
<v-icon color="white">$vuetify.icons.ic_member-leave</v-icon>
<v-btn v-else id="btn-leave-room" icon="$vuetify.icons.ic_member-leave" class="mx-2 box-shadow-none" theme="dark" size="small" color="red" @click.stop="leaveRoom">
</v-btn>
</template>
</v-col>
<v-col v-if="$matrix.joinedRooms.length > 1" cols="auto" class="text-end ma-0 pa-0 ms-1 clickable close-button more-menu-button">
<div :class="{ 'popup-open': showMoreMenu }">
<v-btn class="mx-2 box-shadow-none" fab dark small color="transparent" @click.stop="onShowMoreMenu">
<v-btn class="mx-2 box-shadow-none" icon theme="dark" size="small" color="transparent" @click.stop="onShowMoreMenu">
<v-icon size="15">$vuetify.icons.ic_more</v-icon>
</v-btn>
</div>
@ -66,7 +64,7 @@
</v-row>
<!-- "REALLY LEAVE?" dialog -->
<LeaveRoomDialog :show="showLeaveConfirmation" :room="room" @close="showLeaveConfirmation = false" />
<LeaveRoomDialog v-model="showLeaveConfirmation" :room="room" />
<!-- PROFILE INFO POPUP -->
<ProfileInfoPopup :show="showProfileInfo" @close="showProfileInfo = false" />
@ -75,8 +73,6 @@
<MoreMenuPopup :show="showMoreMenu" :menuItems="moreMenuItems" @close="showMoreMenu = false"
v-on:leave="showLeaveConfirmation = true" />
<RoomExport :room="room" v-if="downloadingChat" v-on:close="downloadingChat = false" />
</v-container>
</template>
@ -84,8 +80,7 @@
import LeaveRoomDialog from "../components/LeaveRoomDialog";
import ProfileInfoPopup from "../components/ProfileInfoPopup";
import MoreMenuPopup from "../components/MoreMenuPopup";
import RoomExport from "../components/RoomExport";
import AuthedImage from "./AuthedImage.vue";
import ChatHeader from "./ChatHeader.vue";
export default {
@ -95,14 +90,14 @@ export default {
LeaveRoomDialog,
ProfileInfoPopup,
MoreMenuPopup,
RoomExport
AuthedImage
},
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
.popup-open {
position: relative;

View file

@ -1,7 +1,7 @@
<template>
<div>
<v-expand-transition>
<v-card class="ma-3" v-show="locationLink" flat>
<v-card class="ma-3" v-show="locationLink" variant="flat">
<v-container>
<slot/>
<v-row cols="12" class="qr-container ma-3">
@ -28,7 +28,7 @@
<v-btn
id="btn-copy-room-link"
:color="locationLinkCopied ? '#DEE6FF' : 'black'"
depressed
variant="flat"
@click.stop="copyRoomLink"
:class="{'filled-button' : true, 'link-copied-in-place' : locationLinkCopied}"
>{{ $t(`room_info.${locationLinkCopied ? 'link_copied' : i18nCopyLinkKey}`) }}</v-btn

View file

@ -1,7 +1,7 @@
<template>
<div class="create-root fill-height">
<div class="mt-4 d-flex flex-row align-center">
<v-btn id="btn-back" text class="back-button" v-show="$navigation && $navigation.canPop()" @click.stop="goBack">
<v-btn id="btn-back" variant="text" class="back-button" v-show="$navigation && $navigation.canPop()" @click.stop="goBack">
<v-icon>arrow_back</v-icon>
</v-btn>
<YouAre class="flex-grow-1 text-end" v-if="loggedIn" />
@ -62,8 +62,9 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/typography.scss";
@import "@/assets/css/create.scss";
@use "@/assets/css/typography.scss";
@use "@/assets/css/create.scss";
@use "@/assets/css/variables" as *;
.create-title {
margin-bottom: 24 * $chat-text-size;

View file

@ -1,7 +1,7 @@
<template>
<div class="create-root fill-height">
<div class="mt-4 d-flex flex-row align-center">
<v-btn id="btn-back" text class="back-button" v-show="$navigation && $navigation.canPop()" @click.stop="goBack"
<v-btn id="btn-back" variant="text" class="back-button" v-show="$navigation && $navigation.canPop()" @click.stop="goBack"
:disabled="creatingRoom">
<v-icon>arrow_back</v-icon>
</v-btn>
@ -18,13 +18,13 @@
<v-text-field v-model="roomTopic" :label="$t('createchannel.channel_topic')"
v-bind="{ ...roomTopicInputFields }" v-on="{ ...roomTopicInputListeners }"></v-text-field>
<div class="caption-2 text-center mt-2 mb-8">{{ $t('createchannel.channel_topic_label') }}</div>
<div class="error--text" v-if="errorMessage != null">{{ this.errorMessage }}</div>
<div class="text-red" v-if="errorMessage != null">{{ this.errorMessage }}</div>
<interactive-auth ref="interactiveAuth" />
<div class="create-buttons">
<!-- Create button -->
<v-btn :disabled="!formIsValid || creatingRoom" color="#6360F0" depressed @click.stop="handleNext"
<v-btn :disabled="!formIsValid || creatingRoom" color="#6360F0" variant="flat" @click.stop="handleNext"
class="filled-button mt-4">
<div v-if="creatingRoom" class="text-center">
{{ creatingRoomStatus }}
@ -33,7 +33,7 @@
</div>
<span v-else>{{ $t("getlink.next") }}</span>
</v-btn>
<v-btn v-if="!loggedIn" color="black" depressed text @click.stop="goToLoginPage" class="text-button">{{
<v-btn v-if="!loggedIn" color="black" variant="flat" @click.stop="goToLoginPage" class="text-button">{{
$t("menu.login")
}}</v-btn>
</div>
@ -182,6 +182,6 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/typography.scss";
@import "@/assets/css/create.scss";
@use "@/assets/css/typography.scss";
@use "@/assets/css/create.scss";
</style>

View file

@ -1,7 +1,7 @@
<template>
<div class="create-root fill-height">
<div class="mt-4 d-flex flex-row align-center">
<v-btn id="btn-back" text class="back-button" v-show="$navigation && $navigation.canPop()" @click.stop="goBack"
<v-btn id="btn-back" variant="text" class="back-button" v-show="$navigation && $navigation.canPop()" @click.stop="goBack"
:disabled="creatingRoom">
<v-icon>arrow_back</v-icon>
</v-btn>
@ -16,13 +16,13 @@
<v-text-field v-model="roomName" :label="$t('createfiledrop.filedrop_name')"
v-bind="{ ...roomNameInputFields }" v-on="{ ...roomNameInputListeners }"></v-text-field>
<div class="error--text" v-if="errorMessage != null">{{ this.errorMessage }}</div>
<div class="text-red" v-if="errorMessage != null">{{ this.errorMessage }}</div>
<interactive-auth ref="interactiveAuth" />
<div class="create-buttons">
<!-- Create button -->
<v-btn :disabled="!formIsValid || creatingRoom" color="#6360F0" depressed @click.stop="handleNext"
<v-btn :disabled="!formIsValid || creatingRoom" color="#6360F0" variant="flat" @click.stop="handleNext"
class="filled-button mt-4">
<div v-if="creatingRoom" class="text-center">
{{ creatingRoomStatus }}
@ -31,7 +31,7 @@
</div>
<span v-else>{{ $t("getlink.next") }}</span>
</v-btn>
<v-btn v-if="!loggedIn" color="black" depressed text @click.stop="goToLoginPage" class="text-button">{{
<v-btn v-if="!loggedIn" color="black" variant="flat" @click.stop="goToLoginPage" class="text-button">{{
$t("menu.login")
}}</v-btn>
</div>
@ -190,6 +190,6 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/typography.scss";
@import "@/assets/css/create.scss";
@use "@/assets/css/typography.scss";
@use "@/assets/css/create.scss";
</style>

View file

@ -1,16 +1,15 @@
<template>
<v-dialog
<RoundedDialog
v-model="showDialog"
v-show="room"
scrollable
class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '940px' : '95%'"
:width="$vuetify.display.smAndUp ? '940px' : '95%'"
>
<v-card>
<v-card-title>
<v-container fluid>
<div class="room-name no-upper">{{ $t("poll_create.title") }}</div>
<v-btn id="btn-close" text class="header-button-right" @click.stop="showDialog = false">
<v-btn id="btn-close" icon class="header-button-right" @click.stop="showDialog = false">
<v-icon>close</v-icon>
</v-btn>
</v-container>
@ -52,7 +51,7 @@
:class="{ deletable: pollAnswers.length > 1 }"
ref="answerInput"
>
<v-btn v-if="pollAnswers.length > 1" icon @click="removeAnswer(index)"><v-icon>delete</v-icon></v-btn>
<v-btn v-if="pollAnswers.length > 1" color="transparent" icon="delete" @click="removeAnswer(index)"></v-btn>
</InputControl>
</v-col>
</v-row>
@ -60,7 +59,7 @@
<v-row cols="12" no-gutters class="ma-0 pa-0">
<v-col class="ma-0 pa-0">
<div :class="{'add-answer-button':true, 'clickable': true, 'hidden': addingAnswer}" @click.stop="addAnswer">
<img src="@/assets/icons/ic_add.svg" />
<v-icon>$vuetify.icons.ic_add</v-icon>
</div>
</v-col>
</v-row>
@ -68,7 +67,7 @@
<v-col cols="auto">
<v-btn
id="btn-create-poll"
depressed
variant="flat"
block
class="filled-button publish-button"
@click.stop="onCreatePoll()"
@ -98,37 +97,22 @@
</v-container>
</v-card-actions>
</v-card>
</v-dialog>
</RoundedDialog>
</template>
<script>
import roomInfoMixin from "./roomInfoMixin";
import util from "../plugins/utils";
import InputControl from "./InputControl.vue";
import RoomDialogBase from "./RoomDialogBase.vue";
import RoundedDialog from "./RoundedDialog.vue";
export default {
name: "CreatePollDialog",
mixins: [roomInfoMixin],
props: {
show: {
type: Boolean,
default: function() {
return false;
},
},
},
extends: RoomDialogBase,
data() {
return this.defaultData();
},
watch: {
show: {
handler(newVal, ignoredOldVal) {
this.showDialog = newVal;
},
},
showDialog() {
if (!this.showDialog) {
this.$emit("close");
} else {
methods: {
onOpenDialog() {
// Reset values
let defaults = this.defaultData();
this.isValid = defaults.isValid;
@ -142,13 +126,9 @@ export default {
if (this.$refs.pollcreateform) {
this.$refs.pollcreateform.resetValidation();
}
}
},
},
methods: {
defaultData() {
return {
showDialog: false,
isValid: true,
isCreating: false,
status: String.fromCharCode(160),
@ -213,11 +193,11 @@ export default {
return this.pollQuestion && this.pollAnswers.length > 0 && this.pollAnswers.every(a => a.text);
}
},
components: { InputControl },
components: { InputControl, RoundedDialog },
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@import "@/assets/css/components/poll.scss";
@use "@/assets/css/chat.scss" as *;
@use "@/assets/css/components/poll.scss";
</style>

View file

@ -1,7 +1,7 @@
<template>
<div class="create-root fill-height">
<div class="mt-4 d-flex flex-row align-center">
<v-btn id="btn-back" text class="back-button" v-show="$navigation && $navigation.canPop()" @click.stop="goBack"
<v-btn id="btn-back" variant="text" class="back-button" v-show="$navigation && $navigation.canPop()" @click.stop="goBack"
:disabled="creatingRoom">
<v-icon>arrow_back</v-icon>
</v-btn>
@ -18,7 +18,7 @@
v-on="{ ...roomTopicInputListeners }"></v-text-field>
<!-- Message History (public room) -->
<v-card class="ma-0 mt-10" flat>
<v-card class="ma-0 mt-10" variant="flat">
<v-card-text class="with-right-label">
<div>
<div class="paragraph-bold">{{ $t('room_info.message_history') }}</div>
@ -33,7 +33,7 @@
<hr color="#ededed" />
<!-- Limit History (message retention) -->
<v-card class="ma-0 mt-2" flat>
<v-card class="ma-0 mt-2" variant="flat">
<v-card-text class="with-right-label">
<div>
<div class="paragraph-bold">{{ $t('room_info.message_retention') }}</div>
@ -48,10 +48,10 @@
</v-card>
<div class="error--text" v-if="errorMessage != null">{{ this.errorMessage }}</div>
<div class="text-red" v-if="errorMessage != null">{{ this.errorMessage }}</div>
<!-- Create button -->
<v-btn :disabled="!formIsValid || creatingRoom" color="#6360F0" depressed @click.stop="onCreate"
<v-btn :disabled="!formIsValid || creatingRoom" color="#6360F0" variant="flat" @click.stop="onCreate"
class="filled-button mt-4">
<div v-if="creatingRoom && !enterRoomDialog" class="text-center">
{{ creatingRoomStatus }}
@ -70,24 +70,45 @@
<input id="user-avatar-picker" ref="useravatar" type="file" name="user-avatar"
@change="handlePickedUserAvatar($event)" accept="image/*" class="d-none" />
<v-dialog v-model="enterRoomDialog" :width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'">
<v-dialog v-model="enterRoomDialog" :width="$vuetify.display.smAndUp ? '50%' : '90%'">
<v-card>
<v-container v-if="canEditProfile" class="pa-10">
<v-row class="align-center">
<v-col class="py-0">
<div class="text-start font-weight-bold">{{ $t("join.choose_name") }}</div>
<v-select ref="avatar" :items="availableAvatars" cache-items outlined dense @change="selectAvatar"
:value="selectedProfile">
<v-select ref="avatar"
:items="availableAvatars"
variant="outlined"
density="compact"
@update:modelValue="selectAvatar"
:modelValue="selectedProfile"
item-props
item-value="id"
return-object
single-line
class="avatar-select-control"
>
<template v-slot:selection>
<v-text-field background-color="transparent" solo flat hide-details
@click.native.stop="(event) => event.target.focus()" @focus="$event.target.select()"
<v-text-field
background-color="transparent"
variant="solo"
flat
density="compact"
hide-details
@keydown="$event.stopPropagation()"
@keypress="$event.stopPropagation()"
@keyup="$event.stopPropagation()"
@click.stop="(event) => event.target.focus()" @focus="$event.target.select()"
v-model="selectedProfile.name"></v-text-field>
</template>
<template v-slot:item="data">
<v-avatar size="32">
<v-img :src="data.item.image" />
</v-avatar>
<div class="ms-2">{{ data.item.name }}</div>
<template v-slot:item="{ props }">
<v-list-item v-bind="props">
<template v-slot:prepend>
<v-avatar size="32">
<v-img :src="props.image" />
</v-avatar>
</template>
</v-list-item>
</template>
</v-select>
</v-col>
@ -99,24 +120,24 @@
</v-row>
<v-row class="mt-0">
<v-col class="py-0">
<v-checkbox id="chk-remember-me" class="mt-0" v-model="rememberMe" @change="onRememberMe"
<v-checkbox id="chk-remember-me" class="mt-0" v-model="rememberMe"
:label="$t('join.remember_me')" />
</v-col>
</v-row>
<v-row class="mt-0">
<v-col class="py-0">
<v-checkbox id="chk-accept-ua" class="mt-0" v-model="acceptUA">
<template slot="label">
<i18n path="join.accept_ua" tag="span">
<template v-slot:label>
<i18n-t keypath="join.accept_ua" tag="span">
<template v-slot:agreement>
<a href="./ua.html" target="_blank" @click.stop>{{ $t("join.ua") }}</a>
</template>
</i18n>
</i18n-t>
</template>
</v-checkbox>
</v-col>
</v-row>
<v-btn color="black" depressed class="filled-button" @click.stop="onEnterRoom"
<v-btn color="black" variant="flat" class="filled-button" @click.stop="onEnterRoom"
:disabled="!acceptUA || !selectedProfile.name">
{{ $t("join.enter_room") }}
</v-btn>
@ -124,8 +145,10 @@
</v-card>
</v-dialog>
<MessageRetentionDialog :initialValue="limitHistoryMilliseconds" :show="selectRetentionDialog"
v-on:close="selectRetentionDialog = false" v-on:message-retention-update="onUpdateHistoryLimit" />
<MessageRetentionDialog
v-model="selectRetentionDialog"
:initialValue="limitHistoryMilliseconds"
v-on:message-retention-update="onUpdateHistoryLimit" />
</div>
</template>
@ -149,7 +172,8 @@ export default {
selectedProfile: {
id: "",
image: "",
name: ""
name: "",
title: "",
},
enterRoomDialog: false,
selectRetentionDialog: false,
@ -333,6 +357,7 @@ export default {
},
selectAvatar(value) {
console.log("SELECT AVATAR", value);
this.selectedProfile = Object.assign({}, value); // Make a copy, so editing does not destroy data
},
@ -369,5 +394,9 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/create.scss";
@use "@/assets/css/create.scss";
.avatar-select-control .v-select__selection {
flex-basis: 100%;
}
</style>

View file

@ -1,27 +1,26 @@
<template>
<v-list dense @click.native.stop="nullEvent">
<v-list-item-group color="primary">
<v-list density="compact" @click.stop="nullEvent" color="primary">
<v-list-item
v-for="device in devices"
:key="device.deviceId"
:value="device.deviceId"
>
<template v-slot:default="{ active }">
<v-list-item-content>
<template v-slot:default>
<v-list-item-title>{{ displayName(device) }}</v-list-item-title>
<v-list-item-subtitle>{{
verificationStatus(device)
}}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action v-if="active">
</template>
<template v-slot:append="{ isActive }">
<v-list-item-action v-if="isActive">
<v-btn icon>
<v-icon
:color="
device.isBlocked()
device.isBlocked
? 'red'
: device.isVerified()
: device.isVerified
? 'green'
: 'grey lighten-1'
: 'grey-lighten-1'
"
>verified_user</v-icon
>
@ -29,11 +28,12 @@
</v-list-item-action>
</template>
</v-list-item>
</v-list-item-group>
</v-list>
</template>
<script>
import { DeviceVerification } from 'matrix-js-sdk';
export default {
name: "DeviceList",
props: {
@ -55,7 +55,7 @@ export default {
handler(member, ignoredOldVal) {
this.updateDevices();
if (member) {
this.$matrix.matrixClient.downloadKeys([member.userId]).then(() => {
this.$matrix.matrixClient.downloadKeysForUsers([member.userId]).then(() => {
this.updateDevices();
});
}
@ -68,23 +68,36 @@ export default {
this.devices = [];
return;
}
this.devices = this.$matrix.matrixClient.getStoredDevicesForUser(
this.member.userId
);
this.$matrix.matrixClient.getCrypto().getUserDeviceInfo(
[this.member.userId], true
).then(deviceMap => {
if (deviceMap && deviceMap.get(this.member.userId)) {
const userDevices = deviceMap.get(this.member.userId);
const devices = [...userDevices.keys()].map(k => {
return userDevices.get(k);
}).map(d => ({
deviceId: d.deviceId,
displayName: d.displayName,
isBlocked: d.verified == DeviceVerification.Blocked,
isVerified: d.verified == DeviceVerification.Verified
}));
this.devices = devices;
}
})
},
displayName(device) {
var name = device.deviceId;
if (device.getDisplayName()) {
name += " - " + device.getDisplayName();
if (device.displayName) {
name += " - " + device.displayName;
}
return name;
},
verificationStatus(device) {
if (device.isBlocked()) {
if (device.isBlocked) {
return this.$t('device_list.blocked');
} else if (device.isVerified()) {
} else if (device.isVerified) {
return this.$t('device_list.verified');
} else {
return this.$t('device_list.not_verified');

View file

@ -7,20 +7,20 @@
<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 :rules="[(v) => !!v || $t('login.username_required')]" :error="userErrorMessage != null"
variant="solo" :rules="[(v) => !!v || $t('login.username_required')]" :error="userErrorMessage != null"
:error-messages="userErrorMessage" required v-on:keyup.enter="onUsernameEnter"
v-on:keydown="hasError = false"></v-text-field>
<!-- <div class="error--text" v-if="loadingLoginFlows">Loading login flows...</div> -->
<!-- <div class="text-red" v-if="loadingLoginFlows">Loading login flows...</div> -->
<div class="error--text" v-if="hasError">{{ this.message }}</div>
<div class="text-red" v-if="hasError">{{ this.message }}</div>
<interactive-auth ref="interactiveAuth" />
<div class="create-buttons">
<v-btn id="btn-login" :disabled="!isValid || loading" color="primary" depressed block
<v-btn id="btn-login" :disabled="!isValid || loading" color="primary" variant="flat" block
@click.stop="handleLogin" :loading="loading" class="filled-button mt-4">{{ $t("getlink.next") }}</v-btn>
<v-btn color="black" depressed text block @click.stop="goToLoginPage" class="text-button">{{
<v-btn color="black" variant="flat" block @click.stop="goToLoginPage" class="text-button">{{
$t("menu.login")
}}</v-btn>
</div>
@ -36,14 +36,14 @@
<template v-slot:share>
<div v-if="shareSupported" class="clickable create-share" @click="shareLink">
<div>{{ $t("getlink.share_qr") }}</div>
<v-img src="@/assets/icons/ic_share.svg" />
<v-icon>$vuetify.icons.ic_share</v-icon>
</div>
</template>
</copy-link>
<div class="create-buttons">
<v-btn color="black" style="color:black;background-color:white !important;" depressed @click.stop="goHome"
<v-btn color="black" style="color:black;background-color:white !important;" variant="flat" @click.stop="goHome"
class="outlined-button">{{ $t("getlink.continue") }}</v-btn>
<v-btn color="black" depressed text block @click.stop="getDifferentLink" class="text-button">{{
<v-btn color="black" variant="flat" block @click.stop="getDifferentLink" class="text-button">{{
$t("getlink.different_link") }}</v-btn>
</div>
</div>
@ -248,6 +248,6 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/typography.scss";
@import "@/assets/css/create.scss";
@use "@/assets/css/typography.scss";
@use "@/assets/css/create.scss";
</style>

View file

@ -12,7 +12,7 @@
</v-row>
</v-container>
<v-card class="members ma-3" flat>
<v-card class="members ma-3" variant="flat">
<v-card-title class="h2">{{ $t("room.room_list_rooms") }}</v-card-title>
<v-card-text class="pa-0">
<RoomList
@ -26,9 +26,8 @@
<!-- Loading indicator -->
<v-container
fluid
fill-height
v-if="loading"
class="loading-indicator"
class="loading-indicator transparent fill-height"
>
<v-row align="center" justify="center">
<v-col class="text-center">
@ -43,7 +42,7 @@
</template>
<script>
import RoomList from "../components/RoomList";
import RoomList from "../components/RoomList.vue";
import YouAre from "../components/YouAre.vue";
import logoMixin from "../components/logoMixin";
export default {

View file

@ -23,6 +23,7 @@ export default {
prop: "modelValue",
event: "update:modelValue",
},
emits: ['update:modelValue'],
props: {
label: {
type: String,
@ -64,7 +65,7 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
.input-field {
width: 100%;

View file

@ -10,8 +10,8 @@
<v-col sm="8" align="center">
<div class="text-start font-weight-light">{{ $t("login.email") }}</div>
<v-text-field v-model="email" color="black" :rules="emailRules" type="email" maxlength="200"
background-color="white" v-on:keyup.enter="onEmailEntered(email)" autofocus solo></v-text-field>
<v-btn :disabled="!emailIsValid" color="black" depressed class="filled-button"
background-color="white" v-on:keyup.enter="onEmailEntered(email)" autofocus variant="solo"></v-text-field>
<v-btn :disabled="!emailIsValid" color="black" variant="flat" class="filled-button"
@click.stop="onEmailEntered(email)">
{{ $t("login.send_verification") }}
</v-btn>
@ -39,7 +39,7 @@
</v-row>
<v-row cols="12" align="center" justify="center">
<v-col sm="8" align="center">
<v-btn color="black" :disabled="!this.allPoliciesAccepted" depressed class="filled-button"
<v-btn color="black" :disabled="!this.allPoliciesAccepted" variant="flat" class="filled-button"
@click.stop="onPoliciesAccepted()">
{{ $t("login.accept_terms") }}
</v-btn>
@ -55,7 +55,7 @@
<v-progress-circular style="display: inline-flex" indeterminate color="primary"
size="20"></v-progress-circular>
</div>
<v-btn color="black" depressed class="filled-button" @click.stop="onEmailResend()">
<v-btn color="black" variant="flat" class="filled-button" @click.stop="onEmailResend()">
{{ $t("login.resend_verification") }}
</v-btn>
</v-col>
@ -67,8 +67,8 @@
<v-col sm="8" align="center">
<div class="text-start font-weight-light">{{ $t("login.registration_token") }}</div>
<v-text-field v-model="token" color="black" :rules="tokenRules" type="text" maxlength="64"
background-color="white" v-on:keyup.enter="onTokenEntered(token)" autofocus solo></v-text-field>
<v-btn :disabled="!tokenIsValid" color="black" depressed class="filled-button"
background-color="white" v-on:keyup.enter="onTokenEntered(token)" autofocus variant="solo"></v-text-field>
<v-btn :disabled="!tokenIsValid" color="black" variant="flat" class="filled-button"
@click.stop="onTokenEntered(token)">
{{ $t("login.send_token") }}
</v-btn>
@ -349,5 +349,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -5,7 +5,7 @@
<v-btn
id="btn-invite-done"
:loading="loading"
text
variant="text"
class="header-button-right"
@click.stop="done"
>
@ -16,7 +16,7 @@
<div class="flex-grow-0 flex-shrink-0 chip-group-wrapper">
<div class="h4">{{$t('invite.send_invites_to')}}</div>
<div>{{ status }}</div>
<v-chip-group active-class="primary--text" column>
<v-chip-group selected-class="primary--text" column>
<v-chip
v-for="member in selectedMembers"
:key="member.userId"
@ -29,34 +29,32 @@
</div>
<div class="flex-grow-1 flex-shrink-1">
<v-list class="member ma-2">
<v-list-item-group multiple v-model="selectedMembers">
<v-list class="member ma-2" v-model:selected="selectedMembers">
<v-list-item
v-for="member in $matrix.getAllFriends()"
:key="member.userId"
:value="member"
>
<template v-slot:default="{ active }">
<v-list-item-avatar color="grey">
<img v-if="memberAvatar(member)" :src="memberAvatar(member)" />
<span v-else class="white--text headline">{{
<template v-slot:prepend>
<v-avatar color="grey">
<AuthedImage v-if="memberAvatar(member)" :src="memberAvatar(member)" />
<span v-else class="text-white headline">{{
member.name.substring(0, 1).toUpperCase()
}}</span>
</v-list-item-avatar>
<v-list-item-content>
</v-avatar>
</template>
<template v-slot:default>
<v-list-item-title>{{ memberName(member) }}</v-list-item-title>
<v-list-item-subtitle
v-text="member.userId"
></v-list-item-subtitle>
</v-list-item-content>
<v-list-item-subtitle>{{ member.userId }}</v-list-item-subtitle>
</template>
<template v-slot:append="{ isActive }">
<v-list-item-action>
<v-btn icon v-if="active">
<v-icon color="grey lighten-1">check</v-icon>
<v-btn icon v-if="isActive">
<v-icon color="grey-lighten-1">check</v-icon>
</v-btn>
</v-list-item-action>
</template>
</v-list-item>
</v-list-item-group>
</v-list>
</div>
</div>
@ -64,9 +62,11 @@
<script>
import util from "../plugins/utils";
import AuthedImage from "./AuthedImage.vue";
export default {
name: "Invite",
comments: { AuthedImage },
data() {
return {
status: "",
@ -155,7 +155,9 @@ export default {
40,
40,
"scale",
true
true,
false,
this.$matrix.useAuthedMedia
);
}
return null;
@ -165,7 +167,7 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
.invite {
.chip-group-wrapper {
min-height: 100px;

View file

@ -4,8 +4,8 @@
<div>
<div class="text-center">
<v-avatar class="join-avatar">
<v-img v-if="roomAvatar" :src="roomAvatar" />
<span v-else class="white--text headline">
<AuthedImage v-if="roomAvatar" :src="roomAvatar" />
<span v-else class="text-white headline">
{{ roomName.substring(0, 1).toUpperCase() }}
</span>
</v-avatar>
@ -22,22 +22,42 @@
<v-row v-if="canEditProfile">
<v-col cols="10" sm="7" class="py-0">
<v-select ref="avatar" :items="availableAvatars" cache-items outlined dense @change="selectAvatar"
:value="selectedProfile" single-line autofocus>
<template v-slot:selection>
<v-text-field background-color="transparent" solo flat hide-details
@click.native.stop="(event) => event.target.focus()" @focus="$event.target.select()"
v-model="selectedProfile.name"></v-text-field>
</template>
<template v-slot:item="data">
<v-avatar size="32">
<v-img :src="data.item.image" />
</v-avatar>
<div class="ms-2">{{ data.item.name }}</div>
</template>
</v-select>
<v-checkbox id="chk-remember-me" class="mt-0" v-model="rememberMe" :label="$t('join.remember_me')"
@change="onRememberMe" />
<v-select ref="avatar"
:items="availableAvatars"
variant="outlined"
density="compact"
@update:modelValue="selectAvatar"
:modelValue="selectedProfile"
item-props
item-value="id"
return-object
single-line
class="avatar-select-control"
>
<template v-slot:selection>
<v-text-field
background-color="transparent"
variant="solo"
flat
density="compact"
hide-details
@keydown="$event.stopPropagation()"
@keypress="$event.stopPropagation()"
@keyup="$event.stopPropagation()"
@click.stop="(event) => event.target.focus()" @focus="$event.target.select()"
v-model="selectedProfile.name"></v-text-field>
</template>
<template v-slot:item="{ props }">
<v-list-item v-bind="props">
<template v-slot:prepend>
<v-avatar size="32">
<v-img :src="props.image" />
</v-avatar>
</template>
</v-list-item>
</template>
</v-select>
<v-checkbox id="chk-remember-me" class="mt-0" v-model="rememberMe" :label="$t('join.remember_me')" />
</v-col>
<v-col cols="2" sm="5" class="py-0">
<v-avatar @click="showAvatarPicker">
@ -50,8 +70,8 @@
{{ $t("join.joining_as") }}
<div class="d-inline-block">
<v-avatar color="#e0e0e0">
<v-img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text headline">{{ userAvatarLetter }}</span>
<AuthedImage v-if="userAvatar" :src="userAvatar" />
<span v-else class="text-white headline">{{ userAvatarLetter }}</span>
</v-avatar>
</div>
{{ userDisplayName }}
@ -59,22 +79,22 @@
</v-row>
<v-checkbox id="chk-accept-ua" class="mt-0" v-model="acceptUA">
<template slot="label">
<i18n path="join.accept_ua" tag="span">
<template v-slot:label>
<i18n-t keypath="join.accept_ua" tag="span">
<template v-slot:agreement>
<a href="./ua.html" target="_blank" @click.stop>{{ $t("join.ua") }}</a>
</template>
</i18n>
</i18n-t>
</template>
</v-checkbox>
<interactive-auth ref="interactiveAuth" />
<v-btn id="btn-join" class="btn-dark" :disabled="!acceptUA || (room && room.selfMembership == 'ban')" large
<v-btn id="btn-join" class="btn-dark" :disabled="!acceptUA || (room && room.selfMembership == 'ban')" size="large"
@click.stop="handleJoin" :loading="loading" v-if="!currentUser">{{
roomId && roomId.startsWith("@") ? $t("join.enter_room_user") : $t("join.enter_room")
}}</v-btn>
<v-btn id="btn-join" class="btn-dark" :disabled="!acceptUA || (room && room.selfMembership == 'ban')" large
<v-btn id="btn-join" class="btn-dark" :disabled="!acceptUA || (room && room.selfMembership == 'ban')" size="large"
block @click.stop="handleJoin" :loading="loading" v-else>{{
roomId && roomId.startsWith("@") ? $t("join.join_user") : $t("join.join")
}}</v-btn>
@ -101,7 +121,7 @@
<v-icon>language</v-icon><v-icon>more_horiz</v-icon>
</v-btn>
</v-col>
<SelectLanguageDialog v-model="showSelectLanguageDialog" v-on:close="showSelectLanguageDialog = false" />
<SelectLanguageDialog v-model="showSelectLanguageDialog" />
</v-row>
</div>
@ -117,7 +137,7 @@
</div>
<!-- Loading indicator -->
<v-container v-else fluid fill-height class="loading-indicator transparent">
<v-container v-else fluid class="loading-indicator transparent fill-height">
<v-row align="center" justify="center">
<v-col class="text-center">
<interactive-auth ref="interactiveAuth" v-if="waitingForRoomCreation" />
@ -134,13 +154,15 @@ import LanguageMixin from "./languageMixin";
import rememberMeMixin from "./rememberMeMixin";
import logoMixin from "./logoMixin";
import SelectLanguageDialog from "./SelectLanguageDialog.vue";
import AuthedImage from "./AuthedImage.vue";
export default {
name: "Join",
mixins: [LanguageMixin, rememberMeMixin, logoMixin],
components: {
SelectLanguageDialog,
InteractiveAuth
InteractiveAuth,
AuthedImage
},
data() {
return {
@ -194,7 +216,7 @@ export default {
if (!this.$matrix.userAvatar) {
return null;
}
return this.$matrix.matrixClient.mxcUrlToHttp(this.$matrix.userAvatar, 80, 80, "scale", true);
return this.$matrix.matrixClient.mxcUrlToHttp(this.$matrix.userAvatar, 80, 80, "scale", true, undefined, this.$matrix.useAuthedMedia);
},
userAvatarLetter() {
@ -444,12 +466,17 @@ export default {
// action and set up conversion tracking triggered by Room Joined (once per visit).
this.$analytics.event("Invitations", "Join Page Shown");
},
destroyed() {
unmounted() {
this.$matrix.off("Room.myMembership", this.onMyMembership);
},
};
</script>
<style lang="scss">
@import "@/assets/css/join.scss";
@use "@/assets/css/join.scss";
.avatar-select-control .v-select__selection {
flex-basis: 100%;
}
</style>

View file

@ -1,9 +1,8 @@
<template>
<v-dialog
v-model="showDialog"
v-show="room"
class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'"
:width="$vuetify.display.smAndUp ? '688px' : '95%'"
>
<div class="dialog-content text-center">
<template v-if="roomJoinRule == 'public'">
@ -17,7 +16,7 @@
v-if="$matrix.currentUser.is_guest && lastRoom"
class="dialog-text"
>
<i18n path="leave.text_public_lastroom" tag="p">
<i18n-t keypath="leave.text_public_lastroom" tag="p">
<template v-slot:user>
<span>{{ $matrix.currentUserDisplayName }}</span>
</template>
@ -26,7 +25,7 @@
$t("leave.create_account")
}}</a>
</template>
</i18n>
</i18n-t>
</div>
<div v-else class="dialog-text">{{ $t("leave.text_public") }}</div>
</template>
@ -44,8 +43,7 @@
<v-col cols="6">
<v-btn
id="btn-back"
depressed
text
variant="flat"
block
class="text-button"
@click="showDialog = false"
@ -56,7 +54,7 @@
<v-btn
id="btn-leave"
color="red"
depressed
variant="flat"
block
class="filled-button"
@click.stop="onLeaveRoom()"
@ -69,41 +67,21 @@
</v-dialog>
</template>
<script>
import RoomDialogBase from "./RoomDialogBase.vue";
import roomInfoMixin from "./roomInfoMixin";
export default {
name: "LeaveRoomDialog",
mixins: [roomInfoMixin],
props: {
show: {
type: Boolean,
default: function () {
return false;
},
},
},
extends: RoomDialogBase,
data() {
return {
showDialog: false,
lastRoom: false,
};
},
watch: {
show: {
handler(newVal, ignoredOldVal) {
this.showDialog = newVal;
},
},
showDialog() {
if (!this.showDialog) {
this.$emit("close");
} else {
this.lastRoom = this.onlyJoinedToThisRoom();
}
},
},
methods: {
onOpenDialog() {
this.lastRoom = this.onlyJoinedToThisRoom();
},
onlyJoinedToThisRoom() {
const joinedRooms = this.$matrix.joinedRooms;
if (
@ -136,5 +114,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -16,7 +16,7 @@
<div class="room-name no-upper">{{ $t("login.title") }}</div>
</v-col>
<v-col class="text-end">
<v-btn id="btn-close" text v-if="showCloseButton" @click.stop="$navigation.pop">
<v-btn id="btn-close" variant="text" v-if="showCloseButton" @click.stop="$navigation.pop">
<v-icon>close</v-icon>
</v-btn>
</v-col>
@ -32,7 +32,7 @@
:label="$t('login.username')"
color="black"
background-color="white"
solo
variant="solo"
:rules="[(v) => !!v || $t('login.username_required')]"
:error="userErrorMessage != null"
:error-messages="userErrorMessage"
@ -42,7 +42,7 @@
v-on:blur="onUsernameBlur"
></v-text-field>
<div class="error--text" v-if="loadingLoginFlows">Loading login flows...</div>
<div class="text-red" v-if="loadingLoginFlows">Loading login flows...</div>
<v-text-field
v-show="showPasswordField"
@ -52,7 +52,7 @@
:label="$t('login.password')"
color="black"
background-color="#f5f5f5"
filled
variant="filled"
type="password"
:rules="[(v) => !!v || $t('login.password_required')]"
:error="passErrorMessage != null"
@ -67,19 +67,18 @@
}
"
></v-text-field>
<div class="error--text" v-if="hasError">{{ this.message }}</div>
<div class="text-red" v-if="hasError">{{ this.message }}</div>
<v-checkbox
id="chk-remember-me"
class="mt-0"
v-model="rememberMe"
@change="onRememberMe"
:label="$t('join.remember_me')"
/>
<v-btn
id="btn-login"
:disabled="!isValid || loading || loadingLoginFlows"
color="black"
depressed
variant="flat"
block
@click.stop="handleLogin"
:loading="loading"
@ -90,7 +89,7 @@
<v-btn
id="btn-create-room"
color="primary"
depressed
variant="flat"
block
@click.stop="handleCreateRoom"
class="filled-button mt-2"
@ -269,5 +268,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/login.scss";
@use "@/assets/css/login.scss";
</style>

View file

@ -1,37 +1,17 @@
<template>
<v-dialog
:value="showLogoutPopup"
class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'"
@click:outside="$emit('onOutsideLogoutPopupClicked')"
>
<v-dialog :modelValue="showLogoutPopup" class="ma-0 pa-0" :width="$vuetify.display.smAndUp ? '688px' : '95%'"
@click:outside="$emit('onOutsideLogoutPopupClicked')">
<div class="dialog-content text-center">
<template>
<h2 class="dialog-title">
{{ $t("logout.confirm_text")}}
</h2>
</template>
<h2 class="dialog-title">{{ $t("logout.confirm_text") }}</h2>
<v-container fluid>
<v-row cols="12">
<v-col cols="6">
<v-btn
depressed
text
block
class="text-button"
@click.stop="$emit('onCancelLogoutClicked')"
>{{ $t("menu.cancel") }}</v-btn
>
<v-btn variant="flat" block class="text-button" @click.stop="$emit('onCancelLogoutClicked')">{{
$t("menu.cancel") }}</v-btn>
</v-col>
<v-col cols="6" align="center">
<v-btn
color="red"
depressed
block
class="filled-button"
@click.stop="logout"
>{{ $t("menu.logout") }}</v-btn
>
<v-btn color="red" variant="flat" block class="filled-button" @click.stop="logout">{{ $t("menu.logout")
}}</v-btn>
</v-col>
</v-row>
</v-container>
@ -47,8 +27,8 @@ export default {
props: {
showLogoutPopup: {
type: Boolean,
default: false
}
}
default: false,
},
},
};
</script>

View file

@ -20,6 +20,7 @@ export default {
},
errorCaptured(err, ignoredvm, ignoredinfo) {
this.err = err;
console.error("IGNORE", err, ignoredvm, ignoredinfo);
return false;
}
};

View file

@ -16,14 +16,12 @@
>
<slot></slot>
<v-btn
fab
x-small
icon="cancel"
size="small"
elevation="0"
color="black"
@click.stop="backgroundClick"
class="bottom-sheet-close"
>
<v-icon color="white" >cancel</v-icon>
</v-btn>
</SwipeableBottomSheet>
</div>
@ -72,7 +70,10 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
@use "@/assets/css/variables" as *;
@use "vuetify/settings" as *;
@use "sass:map";
/* Default implementation only dims background when fully open,
so we use our own flag (data-closed) here to that we can
@ -91,8 +92,8 @@ export default {
.message-operations-bottom-sheet {
.bottom-sheet-close {
position: absolute;
right: 0;
top: 0;
right: 4px;
top: 4px;
}
.transition-bg {
@ -103,7 +104,7 @@ export default {
right: 0;
background-color: rgba(0, 0, 0, 0.15);
@media #{map-get($display-breakpoints, 'sm-and-down')} {
@media #{map.get($display-breakpoints, 'sm-and-down')} {
z-index: 10;
}
@ -113,13 +114,15 @@ export default {
}
.card {
padding: 0px !important;
display: flex;
flex-direction: column;
&[data-state="half"] {
top: 100px !important;
left: 50%;
transform: translate(-50%, 0);
@media #{map-get($display-breakpoints, 'lg-and-up')} {
@media #{map.get($display-breakpoints, 'lg-and-up')} {
width: $dialog-desktop-width;
}
}

View file

@ -1,23 +1,24 @@
<template>
<v-dialog v-model="showDialog" v-show="room" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'">
<v-dialog
v-model="showDialog"
class="ma-0 pa-0"
:width="$vuetify.display.smAndUp ? '688px' : '95%'">
<div class="dialog-content text-center">
<template>
<h2>{{ $t("room_info.message_retention") }}</h2>
</template>
<h2 class="dialog-title">{{ $t("room_info.message_retention") }}</h2>
<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 active-class="radio-active" class="fluid-radio" v-for="p in retentionPeriods"
:key="p.text" :label="p.text" :value="p.value" color="primary" />
</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"
<v-btn color="primary" :disabled="!isValid" variant="flat" block class="filled-button my-0"
@click.stop="setRetention()">{{
$t("menu.done") }}</v-btn>
</v-col>
@ -27,18 +28,12 @@
</v-dialog>
</template>
<script>
import roomInfoMixin from "./roomInfoMixin";
import RoomDialogBase from "./RoomDialogBase.vue";
export default {
name: "MessageRetentionDialog",
mixins: [roomInfoMixin],
extends: RoomDialogBase,
props: {
show: {
type: Boolean,
default: function () {
return false;
},
},
initialValue: {
type: Number,
default: function () {
@ -48,32 +43,23 @@ export default {
},
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: {
shouldShow() {
return this.modelValue ? true : false;
},
onOpenDialog() {
// Showing, reset
this.setInitialValue();
},
setInitialValue() {
this.isValid = true;
if (this.room) {
@ -108,7 +94,8 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
@use "@/assets/css/variables" as *;
.v-radio .other {
display: flex;

View file

@ -57,5 +57,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -1,7 +1,7 @@
<template>
<v-dialog v-model="showDialog" content-class="more-menu-popup" class="ma-0 pa-0">
<div class="popup-wrapper">
<v-card flat>
<v-card variant="flat">
<v-card-text>
<v-container class="mt-0 pa-0 pt-3 pb-3 action-row-container-no-dividers">
@ -10,8 +10,8 @@
<v-row v-if="showProfile" class="profile-row clickable" @click="viewProfile" no-gutters align-content="center">
<v-col cols="auto" class="me-2">
<v-avatar class="avatar-32" size="32" color="#e0e0e0" @click.stop="viewProfile">
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
<AuthedImage v-if="userAvatar" :src="userAvatar" />
<span v-else class="text-white">{{ userAvatarLetter }}</span>
</v-avatar>
</v-col>
<v-col>
@ -28,11 +28,12 @@
<script>
import profileInfoMixin from "./profileInfoMixin";
import ActionRow from "./ActionRow.vue";
import AuthedImage from "./AuthedImage.vue";
export default {
name: "MoreMenuPopup",
mixins: [profileInfoMixin],
components: { ActionRow },
components: { ActionRow, AuthedImage },
props: {
show: {
type: Boolean,
@ -82,8 +83,9 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@import '~vuetify/src/styles/settings/_variables.scss';
@use "@/assets/css/chat.scss" as *;
@use "vuetify/settings" as *;
@use "sass:map";
.popup-wrapper {
width: fit-content;
@ -102,10 +104,11 @@ export default {
font-weight: 400;
letter-spacing: 0.4px;
position: fixed;
margin: 0px;
margin: 0px !important;
top: 70px;
right: 10px;
display: flex;
align-items: flex-end;
justify-content: flex-end;
box-shadow: none;
pointer-events: none;
@ -148,11 +151,11 @@ export default {
//border-radius: 40px;
width: 95%;
@media #{map-get($display-breakpoints, 'sm-and-up')} {
@media #{map.get($display-breakpoints, 'sm-and-up')} {
width: 70%;
}
@media #{map-get($display-breakpoints, 'lg-and-up')} {
@media #{map.get($display-breakpoints, 'lg-and-up')} {
overflow: unset;
width: $main-desktop-width;
;

View file

@ -12,7 +12,7 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
.shield {
margin-bottom: 12px;
}

View file

@ -5,7 +5,7 @@
<div class="room-name no-upper">{{ $t("profile.title") }}</div>
<v-btn
id="btn-close"
text
variant="text"
class="header-button-right"
v-show="$navigation && $navigation.canPop()"
@click.stop="$navigation.pop"
@ -25,8 +25,8 @@
@click="showAvatarPicker"
v-if="isAvatarLoaded"
>
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
<AuthedImage v-if="userAvatar" :src="userAvatar" />
<span v-else class="text-white">{{ userAvatarLetter }}</span>
<input
id="avatar-picker"
ref="avatar"
@ -53,7 +53,7 @@
<!-- <div v-if="$matrix.currentUser.is_guest">
{{ $t("profile.temporary_identity") }}
</div> -->
<v-btn id="btn-logout" depressed block class="outlined-button" @click.stop="showLogoutPopup=true">
<v-btn id="btn-logout" variant="flat" block class="outlined-button" @click.stop="showLogoutPopup=true">
{{ $t("menu.logout") }}
</v-btn>
<LogoutRoomDialog
@ -111,11 +111,11 @@
<v-dialog
v-model="showEditPasswordDialog"
class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '940px' : '80%'"
:width="$vuetify.display.smAndUp ? '940px' : '80%'"
>
<v-card :disabled="settingPassword">
<v-card-title>{{ $matrix.currentUser.is_guest ? $t("profile.set_password") : $t("profile.change_password") }}</v-card-title>
<v-card-text>
<v-card :disabled="settingPassword" class="dialog-content">
<h2 class="dialog-title">{{ $matrix.currentUser.is_guest ? $t("profile.set_password") : $t("profile.change_password") }}</h2>
<div class="dialog-text">
<v-text-field
v-if="!$matrix.currentUser.is_guest"
v-model="password"
@ -144,21 +144,21 @@
:type="showPassword2 ? 'text' : 'password'"
@click:append="showPassword2 = !showPassword2"
/>
<div class="red--text" v-if="passwordErrorMessage">
<div class="text-red" v-if="passwordErrorMessage">
{{ passwordErrorMessage }}
</div>
</v-card-text>
</div>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn id="btn-password-cancel" text @click="closeEditPasswordDialog">{{
<v-btn id="btn-password-cancel" variant="text" @click="closeEditPasswordDialog">{{
$t("menu.cancel")
}}</v-btn>
<v-btn
id="btn-password-set"
:disabled="!passwordsMatch"
color="primary"
text
variant="text"
@click="
setPassword(
$matrix.currentUser.is_guest
@ -174,10 +174,10 @@
</v-dialog>
<!-- edit display name dialog -->
<v-dialog
<RoundedDialog
v-model="showEditDisplaynameDialog"
class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '940px' : '80%'"
:width="$vuetify.display.smAndUp ? '940px' : '80%'"
>
<v-card>
<v-card-title>{{ $t("profile.display_name") }}</v-card-title>
@ -191,13 +191,13 @@
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn id="btn-displayname-cancel" text @click="showEditDisplaynameDialog = false">{{
<v-btn id="btn-displayname-cancel" variant="text" @click="showEditDisplaynameDialog = false">{{
$t("menu.cancel")
}}</v-btn>
<v-btn
id="btn-displayname-set"
color="primary"
text
variant="text"
@click="
updateDisplayName(editValue);
showEditDisplaynameDialog = false;
@ -207,18 +207,18 @@
>
</v-card-actions>
</v-card>
</v-dialog>
</RoundedDialog>
<SelectLanguageDialog
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%'"
:width="$vuetify.display.smAndUp ? '688px' : '95%'"
>
<div class="dialog-content text-center">
<v-icon size="30">notifications_active</v-icon>
@ -230,8 +230,7 @@
<v-row cols="12">
<v-col cols="6">
<v-btn
depressed
text
variant="flat"
block
class="text-button"
@click="onNotifyDialogClosed"
@ -241,7 +240,7 @@
<v-col cols="6" align="center">
<v-btn
color="primary"
depressed
variant="flat"
block
class="filled-button"
@click.stop="onNotifyDialog"
@ -261,9 +260,11 @@ import ActionRow from "./ActionRow.vue";
import util from "../plugins/utils";
import profileInfoMixin from "./profileInfoMixin";
import LogoutRoomDialog from './LogoutRoomDialog.vue';
import AuthedImage from "./AuthedImage.vue";
import CopyLink from "./CopyLink.vue"
import { requestNotificationPermission, windowNotificationPermission } from "../plugins/notificationAndServiceWorker.js"
import { mapState } from 'vuex'
import RoundedDialog from "./RoundedDialog.vue";
export default {
name: "Profile",
@ -272,7 +273,9 @@ export default {
ActionRow,
SelectLanguageDialog,
LogoutRoomDialog,
CopyLink
CopyLink,
AuthedImage,
RoundedDialog
},
data() {
return {
@ -425,5 +428,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -1,13 +1,13 @@
<template>
<v-dialog v-model="showDialog" content-class="profile-info-popup" class="ma-0 pa-0">
<v-card flat>
<v-card variant="flat">
<v-card-text>
<div class="you-are">{{ $t("profile_info_popup.you_are") }}</div>
<v-container fluid>
<v-row>
<v-col :class="['username', { 'editable': editDisplayName }]" cols="pa-2" ref="username">
<div v-if="$matrix.currentUser.is_guest">
<i18n path="profile_info_popup.identity_temporary" tag="span">
<i18n-t keypath="profile_info_popup.identity_temporary" tag="span">
<template v-slot:displayName>
<input v-model="displayName"
@keyup.enter="$event => $event.target.blur()"
@ -16,10 +16,10 @@
editDisplayName = !editDisplayName;
" @focus="editDisplayName = !editDisplayName" />
</template>
</i18n>
</i18n-t>
</div>
<div v-else>
<i18n path="profile_info_popup.identity" tag="span">
<i18n-t keypath="profile_info_popup.identity" tag="span">
<template v-slot:displayName>
<input
v-model="displayName"
@ -28,13 +28,13 @@
@focus="editDisplayName = !editDisplayName"
/>
</template>
</i18n>
</i18n-t>
</div>
</v-col>
<v-col cols="auto" class="pa-2">
<v-avatar class="avatar-32" size="32" color="#e0e0e0" @click.stop="viewProfile">
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
<AuthedImage v-if="userAvatar" :src="userAvatar" />
<span v-else class="text-white">{{ userAvatarLetter }}</span>
</v-avatar>
</v-col>
</v-row>
@ -44,12 +44,12 @@
<div class="want_more">
🙌 {{ $t("profile_info_popup.want_more") }}
</div>
<i18n path="profile_info_popup.powered_by" tag="div">
<i18n-t keypath="profile_info_popup.powered_by" tag="div">
<template v-slot:product>{{ product }}</template>
<template v-slot:productLink>
<a :href="'//' + productLink">{{ productLink }}</a>
</template>
</i18n>
</i18n-t>
<div class="text-end" v-if="!$config.hide_add_room_on_home">
<v-btn id="btn-new-room" class="new_room" text @click="createRoom">
{{ $t("profile_info_popup.new_room") }}
@ -64,10 +64,12 @@
</template>
<script>
import profileInfoMixin from "./profileInfoMixin";
import AuthedImage from "./AuthedImage.vue";
export default {
name: "ProfileInfoPopup",
mixins: [profileInfoMixin],
components: { AuthedImage },
props: {
show: {
type: Boolean,
@ -117,8 +119,9 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@import '~vuetify/src/styles/settings/_variables.scss';
@use "@/assets/css/chat.scss" as *;
@use "vuetify/settings" as *;
@use "sass:map";
.profile-info-popup {
font-family: "Inter", sans-serif !important;
@ -210,11 +213,11 @@ export default {
font-size: 10 * $chat-text-size !important;
}
@media #{map-get($display-breakpoints, 'sm-and-up')} {
@media #{map.get($display-breakpoints, 'sm-and-up')} {
width: 70%;
}
@media #{map-get($display-breakpoints, 'lg-and-up')} {
@media #{map.get($display-breakpoints, 'lg-and-up')} {
overflow: unset;
width: $main-desktop-width;
position: absolute;

View file

@ -2,12 +2,12 @@
<v-dialog
persistent
v-model="showDialog"
v-show="room" class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'"
class="ma-0 pa-0"
:width="$vuetify.display.smAndUp ? '688px' : '95%'"
>
<div v-if="timeout == -1" class="dialog-content text-center">
<template>
<v-img contain height="28" src="@/assets/icons/trash_black.svg" />
<v-icon size="28">$vuetify.icons.trash_black</v-icon>
<h2 class="dialog-title">{{ $t("purge_room.title") }}</h2>
<div class="dialog-text">
{{ $t("purge_room.info") }}
@ -21,8 +21,7 @@
<v-col cols="6">
<v-btn
id="btn-purge-room-cancel"
depressed
text
variant="flat"
block
class="text-button"
:disabled="isPurging"
@ -34,7 +33,7 @@
<v-btn
id="btn-purge-room"
color="red"
depressed
variant="flat"
block
class="filled-button"
:disabled="isPurging"
@ -49,12 +48,9 @@
<!-- Timer -->
<div v-if="timeout >= 0 && !isPurging" class="dialog-content text-center">
<template>
<v-img
contain
width="20"
class="d-inline-block me-2"
src="@/assets/icons/timer.svg"
/>{{ $t("purge_room.n_seconds", { seconds: timeout }) }}
<v-icon size="20"
class="d-inline-block me-2">$vuetify.icons.timer</v-icon>
{{ $t("purge_room.n_seconds", { seconds: timeout }) }}
<h2 class="dialog-title mb-0">{{ $t("purge_room.self_destruct") }}</h2>
<div class="dialog-text text-center mb-5">
{{ $t("purge_room.notified") }}
@ -68,8 +64,7 @@
<v-col cols="6">
<v-btn
id="btn-purge-room-undo"
depressed
text
variant="flat"
block
class="text-button"
:disabled="isPurging"
@ -78,7 +73,7 @@
>
</v-col>
<v-col cols="6">
<v-btn depressed block class="outlined-button" @click="onDoPurgeRoom">
<v-btn variant="flat" block class="outlined-button" @click="onDoPurgeRoom">
{{ $t("menu.delete_now") }}
</v-btn>
</v-col>
@ -96,47 +91,25 @@
</v-dialog>
</template>
<script>
import roomInfoMixin from "./roomInfoMixin";
import { STATE_EVENT_ROOM_DELETION_NOTICE } from "../plugins/utils";
import RoomDialogBase from "./RoomDialogBase.vue";
export default {
name: "LeaveRoomDialog",
mixins: [roomInfoMixin],
props: {
show: {
type: Boolean,
default: function () {
return false;
},
},
},
name: "PurgeRoomDialog",
extends: RoomDialogBase,
data() {
return {
timeout: -1,
timeoutTimer: null,
showDialog: false,
isPurging: false,
status: null,
};
},
watch: {
show: {
handler(newVal, ignoredOldVal) {
this.showDialog = newVal;
},
},
showDialog(val, oldVal) {
if (!val && oldVal) {
this.undo();
this.$emit("close");
} else if (val && !oldVal) {
// Showing, reset
this.status = null;
}
},
},
methods: {
onOpenDialog() {
// Showing, reset
this.status = null;
},
undo() {
if (this.timeoutTimer) {
clearInterval(this.timeoutTimer);
@ -198,5 +171,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -2,7 +2,7 @@
<v-dialog
v-model="showDialog"
class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '940px' : '95%'"
:width="$vuetify.display.smAndUp ? '940px' : '95%'"
>
<div class="dialog-content text-center" ref="qrContainer">
<div class="d-flex justify-center">
@ -106,5 +106,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -6,19 +6,19 @@
class="text-center d-flex flex-column goodbye-wrapper"
>
<div v-if="roomWasPurged">
<v-img src="@/assets/icons/trash.svg" />
<v-icon>$vuetify.icons.trash</v-icon>
</div>
<h2 v-if="roomWasPurged" class="white--text mt-2 mb-8">
<h2 v-if="roomWasPurged" class="text-white mt-2 mb-8">
{{ $t("goodbye.room_deleted") }}
</h2>
<div class="quote white--text">{{ quote }}</div>
<div class="author white--text mt-4">- {{ author }}</div>
<div class="quote text-white">{{ quote }}</div>
<div class="author text-white mt-4">- {{ author }}</div>
<v-btn
v-if="joinedToAnyRoom"
id="btn-goodbye-view-other"
color="white"
text
variant="text"
class="close"
@click.stop="viewOtherRooms"
>{{ $t("goodbye.view_other_rooms") }}</v-btn
@ -27,7 +27,7 @@
v-else
id="btn-goodbye-close"
color="white"
text
variant="text"
class="close"
@click.stop="closeBrowserTab"
>{{ $t("goodbye.close_tab") }}</v-btn
@ -42,34 +42,34 @@
class="goodbye-profile clickable"
@click.stop="viewOtherRooms"
>
<div class="d-inline-block me-2 white--text">
<div class="d-inline-block me-2 text-white">
{{ $t("profile_info_popup.you_are") }}
</div>
<div
v-if="$matrix.currentUser.is_guest"
class="d-inline-block me-2 white--text"
class="d-inline-block me-2 text-white"
>
<i18n path="profile_info_popup.identity_temporary" tag="span">
<i18n-t keypath="profile_info_popup.identity_temporary" tag="span">
<template v-slot:displayName>
<b>{{ displayName }}</b>
</template>
</i18n>
</i18n-t>
</div>
<div v-else class="d-inline-block me-2 white--text">
<i18n path="profile_info_popup.identity" tag="span">
<div v-else class="d-inline-block me-2 text-white">
<i18n-t keypath="profile_info_popup.identity" tag="span">
<template v-slot:displayName>
<b>{{ displayName }}</b>
</template>
</i18n>
</i18n-t>
</div>
<v-avatar
class="avatar-32 d-inline-block"
size="32"
color="#e0e0e0"
>
<img v-if="userAvatar" :src="userAvatar" />
<span v-else class="white--text">{{ userAvatarLetter }}</span>
<AuthedImage v-if="userAvatar" :src="userAvatar" />
<span v-else class="text-white">{{ userAvatarLetter }}</span>
</v-avatar>
</div>
</transition>
@ -77,10 +77,12 @@
</template>
<script>
import profileInfoMixin from "./profileInfoMixin";
import AuthedImage from "./AuthedImage.vue";
export default {
name: "QuoteView",
mixins: [profileInfoMixin],
components: { AuthedImage },
props: {
roomWasPurged: {
type: Boolean,
@ -97,21 +99,26 @@ export default {
};
},
mounted() {
var quotes;
try {
quotes = require("@/assets/quotes/" + this.$i18n.locale + "/quotes");
const quotes = import.meta.glob('@/assets/quotes/*/*.json', {eager: false});
let quoteImport = undefined;
Object.keys(quotes).forEach(path => {
// Remove"./"
const parts = path.split("/");
const locale = parts[parts.length - 2];
if (locale == this.$i18n.locale) {
quoteImport = quotes[path];
}
});
if (quoteImport) {
quoteImport().then((quotes) => this.selectQuote(quotes));
return;
}
} catch (error) {
console.error("No quotes for language");
quotes = undefined;
}
if (!quotes) {
quotes = require("@/assets/quotes/en/quotes"); // Default fallback
}
const n = quotes.quotes.length;
const quote = quotes.quotes[Math.floor(Math.random() * n)];
this.quote = quote.quote;
this.author = quote.author;
this.mounted = true;
import("@/assets/quotes/en/quotes") // Default fallback
.then((quotes) => this.selectQuote(quotes));
},
computed: {
@ -125,6 +132,17 @@ export default {
},
methods: {
selectQuote(quotes) {
const n = quotes.quotes.length;
if (n > 0) {
const quote = quotes.quotes[Math.floor(Math.random() * n)];
this.quote = quote.quote;
this.author = quote.author;
this.mounted = true;
} else {
this.mounted = true;
}
},
closeBrowserTab() {
window.location.href = "about:blank";
},
@ -136,7 +154,7 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
.author {
font-size: 80%;

View file

@ -1,9 +1,8 @@
<template>
<v-dialog
v-model="showDialog"
v-show="room"
class="ma-0 pa-0"
:width="$vuetify.breakpoint.smAndUp ? '688px' : '95%'"
:width="$vuetify.display.smAndUp ? '688px' : '95%'"
>
<div class="dialog-content text-center">
<h2 class="dialog-title">{{ $t("room_info.report") }}</h2>
@ -14,8 +13,7 @@
<v-col cols="6">
<v-btn
id="btn-back"
depressed
text
variant="flat"
block
class="text-button"
@click="showDialog = false"
@ -26,7 +24,7 @@
<v-btn
id="btn-report"
color="red"
depressed
variant="flat"
block
class="filled-button"
@click.stop="onReport()"
@ -39,37 +37,16 @@
</v-dialog>
</template>
<script>
import roomInfoMixin from "./roomInfoMixin";
import RoomDialogBase from "./RoomDialogBase.vue";
export default {
name: "ReportRoomDialog",
mixins: [roomInfoMixin],
props: {
show: {
type: Boolean,
default: function () {
return false;
},
},
},
extends: RoomDialogBase,
data() {
return {
showDialog: false,
reason: ""
};
},
watch: {
show: {
handler(newVal, ignoredOldVal) {
this.showDialog = newVal;
},
},
showDialog() {
if (!this.showDialog) {
this.$emit("close");
}
},
},
methods: {
onReport() {
@ -94,5 +71,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -1,7 +1,7 @@
<template>
<v-avatar :class="{'room-avatar':true, 'cursor-pointer':userCanPurgeRoom}" @click="userCanPurgeRoom?showRoomAvatarPicker():null" v-if="isRoomAvatarLoaded">
<v-img v-if="roomAvatar" :src="roomAvatar"/>
<span v-else class="white--text headline">{{
<AuthedImage v-if="roomAvatar" :src="roomAvatar"/>
<span v-else class="text-white headline">{{
roomName.substring(0, 1).toUpperCase()
}}</span>
<input
@ -28,10 +28,12 @@
<script>
import util from "../plugins/utils";
import roomInfoMixin from "./roomInfoMixin";
import AuthedImage from "./AuthedImage.vue";
export default {
name: "RoomAvatarPicker",
mixins: [roomInfoMixin],
components: { AuthedImage },
data() {
return {
isRoomAvatarLoaded: true,

View file

@ -0,0 +1,43 @@
<template><div></div></template>
<script>
import roomInfoMixin from "./roomInfoMixin";
export default {
name: "RoomDialogBase",
mixins: [roomInfoMixin],
props: ['modelValue'],
emits: ['update:modelValue'],
data() {
return {
showDialog: false,
};
},
watch: {
room() {
this.showDialog = this.shouldShow();
},
modelValue() {
this.showDialog = this.shouldShow();
},
showDialog() {
if (!this.showDialog) {
this.$emit('update:modelValue', false)
} else {
this.onOpenDialog();
}
},
},
methods: {
shouldShow() {
return this.modelValue && this.room ? true : false;
},
onOpenDialog() {
}
},
};
</script>
<style lang="scss">
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -8,7 +8,7 @@
<div class="room-name-inline text-truncate" :title="room.name">
{{ room.name }}
</div>
<div class="num-members">{{ $tc("room.members", room.getJoinedMemberCount()) }}</div>
<div class="num-members">{{ $t("room.members", room.getJoinedMemberCount()) }}</div>
</v-col>
<v-col cols="auto" class="text-end ma-0 pa-0">{{ exportDate }}</v-col>
@ -38,12 +38,12 @@
</div>
<!-- Loading indicator -->
<v-container fluid fill-height class="exporting-indicator">
<v-container fluid class="exporting-indicator fill-height">
<v-row align="center" justify="center">
<v-col class="text-center">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
<div>{{ statusText }}</div>
<v-btn color="black" depressed class="filled-button mt-5" @click.stop="cancelExport">{{
<v-btn color="black" variant="flat" class="filled-button mt-5" @click.stop="cancelExport">{{
$t("menu.cancel")
}}</v-btn>
</v-col>
@ -53,7 +53,6 @@
</template>
<script>
import Vue from "vue";
import MessageIncomingText from "./messages/MessageIncomingText.vue";
import MessageIncomingFile from "./messages/MessageIncomingFile.vue";
import MessageIncomingImage from "./messages/MessageIncomingImage.vue";
@ -94,14 +93,15 @@ import BottomSheet from "./BottomSheet.vue";
import CreatePollDialog from "./CreatePollDialog.vue";
import chatMixin from "./chatMixin";
import util from "../plugins/utils";
import JSZip from "jszip";
import { saveAs } from "file-saver";
import { EventTimelineSet } from "matrix-js-sdk";
import axios from 'axios';
import "../services/jszip.min";
import "../services/filesaver.cjs";
export default {
name: "RoomExport",
mixins: [chatMixin],
emits: ["close"],
components: {
ChatHeader,
MessageIncomingText,
@ -168,7 +168,7 @@ export default {
},
watch: {
processedEvents() {
this.statusText = this.$t("export.processed_n_of_total_events", {
this.statusText = this.$t("room_export.processed_n_of_total_events", {
count: this.processedEvents,
total: this.totalEvents,
});
@ -176,7 +176,7 @@ export default {
},
computed: {
exportDate() {
return this.$t("export.exported_date", { date: util.formatDay(Date.now().valueOf()) });
return this.$t("room_export.exported_date", { date: util.formatDay(Date.now().valueOf()) });
},
},
methods: {
@ -214,13 +214,13 @@ export default {
if (result.chunk.length === 0) break;
if (nToFetch != null) {
nToFetch -= result.chunk.length;
this.statusText = this.$t("export.fetched_n_of_total_events", {
this.statusText = this.$t("room_export.fetched_n_of_total_events", {
count: this.totalEvents - nToFetch,
total: this.totalEvents,
});
} else {
this.totalEvents += result.chunk.length;
this.statusText = this.$t("export.fetched_n_events", { count: this.totalEvents });
this.statusText = this.$t("room_export.fetched_n_events", { count: this.totalEvents });
}
fetchedEvents.push(...result.chunk.map(eventMapper));
@ -254,21 +254,21 @@ export default {
.then((events) => {
// Create a timeline and add the events to that, so that relations etc are aggregated correctly!
this.timelineSet = new EventTimelineSet(null, { unstableClientRelationAggregation: true });
this.timelineSet.addEventsToTimeline(events.reverse(), true, this.timelineSet.getLiveTimeline(), "");
this.timelineSet.addEventsToTimeline(events.reverse(), true, false, this.timelineSet.getLiveTimeline(), "");
this.events = events;
// Need to set thread root events and replyEvents so stuff is rendered correctly.
this.events.filter(event => (event.threadRootId && !event.parentThread)).forEach(event => {
const parentEvent = this.timelineSet.findEventById(event.threadRootId) || this.room.findEventById(event.threadRootId);
if (parentEvent) {
Vue.set(parentEvent, "isMxThread", true);
Vue.set(event, "parentThread", parentEvent);
parentEvent["isMxThread"] = true;
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);
if (parentEvent) {
Vue.set(event, "replyEvent", parentEvent);
event["replyEvent"] = parentEvent;
}
});
@ -307,7 +307,7 @@ export default {
for (const comp of childComponents) {
// Avatars need downloading?
if (comp.$el) {
if (comp.$el && comp.$el.nodeType == 1) {
const avatars = comp.$el.getElementsByClassName("v-avatar");
if (avatars && avatars.length > 0) {
const member = this.room.getMember(comp.event.getSender());
@ -328,7 +328,7 @@ export default {
}
if (!avatarFolder.file(fileName)) {
const url = member.getAvatarUrl(this.$matrix.matrixClient.getHomeserverUrl(), 40, 40, "scale", true);
const url = member.getAvatarUrl(this.$matrix.matrixClient.getHomeserverUrl(), 40, 40, "scale", true, false, this.$matrix.useAuthedMedia);
if (url) {
avatarFolder.file(fileName, "empty");
downloadPromises.push(
@ -353,7 +353,7 @@ export default {
}
}
let componentClass = comp.$vnode.tag.split("-").reverse()[0];
let componentClass = comp.$options ? comp.$options.__file.split("/").reverse()[0].split(".")[0] : "invalid_component";
switch (componentClass) {
case "MessageIncomingImageExport":
case "MessageOutgoingImageExport":
@ -367,7 +367,7 @@ export default {
downloadPromises.push(
util
.getAttachment(this.$matrix.matrixClient, comp.event, null, true)
.getAttachment(this.$matrix.matrixClient, this.$matrix.useAuthedMedia, comp.event, null, true)
.then((blob) => {
return new Promise((resolve, ignoredReject) => {
let mime = blob.type;
@ -386,21 +386,15 @@ export default {
let fileName = comp.event.getId() + extension;
imageFolder.file(fileName, blob); // TODO calc bytes
let blobUrl = URL.createObjectURL(blob);
comp.src = blobUrl;
this.$nextTick(() => {
// Update source
let elements = comp.$el.getElementsByClassName("v-image__image");
let element = elements && elements[0];
if (element) {
element.style.backgroundImage = 'url("./images/' + fileName + '")';
element.classList.remove("v-image__image--preload");
}
URL.revokeObjectURL(blobUrl); // Give the blob back
this.processedEvents += 1;
resolve(true);
});
// Update source
const images = comp.$el.getElementsByTagName("img");
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
const img = images[imageIndex];
img.removeAttribute("src");
img.setAttribute("data-exported-src", './images/' + fileName);
}
this.processedEvents += 1;
resolve(true);
}
});
})
@ -413,16 +407,15 @@ export default {
case "MessageOutgoingAudioExport":
downloadPromises.push(
util
.getAttachment(this.$matrix.matrixClient, comp.event, null, true)
.getAttachment(this.$matrix.matrixClient, this.$matrix.useAuthedMedia, comp.event, null, true)
.then((blob) => {
if (currentMediaSize + blob.size <= maxMediaSize) {
currentMediaSize += blob.size;
return new Promise((resolve, ignoredReject) => {
//let mime = blob.type;
var extension = ".mp3";
var extension = ".webm";
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) {
@ -430,7 +423,6 @@ export default {
}
this.processedEvents += 1;
resolve(true);
//});
});
}
})
@ -443,7 +435,7 @@ export default {
case "MessageOutgoingVideoExport":
downloadPromises.push(
util
.getAttachment(this.$matrix.matrixClient, comp.event, null, true)
.getAttachment(this.$matrix.matrixClient, this.$matrix.useAuthedMedia, comp.event, null, true)
.then((blob) => {
if (currentMediaSize + blob.size <= maxMediaSize) {
currentMediaSize += blob.size;
@ -472,7 +464,7 @@ export default {
case "MessageOutgoingFileExport":
downloadPromises.push(
util
.getAttachment(this.$matrix.matrixClient, comp.event, null, true)
.getAttachment(this.$matrix.matrixClient, this.$matrix.useAuthedMedia, comp.event, null, true)
.then((blob) => {
if (currentMediaSize + blob.size <= maxMediaSize) {
currentMediaSize += blob.size;
@ -538,7 +530,7 @@ export default {
zip.generateAsync({ type: "blob" }).then((content) => {
saveAs(
content,
this.$t("export.export_filename", { date: util.formatDay(Date.now().valueOf()) }) + ".zip"
this.$t("room_export.export_filename", { date: util.formatDay(Date.now().valueOf()) }) + ".zip"
);
this.status = "";
this.$emit("close");

View file

@ -4,7 +4,7 @@
<v-container fluid class="d-flex justify-space-between align-center">
<v-btn
id="btn-back"
text
variant="text"
:class="(($navigation && $navigation.canPop()) || $matrix.currentRoomId) ? 'v-visible' : 'v-hidden'"
@click.stop="goBack()"
>
@ -16,7 +16,7 @@
v-if="!userCanPurgeRoom"
id="btn-leave-room"
color="black"
depressed
variant="flat"
class="filled-button"
@click.stop="showLeaveConfirmation = true"
>👋 {{ $t("room_info.leave_room") }}</v-btn
@ -58,7 +58,7 @@
maxlength="50"
@blur="updateRoomName()"
@keyup.enter="updateRoomName()"
solo
variant="solo"
></v-text-field>
</div>
<div :class="{'topic':true,'cursor-default':!userCanPurgeRoom}">
@ -81,7 +81,7 @@
autofocus
@blur="updateRoomTopic()"
@keyup.enter="updateRoomTopic()"
solo
variant="solo"
>
</v-text-field>
</div>
@ -91,7 +91,7 @@
</div>
<copy-link :locationLink="publicRoomLink" i18nCopyLinkKey="copy_invite_link" />
<v-card class="account ma-3" flat>
<v-card class="account ma-3" variant="flat">
<v-card-title class="h2">{{ $t("room_info.permissions") }}</v-card-title>
<v-card-text>
<v-select
@ -101,63 +101,53 @@
:items="joinRules"
class="mt-4"
v-model="roomJoinRule"
item-title="text"
item-value="id"
item-props
return-object
>
<template v-slot:selection="{ item }">
<v-icon color="black" class="me-2">{{ item.icon }}</v-icon>
{{ item.text }}
<v-icon color="black" class="me-2">{{ item.props.icon }}</v-icon>
{{ item.props.text }}
</template>
<template v-slot:item="{ item, attrs, on }">
<v-list-item v-on="on" v-bind="attrs" #default="{ active }">
<v-list-item-avatar>
<v-icon color="black">{{ item.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn icon v-if="active">
<v-icon color="grey lighten-1">check</v-icon>
</v-btn>
</v-list-item-action>
<template v-slot:item="{ props }">
<v-list-item v-bind="props">
<template v-slot:prepend>
<v-avatar color="grey">
<v-icon color="black">{{ props.icon }}</v-icon>
</v-avatar>
</template>
<template v-slot:append="{ isActive }">
<v-list-item-action>
<v-btn icon v-if="isActive">
<v-icon color="grey-lighten-1">check</v-icon>
</v-btn>
</v-list-item-action>
</template>
</v-list-item>
</template>
</v-select>
<!-- <div v-if="anyoneCanJoin">
<div>Anyone with a link can join.</div>
<v-text-field
:value="publicRoomLink"
readonly
append-icon="content_copy"
filled
type="text"
@click:append="copyRoomLink"
></v-text-field>
</div> -->
</v-card-text>
</v-card>
<v-card class="account ma-3" flat>
<v-card class="account ma-3" variant="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 link v-on:click="showMessageRetentionDialog = true" class="px-0 pb-0">
<template v-slot:prepend>
<v-avatar class="mr-0 pb-0 mb-0">
<v-icon>$vuetify.icons.timer</v-icon>
</v-avatar>
</template>
<v-list-item-action class="pb-0 mb-0">
<v-icon>arrow_drop_down</v-icon>
</v-list-item-action>
{{ messageRetentionDisplay }}
<template v-slot:append>
<v-list-item-action class="pb-0 mb-0">
<v-icon>arrow_drop_down</v-icon>
</v-list-item-action>
</template>
</v-list-item>
<hr />
</v-list>
@ -167,8 +157,8 @@
</v-card-text>
</v-card>
<v-card class="account ma-3" flat v-if="canChangeReadOnly()">
<v-card class="account ma-3" variant="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">
@ -178,33 +168,30 @@
<div class="option-text">{{ $t('room_info.read_only_room_info') }}</div>
</v-card-text>
</v-card>
<v-card class="account ma-3" flat>
<v-card class="account ma-3" variant="flat">
<v-card-title class="h2">{{ $t("room_info.report") }}</v-card-title>
<v-card-text class="">
<div class="with-right-label" style="align-items:center;height:36px">
<div class="option-title">{{ $t('room_info.report_info') }}</div>
<v-btn
color="black"
depressed
small
variant="flat"
size="small"
class="outlined-button"
@click.stop="report"
>{{ $t("room_info.report") }}</v-btn>
</div>
</v-card-text>
</v-card>
<v-card class="members ma-3" flat>
<v-card class="members ma-3" variant="flat">
<v-card-title class="h2"
>{{ $t("room_info.members") }}<v-spacer></v-spacer>
<div>{{ members.length }}</div></v-card-title
>
<v-list>
<v-list-item-group>
<template v-for="(member, index) in members" >
<template v-for="(member, index) in members" :key="member.userId">
<v-list-item
:key="member.userId"
class="member"
v-show="showAllMembers || index < SHOW_MEMBER_LIMIT"
@click="onListItemClick(member)"
@ -212,8 +199,8 @@
<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">{{
<AuthedImage v-if="memberAvatar(member)" :src="memberAvatar(member)" />
<span v-else class="text-white headline">{{
member.name.substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
@ -245,7 +232,6 @@
: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") }}
@ -257,7 +243,7 @@
<v-btn
v-if="userCanExportChat"
color="black"
depressed
variant="flat"
class="filled-button"
@click.stop="exportRoom"
>{{ $t("room_info.export_room") }}</v-btn>
@ -268,35 +254,30 @@
</div>
<UserProfileDialog
:show="showMemberActionConfirmation"
v-model="showMemberActionConfirmation"
:activeMember="activeMember || members[0]"
:room="room"
@close="showMemberActionConfirmation = false"
/>
<LeaveRoomDialog
:show="showLeaveConfirmation"
v-model="showLeaveConfirmation"
:room="room"
@close="showLeaveConfirmation = false"
/>
<PurgeRoomDialog
:show="showPurgeConfirmation"
v-model="showPurgeConfirmation"
:room="room"
@close="showPurgeConfirmation = false"
/>
<MessageRetentionDialog
:show="showMessageRetentionDialog"
v-model="showMessageRetentionDialog"
:room="room"
@close="showMessageRetentionDialog = false"
v-on:message-retention-update="onMessageRetention"
/>
<ReportRoomDialog
:show="showReportDialog"
v-model="showReportDialog"
:room="room"
@close="showReportDialog = false"
/>
<RoomExport :room="room" v-if="exporting" v-on:close="exporting = false" />
@ -311,10 +292,12 @@ import ReportRoomDialog from "../components/ReportRoomDialog";
import RoomExport from "../components/RoomExport";
import RoomAvatarPicker from "../components/RoomAvatarPicker";
import CopyLink from "../components/CopyLink.vue"
import UserProfileDialog from "./UserProfileDialog.vue"
import UserProfileDialog from "./UserProfileDialog.vue";
import AuthedImage from "./AuthedImage.vue";
import roomInfoMixin from "./roomInfoMixin";
import roomTypeMixin from "./roomTypeMixin";
import util, { STATE_EVENT_ROOM_TYPE } from "../plugins/utils";
import buildVersion from "../assets/version.txt?raw";
export default {
name: "RoomInfo",
@ -327,7 +310,8 @@ export default {
UserProfileDialog,
RoomExport,
RoomAvatarPicker,
CopyLink
CopyLink,
AuthedImage
},
data() {
return {
@ -365,12 +349,10 @@ export default {
this.user = this.$matrix.matrixClient.getUser(this.$matrix.currentUserId);
// Display build version
const version = require("!!raw-loader!../assets/version.txt").default;
console.log("Version", version);
this.buildVersion = version;
this.buildVersion = buildVersion;
},
destroyed() {
unmounted() {
this.$matrix.off("Room.timeline", this.onEvent);
},
@ -584,6 +566,6 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -46,5 +46,5 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -1,70 +1,75 @@
<template>
<v-list dense class="room-list">
<v-list-item-group @change="roomChange" color="primary">
<v-list-item v-if="showCreate" @click.stop="$emit('newroom')" class="room-list-room" :value="null">
<v-list-item-avatar class="round" size="42" color="#d9d9d9">
<v-list class="room-list">
<v-list-item v-if="showCreate" @click.stop="$emit('newroom')" class="room-list-room" :value="null">
<template v-slot:prepend>
<v-avatar class="round" size="42" color="#d9d9d9">
<v-icon size="11">$vuetify.icons.ic_new_room</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="room-list-new-room">{{
$t("menu.new_room")
}}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-avatar>
</template>
<!-- invites -->
<v-list-item :disabled="roomsProcessing[room.roomId]" v-for="room in invitedRooms" :key="room.roomId"
:value="room" class="room-list-room">
<v-list-item-avatar size="42" color="#d9d9d9">
<v-img v-if="roomAvatar(room)" :src="roomAvatar(room)" />
<span v-else class="white--text headline">{{
<v-list-item-title class="room-list-new-room">{{
$t("menu.new_room")
}}</v-list-item-title>
</v-list-item>
<!-- invites -->
<v-list-item :disabled="roomsProcessing[room.roomId]" v-for="room in invitedRooms" :key="room.roomId"
:value="room.roomId" class="room-list-room">
<template v-slot:prepend>
<v-avatar size="42" color="#d9d9d9">
<AuthedImage v-if="roomAvatar(room)" :src="roomAvatar(room)" />
<span v-else class="text-white headline">{{
room.name.substring(0, 1).toUpperCase()
}}</span>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="room-list-name">{{ room.name }}</v-list-item-title>
<v-list-item-subtitle>{{ room.topic }}</v-list-item-subtitle>
</v-list-item-content>
</v-avatar>
</template>
<v-list-item-title class="room-list-name">{{ room.name }}</v-list-item-title>
<v-list-item-subtitle>{{ room.topic }}</v-list-item-subtitle>
<template v-slot:append>
<v-list-item-action>
<v-btn id="btn-accept" class="filled-button" depressed color="black" @click.stop="acceptInvitation(room)">{{
<v-btn id="btn-accept" class="filled-button" variant="flat" color="black" @click.stop="acceptInvitation(room)">{{
$t("menu.join") }}</v-btn>
<v-btn v-if="!room.isServiceNoticeRoom" id="btn-reject" class="filled-button" color="black" @click.stop="rejectInvitation(room)" text>{{
$t("menu.ignore") }}</v-btn>
<v-btn v-if="!room.isServiceNoticeRoom" id="btn-reject" class="filled-button" color="black"
@click.stop="rejectInvitation(room)" variant="text">{{
$t("menu.ignore") }}</v-btn>
</v-list-item-action>
</v-list-item>
</template>
</v-list-item>
<v-list-item v-for="room in joinedRooms" :key="room.roomId" :value="room" class="room-list-room">
<v-list-item-avatar size="42" color="#d9d9d9" :class="[{'rounded-circle': isDirect(room)}]">
<v-img v-if="roomAvatar(room)" :src="roomAvatar(room)" />
<span v-else class="white--text headline">{{
<v-list-item v-for="room in joinedRooms" :key="room.roomId" :value="room.roomId"
@click="currentRoomId = room.roomId" class="room-list-room">
<template v-slot:prepend>
<v-avatar size="42" color="#d9d9d9" :class="[{ 'rounded-circle': isDirect(room) }]">
<AuthedImage v-if="roomAvatar(room)" :src="roomAvatar(room)" />
<span v-else class="text-white headline">{{
room.name.substring(0, 1).toUpperCase()
}}</span>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="room-list-name">{{ room.name }}
<!-- <v-icon class="ml-2 mb-1" size="10" v-if="isPublic(room)">$vuetify.icons.ic_public</v-icon> -->
</v-list-item-title>
<v-list-item-subtitle class="room-list-new-messages" v-if="notificationCount(room) > 0">
{{ $t("room.room_list_new_messages", { count: notificationCount(room) }) }}
</v-list-item-subtitle>
</v-list-item-content>
</v-avatar>
</template>
<v-list-item-title class="room-list-name">{{ room.name }}
<!-- <v-icon class="ml-2 mb-1" size="10" v-if="isPublic(room)">$vuetify.icons.ic_public</v-icon> -->
</v-list-item-title>
<v-list-item-subtitle class="room-list-new-messages" v-if="notificationCount(room) > 0">
{{ $t("room.room_list_new_messages", { count: notificationCount(room) }) }}
</v-list-item-subtitle>
<template v-slot:append>
<v-list-item-action>
<v-icon size="16" v-if="room.roomId == $matrix.currentRoomId">$vuetify.icons.ic_circle_filled</v-icon>
<v-icon size="16" v-else>$vuetify.icons.ic_circle</v-icon>
</v-list-item-action>
</v-list-item>
</v-list-item-group>
</template>
</v-list-item>
</v-list>
</template>
<script>
import util from "../plugins/utils";
import Vue from "vue";
import AuthedImage from "./AuthedImage.vue";
export default {
name: "RoomList",
components: { AuthedImage },
props: {
title: {
type: String,
@ -81,6 +86,8 @@ export default {
},
data: () => ({
currentRoomId: null,
/** A list of rooms currently processing some operation, like "join" or "reject" */
roomsProcessing: {},
}),
@ -111,7 +118,9 @@ export default {
42,
42,
"scale",
true
true,
false,
this.$matrix.useAuthedMedia
);
}
} else {
@ -140,7 +149,7 @@ export default {
},
acceptInvitation(room) {
Vue.set(this.roomsProcessing, room.roomId, true);
this.roomsProcessing[room.roomId] = true;
this.$matrix.matrixClient
.joinRoom(room.roomId)
.then((ignoredRoom) => {
@ -162,19 +171,19 @@ export default {
console.error("Failed to accept invite: ", err);
})
.finally(() => {
Vue.delete(this.roomsProcessing, room.roomId);
delete this.roomsProcessing[room.roomId];
});
},
rejectInvitation(room) {
Vue.set(this.roomsProcessing, room.roomId, true);
this.roomsProcessing[room.roomId] = true;
this.$matrix
.leaveRoom(room.roomId)
.catch((err) => {
console.error("Failed to reject invite: ", err);
})
.finally(() => {
Vue.delete(this.roomsProcessing, room.roomId);
delete this.roomsProcessing[room.roomId];
});
},
@ -185,30 +194,30 @@ export default {
isDirect(room) {
return this.$matrix.isDirectRoom(room);
},
},
roomChange(room) {
if (room == null || room == undefined || room == 0) {
watch: {
currentRoomId() {
if (this.currentRoomId == null || this.currentRoomId == undefined) {
// Ignore, this is caused by "new room" etc.
return;
}
if (room.isServiceNoticeRoom && room.selfMembership === "invite") {
return; // Nothing should happen when click on invite to server notices room, just the "join" button is enabled.
}
this.$emit("close");
this.$navigation.push(
{
name: "Chat",
params: { roomId: util.sanitizeRoomId(room.roomId) },
params: { roomId: util.sanitizeRoomId(this.currentRoomId) },
},
-1
);
}
},
},
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>
<style lang="scss" scoped>

View file

@ -1,41 +0,0 @@
<template>
<v-select outlined dense :items="availableRoomTypes"
:value="modelValue"
@change="$emit('update:modelValue', $event)"
:reduce="(obj) => obj.value">
<template v-slot:selection="{ item }">{{ item.title }}</template>
<template v-slot:item="{ item, attrs, on }">
<v-list-item v-on="on" v-bind="attrs" #default="{}">
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</template>
</v-select>
</template>
<script>
import roomTypeMixin from "./roomTypeMixin";
export default {
name: "RoomTypeSelector",
mixins: [roomTypeMixin],
model: {
prop: "modelValue",
event: "update:modelValue",
},
props: {
modelValue: {
type: String,
default: function () {
return null;
},
},
}
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>

View file

@ -0,0 +1,14 @@
<template>
<v-dialog v-bind="{ ...$props, ...$attrs }" content-class="v-dialog-rounded">
<slot></slot>
</v-dialog>
</template>
<script>
export default {
};
</script>
<style lang="scss">
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -1,9 +1,8 @@
<template>
<v-dialog
<RoundedDialog
class="ma-0 pa-0"
v-bind="{ ...$props, ...$attrs }"
v-on="$listeners"
:width="$vuetify.breakpoint.smAndUp ? '940px' : '90%'"
:width="$vuetify.display.smAndUp ? '940px' : '90%'"
>
<v-card class="dialog-card">
<v-card-title class="dialog-title d-block">
@ -16,11 +15,11 @@
</v-card-title>
<v-card-text>
<v-select
v-model="$i18n.locale"
:modelValue="$i18n.locale"
:items="languages"
menu-props="auto"
:menu-props="{ auto: true }"
:label="$t('profile.select_language')"
v-on:change="$store.commit('setLanguage', $i18n.locale)"
@update:modelValue="updateLanguage"
hide-details
prepend-icon="language"
single-line
@ -29,26 +28,27 @@
<v-card-actions>
<v-btn
id="btn-done"
color="black"
depressed
variant="flat"
block
class="btn-dark"
@click="$emit('close')"
@click="$emit('update:model-value', false)"
>{{ $t("menu.done") }}</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</RoundedDialog>
</template>
<script>
import LanguageMixin from "./languageMixin";
import RoundedDialog from "./RoundedDialog.vue";
export default {
mixins: [LanguageMixin],
components: { RoundedDialog }
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -5,18 +5,15 @@
centered
class="tabs"
show-arrows
slider-color="primary"
>
<v-tabs-slider></v-tabs-slider>
<v-tab v-for="pack in packs" :key="pack">
<v-tab v-for="(pack,ipack) in packs" :key="'pack-' + ipack" :value="'pack-' + ipack">
{{ pack }}
<v-icon>mdi-phone</v-icon>
</v-tab>
</v-tabs>
<v-tabs-items v-model="currentStickerPack" class="tab-items">
<v-tab-item v-for="pack in packs" :key="pack">
<v-card flat>
<v-tabs-window v-model="currentStickerPack">
<v-window-item v-for="(pack,ipack) in packs" :key="'pack-content-' + ipack" :value="'pack-' + ipack">
<v-card variant="flat">
<v-container fluid>
<v-row>
<v-col cols="2" v-for="sticker in stickersInPack(pack)" :key="pack + sticker.name">
@ -25,8 +22,8 @@
</v-row>
</v-container>
</v-card>
</v-tab-item>
</v-tabs-items>
</v-window-item>
</v-tabs-window>
</BottomSheet>
</template>
@ -40,17 +37,14 @@ export default {
},
data() {
return {
currentStickerPack: 'tab-0',
currentStickerPack: 'pack-0',
packs: [],
};
},
computed: {
packs() {
return stickers.getPacks();
}
},
mounted() {},
methods: {
open() {
this.packs = stickers.getPacks();
this.$refs.sheet.open();
},
stickersInPack(pack) {
@ -65,10 +59,14 @@ export default {
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
@use "@/assets/css/chat.scss" as *;
</style>
<style lang="scss">
.sheetContent {
top: 40px !important;
padding: 0 20px 20px 20px !important;
}
.sticker-picker {
z-index: 10;
@ -76,6 +74,7 @@ export default {
position: sticky;
top: 0px;
z-index: 1;
background: white;
}
}
</style>

View file

@ -3,7 +3,7 @@
class="ma-0 pa-0"
v-model="isUnSupportedBrowser"
persistent
:width="$vuetify.breakpoint.smAndUp ? '50%' : '90%'"
:width="$vuetify.display.smAndUp ? '50%' : '90%'"
>
<div class="dialog-content text-center">
<h2 class="dialog-title">{{ $t("global.different_browser_title") }}</h2>
@ -12,7 +12,7 @@
<v-spacer></v-spacer>
<v-btn
:color="locationUrlCopied ? '#DEE6FF' : 'black'"
depressed
variant="flat"
@click.stop="copyRoomLink1"
:class="{'filled-button' : true, 'link-copied-in-place' : locationUrlCopied}"
>{{ $t(`room_info.${locationUrlCopied ? 'link_copied' : 'copy_link'}`) }}</v-btn

Some files were not shown because too many files have changed in this diff Show more