From 14895357a3f4b586435aa49b6f2b10fe1f1b0ad6 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Tue, 17 Sep 2024 09:43:53 +0200 Subject: [PATCH 001/163] Handle reverse ordering of events --- src/assets/css/chat.scss | 6 ++- src/components/Chat.vue | 102 +++++++++++++++++++++++++++------------ 2 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss index 0636372..423abd4 100644 --- a/src/assets/css/chat.scss +++ b/src/assets/css/chat.scss @@ -341,8 +341,12 @@ body { .scroll-to-end { position: absolute; - top: -64px; + bottom: 20px; right: 16px; + &.reversed { + top: 120px; + transform: rotate(180deg); + } } .op-button { diff --git a/src/components/Chat.vue b/src/components/Chat.vue index b165bcb..55a0db9 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -16,8 +16,8 @@ :readMarker="readMarker" :recordingMembers="typingMembers" v-on:start-recording="setShowRecorder()" - v-on:loadnext="handleScrolledToBottom(false)" - v-on:loadprevious="handleScrolledToTop()" + v-on:loadnext="handleScrolledToLatest(false)" + v-on:loadprevious="handleScrolledToOldest()" v-on:mark-read="sendRR" v-on:sendclap="sendClapReactionAtTime" /> @@ -58,7 +58,7 @@
-
{{ dayForEvent(event) }}
+
{{ dayForEvent(event) }}
@@ -67,7 +67,10 @@ touchStart(e, event); } " v-on:touchend="touchEnd" v-on:touchcancel="touchCancel" v-on:touchmove="touchMove"> - -
{{ $t('message.unread_messages') }}
+
{{ $t('message.unread_messages') }}
+ + +
{{ dayForEvent(event) }}
+ + + + + arrow_downward + + +
- - - arrow_downward - -
@@ -512,7 +521,8 @@ export default { heartPosition: { top: 0, left: 0 - } + }, + reverseOrder: false }; }, @@ -692,12 +702,12 @@ export default { (!e.getPrevContent() || e.getPrevContent().membership != "join") && e.getStateKey() == this.$matrix.currentUserId) { // Our own join event. - return this.events.slice(idx + 1); + return this.reverseOrder ? this.events.slice(idx + 1).toReversed() : this.events.slice(idx + 1); } } } } - return this.events; + return this.reverseOrder ? this.events.toReversed() : this.events; }, roomCreatedByUsRecently() { @@ -1080,7 +1090,7 @@ export default { }); } else { // Can't paginate, just scroll to bottom of window! - this.smoothScrollToEnd(); + this.smoothScrollToLatest(); } }, @@ -1150,7 +1160,7 @@ export default { this.$nextTick(() => { const container = this.chatContainer; if (container && container.scrollHeight <= container.clientHeight) { - this.handleScrolledToTop(); + this.handleScrolledToOldest(); } }); }, @@ -1162,15 +1172,23 @@ export default { const bufferHeight = container.clientHeight * WINDOW_BUFFER_SIZE; if (container.scrollTop <= bufferHeight) { // Scrolled to top - this.handleScrolledToTop(); + if (this.reverseOrder) { + this.handleScrolledToLatest(false); + } else { + this.handleScrolledToOldest(); + } } else if (container.scrollHeight - container.scrollTop.toFixed(0) - container.clientHeight <= bufferHeight) { - this.handleScrolledToBottom(false); + if (this.reverseOrder) { + this.handleScrolledToOldest(); + } else { + this.handleScrolledToLatest(false); + } } this.showScrollToEnd = container.scrollHeight === container.clientHeight ? false - : container.scrollHeight - container.scrollTop.toFixed(0) > container.clientHeight || + : (this.reverseOrder ? (container.scrollTop.toFixed(0) > 0) : (container.scrollHeight - container.scrollTop.toFixed(0) > container.clientHeight)) || (this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS)); this.restartRRTimer(); @@ -1265,11 +1283,14 @@ export default { var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll const container = this.chatContainer; if (container) { - if (container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) { + if (this.reverseOrder && container.scrollTop.toFixed(0) == 0) { + scrollToSeeNew = true; + } + else if (!this.reverseOrder && container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) { scrollToSeeNew = true; } } - this.handleScrolledToBottom(scrollToSeeNew); + this.handleScrolledToLatest(scrollToSeeNew); // If kick or ban event, redirect to "goodbye"... if (event.getType() === "m.room.member" && @@ -1494,7 +1515,7 @@ export default { }); }, - handleScrolledToTop() { + handleScrolledToOldest() { if ( this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.BACKWARDS) && @@ -1505,7 +1526,7 @@ export default { .paginate(EventTimeline.BACKWARDS, 10, true) .then((success) => { if (success && this.scrollPosition) { - this.scrollPosition.prepareFor("up"); + this.scrollPosition.prepareFor(this.reverseOrder ? "down" : "up"); this.setEvents(this.timelineWindow.getEvents()); this.$nextTick(() => { // restore scroll position! @@ -1520,7 +1541,7 @@ export default { } }, - handleScrolledToBottom(scrollToEnd) { + handleScrolledToLatest(smoothScrollToLatest) { if ( this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS) && @@ -1533,13 +1554,13 @@ export default { if (success) { this.setEvents(this.timelineWindow.getEvents()); if (!this.useVoiceMode && this.scrollPosition) { - this.scrollPosition.prepareFor("down"); + this.scrollPosition.prepareFor(this.reverseOrder ? "up" : "down"); this.$nextTick(() => { // restore scroll position! console.log("Restore scroll!"); this.scrollPosition.restore(); - if (scrollToEnd) { - this.smoothScrollToEnd(); + if (smoothScrollToLatest) { + this.smoothScrollToLatest(); } }); } @@ -1555,6 +1576,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) { + console.log("Scroll to event", eventId); const container = this.chatContainer; const ref = this.$refs[eventId]; if (container && ref) { @@ -1562,7 +1584,7 @@ export default { const item = ref[0].getBoundingClientRect(); let offsetY = (parent.bottom - parent.top) / 2; if (ref[0].clientHeight > offsetY) { - offsetY = Math.max(0, (parent.bottom - parent.top) - ref[0].clientHeight); + offsetY = this.reverseOrder ? 0 : Math.max(0, (parent.bottom - parent.top) - ref[0].clientHeight); } const targetY = parent.top + offsetY; const currentY = item.top; @@ -1573,10 +1595,21 @@ export default { } }, - smoothScrollToEnd() { + smoothScrollToLatest() { this.$nextTick(function () { const container = this.chatContainer; if (container && container.children.length > 0) { + if (this.reverseOrder) { + const firstChild = container.children[0]; + console.log("Scroll into view", firstChild); + window.requestAnimationFrame(() => { + firstChild.scrollIntoView({ + behavior: "smooth", + block: "end", + inline: "nearest", + }); + }); + } else { const lastChild = container.children[container.children.length - 1]; console.log("Scroll into view", lastChild); window.requestAnimationFrame(() => { @@ -1587,6 +1620,7 @@ export default { }); }); } + } }); }, @@ -1809,8 +1843,14 @@ export default { 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 (this.reverseOrder) { + // For reverse order, the "first visible" is actually later in time, so swap them + eventIdFirst = elLast.getAttribute("eventId"); + eventIdLast = elFirst.getAttribute("eventId"); + } else { + eventIdFirst = elFirst.getAttribute("eventId"); + eventIdLast = elLast.getAttribute("eventId"); + } } } if (eventIdFirst && eventIdLast) { From e3bfede77ed038694f5211ab61c2d6b0ec1a4008 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Tue, 24 Sep 2024 11:17:17 +0200 Subject: [PATCH 002/163] Style channel messages a bit differently --- src/assets/css/channel.scss | 24 +++++++++++++++++++++ src/assets/css/chat.scss | 1 + src/components/Chat.vue | 8 ++++--- src/components/chatMixin.js | 7 ++++-- src/components/messages/MessageIncoming.vue | 2 +- src/components/messages/MessageOutgoing.vue | 2 +- 6 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/assets/css/channel.scss diff --git a/src/assets/css/channel.scss b/src/assets/css/channel.scss new file mode 100644 index 0000000..2dc7fca --- /dev/null +++ b/src/assets/css/channel.scss @@ -0,0 +1,24 @@ +.chat-root.channel { + background-color: #f2f2f2; + .chat-content { + width: 100%; + max-width: 700px; + align-self: center; + background-color: white; + } + + .messageOut, .messageIn { + display: flex; + flex-wrap: wrap; + .senderAndTime { + flex: 0 0 100%; + } + .content { + flex: 1 1 auto; + } + .bubble { + width: 100%; + max-width: 100%; + } + } +} \ No newline at end of file diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss index 423abd4..4fae2ca 100644 --- a/src/assets/css/chat.scss +++ b/src/assets/css/chat.scss @@ -2,6 +2,7 @@ @import "@/assets/css/main.scss"; @import "@/assets/css/vendors/v-emoji-picker"; @import "@/assets/css/filedrop.scss"; +@import "@/assets/css/channel.scss"; $admin-bg: black; $admin-fg: white; diff --git a/src/components/Chat.vue b/src/components/Chat.vue index 55a0db9..4625045 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -1,5 +1,5 @@ @@ -465,6 +467,7 @@ export default { currentSendShowSendButton: true, currentSendError: null, currentSendErrorExceededFile: null, + attachmentCaption: undefined, showEmojiPicker: false, selectedEvent: null, editedEvent: null, @@ -593,6 +596,12 @@ export default { isCurrentFileInputsAnArray() { return Array.isArray(this.currentFileInputs) }, + showAttachmentCaptionInput() { + // IFF we are sending one PDF, add option to set caption. + const imageFiles = this.imageFiles || []; + const otherFiles = this.nonImageFiles || []; + return imageFiles.length == 0 && otherFiles.length == 1 && (otherFiles[0].type === "application/pdf" || (otherFiles[0].name || "").endsWith(".pdf")); + }, currentFileInputsDialog: { get() { return this.isCurrentFileInputsAnArray @@ -1547,6 +1556,7 @@ export default { const promise = this.sendAttachments(text, this.currentFileInputs); promise.then(() => { this.currentFileInputs = null; + this.attachmentCaption = undefined; this.sendingStatus = this.sendStatuses.INITIAL; }) .catch((err) => { @@ -1565,6 +1575,7 @@ export default { this.$refs.attachment.value = null; this.cancelSendAttachments(); this.currentFileInputs = null; + this.attachmentCaption = undefined; this.currentSendError = null; this.currentSendErrorExceededFile = null; this.sendingStatus = this.sendStatuses.INITIAL; diff --git a/src/components/messages/MessageIncomingThread.vue b/src/components/messages/MessageIncomingThread.vue index 9b0888b..62e15c0 100644 --- a/src/components/messages/MessageIncomingThread.vue +++ b/src/components/messages/MessageIncomingThread.vue @@ -1,5 +1,5 @@