From 1e333b7412a29083016f19ca9c6aa68eab578f1c Mon Sep 17 00:00:00 2001 From: Mal <=> Date: Wed, 10 Mar 2021 22:54:27 +0100 Subject: [PATCH] Extendable type area added and missed messages fixed --- config.xml | 4 +- src/app/chat.message.ts | 1 + src/app/chat/chat.component.html | 13 ++- src/app/chat/chat.component.ts | 186 +++++++++++++++++++++++++------ src/app/websocket.service.ts | 3 +- src/global.scss | 50 +++++++++ 6 files changed, 214 insertions(+), 43 deletions(-) diff --git a/config.xml b/config.xml index 56e1864..4248593 100644 --- a/config.xml +++ b/config.xml @@ -1,7 +1,7 @@ - + METAsocket - WowApp's awesome instant messenger + WowApp's awesome instant messenger for the whole Greifentanzgeschwader sabolli diff --git a/src/app/chat.message.ts b/src/app/chat.message.ts index 68433c0..1dd6f3e 100644 --- a/src/app/chat.message.ts +++ b/src/app/chat.message.ts @@ -5,4 +5,5 @@ export interface ChatMessage username: string; message: string; datetime: string; + htmlMessage: string; } diff --git a/src/app/chat/chat.component.html b/src/app/chat/chat.component.html index 54f1aba..8f50991 100644 --- a/src/app/chat/chat.component.html +++ b/src/app/chat/chat.component.html @@ -3,16 +3,19 @@
- -
+ +
{{message.username}}
-
{{message.message}}
+
{{message.datetime}}
-
- +
+
+ +
+
diff --git a/src/app/chat/chat.component.ts b/src/app/chat/chat.component.ts index fa12e7b..5f0368d 100644 --- a/src/app/chat/chat.component.ts +++ b/src/app/chat/chat.component.ts @@ -22,10 +22,13 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W userToken: string; userId: number; url: string; + chatText: string; @ViewChild('chatPostArea') chatPostArea: ElementRef; @ViewChild('errorMessage') errorMessage: ElementRef; - chatText: string; + @ViewChild('chatTextArea') chatTextArea: ElementRef; + @ViewChild('chatTypeArea') chatTypeArea: ElementRef; + @ViewChild('buttonSend') buttonSend: ElementRef; private oldScrollHeight = 0; private messageOffset = 0; @@ -82,6 +85,8 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W (response) => { response.forEach( (message: ChatMessage) => { + message.htmlMessage = this.parseHtml(message.message); + this.messages.push(message); this.messageOffset++; } @@ -116,35 +121,39 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W if (this.chatPostArea.nativeElement.scrollTop === 0) { this.apiService.getChatHistory(this.userToken, this.messageOffset, this.messageLimit).subscribe( (response) => { - this.messages = response.concat(this.messages); - this.messageOffset += this.messageLimit; + response.reverse(); + + response.forEach( + (message: ChatMessage) => { + message.htmlMessage = this.parseHtml(message.message); + + this.messages = [message].concat(this.messages); + this.messageOffset++; + } + ); + this.hasBeenReloaded = true; } ); } } - onTextInput(event: Event): void { - if (!(event instanceof KeyboardEvent)) { - return; + onInput(event): void { + this.chatTypeArea.nativeElement.style.height = '50px'; + this.chatTypeArea.nativeElement.style.height = event.target.scrollHeight > 50 ? event.target.scrollHeight + 'px' : '50px'; + + if (this.chatTypeArea.nativeElement.clientHeight > window.innerHeight) { + this.chatTypeArea.nativeElement.style.height = window.innerHeight + 'px'; } + } - switch (event.key) { - case 'Enter': - event.preventDefault(); - - if (this.chatText.trim() === '') { - return; - } - - this.websocketService.sendChatMessage(this.chatText); - this.chatText = ''; - + postMessage(): void { + if (this.chatTextArea.nativeElement.value.trim() === '') { return; + } - default: - return; - } + this.websocketService.sendChatMessage(this.chatTextArea.nativeElement.value); + this.chatTextArea.nativeElement.value = ''; } onLogout() { @@ -154,10 +163,12 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W } onChatMessage(message: ChatMessage): void { + message.htmlMessage = this.parseHtml(message.message); + this.messages.push(message); this.messageOffset++; - if (message.userId === this.userId) { + if (this.hasFocus || message.userId === this.userId) { return; } @@ -168,21 +179,29 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W { this.errorMessage.nativeElement.style.display = 'none'; - this.apiService.getChatMessagesMissed(this.userToken, this.messages[this.messages.length - 1].id).toPromise() - .then( - (messagesMissed) => { - if (messagesMissed.length === 0) { - return; - } + this.enableSendButton(); - this.messages.concat(messagesMissed); - this.messageOffset += messagesMissed.length; - } - ).catch( - (error) => { - console.log('Failed to load messages missed after reconnect!', error); - } - ); + if (this.isReconnection) { + this.apiService.getChatMessagesMissed(this.userToken, this.messages[this.messages.length - 1].id).toPromise() + .then( + (messagesMissed) => { + if (messagesMissed.length === 0) { + return; + } + + for (const message of messagesMissed) { + message.htmlMessage = this.parseHtml(message.message); + + this.messages.push(message); + this.messageOffset++; + } + } + ).catch( + (error) => { + console.log('Failed to load messages missed after reconnect!', error); + } + ); + } this.isReconnection = true; } @@ -196,6 +215,27 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W { this.errorMessage.nativeElement.style.display = 'block'; this.errorMessage.nativeElement.innerText = message; + this.disableSendButton(); + } + + citeUsername(username: string): void + { + this.insertIntoTextareaCursorPosition('@' + username); + } + + citeMessage(username: string, message: string): void + { + const originalMessage = []; + + message.split('\n').forEach( + (paragraph: string) => { + if (paragraph.substr(0, 1) !== '>') { + originalMessage.push(paragraph); + } + } + ); + + this.insertIntoTextareaCursorPosition('> @' + username + ': ' + originalMessage.join('\n') + '\n'); } triggerNotification(message: ChatMessage): void @@ -228,4 +268,80 @@ export class ChatComponent implements OnInit, AfterViewInit, AfterViewChecked, W return false; } + + private parseHtml(messageText: string): string + { + let parsedText = ''; + let isInsideCite = false; + let isInsideUsername = false; + + for (const char of messageText) { + switch (char) { + case '>': + if (isInsideCite) { + parsedText += '
'; + } + + parsedText += '
'; + isInsideCite = true; + break; + + case '\n': + parsedText += isInsideCite ? '
' : '
'; + break; + + default: + if (char === ' ' && isInsideUsername) { + isInsideUsername = false; + parsedText += ''; + } + + parsedText += char; + } + } + + if (isInsideCite) { + parsedText += ''; + } + + const matches = parsedText.match(/(\s|^)@\w+/g); + + if (matches === null) { + return parsedText; + } + + matches.forEach( + (match) => { + parsedText = parsedText.replace(match, '' + match.substr(match.indexOf('@') + 1) + ''); + } + ); + + return parsedText; + } + + private insertIntoTextareaCursorPosition(text: string): void + { + const textarea = this.chatTextArea.nativeElement; + + const textLength = textarea.value.length; + const messagePartA = textarea.value.substr(0, textarea.selectionStart) + (textarea.selectionStart > 0 ? '\n' : ''); + const messagePartB = textarea.value.substr(textarea.selectionStart, textLength); + + textarea.value = messagePartA.trim() + text + messagePartB.trim(); + textarea.focus(); + } + + private enableSendButton(): void + { + this.buttonSend.nativeElement.classList.add('button-send-online'); + this.buttonSend.nativeElement.classList.remove('button-send-offline'); + this.buttonSend.nativeElement.disabled = false; + } + + private disableSendButton(): void + { + this.buttonSend.nativeElement.classList.remove('button-send-online'); + this.buttonSend.nativeElement.classList.add('button-send-offline'); + this.buttonSend.nativeElement.disabled = true; + } } diff --git a/src/app/websocket.service.ts b/src/app/websocket.service.ts index 514935c..3ce4709 100644 --- a/src/app/websocket.service.ts +++ b/src/app/websocket.service.ts @@ -69,7 +69,8 @@ export class WebsocketService { userId: messageReceived.userId, username: this.userList.get(messageReceived.userId), datetime: messageReceived.datetime, - message: messageReceived.message + message: messageReceived.message, + htmlMessage: '', }; this.listener.onChatMessage(message); diff --git a/src/global.scss b/src/global.scss index fdcdc21..a1e70dd 100644 --- a/src/global.scss +++ b/src/global.scss @@ -40,12 +40,22 @@ html, body { #chat-type-area { position: fixed; height: 50px; + background-color: #cccccc; left: 0; right: 0; bottom: 0; box-shadow: 0 0 20px rgba(0, 0, 0, 0.9); } +#textarea-container { + position: absolute; + left: 0; + top: 0; + right: 50px; + bottom: 0; + height: 100%; +} + #chat-textarea { width: 100%; height: 100%; @@ -54,10 +64,13 @@ html, body { font-size: 16px; color: white; border: 2px grey solid; + border-bottom: 2px solid white; + border-right: 2px solid white; box-sizing: border-box; padding: 5px; resize: none; outline: none; + overflow: hidden; } .chat-post { @@ -196,3 +209,40 @@ html, body { display: none; z-index: 10; } + +.message-cite { + padding: 5px; + background-color: green; + border-left: 8px solid #005500; + font-style: italic; + margin: 5px 0; +} + +.message-cite-username { + font-style: italic; + font-weight: bold; +} + +#button-send { + position: absolute; + right: 0; + width: 50px; + height: 50px; + top: 0; + background-image: url("assets/graphics/button_send.svg"); + background-size: contain; + background-position: center center; + background-repeat: no-repeat; + border-radius: 0; + border: 2px solid white; + border-bottom: 2px solid grey; + border-right: 2px solid grey; +} + +.button-send-online { + background-color: green; +} + +.button-send-offline { + background-color: #550000; +}