/* Bundle Includes:
* js/ui/nicknames.js
* js/vendor/verge.js
* js/jquery.tokeninput.js
* js/jquery.checkboxes.js
* js/vendor/notification.js
* js/vendor/moment.js
* js/vendor/perfect-scrollbar.js
* js/gContacts.js
* js/ui/megaRender.js
* js/ui/dialog.js
* js/ui/credentialsWarningDialog.js
* js/ui/loginRequiredDialog.js
* js/ui/registerDialog.js
* js/ui/keySignatureWarningDialog.js
* js/ui/feedbackDialog.js
* js/ui/languageDialog.js
* js/ui/alarm.js
* js/ui/toast.js
* js/ui/top-tooltip-login.js
* js/fm/transfer-progress-widget.js
* js/fm/fileTextEditor.js
* js/fm/textEditorUI.js
*/
/**
* Namespace for contact nicknames/aliases related functionality (setting nicknames, displaying them for contacts etc)
*/
var nicknames = {
/**
* Initial load of nicknames { userhandle : nickname, ... } from 'ug' request
*/
cache: false,
/**
* List of nicknames (handle -> nickname) that are in progress of saving (e.g. not yet confirmed by the server)
*/
_dirty: {},
/**
* Gets the user's nickname if it's available
* @param {String} userId The Base64 string of the user handle
* @returns {String} Returns the display name in format Nickname (FirstName LastName), or (FirstName LastName),
* or FirstName if the last name is not set
*/
getNicknameAndName: function(userId) {
'use strict';
if (M.u && typeof M.u[userId] !== 'undefined') {
// M.u[userId].c === 1 is because we only want those to appear for contacts.
// Set format to FirstName LastName (or just FirstName if the last name is not set)
var userName = (M.u[userId].name || M.u[userId].m).trim();
// Check if a nickname for this contact exists
if (M.u[userId].nickname !== '') {
// If name is available use format: Nickname (FirstName LastName)
userName = M.u[userId].nickname + ' (' + userName + ')';
}
return userName;
}
return '';
},
/**
* Decrypt contact nicknames stored on the API if they exist
* @param {String} privateAttribute The encrypted u_attr['*!>alias'] attribute data
*/
decryptAndCacheNicknames: function(privateAttribute) {
'use strict';
try {
// Try decode, decrypt, convert from TLV into a JS object
var urlDecodedString = base64urldecode(privateAttribute);
var decryptedBlock = tlvstore.blockDecrypt(urlDecodedString, u_k);
var contactNicknames = tlvstore.tlvRecordsToContainer(decryptedBlock);
var decodedContactNicknames = mega.attr.decodeObjectValues(contactNicknames);
// Set
this.cache = decodedContactNicknames;
}
catch (ex) {
this.cache = Object.create(null);
console.error('Failed to decrypt contact nicknames', ex);
}
},
/**
* Update nicknames in the UI when an action packet has been received saying they were updated
*/
updateNicknamesFromActionPacket: function() {
'use strict';
// Get nicknames (*!>alias attribute)
mega.attr.get(
u_handle, // User handle
'alias', // Attribute name without prefixes
false, // Non public
true, // Non historic
false, // Callback not needed
false, // Context not needed
false, // Chat handle not needed
true // Decode values
)
.always(function(contactNicknames) {
// Make sure it existed and decrypted to an object
if (typeof contactNicknames !== 'object') {
return false;
}
// Loop through all the properties in M.u
M.u.keys().forEach(function(key) {
// If an active contact
if (typeof M.u[key] !== 'undefined' && M.u[key].h) {
// Use if set or use empty string so it will get updated in the UI if they had it set before
var newNickname = (typeof contactNicknames[key] !== 'undefined') ? contactNicknames[key] : '';
// Set the nickname in the UI (will automagically update)
var oldNickname = M.u[key].nickname;
if (oldNickname !== newNickname) {
M.u[key].nickname = newNickname;
M.avatars([key]);
}
}
});
// Update left panel if it has been initialised
if (M.getNodeRoot(M.currentdirid) === 'contacts' && $.sortTreePanel) {
M.contacts();
}
});
},
/**
* A dialog to set the contact's nickname
*/
setNicknameDialog: {
/** Cache of the jQuery selector for the dialog */
$dialog: null,
/** Cache of the jQuery selector for the dialog background */
$backgroundOverlay: null,
/** The contact's user handle (base64 encoded string) */
contactUserHandle: null,
/**
* Initialise the dialog
* @param {String} contactUserHandle The contact's user handle (base64 encoded string)
*/
init: function(contactUserHandle) {
'use strict';
// Init global selectors
this.$dialog = $('.contact-nickname-dialog');
this.$backgroundOverlay = $('.dark-overlay');
// Set user handle for use later
this.contactUserHandle = contactUserHandle;
// Init functionality
this.prefillUserNickname();
this.initTextInputTitle();
this.initCancelAndCloseButtons();
this.initSaveButton();
this.showDialog();
this.initInputFocus();
},
/**
* Automatically fill in the user's current nickname if it is set
*/
prefillUserNickname: function() {
'use strict';
var $input = this.$dialog.find('.nickname-input');
var inputValue = '';
// If the contact exists
if (typeof M.u[this.contactUserHandle] !== 'undefined') {
// If the nickname is set, use that
if (M.u[this.contactUserHandle].nickname !== '') {
inputValue = M.u[this.contactUserHandle].nickname;
}
else {
// Otherwise if the contact details are available, pre-populate with their first and last name
var firstName = M.u[this.contactUserHandle].firstName;
var lastName = M.u[this.contactUserHandle].lastName;
inputValue = (firstName + ' ' + lastName).trim();
}
}
// Show the nickname, name or empty string in the text field
$input.val(inputValue);
},
/**
* Initialise the code to hide/show the text input's title/tooltip if there is text entered
*/
initTextInputTitle: function() {
'use strict';
var $input = this.$dialog.find('.nickname-input');
var $inputTitle = this.$dialog.find('.nickname-input-title');
var $saveButton = this.$dialog.find('.save-button');
// Small function to hide/show
var setTitleVisibility = function() {
// If there is text entered, show the text field title
if ($input.val().length > 0) {
$inputTitle.addClass('visible');
}
else {
// Placeholder text is shown so no title needed
$inputTitle.removeClass('visible');
}
};
// Set the keyup handler
$input.rebind('keyup.inputchange', function(event) {
setTitleVisibility();
// If Enter key is pressed, trigger Save action
if (event.which === 13) {
$saveButton.trigger('click');
}
});
// Check on initial load whether it should be shown
setTitleVisibility();
},
/**
* Initialise the Cancel and Close buttons
*/
initCancelAndCloseButtons: function() {
'use strict';
var $cancelButton = this.$dialog.find('.cancel-button');
var $closeIconButton = this.$dialog.find('.fm-dialog-close');
var $input = this.$dialog.find('.nickname-input');
var self = this;
// On click of the Cancel or Close icon
$cancelButton.add($closeIconButton).rebind('click.closeDialog', function() {
// Clear the entered value
$input.val('');
// Close the dialog
self.closeDialog();
});
},
/**
* Initialise the Save button
*/
initSaveButton: function() {
'use strict';
var $saveButton = this.$dialog.find('.save-button');
var $nicknameInput = this.$dialog.find('.nickname-input');
var contactUserHandle = this.contactUserHandle;
var self = this;
// On Save button click
$saveButton.rebind('click.saveNickname', function() {
// A flag for whether to update the API or not. If they entered a blank nickname and that nickname did
// not exist before, then the API won't be updated, but if if did exist before then it will be deleted.
var updateApi = false;
// Get the nickname and trim it
var nickname = $nicknameInput.val().trim();
// If the nickname is empty
if (nickname.length < 1) {
// If the nickname previously existed, delete it
if (M.u[contactUserHandle].nickname !== '') {
M.u[contactUserHandle].nickname = '';
M.avatars([contactUserHandle]);
updateApi = true;
}
}
else {
// Set the nickname
M.u[contactUserHandle].nickname = nickname;
M.avatars([contactUserHandle]);
updateApi = true;
}
// If the API should be updated with the new attribute or have it removed
if (updateApi) {
nicknames._dirty[contactUserHandle] = M.u[contactUserHandle].nickname;
// Get all the contacts with nicknames
var contactNicknames = self.getNicknamesForAllContacts();
// If there are nicknames, save them to a private encrypted attribute
var promise;
if (Object.keys(contactNicknames).length > 0) {
promise = self.saveNicknamesToApi(contactNicknames);
}
else {
promise = self.removeNicknamesFromApi();
}
promise.always(function() {
// versioned attributes would proxy their second .get and merge requests and the original
// promise would wait before getting resolve/rejected (so that merge/set had finished
// successfully)
delete nicknames._dirty[contactUserHandle];
});
// Update left panel if it has been initialised
if (M.getNodeRoot(M.currentdirid) === 'contacts' && $.sortTreePanel) {
M.contacts();
}
}
// Hide the dialog
self.closeDialog();
});
},
/**
* Get a list of contact nicknames to be saved
* @returns {Object} Returns an object containing mappings of contact handles to nicknames
*/
getNicknamesForAllContacts: function() {
'use strict';
var contactNicknames = {};
// Loop through the keys in M.u
M.u.keys().forEach(function(key) {
// If an active contact and they have a nickname, add it
if (typeof M.u[key] !== 'undefined' && M.u[key].nickname !== '') {
contactNicknames[key] = M.u[key].nickname;
}
});
return contactNicknames;
},
/**
* Save the nicknames object to the API which will be TLV encoded and encrypted
* @param {Object} contactNicknames An object containing mappings of contact handles to nicknames
* @returns {MegaPromise}
*/
saveNicknamesToApi: function(contactNicknames) {
'use strict';
loadingDialog.show();
// Set the attribute API side to *!>alias
return mega.attr.set(
'alias', // Attribute name
contactNicknames, // Data to save
false, // Set to private and encrypted
true, // Set to non-historic, this won't retain previous values on API server
false, // No callback required
false, // No context required
undefined, // Use default AES_GCM_12_16 encryption mode
true, // Do not use versioning
true // Set to encode values as UTF-8
)
.always(function() {
loadingDialog.hide();
});
},
/**
* Remove the nicknames object from the API
*
* @returns {MegaPromise}
*/
removeNicknamesFromApi: function() {
'use strict';
loadingDialog.show();
// Set the attribute API side to *!>alias
return mega.attr.set(
'alias', // Attribute name
{}, // Data to save
false, // Set to private and encrypted
true, // Set to non-historic, this won't retain previous values on API server
false, // No callback required
false, // No context required
undefined, // Use default AES_GCM_12_16 encryption mode
true, // Do not use versioning
true // Set to encode values as UTF-8
)
.always(function() {
loadingDialog.hide();
});
},
/**
* When the dialog has opened, put the cursor into the text field
*/
initInputFocus: function() {
'use strict';
this.$dialog.find('.nickname-input').trigger('focus');
},
/**
* Show the dialog
*/
showDialog: function() {
'use strict';
this.$dialog.removeClass('hidden');
this.$backgroundOverlay.removeClass('hidden');
},
/**
* Close the dialog
*/
closeDialog: function() {
'use strict';
this.$dialog.addClass('hidden');
this.$backgroundOverlay.addClass('hidden');
}
}
};
/*!
* verge 1.9.1+201402130803
* https://github.com/ryanve/verge
* MIT License 2013 Ryan Van Etten
*/
(function(root, name, make) {
if (typeof module != 'undefined' && module['exports']) module['exports'] = make();
else root[name] = make();
}(this, 'verge', function() {
var xports = {}
, win = typeof window != 'undefined' && window
, doc = typeof document != 'undefined' && document
, docElem = doc && doc.documentElement
, matchMedia = win['matchMedia'] || win['msMatchMedia']
, mq = matchMedia ? function(q) {
return !!matchMedia.call(win, q).matches;
} : function() {
return false;
}
, viewportW = xports['viewportW'] = function() {
var a = docElem['clientWidth'], b = win['innerWidth'];
return a < b ? b : a;
}
, viewportH = xports['viewportH'] = function() {
var a = docElem['clientHeight'], b = win['innerHeight'];
return a < b ? b : a;
};
/**
* Test if a media query is active. Like Modernizr.mq
* @since 1.6.0
* @return {boolean}
*/
xports['mq'] = mq;
/**
* Normalized matchMedia
* @since 1.6.0
* @return {MediaQueryList|Object}
*/
xports['matchMedia'] = matchMedia ? function() {
// matchMedia must be binded to window
return matchMedia.apply(win, arguments);
} : function() {
// Gracefully degrade to plain object
return {};
};
/**
* @since 1.8.0
* @return {{width:number, height:number}}
*/
function viewport() {
return {'width':viewportW(), 'height':viewportH()};
}
xports['viewport'] = viewport;
/**
* Cross-browser window.scrollX
* @since 1.0.0
* @return {number}
*/
xports['scrollX'] = function() {
return win.pageXOffset || docElem.scrollLeft;
};
/**
* Cross-browser window.scrollY
* @since 1.0.0
* @return {number}
*/
xports['scrollY'] = function() {
return win.pageYOffset || docElem.scrollTop;
};
/**
* @param {{top:number, right:number, bottom:number, left:number}} coords
* @param {number=} cushion adjustment
* @return {Object}
*/
function calibrate(coords, cushion) {
var o = {};
cushion = +cushion || 0;
o['width'] = (o['right'] = coords['right'] + cushion) - (o['left'] = coords['left'] - cushion);
o['height'] = (o['bottom'] = coords['bottom'] + cushion) - (o['top'] = coords['top'] - cushion);
return o;
}
/**
* Cross-browser element.getBoundingClientRect plus optional cushion.
* Coords are relative to the top-left corner of the viewport.
* @since 1.0.0
* @param {Element|Object} el element or stack (uses first item)
* @param {number=} cushion +/- pixel adjustment amount
* @return {Object|boolean}
*/
function rectangle(el, cushion) {
el = el && !el.nodeType ? el[0] : el;
if (!el || 1 !== el.nodeType) return false;
return calibrate(el.getBoundingClientRect(), cushion);
}
xports['rectangle'] = rectangle;
/**
* Get the viewport aspect ratio (or the aspect ratio of an object or element)
* @since 1.7.0
* @param {(Element|Object)=} o optional object with width/height props or methods
* @return {number}
* @link http://w3.org/TR/css3-mediaqueries/#orientation
*/
function aspect(o) {
o = null == o ? viewport() : 1 === o.nodeType ? rectangle(o) : o;
var h = o['height'], w = o['width'];
h = typeof h == 'function' ? h.call(o) : h;
w = typeof w == 'function' ? w.call(o) : w;
return w/h;
}
xports['aspect'] = aspect;
/**
* Test if an element is in the same x-axis section as the viewport.
* @since 1.0.0
* @param {Element|Object} el
* @param {number=} cushion
* @return {boolean}
*/
xports['inX'] = function(el, cushion) {
var r = rectangle(el, cushion);
return !!r && r.right >= 0 && r.left <= viewportW();
};
/**
* Test if an element is in the same y-axis section as the viewport.
* @since 1.0.0
* @param {Element|Object} el
* @param {number=} cushion
* @return {boolean}
*/
xports['inY'] = function(el, cushion) {
var r = rectangle(el, cushion);
return !!r && r.bottom >= 0 && r.top <= viewportH();
};
/**
* Test if an element is in the viewport.
* @since 1.0.0
* @param {Element|Object} el
* @param {number=} cushion
* @return {boolean}
*/
xports['inViewport'] = function(el, cushion) {
// Equiv to `inX(el, cushion) && inY(el, cushion)` but just manually do both
// to avoid calling rectangle() twice. It gzips just as small like this.
var r = rectangle(el, cushion);
return !!r && r.bottom >= 0 && r.right >= 0 && r.top <= viewportH() && r.left <= viewportW();
};
return xports;
}));
(function($) {
// Default settings
var DEFAULT_SETTINGS = {
// Search settings
method: "GET",
queryParam: "q",
searchDelay: 200,
minChars: 1,
propertyToSearch: "id",
jsonContainer: null,
contentType: "json",
excludeCurrent: false,
excludeCurrentParameter: "x",
// Prepopulation settings
prePopulate: null,
processPrePopulate: false,
// Display settings
hintText: "Type in a search term",
noResultsText: "No results",
searchingText: "Searching...",
deleteText: "×",
animateDropdown: true,
placeholder: null,
theme: null,
zindex: 1200,
resultsLimit: null,
searchDropdown: true,
enableHTML: false,
addAvatar: true,
emailCheck: false,
accountHolder: '',
url: '',
visibleComma: false,
scrollLocation: 'add',
/**
* resultsFormatter
*
* Creates contact row for share dialog drop down list.
* Row is consisted of user avatar and two fields one below other
* -------------------------
* | | upper string |
* | avatar |--------------|
* | | lower string |
* -------------------------
* We can have 2 different situations depending on contact name
* 1. Contact does NOT have a name. Top field is contact email address
* bottom field is 'Email' string
* 2. Contact does have a name. Top field is a contact name, bottom
* field is a contact email address.
*
* @@param {Object} item
* @returns {String} Html
*/
resultsFormatter: function (item) {
var id;
var avatar;
var email = item[this.tokenValue];
var contactName = item[this.propertyToSearch];
var upperValue = '';
var lowerValue = '';
M.u.forEach(function (contact, contactHandle) {
if (contact.m === email) {
id = contactHandle;
return false;
}
});
if (id) {
contactName = M.getNameByHandle(id);
}
// Check existance of contact name and arrange upper/lower strings
if ((contactName === email) || (contactName === '')) {// no contact name
upperValue = email;
lowerValue = l[7434];// Email
}
else {// with contact name
upperValue = contactName;
lowerValue = email;
}
avatar = useravatar.contact(id || email, '', 'span');
return '
';
},
// Tokenization settings
tokenLimit: null,
tokenDelimiter: /[ ,;]+/,
preventDoublet: true,
tokenValue: "id",
// Behavioral settings
allowFreeTagging: true,
allowTabOut: false,
autoSelectFirstResult: false,
// Callbacks
onResult: null,
onCachedResult: null,
onAdd: null,
onFreeTaggingAdd: true,
onDelete: null,
onReady: null,
onEmailCheck: null,
onDoublet: null,
onHolder: null,
// Other settings
idPrefix: "token-input-",
// Keep track if the input is currently in disabled mode
disabled: false
};
// Default classes to use when theming
var DEFAULT_CLASSES = {
tokenList: "token-input-list",
token: "token-input-token",
tokenDelete: "token-input-delete-token",
selectedToken: "token-input-selected-token",
highlightedToken: "token-input-highlighted-token",
dropdown: "token-input-dropdown",
dropdownItem: "token-input-dropdown-item",
dropdownItem2: "token-input-dropdown-item2",
selectedDropdownItem: "token-input-selected-dropdown-item",
inputToken: "token-input-input-token"
};
// Input box position "enum"
var POSITION = {
BEFORE: 0,
AFTER: 1,
END: 2
};
// Keys "enum"
var KEY = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
ESCAPE: 27,
SPACE: 32,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
NUMPAD_ENTER: 108,
COMMA: 188,
SEMICOLON: 186
};
var HTML_ESCAPES = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
var HTML_ESCAPE_CHARS = /[&<>"'\/]/g;
function coerceToString(val) {
return String((val === null || val === undefined) ? '' : val);
}
function _escapeHTML(text) {
return coerceToString(text).replace(HTML_ESCAPE_CHARS, function(match) {
return HTML_ESCAPES[match];
});
}
// Additional public (exposed) methods
var methods = {
init: function(url_or_data_or_function, options) {
var settings = $.extend({}, DEFAULT_SETTINGS, options || {});
return this.each(function() {
$(this).data("settings", settings);
$(this).data("tokenInputObject", new $.TokenList(this, url_or_data_or_function, settings));
});
},
clear: function() {
if (this.data("tokenInputObject")) {
this.data("tokenInputObject").clear();
return this;
}
return false;
},
// Clears items from multi-input box, UI elements
clearOnCancel: function() {
if (this.data("tokenInputObject")) {
this.data("tokenInputObject").clearOnCancel();
return this;
}
return false;
},
add: function(item) {
if (this.data("tokenInputObject")) {
this.data("tokenInputObject").add(item);
return this;
}
return false;
},
remove: function(item) {
if (this.data("tokenInputObject")) {
this.data("tokenInputObject").remove(item);
return this;
}
return false;
},
get: function() {
return this.data("tokenInputObject").getTokens();
},
getSettings: function() {
return this.data("settings");
},
toggleDisabled: function(disable) {
this.data("tokenInputObject").toggleDisabled(disable);
return this;
},
setOptions: function(options) {
$(this).data("settings", $.extend({}, $(this).data("settings"), options || {}));
return this;
},
destroy: function() {
if (this.data("tokenInputObject")) {
this.data("tokenInputObject").clear();
var tmpInput = this;
var closest = this.parent();
closest.empty();
tmpInput.show();
closest.append(tmpInput);
return tmpInput;
}
},
// Removes contact from dropdownlist, don't interfere with UI elements
removeFromDDL: function(item) {
var $settings = {},
ld, tokenValue;
if ($(this).data("settings")) {
$settings = $(this).data("settings");
ld = $settings.local_data;
tokenValue = $settings.tokenValue;
// Loop through local data
for (var n in ld) {
if (ld[n][tokenValue] === item[tokenValue]) {
$(this).data("settings").local_data.splice(n, 1);
break;
}
}
}
return false;
},
// Add contacts to drop down list, doesn't interfere with UI elements
addToDDL: function(items) {
var localData = [];
var tokenValue;
var propertyToSearch;
var found = false;
if ($(this).data("settings")) {
localData = $(this).data("settings").local_data;
tokenValue = $(this).data("settings").tokenValue;
propertyToSearch = $(this).data("settings").propertyToSearch;
// Loop through list of available items
for (var i in items) {
if (items.hasOwnProperty(i)) {
found = false;
// Loop through list of item currently available in drop down box
for (var n in localData) {
if (localData.hasOwnProperty(n)) {
// In case that we have item in drop down list, skip and continue search for missing one
if (localData[n][tokenValue] === items[i][tokenValue]) {
found = true;
break;
}
}
}
// Add missing item to drop down list
if (!found) {
$(this).data("settings").local_data.push({
id: items[i][tokenValue],
name: items[i][propertyToSearch]
});
}
}
}
}
return false;
}
};
// Expose the .tokenInput function to jQuery as a plugin
$.fn.tokenInput = function(method) {
// Method calling and initialization logic
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
}
else {
return methods.init.apply(this, arguments);
}
};
// TokenList class for each input
$.TokenList = function(input, url_or_data, settings) {
//
// Initialization
//
// Configure the data source
if (typeof (url_or_data) === "string" || typeof (url_or_data) === "function") {
// Set the url to query against
$(input).data("settings").url = url_or_data;
// If the URL is a function, evaluate it here to do our initalization work
var url = computeURL();
// Make a smart guess about cross-domain if it wasn't explicitly specified
if ($(input).data("settings").crossDomain === undefined && typeof url === "string") {
if (url.indexOf("://") === -1) {
$(input).data("settings").crossDomain = false;
}
else {
$(input).data("settings").crossDomain = (location.href.split(/\/+/g)[1] !== url.split(/\/+/g)[1]);
}
}
}
else if (typeof (url_or_data) === "object") {
// Set the local data to search through
$(input).data("settings").local_data = url_or_data;
}
// Build class names
if ($(input).data("settings").classes) {
// Use custom class names
$(input).data("settings").classes = $.extend({}, DEFAULT_CLASSES, $(input).data("settings").classes);
}
else if ($(input).data("settings").theme) {
// Use theme-suffixed default class names
$(input).data("settings").classes = {};
$.each(DEFAULT_CLASSES, function(key, value) {
$(input).data("settings").classes[key] = value + "-" + $(input).data("settings").theme;
});
}
else {
$(input).data("settings").classes = DEFAULT_CLASSES;
}
// Save the tokens
var saved_tokens = [];
// Keep track of the number of tokens in the list
var token_count = 0;
// Basic cache to save on db hits
var cache = new $.TokenList.Cache();
// Keep track of the timeout, old vals
var timeout;
var input_val;
// Create a new text input an attach keyup events
var input_box = $('')
.css({
outline: "none"
})
.attr("id", $(input).data("settings").idPrefix + input.id)
.on('focus', function() {
if ($(input).data("settings").disabled) {
return false;
}
if ($(input).data("settings").visibleComma) {
var $prevItem = input_token.prev();
if ($prevItem.length && ($prevItem.text().indexOf(',') === -1)) {
$prevItem.text($prevItem.text() + ',');
}
}
token_list.addClass($(input).data("settings").classes.focused);
$('.multiple-input').parent().addClass('active');
$('.permissions-menu').fadeOut(200);
$('.permissions-icon.active').removeClass('active');
$('.share-dialog-permissions.active').removeClass('active');
$('.permissions-menu').removeClass('search-permissions');
})
.on('blur', function() {
hide_dropdown();
if ($(input).data("settings").allowFreeTagging) {
add_freetagging_tokens();
}
if ($(input).data("settings").visibleComma) {
var $prevItem = input_token.prev();
if ($prevItem.length) {
$prevItem.text($prevItem.text().replace(',', ''));
}
}
$(this).val('');
$('.multiple-input').parent().removeClass('active');
$('.multiple-input *').removeClass('red');
})
.on("keyup keydown blur update paste", resize_input)
// Fix of paste issue. These is bug in tokenInut lib.
.rebind("input.testerresize", function() {
$(this).trigger("keydown");
})
// keydown instead of keyup to preventDefault.
.on('keydown', function(event) {
/* jshint -W074 */
var next_token;
var previous_token;
switch (event.keyCode) {
case KEY.LEFT:
case KEY.RIGHT:
case KEY.UP:
case KEY.DOWN:
if (this.value.length === 0) {
previous_token = input_token.prev();
next_token = input_token.next();
if ((previous_token.length && previous_token.get(0) === selected_token) ||
(next_token.length && next_token.get(0) === selected_token)) {
// Check if there is a previous/next token and it is selected
if (event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) {
deselect_token($(selected_token), POSITION.BEFORE);
}
else {
deselect_token($(selected_token), POSITION.AFTER);
}
}
else if ((event.keyCode === KEY.LEFT || event.keyCode === KEY.UP) && previous_token.length) {
// We are moving left, select the previous token if it exists
select_token($(previous_token.get(0)));
}
else if ((event.keyCode === KEY.RIGHT || event.keyCode === KEY.DOWN) && next_token.length) {
// We are moving right, select the next token if it exists
select_token($(next_token.get(0)));
}
}
else {
var dropdown_item = null;
if (event.keyCode === KEY.DOWN || event.keyCode === KEY.RIGHT) {
dropdown_item = $(dropdown).find('li').first();
if (selected_dropdown_item) {
dropdown_item = $(selected_dropdown_item).next();
}
}
else {
dropdown_item = $(dropdown).find('li').last();
if (selected_dropdown_item) {
dropdown_item = $(selected_dropdown_item).prev();
}
}
var $jsp = dropdown_item.getParentJScrollPane();
if ($jsp) {
$jsp.scrollToElement(dropdown_item);
}
select_dropdown_item(dropdown_item);
}
break;
case KEY.BACKSPACE:
previous_token = input_token.prev();
if (this.value.length === 0) {
if (selected_token) {
delete_token($(selected_token));
hidden_input.change();
}
else if (previous_token.length) {
delete_token($(previous_token.get(0)));
focus_with_timeout(input_box);
}
// waiting previous search to be finished and prevent show animation.
setTimeout(function() {
hide_dropdown();
}, $(input).data("settings").searchDelay);
return false;
}
else {
// set a timeout just long enough to let this function finish.
setTimeout(function() {
do_search();
}, 5);
}
break;
case KEY.TAB:
case KEY.SPACE:
case KEY.ENTER:
case KEY.NUMPAD_ENTER:
case KEY.COMMA:
case KEY.SEMICOLON:
// preventDefault to remove default behaviour from the keydown.
event.preventDefault();
if (this.value.length) {
if (selected_dropdown_item) {
add_token($(selected_dropdown_item).data("tokeninput"));
hidden_input.change();
}
else {
if ($(input).data("settings").allowFreeTagging) {
if ($(input).data("settings").allowTabOut && $(this).val() === "") {
return true;
}
else {
add_freetagging_tokens();
}
}
else {
$(this).val("");
if ($(input).data("settings").allowTabOut) {
return true;
}
}
}
}
// If users press enter/return on empty input field behave like done/share button is clicked
else if (event.keyCode === KEY.ENTER || event.keyCode === KEY.NUMPAD_ENTER) {
var $addContactBtn;
var cd;
if ($.dialog === "share") { // if it is share dialog
$addContactBtn = $('.share-dialog .dialog-share-button');
cd = false;
}
else if ($.dialog === "add-user-popup") { // if it is add user dialog.
$addContactBtn = $('.add-user-popup-button');
cd = true;
}
else {
// FIXME: what is this?
console.warn('Cannot add contact from here...', $.dialog);
return false;
}
addNewContact($addContactBtn, cd).done(function() {
if ($.dialog === "share") {
var share = new mega.Share();
share.updateNodeShares();
}
$('.token-input-token-mega').remove();
});
}
return false;
case KEY.ESCAPE:
hide_dropdown();
return true;
default:
if (String.fromCharCode(event.which)) {
// set a timeout just long enough to let this function finish.
setTimeout(function() {
do_search();
}, 5);
}
break;
}
});
// Keep reference for placeholder
if (settings.placeholder) {
input_box.attr("placeholder", settings.placeholder);
}
// Keep a reference to the original input box
var hidden_input = $(input)
.hide()
.val("")
.on('focus', function() {
focus_with_timeout(input_box);
})
.on('blur', function() {
input_box.trigger('blur');
//return the object to this can be referenced in the callback functions.
return hidden_input;
});
// Keep a reference to the selected token and dropdown item
var selected_token = null;
var selected_token_index = 0;
var selected_dropdown_item = null;
// The list to store the token items in
var token_list = $("
")
.addClass($(input).data("settings").classes.tokenList)
.on('click', function(event) {
var li = $(event.target).closest("li");
if (li && li.get(0) && $.data(li.get(0), "tokeninput")) {
toggle_select_token(li);
} else {
// Deselect selected token
if (selected_token) {
deselect_token($(selected_token), POSITION.END);
}
// Focus input box
focus_with_timeout(input_box);
}
})
.on('mouseover', function(event) {
var li = $(event.target).closest("li");
if (li && selected_token !== this) {
li.addClass($(input).data("settings").classes.highlightedToken);
}
})
.on('mouseout', function(event) {
var li = $(event.target).closest("li");
if (li && selected_token !== this) {
li.removeClass($(input).data("settings").classes.highlightedToken);
}
})
.insertBefore(hidden_input);
// The token holding the input box
var input_token = $("")
.addClass($(input).data("settings").classes.inputToken)
.appendTo(token_list)
.append(input_box);
// The list to store the dropdown items in
var dropdown = $("")
.addClass($(input).data("settings").classes.dropdown)
.appendTo("body")
.hide();
// Magic element to help us resize the text input
var input_resizer = $("")
.insertAfter(input_box)
.css({
position: "absolute",
top: -9999,
left: -9999,
width: "auto",
whiteSpace: "nowrap"
});
// Pre-populate list if items exist
hidden_input.val("");
var li_data = $(input).data("settings").prePopulate || hidden_input.data("pre");
if ($(input).data("settings").processPrePopulate && $.isFunction($(input).data("settings").onResult)) {
li_data = $(input).data("settings").onResult.call(hidden_input, li_data);
}
if (li_data && li_data.length) {
$.each(li_data, function(index, value) {
insert_token(value);
checkTokenLimit();
input_box.attr("placeholder", null)
});
}
// Check if widget should initialize as disabled
if ($(input).data("settings").disabled) {
toggleDisabled(true);
}
// Initialization is done
if (typeof ($(input).data("settings").onReady) === "function") {
$(input).data("settings").onReady.call();
}
//
// Public functions
//
this.clear = function() {
token_list.children("li").each(function() {
if ($(this).children("input").length === 0) {
delete_token($(this));
}
});
};
this.clearOnCancel = function() {
token_list.children("li").each(function() {
if ($(this).children("input").length === 0) {
delete_all_tokens($(this));
}
});
};
this.add = function(item) {
add_token(item);
};
this.remove = function(item) {
token_list.children("li").each(function() {
if ($(this).children("input").length === 0) {
var currToken = $(this).data("tokeninput");
var match = true;
for (var prop in item) {
if (item[prop] !== currToken[prop]) {
match = false;
break;
}
}
if (match) {
delete_token($(this));
}
}
});
};
this.getTokens = function() {
return saved_tokens;
};
this.toggleDisabled = function(disable) {
toggleDisabled(disable);
};
// Resize input to maximum width so the placeholder can be seen
resize_input();
//
// Private functions
//
function escapeHTML(text) {
return $(input).data("settings").enableHTML ? text : _escapeHTML(text);
}
// Toggles the widget between enabled and disabled state, or according
// to the [disable] parameter.
function toggleDisabled(disable) {
if (typeof disable === 'boolean') {
$(input).data("settings").disabled = disable
} else {
$(input).data("settings").disabled = !$(input).data("settings").disabled;
}
input_box.attr('disabled', $(input).data("settings").disabled);
token_list.toggleClass($(input).data("settings").classes.disabled, $(input).data("settings").disabled);
// if there is any token selected we deselect it
if (selected_token) {
deselect_token($(selected_token), POSITION.END);
}
hidden_input.attr('disabled', $(input).data("settings").disabled);
}
function checkTokenLimit() {
if ($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) {
input_box.hide();
hide_dropdown();
return;
}
}
function initScroll() {
var $wrapper = $(input).closest('.multiple-input');
initTokenInputsScroll($wrapper);
focus_with_timeout(input_box);
}
function resize_input() {
if (input_val === (input_val = input_box.val())) {
return;
}
// Get width left on the current line
var width_left = token_list.outerWidth() - input_box.offset().left - token_list.offset().left;
// Enter new content into resizer and resize input accordingly
input_resizer.html(_escapeHTML(input_val));
// Get maximum width, minimum the size of input and maximum the widget's width
input_box.width(Math.min(token_list.outerWidth() || 30,
Math.max(width_left, input_resizer.outerWidth() + 30)));
initScroll();
}
function add_freetagging_tokens() {
var value = $.trim(input_box.val()).replace(/\s|\n/gi, '');
var tokens = value.split($(input).data("settings").tokenDelimiter);
$.each(tokens, function(i, token) {
if (!token) {
return;
}
if ($.isFunction($(input).data("settings").onFreeTaggingAdd)) {
token = $(input).data("settings").onFreeTaggingAdd.call(hidden_input, token);
}
var object = {};
object[$(input).data("settings").tokenValue] = object[$(input).data("settings").propertyToSearch] = token;
add_token(object);
});
}
// Inner function to a token to the list
function insert_token(item) {
var $this_token = $($(input).data("settings").tokenFormatter(item));
var readonly = item.readonly === true ? true : false;
if (readonly)
$this_token.addClass($(input).data("settings").classes.tokenReadOnly);
$this_token.addClass($(input).data("settings").classes.token).insertBefore(input_token);
// The 'delete token' button
if (!readonly) {
$("")
.addClass($(input).data("settings").classes.tokenDelete)
.appendTo($this_token)
.on('click', function() {
if (!$(input).data("settings").disabled && $(input).data("settings").something !== '') {
delete_token($(this).parent());
hidden_input.change();
initScroll();
return false;
}
});
}
// Store data on the token
var token_data = item;
$.data($this_token.get(0), "tokeninput", item);
// Save this token for duplicate checking
saved_tokens = saved_tokens.slice(0, selected_token_index).concat([token_data]).concat(saved_tokens.slice(selected_token_index));
selected_token_index++;
// Update the hidden input
update_hidden_input(saved_tokens, hidden_input);
token_count += 1;
// Check the token limit
if ($(input).data("settings").tokenLimit !== null && token_count >= $(input).data("settings").tokenLimit) {
input_box.hide();
hide_dropdown();
}
return $this_token;
}
// Add a token to the token list based on user input
function add_token(item) {
var callback = $(input).data("settings").onAdd;
if ($(input).data("settings").emailCheck) {
var isEmail = isValidEmail(item[$(input).data("settings").tokenValue]);
// Prevent further execution if email format is wrong
if (!isEmail) {
var cb = $(input).data("settings").onEmailCheck;
if ($.isFunction(cb)) {
cb.call(hidden_input, item);
}
return;
}
}
if ($(input).data("settings").accountHolder) {
if ($(input).data("settings").accountHolder.toLowerCase() === item[$(input).data("settings").tokenValue].toLowerCase()) {
select_token(item);
var cb = $(input).data("settings").onHolder;
if ($.isFunction(cb)) {
cb.call(hidden_input, item);
}
return false;
}
}
if ($(input).data("settings").preventDoublet) {
var property = $(input).data("settings").propertyToSearch;
var tokenValue = $(input).data("settings").tokenValue;
var itemFoundType;
var currData = $(input).data("settings").local_data;
for (var k = 0; k < currData.length; k++) {
if (currData[k][property].toLowerCase() === item[tokenValue].toLowerCase()) {
itemFoundType = currData[k].contactType;
break;
}
}
if ((Object.keys(M.opc).length > 0) && (typeof itemFoundType === "undefined")) {
Object.keys(M.opc).forEach(function (g) {
if (M.opc[g].m.toLowerCase() === item[tokenValue].toLowerCase()
&& !M.opc[g].hasOwnProperty('dts')) {
itemFoundType = "opc";
return false;
}
});
}
// Prevent further execution if email is duplicated
if (itemFoundType) {
select_token(item);
var cb = $(input).data("settings").onDoublet;
if ($.isFunction(cb)) {
cb.call(hidden_input, item, itemFoundType);
}
return false;
}
}
// compare against already added contacts, for shared folder exlusivelly
if ($.inArray(item[$(input).data("settings").tokenValue], $.sharedTokens) !== -1) {
var cb = $(input).data("settings").onDoublet;
if ($.isFunction(cb)) {
cb.call(hidden_input, item);
}
return false;
}
// check current multi-input list
// if (token_count > 0 && $(input).data("settings").preventDoublet) {
if (token_count > 0) {
var found_existing_token = null;
token_list.children().each(function() {
var existing_token = $(this);
var existing_data = $.data(existing_token.get(0), "tokeninput");
if (existing_data && existing_data[$(input).data("settings").tokenValue] === item[$(input).data("settings").tokenValue]) {
found_existing_token = existing_token;
return false;
}
});
if (found_existing_token) {
select_token(found_existing_token);
var cb = $(input).data("settings").onDoublet;
if ($.isFunction(cb)) {
cb.call(hidden_input, item);
}
return;
}
}
// Squeeze input_box so we force no unnecessary line break
input_box.width(1);
// Insert the new tokens
if ($(input).data("settings").tokenLimit == null || token_count < $(input).data("settings").tokenLimit && isEmail) {
insert_token(item);
// Remove the placeholder so it's not seen after you've added a token
input_box.attr("placeholder", null);
checkTokenLimit();
}
// Clear input box
input_box.val("");
// Don't show the help dropdown, they've got the idea
hide_dropdown();
// Execute the onAdd callback if defined
if ($.isFunction(callback)) {
callback.call(hidden_input, item);
}
$(input).data("settings").local_data.push({
id: item[$(input).data("settings").tokenValue],
name: item[$(input).data("settings").propertyToSearch]
});
initScroll();
}// END of function add_token
// Select a token in the token list
function select_token(token) {
if (!$(input).data("settings").disabled) {
// Hide input box
input_box.val("");
// Hide dropdown if it is visible (eg if we clicked to select token)
hide_dropdown();
}
}
// Deselect a token in the token list
function deselect_token(token, position) {
token.removeClass($(input).data("settings").classes.selectedToken);
selected_token = null;
if (position === POSITION.BEFORE) {
input_token.insertBefore(token);
selected_token_index--;
} else if (position === POSITION.AFTER) {
input_token.insertAfter(token);
selected_token_index++;
} else {
input_token.appendTo(token_list);
selected_token_index = token_count;
}
// Show the input box and give it focus again
focus_with_timeout(input_box);
}
// Toggle selection of a token in the token list
function toggle_select_token(token) {
var previous_selected_token = selected_token;
if (selected_token) {
deselect_token($(selected_token), POSITION.END);
}
if (previous_selected_token === token.get(0)) {
deselect_token(token, POSITION.END);
} else {
select_token(token);
}
}
// Delete a token from the token list
function delete_token(token) {
// Remove the id from the saved list
var token_data = $.data(token.get(0), "tokeninput"),
callback = $(input).data("settings").onDelete,
index = token.prevAll().length;
if (index > selected_token_index) {
index--;
}
// Delete the token
token.remove();
selected_token = null;
// Show the input box and give it focus again
focus_with_timeout(input_box);
// Remove this token from the saved list
saved_tokens = saved_tokens.slice(0, index).concat(saved_tokens.slice(index + 1));
if (saved_tokens.length === 0) {
input_box.attr("placeholder", settings.placeholder);
}
if (index < selected_token_index) {
selected_token_index--;
}
// Update the hidden input
update_hidden_input(saved_tokens, hidden_input);
token_count -= 1;
if ($(input).data("settings").tokenLimit !== null) {
input_box
.show()
.val("");
focus_with_timeout(input_box);
}
// Execute the onDelete callback if defined
if ($.isFunction(callback)) {
callback.call(hidden_input, token_data);
var ld = $(input).data("settings").local_data;
for (var n in ld) {
if (ld[n].id === token_data.id) {
$(input).data("settings").local_data.splice(n, 1);
break;
}
}
}
initScroll();
}
// Delete a token from the token list
function delete_all_tokens(token) {
// Remove the id from the saved list
var token_data = $.data(token.get(0), "tokeninput");
var index = token.prevAll().length;
if (index > selected_token_index) {
index--;
}
token.remove();
selected_token = null;
// Show the input box and give it focus again
focus_with_timeout(input_box);
// Remove this token from the saved list
saved_tokens = saved_tokens.slice(0, index).concat(saved_tokens.slice(index + 1));
if (saved_tokens.length === 0) {
input_box.attr("placeholder", settings.placeholder);
}
if (index < selected_token_index) {
selected_token_index--;
}
// Update the hidden input
update_hidden_input(saved_tokens, hidden_input);
token_count -= 1;
if ($(input).data("settings").tokenLimit !== null) {
input_box
.show()
.val("");
focus_with_timeout(input_box);
}
var ld = $(input).data("settings").local_data;
for (var n in ld) {
if (ld[n].id === token_data.id) {
$(input).data("settings").local_data.splice(n, 1);
break;
}
}
}
// Update the hidden input box value
function update_hidden_input(saved_tokens, hidden_input) {
var token_values = $.map(saved_tokens, function(el) {
if (typeof $(input).data("settings").tokenValue == 'function')
return $(input).data("settings").tokenValue.call(this, el);
return el[$(input).data("settings").tokenValue];
});
hidden_input.val(token_values.join($(input).data("settings").tokenDelimiter));
}
// Hide and clear the results dropdown
function hide_dropdown() {
if ($(input).data("settings").searchDropdown) {
dropdown.hide().empty();
selected_dropdown_item = null;
}
}
function show_dropdown() {
if ($(input).data("settings").searchDropdown) {
dropdown
.css({
position: "absolute",
top: token_list.offset().top + token_list.outerHeight(true),
left: token_list.offset().left,
width: $(input).closest('.multiple-input').width() + 4,
'z-index': $(input).data("settings").zindex
})
.show();
}
}
function show_dropdown_searching() {
if ($(input).data("settings").searchingText && $(input).data("settings").searchDropdown) {
dropdown.html("
');
$('.collected-data', dialog.$dialog).html(collectedData);
// Content render fix for correct scrolling
var renderTimer = setInterval(function(){
$('.collected-data-textarea').jScrollPane({enableKeyboardNavigation:false,showArrows:true, arrowSize:5,animateScroll: true});
clearInterval(renderTimer);
}, 200);
});
mega.ui.Dialog.prototype._initGenericEvents.apply(self);
};
FeedbackDialog.singleton = function($toggleButton, rating, typeOfFeedback) {
if (!FeedbackDialog._instance) {
FeedbackDialog._instance = new FeedbackDialog();
}
if (typeOfFeedback) {
FeedbackDialog._instance._type = typeOfFeedback;
}
FeedbackDialog._instance.show($toggleButton);
return FeedbackDialog._instance;
};
// export
scope.mega = scope.mega || {};
scope.mega.ui = scope.mega.ui || {};
scope.mega.ui.FeedbackDialog = FeedbackDialog;
})(jQuery, window);
/**
* The language selection dialog code
*/
var langDialog = {
$dialog: null,
$overlay: null,
/**
* Initialises and shows the dialog
*/
show: function() {
// Cache some selectors for performance
langDialog.$dialog = $('.fm-dialog.languages-dialog');
langDialog.$overlay = $('.fm-dialog-overlay');
var $tierOneLanguages = langDialog.$dialog.find('.tier-one-languages');
var $tierTwoLanguages = langDialog.$dialog.find('.tier-two-languages');
// Main tier 1 languages that we support (based on usage analysis)
var tierOneLangCodes = [
'es', 'en', 'br', 'ct', 'fr', 'de', 'ru', 'it', 'ar',
'nl', 'cn', 'jp', 'kr', 'ro', 'id', 'th', 'vi', 'pl'
];
// Remove all the tier 1 languages and we have only the tier 2 languages remaining
var allLangCodes = Object.keys(languages);
var tierTwoLangCodes = allLangCodes.filter(function(langCode) {
return (tierOneLangCodes.indexOf(langCode) < 0);
});
// Generate the HTML for tier one and tier two languages (second param set to true shows beta icon)
var tierOneHtml = langDialog.renderLanguages(tierOneLangCodes, false);
// Display the HTML
$tierOneLanguages.safeHTML(tierOneHtml);
if (tierTwoLangCodes.length) {
var tierTwoHtml = langDialog.renderLanguages(tierTwoLangCodes, true);
$tierTwoLanguages.safeHTML(tierTwoHtml);
}
else {
$('.show-more-languages', langDialog.$dialog).addClass('hidden');
}
// Cache some selectors for performance
var $languageLinks = langDialog.$dialog.find('.nlanguage-lnk');
var $showMoreLanguages = langDialog.$dialog.find('.show-more-languages');
var $arrowIcon = $showMoreLanguages.find('.round-arrow');
var $showHideText = $showMoreLanguages.find('.show-more-text');
// When the user clicks on 'Show more languages', show the Tier 2 languages
$showMoreLanguages.rebind('click', function() {
// If the extra languages section is already open
if ($arrowIcon.hasClass('opened')) {
// Extra languages hidden
$arrowIcon.removeClass('opened');
$showHideText.safeHTML(l[7657]); // Show more languages
$tierTwoLanguages.hide();
langDialog.centerDialog();
}
else {
// Extra languages visible
$arrowIcon.addClass('opened');
$showHideText.safeHTML(l[7658]); // Hide languages
$tierTwoLanguages.show();
langDialog.centerDialog();
}
});
// Show tier two languages if a language is already selected from that list
if (tierTwoLangCodes.indexOf(lang) > -1) {
$arrowIcon.addClass('opened');
$showHideText.safeHTML(l[7658]); // Hide languages
$tierTwoLanguages.show();
langDialog.centerDialog();
}
// Show the dialog
langDialog.$dialog.removeClass('hidden');
langDialog.$overlay.removeClass('hidden');
$('body').addClass('overlayed');
$.dialog = 'languages';
// Initialise the close button
langDialog.$dialog.find('.fm-dialog-close').rebind('click', function() {
langDialog.hide();
});
// Initialise the save button
langDialog.initSaveButton();
// Show different style when language is selected
$languageLinks.rebind('click', function() {
$languageLinks.removeClass('selected');
$(this).addClass('selected');
return false;
});
},
/**
* Re-center the dialog vertically because the height can change when showing/hiding the extra languages
*/
centerDialog: function() {
var currentHeight = langDialog.$dialog.outerHeight();
var newTopMargin = (currentHeight / 2) * -1;
langDialog.$dialog.css('margin-top', newTopMargin);
},
/**
* Hides the language dialog
* @returns {false}
*/
hide: function() {
langDialog.$dialog.addClass('hidden');
langDialog.$overlay.addClass('hidden');
$('body').removeClass('overlayed');
$.dialog = false;
return false;
},
/**
* Create the language HTML from a list of language codes
* @param {Array} langCodes Array of language codes e.g. ['en', 'es', ...]
* @param {Boolean} tierTwo If this is a tier two / beta language
* @returns {String} Returns the HTML to be rendered
*/
renderLanguages: function(langCodes, tierTwo) {
var $template = $('.languages-dialog .language-template').clone();
var html = '';
// Remove template class
$template.removeClass('language-template');
// Sort languages by ISO 639-1 two letter language code (which is reasonably ordered anyway)
langCodes.sort(function(codeA, codeB) {
return codeA.localeCompare(codeB);
});
// Make single array with code, native lang name, and english lang name
for (var i = 0, length = langCodes.length; i < length; i++) {
var langCode = langCodes[i]; // Two letter language code e.g. de
var langItem = Object(languages[langCode]); // map to languages object
var nativeName = langItem[2]; // Deutsch
var englishName = langItem[1]; // German
if (!nativeName) {
console.warn('Language %s not found...', langCode);
continue;
}
// Clone the template
var $langHtml = $template.clone();
// Update the template details
$langHtml.attr('data-lang-code', langCode);
$langHtml.find('.native-language-name').text(nativeName);
$langHtml.attr('title', englishName);
// If they have already chosen a language show it as selected
if (langCode === lang) {
$langHtml.addClass('selected');
}
// If the beta language, show the beta icon
if (tierTwo) {
$langHtml.find('.beta').removeClass('hidden');
}
// Build up the HTML to be rendered
html += $langHtml.prop('outerHTML');
}
return html;
},
/**
* Initialise the Save button to set the language and reload the page
*/
initSaveButton: function() {
// Initialise the save button
langDialog.$dialog.find('.fm-languages-save').rebind('click', function() {
langDialog.hide();
// Get the selected code
var selectedLangCode = langDialog.$dialog.find('.nlanguage-lnk.selected').attr('data-lang-code');
// If not the currently selected language, change to the selected language
if (selectedLangCode !== lang) {
var _reload = function() {
loadingDialog.hide();
// Store the new language in localStorage to be used upon reload
localStorage.lang = selectedLangCode;
// If there are transfers, ask the user to cancel them to reload...
M.abortTransfers().then(function() {
// Reload the site
location.reload();
}).catch(function(ex) {
console.debug('Not reloading upon language change...', ex);
});
};
loadingDialog.show();
// Set a language user attribute on the API (This is a private but unencrypted user
// attribute so that the API can read it and send emails in the correct language)
if (typeof u_attr !== 'undefined') {
mega.attr.set(
'lang',
selectedLangCode, // E.g. en, es, pt
-2, // Set to private private not encrypted
true // Set to non-historic, this won't retain previous values on API server
).then(function() {
setTimeout(_reload, 3e3);
}).catch(_reload);
}
else {
_reload();
}
}
});
}
};
/**
* Various warning triangle popups from the top header. This covers
* cases for over quota, ephemeral session, non activated accounts
* after purchase, PRO plan expired warnings and site updates.
*/
var alarm = {
/**
* A flag for whether the popup has been seen or not so it won't keep auto showing on new pages
* 0 = dialog and icon not hidden (they are visible)
* 1 = dialog hidden but icon still visible (so it can be re-opened)
* 2 = dialog and icon permanently hidden
*/
hidden: 0,
/**
* Shows the warning popup
* @param {Object} $button Button which allows to open dialog
* @param {Object} $dialog The dialog
*/
showWarningPopup: function($button, $dialog) {
// If permanently hidden, make sure it stays hidden
if (alarm.hidden === 2) {
$button.addClass('hidden').removeClass('active');
$dialog.addClass('hidden');
}
// If they have seen it already, we still want to let them open the dialog
// So just show the warning icon and they can click to re-show the dialog if they want
else if (alarm.hidden === 1) {
$button.removeClass('hidden active');
$dialog.addClass('hidden');
}
// Otherwise auto show the dialog and warning icon
else {
$button.removeClass('hidden').addClass('active');
$dialog.removeClass('hidden');
topPopupAlign($button, $dialog, 40);
}
},
/**
* Hides other warning dialogs if they are currently visible so there is no double up
*/
hideAllWarningPopups: function() {
var $buttons = $('.top-icon.warning');
var $dialogs = $('.top-warning-popup');
$buttons.addClass('hidden').removeClass('active');
$dialogs.addClass('hidden');
},
/**
* Adds a click event on the warning icon to hide and show the dialog
* @param {Object} $button Button which allows to open dialog
* @param {Object} $dialog The dialog
*/
initWarningIconButton: function($button, $dialog) {
// On warning icon click
$button.rebind('click', function() {
// If the popup is currently visible
if (!$dialog.hasClass('hidden')) {
// Hide the popup
$dialog.addClass('hidden');
$button.removeClass('active');
// Set flag so it doesn't auto show each time
alarm.hidden = 1;
}
else {
// Otherwise show the popup
$dialog.removeClass('hidden');
$button.addClass('active');
topPopupAlign($button, $dialog, 40);
}
});
},
/**
* Shows when the user is over quota
*/
overQuota: {
/**
* Show the popup
*/
render: function() {
// Cache lookups
var $button = $('.top-icon.warning.over-quota');
var $dialog = $('.top-warning-popup.over-quota');
// Add button click handler
this.initUpgradeButton($dialog);
// Hide other dialogs that may be open and make the icon clickable
alarm.hideAllWarningPopups();
alarm.initWarningIconButton($button, $dialog);
alarm.showWarningPopup($button, $dialog);
},
/**
* Initialises the click handler for the Upgrade Account button
* @param {Object} $dialog The dialog
*/
initUpgradeButton: function($dialog) {
// Redirect to Pro signup page on button click
$dialog.find('.warning-button').click(function() {
// Set a flag so it doesn't show each time
alarm.hidden = 1;
// Go to the Pro page
loadSubPage('pro');
});
}
},
/**
* Shows when a user has uploaded a file to an ephemeral session
*/
ephemeralSession: {
/**
* Show the popup
*/
render: function() {
// Cache lookups
var $button = $('.top-icon.warning.ephemeral-session');
var $dialog = $('.top-warning-popup.ephemeral-session');
// Add button click handler
this.initRegisterButton($button, $dialog);
// Hide other dialogs that may be open and make the icon clickable
alarm.hideAllWarningPopups();
alarm.initWarningIconButton($button, $dialog);
alarm.showWarningPopup($button, $dialog);
},
/**
* Initialises the click handler for the Choose button
* @param {Object} $dialog The dialog
*/
initRegisterButton: function($button, $dialog) {
// Redirect to register signup page on button click
$dialog.find('.warning-button').click(function() {
// If already registered, but email not confirmed, do nothing
if (isNonActivatedAccount()) {
return false;
}
// Set a flag so it doesn't show each time
alarm.hidden = 1;
// Hide the dialog and go to register page
$dialog.addClass('hidden');
$button.removeClass('active');
loadSubPage('register');
});
}
},
/**
* Shows after creating an ephemeral session, then trying to purchase a Pro plan,
* then it asks you to register, then continue purchasing the Pro plan, then the
* popup will show because they still haven't confirmed their email yet
*/
nonActivatedAccount: {
/**
* Show the popup
* @param {Boolean} recentPurchase Flag to immediately show the popup and log the event
*/
render: function(recentPurchase) {
// Cache lookups
var $button = $('.top-icon.warning.non-activated-account');
var $dialog = $('.top-warning-popup.non-activated-account');
// If they just purchased it, log specific event
if (recentPurchase) {
megaAnalytics.log('pro', 'showNonActivatedAccountDialog');
}
// If the user has previously seen an ephemeral dialog and they closed it,
// then they purchased a plan then this forces the dialog to popup. This means
// this dialog always shows so it is an incentive to confirm their email.
alarm.hidden = 0;
// Hide other dialogs that may be open
alarm.hideAllWarningPopups();
alarm.initWarningIconButton($button, $dialog);
alarm.showWarningPopup($button, $dialog);
}
},
/**
* A helpful PRO plan renewal popup which is shown when their PRO plan has expired
*/
planExpired: {
/** All the user's last payment information from the API */
lastPayment: null,
/**
* Show the popup
*/
render: function() {
// If their last payment info is not set by the API, then their plan is not currently expired.
if (this.lastPayment === null) {
return false;
}
// Don't show this dialog if they have already said they don't want to see it again
if ((typeof this.lastPayment.dontShow !== 'undefined') && (this.lastPayment.dontShow === 1)) {
return false;
}
// If they recently upgraded to Pro in this session, don't render the icon & dialog
if (typeof u_attr !== 'undefined' && u_attr.p > 0) {
return false;
}
// Ignored payment provider IDs (not applicable or no longer in use)
var gatewayIgnoreList = [1, 2, 3, 7, 8, 13];
var gatewayId = this.lastPayment.gw;
// Don't display the popup for Apple or Google as they are recurring subscriptions. If the lastPayment is
// set then it means they have purposefully cancelled their account and would not want to see any warnings.
if (gatewayIgnoreList.indexOf(gatewayId) > -1) {
return false;
}
// Cache lookups
var $button = $('.top-icon.warning.astropay-payment-reminder');
var $dialog = $('.top-warning-popup.astropay-payment-reminder');
// Get PRO plan name e.g. PRO III
var proNum = this.lastPayment.p;
var proPlanName = pro.getProPlanName(proNum);
// Convert the timestamps to yyyy-mm-dd format
var purchasedDate = this.formatTimestampToDate(this.lastPayment.ts);
var expiryDate = this.formatTimestampToDate(this.lastPayment.exts);
// Work out the number of months their previous plan was for e.g. 1 month or 3 months
var planMonths = this.lastPayment.m;
var planMonthsPluralisation = (planMonths > 1) ? l[6788] : l[913];
// Get the display name, if it's an Astropay subgateway, then it will have it's own display name
var gatewayInfo = pro.getPaymentGatewayName(gatewayId);
var extraData = (typeof this.lastPayment.gwd !== 'undefined') ? this.lastPayment.gwd : null;
var gatewayName = (extraData) ? extraData.gwname : gatewayInfo.name;
var gatewayDisplayName = (extraData) ? extraData.label : gatewayInfo.displayName;
// Display
$dialog.find('.header-pro-plan').text(proPlanName);
$dialog.find('.purchased-date').text(purchasedDate);
$dialog.find('.expired-date').text(expiryDate);
$dialog.find('.pro-plan').text(proPlanName);
$dialog.find('.plan-duration').text(planMonths + ' ' + planMonthsPluralisation);
$dialog.find('.provider-icon').addClass(gatewayName);
$dialog.find('.gateway-name').text(gatewayDisplayName);
// Add button click handlers
this.initChooseButton($button, $dialog);
this.initRenewButton($button, $dialog, proNum);
this.initDontShowAgainButton($dialog);
this.initFeedbackMessageKeyup($dialog);
this.initSendAndCloseButton($button, $dialog);
// Hide other dialogs that may be open and make the icon clickable
alarm.hideAllWarningPopups();
alarm.initWarningIconButton($button, $dialog);
alarm.showWarningPopup($button, $dialog);
},
/**
* Initialises the click handler for the Choose button
* @param {Object} $dialog The dialog
*/
initChooseButton: function($button, $dialog) {
// On the Choose button click
$dialog.find('.warning-button.choose').rebind('click', function() {
// Hide the dialog and go to pro page
$dialog.addClass('hidden');
$button.removeClass('active');
// Set a flag so it doesn't show each time
alarm.hidden = 1;
// Add a log
api_req({ a: 'log', e: 99608, m: 'User chose a new plan from the plan expiry dialog' });
// Go to the first step of the Pro page so they can choose a new plan
loadSubPage('pro');
});
},
/**
* Initialises the click handler for the Renew button
* @param {Object} $dialog The dialog
* @param {Number} proNum The Pro plan number e.g. 1, 2, 3, 4
*/
initRenewButton: function($button, $dialog, proNum) {
// On the Renew button click
$dialog.find('.warning-button.renew').rebind('click', function() {
// Hide the dialog
$dialog.addClass('hidden');
$button.removeClass('active');
// Set a flag so it doesn't show each time
alarm.hidden = 1;
// Add a log
api_req({ a: 'log', e: 99609, m: 'User chose to renew existing plan from the plan expiry dialog' });
// Go to the second step of the Pro page which will pre-populate the details
loadSubPage('propay_' + proNum);
});
},
/**
* Initialise the 'Do not show again' button. When clicked it will show a text area and
* a button for the user to send some feedback about why they don't want to renew their plan.
* @param {Object} $dialog The dialog
*/
initDontShowAgainButton: function($dialog) {
// Add click handler for the checkbox and its label
$dialog.find('.plan-expired-checkbox, .plan-expired-checkbox-label').rebind('click', function() {
var $checkbox = $dialog.find('.plan-expired-checkbox');
// If checked
if ($checkbox.hasClass('checkboxOn')) {
// Uncheck the box
$checkbox.removeClass('checkboxOn').addClass('checkboxOff');
// Hide the feedback text area and button, show the payment messages/buttons
$dialog.find('.first-message, .second-message').removeClass('hidden');
$dialog.find('.warning-button.choose, .warning-button.renew').removeClass('hidden');
$dialog.find('.confirm-reason, .warning-button.close').addClass('hidden');
}
else {
// Otherwise check the box
$checkbox.removeClass('checkboxOff').addClass('checkboxOn');
// Hide the payment messages/buttons, show the feedback text area and button
$dialog.find('.first-message, .second-message').addClass('hidden');
$dialog.find('.warning-button.choose, .warning-button.renew').addClass('hidden');
$dialog.find('.confirm-reason, .warning-button.close').removeClass('hidden');
}
});
},
/**
* Enable or disable the Send and close button depending on if they've entered enough characters
* @param {Object} $dialog The dialog
*/
initFeedbackMessageKeyup: function($dialog) {
// On entry into the text area
$dialog.find('.confirm-reason-message').rebind('keyup', function() {
// If the message is less than 10 characters, keep the button disabled
if ($(this).val().length < 10) {
$dialog.find('.warning-button.close').addClass('disabled');
}
else {
// Otherwise enable it
$dialog.find('.warning-button.close').removeClass('disabled');
}
});
},
/**
* Initialises the button to send the user's feedback
* @param {Object} $button Button which allows to open dialog
* @param {Object} $dialog The dialog
*/
initSendAndCloseButton: function($button, $dialog) {
// On the Send and close button
$dialog.find('.warning-button.close').rebind('click', function() {
// Set the feedback message for the response
var feedback = $dialog.find('.confirm-reason-message').val();
var email = u_attr.email;
var jsonData = JSON.stringify({ feedback: feedback, email: email });
// Do nothing if less than 10 characters
if (feedback.length < 10) {
return false;
}
// Send the feedback
api_req({
a: 'clog',
t: 'doNotWantToRenewPlanFeedback',
d: jsonData
});
// Set a flag so the icon and dialog never re-appears
alarm.hidden = 2;
// Never show the dialog again for this account
mega.attr.set(
'hideProExpired',
'1', // Simple flag
false, // Set to private attribute
true // Set to non-historic, this won't retain previous values on the API server
);
// Hide the warning icon and the dialog
$button.addClass('hidden');
$dialog.addClass('hidden');
});
},
/**
* Converts a timestamp to a localised yyyy-mm-dd format e.g. 2016-04-17
* @param {Number} timestamp The UNIX timestamp
* @returns {String} Returns the date in yyyy-mm-dd format
*/
formatTimestampToDate: function(timestamp) {
var date = new Date(timestamp * 1000);
var year = date.getFullYear();
var month = (date.getMonth() + 1);
var monthPadded = (month < 10) ? '0' + month : month;
var day = (date.getDate() < 10) ? '0' + date.getDate() : date.getDate();
return year + '-' + monthPadded + '-' + day;
}
},
/**
* A popup to let the user know there is a MEGA website update available.
* This is useful if they have not refreshed or reloaded in 24 hours since a release.
* To test this, load the site and set: localStorage.setItem('testSiteUpdate', '1');
* then reload the page. The popup should appear after 5 seconds.
*/
siteUpdate: {
/** Checks for after 1 day (milliseconds) */
checkInterval: 60 * 60 * 24 * 1000,
/** The URL to check for updates, uses the static server to reduce load on the root servers */
updateUrl: mega.updateURL,
/** The timer ID */
timeoutId: null,
/** Cache of the server build information if they have already seen the popup */
cachedServerBuildVersion: null,
/**
* Initialise the update check mechanism
*/
init: function() {
// If they previously fetched the server information then re-render the popup on subsequent page loads
if (this.cachedServerBuildVersion !== null) {
Soon(function() {
alarm.siteUpdate.render(alarm.siteUpdate.cachedServerBuildVersion, true);
});
}
// Otherwise start timer only if using the legacy extension or website because web extensions have
// their own auto-update mechanism. Also don't start a new timer if there is one already started.
else if (is_chrome_web_ext === false && is_firefox_web_ext === false && this.timeoutId === null) {
this.startUpdateCheckTimer();
}
},
/**
* Start a timer to check the live site to see if there is an update available
*/
startUpdateCheckTimer: function() {
// If localStorage testing variable exists
if (localStorage.getItem('testSiteUpdate')) {
// Check for update in 3 seconds using the test update file
this.checkInterval = 3 * 1000;
this.updateUrl = defaultStaticPath + 'current_ver_test.txt';
}
// Only run the update check if using legacy Firefox ext, or on mega.nz, or the testSiteUpdate flag is set
if (is_chrome_firefox || window.location.hostname === 'mega.nz' ||
localStorage.getItem('testSiteUpdate')) {
// Clear old timer
window.clearTimeout(this.timeoutId);
// Set timeout to check if there is an update available
this.timeoutId = setTimeout(function() {
// Reset the timer id after completion
alarm.siteUpdate.timeoutId = null;
// Get the server version
alarm.siteUpdate.getServerBuildVersion();
}, this.checkInterval);
}
},
/**
* Get what build version is currently available from the live site
*/
getServerBuildVersion: function() {
// Add timestamp to end to break cache
var updateUrl = this.updateUrl + '?time=' + unixtime();
// Fetch the latest current_ver.txt
M.xhr(updateUrl).done(function(event, data) {
// Try parse version info
try {
var serverBuildVersion = JSON.parse(data);
// Display information if data was returned
if (serverBuildVersion) {
alarm.siteUpdate.render(serverBuildVersion);
}
}
catch (exception) {
// Failed to fetch, try again in another 24 hours
alarm.siteUpdate.startUpdateCheckTimer();
}
});
},
/**
* Render the popup if applicable
* @param {Object} serverBuildVersion The deployment information in current_ver.txt
* @param {Boolean} reRender If this is a repeat rendering of the popup
*/
render: function(serverBuildVersion, reRender) {
// Cache lookups
var $button = $('.top-icon.warning.site-update-available');
var $dialog = $('.top-warning-popup.site-update-available');
// Convert versions to integers for easier comparison
var localVersion = M.vtol(buildVersion.website);
var serverVersion = M.vtol(serverBuildVersion.website);
// Calculate the time when the update should be notified to the user (24 hours later)
var currentTimestamp = unixtime();
var updateTimestamp = serverBuildVersion.timestamp + (60 * 60 * 24);
// If the server version is newer and the build has been released for at least 24 hours
if ((localVersion < serverVersion) && (currentTimestamp > updateTimestamp)) {
// If this is the first time the popup has appeared, send a log
if (!reRender) {
api_req({ a: 'log', e: 99610, m: 'Site update dialog triggered after 24 hours' });
}
// Set the release version and date
$dialog.find('.release-version').text(serverBuildVersion.website);
$dialog.find('.release-date-time').text(time2date(serverBuildVersion.timestamp));
// Change the text for the Legacy Extension
if (is_chrome_firefox) {
$dialog.find('.description').addClass('hidden');
$dialog.find('.description-legacy-extension').removeClass('hidden');
}
// Cache server update details so the popup can be immediately re-rendered if they switch page
this.cachedServerBuildVersion = serverBuildVersion;
// Initialise popup buttons
this.initDontUpdateButton($button, $dialog);
this.initUpdateButton($button, $dialog);
// Hide any other popups that may be open, make the icon clickable and show the popup
alarm.hideAllWarningPopups();
alarm.initWarningIconButton($button, $dialog);
alarm.showWarningPopup($button, $dialog);
}
else {
// Using current version, try again in another 24 hours
alarm.siteUpdate.startUpdateCheckTimer();
}
},
/**
* If the Don't Update button is clicked, hide the dialog and don't show it again
* @param {Object} $button Button which allows to open dialog
* @param {Object} $dialog The dialog
*/
initDontUpdateButton: function($button, $dialog) {
$dialog.find('.warning-button.dont-update').rebind('click', function() {
// Set a flag so the icon and dialog never re-appears
alarm.hidden = 2;
// Add a log
api_req({ a: 'log', e: 99611, m: 'User chose not to update from site update dialog' });
// Hide the warning icon and the dialog
$button.addClass('hidden');
$dialog.addClass('hidden');
});
},
/**
* If the Update button is clicked, hard refresh the page to break cache
* @param {Object} $dialog The dialog
*/
initUpdateButton: function($button, $dialog) {
$dialog.find('.warning-button.update').rebind('click', function() {
// Hide it so only the icon is visible and they can re-open the popup after their transfers are done
alarm.hidden = 1;
// Hide the warning dialog
$dialog.addClass('hidden');
$button.removeClass('active');
// Check for pending transfers and if there are, prompt user to see if they want to continue
M.abortTransfers().then(function() {
// Add a log
api_req({ a: 'log', e: 99612, m: 'User chose to update from site update dialog' });
// If Firefox Legacy extension go the URL
if (is_chrome_firefox) {
window.location = 'https://mega.nz/meganz-legacy.xpi';
}
else {
// If on the mega.nz site, hard refresh the page
document.location.reload(true);
}
});
});
}
}
};
(function _toast(global) {
'use strict';
var toastTimeout;
/**
* Show toast notification
* @param {String} toastClass Custom style for the notification
* @param {String} notification The text for the toast notification
* @param {String} [buttonLabel] Optional button label
* @param {String} [secondButtonLabel] Optional button label
* @param {Function} [firstButtonFunction] Optional function to be executed by btn 1
* @param {Function} [secondButtonFunction] Optional function to be executed by btn 2
* @param {Number} [toastTimeoutMs] Optional toast visibility timeout (default 4secs)
* @global
*/
global.showToast = function showToast(toastClass, notification, buttonLabel, secondButtonLabel,
firstButtonFunction, secondButtonFunction, toastTimeoutMs) {
var $toast = $('.toast-notification.common-toast');
$toast.attr('class', 'toast-notification common-toast ' + toastClass)
.find('.toast-col:first-child span').safeHTML(notification);
$toast.addClass('visible');
var toastTime = notification === l[16168] ? 3000 : 4000;
if (toastTimeoutMs > 1000 && toastTimeoutMs < 15000) {
toastTime = toastTimeoutMs;
}
clearTimeout(toastTimeout);
toastTimeout = setTimeout(function () {
hideToast();
}, toastTime);
var closeSelector = '.toast-close-button';
var btn1 = $('.common-toast .toast-button.first');
btn1.off('click');
if (buttonLabel) {
$('span', btn1).safeHTML(buttonLabel);
if (firstButtonFunction && typeof firstButtonFunction === 'function') {
btn1.bind('click', firstButtonFunction);
}
}
else {
closeSelector += ', .common-toast .toast-button';
$('.common-toast .toast-button.first span').safeHTML(l[726]);
}
var btn2 = $('.common-toast .toast-button.second');
btn2.off('click');
btn2.addClass('hidden');
if (secondButtonLabel) {
btn2.removeClass('hidden');
$('span', btn2).safeHTML(secondButtonLabel);
if (secondButtonFunction && typeof secondButtonFunction === 'function') {
btn2.bind('click', secondButtonFunction);
}
}
$(closeSelector)
.rebind('click', function () {
$('.toast-notification').removeClass('visible');
clearTimeout(toastTimeout);
});
$toast.rebind('mouseover', function () {
clearTimeout(toastTimeout);
});
$toast.rebind('mouseout', function () {
toastTimeout = setTimeout(function () {
hideToast();
}, toastTime);
});
};
global.hideToast = function hideToast() {
$('.toast-notification.common-toast').removeClass('visible');
};
})(self);
/**
* Logic for the Account forms Inputs behaviour
*/
var accountinputs = {
/**
* Initialise inputs events
* @param {Object} $formWrapper. DOM form wrapper.
*/
init: function($formWrapper) {
"use strict";
if (!$formWrapper.length) {
return false;
}
var $loginForm = $formWrapper.find('form');
var $inputs = $('input', $formWrapper);
var $checkbox = $('.account.checkbox-block input, .pw-remind.checkbox-block input', $loginForm);
var $button = $('.button', $loginForm);
var $tooltip = $loginForm.find('.account.input-tooltip');
var megaInputs = new mega.ui.MegaInputs($inputs);
$checkbox.rebind('focus.commonevent', function() {
$(this).parents('.checkbox-block').addClass('focused');
});
$checkbox.rebind('blur.commonevent', function() {
$(this).parents('.checkbox-block').removeClass('focused');
});
$checkbox.rebind('keydown.commonevent', function (e) {
if (e.keyCode === 32) {
var $wrapper = $(this).parent().find('.checkbox');
if ($wrapper.hasClass('checkboxOn')) {
$wrapper.addClass('checkboxOff').removeClass('checkboxOn');
}
else {
$wrapper.addClass('checkboxOn').removeClass('checkboxOff');
}
}
});
$button.rebind('click.commonevent', function() {
$button.removeClass('focused');
});
$button.rebind('keydown.commonevent', function (e) {
if (e.keyCode === 9) {
e.preventDefault();
if (e.shiftKey) {
$checkbox.last().focus();
}
else {
$inputs.first().focus();
}
}
else if (e.keyCode === 32) {
e.preventDefault();
$button.triggerHandler('click');
}
});
$button.rebind('focus.commonevent', function() {
$button.addClass('focused');
});
$button.rebind('blur.commonevent', function() {
$(this).removeClass('focused');
});
var isRegister = false;
if ($loginForm[0].className.indexOf('register') > -1) {
$button.addClass('disabled');
isRegister = true;
}
$('.radio-txt, .checkbox', $formWrapper).rebind('click.commonevent', function(e) {
var $wrapper = $(this).parent().find('.checkbox');
$wrapper.parent().removeClass('focused');
if ($wrapper.hasClass('checkboxOn')) {
$wrapper.addClass('checkboxOff').removeClass('checkboxOn');
}
else {
$wrapper .addClass('checkboxOn').removeClass('checkboxOff');
}
if (isRegister) {
if ($('.checkboxOn', $formWrapper).length === $checkbox.length) {
$button.removeClass('disabled');
}
else {
$button.addClass('disabled');
}
}
});
Soon(function() {
$inputs.first().focus()
});
return $formWrapper;
}
};
/**
* Logic for the top navigation bar's signin tooltip
*/
var tooltiplogin = {
/**
* Initialise the tooltip
* @param {Boolean} close Optional flag to hide the tooltip
*/
init: function(close) {
'use strict';
var $dialog = $('.dropdown.top-login-popup');
if (close) {
$dialog.find('form').empty();
$dialog.addClass('hidden');
return false;
}
if (is_extension) {
$('.extension-advise', $dialog).addClass('hidden');
}
else {
$('.extension-advise', $dialog).removeClass('hidden');
}
$dialog.find('form').replaceWith(getTemplate('top-login'));
if (localStorage.hideloginwarning) {
$dialog.find('.top-login-warning').addClass('hidden');
}
var $inputs = $('input', $dialog);
var $button = $dialog.find('.big-red-button');
$inputs.add($button).rebind('keydown.loginpopup', function(e) {
if (e.keyCode === 13) {
tooltiplogin.startLogin();
return false;
}
});
$button.rebind('click.loginpopup', function() {
tooltiplogin.startLogin();
});
$('.top-login-full', $dialog).rebind('click', function() {
tooltiplogin.init(1);
loadSubPage('login');
});
$('.top-login-warning-close', $dialog).rebind('click', function() {
if ($('.loginwarning-checkbox', $dialog).hasClass('checkboxOn')) {
localStorage.hideloginwarning = 1;
}
$('.top-login-warning', $dialog).removeClass('active');
});
$('.top-login-forgot-pass', $dialog).rebind('click', function() {
var email = document.getElementById('login-name').value;
if (isValidEmail(email)) {
$.prefillEmail = email;
}
loadSubPage('recovery');
});
$dialog.removeClass('hidden');
if ($('body').hasClass('logged')) {
topPopupAlign('.top-head .user-name', '.dropdown.top-login-popup', 40);
}
else {
if ($('body').hasClass('business')) {
topPopupAlign('.top-buttons.business .top-login-button', '.dropdown.top-login-popup', 40);
}
else {
topPopupAlign('.top-login-button:visible', '.dropdown.top-login-popup', 40);
}
}
if (is_chrome_firefox) {
mozLoginManager.fillForm.bind(mozLoginManager, 'form_login_header');
}
// Init inputs events
accountinputs.init($dialog);
},
/**
* Start the login process
*/
startLogin: function() {
'use strict';
var $topLoginPopup = $('.top-login-popup');
var $loginForm = $topLoginPopup.find('.account.top-login-form');
var $emailField = $topLoginPopup.find('#login-name');
var $passwordField = $topLoginPopup.find('#login-password');
var $loginButton = $topLoginPopup.find('.top-dialog-login-button');
var $loginWarningCheckbox = $topLoginPopup.find('.loginwarning-checkbox');
var $loginRememberCheckbox = $topLoginPopup.find('.login-check');
var email = $emailField.val().trim();
var password = $passwordField.val();
var rememberMe = false;
var twoFactorPin = null;
if (email === '' || !isValidEmail(email)) {
$emailField.megaInputsShowError(l[141]);
$emailField.focus();
}
else if (password === '') {
$passwordField.megaInputsShowError(l[1791]);
$passwordField.focus();
}
else {
$loginButton.addClass('loading');
if ($loginWarningCheckbox.hasClass('checkboxOn')) {
localStorage.hideloginwarning = 1;
}
if ($loginRememberCheckbox.hasClass('checkboxOn')) {
rememberMe = true;
}
// Checks if they have an old or new registration type, after this the flow will continue to login
security.login.checkLoginMethod(email, password, twoFactorPin, rememberMe,
tooltiplogin.old.startLogin,
tooltiplogin.new.startLogin);
}
},
/**
* Functions for the old login process which will need to be retained until everyone's upgraded to the new process
*/
old: {
/**
* Starts the login proceedure
* @param {String} email The user's email address
* @param {String} password The user's password as entered
* @param {String|null} pinCode The two-factor authentication PIN code (6 digit number), or null if N/A
* @param {Boolean} rememberMe Whether the user clicked the Remember me checkbox or not
*/
startLogin: function(email, password, pinCode, rememberMe) {
'use strict';
postLogin(email, password, pinCode, rememberMe, tooltiplogin.completeLogin);
}
},
/**
* Functions for the new secure login process
*/
new: {
/**
* Start the login proceedure
* @param {String} email The user's email addresss
* @param {String} password The user's password as entered
* @param {String|null} pinCode The two-factor authentication PIN code (6 digit number), or null if N/A
* @param {Boolean} rememberMe A boolean for if they checked the Remember Me checkbox on the login screen
* @param {String} salt The user's salt as a Base64 URL encoded string
*/
startLogin: function(email, password, pinCode, rememberMe, salt) {
'use strict';
// Start the login using the new process
security.login.startLogin(email, password, pinCode, rememberMe, salt, tooltiplogin.completeLogin);
}
},
/**
* Complete the login process and redirect to the cloud drive
* @param {Number} result If the result is negative there is an error, if positive it is the user type
*/
completeLogin: function(result) {
'use strict';
var $topLoginPopup = $('.top-login-popup');
var $emailField = $topLoginPopup.find('#login-name');
var $passwordField = $topLoginPopup.find('#login-password');
var $button = $topLoginPopup.find('.top-dialog-login-button');
// Remove loading spinner on the button
$button.removeClass('loading');
// Check and handle the common login errors
if (security.login.checkForCommonErrors(result, tooltiplogin.old.startLogin, tooltiplogin.new.startLogin)) {
return false;
}
// close two-factor dialog if it was opened
if (twofactor && twofactor.loginDialog) {
twofactor.loginDialog.closeDialog();
}
// If successful result
if (result !== false && result >= 0) {
passwordManager('#form_login_header');
u_type = result;
if (login_next) {
loadSubPage(login_next);
}
else if (M && M.currentdirid && M.currentdirid.substr(0, 5) === "chat/") {
// is a chat link
window.location.reload();
}
else if (page === 'download') {
onIdle(function() {
topmenuUI();
tooltiplogin.init(1);
showRegisterSidePane(1);
if (dlmanager.isOverQuota) {
dlmanager._onOverquotaDispatchRetry();
}
});
}
else if (page !== 'login') {
page = getSitePath().substr(1);
init_page();
}
else {
loadSubPage('fm');
}
login_next = false;
}
else {
// Close the 2FA dialog for a generic error
twofactor.loginDialog.closeDialog();
$emailField.megaInputsShowError();
$passwordField.megaInputsShowError(l[7431]);
$passwordField.focus().select();
var $inputs = $emailField.add($passwordField);
$inputs.rebind('keydown.hideBothError', function() {
$emailField.megaInputsHideError();
$passwordField.megaInputsHideError();
$inputs.off('keydown.hideBothError');
});
}
}
};
/** This class will function as a UI controller.
*/
mega.tpw = new function TransferProgressWidget() {
var downloadRowPrefix = 'tpw_dl_';
var uploadRowPrefix = 'tpw_ul_';
var totalUploads = 0;
var totalDownloads = 0;
var frozenTimeout = 6e4; // 60 sec
var completedTimeToStay = 3e5; // 5 min
var FailedTimeToStay = 9e5; // 15 min
var lastPokeTime = -1;
var inactivityTimespanForClearance = 9e5; // 15 min
var maximumLength = 200; // maximum rows to draw in normal mode
var maximumLengthProgress = 300; // maximum rows to draw in urgent mode
var $widget;
var $widgetWarnings;
var $rowsHeader;
var $widgetHeadAndBody;
var $rowsContainer;
var $bodyContianer;
var $rowTemplate;
var $downloadRowTemplate;
var $uploadRowTemplate;
var $transferActionTemplate;
var $overQuotaBanner;
var rowProgressWidth = 0;
this.INITIALIZING = 0;
this.DOWNLOADING = 1;
this.UPLOADING = 2;
this.FINISHING = 3;
this.DONE = 4;
this.FAILED = 5;
this.FROZEN = 6;
this.PAUSED = 7;
this.DOWNLOAD = 1;
this.UPLOAD = 2;
this.initialized = false;
var mySelf = this;
var isHiddenByUser = false;
var isMinimizedByUser = false;
var monitors = Object.create(null);
/**
* setting the status of the row
* @param {Object} row JQuery object contains the row
* @param {Number} status represents the status id
* @param {Object} extraOption
*/
var setStatus = function(row, status, extraOption) {
'use strict';
if (row && row.length && typeof status !== 'undefined') {
var stText = '';
switch (status) {
case mySelf.DONE:
stText = l['1418'];
break;
case mySelf.FAILED:
stText = extraOption || l['19861'];
break;
case mySelf.FROZEN:
stText = 'Frozen';
break;
case mySelf.PAUSED:
stText = l[1651];
break;
}
row.find('.transfer-task-status').text(stText);
}
else {
return;
}
};
var updateJSP = function() {
if (!$bodyContianer.data('jsp')) {
$bodyContianer.jScrollPane({
enableKeyboardNavigation: true, showArrows: true,
arrowSize: 8, animateScroll: true
});
}
else {
$bodyContianer.data('jsp').reinitialise();
}
};
var removeRow = function($row) {
if (!$row || !$row.length) {
return;
}
var timer = $row.data('timer');
if (timer) {
clearTimeout(timer);
}
var dId = $row.attr('id');
if (monitors[dId]) {
clearTimeout(monitors[dId]);
delete monitors[dId];
}
$row.remove();
updateJSP();
updateHeaderAndContent();
};
var initEventsHandlers = function() {
// minimize event handler
$('.transfer-progress-icon.tpw-c-e', $rowsHeader).off('click').on('click',
function tpw_collapseExpandHandler() {
if ($widgetHeadAndBody.hasClass('expand')) {
isMinimizedByUser = true;
$bodyContianer.slideUp(400, function() {
$widgetHeadAndBody.removeClass('expand').addClass('collapse');
});
}
else {
isMinimizedByUser = false;
$widgetHeadAndBody.removeClass('collapse').addClass('expand');
$bodyContianer.slideDown(400, function() {
updateJSP();
});
}
return false;
});
// close event handler
$('.transfer-progress-icon.tpw-close', $rowsHeader).off('click').on('click',
function tpw_CloseHandler() {
$widget.slideUp();
isHiddenByUser = true;
mega.tpw.hideWidget();
});
// close over quota
$('.close-over', $overQuotaBanner).off('click').on('click',
function overquota_bannerClose() {
$overQuotaBanner.addClass('hidden');
});
// upgrade account
$('.up-action', $overQuotaBanner).off('click').on('click',
function overquota_bannerUpgrade() {
$('.transfer-progress-icon.tpw-close', $rowsHeader).click();
isHiddenByUser = true;
loadSubPage('pro');
});
// open dashboard
$('.left-section.circle-dashboard', $overQuotaBanner).off('click').on('click',
function overquota_bannerUpgrade() {
loadSubPage('dashboard');
});
// open section
$('.transfer-section-button', $widgetHeadAndBody).off('click').on('click',
function section_open() {
viewTransferSection($(this).hasClass('complete-list'));
});
};
var viewOverQuotaBanner = function(type) {
if (!type || !u_type) {
return;
}
if (!$overQuotaBanner.hasClass('hidden')) {
return;
}
$overQuotaBanner.removeClass('almost-overquota').addClass('overquota');
M.accountData(function(acc) {
if (type === mega.tpw.DOWNLOAD) {
$overQuotaBanner.find('.head-title').text(l[20666]);
$overQuotaBanner.find('.content-txt').text(l[18085]);
$overQuotaBanner.find('.quota-info-pct-txt').text('100%');
$overQuotaBanner.find('.quota-info-pr-txt').text(bytesToSize(acc.tfsq.used) + ' ' + l[5775]);
$overQuotaBanner.find('.action').removeClass('default-orange-button').addClass('default-red-button');
$overQuotaBanner.find('.quota-info-pct-circle li.left-c p').rotate(180, 0);
}
else {
$overQuotaBanner.find('.head-title').text(l[5932]);
$overQuotaBanner.find('.content-txt').safeHTML(l[7014].replace('[A]', '').replace('[/A]', ' '));
var perc = Math.round(acc.space_used * 100 / acc.space);
$overQuotaBanner.find('.quota-info-pct-txt').text(perc + '%');
var spaceInfo = bytesToSize(acc.space_used) + '/' + bytesToSize(acc.space, 0);
if (spaceInfo.length >= 15) {
$('.quota-info-pr-txt', $overQuotaBanner).addClass('small-font');
}
$('.quota-info-pr-txt', $overQuotaBanner).text(spaceInfo);
$overQuotaBanner.find('.action').removeClass('default-red-button').addClass('default-orange-button');
var rotateAngle = 18 * perc / 10 >= 180 ? 180 : 18 * perc / 10;
$('.quota-info-pct-circle li.left-c p', $overQuotaBanner).rotate(rotateAngle);
}
$overQuotaBanner.removeClass('hidden');
});
};
/**
* Clear the warnings shown on TPW header
* @param {Number} type flag to distinguish upload/download
*/
this.resetErrorsAndQuotasUI = function(type) {
if (!type) {
return;
}
$overQuotaBanner.addClass('hidden');
var $sectionType = 'download';
if (type === this.UPLOAD) {
$sectionType = 'upload';
}
$rowsHeader.find('.transfer-progress-type.' + $sectionType).removeClass('error overquota');
};
var actionsOnRowEventHandler = function() {
var $me = $(this);
if ($me.hasClass('disabled') || !$me.is(':visible')) {
return;
}
var $transferRow = $me.closest('.transfer-task-row');
var trId = $transferRow.attr('id');
var id = trId.split('_').pop();
var node = null;
if ($me.hasClass('cancel')) {
if ($transferRow.hasClass('download')) {
if (!$transferRow.attr('zippo')) {
id = 'dl_' + id;
}
else {
id = 'zip_' + id;
}
dlmanager.abort(id);
}
else {
id = 'ul_' + id;
ulmanager.abort(id);
}
$('.transfer-table tr#' + id).remove();
if ($.clearTransferPanel) {
$.clearTransferPanel();
}
$transferRow.fadeOut(400, function() {
removeRow($transferRow);
});
}
else if ($me.hasClass('link')) {
var nodeHandle = $transferRow.attr('nhandle');
node = M.d[nodeHandle || id];
if (!node) {
return;
}
$.selected = [nodeHandle || id];
$('.dropdown.body.context .dropdown-item.getlink-item').click();
}
else if ($me.hasClass('cloud-folder')) {
var nHandle = $transferRow.attr('nhandle');
node = M.d[nHandle || id];
if (!node) {
return;
}
if (node.p) {
M.openFolder(node.p);
}
}
else if ($me.hasClass('pause') || $me.hasClass('restart')) {
if ($transferRow.hasClass('download')) {
if ($transferRow.attr('zippo')) {
id = 'zip_' + id;
}
else {
id = 'dl_' + id;
}
}
else {
id = 'ul_' + id;
}
if ($me.hasClass('pause')) {
fm_tfspause(id);
}
else {
$transferRow.removeAttr('prepared');
fm_tfsresume(id);
}
}
return false;
};
var clearAndReturnWidget = function() {
var $currWidget = $('.transfer-prorgess.tpw');
if (!$currWidget || !$currWidget.length) {
return null;
}
if ($currWidget.length === 2) {
if ($('#startholder').is(':visible')) {
$($currWidget[1]).remove();
return $($currWidget[0]);
}
else {
$($currWidget[0]).remove();
return $($currWidget[1]);
}
}
else {
return $currWidget;
}
};
/** Initialize the properties and class members */
var init = function() {
'use strict';
// return;
if (mega.tpw.initialized) {
return;
}
$widget = clearAndReturnWidget();
if (!$widget || !$widget.length) {
return;
}
$widgetWarnings = $('.banner.transfer', $widget);
$rowsHeader = $('.transfer-progress-head', $widget);
$widgetHeadAndBody = $('.transfer-progress-widget', $widget);
$bodyContianer = $('.widget-body-container', $widget);
$rowsContainer = $('.transfer-progress-widget-body', $bodyContianer);
$overQuotaBanner = $('.banner.transfer', $widget);
$rowTemplate = $($('.transfer-task-row', $rowsContainer)[0]).clone();
rowProgressWidth = $($rowsContainer[0]).find('.transfer-progress-bar').width();
$downloadRowTemplate = $rowTemplate.clone().removeClass('upload').addClass('download');
$uploadRowTemplate = $rowTemplate.clone().removeClass('download').addClass('upload');
$transferActionTemplate = $($downloadRowTemplate.find('i.transfer-progress-icon')[0]).clone();
$transferActionTemplate.removeClass('pause cancel link cloud-folder restart');
$transferActionTemplate.off('click').on('click',
actionsOnRowEventHandler);
$rowsContainer.empty();
// events handlers
initEventsHandlers();
mega.tpw.initialized = true;
};
mBroadcaster.once('startMega:desktop', function() {
'use strict';
init();
});
var viewPreparation = function() {
'use strict';
init();
if (!initUI()) {
return;
}
// pages to hide always
if (page.indexOf('transfers') !== -1 || page.indexOf('register') !== -1 || page.indexOf('download') !== -1) {
mega.tpw.hideWidget();
return;
}
if (!isHiddenByUser) {
if (page.indexOf('chat') === -1) {
mega.tpw.showWidget();
}
if ($widgetHeadAndBody.hasClass('expand')) {
if (page.indexOf('chat') !== -1) {
$('.transfer-progress-icon.tpw-c-e.collapse', $rowsHeader).click();
isMinimizedByUser = false;
}
}
else {
if (page.indexOf('chat') === -1 && !isMinimizedByUser) {
$('.transfer-progress-icon.tpw-c-e.expand', $rowsHeader).click();
}
}
}
};
mBroadcaster.addListener('pagechange', viewPreparation);
mBroadcaster.addListener('fm:initialized', viewPreparation);
/**
* update a row when no update has arrived on it for a while
* @param {Number} id row id
* @param {Boolean} u isUpload
*/
var updateToFrozen = function(id, isUpload) {
if (!id) {
return;
}
if (Array.isArray(id)) {
if (!id.length) {
return;
}
id = id[0];
}
var $targetedRow = $rowsContainer.find('#' + ((!isUpload) ? downloadRowPrefix : uploadRowPrefix) + id);
if (!$targetedRow || !$targetedRow.length) {
return;
}
if (!$targetedRow.hasClass('progress')) {
return;
}
$targetedRow.removeClass('complete error progress paused').addClass('inqueue');
setStatus($targetedRow, this.FROZEN);
};
var getDownloadsRows = function() {
var downloads = $rowsContainer.find("[id^='" + downloadRowPrefix + "']");
return downloads;
};
var getUploadsRows = function() {
var uploads = $rowsContainer.find("[id^='" + uploadRowPrefix + "']");
return uploads;
};
var getTotalDownloads = function() {
return getDownloadsRows().length;
};
var getTotalUploads = function() {
return getUploadsRows().length;
};
var getTotalAndDoneDownloads = function() {
var downloadsR = getDownloadsRows();
var doneD = downloadsR.filter('.complete').length;
return { done: doneD, total: downloadsR.length };
};
var getTotalAndDoneUploads = function() {
var uploadsR = getUploadsRows();
var doneU = uploadsR.filter('.complete').length;
return { done: doneU, total: uploadsR.length };
};
/**
* Draws the progress circle in the header of the widget reflecting the done elements
* @param {Object} $headerSection jQuery object containing the download/upload section in header
* @param {Number} total Total elements
* @param {Number} done Done elements
*/
var setProgressCircle = function($headerSection, total, done) {
var perc = done / total;
perc = isNaN(perc) ? 0 : Math.round(perc * 100);
$headerSection.find('.transfer-progress-pct').text(perc + '%');
};
var cleanOverLimitRows = function() {
var $allRows = $rowsContainer.find('.transfer-task-row');
var rowsCount = $allRows.length;
if (rowsCount <= maximumLength) {
return;
}
else {
mega.tpw.clearRows(mega.tpw.DONE);
}
};
var updateHeaderAndContent = function() {
var totalD = 0;
var totalU = 0;
var doneD = 0;
var doneU = 0;
var remainD = 0;
var remainU = 0;
var dRows = getDownloadsRows();
var uRows = getUploadsRows();
totalD = dRows.length;
totalU = uRows.length;
if (!totalD && !totalU) {
mega.tpw.hideWidget();
return;
}
doneD = dRows.filter('.complete').length;
doneU = uRows.filter('.complete').length;
var $downloadHeader = $rowsHeader.find('.transfer-progress-type.download');
var $uploadHeader = $rowsHeader.find('.transfer-progress-type.upload');
var stats;
if (!totalD) {
$downloadHeader.addClass('hidden');
}
else {
remainD = totalD - doneD;
if (remainD) {
$downloadHeader.find('.transfer-progress-txt').text(l[20808]
.replace('{0}', ((remainD > 999) ? '999+' : remainD)));
stats = getTransfersPercent();
setProgressCircle($downloadHeader, stats.dl_total, stats.dl_done);
}
else {
var initialDownloadHeadText = $downloadHeader.find('.transfer-progress-txt').text();
$downloadHeader.find('.transfer-progress-txt').text(l[1418]);
if (initialDownloadHeadText !== l[1418] && page.indexOf('chat') !== -1) {
if ($uploadHeader.hasClass('hidden') ||
$uploadHeader.find('.transfer-progress-txt').text() === l[1418]) {
// widget minimizing code, kept for comparing
// if ($widgetHeadAndBody.hasClass('expand')) {
// $('.transfer-progress-icon.tpw-c-e.collapse', $rowsHeader).click();
// isMinimizedByUser = false;
// }
if ($widgetHeadAndBody.is(':visible')) {
$('.transfer-progress-icon.tpw-close', $rowsHeader).click();
isHiddenByUser = true;
}
}
}
setProgressCircle($downloadHeader, totalD, doneD);
}
$downloadHeader.removeClass('hidden');
if (!dRows.filter('.error').length) {
$downloadHeader.removeClass('error overquota');
}
}
if (!totalU) {
$uploadHeader.addClass('hidden');
}
else {
remainU = totalU - doneU;
if (remainU) {
$uploadHeader.find('.transfer-progress-txt').text(l[20808]
.replace('{0}', ((remainU > 999) ? '999+' : remainU)));
!stats && (stats = getTransfersPercent());
setProgressCircle($uploadHeader, stats.ul_total, stats.ul_done);
}
else {
var initialUploadHeadText = $uploadHeader.find('.transfer-progress-txt').text();
$uploadHeader.find('.transfer-progress-txt').text(l[1418]);
if (initialUploadHeadText !== l[1418] && page.indexOf('chat') !== -1) {
if ($downloadHeader.hasClass('hidden') ||
$downloadHeader.find('.transfer-progress-txt').text() === l[1418]) {
// widget minimizing code, kept for comparing
// if ($widgetHeadAndBody.hasClass('expand')) {
// $('.transfer-progress-icon.tpw-c-e.collapse', $rowsHeader).click();
// isMinimizedByUser = false;
// }
if ($widgetHeadAndBody.is(':visible')) {
$('.transfer-progress-icon.tpw-close', $rowsHeader).click();
isHiddenByUser = true;
}
}
}
setProgressCircle($uploadHeader, totalU, doneU);
}
$uploadHeader.removeClass('hidden');
if (!uRows.filter('.error').length) {
$uploadHeader.removeClass('error overquota');
}
}
};
var initUI = function() {
var $currWidget = clearAndReturnWidget();
if (!$currWidget) {
return false;
}
var rows = $currWidget.find('.transfer-task-row');
if (rows.length) {
if (!$(rows[0]).attr('id')) {
if ($widget.find('.jspContainer, .jspPane').length) {
$widget.find('.transfer-progress-widget-body').unwrap().unwrap();
$widget.find('.jspVerticalBar').remove();
$widget.find('.widget-body-container').attr('style', '');
}
$currWidget.replaceWith($widget);
initEventsHandlers();
$rowsContainer.find('i.transfer-progress-icon').off('click').on('click',
actionsOnRowEventHandler);
}
// init sections
viewTransferSection();
}
return true;
};
/**
* Show a tab in Transfer progress widget
* @param {Number} section 1 = completed, 0 = on progress
*/
var viewTransferSection = function(section) {
'use strict';
var $rows = $rowsContainer.find('.transfer-task-row');
// for enhanced performance, instead of using ".find" or ".filter" 2 times
// I will apply the calls on 1 go O(n).
var completedFound = false;
var progressFound = false;
for (var r = 0; r < $rows.length; r++) {
var $currRow = $($rows[r]);
if ($currRow.hasClass('complete')) {
if (section) {
$currRow.removeClass('hidden');
}
else {
$currRow.addClass('hidden');
}
completedFound = true;
} else {
if (section) {
$currRow.addClass('hidden');
}
else {
$currRow.removeClass('hidden');
}
progressFound = true;
}
}
if (completedFound && progressFound) {
if (section) {
$widgetHeadAndBody.find('.complete-list').addClass('hidden');
$widgetHeadAndBody.find('.process-list').removeClass('hidden');
}
else {
$widgetHeadAndBody.find('.process-list').addClass('hidden');
$widgetHeadAndBody.find('.complete-list').removeClass('hidden');
}
}
else {
$rows.removeClass('hidden');
$widgetHeadAndBody.find('.process-list').addClass('hidden');
$widgetHeadAndBody.find('.complete-list').addClass('hidden');
}
updateJSP();
};
var postPorcessComplete = function() {
var $allRows = $rowsContainer.find('.transfer-task-row');
var $completedRows = $allRows.filter('.complete');
if ($completedRows.length === $allRows.length) {
$allRows.removeClass('hidden');
$widgetHeadAndBody.find('.process-list').addClass('hidden');
$widgetHeadAndBody.find('.complete-list').addClass('hidden');
}
else {
// some optimization
var $tabCompleted = $widgetHeadAndBody.find('.complete-list');
var $tabProgress = $widgetHeadAndBody.find('.process-list');
if ($tabCompleted.hasClass('hidden') && $tabProgress.hasClass('hidden')) {
$tabCompleted.removeClass('hidden');
}
}
updateJSP();
};
/**
* Adding a download/upload entry to transfer progress widget
* @param {Number} type Entry type: 1 download, 2 upload
* @param {Object} entry Download|Upload entry object built at transfer
* @param {Number} specifiedSize to tell the size of download entry
*/
this.addDownloadUpload = function(type, entry, specifiedSize) {
'use strict';
if (!$rowsContainer || typeof type === 'undefined' || !entry) {
return;
}
var entriesArray;
if (Array.isArray(entry)) {
entriesArray = entry;
}
else {
entriesArray = [entry];
}
var $tempRows = new Array(entriesArray.length);
var tempRowPos = 0;
var reverted = false;
if (type === this.UPLOAD) {
tempRowPos = entriesArray.length - 1;
reverted = true;
}
var addAsHidden = $widgetHeadAndBody.find('.complete-list').hasClass('hidden');
if (addAsHidden && $widgetHeadAndBody.find('.process-list').hasClass('hidden')) {
// all done
addAsHidden = false;
$widgetHeadAndBody.find('.complete-list').removeClass('hidden');
$rowsContainer.find('.transfer-task-row').addClass('hidden');
}
for (var r = 0; r < entriesArray.length; r++) {
var fName;
var dId = entriesArray[r].id;
var prefix;
var toolTipText;
if (type === this.DOWNLOAD) {
fName = entriesArray[r].n;
prefix = downloadRowPrefix;
toolTipText = l[1196];
if (entriesArray[r].zipid) {
fName = entriesArray[r].zipname;
dId = entriesArray[r].zipid;
}
}
else {
fName = entriesArray[r].name;
prefix = uploadRowPrefix;
toolTipText = l[1617];
}
lastPokeTime = Date.now();
var $targetedRow = $rowsContainer.find('#' + prefix + dId);
var $row = null;
if ($targetedRow) {
if ($targetedRow.length === 1) {
$row = $targetedRow;
}
else {
// somehow we found multiple instances
$targetedRow.remove();
}
}
if (!$row) {
$row = (type === this.DOWNLOAD) ? $downloadRowTemplate.clone() : $uploadRowTemplate.clone();
$row.attr('id', prefix + dId);
}
if (monitors[dId]) {
clearTimeout(monitors[dId]);
}
monitors[dId] = setTimeout(updateToFrozen, frozenTimeout, dId);
$row.find('.transfer-filetype-txt').text(fName);
var $enqueueAction = $transferActionTemplate.clone().addClass('cancel');
$enqueueAction.find('.tooltips').text(toolTipText);
$row.find('.transfer-task-actions').empty().append($enqueueAction);
if (!specifiedSize) {
$row.find('.transfer-file-size').text(bytesToSize(entriesArray[r].size));
}
else {
$row.find('.transfer-file-size').text(bytesToSize(specifiedSize));
}
$row.find('.transfer-progress-bar .transfer-progress-bar-pct').css('width', 0);
$row.removeClass('complete error progress paused').addClass('inqueue');
$row.find('.transfer-filetype-icon').attr('class', 'transfer-filetype-icon')
.addClass(filetype(fName, true)[0].toLowerCase());
if (entriesArray[r].zipid) {
$row.attr('zippo', 'y');
}
if (addAsHidden) {
$row.addClass('hidden');
}
else {
$row.removeClass('hidden');
}
setStatus($row, this.INITIALIZING);
if (!reverted) {
$tempRows[tempRowPos++] = $row;
}
else {
$tempRows[tempRowPos--] = $row;
}
}
cleanOverLimitRows();
$rowsContainer.prepend($tempRows);
updateHeaderAndContent();
if (!this.isWidgetVisibile() && page.indexOf('download') === -1) {
mega.tpw.showWidget();
}
updateJSP();
$tempRows = null;
};
this.updateDownloadUpload = function(type, id, perc, bytesLoaded, bytesTotal, kbps, queue_num, startTime) {
'use strict';
if (!$rowsContainer || typeof type === 'undefined' || !id) {
return;
}
var dId = id;
lastPokeTime = Date.now();
var pauseText;
var cancelText;
var prefix;
var queue;
var $header;
var done_bytes;
var all_bytes;
var stats = getTransfersPercent();
if (type === this.DOWNLOAD) {
dId = id = id.split('_').pop();
prefix = downloadRowPrefix;
queue = dl_queue;
pauseText = l[16183];
cancelText = l[1196];
$header = $rowsHeader.find('.transfer-progress-type.download');
done_bytes = stats.dl_done;
all_bytes = stats.dl_total;
if (queue[queue_num].zipid) {
dId = queue[queue_num].zipid;
}
kbps *= 1024;
}
else {
prefix = uploadRowPrefix;
queue = ul_queue;
pauseText = l[16185];
cancelText = l[1617];
$header = $rowsHeader.find('.transfer-progress-type.upload');
done_bytes = stats.ul_done;
all_bytes = stats.ul_total;
}
var $targetedRow = $rowsContainer.find('#' + prefix + dId);
if (!$targetedRow || !$targetedRow.length) {
var tempObj = {
n: queue[queue_num].n,
name: queue[queue_num].name, // in upload will be null - OK
id: id,
zipname: queue[queue_num].zipname, // null if upload - OK
zipid: queue[queue_num].zipid, // null if upload - OK
size: queue[queue_num].size
};
this.addDownloadUpload(type, tempObj);
return mySelf.updateDownloadUpload(type, id, perc, bytesLoaded, bytesTotal, kbps, queue_num);
}
if (monitors[dId]) {
clearTimeout(monitors[dId]);
}
monitors[dId] = setTimeout(updateToFrozen, frozenTimeout, dId);
var timeSpent = (new Date().getTime() - startTime) / 1000;
var realSpeed = bytesLoaded / timeSpent; // byte per sec
var speed = (kbps) ? Math.min(realSpeed, kbps) : realSpeed;
$targetedRow.removeClass('complete error inqueue paused').addClass('progress');
if (!$targetedRow.attr('prepared')) {
var $actionsRow = $targetedRow.find('.transfer-task-actions').empty();
var $progressAction = $transferActionTemplate.clone(true).addClass('pause');
$progressAction.find('.tooltips').text(pauseText);
$actionsRow.append($progressAction);
$progressAction = $transferActionTemplate.clone(true).addClass('cancel');
$progressAction.find('.tooltips').text(cancelText);
$actionsRow.append($progressAction);
$targetedRow.attr('prepared', 'yes');
}
var prog = perc * rowProgressWidth / 100;
$targetedRow.find('.transfer-progress-bar-pct').width(prog);
$('.transfer-task-status', $targetedRow).text(bytesToSpeed(speed));
setProgressCircle($header, all_bytes, done_bytes);
};
this.finishDownloadUpload = function(type, entry, handle) {
'use strict';
if (!$rowsContainer || typeof type === 'undefined' || !entry) {
return;
}
lastPokeTime = Date.now();
var dId = entry.id;
var prefix;
var unHide = $widgetHeadAndBody.find('.complete-list').hasClass('hidden') &&
!$widgetHeadAndBody.find('.process-list').hasClass('hidden');
if (type === this.DOWNLOAD) {
if (entry.zipid) {
dId = entry.zipid;
}
prefix = downloadRowPrefix;
}
else {
prefix = uploadRowPrefix;
}
var $targetedRow = $rowsContainer.find('#' + prefix + dId);
if (!$targetedRow || !$targetedRow.length) {
return;
}
if (monitors[dId]) {
clearTimeout(monitors[dId]);
delete monitors[dId];
}
$targetedRow.removeClass('progress error inqueue paused').addClass('complete');
setStatus($targetedRow, this.DONE);
if (handle) {
$targetedRow.attr('nhandle', handle);
}
var $actionsRow = $targetedRow.find('.transfer-task-actions').empty();
var $finishedAction = $transferActionTemplate.clone(true).addClass('link');
if (!$targetedRow.attr('zippo')) {
var $finishedActionsRow = $actionsRow.clone();
$targetedRow.find('.transfer-complete-actions').remove();
$finishedActionsRow.removeClass('transfer-task-actions').addClass('transfer-complete-actions');
if (M.getNodeRoot(handle || dId) !== 'shares') {
$finishedAction.find('.tooltips').text(l[59]);
$finishedActionsRow.append($finishedAction);
}
$finishedAction = $transferActionTemplate.clone(true).addClass('cloud-folder');
$finishedAction.find('.tooltips').text(l[20695]);
$finishedActionsRow.append($finishedAction);
$finishedActionsRow.insertAfter($targetedRow.find('.transfer-file-size'));
}
$finishedAction = $transferActionTemplate.clone(true).addClass('cancel');
$finishedAction.find('.tooltips').text(l[6992]);
$actionsRow.append($finishedAction);
updateHeaderAndContent();
if (unHide) {
$targetedRow.removeClass('hidden');
}
else {
$targetedRow.addClass('hidden');
}
postPorcessComplete();
var timerHandle = setTimeout(function() {
$targetedRow.fadeOut(400, function() {
removeRow($targetedRow);
});
}, completedTimeToStay);
$targetedRow.data('timer', timerHandle);
};
this.errorDownloadUpload = function(type, entry, errorStr, isOverQuota) {
'use strict';
if (!$rowsContainer || typeof type === 'undefined' || !entry) {
return;
}
lastPokeTime = Date.now();
var prefix;
var subHeaderClass;
var cancelText;
var dId = entry.id;
if (type === this.DOWNLOAD) {
prefix = downloadRowPrefix;
subHeaderClass = 'download';
cancelText = l[1196];
if (entry.zipid) {
dId = entry.zipid;
}
}
else {
prefix = uploadRowPrefix;
subHeaderClass = 'upload';
cancelText = l[1617];
}
var $targetedRow = $rowsContainer.find('#' + prefix + dId);
if (!$targetedRow || !$targetedRow.length) {
return;
}
if (monitors[dId]) {
clearTimeout(monitors[dId]);
delete monitors[dId];
}
$targetedRow.removeClass('complete progress inqueue paused').addClass('error');
setStatus($targetedRow, this.FAILED, errorStr);
var $errorCancelAction = $transferActionTemplate.clone(true).addClass('cancel');
$errorCancelAction.find('.tooltips').text(cancelText);
$targetedRow.find('.transfer-task-actions').empty().append($errorCancelAction);
$targetedRow.removeAttr('prepared');
$rowsHeader.find('.transfer-progress-type.' + subHeaderClass).addClass('error');
if (isOverQuota) {
$rowsHeader.find('.transfer-progress-type.' + subHeaderClass).addClass('overquota');
viewOverQuotaBanner(type);
}
var timerH = setTimeout(function() {
$targetedRow.fadeOut(400, function() {
removeRow($targetedRow);
});
}, FailedTimeToStay);
$targetedRow.data('timer', timerH);
};
this.resumeDownloadUpload = function(type, entry) {
'use strict';
if (!$rowsContainer || typeof type === 'undefined' || !entry) {
return;
}
var dId = entry.id;
var prefix;
var toolTipText;
if (type === this.DOWNLOAD) {
prefix = downloadRowPrefix;
toolTipText = l[1196];
if (entry.zipid) {
dId = entry.zipid;
}
}
else {
prefix = uploadRowPrefix;
toolTipText = l[1617];
}
var $targetedRow = $rowsContainer.find('#' + prefix + dId);
if (!$targetedRow || !$targetedRow.length) {
return;
}
if (!$targetedRow.hasClass('paused')) {
return;
}
if (monitors[dId]) {
clearTimeout(monitors[dId]);
delete monitors[dId];
}
monitors[dId] = setTimeout(updateToFrozen, frozenTimeout, dId);
var $enqueueAction = $transferActionTemplate.clone().addClass('cancel');
$enqueueAction.find('.tooltips').text(toolTipText);
$targetedRow.find('.transfer-task-actions').empty().append($enqueueAction);
$targetedRow.removeClass('complete error progress paused').addClass('inqueue');
setStatus($targetedRow, this.INITIALIZING);
cleanOverLimitRows();
updateHeaderAndContent();
if (!this.isWidgetVisibile() && page.indexOf('download') === -1) {
mega.tpw.showWidget();
}
updateJSP();
};
this.pauseDownloadUpload = function(type, entry) {
'use strict';
if (!$rowsContainer || typeof type === 'undefined' || !entry) {
return;
}
lastPokeTime = Date.now();
var dId = entry.id;
var prefix;
var resumeText;
var cancelText;
if (type === this.DOWNLOAD) {
prefix = downloadRowPrefix;
if (entry.zipid) {
dId = entry.zipid;
}
resumeText = l[16182];
cancelText = l[1196];
}
else {
prefix = uploadRowPrefix;
resumeText = l[16184];
cancelText = l[1617];
}
var $targetedRow = $rowsContainer.find('#' + prefix + dId);
if (!$targetedRow || !$targetedRow.length) {
return;
}
if (monitors[dId]) {
clearTimeout(monitors[dId]);
delete monitors[dId];
}
$targetedRow.removeClass('error complete progress inqueue').addClass('paused');
setStatus($targetedRow, this.PAUSED);
var $actionsRow = $targetedRow.find('.transfer-task-actions').empty();
var $pausedAction = $transferActionTemplate.clone(true).addClass('restart');
$pausedAction.find('.tooltips').text(resumeText);
$actionsRow.append($pausedAction);
$pausedAction = $transferActionTemplate.clone(true).addClass('cancel');
$pausedAction.find('.tooltips').text(cancelText);
$actionsRow.append($pausedAction);
$targetedRow.removeAttr('prepared');
};
/**
* Removes a rows from widget
* @param {String} rowId download/upload ID
* @param {Boolean} isUpload {optional} a flag to distinguish transfer Type if rowId doesn't contain dl_/ul_
*/
this.removeRow = function(rowId, isUpload) {
'use strict';
if (!rowId) {
return;
}
if (Array.isArray(rowId)) {
for (var h = 0; h < rowId.length; h++) {
mega.tpw.removeRow(rowId[h]);
}
return;
}
var dId = rowId;
if (rowId[0] === 'd') {
isUpload = false;
dId = rowId.substr(3);
}
if (rowId[0] === 'z') {
isUpload = false;
dId = rowId.substr(4);
}
else if (rowId[0] === 'u') {
isUpload = true;
dId = rowId.substr(3);
}
var prefix = (isUpload) ? uploadRowPrefix : downloadRowPrefix;
var $targetedRow = $rowsContainer.find('#' + prefix + dId);
if (!$targetedRow || !$targetedRow.length) {
return;
}
removeRow($targetedRow);
};
this.isWidgetVisibile = function() {
return $widget.is(':visible');
};
this.showWidget = function() {
init();
initUI();
if (!$rowsContainer || !$rowsContainer.find('.transfer-task-row').length) {
return;
}
$widget.removeClass('hidden');
$widget.show();
updateJSP();
isHiddenByUser = false;
};
this.hideWidget = function() {
$widget.addClass('hidden');
$widget.hide();
};
this.clearRows = function(type) {
var $tasks;
if (!type) { // all
$tasks = $rowsContainer.find('.transfer-task-row');
}
else if (type === this.DONE) {
$tasks = $rowsContainer.find('.transfer-task-row.complete');
}
if ($tasks && $tasks.length) {
for (var r = 0; r < $tasks.length; r++) {
removeRow($($tasks[r]));
}
}
};
};
/** This class is the core of text file editor.
* It will handle uploading/downloading of data
* and performs memory/bandwidth optimization.
*/
mega.fileTextEditor = new function FileTextEditor() {
"use strict";
// the maximum slots in memory for edited files
// we have the maximum editable file size = 20MB --> max Total = 100MB
var maxFilesInMemory = 5;
var filesDataMap = Object.create(null);
var slotIndex = 0;
var slotsMap = [null, null, null, null, null];
/**
* store data in memory
* @param {String} handle Node handle
* @param {String} data File data
* @returns {Void} void
*/
var storeFileData = function(handle, data) {
// store the data in memory
filesDataMap[handle] = data;
if (slotsMap[slotIndex]) {
filesDataMap[slotsMap[slotIndex]] = null;
delete filesDataMap[slotsMap[slotIndex]];
}
// reserve the slot
slotsMap[slotIndex++] = handle;
var slots = maxFilesInMemory - 1;
// round-robin
if (slotIndex > slots) {
slotIndex = 0;
}
};
/**
* Get file data
* @param {String} handle Node handle
* @returns {MegaPromise} Promise of the operation
*/
this.getFile = function(handle) {
// eslint-disable-next-line local-rules/hints
var operationPromise = new MegaPromise();
var node = M.getNodeByHandle(handle);
handle = node.link || node.h;
// if called with no handle or invalid one, exit
if (!handle) {
if (d) {
console.error('Handle rejected in getFile ' + handle);
}
return operationPromise.reject();
}
// if we have the data cached, return it.
if (filesDataMap[handle]) {
return operationPromise.resolve(filesDataMap[handle]);
}
// this is empty file, no need to bother Data Servers + API
if (node.s <= 0) {
storeFileData(handle, '');
return operationPromise.resolve(filesDataMap[handle]);
}
// get the data
M.gfsfetch(handle, 0, -1).done(function(data) {
if (data.buffer === null) {
return operationPromise.reject();
}
var bData = new Blob([data.buffer], { type: "text/plain" });
var binaryReader = new FileReader();
binaryReader.addEventListener('loadend', function(e) {
var text = e.srcElement.result;
storeFileData(handle, text);
operationPromise.resolve(filesDataMap[handle]);
});
binaryReader.readAsText(bData);
}).fail(function(ev) {
if (ev === EOVERQUOTA || Object(ev.target).status === 509) {
dlmanager.setUserFlags();
dlmanager.showOverQuotaDialog();
}
operationPromise.reject();
});
return operationPromise;
};
/**
* Set file's data (save it)
* @param {String} handle Node's handle
* @param {String} content Text content to be saved
* @returns {MegaPromise} Operation Promise
*/
this.setFile = function(handle, content) {
// eslint-disable-next-line local-rules/hints
var operationPromise = new MegaPromise();
// if called with no handle or invalid one, exit
if (!handle || !M.d[handle]) {
return operationPromise.reject();
}
var fileNode = M.d[handle];
var fileType = filemime(fileNode);
var nFile = new File([content], fileNode.name, { type: fileType });
nFile.target = fileNode.p;
nFile.id = ++__ul_id;
nFile.path = '';
nFile.isCreateFile = true;
nFile._replaces = handle;
nFile.promiseToInvoke = operationPromise;
operationPromise.done(function(vHandle) {
// no need to clear here, since we are adding + removing
filesDataMap[handle] = null;
delete filesDataMap[handle];
filesDataMap[vHandle] = content;
});
ul_queue.push(nFile);
return operationPromise;
};
/**
* Save text file As
* @param {String} newName New name
* @param {String} directory Destination handle
* @param {String} content Text content to be saved
* @param {String} nodeToSaveAs Original node's handle
* @returns {MegaPromise} Operation Promise
*/
this.saveFileAs = function(newName, directory, content, nodeToSaveAs) {
// eslint-disable-next-line local-rules/hints
var operationPromise = new MegaPromise();
if (!newName || !directory || !(nodeToSaveAs || content)) {
if (d) {
console.error('saveFileAs is called incorrectly newName=' + newName +
' dir=' + directory + ' !content=' + !content + ' nodetoSave=' + nodeToSaveAs);
}
return operationPromise.reject();
}
loadingDialog.show();
// if content is not changed, then do a copy operation with new name
if (typeof content === 'undefined' || content === null) {
if (typeof nodeToSaveAs === 'string') {
nodeToSaveAs = M.d[nodeToSaveAs];
}
var nNode = Object.create(null);
var node = clone(nodeToSaveAs);
node.name = M.getSafeName(newName);
nNode.k = node.k;
node.lbl = 0;
node.fav = 0;
delete node.rr;
nNode.a = ab_to_base64(crypto_makeattr(node, nNode));
nNode.h = node.h;
nNode.t = node.t;
var opTree = [nNode];
opTree.opSize = node.s || 0;
M.copyNodes([nodeToSaveAs], directory, null, operationPromise, opTree);
}
// if content changed then do upload operation
else {
var fType = filemime(newName);
var nFile = new File([content], newName, { type: fType });
nFile.target = directory;
nFile.id = ++__ul_id;
nFile.path = '';
nFile.isCreateFile = true;
nFile.promiseToInvoke = operationPromise;
operationPromise.done(function(nHandle) {
storeFileData(nHandle, content);
});
ul_queue.push(nFile);
}
return operationPromise;
};
/**
* Remove previously created version, this is used when users do multiple saves to the same file.
* @param {String} handle Node's handle
* @returns {Void} void
*/
this.removeOldVersion = function(handle) {
api_req({ a: 'd', n: handle, v: 1 });
};
this.clearCachedFileData = function(handle) {
if (filesDataMap[handle]) {
filesDataMap[handle] = null;
delete filesDataMap[handle];
}
};
};
/**
* UI Controller to handle operations on the UI of text Editor
* */
mega.textEditorUI = new function TextEditorUI() {
"use strict";
var $myTextarea;
var fileHandle;
var versionHandle;
var fileName;
var savedFileData;
var editor;
var initialized = false;
var $containerDialog;
var $editorContianer;
var $saveButton;
/**
* Check if the file content has been changed and show a message if so
* @param {String} msg Message to show if file content is changed
* @param {String} submsg sub-message to show if file content is changed
* @param {Function} callback callback function to be called if file is not changed or user ignored changes.
* @returns {Void} void
*/
var validateAction = function(msg, submsg, callback) {
if (!$saveButton.hasClass('disabled')) {
msgDialog(
'confirmation',
'',
msg,
submsg,
function(e) {
if (e) {
callback();
}
else {
editor.focus();
}
}
);
}
else {
callback();
}
};
var selectedItemOpen = function() {
var openFile = function() {
loadingDialog.show('common', l[23130]);
var nodeHandle = $.selected && $.selected[0];
if (!nodeHandle) {
loadingDialog.hide();
return;
}
mega.fileTextEditor.getFile(nodeHandle).done(
function(data) {
loadingDialog.hide();
mega.textEditorUI.setupEditor(M.d[nodeHandle].name, data, nodeHandle);
}
).fail(function() {
loadingDialog.hide();
});
};
validateAction(l[22750], l[22754], openFile);
};
var bindChangeListner = function() {
var changeListner = function() {
$saveButton.removeClass('disabled');
editor.off('change', changeListner);
};
editor.on('change', changeListner);
};
var printText = function() {
/* eslint-disable no-unsanitized/method */
// Everything is sanitized.
var mywindow = window.open('', escapeHTML(fileName), 'height=600,width=800');
mywindow.document.write('' + escapeHTML(fileName) + '');
mywindow.document.write('');
var textContent = mywindow.document.createElement('pre');
textContent.textContent = editor.getValue();
// eslint-disable-next-line no-restricted-properties
mywindow.document.write(textContent.outerHTML);
mywindow.document.write('');
mywindow.document.close();
mywindow.focus();
mywindow.print();
mywindow.close();
/* eslint-enable no-unsanitized/method */
return true;
};
/** Init Controller
*@returns {Void} void
*/
var init = function() {
if (initialized) {
return;
}
// there's no jquery parent for this container.
// eslint-disable-next-line local-rules/jquery-scopes
$containerDialog = $('.txt-editor-frame');
$editorContianer = $('#mega-text-editor', $containerDialog);
$saveButton = $('.buttons-holder .save-btn', $editorContianer);
$('.editor-textarea-container', $editorContianer).resizable({
handles: 'e',
resize: function() {
var cm = $('.editor-textarea-container .CodeMirror', $editorContianer)[0];
if (cm) {
cm = cm.CodeMirror;
if (cm) {
cm.setSize();
}
}
}
});
/* eslint-disable sonarjs/no-duplicate-string */
$('.txt-editor-menu', $editorContianer).rebind(
'click.txt-editor',
function textEditorMenuOpen() {
if ($(this).hasClass('disabled')) {
return false;
}
// eslint-disable-next-line local-rules/jquery-replacements
$('.top-menu-popup-editor', $editorContianer).removeClass('hidden').show();
return false;
}
);
$editorContianer.rebind(
'click.txt-editor',
function textEditorGlobalClick() {
// eslint-disable-next-line local-rules/jquery-replacements
$('.top-menu-popup-editor', $editorContianer).addClass('hidden').hide();
return false;
}
);
$('.buttons-holder .close-btn, .editor-btn-container .close-f', $editorContianer).rebind(
'click.txt-editor',
function textEditorCloseBtnClick() {
if (editor) {
validateAction(
l[22750],
l[22751],
function() {
history.back();
mega.textEditorUI.doClose();
}
);
}
else {
history.back();
mega.textEditorUI.doClose();
}
return false;
}
);
$saveButton.rebind(
'click.txt-editor',
function textEditorSaveBtnClick() {
if ($(this).hasClass('disabled')) {
return false;
}
if (editor) {
$saveButton.addClass('disabled');
loadingDialog.show('common', l[23131]);
mega.fileTextEditor.setFile(versionHandle || fileHandle, editor.getValue()).done(function(fh) {
if (versionHandle) {
mega.fileTextEditor.removeOldVersion(versionHandle);
}
else if (M.d[fileHandle] && M.d[fileHandle].s === 0) {
mega.fileTextEditor.removeOldVersion(fileHandle);
fileHandle = fh;
fh = '';
}
versionHandle = fh;
savedFileData = editor.getValue();
bindChangeListner();
loadingDialog.hide();
});
}
}
);
$('.editor-btn-container .open-f', $editorContianer).rebind(
'click.txt-editor',
function openFileClick() {
M.initFileAndFolderSelectDialog('openFile', selectedItemOpen);
}
);
$('.editor-btn-container .save-f', $editorContianer).rebind(
'click.txt-editor',
function saveFileMenuClick() {
$saveButton.trigger('click');
}
);
$('.editor-btn-container .new-f', $editorContianer).rebind(
'click.txt-editor',
function newFileMenuClick() {
validateAction(
l[22750],
l[22752],
function() {
// loadingDialog.show();
openSaveAsDialog(
{ name: 'New file.txt' },
'',
function(handle) {
loadingDialog.hide();
mega.textEditorUI.setupEditor(M.d[handle].name, '', handle);
}
);
}
);
}
);
$('.editor-btn-container .save-as-f', $editorContianer).rebind(
'click.txt-editor',
function saveAsMenuClick() {
// loadingDialog.show();
var editedTxt = editor.getValue();
if (editedTxt === savedFileData) {
editedTxt = null;
}
openSaveAsDialog(
versionHandle || fileHandle,
editedTxt,
function(handle) {
loadingDialog.hide();
mega.textEditorUI.setupEditor(M.d[handle].name, editedTxt || savedFileData, handle);
}
);
}
);
$('.editor-btn-container .get-link-f', $editorContianer).rebind(
'click.txt-editor',
function getLinkFileMenuClick() {
selectionManager.clear_selection();
selectionManager.add_to_selection(versionHandle || fileHandle);
// there's no jquery parent for this container.
// eslint-disable-next-line local-rules/jquery-scopes
$('.dropdown.body.context .dropdown-item.getlink-item').trigger('click');
}
);
$('.editor-btn-container .send-contact-f', $editorContianer).rebind(
'click.txt-editor',
function sendToContactMenuClick() {
selectionManager.clear_selection();
selectionManager.add_to_selection(versionHandle || fileHandle);
// there's no jquery parent for this container.
// eslint-disable-next-line local-rules/jquery-scopes
$('.dropdown.body.context .dropdown-item.send-to-contact-item').trigger('click');
}
);
$('.editor-btn-container .print-f', $editorContianer).rebind('click.txt-editor', printText);
$('.editor-btn-container .txt-editor-download-btn', $editorContianer).rebind(
'click.txt-editor',
function downloadBtnClicked() {
validateAction(
l[22750],
l[22753],
function() {
M.saveAs(savedFileData, fileName);
}
);
}
);
var hotkey = 'ctrlKey';
if (ua.details.os === 'Apple') {
$('.open-f .menu-item-shortcut', $editorContianer).text(' ');
$('.close-f .menu-item-shortcut', $editorContianer).text(' ');
$('.save-f .menu-item-shortcut', $editorContianer).text('\u2318 S');
$('.save-as-f .menu-item-shortcut', $editorContianer).text('\u21E7\u2318 S');
$('.print-f .menu-item-shortcut', $editorContianer).text('\u2318 P');
hotkey = 'metaKey';
}
$editorContianer.rebind(
'keydown.txt-editor',
function keydownHandler(event) {
if (event[hotkey]) {
switch (event.code) {
case 'KeyS':
if (event.shiftKey) {
$('.editor-btn-container .save-as-f', $editorContianer).trigger('click');
}
else {
$saveButton.trigger('click');
}
return false;
case 'KeyO':
if (event.shiftKey) {
return true;
}
$('.editor-btn-container .open-f', $editorContianer).trigger('click');
return false;
case 'KeyQ':
if (event.shiftKey) {
return true;
}
$('.editor-btn-container .close-f', $editorContianer).trigger('click');
return false;
case 'KeyP':
if (event.shiftKey) {
return true;
}
$('.editor-btn-container .print-f', $editorContianer).trigger('click');
return false;
}
}
return true;
}
);
initialized = true;
/* eslint-enable sonarjs/no-duplicate-string */
};
this.doClose = function() {
// eslint-disable-next-line no-unused-expressions
editor && editor.setValue('');
if ($containerDialog) {
$containerDialog.addClass('hidden');
window.textEditorVisible = false;
}
};
/**
* Setup and init Text editor.
* @param {String} fName File name
* @param {String} txt File textual content
* @param {String} handle Node handle
* @param {Boolean} isReadonly Flag to open Editor in read-only mode
* @returns {Void} void
*/
this.setupEditor = function(fName, txt, handle, isReadonly) {
M.require('codemirror_js', 'codemirrorscroll_js').done(function() {
init();
pushHistoryState();
$containerDialog.removeClass('hidden');
window.textEditorVisible = true;
$myTextarea = $('#txtar', $editorContianer);
if (!editor) {
editor = CodeMirror.fromTextArea($myTextarea[0], {
lineNumbers: true,
scrollbarStyle: "overlay",
autofocus: true
});
}
// Without parentheses && will be applied first,
// I want JS to start from left and go in with first match
// eslint-disable-next-line no-extra-parens
if (isReadonly || folderlink || (M.currentrootid === 'shares' && M.getNodeRights(handle) < 1)) {
editor.options.readOnly = true;
$('.txt-editor-menu', $editorContianer).addClass('disabled');
$('.txt-editor-btn.save-btn', $editorContianer).addClass('hidden');
}
else {
editor.options.readOnly = false;
$('.txt-editor-menu', $editorContianer).removeClass('disabled');
$('.txt-editor-btn.save-btn', $editorContianer).removeClass('hidden');
}
if (editor) {
savedFileData = txt;
editor.setValue(txt);
bindChangeListner();
}
$saveButton.addClass('disabled');
$('.txt-editor-opened-f-name', $editorContianer).text(fName);
// eslint-disable-next-line local-rules/jquery-replacements
$('.top-menu-popup-editor', $editorContianer).addClass('hidden').hide();
if (Array.isArray(handle)) {
handle = handle[0];
}
editor.focus();
fileHandle = handle;
versionHandle = '';
fileName = fName;
api_req({ a: 'log', e: 99807, m: 'File Text Editor opened' });
});
};
};