+
@@ -360,6 +372,10 @@ export default {
lxmfMessagesRequestSequence: 0,
chatItems: [],
+ isLoadingPrevious: false,
+ loadPreviousObserver: null,
+ hasMorePrevious: true,
+
newMessageText: "",
newMessageImage: null,
newMessageImageUrl: null,
@@ -398,19 +414,112 @@ export default {
// listen for websocket messages
WebSocketConnection.on("message", this.onWebsocketMessage);
- this.reload();
+ // setup intersection observer
+ this.loadPreviousObserver = new IntersectionObserver((entries) => {
+ const loadMoreElement = entries[0];
+ if(loadMoreElement && loadMoreElement.isIntersecting){
+ this.loadPrevious();
+ }
+ });
},
methods: {
close() {
this.$emit("close");
},
- reload() {
+ onMessagesScroll(event) {
+
+ // check if messages is scrolled to bottom
+ const element = event.target;
+ const isAtBottom = element.scrollTop === (element.scrollHeight - element.offsetHeight);
+
+ // we want to auto scroll if user is at bottom of messages list
+ this.autoScrollOnNewMessage = isAtBottom;
+
+ },
+ async initialLoad() {
+
+ // reset
this.chatItems = [];
- if(this.selectedPeer){
- this.getPeerPath(this.selectedPeer.destination_hash);
- this.loadLxmfMessages(this.selectedPeer.destination_hash);
+ this.hasMorePrevious = true;
+ if(!this.selectedPeer){
+ return;
}
+
+ this.getPeerPath(this.selectedPeer.destination_hash);
+
+ // load 1 page of previous messages
+ await this.loadPrevious();
+
+ // scroll to bottom
+ this.scrollMessagesToBottom();
+
+ // setup auto loading previous
+ this.loadPreviousObserver.disconnect();
+ this.loadPreviousObserver.observe(document.querySelector("#load-previous"));
+
+ },
+ async loadPrevious() {
+
+ // do nothing if already loading
+ if(this.isLoadingPrevious){
+ return;
+ }
+
+ this.isLoadingPrevious = true;
+
+ // when scrolled to top, scroll down a bit to prevent the browser infinitely loading all history...
+ const container = document.getElementById("messages");
+ if(container && container.scrollTop === 0){
+ container.scrollTop = 50;
+ }
+
+ try {
+
+ const seq = ++this.lxmfMessagesRequestSequence;
+
+ // fetch lxmf messages from "us to destination" and from "destination to us"
+ const response = await window.axios.get(`/api/v1/lxmf-messages/conversation/${this.selectedPeer.destination_hash}`, {
+ params: {
+ count: 30,
+ order: "desc",
+ after_id: this.oldestMessageId,
+ },
+ });
+
+ // do nothing if response is for a previous request
+ if(seq !== this.lxmfMessagesRequestSequence){
+ console.log("ignoring response for previous lxmf messages request")
+ return;
+ }
+
+ // convert lxmf messages to chat items
+ const chatItems = [];
+ const lxmfMessages = response.data.lxmf_messages;
+ for(const lxmfMessage of lxmfMessages){
+ chatItems.push({
+ "type": "lxmf_message",
+ "is_outbound": this.myLxmfAddressHash === lxmfMessage.source_hash,
+ "lxmf_message": lxmfMessage,
+ });
+ }
+
+ // add messages to start of existing messages
+ for(const chatItem of chatItems){
+ this.chatItems.unshift(chatItem);
+ }
+
+ // no more previous to load if previous list is empty
+ if(chatItems.length === 0){
+ this.hasMorePrevious = false;
+ }
+
+ } catch(e) {
+ // do nothing
+ } finally {
+ this.isLoadingPrevious = false;
+ }
+
},
async onWebsocketMessage(message) {
const json = JSON.parse(message.data);
@@ -519,46 +628,16 @@ export default {
onDestinationPathClick(path) {
DialogUtils.alert(`${path.hops} ${ path.hops === 1 ? 'hop' : 'hops' } away via ${path.next_hop_interface}`);
},
- async loadLxmfMessages(destinationHash) {
- const seq = ++this.lxmfMessagesRequestSequence;
- try {
-
- // fetch lxmf messages from "us to destination" and from "destination to us"
- const response = await window.axios.get(`/api/v1/lxmf-messages/conversation/${destinationHash}`);
-
- // do nothing if response is for a previous request
- if(seq !== this.lxmfMessagesRequestSequence){
- console.log("ignoring response for previous lxmf messages request")
- return;
- }
-
- // convert lxmf messages to chat items
- const chatItems = [];
- const lxmfMessages = response.data.lxmf_messages;
- for(const lxmfMessage of lxmfMessages){
- chatItems.push({
- "type": "lxmf_message",
- "is_outbound": this.myLxmfAddressHash === lxmfMessage.source_hash,
- "lxmf_message": lxmfMessage,
- });
- }
-
- // update ui
- this.chatItems = chatItems;
-
- // scroll to bottom
- this.scrollMessagesToBottom();
-
- } catch(e) {
- // do nothing if failed to load messages
- }
- },
scrollMessagesToBottom: function() {
- Vue.nextTick(() => {
- const container = document.getElementById("messages");
- if(container){
- container.scrollTop = container.scrollHeight;
- }
+ // next tick waits for the ui to have the new elements added
+ this.$nextTick(() => {
+ // set timeout with zero millis seems to fix issue where it doesn't scroll all the way to the bottom...
+ setTimeout(() => {
+ const container = document.getElementById("messages");
+ if(container){
+ container.scrollTop = container.scrollHeight;
+ }
+ }, 0);
});
},
isLxmfMessageInUi: function(hash) {
@@ -585,7 +664,7 @@ export default {
}
// reload conversation
- await this.loadLxmfMessages(this.selectedPeer.destination_hash);
+ await this.initialLoad();
// reload conversations
this.$emit("reload-conversations");
@@ -1114,10 +1193,23 @@ export default {
return [];
},
+ selectedPeerChatItemsReversed() {
+ // ensure a copy of the array is returned in reverse order
+ return this.selectedPeerChatItems.map((message) => message).reverse();
+ },
+ oldestMessageId() {
+
+ if(this.selectedPeerChatItems.length > 0){
+ return this.selectedPeerChatItems[0].lxmf_message.id;
+ }
+
+ return null;
+
+ },
},
watch: {
selectedPeer() {
- this.reload();
+ this.initialLoad();
},
async selectedPeerChatItems() {