/* 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
*/
(function(scope, $) {
var PUSH = Array.prototype.push;
if (typeof lazy === 'undefined') lazy = function(a,b,c) { a[b] = c.call(a); }
if (typeof delay === 'undefined') delay = function(a,b) { b() }
if (typeof SoonFc === 'undefined') SoonFc = function(a,b) { return b }
/**
* 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];
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 = false;
/**
* 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.throttledOnScroll = function(e) {
var self = this;
delay('megalist:scroll:' + this.listId, function() {
if (self._isUserScroll === true && self.listContainer === e.target) {
if (self.options.enableUserScrollEvent) {
self.trigger('onUserScroll', e);
}
self._onScroll(e);
}
}, 30);
};
/**
* 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, SoonFc(40, self.resized.bind(self)));
$(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) {
PUSH.apply(this.items, itemIdsArray);
if (this._wasRendered) {
this._contentUpdated();
this._applyDOMChanges();
}
};
/**
* Optimised replacing of entries, less DOM updates
*
* @param items {Array} Array of item IDs (Strings)
*/
MegaList.prototype.batchReplace = function(items) {
this.items = items;
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 () {
if (!this._wasRendered) {
return;
}
this._calculated = false;
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;
}
}
};
/**
* 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 = false;
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();
}
};
/**
* 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() {
if (this._calculated) {
return this._calculated;
}
var self = this;
var calculated = this._calculated = Object.create(null);
lazy(calculated, 'scrollWidth', function() {
return self.$listContainer.innerWidth();
});
lazy(calculated, 'scrollHeight', function() {
return self.$listContainer.innerHeight();
});
lazy(calculated, 'itemWidth', function() {
if (self.options.itemWidth === false) {
return this.scrollWidth;
}
return self.options.itemWidth;
});
lazy(calculated, 'contentWidth', function() {
var contentWidth = self.$listContainer.children(":first").outerWidth();
if (contentWidth) {
return contentWidth;
}
return this.itemWidth;
});
lazy(calculated, 'itemsPerRow', function() {
return Math.max(1, Math.floor(this.contentWidth / this.itemWidth));
});
lazy(calculated, 'itemsPerPage', function() {
return Math.ceil(this.scrollHeight / self.options.itemHeight) * this.itemsPerRow;
});
lazy(calculated, 'contentHeight', function() {
return Math.ceil(self.items.length / this.itemsPerRow) * self.options.itemHeight;
});
lazy(calculated, 'scrollLeft', function() {
return self.listContainer.scrollLeft;
});
lazy(calculated, 'scrollTop', function() {
return self.listContainer.scrollTop;
});
lazy(calculated, 'scrolledPercentX', function() {
return 100 / this.scrollWidth * this.scrollLeft;
});
lazy(calculated, 'scrolledPercentY', function() {
return 100 / this.scrollHeight * this.scrollTop;
});
lazy(calculated, 'isAtTop', function() {
return this.scrollTop === 0;
});
lazy(calculated, 'isAtBottom', function() {
return self.listContainer.scrollTop === this.scrollHeight;
});
lazy(calculated, 'itemsPerPage', function() {
return Math.ceil(this.scrollHeight / self.options.itemHeight) * this.itemsPerRow;
});
lazy(calculated, 'visibleFirstItemNum', function() {
var value = 0;
if (self.options.appendOnly !== true) {
value = Math.floor(Math.floor(this.scrollTop / self.options.itemHeight) * this.itemsPerRow);
if (value > 0) {
value = Math.max(0, value - (self.options.extraRows * this.itemsPerRow));
}
}
return value;
});
lazy(calculated, 'visibleLastItemNum', function() {
var value = Math.min(
self.items.length,
Math.ceil(
Math.ceil(this.scrollTop / self.options.itemHeight) *
this.itemsPerRow + this.itemsPerPage
)
);
if (value < self.items.length) {
value = Math.min(self.items.length, value + (self.options.extraRows * this.itemsPerRow));
}
return value;
});
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
);
}
return calculated;
};
/**
* 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) {
if (this._wasRendered || forced) {
this._calculated = false;
this._recalculate();
if (this._lastContentHeight !== this._calculated['contentHeight']) {
this._lastContentHeight = this._calculated['contentHeight'];
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]) {
var renderedNode = this.options.itemRenderFunction(id);
if (!renderedNode) {
console.warn('MegaList: Node not found...', id);
continue;
}
contentWasUpdated = true;
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();
}
var self = this;
delay('megalist:content-updated:' + this.listId, function() {
self._isUserScroll = false;
self.scrollUpdate();
self._isUserScroll = true;
if (self.options.onContentUpdated) {
delay('megalist:content-updated:feedback:' + self.listId, self.options.onContentUpdated, 650);
}
}, 300);
}
};
/**
* 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._calculated = false;
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 megaList = this.megaList;
if (!node) {
node = megaList._currentlyRendered[itemId];
if (!node) {
return;
}
}
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 megaList = this.megaList;
if (!node) {
node = megaList._currentlyRendered[itemId];
}
if (node && !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;
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);
};
MegaDynamicList.prototype.scrollToYPosition = function(value) {
'use strict';
this.listContainer.scrollTop = value;
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;
// Defined allowed dialogs' name
var allowedDialogs = {'copy':true, 'move':true};
// hide on page change
if (QuickFinder._pageChangeListenerId) {
mBroadcaster.removeListener(QuickFinder._pageChangeListenerId);
}
QuickFinder._pageChangeListenerId = mBroadcaster.addListener('pagechange', function () {
if (self.is_active()) {
self.deactivate();
}
// Clear the repeat key press setting if change the page
last_key = null;
});
$(window).rebind('keypress.quickFinder', function(e) {
if (!window.M || M.chat) {
return;
}
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 && !allowedDialogs[$.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");
self.was_activated();
var foundIds = [];
var isCopyToChat = false;
charTyped = charTyped.toLowerCase();
var nodesList = M.v;
if ($.dialog && allowedDialogs[$.dialog]) {
// Assign different nodes list depending on different panels
var activePanel = $('.dialog-content-block').closest('.fm-picker-dialog-tree-panel.active');
if (activePanel.hasClass('cloud-drive')) {
nodesList = Object.values(M.tree[M.RootID]);
}
else if (activePanel.hasClass('shared-with-me')) {
nodesList = Object.values(M.tree.shares);
}
else if (activePanel.hasClass('conversations')) {
isCopyToChat = true;
nodesList = [];
var allContactElements = $('span.nw-contact-item', activePanel).get();
for (var c = 0; c < allContactElements.length; c++) {
var $contactElement = $(allContactElements[c]);
var contactHandle = $contactElement.attr('id').replace('cpy-dlg-chat-itm-spn-', '');
var contactName = $('span.nw-contact-name', $contactElement).text();
nodesList.push({name: contactName, h: contactHandle});
}
}
else {
// Other panels rather than cloud-drive, share-with-me and send-to-chat
return;
}
if (!isCopyToChat) {
// Sort the node list by name except for the conversations panel
nodesList.sort(function(a, b) {
var aName = a.name.toUpperCase();
var bName = b.name.toUpperCase();
return M.compareStrings(aName, bName, d);
});
}
}
foundIds = nodesList.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;
}
return false;
});
if ($.dialog && allowedDialogs[$.dialog]) {
if (foundIds.length > 0) {
// Fetch the first node after quick finding
var dialogQuickIndex = 0;
var $dialogQuickFindNode;
if (isCopyToChat) {
// When it's in the conversations panel
$dialogQuickFindNode = $('#cpy-dlg-chat-itm-spn-' + foundIds[0].h);
}
else {
// When it's in the cloud-drive or share-with-me panel
for (var i = 0; i < foundIds.length; i++) {
$dialogQuickFindNode = $('.nw-fm-tree-item#mctreea_' + foundIds[dialogQuickIndex].h);
if (!$dialogQuickFindNode.hasClass('disabled')) {
// cloud-drive panel: Acquire the first node except for $.selected itself
// share-with-me panel: Acquire the first node with the write permission
break;
}
$dialogQuickFindNode = null;
dialogQuickIndex++;
}
}
if ($dialogQuickFindNode && !$dialogQuickFindNode.hasClass('selected')) {
$dialogQuickFindNode.trigger('click');
}
}
return;
}
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.resetTo(foundIds[next_idx], 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 elem = '.files-grid-view.fm';
if (M.viewmode == 1) {
elem = '.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 ($(elem + ':visible').length) {
elem = $('.grid-scrolling-table:visible, .file-block-scrolling:visible');
var jsp = elem.data('jsp');
if (jsp) {
switch (charCode) {
case 33: /* Page Up */
jsp.scrollByY(-elem.height(), !0);
break;
case 34: /* Page Down */
jsp.scrollByY(elem.height(), !0);
break;
case 35: /* End */
jsp.scrollToBottom(!0);
break;
case 36: /* Home */
jsp.scrollToY(0, !0);
break;
}
}
}
}
});
self.was_activated = function() {
// hide the search field when the user had clicked somewhere in the document
$(document.body).on('mousedown.qfinder', '> *', 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");
$(document.body).off('mousedown.qfinder', '> *');
};
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, li span.nw-fm-tree-folder',
'.files-grid-view, .fm-blocks-view.fm, .contacts-grid-table, .contacts-blocks-scrolling,' +
'.contact-requests-grid, .sent-requests-grid, .fm-picker-dialog .dialog-content-block'
);
/**
* 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;
var idMapper = String;
if (M.currentdirid === "ipc" || M.currentdirid === "opc") {
idMapper = function(n) {
return n.p ? M.currentdirid + "_" + n.p : n.h;
};
}
else if (M.currentdirid === "contacts") {
idMapper = function(u) {
return u.u;
};
}
/**
* Store all selected items in an _ordered_ array.
*
* @type {Array}
*/
this.selected_list = [];
this.selected_totalSize = 0;
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.classList.contains("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) {
var $target = $(e.target);
if ($target.parent().is('.file-block-scrolling:not(.hidden)') &&
!$target.is('.ps-scrollbar-x-rail') && !$target.is('.ps-scrollbar-y-rail')) {
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();
$('.ui-selected', $selectable).removeClass('ui-selected');
this.selected_list = $.selected = [];
this.clear_last_selected();
var self = this;
onIdle(function() {
var list = self.selected_list;
if (list && !list.length) {
self.hideSelectionBar();
}
});
};
/**
* Clear the current selection and select only the pointed item instead
* @param {Array|String|MegaNode} item Either a MegaNode, and array of them, or its handle.
* @param {Boolean} [scrollTo] Whether the item shall be scrolled into view.
* @returns {String} The node handle
*/
this.resetTo = function(item, scrollTo) {
this.clear_selection();
return this.set_currently_selected(item, scrollTo);
};
/**
* 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;
}
};
/**
* Get safe list item..
* @param {Array|String|MegaNode} item Either a MegaNode, and array of them, or its handle.
* @returns {String|Boolean} Either the node handle as an string or false if unable to determine.
* @private
*/
this._getSafeListItem = function(item) {
if (typeof item !== 'string') {
if (!(item instanceof MegaNode)) {
item = item && item[item.length - 1] || false;
}
if (item && typeof item !== 'string') {
item = idMapper(item) || false;
}
}
return item;
};
/**
* Used from the shortcut keys code.
*
* @param nodeId
*/
this.set_currently_selected = function(nodeId, scrollTo) {
self.clear_last_selected();
quickFinder.disable_if_active();
nodeId = this._getSafeListItem(nodeId);
if (!nodeId || this.selected_list.indexOf(nodeId) === -1) {
if (nodeId) {
this.add_to_selection(nodeId, scrollTo);
}
return nodeId;
}
this.last_selected = nodeId;
if (scrollTo) {
$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);
}
}
}
}
return nodeId;
};
this.add_to_selection = function(nodeId, scrollTo, alreadySorted) {
var tmp = this._getSafeListItem(nodeId);
if (!tmp) {
console.error("Unable to determine item type...", nodeId);
return;
}
nodeId = tmp;
if (this.selected_list.indexOf(nodeId) === -1) {
this.selected_list.push(nodeId);
var selectionSize = false;
for (var i = this.selected_list.length; i--;) {
var n = this.selected_list[i];
var e = document.getElementById(n);
if (e) {
e.classList.add('ui-selected');
}
if ((n = M.d[n])) {
selectionSize += n.t ? n.tb : n.s;
}
}
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 = {};
for (var j = 0; j < M.v.length; ++j) {
currentViewOrderMap[idMapper(M.v[j])] = j;
}
// 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;
});
}
if (selectionSize !== false) {
this.selectionNotification(selectionSize);
}
}
$.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));
}
this.selectionNotification(nodeId, false);
}
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() {
$.selected = this.selected_list = M.v.map(idMapper);
var container = this._ensure_selectable_is_available().get(0);
var nodeList = container && container.querySelectorAll('.megaListItem') || false;
if (nodeList.length) {
for (var i = nodeList.length; i--;) {
nodeList[i].classList.add('ui-selected');
}
this.set_currently_selected(nodeList[0].id);
}
else if (this.selected_list.length) {
// Not under a MegaList-powered view
self.add_to_selection(this.selected_list.pop(), false, true);
}
if (M.d[M.currentdirid]) {
this.selectionNotification(M.d[M.currentdirid].tb);
}
};
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 nextId = null;
var currentViewIds = M.v.map(idMapper);
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) {
nextId = currentViewIds[nextIndex - 1];
// clear old selection if no shiftKey
if (!shiftKey) {
this.resetTo(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.resetTo(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) {
nextId = currentViewIds[nextIndex + 1];
// clear old selection if no shiftKey
if (!shiftKey) {
this.resetTo(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.resetTo(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 (!Object(M.v).length) {
// Nothing to do here.
return;
}
if (this.selected_list.length === 0) {
this.set_currently_selected(idMapper(M.v[0]), scrollTo);
return;
}
var currentViewIds = M.v.map(idMapper);
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.map(idMapper);
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');
oDestroy(this);
};
/**
* Update the selection notification message once a node is added or removed
* @param nodeId
* @param isAddToSelection
* @returns {Boolean}
*/
this.selectionNotification = function (nodeId, isAddToSelection) {
if (M.chat || typeof nodeId !== 'number' && !M.d[nodeId]) {
return false;
}
if (this.selected_list.length === 0) {
this.hideSelectionBar();
}
else {
var totalNodes = M.v.length;
var itemsNum = this.selected_list.length;
var itemsTotalSize = "";
var notificationText = "";
if (typeof nodeId === 'number') {
this.selected_totalSize = nodeId;
}
else if (isAddToSelection) {
this.selected_totalSize += M.d[nodeId].t ? M.d[nodeId].tb : M.d[nodeId].s;
}
else {
this.selected_totalSize -= M.d[nodeId].t ? M.d[nodeId].tb : M.d[nodeId].s;
}
itemsTotalSize = bytesToSize(this.selected_totalSize);
if (totalNodes === 1) { // Only one item exists
notificationText = l[20966].replace('%1', itemsNum).replace('%2', itemsTotalSize);
}
else { // Multiple items here
notificationText = l[20967]
.replace('%1', itemsNum + " / " + totalNodes)
.replace('%2', itemsTotalSize);
}
this.showSelectionBar(notificationText);
}
};
/**
* Show the selection notification bar at the bottom of pages
* @param notificationText
*/
this.showSelectionBar = function (notificationText) {
var $selectionBar = $('.selection-status-bar');
var jsp;
$selectionBar.find('.selection-bar-col').safeHTML(notificationText);
$selectionBar.addClass('visible');
$selectionBar.parent('div').addClass('select');
this.vSelectionBar = $('b', $selectionBar).get(0);
if (this.selected_list.length === 1 && M.currentdirid.startsWith("search/")){
$selectionBar.css("opacity", 0);
}
if (M.currentdirid === "out-shares") {
jsp = M.viewmode ? $('.out-shared-blocks-scrolling').data('jsp') :
$('.out-shared-grid-view .grid-scrolling-table').data('jsp');
}
else if (M.currentdirid === "shares") {
jsp = M.viewmode ? $('.shared-blocks-scrolling').data('jsp') :
$('.shared-grid-view .grid-scrolling-table').data('jsp');
}
if (jsp) {
var jspPercentY = jsp.getPercentScrolledY();
jsp.reinitialise();
// If this is scrolled to bottom, keep it stick on bottom
if (jspPercentY === 1) {
jsp.scrollToBottom();
}
}
else {
var scrollBarYClass = (M.viewmode === 1) ?
'.file-block-scrolling.ps-active-y' : '.grid-scrolling-table.ps-active-y';
var scrollBarY = document.querySelector(scrollBarYClass);
if (scrollBarY && (scrollBarY.scrollHeight - scrollBarY.scrollTop - scrollBarY.clientHeight) < 37) {
scrollBarY.scrollTop = scrollBarY.scrollHeight - scrollBarY.clientHeight;
}
}
};
/**
* Hide the selection notification bar at the bottom of pages
*/
this.hideSelectionBar = function () {
this.selected_totalSize = 0;
$('.selection-status-bar').removeClass('visible');
$('.selection-status-bar').parent('div').removeClass('select');
this.vSelectionBar = null;
};
this.reinitialize = function() {
var nodeList = this.selected_list = $.selected = $.selected || [];
if (nodeList.length) {
if (nodeList.length === M.v.length) {
this.select_all();
}
else {
this.add_to_selection(nodeList.shift(), true);
}
}
else {
this.clear_selection(); // remove ANY old .currently-selected values.
}
};
this.reinitialize();
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;
};
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 = [];
var pushAsAddedEmails = function() {
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);
}));
}
};
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();
pushAsAddedEmails();
});
}
if ($.dialog === 'share' && Object.keys($.addContactsToShare).length > 0) {
// Invite new contacts to share
for (var i in $.addContactsToShare) {
email = $.addContactsToShare[i].u;
emailText = $.addContactsToShare[i].msg;
pushAsAddedEmails();
}
}
// 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);
});
}
/**
* setContactLink
*
* Set public link and init CopyToClipboard events
* @param {Node|jQuery} [$container] optional container node, used to scope the `public-contact-link`
* @returns {undefined|Boolean}
*/
function setContactLink($container) {
"use strict";
var $publicLink = $container ? $('.public-contact-link', $container) : $('.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) {
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')
.removeClass('hidden')
.css({
'left': leftPos,
'top': topPos
});
});
$publicLink.rebind('mouseout.publiclnk', function() {
$('.dropdown.tooltip.small')
.removeClass('visible')
.addClass('hidden');
});
$publicLink.rebind('click.publiclnk', 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($d);
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() {
showLoseChangesWarning().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);
});
}
function fmLeftMenuUI() {
"use strict";
// 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) {
if (!$icon.hasClass('filled')) {
$icon.addClass('filled');
}
else if (!$icon.hasClass('glow')) {
$icon.addClass('glow');
}
else {
$icon.removeClass('glow');
}
}
else {
$icon.removeClass('filled glow');
}
if (mega.flags.refpr) {
$('.nw-fm-left-icon.affiliate').removeClass('hidden');
}
else {
$('.nw-fm-left-icon.affiliate').addClass('hidden');
}
}
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() {
'use strict';
var current_operation = null;
mBroadcaster.addListener('crossTab:fms!cut/copy', ev => (current_operation = ev.data));
$(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 = clone(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
};
mBroadcaster.crossTab.notify('fms!cut/copy', current_operation);
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 = current_operation.src;
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 remItems = selectionManager.get_selected();
if (remItems.length === 0 || (M.getNodeRights(M.currentdirid || '') | 0) < 2) {
return; // dont do anything.
}
fmremove(remItems);
// force remove, no confirmation
if (e.ctrlKey || e.metaKey) {
$('#msgDialog:visible .fm-dialog-button.confirm').trigger('click');
}
return false;
}
});
}
function fm_addhtml() {
'use strict';
var elm = document.getElementById('fmholder');
if (elm) {
if (!elm.textContent) {
$(elm).safeHTML(translate(String(pages.fm).replace(/{staticpath}/g, staticpath)));
}
if (!document.getElementById('invoicePdfPrinter')) {
elm = document.querySelector('.invoice-container');
if (elm && elm.parentNode) {
elm.parentNode.insertBefore(mCreateElement('iframe', {
type: 'content',
'class': 'hidden',
src: 'about:blank',
id: 'invoicePdfPrinter'
}), elm);
}
}
}
else {
console.error('fmholder container not found...');
}
}
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 {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(value, target) {
"use strict";
if (!target) {
var items = M.v.filter(function (item) {
return item.name === value;
});
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].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(value, targetFolder)) {
errMsg = l[23219];
}
else {
M.rename(n.h, value);
}
}
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 with-close-btn');
$('#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(
'
' +
'', l[81]
);
$('#msgDialog .default-white-button').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback(true);
$.warningCallback = null;
}
});
}
$('#msgDialog .icon').addClass('fm-notification-icon');
if (type === 'warninga') {
$('#msgDialog').addClass('warning-dialog-a');
}
else if (type === 'warningb') {
$('#msgDialog').addClass('warning-dialog-b');
}
else if (type === 'info') {
$('#msgDialog').addClass('notification-dialog');
}
}
else if (type === 'confirmation' || type === 'remove') {
if (doneButton === l[81]) {
doneButton = false;
}
$('#msgDialog .fm-notifications-bottom')
.safeHTML('
' +
'
' +
'' +
'
' +
'
' +
'
' +
'@@' +
'
' +
'
@@
' +
'', l[229], doneButton || l[78], extraButton || l[79]);
$('#msgDialog .default-green-button').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback(true);
$.warningCallback = null;
}
});
/*!3*/
$('#msgDialog .default-white-button').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback(false);
$.warningCallback = null;
}
});
$('#msgDialog .icon').addClass('fm-notification-icon');
$('#msgDialog').addClass('confirmation-dialog');
if (type === 'remove') {
$('#msgDialog').addClass('remove-dialog');
}
if (checkbox) {
$('#msgDialog .checkbox-block .checkdiv,' +
'#msgDialog .checkbox-block input')
.removeClass('checkboxOn').addClass('checkboxOff');
$.warningCheckbox = false;
$('#msgDialog .checkbox-block').removeClass('hidden');
$('#msgDialog .checkbox-block').rebind('click', function() {
var $o = $('#msgDialog .checkbox-block .checkdiv, #msgDialog .checkbox-block input');
if ($('#msgDialog .checkbox-block input').hasClass('checkboxOff')) {
$o.removeClass('checkboxOff').addClass('checkboxOn');
localStorage.skipDelWarning = 1;
}
else {
$o.removeClass('checkboxOn').addClass('checkboxOff');
delete localStorage.skipDelWarning;
}
});
}
}
else if (type === 'import_login_or_register') {
// Show import confirmation dialog if a user isn't logged in
$('#msgDialog').addClass('warning-dialog-a wide with-close-btn');
$('#msgDialog .fm-notifications-bottom')
.safeHTML('
@@
' +
'
' +
'@@' +
'
' +
'
' +
'@@' +
'
' +
'', l[20754], l[170], l[171]);
// Register a new account to complete the import
$('#msgDialog .default-green-button').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback('register');
$.warningCallback = null;
}
});
// Login to complete the import
$('#msgDialog .default-white-button').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback('login');
$.warningCallback = null;
}
});
// Have an ephemeral account to complete the import
$('#msgDialog .bottom-bar-link').rebind('click', function() {
closeMsg();
if ($.warningCallback) {
$.warningCallback('ephemeral');
$.warningCallback = null;
}
});
}
$('#msgDialog .fm-dialog-title span').text(title);
$('#msgDialog .fm-notification-info h1').safeHTML(msg);
clickURLs();
if (submsg) {
$('#msgDialog .fm-notification-info p').safeHTML(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);
$.warningCallback = null;
}
});
$('#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-left', '-' + $(s).outerWidth() / 2 + 'px');
$(s).css('margin-top', '-' + $(s).outerHeight() / 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;
});
// This contact link is valid to be affilaited
M.affiliate.storeAffiliate(contactLink, 4);
}
}
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);
});
// This contact link is not checked but stored for register case
// and also user click `add contact` anyway so it's user's call
M.affiliate.storeAffiliate(contactLink, 4);
login_next = page;
login_txt = l[1298];
return loadSubPage('login');
});
}
$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() {
"use strict";
var $dialog = $('.fm-dialog.qr-dialog');
var QRdialogPrepare = function _QRdialogPrepare() {
var curAvatar;
$dialog.removeClass('hidden');
$dialog.removeClass('disabled');
if (M.account.contactLink && M.account.contactLink.length) {
var myHost = getBaseUrl() + '/' + 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);
curAvatar = useravatar.contact(u_handle);
$('.avatar-container-qr', $dialog).html(curAvatar);
var handleAutoAccept = function _handleAutoAccept(autoAcc) {
if (autoAcc === '0') {
$('.qr-dialog-label .dialog-feature-toggle', $dialog).removeClass('toggle-on');
}
else {
// if it's 1 or not set
$('.qr-dialog-label .dialog-feature-toggle', $dialog).addClass('toggle-on');
}
};
mega.attr.get(u_handle, 'clv', -2, 0).always(handleAutoAccept);
}
};
$('.fm-dialog-close, #qr-dlg-close', $dialog).rebind('click', function() {
closeDialog();
return false;
});
$('.qr-dialog-label .dialog-feature-toggle', $dialog).rebind('click', function() {
var $this = $(this);
if ($this.hasClass('toggle-on')) {
$this.removeClass('toggle-on');
mega.attr.set('clv', 0, -2, 0);
}
else {
$this.addClass('toggle-on');
mega.attr.set('clv', 1, -2, 0);
}
});
$('.reset-qr-label', $dialog).rebind('click', function() {
// QR Code Regenerate
var msgTitle = l[18227];
// You are about to generate a new QR code.
// Your existing QR code and invitation link will no longer work.
var msgMsg = l[18228] + ' ';
// Do you want to proceed?
var msgQuestion = l[18229];
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) {
// success
if (res === 0) {
api_req(reGenQR, {
callback: function(res2) {
if (typeof res2 === 'string') {
res2 = 'C!' + res2;
}
else {
res2 = '';
}
M.account.contactLink = res2;
QRdialogPrepare();
}
});
}
else {
$dialog.removeClass('disabled');
}
}
});
}
});
});
if (is_extension || M.execCommandUsable()) {
$('#qr-dlg-cpy-lnk').removeClass('hidden').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") {
$('#qr-dlg-sv-img', $dialog).removeClass('hidden').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 {
$('#qr-dlg-sv-img', $dialog).addClass('hidden');
}
M.safeShowDialog('qr-dialog', function() {
QRdialogPrepare();
return $dialog;
});
}
/**
* shareDialogContentCheck
*
* Taking care about share dialog buttons enabled/disabled and scroll
*
*/
function shareDialogContentCheck() {
var dc = '.fm-dialog.share-dialog';
var itemsNum = $('.share-dialog-access-list .share-dialog-access-node', dc).length;
var $doneBtn = $('.done-share', dc);
var $removeBtn = $('.remove-share', dc);
// Hide the share dialog bottom hint message as default
$('.share-dialog-bottom-msg', dc).css('opacity', 0);
// Taking care about the sharing access list scrolling
handleDialogScroll(itemsNum, dc);
// Taking care about the Remove Share button enabled/disabled
if (itemsNum > 1) {
$removeBtn.removeClass('disabled');
}
else {
$removeBtn.addClass('disabled');
}
// Taking care about the Done button enabled/disabled
if (Object.keys($.addContactsToShare).length
|| Object.keys($.changedPermissions).length
|| Object.keys($.removedContactsFromShare).length) {
$doneBtn.removeClass('disabled');
}
else {
$doneBtn.addClass('disabled');
}
}
/**
* Generate the html DOM element for a single share contact of the folder
*
* @param {string} userEmail contact email
* @param {string} type type of contact e.g. type 1 indicates the owner of the folder
* @param {string} id contact handle
* @param {string} av contact avatar
* @param {string} userName contact name
* @param {string} permClass permission classname
*
* @returns {string}
*/
function renderContactRowContent(userEmail, type, id, av, userName, permClass) {
"use strict";
var html = '';
var presence = type === '1' ? M.onlineStatusClass(M.u[id].presence)[1] : '';
if (M.d[id] && M.d[id].presence) {
presence = M.onlineStatusClass(M.d[id].presence === 'unavailable' ? 1 : M.d[id].presence)[1];
}
userName += type === '1' ? ' (' + l[8885] + ')' : '';
permClass = type === '1' ? 'owner' : permClass;
var ownerClass = type === '1' ? ' owner' : '';
html = '
'
+ '
'
+ av
+ '
' + htmlentities(userName) + '
'
+ '
'
+ '
'
+ '
'
+ ''
+ '
'
+ '
'
+ ''
+ '
';
return html;
}
function fillShareDialogWithContent() {
var pendingShares = {};
var nodeHandle = String($.selected[0]);
var node = M.getNodeByHandle(nodeHandle);
var userHandles = M.getNodeShareUsers(node, 'EXP');
$.sharedTokens = [];// GLOBAL VARIABLE, Hold items currently visible in share folder content (above multi-input)
if (M.ps[nodeHandle]) {
pendingShares = Object(M.ps[nodeHandle]);
userHandles = userHandles.concat(Object.keys(pendingShares));
}
var seen = Object.create(null);
// Fill the owner of the folder on the top of the access list
if (u_attr) {
generateShareDialogRow(u_attr.name, u_attr.email, 2, u_attr.u);
}
// Remove items in the removed contacts list
for (var rmContact in $.removedContactsFromShare) {
var rmContactIndex = userHandles.indexOf(rmContact);
if (rmContactIndex > -1) {
userHandles.splice(rmContactIndex, 1);
}
}
// Existing contacts in shares
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;
}
});
// New added contacts
for (var newContact in $.addContactsToShare) {
var newContactName;
var newContactEmail = $.addContactsToShare[newContact].u;
if (!seen[newContactEmail]) {
if (newContact.startsWith('#new_')) {
newContactName = $.addContactsToShare[newContact].u;
}
else {
newContactName = M.getNameByHandle(newContact) || newContactEmail;
}
generateShareDialogRow(newContactName, newContactEmail, $.addContactsToShare[newContact].r, newContact);
seen[newContactEmail] = 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, 'access-node-avatar'),
perm = '',
permissionLevel = 0;
if (typeof shareRights != 'undefined') {
permissionLevel = shareRights;
}
// Restore the latest changed permission
if ($.changedPermissions[userHandle]) {
permissionLevel = $.changedPermissions[userHandle].r;
}
// Permission level
if (permissionLevel === 1) {
perm = 'read-and-write';
} else if (permissionLevel === 2) {
perm = 'full-access';
} else {
perm = 'read-only';
}
// Add contact
$.sharedTokens.push(email.toLowerCase());
rowId = (userHandle) ? userHandle : email;
if (u_attr && userHandle === u_attr.u) {
html = renderContactRowContent(email, '1', rowId, av, displayNameOrEmail, perm);
}
else {
html = renderContactRowContent(email, '', rowId, av, displayNameOrEmail, perm);
}
$('.share-dialog .share-dialog-access-list').safeAppend(html);
}
/**
* Hide the permission menu in the share dialog
*/
function hideShareDialogPermMenu() {
"use strict";
var $shareDialog = $('.fm-dialog.share-dialog');
var $permissionMenu = $('.share-dialog-permissions-menu', $shareDialog);
$permissionMenu.fadeOut(200);
$('.permission-item', $permissionMenu).removeClass('active');
$('.share-dialog-access-node', $shareDialog).removeClass('active');
}
/**
* Show the permission menu in the share dialog with the position x and y
*
* @param {Object} $this The selected contact element in the DOM
* @param {Number} x The x position of showing the menu
* @param {Number} y The y position of showing the menu
*/
function showShareDialogPermMenu($this, x, y) {
"use strict";
var $shareDialog = $('.fm-dialog.share-dialog');
var $permissionMenu = $('.share-dialog-permissions-menu', $shareDialog);
var permissionLevel = checkMultiInputPermission($this);
$('.permission-item', $permissionMenu).removeClass('active');
$('.permission-item.' + permissionLevel[0], $permissionMenu).addClass('active');
$permissionMenu.css('right', x + 'px');
$permissionMenu.css('top', y + 'px');
$permissionMenu.fadeIn(200);
}
/**
* Bind events to various components in the access list of share dialog after rendering
*/
function shareDialogAccessListBinds() {
"use strict";
var $shareDialog = $('.fm-dialog.share-dialog');
// Open the permissions menu
$('.access-node-permission-wrapper', $shareDialog).rebind('click', function(e) {
e.stopPropagation();
var $this = $(this);
var $selectedContact = $this.parent('.share-dialog-access-node');
if ($selectedContact.is('.owner')) {
return false;
}
var $scrollBlock = $('.share-dialog-access-list .jspPane', $shareDialog);
var scrollPos = 0;
var x = 0;
var y = 0;
if ($scrollBlock.length) {
scrollPos = $scrollBlock.position().top;
}
if ($selectedContact.is('.active')) {
hideShareDialogPermMenu();
$selectedContact.removeClass('active');
}
else {
$('.share-dialog-access-node', $shareDialog).removeClass('active');
x = 45;
y = $this.position().top + 74 + scrollPos;
showShareDialogPermMenu($('.access-node-permission', $(this)), x, y);
$selectedContact.addClass('active');
}
});
// Remove the specific contact from share
$('.access-node-remove', $shareDialog).rebind('click', function() {
var $deletedContact = $(this).parent('.share-dialog-access-node');
if ($deletedContact.is('.owner')) {
return false;
}
var userHandle = $deletedContact.attr('id');
var selectedNodeHandle = $.selected[0];
$deletedContact.remove();
if (userHandle !== '') {
var userEmail = '';
if ($.addContactsToShare[userHandle]) {
userEmail = $.addContactsToShare[userHandle].u;
delete $.addContactsToShare[userHandle];
}
else {
// Due to pending shares, the id could be an email instead of a handle
var userEmailOrHandle = Object(M.opc[userHandle]).m || userHandle;
userEmail = Object(M.opc[userHandle]).m || M.getUserByHandle(userHandle).m;
$.removedContactsFromShare[userHandle] = {
'selectedNodeHandle': selectedNodeHandle,
'userEmailOrHandle': userEmailOrHandle,
'userHandle': userHandle
};
// Remove the permission change if exists
if ($.changedPermissions[userHandle]) {
delete $.changedPermissions[userHandle];
}
}
// Remove it from multi-input tokens
var sharedIndex = $.sharedTokens.indexOf(userEmail.toLowerCase());
if (sharedIndex > -1) {
$.sharedTokens.splice(sharedIndex, 1);
}
}
shareDialogContentCheck();
});
// Hide the permission menu once scrolling
$('.share-dialog-access-list', $shareDialog).rebind('jsp-scroll-y.closeMenu', function() {
hideShareDialogPermMenu();
});
// Show the full access message once hover contacts with the full-access permission
$('.share-dialog-access-node', $shareDialog).rebind('mouseover', function() {
if ($('.access-node-permission', $(this)).is('.full-access')) {
$('.share-dialog-bottom-msg', $shareDialog).safeHTML(l[23709]).css('opacity', 1);
}
});
// Hide the full access message once mouse leave contacts
$('.share-dialog-access-node', $shareDialog).rebind('mouseout', function() {
$('.share-dialog-bottom-msg', $shareDialog).css('opacity', 0);
});
}
/**
* 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;
}
/**
* Initialize share dialog multi input plugin
*
* @param {array} alreadyAddedContacts Array of already added contacts
*/
function initShareDialogMultiInput(alreadyAddedContacts) {
"use strict";
var $scope = $('.share-add-dialog');
var $input = $('.share-multiple-input', $scope);
var listedContacts = []; // All listed contact emails
var errorMsg = function(msg) {
var $warning = $('.multiple-input-warning span', $scope);
$('#token-input-share-multiple-input', $scope).val('');
$warning.text(msg);
$scope.addClass('error');
setTimeout(function() {
$scope.removeClass('error');
}, 3000);
};
Object.values(M.getContactsEMails(true)).forEach(function(item) {
listedContacts.push(item.id);
});
// Clear old values in case the name/nickname updated since last opening
$input.tokenInput('destroy');
$input.tokenInput([], {
theme: "mega",
placeholder: l[23711],
searchingText: "",
noResultsText: "",
addAvatar: true,
autocomplete: null,
searchDropdown: false,
emailCheck: true,
preventDoublet: false,
tokenValue: "id",
propertyToSearch: "id",
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[2465]); // Please enter a valid email address
},
onReady: function() {
dialogPositioning($scope);
},
onDoublet: function() {
errorMsg(l[23714]); // This folder has already been shared with this email address
},
onHolder: function() {
errorMsg(l[23715]); // It is not necessary to share this folder with yourself
},
onAdd: function(email) {
if (listedContacts.indexOf(email.id) > -1) {
// If the entered email is one of existing contacts in the picker, select it automatically for users
var $listedItemHandle = M.getUserByEmail(email.id).h;
var $listedItemEle = $('.contacts-search-subsection .' + $listedItemHandle, $scope);
if ($('.contacts-search-scroll', $scope).is('.jspScrollable') && !window.safari) {
// Auto-scroll to the selected element except for Safari since the graphic glitch issue
$('.contacts-search-scroll.jspScrollable', $scope)
.data('jsp').scrollToElement($listedItemEle.parent());
}
if ($.contactPickerSelected.indexOf($listedItemHandle) === -1) {
$listedItemEle.trigger('click');
}
$('.token-input-token-mega .' + $listedItemHandle, $scope)
.siblings('.token-input-delete-token-mega').trigger('click');
}
else {
if (typeof M.findOutgoingPendingContactIdByEmail(email.id) === 'undefined') {
// Show a text area where the user can add a custom message to the pending share request
$('.share-message', $scope).removeClass('hidden');
initTextareaScrolling($('.share-message-textarea textarea', $scope), 72);
}
$('.add-share', $scope).removeClass('disabled');
}
dialogPositioning($scope);
},
onDelete: function() {
var $scope = $('.share-add-dialog');
var $newEmails = $('.token-input-list-mega .token-input-token-mega', $scope);
var newEmailsNum = $newEmails.length;
var noNewContacts = true;
setTimeout(function() {
$('.token-input-input-token-mega input', $scope).trigger("blur");
}, 0);
for (var i = 0; i < newEmailsNum; i++) {
var newEmail = $($newEmails[i]).contents().eq(1).text();
if (typeof M.findOutgoingPendingContactIdByEmail(newEmail) === 'undefined') {
noNewContacts = false;
break;
}
}
// If no new email that hasn't been sent contact request, clear and hide the personal message input box
if (noNewContacts) {
$('.share-message', $scope).addClass('hidden');
$('.share-message textarea', $scope).val('');
}
// If no new email is in multiInput box and contact picker, disable the button
if (JSON.stringify(clone($.contactPickerSelected).sort()) === JSON.stringify(alreadyAddedContacts.sort())
&& newEmailsNum === 0) {
$('.add-share', $scope).addClass('disabled');
}
dialogPositioning($scope);
}
});
}
/**
* Render the content of access list in share dialog
*/
function renderShareDialogAccessList() {
"use strict";
var $shareDialog = $('.fm-dialog.share-dialog');
// Remove all contacts from the access list
$('.share-dialog-access-node').remove();
// Clear the scroll panel in access list
clearScrollPanel($('.share-dialog-access-list', $shareDialog));
// Fill the shared folder's access list
fillShareDialogWithContent();
// Take care about share button enabled/disabled and the access list scrolling
shareDialogContentCheck();
// Bind events to components in the access list after rendering
shareDialogAccessListBinds();
dialogPositioning($shareDialog);
}
/**
* Initializes the share dialog
*/
function initShareDialog() {
"use strict";
var $dialog = $('.share-dialog');
$dialog.rebind('click', function(e) {
var $this = $(this);
// Hide the permission menu once click outside range of it
if (typeof e.originalEvent.path !== 'undefined') {
var trg0 = e.originalEvent.path[0];
var trg1 = e.originalEvent.path[1];
var trg2 = e.originalEvent.path[2];
if (!$(trg0).is('.permission-item-text,.permission-item,.share-dialog-permissions-menu')
&& !$(trg1).is('.permission-item-text,.permission-item,.share-dialog-permissions-menu')
&& !$(trg2).is('.permission-item-text,.permission-item,.share-dialog-permissions-menu')) {
hideShareDialogPermMenu();
}
}
else if ($this.get(0) === e.currentTarget) {
hideShareDialogPermMenu();
}
});
var $shareAddFooterElement = $('.share-add-dialog-bottom', $dialog);
$shareAddFooterElement.detach();
// Close the share dialog
$('.fm-dialog-close', $dialog).rebind('click', function() {
showLoseChangesWarning().done(closeDialog);
});
// Change the permission for the specific contact or group
// eslint-disable-next-line complexity
$('.share-dialog-permissions-menu .permission-item', $dialog).rebind('click', function(e) {
var $this = $(this);
var shares = M.d[$.selected[0]].shares;
var newPermLevel = checkMultiInputPermission($this);
var newPerm = sharedPermissionLevel(newPermLevel[0]);
var $selectedContact = $('.share-dialog-access-node.active', $dialog);
hideShareDialogPermMenu();
var pushNewPermissionIn = function(id) {
if (!shares || !shares[id] || shares[id].r !== newPerm) {
// If it's a pending contact, provide the email
var userEmailOrHandle = Object(M.opc[id]).m || id;
$.changedPermissions[id] = {u: userEmailOrHandle, r: newPerm};
}
};
if (e.shiftKey) {
// Change the permission for all listed contacts
for (var key in $.addContactsToShare) {
$.addContactsToShare[key].r = newPerm;
}
$.changedPermissions = {};
$('.share-dialog-access-node:not(.owner)', $dialog).get().forEach(function(item) {
var itemId = $(item).attr('id');
if (itemId !== undefined && itemId !== '' && !$.addContactsToShare[itemId]) {
pushNewPermissionIn(itemId);
}
});
$('.share-dialog-access-node:not(.owner) .access-node-permission', $dialog)
.removeClass('full-access read-and-write read-only')
.addClass(newPermLevel[0]);
}
else {
// Change the permission for the specific contact
var userHandle = $selectedContact.attr('id');
if (userHandle !== undefined && userHandle !== '') {
if ($.addContactsToShare[userHandle]) {
// Change the permission for new added share contacts
$.addContactsToShare[userHandle].r = newPerm;
}
else {
// Change the permission for existing share contacts
if ($.changedPermissions[userHandle]) {
// Remove the previous permission change if exists
delete $.changedPermissions[userHandle];
}
pushNewPermissionIn(userHandle);
}
}
$('.access-node-permission', $selectedContact)
.removeClass('full-access read-and-write read-only')
.addClass(newPermLevel[0]);
}
// Share button enable/disable control
if (Object.keys($.changedPermissions).length > 0) {
$('.done-share', $dialog).removeClass('disabled');
}
else if (Object.keys($.removedContactsFromShare).length === 0
&& Object.keys($.addContactsToShare).length === 0) {
$('.done-share', $dialog).addClass('disabled');
}
return false;
});
// Open the share add dialog
$('.share-dialog-access-add', $dialog).rebind('click', function() {
var alreadyAddedContacts = [];
$('.share-dialog-access-node:not(.owner)', $dialog).get().forEach(function(item) {
var itemId = $(item).attr('id');
if (!itemId.startsWith('#new_') && M.u[itemId]) {
alreadyAddedContacts.push(itemId);
}
});
M.initShareAddDialog(alreadyAddedContacts, $shareAddFooterElement);
});
$('.done-share', $dialog).rebind('click', function() {
if (!$(this).is('.disabled')) {
addNewContact($(this), false).done(function() {
var share = new mega.Share();
share.updateNodeShares();
});
}
return false;
});
$('.remove-share', $dialog).rebind('click', function() {
if (!$(this).is('.disabled')) {
$.removedContactsFromShare = {};
var nodeHandle = String($.selected[0]);
var userHandles = M.getNodeShareUsers(nodeHandle, 'EXP');
if (M.ps[nodeHandle]) {
var pendingShares = Object(M.ps[nodeHandle]);
userHandles = userHandles.concat(Object.keys(pendingShares));
}
userHandles.forEach(function(userHandle) {
var userEmailOrHandle = Object(M.opc[userHandle]).m || userHandle;
$.removedContactsFromShare[userHandle] = {
'selectedNodeHandle': nodeHandle,
'userEmailOrHandle': userEmailOrHandle,
'userHandle': userHandle
};
});
var share = new mega.Share();
loadingDialog.show();
share.removeContactFromShare().always(function() {
loadingDialog.hide();
closeDialog();
});
}
return false;
});
// Show the group change permission message once hover permission items in the menu
$('.share-dialog-permissions-menu .permission-item', $dialog).rebind('mouseover', function() {
$('.share-dialog-bottom-msg', $dialog).safeHTML(l[23708]).css('opacity', 1);
});
// Hide the group change permission message once mouse leave permission items
$('.share-dialog-permissions-menu .permission-item', $dialog).rebind('mouseout', function() {
$('.share-dialog-bottom-msg', $dialog).css('opacity', 0);
});
}
function addImportedDataToSharedDialog(data) {
$.each(data, function(ind, val) {
$('.share-add-dialog .share-multiple-input').tokenInput("add", {id: val, name: val});
});
closeImportContactNotification('.share-add-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();
if (!$('.imported-contacts-notification').is(".hidden")) {
$('.imported-contacts-notification').fadeOut(200);
}
if (!$(c + ' .import-contacts-dialog').is(".hidden")) {
$(c + ' .import-contacts-dialog').fadeOut(200);
}
$('.import-contacts-link.active').removeClass('active');
// Remove focus from input element, related to tokeninput plugin
if ($(c + ' input#token-input-').is(":focus")) {
$(c + ' input#token-input-').trigger("blur");
}
}
/**
* Check the dialog has token input that is already filled up by user or any unsaved changes.
* Warn user closing dialog will lose all inserted input and unsaved changes.
*/
function showLoseChangesWarning() {
"use strict";
var $dialog = $('.fm-dialog:visible');
if ($dialog.length !== 1) {
console.warn('Unexpected number of dialogs...', [$dialog]);
return MegaPromise.resolve();
}
var promise = new MegaPromise();
// If there is any tokenizer on the dialog and it is triggered by dom event.
var $tokenInput = $('li[class*="token-input-input"]', $dialog);
// Make sure all input is tokenized.
if ($tokenInput.length) {
$('input', $tokenInput).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"]', $dialog);
if ($tokenItems.length) {
// Warn user closing dialog will lose all inserted input
msgDialog('confirmation', '', l[20474], l[18229], function(e) {
if (e) {
$tokenItems.remove();
promise.resolve();
}
else {
promise.reject();
}
});
}
else if ($.dialog === 'share' && Object.keys($.addContactsToShare || []).length > 0
|| Object.keys($.changedPermissions || []).length > 0
|| Object.keys($.removedContactsFromShare || []).length > 0) {
// Warn user closing dialog will lose all unsaved changes
msgDialog('confirmation', '', l[24208], l[18229], function(e) {
if (e) {
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 || $.saveAsDialog)) {
$('.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 === 'share-add') {
$('.fm-dialog.share-add-dialog').addClass('hidden');
}
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");
if ($.dialog === 'share') {
// share dialog
$('.share-dialog-access-node').remove();
hideShareDialogPermMenu();
delete $.sharedTokens;
delete $.contactPickerSelected;
delete $.addContactsToShare;
delete $.changedPermissions;
delete $.removedContactsFromShare;
}
$('.copyrights-dialog').addClass('hidden');
delete $.copyDialog;
delete $.moveDialog;
delete $.copyToShare;
delete $.copyToUpload;
delete $.shareToContactId;
delete $.copyrightsDialog;
delete $.selectFolderDialog;
delete $.saveAsDialog;
delete $.nodeSaveAs;
delete $.shareDialog;
/* 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;
}
if ($.msgDialog) {
if ($.warningCallback) {
onIdle($.warningCallback.bind(null, null));
$.warningCallback = null;
}
delete $.msgDialog;
}
}
$('.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;
}
}
else {
delete $.mcImport;
}
if ($.dialog === 'selectFolder') {
delete $.selectFolderCallback;
}
if (typeof redeem !== 'undefined' && redeem.$dialog) {
redeem.$dialog.addClass('hidden');
}
delete $.dialog;
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 || $.saveAsDialog) {
// the createfolder dialog was closed
// eslint-disable-next-line local-rules/hints
$.dialog = $.copyDialog || $.moveDialog || $.selectFolderDialog || $.saveAsDialog;
$('.fm-dialog').addClass('arrange-to-back');
$('.fm-dialog.fm-picker-dialog').removeClass('arrange-to-back');
}
if ($.shareDialog) {
// if the share-add dialog was closed from the share dialog
// eslint-disable-next-line local-rules/hints
$.dialog = $.shareDialog;
}
mBroadcaster.sendMessage('closedialog');
}
function createFolderDialog(close) {
"use strict";
if (M.isInvalidUserStatus()) {
return;
}
var $dialog = $('.fm-dialog.create-folder-dialog');
var $input = $('input', $dialog);
$input.val('');
if (close) {
if ($.cftarget) {
delete $.cftarget;
}
if ($.dialog === 'createfolder') {
closeDialog();
}
return true;
}
var doCreateFolder = function(v) {
var errorMsg = '';
if (!M.isSafeName(v, true)) {
$dialog.removeClass('active');
errorMsg = l[7436];
}
else {
var specifyTarget = null;
if ($.cftarget) {
specifyTarget = $.cftarget;
}
if (duplicated(v, specifyTarget)) {
errorMsg = l[23219];
}
}
if (errorMsg !== '') {
$('.duplicated-input-warning span', $dialog).text(errorMsg);
$dialog.addClass('duplicate');
$input.addClass('error');
setTimeout(
function() {
$input.removeClass('error');
$dialog.removeClass('duplicate');
$input.trigger("focus");
},
2000
);
return;
}
var target = $.cftarget = $.cftarget || M.currentCustomView.nodeID || M.currentdirid;
var awaitingPromise = $.cfpromise;
delete $.cfpromise;
closeDialog();
loadingDialog.pshow();
M.createFolder(target, v.split(/[/\\]/))
.then(function(h) {
if (d) {
console.log('Created new folder %s->%s', target, h);
}
loadingDialog.phide();
if (awaitingPromise) {
// dispatch an awaiting promise expecting to perform its own action instead of the default one
awaitingPromise.resolve(h);
}
else {
// By default auto-select the newly created folder as long no awaiting promise
M.openFolder(Object(M.d[h]).p || target)
.always(function() {
$.selected = [h];
reselect(1);
});
}
createFolderDialog(1);
})
.catch(function(ex) {
loadingDialog.phide();
msgDialog('warninga', l[135], l[47], ex < 0 ? api_strerror(ex) : ex, function() {
if (awaitingPromise) {
awaitingPromise.reject(ex);
}
});
});
};
$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(e) {
if ($input.val() === '' || $input.val() === l[157]) {
$dialog.removeClass('active');
}
else if (e.which !== 13) {
$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 createFileDialog(close, action, params) {
"use strict";
var closeFunction = function() {
if ($.cftarget) {
delete $.cftarget;
}
closeDialog();
return false;
};
if (close) {
return closeFunction();
}
if (!action) {
action = function(name, t) {
loadingDialog.pshow();
M.addNewFile(name, t)
.done(function(nh) {
if (d) {
console.log('Created new file %s->%s', t, name);
}
loadingDialog.phide();
if ($.selectddUIgrid.indexOf('.grid-scrolling-table') > -1 ||
$.selectddUIgrid.indexOf('.file-block-scrolling') > -1) {
var $grid = $($.selectddUIgrid);
var $newElement = $('#' + nh, $grid);
var jsp = $grid.data('jsp');
if (jsp) {
jsp.scrollToElement($newElement);
}
else if (M.megaRender && M.megaRender.megaList && M.megaRender.megaList._wasRendered) {
M.megaRender.megaList.scrollToItem(nh);
$newElement = $('#' + nh, $grid);
}
// now let's select the item. we can not use the click handler due
// to redraw if element was out of viewport.
$($.selectddUIgrid + ' ' + $.selectddUIitem).removeClass('ui-selected');
$newElement.addClass('ui-selected');
$.gridLastSelected = $newElement;
selectionManager.clear_selection();
selectionManager.add_to_selection(nh);
loadingDialog.show('common', l[23130]);
mega.fileTextEditor.getFile(nh).done(
function(data) {
loadingDialog.hide();
mega.textEditorUI.setupEditor(M.d[nh].name, data, nh);
}
).fail(function() {
loadingDialog.hide();
});
}
})
.fail(function(error) {
loadingDialog.phide();
msgDialog('warninga', l[135], l[47], api_strerror(error));
});
};
}
// there's no jquery parent for this container.
// eslint-disable-next-line local-rules/jquery-scopes
var $dialog = $('.fm-dialog.create-file-dialog');
var $input = $('input', $dialog);
$input.val('.txt')[0].setSelectionRange(0, 0);
var doCreateFile = function(v) {
var target = $.cftarget = $.cftarget || M.currentdirid;
v = $.trim(v);
if (!M.isSafeName(v)) {
$dialog.removeClass('active');
$input.addClass('error');
return;
}
else if (duplicated(v, target)) {
$dialog.addClass('duplicate');
$input.addClass('error');
return;
}
closeFunction();
action(v, target, params);
};
$input.rebind('focus.fileDialog', function() {
if ($(this).val() === l[17506]) {
$input.val('');
}
$dialog.addClass('focused');
});
$input.rebind('blur.fileDialog', function() {
$dialog.removeClass('focused');
});
$input.rebind('keyup.fileDialog', function() {
if ($input.val() === '' || $input.val() === l[17506]) {
$dialog.removeClass('active');
}
else {
$dialog.addClass('active');
$input.removeClass('error');
}
});
$input.rebind('keypress.fileDialog', function(e) {
if (e.which === 13 && $(this).val() !== '') {
doCreateFile($(this).val());
}
else {
$input.removeClass('error');
$dialog.removeClass('duplicate');
}
});
// eslint-disable-next-line sonarjs/no-duplicate-string
$('.fm-dialog-close, .cancel-create-file', $dialog).rebind('click.fileDialog', closeFunction);
$('.fm-dialog-input-clear', $dialog).rebind('click.fileDialog', function() {
$input.val('');
$dialog.removeClass('active');
});
$('.create-file', $dialog).rebind('click.fileDialog', function() {
var v = $input.val();
if (v === '' || v === l[17506]) {
msgDialog('warninga', '', l[8566]);
}
else {
doCreateFile(v);
}
});
M.safeShowDialog('createfile', function() {
$dialog.removeClass('hidden');
$('.fm-dialog-body.mid-pad input', $dialog).focus();
$dialog.removeClass('active');
return $dialog;
});
}
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) {
if (d > 1) {
console.warn('fm_resize_handler');
}
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 */
});
// Lets make manually matching width of the header table with the contents table, due to width mismatching bug.
var $fNameTh = $('.files-grid-view .grid-table-header:visible th[megatype="fname"]');
var fNameThStyle = $fNameTh.length && $fNameTh.attr('style') || '';
if (fNameThStyle.indexOf('calc(100% -') === -1) {
delete M.columnsWidth.cloud.fname.currpx;
}
else {
var fNameThWidth = $fNameTh.outerWidth();
$('.files-grid-view .grid-table:visible td[megatype="fname"]').css('width', fNameThWidth);
M.columnsWidth.cloud.fname.currpx = fNameThWidth;
}
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.currentdirid === 'refer') {
initAffiliateScroll();
}
else if (!M.chat) {
if (M.viewmode) {
initFileblocksScrolling();
}
else {
initGridScrolling();
if ($.gridHeader) {
$.gridHeader();
$.detailsGridHeader();
}
}
}
if (M.currentdirid !== 'transfers') {
var treePaneWidth = Math.round($('.fm-left-panel:visible').outerWidth());
var leftPaneWidth = Math.round($('.nw-fm-left-icons-panel:visible').outerWidth());
if (megaChatIsReady && megaChat.resized) {
megaChat.resized();
}
$('.fm-right-files-block, .fm-right-account-block, .fm-right-block.dashboard').css({
'margin-left': (treePaneWidth + leftPaneWidth) + "px"
});
$('.popup.transfer-widget').outerWidth(treePaneWidth - 9);
}
if (M.currentrootid === 'shares') {
var $sharedDetailsBlock = $('.shared-details-block', '.fm-main');
var sharedDetailsHeight = Math.round($sharedDetailsBlock.outerHeight());
var sharedHeaderHeight = Math.round($('.shared-top-details').outerHeight());
var sharedBlockHeight = sharedDetailsHeight - sharedHeaderHeight;
if ($sharedDetailsBlock.closest('.fm-main').hasClass('fm-notification')) {
sharedBlockHeight -= 24;
}
if (sharedBlockHeight > 0) {
$('.files-grid-view, .fm-blocks-view', $sharedDetailsBlock).css({
'height': sharedBlockHeight + "px",
'min-height': sharedBlockHeight + "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();
if (M.u[userid]) {
M.u[userid].trackDataChange(M.u[userid], "fingerprint");
}
if (result && result.always) {
// wait for the setContactAuthenticated to finish and then trigger re-rendering.
result.always(function() {
if (M.u[userid]) {
M.u[userid].trackDataChange(M.u[userid], "fingerprint");
}
});
}
})
.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} $select 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($select, saveOption, contentBlock) {
'use strict';
var $dropdownsItem = $('.default-dropdown-item', $select);
var $contentBlock = contentBlock ? $(contentBlock) : $('body');
var $hiddenInput = $('.dropdown-hidden-input', $select);
// hidden input for keyboard search
if (!$hiddenInput.length) {
// Skip tab action for hidden input by tabindex="-1"
$select.safePrepend('');
$hiddenInput = $('input.dropdown-hidden-input', $select);
}
$select.rebind('click.defaultselect', function(e) {
var $this = $(this);
var $dropdown = $('.default-select-dropdown', $this);
var $outsideArea = $('.fmholder, .fm-dialog:not(.hidden)', 'body');
if (!$this.hasClass('active')) {
var jsp;
var scrollBlock = ('#' + $this.attr('id')).replace(/\./g, '\\.') + ' .default-select-scroll';
var $activeDropdownItem = $('.default-dropdown-item.active', $this);
var dropdownOffset;
var dropdownBottPos;
var dropdownHeight;
var contentBlockHeight;
//Show select dropdown
$('.default-select.active', 'body').removeClass('active');
$('.active .default-select-dropdown', 'body').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
if ($(scrollBlock).length) {
initSelectScrolling(scrollBlock);
jsp = $(scrollBlock).data('jsp');
if (jsp && $activeDropdownItem.length) {
jsp.scrollToElement($activeDropdownItem);
}
}
$hiddenInput.trigger('focus');
$outsideArea.rebind('mousedown.defaultselect', function(e) {
if (!$this.has($(e.target)).length && !$this.is(e.target)) {
$this.removeClass('active');
$dropdown.addClass('hidden');
$outsideArea.unbind('mousedown.defaultselect');
}
});
}
else if (!$(e.target).closest('.jspVerticalBar').length) {
$this.removeClass('active');
$dropdown.addClass('hidden');
$outsideArea.unbind('mousedown.defaultselect');
}
});
$dropdownsItem.rebind('click.settingsGeneral', function() {
var $this = $(this);
var $select = $(this).closest('.default-select');
// Select dropdown item
$('.default-dropdown-item', $select).removeClass('active');
$this.addClass('active');
$('span', $select).text($this.text());
$hiddenInput.trigger('focus');
if (saveOption) {
var nameLen = String($('#account-firstname').val() || '').trim().length;
// Save changes for account page
if (nameLen) {
$('.save-block', $this.closest('.settings-right-block')).removeClass('hidden');
}
}
});
$dropdownsItem.rebind('mouseenter.settingsGeneral', function() {
var $this = $(this);
// If contents width is bigger than size of dropdown
if (this.offsetWidth < this.scrollWidth) {
$this.addClass('simpletip').attr('data-simpletip', $this.text());
}
});
// Typing search and arrow key up and down features for dropdowns
$hiddenInput.rebind('keyup.defaultselect', 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 = $('.default-select-scroll', $select).data('jsp');
$('.default-dropdown-item.active', $select).removeClass('active');
jsp.scrollToElement($($filteredItem[0]), 1);
$($filteredItem[0]).addClass('active');
}
}
else {
e.preventDefault();
e.stopPropagation();
var $current = $('.default-dropdown-item.active', $select);
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.defaultselect', function() {
delay('dropbox:clearHidden', function() {
// Combination language bug fixs for MacOS.
$hiddenInput.val('').trigger('blur').trigger('focus');
}, 750);
});
// End of typing search for dropdowns
}
/**
* 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 || !u_attr.b.m) {
$('.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');
}
if (u_attr.b.s !== 1 || !u_attr.b.m) {
$('.left-pane.small-txt.plan-date-info', '.dashboard').addClass('hidden');
$('.left-pane.big-txt.plan-date-val', '.dashboard').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 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() {
if (account.contactLink && account.contactLink.length) {
var QRoptions = {
width: 106,
height: 106,
correctLevel: QRErrorCorrectLevel.H, // high
foreground: '#D90007',
text: getBaseUrl() + '/' + account.contactLink
};
// 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 if (u_attr.b && u_attr.b.m) {
$('.account.left-pane.plan-date-info').text(l[987]);
}
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]);
}
else if (u_attr.b.s === 2 && u_attr.b.m) {
$('.suba-status', $businessLeft).addClass('pending').removeClass('disabled active')
.text(l[19609]);
if (u_attr.b.sts && u_attr.b.sts[0] && u_attr.b.sts[0].s === -1) {
var expiryDate = new Date(u_attr.b.sts[0].ts * 1000);
var currentTime = new Date();
var remainingDays = Math.floor((expiryDate - currentTime) / 864e5);
remainingDays = remainingDays < 0 ? 0 : remainingDays;
var daysLeft = l[16284].replace('%1', remainingDays);
$('.suba-days-left', $businessLeft).removeClass('hidden').text(daysLeft);
$('.suba-pay-bill', $businessLeft).removeClass('hidden');
}
}
else {
$('.suba-status', $businessLeft).addClass('disabled').removeClass('pending active')
.text(l[19608]);
if (u_attr.b.m) {
$('.suba-pay-bill', $businessLeft).removeClass('hidden');
}
}
if (u_attr.b.m) { // master
$businessLeft.find('.suba-role').text(l[19610]);
}
else {
$businessLeft.find('.suba-role').text(l[5568]);
}
if (u_attr.b.s !== 1 || !u_attr.b.m) {
$('.left-pane.small-txt.plan-date-info', '.dashboard').addClass('hidden');
$('.left-pane.big-txt.plan-date-val', '.dashboard').addClass('hidden');
}
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 */
$('.default-green-button.upgrade-account, .bandwidth-info a, .pay-bill-btn','.dashboard')
.rebind('click.dboard', 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.learn-more.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.learn-more.right').removeClass('hidden');
$('.dashboard .account.learn-more.right').rebind('click', function() {
var $dropDownItem = $('.dropdown', $(this));
if ($dropDownItem.hasClass('hidden')) {
$dropDownItem.removeClass('hidden');
}
else {
$dropDownItem.addClass('hidden');
}
});
$('.fm-right-block.dashboard').rebind('click', function(e) {
if (!$(e.target).hasClass('learn-more') && !$('.dropdown.body.bandwidth-inf').hasClass('hidden')) {
$('.dropdown.body.bandwidth-info').addClass('hidden');
}
});
// Get more transfer quota button
$('.account.widget.bandwidth .free .more-quota').rebind('click', function() {
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 || !u_attr.b.m) {
$('.dashboard .upgrade-account').addClass('hidden').hide();
}
$('.business-dashboard .user-management-storage .storage-transfer-data')
.text(bytesToSize(account.space_used, 2));
$('.business-dashboard .user-management-transfer .storage-transfer-data')
.text(bytesToSize(account.tfsq.used, 2));
var $dataStats = $('.business-dashboard .subaccount-view-used-data');
var ffNumText = function(value, type) {
var counter = value || 0;
var numTextOutput = "";
if (counter === 0) {
numTextOutput = type === 'file' ? l[23259] : l[23258];
}
else if (counter === 1) {
numTextOutput = type === 'file' ? l[23257] : l[23256];
}
else {
numTextOutput = (type === 'file' ? l[23261] : l[23260]).replace('[X]', counter);
}
return numTextOutput;
};
var folderNumText = ffNumText(account.stats[M.RootID].folders, 'folder');
var fileNumText = ffNumText(account.stats[M.RootID].files, 'file');
$('.ba-root .ff-occupy', $dataStats).text(bytesToSize(account.stats[M.RootID].bytes, 2));
$('.ba-root .folder-number', $dataStats).text(folderNumText);
$('.ba-root .file-number', $dataStats).text(fileNumText);
folderNumText = ffNumText(account.stats.inshares.items, 'folder');
fileNumText = ffNumText(account.stats.inshares.files, 'file');
$('.ba-inshare .ff-occupy', $dataStats).text(bytesToSize(account.stats.inshares.bytes, 2));
$('.ba-inshare .folder-number', $dataStats).text(folderNumText);
$('.ba-inshare .file-number', $dataStats).text(fileNumText);
folderNumText = ffNumText(account.stats.outshares.items, 'folder');
fileNumText = ffNumText(account.stats.outshares.files, 'file');
$('.ba-outshare .ff-occupy', $dataStats).text(bytesToSize(account.stats.outshares.bytes, 2));
$('.ba-outshare .folder-number', $dataStats).text(folderNumText);
$('.ba-outshare .file-number', $dataStats).text(fileNumText);
folderNumText = ffNumText(account.stats[M.RubbishID].folders, 'folder');
fileNumText = ffNumText(account.stats[M.RubbishID].files, 'file');
$('.ba-rubbish .ff-occupy', $dataStats).text(bytesToSize(account.stats[M.RubbishID].bytes, 2));
$('.ba-rubbish .folder-number', $dataStats).text(folderNumText);
$('.ba-rubbish .file-number', $dataStats).text(fileNumText);
folderNumText = ffNumText(account.stats.links.folders, 'folder');
fileNumText = ffNumText(account.stats.links.files, 'file');
$('.ba-pub-links .ff-occupy', $dataStats).text(bytesToSize(account.stats.links.bytes, 2));
$('.ba-pub-links .folder-number', $dataStats).text(folderNumText);
$('.ba-pub-links .file-number', $dataStats).text(fileNumText);
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 file0 = l[23253];
var file1 = 835;
var files = 833;
var folder0 = l[23254];
var folder1 = 834;
var folders = 832;
var data = M.getDashboardData();
var locale = [files, folders, files, folders, folders, folders];
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 .rubbish-bin-dashboard').rebind('click', function() {
loadSubPage('fm/' + M.RubbishID);
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 (cnt === "0") {
str = locale[idx] === files ? file0 : folder0;
}
}
if (props.xfiles > 1) {
str += ', ' + String(l[833]).replace('[X]', props.xfiles);
}
elm.children[1].textContent = idx < 6 ? 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 = {};
var recentsDays = parseInt(localStorage.recentsDays) || 90;
var recentsNodeLimit = parseInt(localStorage.recentsNodeLimit) || 10000;
this._defaultRangeTimestamp = Math.floor((Date.now() - recentsDays * 86400000) / 1000); // 90 days
this._defaultRangeLimit = recentsNodeLimit;
var self = this;
// Init Dependencies
M.initShortcutsAndSelection(this.$container);
// 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;
if (M.currentdirid !== "recents") {
return;
}
// 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;
};
if (!action || !Array.isArray(action.path) || !action.path.length) {
// FIXME: check out where this does originates...
console.warn('Invalid parameters, cannot render breadcrumb...', action);
return;
}
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(M.getNameByHandle(action.user) || l[24061])
if (!user.h) {
// unknown/deleted contact, no business here...
return;
}
$userNameContainer
.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() {
if (user.h) {
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;
var $icon = $(".medium-file-icon", $newRow);
var iconClass = fileIcon(action[0]);
// handle icon
$icon.addClass(iconClass);
if (action.length === 1 && (iconClass === 'image' && is_image2(action[0]) ||
iconClass === 'video' && is_video(action[0]) || iconClass === 'pdf')) {
$icon.addClass('thumb').safeHTML('');
if (M.d[action[0].h]) {
M.d[action[0].h].seen = true;
}
action[0].seen = true;
if (iconClass === 'video') {
$icon.safeAppend(
'
' +
'' +
'
');
}
}
// 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;
var pdfs = mediaCounts.pdfs;
// 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;
if (M.d[node.h].shares && M.d[node.h].shares.EXP) {
$newThumb.addClass('linked');
}
}
if (!node.t && node.tvf) {
$newThumb.addClass('versioning');
}
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));
}
}
else if (fileIcon(node) === 'pdf') {
$(".block-view-file-type", $newThumb).removeClass("image").addClass("pdf");
}
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() {
var numOfFiles = images + videos + pdfs;
var currentStringSet = [l[7470].replace('%d', '%1'), l[24059], l[24060]];
if (isOtherUser) {
if (isCreated) {
$titleString = currentStringSet[1];
} else {
$titleString = currentStringSet[2];
}
$titleString = $titleString
.replace("%3", '')
.replace("[A]", '')
.replace("[/A]", '');
} else {
$titleString = '' + currentStringSet[0] + '';
}
return $titleString.replace("%1", numOfFiles);
};
$titleString = makeTitle();
$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) {
$frontIcon.removeClass('video').addClass('pdf');
}
else {
$rearIcon.removeClass("image").addClass("video");
}
if (pdfs === 0) {
$frontIcon.removeClass('pdf').addClass('image');
}
else {
$rearIcon.removeClass("image").addClass("pdf");
}
// 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,
pdfs: 0
};
for (var idx = action.length; idx--;) {
var n = action[idx];
if (is_video(n)) {
counts.videos++;
}
else if (is_image3(n)) {
counts.images++;
}
else if (fileIcon(n) === 'pdf') {
counts.pdfs++;
}
else if (d) {
console.warn('What is this?...', n);
}
}
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 removedAsExpanded = [];
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];
// If this is expaned action and it is about to removed, save states with ts.
if (this._expandedStates[action.id]) {
removedAsExpanded.push(action.ts);
}
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, removedAsExpanded);
}
};
/**
* Apply the state changes.
* @param added
* @param removed
* @private
*/
RecentsRender.prototype._applyStateChange = function(added, removed, removedAsExpanded) {
'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;
var currentScrollTop = this._dynamicList.getScrollTop();
var keepExpanded = function(id) {
$('.toggle-expanded-state', '.action-' + id).trigger('click');
this._dynamicList.scrollToYPosition(currentScrollTop);
};
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);
if (removedAsExpanded.indexOf(added[handled].ts) > -1) {
onIdle(keepExpanded.bind(this, 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';
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');
fmLeftMenuUI();
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');
}
});
$('.download-sync').rebind('click', function() {
var pf = navigator.platform.toUpperCase();
// If this is Linux let them goes to sync page to select linux type
if (pf.indexOf('LINUX') > -1) {
loadSubPage('sync');
}
// else directly give link of the file.
else {
window.location = megasync.getMegaSyncUrl();
}
});
},
/**
* 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);
$('.chart.data .pecents-txt', $bandwidthChart).text(b2[0]);
$('.chart.data .gb-txt', $bandwidthChart).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');
var usedPercentage = Math.round(account.space_used / account.space * 100);
this.perc_c_s = usedPercentage;
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(' ');
$('.chart.data .pecents-txt', $storageChart).text(b2[0]);
$('.chart.data .gb-txt', $storageChart).text(b2[1]);
$('.chart.data .perc-txt', $storageChart).text(usedPercentage + '%');
$('.chart.data .size-txt', $storageChart).text(bytesToSize(account.space_used));
$('.fm-right-block.dashboard .fm-account-blocks.storage .chart-indicator').css(
'left',
usedPercentage > 100 ?
180 + (Math.floor(Math.log(usedPercentage) * Math.LOG10E) - 2) * 15 + 'px'
: ''
);
$('.dashboard-container .fm-account-blocks.storage .chart-indicator').css(
'left',
usedPercentage > 100 ?
150 + (Math.floor(Math.log(usedPercentage) * Math.LOG10E) - 2) * 15 + 'px'
: ''
);
/** 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[1150]);
}
// update avatar
$('.fm-account-avatar').safeHTML(useravatar.contact(u_handle, '', 'div', false));
$('.fm-avatar').safeHTML(useravatar.contact(u_handle));
$('.avatar-block', '.top-menu-popup').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');
var megaInputs = new mega.ui.MegaInputs($inputs);
}
},
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');
// M.maf is cached in its getter, however, repeated gets will cause unnecessary checks.
var ach = M.maf;
// 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
|| (ach && ach[9] && ach[9].rwd)) {
// 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.
$('.mega-input-title-ontop.birth').addClass('hidden');
$('.mega-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 = $('.mega-input-title-ontop.birth.' + $.dateTimeFormat['structure'] + ' .byear')
.attr('max', i);
if (u_attr.birthyear) {
$input.val(u_attr.birthyear).trigger('input');
}
},
renderBirthMonth: function() {
'use strict';
if (u_attr.birthmonth) {
var $input = $('.mega-input-title-ontop.birth.' + $.dateTimeFormat['structure'] + ' .bmonth');
$input.val(u_attr.birthmonth).trigger('input');
this.zerofill($input[0]);
}
},
renderBirthDay: function() {
'use strict';
if (u_attr.birthday) {
var $input = $('.mega-input-title-ontop.birth.' + $.dateTimeFormat['structure'] + ' .bdate');
$input.val(u_attr.birthday).trigger('input');
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');
var $removeNumberButton = $('.rem-gsm', $phoneSettings);
var $modifyNumberButton = $('.modify-gsm', $phoneSettings);
// If the phone is already added, show that
if (typeof u_attr.smsv !== 'undefined') {
$phoneSettings.addClass('verified');
$phoneNumber.text(u_attr.smsv);
/**
* Send remove command to API, and update UI if needed
* @param {String} msg Message dialog's text to show for confirmation
* @param {String} desc Message dialog's description to show for confirmation
* @param {Boolean} showSuccessMsg Show message dialog on success
*/
var removeNumber = function(msg, desc, showSuccessMsg) {
msgDialog('confirmation', '', msg, desc, function(answer) {
if (answer) {
// lock UI
loadingDialog.show();
api_req(
{ a: 'smsr' },
{
callback: tryCatch(function(res) {
// Unlock UI regardless of the result
loadingDialog.hide();
if (res === 0) {
// success
// no APs, we need to rely on this response.
delete u_attr.smsv;
// update only relevant sections in UI
accountUI.account.profiles.renderPhoneBanner();
accountUI.account.profiles.renderPhoneDetails();
if (showSuccessMsg) {
msgDialog('info', '', l[23427]);
}
else {
sms.phoneInput.init();
}
}
else {
msgDialog('warningb', '', l[23428]);
}
}, function() {
loadingDialog.hide();
msgDialog('warningb', '', l[23428]);
})
}
);
}
});
};
$removeNumberButton.rebind('click.gsmremove', removeNumber.bind(null, l[23425], l[23426], true));
$modifyNumberButton.rebind('click.gsmmodify', removeNumber.bind(null, l[23429], l[23430], false));
}
else {
$phoneSettings.removeClass('verified');
// 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 = $('.mega-input-title-ontop.birth.' + $.dateTimeFormat['structure'],
$personalInfoBlock);
var $firstNameField = $('#account-firstname', $personalInfoBlock);
var $lastNameField = $('#account-lastname', $personalInfoBlock);
var $saveBlock = $('.fm-account-sections .save-block');
var $saveButton = $('.fm-account-save', $saveBlock);
// Avatar
$('.fm-account-avatar, .settings-sub-section.avatar .avatar', $personalInfoBlock)
.rebind('click', function() {
avatarDialog();
});
// All profile text inputs
$firstNameField.add($lastNameField).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 msg').find('.message-container').text(errorMsg);
$saveBlock.addClass('closed');
return false;
}
else if (this.value < min) {
$this.addClass('errored');
$parent.addClass('error msg').find('.message-container').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 msg');
}
}
}
var enteredFirst = $firstNameField.val().trim();
var enteredLast = $lastNameField.val().trim();
if (enteredFirst.length > 0 && enteredLast.length > 0 &&
!$('.errored', $personalInfoBlock).length &&
(enteredFirst !== u_attr.firstname ||
enteredLast !== u_attr.lastname ||
($('.bdate', $birthdayBlock).val() | 0) !== (u_attr.birthday | 0) ||
($('.bmonth', $birthdayBlock).val() | 0) !== (u_attr.birthmonth | 0) ||
($('.byear', $birthdayBlock).val() | 0) !== (u_attr.birthyear | 0))) {
$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);
$('.top-menu-logged .name', '.top-menu-popup').text(u_attr.name);
showToast('settings', l[7698]);
accountUI.account.profiles.bindEvents();
// update megadrop username for existing megadrop
mega.megadrop.updatePUPUserName(u_attr.fullname);
}
}
});
}
// Reset current Internationalization API usage upon save.
onIdle(function() {
mega.intl.reset();
});
$saveBlock.addClass('closed');
$saveButton.removeClass('disabled');
});
},
},
qrcode: {
$QRSettings: null,
render: function(account) {
'use strict';
this.$QRSettings = $('.qr-settings');
var QRoptions = {
width: 106,
height: 106,
correctLevel: QRErrorCorrectLevel.H, // high
foreground: '#dc0000',
text: getBaseUrl() + '/' + account.contactLink
};
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';
var self = this;
// 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));
}
);
self.initHomePageDropdown();
},
/**
* Render and bind events for the home page dropdown.
* @returns {void}
*/
initHomePageDropdown: function() {
'use strict';
var $hPageSelect = $('.fm-account-main .default-select.settings-choose-homepage-dropdown');
var $textField = $('span', $hPageSelect);
// Mark active item.
var $activeItem = $('.default-dropdown-item[data-value="' + getLandingPage() + '"]', $hPageSelect);
$activeItem.addClass('active');
$textField.text($activeItem.text());
// Initialize scrolling. This is to prevent scroll losing bug with action packet.
initSelectScrolling('#account-hpage .default-select-scroll');
// Bind Dropdowns events
bindDropdownEvents($hPageSelect, 1, '.fm-account-main');
$('.default-dropdown-item', $hPageSelect).rebind('click.saveChanges', function() {
var $selectedOption = $('.default-dropdown-item.active', $hPageSelect);
var newValue = $selectedOption.attr('data-value') || 'fm';
showToast('settings', l[16168]);
setLandingPage(newValue);
});
},
},
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
if (paymentType.indexOf('Credit Card') === 0) {
paymentType = paymentType.replace('Credit Card', l[6952]);
}
// 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[1150]);
$('.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(
'€ @@',
mega.intl.number.format(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) {
var intl = mega.intl.number;
// 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 + ''
+ '
'
+ '
€' + escapeHTML(intl.format(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) {
var intl = mega.intl.number;
$(account.transactions).each(function(i, el) {
if (i === $.transactionlimit) {
return false;
}
var credit = '';
var debit = '';
if (el[2] > 0) {
credit = '€' + escapeHTML(intl.format(el[2])) + '';
}
else {
debit = '€' + escapeHTML(intl.format(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[23754] + ''; // Logged-in
}
else {
status = '' + l[1664] + ''; // Expired
}
}
// If unknown country code use question mark png
if (!country.icon || country.icon === '??.png') {
country.icon = 'ud.png';
}
// 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';
this.$page = $('.fm-account-sections.fm-account-transfers');
// 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';
var $section = $('.transfer-tools', accountUI.transfers.$page);
accountUI.inputs.switch.init(
'#dlThroughMEGAsync',
$('#dlThroughMEGAsync').parent(),
fmconfig.dlThroughMEGAsync,
function(val) {
mega.config.setn('dlThroughMEGAsync', val);
});
megasync.isInstalled(function(err, is) {
if (!err && is) {
$('.green-notification', $section).addClass('hidden');
}
else {
$('.green-notification', $section).removeClass('hidden');
}
});
}
}
},
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';
if (window.megaChatIsDisabled) {
console.error('Mega Chat is disabled, cannot proceed to Contact and Chat settings');
return;
}
var self = this;
if (!megaChatIsReady) {
// If chat is not ready waiting for chat_initialized broadcaster.
loadingDialog.show();
var args = toArray.apply(null, arguments);
mBroadcaster.once('chat_initialized', function() {
self.init.apply(self, args);
});
return true;
}
loadingDialog.hide();
$.tresizer();
var presenceInt = megaChat.plugins.presencedIntegration;
if (!presenceInt || !presenceInt.userPresence) {
setTimeout(function() {
throw new Error('presenceInt is not ready...');
});
return true;
}
presenceInt.rebind('settingsUIUpdated.settings', function() {
self.init.apply(self, toArray.apply(null, arguments).slice(1));
});
// Only call this if the call of this function is the first one, made by fm.js -> accountUI
if (autoaway === undefined) {
presenceInt.userPresence.updateui();
return true;
}
this.status.render(presenceInt, autoaway, autoawaylock, autoawaytimeout, persist, persistlock, lastSeen);
this.status.bindEvents(presenceInt, autoawaytimeout);
this.richURL.render();
this.dnd.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';
if (typeof RichpreviewsFilter === 'undefined') {
return;
}
// Auto-away switch
const { previewGenerationConfirmation, confirmationDoConfirm, confirmationDoNever } = RichpreviewsFilter;
accountUI.inputs.switch.init(
'#richpreviews',
$('#richpreviews').parent(),
// previewGenerationConfirmation -> -1 (unset, default) || true || false
previewGenerationConfirmation && previewGenerationConfirmation > 0,
val => val ? confirmationDoConfirm() : confirmationDoNever()
);
}
},
dnd: {
/**
* Cached references for common DOM elements
*/
DOM: {
container: '.fm-account-main',
toggle: '#push-settings-toggle',
button: '#push-settings-button',
dialog: '.push-settings-dialog',
status: '.push-settings-status',
},
/**
* @see PushNotificationSettings.GROUPS
*/
group: 'CHAT',
/**
* hasDnd
* @description Get the current push notification setting
* @returns {Boolean}
*/
hasDnd: function() {
'use strict';
return (
pushNotificationSettings &&
pushNotificationSettings.getDnd(this.group) ||
pushNotificationSettings.getDnd(this.group) === 0
);
},
/**
* getTimeString
* @description Returns human readable and formatted string based on the
* current push notification setting (timestamp)
* @example `Notification will be silent until XX:XX`
* @returns {String}
*/
getTimeString: function() {
'use strict';
var dnd = pushNotificationSettings.getDnd(this.group);
if (dnd) {
return (
// `Notifications will be silent until %s`
l[23540].replace('%s', '' + escapeHTML(unixtimeToTimeString(dnd)) + '')
);
}
return ' ';
},
/**
* renderStatus
* @param hasDnd Boolean the push notification setting status
* @returns {*}
*/
renderStatus: function(hasDnd) {
'use strict';
var $status = $(this.DOM.status, this.DOM.container);
return hasDnd ? $status.safeHTML(this.getTimeString()).removeClass('hidden') : $status.addClass('hidden');
},
/**
* setInitialState
* @description Invoked immediately upon loading the module, sets the initial state -- conditionally
* sets the toggle state, renders formatted timestamp
*/
setInitialState: function() {
'use strict';
if (this.hasDnd()) {
var dnd = pushNotificationSettings.getDnd(this.group);
if (dnd && dnd < unixtime()) {
pushNotificationSettings.disableDnd(this.group);
return;
}
$(this.DOM.toggle, this.DOM.container).addClass('toggle-on');
this.renderStatus(true);
}
},
/**
* setMorningOption
* @description Handles the `Until tomorrow morning, 08:00` / `Until this morning, 08:00` option.
*/
setMorningOption: function() {
'use strict';
var container = '.radio-txt.morning-option';
var $label = $('span', container);
var $radio = $('input', container);
// 00:01 ~ 07:59 -> `Until this morning, 08:00`
// 08:00 ~ 00:00 -> `Until tomorrow morning, 08:00`
var targetTomorrow = (new Date().getHours()) >= 8;
var date = new Date();
// Start of the day -> 08:00
date.setHours(0, 1, 0, 0);
date.setHours(date.getHours() + 8);
if (targetTomorrow) {
// +1 day if we target `tomorrow morning`
date.setDate(date.getDate() + 1);
}
var difference = Math.abs(date - new Date());
var minutesUntil = Math.floor(difference / 1000 / 60);
$label.safeHTML(
targetTomorrow ? l[23671] || 'Until tomorrow morning, 08:00' : l[23670] || 'Until this morning, 08:00'
);
$radio.val(minutesUntil);
},
/**
* handleToggle
* @description Handles the toggle switch -- conditionally adds or removes the toggle active state,
* disables or sets the `Until I Turn It On Again` default setting
* @param ev Object the event object
*/
handleToggle: function(ev) {
'use strict';
var hasDnd = this.hasDnd();
var group = this.group;
if (hasDnd) {
pushNotificationSettings.disableDnd(group);
this.renderStatus(false);
$(ev.currentTarget).removeClass('toggle-on');
}
else {
this.handleDialogOpen();
}
},
/**
* handleDialogOpen
* @description
* Handles the dialog toggle, incl. attaches additional handler that sets the given setting if any is selected
*/
handleDialogOpen: function() {
'use strict';
var self = this;
var $dialog = $(this.DOM.dialog);
var time = unixtime();
this.setMorningOption();
M.safeShowDialog('push-settings-dialog', $dialog);
// Init radio button UI.
accountUI.inputs.radio.init('.custom-radio', $dialog, '');
// Bind the `Done` specific event handling
$('.push-settings-done', $dialog).rebind('click.dndUpdate', function() {
var $radio = $('input[type="radio"]:checked', $dialog);
var value = parseInt($radio.val(), 10);
pushNotificationSettings.setDnd(self.group, value === 0 ? 0 : time + value * 60);
$(self.DOM.toggle, self.DOM.container).addClass('toggle-on');
closeDialog();
self.renderStatus(true);
});
},
/**
* bindEvents
* @description
* Bind the initial event handlers, excl. the `Done` found within the dialog
*/
bindEvents: function() {
'use strict';
$(this.DOM.toggle, this.DOM.container).rebind('click.dndToggleSwitch', this.handleToggle.bind(this));
$(this.DOM.button, this.DOM.container).rebind('click.dndDialogOpen', this.handleDialogOpen.bind(this));
$('.fm-dialog-close, .push-settings-close', this.DOM.dialog).rebind('click.dndDialogClose', closeDialog);
},
/**
* render
* @description
* Initial render, invoked upon mounting the module
*/
render: function() {
'use strict';
this.setInitialState();
this.bindEvents();
}
},
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').trigger('input');
$('#account-confirm-password').val('').trigger('blur');
$('.account-pass-lines').removeClass('good1 good2 good3 good4 good5');
},
/**
* Initialise the handler to change the password strength indicator while typing the password
*/
initPasswordKeyupHandler: function() {
'use strict';
var $newPasswordField = $('#account-new-password');
var $changePasswordButton = $('.account.change-password .button-container');
var bindStrengthChecker = function() {
$newPasswordField.rebind('keyup.pwdchg input.pwdchg change.pwdchg', function() {
// Estimate the password strength
var password = $.trim($(this).val());
var passwordLength = password.length;
$changePasswordButton.removeClass('closed');
if (passwordLength === 0) {
$changePasswordButton.addClass('closed');
return false;
}
});
if ($newPasswordField.val().length) {
// Reset strength after re-rendering.
$newPasswordField.trigger('keyup.pwdchg');
}
};
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('input').trigger('focus');
$newPasswordConfirmField.trigger('blur');
});
// 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';
var self = this;
var failingAction = function(status) {
if (status === 1) {
msgDialog('info', '', l[22126]);
}
self.resetForm();
};
var registerationMethod = 1;
if (u_attr && u_attr.aav === 2) {
registerationMethod = 2;
}
var checkPassPromise = security.changePassword.isPasswordTheSame($.trim(newPassword),
registerationMethod);
checkPassPromise.fail(failingAction);
checkPassPromise.done(
function() {
if (registerationMethod === 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).blur();
$('#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');
}
});
},
/**
* 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.megaInputsShowError(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;
}
}
});
}
};