diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 0f1be491..dee271f1 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,8 +55,8 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); -export const chatLimit = 10 -export const totalMsgCount = 20 +export const chatLimit = 40 +export const totalMsgCount = 120 class ChatPage extends LitElement { static get properties() { return { @@ -1367,6 +1367,8 @@ class ChatPage extends LitElement { this.addToUpdateMessageHashmap = this.addToUpdateMessageHashmap.bind(this) this.getAfterMessages = this.getAfterMessages.bind(this) this.oldMessages = [] + this.lastReadMessageTimestamp = 0 + this.initUpdate = this.initUpdate.bind(this) } setOpenGifModal(value) { @@ -2006,42 +2008,42 @@ class ChatPage extends LitElement { document.addEventListener('keydown', this.initialChat) document.addEventListener('paste', this.pasteImage) - if (this.chatId) { - window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - key: this.chatId, - timestamp: Date.now() - })) - } - - let callback = (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - - this.isPageVisible = true - if (this.chatId) { - window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - key: this.chatId, - timestamp: Date.now() - })) - - } - } else { - this.isPageVisible = false - } - }) - } + // if (this.chatId) { + // window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + // key: this.chatId, + // timestamp: Date.now() + // })) + // } + + // let callback = (entries, observer) => { + // entries.forEach(entry => { + // if (entry.isIntersecting) { + + // this.isPageVisible = true + // if (this.chatId) { + // window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + // key: this.chatId, + // timestamp: Date.now() + // })) + + // } + // } else { + // this.isPageVisible = false + // } + // }) + // } - let options = { - root: null, - rootMargin: '0px', - threshold: 0.5 - } + // let options = { + // root: null, + // rootMargin: '0px', + // threshold: 0.5 + // } - // Create the observer with the callback function and options - this.observer = new IntersectionObserver(callback, options) - const mainContainer = this.shadowRoot.querySelector('.main-container') + // // Create the observer with the callback function and options + // this.observer = new IntersectionObserver(callback, options) + // const mainContainer = this.shadowRoot.querySelector('.main-container') - this.observer.observe(mainContainer) + // this.observer.observe(mainContainer) } disconnectedCallback() { @@ -2416,6 +2418,17 @@ class ChatPage extends LitElement { // this.selectedAddress = selectedAddress // }) + // }) + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 + // parentEpml.subscribe('chat_last_seen', async chatList => { + // const parsedChatList = JSON.parse(chatList) + // console.log({parsedChatList}, this.chatId) + // const findChatSeen = parsedChatList.find(chat=> chat.key === this.chatId) + // console.log({findChatSeen}) + // if(findChatSeen && this.lastReadMessageTimestamp !== findChatSeen.timestamp){ + // this.lastReadMessageTimestamp = findChatSeen.timestamp + + // } // }) parentEpml.imReady() @@ -2994,9 +3007,28 @@ class ChatPage extends LitElement { this.requestUpdate() } + findContent(identifier, data) { + const [type, id] = identifier.split('/'); + + if (type === 'group') { + for (let group of data.groups) { + if (group.groupId === parseInt(id, 10)) { + return group; + } + } + } else if (type === 'direct') { + for (let direct of data.direct) { + if (direct.address === id) { + return direct; + } + } + } + return null; + } + - async processMessages(messages, isInitial) { + async processMessages(messages, isInitial, isUnread) { const isReceipient = this.chatId.includes('direct') let decodedMessages = [] if(!this.webWorkerDecodeMessages){ @@ -3058,10 +3090,27 @@ class ChatPage extends LitElement { // TODO: Determine number of initial messages by screen height... // this.messagesRendered = this._messages - this.messagesRendered = { - messages: this._messages, - type: 'initial' + const lastReadMessageTimestamp = this.lastReadMessageTimestamp + + + if(isUnread){ + this.messagesRendered = { + messages: this._messages, + type: 'initialLastSeen', + lastReadMessageTimestamp + } + + window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + key: this.chatId, + timestamp: Date.now() + })) + } else { + this.messagesRendered = { + messages: this._messages, + type: 'initial' + } } + this.isLoadingMessages = false setTimeout(() => this.downElementObserver(), 500) @@ -3272,25 +3321,47 @@ class ChatPage extends LitElement { directSocketTimeout = setTimeout(pingDirectSocket, 45000) return } + if (initial === 0) { + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 if (noInitial) return - const cachedData = null let getInitialMessages = [] - if (cachedData && cachedData.length !== 0) { - const lastMessage = cachedData[cachedData.length - 1] - const newMessages = await parentEpml.request('apiCall', { + let isUnread = false + + const chatId = this.chatId + console.log('this.chatHeads', this.chatHeads) + const findContent = this.chatHeads.find((item)=> item.url === chatId) + const chatInfoTimestamp = findContent.timestamp || 0 + const lastReadMessageTimestamp = this.lastReadMessageTimestamp + + console.log({lastReadMessageTimestamp, chatInfoTimestamp}) + + if(lastReadMessageTimestamp && chatInfoTimestamp){ + if(lastReadMessageTimestamp < chatInfoTimestamp){ + isUnread = true + } + } + console.log({isUnread}) + if(isUnread){ + const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimit}&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${20}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` }) - getInitialMessages = [...newMessages] + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${20}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` + }) + getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] } else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` }) } + + - this.processMessages(getInitialMessages, true) + this.processMessages(getInitialMessages, true, isUnread) initial = initial + 1 @@ -3371,27 +3442,48 @@ class ChatPage extends LitElement { return } if (initial === 0) { + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 if (noInitial) return - const cachedData = null let getInitialMessages = [] - if (cachedData && cachedData.length !== 0) { + const lastReadMessageTimestamp = this.lastReadMessageTimestamp - const lastMessage = cachedData[cachedData.length - 1] + let isUnread = false + + const chatId = this.chatId + console.log('this.chatHeads', this.chatHeads) + const findContent = this.chatHeads.find((item)=> item.url === chatId) + const chatInfoTimestamp = findContent.timestamp || 0 + console.log({lastReadMessageTimestamp, chatInfoTimestamp}) - const newMessages = await parentEpml.request('apiCall', { - type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` - }) - getInitialMessages = [...newMessages] - }else { - getInitialMessages = await parentEpml.request('apiCall', { - type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` - }) + if(lastReadMessageTimestamp && chatInfoTimestamp){ + if(lastReadMessageTimestamp < chatInfoTimestamp){ + isUnread = true + } + } + console.log({isUnread}, '2') + if(isUnread){ + + + const getInitialMessagesBefore = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${20}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` + }) + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${20}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` + }) + getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] + } else { + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` + }) + } + + - } - this.processMessages(getInitialMessages, true) + this.processMessages(getInitialMessages, true, isUnread) initial = initial + 1 } else { diff --git a/plugins/plugins/core/components/ChatScroller-css.js b/plugins/plugins/core/components/ChatScroller-css.js index 028b0a79..d2ac074a 100644 --- a/plugins/plugins/core/components/ChatScroller-css.js +++ b/plugins/plugins/core/components/ChatScroller-css.js @@ -753,6 +753,17 @@ export const chatStyles = css` visibility: visible; } + .unread-divider { + width: 100%; + background: #9B111E; + padding: 5px; + color: #FAEBD7; + display: flex; + justify-content: center; + border-radius: 2px; + margin-top: 5px; + } + .blink-bg{ border-radius: 8px; animation: blinkingBackground 3s; diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 9b7cce0d..33b4ffc6 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -10,6 +10,7 @@ import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js' import { EmojiPicker } from 'emoji-picker-js' import { generateHTML } from '@tiptap/core' import isElectron from 'is-electron' +import localForage from 'localforage' import axios from 'axios' import Highlight from '@tiptap/extension-highlight' @@ -32,7 +33,9 @@ import '@vaadin/tooltip' import { chatLimit, totalMsgCount } from './ChatPage.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) - +const chatLastSeen = localForage.createInstance({ + name: "chat-last-seen", +}) let toggledMessage = {} const uid = new ShortUniqueId() @@ -259,7 +262,9 @@ class ChatScroller extends LitElement { this.oldMessages = [] this._upObserverhandler = this._upObserverhandler.bind(this) this.newListMessages = this.newListMessages.bind(this) + this.newListMessagesUnreadMessages = this.newListMessagesUnreadMessages.bind(this) this._downObserverHandler = this._downObserverHandler.bind(this) + this.isLastMessageBeforeUnread = this.isLastMessageBeforeUnread.bind(this) this.replaceMessagesWithUpdate = this.replaceMessagesWithUpdate.bind(this) this.__bottomObserverForFetchingMessagesHandler = this.__bottomObserverForFetchingMessagesHandler.bind(this) this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address @@ -273,6 +278,7 @@ class ChatScroller extends LitElement { this.isLoadingBefore = false this.isLoadingAfter = false this.disableAddingNewMessages = false + this.lastReadMessageTimestamp = null } addSeenMessage(val) { @@ -346,6 +352,57 @@ class ChatScroller extends LitElement { } + async newListMessagesUnreadMessages(newMessages, message, lastReadMessageTimestamp) { + const viewElement = this.shadowRoot.querySelector("#viewElement"); + + console.log('sup', lastReadMessageTimestamp); + let data = []; + const copy = [...newMessages]; + + let dividerPlaced = false; // To ensure the divider is added only once + + // Start from the end of the list (newest messages) + for (let i = copy.length - 1; i >= 0; i--) { + let newMessage = copy[i]; + + // Initialize a property for the divider + newMessage.isDivider = false; + + // Check if this is the message before which the divider should be placed + if (!dividerPlaced && newMessage.timestamp <= lastReadMessageTimestamp) { + console.log('true true') + newMessage.isDivider = true; + dividerPlaced = true; // Ensure the divider is only added once + break; // Exit once the divider is placed + } + } + + copy.forEach((newMessage, groupIndex) => { + const lastGroupedMessage = data[data.length - 1]; + + if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { + lastGroupedMessage.messages.push(newMessage); + } else { + data.push({ + messages: [newMessage], + ...newMessage + }); + } + }); + + console.log({ data }); + this.messagesToRender = data; + this.clearLoaders(); + this.requestUpdate(); + await this.updateComplete; + const findElement = this.shadowRoot.getElementById('unread-divider-id') + if (findElement) { + findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) + } + } + + + async addNewMessages(newMessages, type) { @@ -402,7 +459,7 @@ class ChatScroller extends LitElement { if (type === 'initial') { - this.viewElement.scrollTop = this.viewElement.scrollHeight + viewElement.scrollTop = viewElement.scrollHeight @@ -547,13 +604,17 @@ class ChatScroller extends LitElement { async updated(changedProperties) { if (changedProperties && changedProperties.has('messages')) { - + console.log({changedProperties}, this.messages) if (this.messages.type === 'initial') { this.addNewMessages(this.messages.messages, 'initial') - } else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) + } else if (this.messages.type === 'initialLastSeen') { + this.newListMessagesUnreadMessages(this.messages.messages, 'initialLastSeen', this.messages.lastReadMessageTimestamp) + + } + else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) else if(this.messages.type === 'newComingInAuto') this.addNewMessages(this.messages.messages, 'newComingInAuto') else if (this.messages.type === 'old') this.prependOldMessages(this.messages.messages) else if (this.messages.type === 'inBetween') this.newListMessages(this.messages.messages, this.messages.signature) @@ -567,6 +628,15 @@ class ChatScroller extends LitElement { } + isLastMessageBeforeUnread(message, formattedMessages) { + // if the message is the last one in the older messages list and its timestamp is before the user's last seen timestamp + if (message.timestamp < this.lastReadMessageTimestamp && formattedMessages.indexOf(message) === (formattedMessages.length - 21)) { + return true; + } + return false; + } + + render() { // let formattedMessages = this.messages.reduce((messageArray, message) => { // const currentMessage = this.updateMessageHash[message.signature] || message; @@ -617,10 +687,12 @@ class ChatScroller extends LitElement { formattedMessages, (formattedMessage) => formattedMessage.id, // Use .id as the unique key for formattedMessage. (formattedMessage) => html` + ${repeat( formattedMessage.messages, (message) => message.signature, (message, indexMessage) => html` + this.addSeenMessage(val)} .listSeenMessages=${this.listSeenMessages} chatId=${this.chatId} - >` + > + ${message.isDivider ? html`
Unread Messages Below
` : null} + + ` + )} + ` )}
@@ -708,7 +785,6 @@ class ChatScroller extends LitElement { async firstUpdated() { this.changeTheme() - window.addEventListener('storage', () => { const checkTheme = localStorage.getItem('qortalTheme')