diff --git a/package-lock.json b/package-lock.json index 62053a7..15674cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,8 @@ "js-sha256": "^0.9.0", "json-web-key": "^0.4.0", "jszip": "^3.9.1", - "linkifyjs": "3.0.0-beta.3", + "linkify-html": "^4.1.0", + "linkifyjs": "^4.1.0", "material-design-icons-iconfont": "^6.1", "matrix-js-sdk": "^19.7.0", "md-gum-polyfill": "^1.0.0", @@ -9291,7 +9292,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "3.14.1", @@ -9713,19 +9715,19 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, - "node_modules/linkifyjs": { - "version": "3.0.0-beta.3", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-3.0.0-beta.3.tgz", - "integrity": "sha512-aXq4WJs91NsETo5f9dQrt8Vx+OxAvzJAtR8lLgpum8PDjtCgstycwYbIkAjDGRV/YF1LlKKdbWyOpgMYgwgOvQ==", - "engines": { - "node": ">=8" - }, + "node_modules/linkify-html": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/linkify-html/-/linkify-html-4.1.0.tgz", + "integrity": "sha512-cQSNN4i5V1xRjdSUEnXgn855xsl+usD7zBSsNyMSFBf4NlaZFocn7cExJA217azxODeqea79b6fDPXLa7jdkcA==", "peerDependencies": { - "jquery": ">= 1.11.0", - "react": ">= 0.14.0", - "react-dom": ">= 0.14.0" + "linkifyjs": "^4.0.0" } }, + "node_modules/linkifyjs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.0.tgz", + "integrity": "sha512-Ffv8VoY3+ixI1b3aZ3O+jM6x17cOsgwfB1Wq7pkytbo1WlyRp6ZO0YDMqiWT/gQPY/CmtiGuKfzDIVqxh1aCTA==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -10024,18 +10026,6 @@ "url": "https://tidelift.com/funding/github/npm/loglevel" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -12378,31 +12368,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -13034,15 +12999,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", @@ -23765,7 +23721,8 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { "version": "3.14.1", @@ -24146,12 +24103,17 @@ "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", "dev": true }, - "linkifyjs": { - "version": "3.0.0-beta.3", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-3.0.0-beta.3.tgz", - "integrity": "sha512-aXq4WJs91NsETo5f9dQrt8Vx+OxAvzJAtR8lLgpum8PDjtCgstycwYbIkAjDGRV/YF1LlKKdbWyOpgMYgwgOvQ==", + "linkify-html": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/linkify-html/-/linkify-html-4.1.0.tgz", + "integrity": "sha512-cQSNN4i5V1xRjdSUEnXgn855xsl+usD7zBSsNyMSFBf4NlaZFocn7cExJA217azxODeqea79b6fDPXLa7jdkcA==", "requires": {} }, + "linkifyjs": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.0.tgz", + "integrity": "sha512-Ffv8VoY3+ixI1b3aZ3O+jM6x17cOsgwfB1Wq7pkytbo1WlyRp6ZO0YDMqiWT/gQPY/CmtiGuKfzDIVqxh1aCTA==" + }, "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -24384,15 +24346,6 @@ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==" }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -26158,25 +26111,6 @@ "schema-utils": "^3.0.0" } }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -26651,15 +26585,6 @@ } } }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", diff --git a/package.json b/package.json index 521b7f5..353a01d 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "js-sha256": "^0.9.0", "json-web-key": "^0.4.0", "jszip": "^3.9.1", - "linkifyjs": "3.0.0-beta.3", + "linkify-html": "^4.1.0", + "linkifyjs": "^4.1.0", "material-design-icons-iconfont": "^6.1", "matrix-js-sdk": "^19.7.0", "md-gum-polyfill": "^1.0.0", diff --git a/public/favicon.ico b/public/favicon.ico index 0280252..90a2eab 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/App.vue b/src/App.vue index 3e6596f..2b1c114 100644 --- a/src/App.vue +++ b/src/App.vue @@ -145,7 +145,7 @@ export default { }, favicon() { - var favicon = undefined; + var favicon = 'favicon.ico'; if (this.$route.meta.includeFavicon) { if (this.$matrix.currentRoom) { favicon = this.$matrix.currentRoom.avatar || 'favicon.ico'; diff --git a/src/assets/config.json b/src/assets/config.json index 389e901..a816ef7 100644 --- a/src/assets/config.json +++ b/src/assets/config.json @@ -39,5 +39,6 @@ "siteId": "25" } } - ] + ], + "experimental_voice_mode": true } \ No newline at end of file diff --git a/src/assets/css/_utilities.scss b/src/assets/css/_utilities.scss index b2d67e3..51d8edf 100644 --- a/src/assets/css/_utilities.scss +++ b/src/assets/css/_utilities.scss @@ -31,4 +31,10 @@ } .v-hidden { visibility: hidden; +} +.alert-bg { + background-color: $alert-bg-color; +} +.box-shadow-none { + box-shadow: none !important; } \ No newline at end of file diff --git a/src/assets/css/_variables.scss b/src/assets/css/_variables.scss index d0f2e67..bb3aa64 100644 --- a/src/assets/css/_variables.scss +++ b/src/assets/css/_variables.scss @@ -16,4 +16,5 @@ $voice-recorder-color: #6f6f6f; $voice-recording-color: red; $voice-recorded-color: #3ae17d; $poll-hilite-color: #6360f0; -$poll-hilite-color-bg: #d6d5fc; \ No newline at end of file +$poll-hilite-color-bg: #d6d5fc; +$alert-bg-color: #FF3300; \ No newline at end of file diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss index f949b52..bb1df52 100644 --- a/src/assets/css/chat.scss +++ b/src/assets/css/chat.scss @@ -5,6 +5,17 @@ $admin-bg: black; $admin-fg: white; +body { + --v-background-color: white; + --v-foreground-color: black; + --v-divider-color: #eeeeee; + &.dark { + --v-background-color: black; + --v-foreground-color: white; + --v-divider-color: rgba(221, 221, 221, 0.1); + } +} + .home { .v-card { background-color: white; @@ -30,8 +41,8 @@ $admin-fg: white; margin: 0; padding: 0; height: 72px; - background-color: #ffffff; - border-bottom: 1px solid #eeeeee; + background-color: var(--v-background-color); + border-bottom: 1px solid var(--v-divider-color); .chat-header-row { margin: 0; padding: 4px 10px; @@ -47,27 +58,15 @@ $admin-fg: white; font-family: "Inter", sans-serif; font-weight: 400; font-size: 12 * $chat-text-size; - color: black; - } - .v-btn.leave-button { - font-family: "Inter", sans-serif; - font-weight: 700; - font-size: 11 * $chat-text-size; - color: black; - background-color: white !important; - border: 1px solid black; - border-radius: $chat-standard-padding / 2; - height: $chat-standard-padding; - margin-top: $chat-standard-padding-xs; - margin-bottom: $chat-standard-padding-xs; + color: var(--v-foreground-color); } .v-btn.avatar { font-family: "Inter", sans-serif; font-weight: 700; font-size: 11 * $chat-text-size; - color: black; - background-color: white !important; - border: 1px solid black; + color: var(--v-foreground-color); + background-color: var(--v-background-color) !important; + border: 1px solid var(--v-foreground-color); border-radius: $chat-standard-padding / 2; height: $chat-standard-padding; margin-top: $chat-standard-padding-xs; @@ -78,6 +77,19 @@ $admin-fg: white; position: fixed; z-index: 10; } + + .icon-dropdown { + margin: 0px 8px; + } + + .notification-alert { + display: inline-block; + background-color: #ff3300; + width: 8px; + height: 8px; + border-radius: 4px; + margin-bottom: 2px; + } } .room-list-notification-count { @@ -699,7 +711,7 @@ $admin-fg: white; font-weight: 700; font-size: 18 * $chat-text-size; text-transform: uppercase; - color: black; + color: var(--v-foreground-color); text-align: center; } @@ -863,6 +875,12 @@ $admin-fg: white; margin-left: 6px; } + .member .user-power { + margin-left: 6px; + color: #aaa; + font-size: 0.8rem; + } + .member .start-private-chat { margin-left: 38px; } @@ -891,6 +909,26 @@ $admin-fg: white; } } +.with-right-label { + display: flex; + flex-wrap: nowrap; + flex-direction: row; + text-align: start; + & > * { + flex: 1 1 auto; + } + & > *:last-child { + flex: 0 0 auto; + } + .option-title { + color: #000; + font-size: 16 * $chat-text-size; + } + .option-text { + font-size: 13 * $chat-text-size; + } +} + .header-button-left { position: absolute; top: 0px; @@ -1106,6 +1144,21 @@ $admin-fg: white; border: 1px solid #808080 !important; position: relative; } + + .options { + display: flex; + width: 100%; + justify-content: flex-end; + color: black; + font-size: 14 * $chat-text-size; + font-weight: bold; + margin-left: 10px; + [dir="rtl"] & { + margin-left: initial; + margin-right: 10px; + } + text-transform: none !important; + } } .room-link .v-input__slot::before { @@ -1211,3 +1264,125 @@ $admin-fg: white; opacity: 1; transition: opacity 0.3s linear; } + +.auto-audio-player-root { + position: absolute; + top: 72px; + left: 0; + right: 0; + bottom: 0; + margin: 0; + background-color: var(--v-background-color); + color: var(--v-foreground-color); + overflow: hidden; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + .load-earlier { + flex: 1 0 auto; + padding: 20px; + } + .typing-users { + flex: 1 0 52px; + min-height: 52px; + padding: 20px; + overflow-x: auto; + overflow-y: hidden; + max-width: 100%; + display: flex; + .typing-user { + width: 32px !important; + height: 32px !important; + margin-left: -8px !important; + } + .list-enter-active, .list-leave-active { + transition: all 1s; + } + .list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ { + opacity: 0; + transform: translateY(24px); + } + } + .load-later { + flex: 1 0 auto; + padding: 20px; + display: flex; + flex-direction: column; + align-items:center; + justify-content: flex-end; + width: 100%; + } + .mic-button { + align-self: flex-end; + } + .senderAndTime { + .sender { + margin-left: 0; + color: inherit; + } + .time { + color: inherit; + } + text-align: center; + color: inherit; + } + .sound-wave-view { + width: 80%; + max-width: 40vh; + aspect-ratio: 1/1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + position: relative; + .volume-container { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + margin: 0; + div { + width: 0px; + height: 0px; + background-color: transparent; + border-radius: 50%; + } + } + canvas { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 100%; + height: 100%; + } + } + .avatar { + width: 103px !important; + height: 103px !important; + margin: 0 !important; + } + #btn-play, #btn-pause { + margin: 26px; + } + .mic-button { + z-index: 0; + } +} + +.audio-layout.voice-recorder { + left: 20px; + right: 20px; + bottom: 20px; + position: absolute; +} \ No newline at end of file diff --git a/src/assets/icons/audio_import.vue b/src/assets/icons/audio_import.vue new file mode 100644 index 0000000..d43e655 --- /dev/null +++ b/src/assets/icons/audio_import.vue @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/src/assets/icons/audio_import_play.vue b/src/assets/icons/audio_import_play.vue new file mode 100644 index 0000000..b940826 --- /dev/null +++ b/src/assets/icons/audio_import_play.vue @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/src/assets/icons/forward.vue b/src/assets/icons/forward.vue new file mode 100644 index 0000000..e38a979 --- /dev/null +++ b/src/assets/icons/forward.vue @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/src/assets/icons/ic_dropdown.vue b/src/assets/icons/ic_dropdown.vue new file mode 100644 index 0000000..1e2510a --- /dev/null +++ b/src/assets/icons/ic_dropdown.vue @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/src/assets/icons/ic_member-leave.vue b/src/assets/icons/ic_member-leave.vue new file mode 100644 index 0000000..766e7fe --- /dev/null +++ b/src/assets/icons/ic_member-leave.vue @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/src/assets/icons/ic_moderator-delete.vue b/src/assets/icons/ic_moderator-delete.vue new file mode 100644 index 0000000..7a892f2 --- /dev/null +++ b/src/assets/icons/ic_moderator-delete.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/src/assets/icons/pause_circle.vue b/src/assets/icons/pause_circle.vue new file mode 100644 index 0000000..3de8c1d --- /dev/null +++ b/src/assets/icons/pause_circle.vue @@ -0,0 +1,7 @@ + diff --git a/src/assets/icons/play_circle.vue b/src/assets/icons/play_circle.vue new file mode 100644 index 0000000..dad7db5 --- /dev/null +++ b/src/assets/icons/play_circle.vue @@ -0,0 +1,7 @@ + diff --git a/src/assets/icons/rewind.vue b/src/assets/icons/rewind.vue new file mode 100644 index 0000000..6c0b2e8 --- /dev/null +++ b/src/assets/icons/rewind.vue @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/src/assets/logo.png b/src/assets/logo.png deleted file mode 100644 index f3d2503..0000000 Binary files a/src/assets/logo.png and /dev/null differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg index c9b0438..f1dfb58 100644 --- a/src/assets/logo.svg +++ b/src/assets/logo.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json index 1b0b494..e97d73c 100644 --- a/src/assets/translations/en.json +++ b/src/assets/translations/en.json @@ -26,7 +26,12 @@ "undo": "Undo", "join": "Join", "ignore": "Ignore", - "loading": "Loading {appName}" + "loading": "Loading {appName}", + "user_kick": "Kick this user", + "user_kick_and_ban": "Kick and ban this user", + "user_make_admin": "Make administrator", + "user_make_moderator": "Make moderator", + "user_revoke_moderator": "Revoke moderator" }, "message": { "you": "You", @@ -37,6 +42,12 @@ "user_changed_room_avatar": "{user} changed the room avatar", "user_encrypted_room": "{user} made the room encrypted", "user_was_invited": "{user} was invited to the chat...", + "user_was_kicked": "{user} was kicked from the chat.", + "user_was_kicked_by_you": "You kicked {user} from the chat.", + "user_was_kicked_you": "You were kicked from the chat.", + "user_was_banned": "{user} was kicked and banned from the chat.", + "user_was_banned_by_you": "You kicked and banned {user} from the chat.", + "user_was_banned_you": "You were kicked and banned from the chat.", "user_joined": "{user} joined the chat", "user_left": "{user} left the chat", "user_said": "{user} said:", @@ -78,8 +89,8 @@ "members": "no members | 1 member | {count} members", "leave": "Leave", "purge_set_room_state": "Setting room state", - "purge_redacting_events": "Redacting events", - "purge_removing_members": "Removing members", + "purge_redacting_events": "Redacting events ({count} of {total})", + "purge_removing_members": "Removing members ({count} of {total})", "purge_failed": "Failed to purge room!", "room_list_invites": "Invites", "room_list_rooms": "Rooms", @@ -116,7 +127,8 @@ "status_avatar_total": "Uploading avatar: {count} of {total}", "status_avatar": "Uploading avatar: {count}", "room_name_limit_error_msg": "Maximum 50 characters allowed", - "colon_not_allowed": "Colon is not allowed" + "colon_not_allowed": "Colon is not allowed", + "options": "Options" }, "device_list": { "title": "DEVICES", @@ -229,7 +241,12 @@ "leave_room": "Leave", "version_info": "Powered by Guardian Project. Version: {version}", "scan_code": "Scan to join the room", - "export_room": "Export chat" + "export_room": "Export chat", + "user_admin": "Administrator", + "user_moderator": "Moderator", + "experimental_features": "Experimental Features", + "voice_mode": "Voice mode", + "voice_mode_info": "Switches the chat interface to a 'listen and record' mode" }, "room_info_sheet": { "this_room": "This room", diff --git a/src/components/AudioLayout.vue b/src/components/AudioLayout.vue new file mode 100644 index 0000000..d3b9920 --- /dev/null +++ b/src/components/AudioLayout.vue @@ -0,0 +1,457 @@ +
+
+ expand_less +
+ + +
+ + + + {{ + member.name.substring(0, 1).toUpperCase() + }} + + +
+ +
+
+
+
+ + + {{ + eventSenderDisplayName(currentAudioEvent).substring(0, 1).toUpperCase() + }} + +
+
+
{{ eventSenderDisplayName(currentAudioEvent) }}
+
+ {{ formatTime(currentAudioEvent.event.origin_server_ts) }} +
+
+
+ {{ currentTime }} / {{ totalTime }} +
+ +
+ + $vuetify.icons.rewind + + + $vuetify.icons.pause_circle + + + $vuetify.icons.play_circle + + + $vuetify.icons.forward + +
+ +
+ + mic + + expand_more +
+
+ + + + + \ No newline at end of file diff --git a/src/components/Chat.vue b/src/components/Chat.vue index da95b28..1b7472b 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -4,49 +4,40 @@ {{ $tc("room.invitations", invitationCount) }} -
+ + + + +
- + " :originalEvent="selectedEvent" />
- +
@@ -59,55 +50,31 @@
-
- +
+ -
+
- +
- + arrow_downward @@ -121,10 +88,11 @@
{{ $t("message.reply_image") }}
{{ $t("message.reply_audio_message") }}
{{ $t("message.reply_video") }}
-
{{ $t("message.reply_poll") }}
+
{{ $t("message.reply_poll") }}
- + $vuetify.icons.poll @@ -143,24 +111,13 @@ - + " /> @@ -169,150 +126,82 @@ - + $vuetify.icons.poll - - + + mic - + mic - + {{ editedEvent ? "save" : "arrow_upward" }} - - + + $vuetify.icons.addReaction - + face - +
+ +
- +
file: {{ currentImageInputPath.name }} - {{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }} + {{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }} - {{ currentImageInput.dimensions.width }} x {{ currentImageInput.dimensions.height }} + {{ currentImageInput.dimensions.width }} x {{ currentImageInput.dimensions.height }} - ({{ formatBytes(currentImageInput.scaledSize) }}) + ({{ formatBytes(currentImageInput.scaledSize) }}) ({{ formatBytes(currentImageInputPath.size) }}) - +
{{ currentSendError }}
{{ currentSendProgress }}
@@ -321,17 +210,10 @@ {{ - $t("menu.cancel") + $t("menu.cancel") }} - {{ $t("menu.send") }} + {{ $t("menu.send") }}
@@ -363,7 +245,7 @@ {{ - $t("menu.ok") + $t("menu.ok") }} @@ -389,6 +271,7 @@ import BottomSheet from "./BottomSheet.vue"; import ImageResize from "image-resize"; import CreatePollDialog from "./CreatePollDialog.vue"; import chatMixin from "./chatMixin"; +import AudioLayout from "./AudioLayout.vue"; const sizeOf = require("image-size"); const dataUriToBuffer = require("data-uri-to-buffer"); @@ -405,7 +288,7 @@ function ScrollPosition(node) { this.readyFor = "up"; } -ScrollPosition.prototype.restore = function() { +ScrollPosition.prototype.restore = function () { if (this.readyFor === "up") { this.node.scrollTop = this.node.scrollHeight - this.previousScrollHeightMinusTop; } else { @@ -413,7 +296,7 @@ ScrollPosition.prototype.restore = function() { } }; -ScrollPosition.prototype.prepareFor = function(direction) { +ScrollPosition.prototype.prepareFor = function (direction) { this.readyFor = direction || "up"; if (this.readyFor === "up") { this.previousScrollHeightMinusTop = this.node.scrollHeight - this.node.scrollTop; @@ -436,6 +319,7 @@ export default { BottomSheet, AvatarOperations, CreatePollDialog, + AudioLayout }, data() { @@ -516,9 +400,13 @@ export default { }, mounted() { - const container = this.$refs.chatContainer; - this.scrollPosition = new ScrollPosition(container); - this.chatContainerSize = this.$refs.chatContainerResizer.$el.clientHeight; + const container = this.chatContainer; + if (container) { + this.scrollPosition = new ScrollPosition(container); + if (this.$refs.chatContainerResizer) { + this.chatContainerSize = this.$refs.chatContainerResizer.$el.clientHeight; + } + } }, beforeDestroy() { @@ -531,6 +419,14 @@ export default { }, computed: { + chatContainer() { + const container = this.$refs.chatContainer; + console.log("GOT CONTAINER", container); + if (this.useVoiceMode) { + return container.$el; + } + return container; + }, senderDisplayName() { return this.room.getMember(this.replyToEvent.sender.userId).name; }, @@ -615,7 +511,7 @@ export default { return util.browserCanRecordAudio(); }, debugging() { - return (window.location.host || "").startsWith("localhost"); + return false; //(window.location.host || "").startsWith("localhost"); }, invitationCount() { return this.$matrix.invites.length; @@ -627,9 +523,23 @@ export default { me && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("redact", me.powerLevel); return isAdmin; }, + useVoiceMode: { + get: function () { + if (!this.$config.experimental_voice_mode) return false; + return util.useVoiceMode(this.room); + }, + }, }, watch: { + initialLoadDone: { + immediate: true, + handler(value, oldValue) { + if (value && !oldValue) { + console.log("Loading finished!"); + } + } + }, roomId: { immediate: true, handler(value, oldValue) { @@ -697,6 +607,12 @@ export default { }); } }, + showRecorder(show) { + if (this.useVoiceMode) { + // Send typing indicators when recorder UI is opened/closed + this.$matrix.matrixClient.sendTyping(this.roomId, show, 10 * 60 * 1000); + } + } }, methods: { @@ -729,6 +645,7 @@ export default { const getMoreIfNeeded = function _getMoreIfNeeded() { const container = self.$refs.chatContainer; if ( + container && container.scrollHeight <= (1 + 2 * WINDOW_BUFFER_SIZE) * container.clientHeight && self.timelineWindow && self.timelineWindow.canPaginate(EventTimeline.BACKWARDS) @@ -867,22 +784,22 @@ export default { handleChatContainerResize({ ignoredWidth, height }) { const delta = height - this.chatContainerSize; this.chatContainerSize = height; - const container = this.$refs.chatContainer; - if (delta < 0) { + const container = this.chatContainer; + if (container && delta < 0) { container.scrollTop -= delta; } }, paginateBackIfNeeded() { this.$nextTick(() => { - const container = this.$refs.chatContainer; - if (container.scrollHeight <= container.clientHeight) { + const container = this.chatContainer; + if (container && container.scrollHeight <= container.clientHeight) { this.handleScrolledToTop(); } }); }, onScroll(ignoredevent) { - const container = this.$refs.chatContainer; + const container = this.chatContainer; if (!container) { return; } @@ -898,7 +815,7 @@ export default { container.scrollHeight === container.clientHeight ? false : container.scrollHeight - container.scrollTop.toFixed(0) > container.clientHeight || - (this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS)); + (this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS)); this.restartRRTimer(); }, @@ -908,19 +825,20 @@ export default { return; // Not for this room } + const loadingDone = this.initialLoadDone; this.$matrix.matrixClient.decryptEventIfNeeded(event, {}); - if (this.initialLoadDone) { + if (this.initialLoadDone && !this.useVoiceMode) { this.paginateBackIfNeeded(); } - // If we are at bottom, scroll to see new events... - const container = this.$refs.chatContainer; - var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll - if (container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) { - scrollToSeeNew = true; - } - if (this.initialLoadDone && event.forwardLooking && !event.isRelation()) { + if (loadingDone && event.forwardLooking && !event.isRelation()) { + // If we are at bottom, scroll to see new events... + var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll + const container = this.chatContainer; + if (container && container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) { + scrollToSeeNew = true; + } this.handleScrolledToBottom(scrollToSeeNew); } }, @@ -1102,7 +1020,6 @@ export default { }, handleScrolledToTop() { - console.log("@top"); if ( this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.BACKWARDS) && @@ -1129,7 +1046,6 @@ export default { }, handleScrolledToBottom(scrollToEnd) { - console.log("@bottom"); if ( this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS) && @@ -1140,16 +1056,18 @@ export default { .paginate(EventTimeline.FORWARDS, 10, true) .then((success) => { if (success) { - this.scrollPosition.prepareFor("down"); this.events = this.timelineWindow.getEvents(); - this.$nextTick(() => { - // restore scroll position! - console.log("Restore scroll!"); - this.scrollPosition.restore(); - if (scrollToEnd) { - this.smoothScrollToEnd(); - } - }); + if (!this.useVoiceMode) { + this.scrollPosition.prepareFor("down"); + this.$nextTick(() => { + // restore scroll position! + console.log("Restore scroll!"); + this.scrollPosition.restore(); + if (scrollToEnd) { + this.smoothScrollToEnd(); + } + }); + } } }) .finally(() => { @@ -1162,7 +1080,7 @@ export default { * Scroll so that the given event is at the middle of the chat view (if more events) or else at the bottom. */ scrollToEvent(eventId) { - const container = this.$refs.chatContainer; + const container = this.chatContainer; const ref = this.$refs[eventId]; if (container && ref) { const targetY = container.clientHeight / 2; @@ -1172,9 +1090,9 @@ export default { }, smoothScrollToEnd() { - this.$nextTick(function() { - const container = this.$refs.chatContainer; - if (container.children.length > 0) { + this.$nextTick(function () { + const container = this.chatContainer; + if (container && container.children.length > 0) { const lastChild = container.children[container.children.length - 1]; console.log("Scroll into view", lastChild); window.requestAnimationFrame(() => { @@ -1251,7 +1169,7 @@ export default { document.body.appendChild(link); link.click(); - setTimeout(function() { + setTimeout(function () { document.body.removeChild(link); URL.revokeObjectURL(url); }, 200); @@ -1268,8 +1186,8 @@ export default { }, emojiSelected(e) { - if(this.isEmojiQuickReaction) { - // When quick emoji picker is clicked + if (this.isEmojiQuickReaction) { + // When quick emoji picker is clicked if (this.selectedEvent) { const event = this.selectedEvent; this.selectedEvent = null; @@ -1286,6 +1204,24 @@ export default { }, sendQuickReaction(e) { + let previousReaction = null; + + // Figure out if we have already sent this emoji, in that case redact it again (toggle) + // + const reactions = this.timelineSet.relations.getChildEventsForEvent(e.event.getId(), 'm.annotation', 'm.reaction'); + if (reactions && reactions._eventsCount > 0) { + const relations = reactions.getRelations(); + for (const r of relations) { + const emoji = r.getRelation().key; + const sender = r.getSender(); + if (emoji == e.reaction && sender == this.$matrix.currentUserId) { + previousReaction = r.isRedacted() ? null : r; + } + } + } + if (previousReaction) { + this.redact(previousReaction); + } else { util .sendQuickReaction(this.$matrix.matrixClient, this.roomId, e.reaction, e.event) .then(() => { @@ -1294,6 +1230,7 @@ export default { .catch((err) => { console.log("Failed to send quick reaction:", err); }); + } }, sendSticker(stickerShortCode) { @@ -1370,64 +1307,79 @@ export default { */ restartRRTimer() { this.stopRRTimer(); - this.rrTimer = setTimeout(this.rrTimerElapsed, READ_RECEIPT_TIMEOUT); + + if (this.$matrix.currentRoomBeingPurged) { + return; + } + + let eventIdFirst = null; + let eventIdLast = null; + if (!this.useVoiceMode) { + const container = this.chatContainer; + const elFirst = util.getFirstVisibleElement(container, (item) => item.hasAttribute("eventId")); + const elLast = util.getLastVisibleElement(container, (item) => item.hasAttribute("eventId")); + if (elFirst && elLast) { + eventIdFirst = elFirst.getAttribute("eventId"); + eventIdLast = elLast.getAttribute("eventId"); + } + } + if (eventIdFirst && eventIdLast) { + this.rrTimer = setTimeout(() => { this.rrTimerElapsed(eventIdFirst, eventIdLast) }, READ_RECEIPT_TIMEOUT); + } }, - rrTimerElapsed() { + rrTimerElapsed(eventIdFirst, eventIdLast) { this.rrTimer = null; + this.sendRR(eventIdFirst, eventIdLast); + this.restartRRTimer(); + }, - const container = this.$refs.chatContainer; - const elFirst = util.getFirstVisibleElement(container); - const elLast = util.getLastVisibleElement(container); - if (elFirst && elLast) { - const eventIdFirst = elFirst.getAttribute("eventId"); - const eventIdLast = elLast.getAttribute("eventId"); - if (eventIdLast && this.room) { - var event = this.room.findEventById(eventIdLast); - const index = this.events.indexOf(event); + sendRR(eventIdFirst, eventIdLast) { + console.log("SEND RR", eventIdFirst, eventIdLast); + if (eventIdLast && this.room) { + var event = this.room.findEventById(eventIdLast); + const index = this.events.indexOf(event); - // Walk backwards through visible events to the first one that is incoming - // - var lastTimestamp = 0; - if (this.lastRR) { - lastTimestamp = this.lastRR.getTs(); + // Walk backwards through visible events to the first one that is incoming + // + var lastTimestamp = 0; + if (this.lastRR) { + lastTimestamp = this.lastRR.getTs(); + } + + for (var i = index; i >= 0; i--) { + event = this.events[i]; + if (event == this.lastRR || event.getTs() <= lastTimestamp) { + // Already sent this or too old... + break; + } + // Make sure it's not a local echo event... + if (!event.getId().startsWith("~")) { + // Send read receipt + this.$matrix.matrixClient + .sendReadReceipt(event) + .then(() => { + this.$matrix.matrixClient.setRoomReadMarkers(this.room.roomId, event.getId()); + }) + .then(() => { + console.log("RR sent for event: " + event.getId()); + this.lastRR = event; + }) + .catch((err) => { + console.log("Failed to update read marker: ", err); + }) + .finally(() => { + this.restartRRTimer(); + }); + return; // Bail out here } - for (var i = index; i >= 0; i--) { - event = this.events[i]; - if (event == this.lastRR || event.getTs() <= lastTimestamp) { - // Already sent this or too old... - break; - } - // Make sure it's not a local echo event... - if (!event.getId().startsWith("~")) { - // Send read receipt - this.$matrix.matrixClient - .sendReadReceipt(event) - .then(() => { - this.$matrix.matrixClient.setRoomReadMarkers(this.room.roomId, event.getId()); - }) - .then(() => { - console.log("RR sent for event: " + event.getId()); - this.lastRR = event; - }) - .catch((err) => { - console.log("Failed to update read marker: ", err); - }) - .finally(() => { - this.restartRRTimer(); - }); - return; // Bail out here - } - - // Stop iterating at first visible - if (event.getId() == eventIdFirst) { - break; - } + // Stop iterating at first visible + if (event.getId() == eventIdFirst) { + break; } } } - this.restartRRTimer(); }, showRecordingUI() { @@ -1499,9 +1451,9 @@ export default { let div = document.createElement("div"); div.classList.add("toast"); div.innerText = this.$t("poll_create.results_shared"); - this.$refs.chatContainer.parentElement.appendChild(div); + this.chatContainer.parentElement.appendChild(div); setTimeout(() => { - this.$refs.chatContainer.parentElement.removeChild(div); + this.chatContainer.parentElement.removeChild(div); }, 3000); } }, diff --git a/src/components/ChatHeader.vue b/src/components/ChatHeader.vue index 79cfb91..4d0d703 100644 --- a/src/components/ChatHeader.vue +++ b/src/components/ChatHeader.vue @@ -16,7 +16,8 @@ @click.stop="onHeaderClicked" >
- {{ room.name }} + {{ room.name }}$vuetify.icons.ic_dropdown
+
{{ $tc("room.members", memberCount) }}
@@ -25,27 +26,32 @@ - {{ $t("room_info.purge") }} + $vuetify.icons.ic_moderator-delete - {{ $t("room.leave") }} + $vuetify.icons.ic_member-leave @@ -132,6 +138,9 @@ export default { } } return null; + }, + notifications() { + return this.$matrix.joinedRooms.some(room => room.roomId !== this.$matrix.currentRoomId && room.getUnreadNotificationCount("total") > 0); } }, watch: { diff --git a/src/components/CreateRoom.vue b/src/components/CreateRoom.vue index 3332e2a..85d08ea 100644 --- a/src/components/CreateRoom.vue +++ b/src/components/CreateRoom.vue @@ -3,29 +3,23 @@
{{ $t("new_room.new_room") }}
- + arrow_back {{ $t("menu.back") }} + text + :disabled=" + !roomName || (step != steps.INITIAL && step != steps.CREATED) + " + class="header-button-right" + @click.stop="onCreate" + > + {{ + step == steps.CREATED ? $t("new_room.done") : $t("new_room.next") + }} + -->
@@ -39,51 +33,43 @@
- +
{{ $t("new_room.name_room") }}
- -
{{ $t("new_room.room_topic") }}
- -
{{roomCreationErrorMsg}}
- -
- {{ status }} - -
- {{ $t("new_room.create") }} -
+ +
{{ $t("new_room.room_topic") }}
+ + + + + +
{{ roomCreationErrorMsg }}
+ +
+ {{ status }} + +
+ {{ $t("new_room.create") }} +
@@ -91,126 +77,101 @@ + + --> + + {{ status }} +
--> - - + +
{{ $t("join.choose_name") }}
- + -
- - + + +
- + - - {{ $t("join.enter_room") }} - + + {{ $t("join.enter_room") }} +
@@ -253,7 +204,7 @@ diff --git a/src/components/chatMixin.js b/src/components/chatMixin.js index 970fe16..d87a548 100644 --- a/src/components/chatMixin.js +++ b/src/components/chatMixin.js @@ -22,6 +22,8 @@ import MessageOutgoingVideoExport from "./messages/export/MessageOutgoingVideoEx import ContactJoin from "./messages/ContactJoin.vue"; import ContactLeave from "./messages/ContactLeave.vue"; import ContactInvited from "./messages/ContactInvited.vue"; +import ContactKicked from "./messages/ContactKicked.vue"; +import ContactBanned from "./messages/ContactBanned.vue"; import ContactChanged from "./messages/ContactChanged.vue"; import RoomCreated from "./messages/RoomCreated.vue"; import RoomAliased from "./messages/RoomAliased.vue"; @@ -66,6 +68,8 @@ export default { ContactJoin, ContactLeave, ContactInvited, + ContactKicked, + ContactBanned, ContactChanged, RoomCreated, RoomAliased, @@ -123,9 +127,15 @@ export default { return ContactJoin; } } else if (event.getContent().membership == "leave") { + if ((event.getPrevContent() || {}).membership == "join" && + event.getStateKey() != event.getSender()) { + return ContactKicked; + } return ContactLeave; } else if (event.getContent().membership == "invite") { return ContactInvited; + } else if (event.getContent().membership == "ban") { + return ContactBanned; } break; diff --git a/src/components/messages/AudioPlayer.vue b/src/components/messages/AudioPlayer.vue index 00157aa..b5c0815 100644 --- a/src/components/messages/AudioPlayer.vue +++ b/src/components/messages/AudioPlayer.vue @@ -54,9 +54,10 @@ export default { this.player.addEventListener("pause", () => { this.playing = false; }); - this.player.addEventListener("ended", function () { + this.player.addEventListener("ended", () => { this.pause(); this.playing = false; + this.$emit("playback-ended"); }); }, beforeDestroy() { diff --git a/src/components/messages/ContactBanned.vue b/src/components/messages/ContactBanned.vue new file mode 100644 index 0000000..37c2a45 --- /dev/null +++ b/src/components/messages/ContactBanned.vue @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/src/components/messages/ContactChanged.vue b/src/components/messages/ContactChanged.vue index 95622dc..e03d24a 100644 --- a/src/components/messages/ContactChanged.vue +++ b/src/components/messages/ContactChanged.vue @@ -33,7 +33,7 @@ export default { if (this.displayNameChange) { return this.event.getPrevContent().displayname; } - return this.stateEventDisplayName(this.event); + return this.eventStateKeyDisplayName(this.event); }, } }; diff --git a/src/components/messages/ContactInvited.vue b/src/components/messages/ContactInvited.vue index 85f96a8..110c43e 100644 --- a/src/components/messages/ContactInvited.vue +++ b/src/components/messages/ContactInvited.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/ContactJoin.vue b/src/components/messages/ContactJoin.vue index b8bce13..d8aa6d8 100644 --- a/src/components/messages/ContactJoin.vue +++ b/src/components/messages/ContactJoin.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/ContactKicked.vue b/src/components/messages/ContactKicked.vue new file mode 100644 index 0000000..b34da00 --- /dev/null +++ b/src/components/messages/ContactKicked.vue @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/src/components/messages/ContactLeave.vue b/src/components/messages/ContactLeave.vue index 0b77831..1e40799 100644 --- a/src/components/messages/ContactLeave.vue +++ b/src/components/messages/ContactLeave.vue @@ -1,7 +1,7 @@ diff --git a/src/components/messages/MessageIncoming.vue b/src/components/messages/MessageIncoming.vue index 9c31eb3..e1f1727 100644 --- a/src/components/messages/MessageIncoming.vue +++ b/src/components/messages/MessageIncoming.vue @@ -2,7 +2,7 @@
-
{{ messageEventDisplayName(event) }}
+
{{ eventSenderDisplayName(event) }}
{{ formatTime(event.event.origin_server_ts) }}
@@ -10,7 +10,7 @@ {{ - messageEventDisplayName(event).substring(0, 1).toUpperCase() + eventSenderDisplayName(event).substring(0, 1).toUpperCase() }} diff --git a/src/components/messages/QuickReactions.vue b/src/components/messages/QuickReactions.vue index baa69ce..63dc6be 100644 --- a/src/components/messages/QuickReactions.vue +++ b/src/components/messages/QuickReactions.vue @@ -1,6 +1,6 @@