Handle reverse ordering of events

This commit is contained in:
N-Pex 2024-09-17 09:43:53 +02:00
parent 1178d4bb07
commit 14895357a3
2 changed files with 76 additions and 32 deletions

View file

@ -341,8 +341,12 @@ body {
.scroll-to-end {
position: absolute;
top: -64px;
bottom: 20px;
right: 16px;
&.reversed {
top: 120px;
transform: rotate(180deg);
}
}
.op-button {

View file

@ -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 @@
<div v-for="(event, index) in filteredEvents" :key="event.getId()" :eventId="event.getId()">
<!-- DAY Marker, shown for every new day in the timeline -->
<div v-if="showDayMarkerBeforeEvent(event) && !!componentForEvent(event, isForExport = false)" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div>
<div v-if="!reverseOrder && showDayMarkerBeforeEvent(event) && !!componentForEvent(event, isForExport = false)" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div>
<div v-if="!event.isRelation() && !event.isRedaction()" :ref="event.getId()">
<MessageErrorHandler>
@ -67,7 +67,10 @@
touchStart(e, event);
}
" v-on:touchend="touchEnd" v-on:touchcancel="touchCancel" v-on:touchmove="touchMove">
<!-- Note: For threaded media messages, IF there is only one item we show that media item as a single component.
<div v-if="reverseOrder && event.getId() == readMarker && index > 0" class="read-marker"><div class="line"></div><div class="text">{{ $t('message.unread_messages') }}</div><div class="line"></div></div>
<!-- Note: For threaded media messages, IF there is only one item we show that media item as a single component.
We might therefore get calls to v-on:context-menu that has the event set to that single media item, not the top level thread event
that is really displayed in the flow. Therefore, we rewrite these events with "{event: event, anchor: $event.anchor}",
see below. Otherwise things like context menus won't work as designed.
@ -89,24 +92,30 @@
/>
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
<!-- <div v-if="debugging" style="user-select:text">Event: {{ JSON.stringify(event) }}<br /><br /></div> -->
<div v-if="event.getId() == readMarker && index < filteredEvents.length - 1" class="read-marker"><div class="line"></div><div class="text">{{ $t('message.unread_messages') }}</div><div class="line"></div></div>
<div v-if="!reverseOrder && event.getId() == readMarker && index < filteredEvents.length - 1" class="read-marker"><div class="line"></div><div class="text">{{ $t('message.unread_messages') }}</div><div class="line"></div></div>
</div>
</MessageErrorHandler>
</div>
<!-- Day marker when reverseOrder is set -->
<div v-if="reverseOrder && showDayMarkerBeforeEvent(event) && !!componentForEvent(event, isForExport = false)" class="day-marker"><div class="line"></div><div class="text">{{ dayForEvent(event) }}</div><div class="line"></div></div>
</div>
<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"
@click.stop="scrollToEndOfTimeline">
<v-icon color="white">arrow_downward</v-icon>
</v-btn>
</div>
<!-- Input area -->
<v-container v-if="!useVoiceMode && !useFileModeNonAdmin && room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']">
<div :class="[replyToEvent ? 'iput-area-inner-box' : '']">
<!-- "Scroll to end"-button -->
<v-btn v-if="!useVoiceMode" class="scroll-to-end" v-show="showScrollToEnd" fab x-small elevation="0" color="black"
@click.stop="scrollToEndOfTimeline">
<v-icon color="white">arrow_downward</v-icon>
</v-btn>
<v-row class="ma-0 pa-0">
<div v-if="replyToEvent" class="row">
<div class="col">
@ -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) {