diff --git a/component/src/views/chat/messages/fileMessageUtils.ts b/component/src/views/chat/messages/fileMessageUtils.ts index 95ed4ec6..60ab411d 100644 --- a/component/src/views/chat/messages/fileMessageUtils.ts +++ b/component/src/views/chat/messages/fileMessageUtils.ts @@ -8,11 +8,17 @@ export class FileMessageUtils { public static readonly FILE_BUBBLE_CLASS = 'file-message'; // prettier-ignore - public static addMessage( - messages: MessagesBase, elements: MessageElements, styles: keyof MessageStyles, role: string, isTop: boolean) { + public static setElementProps( + messages: MessagesBase, elements: MessageElements, styles: keyof MessageStyles, role: string) { if (styles === 'loading') return; messages.applyCustomStyles(elements, role, true, messages.messageStyles?.[styles]); elements.bubbleElement.classList.add(FileMessageUtils.FILE_BUBBLE_CLASS); + } + + // prettier-ignore + public static addMessage( + messages: MessagesBase, elements: MessageElements, styles: keyof MessageStyles, role: string, isTop: boolean) { + FileMessageUtils.setElementProps(messages, elements, styles, role); if (!isTop) { messages.elementRef.appendChild(elements.outerContainer); messages.elementRef.scrollTop = messages.elementRef.scrollHeight; diff --git a/component/src/views/chat/messages/fileMessages.ts b/component/src/views/chat/messages/fileMessages.ts index a16595cc..20660edb 100644 --- a/component/src/views/chat/messages/fileMessages.ts +++ b/component/src/views/chat/messages/fileMessages.ts @@ -2,29 +2,30 @@ import {MessageFile, MessageFiles} from '../../../types/messageFile'; import {SVGIconUtils} from '../../../utils/svg/svgIconUtils'; import {FILE_ICON_STRING} from '../../../icons/fileIcon'; import {Browser} from '../../../utils/browser/browser'; +import {MessageStyles} from '../../../types/messages'; import {FileMessageUtils} from './fileMessageUtils'; import {MessageUtils} from './messageUtils'; -import {Messages} from './messages'; +import {MessagesBase} from './messagesBase'; export class FileMessages { private static readonly IMAGE_BUBBLE_CLASS = 'image-message'; private static readonly AUDIO_BUBBLE_CLASS = 'audio-message'; private static readonly ANY_FILE_BUBBLE_CLASS = 'any-file-message'; - private static createImage(imageData: MessageFile, messagesContainerEl: HTMLElement, isTop: boolean) { + private static createImage(imageData: MessageFile, messagesContainerEl: HTMLElement, isTop: boolean, scroll = true) { const imageElement = new Image(); imageElement.src = imageData.src as string; - if (!isTop) FileMessageUtils.scrollDownOnImageLoad(imageElement.src, messagesContainerEl); + if (!isTop && scroll) FileMessageUtils.scrollDownOnImageLoad(imageElement.src, messagesContainerEl); return FileMessageUtils.processContent('image', imageElement, imageElement.src, imageData.name); } // WORK - image still does not scroll down when loaded - private static async addNewImageMessage(messages: Messages, imageData: MessageFile, role: string, isTop: boolean) { - const image = FileMessages.createImage(imageData, messages.elementRef, isTop); - const elements = messages.createNewMessageElement('', role); + private static createImageMessage(msg: MessagesBase, imageD: MessageFile, role: string, isTop: boolean, scroll = true) { + const image = FileMessages.createImage(imageD, msg.elementRef, isTop, scroll); + const elements = msg.createNewMessageElement('', role); elements.bubbleElement.appendChild(image); elements.bubbleElement.classList.add(FileMessages.IMAGE_BUBBLE_CLASS); - FileMessageUtils.addMessage(messages, elements, 'image', role, isTop); + return {type: 'image', elements}; } private static createAudioElement(audioData: MessageFile, role: string) { @@ -41,12 +42,12 @@ export class FileMessages { return audioElement; } - private static addNewAudioMessage(messages: Messages, audioData: MessageFile, role: string, isTop: boolean) { + private static createNewAudioMessage(messages: MessagesBase, audioData: MessageFile, role: string, isTop: boolean) { const audioElement = FileMessages.createAudioElement(audioData, role); const elements = messages.createMessageElementsOnOrientation('', role, isTop); elements.bubbleElement.appendChild(audioElement); elements.bubbleElement.classList.add(FileMessages.AUDIO_BUBBLE_CLASS); - FileMessageUtils.addMessage(messages, elements, 'audio', role, isTop); + return {type: 'audio', elements}; } private static createAnyFile(imageData: MessageFile) { @@ -65,25 +66,32 @@ export class FileMessages { return FileMessageUtils.processContent('any', contents, imageData.src, fileNameElement.textContent); } - private static addNewAnyFileMessage(messages: Messages, data: MessageFile, role: string, isTop: boolean) { + private static createNewAnyFileMessage(messages: MessagesBase, data: MessageFile, role: string, isTop: boolean) { const elements = messages.createMessageElementsOnOrientation('', role, isTop); const anyFile = FileMessages.createAnyFile(data); elements.bubbleElement.classList.add(FileMessages.ANY_FILE_BUBBLE_CLASS); elements.bubbleElement.appendChild(anyFile); - FileMessageUtils.addMessage(messages, elements, 'file', role, isTop); + return {type: 'file', elements}; } - // no overwrite previous message logic as it is complex to track which files are to be overwritten - public static addMessages(messages: Messages, files: MessageFiles, role: string, isTop: boolean) { - files.forEach((fileData) => { + public static createMessages(msg: MessagesBase, files: MessageFiles, role: string, isTop = false, scroll = true) { + return files.map((fileData) => { if (fileData.ref) fileData = FileMessageUtils.removeFileRef(fileData); if (FileMessageUtils.isAudioFile(fileData)) { - FileMessages.addNewAudioMessage(messages, fileData, role, isTop); - } else if (FileMessageUtils.isImageFile(fileData)) { - FileMessages.addNewImageMessage(messages, fileData, role, isTop); - } else { - FileMessages.addNewAnyFileMessage(messages, fileData, role, isTop); + return FileMessages.createNewAudioMessage(msg, fileData, role, isTop); + } + if (FileMessageUtils.isImageFile(fileData)) { + return FileMessages.createImageMessage(msg, fileData, role, isTop, scroll); } + return FileMessages.createNewAnyFileMessage(msg, fileData, role, isTop); + }); + } + + // no overwrite previous message logic as it is complex to track which files are to be overwritten + public static addMessages(messages: MessagesBase, files: MessageFiles, role: string, isTop: boolean) { + const typeToElements = FileMessages.createMessages(messages, files, role, isTop); + typeToElements.forEach(({type, elements}) => { + FileMessageUtils.addMessage(messages, elements, type as keyof MessageStyles, role, isTop); }); } } diff --git a/component/src/views/chat/messages/messageUtils.ts b/component/src/views/chat/messages/messageUtils.ts index ce007a66..c9cccd21 100644 --- a/component/src/views/chat/messages/messageUtils.ts +++ b/component/src/views/chat/messages/messageUtils.ts @@ -1,10 +1,12 @@ import {MessageBody, MessageBodyElements, MessageContentI, MessageToElements} from '../../../types/messagesInternal'; +import {MessageContent, MessageStyles} from '../../../types/messages'; import {LoadingStyle} from '../../../utils/loading/loadingStyle'; -import {MessageContent} from '../../../types/messages'; +import {MessageFile} from '../../../types/messageFile'; import {FileMessageUtils} from './fileMessageUtils'; import {HTMLMessages} from './html/htmlMessages'; import {Avatars} from '../../../types/avatars'; import {MessagesBase} from './messagesBase'; +import {FileMessages} from './fileMessages'; import {MessageElements} from './messages'; import {Names} from '../../../types/names'; import {Avatar} from './avatar'; @@ -196,6 +198,15 @@ export class MessageUtils { }); } + private static removeTextHTMLMessage(msg: MessagesBase, messageToEls: MessageToElements[0], type: 'text' | 'html') { + const elemsToRemove = messageToEls[1][type]; + const removalElsIndex = msg.messageElementRefs.findIndex((messageElements) => messageElements === elemsToRemove); + msg.messageElementRefs.splice(removalElsIndex, 1); + elemsToRemove?.outerContainer.remove(); + delete messageToEls[0][type]; + delete messageToEls[1][type]; + } + private static changeHTMLMessage(msg: MessagesBase, messageToEls: MessageToElements[0], newHTML: string) { if (messageToEls[1].html) { HTMLMessages.overwriteElements(msg, newHTML, messageToEls[1].html); @@ -211,13 +222,24 @@ export class MessageUtils { messageToEls[0].html = newHTML; } - private static removeTextMessage(msg: MessagesBase, messageToEls: MessageToElements[0]) { - const elemsToRemove = messageToEls[1].text; - const removalElsIndex = msg.messageElementRefs.findIndex((messageElements) => messageElements === elemsToRemove); - msg.messageElementRefs.splice(removalElsIndex, 1); - elemsToRemove?.outerContainer.remove(); - delete messageToEls[0].text; - delete messageToEls[1].text; + private static changeFileMessages(msg: MessagesBase, messageToEls: MessageToElements[0], newFiles: MessageFile[]) { + const role = messageToEls[0].role; + const typeToElements = FileMessages.createMessages(msg, newFiles, role); + const beforeElement = + messageToEls[1].html?.outerContainer || + messageToEls[1].files?.[messageToEls[1].files?.length - 1].outerContainer?.nextSibling || + messageToEls[1].text?.outerContainer?.nextSibling; + typeToElements.forEach(({type, elements}) => { + FileMessageUtils.setElementProps(msg, elements, type as keyof MessageStyles, role); + msg.elementRef.insertBefore(elements.outerContainer, beforeElement as Node); + }); + if (messageToEls[1].files) { + // remove the existing ones + } else { + // const nextMsgElsIndex = msg.messageElementRefs.findIndex((messageElements) => messageElements === nextElements); + } + messageToEls[1].files = typeToElements.map(({elements}) => elements); + messageToEls[0].files = newFiles; } private static changeTextMessage(msg: MessagesBase, messageToEls: MessageToElements[0], newText: string) { @@ -225,7 +247,7 @@ export class MessageUtils { msg.renderText(messageToEls[1].text.bubbleElement, newText); } else { const messageElements = msg.createElements(newText, messageToEls[0].role); - const nextElements = (messageToEls[1].html || messageToEls[1].files?.[0]) as MessageElements; + const nextElements = (messageToEls[1].files?.[0] || messageToEls[1].html) as MessageElements; msg.elementRef.insertBefore(messageElements.outerContainer, nextElements.outerContainer); const nextMsgElsIndex = msg.messageElementRefs.findIndex((messageElements) => messageElements === nextElements); msg.messageElementRefs.splice(nextMsgElsIndex, 0, messageElements); @@ -238,13 +260,19 @@ export class MessageUtils { if (messageToEls) { if (messageBody.text) { MessageUtils.changeTextMessage(msg, messageToEls, messageBody.text); - } else if (messageToEls[1].text) { - MessageUtils.removeTextMessage(msg, messageToEls); } if (messageBody.html) { MessageUtils.changeHTMLMessage(msg, messageToEls, messageBody.html); - } else if (messageToEls[1].text) { - // MessageUtils.removeTextMessage(msg, messageToEls); + } + if (messageBody.files) { + MessageUtils.changeFileMessages(msg, messageToEls, messageBody.files); + } + // Important to remove after elements are changed as existing element indexes are used + if (!messageBody.text && messageToEls[1].text) { + MessageUtils.removeTextHTMLMessage(msg, messageToEls, 'text'); + } + if (!messageBody.html && messageToEls[1].html) { + MessageUtils.removeTextHTMLMessage(msg, messageToEls, 'html'); } } else { console.error('Message index not found. Please use the `getMessages` method to find the correct index');