diff --git a/chatto/src/main/java/org/ros/chatto/repository/ChatMessageRepository.java b/chatto/src/main/java/org/ros/chatto/repository/ChatMessageRepository.java index 084a399..b5e098a 100644 --- a/chatto/src/main/java/org/ros/chatto/repository/ChatMessageRepository.java +++ b/chatto/src/main/java/org/ros/chatto/repository/ChatMessageRepository.java @@ -26,7 +26,7 @@ public interface ChatMessageRepository extends JpaRepository public List getNewMessages(String fromUser, String toUser, Date lastMessageTime); @Query("select m from ChatMessage m join fetch m.messageCipher where (m.toUser.userName = ?1 or m.toUser.userName = ?2) and " - + "(m.fromUser.userName = ?1 or m.fromUser.userName = ?2) order by m.messageTime asc") + + "(m.fromUser.userName = ?1 or m.fromUser.userName = ?2) order by m.messageTime desc") public List getAllMessages(String fromUser, String toUser, PageRequest pageRequest); diff --git a/chatto/src/main/java/org/ros/chatto/service/ChatServiceImpl.java b/chatto/src/main/java/org/ros/chatto/service/ChatServiceImpl.java index 78dfe29..079bc16 100644 --- a/chatto/src/main/java/org/ros/chatto/service/ChatServiceImpl.java +++ b/chatto/src/main/java/org/ros/chatto/service/ChatServiceImpl.java @@ -14,6 +14,7 @@ import org.ros.chatto.repository.ChatMessageRepository; import org.ros.chatto.repository.MessageCipherRepository; import org.ros.chatto.repository.UserRepository; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/chatto/src/main/javascript/ts/src/model/ChatModel.ts b/chatto/src/main/javascript/ts/src/model/ChatModel.ts index 446a1cb..3c3c3e1 100644 --- a/chatto/src/main/javascript/ts/src/model/ChatModel.ts +++ b/chatto/src/main/javascript/ts/src/model/ChatModel.ts @@ -19,10 +19,12 @@ export class ChatModel implements Subject { */ private readonly _observers: Observer[] = []; private state: ChatMessageViewModel[] | null; + private readonly _messagePageMap: Map; private readonly _messagesMap: Map; constructor() { this.state = null; + this._messagePageMap = new Map(); this._messagesMap = new Map(); } /** @@ -39,10 +41,14 @@ export class ChatModel implements Subject { console.log('Subject: Detached an observer.'); } - private setUserMessages(username: string, messages: ChatMessageViewModel[]) { + private storeUserMessages(username: string, messages: ChatMessageViewModel[]) { this._messagesMap.set(username, messages); } + private getStoredUserMessages(username: string): ChatMessageViewModel[] { + return this._messagesMap.get(username)!; + } + /** * Trigger an update in each subscriber. */ @@ -60,13 +66,35 @@ export class ChatModel implements Subject { this.notify("some user"); } - public async getmessages(contactName: string, passphrase: string, lastMessageTime: string | null): Promise { - const cVMs = await ChatModelHelper.getMessages(contactName, passphrase, lastMessageTime, this); + public async getMessages(contactName: string, passphrase: string, lastMessageTime: string | null): Promise { + if(this._messagePageMap.get(contactName) == null) + this._messagePageMap.set(contactName, 0); + else { + log.debug('page number before = ' + this._messagePageMap.get(contactName)!) + this._messagePageMap.set(contactName, this._messagePageMap.get(contactName)! + 1); + log.debug('page number after = ' + this._messagePageMap.get(contactName)!) + } + const pageNumber = this._messagePageMap.get(contactName) + const cVMs = await ChatModelHelper.getMessages(contactName, passphrase, pageNumber!, lastMessageTime, this); if (cVMs != null) { log.info('Subject: My state has just changed') - log.debug(cVMs); + // this._messagesMap.set(userName, cVMs); - this.setUserMessages(contactName, cVMs); + const existingMessages = this.getStoredUserMessages(contactName); + log.debug(existingMessages); + log.debug(cVMs); + if (existingMessages != null) { + // existingMessages.forEach(function (elem) { + // cVMs.push(elem); + // }) + const newArr = cVMs.concat(existingMessages) + // log.debug(newArr); + this.storeUserMessages(contactName, newArr); + // this.storeUserMessages(contactName, cVMs); + } + else { + this.storeUserMessages(contactName, cVMs); + } JsonAPI.contactName = contactName; this.notify(contactName); } diff --git a/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts b/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts index 9ec0482..5bd6760 100644 --- a/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts +++ b/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts @@ -15,11 +15,11 @@ import { Sprintf } from "../singleton/Sprintf"; export class ChatModelHelper { private static readonly _encryptionService: EncryptionService = new SJCLEncryptionService(); - public static async getMessages(userName: string, passphrase: string, lastMessageTime: string | null, chatModel: ChatModel): Promise { + public static async getMessages(userName: string, passphrase: string, page: number | null, lastMessageTime: string | null, chatModel: ChatModel): Promise { switch (lastMessageTime) { case null: { - const data: ChatMessageDTO[] = await this.getAllMessagesAjax(userName); - return data.map(vm => this.toChatMessageVM(vm, passphrase)); + const data: ChatMessageDTO[] = await this.getPaginatedMessagesAjax(userName, page!); + return data.map(vm => this.toChatMessageVM(vm, passphrase)).reverse(); } default: { const data: ChatMessageDTO[] = await this.getNewMessagesAjax(userName, lastMessageTime); @@ -41,6 +41,32 @@ export class ChatModelHelper { } private static async getAllMessagesAjax(toUser: string): Promise { + const headers = new Headers(); + if (JsonAPI.authToken == null) { + log.error("authToken null"); + return; + }; + headers.append('X-AUTH-TOKEN', JsonAPI.authToken); + const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser); + // const url = Sprintf(JsonAPI.CHAT_MESSAGE_PAGE_GET, toUser,1,5); + log.debug(url) + // const response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}`, { + // method: 'GET', + // headers: headers + // }); + const response = await fetch(url, { + method: 'GET', + headers: headers + }); + console.log(response.clone()); + if (fetchErrorHandler(response.clone())) { + return null; + } + const data: Promise = await response.json(); + return data; + } + + private static async getPaginatedMessagesAjax(toUser: string, page: number): Promise { const headers = new Headers(); if (JsonAPI.authToken == null) { log.error("authToken null"); @@ -48,7 +74,7 @@ export class ChatModelHelper { }; headers.append('X-AUTH-TOKEN', JsonAPI.authToken); // const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser); - const url = Sprintf(JsonAPI.CHAT_MESSAGE_PAGE_GET, toUser,1,5); + const url = Sprintf(JsonAPI.CHAT_MESSAGE_PAGE_GET, toUser, page, 5); log.debug(url) // const response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}`, { // method: 'GET', diff --git a/chatto/src/main/javascript/ts/src/singleton/JsonAPI.ts b/chatto/src/main/javascript/ts/src/singleton/JsonAPI.ts index 488f5c7..b3bef81 100644 --- a/chatto/src/main/javascript/ts/src/singleton/JsonAPI.ts +++ b/chatto/src/main/javascript/ts/src/singleton/JsonAPI.ts @@ -7,29 +7,28 @@ export namespace JsonAPI { /** * Json API URL for retrieving all messages between two users * - * @format /api/chat/get/messages/some-user + * @format /api/chat/get/messages/{contactName} + * @example /api/chat/get/messages/some-user * * ### With sprintf - * const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser); - * - * @param toUser - * The user whose messages wish to retrieve. The 'from user' part of the message is given by the current user logged in. - * /api/chat/get/messages/{toUser} + * const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, contactName); * + * @param contactName + * The user whose messages wish to retrieve. */ export const CHAT_MESSAGES_GET = `/api/chat/get/messages/%s`; export const MESSAGE_POST = '/api/chat/post/message'; /** * Json API URL for retrieving paginated messages between two users - * - * @format /api/chat/get/messages/some-user?page=1&size=5 will give the first page with size 5 + * Page index starts with 0 + * @example /api/chat/get/messages/some-user?page=0&size=5 will give the first page where each page has size 5 * * ### With sprintf - * const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser); + * const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, contactName); * - * @param toUser The user whose messages wish to retrieve. The 'from user' part of the message is given by the current user logged in. - * @param page Denotes the page required - * @param denotes the size of each page + * @param contactName the user whose messages wish to retrieve + * @param page denotes the page required + * @param size denotes the size of each page */ export const CHAT_MESSAGE_PAGE_GET = `/api/chat/get/messages/%s?page=%d&size=%d`; diff --git a/chatto/src/main/javascript/ts/src/view/ChatView.ts b/chatto/src/main/javascript/ts/src/view/ChatView.ts index 05c5964..b4b3872 100644 --- a/chatto/src/main/javascript/ts/src/view/ChatView.ts +++ b/chatto/src/main/javascript/ts/src/view/ChatView.ts @@ -32,24 +32,28 @@ export class ChatView implements Observer { this._messageContainer = deps.messageContainer; this._chatModel = deps.chatModel; this._messageSendTemplate = deps.messageSendTemplate; - this._messageReceiveTemplate = deps.messageReceiveTemplate; - this._markdownService = deps.markdownService; - this._encryptionService = deps.encryptionService; + this._messageReceiveTemplate = deps.messageReceiveTemplate; + this._markdownService = deps.markdownService; + this._encryptionService = deps.encryptionService; this.addEventListeners(); - $(document).ready(function() { - $('#action_menu_btn').click(function() { + $(document).ready(function () { + $('#action_menu_btn').click(function () { $('.action_menu').toggle(); }); }); + + this.chatMessagePageLoadAjax(); } update(data: ChatMessageViewModel[]): void { log.info('ChatView: updating view'); // let html: string = ""; this._messageContainer.innerHTML = ""; + const rev: ChatMessageViewModel[] = Object.create(data) + rev.reverse(); data.forEach((vm: ChatMessageViewModel) => { - const vmTemp = vm; + const vmTemp: ChatMessageViewModel = {...vm}; vmTemp.message = this._markdownService.render(vm.message); /** Very Important!!! * Sanitizing HTML before displaying on webpage to prevent XSS attacks!! @@ -63,7 +67,8 @@ export class ChatView implements Observer { rendered = DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp)); } $(this._messageContainer).append(rendered); - log.debug() + // log.debug(vm) + // log.debug(vmTemp) // html += this._messageSendTemplate(vm); }); @@ -94,8 +99,7 @@ export class ChatView implements Observer { let contactName = JsonAPI.contactName; - if(contactName == null) - { + if (contactName == null) { log.error("Contact name is null"); return; } @@ -162,4 +166,29 @@ export class ChatView implements Observer { .then(response => fetchHandler(response)); } + + chatMessagePageLoadAjax() { + this._messageContainer.addEventListener('scroll', (e) => { + if ($(this._messageContainer).scrollTop() == 0) { + log.debug('Reached top') + // @ts-ignore + let passphrase: string; + let passphraseInput = document.getElementById('passphrase') as HTMLInputElement; + + if (passphraseInput == null) { + log.error('passphraseInput element reference is null'); + return; + } + passphrase = passphraseInput.value + if (passphrase == '' || passphrase == null) { + // alert('Please input passphrase') + // alertify.error('Please enter a passphrase'); + log.error('passphrase is empty or null'); + return; + } + if(JsonAPI.contactName != null) + this._chatModel.getMessages(JsonAPI.contactName , passphrase, null); + } + }) + } } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/view/UserView.ts b/chatto/src/main/javascript/ts/src/view/UserView.ts index eac4408..3d6e6e7 100644 --- a/chatto/src/main/javascript/ts/src/view/UserView.ts +++ b/chatto/src/main/javascript/ts/src/view/UserView.ts @@ -106,9 +106,7 @@ export class UserView implements Observer { let userName = elem.innerText; // @ts-ignore: Object is possibly 'null'. document.getElementById('user-name-span').innerText = userName; - this._chatModel.getmessages(userName, passphrase, null); - // populateMessages(userName, passphrase); - sessionStorage.setItem('selectedUser', userName); + this._chatModel.getMessages(userName, passphrase, null); el.className += " active"; }