diff --git a/public/lib/classes/formatting.php b/public/lib/classes/formatting.php index 0ddb299f656..a84af402661 100644 --- a/public/lib/classes/formatting.php +++ b/public/lib/classes/formatting.php @@ -304,7 +304,9 @@ class formatting { if ($blanktarget) { $domdoc = new \DOMDocument(); libxml_use_internal_errors(true); - $domdoc->loadHTML('' . $text); + // Use meta charset tag to properly handle UTF-8 instead of XML declaration hack. + // The XML declaration approach no longer works with libxml2 >= 2.14.0. + $domdoc->loadHTML('' . $text); libxml_clear_errors(); foreach ($domdoc->getElementsByTagName('a') as $link) { if ($link->hasAttribute('target') && strpos($link->getAttribute('target'), '_blank') === false) { @@ -320,11 +322,23 @@ class formatting { // $domdoc->loadHTML($text, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); however it seems like some libxml // versions don't work properly and end up leaving , so I'm forced to use // this regex to remove those tags as a preventive measure. + // Also strip head and meta tags added by the charset workaround. $text = trim(preg_replace( - '~<(?:!DOCTYPE|/?(?:html|body))[^>]*>\s*~i', + '~<(?:!DOCTYPE|/?(?:html|head|body)|meta\s[^>]*?)>\s*~i', '', $domdoc->saveHTML($domdoc->documentElement), )); + // Libxml2 >= 2.14.0 doesn't wrap plain text in

tags, so add them for consistency. + if (LIBXML_VERSION >= 21400) { + $trimmed = trim($text); + if ($trimmed !== '' && !preg_match('/^' . $text . '

'; + } + } + // The meta charset approach preserves leading/trailing whitespace in

tags more than the old XML + // declaration approach. Normalize this by trimming whitespace inside

tags to match old behavior. + $text = preg_replace('~

\s+~i', '

', $text); + $text = preg_replace('~\s+

~i', '

', $text); } return $text; diff --git a/public/message/amd/build/message_drawer_view_overview_section.min.js b/public/message/amd/build/message_drawer_view_overview_section.min.js index 349bde42e43..8b41b1b0c43 100644 --- a/public/message/amd/build/message_drawer_view_overview_section.min.js +++ b/public/message/amd/build/message_drawer_view_overview_section.min.js @@ -5,6 +5,6 @@ * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("core_message/message_drawer_view_overview_section",["jquery","core/custom_interaction_events","core/notification","core/pubsub","core/str","core/pending","core/templates","core/user_date","core_message/message_repository","core_message/message_drawer_events","core_message/message_drawer_router","core_message/message_drawer_routes","core_message/message_drawer_lazy_load_list","core_message/message_drawer_view_conversation_constants"],(function($,CustomEvents,Notification,PubSub,Str,Pending,Templates,UserDate,MessageRepository,MessageDrawerEvents,MessageDrawerRouter,MessageDrawerRoutes,LazyLoadList,MessageDrawerViewConversationContants){var SELECTORS_CONVERSATION="[data-conversation-id]",SELECTORS_BLOCKED_ICON_CONTAINER='[data-region="contact-icon-blocked"]',SELECTORS_MUTED_ICON_CONTAINER='[data-region="muted-icon-container"]',SELECTORS_UNREAD_COUNT='[data-region="unread-count"]',SELECTORS_SECTION_TOTAL_COUNT='[data-region="section-total-count"]',SELECTORS_SECTION_TOTAL_COUNT_CONTAINER='[data-region="section-total-count-container"]',SELECTORS_SECTION_UNREAD_COUNT='[data-region="section-unread-count"]',SELECTORS_SECTION_UNREAD_COUNT_CONTAINER='[data-region="section-unread-count-container"]',SELECTORS_PLACEHOLDER_CONTAINER='[data-region="placeholder-container"]',TEMPLATES_CONVERSATIONS_LIST="core_message/message_drawer_conversations_list",TEMPLATES_CONVERSATIONS_LIST_ITEMS_PLACEHOLDER="core_message/message_drawer_conversations_list_items_placeholder",loadedConversationsById={},deletedConversationsById={},loadedTotalCounts=!1,loadedUnreadCounts=!1,isVisible=function(root){return LazyLoadList.getRoot(root).hasClass("show")},setExpanded=function(root){root.addClass("expanded")},renderUnreadCount=function(root,count){var container=root.find(SELECTORS_SECTION_UNREAD_COUNT_CONTAINER);container.find(SELECTORS_SECTION_UNREAD_COUNT).text(count),Str.get_string("unreadconversations","core_message",count).done((function(string){$("#"+container.attr("aria-labelledby")).text(string)})),count>0?container.removeClass("hidden"):container.addClass("hidden")},formatConversationFromEvent=function(conversation){var recursivelyLowercaseKeys=function(object){return Object.keys(object).reduce((function(carry,key){return $.isArray(object[key])?carry[key.toLowerCase()]=object[key].map(recursivelyLowercaseKeys):carry[key.toLowerCase()]=object[key],carry}),{})},formatted=recursivelyLowercaseKeys(conversation);return formatted.messages=formatted.messages.map((function(message){return message.useridfrom=message.userfrom.id,message})),formatted},render=function(conversations,userId){var pending=new Pending,mapPromises=conversations.map((function(conversation){var lastMessage=conversation.messages.length?conversation.messages[conversation.messages.length-1]:null;return async function(lastMessage){if(!lastMessage)return null;var tmpElement=document.createElement("element");if(tmpElement.innerHTML=lastMessage.text.replace(/50?conversations=conversations.slice(0,-1):LazyLoadList.setLoadedAll(root,!0),offset+=50,conversations.forEach((function(conversation){loadedConversationsById[conversation.id]=conversation})),conversations})).catch(Notification.exception)}}(types,includeFavourites,0);if(registerEventListeners(namespace,root,loadCallback,types,includeFavourites,fromPanel),isVisible(root)){setExpanded(root);var listRoot=LazyLoadList.getRoot(root);LazyLoadList.show(listRoot,loadCallback,(function(contentContainer,conversations,userId){return render(conversations,userId).then((function(html){return contentContainer.append(html),html})).catch(Notification.exception)}))}totalCountPromise.then((function(count){!function(root,count){var container=root.find(SELECTORS_SECTION_TOTAL_COUNT_CONTAINER);container.find(SELECTORS_SECTION_TOTAL_COUNT).text(count),container.removeClass("hidden"),Str.get_string("totalconversations","core_message",count).done((function(string){$("#"+container.attr("aria-labelledby")).text(string)}));var numPlaceholders=count>20?20:count,placeholders=Array.apply(null,Array(numPlaceholders)).map((function(){return!0}));Templates.render(TEMPLATES_CONVERSATIONS_LIST_ITEMS_PLACEHOLDER,{placeholders:placeholders}).then((function(html){root.find(SELECTORS_PLACEHOLDER_CONTAINER).html(html)})).catch((function(){}))}(root,count),loadedTotalCounts=!0})).catch((function(){})),unreadCountPromise.then((function(count){renderUnreadCount(root,count),loadedUnreadCounts=!0})).catch((function(){})),root.attr("data-init",!0)}},isVisible:isVisible}})); +define("core_message/message_drawer_view_overview_section",["jquery","core/custom_interaction_events","core/notification","core/pubsub","core/str","core/pending","core/templates","core/user_date","core_message/message_repository","core_message/message_drawer_events","core_message/message_drawer_router","core_message/message_drawer_routes","core_message/message_drawer_lazy_load_list","core_message/message_drawer_view_conversation_constants"],(function($,CustomEvents,Notification,PubSub,Str,Pending,Templates,UserDate,MessageRepository,MessageDrawerEvents,MessageDrawerRouter,MessageDrawerRoutes,LazyLoadList,MessageDrawerViewConversationContants){var SELECTORS_CONVERSATION="[data-conversation-id]",SELECTORS_BLOCKED_ICON_CONTAINER='[data-region="contact-icon-blocked"]',SELECTORS_MUTED_ICON_CONTAINER='[data-region="muted-icon-container"]',SELECTORS_UNREAD_COUNT='[data-region="unread-count"]',SELECTORS_SECTION_TOTAL_COUNT='[data-region="section-total-count"]',SELECTORS_SECTION_TOTAL_COUNT_CONTAINER='[data-region="section-total-count-container"]',SELECTORS_SECTION_UNREAD_COUNT='[data-region="section-unread-count"]',SELECTORS_SECTION_UNREAD_COUNT_CONTAINER='[data-region="section-unread-count-container"]',SELECTORS_PLACEHOLDER_CONTAINER='[data-region="placeholder-container"]',TEMPLATES_CONVERSATIONS_LIST="core_message/message_drawer_conversations_list",TEMPLATES_CONVERSATIONS_LIST_ITEMS_PLACEHOLDER="core_message/message_drawer_conversations_list_items_placeholder",loadedConversationsById={},deletedConversationsById={},loadedTotalCounts=!1,loadedUnreadCounts=!1,isVisible=function(root){return LazyLoadList.getRoot(root).hasClass("show")},setExpanded=function(root){root.addClass("expanded")},renderUnreadCount=function(root,count){var container=root.find(SELECTORS_SECTION_UNREAD_COUNT_CONTAINER);container.find(SELECTORS_SECTION_UNREAD_COUNT).text(count),Str.get_string("unreadconversations","core_message",count).done((function(string){$("#"+container.attr("aria-labelledby")).text(string)})),count>0?container.removeClass("hidden"):container.addClass("hidden")},formatConversationFromEvent=function(conversation){var recursivelyLowercaseKeys=function(object){return Object.keys(object).reduce((function(carry,key){return $.isArray(object[key])?carry[key.toLowerCase()]=object[key].map(recursivelyLowercaseKeys):carry[key.toLowerCase()]=object[key],carry}),{})},formatted=recursivelyLowercaseKeys(conversation);return formatted.messages=formatted.messages.map((function(message){return message.useridfrom=message.userfrom.id,message})),formatted},render=function(conversations,userId){var pending=new Pending,mapPromises=conversations.map((function(conversation){var lastMessage=conversation.messages.length?conversation.messages[conversation.messages.length-1]:null;return async function(lastMessage){if(!lastMessage)return null;var tmpElement=document.createElement("element");if(tmpElement.innerHTML=lastMessage.text.replace(/50?conversations=conversations.slice(0,-1):LazyLoadList.setLoadedAll(root,!0),offset+=50,conversations.forEach((function(conversation){loadedConversationsById[conversation.id]=conversation})),conversations})).catch(Notification.exception)}}(types,includeFavourites,0);if(registerEventListeners(namespace,root,loadCallback,types,includeFavourites,fromPanel),isVisible(root)){setExpanded(root);var listRoot=LazyLoadList.getRoot(root);LazyLoadList.show(listRoot,loadCallback,(function(contentContainer,conversations,userId){return render(conversations,userId).then((function(html){return contentContainer.append(html),html})).catch(Notification.exception)}))}totalCountPromise.then((function(count){!function(root,count){var container=root.find(SELECTORS_SECTION_TOTAL_COUNT_CONTAINER);container.find(SELECTORS_SECTION_TOTAL_COUNT).text(count),container.removeClass("hidden"),Str.get_string("totalconversations","core_message",count).done((function(string){$("#"+container.attr("aria-labelledby")).text(string)}));var numPlaceholders=count>20?20:count,placeholders=Array.apply(null,Array(numPlaceholders)).map((function(){return!0}));Templates.render(TEMPLATES_CONVERSATIONS_LIST_ITEMS_PLACEHOLDER,{placeholders:placeholders}).then((function(html){root.find(SELECTORS_PLACEHOLDER_CONTAINER).html(html)})).catch((function(){}))}(root,count),loadedTotalCounts=!0})).catch((function(){})),unreadCountPromise.then((function(count){renderUnreadCount(root,count),loadedUnreadCounts=!0})).catch((function(){})),root.attr("data-init",!0)}},isVisible:isVisible}})); //# sourceMappingURL=message_drawer_view_overview_section.min.js.map \ No newline at end of file diff --git a/public/message/amd/build/message_drawer_view_overview_section.min.js.map b/public/message/amd/build/message_drawer_view_overview_section.min.js.map index 31d3f52dd78..32f4bb60b9c 100644 --- a/public/message/amd/build/message_drawer_view_overview_section.min.js.map +++ b/public/message/amd/build/message_drawer_view_overview_section.min.js.map @@ -1 +1 @@ -{"version":3,"file":"message_drawer_view_overview_section.min.js","sources":["../src/message_drawer_view_overview_section.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls a section of the overview page in the message drawer.\n *\n * @module core_message/message_drawer_view_overview_section\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/notification',\n 'core/pubsub',\n 'core/str',\n 'core/pending',\n 'core/templates',\n 'core/user_date',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core_message/message_drawer_lazy_load_list',\n 'core_message/message_drawer_view_conversation_constants'\n],\nfunction(\n $,\n CustomEvents,\n Notification,\n PubSub,\n Str,\n Pending,\n Templates,\n UserDate,\n MessageRepository,\n MessageDrawerEvents,\n MessageDrawerRouter,\n MessageDrawerRoutes,\n LazyLoadList,\n MessageDrawerViewConversationContants\n) {\n\n var SELECTORS = {\n TOGGLE: '[data-region=\"toggle\"]',\n CONVERSATION: '[data-conversation-id]',\n BLOCKED_ICON_CONTAINER: '[data-region=\"contact-icon-blocked\"]',\n LAST_MESSAGE: '[data-region=\"last-message\"]',\n LAST_MESSAGE_DATE: '[data-region=\"last-message-date\"]',\n MUTED_ICON_CONTAINER: '[data-region=\"muted-icon-container\"]',\n UNREAD_COUNT: '[data-region=\"unread-count\"]',\n SECTION_TOTAL_COUNT: '[data-region=\"section-total-count\"]',\n SECTION_TOTAL_COUNT_CONTAINER: '[data-region=\"section-total-count-container\"]',\n SECTION_UNREAD_COUNT: '[data-region=\"section-unread-count\"]',\n SECTION_UNREAD_COUNT_CONTAINER: '[data-region=\"section-unread-count-container\"]',\n PLACEHOLDER_CONTAINER: '[data-region=\"placeholder-container\"]'\n };\n\n var TEMPLATES = {\n CONVERSATIONS_LIST: 'core_message/message_drawer_conversations_list',\n CONVERSATIONS_LIST_ITEMS_PLACEHOLDER: 'core_message/message_drawer_conversations_list_items_placeholder'\n };\n\n var LOAD_LIMIT = 50;\n var loadedConversationsById = {};\n var deletedConversationsById = {};\n var loadedTotalCounts = false;\n var loadedUnreadCounts = false;\n\n /**\n * Get the section visibility status.\n *\n * @param {Object} root The section container element.\n * @return {Bool} Is section visible.\n */\n var isVisible = function(root) {\n return LazyLoadList.getRoot(root).hasClass('show');\n };\n\n /**\n * Set this section as expanded.\n *\n * @param {Object} root The section container element.\n */\n var setExpanded = function(root) {\n root.addClass('expanded');\n };\n\n /**\n * Set this section as collapsed.\n *\n * @param {Object} root The section container element.\n */\n var setCollapsed = function(root) {\n root.removeClass('expanded');\n };\n\n /**\n * Render the total count value and show it for the user. Also update the placeholder\n * HTML for better visuals.\n *\n * @param {Object} root The section container element.\n * @param {Number} count The total count\n */\n var renderTotalCount = function(root, count) {\n var container = root.find(SELECTORS.SECTION_TOTAL_COUNT_CONTAINER);\n var countElement = container.find(SELECTORS.SECTION_TOTAL_COUNT);\n countElement.text(count);\n container.removeClass('hidden');\n Str.get_string('totalconversations', 'core_message', count).done(function(string) {\n $('#' + container.attr('aria-labelledby')).text(string);\n });\n\n var numPlaceholders = count > 20 ? 20 : count;\n // Array of \"true\" up to the number of placeholders we want.\n var placeholders = Array.apply(null, Array(numPlaceholders)).map(function() {\n return true;\n });\n\n // Replace the current placeholder (loading spinner) with some nicer placeholders that\n // better represent the content.\n Templates.render(TEMPLATES.CONVERSATIONS_LIST_ITEMS_PLACEHOLDER, {placeholders: placeholders})\n .then(function(html) {\n var placeholderContainer = root.find(SELECTORS.PLACEHOLDER_CONTAINER);\n placeholderContainer.html(html);\n return;\n })\n .catch(function() {\n // Silently ignore. Doesn't matter if we can't render the placeholders.\n });\n };\n\n /**\n * Render the unread count value and show it for the user if it's higher than zero.\n *\n * @param {Object} root The section container element.\n * @param {Number} count The unread count\n */\n var renderUnreadCount = function(root, count) {\n var container = root.find(SELECTORS.SECTION_UNREAD_COUNT_CONTAINER);\n var countElement = container.find(SELECTORS.SECTION_UNREAD_COUNT);\n countElement.text(count);\n\n Str.get_string('unreadconversations', 'core_message', count).done(function(string) {\n $('#' + container.attr('aria-labelledby')).text(string);\n });\n\n if (count > 0) {\n container.removeClass('hidden');\n } else {\n container.addClass('hidden');\n }\n };\n\n /**\n * Create a formatted conversation object from the the one we get from events. The new object\n * will be in a format that matches what we receive from the server.\n *\n * @param {Object} conversation\n * @return {Object} formatted conversation.\n */\n var formatConversationFromEvent = function(conversation) {\n // Recursively lowercase all of the keys for an object.\n var recursivelyLowercaseKeys = function(object) {\n return Object.keys(object).reduce(function(carry, key) {\n if ($.isArray(object[key])) {\n carry[key.toLowerCase()] = object[key].map(recursivelyLowercaseKeys);\n } else {\n carry[key.toLowerCase()] = object[key];\n }\n\n return carry;\n }, {});\n };\n\n // Recursively lowercase all of the keys for the conversation.\n var formatted = recursivelyLowercaseKeys(conversation);\n\n // Make sure all messages have the useridfrom property set.\n formatted.messages = formatted.messages.map(function(message) {\n message.useridfrom = message.userfrom.id;\n return message;\n });\n\n return formatted;\n };\n\n /**\n * Render the messages in the overview page.\n *\n * @param {Array} conversations List of conversations to render.\n * @param {Number} userId Logged in user id.\n * @return {Object} jQuery promise.\n */\n var render = function(conversations, userId) {\n\n // Helper to format the last message for rendering.\n // Returns a promise which resolves to either a string, or null\n // (such as in the event of an empty personal space).\n var pending = new Pending();\n\n var formatMessagePreview = async function(lastMessage) {\n if (!lastMessage) {\n return null;\n }\n // Check the message html for a src attribute, indicative of media.\n // Replace LOAD_LIMIT) {\n conversations = conversations.slice(0, -1);\n } else {\n LazyLoadList.setLoadedAll(root, true);\n }\n\n offset = offset + LOAD_LIMIT;\n\n conversations.forEach(function(conversation) {\n loadedConversationsById[conversation.id] = conversation;\n });\n\n return conversations;\n })\n .catch(Notification.exception);\n };\n };\n\n /**\n * Get the total count container element.\n *\n * @param {Object} root Overview messages container element.\n * @return {Object} Total count container element.\n */\n var getTotalConversationCountElement = function(root) {\n return root.find(SELECTORS.SECTION_TOTAL_COUNT);\n };\n\n /**\n * Get the unread conversations count container element.\n *\n * @param {Object} root Overview messages container element.\n * @return {Object} Unread conversations count container element.\n */\n var getTotalUnreadConversationCountElement = function(root) {\n return root.find(SELECTORS.SECTION_UNREAD_COUNT);\n };\n\n /**\n * Increment the total conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var incrementTotalConversationCount = function(root) {\n if (loadedTotalCounts) {\n var element = getTotalConversationCountElement(root);\n var count = parseInt(element.text());\n count = count + 1;\n element.text(count);\n }\n };\n\n /**\n * Decrement the total conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var decrementTotalConversationCount = function(root) {\n if (loadedTotalCounts) {\n var element = getTotalConversationCountElement(root);\n var count = parseInt(element.text());\n count = count - 1;\n element.text(count);\n }\n };\n\n /**\n * Decrement the total unread conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var decrementTotalUnreadConversationCount = function(root) {\n if (loadedUnreadCounts) {\n var element = getTotalUnreadConversationCountElement(root);\n var count = parseInt(element.text());\n count = count - 1;\n // Re-render the unread count.\n renderUnreadCount(root, count);\n }\n };\n\n /**\n * Get a contact / conversation element.\n *\n * @param {Object} root Overview messages container element.\n * @param {Number} conversationId The conversation id.\n * @return {Object} Conversation element.\n */\n var getConversationElement = function(root, conversationId) {\n return root.find('[data-conversation-id=\"' + conversationId + '\"]');\n };\n\n /**\n * Get a contact / conversation element from a user id.\n *\n * @param {Object} root Overview messages container element.\n * @param {Number} userId The user id.\n * @return {Object} Conversation element.\n */\n var getConversationElementFromUserId = function(root, userId) {\n return root.find('[data-user-id=\"' + userId + '\"]');\n };\n\n /**\n * Show the conversation is muted icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var muteConversation = function(conversationElement) {\n conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the conversation is muted icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var unmuteConversation = function(conversationElement) {\n conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the contact is blocked icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var blockContact = function(conversationElement) {\n conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the contact is blocked icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var unblockContact = function(conversationElement) {\n conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Create an render new conversation element in the list of conversations.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversation The conversation.\n * @param {Number} userId The logged in user id.\n * @return {Object} jQuery promise\n */\n var createNewConversationFromEvent = function(root, conversation, userId) {\n var existingConversations = root.find(SELECTORS.CONVERSATION);\n\n if (!existingConversations.length) {\n // If we didn't have any conversations then we need to show\n // the content of the list and hide the empty message.\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.showContent(listRoot);\n LazyLoadList.hideEmptyMessage(listRoot);\n }\n\n // Cache the conversation.\n loadedConversationsById[conversation.id] = conversation;\n\n return render([conversation], userId)\n .then(function(html) {\n var contentContainer = LazyLoadList.getContentContainer(root);\n return contentContainer.prepend(html);\n })\n .then(function() {\n return incrementTotalConversationCount(root);\n })\n .catch(Notification.exception);\n };\n\n /**\n * Delete a conversation from the list of conversations.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversationElement The conversation element.\n */\n var deleteConversation = function(root, conversationElement) {\n conversationElement.remove();\n decrementTotalConversationCount(root);\n\n var conversations = root.find(SELECTORS.CONVERSATION);\n if (!conversations.length) {\n // If we don't have any conversations then we need to hide\n // the content of the list and show the empty message.\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.hideContent(listRoot);\n LazyLoadList.showEmptyMessage(listRoot);\n }\n };\n\n /**\n * Mark a conversation as read.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversationElement The conversation element.\n */\n var markConversationAsRead = function(root, conversationElement) {\n var unreadCount = conversationElement.find(SELECTORS.UNREAD_COUNT);\n unreadCount.text('0');\n unreadCount.addClass('hidden');\n decrementTotalUnreadConversationCount(root);\n };\n\n /**\n * Listen to, and handle events in this section.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Array|null} types The conversation types for this section\n * @param {bool} includeFavourites If this section includes favourites\n * @param {String} fromPanel Routing argument to send if the section is loaded in message index left panel.\n */\n var registerEventListeners = function(namespace, root, loadCallback, types, includeFavourites, fromPanel) {\n var listRoot = LazyLoadList.getRoot(root);\n var conversationBelongsToThisSection = function(conversation) {\n // Make sure the type is an int so that the index of check matches correctly.\n var conversationType = parseInt(conversation.type, 10);\n if (\n // If the conversation type isn't one this section cares about then we can ignore it.\n (types && types.indexOf(conversationType) < 0) ||\n // If this is the favourites section and the conversation isn't a favourite then ignore it.\n (includeFavourites && !conversation.isFavourite) ||\n // If this section doesn't include favourites and the conversation is a favourite then ignore it.\n (!includeFavourites && conversation.isFavourite)\n ) {\n return false;\n }\n\n return true;\n };\n\n\n root[0].addEventListener('show.bs.collapse', function() {\n setExpanded(root);\n LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {\n return render(conversations, userId)\n .then(function(html) {\n contentContainer.append(html);\n return html;\n })\n .catch(Notification.exception);\n });\n });\n\n root[0].addEventListener('hidden.bs.collapse', function() {\n setCollapsed(root);\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONTACT_BLOCKED, function(userId) {\n var conversationElement = getConversationElementFromUserId(root, userId);\n if (conversationElement.length) {\n blockContact(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONTACT_UNBLOCKED, function(userId) {\n var conversationElement = getConversationElementFromUserId(root, userId);\n\n if (conversationElement.length) {\n unblockContact(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_MUTED, function(conversation) {\n var conversationId = conversation.id;\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n muteConversation(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_MUTED, function(conversation) {\n var conversationId = conversation.id;\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n unmuteConversation(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, function(conversation) {\n if (!conversationBelongsToThisSection(conversation)) {\n return;\n }\n\n var pendingPromise = new Pending('core_message/message_drawer_view_overview_section:new');\n var loggedInUserId = conversation.loggedInUserId;\n var conversationId = conversation.id;\n var element = getConversationElement(root, conversationId);\n conversation = formatConversationFromEvent(conversation);\n if (element.length) {\n var contentContainer = LazyLoadList.getContentContainer(root);\n render([conversation], loggedInUserId)\n .then(function(html) {\n if (deletedConversationsById[conversationId]) {\n // This conversation was deleted at some point since the messaging drawer was created.\n if (conversation.messages[0].timeadded < deletedConversationsById[conversationId]) {\n // The 'new' message was added before the conversation was deleted.\n // This is probably stale data.\n return;\n }\n }\n contentContainer.prepend(html);\n element.remove();\n\n return;\n })\n .then(pendingPromise.resolve)\n .catch(Notification.exception);\n } else if (conversation.messages.length) {\n createNewConversationFromEvent(root, conversation, loggedInUserId)\n .then(pendingPromise.resolve)\n .catch();\n } else {\n pendingPromise.resolve();\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_DELETED, function(conversationId) {\n var conversationElement = getConversationElement(root, conversationId);\n delete loadedConversationsById[conversationId];\n deletedConversationsById[conversationId] = new Date();\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ, function(conversationId) {\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n markConversationAsRead(root, conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_FAVOURITE, function(conversation) {\n var conversationElement = null;\n if (conversationBelongsToThisSection(conversation)) {\n conversationElement = getConversationElement(root, conversation.id);\n if (!conversationElement.length) {\n createNewConversationFromEvent(\n root,\n formatConversationFromEvent(conversation),\n conversation.loggedInUserId\n );\n }\n } else {\n conversationElement = getConversationElement(root, conversation.id);\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE, function(conversation) {\n var conversationElement = null;\n if (conversationBelongsToThisSection(conversation)) {\n conversationElement = getConversationElement(root, conversation.id);\n if (!conversationElement.length) {\n createNewConversationFromEvent(\n root,\n formatConversationFromEvent(conversation),\n conversation.loggedInUserId\n );\n }\n } else {\n conversationElement = getConversationElement(root, conversation.id);\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n }\n });\n\n CustomEvents.define(root, [CustomEvents.events.activate]);\n root.on(CustomEvents.events.activate, SELECTORS.CONVERSATION, function(e, data) {\n var conversationElement = $(e.target).closest(SELECTORS.CONVERSATION);\n var conversationId = conversationElement.attr('data-conversation-id');\n var conversation = loadedConversationsById[conversationId];\n MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONVERSATION, conversation, fromPanel);\n\n data.originalEvent.preventDefault();\n });\n };\n\n /**\n * Setup the section.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} header The header container element.\n * @param {Object} body The section container element.\n * @param {Object} footer The footer container element.\n * @param {Array} types The conversation types that show in this section\n * @param {bool} includeFavourites If this section includes favourites\n * @param {Object} totalCountPromise Resolves wth the total conversations count\n * @param {Object} unreadCountPromise Resolves wth the unread conversations count\n * @param {bool} fromPanel shown in message app panel.\n */\n var show = function(namespace, header, body, footer, types, includeFavourites, totalCountPromise, unreadCountPromise,\n fromPanel) {\n var root = $(body);\n\n if (!root.attr('data-init')) {\n var loadCallback = getLoadCallback(types, includeFavourites, 0);\n registerEventListeners(namespace, root, loadCallback, types, includeFavourites, fromPanel);\n\n if (isVisible(root)) {\n setExpanded(root);\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {\n return render(conversations, userId)\n .then(function(html) {\n contentContainer.append(html);\n return html;\n })\n .catch(Notification.exception);\n });\n }\n\n // This is given to us by the calling code because the total counts for all sections\n // are loaded in a single ajax request rather than one request per section.\n totalCountPromise.then(function(count) {\n renderTotalCount(root, count);\n loadedTotalCounts = true;\n return;\n })\n .catch(function() {\n // Silently ignore if we can't updated the counts. No need to bother the user.\n });\n\n // This is given to us by the calling code because the unread counts for all sections\n // are loaded in a single ajax request rather than one request per section.\n unreadCountPromise.then(function(count) {\n renderUnreadCount(root, count);\n loadedUnreadCounts = true;\n return;\n })\n .catch(function() {\n // Silently ignore if we can't updated the counts. No need to bother the user.\n });\n\n root.attr('data-init', true);\n }\n };\n\n return {\n show: show,\n isVisible: isVisible\n };\n});\n"],"names":["define","$","CustomEvents","Notification","PubSub","Str","Pending","Templates","UserDate","MessageRepository","MessageDrawerEvents","MessageDrawerRouter","MessageDrawerRoutes","LazyLoadList","MessageDrawerViewConversationContants","SELECTORS","TEMPLATES","loadedConversationsById","deletedConversationsById","loadedTotalCounts","loadedUnreadCounts","isVisible","root","getRoot","hasClass","setExpanded","addClass","renderUnreadCount","count","container","find","text","get_string","done","string","attr","removeClass","formatConversationFromEvent","conversation","recursivelyLowercaseKeys","object","Object","keys","reduce","carry","key","isArray","toLowerCase","map","formatted","messages","message","useridfrom","userfrom","id","render","conversations","userId","pending","mapPromises","lastMessage","length","async","tmpElement","document","createElement","innerHTML","replace","querySelector","messagePreview","indexOf","pix","label","includes","labelString","renderPix","error","exception","formatMessagePreview","then","formattedConversation","imageurl","name","subname","unreadcount","ismuted","lastmessagedate","timecreated","sentfromcurrentuser","lastmessage","otherUser","type","CONVERSATION_TYPES","SELF","members","PRIVATE","member","userid","showonlinestatus","isonline","isblocked","PUBLIC","lastsendername","fullname","catch","Promise","all","formattedConversations","forEach","Date","toDateString","istoday","html","js","resolve","Deferred","getTotalConversationCountElement","decrementTotalUnreadConversationCount","element","getTotalUnreadConversationCountElement","parseInt","getConversationElement","conversationId","getConversationElementFromUserId","createNewConversationFromEvent","listRoot","showContent","hideEmptyMessage","getContentContainer","prepend","incrementTotalConversationCount","deleteConversation","conversationElement","remove","decrementTotalConversationCount","hideContent","showEmptyMessage","registerEventListeners","namespace","loadCallback","types","includeFavourites","fromPanel","conversationBelongsToThisSection","conversationType","isFavourite","addEventListener","show","contentContainer","append","setCollapsed","subscribe","CONTACT_BLOCKED","blockContact","CONTACT_UNBLOCKED","unblockContact","CONVERSATION_SET_MUTED","muteConversation","CONVERSATION_UNSET_MUTED","unmuteConversation","CONVERSATION_NEW_LAST_MESSAGE","pendingPromise","loggedInUserId","timeadded","CONVERSATION_DELETED","CONVERSATION_READ","unreadCount","markConversationAsRead","CONVERSATION_SET_FAVOURITE","CONVERSATION_UNSET_FAVOURITE","events","activate","on","e","data","target","closest","go","VIEW_CONVERSATION","originalEvent","preventDefault","header","body","footer","totalCountPromise","unreadCountPromise","offset","includeSelfConversations","nonSelfConversationTypes","filter","candidate","getConversations","LOAD_LIMIT","response","slice","setLoadedAll","getLoadCallback","numPlaceholders","placeholders","Array","apply","renderTotalCount"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"message_drawer_view_overview_section.min.js","sources":["../src/message_drawer_view_overview_section.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls a section of the overview page in the message drawer.\n *\n * @module core_message/message_drawer_view_overview_section\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/notification',\n 'core/pubsub',\n 'core/str',\n 'core/pending',\n 'core/templates',\n 'core/user_date',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core_message/message_drawer_lazy_load_list',\n 'core_message/message_drawer_view_conversation_constants'\n],\nfunction(\n $,\n CustomEvents,\n Notification,\n PubSub,\n Str,\n Pending,\n Templates,\n UserDate,\n MessageRepository,\n MessageDrawerEvents,\n MessageDrawerRouter,\n MessageDrawerRoutes,\n LazyLoadList,\n MessageDrawerViewConversationContants\n) {\n\n var SELECTORS = {\n TOGGLE: '[data-region=\"toggle\"]',\n CONVERSATION: '[data-conversation-id]',\n BLOCKED_ICON_CONTAINER: '[data-region=\"contact-icon-blocked\"]',\n LAST_MESSAGE: '[data-region=\"last-message\"]',\n LAST_MESSAGE_DATE: '[data-region=\"last-message-date\"]',\n MUTED_ICON_CONTAINER: '[data-region=\"muted-icon-container\"]',\n UNREAD_COUNT: '[data-region=\"unread-count\"]',\n SECTION_TOTAL_COUNT: '[data-region=\"section-total-count\"]',\n SECTION_TOTAL_COUNT_CONTAINER: '[data-region=\"section-total-count-container\"]',\n SECTION_UNREAD_COUNT: '[data-region=\"section-unread-count\"]',\n SECTION_UNREAD_COUNT_CONTAINER: '[data-region=\"section-unread-count-container\"]',\n PLACEHOLDER_CONTAINER: '[data-region=\"placeholder-container\"]'\n };\n\n var TEMPLATES = {\n CONVERSATIONS_LIST: 'core_message/message_drawer_conversations_list',\n CONVERSATIONS_LIST_ITEMS_PLACEHOLDER: 'core_message/message_drawer_conversations_list_items_placeholder'\n };\n\n var LOAD_LIMIT = 50;\n var loadedConversationsById = {};\n var deletedConversationsById = {};\n var loadedTotalCounts = false;\n var loadedUnreadCounts = false;\n\n /**\n * Get the section visibility status.\n *\n * @param {Object} root The section container element.\n * @return {Bool} Is section visible.\n */\n var isVisible = function(root) {\n return LazyLoadList.getRoot(root).hasClass('show');\n };\n\n /**\n * Set this section as expanded.\n *\n * @param {Object} root The section container element.\n */\n var setExpanded = function(root) {\n root.addClass('expanded');\n };\n\n /**\n * Set this section as collapsed.\n *\n * @param {Object} root The section container element.\n */\n var setCollapsed = function(root) {\n root.removeClass('expanded');\n };\n\n /**\n * Render the total count value and show it for the user. Also update the placeholder\n * HTML for better visuals.\n *\n * @param {Object} root The section container element.\n * @param {Number} count The total count\n */\n var renderTotalCount = function(root, count) {\n var container = root.find(SELECTORS.SECTION_TOTAL_COUNT_CONTAINER);\n var countElement = container.find(SELECTORS.SECTION_TOTAL_COUNT);\n countElement.text(count);\n container.removeClass('hidden');\n Str.get_string('totalconversations', 'core_message', count).done(function(string) {\n $('#' + container.attr('aria-labelledby')).text(string);\n });\n\n var numPlaceholders = count > 20 ? 20 : count;\n // Array of \"true\" up to the number of placeholders we want.\n var placeholders = Array.apply(null, Array(numPlaceholders)).map(function() {\n return true;\n });\n\n // Replace the current placeholder (loading spinner) with some nicer placeholders that\n // better represent the content.\n Templates.render(TEMPLATES.CONVERSATIONS_LIST_ITEMS_PLACEHOLDER, {placeholders: placeholders})\n .then(function(html) {\n var placeholderContainer = root.find(SELECTORS.PLACEHOLDER_CONTAINER);\n placeholderContainer.html(html);\n return;\n })\n .catch(function() {\n // Silently ignore. Doesn't matter if we can't render the placeholders.\n });\n };\n\n /**\n * Render the unread count value and show it for the user if it's higher than zero.\n *\n * @param {Object} root The section container element.\n * @param {Number} count The unread count\n */\n var renderUnreadCount = function(root, count) {\n var container = root.find(SELECTORS.SECTION_UNREAD_COUNT_CONTAINER);\n var countElement = container.find(SELECTORS.SECTION_UNREAD_COUNT);\n countElement.text(count);\n\n Str.get_string('unreadconversations', 'core_message', count).done(function(string) {\n $('#' + container.attr('aria-labelledby')).text(string);\n });\n\n if (count > 0) {\n container.removeClass('hidden');\n } else {\n container.addClass('hidden');\n }\n };\n\n /**\n * Create a formatted conversation object from the the one we get from events. The new object\n * will be in a format that matches what we receive from the server.\n *\n * @param {Object} conversation\n * @return {Object} formatted conversation.\n */\n var formatConversationFromEvent = function(conversation) {\n // Recursively lowercase all of the keys for an object.\n var recursivelyLowercaseKeys = function(object) {\n return Object.keys(object).reduce(function(carry, key) {\n if ($.isArray(object[key])) {\n carry[key.toLowerCase()] = object[key].map(recursivelyLowercaseKeys);\n } else {\n carry[key.toLowerCase()] = object[key];\n }\n\n return carry;\n }, {});\n };\n\n // Recursively lowercase all of the keys for the conversation.\n var formatted = recursivelyLowercaseKeys(conversation);\n\n // Make sure all messages have the useridfrom property set.\n formatted.messages = formatted.messages.map(function(message) {\n message.useridfrom = message.userfrom.id;\n return message;\n });\n\n return formatted;\n };\n\n /**\n * Render the messages in the overview page.\n *\n * @param {Array} conversations List of conversations to render.\n * @param {Number} userId Logged in user id.\n * @return {Object} jQuery promise.\n */\n var render = function(conversations, userId) {\n\n // Helper to format the last message for rendering.\n // Returns a promise which resolves to either a string, or null\n // (such as in the event of an empty personal space).\n var pending = new Pending();\n\n var formatMessagePreview = async function(lastMessage) {\n if (!lastMessage) {\n return null;\n }\n // Check the message html for a src attribute, indicative of media.\n // Replace LOAD_LIMIT) {\n conversations = conversations.slice(0, -1);\n } else {\n LazyLoadList.setLoadedAll(root, true);\n }\n\n offset = offset + LOAD_LIMIT;\n\n conversations.forEach(function(conversation) {\n loadedConversationsById[conversation.id] = conversation;\n });\n\n return conversations;\n })\n .catch(Notification.exception);\n };\n };\n\n /**\n * Get the total count container element.\n *\n * @param {Object} root Overview messages container element.\n * @return {Object} Total count container element.\n */\n var getTotalConversationCountElement = function(root) {\n return root.find(SELECTORS.SECTION_TOTAL_COUNT);\n };\n\n /**\n * Get the unread conversations count container element.\n *\n * @param {Object} root Overview messages container element.\n * @return {Object} Unread conversations count container element.\n */\n var getTotalUnreadConversationCountElement = function(root) {\n return root.find(SELECTORS.SECTION_UNREAD_COUNT);\n };\n\n /**\n * Increment the total conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var incrementTotalConversationCount = function(root) {\n if (loadedTotalCounts) {\n var element = getTotalConversationCountElement(root);\n var count = parseInt(element.text());\n count = count + 1;\n element.text(count);\n }\n };\n\n /**\n * Decrement the total conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var decrementTotalConversationCount = function(root) {\n if (loadedTotalCounts) {\n var element = getTotalConversationCountElement(root);\n var count = parseInt(element.text());\n count = count - 1;\n element.text(count);\n }\n };\n\n /**\n * Decrement the total unread conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var decrementTotalUnreadConversationCount = function(root) {\n if (loadedUnreadCounts) {\n var element = getTotalUnreadConversationCountElement(root);\n var count = parseInt(element.text());\n count = count - 1;\n // Re-render the unread count.\n renderUnreadCount(root, count);\n }\n };\n\n /**\n * Get a contact / conversation element.\n *\n * @param {Object} root Overview messages container element.\n * @param {Number} conversationId The conversation id.\n * @return {Object} Conversation element.\n */\n var getConversationElement = function(root, conversationId) {\n return root.find('[data-conversation-id=\"' + conversationId + '\"]');\n };\n\n /**\n * Get a contact / conversation element from a user id.\n *\n * @param {Object} root Overview messages container element.\n * @param {Number} userId The user id.\n * @return {Object} Conversation element.\n */\n var getConversationElementFromUserId = function(root, userId) {\n return root.find('[data-user-id=\"' + userId + '\"]');\n };\n\n /**\n * Show the conversation is muted icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var muteConversation = function(conversationElement) {\n conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the conversation is muted icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var unmuteConversation = function(conversationElement) {\n conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the contact is blocked icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var blockContact = function(conversationElement) {\n conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the contact is blocked icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var unblockContact = function(conversationElement) {\n conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Create an render new conversation element in the list of conversations.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversation The conversation.\n * @param {Number} userId The logged in user id.\n * @return {Object} jQuery promise\n */\n var createNewConversationFromEvent = function(root, conversation, userId) {\n var existingConversations = root.find(SELECTORS.CONVERSATION);\n\n if (!existingConversations.length) {\n // If we didn't have any conversations then we need to show\n // the content of the list and hide the empty message.\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.showContent(listRoot);\n LazyLoadList.hideEmptyMessage(listRoot);\n }\n\n // Cache the conversation.\n loadedConversationsById[conversation.id] = conversation;\n\n return render([conversation], userId)\n .then(function(html) {\n var contentContainer = LazyLoadList.getContentContainer(root);\n return contentContainer.prepend(html);\n })\n .then(function() {\n return incrementTotalConversationCount(root);\n })\n .catch(Notification.exception);\n };\n\n /**\n * Delete a conversation from the list of conversations.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversationElement The conversation element.\n */\n var deleteConversation = function(root, conversationElement) {\n conversationElement.remove();\n decrementTotalConversationCount(root);\n\n var conversations = root.find(SELECTORS.CONVERSATION);\n if (!conversations.length) {\n // If we don't have any conversations then we need to hide\n // the content of the list and show the empty message.\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.hideContent(listRoot);\n LazyLoadList.showEmptyMessage(listRoot);\n }\n };\n\n /**\n * Mark a conversation as read.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversationElement The conversation element.\n */\n var markConversationAsRead = function(root, conversationElement) {\n var unreadCount = conversationElement.find(SELECTORS.UNREAD_COUNT);\n unreadCount.text('0');\n unreadCount.addClass('hidden');\n decrementTotalUnreadConversationCount(root);\n };\n\n /**\n * Listen to, and handle events in this section.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Array|null} types The conversation types for this section\n * @param {bool} includeFavourites If this section includes favourites\n * @param {String} fromPanel Routing argument to send if the section is loaded in message index left panel.\n */\n var registerEventListeners = function(namespace, root, loadCallback, types, includeFavourites, fromPanel) {\n var listRoot = LazyLoadList.getRoot(root);\n var conversationBelongsToThisSection = function(conversation) {\n // Make sure the type is an int so that the index of check matches correctly.\n var conversationType = parseInt(conversation.type, 10);\n if (\n // If the conversation type isn't one this section cares about then we can ignore it.\n (types && types.indexOf(conversationType) < 0) ||\n // If this is the favourites section and the conversation isn't a favourite then ignore it.\n (includeFavourites && !conversation.isFavourite) ||\n // If this section doesn't include favourites and the conversation is a favourite then ignore it.\n (!includeFavourites && conversation.isFavourite)\n ) {\n return false;\n }\n\n return true;\n };\n\n\n root[0].addEventListener('show.bs.collapse', function() {\n setExpanded(root);\n LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {\n return render(conversations, userId)\n .then(function(html) {\n contentContainer.append(html);\n return html;\n })\n .catch(Notification.exception);\n });\n });\n\n root[0].addEventListener('hidden.bs.collapse', function() {\n setCollapsed(root);\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONTACT_BLOCKED, function(userId) {\n var conversationElement = getConversationElementFromUserId(root, userId);\n if (conversationElement.length) {\n blockContact(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONTACT_UNBLOCKED, function(userId) {\n var conversationElement = getConversationElementFromUserId(root, userId);\n\n if (conversationElement.length) {\n unblockContact(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_MUTED, function(conversation) {\n var conversationId = conversation.id;\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n muteConversation(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_MUTED, function(conversation) {\n var conversationId = conversation.id;\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n unmuteConversation(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, function(conversation) {\n if (!conversationBelongsToThisSection(conversation)) {\n return;\n }\n\n var pendingPromise = new Pending('core_message/message_drawer_view_overview_section:new');\n var loggedInUserId = conversation.loggedInUserId;\n var conversationId = conversation.id;\n var element = getConversationElement(root, conversationId);\n conversation = formatConversationFromEvent(conversation);\n if (element.length) {\n var contentContainer = LazyLoadList.getContentContainer(root);\n render([conversation], loggedInUserId)\n .then(function(html) {\n if (deletedConversationsById[conversationId]) {\n // This conversation was deleted at some point since the messaging drawer was created.\n if (conversation.messages[0].timeadded < deletedConversationsById[conversationId]) {\n // The 'new' message was added before the conversation was deleted.\n // This is probably stale data.\n return;\n }\n }\n contentContainer.prepend(html);\n element.remove();\n\n return;\n })\n .then(pendingPromise.resolve)\n .catch(Notification.exception);\n } else if (conversation.messages.length) {\n createNewConversationFromEvent(root, conversation, loggedInUserId)\n .then(pendingPromise.resolve)\n .catch();\n } else {\n pendingPromise.resolve();\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_DELETED, function(conversationId) {\n var conversationElement = getConversationElement(root, conversationId);\n delete loadedConversationsById[conversationId];\n deletedConversationsById[conversationId] = new Date();\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ, function(conversationId) {\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n markConversationAsRead(root, conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_FAVOURITE, function(conversation) {\n var conversationElement = null;\n if (conversationBelongsToThisSection(conversation)) {\n conversationElement = getConversationElement(root, conversation.id);\n if (!conversationElement.length) {\n createNewConversationFromEvent(\n root,\n formatConversationFromEvent(conversation),\n conversation.loggedInUserId\n );\n }\n } else {\n conversationElement = getConversationElement(root, conversation.id);\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE, function(conversation) {\n var conversationElement = null;\n if (conversationBelongsToThisSection(conversation)) {\n conversationElement = getConversationElement(root, conversation.id);\n if (!conversationElement.length) {\n createNewConversationFromEvent(\n root,\n formatConversationFromEvent(conversation),\n conversation.loggedInUserId\n );\n }\n } else {\n conversationElement = getConversationElement(root, conversation.id);\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n }\n });\n\n CustomEvents.define(root, [CustomEvents.events.activate]);\n root.on(CustomEvents.events.activate, SELECTORS.CONVERSATION, function(e, data) {\n var conversationElement = $(e.target).closest(SELECTORS.CONVERSATION);\n var conversationId = conversationElement.attr('data-conversation-id');\n var conversation = loadedConversationsById[conversationId];\n MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONVERSATION, conversation, fromPanel);\n\n data.originalEvent.preventDefault();\n });\n };\n\n /**\n * Setup the section.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} header The header container element.\n * @param {Object} body The section container element.\n * @param {Object} footer The footer container element.\n * @param {Array} types The conversation types that show in this section\n * @param {bool} includeFavourites If this section includes favourites\n * @param {Object} totalCountPromise Resolves wth the total conversations count\n * @param {Object} unreadCountPromise Resolves wth the unread conversations count\n * @param {bool} fromPanel shown in message app panel.\n */\n var show = function(namespace, header, body, footer, types, includeFavourites, totalCountPromise, unreadCountPromise,\n fromPanel) {\n var root = $(body);\n\n if (!root.attr('data-init')) {\n var loadCallback = getLoadCallback(types, includeFavourites, 0);\n registerEventListeners(namespace, root, loadCallback, types, includeFavourites, fromPanel);\n\n if (isVisible(root)) {\n setExpanded(root);\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {\n return render(conversations, userId)\n .then(function(html) {\n contentContainer.append(html);\n return html;\n })\n .catch(Notification.exception);\n });\n }\n\n // This is given to us by the calling code because the total counts for all sections\n // are loaded in a single ajax request rather than one request per section.\n totalCountPromise.then(function(count) {\n renderTotalCount(root, count);\n loadedTotalCounts = true;\n return;\n })\n .catch(function() {\n // Silently ignore if we can't updated the counts. No need to bother the user.\n });\n\n // This is given to us by the calling code because the unread counts for all sections\n // are loaded in a single ajax request rather than one request per section.\n unreadCountPromise.then(function(count) {\n renderUnreadCount(root, count);\n loadedUnreadCounts = true;\n return;\n })\n .catch(function() {\n // Silently ignore if we can't updated the counts. No need to bother the user.\n });\n\n root.attr('data-init', true);\n }\n };\n\n return {\n show: show,\n isVisible: isVisible\n };\n});\n"],"names":["define","$","CustomEvents","Notification","PubSub","Str","Pending","Templates","UserDate","MessageRepository","MessageDrawerEvents","MessageDrawerRouter","MessageDrawerRoutes","LazyLoadList","MessageDrawerViewConversationContants","SELECTORS","TEMPLATES","loadedConversationsById","deletedConversationsById","loadedTotalCounts","loadedUnreadCounts","isVisible","root","getRoot","hasClass","setExpanded","addClass","renderUnreadCount","count","container","find","text","get_string","done","string","attr","removeClass","formatConversationFromEvent","conversation","recursivelyLowercaseKeys","object","Object","keys","reduce","carry","key","isArray","toLowerCase","map","formatted","messages","message","useridfrom","userfrom","id","render","conversations","userId","pending","mapPromises","lastMessage","length","async","tmpElement","document","createElement","innerHTML","replace","querySelector","messagePreview","textContent","innerText","indexOf","pix","label","includes","labelString","renderPix","error","exception","formatMessagePreview","then","formattedConversation","imageurl","name","subname","unreadcount","ismuted","lastmessagedate","timecreated","sentfromcurrentuser","lastmessage","otherUser","type","CONVERSATION_TYPES","SELF","members","PRIVATE","member","userid","showonlinestatus","isonline","isblocked","PUBLIC","lastsendername","fullname","catch","Promise","all","formattedConversations","forEach","Date","toDateString","istoday","html","js","resolve","Deferred","getTotalConversationCountElement","decrementTotalUnreadConversationCount","element","getTotalUnreadConversationCountElement","parseInt","getConversationElement","conversationId","getConversationElementFromUserId","createNewConversationFromEvent","listRoot","showContent","hideEmptyMessage","getContentContainer","prepend","incrementTotalConversationCount","deleteConversation","conversationElement","remove","decrementTotalConversationCount","hideContent","showEmptyMessage","registerEventListeners","namespace","loadCallback","types","includeFavourites","fromPanel","conversationBelongsToThisSection","conversationType","isFavourite","addEventListener","show","contentContainer","append","setCollapsed","subscribe","CONTACT_BLOCKED","blockContact","CONTACT_UNBLOCKED","unblockContact","CONVERSATION_SET_MUTED","muteConversation","CONVERSATION_UNSET_MUTED","unmuteConversation","CONVERSATION_NEW_LAST_MESSAGE","pendingPromise","loggedInUserId","timeadded","CONVERSATION_DELETED","CONVERSATION_READ","unreadCount","markConversationAsRead","CONVERSATION_SET_FAVOURITE","CONVERSATION_UNSET_FAVOURITE","events","activate","on","e","data","target","closest","go","VIEW_CONVERSATION","originalEvent","preventDefault","header","body","footer","totalCountPromise","unreadCountPromise","offset","includeSelfConversations","nonSelfConversationTypes","filter","candidate","getConversations","LOAD_LIMIT","response","slice","setLoadedAll","getLoadCallback","numPlaceholders","placeholders","Array","apply","renderTotalCount"],"mappings":";;;;;;;AAsBAA,2DACA,CACI,SACA,iCACA,oBACA,cACA,WACA,eACA,iBACA,iBACA,kCACA,qCACA,qCACA,qCACA,6CACA,4DAEJ,SACIC,EACAC,aACAC,aACAC,OACAC,IACAC,QACAC,UACAC,SACAC,kBACAC,oBACAC,oBACAC,oBACAC,aACAC,2CAGIC,uBAEc,yBAFdA,iCAGwB,uCAHxBA,+BAMsB,uCANtBA,uBAOc,+BAPdA,8BAQqB,sCARrBA,wCAS+B,gDAT/BA,+BAUsB,uCAVtBA,yCAWgC,iDAXhCA,gCAYuB,wCAGvBC,6BACoB,iDADpBA,+CAEsC,mEAItCC,wBAA0B,GAC1BC,yBAA2B,GAC3BC,mBAAoB,EACpBC,oBAAqB,EAQrBC,UAAY,SAASC,aACdT,aAAaU,QAAQD,MAAME,SAAS,SAQ3CC,YAAc,SAASH,MACvBA,KAAKI,SAAS,aAqDdC,kBAAoB,SAASL,KAAMM,WAC/BC,UAAYP,KAAKQ,KAAKf,0CACPc,UAAUC,KAAKf,gCACrBgB,KAAKH,OAElBvB,IAAI2B,WAAW,sBAAuB,eAAgBJ,OAAOK,MAAK,SAASC,QACvEjC,EAAE,IAAM4B,UAAUM,KAAK,oBAAoBJ,KAAKG,WAGhDN,MAAQ,EACRC,UAAUO,YAAY,UAEtBP,UAAUH,SAAS,WAWvBW,4BAA8B,SAASC,kBAEnCC,yBAA2B,SAASC,eAC7BC,OAAOC,KAAKF,QAAQG,QAAO,SAASC,MAAOC,YAC1C5C,EAAE6C,QAAQN,OAAOK,MACjBD,MAAMC,IAAIE,eAAiBP,OAAOK,KAAKG,IAAIT,0BAE3CK,MAAMC,IAAIE,eAAiBP,OAAOK,KAG/BD,QACR,KAIHK,UAAYV,yBAAyBD,qBAGzCW,UAAUC,SAAWD,UAAUC,SAASF,KAAI,SAASG,gBACjDA,QAAQC,WAAaD,QAAQE,SAASC,GAC/BH,WAGJF,WAUPM,OAAS,SAASC,cAAeC,YAK7BC,QAAU,IAAIpD,QAkDdqD,YAAcH,cAAcR,KAAI,SAASV,kBAErCsB,YAActB,aAAaY,SAASW,OAASvB,aAAaY,SAASZ,aAAaY,SAASW,OAAS,GAAK,YAlDpFC,eAAeF,iBACjCA,mBACM,SAIPG,WAAaC,SAASC,cAAc,cACxCF,WAAWG,UAAYN,YAAY7B,KAAKoC,QAAQ,SAAU,YAC5CJ,WAAWK,cAAc,SAEzB,KAINC,eAAiBN,WAAWO,aAAeP,WAAWQ,WAAa,MACnEF,iBAEoC,GAAhCA,eAAeG,QAAQ,YAChBH,mBAMfI,IAAM,oCACNC,MAAQ,kCAERd,YAAY7B,KAAK4C,SAAS,SAC1BF,IAAM,wBACNC,MAAQ,uBACDd,YAAY7B,KAAK4C,SAAS,WACjCF,IAAM,wBACNC,MAAQ,uBACDd,YAAY7B,KAAK4C,SAAS,YACjCF,IAAM,wBACNC,MAAQ,+BAIJE,kBAAoBvE,IAAI2B,WAAW0C,MAAO,6BAC7BnE,UAAUsE,UAAUJ,IAAK,OAAQG,aACpC,IAAMA,YACtB,MAAOE,cACL3E,aAAa4E,UAAUD,OAChB,MAQJE,CAAqBpB,aACvBqB,MAAK,SAASZ,oBACPa,sBAAwB,CACxB5B,GAAIhB,aAAagB,GACjB6B,SAAU7C,aAAa6C,SACvBC,KAAM9C,aAAa8C,KACnBC,QAAS/C,aAAa+C,QACtBC,YAAahD,aAAagD,YAC1BC,QAASjD,aAAaiD,QACtBC,gBAAiB5B,YAAcA,YAAY6B,YAAc,KACzDC,oBAAqB9B,YAAcA,YAAYR,YAAcK,OAAS,KACtEkC,YAAatB,gBAGbuB,UAAY,YACZtD,aAAauD,MAAQ/E,sCAAsCgF,mBAAmBC,KAE9EH,UAAYtD,aAAa0D,QAAQ,GAC1B1D,aAAauD,MAAQ/E,sCAAsCgF,mBAAmBG,UAErFL,UAAYtD,aAAa0D,QAAQrD,QAAO,SAASC,MAAOsD,eAC/CtD,OAASsD,OAAO5C,IAAMG,SACvBb,MAAQsD,QAELtD,QACR,OAGW,OAAdgD,YACAV,sBAAsBiB,OAASP,UAAUtC,GACzC4B,sBAAsBkB,iBAAmBR,UAAUQ,iBACnDlB,sBAAsBmB,SAAWT,UAAUS,SAC3CnB,sBAAsBoB,UAAYV,UAAUU,WAG5ChE,aAAauD,MAAQ/E,sCAAsCgF,mBAAmBS,SAC9ErB,sBAAsBsB,eAAiBlE,aAAa0D,QAAQrD,QAAO,SAASC,MAAOsD,eAC1EtD,OAASgB,aAAesC,OAAO5C,IAAMM,YAAYR,aAClDR,MAAQsD,OAAOO,UAEZ7D,QACR,OAGAsC,yBACRwB,MAAMvG,aAAa4E,qBAGvB4B,QAAQC,IAAIjD,aACdsB,MAAK,SAAS4B,+BACXA,uBAAuBC,SAAQ,SAASxE,eAChC,IAAIyE,MAAOC,gBAAkB,IAAID,KAAoC,IAA/BzE,aAAakD,iBAAwBwB,iBAC3E1E,aAAa2E,SAAU,MAIxB1G,UAAUgD,OAAOvC,6BAA8B,CAACwC,cAAeqD,4BACvE5B,MAAK,SAASiC,KAAMC,WACnBzD,QAAQ0D,UACDnH,EAAEoH,WAAWD,QAAQF,KAAMC,OACnCT,OAAM,SAAS5B,OACdpB,QAAQ0D,UACRjH,aAAa4E,UAAUD,WAuE/BwC,iCAAmC,SAAShG,aACrCA,KAAKQ,KAAKf,gCA8CjBwG,sCAAwC,SAASjG,SAC7CF,mBAAoB,KAChBoG,QAvCiC,SAASlG,aAC3CA,KAAKQ,KAAKf,gCAsCC0G,CAAuCnG,MACjDM,MAAQ8F,SAASF,QAAQzF,QAG7BJ,kBAAkBL,KAFlBM,OAAgB,KAapB+F,uBAAyB,SAASrG,KAAMsG,uBACjCtG,KAAKQ,KAAK,0BAA4B8F,eAAiB,OAU9DC,iCAAmC,SAASvG,KAAMmC,eAC3CnC,KAAKQ,KAAK,kBAAoB2B,OAAS,OA+C9CqE,+BAAiC,SAASxG,KAAMgB,aAAcmB,YAClCnC,KAAKQ,KAAKf,wBAEX8C,OAAQ,KAG3BkE,SAAWlH,aAAaU,QAAQD,MACpCT,aAAamH,YAAYD,UACzBlH,aAAaoH,iBAAiBF,iBAIlC9G,wBAAwBqB,aAAagB,IAAMhB,aAEpCiB,OAAO,CAACjB,cAAemB,QACzBwB,MAAK,SAASiC,aACYrG,aAAaqH,oBAAoB5G,MAChC6G,QAAQjB,SAEnCjC,MAAK,kBA3HwB,SAAS3D,SACvCH,kBAAmB,KACfqG,QAAUF,iCAAiChG,MAC3CM,MAAQ8F,SAASF,QAAQzF,QAC7BH,OAAgB,EAChB4F,QAAQzF,KAAKH,QAuHFwG,CAAgC9G,SAE1CoF,MAAMvG,aAAa4E,YASxBsD,mBAAqB,SAAS/G,KAAMgH,wBACpCA,oBAAoBC,SA1Hc,SAASjH,SACvCH,kBAAmB,KACfqG,QAAUF,iCAAiChG,MAC3CM,MAAQ8F,SAASF,QAAQzF,QAC7BH,OAAgB,EAChB4F,QAAQzF,KAAKH,QAsHjB4G,CAAgClH,OAEZA,KAAKQ,KAAKf,wBACX8C,OAAQ,KAGnBkE,SAAWlH,aAAaU,QAAQD,MACpCT,aAAa4H,YAAYV,UACzBlH,aAAa6H,iBAAiBX,YA2BlCY,uBAAyB,SAASC,UAAWtH,KAAMuH,aAAcC,MAAOC,kBAAmBC,eACvFjB,SAAWlH,aAAaU,QAAQD,MAChC2H,iCAAmC,SAAS3G,kBAExC4G,iBAAmBxB,SAASpF,aAAauD,KAAM,YAG9CiD,OAASA,MAAMtE,QAAQ0E,kBAAoB,GAE3CH,oBAAsBzG,aAAa6G,cAElCJ,mBAAqBzG,aAAa6G,cAS5C7H,KAAK,GAAG8H,iBAAiB,oBAAoB,WACzC3H,YAAYH,MACZT,aAAawI,KAAKtB,SAAUc,cAAc,SAASS,iBAAkB9F,cAAeC,eACzEF,OAAOC,cAAeC,QACxBwB,MAAK,SAASiC,aACXoC,iBAAiBC,OAAOrC,MACjBA,QAEVR,MAAMvG,aAAa4E,iBAIhCzD,KAAK,GAAG8H,iBAAiB,sBAAsB,YApgBhC,SAAS9H,MACxBA,KAAKc,YAAY,YAogBboH,CAAalI,SAGjBlB,OAAOqJ,UAAU/I,oBAAoBgJ,iBAAiB,SAASjG,YACvD6E,oBAAsBT,iCAAiCvG,KAAMmC,QAC7D6E,oBAAoBzE,QA/Hb,SAASyE,qBACxBA,oBAAoBxG,KAAKf,kCAAkCqB,YAAY,UA+H/DuH,CAAarB,wBAIrBlI,OAAOqJ,UAAU/I,oBAAoBkJ,mBAAmB,SAASnG,YACzD6E,oBAAsBT,iCAAiCvG,KAAMmC,QAE7D6E,oBAAoBzE,QA9HX,SAASyE,qBAC1BA,oBAAoBxG,KAAKf,kCAAkCW,SAAS,UA8H5DmI,CAAevB,wBAIvBlI,OAAOqJ,UAAU/I,oBAAoBoJ,wBAAwB,SAASxH,kBAC9DsF,eAAiBtF,aAAagB,GAC9BgF,oBAAsBX,uBAAuBrG,KAAMsG,gBACnDU,oBAAoBzE,QAjKT,SAASyE,qBAC5BA,oBAAoBxG,KAAKf,gCAAgCqB,YAAY,UAiK7D2H,CAAiBzB,wBAIzBlI,OAAOqJ,UAAU/I,oBAAoBsJ,0BAA0B,SAAS1H,kBAChEsF,eAAiBtF,aAAagB,GAC9BgF,oBAAsBX,uBAAuBrG,KAAMsG,gBACnDU,oBAAoBzE,QAhKP,SAASyE,qBAC9BA,oBAAoBxG,KAAKf,gCAAgCW,SAAS,UAgK1DuI,CAAmB3B,wBAI3BlI,OAAOqJ,UAAU/I,oBAAoBwJ,+BAA+B,SAAS5H,iBACpE2G,iCAAiC3G,mBAIlC6H,eAAiB,IAAI7J,QAAQ,yDAC7B8J,eAAiB9H,aAAa8H,eAC9BxC,eAAiBtF,aAAagB,GAC9BkE,QAAUG,uBAAuBrG,KAAMsG,mBAC3CtF,aAAeD,4BAA4BC,cACvCkF,QAAQ3D,OAAQ,KACZyF,iBAAmBzI,aAAaqH,oBAAoB5G,MACxDiC,OAAO,CAACjB,cAAe8H,gBAClBnF,MAAK,SAASiC,MACPhG,yBAAyB0G,iBAErBtF,aAAaY,SAAS,GAAGmH,UAAYnJ,yBAAyB0G,kBAMtE0B,iBAAiBnB,QAAQjB,MACzBM,QAAQe,aAIXtD,KAAKkF,eAAe/C,SACpBV,MAAMvG,aAAa4E,gBACjBzC,aAAaY,SAASW,OAC7BiE,+BAA+BxG,KAAMgB,aAAc8H,gBAClDnF,KAAKkF,eAAe/C,SACpBV,QAEDyD,eAAe/C,cAIvBhH,OAAOqJ,UAAU/I,oBAAoB4J,sBAAsB,SAAS1C,oBAC5DU,oBAAsBX,uBAAuBrG,KAAMsG,uBAChD3G,wBAAwB2G,gBAC/B1G,yBAAyB0G,gBAAkB,IAAIb,KAC3CuB,oBAAoBzE,QACpBwE,mBAAmB/G,KAAMgH,wBAIjClI,OAAOqJ,UAAU/I,oBAAoB6J,mBAAmB,SAAS3C,oBACzDU,oBAAsBX,uBAAuBrG,KAAMsG,gBACnDU,oBAAoBzE,QArIH,SAASvC,KAAMgH,yBACpCkC,YAAclC,oBAAoBxG,KAAKf,wBAC3CyJ,YAAYzI,KAAK,KACjByI,YAAY9I,SAAS,UACrB6F,sCAAsCjG,MAkI9BmJ,CAAuBnJ,KAAMgH,wBAIrClI,OAAOqJ,UAAU/I,oBAAoBgK,4BAA4B,SAASpI,kBAClEgG,oBAAsB,KACtBW,iCAAiC3G,eACjCgG,oBAAsBX,uBAAuBrG,KAAMgB,aAAagB,KACvCO,QACrBiE,+BACIxG,KACAe,4BAA4BC,cAC5BA,aAAa8H,iBAIrB9B,oBAAsBX,uBAAuBrG,KAAMgB,aAAagB,KACxCO,QACpBwE,mBAAmB/G,KAAMgH,wBAKrClI,OAAOqJ,UAAU/I,oBAAoBiK,8BAA8B,SAASrI,kBACpEgG,oBAAsB,KACtBW,iCAAiC3G,eACjCgG,oBAAsBX,uBAAuBrG,KAAMgB,aAAagB,KACvCO,QACrBiE,+BACIxG,KACAe,4BAA4BC,cAC5BA,aAAa8H,iBAIrB9B,oBAAsBX,uBAAuBrG,KAAMgB,aAAagB,KACxCO,QACpBwE,mBAAmB/G,KAAMgH,wBAKrCpI,aAAaF,OAAOsB,KAAM,CAACpB,aAAa0K,OAAOC,WAC/CvJ,KAAKwJ,GAAG5K,aAAa0K,OAAOC,SAAU9J,wBAAwB,SAASgK,EAAGC,UAElEpD,eADsB3H,EAAE8K,EAAEE,QAAQC,QAAQnK,wBACLoB,KAAK,wBAC1CG,aAAerB,wBAAwB2G,gBAC3CjH,oBAAoBwK,GAAGvC,UAAWhI,oBAAoBwK,kBAAmB9I,aAAc0G,WAEvFgC,KAAKK,cAAcC,2BAgEpB,CACHjC,KAhDO,SAAST,UAAW2C,OAAQC,KAAMC,OAAQ3C,MAAOC,kBAAmB2C,kBAAmBC,mBAC9F3C,eACI1H,KAAOrB,EAAEuL,UAERlK,KAAKa,KAAK,aAAc,KACrB0G,aAtbU,SAASC,MAAOC,kBAAmB6C,YAOjD/F,KAAO,KAEPgG,0BAA2B,KAC3B/C,OAASA,MAAMjF,OAAQ,KAEnBiI,yBAA2BhD,MAAMiD,QAAO,SAASC,kBAC1CA,WAAalL,sCAAsCgF,mBAAmBC,QAIjF8F,yBAA2B/C,MAAMjF,QAAUiI,yBAAyBjI,OAGpEgC,KAAOiG,yBAAyB,UAG7B,SAASxK,KAAMmC,eACXhD,kBAAkBwL,iBACjBxI,OACAoC,KACAqG,GACAN,OACA7C,kBACA8C,0BAEH5G,MAAK,SAASkH,cACP3I,cAAgB2I,SAAS3I,qBAEzBA,cAAcK,OA3SjB,GA4SGL,cAAgBA,cAAc4I,MAAM,GAAI,GAExCvL,aAAawL,aAAa/K,MAAM,GAGpCsK,QAjTC,GAmTDpI,cAAcsD,SAAQ,SAASxE,cAC3BrB,wBAAwBqB,aAAagB,IAAMhB,gBAGxCkB,iBAEVkD,MAAMvG,aAAa4E,YAqYLuH,CAAgBxD,MAAOC,kBAAmB,MAC7DJ,uBAAuBC,UAAWtH,KAAMuH,aAAcC,MAAOC,kBAAmBC,WAE5E3H,UAAUC,MAAO,CACjBG,YAAYH,UACRyG,SAAWlH,aAAaU,QAAQD,MACpCT,aAAawI,KAAKtB,SAAUc,cAAc,SAASS,iBAAkB9F,cAAeC,eACzEF,OAAOC,cAAeC,QACxBwB,MAAK,SAASiC,aACXoC,iBAAiBC,OAAOrC,MACjBA,QAEVR,MAAMvG,aAAa4E,cAMhC2G,kBAAkBzG,MAAK,SAASrD,QAvqBjB,SAASN,KAAMM,WAC9BC,UAAYP,KAAKQ,KAAKf,yCACPc,UAAUC,KAAKf,+BACrBgB,KAAKH,OAClBC,UAAUO,YAAY,UACtB/B,IAAI2B,WAAW,qBAAsB,eAAgBJ,OAAOK,MAAK,SAASC,QACtEjC,EAAE,IAAM4B,UAAUM,KAAK,oBAAoBJ,KAAKG,eAGhDqK,gBAAkB3K,MAAQ,GAAK,GAAKA,MAEpC4K,aAAeC,MAAMC,MAAM,KAAMD,MAAMF,kBAAkBvJ,KAAI,kBACtD,KAKXzC,UAAUgD,OAAOvC,+CAAgD,CAACwL,aAAcA,eAC3EvH,MAAK,SAASiC,MACgB5F,KAAKQ,KAAKf,iCAChBmG,KAAKA,SAG7BR,OAAM,eAipBHiG,CAAiBrL,KAAMM,OACvBT,mBAAoB,KAGvBuF,OAAM,eAMPiF,mBAAmB1G,MAAK,SAASrD,OAC7BD,kBAAkBL,KAAMM,OACxBR,oBAAqB,KAGxBsF,OAAM,eAIPpF,KAAKa,KAAK,aAAa,KAM3Bd,UAAWA"} \ No newline at end of file diff --git a/public/message/amd/src/message_drawer_view_overview_section.js b/public/message/amd/src/message_drawer_view_overview_section.js index 8b983758971..982dbc007b4 100644 --- a/public/message/amd/src/message_drawer_view_overview_section.js +++ b/public/message/amd/src/message_drawer_view_overview_section.js @@ -225,7 +225,8 @@ function( if (!isMedia) { // Try to get the text value of the content. // If that's not possible, we'll report it under the catch-all 'other media'. - var messagePreview = $(lastMessage.text).text(); + // Use textContent to safely extract text without jQuery selector parsing. + var messagePreview = tmpElement.textContent || tmpElement.innerText || ''; if (messagePreview) { // The text value of the message must have no html/script tags. if (messagePreview.indexOf('<') == -1) { diff --git a/public/message/classes/helper.php b/public/message/classes/helper.php index ef211e55cac..c7ca0754113 100644 --- a/public/message/classes/helper.php +++ b/public/message/classes/helper.php @@ -687,13 +687,25 @@ class helper { if (!empty($message)) { $doc = new DOMDocument(); $olderror = libxml_use_internal_errors(true); - $doc->loadHTML('' . $message); + // Use meta charset tag to properly handle UTF-8 instead of XML declaration hack. + // The XML declaration approach no longer works with libxml2 >= 2.14.0. + $doc->loadHTML('' . $message); libxml_clear_errors(); libxml_use_internal_errors($olderror); - $html = $doc->getElementsByTagName('body')->item(0)->C14N(false, true); - if ($removebody) { - // Remove element added in C14N function. - $html = preg_replace('~<(/?(?:body))[^>]*>\s*~i', '', $html); + $body = $doc->getElementsByTagName('body')->item(0); + if ($body) { + $html = $body->C14N(false, true); + if ($removebody) { + // Remove element added in C14N function. + $html = preg_replace('~<(/?(?:body))[^>]*>\s*~i', '', $html); + // Libxml2 >= 2.14.0 doesn't wrap plain text in

tags, so add them for consistency. + if (LIBXML_VERSION >= 21400) { + $trimmed = trim($html); + if ($trimmed !== '' && !preg_match('/^' . $html . '

'; + } + } + } } } diff --git a/public/message/tests/helper_test.php b/public/message/tests/helper_test.php index db38ecd534f..9a6997fa3c6 100644 --- a/public/message/tests/helper_test.php +++ b/public/message/tests/helper_test.php @@ -221,6 +221,13 @@ final class helper_test extends \advanced_testcase { $this->setAdminUser(); $html = \core_message\helper::prevent_unclosed_html_tags($message, $removebody); + + // Libxml2 >= 2.14.0 closes unclosed comments instead of removing them. + // Check if we're testing the unclosed comment case and adjust expectation based on libxml version. + if ($message === '

Title

Paragraph

'; + } + $this->assertSame($goodhtml, $html); } @@ -248,6 +255,12 @@ final class helper_test extends \advanced_testcase { 'Check encoding UTF-8 is working' => [ '

Title

السلام عليكم

', '

Title

السلام عليكم

', false ], + 'Script tag only returns empty' => [ + '', '', true, + ], + 'Script tag with text' => [ + 'Some text', '

Some text

', true, + ], ]; } }