diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss index ecb8ad3..175cd41 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,15 +58,15 @@ $admin-fg: white; font-family: "Inter", sans-serif; font-weight: 400; font-size: 12 * $chat-text-size; - color: black; + color: var(--v-foreground-color); } .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; + 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; @@ -65,9 +76,9 @@ $admin-fg: white; 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; @@ -699,7 +710,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; } @@ -1217,3 +1228,104 @@ $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; + } + .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/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/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/translations/en.json b/src/assets/translations/en.json index 7c711db..0110511 100644 --- a/src/assets/translations/en.json +++ b/src/assets/translations/en.json @@ -242,7 +242,9 @@ "scan_code": "Scan to join the room", "export_room": "Export chat", "user_admin": "Administrator", - "user_moderator": "Moderator" + "user_moderator": "Moderator", + "ui_options": "UI Options", + "audio_layout": "Use audio layout" }, "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..ff47abd --- /dev/null +++ b/src/components/AudioLayout.vue @@ -0,0 +1,406 @@ + + + + + \ No newline at end of file diff --git a/src/components/Chat.vue b/src/components/Chat.vue index da95b28..acecb6c 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -4,49 +4,39 @@ {{ $tc("room.invitations", invitationCount) }} -
+ + + + +
- + " :originalEvent="selectedEvent" />
- +
@@ -59,55 +49,31 @@
-
- +
+ -
+
- +
- + arrow_downward @@ -121,10 +87,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 +110,13 @@ - + " /> @@ -169,150 +125,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 +209,10 @@ {{ - $t("menu.cancel") + $t("menu.cancel") }} - {{ $t("menu.send") }} + {{ $t("menu.send") }}
@@ -363,7 +244,7 @@ {{ - $t("menu.ok") + $t("menu.ok") }} @@ -389,6 +270,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 +287,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 +295,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 +318,7 @@ export default { BottomSheet, AvatarOperations, CreatePollDialog, + AudioLayout }, data() { @@ -516,9 +399,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 +418,14 @@ export default { }, computed: { + chatContainer() { + const container = this.$refs.chatContainer; + console.log("GOT CONTAINER", container); + if (this.useAudioLayout) { + return container.$el; + } + return container; + }, senderDisplayName() { return this.room.getMember(this.replyToEvent.sender.userId).name; }, @@ -627,9 +522,28 @@ export default { me && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("redact", me.powerLevel); return isAdmin; }, + useAudioLayout: { + get: function () { + if (this.room) { + const tags = this.room.tags; + if (tags && tags["ui_options"]) { + return tags["ui_options"]["audio_layout"] === 1; + } + } + return false; + }, + } }, watch: { + initialLoadDone: { + immediate: true, + handler(value, oldValue) { + if (value && !oldValue) { + console.log("Loading finished!"); + } + } + }, roomId: { immediate: true, handler(value, oldValue) { @@ -729,6 +643,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 +782,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 +813,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 +823,20 @@ export default { return; // Not for this room } + const loadingDone = this.initialLoadDone; this.$matrix.matrixClient.decryptEventIfNeeded(event, {}); - if (this.initialLoadDone) { + if (this.initialLoadDone && !this.useAudioLayout) { 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); } }, @@ -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.useAudioLayout) { + 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; @@ -1369,65 +1287,78 @@ export default { * Start/restart the timer to Read Receipts. */ restartRRTimer() { + console.log("Restart RR timer"); this.stopRRTimer(); - this.rrTimer = setTimeout(this.rrTimerElapsed, READ_RECEIPT_TIMEOUT); + + let eventIdFirst = null; + let eventIdLast = null; + if (!this.useAudioLayout) { + const container = this.chatContainer; + const elFirst = util.getFirstVisibleElement(container); + const elLast = util.getLastVisibleElement(container); + 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) { + console.log("RR timer elapsed", 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 +1430,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/RoomExport.vue b/src/components/RoomExport.vue index 0010efb..7c082d3 100644 --- a/src/components/RoomExport.vue +++ b/src/components/RoomExport.vue @@ -31,7 +31,7 @@ + + {{ $t("room_info.members") }} @@ -319,7 +329,7 @@ export default { ], SHOW_MEMBER_LIMIT: 5, exporting: false, - }; + }; }, mounted() { this.$matrix.on("Room.timeline", this.onEvent); @@ -362,6 +372,26 @@ export default { } return ""; }, + + useAudioLayout: { + get: function () { + if (this.room) { + const tags = this.room.tags; + if (tags && tags["ui_options"]) { + return tags["ui_options"]["audio_layout"] === 1; + } + } + return false; + }, + set: function (audioLayout) { + if (this.room && this.room.tags) { + let options = this.room.tags["ui_options"] || {} + options["audio_layout"] = (audioLayout ? 1 : 0); + this.room.tags["ui_options"] = options; + this.$matrix.matrixClient.setRoomTag(this.room.roomId, "ui_options", options); + } + }, + } }, watch: { 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/attachmentMixin.js b/src/components/messages/attachmentMixin.js index 6a5a1fa..06b39c5 100644 --- a/src/components/messages/attachmentMixin.js +++ b/src/components/messages/attachmentMixin.js @@ -7,25 +7,41 @@ export default { downloadProgress: null } }, - mounted() { - console.log("Mounted with event:", JSON.stringify(this.event.getContent())) - util - .getAttachment(this.$matrix.matrixClient, this.event, (progress) => { - this.downloadProgress = progress; - console.log("Progress: " + progress); - }) - .then((url) => { - this.src = url; - }) - .catch((err) => { - console.log("Failed to fetch attachment: ", err); - }); - }, - beforeDestroy() { - if (this.src) { - const objectUrl = this.src; - this.src = null; - URL.revokeObjectURL(objectUrl); + watch: { + event: { + immediate: false, + handler(value, ignoredOldValue) { + this.loadAttachmentSource(value); + } } }, + mounted() { + this.loadAttachmentSource(this.event); + }, + beforeDestroy() { + this.loadAttachmentSource(null); // Release + }, + methods: { + loadAttachmentSource(event) { + if (this.src) { + const objectUrl = this.src; + this.src = null; + URL.revokeObjectURL(objectUrl); + } + if (event) { + util + .getAttachment(this.$matrix.matrixClient, event, (progress) => { + this.downloadProgress = progress; + console.log("Progress: " + progress); + }) + .then((url) => { + this.src = url; + }) + .catch((err) => { + console.log("Failed to fetch attachment: ", err); + }); + + } + } + } } \ No newline at end of file diff --git a/src/components/messages/messageMixin.js b/src/components/messages/messageMixin.js index 7aea0ba..d36bbc5 100644 --- a/src/components/messages/messageMixin.js +++ b/src/components/messages/messageMixin.js @@ -1,51 +1,51 @@ -import QuickReactions from './QuickReactions.vue'; -var linkify = require('linkifyjs'); -var linkifyHtml = require('linkifyjs/html'); +import QuickReactions from "./QuickReactions.vue"; +var linkify = require("linkifyjs"); +var linkifyHtml = require("linkifyjs/html"); linkify.options.defaults.className = "link"; -linkify.options.defaults.target = { url: '_blank' }; +linkify.options.defaults.target = { url: "_blank" }; export default { components: { - QuickReactions + QuickReactions, }, props: { room: { type: Object, default: function () { - return null - } + return null; + }, }, - event: { + originalEvent: { type: Object, default: function () { - return {} - } + return {}; + }, }, nextEvent: { type: Object, default: function () { - return null - } + return null; + }, }, timelineSet: { type: Object, default: function () { - return null - } + return null; + }, }, }, data() { return { + event: {}, inReplyToEvent: null, - inReplyToSender: null - } + inReplyToSender: null, + }; }, mounted() { - const relatesTo = this.event.getWireContent()['m.relates_to']; - if (relatesTo && relatesTo['m.in_reply_to']) - { + const relatesTo = this.validEvent && this.event.getWireContent()["m.relates_to"]; + if (relatesTo && relatesTo["m.in_reply_to"]) { // Can we find the original message? - const originalEventId = relatesTo['m.in_reply_to'].event_id; + const originalEventId = relatesTo["m.in_reply_to"].event_id; if (originalEventId && this.timelineSet) { const originalEvent = this.timelineSet.findEventById(originalEventId); if (originalEvent) { @@ -55,7 +55,29 @@ export default { } } }, + watch: { + originalEvent: { + immediate: true, + handler(originalEvent, ignoredOldValue) { + this.event = originalEvent; + // Check not null and not {} + if (originalEvent && originalEvent.isBeingDecrypted && originalEvent.isBeingDecrypted()) { + this.originalEvent.getDecryptionPromise().then(() => { + this.event = originalEvent; + }); + } + }, + }, + }, computed: { + /** + * + * @returns true if event is non-null and contains data + */ + validEvent() { + return this.event && Object.keys(this.event).length !== 0; + }, + incoming() { return this.event && this.event.getSender() != this.$matrix.currentUserId; }, @@ -67,21 +89,20 @@ export default { if (this.nextEvent && this.nextEvent.getSender() == this.event.getSender()) { const ts1 = this.nextEvent.event.origin_server_ts; const ts2 = this.event.event.origin_server_ts; - return (ts1 - ts2) < (2 * 60 * 1000); // less than 2 minutes + return ts1 - ts2 < 2 * 60 * 1000; // less than 2 minutes } return true; }, inReplyToText() { - const relatesTo = this.event.getWireContent()['m.relates_to']; - if (relatesTo && relatesTo['m.in_reply_to']) - { + const relatesTo = this.event.getWireContent()["m.relates_to"]; + if (relatesTo && relatesTo["m.in_reply_to"]) { const content = this.event.getContent(); - const lines = content.body.split('\n').reverse(); - while (lines.length && !lines[0].startsWith('> ')) lines.shift(); + const lines = content.body.split("\n").reverse(); + while (lines.length && !lines[0].startsWith("> ")) lines.shift(); // Reply fallback has a blank line after it, so remove it to prevent leading newline - if (lines[0] === '') lines.shift(); - const text = lines[0] && lines[0].replace(/^> (<.*> )?/g, ''); + if (lines[0] === "") lines.shift(); + const text = lines[0] && lines[0].replace(/^> (<.*> )?/g, ""); if (text) { return text; } @@ -92,23 +113,22 @@ export default { } // We don't have the original text (at the moment at least) - return this.$t('fallbacks.original_text'); + return this.$t("fallbacks.original_text"); } return null; }, messageText() { - const relatesTo = this.event.getWireContent()['m.relates_to']; - if (relatesTo && relatesTo['m.in_reply_to']) - { + const relatesTo = this.event.getWireContent()["m.relates_to"]; + if (relatesTo && relatesTo["m.in_reply_to"]) { const content = this.event.getContent(); // Remove the new text and strip "> " from the old original text - const lines = content.body.split('\n'); - while (lines.length && lines[0].startsWith('> ')) lines.shift(); + const lines = content.body.split("\n"); + while (lines.length && lines[0].startsWith("> ")) lines.shift(); // Reply fallback has a blank line after it, so remove it to prevent leading newline - if (lines[0] === '') lines.shift(); - return lines.join('\n'); + if (lines[0] === "") lines.shift(); + return lines.join("\n"); } return this.event.getContent().body; }, @@ -118,40 +138,36 @@ export default { */ messageClasses() { - return {'messageIn':true,'from-admin':this.senderIsAdminOrModerator(this.event)} + return { messageIn: true, "from-admin": this.senderIsAdminOrModerator(this.event) }; }, userAvatar() { 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); }, userAvatarLetter() { if (!this.$matrix.currentUser) { return null; } - return (this.$matrix.currentUserDisplayName || this.$matrix.currentUserId.substring(1)).substring(0, 1).toUpperCase(); - } + return (this.$matrix.currentUserDisplayName || this.$matrix.currentUserId.substring(1)) + .substring(0, 1) + .toUpperCase(); + }, }, methods: { ownAvatarClicked() { - this.$emit("own-avatar-clicked", {event: this.event}); + this.$emit("own-avatar-clicked", { event: this.event }); }, otherAvatarClicked(avatarRef) { - this.$emit("other-avatar-clicked", {event: this.event, anchor: avatarRef}); + this.$emit("other-avatar-clicked", { event: this.event, anchor: avatarRef }); }, showContextMenu(buttonRef) { - this.$emit("context-menu", {event: this.event,anchor: buttonRef}); + this.$emit("context-menu", { event: this.event, anchor: buttonRef }); }, /** @@ -159,7 +175,7 @@ export default { */ eventSenderDisplayName(event) { if (event.getSender() == this.$matrix.currentUserId) { - return this.$t('message.you'); + return this.$t("message.you"); } if (this.room) { const member = this.room.getMember(event.getSender()); @@ -173,12 +189,12 @@ export default { /** * In the case where the state_key points out a userId for an operation (e.g. membership events) * return the display name of the affected user. - * @param event - * @returns + * @param event + * @returns */ eventStateKeyDisplayName(event) { if (event.getStateKey() == this.$matrix.currentUserId) { - return this.$t('message.you'); + return this.$t("message.you"); } if (this.room) { const member = this.room.getMember(event.getStateKey()); @@ -193,13 +209,7 @@ export default { if (this.room) { const member = this.room.getMember(event.getSender()); if (member) { - return member.getAvatarUrl( - this.$matrix.matrixClient.getHomeserverUrl(), - 40, - 40, - "scale", - true - ); + return member.getAvatarUrl(this.$matrix.matrixClient.getHomeserverUrl(), 40, 40, "scale", true); } } return null; @@ -236,6 +246,6 @@ export default { linkify(text) { return linkifyHtml(text); - } + }, }, -} \ No newline at end of file +}; diff --git a/src/plugins/vuetify.js b/src/plugins/vuetify.js index 2614652..2f802b3 100644 --- a/src/plugins/vuetify.js +++ b/src/plugins/vuetify.js @@ -1,37 +1,25 @@ import Vue from 'vue'; import Vuetify from 'vuetify/lib'; -import icUser from '@/assets/icons/user.vue'; -import icPassword from '@/assets/icons/password.vue'; -import icEdit from '@/assets/icons/edit.vue'; -import icGlobe from '@/assets/icons/globe.vue'; -import icAddReaction from '@/assets/icons/addReaction.vue'; -import icPoll from '@/assets/icons/poll.vue'; + +// Import all .vue icons and process them, so they can be used +// as $vuetify.icons. +var icons = {} +function importAll(r) { + return r.keys().map(res => { + // Remove"./" + const parts = res.split("/"); + const iconName = parts[1].split(".")[0]; + icons[iconName] = { component: r(res).default }; + }); +} +importAll(require.context('@/assets/icons/', true, /\.vue$/)); + Vue.use(Vuetify); export default new Vuetify({ icons: { iconfont: 'md', - values: { - user: { - component: icUser - }, - password: { - component: icPassword - }, - edit: { - component: icEdit - }, - globe: { - component: icGlobe - }, - addReaction: { - component: icAddReaction - }, - poll: { - component: icPoll - }, - }, - user: icUser + values: icons, }, });