/* Bundle Includes:
* js/vendor/megalist.js
* js/vendor/megaDynamicList.js
* js/fm/quickfinder.js
* js/fm/selectionmanager.js
* js/fm.js
* js/fm/dashboard.js
* js/fm/recents.js
* js/fm/account.js
* js/fm/account-change-password.js
* js/fm/account-change-email.js
* js/fm/dialogs.js
* js/fm/properties.js
*/
(function(scope, $) {
var isFirefox = navigator.userAgent.indexOf("Firefox") > -1;
var isIE = navigator.userAgent.indexOf('Edge/') > -1 || navigator.userAgent.indexOf('Trident/') > -1;
/**
* Internal/private helper method for doing 'assert's.
*
* @param val {boolean}
* @param msg {String}
*/
var assert = function(val, msg) {
if (!val) {
throw new Error(msg ? msg : "Assertion Failed.");
}
};
/**
* DOM utilities
*
* @type {{}}
*/
var DOMUtils = {};
/**
* Optimised/faster DOM node removal method
*
* @param node
*/
DOMUtils.removeNode = function(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
// else - parentNode is already removed.
};
/**
* Helper for .appendAfter
*
* @param newElement
* @param targetElement
*/
DOMUtils.appendAfter = function(newElement, targetElement) {
// target is what you want it to go after. Look for this elements parent.
var parent = targetElement.parentNode;
if (!parent) {
// TODO: fix me properly...
console.warn('The target element got detached from the DOM...', [targetElement]);
return false;
}
// if the parents lastchild is the targetElement...
if (parent.lastElementChild === targetElement) {
// add the newElement after the target element.
parent.appendChild(newElement);
} else {
// else the target has siblings, insert the new element between the target and it's next sibling.
parent.insertBefore(newElement, targetElement.nextElementSibling);
}
};
/**
* Helper for .prepend
*
* @param newElement
* @param targetElement
*/
DOMUtils.prepend = function(newElement, targetElement) {
if (targetElement.prepend) {
targetElement.prepend(newElement)
}
else {
if (targetElement.firstElementChild) {
targetElement.insertBefore(newElement, targetElement.firstElementChild);
}
else {
targetElement.appendChild(newElement);
}
}
};
var MEGALIST_DEFAULTS = {
/**
* Static, fixed width of the item when rendered (incl margin, padding, etc)
*/
'itemWidth': false,
/**
* Static, fixed height of the item when rendered (incl margin, padding, etc)
*/
'itemHeight': false,
/**
* Oredered list of item IDs
*/
'items': false,
/**
* A Callback function, that receives 1 argument - itemID (string/int) and should return a DOM Object, HTML
* String or a jQuery object that is the actual DOM node to be rendered/appended to the list.
*/
'itemRenderFunction': false,
/**
* Optional jQuery/CSS selector of an object to be used for appending. Must be a child of the container.
* Mainly used for hacking around table's markup and required DOM Tree where, the container would be marked as
* scrollable area, but the tbody would be used for appending the items.
*/
'appendTo': false,
/**
* If set to `true` MegaList would dynamically append, but never remove any nodes.
* This is useful for browsers which have issues doing DOM ops and mess up the actual overall user experience
* in sites using the MegaList for showing stuff, that later on would be cleared when the user changes the page
* (e.g. file managers, when the user goes to a different folder, the DOM is cleared out).
*/
'appendOnly': false,
/**
* Optional feature to insert items before/after their previous nodes, instead of always appending them
* to the bottom of the container.
*
* Note: Can be overwritten by render adapters (e.g. Table)
*/
'preserveOrderInDOM': false,
/**
* By default, the `MegaList.RENDER_ADAPTERS.PositionAbsolute` would be used.
*/
'renderAdapter': false,
/**
* Pass any PerfectScrollbar options here.
*/
'perfectScrollOptions': {},
/**
* Number of extra rows to show (even that they would be out of the viewport and hidden).
* Note: MegaList would render number of `extraRows` before and also `extraRows` after the visible items,
* except for the cases where the end of the list is reached or the list's scroll position is at top.
*/
'extraRows': 0,
/**
* Number of extra # of "pages" (e.g. container/scroll height / itemHeight) to batch when rendering, instead of
* always appending/removing one by one nodes while scrolling (main goal - to reduce FF FPS drops when
* scrolling)
*/
'batchPages': 0,
/**
* Internal option, that would be used by the Table renderer which would force the prepend to always be after
* a specific (runtime defined) DOM element
* @private
*/
'_alwaysPrependAfter': false,
/**
* Force MegaList to trigger a 'onUserScroll' jQuery Event if needed.
*/
'enableUserScrollEvent': false
};
/**
* Helper variable, that create unique IDs by auto incrementing for every new MegaList that gets initialised.
*
* @type {number}
*/
var listId = 0;
/**
* MegaList provides everything needed for efficient rendering of thousands of DOM nodes in a scrollable
* (overflown) list or a grid.
*
* @param listContainer {String|jQuery|DOMNode} the container, which would be used to append list items
* @param options {Object} see MEGALIST_DEFAULTS for defaults and available options.
* @constructor
*/
var MegaList = function (listContainer, options) {
assert(options.itemRenderFunction, 'itemRenderFunction was not provided.');
this.listId = listId++;
this.$listContainer = $(listContainer);
this.$listContainer
.css({'position': 'relative'})
.addClass("megaList");
this.listContainer = this.$listContainer[0];
this._lastScrollPosY = -1;
var items = options.items;
delete options.items;
if (!items) {
items = [];
}
this.items = items;
this.options = $.extend({}, MEGALIST_DEFAULTS, options);
if (this.options.appendTo) {
this.$content = $(this.options.appendTo, this.$listContainer);
this.content = this.$content[0];
}
this._wasRendered = false;
this._isUserScroll = false;
/**
* A dynamic cache to be used as a width/height/numeric calculations
*
* @type {{}}
* @private
*/
this._calculated = {};
/**
* A map of IDs which are currently rendered (cached as a map, so that we can reduce access to the DOM)
*
* @type {Array}
* @private
*/
this._currentlyRendered = {};
/**
* Init the render adapter
*/
if (!this.options.renderAdapter) {
this.options.renderAdapter = new MegaList.RENDER_ADAPTERS.PositionAbsolute();
}
// pass a reference to MegaList, so that _calculated and other stuff can be used.
this.options.renderAdapter.setMegaList(this);
};
/**
* Internal method used for generating unique (per MegaList) instance namespace string. (not prepended with "."!)
*
* @returns {string}
* @private
*/
MegaList.prototype._generateEventNamespace = function() {
return "megalist" + this.listId;
};
MegaList.prototype._actualOnScrollCode = function(e) {
var self = this;
if (self.options.enableUserScrollEvent) {
self.trigger('onUserScroll', e);
}
self._onScroll(e);
};
MegaList.prototype.throttledOnScroll = function(e) {
var wait = isFirefox ? 30 : 5;
var self = this;
if (!self._lastThrottledOnScroll) {
self._lastThrottledOnScroll = Date.now();
}
if ((self._lastThrottledOnScroll + wait - Date.now()) < 0) {
if (
self._lastScrollPosY !== e.target.scrollTop &&
self._isUserScroll === true &&
self.listContainer === e.target
) {
self._lastScrollPosY = e.target.scrollTop;
if (isFirefox) {
if (self._lastOnScrollTimer) {
clearTimeout(self._lastOnScrollTimer);
}
self._lastOnScrollTimer = setTimeout(function() {
self._actualOnScrollCode(e);
}, 0);
}
else {
self._actualOnScrollCode(e);
}
}
self._lastThrottledOnScroll = Date.now();
}
};
/**
* Internal method that would be called when the MegaList renders to the DOM UI and is responsible for binding
* the DOM events.
*
* @private
*/
MegaList.prototype._bindEvents = function () {
var self = this;
var ns = self._generateEventNamespace();
$(window).rebind("resize." + ns, function() {
self.resized();
});
$(document).rebind('ps-scroll-y.ps' + ns, self.throttledOnScroll.bind(self));
};
/**
* Called when .destroy is triggered. Should unbind any DOM events added by this MegaList instance.
*
* @private
*/
MegaList.prototype._unbindEvents = function () {
var ns = this._generateEventNamespace();
$(window).off("resize." + ns);
$(document).off('ps-scroll-y.ps' + ns);
};
/**
* Add an item to the list.
*
* @param itemId {String}
*/
MegaList.prototype.add = function (itemId) {
this.batchAdd([itemId]);
};
/**
* Remove and item from the list.
*
* @param itemId {String}
*/
MegaList.prototype.remove = function (itemId) {
this.batchRemove([itemId]);
};
/**
* Optimised adding of entries, less DOM updates
*
* @param itemIdsArray {Array} Array of item IDs (Strings)
*/
MegaList.prototype.batchAdd = function (itemIdsArray) {
var self = this;
itemIdsArray.forEach(function(itemId) {
self.items.push(itemId);
});
if (this._wasRendered) {
this._contentUpdated();
this._applyDOMChanges();
}
};
/**
* Optimised removing of entries, less DOM updates
*
* @param itemIdsArray {Array} Array of item IDs (Strings)
*/
MegaList.prototype.batchRemove = function (itemIdsArray) {
var self = this;
var requiresRerender = false;
var itemsWereModified = false;
itemIdsArray.forEach(function(itemId) {
var itemIndex = self.items.indexOf(itemId);
if (itemIndex > -1) {
if (self.isRendered(itemId)) {
requiresRerender = true;
DOMUtils.removeNode(self._currentlyRendered[itemId]);
delete self._currentlyRendered[itemId];
}
self.items.splice(itemIndex, 1);
itemsWereModified = true;
}
});
if (itemsWereModified) {
if (this._wasRendered) {
this._contentUpdated();
}
if (requiresRerender) {
this._repositionRenderedItems();
this._applyDOMChanges();
}
}
};
/**
* Checks if an item exists in the list.
*
* @param itemId {String}
* @returns {boolean}
*/
MegaList.prototype.has = function (itemId) {
return this.items.indexOf(itemId) > -1;
};
/**
* Checks if an item is currently rendered.
*
* @param itemId {String}
* @returns {boolean}
*/
MegaList.prototype.isRendered = function (itemId) {
return this._currentlyRendered[itemId] ? true : false;
};
/**
* Should be called when the list container is resized.
* This method would be automatically called on window resize, so no need to do that in the implementing code.
*/
MegaList.prototype.resized = function () {
this._calculated = {};
this._contentUpdated(true);
this._applyDOMChanges();
// destroy PS if ALL items are visible
if (
this._calculated['visibleFirstItemNum'] === 0 &&
this._calculated['visibleLastItemNum'] === this.items.length &&
this._calculated['contentWidth'] <= this._calculated['scrollWidth'] &&
this._calculated['contentHeight'] <= this._calculated['scrollHeight']
) {
if (this._scrollIsInitialized === true) {
this._scrollIsInitialized = false;
Ps.destroy(this.listContainer);
}
}
else {
// not all items are visible after a resize, should we init PS?
if (this._scrollIsInitialized === false) {
Ps.initialize(this.listContainer, this.options.perfectScrollOptions);
this._scrollIsInitialized = true;
}
}
// all done, trigger a resize!
$(this).trigger('resize');
};
/**
* Same as jQuery(megaListInstance).bind('eventName', cb);
*
* @param eventName {String}
* @param cb {Function}
*/
MegaList.prototype.bind = function (eventName, cb) {
$(this).on(eventName, cb);
};
/**
* Same as jQuery(megaListInstance).unbind('eventName', cb) and then .bind('eventName', cb);
*
* @param eventName {String}
* @param cb {Function}
*/
MegaList.prototype.rebind = function (eventName, cb) {
if (eventName.indexOf(".") === -1) {
if (typeof console !== 'undefined' && console.error) {
console.error("MegaList.rebind called with eventName that does not have a namespace, which is an" +
"anti-pattern");
}
return;
}
$(this).rebind(eventName, cb);
};
/**
* Same as jQuery(megaListInstance).unbind('eventName', cb);
* @param eventName {String}
* @param cb {Function}
*/
MegaList.prototype.unbind = function (eventName, cb) {
$(this).unbind(eventName, cb);
};
/**
* Same as jQuery(megaListInstance).trigger(...);
*/
MegaList.prototype.trigger = function () {
if (!this.$megaList) {
this.$megaList = $(this);
}
this.$megaList.trigger.apply(this.$megaList, arguments);
};
/**
* Force update the scrollable area.
*/
MegaList.prototype.scrollUpdate = function() {
if (this._scrollIsInitialized) {
Ps.update(this.listContainer);
}
};
/**
* Scroll the scrollable area to a specific `posTop` or `posLeft`.
* Passing undefined to `posTop` can be used to only scroll the area via `posLeft`
*
* @param posTop {Number|undefined}
* @param [posLeft] {Number|undefined}
*/
MegaList.prototype.scrollTo = function(posTop, posLeft) {
this._calculated = {};
if (typeof posTop !== 'undefined') {
this.listContainer.scrollTop = posTop;
}
if (typeof posLeft !== 'undefined') {
this.listContainer.scrollLeft = posLeft;
}
this.scrollUpdate();
this._repositionRenderedItems();
this._applyDOMChanges();
};
/**
* Returns the current top position of the scrollable area
*
* @returns {number|*|Number|undefined}
*/
MegaList.prototype.getScrollTop = function() {
return this.listContainer.scrollTop;
};
/**
* Returns the current left position of the scrollable area
*
* @returns {Number}
*/
MegaList.prototype.getScrollLeft = function() {
return this.listContainer.scrollLeft;
};
/**
* Returns the scroll's height
*
* @returns {Number}
*/
MegaList.prototype.getScrollHeight = function() {
this._recalculate();
return this._calculated['scrollHeight'];
};
/**
* Returns the scroll's width
*
* @returns {Number}
*/
MegaList.prototype.getScrollWidth = function() {
this._recalculate();
return this._calculated['scrollWidth'];
};
/**
* Returns the total height of the list (incl. the overflown/not visible part).
*
* @returns {Number}
*/
MegaList.prototype.getContentHeight = function() {
this._recalculate();
return this._calculated['contentHeight'];
};
/**
* Returns the total width of the list (incl. the overflown/not visible part).
* @returns {Number}
*/
MegaList.prototype.getContentWidth = function() {
this._recalculate();
return this._calculated['contentWidth'];
};
/**
* Returns true if the scrollable area is scrolled to top.
*
* @returns {boolean}
*/
MegaList.prototype.isAtTop = function() {
this._recalculate();
return this._calculated['isAtTop'];
};
/**
* Returns true if the scrollable area is scrolled to bottom.
*
* @returns {boolean}
*/
MegaList.prototype.isAtBottom = function() {
this._recalculate();
return this._calculated['isAtTop'];
};
/**
* Returns a percent, representing the scroll position X.
*
* @returns {Number}
*/
MegaList.prototype.getScrolledPercentX = function() {
this._recalculate();
return this._calculated['scrolledPercentX'];
};
/**
* Returns a percent, representing the scroll position Y.
*
* @returns {*}
*/
MegaList.prototype.getScrolledPercentY = function() {
this._recalculate();
return this._calculated['scrolledPercentY'];
};
/**
* Scroll the Y axis of the list to `posPerc`
*
* @param posPerc {Number} A percent in the format of 0.0 - 1.0
*/
MegaList.prototype.scrollToPercentY = function(posPerc) {
var targetPx = this.getContentHeight() * posPerc;
if (this.listContainer.scrollTop !== targetPx) {
this.listContainer.scrollTop = targetPx;
this._isUserScroll = false;
this.scrollUpdate();
this._onScroll();
this._isUserScroll = true;
}
};
/**
* Scroll to specific Y position.
*
* @param posY {Number}
*/
MegaList.prototype.scrollToY = function(posY) {
if (this.listContainer.scrollTop !== posY) {
this.listContainer.scrollTop = posY;
this._isUserScroll = false;
this.scrollUpdate();
this._onScroll();
this._isUserScroll = true;
}
};
/**
* Scroll to specific DOM Node.
* Warning: The DOM Node should be a child of the listContainer, otherwise you may notice weird behaviour of this
* function.
*
* @param element {DOMNode}
*/
MegaList.prototype.scrollToDomElement = function(element) {
if (!this._elementIsInViewport(element)) {
this.listContainer.scrollTop = $(element)[0].offsetTop;
this._isUserScroll = false;
this.scrollUpdate();
this._onScroll();
this._isUserScroll = true;
}
};
/**
* Scroll to specific `itemId`
*
* @param itemId {String}
* @returns {boolean} true if found, false if not found.
*/
MegaList.prototype.scrollToItem = function(itemId) {
var elementIndex = this.items.indexOf(itemId);
if (elementIndex === -1) {
return false;
}
var shouldScroll = false;
var itemOffsetTop = Math.floor(elementIndex / this._calculated['itemsPerRow']) * this.options.itemHeight;
var itemOffsetTopPlusHeight = itemOffsetTop + this.options.itemHeight;
// check if the item is above the visible viewport
if (itemOffsetTop < this._calculated['scrollTop']) {
shouldScroll = true;
}
// check if the item is below the visible viewport
else if (itemOffsetTopPlusHeight > (this._calculated['scrollTop'] + this._calculated['scrollHeight'])) {
shouldScroll = true;
}
// have to scroll
if (shouldScroll) {
this.listContainer.scrollTop = itemOffsetTop;
this._isUserScroll = false;
this.scrollUpdate();
this._onScroll();
this._isUserScroll = true;
return true;
}
else {
return false;
}
};
/**
* Alias to .scrollTo(0, 0)
*/
MegaList.prototype.scrollToTop = function() {
this.scrollTo(0, 0);
};
/**
* Alias to .scrollToPercentY(1)
*/
MegaList.prototype.scrollToBottom = function() {
this.scrollToPercentY(1);
};
/**
* Alias to .scrollTo(0, 0)
*/
MegaList.prototype.scrollPageUp = function() {
var top = this._calculated['scrollTop'];
top -= this._calculated['scrollHeight'];
if (top >= 0) {
this.scrollTo(top);
}
else {
this.scrollTo(0);
}
};
/**
* Alias to .scrollToPercentY(1)
*/
MegaList.prototype.scrollPageDown = function() {
var top = this._calculated['scrollTop'];
top += this._calculated['scrollHeight'];
if (top <= this._calculated['contentHeight'] - this._calculated['scrollHeight']) {
this.scrollTo(top);
}
else {
this.scrollTo(this._calculated['contentHeight'] - this._calculated['scrollHeight']);
}
};
/**
* Used in case you want to destroy the MegaList instance and its created DOM nodes
*/
MegaList.prototype.destroy = function () {
// destroy PS
this._unbindEvents();
this.items = [];
this._wasRendered = false;
Ps.destroy(this.listContainer);
if (!this.options.appendTo && this.content) {
DOMUtils.removeNode(this.content);
this.$content = this.content = undefined;
}
};
/**
* Often you may want to initialise the MegaList, but not render it immediately (e.g. some items are still loading
* and waiting to be added to the MegaList). Thats why, this method should be called so that the initial rendering
* of the internal DOM nodes is done.
*/
MegaList.prototype.initialRender = function () {
assert(this._wasRendered === false, 'This MegaList is already rendered');
if (!this.$content) {
this.$content = $('
');
this.$content.css({
'position': 'relative'
});
this.content = this.$content[0];
this.listContainer.appendChild(this.content);
}
// init PS
Ps.initialize(this.listContainer, this.options.perfectScrollOptions);
this._scrollIsInitialized = true;
this._contentUpdated();
if (this.options.renderAdapter._willRender) {
this.options.renderAdapter._willRender();
}
this._wasRendered = true;
this._applyDOMChanges();
this.scrollUpdate();
this._isUserScroll = true;
// bind events
this._bindEvents();
if (this.options.renderAdapter._rendered) {
this.options.renderAdapter._rendered();
}
};
/**
* Internal method to clear precalculated values.
*
* @param name {String}
* @private
*/
MegaList.prototype._clearCalculated = function(name) {
// TODO: write down all dependencies in an array and then calculate dependencies and clear them
// if (name === "scrollWidth") {
// TODO: clear related.
// e.g. scrolledPercentX
// }
delete this._calculated[name];
};
/**
* Does recalculation of the internally precalculated values so that the DOM Re-paints are reduced to minimum,
* while the user is scrolling up/down.
* @private
*/
MegaList.prototype._recalculate = function() {
var $listContainer = this.$listContainer;
var listContainer = this.listContainer;
var itemWidth = this.options.itemWidth;
var calculated = this._calculated;
// TODO: move all those IFs to a getter that would only calculate the requested values, not all of them!
if (!calculated['scrollWidth']) {
calculated['scrollWidth'] = $listContainer.innerWidth();
}
if (!itemWidth) {
itemWidth = calculated['scrollWidth']
};
if (!calculated['scrollHeight']) {
calculated['scrollHeight'] = $listContainer.innerHeight();
}
if (!calculated['contentWidth']) {
var contentWidth = $listContainer.children(":first").outerWidth();
if (contentWidth) {
calculated['contentWidth'] = contentWidth;
}
else {
if (this.options.itemWidth === false) {
calculated['contentWidth'] = calculated['scrollWidth'];
}
else {
calculated['contentWidth'] = this.options.itemWidth;
}
}
}
if (!calculated['itemsPerRow']) {
calculated['itemsPerRow'] = Math.max(
1,
Math.floor(
calculated['contentWidth'] / itemWidth
)
);
}
if (!calculated['contentHeight']) {
calculated['contentHeight'] = (
Math.ceil(this.items.length / calculated['itemsPerRow']) * this.options.itemHeight
);
}
if (!calculated['scrollLeft']) {
calculated['scrollLeft'] = this.listContainer.scrollLeft;
}
if (!calculated['scrollTop']) {
calculated['scrollTop'] = this.listContainer.scrollTop;
}
if (!calculated['scrolledPercentX']) {
calculated['scrolledPercentX'] = 100/calculated['scrollWidth'] * calculated['scrollLeft'];
}
if (!calculated['scrolledPercentY']) {
calculated['scrolledPercentY'] = 100/calculated['scrollHeight'] * calculated['scrollTop'];
}
if (!calculated['isAtTop']) {
calculated['isAtTop'] = calculated['scrollTop'] === 0;
}
if (!calculated['isAtBottom']) {
calculated['isAtBottom'] = this.listContainer.scrollTop === calculated['scrollHeight'];
}
if (!calculated['itemsPerPage']) {
calculated['itemsPerPage'] = Math.ceil(
calculated['scrollHeight'] / this.options.itemHeight
) * calculated['itemsPerRow'];
}
if (!calculated['visibleFirstItemNum']) {
if (this.options.appendOnly !== true) {
calculated['visibleFirstItemNum'] = Math.floor(
Math.floor(calculated['scrollTop'] / this.options.itemHeight) * calculated['itemsPerRow']
);
if (calculated['visibleFirstItemNum'] > 0) {
calculated['visibleFirstItemNum'] = Math.max(
0,
calculated['visibleFirstItemNum'] - (this.options.extraRows * calculated['itemsPerRow'])
);
}
}
else {
calculated['visibleFirstItemNum'] = 0;
}
}
if (!calculated['visibleLastItemNum']) {
calculated['visibleLastItemNum'] = Math.min(
this.items.length,
Math.ceil(
Math.ceil(calculated['scrollTop'] / this.options.itemHeight) *
calculated['itemsPerRow'] + calculated['itemsPerPage']
)
);
if (calculated['visibleLastItemNum'] < this.items.length) {
calculated['visibleLastItemNum'] = Math.min(
this.items.length,
calculated['visibleLastItemNum'] + (this.options.extraRows * calculated['itemsPerRow'])
);
}
}
calculated['itemsPerPage'] = (
Math.ceil(calculated['scrollHeight'] / this.options.itemHeight) * calculated['itemsPerRow']
);
if (this.options.batchPages > 0) {
var perPage = calculated['itemsPerPage'];
var visibleF = calculated['visibleFirstItemNum'];
calculated['visibleFirstItemNum'] = Math.max(
0,
((((visibleF - visibleF % perPage) / perPage) - 1) - this.options.batchPages) * perPage
);
var visibleL = calculated['visibleLastItemNum'];
calculated['visibleLastItemNum'] = Math.min(
this.items.length,
((((visibleL - visibleL % perPage) / perPage) + 1) + this.options.batchPages) * perPage
);
}
};
/**
* Internal method, that gets called when the MegaList's content gets updated (e.g. the internal list of item ids).
*
* @private
*/
MegaList.prototype._contentUpdated = function(forced) {
this._lastScrollPosY = -1;
var oldContentHeight = this._calculated['contentHeight'];
this._clearCalculated('contentWidth');
this._clearCalculated('contentHeight');
this._clearCalculated('visibleFirstItemNum');
this._clearCalculated('visibleLastItemNum');
if (this._wasRendered || forced) {
this._recalculate();
if (oldContentHeight != this._calculated['contentHeight']) {
if (this.content.tagName === "TBODY" && isIE) {
this.content.parentNode.style.height = this._calculated['contentHeight'] + "px";
}
else {
this.content.style.height = this._calculated['contentHeight'] + "px";
}
}
// scrolled out of the viewport if the last item in the list was removed? scroll back a little bit...
if (this._calculated['scrollHeight'] + this._calculated['scrollTop'] > this._calculated['contentHeight']) {
this.scrollToY(
this._calculated['contentHeight'] - this._calculated['scrollHeight']
);
}
}
};
/**
* Internal method, that get called when DOM changes should be done (e.g. render new items since they got in/out
* of the viewport)
* @private
*/
MegaList.prototype._applyDOMChanges = function() {
this._recalculate();
var contentWasUpdated = false;
var first = this._calculated['visibleFirstItemNum'];
var last = this._calculated['visibleLastItemNum'];
// remove items before the first visible item
if (this.options.appendOnly !== true) {
for (var i = 0; i < first; i++) {
var id = this.items[i];
if (this._currentlyRendered[id]) {
contentWasUpdated = true;
DOMUtils.removeNode(this._currentlyRendered[id]);
delete this._currentlyRendered[id];
}
}
// remove items after the last visible item
for (var i = last; i < this.items.length; i++) {
var id = this.items[i];
if (this._currentlyRendered[id]) {
contentWasUpdated = true;
DOMUtils.removeNode(this._currentlyRendered[id]);
delete this._currentlyRendered[id];
}
}
}
var prependQueue = [];
var appendQueue = [];
// show items which are currently visible
for(var i = first; i < last; i++) {
var id = this.items[i];
if (!this._currentlyRendered[id]) {
contentWasUpdated = true;
var renderedNode = this.options.itemRenderFunction(id);
if (this.options.renderAdapter._repositionRenderedItem) {
this.options.renderAdapter._repositionRenderedItem(id, renderedNode);
}
if (!this.options.preserveOrderInDOM) {
appendQueue.push(renderedNode);
}
else {
if (i === 0) {
if (this.options._alwaysPrependAfter) {
DOMUtils.appendAfter(renderedNode, this.options._alwaysPrependAfter);
}
else {
// DOMUtils.prepend(renderedNode, this.content);
prependQueue.push(renderedNode);
}
}
else {
var previousNodeId = this.items[i - 1];
if (this._currentlyRendered[previousNodeId]) {
DOMUtils.appendAfter(renderedNode, this._currentlyRendered[previousNodeId]);
}
else {
if (this.options._alwaysPrependAfter) {
DOMUtils.appendAfter(renderedNode, this.options._alwaysPrependAfter);
}
else {
// no previous, render first
// DOMUtils.prepend(renderedNode, this.content);
appendQueue.push(renderedNode);
}
}
}
}
this._currentlyRendered[id] = renderedNode;
var prependFragment = document.createDocumentFragment();
prependQueue.forEach(function(node) {
DOMUtils.prepend(node, prependFragment);
});
DOMUtils.prepend(prependFragment, this.content);
var appendFragment = document.createDocumentFragment();
appendQueue.forEach(function(node) {
appendFragment.appendChild(node);
});
this.content.appendChild(appendFragment);
}
else {
if (this.options.renderAdapter._repositionRenderedItem) {
this.options.renderAdapter._repositionRenderedItem(id);
}
}
}
if (contentWasUpdated === true) {
if (this.options.renderAdapter._itemsRepositioned) {
this.options.renderAdapter._itemsRepositioned();
}
this._isUserScroll = false;
this.scrollUpdate();
this._isUserScroll = true;
if (this.options.onContentUpdated) {
this.options.onContentUpdated();
}
}
};
/**
* Internal method that *ONLY* repositions items, in case a call to `_applyDOMChanges` is NOT needed, but the
* items in the list should be re-positioned.
* Basically, a lightweight version of `_applyDOMChanges` that does NOT adds or removes DOM nodes.
*
* @private
*/
MegaList.prototype._repositionRenderedItems = function() {
var self = this;
if (self.options.renderAdapter._repositionRenderedItem) {
Object.keys(self._currentlyRendered).forEach(function (k) {
self.options.renderAdapter._repositionRenderedItem(k);
});
}
if (this.options.renderAdapter._itemsRepositioned) {
this.options.renderAdapter._itemsRepositioned();
}
};
/**
* Internal method that gets called when the user scrolls.
*
* @param e {Event}
* @private
*/
MegaList.prototype._onScroll = function(e) {
this._clearCalculated('scrollTop');
this._clearCalculated('scrollLeft');
this._clearCalculated('visibleFirstItemNum');
this._clearCalculated('visibleLastItemNum');
this._applyDOMChanges();
};
/**
* Not-so efficient method of maintaining item ids in sync with your data source, but helpful enough for
* quick prototypes or UIs which don't update their item lists too often.
* Would update the internally stored item ids, with the idsArray.
* Would take care of appending/prepending/positioning newly rendered elements in the UI properly with minimum DOM
* updates.
*
* @param idsArray
*/
MegaList.prototype.syncItemsFromArray = function(idsArray) {
var self = this;
var r = array.diff(this.items, idsArray);
// IF initially the folder was empty, megaList may not had been rendered...so, lets check
var requiresRerender = false;
r.removed.forEach(function (itemId) {
var itemIndex = self.items.indexOf(itemId);
if (itemIndex > -1) {
if (self.isRendered(itemId)) {
requiresRerender = true;
DOMUtils.removeNode(self._currentlyRendered[itemId]);
delete self._currentlyRendered[itemId];
}
self.items.splice(itemIndex, 1);
}
});
r.added.forEach(function(itemId) {
var itemIndex = self.items.indexOf(itemId);
if (itemIndex === -1) {
// XX: Can be made more optimal, e.g. to only rerender if prev/next was updated
requiresRerender = true;
var targetIndex = idsArray.indexOf(itemId);
assert(targetIndex !== -1, 'targetIndex was -1, this should never happen.');
if (targetIndex === 0) {
self.items.unshift(itemId);
}
else {
self.items.splice(targetIndex, 0, itemId);
}
}
});
if (this._wasRendered) {
this._contentUpdated();
}
else {
this.initialRender();
}
if (requiresRerender) {
this._repositionRenderedItems();
this._applyDOMChanges();
}
};
/**
* Utility function for batch adding of new nodes on *specific* positions in the items list
*
* @param idsObj {{int,string}} a hash map with keys = position for the item to be added, string = item id
*/
MegaList.prototype.batchAddFromMap = function(idsObj) {
var self = this;
// IF initially the folder was empty, megaList may not had been rendered...so, lets check
var requiresRerender = false;
Object.keys(idsObj).forEach(function(targetIndex) {
var itemId = idsObj[targetIndex];
var itemIndex = self.items.indexOf(itemId);
if (itemIndex === -1) {
// XX: Can be made more optimal, e.g. to only rerender if prev/next was updated
requiresRerender = true;
if (targetIndex === 0) {
self.items.unshift(itemId);
// console.error('1unshift', itemId);
}
else {
self.items.splice(targetIndex, 0, itemId);
// console.error('1splice', targetIndex, itemId);
}
}
else if (itemIndex !== targetIndex) {
requiresRerender = true;
// delete item from the array
// console.error('2remove', itemIndex);
self.items.splice(itemIndex, 1);
// add it back to the new target position
// console.error('2add', targetIndex);
self.items.splice(targetIndex, 0, itemId);
}
});
if (this._wasRendered) {
this._contentUpdated();
}
else {
this.initialRender();
}
if (requiresRerender) {
this._repositionRenderedItems();
this._applyDOMChanges();
}
};
/**
* Basic util method to check if an element is in the visible viewport
*
* @param el {DOMNode|jQuery}
* @returns {boolean}
* @private
*/
MegaList.prototype._elementIsInViewport = function isElementInViewport (el) {
// refactored from:
// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport/
if (typeof jQuery === "function" && el instanceof jQuery) {
el = el[0];
}
var rect = el.getBoundingClientRect(),
vWidth = window.innerWidth || doc.documentElement.clientWidth,
vHeight = window.innerHeight || doc.documentElement.clientHeight,
efp = function (x, y) { return document.elementFromPoint(x, y) };
return rect.bottom > 0 &&
rect.right > 0 &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
rect.top < (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */;
};
MegaList.RENDER_ADAPTERS = {};
MegaList.RENDER_ADAPTERS.PositionAbsolute = function(options) {
this.options = $.extend({}, MegaList.RENDER_ADAPTERS.PositionAbsolute.DEFAULTS, options);
};
MegaList.RENDER_ADAPTERS.PositionAbsolute.prototype.setMegaList = function(megaList) {
this.megaList = megaList;
};
MegaList.RENDER_ADAPTERS.PositionAbsolute.prototype._repositionRenderedItem = function(itemId, node) {
assert(this.megaList, 'megaList is not set.');
var self = this;
var megaList = this.megaList;
if (!node) {
node = megaList._currentlyRendered[itemId];
}
var itemPos = megaList.items.indexOf(itemId);
var css = {
'position': 'absolute',
'top': (megaList.options.itemHeight * Math.floor(itemPos/megaList._calculated['itemsPerRow'])) + "px"
};
if (megaList._calculated['itemsPerRow'] > 1) {
css['left'] = ((itemPos % megaList._calculated['itemsPerRow']) * megaList.options.itemWidth) + "px";
}
node.classList.add('megaListItem');
Object.keys(css).forEach(function(prop, i) {
node.style[prop] = css[prop];
});
};
MegaList.RENDER_ADAPTERS.PositionAbsolute.prototype._rendered = function() {
var megaList = this.megaList;
assert(megaList.$content, 'megaList.$content is not ready.');
megaList.content.style.height = megaList._calculated['contentHeight'] + "px";
Ps.update(this.megaList.listContainer);
};
MegaList.RENDER_ADAPTERS.PositionAbsolute.DEFAULTS = {};
MegaList.RENDER_ADAPTERS.Table = function(options) {
this.options = $.extend({}, MegaList.RENDER_ADAPTERS.Table.DEFAULTS, options);
};
MegaList.RENDER_ADAPTERS.Table.prototype.setMegaList = function(megaList) {
this.megaList = megaList;
megaList.options.preserveOrderInDOM = true;
};
MegaList.RENDER_ADAPTERS.Table.prototype._willRender = function() {
var self = this;
var megaList = self.megaList;
self.prePusherDOMNode = document.createElement("tr");
self.postPusherDOMNode = document.createElement("tr");
DOMUtils.prepend(self.prePusherDOMNode, megaList.content);
megaList.content.appendChild(self.postPusherDOMNode);
megaList.options._alwaysPrependAfter = self.prePusherDOMNode;
};
MegaList.RENDER_ADAPTERS.Table.prototype._repositionRenderedItem = function(itemId, node) {
assert(this.megaList, 'megaList is not set.');
var self = this;
var megaList = this.megaList;
if (!node) {
node = megaList._currentlyRendered[itemId];
}
if (!node.classList.contains('megaListItem')) {
node.classList.add('megaListItem');
}
};
MegaList.RENDER_ADAPTERS.Table.prototype._itemsRepositioned = function(x) {
assert(this.megaList, 'megaList is not set.');
assert(this.prePusherDOMNode, 'prePusherDOMNode is not set, is the list rendered?');
assert(this.postPusherDOMNode, 'postPusherDOMNode is not set, is the list rendered?');
var self = this;
var megaList = self.megaList;
var calculated = megaList._calculated;
if (this.megaList.options.appendOnly !== true) {
var prepusherHeight = calculated['visibleFirstItemNum'] * megaList.options.itemHeight;
self.prePusherDOMNode.style.height = prepusherHeight + "px";
}
var postpusherHeight = (megaList.items.length - calculated['visibleLastItemNum']) * megaList.options.itemHeight;
self.postPusherDOMNode.style.height = postpusherHeight + "px";
};
MegaList.RENDER_ADAPTERS.Table.prototype._rendered = function() {
var megaList = this.megaList;
assert(megaList.$content, 'megaList.$content is not ready.');
if (megaList.content.tagName === "TBODY" && isIE) {
megaList.content.parentNode.style.height = megaList._calculated['contentHeight'] + "px";
}
else {
megaList.content.style.height = megaList._calculated['contentHeight'] + "px";
}
Ps.update(megaList.listContainer);
};
MegaList.RENDER_ADAPTERS.Table.DEFAULTS = {};
scope.MegaList = MegaList;
})(window, jQuery);
(function(scope, $) {
var isFirefox = navigator.userAgent.indexOf("Firefox") > -1;
var isIE = navigator.userAgent.indexOf('Edge/') > -1 || navigator.userAgent.indexOf('Trident/') > -1;
/**
* Internal/private helper method for doing 'assert's.
*
* @param val {boolean}
* @param msg {String}
*/
var assert = function(val, msg) {
if (!val) {
throw new Error(msg ? msg : "Assertion Failed.");
}
};
/**
* DOM utilities
*
* @type {{}}
*/
var DOMUtils = {};
/**
* Optimised/faster DOM node removal method
*
* @param node
*/
DOMUtils.removeNode = function(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
// else - parentNode is already removed.
};
/**
* Helper for .appendAfter
*
* @param newElement
* @param targetElement
*/
DOMUtils.appendAfter = function(newElement, targetElement) {
// target is what you want it to go after. Look for this elements parent.
var parent = targetElement.parentNode;
if (!parent) {
// TODO: fix me properly...
console.warn('The target element got detached from the DOM...', [targetElement]);
return false;
}
// if the parents lastchild is the targetElement...
if (parent.lastElementChild === targetElement) {
// add the newElement after the target element.
parent.appendChild(newElement);
} else {
// else the target has siblings, insert the new element between the target and it's next sibling.
parent.insertBefore(newElement, targetElement.nextElementSibling);
}
};
/**
* Helper for .prepend
*
* @param newElement
* @param targetElement
*/
DOMUtils.prepend = function(newElement, targetElement) {
if (targetElement.prepend) {
targetElement.prepend(newElement)
}
else {
if (targetElement.firstElementChild) {
targetElement.insertBefore(newElement, targetElement.firstElementChild);
}
else {
targetElement.appendChild(newElement);
}
}
};
var SETTINGS = {
/**
* Callback should return the height of item at id.
* Callback should satisfy the signature function(id) -> height (int)
*/
'itemHeightCallback': false,
/**
* A Callback function, that receives 1 argument - itemID (string/int) and should return a DOM Object, HTML
* String or a jQuery object that is the actual DOM node to be rendered/appended to the list.
*/
'itemRenderFunction': false,
/**
* Pass any PerfectScrollbar options here.
*/
'perfectScrollOptions': {},
/**
* Force MegaDynamicList to trigger a 'onUserScroll' jQuery Event if needed.
*/
'enableUserScrollEvent': false,
/**
* Triggered when the content is updated.
*/
'onContentUpdated': false,
/**
* Offscreen buffer to keep rendered in px.
*/
'viewPortBuffer': 50,
/**
* Custom callback for when the view changes.
*/
'onViewChange': false,
/**
* Custom callback only triggeed when new nodes are inserted into the DOM.
* Can satisfy signature (Array injectedIds) => void
*/
'onNodeInjected': false,
/**
* Custom classes to add to the contentContainer. (The actual content div that gets scrolled.
*/
'contentContainerClasses': false,
/**
* Optional resize callback.
*/
'onResize': false,
/**
* On scroll callback.
*/
'onScroll': false,
/**
* Initial scroll position.
*/
'initialScrollY': false
};
/**
* Helper variable, that create unique IDs by auto incrementing for every new MegaDynamicList that gets initialised.
*
* @type {number}
*/
var listId = 0;
/**
* MegaDynamicList allows for rendering a list inside of a viewport. Only items which are within the visible range
* will be rendered.
*
* @param listContainer {String|jQuery|DOMNode} the container, which would be used to append list items
* @param options {Object} see SETTINGS for defaults and available options.
* @constructor
*/
var MegaDynamicList = function (listContainer, options) {
assert(options.itemRenderFunction, 'itemRenderFunction was not provided.');
assert(options.itemHeightCallback, 'itemHeightCallback was not provided.');
this.listId = listId++;
this.$listContainer = $(listContainer);
this.$listContainer
.css({'position': 'relative'})
.addClass("MegaDynamicList");
this.listContainer = this.$listContainer[0];
this.prepusher = null;
this._lastScrollPosY = -1;
var items = options.items;
delete options.items;
if (!items) {
items = [];
}
this.items = items;
// Maintains the height of each item in the list.
this._heights = {};
// Maintains the top-offset for each item in the list.
this._offsets = {};
this._wasRendered = false;
/**
* A dynamic cache to be used as a width/height/numeric calculations
*
* @type {{}}
* @private
*/
this._calculated = {};
// Remember the last range before scrolling.
this._lastFirstPos = 0;
this._lastLastPos = 0;
this._lastFirstItem = null;
this._lastLastItem = null;
// Indicates if this list is currently rendered and listening for events.
this.active = true;
// Saved state such that when we resume we can restore to the same position.
this._state = {};
/**
* A map of IDs which are currently rendered (cached as a map, so that we can reduce access to the DOM)
*
* @type {Array}
* @private
*/
this._currentlyRendered = {};
this.options = $.extend({}, SETTINGS, options);
this._debug = localStorage.d || 0;
};
/**
* Do the initial render, setting up the content container and scrolling.
*/
MegaDynamicList.prototype.initialRender = function() {
assert(this._wasRendered === false, 'This MegaDynamicList is already rendered');
if (!this.$content) {
this.$content = $('
');
this.$content.css({
'position': 'relative'
});
if (this.options.contentContainerClasses) {
this.$content.addClass(this.options.contentContainerClasses);
}
this.content = this.$content[0];
this.listContainer.appendChild(this.content);
this.prepusher = this.$content.find(".pre-pusher")[0];
}
// init PS
Ps.initialize(this.listContainer, this.options.perfectScrollOptions);
this._wasRendered = true;
this._isUserScroll = true;
this._onContentUpdated();
// bind events
this._bindEvents();
this._calculateHeightAndOffsets(true);
if (this.options.initialScrollY) {
this.listContainer.scrollTop = this.options.initialScrollY;
}
this._calculateScrollValues();
this._viewChanged(true);
};
/**
* Calculate the total height + offsets of each item on screen.
* @private
*/
MegaDynamicList.prototype._calculateHeightAndOffsets = function (applyHeight) {
'use strict';
var totalHeight = 0;
for (var i = 0; i < this.items.length; i++) {
var key = this.items[i];
if (!this._heights[key]) {
this._heights[key] = this.options.itemHeightCallback(key);
}
this._offsets[key] = totalHeight;
totalHeight += this._heights[key];
this._heights[key] = this._heights[key];
}
this._calculated['contentHeight'] = totalHeight;
if (applyHeight) {
this.content.style.height = this._calculated['contentHeight'] + "px";
Ps.update(this.listContainer);
}
};
/**
* Should be triggered when an items render properties are changed (eg Height).
* @param index
*/
MegaDynamicList.prototype.itemRenderChanged = function(id) {
'use strict';
this._updateHeight(id);
this._viewChanged();
};
/**
* Force a DOM element to be re-collected from the collector function if it is in view.
* @param id
*/
MegaDynamicList.prototype.itemChanged = function(id) {
'use strict';
if (this.active && this._currentlyRendered[id]) {
this._removeItemFromView(id);
this._updateHeight(id);
this._viewChanged(true);
}
};
/**
* Handle when an items height changes.
* @param index
* @private
*/
MegaDynamicList.prototype._updateHeight = function(id) {
'use strict';
var newHeight = this.options.itemHeightCallback(id);
this._calculated['contentHeight'] += newHeight - this._heights[id];
this.content.style.height = this._calculated['contentHeight'] + "px";
this._heights[id] = newHeight;
this._calculateHeightAndOffsets(true);
};
/**
* Update all offsets below the start index.
* @param startIndex Index to start at.
* @param offset The offset for the first item.
* @private
*/
MegaDynamicList.prototype._updateOffsetsFrom = function(startIndex, offset) {
'use strict';
for (var i = startIndex; i < this.items.length; i++) {
var id = this.items[i];
this._offsets[id] = offset;
if (!this._heights[id]) {
this._heights[id] = this.options.itemHeightCallback(id);
}
offset += this._heights[id];
}
};
/**
* Calculate the scroll offset and viewport height.
* @private
*/
MegaDynamicList.prototype._calculateScrollValues = function() {
'use strict';
this._calculated['scrollTop'] = this.listContainer.scrollTop;
this._calculated['scrollHeight'] = this.$listContainer.innerHeight();
};
/**
* Calculate the first and last items visible on screen.
* @private
*/
MegaDynamicList.prototype._calculateFirstLast = function() {
'use strict';
var viewportTop = this._calculated['scrollTop'] - this.options.viewPortBuffer;
var viewportBottom = viewportTop + this._calculated['scrollHeight'] + this.options.viewPortBuffer;
var i = 0;
while (this._offsets[this.items[i]] < viewportTop && i < this.items.length - 1) {
i++;
}
var top = i;
if (this._offsets[this.items[i]] > viewportTop) {
top = Math.max(0, i - 1);
}
while (this._offsets[this.items[i]] < viewportBottom && i < this.items.length - 1) {
i++;
}
this._calculated['firstItemPos'] = top;
this._calculated['lastItemPos'] = i;
this._calculated['firstItem'] = this.items[top];
this._calculated['lastItem'] = this.items[i];
};
/**
* Triggered when the view is changed.
* @private
*/
MegaDynamicList.prototype._viewChanged = function(forceDOMCheck) {
'use strict';
this._calculateScrollValues();
this._calculateFirstLast();
if (forceDOMCheck
|| this._calculated['firstItemPos'] !== this._lastFirstPos || this._calculated['lastItemPos'] !== this._lastLastPos
|| this._calculated['firstItem'] !== this._lastFirstItem || this._calculated['lastItem'] !== this._lastLastItem
) {
this._applyDOMChanges();
}
this._lastFirstPos = this._calculated['firstItemPos'];
this._lastLastPos = this._calculated['lastItemPos'];
this._lastFirstItem = this._calculated['firstItem'];
this._lastLastItem = this._calculated['lastItem'];
if (this.options.onViewChange) {
this.options.onViewChange();
}
};
/**
* Apply any required DOM Changes.
* @private
*/
MegaDynamicList.prototype._applyDOMChanges = function() {
var contentHasChanged = false;
var nodeInjected = [];
var low = this._calculated['firstItemPos'];
var high = this._calculated['lastItemPos'];
if (high < this.items.length) {
high += 1;
}
for (var i = 0; i < this.items.length; i++) {
var id = this.items[i];
if (this._currentlyRendered[id] !== undefined && (i < low || i > high)) {
this._removeItemFromView(id);
contentHasChanged = true;
}
}
for (var i = low; i < high; i++) {
var id = this.items[i];
if (!this._currentlyRendered[id]) {
this._currentlyRendered[id] = this.options.itemRenderFunction(id);
this._currentlyRendered[id].classList.add("MegaDynamicListItem");
var afterTarget;
if (this._currentlyRendered[this.items[i - 1]]) {
afterTarget = this._currentlyRendered[this.items[i - 1]];
} else {
afterTarget = this.prepusher;
}
DOMUtils.appendAfter(this._currentlyRendered[id], afterTarget);
contentHasChanged = true;
nodeInjected.push(id);
}
}
this.prepusher.style.height = this._offsets[this.items[low]] + "px";
if (contentHasChanged) {
this._onContentUpdated();
if (nodeInjected) {
this._onNodeInjected(nodeInjected);
}
}
};
MegaDynamicList.prototype.add = function (item) {
'use strict';
this.batchAdd([item]);
};
/**
* Optimised adding of entries, less DOM updates
*
* @param itemIdsArray {Array} Array of item IDs (Strings)
*/
MegaDynamicList.prototype.batchAdd = function (itemIdsArray) {
var self = this;
itemIdsArray.forEach(function(itemId) {
self.items.push(itemId);
});
};
/**
* Internal method to trigger when the dom content is changed.
* @private
*/
MegaDynamicList.prototype._onContentUpdated = function() {
'use strict';
if (this.options.onContentUpdated) {
this.options.onContentUpdated();
}
};
/**
* Similiar to onContentUpdated but will only trigger if a new node added to the view not if a node is removed.
* @private
*/
MegaDynamicList.prototype._onNodeInjected = function() {
if (this.options.onNodeInjected) {
this.options.onNodeInjected();
}
};
/**
* Internal method used for generating unique (per MegaDynamicList) instance namespace string. (not prepended with "."!)
*
* @returns {string}
* @private
*/
MegaDynamicList.prototype._generateEventNamespace = function() {
return "MegaDynamicList" + this.listId;
};
/**
* Should be called when the list container is resized.
* This method would be automatically called on window resize, so no need to do that in the implementing code.
*/
MegaDynamicList.prototype.resized = function () {
'use strict';
this._calculateScrollValues();
this._viewChanged();
if (this.options.onResize) {
this.options.onResize();
}
// all done, trigger a resize!
$(this).trigger('resize');
};
MegaDynamicList.prototype._actualOnScrollCode = function(e) {
var self = this;
if (self.options.onScroll) {
self.options.onScroll();
}
self._onScroll(e);
};
MegaDynamicList.prototype.throttledOnScroll = function(e) {
var wait = isFirefox ? 30 : 5;
var self = this;
if (!self._lastThrottledOnScroll) {
self._lastThrottledOnScroll = Date.now();
}
if ((self._lastThrottledOnScroll + wait - Date.now()) < 0) {
if (
self._lastScrollPosY !== e.target.scrollTop &&
self._isUserScroll === true &&
self.listContainer === e.target
) {
self._lastScrollPosY = e.target.scrollTop;
if (isFirefox) {
if (self._lastOnScrollTimer) {
clearTimeout(self._lastOnScrollTimer);
}
self._lastOnScrollTimer = setTimeout(function() {
self._actualOnScrollCode(e);
}, 0);
}
else {
self._actualOnScrollCode(e);
}
}
self._lastThrottledOnScroll = Date.now();
}
};
/**
* Internal method that gets called when the user scrolls.
*
* @param e {Event}
* @private
*/
MegaDynamicList.prototype._onScroll = function(e) {
this._calculateScrollValues();
this._viewChanged();
};
/**
* Internal method that would be called when the MegaDynamicList renders to the DOM UI and is responsible for binding
* the DOM events.
*
* @private
*/
MegaDynamicList.prototype._bindEvents = function () {
var self = this;
var ns = self._generateEventNamespace();
$(window).rebind("resize." + ns, function() {
self.resized();
});
$(document).rebind('ps-scroll-y.ps' + ns, self.throttledOnScroll.bind(self));
};
/**
* Called when .destroy is triggered. Should unbind any DOM events added by this MegaDynamicList instance.
*
* @private
*/
MegaDynamicList.prototype._unbindEvents = function () {
var ns = this._generateEventNamespace();
$(window).off("resize." + ns);
$(document).off('ps-scroll-y.ps' + ns);
};
/**
* Insert a new item into the list.
* @param after
* @param id
*/
MegaDynamicList.prototype.insert = function(after, id, renderUpdate) {
'use strict';
if (renderUpdate !== false) {
renderUpdate = true;
}
var position;
if (!after) {
position = 0;
} else {
position = this.items.indexOf(after) + 1;
}
[].splice.apply(this.items, [position, 0].concat(id));
this._calculateHeightAndOffsets(true);
if (renderUpdate) {
this._viewChanged(true);
}
};
/**
* Remove an item from the list.
* @param id
*/
MegaDynamicList.prototype.remove = function(id, renderUpdate) {
'use strict';
if (renderUpdate !== false) {
renderUpdate = true;
}
if (!Array.isArray(id)) {
id = [id];
}
var position = this.items.indexOf(id[0]);
this.items.splice(position, id.length);
// Ensure that they are not currently rendered.
for (var i =0; i < id.length; i++) {
this._removeItemFromView(id[i]);
}
this._calculateHeightAndOffsets(true);
if (renderUpdate) {
this._viewChanged(true);
}
};
/**
* Remove an item from the view.
* @param id
* @private
*/
MegaDynamicList.prototype._removeItemFromView = function(id) {
'use strict';
if (this._currentlyRendered[id]) {
DOMUtils.removeNode(this._currentlyRendered[id]);
delete this._currentlyRendered[id];
}
};
/**
* Destroy this instance.
*/
MegaDynamicList.prototype.destroy = function() {
'use strict';
this._unbindEvents();
this.items = [];
this._wasRendered = false;
Ps.destroy(this.listContainer);
DOMUtils.removeNode(this.content);
this.$listContainer.html("");
this.$content = this.content = undefined;
};
/**
* Should be triggered when the list is no longer in view.
*/
MegaDynamicList.prototype.pause = function() {
'use strict';
this.active = false;
this._state = this._calculated;
this._unbindEvents();
Ps.destroy(this.listContainer);
var currentlyRenderedKeys = Object.keys(this._currentlyRendered);
for (var i = 0; i < currentlyRenderedKeys.length; i++) {
this._removeItemFromView(currentlyRenderedKeys[i]);
}
};
/**
* Resume state, should be called when the list is brought back into view.
*/
MegaDynamicList.prototype.resume = function() {
'use strict';
this.active = true;
this._wasRendered = false;
this.initialRender();
};
/**
* Returns the current top position of the scrollable area
*
* @returns {number|*|Number|undefined}
*/
MegaDynamicList.prototype.getScrollTop = function() {
return this.listContainer.scrollTop;
};
MegaDynamicList.prototype.getFirstItemPosition = function() {
'use strict';
return this._calculated['firstItemPos'];
};
MegaDynamicList.prototype.scrollToItemPosition = function(position) {
'use strict';
this.listContainer.scrollTop = this._offsets[this.items[position]] + (this.options.viewPortBuffer * 2);
this._viewChanged(true);
};
scope.MegaDynamicList = MegaDynamicList;
})(window, jQuery);
/**
* Simple way for searching for nodes by their first letter.
*
* PS: This is meant to be somehow reusable.
*
* @param searchable_elements selector/elements a list/selector of elements which should be searched for the user
* specified key press character
* @param containers selector/elements a list/selector of containers to which the input field will be centered
* (the code will dynamically detect and pick the :visible container)
*
* @returns {*}
* @constructor
*/
var QuickFinder = function(searchable_elements, containers) {
'use strict'; /* jshint -W074 */
var self = this;
var DEBUG = false;
self._is_active = false; // defined as a prop of this. so that when in debug mode it can be easily accessed from
// out of this scope
var last_key = null;
var next_idx = 0;
// hide on page change
if (QuickFinder._pageChangeListenerId) {
mBroadcaster.removeListener(QuickFinder._pageChangeListenerId);
}
QuickFinder._pageChangeListenerId = mBroadcaster.addListener('pagechange', function () {
if (self.is_active()) {
self.deactivate();
}
});
$(window).rebind('keypress.quickFinder', function(e) {
e = e || window.event;
// DO NOT start the search in case that the user is typing something in a form field... (eg.g. contacts -> add
// contact field)
if ($(e.target).is("input, textarea, select") || $.dialog) {
return;
}
var charCode = e.which || e.keyCode; // ff
if (
(charCode >= 48 && charCode <= 57) ||
(charCode >= 65 && charCode <= 123) ||
charCode > 255
) {
var charTyped = String.fromCharCode(charCode);
// get the currently visible container
var $container = $(containers).filter(":visible");
if (!$container.length) {
// no active container, this means that we are receiving events for a page, for which we should not
// do anything....
return;
}
self._is_active = true;
$(self).trigger("activated");
var foundIds = [];
charTyped = charTyped.toLowerCase();
foundIds = M.v.filter(function(v) {
var nameStr = "";
if (v.name) {
nameStr = v.name;
}
else if (v.firstName) {
nameStr = v.firstName;
}
else if (v.m) {
// ipc and opc
nameStr = v.m;
}
if (nameStr && nameStr[0] && nameStr[0].toLowerCase() === charTyped) {
return true;
}
});
if (
/* repeat key press, but show start from the first element */
(last_key != null && (foundIds.length - 1) <= next_idx)
||
/* repeat key press is not active, should reset the target idx to always select the first
element */
(last_key == null)
) {
next_idx = 0;
last_key = null;
} else if (last_key == charTyped) {
next_idx++;
} else if (last_key != charTyped) {
next_idx = 0;
}
last_key = charTyped;
if (foundIds[next_idx]) {
var nextId = SelectionManager.dynamicNodeIdRetriever(foundIds[next_idx]);
selectionManager.clear_selection();
selectionManager.set_currently_selected(nextId, true);
if (!M.megaRender.megaList) {
$(searchable_elements).parents(".ui-selectee, .ui-draggable").removeClass('ui-selected');
var $target_elm = $('#' + nextId);
$target_elm.parents(".ui-selectee, .ui-draggable").addClass("ui-selected");
var $jsp = $target_elm.getParentJScrollPane();
if ($jsp) {
var $scrolled_elm = $target_elm.parent("a");
if (!$scrolled_elm.length) { // not in icon view, its a list view, search for a tr
$scrolled_elm = $target_elm.parents('tr:first');
}
$jsp.scrollToElement($scrolled_elm);
}
$(self).trigger('search');
}
}
}
else if (charCode >= 33 && charCode <= 36)
{
var e = '.files-grid-view.fm';
if (M.viewmode == 1) {
e = '.fm-blocks-view.fm';
}
if (M.megaRender && M.megaRender.megaList) {
switch (charCode) {
case 33: /* Page Up */
M.megaRender.megaList.scrollPageUp();
break;
case 34: /* Page Down */
M.megaRender.megaList.scrollPageDown();
break;
case 35: /* End */
M.megaRender.megaList.scrollToBottom();
break;
case 36: /* Home */
M.megaRender.megaList.scrollToTop();
break;
}
}
else {
if ($(e + ':visible').length) {
e = $('.grid-scrolling-table:visible, .file-block-scrolling:visible');
var jsp = e.data('jsp');
if (jsp) {
switch (charCode) {
case 33: /* Page Up */
jsp.scrollByY(-e.height(), !0);
break;
case 34: /* Page Down */
jsp.scrollByY(e.height(), !0);
break;
case 35: /* End */
jsp.scrollToBottom(!0);
break;
case 36: /* Home */
jsp.scrollToY(0, !0);
break;
}
}
}
}
}
});
// hide the search field when the user had clicked somewhere in the document
$(document.body).on('mousedown', '> *', function() {
if (!is_fm()) {
return;
}
if (self.is_active()) {
self.deactivate();
return false;
}
});
// use events as a way to communicate with this from the outside world.
self.deactivate = function() {
self._is_active = false;
$(self).trigger("deactivated");
};
self.is_active = function() {
return self._is_active;
};
self.disable_if_active = function() {
if (self.is_active()) {
self.deactivate();
}
};
return this;
};
var quickFinder = new QuickFinder(
'.tranfer-filetype-txt, .file-block-title, td span.contacts-username',
'.files-grid-view, .fm-blocks-view.fm, .contacts-grid-table, .contacts-blocks-scrolling,' +
'.contact-requests-grid, .sent-requests-grid'
);
/**
* This should take care of flagging the LAST selected item in those cases:
*
* - jQ UI $.selectable's multi selection using drag area (integrated using jQ UI $.selectable's Events)
*
* - Single click selection (integrated by assumption that the .get_currently_selected will also try to cover this
* case when there is only one .ui-selected...this is how no other code had to be changed :))
*
* - Left/right/up/down keys (integrated by using the .set_currently_selected and .get_currently_selected public
* methods)
*
* @param $selectable
* @param resume {boolean}
* @returns {*}
* @constructor
*/
var SelectionManager = function($selectable, resume) {
var self = this;
var idx = window._selectionManagerIdx = window._selectionManagerIdx ? window._selectionManagerIdx + 1 : 1;
self.idx = idx;
var debugMode = !!localStorage.selectionManagerDebug;
/**
* Store all selected items in an _ordered_ array.
*
* @type {Array}
*/
this.selected_list = [];
this.last_selected = null;
/**
* This method should be called, so that a brand new jQuery query would be called each time, to guarantee that
* the currently cached $selectable is the one attached to the DOM.
* This is caused by the deleteScroll called in some sections (contacts, shared) right after the MegaRender init
* finishes. Which makes its MegaRender.container to be the cached node which was just removed from the DOM.
*
* @returns {*}
* @private
*/
this._ensure_selectable_is_available = function() {
var targetScope = $selectable && $selectable[0];
if (
!targetScope ||
!targetScope.parentNode ||
$(targetScope).is(".hidden") ||
!$(targetScope).is(":visible")
) {
// because MegaRender is providing a DOM node, which later on is being removed, we can't cache
// the $selectable in this case, so lets try to use $.selectddUIgrid and do a brand new jq Sizzle query
$selectable = $($.selectddUIgrid + ":visible");
}
return $selectable;
};
/**
* Called to bind the (currently active) selectionManager to the currently active .ui-selectable target
*
* @param target
*/
this.bindSelectable = function(target) {
var $jqSelectable = $(target);
if (debugMode) {
console.error("(re)bindselectable", target, self);
}
/**
* Push the last selected item to the end of the selected_list array.
*/
$jqSelectable.rebind('selectableselecting.sm' + idx + ' selectableselected.sm' + idx, function(e, data) {
var $selected = $(data.selecting || data.selected);
var id = $selected.attr('id');
if (id) {
// dont use 'this/self' but the current/global selectionManager
selectionManager.add_to_selection(id);
}
});
/**
* Remove any unselected element from the selected_list array.
*/
$jqSelectable.rebind('selectableunselecting.sm' + idx + ' selectableunselected.sm' + idx, function(e, data) {
var $unselected = $(data.unselecting || data.unselected);
var unselectedId = $unselected.attr('id');
if (unselectedId) {
// dont use 'this/self' but the current/global selectionManager
selectionManager.remove_from_selection(unselectedId);
}
});
if (selectionManager) {
selectionManager._$jqSelectable = $jqSelectable;
}
if ($(target).is(".file-block-scrolling:not(.hidden)")) {
// jQuery UI won't do trigger unselecting, in case of the ui-selected item is NOT in the DOM, so
// we need to reset it on our own (on drag on the background OR click)
$(target).rebind('mousedown.sm' + idx, function(e) {
if ($(e.target.parentNode).is(".file-block-scrolling:not(.hidden)")) {
selectionManager.clear_selection();
}
});
}
};
/**
* Helper func to clear old reset state from other icons.
*/
this.clear_last_selected = function() {
if (this.last_selected) {
$selectable = this._ensure_selectable_is_available();
$('.currently-selected', $selectable).removeClass('currently-selected');
this.last_selected = null;
}
};
this.clear_selection = function() {
$selectable = this._ensure_selectable_is_available();
this.selected_list.forEach(function(nodeId) {
var node = $('#' + nodeId, $selectable);
if (node && node.length > 0) {
node.removeClass('ui-selected');
}
});
this.selected_list = $.selected = [];
this.clear_last_selected();
};
/**
* The idea of this method is to _validate_ and return the .currently-selected element.
*
* @returns {String|Boolean} node id
*/
this.get_currently_selected = function() {
if (this.last_selected) {
return this.last_selected;
}
else {
return false;
}
};
/**
* Used from the shortcut keys code.
*
* @param nodeId
*/
this.set_currently_selected = function(nodeId, scrollTo) {
self.clear_last_selected();
quickFinder.disable_if_active();
if (this.selected_list.indexOf(nodeId) === -1) {
this.add_to_selection(nodeId, scrollTo);
return;
}
if ($.isArray(nodeId)) {
this.last_selected = nodeId[nodeId.length - 1];
}
else {
this.last_selected = nodeId;
}
if (scrollTo && !$.isArray(nodeId)) {
$selectable = this._ensure_selectable_is_available();
var $element = $('#' + this.last_selected, $selectable);
$element.addClass("currently-selected");
// Do .scrollIntoView if the parent or parent -> parent DOM Element is a JSP.
{
var $jsp = $element.getParentJScrollPane();
if ($jsp) {
$jsp.scrollToElement($element);
}
else {
if (M.megaRender && M.megaRender.megaList) {
M.megaRender.megaList.scrollToItem(this.last_selected);
}
}
}
}
};
this.add_to_selection = function(nodeId, scrollTo, alreadySorted) {
if (!isString(nodeId)) {
if (nodeId && nodeId.h) {
nodeId = nodeId.h;
}
else if (d) {
console.error(".add_to_selection received a non-string as nodeId");
return;
}
}
if (this.selected_list.indexOf(nodeId) === -1) {
this.selected_list.push(nodeId);
$selectable = this._ensure_selectable_is_available();
$('#' + nodeId, $selectable).addClass('ui-selected');
this.set_currently_selected(nodeId, scrollTo);
if (!alreadySorted) {
// shift + up/down requires the selected_list to be in the same order as in M.v (e.g. render order)
var currentViewOrderMap = {};
M.v.forEach(function (v, k) {
currentViewOrderMap[SelectionManager.dynamicNodeIdRetriever(v)] = k;
});
// sort this.selected_list as in M.v
this.selected_list.sort(function (a, b) {
var aa = currentViewOrderMap[a];
var bb = currentViewOrderMap[b];
if (aa < bb) {
return -1;
}
if (aa > bb) {
return 1;
}
return 0;
});
}
}
$.selected = this.selected_list;
if (debugMode) {
console.error("commit: ", JSON.stringify(this.selected_list), self);
}
};
this.remove_from_selection = function(nodeId) {
var foundIndex = this.selected_list.indexOf(nodeId);
if (foundIndex > -1) {
$selectable = this._ensure_selectable_is_available();
this.selected_list.splice(foundIndex, 1);
$('#' + nodeId, $selectable).removeClass('ui-selected');
if (this.last_selected === nodeId) {
$('#' + nodeId, $selectable).removeClass('currently-selected');
this.last_selected = null;
}
$.selected = this.selected_list;
if (debugMode) {
console.error("commit: ", JSON.stringify(this.selected_list));
}
}
else {
if (debugMode) {
console.error("can't remove:", nodeId, JSON.stringify(this.selected_list), JSON.stringify($.selected));
}
}
};
/**
* Simple helper func, for selecting all elements in the current view.
*/
this.select_all = function() {
var self = this;
self.clear_selection();
M.v.forEach(function(v) {
self.add_to_selection(SelectionManager.dynamicNodeIdRetriever(v), false, true);
});
};
this.select_next = function(shiftKey, scrollTo) {
this._select_pointer(1, shiftKey, scrollTo);
};
this.select_prev = function(shiftKey, scrollTo) {
this._select_pointer(-1, shiftKey, scrollTo);
};
this._select_pointer = function(ptr, shiftKey, scrollTo) {
var currentViewIds = [];
M.v.forEach(function(v) {
currentViewIds.push(SelectionManager.dynamicNodeIdRetriever(v));
});
var current = this.get_currently_selected();
var nextIndex = currentViewIds.indexOf(current);
if (ptr === -1) {
// up
// allow selection to go backwards, e.g. start selecting from the end of the list
nextIndex = nextIndex <= 0 ? currentViewIds.length - Math.max(nextIndex, 0) : nextIndex;
if (nextIndex > -1 && nextIndex - 1 >= 0) {
var nextId = currentViewIds[nextIndex - 1];
// clear old selection if no shiftKey
if (!shiftKey) {
this.clear_selection();
this.set_currently_selected(nextId, scrollTo);
}
else if (nextIndex < currentViewIds.length) {
// shift key selection logic
if (
this.selected_list.length > 0 &&
this.selected_list.indexOf(nextId) > -1
) {
// get first item from the list
var firstItemId = this.selected_list[0];
// modify selection
this.clear_selection();
this.set_currently_selected(firstItemId, false);
this.shift_select_to(nextId, scrollTo, false, false);
}
else {
this.add_to_selection(nextId, scrollTo);
}
}
}
}
else if (ptr === 1) {
// down
// allow selection to go back at the start of the list if current = last selected
nextIndex = (
nextIndex + 1 >= currentViewIds.length ? -1 : nextIndex
);
if (nextIndex + 1 < currentViewIds.length) {
var nextId = currentViewIds[nextIndex + 1];
// clear old selection if no shiftKey
if (!shiftKey) {
this.clear_selection();
this.set_currently_selected(nextId, scrollTo);
}
else if (nextIndex > -1) {
// shift key selection logic
if (
this.selected_list.length > 1 &&
this.selected_list.indexOf(nextId) > -1
) {
// get last item from the list
var fromFirstItemId = this.selected_list[1];
var lastItemId = this.selected_list[this.selected_list.length - 1];
// modify selection
this.clear_selection();
this.set_currently_selected(fromFirstItemId, false);
this.shift_select_to(lastItemId, scrollTo, false, false);
this.last_selected = fromFirstItemId;
}
else {
this.add_to_selection(nextId, scrollTo);
}
}
}
}
};
this._select_ptr_grid = function(ptr, shiftKey, scrollTo) {
if (this.selected_list.length === 0) {
this.set_currently_selected(SelectionManager.dynamicNodeIdRetriever(M.v[0]), scrollTo);
return;
}
var currentViewIds = [];
M.v.forEach(function(v) {
currentViewIds.push(SelectionManager.dynamicNodeIdRetriever(v));
});
var items_per_row = Math.floor(
$('.data-block-view:visible').parent().outerWidth() / $('.data-block-view:visible:first').outerWidth(true)
);
var current = this.get_currently_selected();
var current_idx = currentViewIds.indexOf(current);
var target_element_num;
if (ptr === -1) { // up
// handle the case when the users presses ^ and the current row is the first row
target_element_num = current_idx - items_per_row;
} else if (ptr === 1) { // down
// handle the case when the users presses DOWN and the current row is the last row
target_element_num = current_idx + items_per_row;
}
else {
assert('selectionManager._select_ptr_grid received invalid pointer: ' + ptr);
}
// calc the index of the target element
if (target_element_num >= currentViewIds.length) {
if (ptr === -1) { // up
target_element_num = 0;
}
else {
// down
target_element_num = currentViewIds.length - 1;
}
}
if (target_element_num >= 0) {
if (shiftKey) {
this.shift_select_to(currentViewIds[target_element_num], scrollTo, false, false);
}
else {
this.clear_selection();
$("#" + currentViewIds[target_element_num]).addClass('ui-selected');
this.set_currently_selected(currentViewIds[target_element_num], scrollTo);
}
}
else {
// do nothing.
}
};
this.select_grid_up = function(shiftKey, scrollTo) {
this._select_ptr_grid(-1, shiftKey, scrollTo);
};
this.select_grid_down = function(shiftKey, scrollTo) {
this._select_ptr_grid(1, shiftKey, scrollTo);
};
this.shift_select_to = function(lastId, scrollTo, isMouseClick, clear) {
assert(lastId, 'missing lastId for selectionManager.shift_select_to');
var currentViewIds = [];
M.v.forEach(function(v) {
currentViewIds.push(SelectionManager.dynamicNodeIdRetriever(v));
});
var current = this.get_currently_selected();
var current_idx = currentViewIds.indexOf(current);
var last_idx = currentViewIds.indexOf(lastId);
var last_selected = this.last_selected;
if (clear) {
this.clear_selection();
}
if (current_idx !== -1 && last_idx !== -1) {
if (last_idx > current_idx) {
// direction - down
for (var i = Math.min(current_idx, currentViewIds.length - 1); i <= last_idx; i++) {
this.add_to_selection(currentViewIds[i], scrollTo);
}
}
else {
// direction - up
for (var i = Math.max(0, current_idx); i >= last_idx; i--) {
this.add_to_selection(currentViewIds[i], scrollTo);
}
}
}
if (isMouseClick && last_selected) {
this.set_currently_selected(last_selected, false);
}
};
/**
* Use this to get ALL (multiple!) selected items in the currently visible view/grid.
*/
this.get_selected = function() {
return this.selected_list;
};
this.destroy = function() {
if (this._$jqSelectable) {
this._$jqSelectable.off('selectableunselecting.sm' + this.idx + ' selectableunselected.sm' + this.idx);
this._$jqSelectable.off('selectableselecting.sm' + this.idx + ' selectableselected.sm' + this.idx);
}
$('.fm-right-files-block').off('selectablecreate.sm');
};
if (!resume) {
this.clear_selection(); // remove ANY old .currently-selected values.
}
else {
if (debugMode) {
console.error('resuming:', JSON.stringify($.selected));
}
this.selected_list = [];
$.selected.forEach(function(entry) {
self.selected_list.push(entry);
});
// ensure the current 'resume' selection list is matching the current M.v
$.selected.forEach(function(nodeId) {
if (M.currentCustomView && M.previousdirid === M.currentdirid) {
if ((M.currentdirid === 'public-links' && !M.getNodeShare(nodeId, 'EXP')) ||
(M.currentdirid === 'out-shares' && !M.getSharingUsers(nodeId).length)) {
self.remove_from_selection(nodeId);
}
}
else if (M.previousdirid !== M.currentdirid) {
self.remove_from_selection(nodeId);
}
else if (!M.c[M.currentdirid] || !M.c[M.currentdirid][nodeId]) {
self.remove_from_selection(nodeId);
}
});
this.clear_last_selected();
}
var $uiSelectable = $('.fm-right-files-block .ui-selectable:visible:not(.hidden)');
if ($uiSelectable.length === 1) {
this.bindSelectable($uiSelectable);
}
$('.fm-right-files-block')
.off('selectablecreate.sm')
.on('selectablecreate.sm', '.ui-selectable', function(e) {
selectionManager.bindSelectable(e.target);
});
if (localStorage.selectionManagerDebug) {
Object.keys(self).forEach(function(k) {
if (typeof(self[k]) === 'function') {
var old = self[k];
self[k] = function () {
console.error(k, arguments);
return old.apply(this, arguments);
};
}
});
$selectable = this._ensure_selectable_is_available();
this.$selectable = $selectable;
}
return this;
};
/**
* Helper function that would retrieve the DOM Node ID from `n` and convert it to DOM node ID
*
* @param n
*/
SelectionManager.dynamicNodeIdRetriever = function(n) {
if ((M.currentdirid === "ipc" || M.currentdirid === "opc") && n.p) {
return M.currentdirid + "_" + n.p;
}
else {
return n.h;
}
};
var selectionManager;
/**
* initTextareaScrolling
*
* @param {Object} $textarea. DOM textarea element.
* @param {Number} textareaMaxHeight Textarea max height. Default is 100
* @param {Boolean} resizeEvent If we need to bind window resize event
*/
function initTextareaScrolling($textarea, textareaMaxHeight, resizeEvent) {
var textareaWrapperClass = $textarea.parent().attr('class'),
$textareaClone,
textareaLineHeight = parseInt($textarea.css('line-height'));
textareaMaxHeight = textareaMaxHeight ? textareaMaxHeight: 100;
// Textarea Clone block to define height of autoresizeable textarea
if (!$textarea.next('div').length) {
$('').insertAfter($textarea);
}
$textareaClone = $textarea.next('div');
function textareaScrolling(keyEvents) {
var $textareaScrollBlock = $textarea.closest('.textarea-scroll'),
$textareaCloneSpan,
textareaContent = $textarea.val(),
cursorPosition = $textarea.getCursorPosition(),
jsp = $textareaScrollBlock.data('jsp'),
viewLimitTop = 0,
scrPos = 0,
viewRatio = 0;
// Set textarea height according to textarea clone height
textareaContent = ''+textareaContent.substr(0, cursorPosition) +
'' + textareaContent.substr(cursorPosition, textareaContent.length);
// try NOT to update the DOM twice if nothing had changed (and this is NOT a resize event).
if (keyEvents && $textareaClone.data('lastContent') === textareaContent) {
return;
}
else {
$textareaClone.data('lastContent', textareaContent);
textareaContent = textareaContent.replace(/\n/g, ' ');
$textareaClone.safeHTML(textareaContent + ' ');
}
var textareaCloneHeight = $textareaClone.height();
$textarea.height(textareaCloneHeight);
$textareaCloneSpan = $textareaClone.children('span');
var textareaCloneSpanHeight = $textareaCloneSpan.height();
scrPos = jsp ? $textareaScrollBlock.find('.jspPane').position().top : 0;
viewRatio = Math.round(textareaCloneSpanHeight + scrPos);
// Textarea wrapper scrolling init
if (textareaCloneHeight > textareaMaxHeight) {
$textareaScrollBlock.jScrollPane(
{enableKeyboardNavigation: false, showArrows: true, arrowSize: 5, animateScroll: false});
if (!jsp && keyEvents) {
$textarea.trigger("focus");
}
}
else if (jsp) {
jsp.destroy();
if (keyEvents) {
$textarea.trigger("focus");
}
}
// Scrolling according cursor position
if (viewRatio > textareaLineHeight || viewRatio < viewLimitTop) {
jsp = $textareaScrollBlock.data('jsp');
if (textareaCloneSpanHeight > 0 && jsp) {
jsp.scrollToY(textareaCloneSpanHeight - textareaLineHeight);
}
else if (jsp) {
jsp.scrollToY(0);
}
}
$textarea.trigger('autoresized');
}
// Init textarea scrolling
textareaScrolling();
// Reinit scrolling after keyup/keydown/paste events
$textarea.off('keyup keydown paste');
$textarea.on('keyup keydown paste', function() {
textareaScrolling(1);
});
// Bind window resize if textarea is resizeable
if (resizeEvent) {
var eventName = textareaWrapperClass.replace(/[_\s]/g, '');
$(window).rebind('resize.' + eventName, function () {
textareaScrolling();
});
}
}
/**
* addNewContact
*
* User adding new contact/s from add contact dialog.
* @param {String} $addBtnClass, contact dialog add button class, i.e. .add-user-popup-button.
* @param {Boolean} cd close dialog or not. default: true
*/
function addNewContact($addButton, cd) {
var mailNum;
var msg;
var title;
var email;
var emailText;
var $mails;
var $textarea = $addButton.parents('.fm-dialog').find('textarea');
var promise = new MegaPromise();
cd = cd === undefined ? true : cd;
// Add button is enabled
if (!$addButton.hasClass('disabled')) {
// Check user type
if (u_type === 0) {
ephemeralDialog(l[997]);
promise.resolve();
}
else {
var promises = [];
var addedEmails = [];
loadingDialog.pshow();
// Custom text message
emailText = $textarea.val();
if (emailText === '' || emailText === l[6853]) {
emailText = l[17738];
}
// List of email address planned for addition
$mails = $('.token-input-list-mega .token-input-token-mega');
mailNum = $mails.length;
// temp array to hold emails of current contacts to exclude from inviting.
// note: didn't use "getContactsEMails()" to optimize memory usage, since the returned array
// there is bigger (contains: email, name, handle, type)
var currentContactsEmails = [];
M.u.forEach(function(contact) {
// Active contacts with email set
if (contact.c === 1 && contact.m) {
currentContactsEmails.push(contact.m);
}
});
if (mailNum) {
// Loop through new email list
$mails.each(function(index, value) {
// Extract email addresses one by one
email = $(value).contents().eq(1).text();
if (currentContactsEmails.indexOf(email) === -1) {
// if invitation is sent, push as added Emails.
promises.push(M.inviteContact(M.u[u_handle].m, email, emailText).done(function(res) {
addedEmails.push(res);
}));
}
});
}
// after all process is done, and there is added email(s), show invitation sent dialog.
MegaPromise.allDone(promises).always(function() {
if (addedEmails.length > 0) {
// Singular or plural
if (addedEmails.length === 1) {
title = l[150];
msg = l[5898];
}
else {
title = l[165] + ' ' + l[5859];
msg = l[5899];
}
contactsInfoDialog(title, addedEmails[0], msg);
}
if (cd) {
closeDialog();
$('.token-input-token-mega').remove();
}
loadingDialog.phide();
promise.resolve();
});
}
}
else {
promise.reject();
}
return promise;
}
/**
* sharedUInode
*
* Handle shared/export link icons in Cloud Drive
* @param {String} nodeHandle selected node id
*/
function sharedUInode(nodeHandle) {
var oShares;
var bExportLink = false;
var bAvailShares = false;
var UiExportLink = new mega.UI.Share.ExportLink();
var share = new mega.Share();
if (!fminitialized) {
if (d) {
UiExportLink.logger.warn('Skipping sharedUInode call...');
}
return;
}
if (d) {
UiExportLink.logger.debug('Entering sharedUInode...');
}
// Is there a full share or pending share available
if ((M.d[nodeHandle] && M.d[nodeHandle].shares) || M.ps[nodeHandle]) {
// Contains full shares and/or export link
oShares = M.d[nodeHandle] && M.d[nodeHandle].shares;
// Do we have export link for selected node?
if (oShares && oShares.EXP) {
UiExportLink.addExportLinkIcon(nodeHandle);
// Item is taken down, make sure that user is informed
if (oShares.EXP.down === 1) {
UiExportLink.addTakenDownIcon(nodeHandle);
}
bExportLink = true;
}
// Add share icon in left panel for selected node only if we have full or pending share
// Don't show share icon when we have export link only
if (share.isShareExist([nodeHandle], true, true, false)) {
// Left panel
$('#treea_' + nodeHandle + ' .nw-fm-tree-folder').addClass('shared-folder');
bAvailShares = true;
}
}
// t === 1, folder
if (M.d[nodeHandle] && M.d[nodeHandle].t) {
var icon = fileIcon(M.d[nodeHandle]);
// Update right panel selected node with appropriate icon for list view
$('.grid-table.fm #' + nodeHandle + ' .transfer-filetype-icon').addClass(icon);
// Update right panel selected node with appropriate icon for block view
$('#' + nodeHandle + '.data-block-view .block-view-file-type').addClass(icon);
}
// If no shares are available, remove share icon from left panel, right panel (list and block view)
if (!bAvailShares) {
// Left panel
$('#treea_' + nodeHandle + ' .nw-fm-tree-folder').removeClass('shared-folder');
// Right panel list view
$('.grid-table.fm #' + nodeHandle + ' .transfer-filetype-icon').removeClass('folder-shared');
// Right panel block view
$('#' + nodeHandle + '.data-block-view .block-view-file-type').removeClass('folder-shared');
}
// If no export link is available, remove export link from left and right panels (list and block view)
if (!bExportLink) {
UiExportLink.removeExportLinkIcon(nodeHandle);
}
}
/**
* initAddDialogInputPlugin
*/
function initAddDialogMultiInputPlugin() {
// Plugin configuration
var contacts = M.getContactsEMails();
var $this = $('.add-contact-multiple-input');
var $scope = $this.closest('.add-user-popup');
var $addButton = $scope.find('.add-user-popup-button');
$this.tokenInput(contacts, {
theme: 'mega',
placeholder: l[19108],// Enter one or more email address
searchingText: '',
noResultsText: '',
addAvatar: true,
autocomplete: null,
searchDropdown: true,
emailCheck: true,
preventDoublet: true,
tokenValue: 'id',
propertyToSearch: 'id',
resultsLimit: 5,
// Prevent showing of drop down list with contacts email addresses
// Max allowed email address is 254 chars
minChars: 255,
accountHolder: (M.u[u_handle] || {}).m || '',
scrollLocation: 'add',
// Exclude from dropdownlist only emails/names which exists in multi-input (tokens)
excludeCurrent: false,
onEmailCheck: function() {
errorMsg(l[7415]);
},
onDoublet: function (u, iType) {
if (iType === 'opc') {
errorMsg(l[17545]);
}
else if (iType === 'ipc') {
errorMsg(l[17546]);
}
else {
errorMsg(l[7413]);
}
},
onHolder: function() {
errorMsg(l[7414]);
},
onReady: function() {
var $input = $this.parent().find('li input').eq(0);
$input.rebind('keyup', function() {
var value = $.trim($input.val());
var emailList = value.split(/[ ;,]+/);
var itemNum = $scope.find('.share-added-contact').length;
if (isValidEmail(value)) {
itemNum = itemNum + 1;
}
if (itemNum > 1) {
$addButton.text(l[19113].replace('%1', itemNum)).removeClass('disabled');
}
else if (itemNum === 1) {
$addButton.text(l[19112]).removeClass('disabled');
}
else {
$addButton.text(l[19112]).addClass('disabled');
}
});
},
onAdd: function() {
var $inputTokens = $scope.find('.share-added-contact');
var itemNum = $inputTokens.length;
if (itemNum === 0) {
$addButton.text(l[19112]).addClass('disabled');
}
else if (itemNum === 1) {
$addButton.text(l[19112]).removeClass('disabled');
}
else {
var $multiInput = $scope.find('.multiple-input');
$addButton.text(l[19113].replace('%1', itemNum)).removeClass('disabled');
}
},
onDelete: function() {
var $inputTokens = $scope.find('.token-input-list-mega .token-input-token-mega');
var itemNum;
setTimeout(function() {
$inputTokens.find('input').trigger("blur");
}, 0);
// Get number of emails
itemNum = $inputTokens.length;
if (itemNum === 0) {
$addButton.text(l[148]).addClass('disabled');
}
else if (itemNum === 1) {
$addButton.text(l[19112]).removeClass('disabled');
}
else {
$addButton.text(l[19113].replace('%1', itemNum)).removeClass('disabled');
}
}
});
/**
* errorMsg
*
* Show error popup next to multi input box in case that email is wrong.
* @param {String} msg, error message.
*/
function errorMsg(msg) {
var $addUserPopup = $('.add-user-popup'),
$warning = $addUserPopup.find('.multiple-input-warning span');
$warning.text(msg);
$addUserPopup.addClass('error');
setTimeout(function() {
$addUserPopup.removeClass('error');
}, 3000);
}
}
/**
* newContactDialog
*
* Handle add new contact dialog UI
* @param {String} ipcid ipc user id
* @param {Boolean} close dialog parameter
*/
function newContactDialog(ipcId, close) {
var $d = $('.fm-dialog.new-contact');
var $msg = $d.find('.new-user-message');
var ipc = M.ipc[ipcId];
var username = ipc.m;
// Hide
if (close) {
closeDialog();
return true;
}
M.safeShowDialog('new-contact', $d);
// Set default
$msg.addClass('hidden').find('span').text('');
$d.find('.new-contact-info span').text(username);
$d.find('.new-contact-avatar')
.safeHTML(useravatar.contact(username, 'semi-mid-avatar'));
if (ipc.msg) {
$msg.removeClass('hidden').find('span').text(ipc.msg);
}
$d.find('.contact-request-button').rebind('click', function() {
var $self = $(this);
var $reqRow = $('tr#ipc_' + ipcId);
if ($self.is('.accept')) {
if (M.acceptPendingContactRequest(ipcId) === 0) {
$reqRow.remove();
}
}
else if ($self.is('.delete')) {
if (M.denyPendingContactRequest(ipcId) === 0) {
$reqRow.remove();
}
}
else if ($self.is('.ignore')) {
if (M.ignorePendingContactRequest(ipcId) === 0) {
$reqRow.remove();
}
}
newContactDialog(ipcId, 1);
});
$d.find('.fm-dialog-close').rebind('click', function() {
newContactDialog(ipcId, 1);
});
}
/**
* contactsInfoDialog
*
* Handle add new contact dialog UI
* @param {String} title Dialog title
* @param {String} username User name/email
* @param {Boolean} close Dialog parameter
*/
function contactsInfoDialog(title, username, msg, close) {
var $d = $('.fm-dialog.contact-info');
var $msg = $d.find('.new-contact-info');
// Hide
if (close) {
closeDialog();
return true;
}
if (title) {
$d.find('.nw-fm-dialog-title').text(title);
}
else {
$d.find('.nw-fm-dialog-title').text('');
}
if (username && msg) {
$msg.safeHTML(msg.replace(/%1|\[X\]/g, '' + username + ''));
}
else if (msg) {
$msg.text(msg);
}
M.safeShowDialog('contact-info', $d);
$d.find('.fm-dialog-close, .default-white-button.ok').rebind('click', function() {
contactsInfoDialog(undefined, undefined, undefined, 1);
});
}
/**
* publicLinkInit
*
* Set piublick link and init CopyToClipboard events
*
*/
function setContactLink() {
"use strict";
var $publicLink = $('.public-contact-link:visible');
// multiple link data may exists!
var linkData = $publicLink.attr('data-lnk');
var account = M.account || false;
var contactPrefix = '';
// Exit if link exists
if (!$publicLink.length || linkData) {
return false;
}
// Check data exists in M.account
if (account.contactLink && account.contactLink.length) {
contactPrefix = M.account.contactLink.match('^C!') ? '' : 'C!';
$publicLink.attr('data-lnk', 'https://mega.nz/' + contactPrefix + M.account.contactLink);
}
else {
api_req({ a: 'clc' }, {
callback: function (res, ctx) {
if (typeof res === 'string') {
contactPrefix = res.match('^C!') ? '' : 'C!';
res = 'https://mega.nz/' + contactPrefix + res;
$publicLink.attr('data-lnk', res);
}
}
});
}
$publicLink.rebind('mouseover.publiclnk', function() {
var $this = $(this);
var $tooltip = $('.dropdown.tooltip.small');
var leftPos = $this.offset().left + $this.width() / 2 - $tooltip.outerWidth() / 2;
var topPos = $this.offset().top - $tooltip.outerHeight() - 10;
$tooltip.addClass('visible').css({
'left': leftPos,
'top': topPos
});
});
$publicLink.rebind('mouseout.publiclnk', function() {
$('.dropdown.tooltip.small').removeClass('visible');
});
$publicLink.rebind('click', function() {
var linkData = $(this).attr('data-lnk') || '';
if (linkData.length) {
copyToClipboard(linkData, l[371] + '' + linkData + '', 'short');
}
});
}
/**Show Contact VS User difference dialog */
function contactVsUserDialog() {
"use strict";
var $dialog = $('.add-reassign-dialog.user-management-dialog');
$dialog.find('.dif-dlg-contact-add-btn').rebind('click.dlg', function addContactClickHandler() {
closeDialog();
return contactAddDialog(null, true);
});
$dialog.find('.dif-dlg-close').rebind('click.dlg', function closeClickHandler() {
return closeDialog();
});
$dialog.find('.dif-dlg-user-add-btn').rebind('click.dlg', function addUserClickHandler() {
closeDialog();
if (!u_attr || !u_attr.b || !u_attr.b.m || u_attr.b.s === -1) {
return false;
}
window.triggerShowAddSubUserDialog = true;
M.openFolder('user-management', true);
});
M.safeShowDialog('contact-vs-user', $dialog);
}
/**
* addContactUI
*
* Handle add contact dialog UI
* @param {Boolean} close dialog parameter
* @param {Boolean} dontWarnBusiness if true, then porceed to show the dialog
*/
function contactAddDialog(close, dontWarnBusiness) {
var $d = $('.add-user-popup');
// not for ephemeral
if (!u_type) {
return;
}
// Hide
if (close) {
closeDialog();
return true;
}
// Check if this is a business master, then Warn him about the difference between Contact and User
if (!dontWarnBusiness) {
if (u_attr && u_attr.b && u_attr.b.m && u_attr.b.s !== -1) {
return contactVsUserDialog();
}
}
// Init default states
$.sharedTokens = [];
$d.removeClass('private achievements');
M.safeShowDialog('add-user-popup', $d);
setContactLink();
var $textarea = $d.find('.add-user-textarea textarea');
mega.achievem.enabled().done(function () {
$d.addClass('achievements');
});
$textarea.val('');
$d.find('.multiple-input .token-input-token-mega').remove();
initTokenInputsScroll($('.multiple-input', $d));
Soon(function() {
$('.token-input-input-token-mega input', $d).trigger("focus");
});
$d.find('.add-user-popup-button').text(l[19112]).addClass('disabled');
initTextareaScrolling($textarea, 72);
$d.find('.token-input-input-token-mega input').trigger("focus");
focusOnInput();
$d.find('.hidden-textarea-info span').rebind('click', function() {
$d.addClass('private');
});
function focusOnInput() {
var $tokenInput = $('#token-input-');
$tokenInput.trigger("focus");
}
$('.add-user-notification textarea').rebind('focus.add-user-n', function() {
$('.add-user-notification').addClass('focused');
});
$('.add-user-notification textarea').rebind('blur.add-user-n', function() {
$('.add-user-notification').removeClass('focused');
});
if (!$('.add-contact-multiple-input').tokenInput("getSettings")) {
initAddDialogMultiInputPlugin();
}
$('.add-user-popup-button').rebind('click', function() {
addNewContact($(this));
});
$('.add-user-popup .fm-dialog-close').rebind('click', function() {
showWarningTokenInputLose().done(closeDialog);
});
}
function ephemeralDialog(msg) {
msgDialog('confirmation', l[998], msg + ' ' + l[999], l[1000], function(e) {
if (e) {
loadSubPage('register');
}
});
}
function fmtopUI() {
"use strict";
var $contactsTabBlock = $('.contacts-tabs-bl');
var $sharesTabBlock = $('.shares-tabs-bl');
$contactsTabBlock.add($sharesTabBlock).addClass('hidden');
$contactsTabBlock.find('.contacts-tab-lnk.active').removeClass('active');
$sharesTabBlock.find('.shares-tab-lnk.active').removeClass('active');
$('.fm-clearbin-button,.fm-add-user,.fm-new-folder,.fm-file-upload,.fm-folder-upload')
.add('.fm-new-shared-folder,.fm-new-link').addClass('hidden');
$('.fm-new-folder').removeClass('filled-input');
$('.fm-right-files-block').removeClass('visible-notification rubbish-bin');
$('.fm-right-header').removeClass('requests-panel');
$('.fm-breadcrumbs-block').removeClass('hidden');
$('.button.link-button.accept-all').addClass('hidden');
var showUploadBlock = function _showUploadBlock() {
$('.fm-new-folder').removeClass('hidden');
$('.fm-file-upload').removeClass('hidden');
if ((is_chrome_firefox & 2) || 'webkitdirectory' in document.createElement('input')) {
$('.fm-folder-upload').removeClass('hidden');
}
else if (ua.details.engine === 'Gecko') {
$('.fm-folder-upload').removeClass('hidden');
}
else {
$('.fm-file-upload').addClass('last-button');
}
};
if (M.currentrootid === M.RubbishID) {
if (M.v.length) {
$('.fm-clearbin-button').removeClass('hidden');
}
$('.fm-right-files-block').addClass('rubbish-bin visible-notification');
}
else {
if (M.currentrootid === M.InboxID) {
if (d) {
console.log('Inbox');
}
}
else if (M.currentdirid === 'contacts'
|| M.currentdirid === 'ipc'
|| M.currentdirid === 'opc'
|| (String(M.currentdirid).length === 11
&& M.currentdirid.substr(0, 6) !== 'search')) {
M.contactsUI();
// Update IPC indicator
delay('updateIpcRequests', updateIpcRequests);
$('.fm-add-user').removeClass('hidden');
$contactsTabBlock.removeClass('hidden');
// Show Accept All button
if (M.currentdirid === 'ipc' && Object.keys(M.ipc).length > 0) {
$('.button.link-button.accept-all').removeClass('hidden');
}
// Show set active tab, Hide grid/blocks/view buttons
if (M.currentdirid === 'ipc') {
$('.fm-right-header').addClass('requests-panel');
$contactsTabBlock.find('.ipc').addClass('active');
}
else if (M.currentdirid === 'opc') {
$('.fm-right-header').addClass('requests-panel');
$contactsTabBlock.find('.opc').addClass('active');
}
else if (M.currentdirid === 'contacts') {
$contactsTabBlock.find('.contacts').addClass('active');
}
else {
$('.fm-breadcrumbs-block').addClass('hidden');
$contactsTabBlock.find('.contacts').addClass('active');
}
}
else if (M.currentrootid === 'shares') {
M.sharesUI();
$sharesTabBlock.removeClass('hidden');
$sharesTabBlock.find('.in-shares').addClass('active');
$('.fm-right-files-block').addClass('visible-notification');
if (M.currentdirid !== 'shares' && M.getNodeRights(M.currentdirid) > 0) {
showUploadBlock();
}
}
else if (M.currentrootid === 'out-shares' || M.currentrootid === 'public-links') {
M.sharesUI();
$sharesTabBlock.removeClass('hidden');
$sharesTabBlock.find('.' + M.currentrootid).addClass('active');
$('.fm-right-files-block').addClass('visible-notification');
if (M.currentdirid !== M.currentrootid) {
showUploadBlock();
}
else if (M.currentrootid === 'out-shares') {
$('.fm-new-shared-folder').removeClass('hidden');
}
else {
$('.fm-new-link').removeClass('hidden');
}
}
else if (String(M.currentdirid).length === 8
&& M.getNodeRights(M.currentdirid) > 0) {
$('.fm-right-files-block').addClass('visible-notification');
showUploadBlock();
}
}
$('.fm-clearbin-button').rebind('click', function() {
doClearbin(true);
});
// handle the Inbox section use cases
if (M.hasInboxItems()) {
$('.nw-fm-left-icon.inbox').removeClass('hidden');
}
else {
$('.nw-fm-left-icon.inbox').addClass('hidden');
if (M.InboxID && M.currentrootid === M.InboxID) {
M.openFolder(M.RootID);
}
}
// handle the RubbishBin icon changes
var $icon = $('.nw-fm-left-icon.rubbish-bin');
var rubNodes = Object.keys(M.c[M.RubbishID] || {});
if (rubNodes.length) {
$('.fm-tree-header.recycle-item').addClass('recycle-notification contains-subfolders');
if (!$icon.hasClass('filled')) {
$icon.addClass('filled');
}
else if (!$icon.hasClass('glow')) {
$icon.addClass('glow');
}
else {
$icon.removeClass('glow');
}
}
else {
$('.fm-tree-header.recycle-item')
.removeClass('recycle-notification expanded contains-subfolders')
.prev('.fm-connector-first').removeClass('active');
$icon.removeClass('filled glow');
}
}
function doClearbin(all) {
"use strict";
msgDialog('clear-bin', l[14], l[15], l[1007], function(e) {
if (e) {
M.clearRubbish(all);
}
});
}
function handleResetSuccessDialogs(dialog, txt, dlgString) {
$('.fm-dialog' + dialog + ' .reg-success-txt').text(txt);
$('.fm-dialog' + dialog + ' .default-white-button').rebind('click', function() {
$('.fm-dialog-overlay').addClass('hidden');
$('body').removeClass('overlayed');
$('.fm-dialog' + dialog).addClass('hidden');
delete $.dialog;
});
$('.fm-dialog-overlay').removeClass('hidden');
$('body').addClass('overlayed');
$('.fm-dialog' + dialog).removeClass('hidden');
$.dialog = dlgString;
}
function avatarDialog(close) {
'use strict';
var $dialog = $('.fm-dialog.avatar-dialog');
if (close) {
closeDialog();
return true;
}
M.safeShowDialog('avatar', $dialog);
$('.avatar-body').safeHTML(
'
' +
'
' +
'
' +
'
' +
'' +
'' +
'' +
'' +
'
' +
'
' +
'' +
'' +
'' +
'
' +
'
' +
'
' +
'' +
'' +
'
' +
'@@' +
'
' +
'
' +
'@@' +
'
' +
'
' +
'@@' +
'
' +
'' +
'
' +
'
' +
'
', l[1016], l[1017], l[82], l[6974]);
$('#fm-change-avatar').hide();
$('#fm-cancel-avatar').hide();
$('#fm-remove-avatar').hide();
mega.attr.get(u_handle, 'a', true, false)
.fail()
.done(function(res) {
if (res !== null && res !== undefined && res !== "none"){
$('#fm-remove-avatar').show();
}
});
var imageCrop = new ImageUploadAndCrop($("#avatarcrop").find('.image-upload-and-crop-container'),
{
cropButton: $('#fm-change-avatar'),
dragDropUploadPrompt:l[1390],
outputFormat: 'image/jpeg',
onCrop: function(croppedDataURI)
{
if (croppedDataURI.length > 64 * 1024) {
return msgDialog('warninga', l[8645], l[8646]);
}
var data = dataURLToAB(croppedDataURI);
mega.attr.set('a', ab_to_base64(data), true, false);
useravatar.setUserAvatar(u_handle, data, this.outputFormat);
$('.fm-account-avatar').safeHTML(useravatar.contact(u_handle, '', 'div', false));
$('.fm-avatar').safeHTML(useravatar.contact(u_handle));
avatarDialog(1);
},
onImageUpload: function()
{
$('#fm-change-avatar').show();
$('#fm-cancel-avatar').show();
$('#fm-remove-avatar').hide();
},
onImageUploadError: function()
{
}
});
$('#fm-cancel-avatar,.fm-dialog.avatar-dialog .fm-dialog-close').rebind('click', function(e)
{
avatarDialog(1);
});
$("#fm-remove-avatar").rebind('click', function()
{
msgDialog('confirmation', "confirm-remove-avatar", l[18699], l[6973], function(response) {
if (response){
mega.attr.set('a', "none", true, false);
avatarDialog(1);
}
});
});
}
/**
* Really simple shortcut logic for select all, copy, paste, delete
* Note: there is another key binding on initUIKeyEvents() for filemanager.
*
* @constructor
*/
function FMShortcuts() {
var current_operation = null;
$(window).rebind('keydown.fmshortcuts', function(e) {
var isContactRootOrShareRoot = false;
if (
!is_fm() ||
!selectionManager ||
M.currentrootid === 'chat' || // prevent shortcut for chat
M.currentrootid === undefined // prevent shortcut for file transfer, dashboard, settings
) {
return true;
}
else if (M.currentdirid === 'contacts' || M.currentdirid === 'shares') {
isContactRootOrShareRoot = true;
}
e = e || window.event;
// DO NOT start the search in case that the user is typing something in a form field... (eg.g. contacts -> add
// contact field)
if ($(e.target).is("input, textarea, select") || $.dialog) {
return;
}
var charCode = e.which || e.keyCode; // ff
var charTyped = String.fromCharCode(charCode).toLowerCase();
if (charTyped === "a" && (e.ctrlKey || e.metaKey)) {
if (typeof selectionManager != 'undefined' && selectionManager) {
if (M.currentdirid === 'ipc' || M.currentdirid === 'opc') {
return;
}
selectionManager.select_all();
}
return false; // stop prop.
}
else if (
(charTyped === "c" || charTyped === "x") &&
(e.ctrlKey || e.metaKey) &&
!isContactRootOrShareRoot
) {
var items = selectionManager.get_selected();
if (items.length === 0 || M.currentdirid === 'ipc' || M.currentdirid === 'opc') {
return; // dont do anything.
}
current_operation = {
'op': charTyped == "c" ? 'copy' : 'cut',
'src': items
};
return false; // stop prop.
}
else if (
charTyped === "v" &&
(e.ctrlKey || e.metaKey) &&
!isContactRootOrShareRoot
) {
if (!current_operation || (M.getNodeRights(M.currentdirid || '') | 0) < 1
|| M.currentdirid === 'ipc' || M.currentdirid === 'opc') {
return false; // stop prop.
}
var handles = [];
$.each(current_operation.src, function(k, v) {
handles.push(v);
});
if (current_operation.op == "copy") {
M.copyNodes(handles, M.currentdirid);
} else if (current_operation.op == "cut") {
M.moveNodes(handles, M.currentdirid);
current_operation = null;
}
return false; // stop prop.
}
else if (
charCode === 8 &&
!isContactRootOrShareRoot
) {
var items = selectionManager.get_selected();
if (items.length === 0 || (M.getNodeRights(M.currentdirid || '') | 0) < 2) {
return; // dont do anything.
}
fmremove(items);
// force remove, no confirmation
if (e.ctrlKey || e.metaKey) {
$('#msgDialog:visible .fm-dialog-button.confirm').trigger('click');
}
return false;
}
});
}
function fm_hideoverlay() {
"use strict";
if (!$.propertiesDialog) {
$('.fm-dialog-overlay').addClass('hidden');
$('body').removeClass('overlayed');
}
$(document).trigger('MegaCloseDialog');
}
function fm_showoverlay() {
"use strict";
$('.fm-dialog-overlay').removeClass('hidden');
$('body').addClass('overlayed');
}
/**
* Looking for a already existing name of URL (M.v)
* @param {Integer} nodeType file:0 folder:1
* @param {String} value New file/folder name
* @param {String} target {optional}Target handle to check the duplication inside. if not provided M.v will be used
*/
function duplicated(nodeType, value, target) {
"use strict";
if (!target) {
var items = M.v.filter(function (item) {
return item.name === value && item.t === nodeType;
});
return items.length !== 0;
}
else {
if (M.c[target]) {
// Check if a folder/file with the same name already exists.
for (var handle in M.c[target]) {
if (M.d[handle] && M.d[handle].t === nodeType && M.d[handle].name === value) {
return true;
}
}
}
return false;
}
}
function renameDialog() {
"use strict";
if ($.selected.length > 0) {
var n = M.d[$.selected[0]] || false;
var nodeType = n.t;// file: 0, folder: 1
var ext = fileext(n.name);
var $dialog = $('.fm-dialog.rename-dialog');
var $input = $('input', $dialog);
var errMsg = '';
M.safeShowDialog('rename', function() {
$dialog.removeClass('hidden').addClass('active');
$input.trigger("focus");
return $dialog;
});
$('.fm-dialog-close, .rename-dialog-button.cancel', $dialog).rebind('click', closeDialog);
$('.rename-dialog-button.rename').rebind('click', function() {
if ($dialog.hasClass('active')) {
var value = $input.val();
errMsg = '';
if (n.name && value !== n.name) {
if (!value) {
errMsg = l[5744];
}
else if (M.isSafeName(value)) {
var targetFolder = n.p;
if (!duplicated(nodeType, value, targetFolder)) {
M.rename(n.h, value);
}
else {
errMsg = nodeType ? l[17579] : l[17578];
}
}
else {
errMsg = l[7436];
}
if (errMsg) {
$dialog.find('.duplicated-input-warning span').text(errMsg);
$dialog.addClass('duplicate');
$input.addClass('error');
setTimeout(function() {
$dialog.removeClass('duplicate');
$input.removeClass('error');
$input.trigger("focus");
}, 2000);
return;
}
}
closeDialog();
}
});
$('.fm-dialog-title', $dialog).text(n.t ? l[425] : l[426]);
$input.val(n.name);
$('.transfer-filetype-icon', $dialog)
.attr('class', 'transfer-filetype-icon ' + fileIcon(n));
if (!n.t && ext.length > 0) {
$input[0].selectionStart = 0;
$input[0].selectionEnd = $input.val().length - ext.length - 1;
}
$input.rebind('focus', function() {
var selEnd;
$dialog.addClass('focused');
var d = $(this).val().lastIndexOf('.');
if (d > -1) {
selEnd = d;
}
else {
selEnd = $(this).val().length;
}
$(this)[0].selectionStart = 0;
$(this)[0].selectionEnd = selEnd;
});
$input.rebind('blur', function() {
$dialog.removeClass('focused');
});
$input.rebind('keydown', function (event) {
// distingushing only keydown evet, then checking if it's Enter in order to preform the action'
if (event.keyCode === 13) { // Enter
$('.rename-dialog-button.rename').click();
return;
}
else if (event.keyCode === 27){ // ESC
closeDialog();
}
else {
$dialog.removeClass('duplicate').addClass('active');
$input.removeClass('error');
}
});
}
}
function msgDialog(type, title, msg, submsg, callback, checkbox) {
var doneButton = l[81];
var extraButton = String(type).split(':');
if (extraButton.length === 1) {
extraButton = null;
}
else {
type = extraButton.shift();
extraButton = extraButton.join(':');
if (extraButton[0] === '!') {
doneButton = l[82];
extraButton = extraButton.substr(1);
if (extraButton[0] === '^') {
extraButton = extraButton.substr(1);
var pos = extraButton.indexOf('!');
doneButton = extraButton.substr(0, pos++);
extraButton = extraButton.substr(pos);
}
}
}
$.msgDialog = type;
$.warningCallback = callback;
$('#msgDialog').removeClass('clear-bin-dialog confirmation-dialog warning-dialog-b warning-dialog-a ' +
'notification-dialog remove-dialog delete-contact loginrequired-dialog multiple wide');
$('#msgDialog .icon').removeClass('fm-bin-clear-icon .fm-notification-icon');
$('#msgDialog .confirmation-checkbox').addClass('hidden');
if (type === 'clear-bin') {
$('#msgDialog').addClass('clear-bin-dialog');
$('#msgDialog .icon').addClass('fm-bin-clear-icon');
$('#msgDialog .fm-notifications-bottom')
.safeHTML(
'
\n' +
'' + escapeHTML(l[171]) + '\n' +
'' + escapeHTML(l[1076]) + ' ';
var $selectedPlan = $('.reg-st3-membership-bl.selected');
var plan = 1;
if ($selectedPlan.is(".pro4")) { plan = 4; }
else if ($selectedPlan.is(".pro1")) { plan = 1; }
else if ($selectedPlan.is(".pro2")) { plan = 2; }
else if ($selectedPlan.is(".pro3")) { plan = 3; }
$('.loginrequired-dialog .fm-notification-icon')
.removeClass('plan1')
.removeClass('plan2')
.removeClass('plan3')
.removeClass('plan4')
.addClass('plan' + plan);
}
else if (type === 'import_login' || type === 'import_register') {
var buttonLabel = type === 'import_login' ? l[171] : l[170];
$('#msgDialog').addClass('warning-dialog-a wide');
$('#msgDialog .fm-notifications-bottom')
.safeHTML('
@@
' +
'
' +
'@@' +
'
' +
'
' +
'@@' +
'
' +
'', l[20754], buttonLabel, l[79]);
$('#msgDialog .default-green-button').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback(true);
}
});
/*!4*/
$('#msgDialog .default-white-button').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback(undefined);
}
});
$('#msgDialog .bottom-bar-link').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback(false);
}
});
}
$('#msgDialog .fm-dialog-title span').text(title);
$('#msgDialog .fm-notification-info h1').safeHTML(msg);
clickURLs();
if (submsg) {
$('#msgDialog .fm-notification-info p').text(submsg);
$('#msgDialog .fm-notification-info p').removeClass('hidden');
}
else {
$('#msgDialog .fm-notification-info p').addClass('hidden');
}
$('#msgDialog .fm-dialog-close').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback(false);
}
});
$('#msgDialog').removeClass('hidden');
fm_showoverlay();
if ($.dialog) {
$('.fm-dialog:not(#msgDialog)').addClass('arrange-to-back');
}
}
function closeMsg() {
$('#msgDialog').addClass('hidden');
if ($.dialog) {
$('.fm-dialog').removeClass('arrange-to-back');
}
else {
fm_hideoverlay();
}
delete $.msgDialog;
}
function dialogPositioning(s) {
$(s).css('margin-top', '-' + $(s).height() / 2 + 'px');
}
/**
* opens a contact link dialog, after getting all needed info from API
*
* @param {String} contactLink, user contact link, the link we want to get.
* @returns {null} no return value
*/
function openContactInfoLink(contactLink) {
var $dialog = $('.fm-dialog.qr-contact');
var QRContactDialogPrepare = function QRContactDialogPrepare(em, fullname, ctHandle) {
$('.qr-contact-name', $dialog).text(fullname);
$('.qr-contact-email', $dialog).text(em);
var curAvatar = useravatar.contact(em);
$('.avatar-container-qr-contact', $dialog).html(curAvatar);
var contactStatus = 1;
if (u_handle) {
if (ctHandle === u_handle) {
$('#qr-ctn-add', $dialog).addClass('disabled');
$('#qr-ctn-add', $dialog).off('click');
$('.qr-ct-exist', $dialog).text(l[18514]).removeClass('hidden');
}
else if (M.u[ctHandle] && M.u[ctHandle]._data.c) {
contactStatus = 2;
$('#qr-ctn-add', $dialog).addClass('disabled');
$('.qr-ct-exist', $dialog).text(l[17886]).removeClass('hidden');
$('#qr-ctn-add', $dialog).off('click');
}
else {
$('.big-btn-txt', $dialog).text(l[101]);
$('#qr-ctn-add', $dialog).removeClass('disabled');
$('.qr-ct-exist', $dialog).addClass('hidden');
$('#qr-ctn-add', $dialog).rebind('click', function () {
if (contactStatus === 1) {
M.inviteContact(u_attr.email, em, null, contactLink);
}
$('#qr-ctn-add', $dialog).off('click');
closeDialog();
return false;
});
}
}
else {
$('.big-btn-txt', $dialog).text(l[101]);
$('#qr-ctn-add', $dialog).removeClass('disabled');
$('.qr-ct-exist', $dialog).addClass('hidden');
$('#qr-ctn-add', $dialog).rebind('click', function () {
closeDialog();
var page = 'fm/contacts';
mBroadcaster.once('fm:initialized', function () {
openContactInfoLink(contactLink);
});
return loadSubPage(page);
});
}
$dialog.removeClass('hidden');
};
$dialog.find('.fm-dialog-close')
.rebind('click', function () {
closeDialog();
return false;
});
var req = { a: 'clg', cl: contactLink };
api_req(req, {
callback: function (res, ctx) {
if (typeof res === 'object') {
M.safeShowDialog('qr-contact', function () {
QRContactDialogPrepare(res.e, res.fn + ' ' + res.ln, res.h);
return $dialog;
});
}
else {
msgDialog('warningb', l[8531], l[17865]);
}
}
});
}
/**
* IFunction to open QR customization dialog.
* Access QR Code Dialog, can be accessed from dashboard -> QR widget, and from settings -> security
*
* @returns {null} no returned value
*/
function openAccessQRDialog() {
var $dialog = $('.fm-dialog.qr-dialog');
var QRdialogPrepare = function _QRdialogPrepare() {
$dialog.removeClass('hidden');
$dialog.removeClass('disabled');
if (M.account.contactLink && M.account.contactLink.length) {
var cutPlace = location.href.indexOf('/fm/');
var myHost = location.href.substr(0, cutPlace);
myHost += '/' + M.account.contactLink;
var QRoptions = {
width: 222,
height: 222,
correctLevel: QRErrorCorrectLevel.H, // high
foreground: '#D90007',
text: myHost
};
$('.qr-icon-big', $dialog).text('').qrcode(QRoptions);
$('.qr-http-link', $dialog).text(myHost);
var curAvatar = useravatar.contact(u_handle);
$('.avatar-container-qr', $dialog).html(curAvatar);
var handleAutoAccept = function _handleAutoAccept(autoAcc) {
if (autoAcc === '0') {
$dialog.find('.qr-dialog-label .dialog-feature-toggle').removeClass('toggle-on')
.find('.dialog-feature-switch').css('marginLeft', '2px');
}
else { // if it's 1 or not set
$dialog.find('.qr-dialog-label .dialog-feature-toggle').addClass('toggle-on')
.find('.dialog-feature-switch').css('marginLeft', '22px');
}
};
mega.attr.get(u_handle, 'clv', -2, 0).always(handleAutoAccept);
}
};
$dialog.find('.fm-dialog-close')
.rebind('click', function () {
closeDialog();
return false;
});
$dialog.find('#qr-dlg-close')
.rebind('click', function () {
closeDialog();
return false;
});
$dialog.find('.qr-dialog-label .dialog-feature-toggle').rebind('click', function () {
var me = $(this);
if (me.hasClass('toggle-on')) {
me.find('.dialog-feature-switch').animate({ marginLeft: '2px' }, 150, 'swing', function () {
me.removeClass('toggle-on');
mega.attr.set('clv', 0, -2, 0);
});
}
else {
me.find('.dialog-feature-switch').animate({ marginLeft: '22px' }, 150, 'swing', function () {
me.addClass('toggle-on');
mega.attr.set('clv', 1, -2, 0);
});
}
});
$dialog.find('.reset-qr-label')
.rebind('click', function () {
var msgTitle = l[18227]; // 'QR Code Regenerate';
var msgMsg = l[18228] + ' '; // 'You are about to generate a new QR code. ' +
// 'Your existing QR code and invitation link will no longer work. ';
var msgQuestion = l[18229]; // 'Do you want to proceed?';
msgDialog('confirmation', msgTitle, msgMsg,
msgQuestion,
function (regenQR) {
if (regenQR) {
$dialog.addClass('disabled');
var delQR = {
a: 'cld',
cl: M.account.contactLink.substring(2, M.account.contactLink.length)
};
var reGenQR = { a: 'clc' };
api_req(delQR, {
callback: function (res, ctx) {
if (res === 0) { // success
api_req(reGenQR, {
callback: function (res2, ctx2) {
if (typeof res2 !== 'string') {
res2 = '';
}
else {
res2 = 'C!' + res2;
}
M.account.contactLink = res2;
QRdialogPrepare();
}
});
}
else {
$dialog.removeClass('disabled');
}
}
});
}
});
});
if (is_extension || M.execCommandUsable()) {
$('#qr-dlg-cpy-lnk').removeClass('hidden');
$('#qr-dlg-cpy-lnk').rebind('click', function () {
var links = $.trim($('.qr-http-link', $dialog).text());
var toastTxt = l[7654];
copyToClipboard(links, toastTxt);
});
}
else {
$('#qr-dlg-cpy-lnk').addClass('hidden');
}
if (ua.details.browser === "Chrome") {
$dialog.find('#qr-dlg-sv-img').removeClass('hidden');
$dialog.find('#qr-dlg-sv-img').rebind('click', function () {
var canvasQR = $('.qr-icon-big canvas', $dialog)[0];
var genImageURL = canvasQR.toDataURL();
var link = document.createElement("a");
link.setAttribute("href", genImageURL);
link.setAttribute("download", M.account.contactLink);
//link.addClass('hidden');
link.click();
});
}
else {
$dialog.find('#qr-dlg-sv-img').addClass('hidden');
}
M.safeShowDialog('qr-dialog', function () {
QRdialogPrepare();
return $dialog;
});
}
/**
* shareDialogContentCheck
*
* Taking care about share dialog button 'Done'/share enabled/disabled and scroll
*
*/
function shareDialogContentCheck() {
var dc = '.share-dialog',
itemsNum = 0,
newItemsNum = 0,
$btn = $('.default-green-button.dialog-share-button');
var $groupPermissionDropDown = $('.share-dialog .permissions-icon');
newItemsNum = $(dc + ' .token-input-token-mega').length;
itemsNum = $(dc + ' .share-dialog-contacts .share-dialog-contact-bl').length;
if (itemsNum) {
$(dc + ' .share-dialog-img').addClass('hidden');
$(dc + ' .share-dialog-contacts').removeClass('hidden');
handleDialogScroll(itemsNum, dc);
}
else {
$(dc + ' .share-dialog-img').removeClass('hidden');
$(dc + ' .share-dialog-contacts').addClass('hidden');
}
// If new items are availble in multiInput box
// or permission is changed on some of existing items
if (newItemsNum || $.changedPermissions.length || $.removedContactsFromShare.length) {
$btn.removeClass('disabled');
}
else {
$btn.addClass('disabled');
}
if (newItemsNum) {
$groupPermissionDropDown.removeClass('disabled');
}
else {
$groupPermissionDropDown.addClass('disabled');
}
}
function addShareDialogContactToContent(userEmail, type, id, av, userName, permClass, permText) {
var html = '',
htmlEnd = '',
item = '',
exportClass = '';
var contactEmailHtml = '';
if (userEmail !== userName) {
contactEmailHtml = '
'
+ htmlentities(userEmail)
+ '
';
}
item = av
+ '
'
+ '
'
+ '
' + htmlentities(userName) + '
'
+ contactEmailHtml
+ '
'
+ '
';
html = '
'
+ item
+ ''
+ '
'
+ '' + permText
+ '
';
htmlEnd = ''
+ '
';
return html + htmlEnd;
}
function fillShareDialogWithContent() {
$.sharedTokens = [];// GLOBAL VARIABLE, Hold items currently visible in share folder content (above multi-input)
$.changedPermissions = [];// GLOBAL VAR, changed permissions shared dialog
$.removedContactsFromShare = [];// GLOBAL VAR, removed contacts from a share
var pendingShares = {};
var nodeHandle = String($.selected[0]);
var node = M.getNodeByHandle(nodeHandle);
var userHandles = M.getNodeShareUsers(node, 'EXP');
if (M.ps[nodeHandle]) {
pendingShares = Object(M.ps[nodeHandle]);
userHandles = userHandles.concat(Object.keys(pendingShares));
}
var seen = Object.create(null);
userHandles.forEach(function(handle) {
var user = M.getUser(handle) || Object(M.opc[handle]);
if (!user.m) {
console.warn('Unknown user "%s"!', handle);
}
else if (!seen[user.m]) {
var name = M.getNameByHandle(handle) || user.m;
var share = M.getNodeShare(node, handle) || Object(pendingShares[handle]);
generateShareDialogRow(name, user.m, share.r | 0, handle);
seen[user.m] = 1;
}
});
}
/**
* Generates and inserts a share or pending share row into the share dialog
* @param {String} displayNameOrEmail
* @param {String} email
* @param {Number} shareRights
* @param {String} userHandle Optional
*/
function generateShareDialogRow(displayNameOrEmail, email, shareRights, userHandle) {
var rowId = '',
html = '',
av = useravatar.contact(email, 'small-rounded-avatar square'),
perm = '',
permissionLevel = 0;
if (typeof shareRights != 'undefined') {
permissionLevel = shareRights;
}
// Permission level
if (permissionLevel === 1) {
perm = ['read-and-write', l[56]];
} else if (permissionLevel === 2) {
perm = ['full-access', l[57]];
} else {
perm = ['read-only', l[55]];
}
// Add contact
$.sharedTokens.push(email);
// Update token.input plugin
removeFromMultiInputDDL('.share-multiple-input', {id: email, name: email});
rowId = (userHandle) ? userHandle : email;
html = addShareDialogContactToContent(email, '', rowId, av, displayNameOrEmail, perm[0], perm[1]);
$('.share-dialog .share-dialog-contacts').safeAppend(html);
}
/**
* updateDialogDropDownList
*
* Extract id from list of emails, preparing it for extrusion,
* fill multi-input dropdown list with not used emails.
*
* @param {String} dialog multi-input dialog class name.
*/
function updateDialogDropDownList(dialog) {
var listOfEmails = M.getContactsEMails(),
allEmails = [],
contacts;
// Loop through email list and extrude id
for (var i in listOfEmails) {
if (listOfEmails.hasOwnProperty(i)) {
allEmails.push(listOfEmails[i].id);
}
}
contacts = excludeIntersected($.sharedTokens, allEmails);
addToMultiInputDropDownList(dialog, contacts);
}
/**
* checkMultiInputPermission
*
* Check DOM element permission level class name.
* @param {Object} $this, DOM drop down list element.
* @returns {Array} [drop down list permission class name, translation string].
*/
function checkMultiInputPermission($this) {
var permissionLevel;
if ($this.is('.read-and-write')) {
permissionLevel = ['read-and-write', l[56]]; // Read & Write
}
else if ($this.is('.full-access')) {
permissionLevel = ['full-access', l[57]]; // Full access
}
else {
permissionLevel = ['read-only', l[55]]; // Read-only
}
return permissionLevel;
}
/**
* Checks if an email address is already known by the user
* @param {String} email
* @returns {Boolean} Returns true if it exists in the state, false if it is new
*/
function checkIfContactExists(email) {
var userIsAlreadyContact = false;
var userContacts = M.u;
// Loop through the user's contacts
for (var contact in userContacts) {
if (userContacts.hasOwnProperty(contact)) {
// Check if the users are already contacts by comparing email addresses of known contacts and the one entered
if (email === userContacts[contact].m) {
userIsAlreadyContact = true;
break;
}
}
}
return userIsAlreadyContact;
}
/**
* sharedPermissionLevel
*
* Translate class name to numeric permission level.
* @param {String} value Permission level as a string i.e. 'read-and-write', 'full-access', 'read-only'.
* @returns {Number} integer value of permission level.
*/
function sharedPermissionLevel(value) {
var permissionLevel = 0;
if (value === 'read-and-write') {
permissionLevel = 1; // Read and Write access
}
else if (value === 'full-access') {
permissionLevel = 2; // Full access
}
else {
permissionLevel = 0; // read-only
}
return permissionLevel;
}
/**
* initShareDialogMultiInputPlugin
*
* Initialize share dialog multi input plugin
*/
function initShareDialogMultiInputPlugin() {
"use strict";
// Plugin configuration
var contacts = M.getContactsEMails();
var errorMsg = function(msg) {
var $shareDialog = $('.share-dialog');
var $warning = $shareDialog.find('.multiple-input-warning span');
$warning.text(msg);
$shareDialog.addClass('error');
setTimeout(function() {
$shareDialog.removeClass('error');
}, 3000);
};
var $scope = $('.share-dialog');
var $input = $('.share-multiple-input', $scope);
// Clear old values in case the name/nickname updated since last opening
$input.tokenInput('destroy');
$input.tokenInput(contacts, {
theme: "mega",
placeholder: l[19108],// Enter one or more email address
searchingText: "",
noResultsText: "",
addAvatar: true,
autocomplete: null,
searchDropdown: true,
emailCheck: true,
preventDoublet: false,
tokenValue: "id",
propertyToSearch: "name",
resultsLimit: 5,
minChars: 1,
accountHolder: (M.u[u_handle] || {}).m || '',
scrollLocation: 'share',
// Exclude from dropdownlist only emails/names which exists in multi-input (tokens)
excludeCurrent: true,
onEmailCheck: function() {
errorMsg(l[7415]); // Looks like there's a malformed email
},
onReady: function() {
dialogPositioning($scope);
},
onDoublet: function(u) {
errorMsg(l[7413]); // You already have a contact with that email
},
onHolder: function() {
errorMsg(l[7414]); // There's no need to add your own email address
},
onAdd: function(item) {
var $scope = $('.share-dialog');
// If the user is not already a contact, then show a text area
// where they can add a custom message to the pending share request
if (checkIfContactExists(item.id) === false) {
$('.share-message', $scope).show();
initTextareaScrolling($('.share-message-textarea textarea', $scope), 72);
}
$('.dialog-share-button', $scope).removeClass('disabled');
// Enable group permission change drop down list
$('.permissions-icon', $scope).removeClass('disabled');
dialogPositioning($scope);
},
onDelete: function() {
var $scope = $('.share-dialog');
var $btn = $('.dialog-share-button', $scope);
var iNewItemsNum;
var iItemsNum;
setTimeout(function() {
$scope.find('.token-input-input-token-mega input').trigger("blur");
}, 0);
iNewItemsNum = $scope.find('.token-input-list-mega .token-input-token-mega').length;
iItemsNum = $scope.find('.share-dialog-contacts .share-dialog-contact-bl').length;
// If new items are still availble in multiInput box
// or permission is changed on some of existing items
if (iNewItemsNum || $.changedPermissions.length || $.removedContactsFromShare.length) {
$btn.removeClass('disabled');
}
else {
$btn.addClass('disabled');
}
if (!iNewItemsNum) {
// Disable group permission change drop down list
$('.permissions-icon', $scope).addClass('disabled');
}
dialogPositioning($scope);
}
});
}
function initShareDialog() {
"use strict";
var $dialog = $('.share-dialog');
$.shareTokens = [];
/*if (!u_type) {
return; // not for ephemeral
}*/
// Prevents double initialization of token input
if (!$('.share-multiple-input', $dialog).tokenInput("getSettings")) {
initShareDialogMultiInputPlugin();
}
var menuPermissionState = function($this) {
var mi = '.permissions-menu .permissions-menu-item';
var cls = checkMultiInputPermission($this);
$(mi, $dialog).removeClass('active');
$(mi + '.' + cls[0], $dialog).addClass('active');
};
var handlePermissionMenu = function($this, m, x, y) {
m.css('left', x + 'px');
m.css('top', y + 'px');
menuPermissionState($this);
$this.addClass('active');
m.fadeIn(200);
};
$dialog.rebind('click', function(e) {
var hideMenus = function() {
// share dialog permission menu
$('.permissions-menu', $this).fadeOut(200);
$('.import-contacts-dialog').fadeOut(200);
$('.permissions-icon', $this).removeClass('active');
$('.share-dialog-permissions', $this).removeClass('active');
closeImportContactNotification('.share-dialog');
$('.import-contacts-service', $this).removeClass('imported');
};
var $this = $(this);
if (typeof e.originalEvent.path !== 'undefined') {
// This's sensitive to dialog DOM element positioning
var trg = e.originalEvent.path[0];
var trg1 = e.originalEvent.path[1];
var trg2 = e.originalEvent.path[2];
if (!$(trg).is('.permissions-icon,.import-contacts-link,.share-dialog-permissions')
&& !$(trg1).is('.permissions-icon,.import-contacts-link,.share-dialog-permissions')
&& !$(trg2).is('.permissions-icon,.import-contacts-link,.share-dialog-permissions'))
{
hideMenus();
}
}
else if ($this.get(0) === e.currentTarget) {
hideMenus();
}
});
$('.fm-dialog-close, .dialog-cancel-button', $dialog).rebind('click', function() {
$('.export-links-warning').addClass('hidden');
showWarningTokenInputLose().done(closeDialog);
});
/*
* On share dialog, done/share button
*
* Adding new contacts to shared item
*/
$('.dialog-share-button', $dialog).rebind('click', function(e) {
e.stopPropagation();
// since addNewContact goes by itself and fetch data from HTML. we cant intercept exited
// contacts here.
addNewContact($(this), false).done(function() {
var share = new mega.Share();
share.updateNodeShares().always(function() {
$('.token-input-token-mega').remove();
});
});
});
$dialog.off('click', '.share-dialog-remove-button');
$dialog.on('click', '.share-dialog-remove-button', function() {
var $this = $(this);
var handleOrEmail = $this.parent().attr('id').replace('sdcbl_', '');
$this.parent()
.fadeOut(200)
.remove();
var selectedNodeHandle = $.selected[0];
if (handleOrEmail !== '') {
// Due to pending shares, the id could be an email instead of a handle
var userEmail = Object(M.opc[handleOrEmail]).m || handleOrEmail;
$.removedContactsFromShare.push({
'selectedNodeHandle': selectedNodeHandle,
'userEmail': userEmail,
'handleOrEmail': handleOrEmail
});
$.sharedTokens.splice($.sharedTokens.indexOf(userEmail), 1);
}
shareDialogContentCheck();
});
// related to specific contact
$dialog.off('click', '.share-dialog-permissions');
$dialog.on('click', '.share-dialog-permissions', function(e) {
var $this = $(this);
var $m = $('.permissions-menu', $dialog);
var scrollBlock = $('.share-dialog-contacts .jspPane', $dialog);
var scrollPos = 0;
var x = 0;
var y = 0;
$m.removeClass('search-permissions');
if (scrollBlock.length) {
scrollPos = scrollBlock.position().top;
}
// fadeOut this popup
if ($this.is('.active')) {
$m.fadeOut(200);
$this.removeClass('active');
}
else {
$('.share-dialog-permissions', $dialog).removeClass('active');
$('.permissions-icon', $dialog).removeClass('active');
closeImportContactNotification('.share-dialog');
x = $this.position().left;
y = $this.position().top + 3 + scrollPos;
handlePermissionMenu($this, $m, x, y);
}
e.stopPropagation();
});
// related to multi-input contacts
$('.permissions-icon', $dialog).rebind('click', function(e) {
var $this = $(this);
var $m = $('.permissions-menu');
var x = 0;
var y = 0;
if (!$this.is('.disabled')) {
// fadeOut permission menu for this icon
if ($this.is('.active')) {
$m.fadeOut(200);
$this.removeClass('active');
}
else {
$('.share-dialog-permissions', $dialog).removeClass('active');
$('.permissions-icon', $dialog).removeClass('active');
$m.addClass('search-permissions');
closeImportContactNotification('.share-dialog');
x = $this.position().left - 28;
y = $this.position().top - 57;
handlePermissionMenu($this, $m, x, y);
}
}
e.stopPropagation();
});
/* Handles permission changes
* 1. Group permission change '.share-dialog .permissions-icon.active'
* 2. Specific perm. change '.share-dialog .share-dialog-permissions.active'
*/
$('.permissions-menu-item', $dialog).rebind('click', function(e) {
var $this = $(this);
var id;
var perm;
var $existingContacts;
var shares = M.d[$.selected[0]].shares;
var newPermLevel = checkMultiInputPermission($this);
var $itemPermLevel = $('.share-dialog-permissions.active', $dialog);
var $groupPermLevel = $('.permissions-icon.active', $dialog);
var currPermLevel = [];
$('.permissions-menu', $dialog).fadeOut(200);
// Single contact permission change, .share-dialog-permissions
if ($itemPermLevel.length) {
currPermLevel = checkMultiInputPermission($itemPermLevel);
id = $itemPermLevel.parent().attr('id').replace('sdcbl_', '');
if (id !== '') {
perm = sharedPermissionLevel(newPermLevel[0]);
if (!shares || !shares[id] || shares[id].r !== perm) {
if (M.opc[id]) {
// it's a pending contact, provide back the email
id = M.opc[id].m || id;
}
$.changedPermissions.push({ u: id, r: perm });
}
}
$itemPermLevel
.removeClass(currPermLevel[0])
.removeClass('active')
.safeHTML('@@', newPermLevel[1])
.addClass(newPermLevel[0]);
}
else if ($groupPermLevel.length) {// Group permission change, .permissions-icon
// $.changedPermissions = [];// Reset global var
currPermLevel = checkMultiInputPermission($groupPermLevel);
// Get all items from dialog content block (avatar, name/email, permission)
/*$existingContacts = $('.share-dialog-contact-bl');
$.each($existingContacts, function(index, value) {
extract id of contact
id = $(value).attr('id').replace('sdcbl_', '');
if (id !== '') {
perm = sharedPermissionLevel(newPermLevel[0]);
if (!shares || !shares[id] || shares[id].r !== perm) {
$.changedPermissions.push({ u: id, r: perm });
}
}
});*/
$groupPermLevel
.removeClass(currPermLevel[0])
.removeClass('active')
.safeHTML('@@', newPermLevel[1])
.addClass(newPermLevel[0]);
/*$('.share-dialog-contact-bl .share-dialog-permissions')
.removeClass('read-only')
.removeClass('read-and-write')
.removeClass('full-access')
.safeHTML('@@', newPermLevel[1])
.addClass(newPermLevel[0]);*/
}
if ($.changedPermissions.length > 0) {// Enable Done button
$dialog.find('.default-green-button.dialog-share-button').removeClass('disabled');
}
$dialog.find('.permissions-icon.active').removeClass('active');
$dialog.find('.share-dialog-permissions.active').removeClass('active');
e.stopPropagation();
return false;
});
//Pending info block
$dialog.find('.pending-indicator').rebind('mouseover', function() {
var x = $(this).position().left;
var y = $(this).position().top;
var infoBlock = $('.share-pending-info', $dialog);
var scrollPos = 0;
if ($('.share-dialog-contacts .jspPane', $dialog)) {
scrollPos = $dialog.find('.share-dialog-contacts .jspPane').position().top;
}
var infoHeight = infoBlock.outerHeight();
infoBlock.css({
'left': x,
'top': y - infoHeight + scrollPos
});
infoBlock.fadeIn(200);
});
$dialog.find('.pending-indicator').rebind('mouseout', function() {
$dialog.find('.share-pending-info').fadeOut(200);
});
// Personal message
$dialog.find('.share-message textarea').rebind('focus', function() {
var $this = $(this);
$dialog.find('.share-message').addClass('focused');
if ($this.val() === l[6853]) {
// Clear the default message
$this.val('');
onIdle(function() {
$this.select();
});
$this.mouseup(function mouseUpHandler() {
$this.off("mouseup", mouseUpHandler);
return false;
});
}
});
$dialog.find('.share-message textarea').rebind('blur', function() {
$dialog.find('.share-message').removeClass('focused');
});
}
function addImportedDataToSharedDialog(data) {
$.each(data, function(ind, val) {
$('.share-dialog .share-multiple-input').tokenInput("add", {id: val, name: val});
});
closeImportContactNotification('.share-dialog');
}
function addImportedDataToAddContactsDialog(data) {
$.each(data, function(ind, val) {
$('.add-user-popup .add-contact-multiple-input').tokenInput("add", {id: val, name: val});
});
closeImportContactNotification('.add-user-popup');
}
function closeImportContactNotification(c) {
loadingDialog.hide();
$('.imported-contacts-notification').fadeOut(200);
$(c + ' .import-contacts-dialog').fadeOut(200);
$('.import-contacts-link').removeClass('active');
// Remove focus from input element, related to tokeninput plugin
$(c + ' input#token-input-').trigger("blur");
}
/**
* Check the dialog has token input that is already filled up by user.
* Warn user closing dialog will lose all inserted input.
*/
function showWarningTokenInputLose() {
"use strict";
var promise = new MegaPromise();
// If there is any tokenizer on the dialog and it is triggered by dom event.
var $tokenInput = $('.fm-dialog:visible li[class*="token-input-input"]');
// Make sure all input is tokenized.
$tokenInput.find('input').trigger('blur');
// If tokenizer is on the dialog, check it has input already. If it has, warn user.
var $tokenItems = $('li[class*="token-input-token"]');
if ($tokenItems.length) {
msgDialog('confirmation', '', l[20474], l[18229], function(e) {
if (e) {
$tokenItems.remove();
promise.resolve();
}
else {
promise.reject();
}
});
}
else {
promise.resolve();
}
return promise;
}
function closeDialog(ev) {
"use strict";
if (d) {
MegaLogger.getLogger('closeDialog').debug($.dialog);
}
if (!$('.fm-dialog.registration-page-success').hasClass('hidden')) {
fm_hideoverlay();
$('.fm-dialog.registration-page-success').addClass('hidden').removeClass('special');
}
if ($('.fm-dialog.incoming-call-dialog').is(':visible') === true || $.dialog === 'download-pre-warning') {
// managing dialogs should be done properly in the future, so that we won't need ^^ bad stuff like this one
return false;
}
if ($.dialog === 'passwordlink-dialog') {
if (String(page).substr(0, 2) === 'P!') {
// do nothing while on the password-link page
return false;
}
$('.fm-dialog.password-dialog').addClass('hidden');
}
// business account, add sub-user dialog. we wont allow closing before copying password
if ($.dialog === 'sub-user-adding-dlg') {
if ($('.user-management-add-user-dialog.user-management-dialog .dialog-button-container .add-sub-user')
.hasClass('disabled')) {
return false;
}
}
if ($.dialog === 'prd') {
// PasswordReminderDialog manages its own states, so don't do anything.
return;
}
if ($.dialog === 'terms' && $.registerDialog) {
$('.fm-dialog.bottom-pages-dialog').addClass('hidden');
}
else if ($.dialog === 'createfolder' && ($.copyDialog || $.moveDialog || $.selectFolderDialog)) {
$('.fm-dialog.create-folder-dialog').addClass('hidden');
$('.fm-dialog.create-folder-dialog .create-folder-size-icon').removeClass('hidden');
}
else if (($.dialog === 'slideshow') && $.copyrightsDialog) {
$('.copyrights-dialog').addClass('hidden');
delete $.copyrightsDialog;
}
else {
if ($.dialog === 'properties') {
propertiesDialog(2);
}
else {
fm_hideoverlay();
}
$('.fm-dialog' + ($.propertiesDialog ? ':not(.properties-dialog)' : ''))
.trigger('dialog-closed')
.addClass('hidden');
$('.dialog-content-block').empty();
// add contact popup
$('.add-user-popup').addClass('hidden');
$('.fm-add-user').removeClass('active');
$('.add-contact-multiple-input').tokenInput("clearOnCancel");
$('.share-multiple-input').tokenInput("clearOnCancel");
// share dialog
$('.share-dialog-contact-bl').remove();
$('.import-contacts-service').removeClass('imported');
// share dialog permission menu
$('.permissions-menu').fadeOut(0);
$('.permissions-icon').removeClass('active');
closeImportContactNotification('.share-dialog');
closeImportContactNotification('.add-user-popup');
$('.copyrights-dialog').addClass('hidden');
delete $.copyDialog;
delete $.moveDialog;
delete $.copyToShare;
delete $.copyToUpload;
delete $.shareToContactId;
delete $.copyrightsDialog;
delete $.selectFolderDialog;
/* copy/move dialog - save to */
delete $.saveToDialogCb;
delete $.saveToDialogNode;
delete $.saveToDialog;
if ($.saveToDialogPromise) {
if (typeof $.saveToDialogPromise === 'function') {
$.saveToDialogPromise(EEXPIRED);
}
else {
$.saveToDialogPromise.reject(EEXPIRED);
}
delete $.saveToDialogPromise;
}
if ($(ev && ev.target).is('.fm-dialog-overlay, .dialog-cancel-button, .fm-dialog-close')) {
delete $.onImportCopyNodes;
}
}
$('.fm-dialog, .overlay.arrange-to-back').removeClass('arrange-to-back');
// $('.fm-dialog .dialog-sorting-menu').remove();
$('.export-links-warning').addClass('hidden');
if ($.dialog === 'terms' && $.termsAgree) {
delete $.termsAgree;
}
if ($.dialog === 'createfolder') {
if ($.cfpromise) {
$.cfpromise.reject();
delete $.cfpromise;
}
}
if ($.dialog === 'selectFolder') {
delete $.selectFolderCallback;
}
delete $.dialog;
delete $.mcImport;
treesearch = false;
if ($.registerDialog) {
// if the terms dialog was closed from the register dialog
$.dialog = $.registerDialog;
}
if ($.propertiesDialog) {
// if the dialog was close from the properties dialog
$.dialog = $.propertiesDialog;
}
if ($.copyDialog || $.moveDialog || $.selectFolderDialog) {
// the createfolder dialog was closed
$.dialog = $.copyDialog || $.moveDialog || $.selectFolderDialog;
$('.fm-dialog').addClass('arrange-to-back');
$('.fm-dialog.fm-picker-dialog').removeClass('arrange-to-back');
}
mBroadcaster.sendMessage('closedialog');
}
function createFolderDialog(close) {
"use strict";
// Checking if this a business user with expired status
if (u_attr && u_attr.b && u_attr.b.s === -1) {
M.showExpiredBusiness();
return;
}
var $dialog = $('.fm-dialog.create-folder-dialog');
var $input = $('input', $dialog);
$input.val('');
if (close) {
if ($.cftarget) {
delete $.cftarget;
}
closeDialog();
return true;
}
var doCreateFolder = function (v) {
var target = $.cftarget = $.cftarget || M.currentCustomView.nodeID || M.currentdirid;
if (!M.isSafeName(v)) {
$dialog.removeClass('active');
$input.addClass('error');
return;
}
else {
var specifyTarget = null;
if ($.cftarget) {
specifyTarget = $.cftarget;
}
if (duplicated(1, v, specifyTarget)) {
$dialog.addClass('duplicate');
$input.addClass('error');
setTimeout(function () {
$dialog.removeClass('duplicate');
$input.removeClass('error');
$input.trigger("focus");
}, 2000);
return;
}
}
loadingDialog.pshow();
$dialog.addClass('hidden');
M.createFolder(target, v, new MegaPromise())
.done(function(h) {
if (d) {
console.log('Created new folder %s->%s', target, h);
}
loadingDialog.phide();
if ($.cfpromise) {
$.cfpromise.resolve(h);
delete $.cfpromise;
}
createFolderDialog(1);
})
.fail(function(error) {
loadingDialog.phide();
$dialog.removeClass('hidden');
msgDialog('warninga', l[135], l[47], api_strerror(error));
});
};
$input.rebind('focus', function() {
if ($(this).val() === l[157]) {
$input.val('');
}
$dialog.addClass('focused');
});
$input.rebind('blur', function() {
$dialog.removeClass('focused');
});
$input.rebind('keyup', function() {
if ($input.val() === '' || $input.val() === l[157]) {
$dialog.removeClass('active');
}
else {
$dialog.addClass('active');
$input.removeClass('error');
}
});
$input.rebind('keypress', function(e) {
if (e.which === 13 && $(this).val() !== '') {
doCreateFolder($(this).val());
}
});
$('.fm-dialog-close, .create-folder-button-cancel', $dialog).rebind('click', createFolderDialog);
$('.fm-dialog-input-clear').rebind('click', function() {
$input.val('');
$dialog.removeClass('active');
});
$('.fm-dialog-new-folder-button').rebind('click', function() {
var v = $input.val();
if (v === '' || v === l[157]) {
alert(l[1024]);
}
else {
doCreateFolder(v);
}
});
M.safeShowDialog('createfolder', function() {
$dialog.removeClass('hidden');
$('.create-folder-input-bl input').trigger("focus");
$dialog.removeClass('active');
return $dialog;
});
}
function chromeDialog(close) {
'use strict';
var $dialog = $('.fm-dialog.chrome-dialog');
if (close) {
closeDialog();
return true;
}
M.safeShowDialog('chrome', $dialog);
$('.chrome-dialog .browsers-button,.chrome-dialog .fm-dialog-close').rebind('click', function()
{
chromeDialog(1);
});
$('#chrome-checkbox').rebind('click', function()
{
if ($(this).attr('class').indexOf('checkboxOn') === -1)
{
localStorage.chromeDialog = 1;
$(this).attr('class', 'checkboxOn');
$(this).parent().attr('class', 'checkboxOn');
$(this).prop('checked', true);
}
else
{
delete localStorage.chromeDialog;
$(this).attr('class', 'checkboxOff');
$(this).parent().attr('class', 'checkboxOff');
$(this).prop('checked', false);
}
});
}
function browserDialog(close) {
'use strict';
var $dialog = $('.fm-dialog.browsers-dialog');
if (close) {
closeDialog();
return true;
}
M.safeShowDialog('browser', function() {
$.browserDialog = 1;
return $dialog;
});
$('.browsers-dialog .browsers-button,.browsers-dialog .fm-dialog-close').rebind('click', function() {
browserDialog(1);
});
$('#browsers-checkbox').rebind('click', function() {
if (!$(this).hasClass('checkboxOn')) {
localStorage.browserDialog = 1;
$(this).attr('class', 'checkboxOn');
$(this).parent().attr('class', 'checkboxOn');
$(this).prop('checked', true);
}
else {
delete localStorage.chromeDialog;
$(this).attr('class', 'checkboxOff');
$(this).parent().attr('class', 'checkboxOff');
$(this).prop('checked', false);
}
});
$('.browsers-top-icon').removeClass('ie9 ie10 safari');
var bc, bh, bt;
var type = browserDialog.isWeak();
if (type && type.ie11)
{
if (page !== 'download' && ('' + page).split('/').shift() !== 'fm')
{
browserDialog(1);
return false;
}
// IE11
bc = 'ie10';
bh = l[884].replace('[X]', type.edge ? 'Edge' : 'IE 11');
// if (page == 'download') bt = l[1933];
// else bt = l[886];
bt = l[1933];
}
else if (type && type.ie10)
{
bc = 'ie10';
bh = l[884].replace('[X]', 'Internet Explorer 10');
if (page === 'download')
bt = l[1933];
else
bt = l[886];
}
else if (type && type.safari)
{
bc = 'safari';
bh = l[884].replace('[X]', 'Safari');
if (page === 'download')
bt = l[1933];
else
bt = l[887].replace('[X]', 'Safari');
}
else
{
bc = 'safari';
bh = l[884].replace('[X]', l[885]);
bt = l[887].replace('[X]', 'Your browser');
}
$('.browsers-top-icon').addClass(bc);
$('.browsers-info-block p').text(bt);
$('.browsers-info-header').text(bh);
$('.browsers-info-header p').text(bt);
}
browserDialog.isWeak = function() {
var result = {};
var ua = String(navigator.userAgent);
var style = document.documentElement.style;
result.ie10 = (ua.indexOf('MSIE 10') > -1);
result.ie11 = ('-ms-scroll-limit' in style) && ('-ms-ime-align' in style);
result.edge = /\sEdg(?:e|A|iOS)?\/\d/.test(ua);
result.safari = (ua.indexOf('Safari') > -1) && (ua.indexOf('Chrome') === -1);
result.weak = result.edge || result.ie11 || result.ie10 || result.safari;
return result.weak && result;
};
/**
* Show bottom pages dialog
* @param {Boolean} close dialog parameter
* @param {String} bottom page title
* @param {String} dialog header
* @param {Boolean} tickbox tickbox existency to let user agree this dialog
*/
function bottomPageDialog(close, pp, hh, tickbox) {
"use strict";
var $dialog = $('.fm-dialog.bottom-pages-dialog');
var closeDialog = function() {
$dialog.off('dialog-closed');
// reset scroll position to top for re-open
var jsp = $dialog.find('.bp-body').data('jsp');
if (jsp) {
jsp.scrollToY(0);
}
window.closeDialog();
delete $.termsAgree;
delete $.termsDeny;
return false;
};
if (close) {
closeDialog();
return false;
}
if (!pp) {
pp = 'terms';
}
// Show Agree/Cancel buttons for Terms dialogs if it does not have tickbox to agree=
if ((pp === 'terms' && !tickbox) || pp === 'sdkterms') {
$('.fm-bp-cancel, .fm-bp-agree', $dialog).removeClass('hidden');
$('.fm-bp-close', $dialog).addClass('hidden');
$('.fm-dialog-title', $dialog).text(l[385]);
$('.fm-bp-cancel', $dialog).rebind('click', function()
{
if ($.termsDeny) {
$.termsDeny();
}
bottomPageDialog(1);
});
$('.fm-bp-agree', $dialog).rebind('click', function()
{
if ($.termsAgree) {
$.termsAgree();
}
bottomPageDialog(1);
});
$('.fm-dialog-close', $dialog).rebind('click', function()
{
if ($.termsDeny) {
$.termsDeny();
}
bottomPageDialog(1);
});
}
else {
$('.fm-bp-cancel, .fm-bp-agree', $dialog).addClass('hidden');
$('.fm-bp-close', $dialog).removeClass('hidden');
if (hh) {
$('.fm-dialog-title', $dialog).text(hh)
}
$('.fm-dialog-close, .fm-bp-close', $dialog).rebind('click', function()
{
bottomPageDialog(1);
});
}
var asyncTaskID;
if (!pages[pp]) {
asyncTaskID = 'page.' + pp + '.' + makeUUID();
M.require(pp)
.always(function() {
mBroadcaster.sendMessage(asyncTaskID);
asyncTaskID = null;
});
}
M.safeShowDialog(pp, function _showDialog() {
if (asyncTaskID) {
loadingDialog.show();
mBroadcaster.once(asyncTaskID, function() {
loadingDialog.hide();
asyncTaskID = null;
_showDialog();
});
return $dialog;
}
$dialog.rebind('dialog-closed', closeDialog).removeClass('hidden');
$('.bp-main', $dialog)
.safeHTML(translate(String(pages[pp])
.split('((TOP))')[1]
.split('((BOTTOM))')[0]
.replace('main-mid-pad new-bottom-pages', ''))
);
$('.bp-body', $dialog).jScrollPane({
showArrows: true,
arrowSize: 5,
animateScroll: true,
verticalDragMinHeight: 50
});
jScrollFade('.bp-body');
clickURLs();
scrollToURLs();
return $dialog;
});
}
function clipboardcopycomplete()
{
if (d)
console.log('clipboard copied');
}
function saveprogress(id, bytesloaded, bytestotal)
{
if (d)
console.log('saveprogress', id, bytesloaded, bytestotal);
}
function savecomplete(id)
{
$('.fm-dialog.download-dialog').addClass('hidden');
fm_hideoverlay();
if (!$.dialog)
$('#dlswf_' + id).remove();
var dl = dlmanager.getDownloadByHandle(id);
if (dl) {
M.dlcomplete(dl);
dlmanager.cleanupUI(dl, true);
}
}
/**
* Because of the left and transfer panes resizing options, we are now implementing the UI layout logic here, instead of
* the original code from the styles.css.
* The main reason is that, the CSS is not able to correctly calculate values based on other element's properties (e.g.
* width, height, position, etc).
* This is why we do a on('resize') handler which handles the resize of the generic layout of Mega's FM.
*/
function fm_resize_handler(force) {
"use strict";
if ($.tresizer.last === -1 && force !== true) {
return;
}
if (d) {
console.time('fm_resize_handler');
}
if (ulmanager.isUploading || dlmanager.isDownloading) {
var tfse = M.getTransferElements();
if (tfse) {
tfse.domScrollingTable.style.height = (
$(tfse.domTransfersBlock).outerHeight() -
$(tfse.domTableHeader).outerHeight() -
$(tfse.domTransferHeader).outerHeight()
) + "px";
}
}
if (M.currentdirid !== 'transfers') {
$('.files-grid-view .grid-scrolling-table, .file-block-scrolling,' +
' .contacts-grid-view .contacts-grid-scrolling-table')
.css({
'width': $(document.body).outerWidth() - $('.fm-left-panel').outerWidth() - 46 /* margins of icons */
});
initTreeScroll();
}
if (M.currentdirid === 'contacts') {
if (M.viewmode) {
initContactsBlocksScrolling();
}
else {
if ($.contactGridHeader) {
$.contactGridHeader();
}
initContactsGridScrolling();
}
}
else if (M.currentdirid === 'ipc') {
initIpcGridScrolling();
M.addGridUIDelayed(true);
if ($.ipcGridHeader) {
$.ipcGridHeader();
}
}
else if (M.currentdirid === 'opc') {
initOpcGridScrolling();
M.addGridUIDelayed(true);
if ($.opcGridHeader) {
$.opcGridHeader();
}
}
else if (M.currentdirid === 'shares') {
if (M.viewmode) {
initShareBlocksScrolling();
}
else {
initGridScrolling();
if ($.sharedGridHeader) {
$.sharedGridHeader();
}
}
}
else if (M.currentdirid === 'out-shares') {
if (M.viewmode) {
initOutShareBlocksScrolling();
}
else {
initGridScrolling();
if ($.outSharedGridHeader) {
$.outSharedGridHeader();
}
}
}
else if (M.currentdirid === 'transfers') {
fm_tfsupdate(); // this will call $.transferHeader();
}
else if (M.currentdirid && M.currentdirid.substr(0, 7) === 'account') {
var $mainBlock = $('.fm-account-main');
$mainBlock.removeClass('low-width hi-width');
if ($mainBlock.width() > 1675) {
$mainBlock.addClass('hi-width');
}
else if ($mainBlock.width() < 920) {
$mainBlock.addClass('low-width');
}
initAccountScroll();
}
else if (M.currentdirid && M.currentdirid.substr(0, 9) === 'dashboard') {
var $mainBlock = $('.fm-right-block.dashboard');
$mainBlock.removeClass('hidden ultra low-width hi-width');
if ($mainBlock.width() > 1675) {
$mainBlock.addClass('hi-width');
}
else if ($mainBlock.width() < 880 && $mainBlock.width() > 850) {
$mainBlock.addClass('low-width');
}
else if ($mainBlock.width() < 850) {
$mainBlock.addClass('ultra low-width');
}
initDashboardScroll();
}
else {
if (M.viewmode) {
initFileblocksScrolling();
}
else {
initGridScrolling();
if ($.gridHeader) {
$.gridHeader();
$.detailsGridHeader();
}
}
}
if (M.currentdirid !== 'transfers') {
if (megaChatIsReady && megaChat.resized) {
megaChat.resized();
}
$('.fm-right-files-block, .fm-right-account-block, .fm-right-block.dashboard').css({
'margin-left': ($('.fm-left-panel:visible').width() + $('.nw-fm-left-icons-panel').width()) + "px"
});
$('.popup.transfer-widget').width($('.fm-left-panel:visible').width() - 9);
}
if (M.currentrootid === 'shares') {
var shared_block_height = $('.shared-details-block').height() - $('.shared-top-details').height();
if (shared_block_height > 0) {
$('.shared-details-block .files-grid-view, .shared-details-block .fm-blocks-view').css({
'height': shared_block_height + "px",
'min-height': shared_block_height + "px"
});
}
}
if (M.currentdirid && M.currentdirid.indexOf('user-management') > -1) {
var $businessAccountContainer = $('.files-grid-view.user-management-view');
var $subAccountContainer = $('.user-management-subaccount-view-container', $businessAccountContainer);
// sub-user info pgae
if (!$subAccountContainer.hasClass('hidden')) {
$subAccountContainer.jScrollPane({
enableKeyboardNavigation: false, showArrows: true,
arrowSize: 8, animateScroll: true
});
}
// overview page
else if (!$('.user-management-overview-container', $businessAccountContainer).hasClass('hidden')) {
$('.user-management-overview-container', $businessAccountContainer).jScrollPane(
{ enableKeyboardNavigation: false, showArrows: true, arrowSize: 8, animateScroll: true }
);
}
else if (!$('.user-management-account-settings .invoice-detail', $businessAccountContainer)
.hasClass('hidden')) {
$('.user-management-account-settings .invoice-detail', $businessAccountContainer)
.jScrollPane({ enableKeyboardNavigation: false, showArrows: true, arrowSize: 8, animateScroll: true });
}
else if (!$('.user-management-account-settings .invoice', $businessAccountContainer)
.hasClass('hidden')) {
$('.user-management-account-settings .invoice .invoice-table-list-container', $businessAccountContainer)
.jScrollPane({ enableKeyboardNavigation: false, showArrows: true, arrowSize: 8, animateScroll: true });
}
}
if (d) {
console.timeEnd('fm_resize_handler');
}
}
function sharedFolderUI() {
"use strict";
var nodeData = M.d[M.currentdirid];
var browsingSharedContent = false;
// Browsing shared content
if ($('.shared-details-block').length > 0) {
$('.shared-details-block .files-grid-view, .shared-details-block .fm-blocks-view').removeAttr('style');
$('.shared-details-block .shared-folder-content').unwrap();
$('.shared-folder-content').removeClass('shared-folder-content');
$('.shared-top-details').remove();
browsingSharedContent = true;
}
// are we in an inshare?
while (nodeData && !nodeData.su) {
nodeData = M.d[nodeData.p];
}
if (nodeData) {
var rights = l[55];
var rightsclass = ' read-only';
var rightPanelView = '.files-grid-view.fm';
// Handle of initial share owner
var ownersHandle = nodeData.su;
var folderName = (M.d[M.currentdirid] || nodeData).name;
var displayName = htmlentities(M.getNameByHandle(ownersHandle));
var avatar = useravatar.contact(M.d[ownersHandle]);
if (Object(M.u[ownersHandle]).m) {
displayName += ' <' + htmlentities(M.u[ownersHandle].m) + '>';
}
// Access rights
if (nodeData.r === 1) {
rights = l[56];
rightsclass = ' read-and-write';
}
else if (nodeData.r === 2) {
rights = l[57];
rightsclass = ' full-access';
}
if (M.viewmode === 1) {
rightPanelView = '.fm-blocks-view.fm';
}
$(rightPanelView).wrap('');
$('.shared-details-block').prepend(
'
'
+ ''
+ '
'
+ '
'
+ '
' + htmlentities(folderName) + '
'
+ ''
+ '
' + rights + '
'
+ ''
+ avatar
+ '
'
+ '
' + displayName + '
'
+ '
'
+ '
'
+ '
'
+ '
' + l[5866] + '
'
+ '
' + l[63] + '
'
+ '
' + l[58] + '
'
+ ''
+ '
'
+ ''
+ '
'
+ '
');
$(rightPanelView).addClass('shared-folder-content');
if (M.d[M.currentdirid] !== nodeData || M.d[nodeData.p]) {
// hide leave-share under non-root shares
$('.fm-leave-share').addClass('hidden');
}
onIdle(function() {
$(window).trigger('resize');
onIdle(fm_resize_handler);
});
}
return browsingSharedContent;
}
function userFingerprint(userid, callback) {
userid = userid.u || userid;
var user = M.u[userid];
if (!user || !user.u) {
return callback([]);
}
if (userid === u_handle) {
var fprint = authring.computeFingerprint(u_pubEd25519, 'Ed25519', 'hex');
return callback(fprint.toUpperCase().match(/.{4}/g), fprint);
}
var fingerprintPromise = crypt.getFingerprintEd25519(user.h || userid);
fingerprintPromise.done(function (response) {
callback(
response.toUpperCase().match(/.{4}/g),
response
);
});
}
/**
* Get and display the fingerprint
* @param {Object} user The user object e.g. same as M.u[userHandle]
* @param {Selector} $wrapper Container of fingerprint block
*/
function showAuthenticityCredentials(user, $wrapper) {
var $fingerprintContainer = $wrapper.length ?
$wrapper.find('.contact-fingerprint-txt') : $('.contact-fingerprint-txt');
// Compute the fingerprint
userFingerprint(user, function(fingerprints) {
// Clear old values immediately
$fingerprintContainer.empty();
// Render the fingerprint into 10 groups of 4 hex digits
$.each(fingerprints, function(key, value) {
$('').text(value).appendTo($fingerprintContainer);
});
});
}
/**
* Enables the Verify button
* @param {String} userHandle The user handle
*/
function enableVerifyFingerprintsButton(userHandle) {
$('.fm-verify').removeClass('verified');
$('.fm-verify').find('span').text(l[1960] + '...');
$('.fm-verify').rebind('click', function() {
fingerprintDialog(userHandle);
});
}
function fingerprintDialog(userid) {
// Add log to see how often they open the verify dialog
api_req({ a: 'log', e: 99601, m: 'Fingerprint verify dialog opened' });
userid = userid.u || userid;
var user = M.u[userid];
if (!user || !user.u) {
return;
}
var $dialog = $('.fingerprint-dialog');
var closeFngrPrntDialog = function() {
closeDialog();
$('.fm-dialog-close', $dialog).off('click');
$('.dialog-approve-button').off('click');
$('.dialog-skip-button').off('click');
mega.ui.CredentialsWarningDialog.rendernext();
};
$dialog.find('.fingerprint-avatar').empty()
.append($(useravatar.contact(userid, 'semi-mid-avatar')));
$dialog.find('.contact-details-user-name')
.text(M.getNameByHandle(user.u)) // escape HTML things
.end()
.find('.contact-details-email')
.text(user.m); // escape HTML things
$dialog.find('.fingerprint-txt').empty();
userFingerprint(u_handle, function(fprint) {
var target = $('.fingerprint-bott-txt .fingerprint-txt');
fprint.forEach(function(v) {
$('').text(v).appendTo(target);
});
});
userFingerprint(user, function(fprint) {
var offset = 0;
$dialog.find('.fingerprint-code .fingerprint-txt').each(function() {
var that = $(this);
fprint.slice(offset, offset + 5).forEach(function(v) {
$('').text(v).appendTo(that);
offset++;
});
});
});
$('.fm-dialog-close', $dialog).rebind('click', function() {
closeFngrPrntDialog();
});
$('.dialog-approve-button').rebind('click', function() {
// Add log to see how often they verify the fingerprints
api_req({ a: 'log', e: 99602, m: 'Fingerprint verification approved' });
loadingDialog.show();
// Generate fingerprint
crypt.getFingerprintEd25519(userid, 'string')
.done(function(fingerprint) {
// Authenticate the contact
var result = authring.setContactAuthenticated(
userid,
fingerprint,
'Ed25519',
authring.AUTHENTICATION_METHOD.FINGERPRINT_COMPARISON,
authring.KEY_CONFIDENCE.UNSURE
);
// Change button state to 'Verified'
$('.fm-verify').off('click').addClass('verified').find('span').text(l[6776]);
closeFngrPrntDialog();
M.u[userid] && M.u[userid].trackDataChange();
if (result && result.always) {
// wait for the setContactAuthenticated to finish and then trigger re-rendering.
result.always(function() {
M.u[userid] && M.u[userid].trackDataChange();
});
}
})
.always(function() {
loadingDialog.hide();
});
});
$('.dialog-skip-button').rebind('click', function() {
closeFngrPrntDialog();
});
M.safeShowDialog('fingerprint-dialog', $dialog);
}
/**
* Implements the behavior of "File Manager - Resizable Panes":
* - Initializes a jQuery UI .resizable
* - Sets w/h/direction
* - Persistance (only saving is implemented here, you should implement by yourself an initial set of the w/h from the
* localStorage
* - Proxies the jQ UI's resizable events - `resize` and `resizestop`
* - Can be initialized only once per element (instance is stored in $element.data('fmresizable'))
*
* @param element
* @param opts
* @returns {*}
* @constructor
*/
function FMResizablePane(element, opts) {
"use strict";
var $element = $(element);
var self = this;
var $self = $(this);
self.element = element;
/**
* Default options
*
* @type {{direction: string, persistanceKey: string, minHeight: undefined, minWidth: undefined, handle: string}}
*/
var defaults = {
'direction': 'n',
'persistanceKey': 'transferPanelHeight',
'minHeight': undefined,
'minWidth': undefined,
'handle': '.transfer-drag-handle'
};
var size_attr = 'height';
opts = $.extend(true, {}, defaults, opts);
self.options = opts; //expose as public
/**
* Depending on the selected direction, pick which css attr should we be changing - width OR height
*/
if (opts.direction === 'n' || opts.direction === 's') {
size_attr = 'height';
} else if (opts.direction === 'e' || opts.direction === 'w') {
size_attr = 'width';
} else if (opts.direction.length === 2) {
size_attr = 'both';
}
/**
* Already initialized.
*/
if ($element.data('fmresizable')) {
return;
}
self.destroy = function() {
// some optimizations can be done here in the future.
};
/**
* Basic init/constructor code
*/
{
var $handle = $(opts.handle, $element);
if (d) {
if (!$handle.length) {
console.warn('FMResizablePane: Element not found: ' + opts.handle);
}
}
$handle.addClass('ui-resizable-handle ui-resizable-' + opts.direction);
var resizable_opts = {
'handles': {
},
minHeight: opts.minHeight,
minWidth: opts.minWidth,
maxHeight: opts.maxHeight,
maxWidth: opts.maxWidth,
start: function(e, ui) {
},
resize: function(e, ui) {
var css_attrs = {
'top': 0
};
if (size_attr === 'both') {
css_attrs['width'] = ui.size['width'];
css_attrs['height'] = ui.size['height'];
$element.css(css_attrs);
if (opts.persistanceKey) {
mega.config.set(opts.persistanceKey, css_attrs);
}
} else {
css_attrs[size_attr] = ui.size[size_attr];
$element.css(css_attrs);
if (opts.persistanceKey) {
mega.config.set(opts.persistanceKey, ui.size[size_attr]);
}
self["current_" + size_attr] = ui.size[size_attr];
}
$self.trigger('resize', [e, ui]);
},
'stop': function(e, ui) {
$self.trigger('resizestop', [e, ui]);
$(window).trigger('resize');
}
};
if (opts['aspectRatio']) {
resizable_opts['aspectRatio'] = opts['aspectRatio'];
}
resizable_opts['handles'][opts.direction] = $handle;
$element.resizable(resizable_opts);
$element.data('fmresizable', this);
}
return this;
}
/**
* bindDropdownEvents Bind custom select event
*
* @param {Selector} $dropdown Class .dropdown elements selector
* @param {String} saveOption Addition option for account page only. Allows to show "Show changes" notification
* @param {String} classname/id of content block for dropdown aligment
*/
function bindDropdownEvents($dropdown, saveOption, contentBlock) {
var $dropdownsItem = $dropdown.find('.default-dropdown-item');
var $contentBlock = contentBlock ? $(contentBlock) : $(window);
var $hiddenInput = $dropdown.find('.dropdown-hidden-input');
// hidden input for keyboard search
if (!$hiddenInput.length) {
$hiddenInput = $('');
$dropdown.prepend($hiddenInput);
}
$dropdown.rebind('click', function(e) {
var $this = $(this);
if (!$this.hasClass('active')) {
var jsp;
var scrollBlock = '#' + $this.attr('id') + ' .default-select-scroll';
var $dropdown = $this.find('.default-select-dropdown');
var $activeDropdownItem = $this.find('.default-dropdown-item.active');
var dropdownOffset;
var dropdownBottPos;
var dropdownHeight;
var contentBlockHeight;
//Show select dropdown
$('.active .default-select-dropdown').addClass('hidden');
$this.addClass('active');
$dropdown.removeAttr('style');
$dropdown.removeClass('hidden');
//Dropdown position relative to the window
dropdownOffset = $dropdown.offset().top - $contentBlock.offset().top;
contentBlockHeight = $contentBlock.height();
dropdownHeight = $dropdown.outerHeight();
dropdownBottPos = contentBlockHeight - (dropdownOffset + dropdownHeight);
if (contentBlockHeight < (dropdownHeight + 20)) {
$dropdown.css({
'margin-top': '-' + (dropdownOffset - 10) + 'px',
'height': (contentBlockHeight - 20) + 'px'
});
}
else if (dropdownBottPos < 10) {
$dropdown.css({
'margin-top': '-' + (10 - dropdownBottPos) + 'px'
});
}
//Dropdown scrolling initialization
initSelectScrolling(scrollBlock);
jsp = $(scrollBlock).data('jsp');
// Prevent horizontal scrolling
$(scrollBlock).jScrollPane({
contentWidth: '0px'
});
if (jsp && $activeDropdownItem.length) {
jsp.scrollToElement($activeDropdownItem);
}
$hiddenInput.trigger('focus');
}
else if (!$(e.target).parents('.jspVerticalBar').length) {
$this.find('.default-select-dropdown').addClass('hidden');
$this.removeClass('active');
}
});
$dropdownsItem.rebind('click.settingsGeneral', function() {
var $this = $(this);
var $select = $(this).closest('.default-select');
// Select dropdown item
$select.find('.default-dropdown-item').removeClass('active');
$this.addClass('active');
$select.find('span').text($this.text());
$hiddenInput.trigger('focus');
if (saveOption) {
var nameLen = String($('#account-firstname').val() || '').trim().length;
// Save changes for account page
if (nameLen) {
$this.parents('.settings-right-block').find('.save-block').removeClass('hidden');
}
}
});
// Typing search and arrow key up and down features for dropdowns
$hiddenInput.rebind('keyup', function(e) {
var charCode = e.which || e.keyCode; // ff
if ((charCode > 64 && charCode < 91) || (charCode > 96 && charCode < 123)) {
var inputValue = $hiddenInput.val();
var $filteredItem = $dropdownsItem.filter(function() {
return $(this).text().slice(0, inputValue.length).toLowerCase() === inputValue.toLowerCase();
});
if ($filteredItem[0]) {
var jsp = $dropdown.find('.default-select-scroll').data('jsp');
$dropdown.find('.default-dropdown-item.active').removeClass('active');
jsp.scrollToElement($($filteredItem[0]), 1);
$($filteredItem[0]).addClass('active');
}
}
else {
e.preventDefault();
e.stopPropagation();
var $current = $(this).closest('.default-select').find('.default-dropdown-item.active');
if (charCode === 38) { // Up key
$current.removeClass('active').prev().addClass('active');
}
else if (charCode === 40) { // Down key
$current.removeClass('active').next().addClass('active');
}
else if (charCode === 13) {// Enter
$current.trigger('click');
}
}
});
$hiddenInput.rebind('keydown', function() {
delay('dropbox:clearHidden', function() {
// Combination language bug fixs for MacOS.
$hiddenInput.val('').trigger('blur').trigger('focus');
}, 750);
});
// End of typing search for dropdowns
$('#fmholder, .fm-dialog').rebind('click.defaultselect', function(e) {
if (!$dropdown.has($(e.target)).length && !$dropdown.is(e.target)) {
$dropdown.removeClass('active').find('.default-select-dropdown').addClass('hidden');
}
});
}
/**
* addToMultiInputDropDownList
*
* Add item from token.input plugin drop down list.
*
* @param {String} dialog, The class name.
* @param {Array} item An array of JSON objects e.g. { id, name }.
*
*/
function addToMultiInputDropDownList(dialog, item) {
if (dialog) {
$(dialog).tokenInput("addToDDL", item);
}
}
/**
* removeFromMultiInputDDL
*
* Remove item from token.input plugin drop down list.
*
* @param {String} dialog, The class name.
* @param {Array} item An array of JSON objects e.g. { id, name }.
*
*/
function removeFromMultiInputDDL(dialog, item) {
if (dialog) {
$(dialog).tokenInput("removeFromDDL", item);
}
}
function dashboardUI() {
"use strict";
// Prevent ephemeral session to access dashboard via url
if (u_type === 0) {
msgDialog('confirmation', l[998], l[17146]
+ ' ' + l[999], l[1000], function(e) {
if (e) {
loadSubPage('register');
return false;
}
loadSubPage('fm');
});
return false;
}
$('.fm-right-files-block, .section.conversations, .fm-right-account-block').addClass('hidden');
$('.fm-right-block.dashboard').removeClass('hidden');
// Hide backup widget is user already saved recovery key before
if (localStorage.recoverykey) {
$('.account.widget.recovery-key').addClass('hidden');
}
else {
$('.account.widget.recovery-key').removeClass('hidden');
}
M.onSectionUIOpen('dashboard');
accountUI.general.userUIUpdate();
if (u_attr && u_attr.b) {
$('.fm-right-block.dashboard .non-business-dashboard').addClass('hidden');
$('.fm-right-block.dashboard .business-dashboard').removeClass('hidden');
if (u_attr.b.s !== -1) {
$('.dashboard .button.upgrade-account').addClass('hidden');
}
else {
$('.dashboard .button.upgrade-account').removeClass('hidden');
}
if (u_attr.b.m && u_attr.b.s !== -1) {
$('.business-dashboard .go-to-usermanagement-btn').removeClass('hidden');
// event handler for clicking on user-management button in dashboard.
$('.business-dashboard .go-to-usermanagement-btn').off('click').on('click',
function userManagementBtnClickHandler() {
M.openFolder('user-management', true);
}
);
}
else {
$('.business-dashboard .go-to-usermanagement-btn').addClass('hidden');
}
}
else {
$('.fm-right-block.dashboard .non-business-dashboard').removeClass('hidden');
$('.fm-right-block.dashboard .business-dashboard').addClass('hidden');
$('.dashboard .button.upgrade-account').removeClass('hidden');
}
// Add-contact plus
$('.dashboard .contacts-widget .add-contacts').rebind('click', function() {
contactAddDialog();
return false;
});
// Avatar dialog
$('.fm-account-avatar').rebind('click', function(e) {
avatarDialog();
});
$('.data-float-bl .icon-button').rebind('mouseover.dashboardPlus', FileSelectHandlerMegaSyncMouse);
// Data plus, upload file
$('.data-float-bl .icon-button').rebind('click', function() {
$('.fm-file-upload input').trigger('click');
return false;
});
// Space-widget clickable sections
$('.account.widget.storage .pr-item')
.rebind('click', function() {
var section = String($(this).attr('class')).replace(/account|pr-item|empty|ui-droppable/g, '').trim();
if (section.indexOf('cloud-drive') !== -1) {
section = M.RootID;
}
else if (section.indexOf('rubbish-bin') !== -1) {
section = M.RubbishID;
}
else if (section.indexOf('incoming-shares') !== -1) {
section = 'shares';
}
else if (section.indexOf('inbox') !== -1) {
section = M.InboxID;
}
else {
section = null;
}
if (section) {
M.openFolder(section);
}
return false;
});
// Account data
M.accountData(function(account) {
var perc;
// QR Code
var drawQRCanvas = function _drawQRCanvas() {
var cutPlace = location.href.indexOf('/fm/');
var myHost = location.href.substr(0, cutPlace);
myHost += '/' + M.account.contactLink;
if (account.contactLink && account.contactLink.length) {
var QRoptions = {
width: 106,
height: 106,
correctLevel: QRErrorCorrectLevel.H, // high
foreground: '#D90007',
text: myHost
};
// Render the QR code
$('.account.qr-icon').text('').qrcode(QRoptions);
}
else {
$('.account.qr-icon').text('');
}
};
drawQRCanvas();
$('.qr-widget-w .access-qr').rebind('click', function () {
if (account.contactLink && account.contactLink.length) {
openAccessQRDialog();
}
else {
var msgTitle = l[18230]; // 'QR Code Reactivate';
var msgMsg = l[18231]; // 'Your QR Code is deactivated.';
var msgQuestion = l[18232]; // 'Do you want to reactivate your QR Code?';
msgDialog('confirmation', msgTitle, msgMsg,
msgQuestion,
function (reActivate) {
if (reActivate) {
var reGenQR = { a: 'clc' };
api_req(reGenQR, {
callback: function (res2, ctx2) {
if (typeof res2 !== 'string') {
return;
}
M.account.contactLink = 'C!' + res2;
drawQRCanvas();
openAccessQRDialog();
}
});
}
});
}
});
// Show ballance
$('.account.left-pane.balance-info').text(l[7108]);
$('.account.left-pane.balance-txt').safeHTML('@@ € ', account.balance[0][0]);
$('.fm-account-blocks.storage, .fm-account-blocks.bandwidth').removeClass('exceeded going-out');
// Achievements Widget
if (account.maf && !u_attr.b) {
$('.fm-right-block.dashboard').addClass('active-achievements');
var $achWidget = $('.account.widget.achievements');
var maf = M.maf;
var $storage = $('.account.bonuses-size.storage', $achWidget);
var $transfer = $('.account.bonuses-size.transfers', $achWidget);
var storageCurrentValue = maf.storage.current /*+ maf.storage.base*/;
var transferCurrentValue = maf.transfer.current /*+ maf.transfer.base*/;
$storage.text(bytesToSize(storageCurrentValue, 0));
$transfer.text(bytesToSize(transferCurrentValue, 0));
if (maf.storage.current > 0) {
$storage.removeClass('light-grey');
}
else {
$storage.addClass('light-grey');
}
if (maf.transfer.current > 0) {
$transfer.removeClass('light-grey');
}
else {
$transfer.addClass('light-grey');
}
$('.progress-bar.storage', $achWidget)
.css('width', Math.round(maf.storage.current * 100 / maf.storage.max) + '%');
$('.progress-bar.transfers', $achWidget)
.css('width', Math.round(maf.transfer.current * 100 / maf.transfer.max) + '%');
$('.more-bonuses', $achWidget).rebind('click', function() {
mega.achievem.achievementsListDialog();
});
}
else {
$('.fm-right-block.dashboard').removeClass('active-achievements');
}
// Elements for free/pro accounts. Expites date / Registration date
if (u_attr.p || (u_attr.b && u_attr.b.s === -1)) {
var timestamp;
// Subscription
if (account.stype == 'S') {
// Get the date their subscription will renew
timestamp = account.srenew[0];
// Display the date their subscription will renew
if (timestamp > 0) {
$('.account.left-pane.plan-date-val').text(time2date(timestamp, 2));
$('.account.left-pane.plan-date-info').text(l[20154]);
}
else {
// Otherwise hide info blocks
$('.account.left-pane.plan-date-val, .account.left-pane.plan-date-info').addClass('hidden');
}
}
else if (account.stype == 'O') {
// one-time or cancelled subscription
$('.account.left-pane.plan-date-val').text(time2date(account.expiry, 2));
// If user has nextplan, show infomative tooltip
if (account.nextplan) {
$('.account.left-pane.plan-date-info').safeHTML(escapeHTML(l[20153]) +
'');
}
else {
$('.account.left-pane.plan-date-info').text(l[20153]);
}
}
if (u_attr.b && (u_attr.p === 100 || u_attr.b.s === -1)) {
// someone modified the CSS to overwirte the hidden class !!, therefore .hide() will be used
$('.account.left-pane.reg-date-info, .account.left-pane.reg-date-val').addClass('hidden').hide();
var $businessLeft = $('.account.left-pane.info-block.business-users').removeClass('hidden');
if (u_attr.b.s === 1) {
$businessLeft.find('.suba-status').addClass('active').removeClass('disabled pending')
.text(l[7666]);
if (u_attr.b.m) { // master
timestamp = account.srenew[0];
if ((Date.now() / 1000) - timestamp > 0) {
$businessLeft.find('.suba-status').addClass('pending').removeClass('disabled active')
.text(l[19609]);
}
}
}
else {
$businessLeft.find('.suba-status').addClass('disabled').removeClass('pending active')
.text(l[19608]);
}
if (u_attr.b.m) { // master
$businessLeft.find('.suba-role').text(l[19610]);
}
else {
$businessLeft.find('.suba-role').text(l[5568]);
}
var $businessDashboard = $('.fm-right-block.dashboard .business-dashboard').removeClass('hidden');
$('.fm-right-block.dashboard .non-business-dashboard').addClass('hidden');
}
}
else {
// resetting things might be changed in business account
$('.fm-right-block.dashboard .business-dashboard').addClass('hidden');
$('.account.left-pane.info-block.business-users').addClass('hidden');
$('.account.left-pane.reg-date-info, .account.left-pane.reg-date-val').removeClass('hidden').show();
$('.fm-right-block.dashboard .non-business-dashboard').removeClass('hidden');
}
/* Registration date, bandwidth notification link */
$('.dashboard .default-green-button.upgrade-account, .bandwidth-info a').rebind('click', function() {
if (u_attr && u_attr.b && u_attr.b.m && (u_attr.b.s === -1 || u_attr.b.s === 2)) {
loadSubPage('repay');
}
else {
loadSubPage('pro');
}
});
$('.account.left-pane.reg-date-info').text(l[16128]);
$('.account.left-pane.reg-date-val').text(time2date(u_attr.since, 2));
// left-panel responsive contents
var maxwidth = 0;
for (var i = 0; i < $('.account.left-pane.small-txt:visible').length; i++){
var rowwidth = $('.account.left-pane.small-txt:visible').get(i).offsetWidth
+ $('.account.left-pane.big-txt:visible').get(i).offsetWidth;
maxwidth = Math.max(maxwidth, rowwidth);
}
$.leftPaneResizable.options.updateWidth = maxwidth;
$($.leftPaneResizable).trigger('resize');
if (!u_attr.b) {
accountUI.general.charts.init(account, true);
/* Used Storage progressbar */
var percents = [
100 * account.stats[M.RootID].bytes / account.space,
100 * account.stats[M.RubbishID].bytes / account.space,
100 * account.stats.inshares.bytes / account.space,
100 * account.stats[M.InboxID].bytes / account.space,
100 * (account.space - account.space_used) / account.space,
];
for (i = 0; i < 5; i++) {
if (i === 2) {
// escaping showing incoming share percentage
continue;
}
var $percBlock = $('.storage .account.progress-perc.pr' + i);
if (percents[i] > 0) {
$percBlock.text(Math.round(percents[i]) + ' %');
$percBlock.parent().removeClass('empty hidden');
}
else {
$percBlock.text('');
$percBlock.parent().addClass('empty hidden');
}
}
// Cloud drive
$('.account.progress-size.cloud-drive').text(
account.stats[M.RootID].bytes > 0 ? bytesToSize(account.stats[M.RootID].bytes) : '-'
);
// Rubbish bin
$('.account.progress-size.rubbish-bin').text(
account.stats[M.RubbishID].bytes > 0 ? bytesToSize(account.stats[M.RubbishID].bytes) : '-'
);
// Incoming shares
// $('.account.progress-size.incoming-shares').text(
// account.stats.inshares.bytes ? bytesToSize(account.stats.inshares.bytes) : '-'
// );
// Inbox
$('.account.progress-size.inbox').text(
account.stats[M.InboxID].bytes > 0 ? bytesToSize(account.stats[M.InboxID].bytes) : '-'
);
// Available
$('.account.progress-size.available').text(
account.space - account.space_used > 0 ? bytesToSize(account.space - account.space_used) : '-'
);
/* End of Used Storage progressbar */
/* Used Bandwidth progressbar */
$('.bandwidth .account.progress-bar.green').css('width', account.tfsq.perc + '%');
$('.bandwidth .account.progress-size.available-quota').text(bytesToSize(account.tfsq.left, 0));
if (u_attr.p) {
$('.account.widget.bandwidth').addClass('enabled-pr-bar');
$('.dashboard .account.rounded-icon.right').addClass('hidden');
}
else {
// Show available bandwith for FREE accounts with enabled achievements
if (account.tfsq.ach) {
$('.account.widget.bandwidth').addClass('enabled-pr-bar');
}
else {
$('.account.widget.bandwidth').removeClass('enabled-pr-bar');
}
$('.dashboard .account.rounded-icon.right').removeClass('hidden');
$('.dashboard .account.rounded-icon.right').rebind('click', function() {
if (!$(this).hasClass('active')) {
$(this).addClass('active');
$(this).find('.dropdown').removeClass('hidden');
}
else {
$(this).removeClass('active');
$(this).find('.dropdown').addClass('hidden');
}
});
$('.fm-right-block.dashboard').rebind('click', function(e) {
if (!$(e.target).hasClass('rounded-icon') && $('.account.rounded-icon.info').hasClass('active')) {
$('.account.rounded-icon.info').removeClass('active');
$('.dropdown.body.bandwidth-info').addClass('hidden');
}
});
// Get more transfer quota button
$('.account.widget.bandwidth .free .more-quota').rebind('click', function() {
// if the account have achievements, show them, otherwise #pro
if (M.maf) {
mega.achievem.achievementsListDialog();
}
else {
loadSubPage('pro');
}
return false;
});
}
if (account.tfsq.used > 0 || Object(u_attr).p || account.tfsq.ach) {
$('.account.widget.bandwidth').removeClass('hidden');
$('.fm-account-blocks.bandwidth.right').removeClass('hidden');
$('.bandwidth .account.progress-size.base-quota').text(bytesToSize(account.tfsq.used, 0));
}
else {
$('.account.widget.bandwidth').addClass('hidden');
$('.fm-account-blocks.bandwidth.right').addClass('hidden');
}
/* End of Used Bandwidth progressbar */
// $('.dashboard .button.upgrade-account').removeClass('hidden');
// Fill rest of widgets
dashboardUI.updateWidgets();
}
else {
// someone modified CSS, hidden class is overwitten --> .hide()
if (u_attr.b.s !== -1) {
$('.dashboard .upgrade-account').addClass('hidden').hide();
}
$('.business-dashboard .user-management-storage .storage-transfer-data')
.text(bytesToSize(account.space_used));
$('.business-dashboard .user-management-transfer .storage-transfer-data')
.text(bytesToSize(account.tfsq.used));
var $dataStats = $('.business-dashboard .subaccount-view-used-data');
$dataStats.find('.ba-root .ff-occupy').text(bytesToSize(account.stats[M.RootID].bytes));
$dataStats.find('.ba-root .folder-number').text(account.stats[M.RootID].folders + ' ' + l[2035]);
$dataStats.find('.ba-root .file-number').text(account.stats[M.RootID].files + ' ' + l[2034]);
$dataStats.find('.ba-inshare .ff-occupy').text(bytesToSize(account.stats['inshares'].bytes));
$dataStats.find('.ba-inshare .folder-number').text(account.stats['inshares'].items + ' ' + l[2035]);
$dataStats.find('.ba-inshare .file-number').text(account.stats['inshares'].files + ' ' + l[2034]);
$dataStats.find('.ba-outshare .ff-occupy').text(bytesToSize(account.stats['outshares'].bytes));
$dataStats.find('.ba-outshare .folder-number').text(account.stats['outshares'].items + ' ' + l[2035]);
$dataStats.find('.ba-outshare .file-number').text(account.stats['outshares'].files + ' ' + l[2034]);
$dataStats.find('.ba-rubbish .ff-occupy').text(bytesToSize(account.stats[M.RubbishID].bytes));
$dataStats.find('.ba-rubbish .folder-number').text(account.stats[M.RubbishID].folders + ' ' + l[2035]);
$dataStats.find('.ba-rubbish .file-number').text(account.stats[M.RubbishID].files + ' ' + l[2034]);
$dataStats.find('.ba-pub-links .ff-occupy').text(bytesToSize(account.stats['links'].bytes));
$dataStats.find('.ba-pub-links .file-number').text(account.stats['links'].files);
var verFiles = 0;
var verBytes = 0;
verFiles = account.stats[M.RootID]['vfiles'];
verBytes = account.stats[M.RootID]['vbytes'];
// for (var k in account.stats) {
// if (account.stats[k]['vfiles']) {
// verFiles += account.stats[k]['vfiles'];
// }
// if (account.stats[k]['vbytes']) {
// verBytes += account.stats[k]['vbytes'];
// }
// }
$('.ba-version .tiny-icon.cog.versioning-settings').rebind('click', function () {
loadSubPage('fm/account/file-management');
});
$('.business-dashboard .used-storage-info.ba-pub-links').rebind('click.suba', function() {
loadSubPage('fm/links');
});
$dataStats.find('.ba-version .ff-occupy').text(bytesToSize(verBytes));
// $dataStats.find('.ba-version .file-number').text(verFiles + ' ' + l[2034]);
$dataStats.find('.ba-version .file-number').text(verFiles);
}
// if this is a business account user (sub or master)
// if (u_attr.b) {
// $('.dashboard .button.upgrade-account').addClass('hidden');
// $('.account.widget.bandwidth').addClass('hidden');
// $('.account.widget.body.achievements').addClass('hidden');
// }
onIdle(fm_resize_handler);
initTreeScroll();
// Button on dashboard to backup their master key
$('.dashboard .backup-master-key').rebind('click', function() {
M.showRecoveryKeyDialog(2);
});
});
}
dashboardUI.updateWidgets = function(widget) {
/* Contacts block */
dashboardUI.updateContactsWidget();
/* Chat block */
dashboardUI.updateChatWidget();
// Cloud data block
dashboardUI.updateCloudDataWidget();
};
dashboardUI.updateContactsWidget = function() {
var contacts = M.getActiveContacts();
if (!contacts.length) {
$('.account.widget.text.contacts').removeClass('hidden');
$('.account.data-table.contacts').addClass('hidden');
}
else {
var recent = 0;
var now = unixtime();
contacts.forEach(function(handle) {
var user = M.getUserByHandle(handle);
if ((now - user.ts) < (7 * 86400)) {
recent++;
}
});
$('.account.widget.text.contacts').addClass('hidden');
$('.account.data-table.contacts').removeClass('hidden');
$('.data-right-td.all-contacts span').text(contacts.length);
$('.data-right-td.new-contacts span').text(recent);
$('.data-right-td.waiting-approval span').text(Object.keys(M.ipc || {}).length);
$('.data-right-td.sent-requests span').text(Object.keys(M.opc || {}).length);
}
};
dashboardUI.updateChatWidget = function() {
var allChats = 0;
var privateChats = 0;
var groupChats = 0;
var unreadMessages = $('.new-messages-indicator .chat-unread-count:visible').text();
if (!megaChatIsDisabled && typeof megaChat !== 'undefined') {
megaChat.chats.forEach(function(chatRoom) {
if (chatRoom.type === "group" || chatRoom.type === "public") {
groupChats++;
}
else {
privateChats++;
}
allChats++;
});
}
if (allChats === 0) {
$('.account.widget.text.chat').removeClass('hidden');
$('.account.icon-button.add-contacts').addClass('hidden');
$('.account.data-table.chat').addClass('hidden');
}
else {
$('.account.widget.text.chat').addClass('hidden');
$('.account.icon-button.add-contacts').removeClass('hidden');
$('.account.data-table.chat').removeClass('hidden');
$('.data-right-td.all-chats span').text(allChats);
$('.data-right-td.group-chats span').text(groupChats);
$('.data-right-td.private-chats span').text(privateChats);
$('.data-right-td.unread-messages-data span').text(unreadMessages | 0);
}
$('.chat-widget .account.data-item, .chat-widget .account.widget.title')
.rebind('click.chatlink', function() {
loadSubPage('fm/chat');
});
$('.chat-widget .add-contacts').rebind('click.chatlink', function() {
loadSubPage('fm/chat');
Soon(function() {
$('.conversations .small-icon.white-medium-plus').parent().trigger('click');
});
});
};
dashboardUI.updateCloudDataWidget = function() {
var file1 = 835;
var files = 833;
var folder1 = 834;
var folders = 832;
var data = M.getDashboardData();
var locale = [files, folders, files, folders, folders, files];
var map = ['files', 'folders', 'rubbish', 'ishares', 'oshares', 'links', 'versions'];
var intl = typeof Intl !== 'undefined' && Intl.NumberFormat && new Intl.NumberFormat();
$('.data-item .links-s').rebind('click', function() {
loadSubPage('fm/public-links');
return false;
});
$('.data-item .incoming').rebind('click', function() {
loadSubPage('fm/shares');
return false;
});
$('.data-item .outgoing').rebind('click', function() {
loadSubPage('fm/out-shares');
return false;
});
$('.account.data-item .tiny-icon.cog').rebind('click', function() {
loadSubPage('fm/account/file-management');
});
$('.data-float-bl').find('.data-item')
.each(function(idx, elm) {
var props = data[map[idx]];
var str = l[locale[idx]];
var cnt = props.cnt;
if (cnt === 1) {
str = l[(locale[idx] === files) ? file1 : folder1];
}
else if (intl) {
cnt = intl.format(props.cnt || 0);
}
if (props.xfiles > 1) {
str += ', ' + String(l[833]).replace('[X]', props.xfiles);
}
elm.children[1].textContent = idx < 5 ? String(str).replace('[X]', cnt) : cnt;
if (props.cnt > 0) {
elm.children[2].textContent = bytesToSize(props.size);
$(elm).removeClass('empty');
$('.account.data-item .tiny-icon.cog').show();
}
else {
elm.children[2].textContent = '-';
$(elm).addClass('empty');
$('.account.data-item .tiny-icon.cog').hide();
}
});
};
dashboardUI.prototype = undefined;
Object.freeze(dashboardUI);
function initDashboardScroll(scroll) {
$('.fm-right-block.dashboard').jScrollPane({
enableKeyboardNavigation: false, showArrows: true, arrowSize: 5, animateScroll: true
});
jScrollFade('.fm-right-block.dashboard');
if (scroll) {
var jsp = $('.fm-right-block.dashboard').data('jsp');
if (jsp) {
jsp.scrollToBottom();
}
}
}
/* exported openRecents */
/* exported renderRecents */
/**
* Trigger open recents with all default values.
* (Ignore any passed arguments).
*
*/
function openRecents() {
'use strict';
renderRecents();
}
/**
* Render recents interface.
* @param limit Node Limit
* @param until Unix timestamp until.
*/
function renderRecents(limit, until) {
'use strict';
console.log("renderRecents:", limit, until);
if (!M.recentsRender) {
M.recentsRender = new RecentsRender();
}
M.recentsRender.render(limit, until);
}
/**
* Recents Render Controller.
* @constructor
*/
function RecentsRender() {
'use strict';
this.$container = $(".fm-recents.container");
this.container = this.$container[0];
this.$scrollDiv = this.$container.find(".fm-recents.scroll");
this.scrollDiv = this.$scrollDiv[0];
this.$content = this.$container.find(".fm-recents.content");
this.$noContent = this.$container.find(".fm-recents.no-content");
this._$titleTemplate = this.getTemplate("title-template");
this.currentLimit = false;
this.currentUntil = false;
this._rendered = false;
this._maxFitOnScreen = false;
this._resizeListeners = [];
this._renderCache = {};
this._childIds = {};
this._dynamicList = false;
this._renderFunctions = {};
this._view = [];
this.recentActions = [];
this.actionIdMap = {};
this._shortTimeFormatter = new Intl.DateTimeFormat([], {
hour: '2-digit',
minute:'2-digit',
hour12: false
});
this._fullTimeFormatter = new Intl.DateTimeFormat([], {
hour: '2-digit',
minute:'2-digit',
second: '2-digit',
hour12: false
});
this._expandedStates = {};
this._initScrollPosition = false;
// Map all nodes -> action ids.
this._nodeActionMap = {};
// Maps nodes -> rendered item ids (only if different than action id).
this._nodeRenderedItemIdMap = {};
this._actionChildren = {};
if (!localStorage.recentsDays) {
localStorage.recentsDays = 90;
}
if (!localStorage.recentsNodeLimit) {
localStorage.recentsNodeLimit = 10000;
}
this._defaultRangeTimestamp = Math.floor((Date.now() - (localStorage.recentsDays * 86400000)) / 1000); // 90 days
this._defaultRangeLimit = localStorage.recentsNodeLimit;
var self = this;
// Init Dependencies
if (!window.fmShortcuts) {
window.fmShortcuts = new FMShortcuts();
}
if (!window.selectionManager) {
window.selectionManager = new SelectionManager(
this.$container,
$.selected && $.selected.length > 0
);
if ($.selected) {
$.selected.forEach(function(h) {
selectionManager.add_to_selection(h);
});
}
}
// Default click handlers
this.$container.rebind("click contextmenu", function(e) {
$.hideContextMenu(e);
self.markSelected();
selectionManager.clear_selection();
return false;
});
}
/**
* Trigger a render init or update.
* @param limit
* @param until
*/
RecentsRender.prototype.render = function(limit, until, forceInit) {
'use strict';
var self = this;
// Switch to recents panel.
M.onSectionUIOpen('recents');
$('.fmholder').removeClass("transfer-panel-opened");
$('.fm-right-files-block, .fm-left-panel, .fm-transfers-block').addClass('hidden');
$('.top-head').find(".recents-tab-link").removeClass("hidden").addClass('active');
this.$container.removeClass('hidden');
M.viewmode = 1;
M.v = this._view;
if (!this._rendered) {
loadingDialog.show();
}
this.currentLimit = limit || this._defaultRangeLimit;
this.currentUntil = until || this._defaultRangeTimestamp;
if (M.megaRender) {
// Cleanup background nodes
M.megaRender.cleanupLayout(false, M.v);
}
if (this._dynamicList && !this._dynamicList.active) {
this._dynamicList.resume();
}
selectionManager.clear_selection();
this.clearSelected();
M.getRecentActionsList(this.currentLimit, this.currentUntil).then(function(actions) {
self.getMaxFitOnScreen(true);
console.time('recents:render');
self._injectDates(actions);
if (!self._rendered || !self._dynamicList || forceInit) {
self._initialRender(actions);
} else {
self._updateState(actions);
}
loadingDialog.hide();
console.timeEnd('recents:render');
});
};
/**
* Initialise the dynamicList and render the initial view.
* If called after already initialized, will destroy previous instance and recreate.
* @param actions
* @private
*/
RecentsRender.prototype._initialRender = function(actions) {
'use strict';
var self = this;
if (actions.length === 0) {
this.recentActions = actions;
this._view = [];
M.v = this._view;
this.$noContent.removeClass('hidden');
this.$content.addClass('hidden');
} else {
self.$noContent.addClass('hidden');
this.recentActions = actions;
if (this._rendered) {
this._dynamicList.destroy();
this.reset();
}
this._dynamicList = new MegaDynamicList(this.scrollDiv, {
'contentContainerClasses': 'fm-recents content',
'initialScrollY': this._initScrollPosition,
'itemRenderFunction': function(id) { return self._doRenderWorker(id); },
'itemHeightCallback': function(id) { return self._getItemHeight(id); },
'onNodeInjected': function() { return self._onNodeInjected(); },
'onResize': function() { return self.thottledResize(); },
'onScroll': function() { return self.onScroll(); },
'perfectScrollOptions': {
'handlers': ['click-rail', 'drag-scrollbar', 'wheel', 'touch'],
'minScrollbarLength': 20
}
});
if (!actions[0].id) {
this._fillActionIds(actions);
}
this._view = [];
var keys = [];
for (var i = 0; i < actions.length; i++) {
keys.push(actions[i].id);
this.actionIdMap[actions[i].id] = actions[i];
if (actions[i].length && actions[i].length > 0) {
this._view = this._view.concat(actions[i]);
this._populateNodeActionMap(actions[i]);
}
}
M.v = this._view;
this._dynamicList.batchAdd(keys);
this._dynamicList.initialRender();
this._rendered = true;
this._initScrollPosition = false;
}
self.previousActionCount = actions.length;
self.previousNodeCount = actions.nodeCount;
};
RecentsRender.prototype._doRenderWorker = function(id) {
'use strict';
if (!this._renderCache[id]) {
if (this._renderFunctions[id]) {
this._renderCache[id] = this._renderFunctions[id](id);
} else {
var action = this.actionIdMap[id];
if (action.type === "date") {
var $newTitleDiv = this._$titleTemplate.clone().removeClass("template title-template");
$newTitleDiv.text(action.date);
this._renderCache[id] = $newTitleDiv[0];
} else {
this._renderCache[id] = this.generateRow(action, id)[0];
}
}
}
return this._renderCache[id];
};
RecentsRender.prototype._getItemHeight = function(id) {
'use strict';
var h;
if (this._childIds[id]) {
h = 49;
} else if (this._renderCache[id]) {
h = this._renderCache[id].offsetHeight;
} else {
var a = this.actionIdMap[id];
if (a.type === "date") {
h = 62;
} else if (a.type === "media" && a.length > this.getMaxFitOnScreen()) {
h = 254;
} else if (a.type === "media" && a.length > 1) {
h = 219;
} else if (a.length > 1) {
h = 66;
} else {
h = 49;
}
}
return h;
};
RecentsRender.prototype._onNodeInjected = function() {
'use strict';
delay('recentsThumbnails', function() {
fm_thumbnails();
}, 75);
};
/**
* Inject the date titles into the actions array before passing to dynamicList.
* @private
*/
RecentsRender.prototype._injectDates = function(actions) {
'use strict';
var lastSeenDate = false;
for (var i = 0; i < actions.length; i++) {
var action = actions[i];
if (action.date !== lastSeenDate) {
actions.splice(i, 0, {
type: "date",
date: action.date,
ts: moment.unix(action.ts).endOf('day')._d.getTime() / 1000
});
lastSeenDate = action.date;
}
}
return actions;
};
/**
* Mark UI elements as selected.
* Note: Call with no arguments to clear selection.
*/
RecentsRender.prototype.markSelected = function () {
'use strict';
this.clearSelected();
this.appendSelected.apply(this, arguments);
};
RecentsRender.prototype.appendSelected = function() {
'use strict';
for (var i = 0; i < arguments.length; i++) {
$(arguments[i]).addClass('ui-selected');
}
};
RecentsRender.prototype.clearSelected = function() {
'use strict';
this.$container.find('.ui-selected').removeClass('ui-selected');
};
/**
* Generate a breadcrumb based off array of partPart (or node) objects.
* @param $container
* @param action
*/
RecentsRender.prototype.populateBreadCrumb = function($container, action) {
'use strict';
var self = this;
var newBreadCrumb = function(node) {
var $breadCrumb = $('');
$breadCrumb
.attr('id', node.h)
.text(node.name)
.rebind('click dblclick', function () {
M.openFolder(node.h);
return false;
})
.rebind("contextmenu", function(e) {
self.markSelected($breadCrumb, $breadCrumb.closest('.content-row'));
selectionManager.clear_selection();
selectionManager.add_to_selection(node.h);
$.hideTopMenu();
return M.contextMenuUI(e, 1) ? true : false;
});
return $breadCrumb;
};
for (var k = action.path.length - 1; k >= 1; k--) {
$container.append(newBreadCrumb(action.path[k]));
$container.append('');
}
$container.append(newBreadCrumb(action.path[0]));
};
/**
* Populate, enable and attach event listeners to the `by ` parts of the template.
* @param $newRow
* @param action
*/
RecentsRender.prototype.handleByUserHandle = function($newRow, action) {
'use strict';
var self = this;
var user = M.getUserByHandle(action.user);
var $userNameContainer = $newRow.find(".file-name .action-user-name");
$userNameContainer
.removeClass("hidden")
.text(user.name)
.attr('id', user.h)
.rebind("contextmenu", function(e) {
self.markSelected($userNameContainer, $newRow);
selectionManager.clear_selection();
selectionManager.add_to_selection(user.h);
$.hideTopMenu();
return M.contextMenuUI(e, 1) ? true : false;
})
.rebind("click", function(e) {
$userNameContainer.trigger({
type: 'contextmenu',
originalEvent: e.originalEvent
});
return false;
})
.rebind("dblclick", function() {
M.openFolder(user.h);
return false;
});
};
/**
* Handle In/Out share actions for a new row
* @param $newRow
* @param action
*/
RecentsRender.prototype.handleInOutShareState = function($newRow, action) {
'use strict';
$newRow.find(".transfer-filetype-icon")
.removeClass('hidden')
.addClass(action.outshare ? "folder-shared" : "inbound-share");
$newRow.find(".in-out-tooltip span")
.text(action.outshare ? l[5543] : l[5542]);
};
/**
* Get the max number of image thumbs that will fit on the screen horizontally.
* @param force Calulation is cached, use this to force recalculate.
* @returns {int}
*/
RecentsRender.prototype.getMaxFitOnScreen = function(force) {
'use strict';
if (!this._maxFitOnScreen || force) {
this._maxFitOnScreen = Math.ceil(this.$container.width() / 170) || 2;
}
return this._maxFitOnScreen;
};
/**
* Generate a new action row.
* @param action
* @param actionId
* @returns {*|Autolinker.HtmlTag}
*/
RecentsRender.prototype.generateRow = function (action, actionId) {
'use strict';
var self = this;
// Get correct template.
var $newRow;
if (action.type === "media" && action.length > 1) {
$newRow = self.getTemplate("images-content-row-template").removeClass("template");
} else {
$newRow = self.getTemplate("content-row-template").removeClass("template");
}
// Attach unique class & data attributes for this action.
if (actionId !== undefined) {
$newRow.addClass("action-" + actionId).data("action", actionId);
}
// Populate breadcrumb path
this.populateBreadCrumb($newRow.find(".breadcrumbs"), action);
// Render the date/time views.
var date = new Date(action.ts * 1000 || 0);
$newRow.find(".file-data .time").text(this._shortTimeFormatter.format(date));
$newRow.find(".file-data .uploaded-on-message.dark-direct-tooltip span").text(
(action.action !== "added" ? l[19942] : l[19941])
.replace('%1', acc_time2date(action.ts, true))
.replace('%2', this._fullTimeFormatter.format(date))
);
// Render in/out share icons.
if (action.outshare || action.inshare) {
self.handleInOutShareState($newRow, action);
}
// Show correct icon for action.
if (action.action !== 'added') {
$newRow.find(".action-icon.tiny-icon").removeClass("top-arrow").addClass("refresh");
}
if (action.type === "media" && action.length > 1) {
this._renderMedia($newRow, action, actionId);
} else {
$newRow.attr('id', action[0].h);
this._renderFiles($newRow, action, actionId);
}
// Show by user if not current user.
if (action.user !== u_handle) {
self.handleByUserHandle($newRow, action);
}
return $newRow;
};
/**
* Render Files Block
* @param $newRow
* @param action
* @param actionId
* @private
*/
RecentsRender.prototype._renderFiles = function($newRow, action, actionId) {
'use strict';
var self = this;
var isCreated = action.action === "added";
var isOtherUser = action.user !== u_handle;
// handle icon
var $icon = $newRow.find(".medium-file-icon");
$icon.addClass(fileIcon(action[0]));
// handle filename.
var $fileName = $newRow.find(".file-name");
var titleString;
var isMore = action.length > 1;
if (isOtherUser) {
if (isCreated) {
if (isMore) {
titleString = l[19936];
} else {
titleString = l[19937];
}
} else {
if (isMore) {
titleString = l[19939];
} else {
titleString = l[19940];
}
}
} else {
if (isMore) {
titleString = l[19938];
} else {
titleString = '%1';
}
}
titleString = titleString
.replace("%1", '')
.replace("%2", action.length - 1)
.replace("%3", '')
.replace("[A]", '')
.replace("[/A]", '');
$fileName.safeHTML(titleString);
var $fileNameContainer = $fileName.find(".title");
$fileNameContainer
.text(action[0].name)
.attr('id', action[0].h)
.rebind('click', function(e) {
self.markSelected();
$.hideContextMenu();
if (is_image(action[0]) || is_video(action[0]) === 1) {
if (is_video(action[0])) {
$.autoplay = action[0].h;
}
slideshow(action[0].h);
} else {
$fileNameContainer.trigger({
type: 'contextmenu',
originalEvent: e.originalEvent
});
}
return false;
});
// If more than 1 file in action.
if (isMore) {
action.createEmptyClone = function() {
var clone = [];
clone.action = this.action;
clone.ts = this.ts;
clone.date = this.date;
clone.path = this.path;
clone.user = this.user;
clone.recent = this.recent;
if (this.inshare) {
clone.inshare = this.inshare;
}
if (this.outshare) {
clone.outshare = this.outshare;
}
return clone;
};
var $moreLessToggle = $fileName.find(".more-less-toggle");
var expandedIds = [];
// Use a render function to delay the rendering of a child node till it is in view.
var generateRenderFunction = function (i, id) {
return function () {
if (!self._renderCache[id]) {
var nodeAction = action.createEmptyClone();
var node = action[i];
nodeAction.ts = node.ts;
nodeAction.push(node);
var $newChildAction = self.generateRow(nodeAction);
$newChildAction.addClass("action-" + actionId + "-child");
if (i === action.length - 1) {
$newChildAction.addClass('last-child');
}
self._renderCache[id] = $newChildAction[0];
}
return self._renderCache[id];
};
};
var expandCollapseHelper = function() {
self.markSelected();
$.hideContextMenu();
if ($moreLessToggle.hasClass('less')) {
self._expandedStates[actionId] = false;
$newRow.removeClass('expanded').addClass("collapsed");
$moreLessToggle.removeClass("less").addClass("more");
self._dynamicList.remove(expandedIds, false);
self._dynamicList.itemRenderChanged(actionId);
delete self._actionChildren[actionId];
expandedIds = [];
} else {
// Render new action views.
self._expandedStates[actionId] = true;
$newRow.removeClass("collapsed").addClass("expanded");
expandedIds = [];
for (var i = 1; i < action.length; i++) {
var id = actionId + ":" + i;
self._nodeRenderedItemIdMap[action[i].h] = id;
self._renderFunctions[id] = generateRenderFunction(i, id);
self._childIds[id] = true;
expandedIds.push(id);
}
$moreLessToggle.removeClass("more").addClass("less");
self._dynamicList.insert(actionId, expandedIds, false);
self._dynamicList.itemRenderChanged(actionId);
self._actionChildren[actionId] = expandedIds;
}
};
$moreLessToggle.rebind('click', function() {
expandCollapseHelper();
return false;
})
.rebind("dblclick", function() {
return false;
});
$newRow.removeClass("single").addClass("group collapsed");
}
$newRow
.rebind("contextmenu", function(e) {
if (selectionManager.selected_list.indexOf(action[0].h) === -1) {
selectionManager.clear_selection();
selectionManager.add_to_selection(action[0].h);
self.markSelected($newRow);
}
return M.contextMenuUI(e, 1) ? true : false;
})
.rebind('click', function(e) {
return self._handleSelectionClick(e, action[0].h, $newRow);
});
var $contextMenuButton = $newRow.find(".context-menu-button");
$contextMenuButton
.attr('id', action[0].h)
.rebind("click", function (e) {
$contextMenuButton.trigger({
type: 'contextmenu',
originalEvent: e.originalEvent
});
return false;
})
.rebind("dblclick", function() {
return false;
})
.rebind("contextmenu", function(e) {
self.markSelected($newRow);
selectionManager.clear_selection();
selectionManager.add_to_selection(action[0].h);
$.hideTopMenu();
return M.contextMenuUI(e, 1) ? true : false;
});
};
/**
* Render Media Block
* @param $newRow
* @param action
* @private
*/
RecentsRender.prototype._renderMedia = function($newRow, action, actionId) {
'use strict';
var self = this;
var isCreated = action.action === "added";
var isOtherUser = action.user !== u_handle;
var $previewBody = $newRow.find(".previews-body");
var $thumbTemplate = $previewBody.find(".data-block-view.template");
var maxFitOnScreen = self.getMaxFitOnScreen();
var imagesToRender = action.length;
// Maintain the index of images that we have rendered.
var renderedIndex = 0;
var renderedThumbs = [];
var mediaCounts = self._countMedia(action);
var videos = mediaCounts.videos;
var images = mediaCounts.images;
// Create & append new image container, fire async method to collect thumbnail.
var renderThumb = function(i) {
return new Promise(function (resolve) {
var $newThumb = $thumbTemplate.clone().removeClass("template");
var node = action[i];
$newThumb
.attr('id', node.h)
.attr('title', node.name)
.rebind('dblclick', function() {
self.markSelected();
$.hideContextMenu();
slideshow(node.h);
$.autoplay = node.h;
return false;
})
.rebind('click', function (e) {
return self._handleSelectionClick(e, node.h, [$newThumb, $newRow]);
})
.rebind('contextmenu', function(e) {
if (selectionManager.selected_list.indexOf(node.h) === -1) {
selectionManager.clear_selection();
self.clearSelected();
}
self.appendSelected($newThumb, $newRow);
selectionManager.add_to_selection(node.h);
$.hideTopMenu();
return M.contextMenuUI(e, 1) ? true : false;
});
if (M.d[node.h]) {
M.d[node.h].seen = true;
}
node.seen = true;
if (is_video(node)) {
$newThumb.find(".block-view-file-type").removeClass("image").addClass("video");
$newThumb.find(".data-block-bg").addClass("video");
node = MediaAttribute(node, node.k);
if (node && node.data && node.data.playtime) {
$newThumb.find('.video-thumb-details span').text(secondsToTimeShort(node.data.playtime));
}
}
var $contextMenuHandle = $newThumb.find(".file-settings-icon");
$contextMenuHandle
.attr('id', node.h)
.rebind("contextmenu", function(e) {
self.markSelected($newThumb, $newRow);
selectionManager.clear_selection();
selectionManager.add_to_selection(node.h);
$.hideTopMenu();
return M.contextMenuUI(e, 1) ? true : false;
})
.rebind('click', function(e) {
$contextMenuHandle.trigger({
type: 'contextmenu',
originalEvent: e.originalEvent
});
});
$previewBody.append($newThumb);
renderedThumbs[i] = $newThumb;
resolve($newThumb);
});
};
var $toggleExpandedButton = $newRow.find(".toggle-expanded-state");
var $toggleExpandedButtonText = $toggleExpandedButton.find("span");
var $toggleExpandedButtonIcon = $toggleExpandedButton.find("i");
var $previewsScroll = $newRow.find(".previews-scroll");
// If there are more images than we can fit onto the initial screen size.
if (action.length >= maxFitOnScreen) {
imagesToRender = maxFitOnScreen;
$toggleExpandedButton.removeClass('hidden');
}
var toggleOpenState = function() {
if ($previewsScroll.hasClass('expanded')) {
self._expandedStates[actionId] = false;
$previewsScroll.removeClass('expanded');
$toggleExpandedButtonText.text(l[19797]);
$toggleExpandedButtonIcon.removeClass("bold-crossed-eye").addClass("bold-eye");
// Mark thumbs that are no longer viewable as hidden.
for (var i = maxFitOnScreen; i < renderedIndex; i++) {
if (renderedThumbs[i]) {
renderedThumbs[i].addClass('hidden');
}
}
} else {
if (action.length >= maxFitOnScreen) {
self._expandedStates[actionId] = true;
$previewsScroll.addClass('expanded');
$toggleExpandedButtonText.text(l[19963]);
$toggleExpandedButtonIcon.removeClass("bold-eye").addClass("bold-crossed-eye");
$previewsScroll.children().removeClass('hidden');
// Inject the rest of the images that were not loaded initially.
for (;renderedIndex < action.length; renderedIndex++) {
renderThumb(renderedIndex);
}
fm_thumbnails();
}
}
self._dynamicList.itemRenderChanged(actionId);
return false;
};
$toggleExpandedButton.rebind('click', function() {
toggleOpenState();
return false;
});
// render inital image containers.
for (renderedIndex = 0; renderedIndex < imagesToRender; renderedIndex++) {
renderThumb(renderedIndex);
}
// Set title based on content.
var $title = $newRow.find(".file-name");
var $titleString;
var makeTitle = function (selfString, createdByString, modifiedByString) {
if (isOtherUser) {
if (isCreated) {
$titleString = createdByString;
} else {
$titleString = modifiedByString;
}
$titleString = $titleString
.replace("%3", '')
.replace("[A]", '')
.replace("[/A]", '');
} else {
$titleString = '' + selfString + '';
}
return $titleString.replace("%1", images).replace("%2", videos);
};
if (images > 1 && videos > 1) {
$titleString = makeTitle(l[19947], l[19945], l[19946]);
}
else if (images > 1 && videos === 1) {
$titleString = makeTitle(l[19948], l[19949], l[19950]);
}
else if (images === 1 && videos > 1) {
$titleString = makeTitle(l[19951], l[19952], l[19953]);
}
else if (images === 1 && videos === 1) {
$titleString = makeTitle(l[19954], l[19955], l[19956]);
}
else if (images > 0) {
$titleString = makeTitle(l[19960], l[19961], l[19962]);
}
else if (videos > 0) {
$titleString = makeTitle(l[19957], l[19958], l[19959]);
}
$title.safeHTML($titleString);
// Attach title click to open folder.
$title.find("span.title").on('click', function() {
toggleOpenState();
return false;
})
.rebind("dblclick", function() {
return false;
});
// Set the media block icons according to media content.
var $rearIcon = $newRow.find(".medium-file-icon.double");
var $frontIcon = $newRow.find(".medium-file-icon.double .medium-file-icon");
if (images === 0) {
$frontIcon.removeClass("image").addClass("video");
}
if (videos > 0) {
$rearIcon.removeClass("image").addClass("video");
}
// Attach resize listener to the image block.
self._resizeListeners.push(function() {
var newMax = self.getMaxFitOnScreen();
// Render new more images if we can now fit more on the screen.
if (newMax > maxFitOnScreen) {
for (; renderedIndex < newMax && renderedIndex < action.length; renderedIndex++) {
renderThumb(renderedIndex);
}
}
maxFitOnScreen = newMax;
// Enable/disable showall button if resize makes appropriate.
if (newMax < action.length && !$previewsScroll.hasClass('.expanded')) {
$toggleExpandedButton.removeClass("hidden");
} else if (newMax > action.length) {
$toggleExpandedButton.addClass("hidden");
}
});
$newRow.rebind("contextmenu", function(e) {
self.markSelected($newRow);
selectionManager.clear_selection();
for (var i = 0; i < action.length; i++) {
selectionManager.add_to_selection(action[i].h);
}
$.hideTopMenu();
return M.contextMenuUI(e, 3) ? true : false;
});
var $contextMenuButton = $newRow.find(".context-menu-button");
$contextMenuButton
.rebind("click", function (e) {
$contextMenuButton.trigger({
type: 'contextmenu',
originalEvent: e.originalEvent
});
return false;
})
.rebind("dblclick", function() {
return false;
})
.rebind("contextmenu", function(e) {
self.markSelected($newRow);
selectionManager.clear_selection();
for (var i = 0; i < action.length; i++) {
selectionManager.add_to_selection(action[i].h);
}
$.hideTopMenu();
return M.contextMenuUI(e, 3) ? true : false;
});
// Remove the template that we no longer need.
$thumbTemplate.remove();
};
/**
* Get a new instance of a template.
* @param className
* @returns {jQuery}
*/
RecentsRender.prototype.getTemplate = function(className) {
'use strict';
return this.$container.find(".template." + className).clone().removeClass(className);
};
/**
* Generate count of images/videos in action block.
* @param action
* @private
*/
RecentsRender.prototype._countMedia = function(action) {
'use strict';
var counts = {
images: 0,
videos: 0
};
for (var nodeIndex = 0; nodeIndex < action.length; nodeIndex++) {
if (is_image(action[nodeIndex])) {
counts.images += 1;
} else {
counts.videos += 1;
}
}
return counts;
};
/**
* Reset internal variables before reiniting.
*/
RecentsRender.prototype.reset = function() {
'use strict';
var renderCacheIds = Object.keys(this._renderCache);
for (var i = 0; i < renderCacheIds.length; i++) {
var id = renderCacheIds[i];
$(this._renderCache[id]).remove();
delete this._renderCache[id];
}
this._rendered = false;
this._resizeListeners = [];
this._renderCache = {};
this._childIds = {};
this._renderFunctions = {};
this._view = [];
this._nodeActionMap = {};
if (this._dynamicList) {
this._dynamicList.destroy();
this._dynamicList = false;
}
};
/**
* Cleanup function, should be triggered when moving to another section of the webclient.
*/
RecentsRender.prototype.cleanup = function() {
'use strict';
if (this._dynamicList && this._dynamicList.active) {
this._dynamicList.pause();
}
};
/**
* Triggered on resize after a thottle control.
* @private
*/
RecentsRender.prototype._onResize = function() {
'use strict';
this.getMaxFitOnScreen(true);
if (d) {
console.time("recents.resizeListeners");
}
for (var i = 0; i < this._resizeListeners.length; i++) {
this._resizeListeners[i]();
}
fm_thumbnails();
if (d) {
console.timeEnd("recents.resizeListeners");
}
};
RecentsRender.prototype.thottledResize = function() {
'use strict';
var self = this;
delay('recents.resizeListener', function() {
self._onResize();
}, 100);
};
/**
* Triggered when the list scrolls.
*/
RecentsRender.prototype.onScroll = function() {
'use strict';
$.hideContextMenu();
notify.closePopup();
};
/**
* Helper function to add items to the selection based on common key shortcuts.
* @param e
* @param handle
* @param $element
* @returns boolean
* @private
*/
RecentsRender.prototype._handleSelectionClick = function(e, handle, $element) {
'use strict';
$.hideContextMenu();
if (e.ctrlKey !== false || e.metaKey !== false) {
this.appendSelected.apply(this, $element);
selectionManager.add_to_selection(handle);
}
else {
this.markSelected.apply(this, $element);
selectionManager.clear_selection();
selectionManager.add_to_selection(handle);
}
return false;
};
/**
* Trigger for when a single node gets changes (renamed, etc).
* This will attempt to re-redner the action that houses the node.
* For large changes, like moving the file, the RecentsRender.updateState() should be called instead.
*
* @param handle
*/
RecentsRender.prototype.nodeChanged = function(handle) {
'use strict';
if (handle && M.d[handle] && this._nodeActionMap[handle] && this._dynamicList) {
var actionId = this._nodeActionMap[handle];
var action = this.actionIdMap[actionId];
if (action) {
var renderedItemId = this._nodeRenderedItemIdMap[handle] || actionId;
// Remove any cached rendering.
if (this._renderCache[renderedItemId]) {
delete this._renderCache[renderedItemId];
}
var i;
// Get the new node state.
var currentNode = M.d[handle];
currentNode.recent = true;
delete currentNode.ar;
// Update the internal list.
for (i = 0; i < action.length; i++) {
if (action[i].h === handle) {
action[i] = currentNode;
break;
}
}
// Update the view.
for (i = 0; i < this._view.length; i++) {
if (this._view[i].h === handle) {
this._view[i] = currentNode;
break;
}
}
M.v = this._view;
if (!this._updateNodeName(currentNode)) {
this._dynamicList.itemChanged(renderedItemId);
}
}
} else if (this._dynamicList.active) {
this.updateState();
}
};
/**
* Generate a unique id for this action based on its contents.
* @param action
* @private
*/
RecentsRender.prototype._generateId = function(action) {
'use strict';
var idString;
if ($.isArray(action) && action.length > 0) {
var handleAppend = function(summary, node) {
return summary + node.h;
};
var pathString = action.path.reduce(handleAppend, "");
idString = action.reduce(handleAppend, "recents_" + pathString);
} else if (action.type === "date") {
idString = "date_" + action.ts;
}
return fastHashFunction(idString);
};
/**
* Generate IDS for all the actions provided.
* @param actions
* @private
*/
RecentsRender.prototype._fillActionIds = function(actions) {
'use strict';
for (var i = 0; i < actions.length; i++) {
actions[i].id = this._generateId(actions[i]);
}
};
/**
* Update state with changes from new actions list.
* Computes a diff against the current content to find actions that need to be inserted / removed and does so.
* Will only re-render the actions that has been updated.
*
* @param actions
* @private
*/
RecentsRender.prototype._updateState = function(actions) {
'use strict';
if (this.previousActionCount === 0 || actions.length === 0) {
this.reset();
this._initialRender(actions);
return;
}
var removed = [];
var added = [];
var newActionIdMap = {};
var i;
var k;
var action;
var stateChanged = false;
this._injectDates(actions);
this._fillActionIds(actions);
this._firstItemPosition = this._dynamicList.getFirstItemPosition();
// Scan for added nodes
for (i = 0; i < actions.length; i++) {
action = actions[i];
newActionIdMap[action.id] = action;
if (this.actionIdMap[action.id] === undefined) {
this.actionIdMap[action.id] = action;
added.push(action);
stateChanged = true;
}
}
// Scan and remove nodes no longer present in newActions.
for (i = 0; i < this.recentActions.length; i++) {
action = this.recentActions[i];
if (newActionIdMap[action.id] === undefined) {
removed.push(action.id);
delete this._renderCache[action.id];
delete this._renderFunctions[action.id];
delete this.actionIdMap[action.id];
this._dynamicList.remove(action.id);
if (this._actionChildren[action.id]) {
for (k = 0; k < this._actionChildren[action.id].length; k++) {
this._dynamicList.remove(this._actionChildren[action.id][k]);
delete this._renderCache[this._actionChildren[action.id][k]];
delete this._renderFunctions[this._actionChildren[action.id][k]];
}
delete this._actionChildren[action.id];
}
stateChanged = true;
}
}
if (stateChanged) {
this._applyStateChange(added, removed);
}
};
/**
* Apply the state changes.
* @param added
* @param removed
* @private
*/
RecentsRender.prototype._applyStateChange = function(added, removed) {
'use strict';
var action;
var i;
var k;
var after;
var pos;
// Make changes to internal list of recentActions.
var actions = this.recentActions.filter(function(item) {
return removed.indexOf(item.id) === -1;
});
// Inject new actions.
var handled = 0;
i = 0;
while (i < actions.length && handled < added.length) {
action = actions[i];
if (added[handled].ts > action.ts) {
pos = i;
if (pos === 0) {
after = null;
} else {
after = actions[pos - 1].id;
if (this._actionChildren[after]) {
after = this._actionChildren[after][this._actionChildren[after].length - 1];
}
}
actions.splice(pos, 0, added[handled]);
this._populateNodeActionMap(added[handled]);
this._dynamicList.insert(after, added[handled].id);
handled++;
}
i++;
}
for (k = handled; k < added.length; k++ && i++) {
pos = actions.length;
if (pos === 0) {
after = null;
} else {
after = actions[pos - 1].id;
if (this._actionChildren[after]) {
after = this._actionChildren[after][this._actionChildren[after].length - 1];
}
}
actions.splice(pos, 0, added[handled]);
this._populateNodeActionMap(added[handled]);
this._dynamicList.insert(after, added[k].id);
}
if (removed.length > 0) {
this._removeConsecutiveDates(actions);
}
if (this._firstItemPosition !== undefined) {
this._dynamicList.scrollToItemPosition(this._firstItemPosition);
delete this._firstItemPosition;
}
// Update M.v
this._view = [];
for (i = 0; i < actions.length; i++) {
if ($.isArray(actions[i])) {
Array.prototype.push.apply(this._view, actions[i]);
}
}
this.recentActions = actions;
M.v = this._view;
};
/**
* Add action nodes to maps.
* @param action
* @private
*/
RecentsRender.prototype._populateNodeActionMap = function(action) {
'use strict';
if ($.isArray(action)) {
for (var k = 0; k < action.length; k++) {
this._nodeActionMap[action[k].h] = action.id;
}
}
};
/**
* Remove consecutive dates from actions list.
* @param actions
* @private
*/
RecentsRender.prototype._removeConsecutiveDates = function(actions) {
'use strict';
// Remove duplicating dates.
for (i = 0; i < actions.length; i++) {
if (actions[i].type === "date" && i + 1 < actions.length && actions[i + 1].type === "date") {
var id = actions[i].id;
delete this._renderCache[id];
delete this._renderFunctions[id];
delete this.actionIdMap[id];
this._dynamicList.remove(id);
actions.splice(i, 1);
}
}
};
/**
* Trigger when content changes while the recents page is open.
* Thottles the _updateState function.
*/
RecentsRender.prototype.updateState = function() {
'use strict';
var self = this;
delay('recents.updateState', function() {
self.render();
}, 500);
};
/**
* Update the name of a rendered node.
* @param node
* @returns boolean if update was handled.
* @private
*/
RecentsRender.prototype._updateNodeName = function(node) {
'use strict';
var $renderdItem = $("#" + node.h);
if ($renderdItem.length > 0) {
if ($renderdItem.hasClass("data-block-view")) {
$renderdItem.attr('title', node.name);
return true;
}
else if ($renderdItem.hasClass("content-row")) {
$renderdItem.find(".first-node-name").text(node.name);
return true;
}
}
return false;
};
function accountUI() {
"use strict";
// Prevent ephemeral session to access account settings via url
if (u_type === 0) {
msgDialog('confirmation', l[998], l[17146]
+ ' ' + l[999], l[1000], function(e) {
if (e) {
loadSubPage('register');
return false;
}
loadSubPage('fm');
});
return false;
}
$('.fm-account-notifications').removeClass('hidden');
$('.fm-account-button').removeClass('active');
$('.fm-account-sections').addClass('hidden');
$('.fm-right-files-block, .section.conversations, .fm-right-block.dashboard').addClass('hidden');
$('.fm-right-account-block').removeClass('hidden');
$('.nw-fm-left-icon').removeClass('active');
$('.nw-fm-left-icon.settings').addClass('active');
$('.account.data-block.storage-data').removeClass('exceeded');
$('.fm-account-save-block').addClass('hidden');
$('.fm-account-save').removeClass('disabled');
if ($('.fmholder').hasClass('transfer-panel-opened')) {
$.transferClose();
}
// Destroy jScrollings in select dropdowns
$('.fm-account-main .default-select-scroll').each(function(i, e) {
$(e).parent().fadeOut(200).parent().removeClass('active');
deleteScrollPanel(e, 'jsp');
});
M.onSectionUIOpen('account');
if (u_attr && u_attr.b && !u_attr.b.m) {
$('.content-panel.account .fm-account-button.slide-in-out.plan').addClass('hidden');
}
else {
$('.content-panel.account .fm-account-button.slide-in-out.plan').removeClass('hidden');
}
M.accountData(accountUI.renderAccountPage, 1);
}
accountUI.renderAccountPage = function(account) {
'use strict';
if (d) {
console.log('Rendering account pages');
}
var id = getSitePath();
if (u_attr && u_attr.b && !u_attr.b.m && id === '/fm/account/plan') {
id = '/fm/account';
}
var sectionClass;
accountUI.general.init(account);
accountUI.inputs.text.init();
var showOrHideBanner = function(sectionName) {
if (u_attr && u_attr.b) {
$('.settings-banner').addClass('hidden');
return;
}
if (sectionName === '/fm/account' || sectionName === '/fm/account/plan'
|| sectionName === '/fm/account/transfers') {
$('.settings-banner').removeClass('hidden');
}
else {
$('.settings-banner').addClass('hidden');
}
};
showOrHideBanner(id);
// Always hide the add-phone banner if it was shown by the account profile sub page
$('.add-phone-num-banner').addClass('hidden');
switch (id) {
case '/fm/account':
$('.fm-account-profile').removeClass('hidden');
sectionClass = 'account-s';
accountUI.account.init(account);
break;
case '/fm/account/plan':
if ($.openAchievemetsDialog) {
delete $.openAchievemetsDialog;
onIdle(function() {
$('.fm-account-plan.fm-account-sections .btn-achievements:visible').trigger('click');
});
}
$('.fm-account-plan').removeClass('hidden');
sectionClass = 'plan';
accountUI.plan.init(account);
break;
case '/fm/account/security':
$('.fm-account-security').removeClass('hidden');
sectionClass = 'security';
M.require('zxcvbn_js');
accountUI.security.init();
break;
case '/fm/account/file-management':
$('.fm-account-file-management').removeClass('hidden');
sectionClass = 'file-management';
accountUI.fileManagement.init(account);
break;
case '/fm/account/transfers':
$('.fm-account-transfers').removeClass('hidden');
sectionClass = 'transfers';
accountUI.transfers.init(account);
break;
case '/fm/account/contact-chats':
$('.fm-account-contact-chats').removeClass('hidden');
sectionClass = 'contact-chats';
accountUI.contactAndChat.init();
break;
case '/fm/account/reseller' /** && M.account.reseller **/:
if (!account.reseller) {
loadSubPage('fm/account');
return false;
}
$('.fm-account-reseller').removeClass('hidden');
sectionClass = 'reseller';
accountUI.reseller.init(account);
break;
case '/fm/account/notifications':
$('.fm-account-notifications').removeClass('hidden');
$('.settings-banner').addClass('hidden');
sectionClass = 'notifications';
accountUI.notifications.init(account);
break;
default:
// This is the main entry point for users who just had upgraded their accounts
if (isNonActivatedAccount()) {
alarm.nonActivatedAccount.render(true);
break;
}
// If user trying to use wrong url within account page, redirect them to account page.
loadSubPage('fm/account');
break;
}
accountUI.leftPane.init(sectionClass);
// Reinitialize Scroll bar
initAccountScroll();
mBroadcaster.sendMessage('settingPageReady');
loadingDialog.hide();
};
accountUI.general = {
init: function(account) {
'use strict';
$.tresizer();
clickURLs();
this.charts.init(account);
this.userUIUpdate();
this.bindEvents();
},
bindEvents: function() {
'use strict';
// Upgrade Account Button
$('.upgrade-to-pro').rebind('click', function() {
if (u_attr && u_attr.b && u_attr.b.m && (u_attr.b.s === -1 || u_attr.b.s === 2)) {
loadSubPage('repay');
}
else {
loadSubPage('pro');
}
});
},
/**
* Helper function to fill common charts into the dashboard and account sections
* @param {Object} account User account data (I.e. same as M.account)
* @param {Boolean} [onDashboard] Whether invoked from the dashboard
*/
charts: {
perc_c_s : 0,
perc_c_b : 0,
init: function(account, onDashboard) {
'use strict';
this.bandwidthChart(account);
this.usedStorageChart(account);
this.chartWarningNoti(onDashboard);
},
bandwidthChart: function(account) {
'use strict';
/* New Used Bandwidth chart */
this.perc_c_b = account.tfsq.perc > 100 ? 100 : account.tfsq.perc;
var $bandwidthChart = $('.fm-account-blocks.bandwidth');
var deg = 230 * this.perc_c_b / 100;
// Used Bandwidth chart
if (deg <= 180) {
$bandwidthChart.find('.left-chart span').css('transform', 'rotate(' + deg + 'deg)');
$bandwidthChart.find('.right-chart span').removeAttr('style');
}
else {
$bandwidthChart.find('.left-chart span').css('transform', 'rotate(180deg)');
$bandwidthChart.find('.right-chart span').css('transform', 'rotate(' + (deg - 180) + 'deg)');
}
if (this.perc_c_b > 99 || dlmanager.isOverQuota) {
$bandwidthChart.addClass('exceeded');
}
// Maximum bandwidth
var b2 = bytesToSize(account.tfsq.max, 0).split(' ');
var usedB = bytesToSize(account.tfsq.used);
$bandwidthChart.find('.chart.data .size-txt').text(usedB);
$bandwidthChart.find('.chart.data .pecents-txt').text((b2[0]));
$bandwidthChart.find('.chart.data .gb-txt').text((b2[1]));
$bandwidthChart.find('.chart.data .content-txt').text('/');
if ((u_attr.p || account.tfsq.ach) && b2[0] > 0) {
if (this.perc_c_b > 0) {
$bandwidthChart.removeClass('no-percs');
$bandwidthChart.find('.chart.data .perc-txt').text(this.perc_c_b + '%');
}
else {
$bandwidthChart.addClass('no-percs');
}
}
else {
$bandwidthChart.addClass('no-percs');
$bandwidthChart.find('.chart.data > span:not(.size-txt)').text('');
var usedW;
if (usedB[0] === '1') {
usedW = l[17524].toLowerCase().replace('%tq1', '').trim();
}
else if (usedB[0] === '2') {
usedW = l[17525].toLowerCase().replace('%tq2', '').trim();
}
else {
usedW = l[17517].toLowerCase().replace('%tq', '').trim();
}
$bandwidthChart.find('.chart.data .pecents-txt').text(usedW);
}
if (!account.maf) {
$('.fm-right-account-block').removeClass('active-achievements');
}
else {
$('.fm-right-account-block').addClass('active-achievements');
}
/* End of New Used Bandwidth chart */
},
usedStorageChart: function(account) {
'use strict';
/* New Used Storage chart */
var $storageChart = $('.fm-account-blocks.storage');
this.perc_c_s = Math.floor(account.space_used / account.space * 100);
if (this.perc_c_s > 100) {
this.perc_c_s = 100;
}
$storageChart.removeClass('exceeded going-out');
if (this.perc_c_s === 100) {
$storageChart.addClass('exceeded');
}
else if (this.perc_c_s > 80) {
$storageChart.addClass('going-out');
}
var deg = 230 * this.perc_c_s / 100;
// Used space chart
if (deg <= 180) {
$storageChart.find('.left-chart span').css('transform', 'rotate(' + deg + 'deg)');
$storageChart.find('.right-chart span').removeAttr('style');
}
else {
$storageChart.find('.left-chart span').css('transform', 'rotate(180deg)');
$storageChart.find('.right-chart span').css('transform', 'rotate(' + (deg - 180) + 'deg)');
}
// Maximum disk space
var b2 = bytesToSize(account.space, 0).split(' ');
$storageChart.find('.chart.data .pecents-txt').text(b2[0]);
$storageChart.find('.chart.data .gb-txt').text(b2[1]);
$storageChart.find('.chart.data .perc-txt').text(this.perc_c_s + '%');
$storageChart.find('.chart.data .size-txt').text(bytesToSize(account.space_used));
/** End New Used Storage chart */
},
chartWarningNoti: function(onDashboard) {
'use strict';
var b_exceeded = (this.perc_c_t > 99 || dlmanager.isOverQuota) ? true : false;
var s_exceeded = this.perc_c_s === 100 ? true : false;
// Charts warning notifications
var $chartsBlock = $('.account' + (onDashboard ? '.widget.content' : '.data-block.charts'));
$chartsBlock.find('.chart-warning:not(.hidden)').addClass('hidden');
if (b_exceeded && s_exceeded) {
// Bandwidth and Storage quota exceeded
$chartsBlock.find('.chart-warning.storage-and-bandwidth').removeClass('hidden');
}
else if (s_exceeded) {
// Storage quota exceeded
$chartsBlock.find('.chart-warning.storage').removeClass('hidden');
}
else if (b_exceeded) {
// Bandwidth quota exceeded
$chartsBlock.find('.chart-warning.bandwidth').removeClass('hidden');
}
else if (this.perc_c_s > 80) {
// Running out of cloud space
$chartsBlock.find('.chart-warning.out-of-space').removeClass('hidden');
}
if (b_exceeded || s_exceeded || this.perc_c_s > 80) {
$chartsBlock.find('.chart-warning').rebind('click', function() {
loadSubPage('pro');
});
}
/* End of Charts warning notifications */
}
},
/**
* Update user UI (pro plan, avatar, first/last name, email)
*/
userUIUpdate: function() {
'use strict';
// Show Membership plan
$('.small-icon.membership').removeClass('pro1 pro2 pro3 pro4');
if (u_attr.p) {
// LITE/PRO account
var planNum = u_attr.p;
var planText = pro.getProPlanName(planNum);
$('.account.membership-plan').text(planText);
$('.small-icon.membership').addClass('pro' + planNum);
}
else {
$('.account.membership-plan').text(l[435]);
}
// update avatar
$('.fm-account-avatar').safeHTML(useravatar.contact(u_handle, '', 'div', false));
$('.fm-avatar').safeHTML(useravatar.contact(u_handle));
// Show first name or last name
$('.membership-big-txt.name').text(u_attr.fullname);
// Show email address
if (u_attr.email) {
$('.membership-big-txt.email').text(u_attr.email);
}
else {
$('.membership-big-txt.email').hide();
}
},
};
accountUI.controls = {
disableElement: function(element) {
'use strict';
$(element).addClass('disabled').prop('disabled', true);
},
enableElement: function(element) {
'use strict';
$(element).removeClass('disabled').prop('disabled', false);
},
};
accountUI.inputs = {
text: {
init: function() {
'use strict';
var $inputs = $('.fm-account-main input').add('.fm-voucher-popup input');
$inputs.rebind('focus', function() {
$(this).parents('.dialog-input-title-ontop').addClass('active');
});
$inputs.rebind('blur', function() {
if ($(this).val()) {
$(this).parents('.dialog-input-title-ontop').addClass('valued');
}
else {
$(this).parents('.dialog-input-title-ontop').removeClass('valued');
}
$(this).parents('.dialog-input-title-ontop').removeClass('active');
});
$inputs.prev('.title').rebind('click', function() {
if (!$(this).parents('.dialog-input-title-ontop').hasClass('active')) {
$(this).next('input').trigger('focus');
}
});
$inputs.prev('.title').noTransition(function() {
$(this).next('input').trigger('blur');
});
}
},
radio: {
init: function(identifier, $container, currentValue, onChangeCb) {
'use strict';
var self = this;
var $radio = $(identifier, $container);
var $labels = $('.radio-txt', $container);
if (String(currentValue)) {
this.set(identifier, $container, currentValue);
}
$('input', $radio).rebind('click.radio', function() {
var newVal = $(this).val();
self.set(identifier, $container, newVal, onChangeCb);
});
$labels.rebind('click.radioLabel', function() {
$(this).prev(identifier).find('input', $radio).trigger('click');
});
},
set: function(identifier, $container, newVal, onChangeCb) {
'use strict';
var $input = $('input' + identifier + '[value="' + newVal + '"]', $container);
if ($input.is('.disabled')) {
return;
}
$(identifier + '.radioOn', $container).addClass('radioOff').removeClass('radioOn');
$input.removeClass('radioOff').addClass('radioOn').prop('checked', true);
$input.parent().addClass('radioOn').removeClass('radioOff');
if (typeof onChangeCb === 'function') {
onChangeCb(newVal);
}
},
disable: function(value, $container) {
'use strict';
$('input.[value="' + value + '"]', $container).addClass('disabled').prop('disabled', true);
},
enable: function(value, $container) {
'use strict';
$('input.[value="' + value + '"]', $container).removeClass('disabled').prop('disabled', false);
},
},
switch: {
init: function(identifier, $container, currentValue, onChangeCb, onClickCb) {
'use strict';
var self = this;
var $switch = $(identifier, $container);
if ((currentValue && !$switch.hasClass('toggle-on'))
|| (!currentValue && $switch.hasClass('toggle-on'))) {
this.toggle(identifier, $container);
}
Soon(function() {
$('.no-trans-init', $switch).removeClass('no-trans-init');
});
$switch.rebind('click.switch', function() {
var val = $switch.hasClass('toggle-on');
if (typeof onClickCb === 'function') {
onClickCb(val).done(function() {
self.toggle(identifier, $container, onChangeCb);
});
}
else {
self.toggle(identifier, $container, onChangeCb);
}
});
},
toggle: function(identifier, $container, onChangeCb) {
'use strict';
var $switch = $(identifier, $container);
var newVal;
if ($switch.hasClass('toggle-on')) {
$switch.removeClass('toggle-on');
newVal = 0;
}
else {
$switch.addClass('toggle-on');
newVal = 1;
}
if (typeof onChangeCb === 'function') {
onChangeCb(newVal);
}
}
}
};
accountUI.leftPane = {
init: function(sectionClass) {
'use strict';
this.render(sectionClass);
this.bindEvents();
},
render: function(sectionClass) {
'use strict';
$.each($('.fm-account-button'), function() {
var $this = $(this);
if ($this.hasClass(sectionClass)) {
$this.addClass('active');
setTimeout(function() {
$this.removeClass('closed');
}, 600);
}
else {
$this.addClass('closed');
}
if (!$this.is(':has(.sub-title)')) {
$this.find('.settings-menu-arrow').remove();
}
initTreeScroll();
});
if (M.account.reseller) {
// Show reseller button on naviation
$('.fm-account-button.reseller').removeClass('hidden');
}
},
bindEvents: function() {
'use strict';
$('.fm-account-button').rebind('click', function() {
if ($(this).attr('class').indexOf('active') === -1) {
$('.fm-account-main').data('jsp').scrollToY(0, false);
switch (true) {
case $(this).hasClass('account-s'):
loadSubPage('fm/account');
break;
case $(this).hasClass('plan'):
loadSubPage('fm/account/plan');
break;
case $(this).hasClass('notifications'):
loadSubPage('fm/account/notifications');
break;
case $(this).hasClass('security'):
loadSubPage('fm/account/security');
break;
case $(this).hasClass('file-management'):
loadSubPage('fm/account/file-management');
break;
case $(this).hasClass('transfers'):
loadSubPage('fm/account/transfers');
break;
case $(this).hasClass('contact-chats'):
loadSubPage('fm/account/contact-chats');
break;
case $(this).hasClass('reseller'):
loadSubPage('fm/account/reseller');
break;
}
}
});
$('.fm-account-button .settings-menu-arrow').rebind('click', function(e) {
var $button = $(this).parents('.fm-account-button');
if ($button.hasClass('active')) {
return false;
}
e.stopPropagation();
$button.toggleClass('closed');
setTimeout(initTreeScroll, 600);
});
$('.fm-account-button .sub-title').rebind('click', function() {
var $parentBtn = $(this).parents('.fm-account-button');
var $target = $('.data-block.' + $(this).data('scrollto'));
if ($parentBtn.hasClass('active')) {
$('.fm-account-main').data('jsp').scrollToElement($target, true);
}
else {
$parentBtn.trigger('click');
mBroadcaster.once('settingPageReady', function () {
$('.fm-account-main').data('jsp').scrollToElement($target, true, false);
});
}
});
}
};
accountUI.account = {
init: function(account) {
'use strict';
// Profile
this.profiles.resetProfileForm();
this.profiles.renderPhoneBanner();
this.profiles.renderFirstName();
this.profiles.renderLastName();
this.profiles.renderBirth();
this.profiles.renderPhoneDetails();
// if this is a business user, we want to hide some parts in profile page :)
var hideOrViewCancelSection = function(setToHidden) {
if (setToHidden) {
$('.fm-account-main .fm-account-sections .cancel-account-block').addClass('hidden');
$('.content-panel.account .acc-setting-menu-cancel-acc').addClass('hidden');
$('.settings-sub-section.profile #account-firstname').prop('disabled', true);
$('.settings-sub-section.profile #account-lastname').prop('disabled', true);
}
else {
$('.fm-account-main .fm-account-sections .cancel-account-block').removeClass('hidden');
$('.content-panel.account .acc-setting-menu-cancel-acc').removeClass('hidden');
$('.settings-sub-section.profile #account-firstname').prop('disabled', false);
$('.settings-sub-section.profile #account-lastname').prop('disabled', false);
}
};
if (u_attr && u_attr.b) {
$('.fm-account-main .settings-sub-section.profile .acc-setting-country-sec').addClass('hidden');
if (!u_attr.b.m) {
hideOrViewCancelSection(true);
}
else {
hideOrViewCancelSection(false);
}
}
else {
// user can set country only in non-business accounts
$('.fm-account-main .settings-sub-section.profile .acc-setting-country-sec').removeClass('hidden');
this.profiles.renderCountry();
// we allow cancel for only non-business account + master users.
hideOrViewCancelSection(false);
}
this.profiles.bindEvents();
// QR Code
this.qrcode.render(account);
this.qrcode.bindEvents();
// Preference
this.preference.render();
// Cancel Account
this.cancelAccount.bindEvents();
},
profiles: {
/**
* Render a banner at the top of the My Account section for enticing a user to add their phone number
* so that they can get an achievement bonus and link up with their phone contacts that might be on MEGA
*/
renderPhoneBanner: function() {
'use strict';
// Cache selectors
var $addPhoneBanner = $('.add-phone-num-banner');
var $usageBanner = $('.settings-banner');
var $text = $addPhoneBanner.find('.add-phone-text');
var $addPhoneButton = $addPhoneBanner.find('.js-add-phone-button');
var $skipButton = $addPhoneBanner.find('.skip-button');
// If SMS verification enable is not on level 2 (Opt-in and unblock SMS allowed) then do nothing. Or if
// they already have already added a phone number then don't show this banner again. Or if they clicked the
// skip button then don't show the banner.
if (u_attr.flags.smsve !== 2 || typeof u_attr.smsv !== 'undefined' || fmconfig['skipsmsbanner']) {
// If not a business account
if (typeof u_attr.b === 'undefined') {
// Show the standard storage/bandwidth usage banner instead of the phone banner
$usageBanner.removeClass('hidden');
$addPhoneBanner.addClass('hidden');
}
else {
// Otherwise for business account hide both banners
$usageBanner.addClass('hidden');
$addPhoneBanner.addClass('hidden');
}
return false;
}
// On click of the Add Number button load the add phone dialog
$addPhoneButton.rebind('click', function() {
sms.phoneInput.init();
});
// On click of the Skip button, hide the banner and don't show it again
$skipButton.rebind('click', function() {
// Hide the banner
$addPhoneBanner.addClass('hidden');
// Save in fmconfig so it is not shown again on reload or login on different machine
mega.config.set('skipsmsbanner', true);
});
// Set the text for x GB storage and quota
sms.renderAddPhoneText($text);
// Show the phone banner, hide the storage/bandwidth usage banner
$usageBanner.addClass('hidden');
$addPhoneBanner.removeClass('hidden');
},
renderFirstName: function() {
'use strict';
$('#account-firstname').val(u_attr.firstname).trigger('blur');
},
renderLastName: function() {
'use strict';
$('#account-lastname').val(u_attr.lastname).trigger('blur');
},
renderBirth: function () {
'use strict';
// If $.dateTimeFormat['stucture'] is not set, prepare it for birthday
if (!$.dateTimeFormat['structure']) {
$.dateTimeFormat['structure'] = getDateStructure() || 'ymd';
}
// Display only date format that is correct with current locale.
$('.dialog-input-title-ontop.birth').addClass('hidden');
$('.dialog-input-title-ontop.birth.' + $.dateTimeFormat['structure']).removeClass('hidden');
this.renderBirthYear();
this.renderBirthMonth();
this.renderBirthDay();
},
renderBirthYear: function() {
'use strict';
var i = new Date().getFullYear() - 16;
var $input = $('.dialog-input-title-ontop.birth.' + $.dateTimeFormat['structure'] + ' .byear')
.attr('max', i);
if (u_attr.birthyear) {
$input.val(u_attr.birthyear);
}
},
renderBirthMonth: function() {
'use strict';
if (u_attr.birthmonth) {
var $input = $('.dialog-input-title-ontop.birth.' + $.dateTimeFormat['structure'] + ' .bmonth');
$input.val(u_attr.birthmonth);
this.zerofill($input[0]);
}
},
renderBirthDay: function() {
'use strict';
if (u_attr.birthday) {
var $input = $('.dialog-input-title-ontop.birth.' + $.dateTimeFormat['structure'] + ' .bdate');
$input.val(u_attr.birthday);
this.zerofill($input[0]);
}
},
renderCountry: function() {
'use strict';
var html = '';
var sel = '';
var $country = $('.fm-account-main .default-select.country');
$('span', $country).text(l[996]);
var countries = M.getCountries();
for (var country in countries) {
if (!countries.hasOwnProperty(country)) {
continue;
}
if (u_attr.country && country === u_attr.country) {
sel = 'active';
$('span', $country).text(countries[country]);
}
else {
sel = '';
}
html += '
'
+ countries[country]
+ '
';
}
$('.default-select-scroll', $country).safeHTML(html);
// Initialize scrolling. This is to prevent scroll losing bug with action packet.
initSelectScrolling('#account-country .default-select-scroll');
// Bind Dropdowns events
bindDropdownEvents($country, 1, '.fm-account-main');
},
/**
* Show the phone number section if applicable
*/
renderPhoneDetails: function() {
'use strict';
// If SMS Verification Enable is on level 1 (SMS suspended unlock allowed only) and they've verified
// by phone already, show the section and number. Or if SMS Verification Enable is on level 2 (Opt-in SMS
// allowed), then show the section (and number if added, or an Add button).
if ((u_attr.flags.smsve === 1 && typeof u_attr.smsv !== 'undefined') || u_attr.flags.smsve === 2) {
// Cache selectors
var $phoneSettings = $('.fm-account-main .phone-number-settings');
var $text = $phoneSettings.find('.add-phone-text');
var $phoneNumber = $phoneSettings.find('.phone-number');
var $addNumberButton = $phoneSettings.find('.add-number-button');
// If the phone is already added, show that
if (typeof u_attr.smsv !== 'undefined') {
$phoneSettings.addClass('verified');
$phoneNumber.text(u_attr.smsv);
}
else {
// Otherwise set the text for x GB storage and quota
sms.renderAddPhoneText($text);
// On click of the Add Number button load the add phone dialog
$addNumberButton.rebind('click', function() {
sms.phoneInput.init();
});
}
// Show the section
$phoneSettings.removeClass('hidden');
}
},
zerofill: function(elem) {
'use strict';
if (elem.value.length === 1) {
elem.value = '0' + elem.value;
}
},
resetProfileForm: function() {
'use strict';
var $personalInfoBlock = $('.profile-form');
var $saveBlock = $('.fm-account-sections .save-block');
$('input', $personalInfoBlock).val('');
$('.error, .errored', $personalInfoBlock).removeClass('error errored');
$saveBlock.addClass('closed');
},
bindEvents: function() {
'use strict';
// Cache selectors
var self = this;
var $personalInfoBlock = $('.profile-form');
var $birthdayBlock = $('.dialog-input-title-ontop.birth.' + $.dateTimeFormat['structure'],
$personalInfoBlock);
var $firstNameField = $personalInfoBlock.find('#account-firstname');
var $saveBlock = $('.fm-account-sections .save-block');
var $saveButton = $saveBlock.find('.fm-account-save');
// Avatar
$('.fm-account-avatar, .settings-sub-section.avatar .avatar', $personalInfoBlock)
.rebind('click', function() {
avatarDialog();
});
// All profile text inputs
$firstNameField.add('#account-lastname', $personalInfoBlock).add('.byear, .bmonth, .bdate', $birthdayBlock)
.rebind('input.settingsGeneral, change.settingsGeneral', function() {
var $this = $(this);
var $parent = $this.parent();
var errorMsg = l[20960];
var max = parseInt($this.attr('max'));
var min = parseInt($this.attr('min'));
if ($this.is('.byear, .bmonth, .bdate')) {
if (this.value > max) {
$this.addClass('errored');
$parent.addClass('error').find('.error-message').text(errorMsg);
$saveBlock.addClass('closed');
return false;
}
else if (this.value < min) {
$this.addClass('errored');
$parent.addClass('error').find('.error-message').text(errorMsg);
$saveBlock.addClass('closed');
return false;
}
else {
$this.removeClass('errored');
var $erroredInput = $parent.find('.errored');
if ($erroredInput.length){
$($erroredInput[0]).trigger('change');
}
else {
$parent.removeClass('error');
}
}
}
if ($firstNameField.val() && $firstNameField.val().trim().length > 0
&& !$personalInfoBlock.find('.errored').length) {
$saveBlock.removeClass('closed');
}
else {
$saveBlock.addClass('closed');
}
});
$('.byear, .bmonth, .bdate', $birthdayBlock).rebind('keydown.settingsGeneral', function(e) {
var $this = $(this);
var charCode = e.which || e.keyCode; // ff
var $parent = $this.parent();
var max = parseInt($this.attr('max'));
var min = parseInt($this.attr('min'));
if (!e.shiftkey &&
!((charCode >= 48 && charCode <= 57) || (charCode >= 96 && charCode <= 105)) &&
(charCode !== 8 && charCode !== 9 && charCode !== 37 && charCode !== 39)){
e.preventDefault();
}
if (charCode === 38) {
if (!this.value || parseInt(this.value) < parseInt(min)) {
this.value = min;
}
else if (parseInt(this.value) >= parseInt(max)) {
this.value = max;
}
else {
this.value++;
}
$this.removeClass('errored').trigger('change');
$parent.removeClass('error');
self.zerofill(this);
}
if (charCode === 40) {
if (parseInt(this.value) <= parseInt(min)) {
this.value = min;
}
else if (!this.value || parseInt(this.value) > parseInt(max)) {
this.value = max;
}
else {
this.value--;
}
$this.removeClass('errored').trigger('change');
$parent.removeClass('error');
self.zerofill(this);
}
});
$('.bmonth, .bdate', $birthdayBlock).rebind('blur.settingsGeneral', function() {
self.zerofill(this);
});
$('.birth-arrow', $personalInfoBlock).rebind('click', function() {
var $this = $(this);
var $target = $this.parent('.birth-arrow-container').prev('input');
var e = $.Event('keydown.settingsGeneral');
e.which = $this.hasClass('up-control') ? 38 : 40;
$target.trigger(e);
});
$('#account-country .default-dropdown-item', $personalInfoBlock).rebind('click.showSave', function() {
if ($firstNameField.val() && $firstNameField.val().trim().length > 0
&& !$personalInfoBlock.find('.errored').length) {
$saveBlock.removeClass('closed');
}
else {
$saveBlock.addClass('closed');
}
});
$saveButton.rebind('click', function() {
if ($(this).hasClass('disabled')) {
return false;
}
$('.fm-account-avatar').safeHTML(useravatar.contact(u_handle, '', 'div', false));
$('.fm-avatar').safeHTML(useravatar.contact(u_handle));
var checklist = {
firstname: String($('#account-firstname').val() || '').trim(),
lastname: String($('#account-lastname').val() || '').trim(),
birthday: String($('.bdate', $birthdayBlock).val() || ''),
birthmonth: String($('.bmonth', $birthdayBlock).val() || ''),
birthyear: String($('.byear', $birthdayBlock).val() || ''),
country: String($('#account-country .default-dropdown-item.active').attr('data-value') || '')
};
var userAttrRequest = { a: 'up' };
var checkUpdated = function() {
var result = false;
for (var i in checklist) {
if (u_attr[i] === null || u_attr[i] !== checklist[i]) {
// we want also to catch the 'undefined' or null
// and replace with the empty string (or given string)
u_attr[i] = i === 'firstName' ? checklist[i] || 'Nobody' : checklist[i];
userAttrRequest[i] = base64urlencode(to8(u_attr[i]));
result = true;
}
}
return result;
};
if (checkUpdated()) {
api_req(userAttrRequest, {
callback: function (res) {
if (res === u_handle) {
$('.user-name').text(u_attr.name);
showToast('settings', l[7698]);
accountUI.account.profiles.bindEvents();
// update megadrop username for existing megadrop
mega.megadrop.updatePUPUserName(u_attr.fullname);
}
}
});
}
$saveBlock.addClass('closed');
$saveButton.removeClass('disabled');
});
},
},
qrcode: {
$QRSettings: null,
render: function(account) {
'use strict';
this.$QRSettings = $('.qr-settings');
var cutPlace = location.href.indexOf('/fm/');
var myHost = location.href.substr(0, cutPlace) + '/' + account.contactLink;
var QRoptions = {
width: 106,
height: 106,
correctLevel: QRErrorCorrectLevel.H, // high
foreground: '#dc0000',
text: myHost
};
var defaultValue = (account.contactLink && account.contactLink.length);
$('.qr-http-link', this.$QRSettings).text(QRoptions.text);
var $container = $('.enable-qr-container');
if (defaultValue) {
// Render the QR code
$('.account.qr-icon').text('').qrcode(QRoptions);
$('.dialog-feature-toggle.enable-qr', this.$QRSettings).addClass('toggle-on');
$('.access-qr-container').parent().removeClass('closed');
$('.qr-block', this.$QRSettings).removeClass('hidden');
$('.settings-sub-section.enable-qr-container').addClass('border');
}
else {
$('.account.qr-icon').text('');
$('.dialog-feature-toggle.enable-qr', this.$QRSettings).removeClass('toggle-on');
$('.access-qr-container').parent().addClass('closed');
$('.qr-block', this.$QRSettings).addClass('hidden');
$('.settings-sub-section.enable-qr-container').removeClass('border');
}
// Enable QR code
accountUI.inputs.switch.init(
'.enable-qr',
$container,
defaultValue,
function(val) {
if (val) {
$('.access-qr-container').add('.qr-block', self.$QRSettings).parent().removeClass('closed');
$container.addClass('border');
setTimeout(initAccountScroll, 301);
api_req({ a: 'clc' }, {
myAccount: account,
callback: function (res, ctx) {
ctx.myAccount.contactLink = typeof res === 'string' ? 'C!' + res : '';
accountUI.account.qrcode.render(M.account);
}
});
}
else {
$('.access-qr-container').add('.qr-settings .qr-block').parent().addClass('closed');
$container.removeClass('border');
api_req({
a: 'cld',
cl: account.contactLink.substring(2, account.contactLink.length)
}, {
myAccount: account,
callback: function (res, ctx) {
if (res === 0) { // success
ctx.myAccount.contactLink = '';
}
}
});
}
},
function(val) {
var promise = new MegaPromise();
// If it is toggle off, warn user.
if (val) {
msgDialog('confirmation', l[19990], l[20128], l[18229], function (answer) {
if (answer) {
promise.resolve();
}
else {
promise.reject();
}
});
}
else {
// It is toggle on, just proceed;
promise.resolve();
}
return promise;
});
// Automatic accept section
mega.attr.get(u_handle, 'clv', -2, 0).always(function(res) {
accountUI.inputs.switch.init(
'.auto-qr',
$('.access-qr-container'),
parseInt(res),
function(val) {
mega.attr.set('clv', val, -2, 0);
});
});
},
bindEvents: function() {
'use strict';
// Reset Section
$('.reset-qr-label', this.$QRSettings).rebind('click', accountUI.account.qrcode.reset);
// Copy link Section
if (is_extension || M.execCommandUsable()) {
$('.copy-qr-link', this.$QRSettings).removeClass('hidden');
$('.qr-dlg-cpy-lnk', this.$QRSettings).rebind('click', function () {
var links = $.trim($(this).prev('.qr-http-link').text());
var toastTxt = l[7654];
copyToClipboard(links, toastTxt);
});
}
else {
$('.copy-qr-link', this.$QRSettings).addClass('hidden');
}
},
reset: function() {
'use strict';
msgDialog('confirmation', l[18227], l[18228], l[18229], function (regenQR) {
if (regenQR) {
loadingDialog.show();
var delQR = {
a: 'cld',
cl: M.account.contactLink.substring(2, M.account.contactLink.length)
};
var reGenQR = { a: 'clc' };
api_req(delQR, {
callback: function (res) {
if (res === 0) { // success
api_req(reGenQR, {
callback: function (res2) {
if (typeof res2 !== 'string') {
res2 = '';
}
else {
res2 = 'C!' + res2;
}
M.account.contactLink = res2;
accountUI.account.qrcode.render(M.account);
loadingDialog.hide();
}
});
}
else {
loadingDialog.hide();
}
}
});
}
});
}
},
preference: {
render: function() {
'use strict';
// Date/time format setting
accountUI.inputs.radio.init(
'.uidateformat',
$('.uidateformat').parent(),
fmconfig.uidateformat || 0,
function (val) {
showToast('settings', l[16168]);
mega.config.setn('uidateformat', parseInt(val));
}
);
// Font size
accountUI.inputs.radio.init(
'.uifontsize',
$('.uifontsize').parent(),
fmconfig.font_size || 2,
function (val) {
showToast('settings', l[16168]);
$('body').removeClass('fontsize1 fontsize2').addClass('fontsize' + val);
mega.config.setn('font_size', parseInt(val));
}
);
}
},
cancelAccount: {
bindEvents: function() {
'use strict';
// Cancel account button on main Account page
$('.cancel-account').rebind('click', function() {
// Please confirm that all your data will be deleted
var confirmMessage = l[1974];
// Search through their Pro plan purchase history
$(account.purchases).each(function(index, purchaseTransaction) {
// Get payment method name
var paymentMethodId = purchaseTransaction[4];
var paymentMethod = pro.getPaymentGatewayName(paymentMethodId).name;
// If they have paid with iTunes or Google Play in the past
if ((paymentMethod === 'apple') || (paymentMethod === 'google')) {
// Update confirmation message to remind them to cancel iTunes or Google Play
confirmMessage += ' ' + l[8854];
return false;
}
});
/**
* Finalise the account cancellation process
* @param {String|null} twoFactorPin The 2FA PIN code or null if not applicable
*/
var continueCancelAccount = function(twoFactorPin) {
// Prepare the request
var request = { a: 'erm', m: Object(M.u[u_handle]).m, t: 21 };
// If 2FA PIN is set, add it to the request
if (twoFactorPin !== null) {
request.mfa = twoFactorPin;
}
api_req(request, {
callback: function(res) {
loadingDialog.hide();
// Check for invalid 2FA code
if (res === EFAILED || res === EEXPIRED) {
msgDialog('warninga', l[135], l[19216]);
}
// Check for incorrect email
else if (res === ENOENT) {
msgDialog('warningb', l[1513], l[1946]);
}
else if (res === 0) {
handleResetSuccessDialogs('.reset-success', l[735], 'deleteaccount');
}
else {
msgDialog('warningb', l[135], l[200]);
}
}
});
};
// Ask for confirmation
msgDialog('confirmation', l[6181], confirmMessage, false, function(event) {
if (event) {
loadingDialog.show();
// Check if 2FA is enabled on their account
twofactor.isEnabledForAccount(function(result) {
loadingDialog.hide();
// If 2FA is enabled on their account
if (result) {
// Show the verify 2FA dialog to collect the user's PIN
twofactor.verifyActionDialog.init(function(twoFactorPin) {
continueCancelAccount(twoFactorPin);
});
}
else {
continueCancelAccount(null);
}
});
}
});
});
}
}
};
accountUI.plan = {
init: function(account) {
"use strict";
// Plan - Account type
this.accountType.render(account);
this.accountType.bindEvents();
// Plan - Account Balance
this.balance.render(account);
this.balance.bindEvents();
// Plan - History
this.history.renderPurchase(account);
this.history.renderTransaction(account);
this.history.bindEvents(account);
// check if business account
if (u_attr && u_attr.b) {
$('.fm-account-plan.fm-account-sections .acc-storage-space').addClass('hidden');
$('.fm-account-plan.fm-account-sections .acc-bandwidth-vol').addClass('hidden');
$('.fm-account-plan.fm-account-sections .btn-achievements').addClass('hidden');
$('.fm-account-plan.fm-account-sections .data-block.account-balance').addClass('hidden');
$('.content-panel.account .acc-setting-menu-balance-acc').addClass('hidden');
if (!u_attr.b.m || u_attr.b.s !== -1) {
$('.fm-account-plan.fm-account-sections .upgrade-to-pro').addClass('hidden');
}
}
},
accountType: {
render: function(account) {
'use strict';
var renderSubscription = function _renderSubscription() {
// Get the date their subscription will renew
var timestamp = (account.srenew.length > 0) ? account.srenew[0] : 0; // Timestamp e.g. 1493337569
var paymentType = (account.sgw.length > 0) ? account.sgw[0] : ''; // Credit Card etc
var gatewayId = (account.sgwids.length > 0) ? account.sgwids[0] : null; // Gateway ID e.g. 15, etc
// Display the date their subscription will renew if known
if (timestamp > 0) {
var dateString = time2date(timestamp, 2);
// Use format: 14 March 2015 - Credit Card
paymentType = dateString + ' - ' + paymentType;
// Change placeholder 'Expires on' to 'Renews'
$('.account.plan-info.expiry-txt').text(l[6971]);
$('.account.plan-info.expiry').text(paymentType);
}
else {
// Otherwise show nothing
$('.account.plan-info.expiry').text('');
$('.account.plan-info.expiry-txt').text('');
}
var $buttons = $('.account.account-type');
var $cancelSubscriptionButton = $buttons.find('.btn-cancel-sub');
var $achievementsButton = $buttons.find('.btn-achievements');
if (!M.maf){
$achievementsButton.addClass('hidden');
}
// If Apple or Google subscription (see pro.getPaymentGatewayName function for codes)
if ((gatewayId === 2) || (gatewayId === 3)) {
// Tell them they need to cancel their plan off-site and don't show the feedback dialog
$cancelSubscriptionButton.removeClass('hidden').rebind('click', function() {
msgDialog('warninga', l[7179], l[16501]);
});
}
// Otherwise if ECP or Sabadell
else if ((gatewayId === 16) || (gatewayId === 17)) {
// Check if there are any active subscriptions
// ccqns = Credit Card Query Number of Subscriptions
api_req({a: 'ccqns'}, {
callback: function(numOfSubscriptions) {
// If there is an active subscription
if (numOfSubscriptions > 0) {
// Show cancel button and show cancellation dialog
$cancelSubscriptionButton.removeClass('hidden')
.rebind('click', function() {
accountUI.plan.accountType.cancelSubscriptionDialog.init();
});
}
}
});
}
};
if (u_attr.p) {
// LITE/PRO account
var planNum = u_attr.p;
var planText = pro.getProPlanName(planNum);
// if this is p=100 business
if (planNum === 100) {
$('.account.plan-info.accounttype').addClass('business');
$('.fm-account-plan .acc-renew-date-info').removeClass('border');
}
else {
$('.account.plan-info.accounttype').removeClass('business');
$('.fm-account-plan .acc-renew-date-info').addClass('border');
}
// Account type
$('.account.plan-info.accounttype span').text(planText);
$('.small-icon.membership').addClass('pro' + planNum);
// Subscription
if (account.stype === 'S') {
renderSubscription();
}
else if (account.stype === 'O') {
var expiryTimestamp = account.nextplan ? account.nextplan.t : account.expiry;
// one-time or cancelled subscription
$('.account.plan-info.expiry-txt').text(l[987]);
$('.account.plan-info.expiry span').text(time2date(expiryTimestamp, 2));
$('.account.data-block .btn-cancel-sub').addClass('hidden');
}
$('.account.plan-info.bandwidth').parent().removeClass('hidden');
}
else {
// free account:
$('.account.plan-info.accounttype span').text(l[435]);
$('.account.plan-info.expiry').text(l[436]);
$('.btn-cancel-sub').addClass('hidden');
if (account.mxfer) {
$('.account.plan-info.bandwidth').parent().removeClass('hidden');
}
else {
$('.account.plan-info.bandwidth').parent().addClass('hidden');
}
}
/* achievements */
if (!account.maf) {
$('.account.plan-info.storage > span').text(bytesToSize(M.account.space, 0));
$('.account.plan-info.bandwidth > span').text(bytesToSize(M.account.tfsq.max, 0));
$('.account.plan-info .quota-note-container, .account.plan-info .settings-bar, .btn-achievements')
.addClass('hidden');
}
else {
mega.achievem.parseAccountAchievements();
}
},
bindEvents: function() {
"use strict";
$('.btn-achievements').rebind('click', function() {
mega.achievem.achievementsListDialog();
});
},
/**
* Dialog to cancel subscriptions
*/
cancelSubscriptionDialog: {
$backgroundOverlay: null,
$dialog: null,
$dialogSuccess: null,
$accountPageCancelButton: null,
$continueButton: null,
$cancelReason: null,
$expiryTextBlock: null,
$expiryDateBlock: null,
/**
* Initialise the dialog
*/
init: function() {
'use strict';
// Cache some selectors
this.$dialog = $('.cancel-subscription-st1');
this.$dialogSuccess = $('.cancel-subscription-st2');
this.$accountPageCancelButton = $('.btn-cancel-sub');
this.$continueButton = this.$dialog.find('.continue-cancel-subscription');
this.$cancelReason = this.$dialog.find('.cancel-textarea textarea');
this.$backgroundOverlay = $('.fm-dialog-overlay');
this.$expiryTextBlock = $('.account.plan-info.expiry-txt');
this.$expiryDateBlock = $('.account.plan-info.expiry');
// Show the dialog
this.$dialog.removeClass('hidden');
this.$backgroundOverlay.removeClass('hidden').addClass('payment-dialog-overlay');
// Init textarea scrolling
initTextareaScrolling($('.cancel-textarea textarea'), 126);
// Init functionality
this.enableButtonWhenReasonEntered();
this.initSendingReasonToApi();
this.initCloseAndBackButtons();
},
/**
* Close the dialog when either the close or back buttons are clicked
*/
initCloseAndBackButtons: function() {
'use strict';
var self = this;
// Close main dialog
this.$dialog.find('.default-white-button.cancel, .fm-dialog-close').rebind('click', function() {
self.$dialog.addClass('hidden');
self.$backgroundOverlay.addClass('hidden').removeClass('payment-dialog-overlay');
});
// Prevent clicking on the background overlay which closes it unintentionally
self.$backgroundOverlay.rebind('click', function(event) {
event.stopPropagation();
});
},
/**
* Close success dialog
*/
initCloseButtonSuccessDialog: function() {
'use strict';
var self = this;
this.$dialogSuccess.find('.fm-dialog-close').rebind('click', function() {
self.$dialogSuccess.addClass('hidden');
self.$backgroundOverlay.addClass('hidden').removeClass('payment-dialog-overlay');
});
},
/**
* Make sure text has been entered before making the button available
*/
enableButtonWhenReasonEntered: function() {
'use strict';
var self = this;
this.$cancelReason.rebind('keyup', function() {
// Trim for spaces
var reason = $(this).val();
reason = $.trim(reason);
// Make sure at least 1 character
if (reason.length > 0) {
self.$continueButton.removeClass('disabled');
}
else {
self.$continueButton.addClass('disabled');
}
});
},
/**
* Send the cancellation reason
*/
initSendingReasonToApi: function() {
'use strict';
var self = this;
this.$continueButton.rebind('click', function() {
// Get the cancellation reason
var reason = self.$cancelReason.val();
// Hide the dialog and show loading spinner
self.$dialog.addClass('hidden');
self.$backgroundOverlay.addClass('hidden').removeClass('payment-dialog-overlay');
loadingDialog.show();
// Cancel the subscription/s
// cccs = Credit Card Cancel Subscriptions, r = reason
api_req({a: 'cccs', r: reason}, {
callback: function() {
// Hide loading dialog and cancel subscription button on account page, set exiry date
loadingDialog.hide();
self.$accountPageCancelButton.addClass('hidden');
self.$expiryTextBlock.text(l[987]);
self.$expiryDateBlock
.safeHTML('@@',
time2date(account.expiry, 2));
// Show success dialog and refresh UI
self.$dialogSuccess.removeClass('hidden');
self.$backgroundOverlay.removeClass('hidden');
self.$backgroundOverlay.addClass('payment-dialog-overlay');
self.initCloseButtonSuccessDialog();
// Reset account cache so all account data will be refetched
// and re-render the account page UI
M.account.lastupdate = 0;
accountUI();
}
});
});
}
}
},
balance: {
render: function(account) {
"use strict";
$('.account.plan-info.balance span').safeHTML('€ @@', account.balance[0][0]);
},
bindEvents: function() {
"use strict";
var self = this;
$('.redeem-voucher').rebind('click', function() {
var $this = $(this);
if ($this.attr('class').indexOf('active') === -1) {
$('.fm-account-overlay').fadeIn(100);
$this.addClass('active');
$('.fm-voucher-popup').removeClass('hidden');
self.voucherCentering($this);
$(window).rebind('resize.voucher', function() {
self.voucherCentering($this);
});
$('.fm-account-overlay, .fm-purchase-voucher, .fm-voucher-button')
.add('.fm-voucher-popup .fm-dialog-close')
.rebind('click.closeDialog', function() {
$('.fm-account-overlay').fadeOut(100);
$('.redeem-voucher').removeClass('active');
$('.fm-voucher-popup').addClass('hidden');
});
}
else {
$('.fm-account-overlay').fadeOut(200);
$this.removeClass('active');
$('.fm-voucher-popup').addClass('hidden');
$(window).off('resize.voucher');
}
});
$('.fm-voucher-button').rebind('click.voucherBtnClick', function() {
var $input = $('.fm-voucher-body input');
var code = $input.val();
$input.val('');
loadingDialog.show();
$('.fm-voucher-popup').addClass('hidden');
M.require('redeem_js')
.then(function() {
return redeem.redeemVoucher(code);
})
.then(function() {
loadingDialog.hide();
Object(M.account).lastupdate = 0;
onIdle(accountUI);
})
.catch(function(ex) {
loadingDialog.hide();
if (ex) {
msgDialog('warninga', l[135], l[47], ex);
}
});
});
$('.fm-purchase-voucher,.default-white-button.topup').rebind('click', function() {
loadSubPage('resellers');
});
},
voucherCentering: function voucherCentering($button) {
'use strict';
var $popupBlock = $('.fm-voucher-popup');
var popupHeight = $popupBlock.outerHeight();
$popupBlock.css({
'top': $button.offset().top - popupHeight - 10,
'right': $('body').outerWidth() - $button.outerWidth() - $button.offset().left
});
if ($button.offset().top + 10 > popupHeight) {
$popupBlock.css('top', $button.offset().top - popupHeight - 10);
}
else {
$popupBlock.css('top', $button.offset().top + $button.outerHeight() + 10);
}
}
},
history: {
renderPurchase: function(account) {
'use strict';
if (!$.purchaselimit) {
$.purchaselimit = 10;
}
$('.account-history-dropdown-button.purchases').text(l[469].replace('[X]', $.purchaselimit));
$('.account-history-drop-items.purchase10-').text(l[469].replace('[X]', 10));
$('.account-history-drop-items.purchase100-').text(l[469].replace('[X]', 100));
$('.account-history-drop-items.purchase250-').text(l[469].replace('[X]', 250));
M.account.purchases.sort(function(a, b) {
if (a[1] < b[1]) {
return 1;
}
else {
return -1;
}
});
$('.grid-table.purchases tr').remove();
var html = '
' + l[475] + '
' + l[476] +
'
' + l[477] + '
' + l[478] + '
';
if (account.purchases.length) {
// Render every purchase made into Purchase History on Account page
$(account.purchases).each(function(index, purchaseTransaction) {
if (index === $.purchaselimit) {
return false;// Break the loop
}
// Set payment method
var paymentMethodId = purchaseTransaction[4];
var paymentMethod = pro.getPaymentGatewayName(paymentMethodId).displayName;
// Set Date/Time, Item (plan purchased), Amount, Payment Method
var dateTime = time2date(purchaseTransaction[1]);
var price = purchaseTransaction[2];
var proNum = purchaseTransaction[5];
var numOfMonths = purchaseTransaction[6];
var monthWording = (numOfMonths === 1) ? l[931] : 'months'; // Todo: l[6788] when generated
var item = pro.getProPlanName(proNum) + ' (' + numOfMonths + ' ' + monthWording + ')';
// Render table row
html += '
'
+ '
' + dateTime + '
'
+ '
'
+ ''
+ ''
+ ''
+ ' ' + item + ''
+ '
'
+ '
€' + htmlentities(price) + '
'
+ '
' + paymentMethod + '
'
+ '
';
});
}
else {
html += '
' + l[20140] + '
';
}
$('.grid-table.purchases').safeHTML(html);
},
renderTransaction: function(account) {
'use strict';
if (!$.transactionlimit) {
$.transactionlimit = 10;
}
$('.account-history-dropdown-button.transactions').text(l[471].replace('[X]', $.transactionlimit));
$('.account-history-drop-items.transaction10-').text(l[471].replace('[X]', 10));
$('.account-history-drop-items.transaction100-').text(l[471].replace('[X]', 100));
$('.account-history-drop-items.transaction250-').text(l[471].replace('[X]', 250));
M.account.transactions.sort(function(a, b) {
if (a[1] < b[1]) {
return 1;
}
else {
return -1;
}
});
$('.grid-table.transactions tr').remove();
var html = '
' + l[475] + '
' + l[484] +
'
' + l[485] + '
' + l[486] + '
';
if (account.transactions.length) {
$(account.transactions).each(function(i, el) {
if (i === $.transactionlimit) {
return false;
}
var credit = '';
var debit = '';
if (el[2] > 0) {
credit = '€' + htmlentities(el[2]) + '';
}
else {
debit = '€' + htmlentities(el[2]) + '';
}
html += '
' + time2date(el[1]) + '
' + htmlentities(el[0]) + '
'
+ credit + '
' + debit + '
';
});
}
else {
html += '
' + l[20140] + '
';
}
$('.grid-table.transactions').safeHTML(html);
},
bindEvents: function() {
'use strict';
$('.fm-account-plan .account-history-dropdown-button').rebind('click', function() {
$(this).addClass('active');
$('.account-history-dropdown').addClass('hidden');
$(this).next().removeClass('hidden');
});
$('.fm-account-plan .account-history-drop-items').rebind('click', function() {
var $parent = $(this).parent();
$parent.find('.account-history-drop-items').removeClass('active');
$parent.prev('.account-history-dropdown-button').text($(this).text()).removeClass('active');
var c = $(this).attr('class') ? $(this).attr('class') : '';
if (c.indexOf('purchase10-') > -1) {
$.purchaselimit = 10;
}
else if (c.indexOf('purchase100-') > -1) {
$.purchaselimit = 100;
}
else if (c.indexOf('purchase250-') > -1) {
$.purchaselimit = 250;
}
if (c.indexOf('transaction10-') > -1) {
$.transactionlimit = 10;
}
else if (c.indexOf('transaction100-') > -1) {
$.transactionlimit = 100;
}
else if (c.indexOf('transaction250-') > -1) {
$.transactionlimit = 250;
}
$(this).addClass('active');
$(this).closest('.account-history-dropdown').addClass('hidden');
accountUI();
});
}
}
};
accountUI.notifications = {
init: function() {
'use strict';
this.render();
},
render: function() {
'use strict';
// Ensure the loading dialog stays open till enotif is finished.
loadingDialog.show('enotif');
// New setting need to force cloud and contacts notification available.
if (!mega.notif.has('enabled', 'cloud')) {
mega.notif.set('enabled', 'cloud');
}
if (!mega.notif.has('enabled', 'contacts')) {
mega.notif.set('enabled', 'contacts');
}
// Handle account notification switches
var $NToggleAll = $('.fm-account-notifications .account-notification .dialog-feature-toggle.toggle-all');
var $NToggle = $('.fm-account-notifications .account-notification .switch-container .dialog-feature-toggle');
// Toggle Individual Notifications
$NToggle.each(function() {
var $this = $(this);
var $section = $this.parents('.switch-container');
var sectionName = accountUI.notifications.getSectionName($section);
accountUI.inputs.switch.init(
'#' + this.id,
$section,
mega.notif.has($this.attr('name'), sectionName),
function(val) {
var notifChange = val ? mega.notif.set : mega.notif.unset;
notifChange($this.attr('name'), sectionName);
if (val) {
$NToggleAll.addClass('toggle-on');
} else {
($NToggle.hasClass('toggle-on') ? $.fn.addClass : $.fn.removeClass)
.apply($NToggleAll, ['toggle-on']);
}
});
});
// Toggle All Notifications
accountUI.inputs.switch.init(
'#' + $NToggleAll[0].id,
$NToggleAll.parent(),
$NToggle.hasClass('toggle-on'),
function(val) {
$NToggle.each(function() {
var $this = $(this);
var $section = $this.parents('.switch-container');
var sectionName = accountUI.notifications.getSectionName($section);
var notifChange = val ? mega.notif.set : mega.notif.unset;
notifChange($this.attr('name'), sectionName);
(val ? $.fn.addClass : $.fn.removeClass).apply($NToggle, ['toggle-on']);
});
}
);
// Hide achievements toggle if achievements not an option for this user.
if (!M.account.maf) {
$('#enotif-achievements').closest('.switch-container').remove();
}
// Handle email notification switches.
var $EToggleAll = $('.fm-account-notifications .email-notification .dialog-feature-toggle.toggle-all');
var $EToggle = $('.fm-account-notifications .email-notification .switch-container .dialog-feature-toggle');
mega.enotif.all().then(function(enotifStates) {
// Toggle Individual Emails
$EToggle.each(function() {
var $this = $(this);
var $section = $this.parents('.switch-container');
var emailId = $this.attr('name');
accountUI.inputs.switch.init(
'#' + this.id,
$section,
!enotifStates[emailId],
function(val) {
mega.enotif.setState(emailId, !val);
(val || $EToggle.hasClass('toggle-on') ? $.fn.addClass : $.fn.removeClass)
.apply($EToggleAll, ['toggle-on']);
}
);
});
// All Email Notifications Switch
accountUI.inputs.switch.init(
'#' + $EToggleAll[0].id,
$EToggleAll.parents('.settings-sub-section'),
$EToggle.hasClass('toggle-on'),
function(val) {
mega.enotif.setAllState(!val);
(val ? $.fn.addClass : $.fn.removeClass).apply($EToggle, ['toggle-on']);
}
);
// Hide the loading screen.
loadingDialog.hide('enotif');
});
},
getSectionName: function($section) {
'use strict';
var section = String($section.attr('class')).split(" ").filter(function(c) {
return ({
'chat': 1,
'contacts': 1,
'cloud-drive': 1
})[c];
});
return String(section).split('-').shift();
}
};
accountUI.security = {
init: function() {
"use strict";
// Change Password
accountChangePassword.init();
// Change Email
if (!u_attr.b || u_attr.b.m) {
$('.fm-account-security.fm-account-sections .data-block.change-email').removeClass('hidden');
$('.content-panel.account .acc-setting-menu-change-em').removeClass('hidden');
accountChangeEmail.init();
}
else {
$('.fm-account-security.fm-account-sections .data-block.change-email').addClass('hidden');
$('.content-panel.account .acc-setting-menu-change-em').addClass('hidden');
}
// Recovery Key
this.recoveryKey.bindEvents();
// Metadata
this.metadata.render();
// Session
this.session.render();
this.session.bindEvents();
// 2fa
twofactor.account.init();
},
recoveryKey: {
bindEvents: function() {
'use strict';
// Button on main Account page to backup their master key
$('.fm-account-security .backup-master-key').rebind('click', function() {
M.showRecoveryKeyDialog(2);
});
}
},
metadata: {
render: function() {
'use strict';
accountUI.inputs.switch.init(
'.dbDropOnLogout',
$('.personal-data-container'),
fmconfig.dbDropOnLogout,
function(val) {
mega.config.setn('dbDropOnLogout', val);
});
// Initialise the Download personal data button on the /fm/account/security page
gdprDownload.initDownloadDataButton('personal-data-container');
}
},
session: {
/**
* Rendering session history table.
* With session data from M.account.sessions, render table for session history
*/
render: function() {
"use strict";
if (d) {
console.log('Render session history');
}
if (!$.sessionlimit) {
$.sessionlimit = 10;
}
$('.account-history-dropdown-button.sessions').text(l[472].replace('[X]', $.sessionlimit));
$('.account-history-drop-items.session10-').text(l[472].replace('[X]', 10));
$('.account-history-drop-items.session100-').text(l[472].replace('[X]', 100));
$('.account-history-drop-items.session250-').text(l[472].replace('[X]', 250));
M.account.sessions.sort(function(a, b) {
if (a[7] !== b[7]) {
return a[7] > b[7] ? -1 : 1;
}
if (a[5] !== b[5]) {
return a[5] > b[5] ? -1 : 1;
}
return a[0] < b[0] ? 1 : -1;
});
$('#sessions-table-container').empty();
var html =
'
' +
'
' + l[19303] + '
' + l[480] + '
' + l[481] + '
' + l[482] + '
' +
'
' + l[7664] + '
' +
'
';
var numActiveSessions = 0;
for (i = 0; i < M.account.sessions.length; i++) {
var session = M.account.sessions[i];
var currentSession = session[5];
var activeSession = session[7];
// If the current session or active then increment count
if (currentSession || activeSession) {
numActiveSessions++;
}
if (i >= $.sessionlimit) {
continue;
}
html += this.getHtml(session);
}
$('#sessions-table-container').safeHTML(html + '
');
// Don't show button to close other sessions if there's only the current session
if (numActiveSessions === 1) {
$('.fm-close-all-sessions').hide();
}
else {
$('.fm-close-all-sessions').show();
}
},
bindEvents: function() {
'use strict';
$('.fm-close-all-sessions').rebind('click', function() {
msgDialog('confirmation', '', l[18513], false, function(e) {
if (e) {
loadingDialog.show();
var $activeSessionsRows = $('.active-session-txt').parents('tr');
// Expire all sessions but not the current one
api_req({a: 'usr', ko: 1}, {
callback: function() {
M.account = null;
/* clear account cache */
$activeSessionsRows.find('.settings-logout').remove();
$activeSessionsRows.find('.active-session-txt')
.removeClass('active-session-txt').addClass('expired-session-txt').text(l[1664]);
$('.fm-close-all-sessions').hide();
loadingDialog.hide();
}
});
}
});
});
$('.settings-logout').rebind('click', function() {
var $this = $(this).parents('tr');
var sessionId = $this.attr('class');
if (sessionId === 'current') {
mLogout();
}
else {
loadingDialog.show();
/* usr - user session remove
* remove a session Id from the current user,
* usually other than the current session
*/
api_req({a: 'usr', s: [sessionId]}, {
callback: function() {
M.account = null;
/* clear account cache */
$this.find('.settings-logout').remove();
$this.find('.active-session-txt').removeClass('active-session-txt')
.addClass('expired-session-txt').text(l[1664]);
loadingDialog.hide();
}
});
}
});
$('.fm-account-security .account-history-dropdown-button').rebind('click', function() {
$(this).addClass('active');
$('.account-history-dropdown').addClass('hidden');
$(this).next().removeClass('hidden');
});
$('.fm-account-security .account-history-drop-items').rebind('click', function() {
$(this).parent().prev().removeClass('active');
$(this).parent().find('.account-history-drop-items').removeClass('active');
$(this).parent().parent().find('.account-history-dropdown-button').text($(this).text());
var c = $(this).attr('class') ? $(this).attr('class') : '';
if (c.indexOf('session10-') > -1) {
$.sessionlimit = 10;
}
else if (c.indexOf('session100-') > -1) {
$.sessionlimit = 100;
}
else if (c.indexOf('session250-') > -1) {
$.sessionlimit = 250;
}
$(this).addClass('active');
$(this).closest('.account-history-dropdown').addClass('hidden');
accountUI();
});
},
/**
* Get html of one session data for session history table.
* @param {Object} el a session data from M.account.sessions
* @return {String} html
* When draw session hitory table make html for each session data
*/
getHtml: function(el) {
"use strict";
var currentSession = el[5];
var activeSession = el[7];
var userAgent = el[2];
var dateTime = htmlentities(time2date(el[0]));
var browser = browserdetails(userAgent);
var browserName = browser.nameTrans;
var ipAddress = htmlentities(el[3]);
var country = countrydetails(el[4]);
var sessionId = el[6];
var status = '' + l[7665] + ''; // Current
// Show if using an extension e.g. "Firefox on Linux (+Extension)"
if (browser.isExtension) {
browserName += ' (+' + l[7683] + ')';
}
// If not the current session
if (!currentSession) {
if (activeSession) {
status = '' + l[7666] + ''; // Active
}
else {
status = '' + l[1664] + ''; // Expired
}
}
// If unknown country code use question mark gif
if (!country.icon || country.icon === '??.gif') {
country.icon = 'ud.gif';
}
// Generate row html
var html = '
'
+ '
' + htmlentities(browserName)
+ '
'
+ '
' + ipAddress + '
'
+ '
'
+ htmlentities(country.name) + '
'
+ '
' + dateTime + '
'
+ '
' + status + '
';
// If the session is active show logout button
if (activeSession) {
html += '
' + '' + l[967] + '' + '
';
}
else {
html += '
';
}
return html;
},
/**
* Update Session History table html.
* If there is any new session history found (or forced), re-render session history table.
* @param {Boolean} force force update the table.
*/
update: function(force) {
"use strict";
if (page === 'fm/account/security') {
// if first item in sessions list is not match existing Dom list, it need update.
if (d) {
console.log('Updating session history table');
}
M.refreshSessionList(function() {
var fSession = M.account.sessions[0];
var DomList = $('.grid-table.sessions').find('tr');
// update table when it has new active session or forced
if (fSession && (($(DomList[1]).hasClass('current') && !fSession[5])
|| !$(DomList[1]).hasClass(fSession[6])) || force) {
if (d) {
console.log('Update session history table');
}
accountUI.security.session.render();
accountUI.security.session.bindEvents();
}
});
}
}
}
};
accountUI.fileManagement = {
init: function(account) {
'use strict';
// File versioning
this.versioning.render();
this.versioning.bindEvents();
// Rubbish cleaning schedule
this.rubsched.render(account);
this.rubsched.bindEvents(account);
// User Interface
this.userInterface.render();
// Drag and Drop
this.dragAndDrop.render();
},
versioning: {
render: function() {
'use strict';
// Update versioning info
var setVersioningAttr = function(val) {
showToast('settings', l[16168]);
val = val === 1 ? 0 : 1;
mega.attr.set('dv', val, -2, true).done(function() {
fileversioning.dvState = val;
});
};
fileversioning.updateVersionInfo();
accountUI.inputs.switch.init(
'#versioning-status',
$('#versioning-status').parent(),
!fileversioning.dvState,
setVersioningAttr,
function(val) {
var promise = new MegaPromise();
if (val) {
msgDialog('confirmation', l[882], l[17595], false, function(e) {
if (e) {
promise.resolve();
}
else {
promise.reject();
}
});
}
else {
promise.resolve();
}
return promise;
});
},
bindEvents: function() {
'use strict';
$('#delete-all-versions').rebind('click', function() {
if (!$(this).hasClass('disabled')) {
msgDialog('remove', l[1003], l[17581], l[1007], function(e) {
if (e) {
loadingDialog.show();
var req = {a: 'dv'};
api_req(req, {
callback: function(res) {
if (res === 0) {
M.accountData(function() {
fileversioning.updateVersionInfo();
}, false, true);
}
}
});
}
});
}
});
}
},
rubsched: {
render: function(account) {
'use strict';
if (d) {
console.log('Render rubbish bin schedule');
}
var $rubschedParent = $('#rubsched').parent();
var $rubschedGreenNoti = $('.rub-grn-noti');
var $rubschedOptions = $('.rubsched-options');
var initRubschedSwitch = function(defaultValue) {
accountUI.inputs.switch.init(
'#rubsched',
$('#rubsched').parent(),
defaultValue,
function(val) {
if (val) {
$('#rubsched').parents('.slide-in-out').removeClass('closed');
$rubschedParent.addClass('border');
setTimeout(initAccountScroll, 301);
if (!fmconfig.rubsched) {
var defValue = u_attr.p ? 90 : 30;
var defOption = 14;
mega.config.setn('rubsched', defOption + ":" + defValue);
$('#rad' + defOption + '_opt').val(defValue);
}
}
else {
mega.config.setn('rubsched', 0);
$('#rubsched').parents('.slide-in-out').addClass('closed');
$rubschedParent.removeClass('border');
}
});
};
if (u_attr.flags.ssrs > 0) { // Server side scheduler - new
$rubschedOptions.removeClass('hidden');
$('.rubschedopt').addClass('hidden');
$('.rubschedopt-none').addClass('hidden');
var value = account.ssrs ? account.ssrs : (u_attr.p ? 90 : 30);
$('#rad14_opt').val(value);
if (!value) {
$rubschedOptions.addClass('hidden');
}
// show/hide on/off switches
if (u_attr.p) {
$rubschedParent.removeClass('hidden');
$rubschedGreenNoti.addClass('hidden');
$('.rubbish-desc').text(l[18685]).removeClass('hidden');
$('.account.rubbish-cleaning .settings-right-block').addClass('slide-in-out');
if (account.ssrs) {
$rubschedParent.addClass('border').parent().removeClass('closed');
}
else {
$rubschedParent.removeClass('border').parent().addClass('closed');
}
initRubschedSwitch(account.ssrs);
}
else {
$rubschedParent.addClass('hidden');
$rubschedGreenNoti.removeClass('hidden');
$('.rubbish-desc').text(l[18686]).removeClass('hidden');
$('.account.rubbish-cleaning .settings-right-block').removeClass('slide-in-out');
}
}
else { // Client side scheduler - old
initRubschedSwitch(fmconfig.rubsched);
if (u_attr.p) {
$rubschedGreenNoti.addClass('hidden');
}
else {
$rubschedGreenNoti.removeClass('hidden');
}
if (fmconfig.rubsched) {
$rubschedParent.addClass('border').parent().removeClass('closed');
$rubschedOptions.removeClass('hidden');
$('.rubschedopt').removeClass('hidden');
var opt = String(fmconfig.rubsched).split(':');
$('#rad' + opt[0] + '_opt').val(opt[1]);
accountUI.inputs.radio.init(
'.rubschedopt',
$('.rubschedopt').parent(),
opt[0],
function (val) {
mega.config.setn('rubsched', val + ":" + $('#rad' + val + '_opt').val());
}
);
}
else {
$rubschedParent.removeClass('border').parent().addClass('closed');
}
}
},
bindEvents: function() {
'use strict';
$('.rubsched_textopt').rebind('click.rs blur.rs keypress.rs', function(e) {
// Do not save value until user leave input or click Enter button
if (e.which && e.which !== 13) {
return;
}
var curVal = parseInt($(this).val()) | 0;
var maxVal;
if (this.id === 'rad14_opt') { // For days option
var minVal = 7;
maxVal = u_attr.p ? Math.pow(2, 53) : 30;
curVal = Math.min(Math.max(curVal, minVal), maxVal);
}
if (this.id === 'rad15_opt') { // For size option
// Max value cannot be over current account's total storage space.
maxVal = account.space / Math.pow(1024, 3);
curVal = Math.min(curVal, maxVal);
}
$(this).val(curVal);
var id = String(this.id).split('_')[0];
mega.config.setn('rubsched', id.substr(3) + ':' + curVal);
});
}
},
userInterface: {
_initOption: function(name) {
'use strict';
var selector = '.' + name;
accountUI.inputs.radio.init(selector, $(selector).parent(), fmconfig[name] | 0,
function(val) {
mega.config.setn(name, parseInt(val) | 0, l[16168]);
}
);
},
render: function() {
'use strict';
this._initOption('uisorting');
this._initOption('uiviewmode');
}
},
dragAndDrop: {
render: function() {
'use strict';
var initVal = fmconfig.ulddd === undefined ? 1 : 0;
accountUI.inputs.switch.init(
'#ulddd',
$('#ulddd').parent(),
initVal,
function(val) {
val = val === 1 ? undefined : val;
mega.config.setn('ulddd', val);
});
}
}
};
accountUI.transfers = {
init: function(account) {
'use strict';
// Upload and Download - Bandwidth
this.uploadAndDownload.bandwidth.render(account);
// Upload and Download - Upload
this.uploadAndDownload.upload.render();
this.uploadAndDownload.upload.bindEvents();
// Upload and Download - Download
this.uploadAndDownload.download.render();
// Transfer Tools - Megasync
this.transferTools.megasync.render();
// MEGAdrop folders table
mega.megadrop.stngsDraw();
// Download folder setting for PaleMoon ext
this.addDownloadFolderSetting();
},
uploadAndDownload: {
bandwidth: {
render: function(account) {
'use strict';
// LITE/PRO account
if (u_attr.p && !u_attr.b) {
var bandwidthLimit = Math.round(account.servbw_limit | 0);
$('#bandwidth-slider').slider({
min: 0, max: 100, range: 'min', value: bandwidthLimit,
change: function(e, ui) {
if (M.currentdirid === 'account/transfers') {
bandwidthLimit = ui.value;
if (parseInt(localStorage.bandwidthLimit) !== bandwidthLimit) {
var done = delay.bind(null, 'bandwidthLimit', function() {
api_req({"a": "up", "srvratio": Math.round(bandwidthLimit)});
if (localStorage.bandwidthLimit !== undefined) {
showToast('settings', l[16168]);
}
localStorage.bandwidthLimit = bandwidthLimit;
}, 700);
if (bandwidthLimit > 99) {
msgDialog('warningb:!' + l[776], l[882], l[12689], 0, function(e) {
if (e) {
done();
}
else {
$('.slider-percentage span').text('0 %').removeClass('bold warn');
$('#bandwidth-slider').slider('value', 0);
}
});
}
else {
done();
}
}
}
},
slide: function(e, ui) {
$('.slider-percentage span').text(ui.value + ' %');
if (ui.value > 90) {
$('.slider-percentage span').addClass('warn bold');
}
else {
$('.slider-percentage span').removeClass('bold warn');
}
}
});
$('.slider-percentage span').text(bandwidthLimit + ' %');
$('.bandwith-settings').removeClass('disabled').addClass('border');
$('.slider-percentage-bl').removeClass('hidden');
$('.band-grn-noti').addClass('hidden');
}
// Business account
else if (u_attr.b) {
$('.bandwith-settings').addClass('hidden');
$('.slider-percentage-bl').addClass('hidden');
$('.band-grn-noti').addClass('hidden');
}
}
},
upload: {
render: function() {
'use strict';
// Parallel upload slider
$('#slider-range-max').slider({
min: 1, max: 6, range: "min", value: fmconfig.ul_maxSlots || 4,
change: function(e, ui) {
if (M.currentdirid === 'account/transfers' && ui.value !== fmconfig.ul_maxSlots) {
mega.config.setn('ul_maxSlots', ui.value);
ulQueue.setSize(fmconfig.ul_maxSlots);
}
},
slide: function(e, ui) {
$('.download-settings .numbers.active').removeClass('active');
$('.download-settings .numbers.val' + ui.value).addClass('active');
}
});
$('.upload-settings .numbers.active').removeClass('active');
$('.upload-settings .numbers.val' + $('#slider-range-max').slider('value')).addClass('active');
// Speed limit
fmconfig.ul_maxSpeed = fmconfig.ul_maxSpeed || 0;
var currentVal = parseInt(fmconfig.ul_maxSpeed) < 1 ? fmconfig.ul_maxSpeed : 1;
if (currentVal === 1) {
$('#ulspeedvalue').val(fmconfig.ul_maxSpeed / 1024);
}
else if (!$('#ulspeedvalue').val()){
$('#ulspeedvalue').val(100);
}
accountUI.inputs.radio.init(
'.ulspeedradio',
$('.ulspeedradio').parent(),
currentVal,
function (val) {
val = parseInt(val);
var ul_maxSpeed = val;
if (val === 1) {
if (parseInt($('#ulspeedvalue').val()) > 0) {
ul_maxSpeed = parseInt($('#ulspeedvalue').val()) * 1024;
}
else {
ul_maxSpeed = 100 * 1024;
}
}
showToast('settings', l[16168]);
mega.config.setn('ul_maxSpeed', ul_maxSpeed);
}
);
},
bindEvents: function() {
'use strict';
$('#ulspeedvalue').rebind('click.speedValueClick keyup.speedValueKeyup', function() {
$('.ulspeedradio').removeClass('radioOn').addClass('radioOff');
$('#rad3,#rad3_div').addClass('radioOn').removeClass('radioOff');
$('#rad3').trigger('click');
});
}
},
download: {
render: function() {
'use strict';
// Parallel download slider
$('#slider-range-max2').slider({
min: 1, max: 6, range: "min", value: fmconfig.dl_maxSlots || 4,
change: function(e, ui) {
if (M.currentdirid === 'account/transfers' && ui.value !== fmconfig.dl_maxSlots) {
mega.config.setn('dl_maxSlots', ui.value);
dlQueue.setSize(fmconfig.dl_maxSlots);
}
},
slide: function(e, ui) {
$('.upload-settings .numbers.active').removeClass('active');
$('.upload-settings .numbers.val' + ui.value).addClass('active');
}
});
$('.download-settings .numbers.active').removeClass('active');
$('.download-settings .numbers.val' + $('#slider-range-max2').slider('value')).addClass('active');
}
}
},
transferTools: {
megasync: {
render : function() {
'use strict';
accountUI.inputs.switch.init(
'#dlThroughMEGAsync',
$('#dlThroughMEGAsync').parent(),
fmconfig.dlThroughMEGAsync,
function(val) {
mega.config.setn('dlThroughMEGAsync', val);
});
}
}
},
addDownloadFolderSetting: function() {
'use strict';
if (is_chrome_firefox && !$('#acc_dls_folder').length) {
$('.fm-account-transfers').safeAppend(
'
' +
'
' +
'
' +
'
Downloads folder:
' +
'
' +
'' +
'
');
var fld = mozGetDownloadsFolder();
$('#acc_dls_folder').append($('').text(fld && fld.path));
$('#acc_dls_folder input').click(function() {
var fs = mozFilePicker(0, 2);
if (fs) {
mozSetDownloadsFolder(fs);
$(this).next().text(fs.path);
}
});
}
}
};
accountUI.contactAndChat = {
init: function(autoaway, autoawaylock, autoawaytimeout, persist, persistlock, lastSeen) {
'use strict';
var presenceInt = megaChat.plugins.presencedIntegration;
var delaying = this.delayRender(presenceInt, autoaway);
if (delaying) {
return;
}
this.status.render(presenceInt, autoaway, autoawaylock, autoawaytimeout, persist, persistlock, lastSeen);
this.status.bindEvents(presenceInt, autoawaytimeout);
this.richURL.render();
},
status: {
render: function(presenceInt, autoaway, autoawaylock, autoawaytimeout, persist, persistlock, lastSeen) {
'use strict';
// Chat
var $sectionContainerChat = $('.fm-account-contact-chats');
// Status appearance radio buttons
accountUI.inputs.radio.init(
'.chatstatus',
$('.chatstatus').parent(),
presenceInt.getPresence(u_handle),
function(newVal) {
presenceInt.setPresence(parseInt(newVal));
});
// Last seen switch
accountUI.inputs.switch.init(
'#last-seen',
$sectionContainerChat,
lastSeen,
function(val) {
presenceInt.userPresence.ui_enableLastSeen(Boolean(val));
});
if (autoawaytimeout !== false) {
// Auto-away switch
accountUI.inputs.switch.init(
'#auto-away-switch',
$sectionContainerChat,
autoaway,
function(val) {
presenceInt.userPresence.ui_setautoaway(Boolean(val));
});
// Prevent changes to autoaway if autoawaylock is set
if (autoawaylock === true) {
$('#auto-away-switch').addClass('diabled').parent().addClass('hidden');
}
else {
$('#auto-away-switch').removeClass('diabled').parent().removeClass('hidden');
}
// Auto-away input box
$('input#autoaway').val(autoawaytimeout / 60);
// Always editable for user comfort -
accountUI.controls.enableElement($('input#autoaway', $sectionContainerChat));
// Persist switch
accountUI.inputs.switch.init(
'#persist-presence-switch',
$sectionContainerChat,
persist,
function(val) {
presenceInt.userPresence.ui_setpersist(Boolean(val));
});
// Prevent changes to autoaway if autoawaylock is set
if (persistlock === true) {
$('#persist-presence-switch', $sectionContainerChat).addClass('diabled')
.parent().addClass('hidden');
}
else {
$('#persist-presence-switch', $sectionContainerChat).removeClass('diabled')
.parent().removeClass('hidden');
}
}
},
bindEvents: function(presenceInt, autoawaytimeout) {
'use strict';
if (autoawaytimeout !== false) {
var $sectionContainerChat = $('.fm-account-contact-chats');
var lastValidNumber = Math.floor(autoawaytimeout / 60);
// when value is changed, set checkmark
$('input#autoaway', $sectionContainerChat).rebind('change.dashboard', function() {
var val = parseInt($(this).val());
if (val > 3505) {
val = 3505;
}
else if (val < 0) {
val = 5;
}
if (val > 0) {
presenceInt.userPresence.ui_setautoaway(true, val * 60);
lastValidNumber = val;
}
}).rebind('blur.dashboard', function() {
// the goal of the following line is to reset the value of the field if the entered data is invalid
// after the user removes focus from it (and set the internally set value)
$(this).val(presenceInt.userPresence.autoawaytimeout / 60);
}).val(lastValidNumber);
}
}
},
richURL: {
render: function() {
'use strict';
// Auto-away switch
accountUI.inputs.switch.init(
'#richpreviews',
$('#richpreviews').parent(),
RichpreviewsFilter.previewGenerationConfirmation,
function(val) {
if (val) {
RichpreviewsFilter.confirmationDoConfirm();
}
else {
RichpreviewsFilter.confirmationDoNever();
}
});
}
},
delayRender: function(presenceInt, autoaway) {
'use strict';
var self = this;
if (!megaChatIsReady) {
if (megaChatIsDisabled) {
console.error('Mega Chat is disabled, cannot proceed to Contact and Chat settings');
}
else {
// If chat is not ready waiting for chat_initialized broadcaster.
loadingDialog.show();
mBroadcaster.once('chat_initialized', self.delayRender.bind(self, presenceInt, autoaway));
}
return true;
}
loadingDialog.hide();
if (!presenceInt || !presenceInt.userPresence) {
setTimeout(function() {
throw new Error('presenceInt is not ready...');
});
return true;
// ^ FIXME too..!
}
// Only call this if the call of this function is the first one, made by fm.js -> accountUI
if (autoaway === undefined) {
$(presenceInt).rebind('settingsUIUpdated.settings', function(e,
autoaway,
autoawaylock,
autoawaytimeout,
persist,
persistlock,
lastSeen) {
self.init(autoaway, autoawaylock, autoawaytimeout, persist, persistlock, lastSeen);
});
presenceInt.userPresence.updateui();
return true;
}
if (typeof (megaChat) !== 'undefined' && typeof(presenceInt) !== 'undefined') {
$(presenceInt).rebind('settingsUIUpdated.settings', function(e,
autoaway,
autoawaylock,
autoawaytimeout,
persist,
persistlock,
lastSeen) {
self.init(autoaway, autoawaylock, autoawaytimeout, persist, persistlock, lastSeen);
});
}
},
};
accountUI.reseller = {
init: function(account) {
'use strict';
if (M.account.reseller) {
this.voucher.render(account);
this.voucher.bindEvents();
}
},
voucher: {
render: function(account) {
'use strict';
if (!$.voucherlimit) {
$.voucherlimit = 10;
}
var email = 'resellers@mega.nz';
$('.resellerbuy').attr('href', 'mailto:' + email)
.find('span').text(l[9106].replace('%1', email));
// Use 'All' or 'Last 10/100/250' for the dropdown text
var buttonText = ($.voucherlimit === 'all') ? l[7557] : l['466a'].replace('[X]', $.voucherlimit);
$('.account-history-dropdown-button.vouchers').text(buttonText);
$('.fm-account-reseller .balance span').safeHTML('@@ € ', account.balance[0][0]);
$('.account-history-drop-items.voucher10-').text(l['466a'].replace('[X]', 10));
$('.account-history-drop-items.voucher100-').text(l['466a'].replace('[X]', 100));
$('.account-history-drop-items.voucher250-').text(l['466a'].replace('[X]', 250));
// Sort vouchers by most recently created at the top
M.account.vouchers.sort(function(a, b) {
if (a['date'] < b['date']) {
return 1;
}
else {
return -1;
}
});
$('.grid-table.vouchers tr').remove();
var html = '
' + l[475] + '
' + l[7714] + '
' + l[477]
+ '
' + l[488] + '
';
$(account.vouchers).each(function(i, el) {
// Only show the last 10, 100, 250 or if the limit is not set show all vouchers
if (($.voucherlimit !== 'all') && (i >= $.voucherlimit)) {
return false;
}
var status = l[489];
if (el.redeemed > 0 && el.cancelled === 0 && el.revoked === 0) {
status = l[490] + ' ' + time2date(el.redeemed);
}
else if (el.revoked > 0 && el.cancelled > 0) {
status = l[491] + ' ' + time2date(el.revoked);
}
else if (el.cancelled > 0) {
status = l[492] + ' ' + time2date(el.cancelled);
}
var voucherLink = 'https://mega.nz/#voucher' + htmlentities(el.code);
html += '
' + time2date(el.date) + '
' + voucherLink
+ '
€ ' + htmlentities(el.amount) + '
' + status + '
';
});
$('.grid-table.vouchers').safeHTML(html);
$('.default-select.vouchertype .default-select-scroll').text('');
$('.default-select.vouchertype span').text(l[6875]);
var prices = [];
for (var i = 0; i < M.account.prices.length; i++) {
if (M.account.prices[i]) {
prices.push(M.account.prices[i][0]);
}
}
prices.sort(function(a, b) {
return (a - b);
});
var voucheroptions = '';
for (i = 0; i < prices.length; i++) {
voucheroptions += '
€' + htmlentities(prices[i]) + ' voucher
';
}
$('.default-select.vouchertype .default-select-scroll').safeHTML(voucheroptions);
},
bindEvents: function() {
'use strict';
$('.vouchercreate').rebind('click..voucherCreateClick', function() {
var vouchertype = $('.default-select.vouchertype .default-dropdown-item.active').attr('data-value');
var voucheramount = parseInt($('#account-voucheramount').val());
var proceed = false;
for (var i in M.account.prices) {
if (M.account.prices[i][0] === vouchertype) {
proceed = true;
}
}
if (!proceed) {
msgDialog('warninga', l[135], 'Please select the voucher type.');
return false;
}
if (!voucheramount) {
msgDialog('warninga', l[135], 'Please enter a valid voucher amount.');
return false;
}
if (vouchertype === '19.99') {
vouchertype = '19.991';
}
loadingDialog.show();
api_req({a: 'uavi', d: vouchertype, n: voucheramount, c: 'EUR'},
{
callback: function() {
M.account.lastupdate = 0;
accountUI();
}
});
});
$('.fm-account-reseller .account-history-dropdown-button').rebind('click', function() {
$(this).addClass('active');
$('.account-history-dropdown').addClass('hidden');
$(this).next().removeClass('hidden');
});
$('.fm-account-reseller .account-history-drop-items').rebind('click', function() {
var $parent = $(this).parent();
$parent.find('.account-history-drop-items').removeClass('active');
$parent.prev('.default-select.vouchers').text($(this).text()).removeClass('active');
var c = $(this).attr('class') ? $(this).attr('class') : '';
if (c.indexOf('voucher10-') > -1) {
$.voucherlimit = 10;
}
else if (c.indexOf('voucher100-') > -1) {
$.voucherlimit = 100;
}
else if (c.indexOf('voucher250-') > -1) {
$.voucherlimit = 250;
}
else if (c.indexOf('voucherAll-') > -1) {
$.voucherlimit = 'all';
}
$(this).addClass('active');
$(this).closest('.account-history-dropdown').addClass('hidden');
accountUI();
});
bindDropdownEvents($('.default-select.vouchertype'), 0, '.fm-account-reseller');
}
}
};
/**
* Functionality for the My Account page, Security section to change the user's password
*/
var accountChangePassword = {
/**
* Initialise the change password functionality
*/
init:function() {
'use strict';
this.resetForm();
this.initPasswordKeyupHandler();
this.initChangePasswordButton();
},
/**
* Reset the text inputs if coming back to the page
*/
resetForm: function() {
'use strict';
$('#account-new-password').val('').trigger('blur');
$('#account-confirm-password').val('').trigger('blur');
},
/**
* Initialise the handler to change the password strength indicator while typing the password
*/
initPasswordKeyupHandler: function() {
'use strict';
var $changePasswordStrengthBar = $('.account-pass-lines');
var $newPasswordField = $('#account-new-password');
var $changePasswordButton = $('.account.change-password .button-container');
var bindStrengthChecker = function() {
$newPasswordField.rebind('keyup.pwdchg input change', function() {
// Estimate the password strength
var password = $.trim($(this).val());
var passwordScore = zxcvbn(password).score;
var passwordLength = password.length;
// Remove previous strength classes that were added
$changePasswordStrengthBar.removeClass('good1 good2 good3 good4 good5');
$changePasswordButton.removeClass('closed');
// Add colour coding
if (passwordLength === 0) {
$changePasswordButton.addClass('closed');
return false;
}
else if (passwordLength < security.minPasswordLength) {
$changePasswordStrengthBar.addClass('good1'); // Too short
}
else if (passwordScore === 4) {
$changePasswordStrengthBar.addClass('good5'); // Strong
}
else if (passwordScore === 3) {
$changePasswordStrengthBar.addClass('good4'); // Good
}
else if (passwordScore === 2) {
$changePasswordStrengthBar.addClass('good3'); // Medium
}
else if (passwordScore === 1) {
$changePasswordStrengthBar.addClass('good2'); // Weak
}
else {
$changePasswordStrengthBar.addClass('good1'); // Very Weak
}
});
// Reset strength after re-rendering.
$newPasswordField.trigger('keyup.pwdchg');
};
if (typeof zxcvbn === 'undefined') {
M.require('zxcvbn_js').done(bindStrengthChecker);
}
else {
bindStrengthChecker();
}
},
/**
* Initalise the change password button to verify the password (and 2FA if active) then change the password
*/
initChangePasswordButton: function() {
'use strict';
// Cache selectors
var $newPasswordField = $('#account-new-password');
var $newPasswordConfirmField = $('#account-confirm-password');
var $changePasswordButton = $('.account .change-password-button');
var $passwordStrengthBar = $('.account-pass-lines');
$changePasswordButton.rebind('click', function() {
if ($(this).hasClass('disabled')) {
return false;
}
// Trim the whitespace from the passwords
var password = $newPasswordField.val();
var confirmPassword = $newPasswordConfirmField.val();
// Check if the entered passwords are valid or strong enough
var passwordValidationResult = security.isValidPassword(password, confirmPassword);
// If bad result
if (passwordValidationResult !== true) {
// Show error message
msgDialog('warninga', l[135], passwordValidationResult, false, function() {
$newPasswordField.val('');
$newPasswordConfirmField.val('');
$newPasswordField.trigger('focus');
$newPasswordConfirmField.trigger('blur');
$passwordStrengthBar.removeClass('good1 good2 good3 good4 good5');
});
// Return early to prevent password change
return false;
}
// Trigger save password on browser with correct email
accountChangePassword.emulateFormSubmission(password);
// Proceed to change the password
accountChangePassword.changePassword(password);
});
},
/**
* Emulate form submission to trigger correctly behaved password manager update.
* @param {String} password The new password
*/
emulateFormSubmission: function(password) {
'use strict';
var form = document.createElement("form");
form.className = 'hidden';
var elem1 = document.createElement("input");
var elem2 = document.createElement("input");
var elemBtn = document.createElement("input");
elem1.value = u_attr.email;
elem1.type = 'email';
form.appendChild(elem1);
elem2.value = password;
elem2.type = 'password';
form.appendChild(elem2);
elemBtn.type = 'submit';
form.appendChild(elemBtn);
document.body.appendChild(form);
$(form).on('submit', function() {
return false;
});
elemBtn.click();
document.body.removeChild(form);
},
/**
* Change the user's password
* @param {String} newPassword The new password
*/
changePassword: function(newPassword) {
'use strict';
loadingDialog.show();
// Check if 2FA is enabled on their account
twofactor.isEnabledForAccount(function(result) {
loadingDialog.hide();
// If 2FA is enabled on their account
if (result) {
// Show the verify 2FA dialog to collect the user's PIN
twofactor.verifyActionDialog.init(function(twoFactorPin) {
accountChangePassword.continueChangePassword(newPassword, twoFactorPin);
});
}
else {
// Otherwise change the password without 2FA
accountChangePassword.continueChangePassword(newPassword, null);
}
});
},
/**
* Continue to change the user's password (e.g. immediately or after they've entered their 2FA PIN
* @param {String} newPassword The new password
* @param {String|null} twoFactorPin The 2FA PIN code or null if not applicable
*/
continueChangePassword: function(newPassword, twoFactorPin) {
'use strict';
// If the account is registered with the new process (version 2), change password using the new method
if (u_attr.aav === 2) {
security.changePassword.newMethod(newPassword, twoFactorPin, accountChangePassword.completeChangePassword);
}
else {
security.changePassword.oldMethod(newPassword, twoFactorPin, accountChangePassword.completeChangePassword);
}
},
/**
* Update the UI after attempting to change the password and let the user know if it failed or was successful
* @param {Number} result The result from the change password API request
*/
completeChangePassword: function(result) {
'use strict';
loadingDialog.hide();
// If something went wrong with the 2FA PIN
if (result === EFAILED || result === EEXPIRED) {
msgDialog('warninga', l[135], l[19216]);
}
// If something else went wrong, show an error
else if (typeof result === 'number' && result < 0) {
msgDialog('warninga', l[135], l[47]);
}
else {
// Success
showToast('settings', l[725]);
}
// Clear password fields
$('#account-new-password').val('');
$('#account-confirm-password').val('');
// Update the account page
accountUI();
}
};
/**
* Functionality for the My Account page, Security section to change the user's email
*/
var accountChangeEmail = {
/**
* Initialise the change email functionality
*/
init:function() {
'use strict';
this.resetForm();
this.initEmailKeyupHandler();
this.initChangeEmailButton();
},
/**
* Reset the text inputs if coming back to the page
*/
resetForm: function() {
'use strict';
// Reset change email fields after change
$('#current-email').val(u_attr.email);
$('#account-email').val('');
$('.fm-account-change-email').addClass('hidden');
},
/**
* Initialise the handler to show an information message while typing the email
*/
initEmailKeyupHandler: function() {
'use strict';
// Cache selectors
var $newEmail = $('#account-email');
var $emailInfoMessage = $('.fm-account-change-email');
var $changeEmailButton = $('.account .change-email-button');
// On text entry in the new email text field
$newEmail.rebind('keyup', function() {
if ($newEmail.val()) {
// Show information message
$emailInfoMessage.slideDown();
$changeEmailButton.parent().removeClass('closed');
}
else {
// Show information message
$emailInfoMessage.slideUp();
$changeEmailButton.parent().addClass('closed');
}
$newEmail.parent().removeClass('error');
});
},
/**
* Initalise the change email button to verify the email, get the 2FA code (if active) then change the email
*/
initChangeEmailButton: function() {
'use strict';
// Cache selectors
var $changeEmailButton = $('.account .change-email-button');
var $newEmail = $('#account-email');
// On Change Email button click
$changeEmailButton.rebind('click', function() {
if ($(this).hasClass('disabled')) {
return false;
}
// Get the new email address
var newEmailRaw = $newEmail.val();
var newEmail = $.trim(newEmailRaw).toLowerCase();
// If not a valid email, show an error
if (!isValidEmail(newEmail)) {
$newEmail.parent().addClass('error').find('.error-message').text(l[1513]);
return false;
}
// If there is text in the email field and it doesn't match the existing one
if ((newEmail !== '') && (u_attr.email !== newEmail)) {
loadingDialog.show();
// Check if 2FA is enabled on their account
twofactor.isEnabledForAccount(function(result) {
loadingDialog.hide();
// If 2FA is enabled
if (result) {
// Show the verify 2FA dialog to collect the user's PIN
twofactor.verifyActionDialog.init(function(twoFactorPin) {
accountChangeEmail.continueChangeEmail(newEmail, twoFactorPin);
});
}
else {
accountChangeEmail.continueChangeEmail(newEmail, null);
}
});
}
});
},
/**
* Initiate the change email request to the API
* @param {String} newEmail The new email
* @param {String|null} twoFactorPin The 2FA PIN code or null if not applicable
*/
continueChangeEmail: function(newEmail, twoFactorPin) {
'use strict';
loadingDialog.show();
// Prepare the request
var requestParams = {
a: 'se', // Set Email
aa: 'a',
e: newEmail, // The new email address
i: requesti // Last request ID
};
// If the 2FA PIN was entered, send it with the request
if (twoFactorPin !== null) {
requestParams.mfa = twoFactorPin;
}
// Change of email request
api_req(requestParams, {
callback: function(result) {
loadingDialog.hide();
// If something went wrong with the 2FA PIN
if (result === EFAILED || result === EEXPIRED) {
msgDialog('warninga', l[135], l[19216]);
}
// If they have already requested a confirmation link for that email address, show an error
else if (result === -12) {
return msgDialog('warninga', l[135], l[7717]);
}
// EACCESS, the email address is already in use or current user is invalid. (less likely).
else if (typeof result === 'number' && result === -11) {
return msgDialog('warninga', l[135], l[19562]);
}
// If something else went wrong, show an error
else if (typeof result === 'number' && result < 0) {
msgDialog('warninga', l[135], l[47]);
}
else {
// Success
fm_showoverlay();
dialogPositioning('.awaiting-confirmation');
$('.awaiting-confirmation').removeClass('hidden');
localStorage.new_email = newEmail;
}
}
});
}
};
(function _dialogs(global) {
'use strict'; /* jshint -W074 */
// @private pointer to global fm-picker-dialog
var $dialog = false;
// @private reference to active dialog section
var section = 'cloud-drive';
// @private shared nodes metadata
var shares = Object.create(null);
if (d) {
window.mcshares = shares;
}
// ------------------------------------------------------------------------
// ---- Private Functions -------------------------------------------------
/**
* Find shared folders marked read-only and disable it in dialog.
* @private
*/
var disableReadOnlySharedFolders = function() {
var $ro = $('.fm-picker-dialog-tree-panel.shared-with-me .dialog-content-block span[id^="mctreea_"]');
var targets = $.selected || [];
if (!$ro.length) {
if ($('.fm-picker-dialog-button.shared-with-me', $dialog).hasClass('active')) {
// disable import btn
$('.dialog-picker-button', $dialog).addClass('disabled');
}
}
$ro.each(function(i, v) {
var h = $(v).attr('id').replace('mctreea_', '');
var s = shares[h] = Object.create(null);
var n = M.d[h];
while (n && !n.su) {
n = M.d[n.p];
}
if (n) {
s.share = n;
s.owner = n.su;
s.level = n.r;
for (i = targets.length; i--;) {
if (M.isCircular(n.h, targets[i])) {
s.circular = true;
n = null;
break;
}
}
}
if (!n || !n.r) {
$(v).addClass('disabled');
}
});
};
/**
* Disable circular references and read-only shared folders.
* @private
*/
var disableFolders = function() {
$('*[id^="mctreea_"]').removeClass('disabled');
if ($.moveDialog) {
M.disableCircularTargets('#mctreea_');
}
else if (!$.copyToUpload){
var sel = $.selected || [];
for (var i = sel.length; i--;) {
$('#mctreea_' + String(sel[i]).replace(/[^\w-]/g, '')).addClass('disabled');
}
}
disableReadOnlySharedFolders();
};
/**
* Retrieve array of non-circular nodes
* @param {Array} [selectedNodes]
* @returns {Array}
* @private
*/
var getNonCircularNodes = function(selectedNodes) {
var r = [];
if ($.mcselected) {
selectedNodes = selectedNodes || $.selected || [];
for (var i = selectedNodes.length; i--;) {
if (!M.isCircular(selectedNodes[i], $.mcselected)) {
r.push(selectedNodes[i]);
}
}
}
return r;
};
/**
* Retrieves a list of currently selected target chats
* @private
*/
var getSelectedChats = function() {
var chats = $('.nw-contact-item.selected', $dialog).attrs('id');
chats = chats.map(function(c) {
return String(c).replace('cpy-dlg-chat-itm-spn-', '');
});
return chats;
};
/**
* Set the dialog button state to either disabled or enabled
* @param {Object} $btn The jQuery's node or selector
* @private
*/
var setDialogButtonState = function($btn) {
$btn = $($btn);
if (section === 'conversations') {
if (getSelectedChats().length) {
$btn.removeClass('disabled');
}
else {
$btn.addClass('disabled');
}
}
else if (!$.mcselected) {
$btn.addClass('disabled');
}
else if ($.copyDialog && section === 'cloud-drive') {
$btn.removeClass('disabled');
}
else if ($.selectFolderDialog && section === 'cloud-drive' && $.mcselected !== M.RootID) {
$btn.removeClass('disabled');
}
else {
var forceEnabled = $.copyToShare || $.copyToUpload || $.onImportCopyNodes || $.saveToDialog;
console.assert(!$.copyToShare || Object($.selected).length === 0, 'check this...');
if (!forceEnabled && !getNonCircularNodes().length) {
$btn.addClass('disabled');
}
else {
$btn.removeClass('disabled');
}
}
};
/**
* Select tree item node
* @param {String} h The node handle
*/
var selectTreeItem = function(h) {
onIdle(function() {
if (section === 'conversations') {
$('#cpy-dlg-chat-itm-spn-' + h, $dialog).trigger('click');
}
else if (!$('#mctreesub_' + h, $dialog).hasClass('opened')) {
$('#mctreea_' + h, $dialog).trigger('click');
}
});
};
/**
* Render target breadcrumb
* @param {String} [aTarget] Target node handle
* @private
*/
var setDialogBreadcrumb = function(aTarget) {
var path = false;
var names = Object.create(null);
var titles = Object.create(null);
var $dbc = $('.fm-picker-breadcrumbs', $dialog).empty();
var autoSelect = $.copyToUpload && !$.copyToUpload[2];
if (section === 'conversations') {
var chats = getSelectedChats();
if (chats.length > 1) {
path = [u_handle, 'contacts'];
names[u_handle] = l[1025];
}
else {
aTarget = chats[0] || String(aTarget || '').split('/').pop();
}
if (aTarget && String(aTarget).indexOf("#") > -1) {
aTarget = aTarget.split("#")[0];
}
}
// Update global $.mcselected with the target handle
$.mcselected = aTarget && aTarget !== 'transfers' ? aTarget : undefined;
path = path || M.getPath($.mcselected);
titles[M.RootID] = l[164];
titles[M.RubbishID] = l[167];
titles['shares'] = l[5542];
if (path.length === 1) {
names = titles;
}
if ($.mcselected && !path.length) {
// The selected node is likely not in memory, try to rely on DOM and find the ancestors
var el = $dialog[0].querySelector('#mctreea_' + aTarget);
if (el) {
path.push(aTarget);
names[aTarget] = el.querySelector('.nw-fm-tree-folder').textContent;
$(el).parentsUntil('.dialog-content-block', 'ul').each(function(i, elm) {
var h = String(elm.id).split('_')[1];
path.push(h);
elm = $dialog[0].querySelector('#mctreea_' + h + ' .nw-fm-tree-folder');
if (elm) {
names[h] = elm.textContent;
}
});
}
}
for (var i = path.length; i--;) {
var h = path[i];
var type = 'folder';
var name = names[h] || M.getNameByHandle(h);
if (h === M.RootID) {
if (!folderlink) {
type = 'cloud-drive';
}
}
else if (h === M.RubbishID) {
type = 'rubbish-bin';
}
else if (h === 'contacts') {
type = 'contacts';
}
else if (h === 'shares') {
type = 'shared-with-me';
}
else if (h.length === 11) {
type = 'contact';
}
if (name === 'undefined') {
name = '';
}
var nameFmt = '@@';
if (section === 'conversations') {
nameFmt = name && megaChat.plugins.emoticonsFilter.processHtmlMessage(escapeHTML(name)) || nameFmt;
}
$dbc.safeAppend(
'' +
' ' + nameFmt + '' +
'', type, i > 0 ? 'has-next-button' : '', titles[h] || name, h, name
);
if (autoSelect) {
selectTreeItem(h);
}
}
if (path.length) {
$dbc.parent().addClass('correct-input').removeClass('high-light');
}
else {
$dbc.parent().addClass('high-light').removeClass('correct-input')
.find('.transfer-filetype-icon').removeClass('chat folder')
.addClass(section === 'conversations' ? 'chat' : 'folder');
}
$dbc.safeAppend('');
$('a.fm-breadcrumbs', $dialog).rebind('click', function() {
var $elm = $('#mctreea_' + $(this).attr('data-node'), $dialog);
if ($elm.length) {
$elm.trigger('click');
}
else {
$('.fm-picker-dialog-button.active', $dialog).trigger('click');
}
return false;
});
if ($.copyToUpload) {
// auto-select entries once
$.copyToUpload[2] = true;
}
};
/**
* Set selected items...
* @param {*} [single] Single item mode
* @private
*/
var setSelectedItems = function(single) {
var $icon = $('.summary-items-drop-icon', $dialog).removeClass('drop-up-icon drop-down-icon hidden');
var $div = $('.summary-items', $dialog).removeClass('unfold multi').empty();
var names = Object.create(null);
var items = $.selected || [];
var jScrollPane = function() {
var jsp = $div.data('jsp');
if (items.length > 4) {
if (jsp) {
jsp.reinitialise();
}
else {
$div.jScrollPane({animateScroll: true});
}
}
else if (jsp) {
jsp.destroy();
}
};
var setTitle = function() {
$('.fm-picker-dialog-title', $dialog)
.text(
items.length < 2
? l[19338]
: escapeHTML(l[19339]).replace('[X]', items.length)
);
};
if ($.saveToDialogNode) {
items = [$.saveToDialogNode.h];
names[$.saveToDialogNode.h] = $.saveToDialogNode.name;
}
if ($.copyToShare) {
items = [];
single = true;
$('.summary-target-title', $dialog).text(l[19180]);
$('.summary-selected', $dialog).addClass('hidden');
}
else if ($.selectFolderDialog) {
$('.summary-target-title', $dialog).text(l[19181]);
$('.summary-selected', $dialog).addClass('hidden');
}
else {
$('.summary-target-title', $dialog).text(l[19181]);
if ($.onImportCopyNodes) {
$('.summary-selected', $dialog).addClass('hidden');
}
else {
$('.summary-selected', $dialog).removeClass('hidden');
}
if ($.copyToUpload) {
items = $.copyToUpload[0];
for (var j = items.length; j--;) {
items[j].uuid = makeUUID();
}
setTitle();
}
}
if (!single) {
$div.addClass('unfold');
$div.safeAppend('');
$div = $div.find('.item-row-group');
}
for (var i = 0; i < items.length; i++) {
var h = items[i];
var n = M.getNodeByHandle(h) || Object(h);
var name = names[h] || M.getNameByHandle(h) || n.name;
var tail = '';
var icon = fileIcon(n);
var data = n.uuid || h;
if (single) {
tail = '(@@)';
if (items.length < 2) {
tail = '';
}
}
$div.safeAppend(
'
' +
' ' +
'
@@
' + tail +
'
', data, icon, str_mtrunc(name, 42), String(l[10663]).replace('[X]', items.length - 1)
);
if (single) {
break;
}
}
$icon.rebind('click', function() {
$div.off('click.unfold');
setSelectedItems(!single);
return false;
});
if (single) {
if (items.length > 1) {
$div.addClass('multi').rebind('click.unfold', function() {
$icon.trigger('click');
return false;
});
$icon.addClass('drop-down-icon');
}
else {
$icon.addClass('hidden');
}
}
else {
jScrollPane();
$icon.addClass('drop-up-icon');
$('.delete-img.icon', $div).rebind('click', function() {
var $row = $(this).parent();
var data = $row.attr('data-node');
$row.remove();
jScrollPane();
if ($.copyToUpload) {
for (var i = items.length; i--;) {
if (items[i].uuid === data) {
items.splice(i, 1);
break;
}
}
setTitle();
}
else {
array.remove(items, data);
}
if (items.length < 2) {
setSelectedItems(true);
}
return false;
});
}
// reposition dialog in case we did hide selected-items (Ie. importing)
dialogPositioning($dialog);
};
/**
* Get the button label for the dialog's main action button
* @returns {String}
* @private
*/
var getActionButtonLabel = function() {
if ($.mcImport) {
return l[236]; // Import
}
if ($.copyToShare || section === 'shared-with-me') {
return l[1344]; // Share
}
if ($.copyToUpload) {
return l[372]; // Upload
}
if (section === 'conversations') {
return l[1940]; // Send
}
if ($.saveToDialog) {
return l[776]; // Save
}
if ($.moveDialog) {
return l[62]; // Move
}
if ($.selectFolderDialog) {
return l[1523]; // Select
}
return l[16176]; // Paste
};
/**
* Get the dialog title based on operation
* @returns {String}
* @private
*/
var getDialogTitle = function() {
if ($.mcImport) {
return l[236]; // Import
}
if ($.copyToShare) {
return l[1344]; // Share
}
if ($.copyToUpload) {
var len = $.copyToUpload[0].length;
return len < 2
? l[19338]
: escapeHTML(l[19339]).replace('[X]', len);
}
if ($.saveToDialog) {
return l[776]; // Save
}
if (section === 'conversations') {
return l[17764]; // Send to chat
}
if ($.moveDialog) {
return l[62]; // Move
}
return l[63]; // Copy
};
/**
* Getting contacts and view them in copy dialog
* @param {Boolean} allowConversationTab if true then it means show Conversation Tab
*/
var handleConversationTabContent = function _handleConversationTabContent() {
var myChats = megaChat.chats;
var myContacts = M.getContactsEMails(true); // true to exclude requests (incoming and outgoing)
var conversationTab = $('.fm-picker-dialog-tree-panel.conversations');
var conversationNoConvTab = $('.dialog-empty-block.conversations');
var conversationTabHeader = $('.fm-picker-dialog-panel-header', conversationTab);
var contactsContentBlock = $('.dialog-content-block', conversationTab);
if (myContacts && myContacts.length) {
var contactGeneratedList = "";
var ulListId = 'cpy-dlg-chat-' + u_handle;
var createContactEntry = function _createContactEntry(name, email, handle) {
if (name && handle && email) {
var contactElem = '';
contactElem += '';
contactElem += '';
contactElem += '' + escapeHTML(name) + '';
contactElem += '' + escapeHTML(email) + '';
contactElem += '' + '';
contactElem = '
' + contactElem + '
';
return contactElem;
}
else {
return '';
}
};
var createGroupEntry = function _createGroupEntry(names, nb, handle, chatRoom) {
if (names && names.length && nb && handle) {
var groupElem = '';
if (chatRoom && (chatRoom.type === "group" || chatRoom.type === "private")) {
groupElem += '';
}
groupElem += '';
var namesCombine = names[0];
var k = 1;
while (namesCombine.length <= 40 && k < names.length) {
namesCombine += ', ' + names[k];
k++;
}
if (k !== names.length) {
namesCombine = namesCombine.substr(0, 37);
namesCombine += '...';
}
groupElem += '' +
megaChat.plugins.emoticonsFilter.processHtmlMessage(escapeHTML(namesCombine)) +
'';
groupElem += '' + nb + ' chat members ';
groupElem += '' + '';
groupElem = '
' + groupElem + '
';
return groupElem;
}
else {
return '';
}
};
var addedContactsByRecent = [];
var top5 = 5; // defined in specs, top 5 contacts
var nbOfRecent = 0;
if (myChats && myChats.length) {
var sortedChats = obj_values(myChats.toJS());
sortedChats.sort(M.sortObjFn("lastActivity", -1));
for (var chati = 0; chati < sortedChats.length; chati++) {
var chatRoom = sortedChats[chati];
if (chatRoom.isArchived()) {
continue;
}
var isValidGroupOrPubChat = false;
if (chatRoom.type === 'group') {
isValidGroupOrPubChat = true;
}
else if (
chatRoom.type === "public" &&
chatRoom.membersSetFromApi &&
chatRoom.membersSetFromApi.members[u_handle] >= 2
) {
isValidGroupOrPubChat = true;
}
if (isValidGroupOrPubChat) {
var gNames = [];
if (!chatRoom.topic) {
ChatdIntegration._ensureNamesAreLoaded(chatRoom.members);
for (var grHandle in chatRoom.members) {
if (grHandle !== u_handle) {
gNames.push(M.getNameByHandle(grHandle));
}
}
}
else {
gNames.push(chatRoom.topic);
}
if (gNames.length) {
if (nbOfRecent < top5) {
var gElem = createGroupEntry(gNames,
Object.keys(chatRoom.members).length, chatRoom.roomId, chatRoom);
contactGeneratedList = contactGeneratedList + gElem;
}
else {
myContacts.push({
id: Object.keys(chatRoom.members).length,
name: gNames[0], handle: chatRoom.roomId, isG: true,
gMembers: gNames
});
}
nbOfRecent++;
}
}
else {
if (nbOfRecent < top5) {
var contactHandle;
for (var ctHandle in chatRoom.members) {
if (ctHandle !== u_handle) {
contactHandle = ctHandle;
break;
}
}
if (contactHandle) {
if (M.u[contactHandle] && M.u[contactHandle].c === 1 && M.u[contactHandle].m) {
addedContactsByRecent.push(contactHandle);
var ctElemC = createContactEntry(M.getNameByHandle(contactHandle),
M.u[contactHandle].m, contactHandle);
contactGeneratedList = contactGeneratedList + ctElemC;
nbOfRecent++;
}
}
}
}
}
}
myContacts.sort(M.sortObjFn("name", 1));
for (var a = 0; a < myContacts.length; a++) {
if (addedContactsByRecent.includes(myContacts[a].handle)) {
continue;
}
var ctElem;
if (!myContacts[a].isG) {
ctElem = createContactEntry(myContacts[a].name, myContacts[a].id, myContacts[a].handle);
}
else {
ctElem = createGroupEntry(
myContacts[a].gMembers,
myContacts[a].id,
myContacts[a].handle,
megaChat.chats[myContacts[a].handle]
);
}
contactGeneratedList = contactGeneratedList + ctElem;
}
contactGeneratedList = '
' + contactGeneratedList + '
';
contactsContentBlock.html(contactGeneratedList);
conversationTab.addClass('active');
conversationNoConvTab.removeClass('active');
conversationTabHeader.removeClass('hidden');
}
else {
conversationTab.removeClass('active');
conversationNoConvTab.addClass('active');
conversationTabHeader.addClass('hidden');
}
};
/**
* Handle DOM directly, no return value.
* @param {String} dialogTabClass dialog tab class name.
* @param {String} parentTag tag of source element.
* @param {String} htmlContent html content.
* @private
*/
var handleDialogTabContent = function(dialogTabClass, parentTag, htmlContent) {
var tabClass = '.' + dialogTabClass;
var $tab = $('.fm-picker-dialog-tree-panel' + tabClass, $dialog);
var html = String(htmlContent)
.replace(/treea_/ig, 'mctreea_')
.replace(/treesub_/ig, 'mctreesub_')
.replace(/treeli_/ig, 'mctreeli_');
$('.dialog-content-block', $tab).empty().safeHTML(html);
if ($('.dialog-content-block ' + parentTag, $tab).children().length) {
// Items available, hide empty message
$('.dialog-empty-block', $dialog).removeClass('active');
$('.fm-picker-dialog-panel-header', $tab).removeClass('hidden');
$tab.addClass('active'); // TODO check why this was only here
}
else {
// Empty message, no items available
$('.dialog-empty-block' + tabClass, $dialog).addClass('active');
$('.fm-picker-dialog-panel-header', $tab).addClass('hidden');
}
};
/**
* Build tree for a move/copy dialog.
* @private
*/
var buildDialogTree = function() {
var $dpa = $('.fm-picker-dialog-panel-arrows', $dialog).removeClass('hidden');
if (section === 'cloud-drive' || section === 'folder-link') {
M.buildtree(M.d[M.RootID], 'fm-picker-dialog', 'cloud-drive');
}
else if (section === 'shared-with-me') {
M.buildtree({h: 'shares'}, 'fm-picker-dialog');
}
else if (section === 'rubbish-bin') {
M.buildtree({h: M.RubbishID}, 'fm-picker-dialog');
}
else if (section === 'conversations') {
if (window.megaChatIsReady) {
// prepare Conversation Tab if needed
$dpa.addClass('hidden');
handleConversationTabContent();
}
else {
console.error('MEGAchat is not ready');
}
}
if (!treesearch) {
$('.fm-picker-dialog .nw-fm-tree-item').removeClass('expanded active opened selected');
$('.fm-picker-dialog ul').removeClass('opened');
}
disableFolders();
dialogScroll('.dialog-tree-panel-scroll');
onIdle(function() {
dialogScroll('.dialog-tree-panel-scroll');
});
};
/**
* Dialogs content handler
* @param {String} dialogTabClass Dialog tab class name.
* @param {String} [buttonLabel] Action button label.
* @private
*/
var handleDialogContent = function(dialogTabClass, buttonLabel) {
section = dialogTabClass || 'cloud-drive';
buttonLabel = buttonLabel || getActionButtonLabel();
$('.dialog-sorting-menu', $dialog).addClass('hidden');
$('.dialog-empty-block', $dialog).removeClass('active');
$('.fm-picker-dialog-button', $dialog).removeClass('active');
$('.fm-picker-dialog-tree-panel', $dialog).removeClass('active');
$('.fm-picker-dialog-panel-arrows', $dialog).removeClass('active');
// inherited dialog content...
var html = section !== 'conversations' && $('.content-panel.' + section).html();
// Action button label
$('.dialog-picker-button', $dialog).text(buttonLabel);
// if the site is initialized on the chat, $.selected may be `undefined`,
// which may cause issues doing .length on it in dialogs.js, so lets define it as empty array
// if is not def.
$.selected = $.selected || [];
// check if we will enable conversation tab
var allowConversationTab = false;
if ($.dialog === 'copy' && $.selected.length) {
allowConversationTab = true;
for (var i = $.selected.length; i--;) {
var n = M.getNodeByHandle($.selected[i]);
if (n.t !== 0) {
allowConversationTab = false;
break;
}
}
}
if (allowConversationTab || $.copyToUpload || $.saveToDialogNode) {
$('.fm-picker-dialog-button.rubbish-bin', $dialog).addClass('hidden');
$('.fm-picker-dialog-button.conversations', $dialog).removeClass('hidden');
}
else {
$('.fm-picker-dialog-button.conversations', $dialog).addClass('hidden');
$('.fm-picker-dialog-button.rubbish-bin', $dialog).removeClass('hidden');
}
if (!u_type || $.saveToDialog || $.copyToShare || $.mcImport || $.selectFolderDialog) {
$('.fm-picker-dialog-button.rubbish-bin', $dialog).addClass('hidden');
$('.fm-picker-dialog-button.conversations', $dialog).addClass('hidden');
}
if ($.copyToShare || $.selectFolderDialog) {
$('.fm-picker-dialog-button.shared-with-me', $dialog).addClass('hidden');
}
else {
$('.fm-picker-dialog-button.shared-with-me', $dialog).removeClass('hidden');
}
if ($.copyToUpload) {
$('.fm-picker-notagain', $dialog).removeClass('hidden');
}
else {
$('.fm-picker-notagain', $dialog).addClass('hidden');
}
handleDialogTabContent(section, section === 'conversations' ? 'div' : 'ul', html);
buildDialogTree();
// 'New Folder' button
if (section === 'shared-with-me' || section === 'conversations') {
$('.dialog-newfolder-button', $dialog).addClass('hidden');
}
else {
$('.dialog-newfolder-button', $dialog).removeClass('hidden');
}
// If copying from contacts tab (Ie, sharing)
if (section === 'cloud-drive' && M.currentrootid === 'contacts') {
$('.fm-picker-dialog-title', $dialog).text(l[1344]);
$('.dialog-newfolder-button', $dialog).addClass('hidden');
$('.share-dialog-permissions', $dialog).removeClass('hidden')
.rebind('click', function() {
var $btn = $(this);
var $menu = $('.permissions-menu', this.parentNode);
var $items = $('.permissions-menu-item', $menu);
$menu.removeAttr('style');
$items
.rebind('click', function() {
$items.off('click');
$items.removeClass('active');
$(this).addClass('active');
$btn.attr('class', 'share-dialog-permissions ' + this.classList[1])
.safeHTML('' + $(this).text());
$menu.fadeOut(200);
});
$menu.fadeIn(200);
});
}
else if ($.selectFolderDialog) {
$('.fm-picker-dialog-title', $dialog).text(l[16533]);
}
else {
$('.share-dialog-permissions', $dialog).addClass('hidden');
$('.fm-picker-dialog-title', $dialog).text(getDialogTitle());
}
dialogPositioning($dialog);
// Activate tab
$('.fm-picker-dialog-button.' + section, $dialog).addClass('active');
};
/**
* Handle opening dialogs and content
* @param {String} aTab The section/tab to activate
* @param {String} [aTarget] The target folder for the operation
* @param {String|Object} [aMode] Copy dialog mode (share, save, etc)
*/
var handleOpenDialog = function(aTab, aTarget, aMode) {
onIdle(function() {
/** @name $.copyDialog */
/** @name $.moveDialog */
/** @name $.selectFolderDialog */
$[$.dialog + 'Dialog'] = $.dialog;
if (aMode) {
/** @name $.copyToShare */
/** @name $.copyToUpload */
/** @name $.saveToDialog */
$[aMode.key || aMode] = aMode.value || true;
}
$('.search-bar input', $dialog).val('');
handleDialogContent(typeof aTab === 'string' && aTab);
setDialogBreadcrumb(aTarget);
setDialogButtonState($('.dialog-picker-button', $dialog).addClass('active'));
setSelectedItems(true);
});
$.hideContextMenu();
dialogPositioning($dialog);
console.assert($dialog, 'The dialogs subsystem is not yet initialized!...');
};
/** Checks if the user can access dialogs copy/move/share */
var isUserAllowedToOpenDialogs = function() {
if (u_attr && u_attr.b && u_attr.b.s === -1) {
$.hideContextMenu();
M.showExpiredBusiness();
return false;
}
return true;
};
// ------------------------------------------------------------------------
// ---- Public Functions --------------------------------------------------
/**
* Refresh copy/move dialog content with newly created directory.
*/
global.refreshDialogContent = function refreshDialogContent() {
var tab = $.cfsection || 'cloud-drive';
var b = $('.content-panel.' + tab).html();
handleDialogTabContent(tab, 'ul', b);
buildDialogTree();
delete $.cfsection; // safe deleting
disableFolders($.moveDialog && 'move');
dialogScroll('.dialog-tree-panel-scroll');
};
/**
* A version of the Copy dialog used in the contacts page for sharing.
* @param {String} [u_id] Share to contact handle.
* @global
*/
global.openCopyShareDialog = function openCopyShareDialog(u_id) {
if (isUserAllowedToOpenDialogs()) {
M.safeShowDialog('copy', function() {
$.shareToContactId = u_id;
handleOpenDialog('cloud-drive', false, 'copyToShare');
return $dialog;
});
}
return false;
};
/**
* A version of the Copy dialog used when uploading.
* @param {Array} files The files being uploaded.
* @param {Object} [emptyFolders] Empty folders to create hierarchy for.
* @global
*/
global.openCopyUploadDialog = function openCopyUploadDialog(files, emptyFolders) {
if (isUserAllowedToOpenDialogs()) {
M.safeShowDialog('copy', function() {
var tab = M.chat ? 'conversations' : M.currentrootid === 'shares' ? 'shared-with-me' : 'cloud-drive';
handleOpenDialog(tab, M.currentdirid, { key: 'copyToUpload', value: [files, emptyFolders] });
return uiCheckboxes($dialog);
});
}
return false;
};
/**
* Generic function to open the Copy dialog.
* @global
*/
global.openCopyDialog = function openCopyDialog(activeTab, onBeforeShown) {
if (isUserAllowedToOpenDialogs()) {
M.safeShowDialog('copy', function() {
if (typeof activeTab === 'function') {
onBeforeShown = activeTab;
activeTab = false;
}
if (typeof onBeforeShown === 'function') {
onBeforeShown($dialog);
}
handleOpenDialog(activeTab, M.RootID);
return $dialog;
});
}
return false;
};
/**
* Generic function to open the Move dialog.
* @global
*/
global.openMoveDialog = function openMoveDialog() {
if (isUserAllowedToOpenDialogs()) {
M.safeShowDialog('move', function() {
handleOpenDialog(0, M.RootID);
return $dialog;
});
}
return false;
};
/**
* A version of the Copy dialog used for "Save to" in chat.
* @global
*/
global.openSaveToDialog = function openSaveToDialog(node, cb, activeTab) {
if (isUserAllowedToOpenDialogs()) {
M.safeShowDialog('copy', function() {
$.saveToDialogCb = cb;
$.saveToDialogNode = node;
handleOpenDialog(activeTab, M.RootID, activeTab !== 'conversations' && 'saveToDialog');
return $dialog;
});
}
return false;
};
/**
* A version of the select a folder dialog used for "New Shared Folder" in out-shares.
* @global
*/
global.openNewSharedFolderDialog = function openNewSharedFolderDialog() {
if (isUserAllowedToOpenDialogs()) {
M.safeShowDialog('selectFolder', function() {
$.selected = [];
handleOpenDialog(0, M.RootID);
$.selectFolderCallback = function() {
closeDialog();
$.selected = [$.mcselected];
M.openSharingDialog();
};
return $dialog;
});
}
return false;
};
mBroadcaster.addListener('fm:initialized', function copyMoveDialogs() {
if (folderlink) {
return false;
}
$dialog = $('.fm-dialog.fm-picker-dialog');
var $btn = $('.dialog-picker-button', $dialog);
var $swm = $('.shared-with-me', $dialog);
var dialogTooltipTimer;
var treePanelHeader = document.querySelector('.fm-picker-dialog-panel-header');
$('.fm-picker-dialog-tree-panel', $dialog).each(function(i, elm) {
elm.insertBefore(treePanelHeader.cloneNode(true), elm.firstElementChild);
});
treePanelHeader.parentNode.removeChild(treePanelHeader);
// dialog sort
$dialog.find('.fm-picker-dialog-panel-header').append($('.dialog-sorting-menu.hidden').clone());
$('.fm-picker-dialog-tree-panel.conversations .fm-picker-dialog-panel-header span:first').text(l[17765]);
$('.fm-dialog-close, .dialog-cancel-button', $dialog).rebind('click', closeDialog);
$('.fm-picker-dialog-button', $dialog).rebind('click', function _(ev) {
section = $(this).attr('class').split(" ")[1];
if (section === 'shared-with-me' && ev !== -0x3f) {
$('.dialog-content-block', $dialog).empty();
$('.fm-picker-dialog-button', $dialog).removeClass('active');
$(this).addClass('active');
dbfetch.geta(Object.keys(M.c.shares || {}), new MegaPromise())
.always(function() {
if (section === 'shared-with-me') {
_.call(this, -0x3f);
}
}.bind(this));
return false;
}
treesearch = false;
handleDialogContent(section);
$('.search-bar input', $dialog).val('');
$('.nw-fm-tree-item', $dialog).removeClass('selected');
if ($.copyToShare) {
setDialogBreadcrumb();
}
else if (section === 'cloud-drive' || section === 'folder-link') {
setDialogBreadcrumb(M.RootID);
}
else if (section === 'rubbish-bin') {
setDialogBreadcrumb(M.RubbishID);
}
else {
setDialogBreadcrumb();
}
setDialogButtonState($btn);
});
/**
* On click, copy dialog, dialog-sorting-menu will be shown.
* Handles that valid informations about current sorting options
* for selected tab of copy dialog are up to date.
*/
$('.fm-picker-dialog-panel-arrows', $dialog).rebind('click', function() {
var $self = $(this);
if (!$self.hasClass('active')) {
var $menu = $('.dialog-sorting-menu', $dialog).removeClass('hidden');
var p = $self.position();
$menu.css('left', p.left + 24 + 'px');
$menu.css('top', p.top - 8 + 'px');
if (section === 'contacts') {
// Enable all menu items
$menu.find('.sorting-item-divider,.sorting-menu-item').removeClass('hidden');
$menu.find('*[data-by=label]').addClass('hidden');
}
else {
// Hide sort by status and last-interaction items from menu
$menu.find('*[data-by=status],*[data-by=last-interaction],*[data-by=fav]').addClass('hidden');
$menu.find('*[data-by=label]').removeClass('hidden');
}
// @ToDo: Make sure .by is hadeled properly once when we have chat available
// Copy dialog key only
var key = $.dialog[0].toUpperCase() + $.dialog.substr(1) + section;
$menu.find('.dropdown-item').removeClass('active asc desc');
// Check existance of previous sort options, direction (dir)
if (localStorage['sort' + key + 'Dir']) {
$.sortTreePanel[key].dir = localStorage['sort' + key + 'Dir'];
$menu.find('.dropdown-item[data-by="' + localStorage['sort' + key + 'By'] + '"]')
.addClass($.sortTreePanel[key].dir > 0 ? 'asc' : 'desc');
}
else {
$.sortTreePanel[key].dir = 1;
$menu.find('.dropdown-item[data-by="' + localStorage['sort' + key + 'By'] + '"]')
.addClass('asc');
}
// Check existance of previous sort option, ascending/descending (By)
if (localStorage['sort' + key + 'By']) {
$.sortTreePanel[key].by = localStorage['sort' + key + 'By'];
$menu.find('.dropdown-item[data-by="' + localStorage['sort' + key + 'By'] + '"]')
.addClass('active');
}
else {
$.sortTreePanel[key].by = 'name';
$menu.find('.dropdown-item[data-by="name"]')
.addClass('active');
}
$self.addClass('active');
$dialog.find('.dialog-sorting-menu').removeClass('hidden');
}
else {
$self.removeClass('active');
$dialog.find('.dialog-sorting-menu').addClass('hidden');
}
});
$('.dialog-sorting-menu .dropdown-item', $dialog).rebind('click', function() {
var $self = $(this);
// Arbitrary element data
var data = $self.data();
var key = $.dialog[0].toUpperCase() + $.dialog.substr(1) + section;
if (data.by) {
localStorage['sort' + key + 'By'] = $.sortTreePanel[key].by = data.by;
}
$self.removeClass('asc desc');
if ($self.hasClass('active')) {
$.sortTreePanel[key].dir *= -1;
localStorage['sort' + key + 'Dir'] = $.sortTreePanel[key].dir;
$self.addClass($.sortTreePanel[key].dir > 0 ? 'asc' : 'desc');
}
buildDialogTree();
// Disable previously selected
$self.parent().find('.sorting-menu-item').removeClass('active');
$self.addClass('active');
// Hide menu
$('.dialog-sorting-menu', $dialog).addClass('hidden');
$('.fm-picker-dialog-panel-arrows.active').removeClass('active');
});
$('.search-bar input', $dialog).rebind('keyup.dsb', function(ev) {
var value = String($(this).val()).toLowerCase();
var exit = ev.keyCode === 27 || !value;
if (section === 'conversations') {
var $lis = $('.nw-contact-item', $dialog).parent();
if (exit) {
$lis.removeClass('tree-item-on-search-hidden');
if (value) {
$(this).val('').trigger("blur");
}
}
else {
$lis.addClass('tree-item-on-search-hidden').each(function(i, elm) {
var sel = ['.nw-contact-name', '.nw-contact-email'];
for (i = sel.length; i--;) {
var tmp = elm.querySelector(sel[i]);
if (tmp) {
tmp = String(tmp.textContent).toLowerCase();
if (tmp.indexOf(value) !== -1) {
elm.classList.remove('tree-item-on-search-hidden');
break;
}
}
}
});
}
onIdle(function() {
dialogScroll('.dialog-tree-panel-scroll');
});
}
else {
if (exit) {
treesearch = false;
if (value) {
$(this).val('').trigger("blur");
}
}
else {
treesearch = value;
}
delay('mctree:search', buildDialogTree);
}
return false;
});
$('.dialog-newfolder-button', $dialog).rebind('click', function() {
$dialog.addClass('arrange-to-back');
$.cfpromise = new MegaPromise();
$.cftarget = $.mcselected || (section === 'cloud-drive' ? M.RootID : M.RubbishID);
$.cfsection = section;
createFolderDialog();
$('.fm-dialog.create-folder-dialog .create-folder-size-icon').addClass('hidden');
// Auto-select the created folder.
$.cfpromise.done(function(h) {
var p = $.cftarget;
// Make sure parent has selected class to make it expand
$('#mctreea_' + p, $dialog).addClass('selected');
selectTreeItem(p);
selectTreeItem(h);
});
});
$dialog.rebind('click', '.nw-contact-item', function() {
var $this = $(this);
if ($this.hasClass('selected')) {
$this.removeClass('selected');
}
else {
$this.addClass('selected');
}
setDialogBreadcrumb();
setDialogButtonState($btn);
dialogScroll('.dialog-tree-panel-scroll');
// Scroll the element into view, only needed if element triggered.
var jsp = $(this).parents('.dialog-tree-panel-scroll').data('jsp');
if (jsp) {
jsp.scrollToElement($(this));
}
});
$dialog.rebind('click', '.nw-fm-tree-item', function(e) {
var ts = treesearch;
var old = $.mcselected;
setDialogBreadcrumb(String($(this).attr('id')).replace('mctreea_', ''));
treesearch = false;
M.buildtree({h: $.mcselected}, 'fm-picker-dialog', section);
treesearch = ts;
disableFolders();
var c = $(e.target).attr('class');
// Sub-folder exist?
if (c && c.indexOf('nw-fm-arrow-icon') > -1) {
c = $(this).attr('class');
// Sub-folder expanded
if (c && c.indexOf('expanded') > -1) {
$(this).removeClass('expanded');
$('#mctreesub_' + $.mcselected).removeClass('opened');
}
else {
$(this).addClass('expanded');
$('#mctreesub_' + $.mcselected).addClass('opened');
}
}
else {
c = $(this).attr('class');
if (c && c.indexOf('selected') > -1) {
if (c && c.indexOf('expanded') > -1) {
$(this).removeClass('expanded');
$('#mctreesub_' + $.mcselected).removeClass('opened');
}
else {
$(this).addClass('expanded');
$('#mctreesub_' + $.mcselected).addClass('opened');
}
}
}
if (!$(this).is('.disabled')) {
// unselect previously selected item
$('.nw-fm-tree-item', $dialog).removeClass('selected');
$(this).addClass('selected');
$btn.removeClass('disabled');
}
else if ($('#mctreea_' + old + ':visible').length) {
setDialogBreadcrumb(old);
$('#mctreea_' + old).addClass('selected');
}
else {
setDialogBreadcrumb();
}
// dialogScroll('.fm-picker-dialog-tree-panel .dialog-tree-panel-scroll');
dialogScroll('.dialog-tree-panel-scroll');
// Disable action button if there is no selected items
setDialogButtonState($btn);
// Set opened & expanded ancestors, only needed if element triggered.
$(this).parentsUntil('.dialog-content-block', 'ul').addClass('opened')
.prev('.nw-fm-tree-item').addClass('expanded');
// Scroll the element into view, only needed if element triggered.
var jsp = $(this).parents('.dialog-tree-panel-scroll').data('jsp');
if (jsp) {
jsp.scrollToElement($(this), true);
}
if ($.mcselected && M.getNodeRights($.mcselected) > 0) {
$('.dialog-newfolder-button', $dialog).removeClass('hidden');
}
else {
$('.dialog-newfolder-button', $dialog).addClass('hidden');
}
});
$swm.rebind('mouseenter', '.nw-fm-tree-item', function _try(ev) {
var h = $(this).attr('id').replace('mctreea_', '');
if (ev !== 0xEFAEE && !M.c[h]) {
var self = this;
dbfetch.get(h).always(function() {
_try.call(self, 0xEFAEE);
});
return false;
}
var share = shares[h];
var owner = share && share.owner;
var user = M.getUserByHandle(owner);
if (!user) {
return false;
}
var $item = $(this).find('.nw-fm-tree-folder');
var itemLeftPos = $item.offset().left;
var itemTopPos = $item.offset().top;
var $tooltip = $('.contact-preview', $dialog);
var avatar = useravatar.contact(owner, '', 'div');
var note = !share.level && !share.circular && l[19340];
$tooltip.find('.contacts-info.body')
.safeHTML(
avatar +
'
' +
'
@@(@@)
' +
'
@@
' +
'
@@
' +
'
', user.name || '', l[8664], user.m || '', note ? 'note' : '', note || ''
);
clearTimeout(dialogTooltipTimer);
dialogTooltipTimer = setTimeout(function() {
$tooltip.css({
'left': itemLeftPos + $item.outerWidth() / 2 - $tooltip.outerWidth() / 2 + 'px',
'top': (itemTopPos - (note ? 99 : 63)) + 'px'
});
$tooltip.fadeIn(200);
}, 200);
return false;
});
$swm.rebind('mouseleave', '.nw-fm-tree-item', function() {
var $tooltip = $('.contact-preview', $dialog);
clearTimeout(dialogTooltipTimer);
$tooltip.hide();
return false;
});
// Handle conversations tab item selection
$dialog.rebind('click', '.nw-conversations-item', function() {
setDialogBreadcrumb(String($(this).attr('id')).replace('contact2_', ''));
// unselect previously selected item
$('.nw-conversations-item', $dialog).removeClass('selected');
$(this).addClass('selected');
$btn.removeClass('disabled');
// Disable action button if there is no selected items
setDialogButtonState($btn);
});
// Handle copy/move/share button action
$btn.rebind('click', function() {
var chats = getSelectedChats();
var skip = !$.mcselected && section !== 'conversations';
if (skip || $(this).hasClass('disabled')) {
return false;
}
var selectedNodes = ($.selected || []).concat();
// closeDialog would cleanup some $.* variables, so we need them cloned here
var saveToDialogNode = $.saveToDialogNode;
var saveToDialogCb = $.saveToDialogCb;
var saveToDialog = $.saveToDialog || saveToDialogNode;
var shareToContactId = $.shareToContactId;
delete $.saveToDialogPromise;
if ($.copyToUpload) {
var data = $.copyToUpload;
var target = $.mcselected;
if (section === 'conversations') {
target = chats.map(function(h) {
if (megaChat.chats[h]) {
return megaChat.chats[h].getRoomUrl().replace("fm/", "");
} else if (M.u[h]) {
return 'chat/p/' + h;
}
else {
if (d) {
console.error("Chat room not found for handle:", h);
}
return '';
}
});
}
if ($('.notagain', $dialog).prop('checked')) {
mega.config.setn('ulddd', 0);
}
closeDialog();
M.addUpload(data[0], false, data[1], target);
return false;
}
if ($.moveDialog) {
if (section === "shared-with-me") {
var $tooltip = $('.contact-preview', $dialog);
clearTimeout(dialogTooltipTimer);
$tooltip.hide();
}
closeDialog();
M.safeMoveNodes($.mcselected);
return false;
}
if ($.selectFolderDialog && typeof $.selectFolderCallback === 'function') {
$.selectFolderCallback();
return false;
}
closeDialog();
if (saveToDialog) {
saveToDialogCb(saveToDialogNode, section === 'conversations' && chats || $.mcselected);
return false;
}
// Get active tab
if (section === 'cloud-drive' || section === 'folder-link' || section === 'rubbish-bin') {
// If copying from contacts tab (Ie, sharing)
if ($(this).text().trim() === l[1344]) {
var user = {
u: shareToContactId ? shareToContactId : M.currentdirid
};
var $sp = $('.share-dialog-permissions', $dialog);
if ($sp.hasClass('read-and-write')) {
user.r = 1;
}
else if ($sp.hasClass('full-access')) {
user.r = 2;
}
else {
user.r = 0;
}
doShare($.mcselected, [user], true);
}
else {
M.copyNodes(selectedNodes, $.mcselected);
}
}
else if (section === 'shared-with-me') {
M.copyNodes(getNonCircularNodes(selectedNodes), $.mcselected);
}
else if (section === 'conversations') {
if (window.megaChatIsReady) {
megaChat.openChatAndAttachNodes(chats, selectedNodes).dump();
}
else if (d) {
console.error('MEGAchat is not ready');
}
}
delete $.onImportCopyNodes;
return false;
});
return 0xDEAD;
});
})(self);
(function _properties(global) {
'use strict';
/**
* Handles node properties/info dialog contact list content
* @param {Object} $dialog The properties dialog
* @param {Array} users The list of users to whom we're sharing the selected nodes
* @private
*/
function fillPropertiesContactList($dialog, users) {
var MAX_CONTACTS = 5;
var shareUsersHtml = '';
var $shareUsers = $dialog.find('.properties-body .properties-context-menu')
.empty()
.append('');
for (var i = users.length; i--;) {
var user = users[i];
var userHandle = user.u || user.p;
var hidden = i >= MAX_CONTACTS ? 'hidden' : '';
var status = megaChatIsReady && megaChat.getPresenceAsCssClass(user.u);
shareUsersHtml += '