/** * Conversation chat window functions * * @author Pierre HUBERT */ const ConvChatWindow = { /** * @var {Object} __conversationsCache Chat windows cache */ __conversationsCache: {}, /** * Open a new conversation window * * @param {Integer} conversationID The ID of the window to open * @return {Boolean} True for a success */ openConversation: function(conversationID){ //Log action ComunicWeb.debug.logMessage("Opening conversation " + conversationID); //Create a conversation window var conversationWindow = this.create({ target: byId(ComunicWeb.components.conversations.manager.__conversationsContainerID), conversationID: conversationID, }); //Load the conversation this.load(conversationID, conversationWindow); //Success return true; }, /** * Create a new chat window * * @param {Object} infos Informations required for the new chat window * @info {HTMLElement} target The target of the new chat window * @info {Integer} conversationID The ID of the target conversation * @return {Object} Informations about the new chat window */ create: function(infos){ //Log action ComunicWeb.debug.logMessage("Create a new chat window"); //First, create the generic conversation window var infosBox = ComunicWeb.components.conversations.windows.create(infos.target.children[0]); //Save conversation ID infosBox.conversationID = infos.conversationID; //Change box root class name infosBox.rootElem.className += " chat-window direct-chat direct-chat-primary"; //Adapt close button behaviour infosBox.closeFunction = function(){ //Remove root element infosBox.rootElem.remove(); //Remove the conversation from opened ones ComunicWeb.components.conversations.cachingOpened.remove(infosBox.conversationID); //Unload conversation ComunicWeb.components.conversations.chatWindows.unload(infosBox.conversationID); } infosBox.closeButton.onclick = infosBox.closeFunction; //Debug //Create messages container infosBox.messagesArea = createElem2({ appendTo: infosBox.boxBody, type: "div", class: "direct-chat-messages", innerHTML: "", }); //Add button to get conversation members infosBox.membersButton = createElem2({ type: "button", insertBefore: infosBox.closeButton, elemType: "button", class: "btn btn-box-tool", title: "Conversation members" }); infosBox.membersButton.setAttribute("data-toggle", "tooltip"); infosBox.membersButton.setAttribute("data-widget", "chat-pane-toggle"); //Add button icon var buttonIcon = createElem("i", infosBox.membersButton); buttonIcon.className = "fa fa-users"; //Add conversation members pane var membersPane = createElem("div", infosBox.boxBody); membersPane.className = "direct-chat-contacts"; //Add conversation members list infosBox.membersList = createElem("ul", membersPane); infosBox.membersList.className = "contacts-list"; //Add send a message form this.addMessageform(infosBox); //Return informations about the chat window return infosBox; }, /** * Add a message form to the chat windows * * @param {Object} infosBox Informations about the chat box * @return {Boolean} True for a success */ addMessageform: function(infosBox){ //Create form container var conversationFormContainer = createElem2({ appendTo: infosBox.boxFooter, type: "form", class: "create-message-form" }); //Create input group var inputGroup = createElem2({ appendTo: conversationFormContainer, type: "div", class: "input-group" }); //Create text input (for message) var inputText = createElem2({ appendTo: inputGroup, type: "textarea", class: "form-control", placeholder: tr("New message..."), }); inputText.maxLength = ServerConfig.conf.max_conversation_message_len; //Enable textarea 2.0 on the message var textarea2 = new ComunicWeb.components.textarea(); textarea2.init({ element: inputText, minHeight: "34px", autosize: true, }); //Create file input (for optionnal file) var fileInput = createElem2({ type: "input", elemType: "file", }); fileInput.accept = ServerConfig.conf.allowed_conversation_files_type.join(", "); //Create button group var buttonGroup = createElem2({ appendTo: inputGroup, type: "span", class: "input-group-btn", }); //Add emojie button var emojiButton = createElem2({ appendTo: buttonGroup, type: "button", elemType: "button", class: "btn btn-flat btn-add-emoji", }); createElem2({ type: "i", appendTo: emojiButton, class: "fa fa-smile-o" }); //Make emojie button lives ComunicWeb.components.emoji.picker.addPicker(inputText, emojiButton, function(){ //Make the emojie picker visible wdtEmojiBundle.popup.style.top = (abs_height_bottom_screen()-357)+"px"; //Make the smile button visible var currLeft = Number(wdtEmojiBundle.popup.style.left.replace("px", "")); var potentialLeft = currLeft - 20; if(potentialLeft > 0) wdtEmojiBundle.popup.style.left = potentialLeft + "px"; }); // =========== SEND FILES =========== //Add image button const fileButton = createElem2({ appendTo: buttonGroup, type: "button", elemType: "button", class: "btn btn-flat btn-add-image", }); fileButton.onclick = function(){ //Call file selector fileInput.click(); }; //Add image icon createElem2({ type: "i", appendTo: fileButton, class: "fa fa-plus" }); //Add send button var sendButton = createElem2({ appendTo: buttonGroup, type: "button", class: "btn btn-primary btn-flat", elemType: "submit", }); //Add send icon createElem2({ appendTo: sendButton, type: "i", class: "fa fa-send-o", }); ConversationsUtils.registerInputToSendFile(infosBox.conversationID, fileInput, conversationFormContainer); // =========== /SEND FILES =========== //Prevent textarea from adding a new line when pressing enter $(inputText).keypress(function(event){ if(event.keyCode == 13){ event.preventDefault(); sendButton.click(); } }); //Add required elements to infosBox infosBox.sendMessageForm = { formRoot: conversationFormContainer, sendButton: sendButton, inputText: inputText, textarea2: textarea2, }; //Success return true; }, /** * Load (or reload) a conversation * * @param {Integer} conversationID The ID of the conversation to load * @param {Object} conversationWindow Informations about the conversation window * @return {Boolean} True for a success */ load: async function(conversationID, conversationWindow, forceRefresh) { try { //Change conversation window name (loading state) this.changeName("Loading", conversationWindow); /** @type {Conversation} */ const conv = await new Promise((res, rej) => { ConversationsInterface.getInfosOne(conversationID, (info) => { if (info.error) rej(info) else res(info) }, forceRefresh); }) const users = await getUsers(conv.members.map(m => m.user_id)); // Create conversation informations root object var conversationInfos = { box: conversationWindow, membersInfos: users, infos: conv }; // Save conversation informations in the cache this.__conversationsCache["conversation-"+conversationID] = conversationInfos; //Change the name of the conversation this.changeName(await getConvName(conv), conversationWindow); // Apply the color of the conversation (if any) if (conv.color) conversationWindow.rootElem.setAttribute("style", "--primary-blue: #" +conv.color) // Update conversation members informations this.updateMembersList(conversationInfos); // Display conversation settings pane this.showConversationSettings(conversationInfos); // Register the conversation in the service ConvService.registerConversation(conversationID); // Make send a message button lives conversationInfos.box.sendMessageForm.formRoot.onsubmit = (e) => { e.preventDefault(); //Submit new message this.submitMessageForm(conversationInfos); }; //Add call button (if possible) this.showCallButton(conversationInfos); } catch(e) { console.error(e); notify(tr("Failed to load conversation!"), "danger"); } }, /** * Unload a chat window * * @param {Integer} conversationID The ID of the conversation to unload * @param {Boolean} keepInfos Keep informations about the chat window * @return {Boolean} True for a success */ unload: function(conversationID, keepInfos){ if(!this.__conversationsCache["conversation-"+conversationID]){ ComunicWeb.debug.logMessage("Couldn't unload conversation: " + conversationID +". It seems not to be loaded..."); return false; } //Log action ComunicWeb.debug.logMessage("Unloading a conversation: " + conversationID); //Get informations var conversationInfos = this.__conversationsCache["conversation-"+conversationID]; //Empty messages area emptyElem(conversationInfos.box.messagesArea); conversationInfos.box.messagesArea.innerHTML = ""; //Un-register conversation ComunicWeb.components.conversations.service.unregisterConversation(conversationID); //Remove informations if required if(!keepInfos){ delete this.__conversationsCache["conversation-"+conversationID]; } //Success return true; }, /** * Unload all chat windows * * @return {Boolean} True for a success */ unloadAll: function(){ //Clear conversation object clearObject(this.__conversationsCache); //Success return true; }, /** * Change the name of the converation at the top of the windows * * @param {String} newName The new name for the conversation window * @param {Ojbect} info Information about the conversation window * @return {Boolean} True for a success */ changeName: function(newName, info){ //Reduce new name if(newName.length > 18) newName = newName.slice(0, 17) + "..."; //Empty name field emptyElem(info.boxTitle); //Create conversation icon createElem2({ type: "i", appendTo: info.boxTitle, class: "fa fa-comments", ondblclick: () => { openConversation(info.conversationID, true); info.closeFunction(); } }); //Add conversation title var conversationTitle = createElem("span", info.boxTitle); conversationTitle.innerHTML = " " + newName; //Success return true; }, /** * Update conversation members list * * @param {Object} conv Information about the conversation * @return {Boolean} True for a success */ updateMembersList: function(info) { //First, make sure conversation members pane is empty emptyElem(info.box.membersList); /** @type {Conversation} */ const conv = info.infos; let isAdmin = conv.members.find(m => m.user_id == userID()).is_admin; let canAddUser = conv.group_id == null && (conv.can_everyone_add_members || isAdmin); let canRemoveUsers = isAdmin && canAddUser; // =================== Add a member =================== if (canAddUser) { //Create form container var addUserForm = createElem2({ appendTo: info.box.membersList, type: "form", class: "invite-user-form" }); //Form input let userInput = createFormGroup({ target: addUserForm, multiple: false, placeholder: "Select user", type: "select2"}); userInput.parentNode.className = "input-group"; ComunicWeb.components.userSelect.init(userInput); //Add submit button var groupsButton = createElem2({ appendTo: userInput.parentNode, type: "div", class: "input-group-btn" }); createElem2({ appendTo: groupsButton, type: "button", elemType: "submit", class: "btn btn-primary", innerHTML: "Add" }); addUserForm.addEventListener("submit", async e => { try { e.preventDefault(); //Get the list of selected users var usersToInvite = ComunicWeb.components.userSelect.getResults(userInput); //Check if there is not any user to invite if(usersToInvite.length == 0){ notify(tr("Please choose a user to add!"), "danger"); return; } await ConversationsInterface.addUser(conv.id, usersToInvite[0]); ConvChatWindow.reload(info) } catch(e) { console.error(e); notify(tr("Failed to update conversation settings!"), "danger") } }) } // =================== / Add a member =================== // Then process each user for(let member of conv.members) { let user = info.membersInfos.get(member.user_id); if(!user) continue; //Display user informations var userLi = createElem("li", info.box.membersList); var userLink = createElem("a", userLi); //Add user account image var userImage = createElem("img", userLink); userImage.className = "contacts-list-img"; userImage.src = user.image; //Add member informations var memberInfosList = createElem2({ type: "div", appendTo: userLink, class: "contacts-list-info", }); //Add user name var memberName = createElem2({ type: "span", appendTo: memberInfosList, class: "contacts-list-name", innerHTML: user.fullName, }); //Add member status let status = createElem2({ type: "span", appendTo: memberInfosList, class: "contacts-list-msg", innerHTML: (member.is_admin ? tr("Admin") : tr("Member")) + " " }); // Set / unset admin if(canRemoveUsers && member.user_id != userID()) { let removeLink = createElem2({ type: "a", appendTo: status, innerHTML: (member.is_admin ? tr("Unset admin") : tr("Set admin")) }) removeLink.addEventListener("click", async e => { e.preventDefault(); try { await ConversationsInterface.toggleAdminStatus(conv.id, member.user_id, !member.is_admin); ConvChatWindow.reload(info); } catch(e) { console.error(e); notify(tr("Failed to toggle admin status of %1%!", {"1": user.fullName}), "danger"); } }) } add_space(status) // Remove user if(canRemoveUsers && member.user_id != userID()) { let removeLink = createElem2({ type: "a", appendTo: status, innerHTML: tr("Remove") }) removeLink.addEventListener("click", async e => { e.preventDefault(); if(!await showConfirmDialog(tr("Do you really want to remove %1% from the conversation?", {"1": user.fullName}))) return; try { await ConversationsInterface.removeUser(conv.id, member.user_id); ConvChatWindow.reload(info); } catch(e) { console.error(e); notify(tr("Failed to remove %1% from the conversation!", {"1": user.fullName}), "danger"); } }) } // Leave conversation if(member.user_id == userID()) { let removeLink = createElem2({ type: "a", appendTo: status, innerHTML: tr("Leave") }) removeLink.addEventListener("click", async e => { e.preventDefault(); const isLastAdmin = conv.members.filter(m => m.is_admin && m.user_id != userID()).length == 0; const msg = isLastAdmin ? tr("As you are its last admin, if you leave this conversation, it will be permanently deleted!") : tr("Do you really want to leave this conversation?"); if(!await showConfirmDialog(msg)) return; try { await ConversationsInterface.leaveConversation(conv.id, member.user_id); // Close the conversation info.box.closeFunction(); } catch(e) { console.error(e); notify(tr("Failed to leave conversation!"), "danger"); } }) } } //Enable slimscrooll $(info.box.membersList).slimscroll({ height: "100%", color: "#FFFFFF" }); //Success return true; }, /** * Show conversation settings (button + pane) * * @param {Object} conversation Informations about the conversation * @return {Boolean} True for a success */ showConversationSettings: function(conversation){ //First, check conversation settings button and pane don't exists yet if(conversation.box.settingsButton && conversation.box.settingsButton.remove){ conversation.box.settingsButton.remove(); } if(conversation.box.settingsPane && conversation.box.settingsPane.remove){ conversation.box.settingsPane.remove(); } //Create and display conversation settings button wheel conversation.box.settingsButton = createElem2({ type: "button", insertBefore: conversation.box.membersButton, class: "btn btn-box-tool", type: "button" }); //Add button icon createElem2({ type: "i", appendTo: conversation.box.settingsButton, class: "fa fa-gear", }); //Create settings pane var settingsPane = createElem2({ type: "div", appendTo: conversation.box.boxBody, class: "conversation-settings-pane", }); conversation.box.settingsPane = settingsPane; //Make the settings button lives conversation.box.settingsButton.onclick = function(){ //Update settings pane classname if(settingsPane.className.includes(" open")) settingsPane.className = settingsPane.className.replace(" open", ""); //Close the pane else settingsPane.className += " open"; //Open the pane }; //Create the conversation form const settingsForm = ConversationsUtils.createConversationForm(settingsPane); //Update form informations settingsForm.createButton.innerHTML = "Update settings"; //Update conversation name if(conversation.infos.name) settingsForm.conversationNameInput.value = conversation.infos.name; // Apply conversation color if (conversation.infos.color) { settingsForm.conversationColorInput.value = "#" + conversation.infos.color; settingsForm.conversationColorInput.dispatchEvent(new CustomEvent("change")) } //Update conversation members ComunicWeb.components.userSelect.pushEntries(settingsForm.usersElement, conversation.infos.members.map(m => m.user_id)); // Update checkbox to allow or not everyone to add members $(settingsForm.allowEveryoneToAddMembers).iCheck(conversation.infos.can_everyone_add_members ? "check" : "uncheck"); settingsForm.usersElement.parentNode.style.display = "none"; //Check if user is a conversation moderator or not if(!conversation.infos.members.find(m => m.user_id == userID()).is_admin) { settingsForm.conversationNameInput.disabled = true; settingsForm.conversationColorInput.parentNode.parentNode.style.display = "none"; settingsForm.allowEveryoneToAddMembers.parentNode.parentNode.remove(); } //Update follow conversation checkbox status $(settingsForm.followConversationInput).iCheck(conversation.infos.members.find(m => m.user_id == userID()).following ? "check" : "uncheck"); //Save settings form in global form conversation.settingsForm = settingsForm; //Make update settings button lives settingsForm.createButton.onclick = () => { this.submitUpdateForm(conversation); }; // Add conversation image section if (conversation.infos.members.find(m => m.user_id == userID()).is_admin) { const convImageSection = createElem2({ appendTo: settingsForm.rootElem, type: "div", class: "conversation-image-settings", innerHTML: "

" + tr("Conversation image") + "

" }) // Show current if (conversation.infos.logo != null) { createElem2({ appendTo: convImageSection, type: "img", src: conversation.infos.logo, class: "current-image" }) } // Upload a new logo const newConvImagebutton = createElem2({ appendTo: convImageSection, type: "button", class: "btn btn-default", innerHTML: tr("Upload a new conversation logo") }); newConvImagebutton.addEventListener("click", async e => { e.preventDefault(); try { await ConversationsUtils.uploadNewConversationImage(conversation.infos.id); ConvChatWindow.reload(conversation) } catch(e) { console.error(e); notify(tr("Failed to change conversation image!"), "danger"); } }) // Delete current image if (conversation.infos.logo != null) { const deleteConvImage = createElem2({ appendTo: convImageSection, type: "button", class: "btn btn-danger", innerHTML: tr("Delete current logo") }); deleteConvImage.addEventListener("click", async e => { e.preventDefault(); try { if (!await showConfirmDialog(tr("Do you really want to delete this image ?"))) return; await ConversationsInterface.deleteConversationImage(conversation.infos.id); ConvChatWindow.reload(conversation) } catch(e) { console.error(e); notify(tr("Failed to remove conversation image!"), "danger"); } }) } } }, /** * Add a call button to the conversation, if possible * * @param {Object} conversation Information about the conversation */ showCallButton: function(conversation){ // Remove previous button (if any) let previousButton = conversation.box.boxTools.querySelector(".phone-button"); if(previousButton) previousButton.remove(); //Check if calls are disabled if(!conversation.infos.can_have_call) return; //Add the call button const button = createElem2({ insertBefore: conversation.box.boxTools.firstChild, type: "button", class: "btn btn-box-tool phone-button", innerHTML: "" }); conversation.box.callButton = button; button.addEventListener("click", function(){ CallsController.Open(conversation.infos) }); }, /** * Process submited update conversation form * * @param {Object} conversation Information about the conversation * @return {Boolean} True for a success */ submitUpdateForm: function(conversation){ //Then, get information about the input var newValues = { conversationID: conversation.infos.id, following: conversation.settingsForm.followConversationInput.checked, } //Add other fields if the user is a conversation moderator if(conversation.infos.members.find(m => m.user_id == userID()).is_admin){ //Specify conversation name let nameValue = conversation.settingsForm.conversationNameInput.value newValues.name = (nameValue === "" ? null : nameValue); let colorValue = conversation.settingsForm.conversationColorInput.value newValues.color = (colorValue == "" ? null : colorValue) newValues.canEveryoneAddMembers = conversation.settingsForm.allowEveryoneToAddMembers.checked; } //Now, freeze the submit button conversation.settingsForm.createButton.disabled = true; //Peform a request through the interface ConversationsInterface.updateSettings(newValues, function(result){ //Enable again update button conversation.settingsForm.createButton.disabled = false; //Check for errors if(result.error) notify("An error occured while trying to update conversation settings !", "danger", 4); //Reload the conversation ConvChatWindow.reload(conversation); }); }, /** * Reload the conversation * * @param {Object} conversation Information about the conversation */ reload: function(conversation) { ConvChatWindow.unload(conversation.infos.id, true); ConvChatWindow.load(conversation.infos.id, conversation.box, true); }, /** * Submit a new message form * * @param {Object} convInfos Information about the conversation * @return {Boolean} True for a success */ submitMessageForm: async function(convInfos){ try { //Extract main fields var form = convInfos.box.sendMessageForm; //Check if message is empty. let message = form.inputText.value; if (message.length == 0) return; if(message.length < ServerConfig.conf.min_conversation_message_len || message.length > ServerConfig.conf.max_conversation_message_len){ notify(tr("Invalid message length!"), "danger", 2); return; } //Lock send button form.sendButton.disabled = true; await ConversationsInterface.sendMessage(convInfos.infos.id, message); //Reset the form ConvChatWindow.resetCreateMessageForm(convInfos); } catch(e) { console.error(e) notify(tr("An error occured while trying to send message! Please try again..."), "danger", 2); } //Unlock send button form.sendButton.disabled = false; }, /** * Reset a create a message form * * @param {Object} infos Information about the conversation * @return {Boolean} True for a success */ resetCreateMessageForm: function(infos){ //Extract form informations var form = infos.box.sendMessageForm; //Unlock send button and reset its value form.sendButton.disabled = false; //Empty textarea form.inputText.value = ""; form.textarea2.resetHeight(); }, /** * Add a message to a conversation window * * @param {Integer} conversationID The ID of the conversation to update * @param {Object} messageInfo Information about the message to add * @return {Boolean} True for a success */ addMessage: function(conversationID, messageInfo){ //First, check if the conversation information can be found if(!this.__conversationsCache["conversation-"+conversationID]){ ComunicWeb.debug.logMessage("Conversation Chat Windows : Error ! Couldn't add a message to the conversation because the conversation was not found !"); return false; } //Else extract conversation informations var convInfos = this.__conversationsCache["conversation-"+conversationID]; //Check if this is the first message of the conversation or not if(!convInfos.messages){ convInfos.messages = []; } //Get message HTML element add append it var uiMessageInfo = this._get_message_element(convInfos, messageInfo); convInfos.box.messagesArea.appendChild(uiMessageInfo.rootElem); //Perform post-processing operations var num = convInfos.messages.push(uiMessageInfo); //Check if it is not the first message from the current user this._makeMessageFollowAnotherMessage(convInfos, num - 1); //Enable slimscroll $(convInfos.box.messagesArea).slimscroll({ height: "250px", }); //Scroll to the bottom of the conversation var scrollBottom = $(convInfos.box.messagesArea).prop("scrollHeight")+"px"; $(convInfos.box.messagesArea).slimScroll({ scrollTo: scrollBottom }); //Initialize top scroll detection if required this.initTopScrollDetection(conversationID); //Success return true; }, /** * Add old messages to a conversation window * * @param {number} conversationID The ID of the target conversation * @param {array} messages The list of messages to add */ addOldMessages: function(conversationID, messages){ //First, check if the conversation information can be found if(!this.__conversationsCache["conversation-"+conversationID]){ ComunicWeb.debug.logMessage("Conversation Chat Windows : Error ! Couldn't add a message to the conversation because the conversation was not found !"); return false; } //Else extract conversation informations var conv = this.__conversationsCache["conversation-"+conversationID]; //Save the position of the oldest message element var currOldestMessageElem = conv.messages[0].rootElem; //Process the list of messages in reverse order messages.reverse(); messages.forEach(function(message){ //Get message element var uiMessageInfo = ComunicWeb.components.conversations.chatWindows._get_message_element(conv, message); //Add the messages at the begining of the conversation conv.box.messagesArea.insertBefore(uiMessageInfo.rootElem, conv.messages[0].rootElem); //Add the message to the list conv.messages.unshift(uiMessageInfo); //Check if some information about the post can be updated ComunicWeb.components.conversations.chatWindows._makeMessageFollowAnotherMessage(conv, 1); }); //Update slimscroll newScrollPos = currOldestMessageElem.offsetTop - 30; if(newScrollPos < 0) newScrollPos = 0; $(conv.box.messagesArea).slimScroll({ scrollTo: newScrollPos + "px" }); }, /** * Generate message HTML node based on given information * * @param {object} conversationInfo Information about the created conversation * @param {ConversationMessage} message Information about the target message * @return {object} Information about the created message element */ _get_message_element: function(conversationInfo, message){ if (message.user_id != null && message.user_id > 0) return this._get_user_message(conversationInfo, message); else return this._get_server_message(conversationInfo, message); }, /** * @param {Object} conversationInfo * @param {ConversationMessage} message */ _get_user_message: (conversationInfo, message) => { //Check if it is the current user who sent the message var userIsPoster = message.user_id == userID(); //Create message element const messageContainer = createElem2({ type: "div", class: "direct-chat-msg " + (userIsPoster ? "right" : "") }); messageContainer.setAttribute("data-chatwin-msg-id", message.id) //Display message header var messageHeader = createElem2({ appendTo: messageContainer, type: "div", class: "direct-chat-info clearfix" }); //Add top information var topInfosElem = createElem2({ appendTo: messageHeader, type: "div", class: "direct-chat-name pull-" + (userIsPoster ? "right" : "left"), }); //Add user name var usernameElem = createElem2({ appendTo: topInfosElem, type: "span", innerHTML: "Loading", }); //Hide user name if it is the current user if(userIsPoster) usernameElem.style.display = "none"; //Add user account image var userAccountImage = createElem2({ appendTo: messageContainer, type: "img", class: "direct-chat-img", src: ComunicWeb.__config.assetsURL + "img/defaultAvatar.png", alt: "User account image", }); //Load user informations let userInfo = conversationInfo.membersInfos.get(message.user_id); if(userInfo) { usernameElem.innerHTML = userInfo.fullName; userAccountImage.src = userInfo.image; } else { async () => { try { const userInfo = await userInfo(message.user_id); usernameElem.innerHTML = userInfo.fullName; userAccountImage.src = userInfo.image; } catch(e) { console.error(e); } } } //Add message var messageTargetElem = createElem2({ appendTo: messageContainer, type: "div", class: "direct-chat-text ", }); //Add text message var textMessage = createElem2({ appendTo: messageTargetElem, type: "span", innerHTML: removeHtmlTags(message.message), //Remove HTML tags }); //Check if an image has to be added if(message.file != null){ const messageFile = message.file; if (messageFile.type == "image/png") { var imageLink = createElem2({ appendTo: messageTargetElem, type: "a", href: messageFile.url }); //Apply image createElem2({ appendTo: imageLink, type: "img", class: "conversation-msg-image", src: messageFile.thumbnail == null ? messageFile.url : messageFile.thumbnail }); imageLink.onclick = function(){ $(this).ekkoLightbox({ alwaysShowClose: true, }); return false; }; } else if(messageFile.type == "audio/mpeg") { new SmallMediaPlayer(messageTargetElem, messageFile.url, false) } else if(messageFile.type == "video/mp4") { new SmallMediaPlayer(messageTargetElem, messageFile.url, true) } // Fallback else { let letFileLink = createElem2({ appendTo: messageTargetElem, type: "a", href: messageFile.url, innerHTML: " "+ messageFile.name + " ("+fileSizeToHuman(messageFile.size)+")", }) letFileLink.target = "_blank" } } //Add date var dateElem = createElem2({ appendTo: messageContainer, type: "div", class: "date-conversation-message", innerHTML: ComunicWeb.common.date.timeDiffToStr(message.time_sent) }); //Parse emojies in text message ComunicWeb.components.textParser.parse({ element: textMessage, user: userInfo, }); //Add message dropdown menu messageContainer.className += " dropdown"; var dropdownToggle = createElem2({ insertBefore: dateElem, type: "i", class: "hidden" }); dropdownToggle.setAttribute("data-toggle", "dropdown"); var dropdownMenu = createElem2({ insertBefore: dateElem, type: "ul", class: "dropdown-menu" }); dropdownMenu.setAttribute("role", "menu"); messageTargetElem.addEventListener("dblclick", function(){ $(dropdownToggle).dropdown("toggle"); }); //Add message options if(userIsPoster){ if (message.file == null) { //Update message content var updateLi = createElem2({ type: "li", appendTo: dropdownMenu }); var updateLink = createElem2({ type: "a", appendTo: updateLi, innerHTML: "Edit" }); updateLink.addEventListener("click", function(){ ComunicWeb.components.conversations.messageEditor.open(message, function(newContent){ /* DEPRECATED WITH WEBSOCKETS */ }); }); } //Delete the message var deleteLi = createElem2({ type: "li", appendTo: dropdownMenu }); var deleteLink = createElem2({ type: "a", appendTo: deleteLi, innerHTML: "Delete" }); deleteLink.addEventListener("click", function(){ ComunicWeb.common.messages.confirm( "Do you really want to delete this message? The operation can not be reverted!", function(confirm){ if(!confirm) return; //Hide the message messageTargetElem.style.display = "none"; //Execute the request ConversationsInterface.DeleteSingleMessage( message.id, function(result){ if(!result){ messageTargetElem.style.display = "block"; notify("Could delete conversation message!", "danger"); } } ); } ) }); } //Return information about the message return { userID: message.user_id, rootElem: messageContainer, userNameElem: usernameElem, dateElem: dateElem, time_sent: message.time_sent, messageTargetElem: messageTargetElem, accountImage: userAccountImage }; }, /** * Apply a server message * * @param {Object} convInfo Information about the conversation * @param {ConversationMessage} message The message */ _get_server_message: (convInfo, message) => { //Create message element const messageContainer = createElem2({ type: "div", class: "direct-chat-msg " }); messageContainer.setAttribute("data-chatwin-msg-id", message.id); //Add message let messageTargetElem = createElem2({ appendTo: messageContainer, type: "div", class: "server_message", }); (async () => { try { let ids = ConversationsUtils.getUsersIDForMessage(message) let users = await getUsers(ids); let msg = ConversationsUtils.getServerMessage(message, users); messageTargetElem.innerHTML = msg } catch(e) { console.error(e); notify(tr("Failed to load a server message!")) } })(); return { userID: null, rootElem: messageContainer, userNameElem: document.createElement("span"), dateElem: document.createElement("span"), time_sent: message.time_sent, messageTargetElem: messageTargetElem, accountImage: document.createElement("span") }; }, /** * Make a conversation message "follow" another conversation message from the * same user * * @param {object} conv Information about the target conversation * @param {number} num The number of the conversation message to update */ _makeMessageFollowAnotherMessage: function(conv, num){ if(conv.messages[num - 1]){ if(conv.messages[num-1].userID == conv.messages[num].userID){ //Update object class name conv.messages[num].messageTargetElem.className += " not-first-message"; //Hide user name and account image conv.messages[num].userNameElem.style.display = "none"; conv.messages[num].accountImage.style.display = "none"; //Update the class of the previous message conv.messages[num-1].rootElem.className += " not-last-message-from-user"; } //Check the difference of time between the two messages if(conv.messages[num].time_sent - conv.messages[num - 1].time_sent < 3600 || conv.messages[num].dateElem.innerHTML == conv.messages[num - 1].dateElem.innerHTML) conv.messages[num].dateElem.style.display = "none"; } }, /** * Init top scroll detection (if required) * * @param {number} conversationID The ID of the target conversation */ initTopScrollDetection: function(conversationID){ //Extract conversation informations var convInfo = this.__conversationsCache["conversation-"+conversationID]; //Check if nothing has to be done if(convInfo.box.initializedScrollDetection) return; //Mark scroll detection as initialized convInfo.box.initializedScrollDetection = true; var scrollDetectionLocked = false; var scrollTopCount = 0; $(convInfo.box.messagesArea).slimScroll().bind("slimscrolling", function(e, pos){ if(scrollDetectionLocked) return; if(pos != 0){ scrollTopCount = 0; } scrollTopCount++; //Check top count if(scrollTopCount < 3) return; //Lock the detection scrollDetectionLocked = true; //Fetch older messages ConversationsInterface.getOlderMessages( conversationID, ConvService.getOldestMessageID(conversationID), 10, function(result){ //Unlock scroll detection scrollDetectionLocked = false; //Check for errors if(result.error){ notify("An error occured while trying to fetch older messages for the conversation !"); return; } //Check for results if(result.length == 0){ //Lock scroll detection in order to avoid useless traffic scrollDetectionLocked = true; return; } //Save the ID of the oldest message ConvService.setOldestMessageID(conversationID, result[0].id); //Display the list of messages ComunicWeb.components.conversations.chatWindows.addOldMessages(conversationID, result); } ); }); } } ComunicWeb.components.conversations.chatWindows = ConvChatWindow; //Register conversations cache cleaning function ComunicWeb.common.cacheManager.registerCacheCleaner("ComunicWeb.components.conversations.chatWindows.unloadAll"); // Register to messages update events document.addEventListener("updatedConvMessage", (e) => { const msg = e.detail; // Get message target const target = document.querySelector("[data-chatwin-msg-id='"+msg.id+"']"); if(!target) return; // Get conversation info const convInfo = ConvChatWindow.__conversationsCache["conversation-"+msg.conv_id]; if(!convInfo) return; target.replaceWith(ConvChatWindow._get_message_element(convInfo, msg).rootElem) }); // Register to message deletion events document.addEventListener("deletedConvMessage", (e) => { const msgID = e.detail; // Get message target const target = document.querySelector("[data-chatwin-msg-id='"+msgID+"'] .direct-chat-text"); if(!target) return; target.style.display = "none"; }) // Register to conversation removal document.addEventListener("removedUserFromConv", e => { const msg = e.detail; if (msg.user_id != userID()) return; if(!ConvService.__serviceCache.hasOwnProperty("conversation-" + msg.conv_id)) return; ConvChatWindow.__conversationsCache["conversation-"+msg.conv_id].box.closeFunction(); });