From c44c5c714de799be12c100960875604e71992763 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Wed, 17 Feb 2021 11:59:07 +0100 Subject: [PATCH] Add "scroll to end" button Work on issue #15. --- src/assets/css/chat.scss | 7 + src/components/Chat.vue | 320 +++++++++++++++++++++++++-------------- 2 files changed, 210 insertions(+), 117 deletions(-) diff --git a/src/assets/css/chat.scss b/src/assets/css/chat.scss index 1edd287..0150e87 100644 --- a/src/assets/css/chat.scss +++ b/src/assets/css/chat.scss @@ -109,6 +109,7 @@ $admin-fg: white; } .input-area-outer { + position: relative; background-color: #ffffff; margin: 0; padding-left: 2 * $chat-standard-padding-s; @@ -149,6 +150,12 @@ $admin-fg: white; } } + .scroll-to-end { + position:absolute; + top:-64px; + right:16px; + } + .op-button { position: relative; display: inline-block; diff --git a/src/components/Chat.vue b/src/components/Chat.vue index 868a7d3..82d564d 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -9,19 +9,19 @@ @click.prevent="closeContextMenuIfOpen" >
- -
+ + -
- - -
- +
+ +
+
-
+
@@ -76,8 +87,23 @@ + + + arrow_downward + + -
REPLYING TO EVENT: {{ replyToEvent.getContent().body }}
+
+ REPLYING TO EVENT: {{ replyToEvent.getContent().body }} +
@@ -87,7 +113,13 @@ - - + + cancel - - {{ editedEvent ? 'save' : 'arrow_upward' }} + + {{ + editedEvent ? "save" : "arrow_upward" + }} @@ -169,19 +223,21 @@
- - - You are logged in as a guest - -
Unfortunately guests are not allowed to upload files.
-
- - - - Ok - -
-
+ + + You are logged in as a guest + +
Unfortunately guests are not allowed to upload files.
+
+ + + + Ok + +
+
@@ -281,6 +337,12 @@ export default { */ chatContainerSize: 0, + /** + * True if we should show the "scroll to end" marker in the chat. For now at least, we use a simple + * method here, basically just "if we can scroll, show it". + */ + showScrollToEnd: false, + /** Shows a dialog with info about an operation being disallowed for guests */ showNotAllowedForGuests: false, @@ -333,17 +395,24 @@ export default { // If we have sent a RR, use that as read marker (so we don't have to wait for server round trip) return this.lastRR.getId(); } - return this.fullyReadMarker || this.room.getEventReadUpTo(this.$matrix.currentUserId, false); + return ( + this.fullyReadMarker || + this.room.getEventReadUpTo(this.$matrix.currentUserId, false) + ); }, fullyReadMarker() { - const readEvent = this.room.getAccountData('m.fully_read'); + const readEvent = this.room.getAccountData("m.fully_read"); if (readEvent) { return readEvent.getContent().event_id; } return null; }, attachButtonDisabled() { - return this.editedEvent != null || this.replyToEvent != null || this.currentInput.length > 0; + return ( + this.editedEvent != null || + this.replyToEvent != null || + this.currentInput.length > 0 + ); }, sendButtonDisabled() { return this.currentInput.length == 0; @@ -373,8 +442,7 @@ export default { } } return "top:" + top + "px;left:" + left + "px"; - } - + }, }, watch: { @@ -384,7 +452,9 @@ export default { if (value && value == oldValue) { return; // No change. } - console.log("Chat: Current room changed to " + (value ? value : "null")); + console.log( + "Chat: Current room changed to " + (value ? value : "null") + ); // Clear old events this.events = []; @@ -398,76 +468,84 @@ export default { if (!this.room) { // Public room? - if (this.roomId && this.roomId.startsWith('#')) { + if (this.roomId && this.roomId.startsWith("#")) { this.onRoomNotJoined(); } return; // no room } - // Joined? - if (this.room.hasMembershipState(this.currentUser.user_id, "join")) { - // Yes, load everything - this.onRoomJoined(); - } else { - this.onRoomNotJoined(); - } - } + // Joined? + if (this.room.hasMembershipState(this.currentUser.user_id, "join")) { + // Yes, load everything + this.onRoomJoined(); + } else { + this.onRoomNotJoined(); + } + }, }, }, methods: { onRoomJoined() { - var initialEventId = this.readMarker; console.log("Read up to " + initialEventId); //initialEventId = null; - + this.timelineWindow = new TimelineWindow( - this.$matrix.matrixClient, - this.room.getUnfilteredTimelineSet(), - {} + this.$matrix.matrixClient, + this.room.getUnfilteredTimelineSet(), + {} ); const self = this; this.timelineWindow.load(initialEventId, 20).then(() => { console.log("This is", self); - self.events = self.timelineWindow.getEvents(); + self.events = self.timelineWindow.getEvents(); - const getMoreIfNeeded = function _getMoreIfNeeded() { - const container = self.$refs.chatContainer; - if (container.scrollHeight <= container.clientHeight && - self.timelineWindow && - self.timelineWindow.canPaginate(EventTimeline.BACKWARDS)) { - return self.timelineWindow.paginate(EventTimeline.BACKWARDS, 10, true) - .then(success => { - if (success) { - self.events = self.timelineWindow.getEvents(); - return _getMoreIfNeeded.call(self); - } else { - return Promise.reject("Failed to paginate"); - } - }); - } else { - return Promise.resolve("Done"); - } - }.bind(self); + const getMoreIfNeeded = function _getMoreIfNeeded() { + const container = self.$refs.chatContainer; + if ( + container.scrollHeight <= container.clientHeight && + self.timelineWindow && + self.timelineWindow.canPaginate(EventTimeline.BACKWARDS) + ) { + return self.timelineWindow + .paginate(EventTimeline.BACKWARDS, 10, true) + .then((success) => { + if (success) { + self.events = self.timelineWindow.getEvents(); + return _getMoreIfNeeded.call(self); + } else { + return Promise.reject("Failed to paginate"); + } + }); + } else { + return Promise.resolve("Done"); + } + }.bind(self); - getMoreIfNeeded() - .catch(err => { + getMoreIfNeeded() + .catch((err) => { console.log("ERROR " + err); }) .finally(() => { self.initialLoadDone = true; - if (initialEventId) { - self.scrollToEvent(initialEventId); - } - self.restartRRTimer(); + if (initialEventId) { + self.scrollToEvent(initialEventId); + } + self.restartRRTimer(); }); - }); + }); }, onRoomNotJoined() { - this.$navigation.push({ name: "Join", params: { roomId: util.sanitizeRoomId(this.roomAliasOrId) }}, 0); + this.$navigation.push( + { + name: "Join", + params: { roomId: util.sanitizeRoomId(this.roomAliasOrId) }, + }, + 0 + ); }, touchX(event) { @@ -594,6 +672,10 @@ export default { ) { this.handleScrolledToBottom(false); } + this.showScrollToEnd = + container.scrollHeight - container.scrollTop.toFixed(0) > + container.clientHeight; + this.restartRRTimer(); }, onEvent(event) { @@ -631,7 +713,7 @@ export default { } else { const index = this.typingMembers.indexOf(member); if (index > -1) { - this.typingMembers.splice(index, 1); + this.typingMembers.splice(index, 1); } } console.log("Typing: ", this.typingMembers); @@ -669,7 +751,7 @@ export default { // this.showNotAllowedForGuests = true; // return; // } - this.$refs.attachment.click() + this.$refs.attachment.click(); }, /** @@ -776,8 +858,8 @@ export default { }, /** - * Scroll so that the given event is at the middle of the chat view (if more events) or else at the bottom. - */ + * 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 ref = this.$refs[eventId]; @@ -825,13 +907,14 @@ export default { }, redact(event) { - this.$matrix.matrixClient.redactEvent(event.getRoomId(), event.getId()) - .then(() => { - console.log("Message redacted"); - }) - .catch(err => { - console.log("Redaction failed: ", err); - }) + this.$matrix.matrixClient + .redactEvent(event.getRoomId(), event.getId()) + .then(() => { + console.log("Message redacted"); + }) + .catch((err) => { + console.log("Redaction failed: ", err); + }); }, download(event) { @@ -906,10 +989,10 @@ export default { this.rrTimer = null; } }, - + /** - * Start/restart the timer to Read Receipts. - */ + * Start/restart the timer to Read Receipts. + */ restartRRTimer() { this.stopRRTimer(); this.rrTimer = setInterval(this.rrTimerElapsed, READ_RECEIPT_TIMEOUT); @@ -919,30 +1002,33 @@ export default { const container = this.$refs.chatContainer; const el = util.getLastVisibleElement(container); if (el) { - const eventId = el.getAttribute('eventId'); + const eventId = el.getAttribute("eventId"); if (eventId && this.room) { const event = this.room.findEventById(eventId); if (event && (!this.lastRR || event.getTs() > this.lastRR.getTs())) { - // Disable timer while we are sending clearInterval(this.rrTimer); this.rrTimer = null; // Send read receipt - this.$matrix.matrixClient.sendReadReceipt(event) - .then(() => { - this.$matrix.matrixClient.setRoomReadMarkers(this.room.roomId, eventId) - }) - .then(() => { - console.log("RR sent for event: " + eventId); - this.lastRR = event; - }) - .catch(err => { - console.log("Failed to update read marker: ", err); - }) - .finally(() => { - this.restartRRTimer(); - }); + this.$matrix.matrixClient + .sendReadReceipt(event) + .then(() => { + this.$matrix.matrixClient.setRoomReadMarkers( + this.room.roomId, + eventId + ); + }) + .then(() => { + console.log("RR sent for event: " + eventId); + this.lastRR = event; + }) + .catch((err) => { + console.log("Failed to update read marker: ", err); + }) + .finally(() => { + this.restartRRTimer(); + }); } } } @@ -959,7 +1045,7 @@ export default { dayForEvent(event) { return util.formatDay(event.getTs()); - } + }, }, };