Failed to save the file to the "xx" directory.

Failed to save the file to the "ll" directory.

Failed to save the file to the "mm" directory.

Failed to save the file to the "wp" directory.

403WebShell
403Webshell
Server IP : 66.29.132.124  /  Your IP : 3.141.198.75
Web Server : LiteSpeed
System : Linux business141.web-hosting.com 4.18.0-553.lve.el8.x86_64 #1 SMP Mon May 27 15:27:34 UTC 2024 x86_64
User : wavevlvu ( 1524)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/wavevlvu/wavestudioz.com/wp-content/plugins/wp-file-manager/lib/js/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/wavevlvu/wavestudioz.com/wp-content/plugins/wp-file-manager/lib/js/elfinder.full.js
/*!
 * elFinder - file manager for web
 * Version 2.1.49 (2019-04-14)
 * http://elfinder.org
 * 
 * Copyright 2009-2019, Studio 42
 * Licensed under a 3-clauses BSD license
 */
(function(root, factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD
		define(['jquery','jquery-ui'], factory);
	} else if (typeof exports !== 'undefined') {
		// CommonJS
		var $, ui;
		try {
			$ = require('jquery');
			ui = require('jquery-ui');
		} catch (e) {}
		module.exports = factory($, ui);
	} else {
		// Browser globals (Note: root is window)
		factory(root.jQuery, root.jQuery.ui, true);
	}
}(this, function($, _ui, toGlobal) {
toGlobal = toGlobal || false;


/*
 * File: /js/elFinder.js
 */

/**
 * @class elFinder - file manager for web
 *
 * @author Dmitry (dio) Levashov
 **/
var elFinder = function(elm, opts, bootCallback) {
		//this.time('load');
	var self = this,
		
		/**
		 * Objects array of jQuery.Deferred that calls before elFinder boot up
		 * 
		 * @type Array
		 */
		dfrdsBeforeBootup = [],
		
		/**
		 * Plugin name to check for conflicts with bootstrap etc
		 *
		 * @type Array
		 **/
		conflictChecks = ['button', 'tooltip'],
		
		/**
		 * Node on which elfinder creating
		 *
		 * @type jQuery
		 **/
		node = jQuery(elm),
		
		/**
		 * Object of events originally registered in this node
		 * 
		 * @type Object
		 */
		prevEvents = jQuery.extend(true, {}, jQuery._data(node.get(0), 'events')),
		
		/**
		 * Store node contents.
		 *
		 * @see this.destroy
		 * @type jQuery
		 **/
		prevContent = jQuery('<div/>').append(node.contents()).attr('class', node.attr('class') || '').attr('style', node.attr('style') || ''),
		
		/**
		 * Instance ID. Required to get/set cookie
		 *
		 * @type String
		 **/
		id = node.attr('id') || '',
		
		/**
		 * Events namespace
		 *
		 * @type String
		 **/
		namespace = 'elfinder-' + (id ? id : Math.random().toString().substr(2, 7)),
		
		/**
		 * Mousedown event
		 *
		 * @type String
		 **/
		mousedown = 'mousedown.'+namespace,
		
		/**
		 * Keydown event
		 *
		 * @type String
		 **/
		keydown = 'keydown.'+namespace,
		
		/**
		 * Keypress event
		 *
		 * @type String
		 **/
		keypress = 'keypress.'+namespace,
		
		/**
		 * Keypup event
		 *
		 * @type String
		 **/
		keyup    = 'keyup.'+namespace,

		/**
		 * Is shortcuts/commands enabled
		 *
		 * @type Boolean
		 **/
		enabled = false,
		
		/**
		 * Store enabled value before ajax request
		 *
		 * @type Boolean
		 **/
		prevEnabled = false,
		
		/**
		 * List of build-in events which mapped into methods with same names
		 *
		 * @type Array
		 **/
		events = ['enable', 'disable', 'load', 'open', 'reload', 'select',  'add', 'remove', 'change', 'dblclick', 'getfile', 'lockfiles', 'unlockfiles', 'selectfiles', 'unselectfiles', 'dragstart', 'dragstop', 'search', 'searchend', 'viewchange'],
		
		/**
		 * Rules to validate data from backend
		 *
		 * @type Object
		 **/
		rules = {},
		
		/**
		 * Current working directory hash
		 *
		 * @type String
		 **/
		cwd = '',
		
		/**
		 * Current working directory options default
		 *
		 * @type Object
		 **/
		cwdOptionsDefault = {
			path          : '',
			url           : '',
			tmbUrl        : '',
			disabled      : [],
			separator     : '/',
			archives      : [],
			extract       : [],
			copyOverwrite : true,
			uploadOverwrite : true,
			uploadMaxSize : 0,
			jpgQuality    : 100,
			tmbCrop       : false,
			tmb           : false // old API
		},
		
		/**
		 * Current working directory options
		 *
		 * @type Object
		 **/
		cwdOptions = {},
		
		/**
		 * Files/dirs cache
		 *
		 * @type Object
		 **/
		files = {},
		
		/**
		 * Hidden Files/dirs cache
		 *
		 * @type Object
		 **/
		hiddenFiles = {},

		/**
		 * Files/dirs hash cache of each dirs
		 *
		 * @type Object
		 **/
		ownFiles = {},
		
		/**
		 * Selected files hashes
		 *
		 * @type Array
		 **/
		selected = [],
		
		/**
		 * Events listeners
		 *
		 * @type Object
		 **/
		listeners = {},
		
		/**
		 * Shortcuts
		 *
		 * @type Object
		 **/
		shortcuts = {},
		
		/**
		 * Buffer for copied files
		 *
		 * @type Array
		 **/
		clipboard = [],
		
		/**
		 * Copied/cuted files hashes
		 * Prevent from remove its from cache.
		 * Required for dispaly correct files names in error messages
		 *
		 * @type Object
		 **/
		remember = {},
		
		/**
		 * Queue for 'open' requests
		 *
		 * @type Array
		 **/
		queue = [],
		
		/**
		 * Queue for only cwd requests e.g. `tmb`
		 *
		 * @type Array
		 **/
		cwdQueue = [],
		
		/**
		 * Commands prototype
		 *
		 * @type Object
		 **/
		base = new self.command(self),
		
		/**
		 * elFinder node width
		 *
		 * @type String
		 * @default "auto"
		 **/
		width  = 'auto',
		
		/**
		 * elFinder node height
		 * Number: pixcel or String: Number + "%"
		 *
		 * @type Number | String
		 * @default 400
		 **/
		height = 400,
		
		/**
		 * Base node object or selector
		 * Element which is the reference of the height percentage
		 *
		 * @type Object|String
		 * @default null | jQuery(window) (if height is percentage)
		 **/
		heightBase = null,
		
		/**
		 * MIME type list(Associative array) handled as a text file
		 * 
		 * @type Object|null
		 */
		textMimes = null,
		
		/**
		 * elfinder path for sound played on remove
		 * @type String
		 * @default ./sounds/
		 **/
		soundPath = 'sounds/',
		
		/**
		 * JSON.stringify of previous fm.sorters
		 * @type String
		 */
		prevSorterStr = '',

		/**
		 * Map table of file extention to MIME-Type
		 * @type Object
		 */
		extToMimeTable,

		beeper = jQuery(document.createElement('audio')).hide().appendTo('body')[0],
			
		syncInterval,
		autoSyncStop = 0,
		
		uiCmdMapPrev = '',
		
		gcJobRes = null,
		
		open = function(data) {
			// NOTES: Do not touch data object
		
			var volumeid, contextmenu, emptyDirs = {}, stayDirs = {},
				rmClass, hashes, calc, gc, collapsed, prevcwd, sorterStr;
			
			if (self.api >= 2.1) {
				// support volume driver option `uiCmdMap`
				self.commandMap = (data.options.uiCmdMap && Object.keys(data.options.uiCmdMap).length)? data.options.uiCmdMap : {};
				if (uiCmdMapPrev !== JSON.stringify(self.commandMap)) {
					uiCmdMapPrev = JSON.stringify(self.commandMap);
				}
			} else {
				self.options.sync = 0;
			}
			
			if (data.init) {
				// init - reset cache
				files = {};
				ownFiles = {};
			} else {
				// remove only files from prev cwd
				// and collapsed directory (included 100+ directories) to empty for perfomance tune in DnD
				prevcwd = cwd;
				rmClass = 'elfinder-subtree-loaded ' + self.res('class', 'navexpand');
				collapsed = self.res('class', 'navcollapse');
				hashes = Object.keys(files);
				calc = function(i) {
					if (!files[i]) {
						return true;
					}
					
					var isDir = (files[i].mime === 'directory'),
						phash = files[i].phash,
						pnav;
						
					if (
						(!isDir
							|| emptyDirs[phash]
							|| (!stayDirs[phash]
								&& self.navHash2Elm(files[i].hash).is(':hidden')
								&& self.navHash2Elm(phash).next('.elfinder-navbar-subtree').children().length > 100
							)
						)
						&& (isDir || phash !== cwd)
						&& ! remember[i]
					) {
						if (isDir && !emptyDirs[phash]) {
							emptyDirs[phash] = true;
							self.navHash2Elm(phash)
							 .removeClass(rmClass)
							 .next('.elfinder-navbar-subtree').empty();
						}
						deleteCache(files[i]);
					} else if (isDir) {
						stayDirs[phash] = true;
					}
				};
				gc = function() {
					if (hashes.length) {
						gcJobRes && gcJobRes._abort();
						gcJobRes = self.asyncJob(calc, hashes, {
							interval : 20,
							numPerOnce : 100
						}).done(function() {
							var hd = self.storage('hide') || {items: {}};
							if (Object.keys(hiddenFiles).length) {
								jQuery.each(hiddenFiles, function(h) {
									if (!hd.items[h]) {
										delete hiddenFiles[h];
									}
								});
							}
						});
					}
				};
				
				self.trigger('filesgc').one('filesgc', function() {
					hashes = [];
				});
				
				self.one('opendone', function() {
					if (prevcwd !== cwd) {
						if (! node.data('lazycnt')) {
							gc();
						} else {
							self.one('lazydone', gc);
						}
					}
				});
			}

			self.sorters = {};
			cwd = data.cwd.hash;
			cache(data.files);
			if (!files[cwd]) {
				cache([data.cwd]);
			}

			// trigger event 'sorterupdate'
			sorterStr = JSON.stringify(self.sorters);
			if (prevSorterStr !== sorterStr) {
				self.trigger('sorterupdate');
				prevSorterStr = sorterStr;
			}

			self.lastDir(cwd);
			
			self.autoSync();
		},
		
		/**
		 * Store info about files/dirs in "files" object.
		 *
		 * @param  Array  files
		 * @param  String data type
		 * @return void
		 **/
		cache = function(data, type) {
			var defsorter = { name: true, perm: true, date: true,  size: true, kind: true },
				sorterChk = !self.sorters._checked,
				l         = data.length,
				setSorter = function(file) {
					var f = file || {},
						sorters = [];
					jQuery.each(self.sortRules, function(key) {
						if (defsorter[key] || typeof f[key] !== 'undefined' || (key === 'mode' && typeof f.perm !== 'undefined')) {
							sorters.push(key);
						}
					});
					self.sorters = self.arrayFlip(sorters, true);
					self.sorters._checked = true;
				},
				keeps = ['sizeInfo'],
				changedParents = {},
				hideData = self.storage('hide') || {},
				hides = hideData.items || {},
				f, i, keepProp, parents, hidden;

			for (i = 0; i < l; i++) {
				f = Object.assign({}, data[i]);
				hidden = (!hideData.show && hides[f.hash])? true : false;
				if (f.name && f.hash && f.mime) {
					if (!hidden) {
						if (sorterChk && f.phash === cwd) {
							setSorter(f);
							sorterChk = false;
						}
						
						if (f.phash && (type === 'add' || type === 'change')) {
							if (parents = self.parents(f.phash)) {
								jQuery.each(parents, function() {
									changedParents[this] = true;
								});
							}
						}
					}

					if (files[f.hash]) {
						jQuery.each(keeps, function() {
							if(files[f.hash][this] && ! f[this]) {
								f[this] = files[f.hash][this];
							}
						});
						if (f.sizeInfo && !f.size) {
							f.size = f.sizeInfo.size;
						}
						deleteCache(files[f.hash], true);
					}
					if (hides[f.hash]) {
						hiddenFiles[f.hash] = f;
					}
					if (hidden) {
						l--;
						data.splice(i--, 1);
					} else {
						files[f.hash] = f;
						if (f.mime === 'directory' && !ownFiles[f.hash]) {
							ownFiles[f.hash] = {};
						}
						if (f.phash) {
							if (!ownFiles[f.phash]) {
								ownFiles[f.phash] = {};
							}
							ownFiles[f.phash][f.hash] = true;
						}
					}
				}
			}
			// delete sizeInfo cache
			jQuery.each(Object.keys(changedParents), function() {
				var target = files[this];
				if (target && target.sizeInfo) {
					delete target.sizeInfo;
				}
			});
			
			// for empty folder
			sorterChk && setSorter();
		},
		
		/**
		 * Delete file object from files caches
		 * 
		 * @param  Array  removed hashes
		 * @return void
		 */
		remove = function(removed) {
			var l       = removed.length,
				roots   = {},
				rm      = function(hash) {
					var file = files[hash], i;
					if (file) {
						if (file.mime === 'directory') {
							if (roots[hash]) {
								delete self.roots[roots[hash]];
							}
							// restore stats of deleted root parent directory
							jQuery.each(self.leafRoots, function(phash, roots) {
								var idx, pdir;
								if ((idx = jQuery.inArray(hash, roots))!== -1) {
									if (roots.length === 1) {
										if ((pdir = Object.assign({}, files[phash])) && pdir._realStats) {
											jQuery.each(pdir._realStats, function(k, v) {
												pdir[k] = v;
											});
											remove(files[phash]._realStats);
											self.change({ changed: [pdir] });
										}
										delete self.leafRoots[phash];
									} else {
										self.leafRoots[phash].splice(idx, 1);
									}
								}
							});
							if (self.searchStatus.state < 2) {
								jQuery.each(files, function(h, f) {
									f.phash == hash && rm(h);
								});
							}
						}
						if (file.phash) {
							if (parents = self.parents(file.phash)) {
								jQuery.each(parents, function() {
									changedParents[this] = true;
								});
							}
						}
						deleteCache(files[hash]);
					}
				},
				changedParents = {},
				parents;
		
			jQuery.each(self.roots, function(k, v) {
				roots[v] = k;
			});
			while (l--) {
				rm(removed[l]);
			}
			// delete sizeInfo cache
			jQuery.each(Object.keys(changedParents), function() {
				var target = files[this];
				if (target && target.sizeInfo) {
					delete target.sizeInfo;
				}
			});
		},
		
		/**
		 * Update file object in files caches
		 * 
		 * @param  Array  changed file objects
		 * @return void
		 */
		change = function(changed) {
			jQuery.each(changed, function(i, file) {
				var hash = file.hash;
				if (files[hash]) {
					jQuery.each(Object.keys(files[hash]), function(i, v){
						if (typeof file[v] === 'undefined') {
							delete files[hash][v];
						}
					});
				}
				files[hash] = files[hash] ? Object.assign(files[hash], file) : file;
			});
		},
		
		/**
		 * Delete cache data of files, ownFiles and self.optionsByHashes
		 * 
		 * @param  Object  file
		 * @param  Boolean update
		 * @return void
		 */
		deleteCache = function(file, update) {
			var hash = file.hash,
				phash = file.phash;
			
			if (phash && ownFiles[phash]) {
				 delete ownFiles[phash][hash];
			}
			if (!update) {
				ownFiles[hash] && delete ownFiles[hash];
				self.optionsByHashes[hash] && delete self.optionsByHashes[hash];
			}
			delete files[hash];
		},
		
		/**
		 * Maximum number of concurrent connections on request
		 * 
		 * @type Number
		 */
		requestMaxConn,
		
		/**
		 * Current number of connections
		 * 
		 * @type Number
		 */
		requestCnt = 0,
		
		/**
		 * Queue waiting for connection
		 * 
		 * @type Array
		 */
		requestQueue = [],
		
		/**
		 * Flag to cancel the `open` command waiting for connection
		 * 
		 * @type Boolean
		 */
		requestQueueSkipOpen = false,
		
		/**
		 * Exec shortcut
		 *
		 * @param  jQuery.Event  keydown/keypress event
		 * @return void
		 */
		execShortcut = function(e) {
			var code    = e.keyCode,
				ctrlKey = !!(e.ctrlKey || e.metaKey),
				isMousedown = e.type === 'mousedown',
				ddm;

			!isMousedown && (self.keyState.keyCode = code);
			self.keyState.ctrlKey  = ctrlKey;
			self.keyState.shiftKey = e.shiftKey;
			self.keyState.metaKey  = e.metaKey;
			self.keyState.altKey   = e.altKey;
			if (isMousedown) {
				return;
			} else if (e.type === 'keyup') {
				self.keyState.keyCode = null;
				return;
			}

			if (enabled) {

				jQuery.each(shortcuts, function(i, shortcut) {
					if (shortcut.type    == e.type 
					&& shortcut.keyCode  == code 
					&& shortcut.shiftKey == e.shiftKey 
					&& shortcut.ctrlKey  == ctrlKey 
					&& shortcut.altKey   == e.altKey) {
						e.preventDefault();
						e.stopPropagation();
						shortcut.callback(e, self);
						self.debug('shortcut-exec', i+' : '+shortcut.description);
					}
				});
				
				// prevent tab out of elfinder
				if (code == jQuery.ui.keyCode.TAB && !jQuery(e.target).is(':input')) {
					e.preventDefault();
				}
				
				// cancel any actions by [Esc] key
				if (e.type === 'keydown' && code == jQuery.ui.keyCode.ESCAPE) {
					// copy or cut 
					if (! node.find('.ui-widget:visible').length) {
						self.clipboard().length && self.clipboard([]);
					}
					// dragging
					if (jQuery.ui.ddmanager) {
						ddm = jQuery.ui.ddmanager.current;
						ddm && ddm.helper && ddm.cancel();
					}
					// button menus
					self.toHide(node.find('.ui-widget.elfinder-button-menu.elfinder-frontmost:visible'));
					// trigger keydownEsc
					self.trigger('keydownEsc', e);
				}

			}
		},
		date = new Date(),
		utc,
		i18n,
		inFrame = (window.parent !== window),
		parentIframe = (function() {
			var pifm, ifms;
			if (inFrame) {
				try {
					ifms = jQuery('iframe', window.parent.document);
					if (ifms.length) {
						jQuery.each(ifms, function(i, ifm) {
							if (ifm.contentWindow === window) {
								pifm = jQuery(ifm);
								return false;
							}
						});
					}
				} catch(e) {}
			}
			return pifm;
		})(),
		/**
		 * elFinder boot up function
		 * 
		 * @type Function
		 */
		bootUp,
		/**
		 * Original function of XMLHttpRequest.prototype.send
		 * 
		 * @type Function
		 */
		savedXhrSend;
	
	// opts must be an object
	if (!opts) {
		opts = {};
	}
	
	// set UA.Angle, UA.Rotated for mobile devices
	if (self.UA.Mobile) {
		jQuery(window).on('orientationchange.'+namespace, function() {
			var a = ((screen && screen.orientation && screen.orientation.angle) || window.orientation || 0) + 0;
			if (a === -90) {
				a = 270;
			}
			self.UA.Angle = a;
			self.UA.Rotated = a % 180 === 0? false : true;
		}).trigger('orientationchange.'+namespace);
	}
	
	// check opt.bootCallback
	if (opts.bootCallback && typeof opts.bootCallback === 'function') {
		(function() {
			var func = bootCallback,
				opFunc = opts.bootCallback;
			bootCallback = function(fm, extraObj) {
				func && typeof func === 'function' && func.call(this, fm, extraObj);
				opFunc.call(this, fm, extraObj);
			};
		})();
	}
	delete opts.bootCallback;

	/**
	 * Protocol version
	 *
	 * @type String
	 **/
	this.api = null;
	
	/**
	 * elFinder use new api
	 *
	 * @type Boolean
	 **/
	this.newAPI = false;
	
	/**
	 * elFinder use old api
	 *
	 * @type Boolean
	 **/
	this.oldAPI = false;
	
	/**
	 * Net drivers names
	 *
	 * @type Array
	 **/
	this.netDrivers = [];
	
	/**
	 * Base URL of elfFinder library starting from Manager HTML
	 * 
	 * @type String
	 */
	this.baseUrl = '';
	
	/**
	 * Base URL of i18n js files
	 * baseUrl + "js/i18n/" when empty value
	 * 
	 * @type String
	 */
	this.i18nBaseUrl = '';

	/**
	 * Is elFinder CSS loaded
	 * 
	 * @type Boolean
	 */
	this.cssloaded = false;
	
	/**
	 * Current theme object
	 * 
	 * @type Object|Null
	 */
	this.theme = null;

	this.mimesCanMakeEmpty = {};

	/**
	 * Callback function at boot up that option specified at elFinder starting
	 * 
	 * @type Function
	 */
	this.bootCallback;

	/**
	 * ID. Required to create unique cookie name
	 *
	 * @type String
	 **/
	this.id = id;

	/**
	 * Method to store/fetch data
	 *
	 * @type Function
	 **/
	this.storage = (function() {
		try {
			if ('localStorage' in window && window.localStorage !== null) {
				if (self.UA.Safari) {
					// check for Mac/iOS safari private browsing mode
					window.localStorage.setItem('elfstoragecheck', 1);
					window.localStorage.removeItem('elfstoragecheck');
				}
				return self.localStorage;
			} else {
				return self.cookie;
			}
		} catch (e) {
			return self.cookie;
		}
	})();

	/**
	 * Configuration options
	 *
	 * @type Object
	 **/
	//this.options = jQuery.extend(true, {}, this._options, opts);
	this.options = Object.assign({}, this._options);
	
	// for old type configuration
	if (opts.uiOptions) {
		if (opts.uiOptions.toolbar && Array.isArray(opts.uiOptions.toolbar)) {
			if (jQuery.isPlainObject(opts.uiOptions.toolbar[opts.uiOptions.toolbar.length - 1])) {
				self.options.uiOptions.toolbarExtra = Object.assign(self.options.uiOptions.toolbarExtra || {}, opts.uiOptions.toolbar.pop());
			}
		}
	}
	
	// Overwrite if opts value is an array
	(function() {
		var arrOv = function(obj, base) {
			if (jQuery.isPlainObject(obj)) {
				jQuery.each(obj, function(k, v) {
					if (jQuery.isPlainObject(v)) {
						if (!base[k]) {
							base[k] = {};
						}
						arrOv(v, base[k]);
					} else {
						base[k] = v;
					}
				});
			}
		};
		arrOv(opts, self.options);
	})();
	
	// join toolbarExtra to toolbar
	this.options.uiOptions.toolbar.push(this.options.uiOptions.toolbarExtra);
	delete this.options.uiOptions.toolbarExtra;

	/**
	 * Arrays that has to unbind events
	 * 
	 * @type Object
	 */
	this.toUnbindEvents = {};
	
	/**
	 * Attach listener to events
	 * To bind to multiply events at once, separate events names by space
	 * 
	 * @param  String  event(s) name(s)
	 * @param  Object  event handler or {done: handler}
	 * @param  Boolean priority first
	 * @return elFinder
	 */
	this.bind = function(event, callback, priorityFirst) {
		var i, len;
		
		if (callback && (typeof callback === 'function' || typeof callback.done === 'function')) {
			event = ('' + event).toLowerCase().replace(/^\s+|\s+$/g, '').split(/\s+/);
			
			len = event.length;
			for (i = 0; i < len; i++) {
				if (listeners[event[i]] === void(0)) {
					listeners[event[i]] = [];
				}
				listeners[event[i]][priorityFirst? 'unshift' : 'push'](callback);
			}
		}
		return this;
	};
	
	/**
	 * Remove event listener if exists
	 * To un-bind to multiply events at once, separate events names by space
	 *
	 * @param  String    event(s) name(s)
	 * @param  Function  callback
	 * @return elFinder
	 */
	this.unbind = function(event, callback) {
		var i, len, l, ci;
		
		event = ('' + event).toLowerCase().split(/\s+/);
		
		len = event.length;
		for (i = 0; i < len; i++) {
			if (l = listeners[event[i]]) {
				ci = jQuery.inArray(callback, l);
				ci > -1 && l.splice(ci, 1);
			}
		}
		
		callback = null;
		return this;
	};
	
	/**
	 * Fire event - send notification to all event listeners
	 * In the callback `this` becames an event object
	 *
	 * @param  String   event type
	 * @param  Object   data to send across event
	 * @param  Boolean  allow modify data (call by reference of data) default: true
	 * @return elFinder
	 */
	this.trigger = function(evType, data, allowModify) {
		var type      = evType.toLowerCase(),
			isopen    = (type === 'open'),
			dataIsObj = (typeof data === 'object'),
			handlers  = listeners[type] || [],
			dones     = [],
			i, l, jst, event;
		
		this.debug('event-'+type, data);
		
		if (! dataIsObj || typeof allowModify === 'undefined') {
			allowModify = true;
		}
		if (l = handlers.length) {
			event = jQuery.Event(type);
			if (data) {
				data._event = event;
			}
			if (allowModify) {
				event.data = data;
			}

			for (i = 0; i < l; i++) {
				if (! handlers[i]) {
					// probably un-binded this handler
					continue;
				}

				// handler is jQuery.Deferred(), call all functions upon completion
				if (handlers[i].done) {
					dones.push(handlers[i].done);
					continue;
				}
				
				// set `event.data` only callback has argument
				if (handlers[i].length) {
					if (!allowModify) {
						// to avoid data modifications. remember about "sharing" passing arguments in js :) 
						if (typeof jst === 'undefined') {
							try {
								jst = JSON.stringify(data);
							} catch(e) {
								jst = false;
							}
						}
						event.data = jst? JSON.parse(jst) : data;
					}
				}

				try {
					if (handlers[i].call(event, event, this) === false || event.isDefaultPrevented()) {
						this.debug('event-stoped', event.type);
						break;
					}
				} catch (ex) {
					window.console && window.console.log && window.console.log(ex);
				}
				
			}
			
			// call done functions
			if (l = dones.length) {
				for (i = 0; i < l; i++) {
					try {
						if (dones[i].call(event, event, this) === false || event.isDefaultPrevented()) {
							this.debug('event-stoped', event.type + '(done)');
							break;
						}
					} catch (ex) {
						window.console && window.console.log && window.console.log(ex);
					}
				}
			}

			if (this.toUnbindEvents[type] && this.toUnbindEvents[type].length) {
				jQuery.each(this.toUnbindEvents[type], function(i, v) {
					self.unbind(v.type, v.callback);
				});
				delete this.toUnbindEvents[type];
			}
		}
		return this;
	};
	
	/**
	 * Get event listeners
	 *
	 * @param  String   event type
	 * @return Array    listed event functions
	 */
	this.getListeners = function(event) {
		return event? listeners[event.toLowerCase()] : listeners;
	};

	// set fm.baseUrl
	this.baseUrl = (function() {
		var myTag, myCss, base, baseUrl;
		
		if (self.options.baseUrl) {
			return self.options.baseUrl;
		} else {
			baseUrl = '';
			//myTag = jQuery('head > script[src$="js/elfinder.min.js"],script[src$="js/elfinder.full.js"]:first');
			myTag = null;
			jQuery('head > script').each(function() {
				if (this.src && this.src.match(/js\/elfinder(?:-[a-z0-9_-]+)?\.(?:min|full)\.js$/i)) {
					myTag = jQuery(this);
					return false;
				}
			});
			if (myTag) {
				myCss = jQuery('head > link[href$="css/elfinder.min.css"],link[href$="css/elfinder.full.css"]:first').length;
				if (! myCss) {
					// to request CSS auto loading
					self.cssloaded = null;
				}
				baseUrl = myTag.attr('src').replace(/js\/[^\/]+$/, '');
				if (! baseUrl.match(/^(https?\/\/|\/)/)) {
					// check <base> tag
					if (base = jQuery('head > base[href]').attr('href')) {
						baseUrl = base.replace(/\/$/, '') + '/' + baseUrl; 
					}
				}
			}
			if (baseUrl !== '') {
				self.options.baseUrl = baseUrl;
			} else {
				if (! self.options.baseUrl) {
					self.options.baseUrl = './';
				}
				baseUrl = self.options.baseUrl;
			}
			return baseUrl;
		}
	})();
	
	this.i18nBaseUrl = (this.options.i18nBaseUrl || this.baseUrl + 'js/i18n').replace(/\/$/, '') + '/';

	this.options.maxErrorDialogs = Math.max(1, parseInt(this.options.maxErrorDialogs || 5));

	// set dispInlineRegex
	cwdOptionsDefault.dispInlineRegex = this.options.dispInlineRegex;

	// auto load required CSS
	if (this.options.cssAutoLoad) {
		(function() {
			var baseUrl = self.baseUrl;
			
			// additional CSS files
			if (Array.isArray(self.options.cssAutoLoad)) {
				if (self.cssloaded === true) {
					self.loadCss(self.options.cssAutoLoad);
				} else {
					self.bind('cssloaded', function() {
						self.loadCss(self.options.cssAutoLoad);
					});
				}
			}

			// try to load main css
			if (self.cssloaded === null) {
				// hide elFinder node while css loading
				node.data('cssautoloadHide', jQuery('<style>.elfinder{visibility:hidden;overflow:hidden}</style>'));
				jQuery('head').append(node.data('cssautoloadHide'));

				// set default theme
				if (!self.options.themes.default) {
					self.options.themes = Object.assign({
						'default' : {
							'name': 'default',
							'cssurls': 'css/theme.css',
							'author': 'elFinder Project',
							'license': '3-clauses BSD'
						}
					}, self.options.themes);
					if (!self.options.theme) {
						self.options.theme = 'default';
					}
				}

				// load CSS
				self.loadCss([baseUrl+'css/elfinder.min.css'], {
					dfd: jQuery.Deferred().always(function() {
						if (node.data('cssautoloadHide')) {
							node.data('cssautoloadHide').remove();
							node.removeData('cssautoloadHide');
						}
					}).done(function() {
						if (!self.cssloaded) {
							self.cssloaded = true;
							self.trigger('cssloaded');
						}
					}).fail(function() {
						self.cssloaded = false;
						self.error(['errRead', 'CSS (elfinder or theme)']);
					})
				});
			}
			self.options.cssAutoLoad = false;
		})();
	}

	// load theme if exists
	this.changeTheme(this.storage('theme') || this.options.theme);
	
	/**
	 * Volume option to set the properties of the root Stat
	 * 
	 * @type Object
	 */
	this.optionProperties = {
		icon: void(0),
		csscls: void(0),
		tmbUrl: void(0),
		uiCmdMap: {},
		netkey: void(0),
		disabled: []
	};
	
	if (! inFrame && ! this.options.enableAlways && jQuery('body').children().length === 2) { // only node and beeper
		this.options.enableAlways = true;
	}
	
	// make options.debug
	if (this.options.debug === true) {
		this.options.debug = 'all';
	} else if (Array.isArray(this.options.debug)) {
		(function() {
			var d = {};
			jQuery.each(self.options.debug, function() {
				d[this] = true;
			});
			self.options.debug = d;
		})();
	} else {
		this.options.debug = false;
	}
	
	/**
	 * Original functions evacuated by conflict check
	 * 
	 * @type Object
	 */
	this.noConflicts = {};
	
	/**
	 * Check and save conflicts with bootstrap etc
	 * 
	 * @type Function
	 */
	this.noConflict = function() {
		jQuery.each(conflictChecks, function(i, p) {
			if (jQuery.fn[p] && typeof jQuery.fn[p].noConflict === 'function') {
				self.noConflicts[p] = jQuery.fn[p].noConflict();
			}
		});
	};
	// do check conflict
	this.noConflict();
	
	/**
	 * Is elFinder over CORS
	 *
	 * @type Boolean
	 **/
	this.isCORS = false;
	
	// configure for CORS
	(function(){
		if (typeof self.options.cors !== 'undefined' && self.options.cors !== null) {
			self.isCORS = self.options.cors? true : false;
		} else {
			var parseUrl = document.createElement('a'),
				parseUploadUrl,
				selfProtocol = window.location.protocol,
				portReg = function(protocol) {
					protocol = (!protocol || protocol === ':')? selfProtocol : protocol;
					return protocol === 'https:'? /\:443$/ : /\:80$/;
				},
				selfHost = window.location.host.replace(portReg(selfProtocol), '');
			parseUrl.href = opts.url;
			if (opts.urlUpload && (opts.urlUpload !== opts.url)) {
				parseUploadUrl = document.createElement('a');
				parseUploadUrl.href = opts.urlUpload;
			}
			if (selfHost !== parseUrl.host.replace(portReg(parseUrl.protocol), '')
				|| (parseUrl.protocol !== ':'&& parseUrl.protocol !== '' && (selfProtocol !== parseUrl.protocol))
				|| (parseUploadUrl && 
					(selfHost !== parseUploadUrl.host.replace(portReg(parseUploadUrl.protocol), '')
					|| (parseUploadUrl.protocol !== ':' && parseUploadUrl.protocol !== '' && (selfProtocol !== parseUploadUrl.protocol))
					)
				)
			) {
				self.isCORS = true;
			}
		}
		if (self.isCORS) {
			if (!jQuery.isPlainObject(self.options.customHeaders)) {
				self.options.customHeaders = {};
			}
			if (!jQuery.isPlainObject(self.options.xhrFields)) {
				self.options.xhrFields = {};
			}
			self.options.requestType = 'post';
			self.options.customHeaders['X-Requested-With'] = 'XMLHttpRequest';
			self.options.xhrFields['withCredentials'] = true;
		}
	})();

	/**
	 * Ajax request type
	 *
	 * @type String
	 * @default "get"
	 **/
	this.requestType = /^(get|post)$/i.test(this.options.requestType) ? this.options.requestType.toLowerCase() : 'get';
	
	// set `requestMaxConn` by option
	requestMaxConn = Math.max(parseInt(this.options.requestMaxConn), 1);
	
	/**
	 * Custom data that given as options
	 * 
	 * @type Object
	 * @default {}
	 */
	this.optsCustomData = jQuery.isPlainObject(this.options.customData) ? this.options.customData : {};

	/**
	 * Any data to send across every ajax request
	 *
	 * @type Object
	 * @default {}
	 **/
	this.customData = Object.assign({}, this.optsCustomData);

	/**
	 * Previous custom data from connector
	 * 
	 * @type Object|null
	 */
	this.prevCustomData = null;

	/**
	 * Any custom headers to send across every ajax request
	 *
	 * @type Object
	 * @default {}
	*/
	this.customHeaders = jQuery.isPlainObject(this.options.customHeaders) ? this.options.customHeaders : {};

	/**
	 * Any custom xhrFields to send across every ajax request
	 *
	 * @type Object
	 * @default {}
	 */
	this.xhrFields = jQuery.isPlainObject(this.options.xhrFields) ? this.options.xhrFields : {};

	/**
	 * Replace XMLHttpRequest.prototype.send to extended function for 3rd party libs XHR request etc.
	 * 
	 * @type Function
	 */
	this.replaceXhrSend = function() {
		if (! savedXhrSend) {
			savedXhrSend = XMLHttpRequest.prototype.send;
		}
		XMLHttpRequest.prototype.send = function() {
			var xhr = this;
			// set request headers
			if (self.customHeaders) {
				jQuery.each(self.customHeaders, function(key) {
					xhr.setRequestHeader(key, this);
				});
			}
			// set xhrFields
			if (self.xhrFields) {
				jQuery.each(self.xhrFields, function(key) {
					if (key in xhr) {
						xhr[key] = this;
					}
				});
			}
			return savedXhrSend.apply(this, arguments);
		};
	};
	
	/**
	 * Restore saved original XMLHttpRequest.prototype.send
	 * 
	 * @type Function
	 */
	this.restoreXhrSend = function() {
		savedXhrSend && (XMLHttpRequest.prototype.send = savedXhrSend);
	};

	/**
	 * command names for into queue for only cwd requests
	 * these commands aborts before `open` request
	 *
	 * @type Array
	 * @default ['tmb', 'parents']
	 */
	this.abortCmdsOnOpen = this.options.abortCmdsOnOpen || ['tmb', 'parents'];

	/**
	 * ui.nav id prefix
	 * 
	 * @type String
	 */
	this.navPrefix = 'nav' + (elFinder.prototype.uniqueid? elFinder.prototype.uniqueid : '') + '-';
	
	/**
	 * ui.cwd id prefix
	 * 
	 * @type String
	 */
	this.cwdPrefix = elFinder.prototype.uniqueid? ('cwd' + elFinder.prototype.uniqueid + '-') : '';
	
	// Increment elFinder.prototype.uniqueid
	++elFinder.prototype.uniqueid;
	
	/**
	 * URL to upload files
	 *
	 * @type String
	 **/
	this.uploadURL = opts.urlUpload || opts.url;
	
	/**
	 * Events namespace
	 *
	 * @type String
	 **/
	this.namespace = namespace;

	/**
	 * Today timestamp
	 *
	 * @type Number
	 **/
	this.today = (new Date(date.getFullYear(), date.getMonth(), date.getDate())).getTime()/1000;
	
	/**
	 * Yesterday timestamp
	 *
	 * @type Number
	 **/
	this.yesterday = this.today - 86400;
	
	utc = this.options.UTCDate ? 'UTC' : '';
	
	this.getHours    = 'get'+utc+'Hours';
	this.getMinutes  = 'get'+utc+'Minutes';
	this.getSeconds  = 'get'+utc+'Seconds';
	this.getDate     = 'get'+utc+'Date';
	this.getDay      = 'get'+utc+'Day';
	this.getMonth    = 'get'+utc+'Month';
	this.getFullYear = 'get'+utc+'FullYear';
	
	/**
	 * elFinder node z-index (auto detect on elFinder load)
	 *
	 * @type null | Number
	 **/
	this.zIndex;

	/**
	 * Current search status
	 * 
	 * @type Object
	 */
	this.searchStatus = {
		state  : 0, // 0: search ended, 1: search started, 2: in search result
		query  : '',
		target : '',
		mime   : '',
		mixed  : false, // in multi volumes search: false or Array that target volume ids
		ininc  : false // in incremental search
	};

	/**
	 * Interface language
	 *
	 * @type String
	 * @default "en"
	 **/
	this.lang = this.storage('lang') || this.options.lang;
	if (this.lang === 'jp') {
		this.lang = this.options.lang = 'ja';
	}

	this.viewType = this.storage('view') || this.options.defaultView || 'icons';

	this.sortType = this.storage('sortType') || this.options.sortType || 'name';
	
	this.sortOrder = this.storage('sortOrder') || this.options.sortOrder || 'asc';

	this.sortStickFolders = this.storage('sortStickFolders');
	if (this.sortStickFolders === null) {
		this.sortStickFolders = !!this.options.sortStickFolders;
	} else {
		this.sortStickFolders = !!this.sortStickFolders;
	}

	this.sortAlsoTreeview = this.storage('sortAlsoTreeview');
	if (this.sortAlsoTreeview === null || this.options.sortAlsoTreeview === null) {
		this.sortAlsoTreeview = !!this.options.sortAlsoTreeview;
	} else {
		this.sortAlsoTreeview = !!this.sortAlsoTreeview;
	}

	this.sortRules = jQuery.extend(true, {}, this._sortRules, this.options.sortRules);
	
	jQuery.each(this.sortRules, function(name, method) {
		if (typeof method != 'function') {
			delete self.sortRules[name];
		} 
	});
	
	this.compare = jQuery.proxy(this.compare, this);
	
	/**
	 * Delay in ms before open notification dialog
	 *
	 * @type Number
	 * @default 500
	 **/
	this.notifyDelay = this.options.notifyDelay > 0 ? parseInt(this.options.notifyDelay) : 500;
	
	/**
	 * Dragging UI Helper object
	 *
	 * @type jQuery | null
	 **/
	this.draggingUiHelper = null;
	
	/**
	 * Base droppable options
	 *
	 * @type Object
	 **/
	this.droppable = {
		greedy     : true,
		tolerance  : 'pointer',
		accept     : '.elfinder-cwd-file-wrapper,.elfinder-navbar-dir,.elfinder-cwd-file,.elfinder-cwd-filename',
		hoverClass : this.res('class', 'adroppable'),
		classes    : { // Deprecated hoverClass jQueryUI>=1.12.0
			'ui-droppable-hover': this.res('class', 'adroppable')
		},
		autoDisable: true, // elFinder original, see jquery.elfinder.js
		drop : function(e, ui) {
			var dst     = jQuery(this),
				targets = jQuery.grep(ui.helper.data('files')||[], function(h) { return h? true : false; }),
				result  = [],
				dups    = [],
				faults  = [],
				isCopy  = ui.helper.hasClass('elfinder-drag-helper-plus'),
				c       = 'class',
				cnt, hash, i, h;
			
			if (typeof e.button === 'undefined' || ui.helper.data('namespace') !== namespace || ! self.insideWorkzone(e.pageX, e.pageY)) {
				return false;
			}
			if (dst.hasClass(self.res(c, 'cwdfile'))) {
				hash = self.cwdId2Hash(dst.attr('id'));
			} else if (dst.hasClass(self.res(c, 'navdir'))) {
				hash = self.navId2Hash(dst.attr('id'));
			} else {
				hash = cwd;
			}

			cnt = targets.length;
			
			while (cnt--) {
				h = targets[cnt];
				// ignore drop into itself or in own location
				if (h != hash && files[h].phash != hash) {
					result.push(h);
				} else {
					((isCopy && h !== hash && files[hash].write)? dups : faults).push(h);
				}
			}
			
			if (faults.length) {
				return false;
			}
			
			ui.helper.data('droped', true);
			
			if (dups.length) {
				ui.helper.hide();
				self.exec('duplicate', dups, {_userAction: true});
			}
			
			if (result.length) {
				ui.helper.hide();
				self.clipboard(result, !isCopy);
				self.exec('paste', hash, {_userAction: true}, hash).always(function(){
					self.clipboard([]);
					self.trigger('unlockfiles', {files : targets});
				});
				self.trigger('drop', {files : targets});
			}
		}
	};
	
	/**
	 * Return true if filemanager is active
	 *
	 * @return Boolean
	 **/
	this.enabled = function() {
		return enabled && this.visible();
	};
	
	/**
	 * Return true if filemanager is visible
	 *
	 * @return Boolean
	 **/
	this.visible = function() {
		return node[0].elfinder && node.is(':visible');
	};
	
	/**
	 * Return file is root?
	 * 
	 * @param  Object  target file object
	 * @return Boolean
	 */
	this.isRoot = function(file) {
		return (file.isroot || ! file.phash)? true : false;
	};
	
	/**
	 * Return root dir hash for current working directory
	 * 
	 * @param  String   target hash
	 * @param  Boolean  include fake parent (optional)
	 * @return String
	 */
	this.root = function(hash, fake) {
		hash = hash || cwd;
		var dir, i;
		
		if (! fake) {
			jQuery.each(self.roots, function(id, rhash) {
				if (hash.indexOf(id) === 0) {
					dir = rhash;
					return false;
				}
			});
			if (dir) {
				return dir;
			}
		}
		
		dir = files[hash];
		while (dir && dir.phash && (fake || ! dir.isroot)) {
			dir = files[dir.phash];
		}
		if (dir) {
			return dir.hash;
		}
		
		while (i in files && files.hasOwnProperty(i)) {
			dir = files[i];
			if (dir.mime === 'directory' && !dir.phash && dir.read) {
				return dir.hash;
			}
		}
		
		return '';
	};
	
	/**
	 * Return current working directory info
	 * 
	 * @return Object
	 */
	this.cwd = function() {
		return files[cwd] || {};
	};
	
	/**
	 * Return required cwd option
	 * 
	 * @param  String  option name
	 * @param  String  target hash (optional)
	 * @return mixed
	 */
	this.option = function(name, target) {
		var res, item;
		target = target || cwd;
		if (self.optionsByHashes[target] && typeof self.optionsByHashes[target][name] !== 'undefined') {
			return self.optionsByHashes[target][name];
		}
		if (self.hasVolOptions && cwd !== target && (!(item = self.file(target)) || item.phash !== cwd)) {
			res = '';
			jQuery.each(self.volOptions, function(id, opt) {
				if (target.indexOf(id) === 0) {
					res = opt[name] || '';
					return false;
				}
			});
			return res;
		} else {
			return cwdOptions[name] || '';
		}
	};
	
	/**
	 * Return disabled commands by each folder
	 * 
	 * @param  Array  target hashes
	 * @return Array
	 */
	this.getDisabledCmds = function(targets, flip) {
		var disabled = {'hidden': true};
		if (! Array.isArray(targets)) {
			targets = [ targets ];
		}
		jQuery.each(targets, function(i, h) {
			var disCmds = self.option('disabledFlip', h);
			if (disCmds) {
				Object.assign(disabled, disCmds);
			}
		});
		return flip? disabled : Object.keys(disabled);
	};
	
	/**
	 * Return file data from current dir or tree by it's hash
	 * 
	 * @param  String  file hash
	 * @return Object
	 */
	this.file = function(hash, alsoHidden) { 
		return hash? (files[hash] || (alsoHidden? hiddenFiles[hash] : void(0))) : void(0); 
	};
	
	/**
	 * Return all cached files
	 * 
	 * @param  String  parent hash
	 * @return Object
	 */
	this.files = function(phash) {
		var items = {};
		if (phash) {
			if (!ownFiles[phash]) {
				return {};
			}
			jQuery.each(ownFiles[phash], function(h) {
				if (files[h]) {
					items[h] = files[h];
				} else {
					delete ownFiles[phash][h];
				}
			});
			return Object.assign({}, items);
		}
		return Object.assign({}, files);
	};
	
	/**
	 * Return list of file parents hashes include file hash
	 * 
	 * @param  String  file hash
	 * @return Array
	 */
	this.parents = function(hash) {
		var parents = [],
			dir;
		
		while (hash && (dir = this.file(hash))) {
			parents.unshift(dir.hash);
			hash = dir.phash;
		}
		return parents;
	};
	
	this.path2array = function(hash, i18) {
		var file, 
			path = [];
			
		while (hash) {
			if ((file = files[hash]) && file.hash) {
				path.unshift(i18 && file.i18 ? file.i18 : file.name);
				hash = file.isroot? null : file.phash;
			} else {
				path = [];
				break;
			}
		}
			
		return path;
	};
	
	/**
	 * Return file path or Get path async with jQuery.Deferred
	 * 
	 * @param  Object  file
	 * @param  Boolean i18
	 * @param  Object  asyncOpt
	 * @return String|jQuery.Deferred
	 */
	this.path = function(hash, i18, asyncOpt) { 
		var path = files[hash] && files[hash].path
			? files[hash].path
			: this.path2array(hash, i18).join(cwdOptions.separator);
		if (! asyncOpt || ! files[hash]) {
			return path;
		} else {
			asyncOpt = Object.assign({notify: {type : 'parents', cnt : 1, hideCnt : true}}, asyncOpt);
			
			var dfd    = jQuery.Deferred(),
				notify = asyncOpt.notify,
				noreq  = false,
				req    = function() {
					self.request({
						data : {cmd : 'parents', target : files[hash].phash},
						notify : notify,
						preventFail : true
					})
					.done(done)
					.fail(function() {
						dfd.reject();
					});
				},
				done   = function() {
					self.one('parentsdone', function() {
						path = self.path(hash, i18);
						if (path === '' && noreq) {
							//retry with request
							noreq = false;
							req();
						} else {
							if (notify) {
								clearTimeout(ntftm);
								notify.cnt = -(parseInt(notify.cnt || 0));
								self.notify(notify);
							}
							dfd.resolve(path);
						}
					});
				},
				ntftm;
		
			if (path) {
				return dfd.resolve(path);
			} else {
				if (self.ui['tree']) {
					// try as no request
					if (notify) {
						ntftm = setTimeout(function() {
							self.notify(notify);
						}, self.notifyDelay);
					}
					noreq = true;
					done(true);
				} else {
					req();
				}
				return dfd;
			}
		}
	};
	
	/**
	 * Return file url if set
	 * 
	 * @param  String  file hash
	 * @param  Object  Options
	 * @return String|Object of jQuery Deferred
	 */
	this.url = function(hash, o) {
		var file   = files[hash],
			opts   = o || {},
			async  = opts.async || false,
			temp   = opts.temporary || false,
			onetm  = (opts.onetime && self.option('onetimeUrl', hash)) || false,
			absurl = opts.absurl || false,
			dfrd   = (async || onetm)? jQuery.Deferred() : null,
			filter = function(url) {
				if (url && absurl) {
					url = self.convAbsUrl(url);
				}
				return url;
			},
			getUrl = function(url) {
				if (url) {
					return filter(url);
				}
				if (file.url) {
					return filter(file.url);
				}
				
				if (typeof baseUrl === 'undefined') {
					baseUrl = self.option('url', (!self.isRoot(file) && file.phash) || file.hash);
				}
				
				if (baseUrl) {
					return filter(baseUrl + jQuery.map(self.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/'));
				}

				var params = Object.assign({}, self.customData, {
					cmd: 'file',
					target: file.hash
				});
				if (self.oldAPI) {
					params.cmd = 'open';
					params.current = file.phash;
				}
				return filter(self.options.url + (self.options.url.indexOf('?') === -1 ? '?' : '&') + jQuery.param(params, true));
			}, 
			baseUrl, res;
		
		if (!file || !file.read) {
			return async? dfrd.resolve('') : '';
		}
		
		if (onetm) {
			async = true;
			this.request({
				data : { cmd : 'url', target : hash, options : { onetime: 1 } },
				preventDefault : true,
				options: {async: async},
				notify: {type : 'file', cnt : 1, hideCnt : true}
			}).done(function(data) {
				dfrd.resolve(filter(data.url || ''));
			}).fail(function() {
				dfrd.resolve('');
			});
		} else {
			if (file.url == '1' || (temp && !file.url && !(baseUrl = self.option('url', (!self.isRoot(file) && file.phash) || file.hash)))) {
				this.request({
					data : { cmd : 'url', target : hash, options : { temporary: temp? 1 : 0 } },
					preventDefault : true,
					options: {async: async},
					notify: async? {type : temp? 'file' : 'url', cnt : 1, hideCnt : true} : {}
				})
				.done(function(data) {
					file.url = data.url || '';
				})
				.fail(function() {
					file.url = '';
				})
				.always(function() {
					var url;
					if (file.url && temp) {
						url = file.url;
						file.url = '1'; // restore
					}
					if (async) {
						dfrd.resolve(getUrl(url));
					} else {
						return getUrl(url);
					}
				});
			} else {
				if (async) {
					dfrd.resolve(getUrl());
				} else {
					return getUrl();
				}
			}
		}
		if (async) {
			return dfrd;
		}
	};
	
	/**
	 * Return file url for the extarnal service
	 *
	 * @param      String  hash     The hash
	 * @param      Object  options  The options
	 * @return     Object  jQuery Deferred
	 */
	this.forExternalUrl = function(hash, options) {
		var onetime = self.option('onetimeUrl', hash),
			opts = {
				async: true,
				absurl: true
			};

		opts[onetime? 'onetime' : 'temporary'] = true;
		return self.url(hash, Object.assign({}, options, opts));
	};

	/**
	 * Return file url for open in elFinder
	 * 
	 * @param  String  file hash
	 * @param  Boolean for download link
	 * @return String
	 */
	this.openUrl = function(hash, download) {
		var file = files[hash],
			url  = '';
		
		if (!file || !file.read) {
			return '';
		}
		
		if (!download) {
			if (file.url) {
				if (file.url != 1) {
					url = file.url;
				}
			} else if (cwdOptions.url && file.hash.indexOf(self.cwd().volumeid) === 0) {
				url = cwdOptions.url + jQuery.map(this.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/');
			}
			if (url) {
				url += (url.match(/\?/)? '&' : '?') + '_'.repeat((url.match(/[\?&](_+)t=/g) || ['&t=']).sort().shift().match(/[\?&](_*)t=/)[1].length + 1) + 't=' + (file.ts || parseInt(+new Date()/1000));
				return url;
			}
		}
		
		url = this.options.url;
		url = url + (url.indexOf('?') === -1 ? '?' : '&')
			+ (this.oldAPI ? 'cmd=open&current='+file.phash : 'cmd=file')
			+ '&target=' + file.hash
			+ '&_t=' + (file.ts || parseInt(+new Date()/1000));
		
		if (download) {
			url += '&download=1';
		}
		
		jQuery.each(this.customData, function(key, val) {
			url += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(val);
		});
		
		return url;
	};
	
	/**
	 * Return thumbnail url
	 * 
	 * @param  Object  file object
	 * @return String
	 */
	this.tmb = function(file) {
		var tmbUrl, tmbCrop,
			cls    = 'elfinder-cwd-bgurl',
			url    = '';

		if (jQuery.isPlainObject(file)) {
			if (self.searchStatus.state && file.hash.indexOf(self.cwd().volumeid) !== 0) {
				tmbUrl = self.option('tmbUrl', file.hash);
				tmbCrop = self.option('tmbCrop', file.hash);
			} else {
				tmbUrl = cwdOptions['tmbUrl'];
				tmbCrop = cwdOptions['tmbCrop'];
			}
			if (tmbCrop) {
				cls += ' elfinder-cwd-bgurl-crop';
			}
			if (tmbUrl === 'self' && file.mime.indexOf('image/') === 0) {
				url = self.openUrl(file.hash);
				cls += ' elfinder-cwd-bgself';
			} else if ((self.oldAPI || tmbUrl) && file && file.tmb && file.tmb != 1) {
				url = tmbUrl + file.tmb;
			} else if (self.newAPI && file && file.tmb && file.tmb != 1) {
				url = file.tmb;
			}
			if (url) {
				if (file.ts && tmbUrl !== 'self') {
					url += (url.match(/\?/)? '&' : '?') + '_t=' + file.ts;
				}
				return { url: url, className: cls };
			}
		}
		
		return false;
	};
	
	/**
	 * Return selected files hashes
	 *
	 * @return Array
	 **/
	this.selected = function() {
		return selected.slice(0);
	};
	
	/**
	 * Return selected files info
	 * 
	 * @return Array
	 */
	this.selectedFiles = function() {
		return jQuery.map(selected, function(hash) { return files[hash] ? Object.assign({}, files[hash]) : null; });
	};
	
	/**
	 * Return true if file with required name existsin required folder
	 * 
	 * @param  String  file name
	 * @param  String  parent folder hash
	 * @return Boolean
	 */
	this.fileByName = function(name, phash) {
		var hash;
	
		for (hash in files) {
			if (files.hasOwnProperty(hash) && files[hash].phash == phash && files[hash].name == name) {
				return files[hash];
			}
		}
	};
	
	/**
	 * Valid data for required command based on rules
	 * 
	 * @param  String  command name
	 * @param  Object  cammand's data
	 * @return Boolean
	 */
	this.validResponse = function(cmd, data) {
		return data.error || this.rules[this.rules[cmd] ? cmd : 'defaults'](data);
	};
	
	/**
	 * Return bytes from ini formated size
	 * 
	 * @param  String  ini formated size
	 * @return Integer
	 */
	this.returnBytes = function(val) {
		var last;
		if (isNaN(val)) {
			if (! val) {
				val = '';
			}
			// for ex. 1mb, 1KB
			val = val.replace(/b$/i, '');
			last = val.charAt(val.length - 1).toLowerCase();
			val = val.replace(/[tgmk]$/i, '');
			if (last == 't') {
				val = val * 1024 * 1024 * 1024 * 1024;
			} else if (last == 'g') {
				val = val * 1024 * 1024 * 1024;
			} else if (last == 'm') {
				val = val * 1024 * 1024;
			} else if (last == 'k') {
				val = val * 1024;
			}
			val = isNaN(val)? 0 : parseInt(val);
		} else {
			val = parseInt(val);
			if (val < 1) val = 0;
		}
		return val;
	};
	
	/**
	 * Process ajax request.
	 * Fired events :
	 * @todo
	 * @example
	 * @todo
	 * @return jQuery.Deferred
	 */
	this.request = function(opts) { 
		var self     = this,
			o        = this.options,
			dfrd     = jQuery.Deferred(),
			// request ID
			reqId    = (+ new Date()).toString(16) + Math.floor(1000 * Math.random()).toString(16), 
			// request data
			data     = Object.assign({}, self.customData, {mimes : o.onlyMimes}, opts.data || opts),
			// command name
			cmd      = data.cmd,
			// request type is binary
			isBinary = (opts.options || {}).dataType === 'binary',
			// current cmd is "open"
			isOpen   = (!opts.asNotOpen && cmd === 'open'),
			// call default fail callback (display error dialog) ?
			deffail  = !(isBinary || opts.preventDefault || opts.preventFail),
			// call default success callback ?
			defdone  = !(isBinary || opts.preventDefault || opts.preventDone),
			// options for notify dialog
			notify   = Object.assign({}, opts.notify),
			// make cancel button
			cancel   = !!opts.cancel,
			// do not normalize data - return as is
			raw      = isBinary || !!opts.raw,
			// sync files on request fail
			syncOnFail = opts.syncOnFail,
			// use lazy()
			lazy     = !!opts.lazy,
			// prepare function before done()
			prepare  = opts.prepare,
			// navigate option object when cmd done
			navigate = opts.navigate,
			// open notify dialog timeout
			timeout,
			// use browser cache
			useCache = (opts.options || {}).cache,
			// request options
			options = Object.assign({
				url      : o.url,
				async    : true,
				type     : this.requestType,
				dataType : 'json',
				cache    : (self.api >= 2.1029), // api >= 2.1029 has unique request ID
				data     : data,
				headers  : this.customHeaders,
				xhrFields: this.xhrFields
			}, opts.options || {}),
			/**
			 * Default success handler. 
			 * Call default data handlers and fire event with command name.
			 *
			 * @param Object  normalized response data
			 * @return void
			 **/
			done = function(data) {
				data.warning && self.error(data.warning);
				
				if (isOpen) {
					open(data);
				} else {
					self.updateCache(data);
				}
				
				data.changed && data.changed.length && change(data.changed);
				
				self.lazy(function() {
					// fire some event to update cache/ui
					data.removed && data.removed.length && self.remove(data);
					data.added   && data.added.length   && self.add(data);
					data.changed && data.changed.length && self.change(data);
				}).then(function() {
					// fire event with command name
					return self.lazy(function() {
						self.trigger(cmd, data, false);
					});
				}).then(function() {
					// fire event with command name + 'done'
					return self.lazy(function() {
						self.trigger(cmd + 'done');
					});
				}).then(function() {
					// make toast message
					if (data.toasts && Array.isArray(data.toasts)) {
						jQuery.each(data.toasts, function() {
							this.msg && self.toast(this);
						});
					}
					// force update content
					data.sync && self.sync();
				});
			},
			/**
			 * Request error handler. Reject dfrd with correct error message.
			 *
			 * @param jqxhr  request object
			 * @param String request status
			 * @return void
			 **/
			error = function(xhr, status) {
				var error, data, 
					d = self.options.debug;
				
				switch (status) {
					case 'abort':
						error = xhr.quiet ? '' : ['errConnect', 'errAbort'];
						break;
					case 'timeout':	    
						error = ['errConnect', 'errTimeout'];
						break;
					case 'parsererror': 
						error = ['errResponse', 'errDataNotJSON'];
						if (xhr.responseText) {
							if (! cwd || (d && (d === 'all' || d['backend-error']))) {
								error.push(xhr.responseText);
							}
						}
						break;
					default:
						if (xhr.responseText) {
							// check responseText, Is that JSON?
							try {
								data = JSON.parse(xhr.responseText);
								if (data && data.error) {
									error = data.error;
								}
							} catch(e) {}
						}
						if (! error) {
							if (xhr.status == 403) {
								error = ['errConnect', 'errAccess', 'HTTP error ' + xhr.status];
							} else if (xhr.status == 404) {
								error = ['errConnect', 'errNotFound', 'HTTP error ' + xhr.status];
							} else if (xhr.status >= 500) {
								error = ['errResponse', 'errServerError', 'HTTP error ' + xhr.status];
							} else {
								if (xhr.status == 414 && options.type === 'get') {
									// retry by POST method
									options.type = 'post';
									self.abortXHR(xhr);
									dfrd.xhr = xhr = self.transport.send(options).fail(error).done(success);
									return;
								}
								error = xhr.quiet ? '' : ['errConnect', 'HTTP error ' + xhr.status];
							} 
						}
				}
				
				self.trigger(cmd + 'done');
				dfrd.reject({error: error}, xhr, status);
			},
			/**
			 * Request success handler. Valid response data and reject/resolve dfrd.
			 *
			 * @param Object  response data
			 * @param String request status
			 * @return void
			 **/
			success = function(response) {
				var d = self.options.debug;
				
				// Set currrent request command name
				self.currentReqCmd = cmd;
				
				if (response.debug && (!d || d !== 'all')) {
					if (!d) {
						d = self.options.debug = {};
					}
					d['backend-error'] = true;
					d['warning'] = true;
				}
				
				if (raw) {
					self.abortXHR(xhr);
					response && response.debug && self.debug('backend-debug', response);
					return dfrd.resolve(response);
				}
				
				if (!response) {
					return dfrd.reject({error :['errResponse', 'errDataEmpty']}, xhr, response);
				} else if (!jQuery.isPlainObject(response)) {
					return dfrd.reject({error :['errResponse', 'errDataNotJSON']}, xhr, response);
				} else if (response.error) {
					if (isOpen) {
						// check leafRoots
						jQuery.each(self.leafRoots, function(phash, roots) {
							self.leafRoots[phash] = jQuery.grep(roots, function(h) { return h !== data.target; });
						});
					}
					return dfrd.reject({error :response.error}, xhr, response);
				}
				
				var resolve = function() {
					var pushLeafRoots = function(name) {
						if (self.leafRoots[data.target] && response[name]) {
							jQuery.each(self.leafRoots[data.target], function(i, h) {
								var root;
								if (root = self.file(h)) {
									response[name].push(root);
								}
							});
						}
					},
					setTextMimes = function() {
						self.textMimes = {};
						jQuery.each(self.res('mimes', 'text'), function() {
							self.textMimes[this.toLowerCase()] = true;
						});
					},
					actionTarget;
					
					if (isOpen) {
						pushLeafRoots('files');
					} else if (cmd === 'tree') {
						pushLeafRoots('tree');
					}
					
					response = self.normalize(response);
					
					if (!self.validResponse(cmd, response)) {
						return dfrd.reject({error :(response.norError || 'errResponse')}, xhr, response);
					}
					
					if (isOpen) {
						if (!self.api) {
							self.api    = response.api || 1;
							if (self.api == '2.0' && typeof response.options.uploadMaxSize !== 'undefined') {
								self.api = '2.1';
							}
							self.newAPI = self.api >= 2;
							self.oldAPI = !self.newAPI;
						}
						
						if (response.textMimes && Array.isArray(response.textMimes)) {
							self.resources.mimes.text = response.textMimes;
							setTextMimes();
						}
						!self.textMimes && setTextMimes();
						
						if (response.options) {
							cwdOptions = Object.assign({}, cwdOptionsDefault, response.options);
						}

						if (response.netDrivers) {
							self.netDrivers = response.netDrivers;
						}

						if (response.maxTargets) {
							self.maxTargets = response.maxTargets;
						}

						if (!!data.init) {
							self.uplMaxSize = self.returnBytes(response.uplMaxSize);
							self.uplMaxFile = !!response.uplMaxFile? Math.min(parseInt(response.uplMaxFile), 50) : 20;
						}
					}

					if (typeof prepare === 'function') {
						prepare(response);
					}
					
					if (navigate) {
						actionTarget = navigate.target || 'added';
						if (response[actionTarget] && response[actionTarget].length) {
							self.one(cmd + 'done', function() {
								var targets  = response[actionTarget],
									newItems = self.findCwdNodes(targets),
									inCwdHashes = function() {
										var cwdHash = self.cwd().hash;
										return jQuery.map(targets, function(f) { return (f.phash && cwdHash === f.phash)? f.hash : null; });
									},
									hashes   = inCwdHashes(),
									makeToast  = function(t) {
										var node = void(0),
											data = t.action? t.action.data : void(0),
											cmd, msg, done;
										if ((data || hashes.length) && t.action && (msg = t.action.msg) && (cmd = t.action.cmd) && (!t.action.cwdNot || t.action.cwdNot !== self.cwd().hash)) {
											done = t.action.done;
											data = t.action.data;
											node = jQuery('<div/>')
												.append(
													jQuery('<button type="button" class="ui-button ui-widget ui-state-default ui-corner-all elfinder-tabstop"><span class="ui-button-text">'
														+self.i18n(msg)
														+'</span></button>')
													.on('mouseenter mouseleave', function(e) { 
														jQuery(this).toggleClass('ui-state-hover', e.type == 'mouseenter');
													})
													.on('click', function() {
														self.exec(cmd, data || hashes, {_userAction: true, _currentType: 'toast', _currentNode: jQuery(this) });
														if (done) {
															self.one(cmd+'done', function() {
																if (typeof done === 'function') {
																	done();
																} else if (done === 'select') {
																	self.trigger('selectfiles', {files : inCwdHashes()});
																}
															});
														}
													})
												);
										}
										delete t.action;
										t.extNode = node;
										return t;
									};
								
								if (! navigate.toast) {
									navigate.toast = {};
								}
								
								!navigate.noselect && self.trigger('selectfiles', {files : self.searchStatus.state > 1 ? jQuery.map(targets, function(f) { return f.hash; }) : hashes});
								
								if (newItems.length) {
									if (!navigate.noscroll) {
										newItems.first().trigger('scrolltoview', {blink : false});
										self.resources.blink(newItems, 'lookme');
									}
									if (jQuery.isPlainObject(navigate.toast.incwd)) {
										self.toast(makeToast(navigate.toast.incwd));
									}
								} else {
									if (jQuery.isPlainObject(navigate.toast.inbuffer)) {
										self.toast(makeToast(navigate.toast.inbuffer));
									}
								}
							});
						}
					}
					
					dfrd.resolve(response);
					
					response.debug && self.debug('backend-debug', response);
				};
				self.abortXHR(xhr);
				lazy? self.lazy(resolve) : resolve();
			},
			xhr, _xhr,
			xhrAbort = function(e) {
				if (xhr && xhr.state() === 'pending') {
					self.abortXHR(xhr, { quiet: true , abort: true });
					if (!e || (e.type !== 'unload' && e.type !== 'destroy')) {
						self.autoSync();
					}
				}
			},
			abort = function(e){
				self.trigger(cmd + 'done');
				if (e.type == 'autosync') {
					if (e.data.action != 'stop') return;
				} else if (e.type != 'unload' && e.type != 'destroy' && e.type != 'openxhrabort') {
					if (!e.data.added || !e.data.added.length) {
						return;
					}
				}
				xhrAbort(e);
			},
			request = function(mode) {
				var queueAbort = function() {
					syncOnFail = false;
					dfrd.reject();
				};
				
				if (mode) {
					if (mode === 'cmd') {
						return cmd;
					}
				}
				
				if (isOpen) {
					if (requestQueueSkipOpen) {
						return dfrd.reject();
					}
					requestQueueSkipOpen = true;
				}
				
				dfrd.always(function() {
					delete options.headers['X-elFinderReqid'];
				}).fail(function(error, xhr, response) {
					var errData = {
						cmd: cmd,
						err: error,
						xhr: xhr,
						rc: response
					};

					// unset this cmd queue when user canceling
					// see notify : function - `cancel.reject(0);`
					if (error === 0) {
						if (requestQueue.length) {
							requestQueue = jQuery.grep(requestQueue, function(req) {
								return (req('cmd') === cmd) ? false : true;
							});
						}
					}
					// trigger "requestError" event
					self.trigger('requestError', errData);
					if (errData._event && errData._event.isDefaultPrevented()) {
						deffail = false;
						syncOnFail = false;
						if (error) {
							error.error = '';
						}
					}
					// abort xhr
					xhrAbort();
					if (isOpen) {
						openDir = self.file(data.target);
						openDir && openDir.volumeid && self.isRoot(openDir) && delete self.volumeExpires[openDir.volumeid];
					}
					self.trigger(cmd + 'fail', response);
					if (error) {
						deffail ? self.error(error) : self.debug('error', self.i18n(error));
					}
					syncOnFail && self.sync();
				});

				if (!cmd) {
					syncOnFail = false;
					return dfrd.reject({error :'errCmdReq'});
				}
				
				if (self.maxTargets && data.targets && data.targets.length > self.maxTargets) {
					syncOnFail = false;
					return dfrd.reject({error :['errMaxTargets', self.maxTargets]});
				}

				defdone && dfrd.done(done);
				
				// quiet abort not completed "open" requests
				if (isOpen) {
					while ((_xhr = queue.pop())) {
						_xhr.queueAbort();
					}
					if (cwd !== data.target) {
						while ((_xhr = cwdQueue.pop())) {
							_xhr.queueAbort();
						}
					}
				}

				// trigger abort autoSync for commands to add the item
				if (jQuery.inArray(cmd, (self.cmdsToAdd + ' autosync').split(' ')) !== -1) {
					if (cmd !== 'autosync') {
						self.autoSync('stop');
						dfrd.always(function() {
							self.autoSync();
						});
					}
					self.trigger('openxhrabort');
				}

				delete options.preventFail;

				if (self.api >= 2.1029) {
					if (useCache) {
						options.headers['X-elFinderReqid'] = reqId;
					} else {
						Object.assign(options.data, { reqid : reqId });
					}
				}
				
				// function for set value of this syncOnFail
				dfrd.syncOnFail = function(state) {
					syncOnFail = !!state;
				};

				requestCnt++;

				dfrd.xhr = xhr = self.transport.send(options).always(function() {
					// set responseURL from native xhr object
					if (options._xhr && typeof options._xhr.responseURL !== 'undefined') {
						xhr.responseURL = options._xhr.responseURL || '';
					}
					--requestCnt;
					if (requestQueue.length) {
						requestQueue.shift()();
					} else {
						requestQueueSkipOpen = false;
					}
				}).fail(error).done(success);
				
				if (self.api >= 2.1029) {
					xhr._requestId = reqId;
				}
				
				if (isOpen || (data.compare && cmd === 'info')) {
					// regist function queueAbort
					xhr.queueAbort = queueAbort;
					// add autoSync xhr into queue
					queue.unshift(xhr);
					// bind abort()
					data.compare && self.bind(self.cmdsToAdd + ' autosync openxhrabort', abort);
					dfrd.always(function() {
						var ndx = jQuery.inArray(xhr, queue);
						data.compare && self.unbind(self.cmdsToAdd + ' autosync openxhrabort', abort);
						ndx !== -1 && queue.splice(ndx, 1);
					});
				} else if (jQuery.inArray(cmd, self.abortCmdsOnOpen) !== -1) {
					// regist function queueAbort
					xhr.queueAbort = queueAbort;
					// add "open" xhr, only cwd xhr into queue
					cwdQueue.unshift(xhr);
					dfrd.always(function() {
						var ndx = jQuery.inArray(xhr, cwdQueue);
						ndx !== -1 && cwdQueue.splice(ndx, 1);
					});
				}
				
				// abort pending xhr on window unload or elFinder destroy
				self.bind('unload destroy', abort);
				dfrd.always(function() {
					self.unbind('unload destroy', abort);
				});
				
				return dfrd;
			},
			queueingRequest = function() {
				// show notify
				if (notify.type && notify.cnt) {
					if (cancel) {
						notify.cancel = dfrd;
						opts.eachCancel && (notify.id = +new Date());
					}
					timeout = setTimeout(function() {
						self.notify(notify);
						dfrd.always(function() {
							notify.cnt = -(parseInt(notify.cnt)||0);
							self.notify(notify);
						});
					}, self.notifyDelay);
					
					dfrd.always(function() {
						clearTimeout(timeout);
					});
				}
				// queueing
				if (isOpen) {
					requestQueueSkipOpen = false;
				}
				if (requestCnt < requestMaxConn) {
					// do request
					return request();
				} else {
					if (isOpen) {
						requestQueue.unshift(request);
					} else {
						requestQueue.push(request);
					}
					return dfrd;
				}
			},
			bindData = {opts: opts, result: true},
			openDir;
		
		// prevent request initial request is completed
		if (!self.api && !data.init) {
			syncOnFail = false;
			return dfrd.reject();
		}

		// trigger "request.cmd" that callback be able to cancel request by substituting "false" for "event.data.result"
		self.trigger('request.' + cmd, bindData, true);
		
		if (! bindData.result) {
			self.trigger(cmd + 'done');
			return dfrd.reject();
		} else if (typeof bindData.result === 'object' && bindData.result.promise) {
			bindData.result
				.done(queueingRequest)
				.fail(function() {
					self.trigger(cmd + 'done');
					dfrd.reject();
				});
			return dfrd;
		}
		
		return queueingRequest();
	};
	
	/**
	 * Call cache()
	 * Store info about files/dirs in "files" object.
	 *
	 * @param  Array  files
	 * @return void
	 */
	this.cache = function(dataArray) {
		if (! Array.isArray(dataArray)) {
			dataArray = [ dataArray ];
		}
		cache(dataArray);
	};
	
	/**
	 * Update file object caches by respose data object
	 * 
	 * @param  Object  respose data object
	 * @return void
	 */
	this.updateCache = function(data) {
		if (jQuery.isPlainObject(data)) {
			data.files && data.files.length && cache(data.files, 'files');
			data.tree && data.tree.length && cache(data.tree, 'tree');
			data.removed && data.removed.length && remove(data.removed);
			data.added && data.added.length && cache(data.added, 'add');
			data.changed && data.changed.length && change(data.changed, 'change');
		}
	};
	
	/**
	 * Compare current files cache with new files and return diff
	 * 
	 * @param  Array   new files
	 * @param  String  target folder hash
	 * @param  Array   exclude properties to compare
	 * @return Object
	 */
	this.diff = function(incoming, onlydir, excludeProps) {
		var raw       = {},
			added     = [],
			removed   = [],
			changed   = [],
			excludes  = null,
			isChanged = function(hash) {
				var l = changed.length;

				while (l--) {
					if (changed[l].hash == hash) {
						return true;
					}
				}
			};
		
		jQuery.each(incoming, function(i, f) {
			raw[f.hash] = f;
		});
		
		// make excludes object
		if (excludeProps && excludeProps.length) {
			excludes = {};
			jQuery.each(excludeProps, function() {
				excludes[this] = true;
			});
		}
		
		// find removed
		jQuery.each(files, function(hash, f) {
			if (! raw[hash] && (! onlydir || f.phash === onlydir)) {
				removed.push(hash);
			}
		});
		
		// compare files
		jQuery.each(raw, function(hash, file) {
			var origin  = files[hash],
				orgKeys = {},
				chkKeyLen;

			if (!origin) {
				added.push(file);
			} else {
				// make orgKeys object
				jQuery.each(Object.keys(origin), function() {
					orgKeys[this] = true;
				});
				jQuery.each(file, function(prop) {
					delete orgKeys[prop];
					if (! excludes || ! excludes[prop]) {
						if (file[prop] !== origin[prop]) {
							changed.push(file);
							orgKeys = {};
							return false;
						}
					}
				});
				chkKeyLen = Object.keys(orgKeys).length;
				if (chkKeyLen !== 0) {
					if (excludes) {
						jQuery.each(orgKeys, function(prop) {
							if (excludes[prop]) {
								--chkKeyLen;
							}
						});
					}
					(chkKeyLen !== 0) && changed.push(file);
				}
			}
		});
		
		// parents of removed dirs mark as changed (required for tree correct work)
		jQuery.each(removed, function(i, hash) {
			var file  = files[hash], 
				phash = file.phash;

			if (phash 
			&& file.mime == 'directory' 
			&& jQuery.inArray(phash, removed) === -1 
			&& raw[phash] 
			&& !isChanged(phash)) {
				changed.push(raw[phash]);
			}
		});
		
		return {
			added   : added,
			removed : removed,
			changed : changed
		};
	};
	
	/**
	 * Sync content
	 * 
	 * @return jQuery.Deferred
	 */
	this.sync = function(onlydir, polling) {
		this.autoSync('stop');
		var self  = this,
			compare = function(){
				var c = '', cnt = 0, mtime = 0;
				if (onlydir && polling) {
					jQuery.each(files, function(h, f) {
						if (f.phash && f.phash === onlydir) {
							++cnt;
							mtime = Math.max(mtime, f.ts);
						}
						c = cnt+':'+mtime;
					});
				}
				return c;
			},
			comp  = compare(),
			dfrd  = jQuery.Deferred().done(function() { self.trigger('sync'); }),
			opts = [this.request({
				data           : {cmd : 'open', reload : 1, target : cwd, tree : (! onlydir && this.ui.tree) ? 1 : 0, compare : comp},
				preventDefault : true
			})],
			exParents = function() {
				var parents = [],
					curRoot = self.file(self.root(cwd)),
					curId = curRoot? curRoot.volumeid : null,
					phash = self.cwd().phash,
					isroot,pdir;
				
				while(phash) {
					if (pdir = self.file(phash)) {
						if (phash.indexOf(curId) !== 0) {
							parents.push( {target: phash, cmd: 'tree'} );
							if (! self.isRoot(pdir)) {
								parents.push( {target: phash, cmd: 'parents'} );
							}
							curRoot = self.file(self.root(phash));
							curId = curRoot? curRoot.volumeid : null;
						}
						phash = pdir.phash;
					} else {
						phash = null;
					}
				}
				return parents;
			};
		
		if (! onlydir && self.api >= 2) {
			(cwd !== this.root()) && opts.push(this.request({
				data           : {cmd : 'parents', target : cwd},
				preventDefault : true
			}));
			jQuery.each(exParents(), function(i, data) {
				opts.push(self.request({
					data           : {cmd : data.cmd, target : data.target},
					preventDefault : true
				}));
			});
		}
		jQuery.when.apply($, opts)
		.fail(function(error, xhr) {
			if (! polling || jQuery.inArray('errOpen', error) !== -1) {
				dfrd.reject(error);
				self.parseError(error) && self.request({
					data   : {cmd : 'open', target : (self.lastDir('') || self.root()), tree : 1, init : 1},
					notify : {type : 'open', cnt : 1, hideCnt : true}
				});
			} else {
				dfrd.reject((error && xhr.status != 0)? error : void 0);
			}
		})
		.done(function(odata) {
			var pdata, argLen, i;
			
			if (odata.cwd.compare) {
				if (comp === odata.cwd.compare) {
					return dfrd.reject();
				}
			}
			
			// for 2nd and more requests
			pdata = {tree : []};
			
			// results marge of 2nd and more requests
			argLen = arguments.length;
			if (argLen > 1) {
				for(i = 1; i < argLen; i++) {
					if (arguments[i].tree && arguments[i].tree.length) {
						pdata.tree.push.apply(pdata.tree, arguments[i].tree);
					}
				}
			}
			
			if (self.api < 2.1) {
				if (! pdata.tree) {
					pdata.tree = [];
				}
				pdata.tree.push(odata.cwd);
			}
			
			// data normalize
			odata = self.normalize(odata);
			if (!self.validResponse('open', odata)) {
				return dfrd.reject((odata.norError || 'errResponse'));
			}
			pdata = self.normalize(pdata);
			if (!self.validResponse('tree', pdata)) {
				return dfrd.reject((pdata.norError || 'errResponse'));
			}
			
			var diff = self.diff(odata.files.concat(pdata && pdata.tree ? pdata.tree : []), onlydir);

			diff.added.push(odata.cwd);
			
			self.updateCache(diff);
			
			// trigger events
			diff.removed.length && self.remove(diff);
			diff.added.length   && self.add(diff);
			diff.changed.length && self.change(diff);
			return dfrd.resolve(diff);
		})
		.always(function() {
			self.autoSync();
		});
		
		return dfrd;
	};
	
	this.upload = function(files) {
		return this.transport.upload(files, this);
	};
	
	/**
	 * Bind keybord shortcut to keydown event
	 *
	 * @example
	 *    elfinder.shortcut({ 
	 *       pattern : 'ctrl+a', 
	 *       description : 'Select all files', 
	 *       callback : function(e) { ... }, 
	 *       keypress : true|false (bind to keypress instead of keydown) 
	 *    })
	 *
	 * @param  Object  shortcut config
	 * @return elFinder
	 */
	this.shortcut = function(s) {
		var patterns, pattern, code, i, parts;
		
		if (this.options.allowShortcuts && s.pattern && jQuery.isFunction(s.callback)) {
			patterns = s.pattern.toUpperCase().split(/\s+/);
			
			for (i= 0; i < patterns.length; i++) {
				pattern = patterns[i];
				parts   = pattern.split('+');
				code    = (code = parts.pop()).length == 1 
					? (code > 0 ? code : code.charCodeAt(0))
					: (code > 0 ? code : jQuery.ui.keyCode[code]);

				if (code && !shortcuts[pattern]) {
					shortcuts[pattern] = {
						keyCode     : code,
						altKey      : jQuery.inArray('ALT', parts)   != -1,
						ctrlKey     : jQuery.inArray('CTRL', parts)  != -1,
						shiftKey    : jQuery.inArray('SHIFT', parts) != -1,
						type        : s.type || 'keydown',
						callback    : s.callback,
						description : s.description,
						pattern     : pattern
					};
				}
			}
		}
		return this;
	};
	
	/**
	 * Registered shortcuts
	 *
	 * @type Object
	 **/
	this.shortcuts = function() {
		var ret = [];
		
		jQuery.each(shortcuts, function(i, s) {
			ret.push([s.pattern, self.i18n(s.description)]);
		});
		return ret;
	};
	
	/**
	 * Get/set clipboard content.
	 * Return new clipboard content.
	 *
	 * @example
	 *   this.clipboard([]) - clean clipboard
	 *   this.clipboard([{...}, {...}], true) - put 2 files in clipboard and mark it as cutted
	 * 
	 * @param  Array    new files hashes
	 * @param  Boolean  cut files?
	 * @return Array
	 */
	this.clipboard = function(hashes, cut) {
		var map = function() { return jQuery.map(clipboard, function(f) { return f.hash; }); };

		if (hashes !== void(0)) {
			clipboard.length && this.trigger('unlockfiles', {files : map()});
			remember = {};
			
			clipboard = jQuery.map(hashes||[], function(hash) {
				var file = files[hash];
				if (file) {
					
					remember[hash] = true;
					
					return {
						hash   : hash,
						phash  : file.phash,
						name   : file.name,
						mime   : file.mime,
						read   : file.read,
						locked : file.locked,
						cut    : !!cut
					};
				}
				return null;
			});
			this.trigger('changeclipboard', {clipboard : clipboard.slice(0, clipboard.length)});
			cut && this.trigger('lockfiles', {files : map()});
		}

		// return copy of clipboard instead of refrence
		return clipboard.slice(0, clipboard.length);
	};
	
	/**
	 * Return true if command enabled
	 * 
	 * @param  String       command name
	 * @param  String|void  hash for check of own volume's disabled cmds
	 * @return Boolean
	 */
	this.isCommandEnabled = function(name, dstHash) {
		var disabled, cmd,
			cvid = self.cwd().volumeid || '';
		
		// In serach results use selected item hash to check
		if (!dstHash && self.searchStatus.state > 1 && self.selected().length) {
			dstHash = self.selected()[0];
		}
		if (dstHash && (! cvid || dstHash.indexOf(cvid) !== 0)) {
			disabled = self.option('disabledFlip', dstHash);
			//if (! disabled) {
			//	disabled = {};
			//}
		} else {
			disabled = cwdOptions.disabledFlip/* || {}*/;
		}
		cmd = this._commands[name];
		return cmd ? (cmd.alwaysEnabled || !disabled[name]) : false;
	};
	
	/**
	 * Exec command and return result;
	 *
	 * @param  String         command name
	 * @param  String|Array   usualy files hashes
	 * @param  String|Array   command options
	 * @param  String|void    hash for enabled check of own volume's disabled cmds
	 * @return jQuery.Deferred
	 */		
	this.exec = function(cmd, files, opts, dstHash) {
		var dfrd, resType;
		
		// apply commandMap for keyboard shortcut
		if (!dstHash && this.commandMap[cmd] && this.commandMap[cmd] !== 'hidden') {
			cmd = this.commandMap[cmd];
		}

		if (cmd === 'open') {
			if (this.searchStatus.state || this.searchStatus.ininc) {
				this.trigger('searchend', { noupdate: true });
			}
			this.autoSync('stop');
		}
		if (!dstHash && files) {
			if (jQuery.isArray(files)) {
				if (files.length) {
					dstHash = files[0];
				}
			} else {
				dstHash = files;
			}
		}
		dfrd = this._commands[cmd] && this.isCommandEnabled(cmd, dstHash) 
			? this._commands[cmd].exec(files, opts) 
			: jQuery.Deferred().reject('No such command');
		
		resType = typeof dfrd;
		if (!(resType === 'object' && dfrd.promise)) {
			self.debug('warning', '"cmd.exec()" should be returned "jQuery.Deferred" but cmd "' + cmd + '" returned "' + resType + '"');
			dfrd = jQuery.Deferred().resolve();
		}
		
		this.trigger('exec', { dfrd : dfrd, cmd : cmd, files : files, opts : opts, dstHash : dstHash });
		return dfrd;
	};
	
	/**
	 * Create and return dialog.
	 *
	 * @param  String|DOMElement  dialog content
	 * @param  Object             dialog options
	 * @return jQuery
	 */
	this.dialog = function(content, options) {
		var dialog = jQuery('<div/>').append(content).appendTo(node).elfinderdialog(options, self),
			dnode  = dialog.closest('.ui-dialog'),
			resize = function(){
				! dialog.data('draged') && dialog.is(':visible') && dialog.elfinderdialog('posInit');
			};
		if (dnode.length) {
			self.bind('resize', resize);
			dnode.on('remove', function() {
				self.unbind('resize', resize);
			});
		}
		return dialog;
	};
	
	/**
	 * Create and return toast.
	 *
	 * @param  Object  toast options - see ui/toast.js
	 * @return jQuery
	 */
	this.toast = function(options) {
		return jQuery('<div class="ui-front"/>').appendTo(this.ui.toast).elfindertoast(options || {}, this);
	};
	
	/**
	 * Return UI widget or node
	 *
	 * @param  String  ui name
	 * @return jQuery
	 */
	this.getUI = function(ui) {
		return this.ui[ui] || (ui? jQuery() : node);
	};
	
	/**
	 * Return elFinder.command instance or instances array
	 *
	 * @param  String  command name
	 * @return Object | Array
	 */
	this.getCommand = function(name) {
		return name === void(0) ? this._commands : this._commands[name];
	};
	
	/**
	 * Resize elfinder node
	 * 
	 * @param  String|Number  width
	 * @param  String|Number  height
	 * @return void
	 */
	this.resize = function(w, h) {
		var getMargin = function() {
				var m = node.outerHeight(true) - node.innerHeight(),
					p = node;
				
				while(p.get(0) !== heightBase.get(0)) {
					p = p.parent();
					m += p.outerHeight(true) - p.innerHeight();
					if (! p.parent().length) {
						// reached the document
						break;
					}
				}
				return m;
			},
			fit = ! node.hasClass('ui-resizable'),
			prv = node.data('resizeSize') || {w: 0, h: 0},
			mt, size = {};

		if (heightBase && heightBase.data('resizeTm')) {
			clearTimeout(heightBase.data('resizeTm'));
		}
		
		if (typeof h === 'string') {
			if (mt = h.match(/^([0-9.]+)%$/)) {
				// setup heightBase
				if (! heightBase || ! heightBase.length) {
					heightBase = jQuery(window);
				}
				if (! heightBase.data('marginToMyNode')) {
					heightBase.data('marginToMyNode', getMargin());
				}
				if (! heightBase.data('fitToBaseFunc')) {
					heightBase.data('fitToBaseFunc', function(e) {
						var tm = heightBase.data('resizeTm');
						e.preventDefault();
						e.stopPropagation();
						tm && cancelAnimationFrame(tm);
						if (! node.hasClass('elfinder-fullscreen') && (!self.UA.Mobile || heightBase.data('rotated') !== self.UA.Rotated)) {
							heightBase.data('rotated', self.UA.Rotated);
							heightBase.data('resizeTm', requestAnimationFrame(function() {
								self.restoreSize();
							}));
						}
					});
				}
				if (typeof heightBase.data('rotated') === 'undefined') {
					heightBase.data('rotated', self.UA.Rotated);
				}
				h = heightBase.height() * (mt[1] / 100) - heightBase.data('marginToMyNode');
				
				heightBase.off('resize.' + self.namespace, heightBase.data('fitToBaseFunc'));
				fit && heightBase.on('resize.' + self.namespace, heightBase.data('fitToBaseFunc'));
			}
		}
		
		node.css({ width : w, height : parseInt(h) });
		size.w = Math.round(node.width());
		size.h = Math.round(node.height());
		node.data('resizeSize', size);
		if (size.w !== prv.w || size.h !== prv.h) {
			node.trigger('resize');
			this.trigger('resize', {width : size.w, height : size.h});
		}
	};
	
	/**
	 * Restore elfinder node size
	 * 
	 * @return elFinder
	 */
	this.restoreSize = function() {
		this.resize(width, height);
	};
	
	this.show = function() {
		node.show();
		this.enable().trigger('show');
	};
	
	this.hide = function() {
		if (this.options.enableAlways) {
			prevEnabled = enabled;
			enabled = false;
		}
		this.disable();
		this.trigger('hide');
		node.hide();
	};
	
	/**
	 * Lazy execution function
	 * 
	 * @param  Object  function
	 * @param  Number  delay
	 * @param  Object  options
	 * @return Object  jQuery.Deferred
	 */
	this.lazy = function(func, delay, opts) {
		var busy = function(state) {
				var cnt = node.data('lazycnt'),
					repaint;
				
				if (state) {
					repaint = node.data('lazyrepaint')? false : opts.repaint;
					if (! cnt) {
						node.data('lazycnt', 1)
							.addClass('elfinder-processing');
					} else {
						node.data('lazycnt', ++cnt);
					}
					if (repaint) {
						node.data('lazyrepaint', true).css('display'); // force repaint
					}
				} else {
					if (cnt && cnt > 1) {
						node.data('lazycnt', --cnt);
					} else {
						repaint = node.data('lazyrepaint');
						node.data('lazycnt', 0)
							.removeData('lazyrepaint')
							.removeClass('elfinder-processing');
						repaint && node.css('display'); // force repaint;
						self.trigger('lazydone');
					}
				}
			},
			dfd  = jQuery.Deferred(),
			callFunc = function() {
				dfd.resolve(func.call(dfd));
				busy(false);
			};
		
		delay = delay || 0;
		opts = opts || {};
		busy(true);
		
		if (delay) {
			setTimeout(callFunc, delay);
		} else {
			requestAnimationFrame(callFunc);
		}
		
		return dfd;
	};
	
	/**
	 * Destroy this elFinder instance
	 *
	 * @return void
	 **/
	this.destroy = function() {
		if (node && node[0].elfinder) {
			node.hasClass('elfinder-fullscreen') && self.toggleFullscreen(node);
			this.options.syncStart = false;
			this.autoSync('forcestop');
			this.trigger('destroy').disable();
			clipboard = [];
			selected = [];
			listeners = {};
			shortcuts = {};
			jQuery(window).off('.' + namespace);
			jQuery(document).off('.' + namespace);
			self.trigger = function(){};
			jQuery(beeper).remove();
			node.off()
				.removeData()
				.empty()
				.append(prevContent.contents())
				.attr('class', prevContent.attr('class'))
				.attr('style', prevContent.attr('style'));
			delete node[0].elfinder;
			// restore kept events
			jQuery.each(prevEvents, function(n, arr) {
				jQuery.each(arr, function(i, o) {
					node.on(o.type + (o.namespace? '.'+o.namespace : ''), o.selector, o.handler);
				});
			});
		}
	};
	
	/**
	 * Start or stop auto sync
	 * 
	 * @param  String|Bool  stop
	 * @return void
	 */
	this.autoSync = function(mode) {
		var sync;
		if (self.options.sync >= 1000) {
			if (syncInterval) {
				clearTimeout(syncInterval);
				syncInterval = null;
				self.trigger('autosync', {action : 'stop'});
			}
			
			if (mode === 'stop') {
				++autoSyncStop;
			} else {
				autoSyncStop = Math.max(0, --autoSyncStop);
			}
			
			if (autoSyncStop || mode === 'forcestop' || ! self.options.syncStart) {
				return;
			} 
			
			// run interval sync
			sync = function(start){
				var timeout;
				if (cwdOptions.syncMinMs && (start || syncInterval)) {
					start && self.trigger('autosync', {action : 'start'});
					timeout = Math.max(self.options.sync, cwdOptions.syncMinMs);
					syncInterval && clearTimeout(syncInterval);
					syncInterval = setTimeout(function() {
						var dosync = true, hash = cwd, cts;
						if (cwdOptions.syncChkAsTs && files[hash] && (cts = files[hash].ts)) {
							self.request({
								data : {cmd : 'info', targets : [hash], compare : cts, reload : 1},
								preventDefault : true
							})
							.done(function(data){
								var ts;
								dosync = true;
								if (data.compare) {
									ts = data.compare;
									if (ts == cts) {
										dosync = false;
									}
								}
								if (dosync) {
									self.sync(hash).always(function(){
										if (ts) {
											// update ts for cache clear etc.
											files[hash].ts = ts;
										}
										sync();
									});
								} else {
									sync();
								}
							})
							.fail(function(error, xhr){
								var err = self.parseError(error);
								if (err && xhr.status != 0) {
									self.error(err);
									if (Array.isArray(err) && jQuery.inArray('errOpen', err) !== -1) {
										self.request({
											data   : {cmd : 'open', target : (self.lastDir('') || self.root()), tree : 1, init : 1},
											notify : {type : 'open', cnt : 1, hideCnt : true}
										});
									}
								} else {
									syncInterval = setTimeout(function() {
										sync();
									}, timeout);
								}
							});
						} else {
							self.sync(cwd, true).always(function(){
								sync();
							});
						}
					}, timeout);
				}
			};
			sync(true);
		}
	};
	
	/**
	 * Return bool is inside work zone of specific point
	 * 
	 * @param  Number event.pageX
	 * @param  Number event.pageY
	 * @return Bool
	 */
	this.insideWorkzone = function(x, y, margin) {
		var rectangle = this.getUI('workzone').data('rectangle');
		
		margin = margin || 1;
		if (x < rectangle.left + margin
		|| x > rectangle.left + rectangle.width + margin
		|| y < rectangle.top + margin
		|| y > rectangle.top + rectangle.height + margin) {
			return false;
		}
		return true;
	};
	
	/**
	 * Target ui node move to last of children of elFinder node fot to show front
	 * 
	 * @param  Object  target    Target jQuery node object
	 */
	this.toFront = function(target) {
		var nodes = node.children('.ui-front').removeClass('elfinder-frontmost'),
			lastnode = nodes.last();
		nodes.css('z-index', '');
		jQuery(target).addClass('ui-front elfinder-frontmost').css('z-index', lastnode.css('z-index') + 1);
	};
	
	/**
	 * Remove class 'elfinder-frontmost' and hide() to target ui node
	 *
	 * @param      Object   target  Target jQuery node object
	 * @param      Boolean  nohide  Do not hide
	 */
	this.toHide =function(target, nohide) {
		var tgt = jQuery(target),
			last;

		!nohide && tgt.hide();
		if (tgt.hasClass('elfinder-frontmost')) {
			tgt.removeClass('elfinder-frontmost');
			last = node.children('.ui-front:visible:not(.elfinder-frontmost)').last();
			if (last.length) {
				requestAnimationFrame(function() {
					if (!node.children('.elfinder-frontmost:visible').length) {
						self.toFront(last);
						last.trigger('frontmost');
					}
				});
			}
		}
	};

	/**
	 * Return css object for maximize
	 * 
	 * @return Object
	 */
	this.getMaximizeCss = function() {
		return {
			width   : '100%',
			height  : '100%',
			margin  : 0,
			top     : 0,
			left    : 0,
			display : 'block',
			position: 'fixed',
			zIndex  : Math.max(self.zIndex? (self.zIndex + 1) : 0 , 1000),
			maxWidth : '',
			maxHeight: ''
		};
	};
	
	// Closure for togglefullscreen
	(function() {
		// check is in iframe
		if (inFrame && self.UA.Fullscreen) {
			self.UA.Fullscreen = false;
			if (parentIframe && typeof parentIframe.attr('allowfullscreen') !== 'undefined') {
				self.UA.Fullscreen = true;
			}
		}

		var orgStyle, bodyOvf, resizeTm, fullElm, exitFull, toFull,
			cls = 'elfinder-fullscreen',
			clsN = 'elfinder-fullscreen-native',
			checkDialog = function() {
				var t = 0,
					l = 0;
				jQuery.each(node.children('.ui-dialog,.ui-draggable'), function(i, d) {
					var $d = jQuery(d),
						pos = $d.position();
					
					if (pos.top < 0) {
						$d.css('top', t);
						t += 20;
					}
					if (pos.left < 0) {
						$d.css('left', l);
						l += 20;
					}
				});
			},
			funcObj = self.UA.Fullscreen? {
				// native full screen mode
				
				fullElm: function() {
					return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement || null;
				},
				
				exitFull: function() {
					if (document.exitFullscreen) {
						return document.exitFullscreen();
					} else if (document.webkitExitFullscreen) {
						return document.webkitExitFullscreen();
					} else if (document.mozCancelFullScreen) {
						return document.mozCancelFullScreen();
					} else if (document.msExitFullscreen) {
						return document.msExitFullscreen();
					}
				},
				
				toFull: function(elem) {
					if (elem.requestFullscreen) {
						return elem.requestFullscreen();
					} else if (elem.webkitRequestFullscreen) {
						return elem.webkitRequestFullscreen();
					} else if (elem.mozRequestFullScreen) {
						return elem.mozRequestFullScreen();
					} else if (elem.msRequestFullscreen) {
						return elem.msRequestFullscreen();
					}
					return false;
				}
			} : {
				// node element maximize mode
				
				fullElm: function() {
					var full;
					if (node.hasClass(cls)) {
						return node.get(0);
					} else {
						full = node.find('.' + cls);
						if (full.length) {
							return full.get(0);
						}
					}
					return null;
				},
				
				exitFull: function() {
					var elm;
					
					jQuery(window).off('resize.' + namespace, resize);
					if (bodyOvf !== void(0)) {
						jQuery('body').css('overflow', bodyOvf);
					}
					bodyOvf = void(0);
					
					if (orgStyle) {
						elm = orgStyle.elm;
						restoreStyle(elm);
						jQuery(elm).trigger('resize', {fullscreen: 'off'});
					}
					
					jQuery(window).trigger('resize');
				},
				
				toFull: function(elem) {
					bodyOvf = jQuery('body').css('overflow') || '';
					jQuery('body').css('overflow', 'hidden');
					
					jQuery(elem).css(self.getMaximizeCss())
						.addClass(cls)
						.trigger('resize', {fullscreen: 'on'});
					
					checkDialog();
					
					jQuery(window).on('resize.' + namespace, resize).trigger('resize');
					
					return true;
				}
			},
			restoreStyle = function(elem) {
				if (orgStyle && orgStyle.elm == elem) {
					jQuery(elem).removeClass(cls + ' ' + clsN).attr('style', orgStyle.style);
					orgStyle = null;
				}
			},
			resize = function(e) {
				var elm;
				if (e.target === window) {
					resizeTm && cancelAnimationFrame(resizeTm);
					resizeTm = requestAnimationFrame(function() {
						if (elm = funcObj.fullElm()) {
							jQuery(elm).trigger('resize', {fullscreen: 'on'});
						}
					});
				}
			};
		
		jQuery(document).on('fullscreenchange.' + namespace + ' webkitfullscreenchange.' + namespace + ' mozfullscreenchange.' + namespace + ' MSFullscreenChange.' + namespace, function(e){
			if (self.UA.Fullscreen) {
				var elm = funcObj.fullElm(),
					win = jQuery(window);
				
				resizeTm && cancelAnimationFrame(resizeTm);
				if (elm === null) {
					win.off('resize.' + namespace, resize);
					if (orgStyle) {
						elm = orgStyle.elm;
						restoreStyle(elm);
						jQuery(elm).trigger('resize', {fullscreen: 'off'});
					}
				} else {
					jQuery(elm).addClass(cls + ' ' + clsN)
						.attr('style', 'width:100%; height:100%; margin:0; padding:0;')
						.trigger('resize', {fullscreen: 'on'});
					win.on('resize.' + namespace, resize);
					checkDialog();
				}
				win.trigger('resize');
			}
		});
		
		/**
		 * Toggle Full Scrren Mode
		 * 
		 * @param  Object target
		 * @param  Bool   full
		 * @return Object | Null  DOM node object of current full scrren
		 */
		self.toggleFullscreen = function(target, full) {
			var elm = jQuery(target).get(0),
				curElm = null;
			
			curElm = funcObj.fullElm();
			if (curElm) {
				if (curElm == elm) {
					if (full === true) {
						return curElm;
					}
				} else {
					if (full === false) {
						return curElm;
					}
				}
				funcObj.exitFull();
				return null;
			} else {
				if (full === false) {
					return null;
				}
			}
			
			orgStyle = {elm: elm, style: jQuery(elm).attr('style')};
			if (funcObj.toFull(elm) !== false) {
				return elm;
			} else {
				orgStyle = null;
				return null;
			}
		};
	})();
	
	// Closure for toggleMaximize
	(function(){
		var cls = 'elfinder-maximized',
		resizeTm,
		resize = function(e) {
			if (e.target === window && e.data && e.data.elm) {
				var elm = e.data.elm;
				resizeTm && cancelAnimationFrame(resizeTm);
				resizeTm = requestAnimationFrame(function() {
					elm.trigger('resize', {maximize: 'on'});
				});
			}
		},
		exitMax = function(elm) {
			jQuery(window).off('resize.' + namespace, resize);
			jQuery('body').css('overflow', elm.data('bodyOvf'));
			elm.removeClass(cls)
				.attr('style', elm.data('orgStyle'))
				.removeData('bodyOvf')
				.removeData('orgStyle');
			elm.trigger('resize', {maximize: 'off'});
		},
		toMax = function(elm) {
			elm.data('bodyOvf', jQuery('body').css('overflow') || '')
				.data('orgStyle', elm.attr('style'))
				.addClass(cls)
				.css(self.getMaximizeCss());
			jQuery('body').css('overflow', 'hidden');
			jQuery(window).on('resize.' + namespace, {elm: elm}, resize);
			elm.trigger('resize', {maximize: 'on'});
		};
		
		/**
		 * Toggle Maximize target node
		 * 
		 * @param  Object target
		 * @param  Bool   max
		 * @return void
		 */
		self.toggleMaximize = function(target, max) {
			var elm = jQuery(target),
				maximized = elm.hasClass(cls);
			
			if (maximized) {
				if (max === true) {
					return;
				}
				exitMax(elm);
			} else {
				if (max === false) {
					return;
				}
				toMax(elm);
			}
		};
	})();
	
	/*************  init stuffs  ****************/
	Object.assign(jQuery.ui.keyCode, {
		'F1' : 112,
		'F2' : 113,
		'F3' : 114,
		'F4' : 115,
		'F5' : 116,
		'F6' : 117,
		'F7' : 118,
		'F8' : 119,
		'F9' : 120,
		'F10' : 121,
		'F11' : 122,
		'F12' : 123,
		'DIG0' : 48,
		'DIG1' : 49,
		'DIG2' : 50,
		'DIG3' : 51,
		'DIG4' : 52,
		'DIG5' : 53,
		'DIG6' : 54,
		'DIG7' : 55,
		'DIG8' : 56,
		'DIG9' : 57,
		'NUM0' : 96,
		'NUM1' : 97,
		'NUM2' : 98,
		'NUM3' : 99,
		'NUM4' : 100,
		'NUM5' : 101,
		'NUM6' : 102,
		'NUM7' : 103,
		'NUM8' : 104,
		'NUM9' : 105,
		'CONTEXTMENU' : 93,
		'DOT'  : 190
	});
	
	this.dragUpload = false;
	this.xhrUpload  = (typeof XMLHttpRequestUpload != 'undefined' || typeof XMLHttpRequestEventTarget != 'undefined') && typeof File != 'undefined' && typeof FormData != 'undefined';
	
	// configure transport object
	this.transport = {};

	if (typeof(this.options.transport) == 'object') {
		this.transport = this.options.transport;
		if (typeof(this.transport.init) == 'function') {
			this.transport.init(this);
		}
	}
	
	if (typeof(this.transport.send) != 'function') {
		this.transport.send = function(opts) {
			if (!self.UA.IE) {
				// keep native xhr object for handling property responseURL
				opts._xhr = new XMLHttpRequest();
				opts.xhr = function() { return opts._xhr; };
			}
			return jQuery.ajax(opts);
		};
	}
	
	if (this.transport.upload == 'iframe') {
		this.transport.upload = jQuery.proxy(this.uploads.iframe, this);
	} else if (typeof(this.transport.upload) == 'function') {
		this.dragUpload = !!this.options.dragUploadAllow;
	} else if (this.xhrUpload && !!this.options.dragUploadAllow) {
		this.transport.upload = jQuery.proxy(this.uploads.xhr, this);
		this.dragUpload = true;
	} else {
		this.transport.upload = jQuery.proxy(this.uploads.iframe, this);
	}

	/**
	 * Decoding 'raw' string converted to unicode
	 * 
	 * @param  String str
	 * @return String
	 */
	this.decodeRawString = function(str) {
		var charCodes = function(str) {
			var i, len, arr;
			for (i=0,len=str.length,arr=[]; i<len; i++) {
				arr.push(str.charCodeAt(i));
			}
			return arr;
		},
		scalarValues = function(arr) {
			var scalars = [], i, len, c;
			if (typeof arr === 'string') {arr = charCodes(arr);}
			for (i=0,len=arr.length; c=arr[i],i<len; i++) {
				if (c >= 0xd800 && c <= 0xdbff) {
					scalars.push((c & 1023) + 64 << 10 | arr[++i] & 1023);
				} else {
					scalars.push(c);
				}
			}
			return scalars;
		},
		decodeUTF8 = function(arr) {
			var i, len, c, str, char = String.fromCharCode;
			for (i=0,len=arr.length,str=""; c=arr[i],i<len; i++) {
				if (c <= 0x7f) {
					str += char(c);
				} else if (c <= 0xdf && c >= 0xc2) {
					str += char((c&31)<<6 | arr[++i]&63);
				} else if (c <= 0xef && c >= 0xe0) {
					str += char((c&15)<<12 | (arr[++i]&63)<<6 | arr[++i]&63);
				} else if (c <= 0xf7 && c >= 0xf0) {
					str += char(
						0xd800 | ((c&7)<<8 | (arr[++i]&63)<<2 | arr[++i]>>>4&3) - 64,
						0xdc00 | (arr[i++]&15)<<6 | arr[i]&63
					);
				} else {
					str += char(0xfffd);
				}
			}
			return str;
		};
		
		return decodeUTF8(scalarValues(str));
	};

	/**
	 * Gets target file contents by file.hash
	 *
	 * @param      String  hash          The hash
	 * @param      String  responseType  'blob' or 'arraybuffer' (default)
	 * @return     arraybuffer|blob  The contents.
	 */
	this.getContents = function(hash, responseType) {
		var self = this,
			dfd = jQuery.Deferred(),
			type = responseType || 'arraybuffer',
			url, req;

		dfd.fail(function() {
			req && req.state() === 'pending' && req.reject();
		});

		url = self.openUrl(hash);
		if (!self.isSameOrigin(url)) {
			url = self.openUrl(hash, true);
		}
		req = self.request({
			data    : {cmd : 'get'},
			options : {
				url: url,
				type: 'get',
				cache : true,
				dataType : 'binary',
				responseType : type,
				processData: false
			}
		})
		.fail(function() {
			dfd.reject();
		})
		.done(function(data) {
			dfd.resolve(data);
		});

		return dfd;
	};

	this.getMimetype = function(name, orgMime) {
		var mime = orgMime,
			ext, m;
		m = (name + '').match(/\.([^.]+)$/);
		if (m && (ext = m[1])) {
			if (!extToMimeTable) {
				extToMimeTable = self.arrayFlip(self.mimeTypes);
			}
			if (!(mime = extToMimeTable[ext.toLowerCase()])) {
				mime = orgMime;
			}
		}
		return mime;
	};

	/**
	 * Supported check hash algorisms
	 * 
	 * @type Array
	 */
	self.hashCheckers = [];

	/**
	 * Closure of getContentsHashes()
	 */
	(function(self) {
		var hashLibs = {
				check : true
			},
			md5Calc = function(arr) {
				var spark = new hashLibs.SparkMD5.ArrayBuffer(),
					job;

				job = self.asyncJob(function(buf) {
					spark.append(buf);
				}, arr).done(function() {
					job._md5 = spark.end();
				});

				return job;
			},
			shaCalc = function(arr, length) {
				var sha, job;

				try {
					sha = new hashLibs.jsSHA('SHA' + (length.substr(0, 1) === '3'? length : ('-' + length)), 'ARRAYBUFFER');
					job = self.asyncJob(function(buf) {
						sha.update(buf);
					}, arr).done(function() {
						job._sha = sha.getHash('HEX');
					});
				} catch(e) {
					job = jQuery.Deferred.reject();
				}

				return job;
			};

		// make fm.hashCheckers
		if (self.options.cdns.sparkmd5) {
			self.hashCheckers.push('md5');
		}
		if (self.options.cdns.jssha) {
			self.hashCheckers = self.hashCheckers.concat(['sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512', 'shake128', 'shake256']);
		}

		/**
		 * Gets the contents hashes.
		 *
		 * @param      String  target      target file.hash
		 * @param      Object  needHashes  need hash lib names
		 * @return     Object  hashes with lib name as key
		 */
		self.getContentsHashes = function(target, needHashes) {
			var dfd = jQuery.Deferred(),
				needs = self.arrayFlip(needHashes || ['md5'], true),
				libs = [],
				jobs = [],
				res = {},
				req;

			dfd.fail(function() {
				req && req.reject();
			});

			if (hashLibs.check) {

				delete hashLibs.check;

				// load SparkMD5
				var libsmd5 = jQuery.Deferred();
				if (window.ArrayBuffer && self.options.cdns.sparkmd5) {
					libs.push(libsmd5);
					self.loadScript([self.options.cdns.sparkmd5],
						function(res) { 
							var SparkMD5 = res || window.SparkMD5;
							window.SparkMD5 && delete window.SparkMD5;
							libsmd5.resolve();
							if (SparkMD5) {
								hashLibs.SparkMD5 = SparkMD5;
							}
						},
						{
							tryRequire: true,
							error: function() {
								libsmd5.reject();
							}
						}
					);
				}

				// load jsSha
				var libssha = jQuery.Deferred();
				if (window.ArrayBuffer && self.options.cdns.jssha) {
					libs.push(libssha);
					self.loadScript([self.options.cdns.jssha],
						function(res) { 
							var jsSHA = res || window.jsSHA;
							window.jsSHA && delete window.jsSHA;
							libssha.resolve();
							if (jsSHA) {
								hashLibs.jsSHA = jsSHA;
							}
						},
						{
							tryRequire: true,
							error: function() {
								libssha.reject();
							}
						}
					);
				}
			}
			
			jQuery.when.apply(null, libs).always(function() {
				if (Object.keys(hashLibs).length) {
					req = self.getContents(target).done(function(arrayBuffer) {
						var arr = (arrayBuffer instanceof ArrayBuffer && arrayBuffer.byteLength > 0)? self.sliceArrayBuffer(arrayBuffer, 1048576) : false,
							i;

						if (needs.md5 && hashLibs.SparkMD5) {
							jobs.push(function() {
								var job = md5Calc(arr).done(function() {
									var f;
									res.md5 = job._md5;
									if (f = self.file(target)) {
										f.md5 = job._md5;
									}
									dfd.notify(res);
								});
								dfd.fail(function() {
									job.reject();
								});
								return job;
							});
						}
						if (hashLibs.jsSHA) {
							jQuery.each(['1', '224', '256', '384', '512', '3-224', '3-256', '3-384', '3-512', 'ke128', 'ke256'], function(i, v) {
								if (needs['sha' + v]) {
									jobs.push(function() {
										var job = shaCalc(arr, v).done(function() {
											var f;
											res['sha' + v] = job._sha;
											if (f = self.file(target)) {
												f['sha' + v] = job._sha;
											}
											dfd.notify(res);
										});
										return job;
									});
								}
							});
						}
						if (jobs.length) {
							self.sequence(jobs).always(function() {
								dfd.resolve(res);
							});
						} else {
							dfd.reject();
						}
					}).fail(function() {
						dfd.reject();
					});
				} else {
					dfd.reject();
				}
			});

			return dfd;
		};
	})(this);

	/**
	 * Parse error value to display
	 *
	 * @param  Mixed  error
	 * @return Mixed  parsed error
	 */
	this.parseError = function(error) {
		var arg = error;
		if (jQuery.isPlainObject(arg)) {
			arg = arg.error;
		}
		return arg;
	};

	/**
	 * Alias for this.trigger('error', {error : 'message'})
	 *
	 * @param  String  error message
	 * @return elFinder
	 **/
	this.error = function() {
		var arg = arguments[0],
			opts = arguments[1] || null,
			err;
		if (arguments.length == 1 && typeof(arg) === 'function') {
			return self.bind('error', arg);
		} else {
			err = this.parseError(arg);
			return (err === true || !err)? this : self.trigger('error', {error: err, opts : opts});
		}
	};
	
	// create bind/trigger aliases for build-in events
	jQuery.each(events, function(i, name) {
		self[name] = function() {
			var arg = arguments[0];
			return arguments.length == 1 && typeof(arg) == 'function'
				? self.bind(name, arg)
				: self.trigger(name, jQuery.isPlainObject(arg) ? arg : {});
		};
	});

	// bind core event handlers
	this
		.enable(function() {
			if (!enabled && self.api && self.visible() && self.ui.overlay.is(':hidden') && ! node.children('.elfinder-dialog.' + self.res('class', 'editing') + ':visible').length) {
				enabled = true;
				document.activeElement && document.activeElement.blur();
				node.removeClass('elfinder-disabled');
			}
		})
		.disable(function() {
			prevEnabled = enabled;
			enabled = false;
			node.addClass('elfinder-disabled');
		})
		.open(function() {
			selected = [];
		})
		.select(function(e) {
			var cnt = 0,
				unselects = [];
			selected = jQuery.grep(e.data.selected || e.data.value|| [], function(hash) {
				if (unselects.length || (self.maxTargets && ++cnt > self.maxTargets)) {
					unselects.push(hash);
					return false;
				} else {
					return files[hash] ? true : false;
				}
			});
			if (unselects.length) {
				self.trigger('unselectfiles', {files: unselects, inselect: true});
				self.toast({mode: 'warning', msg: self.i18n(['errMaxTargets', self.maxTargets])});
			}
		})
		.error(function(e) { 
			var opts  = {
					cssClass  : 'elfinder-dialog-error',
					title     : self.i18n('error'),
					resizable : false,
					destroyOnClose : true,
					buttons   : {}
				},
				node = self.getUI(),
				cnt = node.children('.elfinder-dialog-error').length,
				last, counter;
			
			if (cnt < self.options.maxErrorDialogs) {
				opts.buttons[self.i18n(self.i18n('btnClose'))] = function() { jQuery(this).elfinderdialog('close'); };

				if (e.data.opts && jQuery.isPlainObject(e.data.opts)) {
					Object.assign(opts, e.data.opts);
				}

				self.dialog('<span class="elfinder-dialog-icon elfinder-dialog-icon-error"/>'+self.i18n(e.data.error), opts);
			} else {
				last = node.children('.elfinder-dialog-error:last').children('.ui-dialog-content:first');
				counter = last.children('.elfinder-error-counter');
				if (counter.length) {
					counter.data('cnt', parseInt(counter.data('cnt')) + 1).html(self.i18n(['moreErrors', counter.data('cnt')]));
				} else {
					counter = jQuery('<span class="elfinder-error-counter">'+ self.i18n(['moreErrors', 1]) +'</span>').data('cnt', 1);
					last.append('<br/>', counter);
				}
			}
		})
		.bind('tmb', function(e) {
			jQuery.each(e.data.images||[], function(hash, tmb) {
				if (files[hash]) {
					files[hash].tmb = tmb;
				}
			});
		})
		.bind('searchstart', function(e) {
			Object.assign(self.searchStatus, e.data);
			self.searchStatus.state = 1;
		})
		.bind('search', function(e) {
			self.searchStatus.state = 2;
		})
		.bind('searchend', function() {
			self.searchStatus.state = 0;
			self.searchStatus.ininc = false;
			self.searchStatus.mixed = false;
		})
		.bind('canMakeEmptyFile', function(e) {
			var data = e.data,
				obj = {};
			if (data && Array.isArray(data.mimes)) {
				if (!data.unshift) {
					obj = self.mimesCanMakeEmpty;
				}
				jQuery.each(data.mimes, function() {
					if (!obj[this]) {
						obj[this] = self.mimeTypes[this];
					}
				});
				if (data.unshift) {
					self.mimesCanMakeEmpty = Object.assign(obj, self.mimesCanMakeEmpty);
				}
			}
		})
		.bind('themechange', function() {
			requestAnimationFrame(function() {
				self.trigger('uiresize');
			});
		})
		;

	// We listen and emit a sound on delete according to option
	if (true === this.options.sound) {
		this.bind('playsound', function(e) {
			var play  = beeper.canPlayType && beeper.canPlayType('audio/wav; codecs="1"'),
				file = e.data && e.data.soundFile;

			play && file && play != '' && play != 'no' && jQuery(beeper).html('<source src="' + soundPath + file + '" type="audio/wav">')[0].play();
		});
	}

	// bind external event handlers
	jQuery.each(this.options.handlers, function(event, callback) {
		self.bind(event, callback);
	});

	/**
	 * History object. Store visited folders
	 *
	 * @type Object
	 **/
	this.history = new this.history(this);
	
	/**
	 * Root hashed
	 * 
	 * @type Object
	 */
	this.roots = {};
	
	/**
	 * leaf roots
	 * 
	 * @type Object
	 */
	this.leafRoots = {};
	
	this.volumeExpires = {};

	/**
	 * Loaded commands
	 *
	 * @type Object
	 **/
	this._commands = {};
	
	if (!Array.isArray(this.options.commands)) {
		this.options.commands = [];
	}
	
	if (jQuery.inArray('*', this.options.commands) !== -1) {
		this.options.commands = Object.keys(this.commands);
	}
	
	/**
	 * UI command map of cwd volume ( That volume driver option `uiCmdMap` )
	 *
	 * @type Object
	 **/
	this.commandMap = {};
	
	/**
	 * cwd options of each volume
	 * key: volumeid
	 * val: options object
	 * 
	 * @type Object
	 */
	this.volOptions = {};

	/**
	 * Has volOptions data
	 * 
	 * @type Boolean
	 */
	this.hasVolOptions = false;

	/**
	 * Hash of trash holders
	 * key: trash folder hash
	 * val: source volume hash
	 * 
	 * @type Object
	 */
	this.trashes = {};

	/**
	 * cwd options of each folder/file
	 * key: hash
	 * val: options object
	 *
	 * @type Object
	 */
	this.optionsByHashes = {};
	
	/**
	 * UI Auto Hide Functions
	 * Each auto hide function mast be call to `fm.trigger('uiautohide')` at end of process
	 *
	 * @type Array
	 **/
	this.uiAutoHide = [];
	
	// trigger `uiautohide`
	this.one('open', function() {
		if (self.uiAutoHide.length) {
			setTimeout(function() {
				self.trigger('uiautohide');
			}, 500);
		}
	});
	
	// Auto Hide Functions sequential processing start
	this.bind('uiautohide', function() {
		if (self.uiAutoHide.length) {
			self.uiAutoHide.shift()();
		}
	});

	if (this.options.width) {
		width = this.options.width;
	}
	
	if (this.options.height) {
		height = this.options.height;
	}
	
	if (this.options.heightBase) {
		heightBase = jQuery(this.options.heightBase);
	}
	
	if (this.options.soundPath) {
		soundPath = this.options.soundPath.replace(/\/+$/, '') + '/';
	} else {
		soundPath = this.baseUrl + soundPath;
	}
	
	self.one('opendone', function() {
		var tm;
		// attach events to document
		jQuery(document)
			// disable elfinder on click outside elfinder
			.on('click.'+namespace, function(e) { enabled && ! self.options.enableAlways && !jQuery(e.target).closest(node).length && self.disable(); })
			// exec shortcuts
			.on(keydown+' '+keypress+' '+keyup+' '+mousedown, execShortcut);
		
		// attach events to window
		self.options.useBrowserHistory && jQuery(window)
			.on('popstate.' + namespace, function(ev) {
				var state = ev.originalEvent.state || {},
					hasThash = state.thash? true : false,
					dialog = node.find('.elfinder-frontmost:visible'),
					input = node.find('.elfinder-navbar-dir,.elfinder-cwd-filename').find('input,textarea'),
					onOpen, toast;
				if (!hasThash) {
					state = { thash: self.cwd().hash };
					// scroll to elFinder node
					jQuery('html,body').animate({ scrollTop: node.offset().top });
				}
				if (dialog.length || input.length) {
					history.pushState(state, null, location.pathname + location.search + '#elf_' + state.thash);
					if (dialog.length) {
						if (!dialog.hasClass(self.res('class', 'preventback'))) {
							if (dialog.hasClass('elfinder-contextmenu')) {
								jQuery(document).trigger(jQuery.Event('keydown', { keyCode: jQuery.ui.keyCode.ESCAPE, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
							} else if (dialog.hasClass('elfinder-dialog')) {
								dialog.elfinderdialog('close');
							} else {
								dialog.trigger('close');
							}
						}
					} else {
						input.trigger(jQuery.Event('keydown', { keyCode: jQuery.ui.keyCode.ESCAPE, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
					}
				} else {
					if (hasThash) {
						!jQuery.isEmptyObject(self.files()) && self.request({
							data   : {cmd  : 'open', target : state.thash, onhistory : 1},
							notify : {type : 'open', cnt : 1, hideCnt : true},
							syncOnFail : true
						});
					} else {
						onOpen = function() {
							toast.trigger('click');
						};
						self.one('open', onOpen, true);
						toast = self.toast({
							msg: self.i18n('pressAgainToExit'),
							onHidden: function() {
								self.unbind('open', onOpen);
								history.pushState(state, null, location.pathname + location.search + '#elf_' + state.thash);
							}
						});
					}
				}
			});
		
		jQuery(window).on('resize.' + namespace, function(e){
			if (e.target === this) {
				tm && cancelAnimationFrame(tm);
				tm = requestAnimationFrame(function() {
					var prv = node.data('resizeSize') || {w: 0, h: 0},
						size = {w: Math.round(node.width()), h: Math.round(node.height())};
					node.data('resizeSize', size);
					if (size.w !== prv.w || size.h !== prv.h) {
						node.trigger('resize');
						self.trigger('resize', {width : size.w, height : size.h});
					}
				});
			}
		})
		.on('beforeunload.' + namespace,function(e){
			var msg, cnt;
			if (node.is(':visible')) {
				if (self.ui.notify.children().length && jQuery.inArray('hasNotifyDialog', self.options.windowCloseConfirm) !== -1) {
					msg = self.i18n('ntfsmth');
				} else if (node.find('.'+self.res('class', 'editing')).length && jQuery.inArray('editingFile', self.options.windowCloseConfirm) !== -1) {
					msg = self.i18n('editingFile');
				} else if ((cnt = Object.keys(self.selected()).length) && jQuery.inArray('hasSelectedItem', self.options.windowCloseConfirm) !== -1) {
					msg = self.i18n('hasSelected', ''+cnt);
				} else if ((cnt = Object.keys(self.clipboard()).length) && jQuery.inArray('hasClipboardData', self.options.windowCloseConfirm) !== -1) {
					msg = self.i18n('hasClipboard', ''+cnt);
				}
				if (msg) {
					e.returnValue = msg;
					return msg;
				}
			}
			self.trigger('unload');
		});

		// bind window onmessage for CORS
		jQuery(window).on('message.' + namespace, function(e){
			var res = e.originalEvent || null,
				obj, data;
			if (res && self.uploadURL.indexOf(res.origin) === 0) {
				try {
					obj = JSON.parse(res.data);
					data = obj.data || null;
					if (data) {
						if (data.error) {
							if (obj.bind) {
								self.trigger(obj.bind+'fail', data);
							}
							self.error(data.error);
						} else {
							data.warning && self.error(data.warning);
							self.updateCache(data);
							data.removed && data.removed.length && self.remove(data);
							data.added   && data.added.length   && self.add(data);
							data.changed && data.changed.length && self.change(data);
							if (obj.bind) {
								self.trigger(obj.bind, data);
								self.trigger(obj.bind+'done');
							}
							data.sync && self.sync();
						}
					}
				} catch (e) {
					self.sync();
				}
			}
		});

		// elFinder enable always
		if (self.options.enableAlways) {
			jQuery(window).on('focus.' + namespace, function(e){
				(e.target === this) && self.enable();
			});
			if (inFrame) {
				jQuery(window.top).on('focus.' + namespace, function() {
					if (self.enable() && (! parentIframe || parentIframe.is(':visible'))) {
						requestAnimationFrame(function() {
							jQuery(window).trigger('focus');
						});
					}
				});
			}
		} else if (inFrame) {
			jQuery(window).on('blur.' + namespace, function(e){
				enabled && e.target === this && self.disable();
			});
		}

		// return focus to the window on click (elFInder in the frame)
		if (inFrame) {
			node.on('click', function(e) {
				jQuery(window).trigger('focus');
			});
		}
		
		// elFinder to enable by mouse over
		if (self.options.enableByMouseOver) {
			node.on('mouseenter touchstart', function(e) {
				(inFrame) && jQuery(window).trigger('focus');
				! self.enabled() && self.enable();
			});
		}
	});

	// store instance in node
	node[0].elfinder = this;

	// auto load language file
	dfrdsBeforeBootup.push((function() {
		var lang   = self.lang,
			langJs = self.i18nBaseUrl + 'elfinder.' + lang + '.js',
			dfd    = jQuery.Deferred().done(function() {
				if (self.i18[lang]) {
					self.lang = lang;
				}
				self.trigger('i18load');
				i18n = self.lang === 'en' 
					? self.i18['en'] 
					: jQuery.extend(true, {}, self.i18['en'], self.i18[self.lang]);
			});
		
		if (!self.i18[lang]) {
			self.lang = 'en';
			if (self.hasRequire) {
				require([langJs], function() {
					dfd.resolve();
				}, function() {
					dfd.resolve();
				});
			} else {
				self.loadScript([langJs], function() {
					dfd.resolve();
				}, {
					loadType: 'tag',
					error : function() {
						dfd.resolve();
					}
				});
			}
		} else {
			dfd.resolve();
		}
		return dfd;
	})());
	
	// elFinder boot up function
	bootUp = function() {
		var columnNames;

		/**
		 * i18 messages
		 *
		 * @type Object
		 **/
		self.messages = i18n.messages;
		
		// check jquery ui
		if (!(jQuery.fn.selectable && jQuery.fn.draggable && jQuery.fn.droppable && jQuery.fn.resizable && jQuery.fn.slider)) {
			return alert(self.i18n('errJqui'));
		}
		
		// check node
		if (!node.length) {
			return alert(self.i18n('errNode'));
		}
		// check connector url
		if (!self.options.url) {
			return alert(self.i18n('errURL'));
		}
		
		// column key/name map for fm.getColumnName()
		columnNames = Object.assign({
			name : self.i18n('name'),
			perm : self.i18n('perms'),
			date : self.i18n('modify'),
			size : self.i18n('size'),
			kind : self.i18n('kind'),
			modestr : self.i18n('mode'),
			modeoct : self.i18n('mode'),
			modeboth : self.i18n('mode')
		}, self.options.uiOptions.cwd.listView.columnsCustomName);

		/**
		 * Gets the column name of cwd list view
		 *
		 * @param      String  key     The key
		 * @return     String  The column name.
		 */
		self.getColumnName = function(key) {
			return columnNames[key] || self.i18n(key);
		};

		/**
		 * Interface direction
		 *
		 * @type String
		 * @default "ltr"
		 **/
		self.direction = i18n.direction;
		
		/**
		 * Date/time format
		 *
		 * @type String
		 * @default "m.d.Y"
		 **/
		self.dateFormat = self.options.dateFormat || i18n.dateFormat;
		
		/**
		 * Date format like "Yesterday 10:20:12"
		 *
		 * @type String
		 * @default "{day} {time}"
		 **/
		self.fancyFormat = self.options.fancyDateFormat || i18n.fancyDateFormat;
		
		/**
		 * Date format for if upload file has not original unique name
		 * e.g. Clipboard image data, Image data taken with iOS
		 *
		 * @type String
		 * @default "ymd-His"
		 **/
		self.nonameDateFormat = (self.options.nonameDateFormat || i18n.nonameDateFormat).replace(/[\/\\]/g, '_');

		/**
		 * Css classes 
		 *
		 * @type String
		 **/
		self.cssClass = 'ui-helper-reset ui-helper-clearfix ui-widget ui-widget-content ui-corner-all elfinder elfinder-'
				+(self.direction == 'rtl' ? 'rtl' : 'ltr')
				+(self.UA.Touch? (' elfinder-touch' + (self.options.resizable ? ' touch-punch' : '')) : '')
				+(self.UA.Mobile? ' elfinder-mobile' : '')
				+(self.UA.iOS? ' elfinder-ios' : '')
				+' '+self.options.cssClass;

		// prepare node
		node.addClass(self.cssClass)
			.on(mousedown, function() {
				!enabled && self.enable();
			});

		// draggable closure
		(function() {
			var ltr, wzRect, wzBottom, wzBottom2, nodeStyle,
				keyEvt = keydown + 'draggable' + ' keyup.' + namespace + 'draggable';
			
			/**
			 * Base draggable options
			 *
			 * @type Object
			 **/
			self.draggable = {
				appendTo   : node,
				addClasses : false,
				distance   : 4,
				revert     : true,
				refreshPositions : false,
				cursor     : 'crosshair',
				cursorAt   : {left : 50, top : 47},
				scroll     : false,
				start      : function(e, ui) {
					var helper   = ui.helper,
						targets  = jQuery.grep(helper.data('files')||[], function(h) {
							if (h) {
								remember[h] = true;
								return true;
							}
							return false;
						}),
						locked   = false,
						cnt, h;
					
					// fix node size
					nodeStyle = node.attr('style');
					node.width(node.width()).height(node.height());
					
					// set var for drag()
					ltr = (self.direction === 'ltr');
					wzRect = self.getUI('workzone').data('rectangle');
					wzBottom = wzRect.top + wzRect.height;
					wzBottom2 = wzBottom - self.getUI('navdock').outerHeight(true);
					
					self.draggingUiHelper = helper;
					cnt = targets.length;
					while (cnt--) {
						h = targets[cnt];
						if (files[h].locked) {
							locked = true;
							helper.data('locked', true);
							break;
						}
					}
					!locked && self.trigger('lockfiles', {files : targets});
		
					helper.data('autoScrTm', setInterval(function() {
						if (helper.data('autoScr')) {
							self.autoScroll[helper.data('autoScr')](helper.data('autoScrVal'));
						}
					}, 50));
				},
				drag       : function(e, ui) {
					var helper = ui.helper,
						autoScr, autoUp, bottom;
					
					if ((autoUp = wzRect.top > e.pageY) || wzBottom2 < e.pageY) {
						if (wzRect.cwdEdge > e.pageX) {
							autoScr = (ltr? 'navbar' : 'cwd') + (autoUp? 'Up' : 'Down');
						} else {
							autoScr = (ltr? 'cwd' : 'navbar') + (autoUp? 'Up' : 'Down');
						}
						if (!autoUp) {
							if (autoScr.substr(0, 3) === 'cwd') {
								if (wzBottom < e.pageY) {
									bottom = wzBottom;
								} else {
									autoScr = null;
								}
							} else {
								bottom = wzBottom2;
							}
						}
						if (autoScr) {
							helper.data('autoScr', autoScr);
							helper.data('autoScrVal', Math.pow((autoUp? wzRect.top - e.pageY : e.pageY - bottom), 1.3));
						}
					}
					if (! autoScr) {
						if (helper.data('autoScr')) {
							helper.data('refreshPositions', 1).data('autoScr', null);
						}
					}
					if (helper.data('refreshPositions') && jQuery(this).elfUiWidgetInstance('draggable')) {
						if (helper.data('refreshPositions') > 0) {
							jQuery(this).draggable('option', { refreshPositions : true, elfRefresh : true });
							helper.data('refreshPositions', -1);
						} else {
							jQuery(this).draggable('option', { refreshPositions : false, elfRefresh : false });
							helper.data('refreshPositions', null);
						}
					}
				},
				stop       : function(e, ui) {
					var helper = ui.helper,
						files;
					
					jQuery(document).off(keyEvt);
					jQuery(this).elfUiWidgetInstance('draggable') && jQuery(this).draggable('option', { refreshPositions : false });
					self.draggingUiHelper = null;
					self.trigger('focus').trigger('dragstop');
					if (! helper.data('droped')) {
						files = jQuery.grep(helper.data('files')||[], function(h) { return h? true : false ;});
						self.trigger('unlockfiles', {files : files});
						self.trigger('selectfiles', {files : self.selected()});
					}
					self.enable();
					
					// restore node style
					node.attr('style', nodeStyle);
					
					helper.data('autoScrTm') && clearInterval(helper.data('autoScrTm'));
				},
				helper     : function(e, ui) {
					var element = this.id ? jQuery(this) : jQuery(this).parents('[id]:first'),
						helper  = jQuery('<div class="elfinder-drag-helper"><span class="elfinder-drag-helper-icon-status"/></div>'),
						icon    = function(f) {
							var mime = f.mime, i, tmb = self.tmb(f);
							i = '<div class="elfinder-cwd-icon elfinder-cwd-icon-drag '+self.mime2class(mime)+' ui-corner-all"/>';
							if (tmb) {
								i = jQuery(i).addClass(tmb.className).css('background-image', "url('"+tmb.url+"')").get(0).outerHTML;
							} else if (f.icon) {
								i = jQuery(i).css(self.getIconStyle(f, true)).get(0).outerHTML;
							}
							if (f.csscls) {
								i = '<div class="'+f.csscls+'">' + i + '</div>';
							}
							return i;
						},
						hashes, l, ctr;
					
					self.draggingUiHelper && self.draggingUiHelper.stop(true, true);
					
					self.trigger('dragstart', {target : element[0], originalEvent : e}, true);
					
					hashes = element.hasClass(self.res('class', 'cwdfile')) 
						? self.selected() 
						: [self.navId2Hash(element.attr('id'))];
					
					helper.append(icon(files[hashes[0]])).data('files', hashes).data('locked', false).data('droped', false).data('namespace', namespace).data('dropover', 0);
		
					if ((l = hashes.length) > 1) {
						helper.append(icon(files[hashes[l-1]]) + '<span class="elfinder-drag-num">'+l+'</span>');
					}
					
					jQuery(document).on(keyEvt, function(e){
						var chk = (e.shiftKey||e.ctrlKey||e.metaKey);
						if (ctr !== chk) {
							ctr = chk;
							if (helper.is(':visible') && helper.data('dropover') && ! helper.data('droped')) {
								helper.toggleClass('elfinder-drag-helper-plus', helper.data('locked')? true : ctr);
								self.trigger(ctr? 'unlockfiles' : 'lockfiles', {files : hashes, helper: helper});
							}
						}
					});
					
					return helper;
				}
			};
		})();

		// in getFileCallback set - change default actions on double click/enter/ctrl+enter
		if (self.commands.getfile) {
			if (typeof(self.options.getFileCallback) == 'function') {
				self.bind('dblclick', function(e) {
					e.preventDefault();
					self.exec('getfile').fail(function() {
						self.exec('open', e.data && e.data.file? [ e.data.file ]: void(0));
					});
				});
				self.shortcut({
					pattern     : 'enter',
					description : self.i18n('cmdgetfile'),
					callback    : function() { self.exec('getfile').fail(function() { self.exec(self.OS == 'mac' ? 'rename' : 'open'); }); }
				})
				.shortcut({
					pattern     : 'ctrl+enter',
					description : self.i18n(self.OS == 'mac' ? 'cmdrename' : 'cmdopen'),
					callback    : function() { self.exec(self.OS == 'mac' ? 'rename' : 'open'); }
				});
			} else {
				self.options.getFileCallback = null;
			}
		}

		// load commands
		jQuery.each(self.commands, function(name, cmd) {
			var proto = Object.assign({}, cmd.prototype),
				extendsCmd, opts;
			if (jQuery.isFunction(cmd) && !self._commands[name] && (cmd.prototype.forceLoad || jQuery.inArray(name, self.options.commands) !== -1)) {
				extendsCmd = cmd.prototype.extendsCmd || '';
				if (extendsCmd) {
					if (jQuery.isFunction(self.commands[extendsCmd])) {
						cmd.prototype = Object.assign({}, base, new self.commands[extendsCmd](), cmd.prototype);
					} else {
						return true;
					}
				} else {
					cmd.prototype = Object.assign({}, base, cmd.prototype);
				}
				self._commands[name] = new cmd();
				cmd.prototype = proto;
				opts = self.options.commandsOptions[name] || {};
				if (extendsCmd && self.options.commandsOptions[extendsCmd]) {
					opts = jQuery.extend(true, {}, self.options.commandsOptions[extendsCmd], opts);
				}
				self._commands[name].setup(name, opts);
				// setup linked commands
				if (self._commands[name].linkedCmds.length) {
					jQuery.each(self._commands[name].linkedCmds, function(i, n) {
						var lcmd = self.commands[n];
						if (jQuery.isFunction(lcmd) && !self._commands[n]) {
							lcmd.prototype = base;
							self._commands[n] = new lcmd();
							self._commands[n].setup(n, self.options.commandsOptions[n]||{});
						}
					});
				}
			}
		});

		/**
		 * UI nodes
		 *
		 * @type Object
		 **/
		self.ui = {
			// container for nav panel and current folder container
			workzone : jQuery('<div/>').appendTo(node).elfinderworkzone(self),
			// container for folders tree / places
			navbar : jQuery('<div/>').appendTo(node).elfindernavbar(self, self.options.uiOptions.navbar || {}),
			// container for for preview etc at below the navbar
			navdock : jQuery('<div/>').appendTo(node).elfindernavdock(self, self.options.uiOptions.navdock || {}),
			// contextmenu
			contextmenu : jQuery('<div/>').appendTo(node).elfindercontextmenu(self),
			// overlay
			overlay : jQuery('<div/>').appendTo(node).elfinderoverlay({
				show : function() { self.disable(); },
				hide : function() { prevEnabled && self.enable(); }
			}),
			// current folder container
			cwd : jQuery('<div/>').appendTo(node).elfindercwd(self, self.options.uiOptions.cwd || {}),
			// notification dialog window
			notify : self.dialog('', {
				cssClass      : 'elfinder-dialog-notify',
				position      : self.options.notifyDialog.position,
				absolute      : true,
				resizable     : false,
				autoOpen      : false,
				closeOnEscape : false,
				title         : '&nbsp;',
				width         : self.options.notifyDialog.width? parseInt(self.options.notifyDialog.width) : null,
				minHeight     : null
			}),
			statusbar : jQuery('<div class="ui-widget-header ui-helper-clearfix ui-corner-bottom elfinder-statusbar"/>').hide().appendTo(node),
			toast : jQuery('<div class="elfinder-toast"/>').appendTo(node),
			bottomtray : jQuery('<div class="elfinder-bottomtray">').appendTo(node)
		};

		self.trigger('uiready');

		// load required ui
		jQuery.each(self.options.ui || [], function(i, ui) {
			var name = 'elfinder'+ui,
				opts = self.options.uiOptions[ui] || {};
	
			if (!self.ui[ui] && jQuery.fn[name]) {
				// regist to self.ui before make instance
				self.ui[ui] = jQuery('<'+(opts.tag || 'div')+'/>').appendTo(node);
				self.ui[ui][name](self, opts);
			}
		});
		
		// update size	
		self.resize(width, height);
		
		// make node resizable
		if (self.options.resizable) {
			node.resizable({
				resize    : function(e, ui) {
					self.resize(ui.size.width, ui.size.height);
				},
				handles   : 'se',
				minWidth  : 300,
				minHeight : 200
			});
			if (self.UA.Touch) {
				node.addClass('touch-punch');
			}
		}

		(function() {
			var navbar = self.getUI('navbar'),
				cwd    = self.getUI('cwd').parent();
			
			self.autoScroll = {
				navbarUp   : function(v) {
					navbar.scrollTop(Math.max(0, navbar.scrollTop() - v));
				},
				navbarDown : function(v) {
					navbar.scrollTop(navbar.scrollTop() + v);
				},
				cwdUp     : function(v) {
					cwd.scrollTop(Math.max(0, cwd.scrollTop() - v));
				},
				cwdDown   : function(v) {
					cwd.scrollTop(cwd.scrollTop() + v);
				}
			};
		})();

		// Swipe on the touch devices to show/hide of toolbar or navbar
		if (self.UA.Touch) {
			(function() {
				var lastX, lastY, nodeOffset, nodeWidth, nodeTop, navbarW, toolbarH,
					navbar = self.getUI('navbar'),
					toolbar = self.getUI('toolbar'),
					moveEv = 'touchmove.stopscroll',
					moveTm,
					moveUpOn = function(e) {
						var touches = e.originalEvent.touches || [{}],
							y = touches[0].pageY || null;
						if (!lastY || y < lastY) {
							e.preventDefault();
							moveTm && clearTimeout(moveTm);
						}
					},
					moveDownOn = function(e) {
						e.preventDefault();
						moveTm && clearTimeout(moveTm);
					},
					moveOff = function() {
						moveTm = setTimeout(function() {
							node.off(moveEv);
						}, 100);
					},
					handleW, handleH = 50;

				navbar = navbar.children().length? navbar : null;
				toolbar = toolbar.length? toolbar : null;
				node.on('touchstart touchmove touchend', function(e) {
					if (e.type === 'touchend') {
						lastX = false;
						lastY = false;
						moveOff();
						return;
					}
					
					var touches = e.originalEvent.touches || [{}],
						x = touches[0].pageX || null,
						y = touches[0].pageY || null,
						ltr = (self.direction === 'ltr'),
						navbarMode, treeWidth, swipeX, moveX, toolbarT, mode;
					
					if (x === null || y === null || (e.type === 'touchstart' && touches.length > 1)) {
						return;
					}
					
					if (e.type === 'touchstart') {
						nodeOffset = node.offset();
						nodeWidth = node.width();
						if (navbar) {
							lastX = false;
							if (navbar.is(':hidden')) {
								if (! handleW) {
									handleW = Math.max(50, nodeWidth / 10);
								}
								if ((ltr? (x - nodeOffset.left) : (nodeWidth + nodeOffset.left - x)) < handleW) {
									lastX = x;
								}
							} else if (! e.originalEvent._preventSwipeX) {
								navbarW = navbar.width();
								if (ltr) {
									swipeX = (x < nodeOffset.left + navbarW);
								} else {
									swipeX = (x > nodeOffset.left + nodeWidth - navbarW);
								}
								if (swipeX) {
									handleW = Math.max(50, nodeWidth / 10);
									lastX = x;
								} else {
									lastX = false;
								}
							}
						}
						if (toolbar) {
							lastY = false;
							if (! e.originalEvent._preventSwipeY) {
								toolbarH = toolbar.height();
								nodeTop = nodeOffset.top;
								if (y - nodeTop < (toolbar.is(':hidden')? handleH : (toolbarH + 30))) {
									lastY = y;
									node.on(moveEv, toolbar.is(':hidden')? moveDownOn: moveUpOn);
								}
							}
						}
					} else {
						if (navbar && lastX !== false) {
							navbarMode = (ltr? (lastX > x) : (lastX < x))? 'navhide' : 'navshow';
							moveX = Math.abs(lastX - x);
							if (navbarMode === 'navhide' && moveX > navbarW * 0.6
								|| (moveX > (navbarMode === 'navhide'? navbarW / 3 : 45)
									&& (navbarMode === 'navshow'
										|| (ltr? x < nodeOffset.left + 20 : x > nodeOffset.left + nodeWidth - 20)
									))
							) {
								self.getUI('navbar').trigger(navbarMode, {handleW: handleW});
								lastX = false;
							}
						}
						if (toolbar && lastY !== false ) {
							toolbarT = toolbar.offset().top;
							if (Math.abs(lastY - y) > Math.min(45, toolbarH / 3)) {
								mode = (lastY > y)? 'slideUp' : 'slideDown';
								if (mode === 'slideDown' || toolbarT + 20 > y) {
									if (toolbar.is(mode === 'slideDown' ? ':hidden' : ':visible')) {
										toolbar.stop(true, true).trigger('toggle', {duration: 100, handleH: handleH});
									}
									lastY = false;
								}
							}
						}
					}
				});
			})();
		}

		if (self.dragUpload) {
			// add event listener for HTML5 DnD upload
			(function() {
				var isin = function(e) {
					return (e.target.nodeName !== 'TEXTAREA' && e.target.nodeName !== 'INPUT' && jQuery(e.target).closest('div.ui-dialog-content').length === 0);
				},
				ent       = 'native-drag-enter',
				disable   = 'native-drag-disable',
				c         = 'class',
				navdir    = self.res(c, 'navdir'),
				droppable = self.res(c, 'droppable'),
				dropover  = self.res(c, 'adroppable'),
				arrow     = self.res(c, 'navarrow'),
				clDropActive = self.res(c, 'adroppable'),
				wz        = self.getUI('workzone'),
				ltr       = (self.direction === 'ltr'),
				clearTm   = function() {
					autoScrTm && cancelAnimationFrame(autoScrTm);
					autoScrTm = null;
				},
				wzRect, autoScrFn, autoScrTm;
				
				node.on('dragenter', function(e) {
					clearTm();
					if (isin(e)) {
						e.preventDefault();
						e.stopPropagation();
						wzRect = wz.data('rectangle');
					}
				})
				.on('dragleave', function(e) {
					clearTm();
					if (isin(e)) {
						e.preventDefault();
						e.stopPropagation();
					}
				})
				.on('dragover', function(e) {
					var autoUp;
					if (isin(e)) {
						e.preventDefault();
						e.stopPropagation();
						e.originalEvent.dataTransfer.dropEffect = 'none';
						if (! autoScrTm) {
							autoScrTm = requestAnimationFrame(function() {
								var wzBottom = wzRect.top + wzRect.height,
									wzBottom2 = wzBottom - self.getUI('navdock').outerHeight(true),
									fn;
								if ((autoUp = e.pageY < wzRect.top) || e.pageY > wzBottom2 ) {
									if (wzRect.cwdEdge > e.pageX) {
										fn = (ltr? 'navbar' : 'cwd') + (autoUp? 'Up' : 'Down');
									} else {
										fn = (ltr? 'cwd' : 'navbar') + (autoUp? 'Up' : 'Down');
									}
									if (!autoUp) {
										if (fn.substr(0, 3) === 'cwd') {
											if (wzBottom < e.pageY) {
												wzBottom2 = wzBottom;
											} else {
												fn = '';
											}
										}
									}
									fn && self.autoScroll[fn](Math.pow((autoUp? wzRect.top - e.pageY : e.pageY - wzBottom2), 1.3));
								}
								autoScrTm = null;
							});
						}
					} else {
						clearTm();
					}
				})
				.on('drop', function(e) {
					clearTm();
					if (isin(e)) {
						e.stopPropagation();
						e.preventDefault();
					}
				});
				
				node.on('dragenter', '.native-droppable', function(e){
					if (e.originalEvent.dataTransfer) {
						var $elm = jQuery(e.currentTarget),
							id   = e.currentTarget.id || null,
							cwd  = null,
							elfFrom;
						if (!id) { // target is cwd
							cwd = self.cwd();
							$elm.data(disable, false);
							try {
								jQuery.each(e.originalEvent.dataTransfer.types, function(i, v){
									if (v.substr(0, 13) === 'elfinderfrom:') {
										elfFrom = v.substr(13).toLowerCase();
									}
								});
							} catch(e) {}
						}
						if (!cwd || (cwd.write && (!elfFrom || elfFrom !== (window.location.href + cwd.hash).toLowerCase()))) {
							e.preventDefault();
							e.stopPropagation();
							$elm.data(ent, true);
							$elm.addClass(clDropActive);
						} else {
							$elm.data(disable, true);
						}
					}
				})
				.on('dragleave', '.native-droppable', function(e){
					if (e.originalEvent.dataTransfer) {
						var $elm = jQuery(e.currentTarget);
						e.preventDefault();
						e.stopPropagation();
						if ($elm.data(ent)) {
							$elm.data(ent, false);
						} else {
							$elm.removeClass(clDropActive);
						}
					}
				})
				.on('dragover', '.native-droppable', function(e){
					if (e.originalEvent.dataTransfer) {
						var $elm = jQuery(e.currentTarget);
						e.preventDefault();
						e.stopPropagation();
						e.originalEvent.dataTransfer.dropEffect = $elm.data(disable)? 'none' : 'copy';
						$elm.data(ent, false);
					}
				})
				.on('drop', '.native-droppable', function(e){
					if (e.originalEvent && e.originalEvent.dataTransfer) {
						var $elm = jQuery(e.currentTarget),
							id;
						e.preventDefault();
						e.stopPropagation();
						$elm.removeClass(clDropActive);
						if (e.currentTarget.id) {
							id = $elm.hasClass(navdir)? self.navId2Hash(e.currentTarget.id) : self.cwdId2Hash(e.currentTarget.id);
						} else {
							id = self.cwd().hash;
						}
						e.originalEvent._target = id;
						self.exec('upload', {dropEvt: e.originalEvent, target: id}, void 0, id);
					}
				});
			})();
		}

		// trigger event cssloaded if cddAutoLoad disabled
		if (self.cssloaded === null) {
			// check css loaded and remove hide
			(function() {
				var loaded = function() {
						if (node.data('cssautoloadHide')) {
							node.data('cssautoloadHide').remove();
							node.removeData('cssautoloadHide');
						}
						self.cssloaded = true;
						requestAnimationFrame(function() {
							self.trigger('cssloaded');
						});
					},
					cnt, fi;
				if (node.css('visibility') === 'hidden') {
					cnt = 1000; // timeout 10 secs
					fi  = setInterval(function() {
						if (--cnt < 0 || node.css('visibility') !== 'hidden') {
							clearInterval(fi);
							loaded();
						}
					}, 10);
				} else {
					loaded();
				}
			})();
		} else {
			self.cssloaded = true;
			self.trigger('cssloaded');
		}

		// calculate elFinder node z-index
		self.zIndexCalc();

		// send initial request and start to pray >_<
		self.trigger('init')
			.request({
				data        : {cmd : 'open', target : self.startDir(), init : 1, tree : 1}, 
				preventDone : true,
				notify      : {type : 'open', cnt : 1, hideCnt : true},
				freeze      : true
			})
			.fail(function() {
				self.trigger('fail').disable().lastDir('');
				listeners = {};
				shortcuts = {};
				jQuery(document).add(node).off('.'+namespace);
				self.trigger = function() { };
			})
			.done(function(data) {
				var trashDisable = function(th) {
						var src = self.file(self.trashes[th]),
							d = self.options.debug,
							error;
						
						if (src && src.volumeid) {
							delete self.volOptions[src.volumeid].trashHash;
						}
						self.trashes[th] = false;
						self.debug('backend-error', 'Trash hash "'+th+'" was not found or not writable.');
					},
					toChkTh = {};
				
				// regist rawStringDecoder
				if (self.options.rawStringDecoder) {
					self.registRawStringDecoder(self.options.rawStringDecoder);
				}

				// re-calculate elFinder node z-index
				self.zIndexCalc();
				
				self.load().debug('api', self.api);
				// update ui's size after init
				node.trigger('resize');
				// initial open
				open(data);
				self.trigger('open', data, false);
				self.trigger('opendone');
				
				if (inFrame && self.options.enableAlways) {
					jQuery(window).trigger('focus');
				}
				
				// check self.trashes
				jQuery.each(self.trashes, function(th) {
					var dir = self.file(th),
						src;
					if (! dir) {
						toChkTh[th] = true;
					} else if (dir.mime !== 'directory' || ! dir.write) {
						trashDisable(th);
					}
				});
				if (Object.keys(toChkTh).length) {
					self.request({
						data : {cmd : 'info', targets : Object.keys(toChkTh)},
						preventDefault : true
					}).done(function(data) {
						if (data && data.files) {
							jQuery.each(data.files, function(i, dir) {
								if (dir.mime === 'directory' && dir.write) {
									delete toChkTh[dir.hash];
								}
							});
						}
					}).always(function() {
						jQuery.each(toChkTh, trashDisable);
					});
				}
				// to enable / disable
				self[self.options.enableAlways? 'enable' : 'disable']();
			});
		
		// self.timeEnd('load');
		// End of bootUp()
	};
	
	// call bootCallback function with elFinder instance, extraObject - { dfrdsBeforeBootup: dfrdsBeforeBootup }
	if (bootCallback && typeof bootCallback === 'function') {
		self.bootCallback = bootCallback;
		bootCallback.call(node.get(0), self, { dfrdsBeforeBootup: dfrdsBeforeBootup });
	}
	
	// call dfrdsBeforeBootup functions then boot up elFinder
	jQuery.when.apply(null, dfrdsBeforeBootup).done(function() {
		bootUp();
	}).fail(function(error) {
		self.error(error);
	});
};

//register elFinder to global scope
if (typeof toGlobal === 'undefined' || toGlobal) {
	window.elFinder = elFinder;
}

/**
 * Prototype
 * 
 * @type  Object
 */
elFinder.prototype = {
	
	uniqueid : 0,
	
	res : function(type, id) {
		return this.resources[type] && this.resources[type][id];
	}, 

	/**
	 * User os. Required to bind native shortcuts for open/rename
	 *
	 * @type String
	 **/
	OS : navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : navigator.userAgent.indexOf('Win') !== -1  ? 'win' : 'other',
	
	/**
	 * User browser UA.
	 * jQuery.browser: version deprecated: 1.3, removed: 1.9
	 *
	 * @type Object
	 **/
	UA : (function(){
		var self = this,
			webkit = !document.unqueID && !window.opera && !window.sidebar && window.localStorage && 'WebkitAppearance' in document.documentElement.style,
			chrome = webkit && window.chrome,
			/*setRotated = function() {
				var a = ((screen && screen.orientation && screen.orientation.angle) || window.orientation || 0) + 0;
				if (a === -90) {
					a = 270;
				}
				UA.Angle = a;
				UA.Rotated = a % 180 === 0? false : true;
			},*/
			UA = {
				// Browser IE <= IE 6
				ltIE6   : typeof window.addEventListener == "undefined" && typeof document.documentElement.style.maxHeight == "undefined",
				// Browser IE <= IE 7
				ltIE7   : typeof window.addEventListener == "undefined" && typeof document.querySelectorAll == "undefined",
				// Browser IE <= IE 8
				ltIE8   : typeof window.addEventListener == "undefined" && typeof document.getElementsByClassName == "undefined",
				// Browser IE <= IE 9
				ltIE9  : document.uniqueID && document.documentMode <= 9,
				// Browser IE <= IE 10
				ltIE10  : document.uniqueID && document.documentMode <= 10,
				// Browser IE >= IE 11
				gtIE11  : document.uniqueID && document.documentMode >= 11,
				IE      : document.uniqueID,
				Firefox : window.sidebar,
				Opera   : window.opera,
				Webkit  : webkit,
				Chrome  : chrome,
				Edge    : (chrome && window.msCredentials)? true : false,
				Safari  : webkit && !window.chrome,
				Mobile  : typeof window.orientation != "undefined",
				Touch   : typeof window.ontouchstart != "undefined",
				iOS     : navigator.platform.match(/^iP(?:[ao]d|hone)/),
				Fullscreen : (typeof (document.exitFullscreen || document.webkitExitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen) !== 'undefined'),
				Angle   : 0,
				Rotated : false,
				CSS : (function() {
					var aStyle = document.createElement('a').style,
						pStyle = document.createElement('p').style,
						css;
					css = 'position:sticky;position:-webkit-sticky;';
					css += 'width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:max-content;';
					aStyle.cssText = css;
					return {
						positionSticky : aStyle.position.indexOf('sticky')!==-1,
						widthMaxContent : aStyle.width.indexOf('max-content')!==-1,
						flex : typeof pStyle.flex !== 'undefined'
					};
				})()
			};
			return UA;
	})(),
	
	/**
	 * Has RequireJS?
	 * 
	 * @type Boolean
	 */
	hasRequire : (typeof define === 'function' && define.amd),
	
	/**
	 * Current request command
	 * 
	 * @type  String
	 */
	currentReqCmd : '',
	
	/**
	 * Current keyboard state
	 * 
	 * @type  Object
	 */
	keyState : {},
	
	/**
	 * Internationalization object
	 * 
	 * @type  Object
	 */
	i18 : {
		en : {
			translator      : '',
			language        : 'English',
			direction       : 'ltr',
			dateFormat      : 'd.m.Y H:i',
			fancyDateFormat : '$1 H:i',
			nonameDateFormat : 'ymd-His',
			messages        : {}
		},
		months : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
		monthsShort : ['msJan', 'msFeb', 'msMar', 'msApr', 'msMay', 'msJun', 'msJul', 'msAug', 'msSep', 'msOct', 'msNov', 'msDec'],

		days : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
		daysShort : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
	},
	
	/**
	 * File mimetype to kind mapping
	 * 
	 * @type  Object
	 */
	kinds : 	{
			'unknown'                       : 'Unknown',
			'directory'                     : 'Folder',
			'group'                         : 'Selects',
			'symlink'                       : 'Alias',
			'symlink-broken'                : 'AliasBroken',
			'application/x-empty'           : 'TextPlain',
			'application/postscript'        : 'Postscript',
			'application/vnd.ms-office'     : 'MsOffice',
			'application/msword'            : 'MsWord',
			'application/vnd.ms-word'       : 'MsWord',
			'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : 'MsWord',
			'application/vnd.ms-word.document.macroEnabled.12'                        : 'MsWord',
			'application/vnd.openxmlformats-officedocument.wordprocessingml.template' : 'MsWord',
			'application/vnd.ms-word.template.macroEnabled.12'                        : 'MsWord',
			'application/vnd.ms-excel'      : 'MsExcel',
			'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'       : 'MsExcel',
			'application/vnd.ms-excel.sheet.macroEnabled.12'                          : 'MsExcel',
			'application/vnd.openxmlformats-officedocument.spreadsheetml.template'    : 'MsExcel',
			'application/vnd.ms-excel.template.macroEnabled.12'                       : 'MsExcel',
			'application/vnd.ms-excel.sheet.binary.macroEnabled.12'                   : 'MsExcel',
			'application/vnd.ms-excel.addin.macroEnabled.12'                          : 'MsExcel',
			'application/vnd.ms-powerpoint' : 'MsPP',
			'application/vnd.openxmlformats-officedocument.presentationml.presentation' : 'MsPP',
			'application/vnd.ms-powerpoint.presentation.macroEnabled.12'              : 'MsPP',
			'application/vnd.openxmlformats-officedocument.presentationml.slideshow'  : 'MsPP',
			'application/vnd.ms-powerpoint.slideshow.macroEnabled.12'                 : 'MsPP',
			'application/vnd.openxmlformats-officedocument.presentationml.template'   : 'MsPP',
			'application/vnd.ms-powerpoint.template.macroEnabled.12'                  : 'MsPP',
			'application/vnd.ms-powerpoint.addin.macroEnabled.12'                     : 'MsPP',
			'application/vnd.openxmlformats-officedocument.presentationml.slide'      : 'MsPP',
			'application/vnd.ms-powerpoint.slide.macroEnabled.12'                     : 'MsPP',
			'application/pdf'               : 'PDF',
			'application/xml'               : 'XML',
			'application/vnd.oasis.opendocument.text' : 'OO',
			'application/vnd.oasis.opendocument.text-template'         : 'OO',
			'application/vnd.oasis.opendocument.text-web'              : 'OO',
			'application/vnd.oasis.opendocument.text-master'           : 'OO',
			'application/vnd.oasis.opendocument.graphics'              : 'OO',
			'application/vnd.oasis.opendocument.graphics-template'     : 'OO',
			'application/vnd.oasis.opendocument.presentation'          : 'OO',
			'application/vnd.oasis.opendocument.presentation-template' : 'OO',
			'application/vnd.oasis.opendocument.spreadsheet'           : 'OO',
			'application/vnd.oasis.opendocument.spreadsheet-template'  : 'OO',
			'application/vnd.oasis.opendocument.chart'                 : 'OO',
			'application/vnd.oasis.opendocument.formula'               : 'OO',
			'application/vnd.oasis.opendocument.database'              : 'OO',
			'application/vnd.oasis.opendocument.image'                 : 'OO',
			'application/vnd.openofficeorg.extension'                  : 'OO',
			'application/x-shockwave-flash' : 'AppFlash',
			'application/flash-video'       : 'Flash video',
			'application/x-bittorrent'      : 'Torrent',
			'application/javascript'        : 'JS',
			'application/rtf'               : 'RTF',
			'application/rtfd'              : 'RTF',
			'application/x-font-ttf'        : 'TTF',
			'application/x-font-otf'        : 'OTF',
			'application/x-rpm'             : 'RPM',
			'application/x-web-config'      : 'TextPlain',
			'application/xhtml+xml'         : 'HTML',
			'application/docbook+xml'       : 'DOCBOOK',
			'application/x-awk'             : 'AWK',
			'application/x-gzip'            : 'GZIP',
			'application/x-bzip2'           : 'BZIP',
			'application/x-xz'              : 'XZ',
			'application/zip'               : 'ZIP',
			'application/x-zip'               : 'ZIP',
			'application/x-rar'             : 'RAR',
			'application/x-tar'             : 'TAR',
			'application/x-7z-compressed'   : '7z',
			'application/x-jar'             : 'JAR',
			'text/plain'                    : 'TextPlain',
			'text/x-php'                    : 'PHP',
			'text/html'                     : 'HTML',
			'text/javascript'               : 'JS',
			'text/css'                      : 'CSS',
			'text/rtf'                      : 'RTF',
			'text/rtfd'                     : 'RTF',
			'text/x-c'                      : 'C',
			'text/x-csrc'                   : 'C',
			'text/x-chdr'                   : 'CHeader',
			'text/x-c++'                    : 'CPP',
			'text/x-c++src'                 : 'CPP',
			'text/x-c++hdr'                 : 'CPPHeader',
			'text/x-shellscript'            : 'Shell',
			'application/x-csh'             : 'Shell',
			'text/x-python'                 : 'Python',
			'text/x-java'                   : 'Java',
			'text/x-java-source'            : 'Java',
			'text/x-ruby'                   : 'Ruby',
			'text/x-perl'                   : 'Perl',
			'text/x-sql'                    : 'SQL',
			'text/xml'                      : 'XML',
			'text/x-comma-separated-values' : 'CSV',
			'text/x-markdown'               : 'Markdown',
			'image/x-ms-bmp'                : 'BMP',
			'image/jpeg'                    : 'JPEG',
			'image/gif'                     : 'GIF',
			'image/png'                     : 'PNG',
			'image/tiff'                    : 'TIFF',
			'image/x-targa'                 : 'TGA',
			'image/vnd.adobe.photoshop'     : 'PSD',
			'image/xbm'                     : 'XBITMAP',
			'image/pxm'                     : 'PXM',
			'audio/mpeg'                    : 'AudioMPEG',
			'audio/midi'                    : 'AudioMIDI',
			'audio/ogg'                     : 'AudioOGG',
			'audio/mp4'                     : 'AudioMPEG4',
			'audio/x-m4a'                   : 'AudioMPEG4',
			'audio/wav'                     : 'AudioWAV',
			'audio/x-mp3-playlist'          : 'AudioPlaylist',
			'video/x-dv'                    : 'VideoDV',
			'video/mp4'                     : 'VideoMPEG4',
			'video/mpeg'                    : 'VideoMPEG',
			'video/x-msvideo'               : 'VideoAVI',
			'video/quicktime'               : 'VideoMOV',
			'video/x-ms-wmv'                : 'VideoWM',
			'video/x-flv'                   : 'VideoFlash',
			'video/x-matroska'              : 'VideoMKV',
			'video/ogg'                     : 'VideoOGG'
		},
	
	/**
	 * File mimetype to file extention mapping
	 * 
	 * @type  Object
	 * @see   elFinder.mimetypes.js
	 */
	mimeTypes : {},
	
	/**
	 * Ajax request data validation rules
	 * 
	 * @type  Object
	 */
	rules : {
		defaults : function(data) {
			if (!data
			|| (data.added && !Array.isArray(data.added))
			||  (data.removed && !Array.isArray(data.removed))
			||  (data.changed && !Array.isArray(data.changed))) {
				return false;
			}
			return true;
		},
		open    : function(data) { return data && data.cwd && data.files && jQuery.isPlainObject(data.cwd) && Array.isArray(data.files); },
		tree    : function(data) { return data && data.tree && Array.isArray(data.tree); },
		parents : function(data) { return data && data.tree && Array.isArray(data.tree); },
		tmb     : function(data) { return data && data.images && (jQuery.isPlainObject(data.images) || Array.isArray(data.images)); },
		upload  : function(data) { return data && (jQuery.isPlainObject(data.added) || Array.isArray(data.added));},
		search  : function(data) { return data && data.files && Array.isArray(data.files); }
	},
	
	/**
	 * Commands costructors
	 *
	 * @type Object
	 */
	commands : {},
	
	/**
	 * Commands to add the item (space delimited)
	 * 
	 * @type String
	 */
	cmdsToAdd : 'archive duplicate extract mkdir mkfile paste rm upload',
	
	parseUploadData : function(text) {
		var self = this,
			data;
		
		if (!jQuery.trim(text)) {
			return {error : ['errResponse', 'errDataEmpty']};
		}
		
		try {
			data = JSON.parse(text);
		} catch (e) {
			return {error : ['errResponse', 'errDataNotJSON']};
		}
		
		data = self.normalize(data);
		if (!self.validResponse('upload', data)) {
			return {error : (response.norError || ['errResponse'])};
		}
		data.removed = jQuery.merge((data.removed || []), jQuery.map(data.added || [], function(f) { return self.file(f.hash)? f.hash : null; }));
		return data;
		
	},
	
	iframeCnt : 0,
	
	uploads : {
		// xhr muiti uploading flag
		xhrUploading: false,
		
		// Timer of request fail to sync
		failSyncTm: null,
		
		// current chunkfail requesting chunk
		chunkfailReq: {},
		
		// check file/dir exists
		checkExists: function(files, target, fm, isDir) {
			var dfrd = jQuery.Deferred(),
				names, renames = [], hashes = {}, chkFiles = [],
				cancel = function() {
					var i = files.length;
					while (--i > -1) {
						files[i]._remove = true;
					}
				},
				resolve = function() {
					dfrd.resolve(renames, hashes);
				},
				check = function() {
					var existed = [], exists = [], i, c,
						pathStr = target !== fm.cwd().hash? fm.path(target, true) + fm.option('separator', target) : '',
						confirm = function(ndx) {
							alert(1);
							var last = ndx == exists.length-1,
								opts = {
									cssClass : 'elfinder-confirm-upload',
									title  : fm.i18n('cmdupload'),
									text   : ['errExists', pathStr + exists[ndx].name, 'confirmRepl'], 
									all    : !last,
									accept : {
										label    : 'btnYes',
										callback : function(all) {
											!last && !all
												? confirm(++ndx)
												: resolve();
										}
									},
									reject : {
										label    : 'btnNo',
										callback : function(all) {
											var i;
			
											if (all) {
												i = exists.length;
												while (ndx < i--) {
													files[exists[i].i]._remove = true;
												}
											} else {
												files[exists[ndx].i]._remove = true;
											}
			
											!last && !all
												? confirm(++ndx)
												: resolve();
										}
									},
									cancel : {
										label    : 'btnCancel',
										callback : function() {
											cancel();
											resolve();
										}
									},
									buttons : [
										{
											label : 'btnBackup',
											cssClass : 'elfinder-confirm-btn-backup',
											callback : function(all) {
												var i;
												if (all) {
													i = exists.length;
													while (ndx < i--) {
														renames.push(exists[i].name);
													}
												} else {
													renames.push(exists[ndx].name);
												}
												!last && !all
													? confirm(++ndx)
													: resolve();
											}
										}
									]
								};
							
							if (!isDir) {
								opts.buttons.push({
									label : 'btnRename' + (last? '' : 'All'),
									cssClass : 'elfinder-confirm-btn-rename',
									callback : function() {
										renames = null;
										resolve();
									}
								});
							}
							if (fm.iframeCnt > 0) {
								delete opts.reject;
							}
							fm.confirm(opts);
						};
					
					if (! fm.file(target).read) {
						// for dropbox type
						resolve();
						return;
					}
					
					names = jQuery.map(files, function(file, i) { return file.name && (!fm.UA.iOS || file.name !== 'image.jpg')? {i: i, name: file.name} : null ;});
					
					fm.request({
						data : {cmd : 'ls', target : target, intersect : jQuery.map(names, function(item) { return item.name;})},
						notify : {type : 'preupload', cnt : 1, hideCnt : true},
						preventDefault : true
					})
					.done(function(data) {
						var existedArr, cwdItems;
						if (data) {
							if (data.error) {
								cancel();
							} else {
								if (fm.options.overwriteUploadConfirm && fm.option('uploadOverwrite', target)) {
									if (data.list) {
										if (Array.isArray(data.list)) {
											existed = data.list || [];
										} else {
											existedArr = [];
											existed = jQuery.map(data.list, function(n) {
												if (typeof n === 'string') {
													return n;
												} else {
													// support to >=2.1.11 plugin Normalizer, Sanitizer
													existedArr = existedArr.concat(n);
													return false;
												}
											});
											if (existedArr.length) {
												existed = existed.concat(existedArr);
											}
											hashes = data.list;
										}
										exists = jQuery.grep(names, function(name){
											return jQuery.inArray(name.name, existed) !== -1 ? true : false ;
										});
										if (exists.length && existed.length && target == fm.cwd().hash) {
											cwdItems = jQuery.map(fm.files(target), function(file) { return file.name; } );
											if (jQuery.grep(existed, function(n) { 
												return jQuery.inArray(n, cwdItems) === -1? true : false;
											}).length){
												fm.sync();
											}
										}
									}
								}
							}
						}
						if (exists.length > 0) {
							confirm(0);
						} else {
							resolve();
						}
					})
					.fail(function(error) {
						cancel();
						resolve();
						error && fm.error(error);
					});
				};
			if (fm.api >= 2.1 && typeof files[0] == 'object') {
				check();
			} else {
				resolve();
			}
			return dfrd;
		},
		
		// check droped contents
		checkFile : function(data, fm, target) {
			if (!!data.checked || data.type == 'files') {
				return data.files;
			} else if (data.type == 'data') {
				var dfrd = jQuery.Deferred(),
				scanDfd = jQuery.Deferred(),
				files = [],
				paths = [],
				dirctorys = [],
				processing = 0,
				items,
				mkdirs = [],
				cancel = false,
				toArray = function(list) {
					return Array.prototype.slice.call(list || [], 0);
				},
				doScan = function(items) {
					var entry, readEntries,
						excludes = fm.options.folderUploadExclude[fm.OS] || null,
						length = items.length,
						check = function() {
							if (--processing < 1 && scanDfd.state() === 'pending') {
								scanDfd.resolve();
							}
						},
						pushItem = function(file) {
							if (! excludes || ! file.name.match(excludes)) {
								paths.push(entry.fullPath || '');
								files.push(file);
							}
							check();
						},
						readEntries = function(dirReader) {
							var entries = [],
								read = function() {
									dirReader.readEntries(function(results) {
										if (cancel || !results.length) {
											for (var i = 0; i < entries.length; i++) {
												if (cancel) {
													scanDfd.reject();
													break;
												}
												doScan([entries[i]]);
											}
											check();
										} else {
											entries = entries.concat(toArray(results));
											read();
										}
									}, check);
								};
							read();
						};
					
					processing++;
					for (var i = 0; i < length; i++) {
						if (cancel) {
							scanDfd.reject();
							break;
						}
						entry = items[i];
						if (entry) {
							if (entry.isFile) {
								processing++;
								entry.file(pushItem, check);
							} else if (entry.isDirectory) {
								if (fm.api >= 2.1) {
									processing++;
									mkdirs.push(entry.fullPath);
									readEntries(entry.createReader()); // Start reading dirs.
								}
							}
						}
					}
					check();
					return scanDfd;
				}, hasDirs;
				
				items = jQuery.map(data.files.items, function(item){
					return item.getAsEntry? item.getAsEntry() : item.webkitGetAsEntry();
				});
				jQuery.each(items, function(i, item) {
					if (item.isDirectory) {
						hasDirs = true;
						return false;
					}
				});
				if (items.length > 0) {
					fm.uploads.checkExists(items, target, fm, hasDirs).done(function(renames, hashes){
						var dfds = [];
						if (fm.options.overwriteUploadConfirm && fm.option('uploadOverwrite', target)) {
							if (renames === null) {
								data.overwrite = 0;
								renames = [];
							}
							items = jQuery.grep(items, function(item){
								var i, bak, hash, dfd, hi;
								if (item.isDirectory && renames.length) {
									i = jQuery.inArray(item.name, renames);
									if (i !== -1) {
										renames.splice(i, 1);
										bak = fm.uniqueName(item.name + fm.options.backupSuffix , null, '');
										jQuery.each(hashes, function(h, name) {
											if (item.name == name) {
												hash = h;
												return false;
											}
										});
										if (! hash) {
											hash = fm.fileByName(item.name, target).hash;
										}
										fm.lockfiles({files : [hash]});
										dfd = fm.request({
											data   : {cmd : 'rename', target : hash, name : bak},
											notify : {type : 'rename', cnt : 1}
										})
										.fail(function() {
											item._remove = true;
											fm.sync();
										})
										.always(function() {
											fm.unlockfiles({files : [hash]});
										});
										dfds.push(dfd);
									}
								}
								return !item._remove? true : false;
							});
						}
						jQuery.when.apply($, dfds).done(function(){
							var notifyto, msg,
								id = +new Date();
							
							if (items.length > 0) {
								msg = fm.escape(items[0].name);
								if (items.length > 1) {
									msg += ' ... ' + items.length + fm.i18n('items');
								}
								notifyto = setTimeout(function() {
									fm.notify({
										type : 'readdir',
										id : id,
										cnt : 1,
										hideCnt: true,
										msg : fm.i18n('ntfreaddir') + ' (' + msg + ')',
										cancel: function() {
											cancel = true;
										}
									});
								}, fm.options.notifyDelay);
								doScan(items).done(function() {
									notifyto && clearTimeout(notifyto);
									fm.notify({type : 'readdir', id: id, cnt : -1});
									if (cancel) {
										dfrd.reject();
									} else {
										dfrd.resolve([files, paths, renames, hashes, mkdirs]);
									}
								}).fail(function() {
									dfrd.reject();
								});
							} else {
								dfrd.reject();
							}
						});
					});
					return dfrd.promise();
				} else {
					return dfrd.reject();
				}
			} else {
				var ret = [];
				var check = [];
				var str = data.files[0];
				if (data.type == 'html') {
					var tmp = jQuery("<html/>").append(jQuery.parseHTML(str.replace(/ src=/ig, ' _elfsrc='))),
						atag;
					jQuery('img[_elfsrc]', tmp).each(function(){
						var url, purl,
						self = jQuery(this),
						pa = self.closest('a');
						if (pa && pa.attr('href') && pa.attr('href').match(/\.(?:jpe?g|gif|bmp|png)/i)) {
							purl = pa.attr('href');
						}
						url = self.attr('_elfsrc');
						if (url) {
							if (purl) {
								jQuery.inArray(purl, ret) == -1 && ret.push(purl);
								jQuery.inArray(url, check) == -1 &&  check.push(url);
							} else {
								jQuery.inArray(url, ret) == -1 && ret.push(url);
							}
						}
						// Probably it's clipboard data
						if (ret.length === 1 && ret[0].match(/^data:image\/png/)) {
							data.clipdata = true;
						}
					});
					atag = jQuery('a[href]', tmp);
					atag.each(function(){
						var text, loc,
							parseUrl = function(url) {
								var a = document.createElement('a');
								a.href = url;
								return a;
							};
						if (text = jQuery(this).text()) {
							loc = parseUrl(jQuery(this).attr('href'));
							if (loc.href && loc.href.match(/^(?:ht|f)tp/i) && (atag.length === 1 || ! loc.pathname.match(/(?:\.html?|\/[^\/.]*)$/i) || jQuery.trim(text).match(/\.[a-z0-9-]{1,10}$/i))) {
								if (jQuery.inArray(loc.href, ret) == -1 && jQuery.inArray(loc.href, check) == -1) ret.push(loc.href);
							}
						}
					});
				} else {
					var regex, m, url;
					regex = /((?:ht|f)tps?:\/\/[-_.!~*\'()a-z0-9;/?:\@&=+\$,%#\*\[\]]+)/ig;
					while (m = regex.exec(str)) {
						url = m[1].replace(/&amp;/g, '&');
						if (jQuery.inArray(url, ret) == -1) ret.push(url);
					}
				}
				return ret;
			}
		},

		// upload transport using XMLHttpRequest
		xhr : function(data, fm) { 
			var self   = fm ? fm : this,
				node        = self.getUI(),
				xhr         = new XMLHttpRequest(),
				notifyto    = null, notifyto2 = null,
				dataChecked = data.checked,
				isDataType  = (data.isDataType || data.type == 'data'),
				target      = (data.target || self.cwd().hash),
				dropEvt     = (data.dropEvt || null),
				extraData  = data.extraData || null,
				chunkEnable = (self.option('uploadMaxConn', target) != -1),
				multiMax    = Math.min(5, Math.max(1, self.option('uploadMaxConn', target))),
				retryWait   = 10000, // 10 sec
				retryMax    = 30, // 10 sec * 30 = 300 secs (Max 5 mins)
				retry       = 0,
				getFile     = function(files) {
					var dfd = jQuery.Deferred(),
						file;
					if (files.promise) {
						files.always(function(f) {
							dfd.resolve(Array.isArray(f) && f.length? (isDataType? f[0][0] : f[0]) : {});
						});
					} else {
						dfd.resolve(files.length? (isDataType? files[0][0] : files[0]) : {});
					}
					return dfd;
				},
				dfrd   = jQuery.Deferred()
					.fail(function(err) {
						var error = self.parseError(err),
							userAbort;
						if (error === 'userabort') {
							userAbort = true;
							error = void 0;
						}
						if (files && (self.uploads.xhrUploading || userAbort)) {
							// send request om fail
							getFile(files).done(function(file) {
								if (!userAbort) {
									triggerError(error, file);
								}
								if (! file._cid) {
									// send sync request
									self.uploads.failSyncTm && clearTimeout(self.uploads.failSyncTm);
									self.uploads.failSyncTm = setTimeout(function() {
										self.sync(target);
									}, 1000);
								} else if (! self.uploads.chunkfailReq[file._cid]) {
									// send chunkfail request
									self.uploads.chunkfailReq[file._cid] = true;
									setTimeout(function() {
										fm.request({
											data : {
												cmd: 'upload',
												target: target,
												chunk: file._chunk,
												cid: file._cid,
												upload: ['chunkfail'],
												mimes: 'chunkfail'
											},
											options : {
												type: 'post',
												url: self.uploadURL
											},
											preventDefault: true
										}).always(function() {
											delete self.uploads.chunkfailReq[file._chunk];
										});
									}, 1000);
								}
							});
						} else {
							triggerError(error);
						}
						!userAbort && self.sync();
						self.uploads.xhrUploading = false;
						files = null;
					})
					.done(function(data) {
						self.uploads.xhrUploading = false;
						files = null;
						if (data) {
							self.currentReqCmd = 'upload';
							data.warning && triggerError(data.warning);
							self.updateCache(data);
							data.removed && data.removed.length && self.remove(data);
							data.added   && data.added.length   && self.add(data);
							data.changed && data.changed.length && self.change(data);
							self.trigger('upload', data, false);
							self.trigger('uploaddone');
							if (data.toasts && Array.isArray(data.toasts)) {
								jQuery.each(data.toasts, function() {
									this.msg && self.toast(this);
								});
							}
							data.sync && self.sync();
							data.debug && fm.debug('backend-debug', data);
						}
					})
					.always(function() {
						self.abortXHR(xhr);
						// unregist fnAbort function
						node.off('uploadabort', fnAbort);
						jQuery(window).off('unload', fnAbort);
						notifyto && clearTimeout(notifyto);
						notifyto2 && clearTimeout(notifyto2);
						dataChecked && !data.multiupload && checkNotify() && self.notify({type : 'upload', cnt : -cnt, progress : 0, size : 0});
						chunkMerge && notifyElm.children('.elfinder-notify-chunkmerge').length && self.notify({type : 'chunkmerge', cnt : -1});
					}),
				formData    = new FormData(),
				files       = data.input ? data.input.files : self.uploads.checkFile(data, self, target), 
				cnt         = data.checked? (isDataType? files[0].length : files.length) : files.length,
				loaded      = 0,
				prev        = 0,
				filesize    = 0,
				notify      = false,
				notifyElm   = self.ui.notify,
				cancelBtn   = true,
				abort       = false,
				checkNotify = function() {
					if (!notify && (ntfUpload = notifyElm.children('.elfinder-notify-upload')).length) {
						notify = true;
					}
					return notify;
				},
				fnAbort     = function(e, error) {
					abort = true;
					self.abortXHR(xhr, { quiet: true, abort: true });
					dfrd.reject(error);
					if (checkNotify()) {
						self.notify({type : 'upload', cnt : ntfUpload.data('cnt') * -1, progress : 0, size : 0});
					}
				},
				cancelToggle = function(show) {
					ntfUpload.children('.elfinder-notify-cancel')[show? 'show':'hide']();
				},
				startNotify = function(size) {
					if (!size) size = filesize;
					return setTimeout(function() {
						notify = true;
						self.notify({type : 'upload', cnt : cnt, progress : loaded - prev, size : size,
							cancel: function() {
								node.trigger('uploadabort', 'userabort');
							}
						});
						ntfUpload = notifyElm.children('.elfinder-notify-upload');
						prev = loaded;
						if (data.multiupload) {
							cancelBtn && cancelToggle(true);
						} else {
							cancelToggle(cancelBtn && loaded < size);
						}
					}, self.options.notifyDelay);
				},
				doRetry = function() {
					if (retry++ <= retryMax) {
						if (checkNotify() && prev) {
							self.notify({type : 'upload', cnt : 0, progress : 0, size : prev});
						}
						self.abortXHR(xhr, { quiet: true });
						prev = loaded = 0;
						setTimeout(function() {
							var reqId;
							if (! abort) {
								xhr.open('POST', self.uploadURL, true);
								if (self.api >= 2.1029) {
									reqId = (+ new Date()).toString(16) + Math.floor(1000 * Math.random()).toString(16);
									(typeof formData['delete'] === 'function') && formData['delete']('reqid');
									formData.append('reqid', reqId);
									xhr._requestId = reqId;
								}
								xhr.send(formData);
							}
						}, retryWait);
					} else {
						node.trigger('uploadabort', ['errAbort', 'errTimeout']);
					}
				},
				progress = function() {
					var node;
					if (notify) {
						dfrd.notifyWith(ntfUpload, [{
							cnt: ntfUpload.data('cnt'),
							progress: ntfUpload.data('progress'),
							total: ntfUpload.data('total')
						}]);
					}
				},
				triggerError = function(err, file, unite) {
					err && self.trigger('xhruploadfail', { error: err, file: file });
					if (unite) {
						if (err) {
							if (errCnt < self.options.maxErrorDialogs) {
								if (Array.isArray(err)) {
									errors = errors.concat(err);
								} else {
									errors.push(err);
								}
							}
							errCnt++;
						}
					} else {
						if (err) {
							self.error(err);
						} else {
							if (errors.length) {
								if (errCnt >= self.options.maxErrorDialogs) {
									errors = errors.concat('moreErrors', errCnt - self.options.maxErrorDialogs);
								}
								self.error(errors);
							}
							errors = [];
							errCnt = 0;
						}
					}
				},
				errors = [],
				errCnt = 0,
				renames = (data.renames || null),
				hashes = (data.hashes || null),
				chunkMerge = false,
				ntfUpload = jQuery();
			
			// regist fnAbort function
			node.one('uploadabort', fnAbort);
			jQuery(window).one('unload.' + fm.namespace, fnAbort);
			
			!chunkMerge && (prev = loaded);
			
			if (!isDataType && !cnt) {
				return dfrd.reject(['errUploadNoFiles']);
			}
			
			xhr.addEventListener('error', function() {
				if (xhr.status == 0) {
					if (abort) {
						dfrd.reject();
					} else {
						// ff bug while send zero sized file
						// for safari - send directory
						if (!isDataType && data.files && jQuery.grep(data.files, function(f){return ! f.type && f.size === (self.UA.Safari? 1802 : 0)? true : false;}).length) {
							dfrd.reject(['errAbort', 'errFolderUpload']);
						} else if (data.input && jQuery.grep(data.input.files, function(f){return ! f.type && f.size === (self.UA.Safari? 1802 : 0)? true : false;}).length) {
							dfrd.reject(['errUploadNoFiles']);
						} else {
							doRetry();
						}
					}
				} else {
					node.trigger('uploadabort', 'errConnect');
				}
			}, false);
			
			xhr.addEventListener('load', function(e) {
				var status = xhr.status, res, curr = 0, error = '', errData, errObj;
				
				if (status >= 400) {
					if (status > 500) {
						error = 'errResponse';
					} else {
						error = ['errResponse', 'errServerError'];
					}
				} else {
					if (!xhr.responseText) {
						error = ['errResponse', 'errDataEmpty'];
					}
				}
				
				if (error) {
					node.trigger('uploadabort');
					getFile(files).done(function(file) {
						return dfrd.reject(file._cid? null : error);
					});
				}
				
				loaded = filesize;
				
				if (checkNotify() && (curr = loaded - prev)) {
					self.notify({type : 'upload', cnt : 0, progress : curr, size : 0});
					progress();
				}

				res = self.parseUploadData(xhr.responseText);
				
				// chunked upload commit
				if (res._chunkmerged) {
					formData = new FormData();
					var _file = [{_chunkmerged: res._chunkmerged, _name: res._name, _mtime: res._mtime}];
					chunkMerge = true;
					node.off('uploadabort', fnAbort);
					notifyto2 = setTimeout(function() {
						self.notify({type : 'chunkmerge', cnt : 1});
					}, self.options.notifyDelay);
					isDataType? send(_file, files[1]) : send(_file);
					return;
				}
				
				res._multiupload = data.multiupload? true : false;
				if (res.error) {
					errData = {
						cmd: 'upload',
						err: res,
						xhr: xhr,
						rc: xhr.status
					};
					self.trigger('uploadfail', res);
					// trigger "requestError" event
					self.trigger('requestError', errData);
					if (errData._event && errData._event.isDefaultPrevented()) {
						res.error = '';
					}
					if (res._chunkfailure || res._multiupload) {
						abort = true;
						self.uploads.xhrUploading = false;
						notifyto && clearTimeout(notifyto);
						if (ntfUpload.length) {
							self.notify({type : 'upload', cnt : -cnt, progress : 0, size : 0});
							dfrd.reject(res);
						} else {
							// for multi connection
							dfrd.reject();
						}
					} else {
						dfrd.reject(res);
					}
				} else {
					dfrd.resolve(res);
				}
			}, false);
			
			xhr.upload.addEventListener('loadstart', function(e) {
				if (!chunkMerge && e.lengthComputable) {
					loaded = e.loaded;
					retry && (loaded = 0);
					filesize = e.total;
					if (!loaded) {
						loaded = parseInt(filesize * 0.05);
					}
					if (checkNotify()) {
						self.notify({type : 'upload', cnt : 0, progress : loaded - prev, size : data.multiupload? 0 : filesize});
						prev = loaded;
						progress();
					}
				}
			}, false);
			
			xhr.upload.addEventListener('progress', function(e) {
				var curr;

				if (e.lengthComputable && !chunkMerge && xhr.readyState < 2) {
					
					loaded = e.loaded;

					// to avoid strange bug in safari (not in chrome) with drag&drop.
					// bug: macos finder opened in any folder,
					// reset safari cache (option+command+e), reload elfinder page,
					// drop file from finder
					// on first attempt request starts (progress callback called ones) but never ends.
					// any next drop - successfull.
					if (!data.checked && loaded > 0 && !notifyto) {
						notifyto = startNotify(xhr._totalSize - loaded);
					}
					
					if (!filesize) {
						filesize = e.total;
						if (!loaded) {
							loaded = parseInt(filesize * 0.05);
						}
					}
					
					curr = loaded - prev;
					if (checkNotify() && (curr/e.total) >= 0.05) {
						self.notify({type : 'upload', cnt : 0, progress : curr, size : 0});
						prev = loaded;
						progress();
					}
					
					if (! data.multiupload && loaded >= filesize) {
						cancelBtn = false;
						cancelToggle(false);
					}
				}
			}, false);
			
			var send = function(files, paths){
				var size = 0,
				fcnt = 1,
				sfiles = [],
				c = 0,
				total = cnt,
				maxFileSize,
				totalSize = 0,
				chunked = [],
				chunkID = new Date().getTime().toString().substr(-9), // for take care of the 32bit backend system
				BYTES_PER_CHUNK = Math.min((fm.uplMaxSize? fm.uplMaxSize : 2097152) - 8190, fm.options.uploadMaxChunkSize), // uplMaxSize margin 8kb or options.uploadMaxChunkSize
				blobSlice = chunkEnable? false : '',
				blobSize, blobMtime, i, start, end, chunks, blob, chunk, added, done, last, failChunk,
				multi = function(files, num){
					var sfiles = [], cid, sfilesLen = 0, cancelChk;
					if (!abort) {
						while(files.length && sfiles.length < num) {
							sfiles.push(files.shift());
						}
						sfilesLen = sfiles.length;
						if (sfilesLen) {
							cancelChk = sfilesLen;
							for (var i=0; i < sfilesLen; i++) {
								if (abort) {
									break;
								}
								cid = isDataType? (sfiles[i][0][0]._cid || null) : (sfiles[i][0]._cid || null);
								if (!!failChunk[cid]) {
									last--;
									continue;
								}
								fm.exec('upload', {
									type: data.type,
									isDataType: isDataType,
									files: sfiles[i],
									checked: true,
									target: target,
									dropEvt: dropEvt,
									renames: renames,
									hashes: hashes,
									multiupload: true,
									overwrite: data.overwrite === 0? 0 : void 0
								}, void 0, target)
								.fail(function(error) {
									if (error && error === 'No such command') {
										abort = true;
										fm.error(['errUpload', 'errPerm']);
									}
									if (cid) {	
										failChunk[cid] = true;
									}
								})
								.always(function(e) {
									if (e && e.added) added = jQuery.merge(added, e.added);
									if (last <= ++done) {
										fm.trigger('multiupload', {added: added});
										notifyto && clearTimeout(notifyto);
										if (checkNotify()) {
											self.notify({type : 'upload', cnt : -cnt, progress : 0, size : 0});
										}
									}
									if (files.length) {
										multi(files, 1); // Next one
									} else {
										if (--cancelChk <= 1) {
											cancelBtn = false;
											cancelToggle(false);
										}
									}
								});
							}
						}
					}
					if (sfiles.length < 1 || abort) {
						if (abort) {
							notifyto && clearTimeout(notifyto);
							if (cid) {
								failChunk[cid] = true;
							}
							dfrd.reject();
						} else {
							dfrd.resolve();
							self.uploads.xhrUploading = false;
						}
					}
				},
				check = function(){
					if (!self.uploads.xhrUploading) {
						self.uploads.xhrUploading = true;
						multi(sfiles, multiMax); // Max connection: 3
					} else {
						setTimeout(check, 100);
					}
				},
				reqId, err;

				if (! dataChecked && (isDataType || data.type == 'files')) {
					if (! (maxFileSize = fm.option('uploadMaxSize', target))) {
						maxFileSize = 0;
					}
					for (i=0; i < files.length; i++) {
						try {
							blob = files[i];
							blobSize = blob.size;
							if (blobSlice === false) {
								blobSlice = '';
								if (self.api >= 2.1) {
									if ('slice' in blob) {
										blobSlice = 'slice';
									} else if ('mozSlice' in blob) {
										blobSlice = 'mozSlice';
									} else if ('webkitSlice' in blob) {
										blobSlice = 'webkitSlice';
									}
								}
							}
						} catch(e) {
							cnt--;
							total--;
							continue;
						}
						
						// file size check
						if ((maxFileSize && blobSize > maxFileSize) || (!blobSlice && fm.uplMaxSize && blobSize > fm.uplMaxSize)) {
							triggerError(['errUploadFile', blob.name, 'errUploadFileSize'], blob, true);
							cnt--;
							total--;
							continue;
						}
						
						// file mime check
						if (blob.type && ! self.uploadMimeCheck(blob.type, target)) {
							triggerError(['errUploadFile', blob.name, 'errUploadMime', '(' + blob.type + ')'], blob, true);
							cnt--;
							total--;
							continue;
						}
						
						if (blobSlice && blobSize > BYTES_PER_CHUNK) {
							start = 0;
							end = BYTES_PER_CHUNK;
							chunks = -1;
							total = Math.floor((blobSize - 1) / BYTES_PER_CHUNK);
							blobMtime = blob.lastModified? Math.round(blob.lastModified/1000) : 0;

							totalSize += blobSize;
							chunked[chunkID] = 0;
							while(start < blobSize) {
								chunk = blob[blobSlice](start, end);
								chunk._chunk = blob.name + '.' + (++chunks) + '_' + total + '.part';
								chunk._cid   = chunkID;
								chunk._range = start + ',' + chunk.size + ',' + blobSize;
								chunk._mtime = blobMtime;
								chunked[chunkID]++;
								
								if (size) {
									c++;
								}
								if (typeof sfiles[c] == 'undefined') {
									sfiles[c] = [];
									if (isDataType) {
										sfiles[c][0] = [];
										sfiles[c][1] = [];
									}
								}
								size = BYTES_PER_CHUNK;
								fcnt = 1;
								if (isDataType) {
									sfiles[c][0].push(chunk);
									sfiles[c][1].push(paths[i]);
								} else {
									sfiles[c].push(chunk);
								}

								start = end;
								end = start + BYTES_PER_CHUNK;
							}
							if (chunk == null) {
								triggerError(['errUploadFile', blob.name, 'errUploadFileSize'], blob, true);
								cnt--;
								total--;
							} else {
								total += chunks;
								size = 0;
								fcnt = 1;
								c++;
							}
							continue;
						}
						if ((fm.uplMaxSize && size + blobSize > fm.uplMaxSize) || fcnt > fm.uplMaxFile) {
							size = 0;
							fcnt = 1;
							c++;
						}
						if (typeof sfiles[c] == 'undefined') {
							sfiles[c] = [];
							if (isDataType) {
								sfiles[c][0] = [];
								sfiles[c][1] = [];
							}
						}
						if (isDataType) {
							sfiles[c][0].push(blob);
							sfiles[c][1].push(paths[i]);
						} else {
							sfiles[c].push(blob);
						}
						size += blobSize;
						totalSize += blobSize;
						fcnt++;
					}
					
					if (errors.length) {
						triggerError();
					}

					if (sfiles.length == 0) {
						// no data
						data.checked = true;
						return false;
					}
					
					if (sfiles.length > 1) {
						// multi upload
						notifyto = startNotify(totalSize);
						added = [];
						done = 0;
						last = sfiles.length;
						failChunk = [];
						check();
						return true;
					}
					
					// single upload
					if (isDataType) {
						files = sfiles[0][0];
						paths = sfiles[0][1];
					} else {
						files = sfiles[0];
					}
				}
				
				if (!dataChecked) {
					if (!fm.UA.Safari || !data.files) {
						notifyto = startNotify(totalSize);
					} else {
						xhr._totalSize = totalSize;
					}
				}
				
				dataChecked = true;
				
				if (! files.length) {
					dfrd.reject(['errUploadNoFiles']);
				}
				
				xhr.open('POST', self.uploadURL, true);
				
				// set request headers
				if (fm.customHeaders) {
					jQuery.each(fm.customHeaders, function(key) {
						xhr.setRequestHeader(key, this);
					});
				}
				
				// set xhrFields
				if (fm.xhrFields) {
					jQuery.each(fm.xhrFields, function(key) {
						if (key in xhr) {
							xhr[key] = this;
						}
					});
				}

				if (self.api >= 2.1029) {
					// request ID
					reqId = (+ new Date()).toString(16) + Math.floor(1000 * Math.random()).toString(16);
					formData.append('reqid', reqId);
					xhr._requestId = reqId;
				}
				formData.append('cmd', 'upload');
				formData.append(self.newAPI ? 'target' : 'current', target);
				if (renames && renames.length) {
					jQuery.each(renames, function(i, v) {
						formData.append('renames[]', v);
					});
					formData.append('suffix', fm.options.backupSuffix);
				}
				if (hashes) {
					jQuery.each(hashes, function(i, v) {
						formData.append('hashes['+ i +']', v);
					});
				}
				jQuery.each(self.customData, function(key, val) {
					formData.append(key, val);
				});
				jQuery.each(self.options.onlyMimes, function(i, mime) {
					formData.append('mimes[]', mime);
				});
				
				jQuery.each(files, function(i, file) {
					if (file._chunkmerged) {
						formData.append('chunk', file._chunkmerged);
						formData.append('upload[]', file._name);
						formData.append('mtime[]', file._mtime);
					} else {
						if (file._chunkfail) {
							formData.append('upload[]', 'chunkfail');
							formData.append('mimes', 'chunkfail');
						} else {
							formData.append('upload[]', file);
							if (data.clipdata) {
								data.overwrite = 0;
								formData.append('name[]', fm.date(fm.nonameDateFormat) + '.png');
							}
							if (file.name && fm.UA.iOS) {
								if (file.name.match(/^image\.jpe?g$/i)) {
									data.overwrite = 0;
									formData.append('name[]', fm.date(fm.nonameDateFormat) + '.jpg');
								} else if (file.name.match(/^capturedvideo\.mov$/i)) {
									data.overwrite = 0;
									formData.append('name[]', fm.date(fm.nonameDateFormat) + '.mov');
								}
							}
						}
						if (file._chunk) {
							formData.append('chunk', file._chunk);
							formData.append('cid'  , file._cid);
							formData.append('range', file._range);
							formData.append('mtime[]', file._mtime);
						} else {
							formData.append('mtime[]', file.lastModified? Math.round(file.lastModified/1000) : 0);
						}
					}
				});
				
				if (isDataType) {
					jQuery.each(paths, function(i, path) {
						formData.append('upload_path[]', path);
					});
				}
				
				if (data.overwrite === 0) {
					formData.append('overwrite', 0);
				}
				
				// send int value that which meta key was pressed when dropped  as `dropWith`
				if (dropEvt) {
					formData.append('dropWith', parseInt(
						(dropEvt.altKey  ? '1' : '0')+
						(dropEvt.ctrlKey ? '1' : '0')+
						(dropEvt.metaKey ? '1' : '0')+
						(dropEvt.shiftKey? '1' : '0'), 2));
				}
				
				// set extraData on current request
				if (extraData) {
					jQuery.each(extraData, function(key, val) {
						formData.append(key, val);
					});
				}

				xhr.send(formData);
				
				return true;
			};
			
			if (! isDataType) {
				if (files.length > 0) {
					if (! data.clipdata && renames == null) {
						var mkdirs = [],
							paths = [],
							excludes = fm.options.folderUploadExclude[fm.OS] || null;
						jQuery.each(files, function(i, file) {
							var relPath = file.webkitRelativePath || file.relativePath || '',
								idx, rootDir;
							if (! relPath) {
								return false;
							}
							if (excludes && file.name.match(excludes)) {
								file._remove = true;
								relPath = void(0);
							} else {
								// add '/' as prefix to make same to folder uploading with DnD, see #2607
								relPath = '/' + relPath.replace(/\/[^\/]*$/, '').replace(/^\//, '');
								if (relPath && jQuery.inArray(relPath, mkdirs) === -1) {
									mkdirs.push(relPath);
									// checking the root directory to supports <input type="file" webkitdirectory> see #2378
									idx = relPath.substr(1).indexOf('/');
									if (idx !== -1 && (rootDir = relPath.substr(0, idx + 1)) && jQuery.inArray(rootDir, mkdirs) === -1) {
										mkdirs.unshift(rootDir);
									}
								}
							}
							paths.push(relPath);
						});
						renames = [];
						hashes = {};
						if (mkdirs.length) {
							(function() {
								var checkDirs = jQuery.map(mkdirs, function(name) { return name.substr(1).indexOf('/') === -1 ? {name: name.substr(1)} : null;}),
									cancelDirs = [];
								fm.uploads.checkExists(checkDirs, target, fm, true).done(
									function(res, res2) {
										var dfds = [], dfd, bak, hash;
										if (fm.options.overwriteUploadConfirm && fm.option('uploadOverwrite', target)) {
											cancelDirs = jQuery.map(checkDirs, function(dir) { return dir._remove? dir.name : null ;} );
											checkDirs = jQuery.grep(checkDirs, function(dir) { return !dir._remove? true : false ;} );
										}
										if (cancelDirs.length) {
											jQuery.each(paths.concat(), function(i, path) {
												if (jQuery.inArray(path, cancelDirs) === 0) {
													files[i]._remove = true;
													paths[i] = void(0);
												}
											});
										}
										files = jQuery.grep(files, function(file) { return file._remove? false : true; });
										paths = jQuery.grep(paths, function(path) { return path === void 0 ? false : true; });
										if (checkDirs.length) {
											dfd = jQuery.Deferred();
											if (res.length) {
												jQuery.each(res, function(i, existName) {
													// backup
													bak = fm.uniqueName(existName + fm.options.backupSuffix , null, '');
													jQuery.each(res2, function(h, name) {
														if (res[0] == name) {
															hash = h;
															return false;
														}
													});
													if (! hash) {
														hash = fm.fileByName(res[0], target).hash;
													}
													fm.lockfiles({files : [hash]});
													dfds.push(
														fm.request({
															data   : {cmd : 'rename', target : hash, name : bak},
															notify : {type : 'rename', cnt : 1}
														})
														.fail(function(error) {
															dfrd.reject(error);
															fm.sync();
														})
														.always(function() {
															fm.unlockfiles({files : [hash]});
														})
													);
												});
											} else {
												dfds.push(null);
											}
											
											jQuery.when.apply($, dfds).done(function() {
												// ensure directories
												fm.request({
													data   : {cmd : 'mkdir', target : target, dirs : mkdirs},
													notify : {type : 'mkdir', cnt : mkdirs.length},
													preventFail: true
												})
												.fail(function(error) {
													error = error || ['errUnknown'];
													if (error[0] === 'errCmdParams') {
														multiMax = 1;
													} else {
														multiMax = 0;
														dfrd.reject(error);
													}
												})
												.done(function(data) {
													var rm = false;
													if (!data.hashes) {
														data.hashes = {};
													}
													paths = jQuery.map(paths.concat(), function(p, i) {
														if (p === '/') {
															return target;
														} else {
															if (data.hashes[p]) {
																return data.hashes[p];
															} else {
																rm = true;
																files[i]._remove = true;
																return null;
															}
														}
													});
													if (rm) {
														files = jQuery.grep(files, function(file) { return file._remove? false : true; });
													}
												})
												.always(function(data) {
													if (multiMax) {
														isDataType = true;
														if (! send(files, paths)) {
															dfrd.reject();
														}
													}
												});
											});
										} else {
											dfrd.reject();
										}
									}
								);
							})();
						} else {
							fm.uploads.checkExists(files, target, fm).done(
								function(res, res2){
									if (fm.options.overwriteUploadConfirm && fm.option('uploadOverwrite', target)) {
										hashes = res2;
										if (res === null) {
											data.overwrite = 0;
										} else {
											renames = res;
										}
										files = jQuery.grep(files, function(file){return !file._remove? true : false ;});
									}
									cnt = files.length;
									if (cnt > 0) {
										if (! send(files)) {
											dfrd.reject();
										}
									} else {
										dfrd.reject();
									}
								}
							);
						}
					} else {
						if (! send(files)) {
							dfrd.reject();
						}
					}
				} else {
					dfrd.reject();
				}
			} else {
				if (dataChecked) {
					send(files[0], files[1]);
				} else {
					files.done(function(result) { // result: [files, paths, renames, hashes, mkdirs]
						renames = [];
						cnt = result[0].length;
						if (cnt) {
							if (result[4] && result[4].length) {
								// ensure directories
								fm.request({
									data   : {cmd : 'mkdir', target : target, dirs : result[4]},
									notify : {type : 'mkdir', cnt : result[4].length},
									preventFail: true
								})
								.fail(function(error) {
									error = error || ['errUnknown'];
									if (error[0] === 'errCmdParams') {
										multiMax = 1;
									} else {
										multiMax = 0;
										dfrd.reject(error);
									}
								})
								.done(function(data) {
									var rm = false;
									if (!data.hashes) {
										data.hashes = {};
									}
									result[1] = jQuery.map(result[1], function(p, i) {
										p = p.replace(/\/[^\/]*$/, '');
										if (p === '') {
											return target;
										} else {
											if (data.hashes[p]) {
												return data.hashes[p];
											} else {
												rm = true;
												result[0][i]._remove = true;
												return null;
											}
										}
									});
									if (rm) {
										result[0] = jQuery.grep(result[0], function(file) { return file._remove? false : true; });
									}
								})
								.always(function(data) {
									if (multiMax) {
										renames = result[2];
										hashes = result[3];
										send(result[0], result[1]);
									}
								});
								return;
							} else {
								result[1] = jQuery.map(result[1], function() { return target; });
							}
							renames = result[2];
							hashes = result[3];
							send(result[0], result[1]);
						} else {
							dfrd.reject(['errUploadNoFiles']);
						}
					}).fail(function(){
						dfrd.reject();
					});
				}
			}

			return dfrd;
		},
		
		// upload transport using iframe
		iframe : function(data, fm) { 
			var self   = fm ? fm : this,
				input  = data.input? data.input : false,
				files  = !input ? self.uploads.checkFile(data, self) : false,
				dfrd   = jQuery.Deferred()
					.fail(function(error) {
						error && self.error(error);
					}),
				name = 'iframe-'+fm.namespace+(++self.iframeCnt),
				form = jQuery('<form action="'+self.uploadURL+'" method="post" enctype="multipart/form-data" encoding="multipart/form-data" target="'+name+'" style="display:none"><input type="hidden" name="cmd" value="upload" /></form>'),
				msie = this.UA.IE,
				// clear timeouts, close notification dialog, remove form/iframe
				onload = function() {
					abortto  && clearTimeout(abortto);
					notifyto && clearTimeout(notifyto);
					notify   && self.notify({type : 'upload', cnt : -cnt});
					
					setTimeout(function() {
						msie && jQuery('<iframe src="javascript:false;"/>').appendTo(form);
						form.remove();
						iframe.remove();
					}, 100);
				},
				iframe = jQuery('<iframe src="'+(msie ? 'javascript:false;' : 'about:blank')+'" name="'+name+'" style="position:absolute;left:-1000px;top:-1000px" />')
					.on('load', function() {
						iframe.off('load')
							.on('load', function() {
								onload();
								// data will be processed in callback response or window onmessage
								dfrd.resolve();
							});
							
							// notify dialog
							notifyto = setTimeout(function() {
								notify = true;
								self.notify({type : 'upload', cnt : cnt});
							}, self.options.notifyDelay);
							
							// emulate abort on timeout
							if (self.options.iframeTimeout > 0) {
								abortto = setTimeout(function() {
									onload();
									dfrd.reject(['errConnect', 'errTimeout']);
								}, self.options.iframeTimeout);
							}
							
							form.submit();
					}),
				target  = (data.target || self.cwd().hash),
				names   = [],
				dfds    = [],
				renames = [],
				hashes  = {},
				cnt, notify, notifyto, abortto;

			if (files && files.length) {
				jQuery.each(files, function(i, val) {
					form.append('<input type="hidden" name="upload[]" value="'+val+'"/>');
				});
				cnt = 1;
			} else if (input && jQuery(input).is(':file') && jQuery(input).val()) {
				if (fm.options.overwriteUploadConfirm && fm.option('uploadOverwrite', target)) {
					names = input.files? input.files : [{ name: jQuery(input).val().replace(/^(?:.+[\\\/])?([^\\\/]+)$/, '$1') }];
					//names = jQuery.map(names, function(file){return file.name? { name: file.name } : null ;});
					dfds.push(self.uploads.checkExists(names, target, self).done(
						function(res, res2){
							hashes = res2;
							if (res === null) {
								data.overwrite = 0;
							} else{
								renames = res;
								cnt = jQuery.grep(names, function(file){return !file._remove? true : false ;}).length;
								if (cnt != names.length) {
									cnt = 0;
								}
							}
						}
					));
				}
				cnt = input.files ? input.files.length : 1;
				form.append(input);
			} else {
				return dfrd.reject();
			}
			
			jQuery.when.apply($, dfds).done(function() {
				if (cnt < 1) {
					return dfrd.reject();
				}
				form.append('<input type="hidden" name="'+(self.newAPI ? 'target' : 'current')+'" value="'+target+'"/>')
					.append('<input type="hidden" name="html" value="1"/>')
					.append('<input type="hidden" name="node" value="'+self.id+'"/>')
					.append(jQuery(input).attr('name', 'upload[]'));
				
				if (renames.length > 0) {
					jQuery.each(renames, function(i, rename) {
						form.append('<input type="hidden" name="renames[]" value="'+self.escape(rename)+'"/>');
					});
					form.append('<input type="hidden" name="suffix" value="'+fm.options.backupSuffix+'"/>');
				}
				if (hashes) {
					jQuery.each(renames, function(i, v) {
						form.append('<input type="hidden" name="['+i+']" value="'+self.escape(v)+'"/>');
					});
				}
				
				if (data.overwrite === 0) {
					form.append('<input type="hidden" name="overwrite" value="0"/>');
				}
				
				jQuery.each(self.options.onlyMimes||[], function(i, mime) {
					form.append('<input type="hidden" name="mimes[]" value="'+self.escape(mime)+'"/>');
				});
				
				jQuery.each(self.customData, function(key, val) {
					form.append('<input type="hidden" name="'+key+'" value="'+self.escape(val)+'"/>');
				});
				
				form.appendTo('body');
				iframe.appendTo('body');
			});
			
			return dfrd;
		}
	},
	
	
	/**
	 * Bind callback to event(s) The callback is executed at most once per event.
	 * To bind to multiply events at once, separate events names by space
	 *
	 * @param  String    event name
	 * @param  Function  callback
	 * @param  Boolan    priority first
	 * @return elFinder
	 */
	one : function(ev, callback, priorityFirst) {
		var self  = this,
			event = ev.toLowerCase(),
			h     = function(e, f) {
				if (!self.toUnbindEvents[event]) {
					self.toUnbindEvents[event] = [];
				}
				self.toUnbindEvents[event].push({
					type: event,
					callback: h
				});
				return (callback.done? callback.done : callback).apply(this, arguments);
			};
		if (callback.done) {
			h = {done: h};
		}
		return this.bind(event, h, priorityFirst);
	},
	
	/**
	 * Set/get data into/from localStorage
	 *
	 * @param  String       key
	 * @param  String|void  value
	 * @return String|null
	 */
	localStorage : function(key, val) {
		var self   = this,
			s      = window.localStorage,
			oldkey = 'elfinder-'+(key || '')+this.id, // old key of elFinder < 2.1.6
			prefix = window.location.pathname+'-elfinder-',
			suffix = this.id,
			clrs   = [],
			retval, oldval, t, precnt, sufcnt;

		// reset this node data
		if (typeof(key) === 'undefined') {
			precnt = prefix.length;
			sufcnt = suffix.length * -1;
			jQuery.each(s, function(key) {
				if (key.substr(0, precnt) === prefix && key.substr(sufcnt) === suffix) {
					clrs.push(key);
				}
			});
			jQuery.each(clrs, function(i, key) {
				s.removeItem(key);
			});
			return true;
		}
		
		// new key of elFinder >= 2.1.6
		key = prefix+key+suffix;
		
		if (val === null) {
			return s.removeItem(key);
		}
		
		if (val === void(0) && !(retval = s.getItem(key)) && (oldval = s.getItem(oldkey))) {
			val = oldval;
			s.removeItem(oldkey);
		}
		
		if (val !== void(0)) {
			t = typeof val;
			if (t !== 'string' && t !== 'number') {
				val = JSON.stringify(val);
			}
			try {
				s.setItem(key, val);
			} catch (e) {
				try {
					s.clear();
					s.setItem(key, val);
				} catch (e) {
					self.debug('error', e.toString());
				}
			}
			retval = s.getItem(key);
		}

		if (retval && (retval.substr(0,1) === '{' || retval.substr(0,1) === '[')) {
			try {
				return JSON.parse(retval);
			} catch(e) {}
		}
		return retval;
	},
	
	/**
	 * Get/set cookie
	 *
	 * @param  String       cookie name
	 * @param  String|void  cookie value
	 * @return String|null
	 */
	cookie : function(name, value) {
		var d, o, c, i, retval, t;

		name = 'elfinder-'+name+this.id;

		if (value === void(0)) {
			if (document.cookie && document.cookie != '') {
				c = document.cookie.split(';');
				name += '=';
				for (i=0; i<c.length; i++) {
					c[i] = jQuery.trim(c[i]);
					if (c[i].substring(0, name.length) == name) {
						retval = decodeURIComponent(c[i].substring(name.length));
						if (retval.substr(0,1) === '{' || retval.substr(0,1) === '[') {
							try {
								return JSON.parse(retval);
							} catch(e) {}
						}
						return retval;
					}
				}
			}
			return null;
		}

		o = Object.assign({}, this.options.cookie);
		if (value === null) {
			value = '';
			o.expires = -1;
		} else {
			t = typeof value;
			if (t !== 'string' && t !== 'number') {
				value = JSON.stringify(value);
			}
		}
		if (typeof(o.expires) == 'number') {
			d = new Date();
			d.setTime(d.getTime()+(o.expires * 86400000));
			o.expires = d;
		}
		document.cookie = name+'='+encodeURIComponent(value)+'; expires='+o.expires.toUTCString()+(o.path ? '; path='+o.path : '')+(o.domain ? '; domain='+o.domain : '')+(o.secure ? '; secure' : '');
		if (value && (value.substr(0,1) === '{' || value.substr(0,1) === '[')) {
			try {
				return JSON.parse(value);
			} catch(e) {}
		}
		return value;
	},
	
	/**
	 * Get start directory (by location.hash or last opened directory)
	 * 
	 * @return String
	 */
	startDir : function() {
		var locHash = window.location.hash;
		if (locHash && locHash.match(/^#elf_/)) {
			return locHash.replace(/^#elf_/, '');
		} else if (this.options.startPathHash) {
			return this.options.startPathHash;
		} else {
			return this.lastDir();
		}
	},
	
	/**
	 * Get/set last opened directory
	 * 
	 * @param  String|undefined  dir hash
	 * @return String
	 */
	lastDir : function(hash) { 
		return this.options.rememberLastDir ? this.storage('lastdir', hash) : '';
	},
	
	/**
	 * Node for escape html entities in texts
	 * 
	 * @type jQuery
	 */
	_node : jQuery('<span/>'),
	
	/**
	 * Replace not html-safe symbols to html entities
	 * 
	 * @param  String  text to escape
	 * @return String
	 */
	escape : function(name) {
		return this._node.text(name).html().replace(/"/g, '&quot;').replace(/'/g, '&#039;');
	},
	
	/**
	 * Cleanup ajax data.
	 * For old api convert data into new api format
	 * 
	 * @param  String  command name
	 * @param  Object  data from backend
	 * @return Object
	 */
	normalize : function(data) {
		var self   = this,
			fileFilter = (function() {
				var func, filter;
				if (filter = self.options.fileFilter) {
					if (typeof filter === 'function') {
						func = function(file) {
							return filter.call(self, file);
						};
					} else if (filter instanceof RegExp) {
						func = function(file) {
							return filter.test(file.name);
						};
					}
				}
				return func? func : null;
			})(),
			chkCmdMap = function(opts) {
				// Disable command to replace with other command
				var disabled;
				if (opts.uiCmdMap) {
					if (jQuery.isPlainObject(opts.uiCmdMap) && Object.keys(opts.uiCmdMap).length) {
						if (!opts.disabledFlip) {
							opts.disabledFlip = {};
						}
						disabled = opts.disabledFlip;
						jQuery.each(opts.uiCmdMap, function(f, t) {
							if (t === 'hidden' && !disabled[f]) {
								opts.disabled.push(f);
								opts.disabledFlip[f] = true;
							}
						});
					} else {
						delete opts.uiCmdMap;
					}
				}
			},
			normalizeOptions = function(opts) {
				var getType = function(v) {
					var type = typeof v;
					if (type === 'object' && Array.isArray(v)) {
						type = 'array';
					}
					return type;
				};
				jQuery.each(self.optionProperties, function(k, empty) {
					if (empty !== void(0)) {
						if (opts[k] && getType(opts[k]) !== getType(empty)) {
							opts[k] = empty;
						}
					}
				});
				if (opts['disabled']) {
					opts['disabledFlip'] = self.arrayFlip(opts['disabled'], true);
				} else {
					opts['disabledFlip'] = {};
				}
				return opts;
			},
			filter = function(file, asMap, type) { 
				var res = asMap? file : true,
					ign = asMap? null : false,
					vid, targetOptions, isRoot, rootNames;
				
				if (file && file.hash && file.name && file.mime) {
					if (file.mime === 'application/x-empty') {
						file.mime = 'text/plain';
					}
					
					isRoot = self.isRoot(file);
					if (isRoot && ! file.volumeid) {
						self.debug('warning', 'The volume root statuses requires `volumeid` property.');
					}
					if (isRoot || file.mime === 'directory') {
						// Prevention of circular reference
						if (file.phash) {
							if (file.phash === file.hash) {
								error = error.concat(['Parent folder of "$1" is itself.', file.name]);
								return ign;
							}
							if (isRoot && file.volumeid && file.phash.indexOf(file.volumeid) === 0) {
								error = error.concat(['Parent folder of "$1" is inner itself.', file.name]);
								return ign;
							}
						}
						
						// set options, tmbUrls for each volume
						if (file.volumeid) {
							vid = file.volumeid;
							
							if (isRoot) {
								// make or update of leaf roots cache
								if (file.phash) {
									if (! self.leafRoots[file.phash]) {
										self.leafRoots[file.phash] = [ file.hash ];
									} else {
										if (jQuery.inArray(file.hash, self.leafRoots[file.phash]) === -1) {
											self.leafRoots[file.phash].push(file.hash);
										}
									}
								}

								self.hasVolOptions = true;
								if (! self.volOptions[vid]) {
									self.volOptions[vid] = {
										// set dispInlineRegex
										dispInlineRegex: self.options.dispInlineRegex
									};
								}
								
								targetOptions = self.volOptions[vid];
								
								if (file.options) {
									// >= v.2.1.14 has file.options
									Object.assign(targetOptions, file.options);
								}
								
								// for compat <= v2.1.13
								if (file.disabled) {
									targetOptions.disabled = file.disabled;
									targetOptions.disabledFlip = self.arrayFlip(file.disabled, true);
								}
								if (file.tmbUrl) {
									targetOptions.tmbUrl = file.tmbUrl;
								}
								
								// '/' required at the end of url
								if (targetOptions.url && targetOptions.url.substr(-1) !== '/') {
									targetOptions.url += '/';
								}

								// check uiCmdMap
								chkCmdMap(targetOptions);
								
								// check trash bin hash
								if (targetOptions.trashHash) {
									if (self.trashes[targetOptions.trashHash] === false) {
										delete targetOptions.trashHash;
									} else {
										self.trashes[targetOptions.trashHash] = file.hash;
									}
								}
								
								// set immediate properties
								jQuery.each(self.optionProperties, function(k) {
									if (targetOptions[k]) {
										file[k] = targetOptions[k];
									}
								});

								// regist fm.roots
								if (type !== 'cwd') {
									self.roots[vid] = file.hash;
								}

								// regist fm.volumeExpires
								if (file.expires) {
									self.volumeExpires[vid] = file.expires;
								}
							}
							
							if (prevId !== vid) {
								prevId = vid;
								i18nFolderName = self.option('i18nFolderName', vid);
							}
						}
						
						// volume root i18n name
						if (isRoot && ! file.i18) {
							name = 'volume_' + file.name,
							i18 = self.i18n(false, name);
	
							if (name !== i18) {
								file.i18 = i18;
							}
						}
						
						// i18nFolderName
						if (i18nFolderName && ! file.i18) {
							name = 'folder_' + file.name,
							i18 = self.i18n(false, name);
	
							if (name !== i18) {
								file.i18 = i18;
							}
						}
						
						if (isRoot) {
							if (rootNames = self.storage('rootNames')) {
								if (rootNames[file.hash]) {
									file._name = file.name;
									file._i18 = file.i18;
									file.name = rootNames[file.hash] = rootNames[file.hash];
									delete file.i18;
								}
								self.storage('rootNames', rootNames);
							}
						}

						// lock trash bins holder
						if (self.trashes[file.hash]) {
							file.locked = true;
						}
					} else {
						if (fileFilter) {
							try {
								if (! fileFilter(file)) {
									return ign;
								}
							} catch(e) {
								self.debug(e);
							}
						}
						if (file.size == 0) {
							file.mime = self.getMimetype(file.name, file.mime);
						}
					}
					
					if (file.options) {
						self.optionsByHashes[file.hash] = normalizeOptions(file.options);
					}
					
					delete file.options;
					
					return res;
				}
				return ign;
			},
			getDescendants = function(hashes) {
				var res = [];
				jQuery.each(self.files(), function(h, f) {
					jQuery.each(self.parents(h), function(i, ph) {
						if (jQuery.inArray(ph, hashes) !== -1 && jQuery.inArray(h, hashes) === -1) {
							res.push(h);
							return false;
						}
					});
				});
				return res;
			},
			applyLeafRootStats = function(dataArr, type) {
				jQuery.each(dataArr, function(i, f) {
					var pfile, done;
					if (self.leafRoots[f.hash]) {
						self.applyLeafRootStats(f);
					}
					// update leaf root parent stat
					if (type !== 'change' && f.phash && self.isRoot(f) && (pfile = self.file(f.phash))) {
						self.applyLeafRootStats(pfile);
						// add to data.changed
						if (!data.changed) {
							data.changed = [pfile];
						} else {
							jQuery.each(data.changed, function(i, f) {
								if (f.hash === pfile.hash) {
									data.changed[i] = pfile;
									done = true;
									return false;
								}
							});
							if (!done) {
								data.changed.push(pfile);
							}
						}
					}
				});
			},
			error = [],
			name, i18, i18nFolderName, prevId, cData;
		
		// set cunstom data
		if (data.customData && data.customData !== self.prevCustomData) {
			self.prevCustomData = data.customData;
			try {
				cData = JSON.parse(data.customData);
				if (jQuery.isPlainObject(cData)) {
					self.prevCustomData = cData;
					jQuery.each(Object.keys(cData), function(i, key) {
						if (cData[key] === null) {
							delete cData[key];
							delete self.optsCustomData[key];
						}
					});
					self.customData = Object.assign({}, self.optsCustomData, cData);
				}
			} catch(e) {}
		}

		if (data.options) {
			normalizeOptions(data.options);
		}
		
		if (data.cwd) {
			if (data.cwd.volumeid && data.options && Object.keys(data.options).length && self.isRoot(data.cwd)) {
				self.hasVolOptions = true;
				self.volOptions[data.cwd.volumeid] = data.options;
			}
			data.cwd = filter(data.cwd, true, 'cwd');
		}
		if (data.files) {
			data.files = jQuery.grep(data.files, filter);
		} 
		if (data.tree) {
			data.tree = jQuery.grep(data.tree, filter);
		}
		if (data.added) {
			data.added = jQuery.grep(data.added, filter);
		}
		if (data.changed) {
			data.changed = jQuery.grep(data.changed, filter);
		}
		if (data.removed && data.removed.length && self.searchStatus.state === 2) {
			data.removed = data.removed.concat(getDescendants(data.removed));
		}
		if (data.api) {
			data.init = true;
		}

		if (Object.keys(self.leafRoots).length) {
			data.files && applyLeafRootStats(data.files);
			data.tree && applyLeafRootStats(data.tree);
			data.added && applyLeafRootStats(data.added);
			data.changed && applyLeafRootStats(data.changed, 'change');
		}

		// merge options that apply only to cwd
		if (data.cwd && data.cwd.options && data.options) {
			Object.assign(data.options, normalizeOptions(data.cwd.options));
		}

		// '/' required at the end of url
		if (data.options && data.options.url && data.options.url.substr(-1) !== '/') {
			data.options.url += '/';
		}
		
		// check error
		if (error.length) {
			data.norError = ['errResponse'].concat(error);
		}
		
		return data;
	},
	
	/**
	 * Update sort options
	 *
	 * @param {String} sort type
	 * @param {String} sort order
	 * @param {Boolean} show folder first
	 */
	setSort : function(type, order, stickFolders, alsoTreeview) {
		this.storage('sortType', (this.sortType = this.sortRules[type] ? type : 'name'));
		this.storage('sortOrder', (this.sortOrder = /asc|desc/.test(order) ? order : 'asc'));
		this.storage('sortStickFolders', (this.sortStickFolders = !!stickFolders) ? 1 : '');
		this.storage('sortAlsoTreeview', (this.sortAlsoTreeview = !!alsoTreeview) ? 1 : '');
		this.trigger('sortchange');
	},
	
	_sortRules : {
		name : function(file1, file2) {
			return elFinder.prototype.naturalCompare(file1.i18 || file1.name, file2.i18 || file2.name);
		},
		size : function(file1, file2) { 
			var size1 = parseInt(file1.size) || 0,
				size2 = parseInt(file2.size) || 0;
				
			return size1 === size2 ? 0 : size1 > size2 ? 1 : -1;
		},
		kind : function(file1, file2) {
			return elFinder.prototype.naturalCompare(file1.mime, file2.mime);
		},
		date : function(file1, file2) { 
			var date1 = file1.ts || file1.date || 0,
				date2 = file2.ts || file2.date || 0;

			return date1 === date2 ? 0 : date1 > date2 ? 1 : -1;
		},
		perm : function(file1, file2) { 
			var val = function(file) { return (file.write? 2 : 0) + (file.read? 1 : 0); },
				v1  = val(file1),
				v2  = val(file2);
			return v1 === v2 ? 0 : v1 > v2 ? 1 : -1;
		},
		mode : function(file1, file2) { 
			var v1 = file1.mode || (file1.perm || ''),
				v2 = file2.mode || (file2.perm || '');
			return elFinder.prototype.naturalCompare(v1, v2);
		},
		owner : function(file1, file2) { 
			var v1 = file1.owner || '',
				v2 = file2.owner || '';
			return elFinder.prototype.naturalCompare(v1, v2);
		},
		group : function(file1, file2) { 
			var v1 = file1.group || '',
				v2 = file2.group || '';
			return elFinder.prototype.naturalCompare(v1, v2);
		}
	},
	
	/**
	 * Valid sort rule names
	 * 
	 * @type Object
	 */
	sorters : {},
	
	/**
	 * Compare strings for natural sort
	 *
	 * @param  String
	 * @param  String
	 * @return Number
	 */
	naturalCompare : function(a, b) {
		var self = elFinder.prototype.naturalCompare;
		if (typeof self.loc == 'undefined') {
			self.loc = (navigator.userLanguage || navigator.browserLanguage || navigator.language || 'en-US');
		}
		if (typeof self.sort == 'undefined') {
			if ('11'.localeCompare('2', self.loc, {numeric: true}) > 0) {
				// Native support
				if (window.Intl && window.Intl.Collator) {
					self.sort = new Intl.Collator(self.loc, {numeric: true}).compare;
				} else {
					self.sort = function(a, b) {
						return a.localeCompare(b, self.loc, {numeric: true});
					};
				}
			} else {
				/*
				 * Edited for elFinder (emulates localeCompare() by numeric) by Naoki Sawada aka nao-pon
				 */
				/*
				 * Huddle/javascript-natural-sort (https://github.com/Huddle/javascript-natural-sort)
				 */
				/*
				 * Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license
				 * Author: Jim Palmer (based on chunking idea from Dave Koelle)
				 * http://opensource.org/licenses/mit-license.php
				 */
				self.sort = function(a, b) {
					var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,
					sre = /(^[ ]*|[ ]*$)/g,
					dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,
					hre = /^0x[0-9a-f]+$/i,
					ore = /^0/,
					syre = /^[\x01\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e]/, // symbol first - (Naoki Sawada)
					i = function(s) { return self.sort.insensitive && (''+s).toLowerCase() || ''+s; },
					// convert all to strings strip whitespace
					// first character is "_", it's smallest - (Naoki Sawada)
					x = i(a).replace(sre, '').replace(/^_/, "\x01") || '',
					y = i(b).replace(sre, '').replace(/^_/, "\x01") || '',
					// chunk/tokenize
					xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
					yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
					// numeric, hex or date detection
					xD = parseInt(x.match(hre)) || (xN.length != 1 && x.match(dre) && Date.parse(x)),
					yD = parseInt(y.match(hre)) || xD && y.match(dre) && Date.parse(y) || null,
					oFxNcL, oFyNcL,
					locRes = 0;

					// first try and sort Hex codes or Dates
					if (yD) {
						if ( xD < yD ) return -1;
						else if ( xD > yD ) return 1;
					}
					// natural sorting through split numeric strings and default strings
					for(var cLoc=0, numS=Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {

						// find floats not starting with '0', string or 0 if not defined (Clint Priest)
						oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc]) || xN[cLoc] || 0;
						oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc]) || yN[cLoc] || 0;

						// handle numeric vs string comparison - number < string - (Kyle Adams)
						// but symbol first < number - (Naoki Sawada)
						if (isNaN(oFxNcL) !== isNaN(oFyNcL)) {
							if (isNaN(oFxNcL) && (typeof oFxNcL !== 'string' || ! oFxNcL.match(syre))) {
								return 1;
							} else if (typeof oFyNcL !== 'string' || ! oFyNcL.match(syre)) {
								return -1;
							}
						}

						// use decimal number comparison if either value is string zero
						if (parseInt(oFxNcL, 10) === 0) oFxNcL = 0;
						if (parseInt(oFyNcL, 10) === 0) oFyNcL = 0;

						// rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
						if (typeof oFxNcL !== typeof oFyNcL) {
							oFxNcL += '';
							oFyNcL += '';
						}

						// use locale sensitive sort for strings when case insensitive
						// note: localeCompare interleaves uppercase with lowercase (e.g. A,a,B,b)
						if (self.sort.insensitive && typeof oFxNcL === 'string' && typeof oFyNcL === 'string') {
							locRes = oFxNcL.localeCompare(oFyNcL, self.loc);
							if (locRes !== 0) return locRes;
						}

						if (oFxNcL < oFyNcL) return -1;
						if (oFxNcL > oFyNcL) return 1;
					}
					return 0;
				};
				self.sort.insensitive = true;
			}
		}
		return self.sort(a, b);
	},
	
	/**
	 * Compare files based on elFinder.sort
	 *
	 * @param  Object  file
	 * @param  Object  file
	 * @return Number
	 */
	compare : function(file1, file2) {
		var self  = this,
			type  = self.sortType,
			asc   = self.sortOrder == 'asc',
			stick = self.sortStickFolders,
			rules = self.sortRules,
			sort  = rules[type],
			d1    = file1.mime == 'directory',
			d2    = file2.mime == 'directory',
			res;
			
		if (stick) {
			if (d1 && !d2) {
				return -1;
			} else if (!d1 && d2) {
				return 1;
			}
		}
		
		res = asc ? sort(file1, file2) : sort(file2, file1);
		
		return type !== 'name' && res === 0
			? res = asc ? rules.name(file1, file2) : rules.name(file2, file1)
			: res;
	},
	
	/**
	 * Sort files based on config
	 *
	 * @param  Array  files
	 * @return Array
	 */
	sortFiles : function(files) {
		return files.sort(this.compare);
	},
	
	/**
	 * Open notification dialog 
	 * and append/update message for required notification type.
	 *
	 * @param  Object  options
	 * @example  
	 * this.notify({
	 *    type : 'copy',
	 *    msg : 'Copy files', // not required for known types @see this.notifyType
	 *    cnt : 3,
	 *    hideCnt  : false,   // true for not show count
	 *    progress : 10,      // progress bar percents (use cnt : 0 to update progress bar)
	 *    cancel   : callback // callback function for cancel button
	 * })
	 * @return elFinder
	 */
	notify : function(opts) {
		var type     = opts.type,
			id       = opts.id? 'elfinder-notify-'+opts.id : '',
			msg      = this.i18n((typeof opts.msg !== 'undefined')? opts.msg : (this.messages['ntf'+type] ? 'ntf'+type : 'ntfsmth')),
			ndialog  = this.ui.notify,
			notify   = ndialog.children('.elfinder-notify-'+type+(id? ('.'+id) : '')),
			button   = notify.children('div.elfinder-notify-cancel').children('button'),
			ntpl     = '<div class="elfinder-notify elfinder-notify-{type}'+(id? (' '+id) : '')+'"><span class="elfinder-dialog-icon elfinder-dialog-icon-{type}"/><span class="elfinder-notify-msg">{msg}</span> <span class="elfinder-notify-cnt"/><div class="elfinder-notify-progressbar"><div class="elfinder-notify-progress"/></div><div class="elfinder-notify-cancel"/></div>',
			delta    = opts.cnt,
			size     = (typeof opts.size != 'undefined')? parseInt(opts.size) : null,
			progress = (typeof opts.progress != 'undefined' && opts.progress >= 0) ? opts.progress : null,
			cancel   = opts.cancel,
			clhover  = 'ui-state-hover',
			close    = function() {
				notify._esc && jQuery(document).off('keydown', notify._esc);
				notify.remove();
				!ndialog.children().length && ndialog.elfinderdialog('close');
			},
			cnt, total, prc;

		if (!type) {
			return this;
		}
		
		if (!notify.length) {
			notify = jQuery(ntpl.replace(/\{type\}/g, type).replace(/\{msg\}/g, msg))
				.appendTo(ndialog)
				.data('cnt', 0);

			if (progress != null) {
				notify.data({progress : 0, total : 0});
			}

			if (cancel) {
				button = jQuery('<button type="button" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"><span class="ui-button-text">'+this.i18n('btnCancel')+'</span></button>')
					.on('mouseenter mouseleave', function(e) { 
						jQuery(this).toggleClass(clhover, e.type === 'mouseenter');
					});
				notify.children('div.elfinder-notify-cancel').append(button);
			}
		} else if (typeof opts.msg !== 'undefined') {
			notify.children('span.elfinder-notify-msg').html(msg);
		}

		cnt = delta + parseInt(notify.data('cnt'));
		
		if (cnt > 0) {
			if (cancel && button.length) {
				if (jQuery.isFunction(cancel) || (typeof cancel === 'object' && cancel.promise)) {
					notify._esc = function(e) {
						if (e.type == 'keydown' && e.keyCode != jQuery.ui.keyCode.ESCAPE) {
							return;
						}
						e.preventDefault();
						e.stopPropagation();
						close();
						if (cancel.promise) {
							cancel.reject(0); // 0 is canceling flag
						} else {
							cancel(e);
						}
					};
					button.on('click', function(e) {
						notify._esc(e);
					});
					jQuery(document).on('keydown.' + this.namespace, notify._esc);
				}
			}
			
			!opts.hideCnt && notify.children('.elfinder-notify-cnt').text('('+cnt+')');
			ndialog.is(':hidden') && ndialog.elfinderdialog('open', this).height('auto');
			notify.data('cnt', cnt);
			
			if ((progress != null)
			&& (total = notify.data('total')) >= 0
			&& (prc = notify.data('progress')) >= 0) {

				total += size != null? size : delta;
				prc   += progress;
				(size == null && delta < 0) && (prc += delta * 100);
				notify.data({progress : prc, total : total});
				if (size != null) {
					prc *= 100;
					total = Math.max(1, total);
				}
				progress = parseInt(prc/total);
				
				notify.find('.elfinder-notify-progress')
					.animate({
						width : (progress < 100 ? progress : 100)+'%'
					}, 20);
			}
			
		} else {
			close();
		}
		
		return this;
	},
	
	/**
	 * Open confirmation dialog 
	 *
	 * @param  Object  options
	 * @example  
	 * this.confirm({
	 *    cssClass : 'elfinder-confirm-mydialog',
	 *    title : 'Remove files',
	 *    text  : 'Here is question text',
	 *    accept : {  // accept callback - required
	 *      label : 'Continue',
	 *      callback : function(applyToAll) { fm.log('Ok') }
	 *    },
	 *    cancel : { // cancel callback - required
	 *      label : 'Cancel',
	 *      callback : function() { fm.log('Cancel')}
	 *    },
	 *    reject : { // reject callback - optionally
	 *      label : 'No',
	 *      callback : function(applyToAll) { fm.log('No')}
	 *    },
	 *    buttons : [ // additional buttons callback - optionally
	 *      {
	 *        label : 'Btn1',
	 *        callback : function(applyToAll) { fm.log('Btn1')}
	 *      }
	 *    ],
	 *    all : true  // display checkbox "Apply to all"
	 * })
	 * @return elFinder
	 */
	confirm : function(opts) {
		var self     = this,
			complete = false,
			options = {
				cssClass  : 'elfinder-dialog-confirm',
				modal     : true,
				resizable : false,
				title     : this.i18n(opts.title || 'confirmReq'),
				buttons   : {},
				close     : function() { 
					!complete && opts.cancel.callback();
					jQuery(this).elfinderdialog('destroy');
				}
			},
			apply = this.i18n('apllyAll'),
			label, checkbox, btnNum;

		if (opts.cssClass) {
			options.cssClass += ' ' + opts.cssClass;
		}
		options.buttons[this.i18n(opts.accept.label)] = function() {
			opts.accept.callback(!!(checkbox && checkbox.prop('checked')));
			complete = true;
			jQuery(this).elfinderdialog('close');
		};
		options.buttons[this.i18n(opts.accept.label)]._cssClass = 'elfinder-confirm-accept';
		
		if (opts.reject) {
			options.buttons[this.i18n(opts.reject.label)] = function() {
				opts.reject.callback(!!(checkbox && checkbox.prop('checked')));
				complete = true;
				jQuery(this).elfinderdialog('close');
			};
			options.buttons[this.i18n(opts.reject.label)]._cssClass = 'elfinder-confirm-reject';
		}
		
		if (opts.buttons && opts.buttons.length > 0) {
			btnNum = 1;
			jQuery.each(opts.buttons, function(i, v){
				options.buttons[self.i18n(v.label)] = function() {
					v.callback(!!(checkbox && checkbox.prop('checked')));
					complete = true;
					jQuery(this).elfinderdialog('close');
				};
				options.buttons[self.i18n(v.label)]._cssClass = 'elfinder-confirm-extbtn' + (btnNum++);
				if (v.cssClass) {
					options.buttons[self.i18n(v.label)]._cssClass += ' ' + v.cssClass;
				}
			});
		}
		
		options.buttons[this.i18n(opts.cancel.label)] = function() {
			jQuery(this).elfinderdialog('close');
		};
		options.buttons[this.i18n(opts.cancel.label)]._cssClass = 'elfinder-confirm-cancel';
		
		if (opts.all) {
			options.create = function() {
				var base = jQuery('<div class="elfinder-dialog-confirm-applyall"/>');
				checkbox = jQuery('<input type="checkbox" />');
				jQuery(this).next().find('.ui-dialog-buttonset')
					.prepend(base.append(jQuery('<label>'+apply+'</label>').prepend(checkbox)));
			};
		}
		
		if (opts.optionsCallback && jQuery.isFunction(opts.optionsCallback)) {
			opts.optionsCallback(options);
		}
		
		return this.dialog('<span class="elfinder-dialog-icon elfinder-dialog-icon-confirm"/>' + this.i18n(opts.text), options);
	},
	
	/**
	 * Create unique file name in required dir
	 * 
	 * @param  String  file name
	 * @param  String  parent dir hash
	 * @param  String  glue
	 * @return String
	 */
	uniqueName : function(prefix, phash, glue) {
		var i = 0, ext = '', p, name;
		
		prefix = this.i18n(false, prefix);
		phash = phash || this.cwd().hash;
		glue = (typeof glue === 'undefined')? ' ' : glue;

		if (p = prefix.match(/^(.+)(\.[^.]+)$/)) {
			ext    = p[2];
			prefix = p[1];
		}
		
		name   = prefix+ext;
		
		if (!this.fileByName(name, phash)) {
			return name;
		}
		while (i < 10000) {
			name = prefix + glue + (++i) + ext;
			if (!this.fileByName(name, phash)) {
				return name;
			}
		}
		return prefix + Math.random() + ext;
	},
	
	/**
	 * Return message translated onto current language
	 * Allowed accept HTML element that was wrapped in jQuery object
	 * To be careful to XSS vulnerability of HTML element Ex. You should use `fm.escape(file.name)`
	 *
	 * @param  String|Array  message[s]|Object jQuery
	 * @return String
	 **/
	i18n : function() {
		var self = this,
			messages = this.messages, 
			input    = [],
			ignore   = [], 
			message = function(m) {
				var file;
				if (m.indexOf('#') === 0) {
					if ((file = self.file(m.substr(1)))) {
						return file.name;
					}
				}
				return m;
			},
			i, j, m, escFunc, start = 0, isErr;
		
		if (arguments.length && arguments[0] === false) {
			escFunc = function(m){ return m; };
			start = 1;
		}
		for (i = start; i< arguments.length; i++) {
			m = arguments[i];
			
			if (Array.isArray(m)) {
				for (j = 0; j < m.length; j++) {
					if (m[j] instanceof jQuery) {
						// jQuery object is HTML element
						input.push(m[j]);
					} else if (typeof m[j] !== 'undefined'){
						input.push(message('' + m[j]));
					}
				}
			} else if (m instanceof jQuery) {
				// jQuery object is HTML element
				input.push(m[j]);
			} else if (typeof m !== 'undefined'){
				input.push(message('' + m));
			}
		}
		
		for (i = 0; i < input.length; i++) {
			// dont translate placeholders
			if (jQuery.inArray(i, ignore) !== -1) {
				continue;
			}
			m = input[i];
			if (typeof m == 'string') {
				isErr = !!(messages[m] && m.match(/^err/));
				// translate message
				m = messages[m] || (escFunc? escFunc(m) : self.escape(m));
				// replace placeholders in message
				m = m.replace(/\$(\d+)/g, function(match, placeholder) {
					var res;
					placeholder = i + parseInt(placeholder);
					if (placeholder > 0 && input[placeholder]) {
						ignore.push(placeholder);
					}
					res = escFunc? escFunc(input[placeholder]) : self.escape(input[placeholder]);
					if (isErr) {
						res = '<span class="elfinder-err-var elfinder-err-var' + placeholder + '">' + res + '</span>';
					}
					return res;
				});
			} else {
				// get HTML from jQuery object
				m = m.get(0).outerHTML;
			}

			input[i] = m;
		}

		return jQuery.grep(input, function(m, i) { return jQuery.inArray(i, ignore) === -1 ? true : false; }).join('<br>');
	},
	
	/**
	 * Get icon style from file.icon
	 * 
	 * @param  Object  elFinder file object
	 * @return String|Object
	 */
	getIconStyle : function(file, asObject) {
		var self = this,
			template = {
				'background' : 'url(\'{url}\') 0 0 no-repeat',
				'background-size' : 'contain'
			},
			style = '',
			cssObj = {},
			i = 0;
		if (file.icon) {
			style = 'style="';
			jQuery.each(template, function(k, v) {
				if (i++ === 0) {
					v = v.replace('{url}', self.escape(file.icon));
				}
				if (asObject) {
					cssObj[k] = v;
				} else {
					style += k+':'+v+';';
				}
			});
			style += '"';
		}
		return asObject? cssObj : style;
	},
	
	/**
	 * Convert mimetype into css classes
	 * 
	 * @param  String  file mimetype
	 * @return String
	 */
	mime2class : function(mimeType) {
		var prefix = 'elfinder-cwd-icon-',
			mime   = mimeType.toLowerCase(),
			isText = this.textMimes[mime];
		
		mime = mime.split('/');
		if (isText) {
			mime[0] += ' ' + prefix + 'text';
		} else if (mime[1] && mime[1].match(/\+xml$/)) {
			mime[0] += ' ' + prefix + 'xml';
		}
		
		return prefix + mime[0] + (mime[1] ? ' ' + prefix + mime[1].replace(/(\.|\+)/g, '-') : '');
	},
	
	/**
	 * Return localized kind of file
	 * 
	 * @param  Object|String  file or file mimetype
	 * @return String
	 */
	mime2kind : function(f) {
		var isObj = typeof(f) == 'object' ? true : false,
			mime  = isObj ? f.mime : f,
			kind;
		

		if (isObj && f.alias && mime != 'symlink-broken') {
			kind = 'Alias';
		} else if (this.kinds[mime]) {
			if (isObj && mime === 'directory' && (! f.phash || f.isroot)) {
				kind = 'Root';
			} else {
				kind = this.kinds[mime];
			}
		}
		if (! kind) {
			if (mime.indexOf('text') === 0) {
				kind = 'Text';
			} else if (mime.indexOf('image') === 0) {
				kind = 'Image';
			} else if (mime.indexOf('audio') === 0) {
				kind = 'Audio';
			} else if (mime.indexOf('video') === 0) {
				kind = 'Video';
			} else if (mime.indexOf('application') === 0) {
				kind = 'App';
			} else {
				kind = mime;
			}
		}
		
		return this.messages['kind'+kind] ? this.i18n('kind'+kind) : mime;
	},
	
	/**
	 * Return boolean Is mime-type text file
	 * 
	 * @param  String  mime-type
	 * @return Boolean
	 */
	mimeIsText : function(mime) {
		return (this.textMimes[mime.toLowerCase()] || (mime.indexOf('text/') === 0 && mime.substr(5, 3) !== 'rtf') || mime.match(/^application\/.+\+xml$/))? true : false;
	},
	
	/**
	 * Returns a date string formatted according to the given format string
	 * 
	 * @param  String  format string
	 * @param  Object  Date object
	 * @return String
	 */
	date : function(format, date) {
		var self = this,
			output, d, dw, m, y, h, g, i, s;
		
		if (! date) {
			date = new Date();
		}
		
		h  = date[self.getHours]();
		g  = h > 12 ? h - 12 : h;
		i  = date[self.getMinutes]();
		s  = date[self.getSeconds]();
		d  = date[self.getDate]();
		dw = date[self.getDay]();
		m  = date[self.getMonth]() + 1;
		y  = date[self.getFullYear]();
		
		output = format.replace(/[a-z]/gi, function(val) {
			switch (val) {
				case 'd': return d > 9 ? d : '0'+d;
				case 'j': return d;
				case 'D': return self.i18n(self.i18.daysShort[dw]);
				case 'l': return self.i18n(self.i18.days[dw]);
				case 'm': return m > 9 ? m : '0'+m;
				case 'n': return m;
				case 'M': return self.i18n(self.i18.monthsShort[m-1]);
				case 'F': return self.i18n(self.i18.months[m-1]);
				case 'Y': return y;
				case 'y': return (''+y).substr(2);
				case 'H': return h > 9 ? h : '0'+h;
				case 'G': return h;
				case 'g': return g;
				case 'h': return g > 9 ? g : '0'+g;
				case 'a': return h >= 12 ? 'pm' : 'am';
				case 'A': return h >= 12 ? 'PM' : 'AM';
				case 'i': return i > 9 ? i : '0'+i;
				case 's': return s > 9 ? s : '0'+s;
			}
			return val;
		});
		
		return output;
	},
	
	/**
	 * Return localized date
	 * 
	 * @param  Object  file object
	 * @return String
	 */
	formatDate : function(file, t) {
		var self = this, 
			ts   = t || file.ts, 
			i18  = self.i18,
			date, format, output, d, dw, m, y, h, g, i, s;

		if (self.options.clientFormatDate && ts > 0) {

			date = new Date(ts*1000);
			format = ts >= this.yesterday 
				? this.fancyFormat 
				: this.dateFormat;

			output = self.date(format, date);
			
			return ts >= this.yesterday
				? output.replace('$1', this.i18n(ts >= this.today ? 'Today' : 'Yesterday'))
				: output;
		} else if (file.date) {
			return file.date.replace(/([a-z]+)\s/i, function(a1, a2) { return self.i18n(a2)+' '; });
		}
		
		return self.i18n('dateUnknown');
	},
	
	/**
	 * Return localized number string
	 * 
	 * @param  Number
	 * @return String
	 */
	toLocaleString : function(num) {
		var v = new Number(num);
		if (v) {
			if (v.toLocaleString) {
				return v.toLocaleString();
			} else {
				return String(num).replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
			}
		}
		return num;
	},
	
	/**
	 * Return css class marks file permissions
	 * 
	 * @param  Object  file 
	 * @return String
	 */
	perms2class : function(o) {
		var c = '';
		
		if (!o.read && !o.write) {
			c = 'elfinder-na';
		} else if (!o.read) {
			c = 'elfinder-wo';
		} else if (!o.write) {
			c = 'elfinder-ro';
		}
		
		if (o.type) {
			c += ' elfinder-' + this.escape(o.type);
		}
		
		return c;
	},
	
	/**
	 * Return localized string with file permissions
	 * 
	 * @param  Object  file
	 * @return String
	 */
	formatPermissions : function(f) {
		var p  = [];
			
		f.read && p.push(this.i18n('read'));
		f.write && p.push(this.i18n('write'));	

		return p.length ? p.join(' '+this.i18n('and')+' ') : this.i18n('noaccess');
	},
	
	/**
	 * Return formated file size
	 * 
	 * @param  Number  file size
	 * @return String
	 */
	formatSize : function(s) {
		var n = 1, u = 'b';
		
		if (s == 'unknown') {
			return this.i18n('unknown');
		}
		
		if (s > 1073741824) {
			n = 1073741824;
			u = 'GB';
		} else if (s > 1048576) {
			n = 1048576;
			u = 'MB';
		} else if (s > 1024) {
			n = 1024;
			u = 'KB';
		}
		s = s/n;
		return (s > 0 ? n >= 1048576 ? s.toFixed(2) : Math.round(s) : 0) +' '+u;
	},
	
	/**
	 * Return formated file mode by options.fileModeStyle
	 * 
	 * @param  String  file mode
	 * @param  String  format style
	 * @return String
	 */
	formatFileMode : function(p, style) {
		var i, o, s, b, sticy, suid, sgid, str, oct;
		
		if (!style) {
			style = this.options.fileModeStyle.toLowerCase();
		}
		p = jQuery.trim(p);
		if (p.match(/[rwxs-]{9}$/i)) {
			str = p = p.substr(-9);
			if (style == 'string') {
				return str;
			}
			oct = '';
			s = 0;
			for (i=0; i<7; i=i+3) {
				o = p.substr(i, 3);
				b = 0;
				if (o.match(/[r]/i)) {
					b += 4;
				}
				if (o.match(/[w]/i)) {
					b += 2;
				}
				if (o.match(/[xs]/i)) {
					if (o.match(/[xs]/)) {
						b += 1;
					}
					if (o.match(/[s]/i)) {
						if (i == 0) {
							s += 4;
						} else if (i == 3) {
							s += 2;
						}
					}
				}
				oct += b.toString(8);
			}
			if (s) {
				oct = s.toString(8) + oct;
			}
		} else {
			p = parseInt(p, 8);
			oct = p? p.toString(8) : '';
			if (!p || style == 'octal') {
				return oct;
			}
			o = p.toString(8);
			s = 0;
			if (o.length > 3) {
				o = o.substr(-4);
				s = parseInt(o.substr(0, 1), 8);
				o = o.substr(1);
			}
			sticy = ((s & 1) == 1); // not support
			sgid = ((s & 2) == 2);
			suid = ((s & 4) == 4);
			str = '';
			for(i=0; i<3; i++) {
				if ((parseInt(o.substr(i, 1), 8) & 4) == 4) {
					str += 'r';
				} else {
					str += '-';
				}
				if ((parseInt(o.substr(i, 1), 8) & 2) == 2) {
					str += 'w';
				} else {
					str += '-';
				}
				if ((parseInt(o.substr(i, 1), 8) & 1) == 1) {
					str += ((i==0 && suid)||(i==1 && sgid))? 's' : 'x';
				} else {
					str += '-';
				}
			}
		}
		if (style == 'both') {
			return str + ' (' + oct + ')';
		} else if (style == 'string') {
			return str;
		} else {
			return oct;
		}
	},
	
	/**
	 * Regist this.decodeRawString function
	 * 
	 * @return void
	 */
	registRawStringDecoder : function(rawStringDecoder) {
		if (jQuery.isFunction(rawStringDecoder)) {
			this.decodeRawString = this.options.rawStringDecoder = rawStringDecoder;
		}
	},
	
	/**
	 * Return boolean that uploadable MIME type into target folder
	 * 
	 * @param  String  mime    MIME type
	 * @param  String  target  target folder hash
	 * @return Bool
	 */
	uploadMimeCheck : function(mime, target) {
		target = target || this.cwd().hash;
		var res   = true, // default is allow
			mimeChecker = this.option('uploadMime', target),
			allow,
			deny,
			check = function(checker) {
				var ret = false;
				if (typeof checker === 'string' && checker.toLowerCase() === 'all') {
					ret = true;
				} else if (Array.isArray(checker) && checker.length) {
					jQuery.each(checker, function(i, v) {
						v = v.toLowerCase();
						if (v === 'all' || mime.indexOf(v) === 0) {
							ret = true;
							return false;
						}
					});
				}
				return ret;
			};
		if (mime && jQuery.isPlainObject(mimeChecker)) {
			mime = mime.toLowerCase();
			allow = check(mimeChecker.allow);
			deny = check(mimeChecker.deny);
			if (mimeChecker.firstOrder === 'allow') {
				res = false; // default is deny
				if (! deny && allow === true) { // match only allow
					res = true;
				}
			} else {
				res = true; // default is allow
				if (deny === true && ! allow) { // match only deny
					res = false;
				}
			}
		}
		return res;
	},
	
	/**
	 * call chained sequence of async deferred functions
	 * 
	 * @param  Array   tasks async functions
	 * @return Object  jQuery.Deferred
	 */
	sequence : function(tasks) {
		var l = tasks.length,
			chain = function(task, idx) {
				++idx;
				if (tasks[idx]) {
					return chain(task.then(tasks[idx]), idx);
				} else {
					return task;
				}
			};
		if (l > 1) {
			return chain(tasks[0](), 0);
		} else {
			return tasks[0]();
		}
	},
	
	/**
	 * Reload contents of target URL for clear browser cache
	 * 
	 * @param  String  url target URL
	 * @return Object  jQuery.Deferred
	 */
	reloadContents : function(url) {
		var dfd = jQuery.Deferred(),
			ifm;
		try {
			ifm = jQuery('<iframe width="1" height="1" scrolling="no" frameborder="no" style="position:absolute; top:-1px; left:-1px" crossorigin="use-credentials">')
				.attr('src', url)
				.one('load', function() {
					var ifm = jQuery(this);
					try {
						this.contentDocument.location.reload(true);
						ifm.one('load', function() {
							ifm.remove();
							dfd.resolve();
						});
					} catch(e) {
						ifm.attr('src', '').attr('src', url).one('load', function() {
							ifm.remove();
							dfd.resolve();
						});
					}
				})
				.appendTo('body');
		} catch(e) {
			ifm && ifm.remove();
			dfd.reject();
		}
		return dfd;
	},
	
	/**
	 * Make netmount option for OAuth2
	 * 
	 * @param  String   protocol
	 * @param  String   name
	 * @param  String   host
	 * @param  Object   opts  Default {noOffline: false, root: 'root', pathI18n: 'folderId', folders: true}
			}
	 * 
	 * @return Object
	 */
	makeNetmountOptionOauth : function(protocol, name, host, opt) {
		var noOffline = typeof opt === 'boolean'? opt : null, // for backward compat
			opts = Object.assign({
				noOffline : false,
				root      : 'root',
				pathI18n  : 'folderId',
				folders   : true
			}, (noOffline === null? (opt || {}) : {noOffline : noOffline})),
			addFolders = function(fm, bro, folders) {
				var self = this,
					cnt  = Object.keys(jQuery.isPlainObject(folders)? folders : {}).length,
					select;
				
				bro.next().remove();
				if (cnt) {
					select = jQuery('<select class="ui-corner-all elfinder-tabstop" style="max-width:200px;">').append(
						jQuery(jQuery.map(folders, function(n,i){return '<option value="'+fm.escape((i+'').trim())+'">'+fm.escape(n)+'</option>';}).join(''))
					).on('change click', function(e){
						var node = jQuery(this),
							path = node.val(),
							spn;
						self.inputs.path.val(path);
						if (opts.folders && (e.type === 'change' || node.data('current') !== path)) {
							node.next().remove();
							node.data('current', path);
							if (path != opts.root) {
								spn = spinner();
								if (xhr && xhr.state() === 'pending') {
									fm.abortXHR(xhr, { quiet: true , abort: true });
								}
								node.after(spn);
								xhr = fm.request({
									data : {cmd : 'netmount', protocol: protocol, host: host, user: 'init', path: path, pass: 'folders'},
									preventDefault : true
								}).done(function(data){
									addFolders.call(self, fm, node, data.folders);
								}).always(function() {
									fm.abortXHR(xhr, { quiet: true });
									spn.remove();
								}).xhr;
							}
						}
					});
					bro.after(jQuery('<div/>').append(select))
						.closest('.ui-dialog').trigger('tabstopsInit');
					select.trigger('focus');
				}
			},
			spinner = function() {
				return jQuery('<div class="elfinder-netmount-spinner"/>').append('<span class="elfinder-spinner"/>');
			},
			xhr;
		return {
			vars : {},
			name : name,
			inputs: {
				offline  : jQuery('<input type="checkbox"/>').on('change', function() {
					jQuery(this).parents('table.elfinder-netmount-tb').find('select:first').trigger('change', 'reset');
				}),
				host     : jQuery('<span><span class="elfinder-spinner"/></span><input type="hidden"/>'),
				path     : jQuery('<input type="text" value="'+opts.root+'"/>'),
				user     : jQuery('<input type="hidden"/>'),
				pass     : jQuery('<input type="hidden"/>')
			},
			select: function(fm, ev, d){
				var f = this.inputs,
					oline = f.offline,
					f0 = jQuery(f.host[0]),
					data = d || null;
				this.vars.mbtn = f.host.closest('.ui-dialog').children('.ui-dialog-buttonpane:first').find('button.elfinder-btncnt-0');
				if (! f0.data('inrequest')
						&& (f0.find('span.elfinder-spinner').length
							|| data === 'reset'
							|| (data === 'winfocus' && ! f0.siblings('span.elfinder-button-icon-reload').length))
							)
				{
					if (oline.parent().children().length === 1) {
						f.path.parent().prev().html(fm.i18n(opts.pathI18n));
						oline.attr('title', fm.i18n('offlineAccess'));
						oline.uniqueId().after(jQuery('<label/>').attr('for', oline.attr('id')).html(' '+fm.i18n('offlineAccess')));
					}
					f0.data('inrequest', true).empty().addClass('elfinder-spinner')
						.parent().find('span.elfinder-button-icon').remove();
					fm.request({
						data : {cmd : 'netmount', protocol: protocol, host: host, user: 'init', options: {id: fm.id, offline: oline.prop('checked')? 1:0, pass: f.host[1].value}},
						preventDefault : true
					}).done(function(data){
						f0.removeClass("elfinder-spinner").html(data.body.replace(/\{msg:([^}]+)\}/g, function(whole,s1){return fm.i18n(s1, host);}));
					});
					opts.noOffline && oline.closest('tr').hide();
				} else {
					oline.closest('tr')[(opts.noOffline || f.user.val())? 'hide':'show']();
					f0.data('funcexpup') && f0.data('funcexpup')();
				}
				this.vars.mbtn[jQuery(f.host[1]).val()? 'show':'hide']();
			},
			done: function(fm, data){
				var f = this.inputs,
					p = this.protocol,
					f0 = jQuery(f.host[0]),
					f1 = jQuery(f.host[1]),
					expires = '&nbsp;';
				
				opts.noOffline && f.offline.closest('tr').hide();
				if (data.mode == 'makebtn') {
					f0.removeClass('elfinder-spinner').removeData('expires').removeData('funcexpup');
					f.host.find('input').on('mouseenter mouseleave', function(){jQuery(this).toggleClass('ui-state-hover');});
					f1.val('');
					f.path.val(opts.root).next().remove();
					f.user.val('');
					f.pass.val('');
					! opts.noOffline && f.offline.closest('tr').show();
					this.vars.mbtn.hide();
				} else if (data.mode == 'folders') {
					if (data.folders) {
						addFolders.call(this, fm, f.path.nextAll(':last'), data.folders);
					}
				} else {
					if (data.expires) {
						expires = '()';
						f0.data('expires', data.expires);
					}
					f0.html(host + expires).removeClass('elfinder-spinner');
					if (data.expires) {
						f0.data('funcexpup', function() {
							var rem = Math.floor((f0.data('expires') - (+new Date()) / 1000) / 60);
							if (rem < 3) {
								f0.parent().children('.elfinder-button-icon-reload').click();
							} else {
								f0.text(f0.text().replace(/\(.*\)/, '('+fm.i18n(['minsLeft', rem])+')'));
								setTimeout(function() {
									if (f0.is(':visible')) {
										f0.data('funcexpup')();
									}
								}, 60000);
							}
						});
						f0.data('funcexpup')();
					}
					if (data.reset) {
						p.trigger('change', 'reset');
						return;
					}
					f0.parent().append(jQuery('<span class="elfinder-button-icon elfinder-button-icon-reload" title="'+fm.i18n('reAuth')+'">')
						.on('click', function() {
							f1.val('reauth');
							p.trigger('change', 'reset');
						}));
					f1.val(protocol);
					this.vars.mbtn.show();
					if (data.folders) {
						addFolders.call(this, fm, f.path, data.folders);
					}
					f.user.val('done');
					f.pass.val('done');
					f.offline.closest('tr').hide();
				}
				f0.removeData('inrequest');
			},
			fail: function(fm, err){
				jQuery(this.inputs.host[0]).removeData('inrequest');
				this.protocol.trigger('change', 'reset');
			},
			integrateInfo: opts.integrate
		};
	},
	
	/**
	 * Find cwd's nodes from files
	 * 
	 * @param  Array    files
	 * @param  Object   opts   {firstOnly: true|false}
	 */
	findCwdNodes : function(files, opts) {
		var self    = this,
			cwd     = this.getUI('cwd'),
			cwdHash = this.cwd().hash,
			newItem = jQuery();
		
		opts = opts || {};
		
		jQuery.each(files, function(i, f) {
			if (f.phash === cwdHash || self.searchStatus.state > 1) {
				newItem = newItem.add(self.cwdHash2Elm(f.hash));
				if (opts.firstOnly) {
					return false;
				}
			}
		});
		
		return newItem;
	},
	
	/**
	 * Convert from relative URL to abstract URL based on current URL
	 * 
	 * @param  String  URL
	 * @return String
	 */
	convAbsUrl : function(url) {
		if (url.match(/^http/i)) {
			return url;
		}
		if (url.substr(0,2) === '//') {
			return window.location.protocol + url;
		}
		var root = window.location.protocol + '//' + window.location.host,
			reg  = /[^\/]+\/\.\.\//,
			ret;
		if (url.substr(0, 1) === '/') {
			ret = root + url;
		} else {
			ret = root + window.location.pathname.replace(/\/[^\/]+$/, '/') + url;
		}
		ret = ret.replace('/./', '/');
		while(reg.test(ret)) {
			ret = ret.replace(reg, '');
		}
		return ret;
	},
	
	/**
	 * Is same origin to current site
	 * 
	 * @param  String  check url
	 * @return Boolean
	 */
	isSameOrigin : function (checkUrl) {
		var url;
		checkUrl = this.convAbsUrl(checkUrl);
		if (location.origin && window.URL) {
			try {
				url = new URL(checkUrl);
				return location.origin === url.origin;
			} catch(e) {}
		}
		url = document.createElement('a');
		url.href = checkUrl;
		return location.protocol === url.protocol && location.host === url.host && location.port && url.port;
	},
	
	navHash2Id : function(hash) {
		return this.navPrefix + hash;
	},
	
	navId2Hash : function(id) {
		return typeof(id) == 'string' ? id.substr(this.navPrefix.length) : false;
	},
	
	cwdHash2Id : function(hash) {
		return this.cwdPrefix + hash;
	},
	
	cwdId2Hash : function(id) {
		return typeof(id) == 'string' ? id.substr(this.cwdPrefix.length) : false;
	},
	
	/**
	 * navHash to jQuery element object
	 *
	 * @param      String  hash    nav hash
	 * @return     Object  jQuery element object
	 */
	navHash2Elm : function(hash) {
		return jQuery(document.getElementById(this.navHash2Id(hash)));
	},

	/**
	 * cwdHash to jQuery element object
	 *
	 * @param      String  hash    cwd hash
	 * @return     Object  jQuery element object
	 */
	cwdHash2Elm : function(hash) {
		return jQuery(document.getElementById(this.cwdHash2Id(hash)));
	},

	isInWindow : function(elem, nochkHide) {
		var elm, rect;
		if (! (elm = elem.get(0))) {
			return false;
		}
		if (! nochkHide && elm.offsetParent === null) {
			return false;
		}
		rect = elm.getBoundingClientRect();
		return document.elementFromPoint(rect.left, rect.top)? true : false;
	},
	
	/**
	 * calculate elFinder node z-index
	 * 
	 * @return void
	 */
	zIndexCalc : function() {
		var self = this,
			node = this.getUI(),
			ni = node.css('z-index');
		if (ni && ni !== 'auto' && ni !== 'inherit') {
			self.zIndex = ni;
		} else {
			node.parents().each(function(i, n) {
				var z = jQuery(n).css('z-index');
				if (z !== 'auto' && z !== 'inherit' && (z = parseInt(z))) {
					self.zIndex = z;
					return false;
				}
			});
		}
	},
	
	/**
	 * Load JavaScript files
	 * 
	 * @param  Array    urls      to load JavaScript file URLs
	 * @param  Function callback  call back function on script loaded
	 * @param  Object   opts      Additional options to jQuery.ajax OR {loadType: 'tag'} to load by script tag
	 * @param  Object   check     { obj: (Object)ParentObject, name: (String)"Attribute name", timeout: (Integer)milliseconds }
	 * @return elFinder
	 */
	loadScript : function(urls, callback, opts, check) {
		var defOpts = {
				dataType : 'script',
				cache    : true
			},
			success, cnt, scripts = {}, results = {};
		
		opts = opts || {};
		if (opts.tryRequire && this.hasRequire) {
			require(urls, callback, opts.error);
		} else {
			success = function() {
				var cnt, fi, hasError;
				jQuery.each(results, function(i, status) {
					if (status !== 'success' && status !== 'notmodified') {
						hasError = true;
						return false;
					}
				});
				if (!hasError) {
					if (jQuery.isFunction(callback)) {
						if (check) {
							if (typeof check.obj[check.name] === 'undefined') {
								cnt = check.timeout? (check.timeout / 10) : 1;
								fi = setInterval(function() {
									if (--cnt < 0 || typeof check.obj[check.name] !== 'undefined') {
										clearInterval(fi);
										callback();
									}
								}, 10);
							} else {
								callback();
							}
						} else {
							callback();
						}
					}
				} else {
					if (opts.error && jQuery.isFunction(opts.error)) {
						opts.error({ loadResults: results });
					}
				}
			};

			if (opts.loadType === 'tag') {
				jQuery('head > script').each(function() {
					scripts[this.src] = this;
				});
				cnt = urls.length;
				jQuery.each(urls, function(i, url) {
					var done = false,
						script;
					
					if (scripts[url]) {
						results[i] = scripts[url]._error || 'success';
						(--cnt < 1) && success();
					} else {
						script = document.createElement('script');
						script.charset = opts.charset || 'UTF-8';
						jQuery('head').append(script);
						script.onload = script.onreadystatechange = function() {
							if ( !done && (!this.readyState ||
									this.readyState === 'loaded' || this.readyState === 'complete') ) {
								done = true;
								results[i] = 'success';
								(--cnt < 1) && success();
							}
						};
						script.onerror = function(err) {
							results[i] = script._error = (err && err.type)? err.type : 'error';
							(--cnt < 1) && success();
						};
						script.src = url;
					}
				});
			} else {
				opts = jQuery.isPlainObject(opts)? Object.assign(defOpts, opts) : defOpts;
				cnt = 0;
				(function appendScript(d, status) {
					if (d !== void(0)) {
						results[cnt++] = status;
					}
					if (urls.length) {
						jQuery.ajax(Object.assign({}, opts, {
							url: urls.shift(),
							success: appendScript,
							error: appendScript
						}));
					} else {
						success();
					}
				})();
			}
		}
		return this;
	},
	
	/**
	 * Load CSS files
	 * 
	 * @param  Array    to load CSS file URLs
	 * @param  Object   options
	 * @return elFinder
	 */
	loadCss : function(urls, opts) {
		var self = this,
			clName, dfds;
		if (typeof urls === 'string') {
			urls = [ urls ];
		}
		if (opts) {
			if (opts.className) {
				clName = opts.className;
			}
			if (opts.dfd && opts.dfd.promise) {
				dfds = [];
			}
		}
		jQuery.each(urls, function(i, url) {
			var link, df;
			url = self.convAbsUrl(url).replace(/^https?:/i, '');
			if (dfds) {
				dfds[i] = jQuery.Deferred();
			}
			if (! jQuery("head > link[href='+url+']").length) {
				link = document.createElement('link');
				link.type = 'text/css';
				link.rel = 'stylesheet';
				link.href = url;
				if (clName) {
					link.className = clName;
				}
				if (dfds) {
					link.onload = function() {
						dfds[i].resolve();
					};
					link.onerror = function() {
						dfds[i].reject();
					};
				}
				jQuery('head').append(link);
			} else {
				dfds && dfds[i].resolve();
			}
		});
		if (dfds) {
			jQuery.when.apply(null, dfds).done(function() {
				opts.dfd.resolve();
			}).fail(function() {
				opts.dfd.reject();
			});
		}
		return this;
	},
	
	/**
	 * Abortable async job performer
	 * 
	 * @param func Function
	 * @param arr  Array
	 * @param opts Object
	 * 
	 * @return Object jQuery.Deferred that has an extended method _abort()
	 */
	asyncJob : function(func, arr, opts) {
		var dfrd = jQuery.Deferred(),
			abortFlg = false,
			parms = Object.assign({
				interval : 0,
				numPerOnce : 1
			}, opts || {}),
			resArr = [],
			vars =[],
			curVars = [],
			exec,
			tm;
		
		dfrd._abort = function(resolve) {
			tm && clearTimeout(tm);
			vars = [];
			abortFlg = true;
			if (dfrd.state() === 'pending') {
				dfrd[resolve? 'resolve' : 'reject'](resArr);
			}
		};
		
		dfrd.fail(function() {
			dfrd._abort();
		}).always(function() {
			dfrd._abort = function() {};
		});

		if (typeof func === 'function' && Array.isArray(arr)) {
			vars = arr.concat();
			exec = function() {
				var i, len, res;
				if (abortFlg) {
					return;
				}
				curVars = vars.splice(0, parms.numPerOnce);
				len = curVars.length;
				for (i = 0; i < len; i++) {
					if (abortFlg) {
						break;
					}
					res = func(curVars[i]);
					(res !== null) && resArr.push(res);
				}
				if (abortFlg) {
					return;
				}
				if (vars.length) {
					tm = setTimeout(exec, parms.interval);
				} else {
					dfrd.resolve(resArr);
				}
			};
			if (vars.length) {
				tm = setTimeout(exec, 0);
			} else {
				dfrd.resolve(resArr);
			}
		} else {
			dfrd.reject();
		}
		return dfrd;
	},
	
	getSize : function(targets) {
		var self = this,
			reqs = [],
			tgtlen = targets.length,
			dfrd = jQuery.Deferred().fail(function() {
				jQuery.each(reqs, function(i, req) {
					if (req) {
						req.syncOnFail && req.syncOnFail(false);
						req.reject();
					}
				});
			}),
			getLeafRoots = function(file) {
				var targets = [];
				if (file.mime === 'directory') {
					jQuery.each(self.leafRoots, function(hash, roots) {
						var phash;
						if (hash === file.hash) {
							targets.push.apply(targets, roots);
						} else {
							phash = (self.file(hash) || {}).phash;
							while(phash) {
								if (phash === file.hash) {
									targets.push.apply(targets, roots);
								}
								phash = (self.file(phash) || {}).phash;
							}
						}
					});
				}
				return targets;
			},
			checkPhash = function(hash) {
				var dfd = jQuery.Deferred(),
					dir = self.file(hash),
					target = dir? dir.phash : hash;
				if (target && ! self.file(target)) {
					self.request({
						data : {
							cmd    : 'parents',
							target : target
						},
						preventFail : true
					}).done(function() {
						self.one('parentsdone', function() {
							dfd.resolve();
						});
					}).fail(function() {
						dfd.resolve();
					});
				} else {
					dfd.resolve();
				}
				return dfd;
			},
			cache = function() {
				var dfd = jQuery.Deferred(),
					cnt = Object.keys(self.leafRoots).length;
				
				if (cnt > 0) {
					jQuery.each(self.leafRoots, function(hash) {
						checkPhash(hash).done(function() {
							--cnt;
							if (cnt < 1) {
								dfd.resolve();
							}
						});
					});
				} else {
					dfd.resolve();
				}
				return dfd;
			};

		self.autoSync('stop');
		cache().done(function() {
			var files = [], grps = {}, dfds = [], cache = [], singles = {};
			
			jQuery.each(targets, function() {
				files.push.apply(files, getLeafRoots(self.file(this)));
			});
			targets.push.apply(targets, files);
			
			jQuery.each(targets, function() {
				var root = self.root(this),
					file = self.file(this);
				if (file && (file.sizeInfo || file.mime !== 'directory')) {
					cache.push(jQuery.Deferred().resolve(file.sizeInfo? file.sizeInfo : {size: file.size, dirCnt: 0, fileCnt : 1}));
				} else {
					if (! grps[root]) {
						grps[root] = [ this ];
					} else {
						grps[root].push(this);
					}
				}
			});
			
			jQuery.each(grps, function() {
				var idx = dfds.length;
				if (this.length === 1) {
					singles[idx] = this[0];
				}
				dfds.push(self.request({
					data : {cmd : 'size', targets : this},
					preventDefault : true
				}));
			});
			reqs.push.apply(reqs, dfds);
			dfds.push.apply(dfds, cache);
			
			jQuery.when.apply($, dfds).fail(function() {
				dfrd.reject();
			}).done(function() {
				var cache = function(h, data) {
						var file;
						if (file = self.file(h)) {
							file.sizeInfo = { isCache: true };
							jQuery.each(['size', 'dirCnt', 'fileCnt'], function() {
								file.sizeInfo[this] = data[this] || 0;
							});
							file.size = parseInt(file.sizeInfo.size);
							changed.push(file);
						}
					},
					size = 0,
					fileCnt = 0,
					dirCnt = 0,
					argLen = arguments.length,
					cnts = [],
					cntsTxt = '',
					changed = [],
					i, file, data;
				
				for (i = 0; i < argLen; i++) {
					data = arguments[i];
					file = null;
					if (!data.isCache) {
						if (singles[i] && (file = self.file(singles[i]))) {
							cache(singles[i], data);
						} else if (data.sizes && jQuery.isPlainObject(data.sizes)) {
							jQuery.each(data.sizes, function(h, sizeInfo) {
								cache(h, sizeInfo);
							});
						}
					}
					size += parseInt(data.size);
					if (fileCnt !== false) {
						if (typeof data.fileCnt === 'undefined') {
							fileCnt = false;
						}
						fileCnt += parseInt(data.fileCnt || 0);
					}
					if (dirCnt !== false) {
						if (typeof data.dirCnt === 'undefined') {
							dirCnt = false;
						}
						dirCnt += parseInt(data.dirCnt || 0);
					}
				}
				changed.length && self.change({changed: changed});
				
				if (dirCnt !== false){
					cnts.push(self.i18n('folders') + ': ' + (dirCnt - (tgtlen > 1? 0 : 1)));
				}
				if (fileCnt !== false){
					cnts.push(self.i18n('files') + ': ' + fileCnt);
				}
				if (cnts.length) {
					cntsTxt = '<br>' + cnts.join(', ');
				}
				dfrd.resolve({
					size: size,
					fileCnt: fileCnt,
					dirCnt: dirCnt,
					formated: (size >= 0 ? self.formatSize(size) : self.i18n('unknown')) + cntsTxt
				});
			});
			
			self.autoSync();
		});
		
		return dfrd;
	},
	
	/**
	 * Gets the theme object by settings of options.themes
	 *
	 * @param  String  themeid  The themeid
	 * @return Object  jQuery.Deferred
	 */
	getTheme : function(themeid) {
		var self = this,
			dfd = jQuery.Deferred(),
			absUrl = function(url, base) {
				if (!base) {
					base = self.convAbsUrl(self.baseUrl);
				}
				if (Array.isArray(url)) {
					return jQuery.map(url, function(v) {
						return absUrl(v, base);
					});
				} else {
					return url.match(/^(?:http|\/\/)/i)? url : base + url.replace(/^(?:\.\/|\/)/, '');
				}
			},
			themeObj, m;
		if (themeid && (themeObj = self.options.themes[themeid])) {
			if (typeof themeObj === 'string') {
				url = absUrl(themeObj);
				if (m = url.match(/^(.+\/)[^/]+\.json$/i)) {
					jQuery.getJSON(url).done(function(data) {
						themeObj = data;
						themeObj.id = themeid;
						if (themeObj.cssurls) {
							themeObj.cssurls = absUrl(themeObj.cssurls, m[1]);
						}
						dfd.resolve(themeObj);
					}).fail(function() {
						dfd.reject();
					});
				} else {
					dfd.resolve({
						id: themeid,
						name: themeid,
						cssurls: [url]
					});
				}
			} else if (jQuery.isPlainObject(themeObj) && themeObj.cssurls) {
				themeObj.id = themeid;
				themeObj.cssurls = absUrl(themeObj.cssurls);
				if (!Array.isArray(themeObj.cssurls)) {
					themeObj.cssurls = [themeObj.cssurls];
				}
				if (!themeObj.name) {
					themeObj.name = themeid;
				}
				dfd.resolve(themeObj);
			} else {
				dfd.reject();
			}
		} else {
			dfd.reject();
		}
		return dfd;
	},

	/**
	 * Change current theme
	 *
	 * @param  String  themeid  The themeid
	 * @return Object  this elFinder instance
	 */
	changeTheme : function(themeid) {
		var self = this;
		if (themeid) {
			if (self.options.themes[themeid] && (!self.theme || self.theme.id !== themeid)) {
				self.getTheme(themeid).done(function(themeObj) {
					if (themeObj.cssurls) {
						jQuery('head>link.elfinder-theme-ext').remove();
						self.loadCss(themeObj.cssurls, {
							className: 'elfinder-theme-ext',
							dfd: jQuery.Deferred().done(function() {
								self.theme = themeObj;
								self.trigger && self.trigger('themechange');
							})
						});
					}
				});
			} else if (themeid === 'default' && self.theme) {
				jQuery('head>link.elfinder-theme-ext').remove();
				self.theme = null;
				self.trigger && self.trigger('themechange');
			}
		}
		return this;
	},

	/**
	 * Apply leaf root stats to target directory
	 *
	 * @param      object     dir     object of target directory
	 * @param      boolean    update  is force update
	 * 
	 * @return     boolean    dir object was chenged 
	 */
	applyLeafRootStats : function(dir, update) {
		var self = this,
			prev = update? dir : (self.file(dir.hash) || dir),
			prevTs = prev.ts,
			change = false;
		// backup original stats
		if (update || !dir._realStats) {
			dir._realStats = {
				locked: dir.locked || 0,
				dirs: dir.dirs || 0,
				ts: dir.ts
			};
		}
		// set lock
		dir.locked = 1;
		if (!prev.locked) {
			change = true;
		}
		// has leaf root to `dirs: 1`
		dir.dirs = 1;
		if (!prev.dirs) {
			change = true;
		}
		// set ts
		jQuery.each(self.leafRoots[dir.hash], function() {
			var f = self.file(this);
			if (f && f.ts && (dir.ts || 0) < f.ts) {
				dir.ts = f.ts;
			}
		});
		if (prevTs !== dir.ts) {
			change = true;
		}

		return change;
	},

	/**
	 * To aborted XHR object
	 * 
	 * @param Object xhr
	 * @param Object opts
	 * 
	 * @return void
	 */
	abortXHR : function(xhr, o) {
		var opts = o || {};
		
		if (xhr) {
			opts.quiet && (xhr.quiet = true);
			if (opts.abort && xhr._requestId) {
				this.request({
					data: {
						cmd: 'abort',
						id: xhr._requestId
					},
					preventDefault: true
				});
			}
			xhr.abort();
			xhr = void 0;
		}
	},

	/**
	 * Gets the request identifier
	 *
	 * @return  String  The request identifier.
	 */
	getRequestId : function() {
		return (+ new Date()).toString(16) + Math.floor(1000 * Math.random()).toString(16);
	},
	
	/**
	 * Flip key and value of array or object
	 * 
	 * @param  Array | Object  { a: 1, b: 1, c: 2 }
	 * @param  Mixed           Static value
	 * @return Object          { 1: "b", 2: "c" }
	 */
	arrayFlip : function (trans, val) {
		var key,
			tmpArr = {},
			isArr = jQuery.isArray(trans);
		for (key in trans) {
			if (isArr || trans.hasOwnProperty(key)) {
				tmpArr[trans[key]] = val || key;
			}
		}
		return tmpArr;
	},
	
	/**
	 * Return array ["name without extention", "extention"]
	 * 
	 * @param String name
	 * 
	 * @return Array
	 * 
	 */
	splitFileExtention : function(name) {
		var m;
		if (m = name.match(/^(.+?)?\.((?:tar\.(?:gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(?:gz|bz2)|[a-z0-9]{1,10})$/i)) {
			if (typeof m[1] === 'undefined') {
				m[1] = '';
			}
			return [m[1], m[2]];
		} else {
			return [name, ''];
		}
	},
	
	/**
	 * Slice the ArrayBuffer by sliceSize
	 *
	 * @param      arraybuffer  arrayBuffer  The array buffer
	 * @param      Number       sliceSize    The slice size
	 * @return     Array   Array of sleced arraybuffer
	 */
	sliceArrayBuffer : function(arrayBuffer, sliceSize) {
		var segments= [],
			fi = 0;
		while(fi * sliceSize < arrayBuffer.byteLength){
			segments.push(arrayBuffer.slice(fi * sliceSize, (fi + 1) * sliceSize));
			fi++;
		}
		return segments;
	},

	arrayBufferToBase64 : function(ab) {
		if (!window.btoa) {
			return '';
		}
		var dView = new Uint8Array(ab), // Get a byte view
			arr = Array.prototype.slice.call(dView), // Create a normal array
			arr1 = arr.map(function(item) {
				return String.fromCharCode(item); // Convert
			});
	    return window.btoa(arr1.join('')); // Form a string
	},

	log : function(m) { window.console && window.console.log && window.console.log(m); return this; },
	
	debug : function(type, m) {
		var d = this.options.debug;

		if (d && (d === 'all' || d[type])) {
			window.console && window.console.log && window.console.log('elfinder debug: ['+type+'] ['+this.id+']', m);
		} 
		
		if (type === 'backend-error') {
			if (! this.cwd().hash || (d && (d === 'all' || d['backend-error']))) {
				m = Array.isArray(m)? m : [ m ];
				this.error(m);
			}
		} else if (type === 'backend-debug') {
			this.trigger('backenddebug', m);
		}
		
		return this;
	},
	time : function(l) { window.console && window.console.time && window.console.time(l); },
	timeEnd : function(l) { window.console && window.console.timeEnd && window.console.timeEnd(l); }
	

};

/**
 * for conpat ex. ie8...
 *
 * Object.keys() - JavaScript | MDN
 * https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
 */
if (!Object.keys) {
	Object.keys = (function () {
		var hasOwnProperty = Object.prototype.hasOwnProperty,
				hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
				dontEnums = [
					'toString',
					'toLocaleString',
					'valueOf',
					'hasOwnProperty',
					'isPrototypeOf',
					'propertyIsEnumerable',
					'constructor'
				],
				dontEnumsLength = dontEnums.length;

		return function (obj) {
			if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object');

			var result = [];

			for (var prop in obj) {
				if (hasOwnProperty.call(obj, prop)) result.push(prop);
			}

			if (hasDontEnumBug) {
				for (var i=0; i < dontEnumsLength; i++) {
					if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
				}
			}
			return result;
		};
	})();
}
// Array.isArray
if (!Array.isArray) {
	Array.isArray = function(arr) {
		return jQuery.isArray(arr);
	};
}
// Object.assign
if (!Object.assign) {
	Object.assign = function() {
		return jQuery.extend.apply(null, arguments);
	};
}
// String.repeat
if (!String.prototype.repeat) {
	String.prototype.repeat = function(count) {
		'use strict';
		if (this == null) {
			throw new TypeError('can\'t convert ' + this + ' to object');
		}
		var str = '' + this;
		count = +count;
		if (count != count) {
			count = 0;
		}
		if (count < 0) {
			throw new RangeError('repeat count must be non-negative');
		}
		if (count == Infinity) {
			throw new RangeError('repeat count must be less than infinity');
		}
		count = Math.floor(count);
		if (str.length == 0 || count == 0) {
			return '';
		}
		// Ensuring count is a 31-bit integer allows us to heavily optimize the
		// main part. But anyway, most current (August 2014) browsers can't handle
		// strings 1 << 28 chars or longer, so:
		if (str.length * count >= 1 << 28) {
			throw new RangeError('repeat count must not overflow maximum string size');
		}
		var rpt = '';
		for (var i = 0; i < count; i++) {
			rpt += str;
		}
		return rpt;
	};
}
// String.trim
if (!String.prototype.trim) {
	String.prototype.trim = function() {
		return this.replace(/^\s+|\s+$/g, '');
	};
}
// Array.apply
(function () {
	try {
		Array.apply(null, {});
		return;
	} catch (e) { }

	var toString = Object.prototype.toString,
		arrayType = '[object Array]',
		_apply = Function.prototype.apply,
		slice = /*@cc_on @if (@_jscript_version <= 5.8)
			function () {
				var a = [], i = this.length;
				while (i-- > 0) a[i] = this[i];
				return a;
			}@else@*/Array.prototype.slice/*@end@*/;

	Function.prototype.apply = function apply(thisArg, argArray) {
		return _apply.call(this, thisArg,
			toString.call(argArray) === arrayType ? argArray : slice.call(argArray));
	};
})();
// Array.from
if (!Array.from) {
	Array.from = function(obj) {
		return obj.length === 1 ? [obj[0]] : Array.apply(null, obj);
	};
}
// window.requestAnimationFrame and window.cancelAnimationFrame
if (!window.cancelAnimationFrame) {
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
 
    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());
}


/*
 * File: /js/elFinder.version.js
 */

/**
 * Application version
 *
 * @type String
 **/
elFinder.prototype.version = '2.1.49';



/*
 * File: /js/jquery.elfinder.js
 */

/*** jQuery UI droppable performance tune for elFinder ***/
(function(){
if (jQuery.ui) {
	if (jQuery.ui.ddmanager) {
		var origin = jQuery.ui.ddmanager.prepareOffsets;
		jQuery.ui.ddmanager.prepareOffsets = function( t, event ) {
			var isOutView = function(elem) {
				if (elem.is(':hidden')) {
					return true;
				}
				var rect = elem[0].getBoundingClientRect();
				return document.elementFromPoint(rect.left, rect.top) || document.elementFromPoint(rect.left + rect.width, rect.top + rect.height)? false : true;
			};
			
			if (event.type === 'mousedown' || t.options.elfRefresh) {
				var i, d,
				m = jQuery.ui.ddmanager.droppables[ t.options.scope ] || [],
				l = m.length;
				for ( i = 0; i < l; i++ ) {
					d = m[ i ];
					if (d.options.autoDisable && (!d.options.disabled || d.options.autoDisable > 1)) {
						d.options.disabled = isOutView(d.element);
						d.options.autoDisable = d.options.disabled? 2 : 1;
					}
				}
			}
			
			// call origin function
			return origin( t, event );
		};
	}
}
})();

 /**
 *
 * jquery.binarytransport.js
 *
 * @description. jQuery ajax transport for making binary data type requests.
 * @version 1.0 
 * @author Henry Algus <henryalgus@gmail.com>
 *
 */

// use this transport for "binary" data type
jQuery.ajaxTransport('+binary', function(options, originalOptions, jqXHR) {
	// check for conditions and support for blob / arraybuffer response type
	if (window.FormData && ((options.dataType && (options.dataType == 'binary')) || (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || (window.Blob && options.data instanceof Blob)))))
	{
		var xhr;
		return {
			// create new XMLHttpRequest
			send: function(headers, callback){
				// setup all variables
				xhr = new XMLHttpRequest();
				var url = options.url,
					type = options.type,
					async = options.async || true,
					// blob or arraybuffer. Default is blob
					dataType = options.responseType || 'blob',
					data = options.data || null,
					username = options.username,
					password = options.password;
					
				xhr.addEventListener('load', function(){
					var data = {};
					data[options.dataType] = xhr.response;
					// make callback and send data
					callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());
				});

				xhr.open(type, url, async, username, password);
				
				// setup custom headers
				for (var i in headers ) {
					xhr.setRequestHeader(i, headers[i] );
				}

				// setuo xhrFields
				if (options.xhrFields) {
					for (var key in options.xhrFields) {
						if (key in xhr) {
							xhr[key] = options.xhrFields[key];
						}
					}
				}

				xhr.responseType = dataType;
				xhr.send(data);
			},
			abort: function(){
				xhr.abort();
			}
		};
	}
});

/*!
 * jQuery UI Touch Punch 0.2.3
 *
 * Copyright 2011–2014, Dave Furfero
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Depends:
 *	jquery.ui.widget.js
 *	jquery.ui.mouse.js
 */
(function ($) {

  // Detect touch support
  jQuery.support.touch = 'ontouchend' in document;

  // Ignore browsers without touch support
  if (!jQuery.support.touch) {
	return;
  }

  var mouseProto = jQuery.ui.mouse.prototype,
	  _mouseInit = mouseProto._mouseInit,
	  _mouseDestroy = mouseProto._mouseDestroy,
	  touchHandled,
	  posX, posY;

  /**
   * Simulate a mouse event based on a corresponding touch event
   * @param {Object} event A touch event
   * @param {String} simulatedType The corresponding mouse event
   */
  function simulateMouseEvent (event, simulatedType) {

	// Ignore multi-touch events
	if (event.originalEvent.touches.length > 1) {
	  return;
	}

	if (! jQuery(event.currentTarget).hasClass('touch-punch-keep-default')) {
		event.preventDefault();
	}

	var touch = event.originalEvent.changedTouches[0],
		simulatedEvent = document.createEvent('MouseEvents');
	
	// Initialize the simulated mouse event using the touch event's coordinates
	simulatedEvent.initMouseEvent(
	  simulatedType,	// type
	  true,				// bubbles					  
	  true,				// cancelable				  
	  window,			// view						  
	  1,				// detail					  
	  touch.screenX,	// screenX					  
	  touch.screenY,	// screenY					  
	  touch.clientX,	// clientX					  
	  touch.clientY,	// clientY					  
	  false,			// ctrlKey					  
	  false,			// altKey					  
	  false,			// shiftKey					  
	  false,			// metaKey					  
	  0,				// button					  
	  null				// relatedTarget			  
	);

	// Dispatch the simulated event to the target element
	event.target.dispatchEvent(simulatedEvent);
  }

  /**
   * Handle the jQuery UI widget's touchstart events
   * @param {Object} event The widget element's touchstart event
   */
  mouseProto._touchStart = function (event) {

	var self = this;

	// Ignore the event if another widget is already being handled
	if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
	  return;
	}

	// Track element position to avoid "false" move
	posX = event.originalEvent.changedTouches[0].screenX.toFixed(0);
	posY = event.originalEvent.changedTouches[0].screenY.toFixed(0);

	// Set the flag to prevent other widgets from inheriting the touch event
	touchHandled = true;

	// Track movement to determine if interaction was a click
	self._touchMoved = false;

	// Simulate the mouseover event
	simulateMouseEvent(event, 'mouseover');

	// Simulate the mousemove event
	simulateMouseEvent(event, 'mousemove');

	// Simulate the mousedown event
	simulateMouseEvent(event, 'mousedown');
  };

  /**
   * Handle the jQuery UI widget's touchmove events
   * @param {Object} event The document's touchmove event
   */
  mouseProto._touchMove = function (event) {

	// Ignore event if not handled
	if (!touchHandled) {
	  return;
	}

	// Ignore if it's a "false" move (position not changed)
	var x = event.originalEvent.changedTouches[0].screenX.toFixed(0);
	var y = event.originalEvent.changedTouches[0].screenY.toFixed(0);
	// Ignore if it's a "false" move (position not changed)
	if (Math.abs(posX - x) <= 4 && Math.abs(posY - y) <= 4) {
		return;
	}

	// Interaction was not a click
	this._touchMoved = true;

	// Simulate the mousemove event
	simulateMouseEvent(event, 'mousemove');
  };

  /**
   * Handle the jQuery UI widget's touchend events
   * @param {Object} event The document's touchend event
   */
  mouseProto._touchEnd = function (event) {

	// Ignore event if not handled
	if (!touchHandled) {
	  return;
	}

	// Simulate the mouseup event
	simulateMouseEvent(event, 'mouseup');

	// Simulate the mouseout event
	simulateMouseEvent(event, 'mouseout');

	// If the touch interaction did not move, it should trigger a click
	if (!this._touchMoved) {

	  // Simulate the click event
	  simulateMouseEvent(event, 'click');
	}

	// Unset the flag to allow other widgets to inherit the touch event
	touchHandled = false;
	this._touchMoved = false;
  };

  /**
   * A duck punch of the jQuery.ui.mouse _mouseInit method to support touch events.
   * This method extends the widget with bound touch event handlers that
   * translate touch events to mouse events and pass them to the widget's
   * original mouse event handling methods.
   */
  mouseProto._mouseInit = function () {
	
	var self = this;

	if (self.element.hasClass('touch-punch')) {
		// Delegate the touch handlers to the widget's element
		self.element.on({
		  touchstart: jQuery.proxy(self, '_touchStart'),
		  touchmove: jQuery.proxy(self, '_touchMove'),
		  touchend: jQuery.proxy(self, '_touchEnd')
		});
	}

	// Call the original jQuery.ui.mouse init method
	_mouseInit.call(self);
  };

  /**
   * Remove the touch event handlers
   */
  mouseProto._mouseDestroy = function () {
	
	var self = this;

	if (self.element.hasClass('touch-punch')) {
		// Delegate the touch handlers to the widget's element
		self.element.off({
		  touchstart: jQuery.proxy(self, '_touchStart'),
		  touchmove: jQuery.proxy(self, '_touchMove'),
		  touchend: jQuery.proxy(self, '_touchEnd')
		});
	}

	// Call the original jQuery.ui.mouse destroy method
	_mouseDestroy.call(self);
  };

})(jQuery);

jQuery.fn.elfinder = function(o, o2) {
	
	if (o === 'instance') {
		return this.getElFinder();
	}
	
	return this.each(function() {
		
		var cmd          = typeof o  === 'string'  ? o  : '',
			bootCallback = typeof o2 === 'function'? o2 : void(0),
			opts;
		
		if (!this.elfinder) {
			if (jQuery.isPlainObject(o)) {
				new elFinder(this, o, bootCallback);
			}
		} else {
			switch(cmd) {
				case 'close':
				case 'hide':
					this.elfinder.hide();
					break;
					
				case 'open':
				case 'show':
					this.elfinder.show();
					break;
					
				case 'destroy':
					this.elfinder.destroy();
					break;
				
				case 'reload':
				case 'restart':
					if (this.elfinder) {
						opts = this.elfinder.options;
						bootCallback = this.elfinder.bootCallback;
						this.elfinder.destroy();
						new elFinder(this, jQuery.extend(true, opts, jQuery.isPlainObject(o2)? o2 : {}), bootCallback);
					}
					break;
			}
		}
	});
};

jQuery.fn.getElFinder = function() {
	var instance;
	
	this.each(function() {
		if (this.elfinder) {
			instance = this.elfinder;
			return false;
		}
	});
	
	return instance;
};

jQuery.fn.elfUiWidgetInstance = function(name) {
	try {
		return this[name]('instance');
	} catch(e) {
		// fallback for jQuery UI < 1.11
		var data = this.data('ui-' + name);
		if (data && typeof data === 'object' && data.widgetFullName === 'ui-' + name) {
			return data;
		}
		return null;
	}
};

// function scrollRight
if (! jQuery.fn.scrollRight) {
	jQuery.fn.extend({
		scrollRight: function (val) {
			var node = this.get(0);
			if (val === undefined) {
				return Math.max(0, node.scrollWidth - (node.scrollLeft + node.clientWidth));
			}
			return this.scrollLeft(node.scrollWidth - node.clientWidth - val);
		}
	});
}

// function scrollBottom
if (! jQuery.fn.scrollBottom) {
	jQuery.fn.extend({
		scrollBottom: function(val) { 
			var node = this.get(0);
			if (val === undefined) {
				return Math.max(0, node.scrollHeight - (node.scrollTop + node.clientHeight));
			}
			return this.scrollTop(node.scrollHeight - node.clientHeight - val);
		}
	});
}


/*
 * File: /js/elFinder.mimetypes.js
 */

elFinder.prototype.mimeTypes = {"application\/x-executable":"exe","application\/x-jar":"jar","application\/x-gzip":"gz","application\/x-bzip2":"tbz","application\/x-rar":"rar","text\/x-php":"php","text\/javascript":"js","application\/rtfd":"rtfd","text\/x-python":"py","text\/x-ruby":"rb","text\/x-shellscript":"sh","text\/x-perl":"pl","text\/xml":"xml","text\/x-csrc":"c","text\/x-chdr":"h","text\/x-c++src":"cpp","text\/x-c++hdr":"hh","text\/x-markdown":"md","text\/x-yaml":"yml","image\/x-ms-bmp":"bmp","image\/x-targa":"tga","image\/xbm":"xbm","image\/pxm":"pxm","audio\/wav":"wav","video\/x-dv":"dv","video\/x-ms-wmv":"wm","video\/ogg":"ogm","video\/MP2T":"m2ts","application\/x-mpegURL":"m3u8","application\/dash+xml":"mpd","application\/andrew-inset":"ez","application\/applixware":"aw","application\/atom+xml":"atom","application\/atomcat+xml":"atomcat","application\/atomsvc+xml":"atomsvc","application\/ccxml+xml":"ccxml","application\/cdmi-capability":"cdmia","application\/cdmi-container":"cdmic","application\/cdmi-domain":"cdmid","application\/cdmi-object":"cdmio","application\/cdmi-queue":"cdmiq","application\/cu-seeme":"cu","application\/davmount+xml":"davmount","application\/docbook+xml":"dbk","application\/dssc+der":"dssc","application\/dssc+xml":"xdssc","application\/ecmascript":"ecma","application\/emma+xml":"emma","application\/epub+zip":"epub","application\/exi":"exi","application\/font-tdpfr":"pfr","application\/gml+xml":"gml","application\/gpx+xml":"gpx","application\/gxf":"gxf","application\/hyperstudio":"stk","application\/inkml+xml":"ink","application\/ipfix":"ipfix","application\/java-serialized-object":"ser","application\/java-vm":"class","application\/json":"json","application\/jsonml+json":"jsonml","application\/lost+xml":"lostxml","application\/mac-binhex40":"hqx","application\/mac-compactpro":"cpt","application\/mads+xml":"mads","application\/marc":"mrc","application\/marcxml+xml":"mrcx","application\/mathematica":"ma","application\/mathml+xml":"mathml","application\/mbox":"mbox","application\/mediaservercontrol+xml":"mscml","application\/metalink+xml":"metalink","application\/metalink4+xml":"meta4","application\/mets+xml":"mets","application\/mods+xml":"mods","application\/mp21":"m21","application\/mp4":"mp4s","application\/msword":"doc","application\/mxf":"mxf","application\/octet-stream":"bin","application\/oda":"oda","application\/oebps-package+xml":"opf","application\/ogg":"ogx","application\/omdoc+xml":"omdoc","application\/onenote":"onetoc","application\/oxps":"oxps","application\/patch-ops-error+xml":"xer","application\/pdf":"pdf","application\/pgp-encrypted":"pgp","application\/pgp-signature":"asc","application\/pics-rules":"prf","application\/pkcs10":"p10","application\/pkcs7-mime":"p7m","application\/pkcs7-signature":"p7s","application\/pkcs8":"p8","application\/pkix-attr-cert":"ac","application\/pkix-cert":"cer","application\/pkix-crl":"crl","application\/pkix-pkipath":"pkipath","application\/pkixcmp":"pki","application\/pls+xml":"pls","application\/postscript":"ai","application\/prs.cww":"cww","application\/pskc+xml":"pskcxml","application\/rdf+xml":"rdf","application\/reginfo+xml":"rif","application\/relax-ng-compact-syntax":"rnc","application\/resource-lists+xml":"rl","application\/resource-lists-diff+xml":"rld","application\/rls-services+xml":"rs","application\/rpki-ghostbusters":"gbr","application\/rpki-manifest":"mft","application\/rpki-roa":"roa","application\/rsd+xml":"rsd","application\/rss+xml":"rss","application\/rtf":"rtf","application\/sbml+xml":"sbml","application\/scvp-cv-request":"scq","application\/scvp-cv-response":"scs","application\/scvp-vp-request":"spq","application\/scvp-vp-response":"spp","application\/sdp":"sdp","application\/set-payment-initiation":"setpay","application\/set-registration-initiation":"setreg","application\/shf+xml":"shf","application\/smil+xml":"smi","application\/sparql-query":"rq","application\/sparql-results+xml":"srx","application\/srgs":"gram","application\/srgs+xml":"grxml","application\/sru+xml":"sru","application\/ssdl+xml":"ssdl","application\/ssml+xml":"ssml","application\/tei+xml":"tei","application\/thraud+xml":"tfi","application\/timestamped-data":"tsd","application\/vnd.3gpp.pic-bw-large":"plb","application\/vnd.3gpp.pic-bw-small":"psb","application\/vnd.3gpp.pic-bw-var":"pvb","application\/vnd.3gpp2.tcap":"tcap","application\/vnd.3m.post-it-notes":"pwn","application\/vnd.accpac.simply.aso":"aso","application\/vnd.accpac.simply.imp":"imp","application\/vnd.acucobol":"acu","application\/vnd.acucorp":"atc","application\/vnd.adobe.air-application-installer-package+zip":"air","application\/vnd.adobe.formscentral.fcdt":"fcdt","application\/vnd.adobe.fxp":"fxp","application\/vnd.adobe.xdp+xml":"xdp","application\/vnd.adobe.xfdf":"xfdf","application\/vnd.ahead.space":"ahead","application\/vnd.airzip.filesecure.azf":"azf","application\/vnd.airzip.filesecure.azs":"azs","application\/vnd.amazon.ebook":"azw","application\/vnd.americandynamics.acc":"acc","application\/vnd.amiga.ami":"ami","application\/vnd.android.package-archive":"apk","application\/vnd.anser-web-certificate-issue-initiation":"cii","application\/vnd.anser-web-funds-transfer-initiation":"fti","application\/vnd.antix.game-component":"atx","application\/vnd.apple.installer+xml":"mpkg","application\/vnd.aristanetworks.swi":"swi","application\/vnd.astraea-software.iota":"iota","application\/vnd.audiograph":"aep","application\/vnd.blueice.multipass":"mpm","application\/vnd.bmi":"bmi","application\/vnd.businessobjects":"rep","application\/vnd.chemdraw+xml":"cdxml","application\/vnd.chipnuts.karaoke-mmd":"mmd","application\/vnd.cinderella":"cdy","application\/vnd.claymore":"cla","application\/vnd.cloanto.rp9":"rp9","application\/vnd.clonk.c4group":"c4g","application\/vnd.cluetrust.cartomobile-config":"c11amc","application\/vnd.cluetrust.cartomobile-config-pkg":"c11amz","application\/vnd.commonspace":"csp","application\/vnd.contact.cmsg":"cdbcmsg","application\/vnd.cosmocaller":"cmc","application\/vnd.crick.clicker":"clkx","application\/vnd.crick.clicker.keyboard":"clkk","application\/vnd.crick.clicker.palette":"clkp","application\/vnd.crick.clicker.template":"clkt","application\/vnd.crick.clicker.wordbank":"clkw","application\/vnd.criticaltools.wbs+xml":"wbs","application\/vnd.ctc-posml":"pml","application\/vnd.cups-ppd":"ppd","application\/vnd.curl.car":"car","application\/vnd.curl.pcurl":"pcurl","application\/vnd.dart":"dart","application\/vnd.data-vision.rdz":"rdz","application\/vnd.dece.data":"uvf","application\/vnd.dece.ttml+xml":"uvt","application\/vnd.dece.unspecified":"uvx","application\/vnd.dece.zip":"uvz","application\/vnd.denovo.fcselayout-link":"fe_launch","application\/vnd.dna":"dna","application\/vnd.dolby.mlp":"mlp","application\/vnd.dpgraph":"dpg","application\/vnd.dreamfactory":"dfac","application\/vnd.ds-keypoint":"kpxx","application\/vnd.dvb.ait":"ait","application\/vnd.dvb.service":"svc","application\/vnd.dynageo":"geo","application\/vnd.ecowin.chart":"mag","application\/vnd.enliven":"nml","application\/vnd.epson.esf":"esf","application\/vnd.epson.msf":"msf","application\/vnd.epson.quickanime":"qam","application\/vnd.epson.salt":"slt","application\/vnd.epson.ssf":"ssf","application\/vnd.eszigno3+xml":"es3","application\/vnd.ezpix-album":"ez2","application\/vnd.ezpix-package":"ez3","application\/vnd.fdf":"fdf","application\/vnd.fdsn.mseed":"mseed","application\/vnd.fdsn.seed":"seed","application\/vnd.flographit":"gph","application\/vnd.fluxtime.clip":"ftc","application\/vnd.framemaker":"fm","application\/vnd.frogans.fnc":"fnc","application\/vnd.frogans.ltf":"ltf","application\/vnd.fsc.weblaunch":"fsc","application\/vnd.fujitsu.oasys":"oas","application\/vnd.fujitsu.oasys2":"oa2","application\/vnd.fujitsu.oasys3":"oa3","application\/vnd.fujitsu.oasysgp":"fg5","application\/vnd.fujitsu.oasysprs":"bh2","application\/vnd.fujixerox.ddd":"ddd","application\/vnd.fujixerox.docuworks":"xdw","application\/vnd.fujixerox.docuworks.binder":"xbd","application\/vnd.fuzzysheet":"fzs","application\/vnd.genomatix.tuxedo":"txd","application\/vnd.geogebra.file":"ggb","application\/vnd.geogebra.tool":"ggt","application\/vnd.geometry-explorer":"gex","application\/vnd.geonext":"gxt","application\/vnd.geoplan":"g2w","application\/vnd.geospace":"g3w","application\/vnd.gmx":"gmx","application\/vnd.google-earth.kml+xml":"kml","application\/vnd.google-earth.kmz":"kmz","application\/vnd.grafeq":"gqf","application\/vnd.groove-account":"gac","application\/vnd.groove-help":"ghf","application\/vnd.groove-identity-message":"gim","application\/vnd.groove-injector":"grv","application\/vnd.groove-tool-message":"gtm","application\/vnd.groove-tool-template":"tpl","application\/vnd.groove-vcard":"vcg","application\/vnd.hal+xml":"hal","application\/vnd.handheld-entertainment+xml":"zmm","application\/vnd.hbci":"hbci","application\/vnd.hhe.lesson-player":"les","application\/vnd.hp-hpgl":"hpgl","application\/vnd.hp-hpid":"hpid","application\/vnd.hp-hps":"hps","application\/vnd.hp-jlyt":"jlt","application\/vnd.hp-pcl":"pcl","application\/vnd.hp-pclxl":"pclxl","application\/vnd.hydrostatix.sof-data":"sfd-hdstx","application\/vnd.ibm.minipay":"mpy","application\/vnd.ibm.modcap":"afp","application\/vnd.ibm.rights-management":"irm","application\/vnd.ibm.secure-container":"sc","application\/vnd.iccprofile":"icc","application\/vnd.igloader":"igl","application\/vnd.immervision-ivp":"ivp","application\/vnd.immervision-ivu":"ivu","application\/vnd.insors.igm":"igm","application\/vnd.intercon.formnet":"xpw","application\/vnd.intergeo":"i2g","application\/vnd.intu.qbo":"qbo","application\/vnd.intu.qfx":"qfx","application\/vnd.ipunplugged.rcprofile":"rcprofile","application\/vnd.irepository.package+xml":"irp","application\/vnd.is-xpr":"xpr","application\/vnd.isac.fcs":"fcs","application\/vnd.jam":"jam","application\/vnd.jcp.javame.midlet-rms":"rms","application\/vnd.jisp":"jisp","application\/vnd.joost.joda-archive":"joda","application\/vnd.kahootz":"ktz","application\/vnd.kde.karbon":"karbon","application\/vnd.kde.kchart":"chrt","application\/vnd.kde.kformula":"kfo","application\/vnd.kde.kivio":"flw","application\/vnd.kde.kontour":"kon","application\/vnd.kde.kpresenter":"kpr","application\/vnd.kde.kspread":"ksp","application\/vnd.kde.kword":"kwd","application\/vnd.kenameaapp":"htke","application\/vnd.kidspiration":"kia","application\/vnd.kinar":"kne","application\/vnd.koan":"skp","application\/vnd.kodak-descriptor":"sse","application\/vnd.las.las+xml":"lasxml","application\/vnd.llamagraphics.life-balance.desktop":"lbd","application\/vnd.llamagraphics.life-balance.exchange+xml":"lbe","application\/vnd.lotus-1-2-3":123,"application\/vnd.lotus-approach":"apr","application\/vnd.lotus-freelance":"pre","application\/vnd.lotus-notes":"nsf","application\/vnd.lotus-organizer":"org","application\/vnd.lotus-screencam":"scm","application\/vnd.lotus-wordpro":"lwp","application\/vnd.macports.portpkg":"portpkg","application\/vnd.mcd":"mcd","application\/vnd.medcalcdata":"mc1","application\/vnd.mediastation.cdkey":"cdkey","application\/vnd.mfer":"mwf","application\/vnd.mfmp":"mfm","application\/vnd.micrografx.flo":"flo","application\/vnd.micrografx.igx":"igx","application\/vnd.mif":"mif","application\/vnd.mobius.daf":"daf","application\/vnd.mobius.dis":"dis","application\/vnd.mobius.mbk":"mbk","application\/vnd.mobius.mqy":"mqy","application\/vnd.mobius.msl":"msl","application\/vnd.mobius.plc":"plc","application\/vnd.mobius.txf":"txf","application\/vnd.mophun.application":"mpn","application\/vnd.mophun.certificate":"mpc","application\/vnd.mozilla.xul+xml":"xul","application\/vnd.ms-artgalry":"cil","application\/vnd.ms-cab-compressed":"cab","application\/vnd.ms-excel":"xls","application\/vnd.ms-excel.addin.macroenabled.12":"xlam","application\/vnd.ms-excel.sheet.binary.macroenabled.12":"xlsb","application\/vnd.ms-excel.sheet.macroenabled.12":"xlsm","application\/vnd.ms-excel.template.macroenabled.12":"xltm","application\/vnd.ms-fontobject":"eot","application\/vnd.ms-htmlhelp":"chm","application\/vnd.ms-ims":"ims","application\/vnd.ms-lrm":"lrm","application\/vnd.ms-officetheme":"thmx","application\/vnd.ms-pki.seccat":"cat","application\/vnd.ms-pki.stl":"stl","application\/vnd.ms-powerpoint":"ppt","application\/vnd.ms-powerpoint.addin.macroenabled.12":"ppam","application\/vnd.ms-powerpoint.presentation.macroenabled.12":"pptm","application\/vnd.ms-powerpoint.slide.macroenabled.12":"sldm","application\/vnd.ms-powerpoint.slideshow.macroenabled.12":"ppsm","application\/vnd.ms-powerpoint.template.macroenabled.12":"potm","application\/vnd.ms-project":"mpp","application\/vnd.ms-word.document.macroenabled.12":"docm","application\/vnd.ms-word.template.macroenabled.12":"dotm","application\/vnd.ms-works":"wps","application\/vnd.ms-wpl":"wpl","application\/vnd.ms-xpsdocument":"xps","application\/vnd.mseq":"mseq","application\/vnd.musician":"mus","application\/vnd.muvee.style":"msty","application\/vnd.mynfc":"taglet","application\/vnd.neurolanguage.nlu":"nlu","application\/vnd.nitf":"ntf","application\/vnd.noblenet-directory":"nnd","application\/vnd.noblenet-sealer":"nns","application\/vnd.noblenet-web":"nnw","application\/vnd.nokia.n-gage.data":"ngdat","application\/vnd.nokia.n-gage.symbian.install":"n-gage","application\/vnd.nokia.radio-preset":"rpst","application\/vnd.nokia.radio-presets":"rpss","application\/vnd.novadigm.edm":"edm","application\/vnd.novadigm.edx":"edx","application\/vnd.novadigm.ext":"ext","application\/vnd.oasis.opendocument.chart":"odc","application\/vnd.oasis.opendocument.chart-template":"otc","application\/vnd.oasis.opendocument.database":"odb","application\/vnd.oasis.opendocument.formula":"odf","application\/vnd.oasis.opendocument.formula-template":"odft","application\/vnd.oasis.opendocument.graphics":"odg","application\/vnd.oasis.opendocument.graphics-template":"otg","application\/vnd.oasis.opendocument.image":"odi","application\/vnd.oasis.opendocument.image-template":"oti","application\/vnd.oasis.opendocument.presentation":"odp","application\/vnd.oasis.opendocument.presentation-template":"otp","application\/vnd.oasis.opendocument.spreadsheet":"ods","application\/vnd.oasis.opendocument.spreadsheet-template":"ots","application\/vnd.oasis.opendocument.text":"odt","application\/vnd.oasis.opendocument.text-master":"odm","application\/vnd.oasis.opendocument.text-template":"ott","application\/vnd.oasis.opendocument.text-web":"oth","application\/vnd.olpc-sugar":"xo","application\/vnd.oma.dd2+xml":"dd2","application\/vnd.openofficeorg.extension":"oxt","application\/vnd.openxmlformats-officedocument.presentationml.presentation":"pptx","application\/vnd.openxmlformats-officedocument.presentationml.slide":"sldx","application\/vnd.openxmlformats-officedocument.presentationml.slideshow":"ppsx","application\/vnd.openxmlformats-officedocument.presentationml.template":"potx","application\/vnd.openxmlformats-officedocument.spreadsheetml.sheet":"xlsx","application\/vnd.openxmlformats-officedocument.spreadsheetml.template":"xltx","application\/vnd.openxmlformats-officedocument.wordprocessingml.document":"docx","application\/vnd.openxmlformats-officedocument.wordprocessingml.template":"dotx","application\/vnd.osgeo.mapguide.package":"mgp","application\/vnd.osgi.dp":"dp","application\/vnd.osgi.subsystem":"esa","application\/vnd.palm":"pdb","application\/vnd.pawaafile":"paw","application\/vnd.pg.format":"str","application\/vnd.pg.osasli":"ei6","application\/vnd.picsel":"efif","application\/vnd.pmi.widget":"wg","application\/vnd.pocketlearn":"plf","application\/vnd.powerbuilder6":"pbd","application\/vnd.previewsystems.box":"box","application\/vnd.proteus.magazine":"mgz","application\/vnd.publishare-delta-tree":"qps","application\/vnd.pvi.ptid1":"ptid","application\/vnd.quark.quarkxpress":"qxd","application\/vnd.realvnc.bed":"bed","application\/vnd.recordare.musicxml":"mxl","application\/vnd.recordare.musicxml+xml":"musicxml","application\/vnd.rig.cryptonote":"cryptonote","application\/vnd.rim.cod":"cod","application\/vnd.rn-realmedia":"rm","application\/vnd.rn-realmedia-vbr":"rmvb","application\/vnd.route66.link66+xml":"link66","application\/vnd.sailingtracker.track":"st","application\/vnd.seemail":"see","application\/vnd.sema":"sema","application\/vnd.semd":"semd","application\/vnd.semf":"semf","application\/vnd.shana.informed.formdata":"ifm","application\/vnd.shana.informed.formtemplate":"itp","application\/vnd.shana.informed.interchange":"iif","application\/vnd.shana.informed.package":"ipk","application\/vnd.simtech-mindmapper":"twd","application\/vnd.smaf":"mmf","application\/vnd.smart.teacher":"teacher","application\/vnd.solent.sdkm+xml":"sdkm","application\/vnd.spotfire.dxp":"dxp","application\/vnd.spotfire.sfs":"sfs","application\/vnd.stardivision.calc":"sdc","application\/vnd.stardivision.draw":"sda","application\/vnd.stardivision.impress":"sdd","application\/vnd.stardivision.math":"smf","application\/vnd.stardivision.writer":"sdw","application\/vnd.stardivision.writer-global":"sgl","application\/vnd.stepmania.package":"smzip","application\/vnd.stepmania.stepchart":"sm","application\/vnd.sun.xml.calc":"sxc","application\/vnd.sun.xml.calc.template":"stc","application\/vnd.sun.xml.draw":"sxd","application\/vnd.sun.xml.draw.template":"std","application\/vnd.sun.xml.impress":"sxi","application\/vnd.sun.xml.impress.template":"sti","application\/vnd.sun.xml.math":"sxm","application\/vnd.sun.xml.writer":"sxw","application\/vnd.sun.xml.writer.global":"sxg","application\/vnd.sun.xml.writer.template":"stw","application\/vnd.sus-calendar":"sus","application\/vnd.svd":"svd","application\/vnd.symbian.install":"sis","application\/vnd.syncml+xml":"xsm","application\/vnd.syncml.dm+wbxml":"bdm","application\/vnd.syncml.dm+xml":"xdm","application\/vnd.tao.intent-module-archive":"tao","application\/vnd.tcpdump.pcap":"pcap","application\/vnd.tmobile-livetv":"tmo","application\/vnd.trid.tpt":"tpt","application\/vnd.triscape.mxs":"mxs","application\/vnd.trueapp":"tra","application\/vnd.ufdl":"ufd","application\/vnd.uiq.theme":"utz","application\/vnd.umajin":"umj","application\/vnd.unity":"unityweb","application\/vnd.uoml+xml":"uoml","application\/vnd.vcx":"vcx","application\/vnd.visio":"vsd","application\/vnd.visionary":"vis","application\/vnd.vsf":"vsf","application\/vnd.wap.wbxml":"wbxml","application\/vnd.wap.wmlc":"wmlc","application\/vnd.wap.wmlscriptc":"wmlsc","application\/vnd.webturbo":"wtb","application\/vnd.wolfram.player":"nbp","application\/vnd.wordperfect":"wpd","application\/vnd.wqd":"wqd","application\/vnd.wt.stf":"stf","application\/vnd.xara":"xar","application\/vnd.xfdl":"xfdl","application\/vnd.yamaha.hv-dic":"hvd","application\/vnd.yamaha.hv-script":"hvs","application\/vnd.yamaha.hv-voice":"hvp","application\/vnd.yamaha.openscoreformat":"osf","application\/vnd.yamaha.openscoreformat.osfpvg+xml":"osfpvg","application\/vnd.yamaha.smaf-audio":"saf","application\/vnd.yamaha.smaf-phrase":"spf","application\/vnd.yellowriver-custom-menu":"cmp","application\/vnd.zul":"zir","application\/vnd.zzazz.deck+xml":"zaz","application\/voicexml+xml":"vxml","application\/widget":"wgt","application\/winhlp":"hlp","application\/wsdl+xml":"wsdl","application\/wspolicy+xml":"wspolicy","application\/x-7z-compressed":"7z","application\/x-abiword":"abw","application\/x-ace-compressed":"ace","application\/x-apple-diskimage":"dmg","application\/x-authorware-bin":"aab","application\/x-authorware-map":"aam","application\/x-authorware-seg":"aas","application\/x-bcpio":"bcpio","application\/x-bittorrent":"torrent","application\/x-blorb":"blb","application\/x-bzip":"bz","application\/x-cbr":"cbr","application\/x-cdlink":"vcd","application\/x-cfs-compressed":"cfs","application\/x-chat":"chat","application\/x-chess-pgn":"pgn","application\/x-conference":"nsc","application\/x-cpio":"cpio","application\/x-csh":"csh","application\/x-debian-package":"deb","application\/x-dgc-compressed":"dgc","application\/x-director":"dir","application\/x-doom":"wad","application\/x-dtbncx+xml":"ncx","application\/x-dtbook+xml":"dtb","application\/x-dtbresource+xml":"res","application\/x-dvi":"dvi","application\/x-envoy":"evy","application\/x-eva":"eva","application\/x-font-bdf":"bdf","application\/x-font-ghostscript":"gsf","application\/x-font-linux-psf":"psf","application\/x-font-pcf":"pcf","application\/x-font-snf":"snf","application\/x-font-type1":"pfa","application\/x-freearc":"arc","application\/x-futuresplash":"spl","application\/x-gca-compressed":"gca","application\/x-glulx":"ulx","application\/x-gnumeric":"gnumeric","application\/x-gramps-xml":"gramps","application\/x-gtar":"gtar","application\/x-hdf":"hdf","application\/x-install-instructions":"install","application\/x-iso9660-image":"iso","application\/x-java-jnlp-file":"jnlp","application\/x-latex":"latex","application\/x-lzh-compressed":"lzh","application\/x-mie":"mie","application\/x-mobipocket-ebook":"prc","application\/x-ms-application":"application","application\/x-ms-shortcut":"lnk","application\/x-ms-wmd":"wmd","application\/x-ms-wmz":"wmz","application\/x-ms-xbap":"xbap","application\/x-msaccess":"mdb","application\/x-msbinder":"obd","application\/x-mscardfile":"crd","application\/x-msclip":"clp","application\/x-msdownload":"dll","application\/x-msmediaview":"mvb","application\/x-msmetafile":"wmf","application\/x-msmoney":"mny","application\/x-mspublisher":"pub","application\/x-msschedule":"scd","application\/x-msterminal":"trm","application\/x-mswrite":"wri","application\/x-netcdf":"nc","application\/x-nzb":"nzb","application\/x-pkcs12":"p12","application\/x-pkcs7-certificates":"p7b","application\/x-pkcs7-certreqresp":"p7r","application\/x-research-info-systems":"ris","application\/x-shar":"shar","application\/x-shockwave-flash":"swf","application\/x-silverlight-app":"xap","application\/x-sql":"sql","application\/x-stuffit":"sit","application\/x-stuffitx":"sitx","application\/x-subrip":"srt","application\/x-sv4cpio":"sv4cpio","application\/x-sv4crc":"sv4crc","application\/x-t3vm-image":"t3","application\/x-tads":"gam","application\/x-tar":"tar","application\/x-tcl":"tcl","application\/x-tex":"tex","application\/x-tex-tfm":"tfm","application\/x-texinfo":"texinfo","application\/x-tgif":"obj","application\/x-ustar":"ustar","application\/x-wais-source":"src","application\/x-x509-ca-cert":"der","application\/x-xfig":"fig","application\/x-xliff+xml":"xlf","application\/x-xpinstall":"xpi","application\/x-xz":"xz","application\/x-zmachine":"z1","application\/xaml+xml":"xaml","application\/xcap-diff+xml":"xdf","application\/xenc+xml":"xenc","application\/xhtml+xml":"xhtml","application\/xml":"xsl","application\/xml-dtd":"dtd","application\/xop+xml":"xop","application\/xproc+xml":"xpl","application\/xslt+xml":"xslt","application\/xspf+xml":"xspf","application\/xv+xml":"mxml","application\/yang":"yang","application\/yin+xml":"yin","application\/zip":"zip","audio\/adpcm":"adp","audio\/basic":"au","audio\/midi":"mid","audio\/mp4":"m4a","audio\/mpeg":"mpga","audio\/ogg":"oga","audio\/s3m":"s3m","audio\/silk":"sil","audio\/vnd.dece.audio":"uva","audio\/vnd.digital-winds":"eol","audio\/vnd.dra":"dra","audio\/vnd.dts":"dts","audio\/vnd.dts.hd":"dtshd","audio\/vnd.lucent.voice":"lvp","audio\/vnd.ms-playready.media.pya":"pya","audio\/vnd.nuera.ecelp4800":"ecelp4800","audio\/vnd.nuera.ecelp7470":"ecelp7470","audio\/vnd.nuera.ecelp9600":"ecelp9600","audio\/vnd.rip":"rip","audio\/webm":"weba","audio\/x-aac":"aac","audio\/x-aiff":"aif","audio\/x-caf":"caf","audio\/x-flac":"flac","audio\/x-matroska":"mka","audio\/x-mpegurl":"m3u","audio\/x-ms-wax":"wax","audio\/x-ms-wma":"wma","audio\/x-pn-realaudio":"ram","audio\/x-pn-realaudio-plugin":"rmp","audio\/xm":"xm","chemical\/x-cdx":"cdx","chemical\/x-cif":"cif","chemical\/x-cmdf":"cmdf","chemical\/x-cml":"cml","chemical\/x-csml":"csml","chemical\/x-xyz":"xyz","font\/collection":"ttc","font\/otf":"otf","font\/ttf":"ttf","font\/woff":"woff","font\/woff2":"woff2","image\/cgm":"cgm","image\/g3fax":"g3","image\/gif":"gif","image\/ief":"ief","image\/jpeg":"jpeg","image\/ktx":"ktx","image\/png":"png","image\/prs.btif":"btif","image\/sgi":"sgi","image\/svg+xml":"svg","image\/tiff":"tiff","image\/vnd.adobe.photoshop":"psd","image\/vnd.dece.graphic":"uvi","image\/vnd.djvu":"djvu","image\/vnd.dvb.subtitle":"sub","image\/vnd.dwg":"dwg","image\/vnd.dxf":"dxf","image\/vnd.fastbidsheet":"fbs","image\/vnd.fpx":"fpx","image\/vnd.fst":"fst","image\/vnd.fujixerox.edmics-mmr":"mmr","image\/vnd.fujixerox.edmics-rlc":"rlc","image\/vnd.ms-modi":"mdi","image\/vnd.ms-photo":"wdp","image\/vnd.net-fpx":"npx","image\/vnd.wap.wbmp":"wbmp","image\/vnd.xiff":"xif","image\/webp":"webp","image\/x-3ds":"3ds","image\/x-cmu-raster":"ras","image\/x-cmx":"cmx","image\/x-freehand":"fh","image\/x-icon":"ico","image\/x-mrsid-image":"sid","image\/x-pcx":"pcx","image\/x-pict":"pic","image\/x-portable-anymap":"pnm","image\/x-portable-bitmap":"pbm","image\/x-portable-graymap":"pgm","image\/x-portable-pixmap":"ppm","image\/x-rgb":"rgb","image\/x-xpixmap":"xpm","image\/x-xwindowdump":"xwd","message\/rfc822":"eml","model\/iges":"igs","model\/mesh":"msh","model\/vnd.collada+xml":"dae","model\/vnd.dwf":"dwf","model\/vnd.gdl":"gdl","model\/vnd.gtw":"gtw","model\/vnd.vtu":"vtu","model\/vrml":"wrl","model\/x3d+binary":"x3db","model\/x3d+vrml":"x3dv","model\/x3d+xml":"x3d","text\/cache-manifest":"appcache","text\/calendar":"ics","text\/css":"css","text\/csv":"csv","text\/html":"html","text\/n3":"n3","text\/plain":"txt","text\/prs.lines.tag":"dsc","text\/richtext":"rtx","text\/sgml":"sgml","text\/tab-separated-values":"tsv","text\/troff":"t","text\/turtle":"ttl","text\/uri-list":"uri","text\/vcard":"vcard","text\/vnd.curl":"curl","text\/vnd.curl.dcurl":"dcurl","text\/vnd.curl.mcurl":"mcurl","text\/vnd.curl.scurl":"scurl","text\/vnd.fly":"fly","text\/vnd.fmi.flexstor":"flx","text\/vnd.graphviz":"gv","text\/vnd.in3d.3dml":"3dml","text\/vnd.in3d.spot":"spot","text\/vnd.sun.j2me.app-descriptor":"jad","text\/vnd.wap.wml":"wml","text\/vnd.wap.wmlscript":"wmls","text\/x-asm":"s","text\/x-c":"cc","text\/x-fortran":"f","text\/x-java-source":"java","text\/x-nfo":"nfo","text\/x-opml":"opml","text\/x-pascal":"p","text\/x-setext":"etx","text\/x-sfv":"sfv","text\/x-uuencode":"uu","text\/x-vcalendar":"vcs","text\/x-vcard":"vcf","video\/3gpp":"3gp","video\/3gpp2":"3g2","video\/h261":"h261","video\/h263":"h263","video\/h264":"h264","video\/jpeg":"jpgv","video\/jpm":"jpm","video\/mj2":"mj2","video\/mp4":"mp4","video\/mpeg":"mpeg","video\/quicktime":"qt","video\/vnd.dece.hd":"uvh","video\/vnd.dece.mobile":"uvm","video\/vnd.dece.pd":"uvp","video\/vnd.dece.sd":"uvs","video\/vnd.dece.video":"uvv","video\/vnd.dvb.file":"dvb","video\/vnd.fvt":"fvt","video\/vnd.mpegurl":"mxu","video\/vnd.ms-playready.media.pyv":"pyv","video\/vnd.uvvu.mp4":"uvu","video\/vnd.vivo":"viv","video\/webm":"webm","video\/x-f4v":"f4v","video\/x-fli":"fli","video\/x-flv":"flv","video\/x-m4v":"m4v","video\/x-matroska":"mkv","video\/x-mng":"mng","video\/x-ms-asf":"asf","video\/x-ms-vob":"vob","video\/x-ms-wmx":"wmx","video\/x-ms-wvx":"wvx","video\/x-msvideo":"avi","video\/x-sgi-movie":"movie","video\/x-smv":"smv","x-conference\/x-cooltalk":"ice","text\/x-sql":"sql","image\/x-pixlr-data":"pxd","image\/x-adobe-dng":"dng","image\/x-sketch":"sketch","image\/x-xcf":"xcf","audio\/amr":"amr","application\/plt":"plt","application\/sat":"sat","application\/step":"step","text\/x-httpd-cgi":"cgi","text\/x-asap":"asp","text\/x-jsp":"jsp"};

/*
 * File: /js/elFinder.options.js
 */

/**
 * Default elFinder config
 *
 * @type  Object
 * @autor Dmitry (dio) Levashov
 */
elFinder.prototype._options = {
	/**
	 * URLs of 3rd party libraries CDN
	 * 
	 * @type Object
	 */
	cdns : {
		// for editor etc.
		ace        : 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.1',
		codemirror : 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.40.2',
		ckeditor   : 'https://cdnjs.cloudflare.com/ajax/libs/ckeditor/4.10.0',
		ckeditor5  : 'https://cdn.ckeditor.com/ckeditor5/11.1.1',
		tinymce    : 'https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.8.3',
		simplemde  : 'https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2',
		fabric16   : 'https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.7',
		tui        : 'https://uicdn.toast.com',
		// for quicklook etc.
		hls        : 'https://cdnjs.cloudflare.com/ajax/libs/hls.js/0.10.1/hls.min.js',
		dash       : 'https://cdnjs.cloudflare.com/ajax/libs/dashjs/2.9.1/dash.all.min.js',
		flv        : 'https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.4.2/flv.min.js',
		prettify   : 'https://cdn.jsdelivr.net/gh/google/code-prettify@453bd5f51e61245339b738b1bbdd42d7848722ba/loader/run_prettify.js',
		psd        : 'https://cdnjs.cloudflare.com/ajax/libs/psd.js/3.2.0/psd.min.js',
		rar        : 'https://cdn.jsdelivr.net/gh/nao-pon/rar.js@6cef13ec66dd67992fc7f3ea22f132d770ebaf8b/rar.min.js',
		zlibUnzip  : 'https://cdn.jsdelivr.net/gh/imaya/zlib.js@0.3.1/bin/unzip.min.js', // need check unzipFiles() in quicklook.plugins.js when update
		zlibGunzip : 'https://cdn.jsdelivr.net/gh/imaya/zlib.js@0.3.1/bin/gunzip.min.js',
		marked     : 'https://cdnjs.cloudflare.com/ajax/libs/marked/0.5.1/marked.min.js',
		sparkmd5   : 'https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.min.js',
		jssha      : 'https://cdnjs.cloudflare.com/ajax/libs/jsSHA/2.3.1/sha.js',
		amr        : 'https://cdn.jsdelivr.net/gh/yxl/opencore-amr-js@dcf3d2b5f384a1d9ded2a54e4c137a81747b222b/js/amrnb.js'
	},
	
	/**
	 * Connector url. Required!
	 *
	 * @type String
	 */
	url : '',

	/**
	 * Ajax request type.
	 *
	 * @type String
	 * @default "get"
	 */
	requestType : 'get',
	
	/**
	 * Use CORS to connector url
	 * 
	 * @type Boolean|null  true|false|null(Auto detect)
	 */
	cors : null,

	/**
	 * Maximum number of concurrent connections on request
	 * 
	 * @type Number
	 * @default 3
	 */
	requestMaxConn : 3,

	/**
	 * Transport to send request to backend.
	 * Required for future extensions using websockets/webdav etc.
	 * Must be an object with "send" method.
	 * transport.send must return jQuery.Deferred() object
	 *
	 * @type Object
	 * @default null
	 * @example
	 *  transport : {
	 *    init : function(elfinderInstance) { },
	 *    send : function(options) {
	 *      var dfrd = jQuery.Deferred();
	 *      // connect to backend ...
	 *      return dfrd;
	 *    },
	 *    upload : function(data) {
	 *      var dfrd = jQuery.Deferred();
	 *      // upload ...
	 *      return dfrd;
	 *    }
	 *    
	 *  }
	 **/
	transport : {},

	/**
	 * URL to upload file to.
	 * If not set - connector URL will be used
	 *
	 * @type String
	 * @default  ''
	 */
	urlUpload : '',

	/**
	 * Allow to drag and drop to upload files
	 *
	 * @type Boolean|String
	 * @default  'auto'
	 */
	dragUploadAllow : 'auto',
	
	/**
	 * Confirmation dialog displayed at the time of overwriting upload
	 * 
	 * @type Boolean
	 * @default true
	 */
	overwriteUploadConfirm : true,
	
	/**
	 * Max size of chunked data of file upload
	 * 
	 * @type Number
	 * @default  10485760(10MB)
	 */
	uploadMaxChunkSize : 10485760,
	
	/**
	 * Regular expression of file name to exclude when uploading folder
	 * 
	 * @type Object
	 * @default { win: /^(?:desktop\.ini|thumbs\.db)$/i, mac: /^\.ds_store$/i }
	 */
	folderUploadExclude : {
		win: /^(?:desktop\.ini|thumbs\.db)$/i,
		mac: /^\.ds_store$/i
	},
	
	/**
	 * Timeout for upload using iframe
	 *
	 * @type Number
	 * @default  0 - no timeout
	 */
	iframeTimeout : 0,
	
	/**
	 * Data to append to all requests and to upload files
	 *
	 * @type Object
	 * @default  {}
	 */
	customData : {},
	
	/**
	 * Event listeners to bind on elFinder init
	 *
	 * @type Object
	 * @default  {}
	 */
	handlers : {},

	/**
	 * Any custom headers to send across every ajax request
	 *
	 * @type Object
	 * @default {}
	 */
	customHeaders : {},

	/**
	 * Any custom xhrFields to send across every ajax request
	 *
	 * @type Object
	 * @default {}
	 */
	xhrFields : {},

	/**
	 * Interface language
	 *
	 * @type String
	 * @default "en"
	 */
	lang : 'en',

	/**
	 * Base URL of elfFinder library starting from Manager HTML
	 * Auto detect when empty value
	 * 
	 * @type String
	 * @default ""
	 */
	baseUrl : '',

	/**
	 * Base URL of i18n js files
	 * baseUrl + "js/i18n/" when empty value
	 * 
	 * @type String
	 * @default ""
	 */
	i18nBaseUrl : '',
	
	/**
	 * Auto load required CSS
	 * `false` to disable this function or
	 * CSS URL Array to load additional CSS files
	 * 
	 * @type Boolean|Array
	 * @default true
	 */
	cssAutoLoad : true,

	/**
	 * Theme to load
	 * {"themeid" : "Theme CSS URL"} or
	 * {"themeid" : "Theme manifesto.json URL"} or
	 * Theme manifesto.json Object
	 * {
	 *   "themeid" : {
	 *     "name":"Theme Name",
	 *     "cssurls":"Theme CSS URL",
	 *     "author":"Author Name",
	 *     "email":"Author Email",
	 *     "license":"License",
	 *     "link":"Web Site URL",
	 *     "image":"Screen Shot URL",
	 *     "description":"Description"
	 *   }
	 * }
	 * 
	 * @type Object
	 */
	themes : {},

	/**
	 * Theme id to initial theme
	 * 
	 * @type String|Null
	 */
	theme : null,

	/**
	 * Maximum value of error dialog open at the same time
	 * 
	 * @type Number
	 */
	maxErrorDialogs : 5,

	/**
	 * Additional css class for filemanager node.
	 *
	 * @type String
	 */
	cssClass : '',

	/**
	 * Active commands list. '*' means all of the commands that have been load.
	 * If some required commands will be missed here, elFinder will add its
	 *
	 * @type Array
	 */
	commands : ['*'],
	// Available commands list
	//commands : [
	//	'archive', 'back', 'chmod', 'colwidth', 'copy', 'cut', 'download', 'duplicate', 'edit', 'extract',
	//	'forward', 'fullscreen', 'getfile', 'help', 'home', 'info', 'mkdir', 'mkfile', 'netmount', 'netunmount',
	//	'open', 'opendir', 'paste', 'places', 'quicklook', 'reload', 'rename', 'resize', 'restore', 'rm',
	//	'search', 'sort', 'up', 'upload', 'view', 'zipdl'
	//],
	
	/**
	 * Commands options.
	 *
	 * @type Object
	 **/
	commandsOptions : {
		// // configure shortcuts of any command
		// // add `shortcuts` property into each command
		// any_command_name : {
		// 	shortcuts : [] // for disable this command's shortcuts
		// },
		// any_command_name : {
		// 	shortcuts : function(fm, shortcuts) {
		// 		// for add `CTRL + E` for this command action
		// 		shortcuts[0]['pattern'] += ' ctrl+e';
		// 		return shortcuts;
		// 	}
		// },
		// any_command_name : {
		// 	shortcuts : function(fm, shortcuts) {
		// 		// for full customize of this command's shortcuts
		// 		return [ { pattern: 'ctrl+e ctrl+down numpad_enter' + (fm.OS != 'mac' && ' enter') } ];
		// 	}
		// },
		// "getfile" command options.
		getfile : {
			onlyURL  : false,
			// allow to return multiple files info
			multiple : false,
			// allow to return filers info
			folders  : false,
			// action after callback (""/"close"/"destroy")
			oncomplete : '',
			// action when callback is fail (""/"close"/"destroy")
			onerror : '',
			// get path before callback call
			getPath    : true, 
			// get image sizes before callback call
			getImgSize : false
		},
		open : {
			// HTTP method that request to the connector when item URL is not valid URL.
			// If you set to "get" will be displayed request parameter in the browser's location field
			// so if you want to conceal its parameters should be given "post".
			// Nevertheless, please specify "get" if you want to enable the partial request by HTTP Range header.
			method : 'post',
			// Where to open into : 'window'(default), 'tab' or 'tabs'
			// 'tabs' opens in each tabs
			into   : 'window',
			// Default command list of action when select file
			// String value that is 'Command Name' or 'Command Name1/CommandName2...'
			selectAction : 'open'
		},
		opennew : {
			// URL of to open elFinder manager
			// Default '' : Origin URL
			url : '',
			// Use search query of origin URL
			useOriginQuery : true
		},
		// "upload" command options.
		upload : {
			// Open elFinder upload dialog: 'button' OR Open system OS upload dialog: 'uploadbutton'
			ui : 'button'
		},
		// "download" command options.
		download : {
			// max request to download files when zipdl disabled
			maxRequests : 10,
			// minimum count of files to use zipdl
			minFilesZipdl : 2
		},
		// "quicklook" command options.
		quicklook : {
			autoplay : true,
			width    : 450,
			height   : 300,
			// ControlsList of HTML5 audio/video preview
			// see https://googlechrome.github.io/samples/media/controlslist.html
			mediaControlsList : '', // e.g. 'nodownload nofullscreen noremoteplayback'
			// Show toolbar of PDF preview (with <embed> tag)
			pdfToolbar : true,
			// Maximum characters length to preview
			textMaxlen : 2000,
			// quicklook window must be contained in elFinder node on window open (true|false)
			contain : false,
			// preview window into NavDock (0 : undocked | 1 : docked(show) | 2 : docked(hide))
			docked   : 0,
			// Docked preview height ('auto' or Number of pixel) 'auto' is setted to the Navbar width
			dockHeight : 'auto',
			// media auto play when docked
			dockAutoplay : false,
			// Google Maps API key (Require Maps JavaScript API)
			googleMapsApiKey : '',
			// Google Maps API Options
			googleMapsOpts : {
				maps : {},
				kml : {
					suppressInfoWindows : false,
					preserveViewport : false
				}
			},
			// ViewerJS (https://viewerjs.org/) Options
			// To enable this you need to place ViewerJS on the same server as elFinder and specify that URL in `url`.
			viewerjs : {
				url: '', // Example '/ViewerJS/index.html'
				mimes: ['application/pdf', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation']
			},
			// MIME types to CAD-Files and 3D-Models online viewer on sharecad.org
			// Example ['image/vnd.dwg', 'image/vnd.dxf', 'model/vnd.dwf', 'application/vnd.hp-hpgl', 'application/plt', 'application/step', 'model/iges', 'application/vnd.ms-pki.stl', 'application/sat', 'image/cgm', 'application/x-msmetafile']
			sharecadMimes : [],
			// MIME types to use Google Docs online viewer
			// Example ['application/pdf', 'image/tiff', 'application/vnd.ms-office', 'application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/postscript', 'application/rtf']
			googleDocsMimes : [],
			// MIME types to use Microsoft Office Online viewer
			// Example ['application/msword', 'application/vnd.ms-word', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.presentation']
			// These MIME types override "googleDocsMimes"
			officeOnlineMimes : [],
			// File size (byte) threshold when using the dim command for obtain the image size necessary to image preview
			getDimThreshold : 200000,
			// MIME-Type regular expression that does not check empty files
			mimeRegexNotEmptyCheck : /^application\/vnd\.google-apps\./
		},
		// "quicklook" command options.
		edit : {
			// dialog width, integer(px) or integer+'%' (example: 650, '80%' ...)
			dialogWidth : void(0),
			// list of allowed mimetypes to edit of text files
			// if empty - any text files can be edited
			mimes : [],
			// MIME-types of text file to make as empty files
			makeTextMimes : ['text/plain', 'text/css', 'text/html'],
			// Use the editor stored in the browser
			// This value allowd overwrite with user preferences
			useStoredEditor : false,
			// Open the maximized editor window
			// This value allowd overwrite with user preferences
			editorMaximized : false,
			// edit files in wysisyg's
			editors : [
				// {
				// 	/**
				// 	 * editor info
				// 	 * @type  Object
				// 	 */
				// 	info : { name: 'Editor Name' },
				// 	/**
				// 	 * files mimetypes allowed to edit in current wysisyg
				// 	 * @type  Array
				// 	 */
				// 	mimes : ['text/html'], 
				// 	/**
				// 	 * HTML element for editing area (optional for text editor)
				// 	 * @type  String
				// 	 */
				// 	html : '<textarea></textarea>', 
				// 	/**
				// 	 * Initialize editing area node (optional for text editor)
				// 	 * 
				// 	 * @param  String  dialog DOM id
				// 	 * @param  Object  target file object
				// 	 * @param  String  target file content (text or Data URI Scheme(binary file))
				// 	 * @param  Object  elFinder instance
				// 	 * @type  Function
				// 	 */
				// 	init : function(id, file, content, fm) {
				// 		jQuery(this).attr('id', id + '-text').val(content);
				// 	},
				// 	/**
				// 	 * Get edited contents (optional for text editor)
				// 	 * @type  Function
				// 	 */
				// 	getContent : function() {
				// 		return jQuery(this).val();
				// 	},
				// 	/**
				// 	 * Called when "edit" dialog loaded.
				// 	 * Place to init wysisyg.
				// 	 * Can return wysisyg instance
				// 	 *
				// 	 * @param  DOMElement  textarea node
				// 	 * @return Object      editor instance|jQuery.Deferred(return instance on resolve())
				// 	 */
				// 	load : function(textarea) { },
				// 	/**
				// 	 * Called before "edit" dialog closed.
				// 	 * Place to destroy wysisyg instance.
				// 	 *
				// 	 * @param  DOMElement  textarea node
				// 	 * @param  Object      wysisyg instance (if was returned by "load" callback)
				// 	 * @return void
				// 	 */
				// 	close : function(textarea, instance) { },
				// 	/**
				// 	 * Called before file content send to backend.
				// 	 * Place to update textarea content if needed.
				// 	 *
				// 	 * @param  DOMElement  textarea node
				// 	 * @param  Object      wysisyg instance (if was returned by "load" callback)
				// 	 * @return void
				// 	 */
				// 	save : function(textarea, instance) {},
				// 	/**
				// 	 * Called after load() or save().
				// 	 * Set focus to wysisyg editor.
				// 	 *
				// 	 * @param  DOMElement  textarea node
				// 	 * @param  Object      wysisyg instance (if was returned by "load" callback)
				// 	 * @return void
				// 	 */
				// 	focus : function(textarea, instance) {}
				// 	/**
				// 	 * Called after dialog resized..
				// 	 *
				// 	 * @param  DOMElement  textarea node
				// 	 * @param  Object      wysisyg instance (if was returned by "load" callback)
				// 	 * @param  Object      resize event object
				// 	 * @param  Object      data object
				// 	 * @return void
				// 	 */
				// 	resize : function(textarea, instance, event, data) {}
				// 
				// }
			],
			// Character encodings of select box
			encodings : ['Big5', 'Big5-HKSCS', 'Cp437', 'Cp737', 'Cp775', 'Cp850', 'Cp852', 'Cp855', 'Cp857', 'Cp858', 
				'Cp862', 'Cp866', 'Cp874', 'EUC-CN', 'EUC-JP', 'EUC-KR', 'GB18030', 'ISO-2022-CN', 'ISO-2022-JP', 'ISO-2022-KR', 
				'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', 'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 
				'ISO-8859-8', 'ISO-8859-9', 'ISO-8859-13', 'ISO-8859-15', 'KOI8-R', 'KOI8-U', 'Shift-JIS', 
				'Windows-1250', 'Windows-1251', 'Windows-1252', 'Windows-1253', 'Windows-1254', 'Windows-1257'],
			// options for extra editors
			extraOptions : {
				// TUI Image Editor's options
				tuiImgEditOpts : {
					// Path prefix of icon-a.svg, icon-b.svg, icon-c.svg and icon-d.svg in the Theme. 
					// `iconsPath` MUST follow the same origin policy.
					iconsPath : void(0), // default is "./img/tui-"
					// Theme object
					theme : {}
				},
				// Pixo image editor constructor options - https://pixoeditor.com/
				// Require 'apikey' to enable it
				pixo: {
					apikey: ''
				},
				// Specify the Creative Cloud API key when using Creative SDK image editor of Creative Cloud.
				// You can get the API key at https://console.adobe.io/.
				creativeCloudApiKey : '',
				// Browsing manager URL for CKEditor, TinyMCE
				// Uses self location with the empty value or not defined.
				//managerUrl : 'elfinder.html'
				managerUrl : null,
				// CKEditor5' builds mode - 'classic', 'inline' or 'balloon' 
				ckeditor5Mode : 'balloon',
				// Setting for Online-Convert.com
				onlineConvert : {
					maxSize  : 100, // (MB) Max 100MB on free account
					showLink : true // It must be enabled with free account
				}
			}
		},
		search : {
			// Incremental search from the current view
			incsearch : {
				enable : true, // is enable true or false
				minlen : 1,    // minimum number of characters
				wait   : 500   // wait milliseconds
			},
			// Additional search types
			searchTypes : {
				// "SearchMime" is implemented in default
				SearchMime : {           // The key is search type that send to the connector
					name : 'btnMime',    // Button text to be processed in i18n()
					title : 'searchMime' // Button title to be processed in i18n()
				}
			}
		},
		// "info" command options.
		info : {
			// If the URL of the Directory is null,
			// it is assumed that the link destination is a URL to open the folder in elFinder
			nullUrlDirLinkSelf : true,
			// Information items to be hidden by default
			// These name are 'size', 'aliasfor', 'path', 'link', 'dim', 'modify', 'perms', 'locked', 'owner', 'group', 'perm' and your custom info items label
			hideItems : [],
			// Maximum file size (byte) to get file contents hash (md5, sha256 ...)
			showHashMaxsize : 104857600, // 100 MB
			// Array of hash algorisms to show on info dialog
			// These name are 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512', 'shake128' and 'shake256'
			showHashAlgorisms : ['md5', 'sha256'],
			custom : {
				// /**
				//  * Example of custom info `desc`
				//  */
				// desc : {
				// 	/**
				// 	 * Lable (require)
				// 	 * It is filtered by the `fm.i18n()`
				// 	 * 
				// 	 * @type String
				// 	 */
				// 	label : 'Description',
				// 	
				// 	/**
				// 	 * Template (require)
				// 	 * `{id}` is replaced in dialog.id
				// 	 * 
				// 	 * @type String
				// 	 */
				// 	tpl : '<div class="elfinder-info-desc"><span class="elfinder-spinner"></span></div>',
				// 	
				// 	/**
				// 	 * Restricts to mimetypes (optional)
				// 	 * Exact match or category match
				// 	 * 
				// 	 * @type Array
				// 	 */
				// 	mimes : ['text', 'image/jpeg', 'directory'],
				// 	
				// 	/**
				// 	 * Restricts to file.hash (optional)
				// 	 * 
				// 	 * @ type Regex
				// 	 */
				// 	hashRegex : /^l\d+_/,
				// 
				// 	/**
				// 	 * Request that asks for the description and sets the field (optional)
				// 	 * 
				// 	 * @type Function
				// 	 */
				// 	action : function(file, fm, dialog) {
				// 		fm.request({
				// 		data : { cmd : 'desc', target: file.hash },
				// 			preventDefault: true,
				// 		})
				// 		.fail(function() {
				// 			dialog.find('div.elfinder-info-desc').html(fm.i18n('unknown'));
				// 		})
				// 		.done(function(data) {
				// 			dialog.find('div.elfinder-info-desc').html(data.desc);
				// 		});
				// 	}
				// }
			}
		},
		mkdir: {
			// Enable automatic switching function ["New Folder" / "Into New Folder"] of toolbar buttton
			intoNewFolderToolbtn: false
		},
		resize: {
			// defalt status of snap to 8px grid of the jpeg image ("enable" or "disable")
			grid8px : 'disable',
			// Preset size array [width, height]
			presetSize : [[320, 240], [400, 400], [640, 480], [800,600]],
			// File size (bytes) threshold when using the `dim` command for obtain the image size necessary to start editing
			getDimThreshold : 204800,
			// File size (bytes) to request to get substitute image (400px) with the `dim` command
			dimSubImgSize : 307200
		},
		rm: {
			// If trash is valid, items moves immediately to the trash holder without confirm.
			quickTrash : true,
			// Maximum wait seconds when checking the number of items to into the trash
			infoCheckWait : 10,
			// Maximum number of items that can be placed into the Trash at one time
			toTrashMaxItems : 1000
		},
		help : {
			// Tabs to show
			view : ['about', 'shortcuts', 'help', 'integrations', 'debug'],
			// HTML source URL of the heip tab
			helpSource : ''
		},
		preference : {
			// dialog width
			width: 600,
			// dialog height
			height: 400,
			// tabs setting see preference.js : build()
			categories: null,
			// preference setting see preference.js : build()
			prefs: null,
			// language setting  see preference.js : build()
			langs: null,
			// Command list of action when select file
			// Array value are 'Command Name' or 'Command Name1/CommandName2...'
			selectActions : ['open', 'edit/download', 'resize/edit/download', 'download', 'quicklook']
		}
	},
	
	/**
	 * Callback for prepare boot up
	 * 
	 * - The this object in the function is an elFinder node
	 * - The first parameter is elFinder Instance
	 * - The second parameter is an object of other parameters
	 *   For now it can use `dfrdsBeforeBootup` Array
	 * 
	 * @type Function
	 * @default null
	 * @return void
	 */
	bootCallback : null,
	
	/**
	 * Callback for "getfile" commands.
	 * Required to use elFinder with WYSIWYG editors etc..
	 *
	 * @type Function
	 * @default null (command not active)
	 */
	getFileCallback : null,
	
	/**
	 * Default directory view. icons/list
	 *
	 * @type String
	 * @default "icons"
	 */
	defaultView : 'icons',
	
	/**
	 * Hash of default directory path to open
	 * 
	 * NOTE: This setting will be disabled if the target folder is specified in location.hash.
	 * 
	 * If you want to find the hash in Javascript
	 * can be obtained with the following code. (In the case of a standard hashing method)
	 * 
	 * var volumeId = 'l1_'; // volume id
	 * var path = 'path/to/target'; // without root path
	 * //var path = 'path\\to\\target'; // use \ on windows server
	 * var hash = volumeId + btoa(path).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '.').replace(/\.+$/, '');
	 * 
	 * @type String
	 * @default ""
	 */
	startPathHash : '',

	/**
	 * Emit a sound when a file is deleted
	 * Sounds are in sounds/ folder
	 * 
	 * @type Boolean
	 * @default true
	 */
	sound : true,
	
	/**
	 * UI plugins to load.
	 * Current dir ui and dialogs loads always.
	 * Here set not required plugins as folders tree/toolbar/statusbar etc.
	 *
	 * @type Array
	 * @default ['toolbar', 'tree', 'path', 'stat']
	 * @full ['toolbar', 'places', 'tree', 'path', 'stat']
	 */
	ui : ['toolbar', 'tree', 'path', 'stat'],
	
	/**
	 * Some UI plugins options.
	 * @type Object
	 */
	uiOptions : {
		// toolbar configuration
		toolbar : [
			['home', 'back', 'forward', 'up', 'reload'],
			['netmount'],
			['mkdir', 'mkfile', 'upload'],
			['open', 'download', 'getfile'],
			['undo', 'redo'],
			['copy', 'cut', 'paste', 'rm', 'empty', 'hide'],
			['duplicate', 'rename', 'edit', 'resize', 'chmod'],
			['selectall', 'selectnone', 'selectinvert'],
			['quicklook', 'info'],
			['extract', 'archive'],
			['search'],
			['view', 'sort'],
			['help'],
			['fullscreen']
		],
		// toolbar extra options
		toolbarExtra : {
			// also displays the text label on the button (true / false / 'none')
			displayTextLabel: false,
			// Exclude `displayTextLabel` setting UA type
			labelExcludeUA: ['Mobile'],
			// auto hide on initial open
			autoHideUA: ['Mobile'],
			// Initial setting value of hide button in toolbar setting
			defaultHides: ['home', 'reload'],
			// show Preference button ('none', 'auto', 'always')
			// If you do not include 'preference' in the context menu you should specify 'auto' or 'always'
			showPreferenceButton: 'none',
			// show Preference button into contextmenu of the toolbar (true / false)
			preferenceInContextmenu: true
		},
		// directories tree options
		tree : {
			// expand current root on init
			openRootOnLoad : true,
			// expand current work directory on open
			openCwdOnOpen  : true,
			// auto loading current directory parents and do expand their node.
			syncTree : true,
			// Maximum number of display of each child trees
			// The tree of directories with children exceeding this number will be split
			subTreeMax : 100,
			// Numbar of max connctions of subdirs request
			subdirsMaxConn : 2,
			// Number of max simultaneous processing directory of subdirs
			subdirsAtOnce : 5,
			// Durations of each animations
			durations : {
				slideUpDown : 'fast',
				autoScroll : 'fast'
			}
			// ,
			// /**
			//  * Add CSS class name to navbar directories (optional)
			//  * see: https://github.com/Studio-42/elFinder/pull/1061,
			//  *      https://github.com/Studio-42/elFinder/issues/1231
			//  * 
			//  * @type Function
			//  */
			// getClass: function(dir) {
			// 	// e.g. This adds the directory's name (lowercase) with prefix as a CSS class
			// 	return 'elfinder-tree-' + dir.name.replace(/[ "]/g, '').toLowerCase();
			// }
		},
		// navbar options
		navbar : {
			minWidth : 150,
			maxWidth : 500,
			// auto hide on initial open
			autoHideUA: [] // e.g. ['Mobile']
		},
		navdock : {
			// disabled navdock ui
			disabled : false,
			// percentage of initial maximum height to work zone
			initMaxHeight : '50%',
			// percentage of maximum height to work zone by user resize action
			maxHeight : '90%'
		},
		cwd : {
			// display parent folder with ".." name :)
			oldSchool : false,
			
			// fm.UA types array to show item select checkboxes e.g. ['All'] or ['Mobile'] etc. default: ['Touch']
			showSelectCheckboxUA : ['Touch'],
			
			// file info columns displayed
			listView : {
				// name is always displayed, cols are ordered
				// e.g. ['perm', 'date', 'size', 'kind', 'owner', 'group', 'mode']
				// mode: 'mode'(by `fileModeStyle` setting), 'modestr'(rwxr-xr-x) , 'modeoct'(755), 'modeboth'(rwxr-xr-x (755))
				// 'owner', 'group' and 'mode', It's necessary set volume driver option "statOwner" to `true`
				// for custom, characters that can be used in the name is `a-z0-9_`
				columns : ['perm', 'date', 'size', 'kind'],
				// override this if you want custom columns name
				// example
				// columnsCustomName : {
				//		date : 'Last modification',
				// 		kind : 'Mime type'
				// }
				columnsCustomName : {},
				// fixed list header colmun
				fixedHeader : true
			},

			// icons view setting
			iconsView : {
				// default icon size (0-3 in default CSS (cwd.css - elfinder-cwd-size[number]))
				size: 0,
				// number of maximum size (3 in default CSS (cwd.css - elfinder-cwd-size[number]))
				// uses in preference.js
				sizeMax: 3,
				// Name of each size
				sizeNames: {
					0: 'viewSmall',
					1: 'viewMedium',
					2: 'viewLarge',
					3: 'viewExtraLarge' 
				}
			},

			// /**
			//  * Add CSS class name to cwd directories (optional)
			//  * see: https://github.com/Studio-42/elFinder/pull/1061,
			//  *      https://github.com/Studio-42/elFinder/issues/1231
			//  * 
			//  * @type Function
			//  */
			// ,
			// getClass: function(file) {
			// 	// e.g. This adds the directory's name (lowercase) with prefix as a CSS class
			// 	return 'elfinder-cwd-' + file.name.replace(/[ "]/g, '').toLowerCase();
			//}
			
			//,
			//// Template placeholders replacement rules for overwrite. see ui/cwd.js replacement
			//replacement : {
			//	tooltip : function(f, fm) {
			//		var list = fm.viewType == 'list', // current view type
			//			query = fm.searchStatus.state == 2, // is in search results
			//			title = fm.formatDate(f) + (f.size > 0 ? ' ('+fm.formatSize(f.size)+')' : ''),
			//			info  = '';
			//		if (query && f.path) {
			//			info = fm.escape(f.path.replace(/\/[^\/]*$/, ''));
			//		} else {
			//			info = f.tooltip? fm.escape(f.tooltip).replace(/\r/g, '&#13;') : '';
			//		}
			//		if (list) {
			//			info += (info? '&#13;' : '') + fm.escape(f.name);
			//		}
			//		return info? info + '&#13;' + title : title;
			//	}
			//}
		},
		path : {
			// Move to head of work zone without UI navbar
			toWorkzoneWithoutNavbar : true
		},
		dialog : {
			// Enable to auto focusing on mouse over in the target form element
			focusOnMouseOver : true
		},
		toast : {
			animate : {
				// to show
				showMethod: 'fadeIn', // fadeIn, slideDown, and show are built into jQuery
				showDuration: 300,    // milliseconds
				showEasing: 'swing',  // swing and linear are built into jQuery
				// timeout to hide
				timeOut: 3000,
				// to hide
				hideMethod: 'fadeOut',
				hideDuration: 1500,
				hideEasing: 'swing'
			}
		}
	},

	/**
	 * MIME regex of send HTTP header "Content-Disposition: inline" or allow preview in quicklook
	 * This option will overwrite by connector configuration
	 * 
	 * @type String
	 * @default '^(?:(?:image|video|audio)|text/plain|application/pdf$)'
	 * @example
	 *  dispInlineRegex : '.',  // is allow inline of all of MIME types
	 *  dispInlineRegex : '$^', // is not allow inline of all of MIME types
	 */
	dispInlineRegex : '^(?:(?:image|video|audio)|application/(?:x-mpegURL|dash\+xml)|(?:text/plain|application/pdf)$)',

	/**
	 * Display only required files by types
	 *
	 * @type Array
	 * @default []
	 * @example
	 *  onlyMimes : ["image"] - display all images
	 *  onlyMimes : ["image/png", "application/x-shockwave-flash"] - display png and flash
	 */
	onlyMimes : [],

	/**
	 * Custom files sort rules.
	 * All default rules (name/size/kind/date/perm/mode/owner/group) set in elFinder._sortRules
	 *
	 * @type {Object}
	 * @example
	 * sortRules : {
	 *   name : function(file1, file2) { return file1.name.toLowerCase().localeCompare(file2.name.toLowerCase()); }
	 * }
	 */
	sortRules : {},

	/**
	 * Default sort type.
	 *
	 * @type {String}
	 */
	sortType : 'name',
	
	/**
	 * Default sort order.
	 *
	 * @type {String}
	 * @default "asc"
	 */
	sortOrder : 'asc',
	
	/**
	 * Display folders first?
	 *
	 * @type {Boolean}
	 * @default true
	 */
	sortStickFolders : true,
	
	/**
	 * Sort also applies to the treeview (null: disable this feature)
	 *
	 * @type Boolean|null
	 * @default false
	 */
	sortAlsoTreeview : false,
	
	/**
	 * If true - elFinder will formating dates itself, 
	 * otherwise - backend date will be used.
	 *
	 * @type Boolean
	 */
	clientFormatDate : true,
	
	/**
	 * Show UTC dates.
	 * Required set clientFormatDate to true
	 *
	 * @type Boolean
	 */
	UTCDate : false,
	
	/**
	 * File modification datetime format.
	 * Value from selected language data  is used by default.
	 * Set format here to overwrite it.
	 *
	 * @type String
	 * @default  ""
	 */
	dateFormat : '',
	
	/**
	 * File modification datetime format in form "Yesterday 12:23:01".
	 * Value from selected language data is used by default.
	 * Set format here to overwrite it.
	 * Use $1 for "Today"/"Yesterday" placeholder
	 *
	 * @type String
	 * @default  ""
	 * @example "$1 H:m:i"
	 */
	fancyDateFormat : '',
	
	/**
	 * Style of file mode at cwd-list, info dialog
	 * 'string' (ex. rwxr-xr-x) or 'octal' (ex. 755) or 'both' (ex. rwxr-xr-x (755))
	 * 
	 * @type {String}
	 * @default 'both'
	 */
	fileModeStyle : 'both',
	
	/**
	 * elFinder width
	 *
	 * @type String|Number
	 * @default  "auto"
	 */
	width : 'auto',
	
	/**
	 * elFinder node height
	 * Number: pixcel or String: Number + "%"
	 *
	 * @type Number | String
	 * @default  400
	 */
	height : 400,
	
	/**
	 * Base node object or selector
	 * Element which is the reference of the height percentage
	 *
	 * @type Object|String
	 * @default null | jQuery(window) (if height is percentage)
	 **/
	heightBase : null,
	
	/**
	 * Make elFinder resizable if jquery ui resizable available
	 *
	 * @type Boolean
	 * @default  true
	 */
	resizable : true,
	
	/**
	 * Timeout before open notifications dialogs
	 *
	 * @type Number
	 * @default  500 (.5 sec)
	 */
	notifyDelay : 500,
	
	/**
	 * Position CSS, Width of notifications dialogs
	 *
	 * @type Object
	 * @default {position: {}, width : null} - Apply CSS definition
	 * position: CSS object | null (null: position center & middle)
	 */
	notifyDialog : {position: {}, width : null},
	
	/**
	 * Dialog contained in the elFinder node
	 * 
	 * @type Boolean
	 * @default false
	 */
	dialogContained : false,
	
	/**
	 * Allow shortcuts
	 *
	 * @type Boolean
	 * @default  true
	 */
	allowShortcuts : true,
	
	/**
	 * Remeber last opened dir to open it after reload or in next session
	 *
	 * @type Boolean
	 * @default  true
	 */
	rememberLastDir : true,
	
	/**
	 * Clear historys(elFinder) on reload(not browser) function
	 * Historys was cleared on Reload function on elFinder 2.0 (value is true)
	 * 
	 * @type Boolean
	 * @default  false
	 */
	reloadClearHistory : false,
	
	/**
	 * Use browser native history with supported browsers
	 *
	 * @type Boolean
	 * @default  true
	 */
	useBrowserHistory : true,
	
	/**
	 * Lazy load config.
	 * How many files display at once?
	 *
	 * @type Number
	 * @default  50
	 */
	showFiles : 50,
	
	/**
	 * Lazy load config.
	 * Distance in px to cwd bottom edge to start display files
	 *
	 * @type Number
	 * @default  50
	 */
	showThreshold : 50,
	
	/**
	 * Additional rule to valid new file name.
	 * By default not allowed empty names or '..'
	 * This setting does not have a sense of security.
	 *
	 * @type false|RegExp|function
	 * @default  false
	 * @example
	 *  disable names with spaces:
	 *  validName : /^[^\s]+$/,
	 */
	validName : false,
	
	/**
	 * Additional rule to filtering for browsing.
	 * This setting does not have a sense of security.
	 * 
	 * The object `this` is elFinder instance object in this function
	 *
	 * @type false|RegExp|function
	 * @default  false
	 * @example
	 *  show only png and jpg files:
	 *  fileFilter : /.*\.(png|jpg)$/i,
	 *  
	 *  show only image type files:
	 *  fileFilter : function(file) { return file.mime && file.mime.match(/^image\//i); },
	 */
	fileFilter : false,
	
	/**
	 * Backup name suffix.
	 *
	 * @type String
	 * @default  "~"
	 */
	backupSuffix : '~',
	
	/**
	 * Sync content interval
	 *
	 * @type Number
	 * @default  0 (do not sync)
	 */
	sync : 0,
	
	/**
	 * Sync start on load if sync value >= 1000
	 *
	 * @type     Bool
	 * @default  true
	 */
	syncStart : true,
	
	/**
	 * How many thumbnails create in one request
	 *
	 * @type Number
	 * @default  5
	 */
	loadTmbs : 5,
	
	/**
	 * Cookie option for browsersdoes not suppot localStorage
	 *
	 * @type Object
	 */
	cookie         : {
		expires : 30,
		domain  : '',
		path    : '/',
		secure  : false
	},
	
	/**
	 * Contextmenu config
	 *
	 * @type Object
	 */
	contextmenu : {
		// navbarfolder menu
		navbar : ['open', 'opennew', 'download', '|', 'upload', 'mkdir', '|', 'copy', 'cut', 'paste', 'duplicate', '|', 'rm', 'empty', 'hide', '|', 'rename', '|', 'archive', '|', 'places', 'info', 'chmod', 'netunmount'],
		// current directory menu
		cwd    : ['undo', 'redo', '|', 'back', 'up', 'reload', '|', 'upload', 'mkdir', 'mkfile', 'paste', '|', 'empty', 'hide', '|', 'view', 'sort', 'selectall', 'colwidth', '|', 'places', 'info', 'chmod', 'netunmount', '|', 'fullscreen'],
		// current directory file menu
		files  : ['getfile', '|' ,'open', 'opennew', 'download', 'opendir', 'quicklook', '|', 'upload', 'mkdir', '|', 'copy', 'cut', 'paste', 'duplicate', '|', 'rm', 'empty', 'hide', '|', 'rename', 'edit', 'resize', '|', 'archive', 'extract', '|', 'selectall', 'selectinvert', '|', 'places', 'info', 'chmod', 'netunmount']
	},

	/**
	 * elFinder node enable always
	 * This value will set to `true` if <body> has elFinder node only
	 * 
	 * @type     Bool
	 * @default  false
	 */
	enableAlways : false,
	
	/**
	 * elFinder node enable by mouse over
	 * 
	 * @type     Bool
	 * @default  true
	 */
	enableByMouseOver : true,

	/**
	 * Show window close confirm dialog
	 * Value is which state to show
	 * 'hasNotifyDialog', 'editingFile', 'hasSelectedItem' and 'hasClipboardData'
	 * 
	 * @type     Array
	 * @default  ['hasNotifyDialog', 'editingFile']
	 */
	windowCloseConfirm : ['hasNotifyDialog', 'editingFile'],

	/**
	 * Function decoding 'raw' string converted to unicode
	 * It is used instead of fm.decodeRawString(str)
	 * 
	 * @type Null|Function
	 */
	rawStringDecoder : typeof Encoding === 'object' && jQuery.isFunction(Encoding.convert)? function(str) {
		return Encoding.convert(str, {
			to: 'UNICODE',
			type: 'string'
		});
	} : null,

	/**
	 * Debug config
	 *
	 * @type Array|String('auto')|Boolean(true|false)
	 */
	// debug : true
	debug : ['error', 'warning', 'event-destroy']
};


/*
 * File: /js/elFinder.options.netmount.js
 */

/**
 * Default elFinder config of commandsOptions.netmount
 *
 * @type  Object
 */

elFinder.prototype._options.commandsOptions.netmount = {
	ftp: {
		name : 'FTP',
		inputs: {
			host     : jQuery('<input type="text"/>'),
			port     : jQuery('<input type="number" placeholder="21" class="elfinder-input-optional"/>'),
			path     : jQuery('<input type="text" value="/"/>'),
			user     : jQuery('<input type="text"/>'),
			pass     : jQuery('<input type="password" autocomplete="new-password"/>'),
			FTPS     : jQuery('<input type="checkbox" value="1" title="File Transfer Protocol over SSL/TLS"/>'),
			encoding : jQuery('<input type="text" placeholder="Optional" class="elfinder-input-optional"/>'),
			locale   : jQuery('<input type="text" placeholder="Optional" class="elfinder-input-optional"/>')
		}
	},
	dropbox2: elFinder.prototype.makeNetmountOptionOauth('dropbox2', 'Dropbox', 'Dropbox', {noOffline : true,
		root : '/',
		pathI18n : 'path',
		integrate : {
			title: 'Dropbox.com',
			link: 'https://www.dropbox.com'
		}
	}),
	googledrive: elFinder.prototype.makeNetmountOptionOauth('googledrive', 'Google Drive', 'Google', {
		integrate : {
			title: 'Google Drive',
			link: 'https://www.google.com/drive/'
		}
	}),
	onedrive: elFinder.prototype.makeNetmountOptionOauth('onedrive', 'One Drive', 'OneDrive', {
		integrate : {
			title: 'Microsoft OneDrive',
			link: 'https://onedrive.live.com'
		}
	}),
	box: elFinder.prototype.makeNetmountOptionOauth('box', 'Box', 'Box', {
		noOffline : true,
		integrate : {
			title: 'Box.com',
			link: 'https://www.box.com'
		}
	})
};


/*
 * File: /js/elFinder.history.js
 */

/**
 * @class elFinder.history
 * Store visited folders
 * and provide "back" and "forward" methods
 *
 * @author Dmitry (dio) Levashov
 */
elFinder.prototype.history = function(fm) {
		var self = this,
		/**
		 * Update history on "open" event?
		 *
		 * @type Boolean
		 */
		update = true,
		/**
		 * Directories hashes storage
		 *
		 * @type Array
		 */
		history = [],
		/**
		 * Current directory index in history
		 *
		 * @type Number
		 */
		current,
		/**
		 * Clear history
		 *
		 * @return void
		 */
		reset = function() {
			history = [fm.cwd().hash];
			current = 0;
			update  = true;
		},
		/**
		 * Browser native history object
		 */
		nativeHistory = (fm.options.useBrowserHistory && window.history && window.history.pushState)? window.history : null,
		/**
		 * Open prev/next folder
		 *
		 * @Boolen  open next folder?
		 * @return jQuery.Deferred
		 */
		go = function(fwd) {
			if ((fwd && self.canForward()) || (!fwd && self.canBack())) {
				update = false;
				return fm.exec('open', history[fwd ? ++current : --current]).fail(reset);
			}
			return jQuery.Deferred().reject();
		},
		/**
		 * Sets the native history.
		 *
		 * @param String thash target hash
		 */
		setNativeHistory = function(thash) {
			if (nativeHistory && (! nativeHistory.state || nativeHistory.state.thash !== thash)) {
				nativeHistory.pushState({thash: thash}, null, location.pathname + location.search + (thash? '#elf_' + thash : ''));
			}
		};
	
	/**
	 * Return true if there is previous visited directories
	 *
	 * @return Boolen
	 */
	this.canBack = function() {
		return current > 0;
	};
	
	/**
	 * Return true if can go forward
	 *
	 * @return Boolen
	 */
	this.canForward = function() {
		return current < history.length - 1;
	};
	
	/**
	 * Go back
	 *
	 * @return void
	 */
	this.back = go;
	
	/**
	 * Go forward
	 *
	 * @return void
	 */
	this.forward = function() {
		return go(true);
	};
	
	// bind to elfinder events
	fm.bind('init', function() {
		if (nativeHistory && !nativeHistory.state) {
			setNativeHistory(fm.startDir());
		}
	})
	.open(function() {
		var l = history.length,
			cwd = fm.cwd().hash;

		if (update) {
			current >= 0 && l > current + 1 && history.splice(current+1);
			history[history.length-1] != cwd && history.push(cwd);
			current = history.length - 1;
		}
		update = true;

		setNativeHistory(cwd);
	})
	.reload(fm.options.reloadClearHistory && reset);
	
};


/*
 * File: /js/elFinder.command.js
 */

/**
 * elFinder command prototype
 *
 * @type  elFinder.command
 * @author  Dmitry (dio) Levashov
 */
elFinder.prototype.command = function(fm) {
		/**
	 * elFinder instance
	 *
	 * @type  elFinder
	 */
	this.fm = fm;
	
	/**
	 * Command name, same as class name
	 *
	 * @type  String
	 */
	this.name = '';
	
	/**
	 * Dialog class name
	 *
	 * @type  String
	 */
	this.dialogClass = '';

	/**
	 * Command icon class name with out 'elfinder-button-icon-'
	 * Use this.name if it is empty
	 *
	 * @type  String
	 */
	this.className = '';

	/**
	 * Short command description
	 *
	 * @type  String
	 */
	this.title = '';
	
	/**
	 * Linked(Child) commands name
	 * They are loaded together when tthis command is loaded.
	 * 
	 * @type  Array
	 */
	this.linkedCmds = [];
	
	/**
	 * Current command state
	 *
	 * @example
	 * this.state = -1; // command disabled
	 * this.state = 0;  // command enabled
	 * this.state = 1;  // command active (for example "fullscreen" command while elfinder in fullscreen mode)
	 * @default -1
	 * @type  Number
	 */
	this.state = -1;
	
	/**
	 * If true, command can not be disabled by connector.
	 * @see this.update()
	 *
	 * @type  Boolen
	 */
	this.alwaysEnabled = false;
	
	/**
	 * Do not change dirctory on removed current work directory
	 * 
	 * @type  Boolen
	 */
	this.noChangeDirOnRemovedCwd = false;
	
	/**
	 * If true, this means command was disabled by connector.
	 * @see this.update()
	 *
	 * @type  Boolen
	 */
	this._disabled = false;
	
	/**
	 * If true, this command is disabled on serach results
	 * 
	 * @type  Boolean
	 */
	this.disableOnSearch = false;
	
	/**
	 * Call update() when event select fired
	 * 
	 * @type  Boolean
	 */
	this.updateOnSelect = true;
	
	/**
	 * Sync toolbar button title on change
	 * 
	 * @type  Boolean
	 */
	this.syncTitleOnChange = false;

	/**
	 * Keep display of the context menu when command execution
	 * 
	 * @type  Boolean
	 */
	this.keepContextmenu = false;
	
	/**
	 * elFinder events defaults handlers.
	 * Inside handlers "this" is current command object
	 *
	 * @type  Object
	 */
	this._handlers = {
		enable  : function() { this.update(void(0), this.value); },
		disable : function() { this.update(-1, this.value); },
		'open reload load sync'    : function() { 
			this._disabled = !(this.alwaysEnabled || this.fm.isCommandEnabled(this.name));
			this.update(void(0), this.value);
			this.change(); 
		}
	};
	
	/**
	 * elFinder events handlers.
	 * Inside handlers "this" is current command object
	 *
	 * @type  Object
	 */
	this.handlers = {};
	
	/**
	 * Shortcuts
	 *
	 * @type  Array
	 */
	this.shortcuts = [];
	
	/**
	 * Command options
	 *
	 * @type  Object
	 */
	this.options = {ui : 'button'};
	
	/**
	 * Callback functions on `change` event
	 * 
	 * @type  Array
	 */
	this.listeners = [];

	/**
	 * Prepare object -
	 * bind events and shortcuts
	 *
	 * @return void
	 */
	this.setup = function(name, opts) {
		var self = this,
			fm   = this.fm,
			setCallback = function(s) {
				var cb = s.callback || function(e) {
							fm.exec(self.name, void(0), {
							_userAction: true,
							_currentType: 'shortcut'
						});
					};
				s.callback = function(e) {
					var enabled, checks = {};
					if (self.enabled()) {
						if (fm.searchStatus.state < 2) {
							enabled = fm.isCommandEnabled(self.name);
						} else {
							jQuery.each(fm.selected(), function(i, h) {
								if (fm.optionsByHashes[h]) {
									checks[h] = true;
								} else {
									jQuery.each(fm.volOptions, function(id) {
										if (!checks[id] && h.indexOf(id) === 0) {
											checks[id] = true;
											return false;
										}
									});
								}
							});
							jQuery.each(checks, function(h) {
								enabled = fm.isCommandEnabled(self.name, h);
								if (! enabled) {
									return false;
								}
							});
						}
						if (enabled) {
							self.event = e;
							cb.call(self);
							delete self.event;
						}
					}
				};
			},
			i, s, sc;

		this.name      = name;
		this.title     = fm.messages['cmd'+name] ? fm.i18n('cmd'+name)
		               : ((this.extendsCmd && fm.messages['cmd'+this.extendsCmd]) ? fm.i18n('cmd'+this.extendsCmd) : name);
		this.options   = Object.assign({}, this.options, opts);
		this.listeners = [];
		this.dialogClass = 'elfinder-dialog-' + name;

		if (opts.shortcuts) {
			if (typeof opts.shortcuts === 'function') {
				sc = opts.shortcuts(this.fm, this.shortcuts);
			} else if (Array.isArray(opts.shortcuts)) {
				sc = opts.shortcuts;
			}
			this.shortcuts = sc || [];
		}

		if (this.updateOnSelect) {
			this._handlers.select = function() { this.update(void(0), this.value); };
		}

		jQuery.each(Object.assign({}, self._handlers, self.handlers), function(cmd, handler) {
			fm.bind(cmd, jQuery.proxy(handler, self));
		});

		for (i = 0; i < this.shortcuts.length; i++) {
			s = this.shortcuts[i];
			setCallback(s);
			!s.description && (s.description = this.title);
			fm.shortcut(s);
		}

		if (this.disableOnSearch) {
			fm.bind('search searchend', function() {
				self._disabled = this.type === 'search'? true : ! (this.alwaysEnabled || fm.isCommandEnabled(name));
				self.update(void(0), self.value);
			});
		}

		this.init();
	};

	/**
	 * Command specific init stuffs
	 *
	 * @return void
	 */
	this.init = function() {};

	/**
	 * Exec command
	 *
	 * @param  Array         target files hashes
	 * @param  Array|Object  command value
	 * @return jQuery.Deferred
	 */
	this.exec = function(files, opts) { 
		return jQuery.Deferred().reject(); 
	};
	
	this.getUndo = function(opts, resData) {
		return false;
	};
	
	/**
	 * Return true if command disabled.
	 *
	 * @return Boolen
	 */
	this.disabled = function() {
		return this.state < 0;
	};
	
	/**
	 * Return true if command enabled.
	 *
	 * @return Boolen
	 */
	this.enabled = function() {
		return this.state > -1;
	};
	
	/**
	 * Return true if command active.
	 *
	 * @return Boolen
	 */
	this.active = function() {
		return this.state > 0;
	};
	
	/**
	 * Return current command state.
	 * Must be overloaded in most commands
	 *
	 * @return Number
	 */
	this.getstate = function() {
		return -1;
	};
	
	/**
	 * Update command state/value
	 * and rize 'change' event if smth changed
	 *
	 * @param  Number  new state or undefined to auto update state
	 * @param  mixed   new value
	 * @return void
	 */
	this.update = function(s, v) {
		var state = this.state,
			value = this.value;

		if (this._disabled && this.fm.searchStatus === 0) {
			this.state = -1;
		} else {
			this.state = s !== void(0) ? s : this.getstate();
		}

		this.value = v;
		
		if (state != this.state || value != this.value) {
			this.change();
		}
	};
	
	/**
	 * Bind handler / fire 'change' event.
	 *
	 * @param  Function|undefined  event callback
	 * @return void
	 */
	this.change = function(c) {
		var cmd, i;
		
		if (typeof(c) === 'function') {
			this.listeners.push(c);			
		} else {
			for (i = 0; i < this.listeners.length; i++) {
				cmd = this.listeners[i];
				try {
					cmd(this.state, this.value);
				} catch (e) {
					this.fm.debug('error', e);
				}
			}
		}
		return this;
	};
	

	/**
	 * With argument check given files hashes and return list of existed files hashes.
	 * Without argument return selected files hashes.
	 *
	 * @param  Array|String|void  hashes
	 * @return Array
	 */
	this.hashes = function(hashes) {
		return hashes
			? jQuery.grep(Array.isArray(hashes) ? hashes : [hashes], function(hash) { return fm.file(hash) ? true : false; })
			: fm.selected();
	};
	
	/**
	 * Return only existed files from given fils hashes | selected files
	 *
	 * @param  Array|String|void  hashes
	 * @return Array
	 */
	this.files = function(hashes) {
		var fm = this.fm;
		
		return hashes
			? jQuery.map(Array.isArray(hashes) ? hashes : [hashes], function(hash) { return fm.file(hash) || null; })
			: fm.selectedFiles();
	};

	/**
	 * Wrapper to fm.dialog()
	 *
	 * @param  String|DOMElement  content
	 * @param  Object             options
	 * @return Object             jQuery element object
	 */
	this.fmDialog = function(content, options) {
		if (options.cssClass) {
			options.cssClass += ' ' + this.dialogClass;
		} else {
			options.cssClass = this.dialogClass;
		}
		return this.fm.dialog(content, options);
	};
};


/*
 * File: /js/elFinder.resources.js
 */

/**
 * elFinder resources registry.
 * Store shared data
 *
 * @type Object
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.resources = {
	'class' : {
		hover       : 'ui-state-hover',
		active      : 'ui-state-active',
		disabled    : 'ui-state-disabled',
		draggable   : 'ui-draggable',
		droppable   : 'ui-droppable',
		adroppable  : 'elfinder-droppable-active',
		cwdfile     : 'elfinder-cwd-file',
		cwd         : 'elfinder-cwd',
		tree        : 'elfinder-tree',
		treeroot    : 'elfinder-navbar-root',
		navdir      : 'elfinder-navbar-dir',
		navdirwrap  : 'elfinder-navbar-dir-wrapper',
		navarrow    : 'elfinder-navbar-arrow',
		navsubtree  : 'elfinder-navbar-subtree',
		navcollapse : 'elfinder-navbar-collapsed',
		navexpand   : 'elfinder-navbar-expanded',
		treedir     : 'elfinder-tree-dir',
		placedir    : 'elfinder-place-dir',
		searchbtn   : 'elfinder-button-search',
		editing     : 'elfinder-to-editing',
		preventback : 'elfinder-prevent-back',
		tabstab     : 'ui-state-default ui-tabs-tab ui-corner-top ui-tab',
		tabsactive  : 'ui-tabs-active ui-state-active'
	},
	tpl : {
		perms      : '<span class="elfinder-perms"/>',
		lock       : '<span class="elfinder-lock"/>',
		symlink    : '<span class="elfinder-symlink"/>',
		navicon    : '<span class="elfinder-nav-icon"/>',
		navspinner : '<span class="elfinder-spinner elfinder-navbar-spinner"/>',
		navdir     : '<div class="elfinder-navbar-wrapper{root}"><span id="{id}" class="ui-corner-all elfinder-navbar-dir {cssclass}"><span class="elfinder-navbar-arrow"/><span class="elfinder-navbar-icon" {style}/>{symlink}{permissions}{name}</span><div class="elfinder-navbar-subtree" style="display:none"/></div>',
		placedir   : '<div class="elfinder-navbar-wrapper"><span id="{id}" class="ui-corner-all elfinder-navbar-dir {cssclass}" title="{title}"><span class="elfinder-navbar-arrow"/><span class="elfinder-navbar-icon" {style}/>{symlink}{permissions}{name}</span><div class="elfinder-navbar-subtree" style="display:none"/></div>'
		
	},
	// mimes.text will be overwritten with connector config if `textMimes` is included in initial response
	// @see php/elFInder.class.php `public static $textMimes`
	mimes : {
		text : [
			'application/dash+xml',
			'application/docbook+xml',
			'application/javascript',
			'application/json',
			'application/plt',
			'application/sat',
			'application/sql',
			'application/step',
			'application/vnd.hp-hpgl',
			'application/x-awk',
			'application/x-config',
			'application/x-csh',
			'application/x-empty',
			'application/x-mpegurl',
			'application/x-perl',
			'application/x-php',
			'application/x-web-config',
			'application/xhtml+xml',
			'application/xml',
			'audio/x-mp3-playlist',
			'image/cgm',
			'image/svg+xml',
			'image/vnd.dxf',
			'model/iges'
		]
	},
	
	mixin : {
		make : function() {
						var self = this,
				fm   = this.fm,
				cmd  = this.name,
				req  = this.requestCmd || cmd,
				wz   = fm.getUI('workzone'),
				org  = (this.origin && this.origin === 'navbar')? 'tree' : 'cwd',
				tree = (org === 'tree'),
				find = tree? 'navHash2Elm' : 'cwdHash2Elm',
				tarea= (! tree && fm.storage('view') != 'list'),
				sel  = fm.selected(),
				move = this.move || false,
				empty= wz.hasClass('elfinder-cwd-wrapper-empty'),
				unselect = function() {
					requestAnimationFrame(function() {
						input && input.trigger('blur');
					});
				},
				rest = function(){
					if (!overlay.is(':hidden')) {
						overlay.elfinderoverlay('hide').off('click close', cancel);
					}
					pnode.removeClass('ui-front')
						.css('position', '')
						.off('unselect.'+fm.namespace, unselect);
					if (tarea) {
						nnode && nnode.css('max-height', '');
					} else if (!tree) {
						pnode.css('width', '')
							.parent('td').css('overflow', '');
					}
				}, colwidth,
				dfrd = jQuery.Deferred()
					.fail(function(error) {
						dstCls && dst.attr('class', dstCls);
						empty && wz.addClass('elfinder-cwd-wrapper-empty');
						if (sel) {
							move && fm.trigger('unlockfiles', {files: sel});
							fm.clipboard([]);
							fm.trigger('selectfiles', { files: sel });
						}
						error && fm.error(error);
					})
					.always(function() {
						rest();
						cleanup();
						fm.enable().unbind('open', openCallback).trigger('resMixinMake');
					}),
				id    = 'tmp_'+parseInt(Math.random()*100000),
				phash = this.data && this.data.target? this.data.target : (tree? fm.file(sel[0]).hash : fm.cwd().hash),
				date = new Date(),
				file   = {
					hash  : id,
					phash : phash,
					name  : fm.uniqueName(this.prefix, phash),
					mime  : this.mime,
					read  : true,
					write : true,
					date  : 'Today '+date.getHours()+':'+date.getMinutes(),
					move  : move
				},
				dum = fm.getUI(org).trigger('create.'+fm.namespace, file),
				data = this.data || {},
				node = fm[find](id),
				nnode, pnode,
				overlay = fm.getUI('overlay'),
				cleanup = function() {
					if (node && node.length) {
						input.off();
						node.hide();
						fm.unselectfiles({files : [id]}).unbind('resize', resize);
						requestAnimationFrame(function() {
							if (tree) {
								node.closest('.elfinder-navbar-wrapper').remove();
							} else {
								node.remove();
							}
						});
					}
				},
				cancel = function(e) { 
					if (!overlay.is(':hidden')) {
						pnode.css('z-index', '');
					}
					if (! inError) {
						cleanup();
						dfrd.reject();
						if (e) {
							e.stopPropagation();
							e.preventDefault();
						}
					}
				},
				input = jQuery(tarea? '<textarea/>' : '<input type="text"/>')
					.on('keyup text', function(){
						if (tarea) {
							this.style.height = '1px';
							this.style.height = this.scrollHeight + 'px';
						} else if (colwidth) {
							this.style.width = colwidth + 'px';
							if (this.scrollWidth > colwidth) {
								this.style.width = this.scrollWidth + 10 + 'px';
							}
						}
					})
					.on('keydown', function(e) {
						e.stopImmediatePropagation();
						if (e.keyCode == jQuery.ui.keyCode.ESCAPE) {
							dfrd.reject();
						} else if (e.keyCode == jQuery.ui.keyCode.ENTER) {
							e.preventDefault();
							input.trigger('blur');
						}
					})
					.on('mousedown click dblclick', function(e) {
						e.stopPropagation();
						if (e.type === 'dblclick') {
							e.preventDefault();
						}
					})
					.on('blur', function() {
						var name   = jQuery.trim(input.val()),
							parent = input.parent(),
							valid  = true,
							cut;

						if (!overlay.is(':hidden')) {
							pnode.css('z-index', '');
						}
						if (name === '') {
							return cancel();
						}
						if (!inError && parent.length) {

							if (fm.options.validName && fm.options.validName.test) {
								try {
									valid = fm.options.validName.test(name);
								} catch(e) {
									valid = false;
								}
							}
							if (!name || name === '.' || name === '..' || !valid) {
								inError = true;
								fm.error(file.mime === 'directory'? 'errInvDirname' : 'errInvName', {modal: true, close: function(){setTimeout(select, 120);}});
								return false;
							}
							if (fm.fileByName(name, phash)) {
								inError = true;
								fm.error(['errExists', name], {modal: true, close: function(){setTimeout(select, 120);}});
								return false;
							}

							cut = (sel && move)? fm.exec('cut', sel) : null;

							jQuery.when(cut)
							.done(function() {
								var toast   = {},
									nextAct = {};
								
								rest();
								input.hide().before(jQuery('<span>').text(name));

								fm.lockfiles({files : [id]});

								fm.request({
										data        : Object.assign({cmd : req, name : name, target : phash}, data || {}), 
										notify      : {type : req, cnt : 1},
										preventFail : true,
										syncOnFail  : true,
										navigate    : {toast : toast},
									})
									.fail(function(error) {
										fm.unlockfiles({files : [id]});
										inError = true;
										input.show().prev().remove();
										fm.error(error, {
											modal: true,
											close: function() {
												if (Array.isArray(error) && jQuery.inArray('errUploadMime', error) !== -1) {
													dfrd.notify('errUploadMime').reject();
												} else {
													setTimeout(select, 120);
												}
											}
										});
									})
									.done(function(data) {
										if (data && data.added && data.added[0]) {
											var item    = data.added[0],
												dirhash = item.hash,
												newItem = fm[find](dirhash),
												acts    = {
													'directory' : { cmd: 'open', msg: 'cmdopendir' },
													'text'      : { cmd: 'edit', msg: 'cmdedit' },
													'default'   : { cmd: 'open', msg: 'cmdopen' }
												},
												tmpMimes;
											if (sel && move) {
												fm.one(req+'done', function() {
													fm.exec('paste', dirhash);
												});
											}
											if (!move) {
												if (fm.mimeIsText(item.mime) && !fm.mimesCanMakeEmpty[item.mime] && fm.mimeTypes[item.mime]) {
													fm.trigger('canMakeEmptyFile', {mimes: [item.mime], unshift: true});
													tmpMimes = {};
													tmpMimes[item.mime] = fm.mimeTypes[item.mime];
													fm.storage('mkfileTextMimes', Object.assign(tmpMimes, fm.storage('mkfileTextMimes') || {}));
												}
												Object.assign(nextAct, nextAction || acts[item.mime] || acts[item.mime.split('/')[0]] || acts[(fm.mimesCanMakeEmpty[item.mime] || jQuery.inArray(item.mime, fm.resources.mimes.text) !== -1) ? 'text' : 'none'] || acts['default']);
												Object.assign(toast, nextAct.cmd ? {
													incwd    : {msg: fm.i18n(['complete', fm.i18n('cmd'+cmd)]), action: nextAct},
													inbuffer : {msg: fm.i18n(['complete', fm.i18n('cmd'+cmd)]), action: nextAct}
												} : {
													inbuffer : {msg: fm.i18n(['complete', fm.i18n('cmd'+cmd)])}
												});
											}
										}
										dfrd.resolve(data);
									});
							})
							.fail(function() {
								dfrd.reject();
							});
						}
					})
					.on('dragenter dragleave dragover drop', function(e) {
						// stop bubbling to prevent upload with native drop event
						e.stopPropagation();
					}),
				select = function() {
					var name = fm.splitFileExtention(input.val())[0];
					if (!inError && fm.UA.Mobile && !fm.UA.iOS) { // since iOS has a bug? (z-index not effect) so disable it
						overlay.on('click close', cancel).elfinderoverlay('show');
						pnode.css('z-index', overlay.css('z-index') + 1);
					}
					inError = false;
					! fm.enabled() && fm.enable();
					input.trigger('focus').trigger('select');
					input[0].setSelectionRange && input[0].setSelectionRange(0, name.length);
				},
				resize = function() {
					node.trigger('scrolltoview', {blink : false});
				},
				openCallback = function() {
					dfrd && (dfrd.state() === 'pending') && dfrd.reject();
				},
				inError = false,
				nextAction,
				// for tree
				dst, dstCls, collapsed, expanded, arrow, subtree;

			if (!fm.isCommandEnabled(req, phash) || !node.length) {
				return dfrd.reject();
			}

			if (jQuery.isPlainObject(self.nextAction)){
				nextAction = Object.assign({}, self.nextAction);
			}
			
			if (tree) {
				dst = fm[find](phash);
				collapsed = fm.res('class', 'navcollapse');
				expanded  = fm.res('class', 'navexpand');
				arrow = fm.res('class', 'navarrow');
				subtree = fm.res('class', 'navsubtree');
				
				node.closest('.'+subtree).show();
				if (! dst.hasClass(collapsed)) {
					dstCls = dst.attr('class');
					dst.addClass(collapsed+' '+expanded+' elfinder-subtree-loaded');
				}
				if (dst.is('.'+collapsed+':not(.'+expanded+')')) {
					dst.children('.'+arrow).trigger('click').data('dfrd').done(function() {
						if (input.val() === file.name) {
							input.val(fm.uniqueName(self.prefix, phash)).trigger('select').trigger('focus');
						}
					});
				}
				nnode = node.contents().filter(function(){ return this.nodeType==3 && jQuery(this).parent().attr('id') === fm.navHash2Id(file.hash); });
				pnode = nnode.parent();
				nnode.replaceWith(input.val(file.name));
			} else {
				empty && wz.removeClass('elfinder-cwd-wrapper-empty');
				nnode = node.find('.elfinder-cwd-filename');
				pnode = nnode.parent();
				if (tarea) {
					nnode.css('max-height', 'none');
				} else {
					colwidth = pnode.width();
					pnode.width(colwidth - 15)
						.parent('td').css('overflow', 'visible');
				}
				nnode.empty().append(input.val(file.name));
			}
			pnode.addClass('ui-front')
				.css('position', 'relative')
				.on('unselect.'+fm.namespace, unselect);
			
			fm.bind('resize', resize).one('open', openCallback);
			
			input.trigger('keyup');
			select();

			return dfrd;

		}
	},
	blink: function(elm, mode) {
				var acts = {
			slowonce : function(){elm.hide().delay(250).fadeIn(750).delay(500).fadeOut(3500);},
			lookme   : function(){elm.show().fadeOut(500).fadeIn(750);}
		}, func;
		mode = mode || 'slowonce';
		
		func = acts[mode] || acts['lookme'];
		
		elm.stop(true, true);
		func();
	}
};


/*
 * File: /js/jquery.dialogelfinder.js
 */

/**
 * @class dialogelfinder - open elFinder in dialog window
 *
 * @param  Object  elFinder options with dialog options
 * @example
 * jQuery(selector).dialogelfinder({
 *     // some elfinder options
 *     title          : 'My files', // dialog title, default = "Files"
 *     width          : 850,        // dialog width, default 840
 *     autoOpen       : false,      // if false - dialog will not be opened after init, default = true
 *     destroyOnClose : true        // destroy elFinder on close dialog, default = false
 * })
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.dialogelfinder = function(opts) {
		var position = 'elfinderPosition',
		destroy  = 'elfinderDestroyOnClose',
		node;
	
	this.not('.elfinder').each(function() {

		
		var doc     = jQuery(document),
			toolbar = jQuery('<div class="ui-widget-header dialogelfinder-drag ui-corner-top">'+(opts.title || 'Files')+'</div>'),
			button  = jQuery('<a href="#" class="dialogelfinder-drag-close ui-corner-all"><span class="ui-icon ui-icon-closethick"> </span></a>')
				.appendTo(toolbar)
				.on('click', function(e) {
					e.preventDefault();
					
					node.dialogelfinder('close');
				}),
			node    = jQuery(this).addClass('dialogelfinder')
				.css('position', 'absolute')
				.hide()
				.appendTo('body')
				.draggable({
					handle : '.dialogelfinder-drag',
					containment : 'window',
					stop : function() {
						node.trigger('resize');
						elfinder.trigger('resize');
					}
				})
				.elfinder(opts)
				.prepend(toolbar),
			elfinder = node.elfinder('instance');
		
		
		node.width(parseInt(node.width()) || 840) // fix width if set to "auto"
			.data(destroy, !!opts.destroyOnClose)
			.find('.elfinder-toolbar').removeClass('ui-corner-top');
		
		opts.position && node.data(position, opts.position);
		
		opts.autoOpen !== false && jQuery(this).dialogelfinder('open');

	});
	
	if (opts == 'open') {
		var node = jQuery(this),
			pos  = node.data(position) || {
				top  : parseInt(jQuery(document).scrollTop() + (jQuery(window).height() < node.height() ? 2 : (jQuery(window).height() - node.height())/2)),
				left : parseInt(jQuery(document).scrollLeft() + (jQuery(window).width() < node.width()  ? 2 : (jQuery(window).width()  - node.width())/2))
			};

		if (node.is(':hidden')) {
			node.addClass('ui-front').css(pos).show().trigger('resize');

			setTimeout(function() {
				// fix resize icon position and make elfinder active
				node.trigger('resize').trigger('mousedown');
			}, 200);
		}
	} else if (opts == 'close') {
		node = jQuery(this).removeClass('ui-front');
			
		if (node.is(':visible')) {
			!!node.data(destroy)
				? node.elfinder('destroy').remove()
				: node.elfinder('close');
		}
	} else if (opts == 'instance') {
		return jQuery(this).getElFinder();
	}

	return this;
};


/*
 * File: /js/i18n/elfinder.en.js
 */

/**
 * English translation
 * @author Troex Nevelin <troex@fury.scancode.ru>
 * @author Naoki Sawada <hypweb+elfinder@gmail.com>
 * @version 2018-12-09
 */
// elfinder.en.js is integrated into elfinder.(full|min).js by jake build
if (typeof elFinder === 'function' && elFinder.prototype.i18) {
	elFinder.prototype.i18.en = {
		translator : 'Troex Nevelin &lt;troex@fury.scancode.ru&gt;, Naoki Sawada &lt;hypweb+elfinder@gmail.com&gt;',
		language   : 'English',
		direction  : 'ltr',
		dateFormat : 'M d, Y h:i A', // will show like: Aug 24, 2018 04:39 PM
		fancyDateFormat : '$1 h:i A', // will show like: Today 04:39 PM
		nonameDateFormat : 'ymd-His', // noname upload will show like: 180824-163916
		messages   : {

			/********************************** errors **********************************/
			'error'                : 'Error',
			'errUnknown'           : 'Unknown error.',
			'errUnknownCmd'        : 'Unknown command.',
			'errJqui'              : 'Invalid jQuery UI configuration. Selectable, draggable and droppable components must be included.',
			'errNode'              : 'elFinder requires DOM Element to be created.',
			'errURL'               : 'Invalid elFinder configuration! URL option is not set.',
			'errAccess'            : 'Access denied.',
			'errConnect'           : 'Unable to connect to backend.',
			'errAbort'             : 'Connection aborted.',
			'errTimeout'           : 'Connection timeout.',
			'errNotFound'          : 'Backend not found.',
			'errResponse'          : 'Invalid backend response.',
			'errConf'              : 'Invalid backend configuration.',
			'errJSON'              : 'PHP JSON module not installed.',
			'errNoVolumes'         : 'Readable volumes not available.',
			'errCmdParams'         : 'Invalid parameters for command "$1".',
			'errDataNotJSON'       : 'Data is not JSON.',
			'errDataEmpty'         : 'Data is empty.',
			'errCmdReq'            : 'Backend request requires command name.',
			'errOpen'              : 'Unable to open "$1".',
			'errNotFolder'         : 'Object is not a folder.',
			'errNotFile'           : 'Object is not a file.',
			'errRead'              : 'Unable to read "$1".',
			'errWrite'             : 'Unable to write into "$1".',
			'errPerm'              : 'Permission denied.',
			'errLocked'            : '"$1" is locked and can not be renamed, moved or removed.',
			'errExists'            : 'Item named "$1" already exists.',
			'errInvName'           : 'Invalid file name.',
			'errInvDirname'        : 'Invalid folder name.',  // from v2.1.24 added 12.4.2017
			'errFolderNotFound'    : 'Folder not found.',
			'errFileNotFound'      : 'File not found.',
			'errTrgFolderNotFound' : 'Target folder "$1" not found.',
			'errPopup'             : 'Browser prevented opening popup window. To open file enable it in browser options.',
			'errMkdir'             : 'Unable to create folder "$1".',
			'errMkfile'            : 'Unable to create file "$1".',
			'errRename'            : 'Unable to rename "$1".',
			'errCopyFrom'          : 'Copying files from volume "$1" not allowed.',
			'errCopyTo'            : 'Copying files to volume "$1" not allowed.',
			'errMkOutLink'         : 'Unable to create a link to outside the volume root.', // from v2.1 added 03.10.2015
			'errUpload'            : 'Upload error.',  // old name - errUploadCommon
			'errUploadFile'        : 'Unable to upload "$1".', // old name - errUpload
			'errUploadNoFiles'     : 'No files found for upload.',
			'errUploadTotalSize'   : 'Data exceeds the maximum allowed size.', // old name - errMaxSize
			'errUploadFileSize'    : 'File exceeds maximum allowed size.', //  old name - errFileMaxSize
			'errUploadMime'        : 'File type not allowed.',
			'errUploadTransfer'    : '"$1" transfer error.',
			'errUploadTemp'        : 'Unable to make temporary file for upload.', // from v2.1 added 26.09.2015
			'errNotReplace'        : 'Object "$1" already exists at this location and can not be replaced by object with another type.', // new
			'errReplace'           : 'Unable to replace "$1".',
			'errSave'              : 'Unable to save "$1".',
			'errCopy'              : 'Unable to copy "$1".',
			'errMove'              : 'Unable to move "$1".',
			'errCopyInItself'      : 'Unable to copy "$1" into itself.',
			'errRm'                : 'Unable to remove "$1".',
			'errTrash'             : 'Unable into trash.', // from v2.1.24 added 30.4.2017
			'errRmSrc'             : 'Unable remove source file(s).',
			'errExtract'           : 'Unable to extract files from "$1".',
			'errArchive'           : 'Unable to create archive.',
			'errArcType'           : 'Unsupported archive type.',
			'errNoArchive'         : 'File is not archive or has unsupported archive type.',
			'errCmdNoSupport'      : 'Backend does not support this command.',
			'errReplByChild'       : 'The folder "$1" can\'t be replaced by an item it contains.',
			'errArcSymlinks'       : 'For security reason denied to unpack archives contains symlinks or files with not allowed names.', // edited 24.06.2012
			'errArcMaxSize'        : 'Archive files exceeds maximum allowed size.',
			'errResize'            : 'Unable to resize "$1".',
			'errResizeDegree'      : 'Invalid rotate degree.',  // added 7.3.2013
			'errResizeRotate'      : 'Unable to rotate image.',  // added 7.3.2013
			'errResizeSize'        : 'Invalid image size.',  // added 7.3.2013
			'errResizeNoChange'    : 'Image size not changed.',  // added 7.3.2013
			'errUsupportType'      : 'Unsupported file type.',
			'errNotUTF8Content'    : 'File "$1" is not in UTF-8 and cannot be edited.',  // added 9.11.2011
			'errNetMount'          : 'Unable to mount "$1".', // added 17.04.2012
			'errNetMountNoDriver'  : 'Unsupported protocol.',     // added 17.04.2012
			'errNetMountFailed'    : 'Mount failed.',         // added 17.04.2012
			'errNetMountHostReq'   : 'Host required.', // added 18.04.2012
			'errSessionExpires'    : 'Your session has expired due to inactivity.',
			'errCreatingTempDir'   : 'Unable to create temporary directory: "$1"',
			'errFtpDownloadFile'   : 'Unable to download file from FTP: "$1"',
			'errFtpUploadFile'     : 'Unable to upload file to FTP: "$1"',
			'errFtpMkdir'          : 'Unable to create remote directory on FTP: "$1"',
			'errArchiveExec'       : 'Error while archiving files: "$1"',
			'errExtractExec'       : 'Error while extracting files: "$1"',
			'errNetUnMount'        : 'Unable to unmount.', // from v2.1 added 30.04.2012
			'errConvUTF8'          : 'Not convertible to UTF-8', // from v2.1 added 08.04.2014
			'errFolderUpload'      : 'Try the modern browser, If you\'d like to upload the folder.', // from v2.1 added 26.6.2015
			'errSearchTimeout'     : 'Timed out while searching "$1". Search result is partial.', // from v2.1 added 12.1.2016
			'errReauthRequire'     : 'Re-authorization is required.', // from v2.1.10 added 24.3.2016
			'errMaxTargets'        : 'Max number of selectable items is $1.', // from v2.1.17 added 17.10.2016
			'errRestore'           : 'Unable to restore from the trash. Can\'t identify the restore destination.', // from v2.1.24 added 3.5.2017
			'errEditorNotFound'    : 'Editor not found to this file type.', // from v2.1.25 added 23.5.2017
			'errServerError'       : 'Error occurred on the server side.', // from v2.1.25 added 16.6.2017
			'errEmpty'             : 'Unable to empty folder "$1".', // from v2.1.25 added 22.6.2017
			'moreErrors'           : 'There are $1 more errors.', // from v2.1.44 added 9.12.2018

			/******************************* commands names ********************************/
			'cmdarchive'   : 'Create archive',
			'cmdback'      : 'Back',
			'cmdcopy'      : 'Copy',
			'cmdcut'       : 'Cut',
			'cmddownload'  : 'Download',
			'cmdduplicate' : 'Duplicate',
			'cmdedit'      : 'Edit file',
			'cmdextract'   : 'Extract files from archive',
			'cmdforward'   : 'Forward',
			'cmdgetfile'   : 'Select files',
			'cmdhelp'      : 'About this software',
			'cmdhome'      : 'Root',
			'cmdinfo'      : 'Get info & Share',
			'cmdmkdir'     : 'New folder',
			'cmdmkdirin'   : 'Into New Folder', // from v2.1.7 added 19.2.2016
			'cmdmkfile'    : 'New file',
			'cmdopen'      : 'Open',
			'cmdpaste'     : 'Paste',
			'cmdquicklook' : 'Preview',
			'cmdreload'    : 'Reload',
			'cmdrename'    : 'Rename',
			'cmdrm'        : 'Delete',
			'cmdtrash'     : 'Into trash', //from v2.1.24 added 29.4.2017
			'cmdrestore'   : 'Restore', //from v2.1.24 added 3.5.2017
			'cmdsearch'    : 'Find files',
			'cmdup'        : 'Go to parent folder',
			'cmdupload'    : 'Upload files',
			'cmdview'      : 'View',
			'cmdresize'    : 'Resize & Rotate',
			'cmdsort'      : 'Sort',
			'cmdnetmount'  : 'Mount network volume', // added 18.04.2012
			'cmdnetunmount': 'Unmount', // from v2.1 added 30.04.2012
			'cmdplaces'    : 'To Places', // added 28.12.2014
			'cmdchmod'     : 'Change mode', // from v2.1 added 20.6.2015
			'cmdopendir'   : 'Open a folder', // from v2.1 added 13.1.2016
			'cmdcolwidth'  : 'Reset column width', // from v2.1.13 added 12.06.2016
			'cmdfullscreen': 'Full Screen', // from v2.1.15 added 03.08.2016
			'cmdmove'      : 'Move', // from v2.1.15 added 21.08.2016
			'cmdempty'     : 'Empty the folder', // from v2.1.25 added 22.06.2017
			'cmdundo'      : 'Undo', // from v2.1.27 added 31.07.2017
			'cmdredo'      : 'Redo', // from v2.1.27 added 31.07.2017
			'cmdpreference': 'Preferences', // from v2.1.27 added 03.08.2017
			'cmdselectall' : 'Select all', // from v2.1.28 added 15.08.2017
			'cmdselectnone': 'Select none', // from v2.1.28 added 15.08.2017
			'cmdselectinvert': 'Invert selection', // from v2.1.28 added 15.08.2017
			'cmdopennew'   : 'Open in new window', // from v2.1.38 added 3.4.2018
			'cmdhide'      : 'Hide (Preference)', // from v2.1.41 added 24.7.2018

			/*********************************** buttons ***********************************/
			'btnClose'  : 'Close',
			'btnSave'   : 'Save',
			'btnRm'     : 'Remove',
			'btnApply'  : 'Apply',
			'btnCancel' : 'Cancel',
			'btnNo'     : 'No',
			'btnYes'    : 'Yes',
			'btnMount'  : 'Mount',  // added 18.04.2012
			'btnApprove': 'Goto $1 & approve', // from v2.1 added 26.04.2012
			'btnUnmount': 'Unmount', // from v2.1 added 30.04.2012
			'btnConv'   : 'Convert', // from v2.1 added 08.04.2014
			'btnCwd'    : 'Here',      // from v2.1 added 22.5.2015
			'btnVolume' : 'Volume',    // from v2.1 added 22.5.2015
			'btnAll'    : 'All',       // from v2.1 added 22.5.2015
			'btnMime'   : 'MIME Type', // from v2.1 added 22.5.2015
			'btnFileName':'Filename',  // from v2.1 added 22.5.2015
			'btnSaveClose': 'Save & Close', // from v2.1 added 12.6.2015
			'btnBackup' : 'Backup', // fromv2.1 added 28.11.2015
			'btnRename'    : 'Rename',      // from v2.1.24 added 6.4.2017
			'btnRenameAll' : 'Rename(All)', // from v2.1.24 added 6.4.2017
			'btnPrevious' : 'Prev ($1/$2)', // from v2.1.24 added 11.5.2017
			'btnNext'     : 'Next ($1/$2)', // from v2.1.24 added 11.5.2017
			'btnSaveAs'   : 'Save As', // from v2.1.25 added 24.5.2017

			/******************************** notifications ********************************/
			'ntfopen'     : 'Open folder',
			'ntffile'     : 'Open file',
			'ntfreload'   : 'Reload folder content',
			'ntfmkdir'    : 'Creating folder',
			'ntfmkfile'   : 'Creating files',
			'ntfrm'       : 'Delete items',
			'ntfcopy'     : 'Copy items',
			'ntfmove'     : 'Move items',
			'ntfprepare'  : 'Checking existing items',
			'ntfrename'   : 'Rename files',
			'ntfupload'   : 'Uploading files',
			'ntfdownload' : 'Downloading files',
			'ntfsave'     : 'Save files',
			'ntfarchive'  : 'Creating archive',
			'ntfextract'  : 'Extracting files from archive',
			'ntfsearch'   : 'Searching files',
			'ntfresize'   : 'Resizing images',
			'ntfsmth'     : 'Doing something',
			'ntfloadimg'  : 'Loading image',
			'ntfnetmount' : 'Mounting network volume', // added 18.04.2012
			'ntfnetunmount': 'Unmounting network volume', // from v2.1 added 30.04.2012
			'ntfdim'      : 'Acquiring image dimension', // added 20.05.2013
			'ntfreaddir'  : 'Reading folder infomation', // from v2.1 added 01.07.2013
			'ntfurl'      : 'Getting URL of link', // from v2.1 added 11.03.2014
			'ntfchmod'    : 'Changing file mode', // from v2.1 added 20.6.2015
			'ntfpreupload': 'Verifying upload file name', // from v2.1 added 31.11.2015
			'ntfzipdl'    : 'Creating a file for download', // from v2.1.7 added 23.1.2016
			'ntfparents'  : 'Getting path infomation', // from v2.1.17 added 2.11.2016
			'ntfchunkmerge': 'Processing the uploaded file', // from v2.1.17 added 2.11.2016
			'ntftrash'    : 'Doing throw in the trash', // from v2.1.24 added 2.5.2017
			'ntfrestore'  : 'Doing restore from the trash', // from v2.1.24 added 3.5.2017
			'ntfchkdir'   : 'Checking destination folder', // from v2.1.24 added 3.5.2017
			'ntfundo'     : 'Undoing previous operation', // from v2.1.27 added 31.07.2017
			'ntfredo'     : 'Redoing previous undone', // from v2.1.27 added 31.07.2017
			'ntfchkcontent' : 'Checking contents', // from v2.1.41 added 3.8.2018

			/*********************************** volumes *********************************/
			'volume_Trash' : 'Trash', //from v2.1.24 added 29.4.2017

			/************************************ dates **********************************/
			'dateUnknown' : 'unknown',
			'Today'       : 'Today',
			'Yesterday'   : 'Yesterday',
			'msJan'       : 'Jan',
			'msFeb'       : 'Feb',
			'msMar'       : 'Mar',
			'msApr'       : 'Apr',
			'msMay'       : 'May',
			'msJun'       : 'Jun',
			'msJul'       : 'Jul',
			'msAug'       : 'Aug',
			'msSep'       : 'Sep',
			'msOct'       : 'Oct',
			'msNov'       : 'Nov',
			'msDec'       : 'Dec',
			'January'     : 'January',
			'February'    : 'February',
			'March'       : 'March',
			'April'       : 'April',
			'May'         : 'May',
			'June'        : 'June',
			'July'        : 'July',
			'August'      : 'August',
			'September'   : 'September',
			'October'     : 'October',
			'November'    : 'November',
			'December'    : 'December',
			'Sunday'      : 'Sunday',
			'Monday'      : 'Monday',
			'Tuesday'     : 'Tuesday',
			'Wednesday'   : 'Wednesday',
			'Thursday'    : 'Thursday',
			'Friday'      : 'Friday',
			'Saturday'    : 'Saturday',
			'Sun'         : 'Sun',
			'Mon'         : 'Mon',
			'Tue'         : 'Tue',
			'Wed'         : 'Wed',
			'Thu'         : 'Thu',
			'Fri'         : 'Fri',
			'Sat'         : 'Sat',

			/******************************** sort variants ********************************/
			'sortname'          : 'by name',
			'sortkind'          : 'by kind',
			'sortsize'          : 'by size',
			'sortdate'          : 'by date',
			'sortFoldersFirst'  : 'Folders first',
			'sortperm'          : 'by permission', // from v2.1.13 added 13.06.2016
			'sortmode'          : 'by mode',       // from v2.1.13 added 13.06.2016
			'sortowner'         : 'by owner',      // from v2.1.13 added 13.06.2016
			'sortgroup'         : 'by group',      // from v2.1.13 added 13.06.2016
			'sortAlsoTreeview'  : 'Also Treeview',  // from v2.1.15 added 01.08.2016

			/********************************** new items **********************************/
			'untitled file.txt' : 'NewFile.txt', // added 10.11.2015
			'untitled folder'   : 'NewFolder',   // added 10.11.2015
			'Archive'           : 'NewArchive',  // from v2.1 added 10.11.2015
			'untitled file'     : 'NewFile.$1',  // from v2.1.41 added 6.8.2018
			'extentionfile'     : '$1: File',    // from v2.1.41 added 6.8.2018
			'extentiontype'     : '$1: $2',      // from v2.1.43 added 17.10.2018

			/********************************** messages **********************************/
			'confirmReq'      : 'Confirmation required',
			'confirmRm'       : 'Are you sure you want to permanently remove items?<br/>This cannot be undone!',
			'confirmRepl'     : 'Replace old file with new one? (If it contains folders, it will be merged. To backup and replace, select Backup.)',
			'confirmRest'     : 'Replace existing item with the item in trash?', // fromv2.1.24 added 5.5.2017
			'confirmConvUTF8' : 'Not in UTF-8<br/>Convert to UTF-8?<br/>Contents become UTF-8 by saving after conversion.', // from v2.1 added 08.04.2014
			'confirmNonUTF8'  : 'Character encoding of this file couldn\'t be detected. It need to temporarily convert to UTF-8 for editting.<br/>Please select character encoding of this file.', // from v2.1.19 added 28.11.2016
			'confirmNotSave'  : 'It has been modified.<br/>Losing work if you do not save changes.', // from v2.1 added 15.7.2015
			'confirmTrash'    : 'Are you sure you want to move items to trash bin?', //from v2.1.24 added 29.4.2017
			'apllyAll'        : 'Apply to all',
			'name'            : 'Name',
			'size'            : 'Size',
			'perms'           : 'Permissions',
			'modify'          : 'Modified',
			'kind'            : 'Kind',
			'read'            : 'read',
			'write'           : 'write',
			'noaccess'        : 'no access',
			'and'             : 'and',
			'unknown'         : 'unknown',
			'selectall'       : 'Select all items',
			'selectfiles'     : 'Select item(s)',
			'selectffile'     : 'Select first item',
			'selectlfile'     : 'Select last item',
			'viewlist'        : 'List view',
			'viewicons'       : 'Icons view',
			'viewSmall'       : 'Small icons', // from v2.1.39 added 22.5.2018
			'viewMedium'      : 'Medium icons', // from v2.1.39 added 22.5.2018
			'viewLarge'       : 'Large icons', // from v2.1.39 added 22.5.2018
			'viewExtraLarge'  : 'Extra large icons', // from v2.1.39 added 22.5.2018
			'places'          : 'Places',
			'calc'            : 'Calculate',
			'path'            : 'Path',
			'aliasfor'        : 'Alias for',
			'locked'          : 'Locked',
			'dim'             : 'Dimensions',
			'files'           : 'Files',
			'folders'         : 'Folders',
			'items'           : 'Items',
			'yes'             : 'yes',
			'no'              : 'no',
			'link'            : 'Link',
			'searcresult'     : 'Search results',
			'selected'        : 'selected items',
			'about'           : 'About',
			'shortcuts'       : 'Shortcuts',
			'help'            : 'Help',
			'webfm'           : 'Web file manager',
			'ver'             : 'Version',
			'protocolver'     : 'protocol version',
			'homepage'        : 'Project home',
			'docs'            : 'Documentation',
			'github'          : 'Fork us on GitHub',
			'twitter'         : 'Follow us on Twitter',
			'facebook'        : 'Join us on Facebook',
			'team'            : 'Team',
			'chiefdev'        : 'chief developer',
			'developer'       : 'developer',
			'contributor'     : 'contributor',
			'maintainer'      : 'maintainer',
			'translator'      : 'translator',
			'icons'           : 'Icons',
			'dontforget'      : 'and don\'t forget to take your towel',
			'shortcutsof'     : 'Shortcuts disabled',
			'dropFiles'       : 'Drop files here',
			'or'              : 'or',
			'selectForUpload' : 'Select files',
			'moveFiles'       : 'Move items',
			'copyFiles'       : 'Copy items',
			'restoreFiles'    : 'Restore items', // from v2.1.24 added 5.5.2017
			'rmFromPlaces'    : 'Remove from places',
			'aspectRatio'     : 'Aspect ratio',
			'scale'           : 'Scale',
			'width'           : 'Width',
			'height'          : 'Height',
			'resize'          : 'Resize',
			'crop'            : 'Crop',
			'rotate'          : 'Rotate',
			'rotate-cw'       : 'Rotate 90 degrees CW',
			'rotate-ccw'      : 'Rotate 90 degrees CCW',
			'degree'          : '°',
			'netMountDialogTitle' : 'Mount network volume', // added 18.04.2012
			'protocol'            : 'Protocol', // added 18.04.2012
			'host'                : 'Host', // added 18.04.2012
			'port'                : 'Port', // added 18.04.2012
			'user'                : 'User', // added 18.04.2012
			'pass'                : 'Password', // added 18.04.2012
			'confirmUnmount'      : 'Are you sure to unmount $1?',  // from v2.1 added 30.04.2012
			'dropFilesBrowser': 'Drop or Paste files from browser', // from v2.1 added 30.05.2012
			'dropPasteFiles'  : 'Drop files, Paste URLs or images(clipboard) here', // from v2.1 added 07.04.2014
			'encoding'        : 'Encoding', // from v2.1 added 19.12.2014
			'locale'          : 'Locale',   // from v2.1 added 19.12.2014
			'searchTarget'    : 'Target: $1',                // from v2.1 added 22.5.2015
			'searchMime'      : 'Search by input MIME Type', // from v2.1 added 22.5.2015
			'owner'           : 'Owner', // from v2.1 added 20.6.2015
			'group'           : 'Group', // from v2.1 added 20.6.2015
			'other'           : 'Other', // from v2.1 added 20.6.2015
			'execute'         : 'Execute', // from v2.1 added 20.6.2015
			'perm'            : 'Permission', // from v2.1 added 20.6.2015
			'mode'            : 'Mode', // from v2.1 added 20.6.2015
			'emptyFolder'     : 'Folder is empty', // from v2.1.6 added 30.12.2015
			'emptyFolderDrop' : 'Folder is empty\\A Drop to add items', // from v2.1.6 added 30.12.2015
			'emptyFolderLTap' : 'Folder is empty\\A Long tap to add items', // from v2.1.6 added 30.12.2015
			'quality'         : 'Quality', // from v2.1.6 added 5.1.2016
			'autoSync'        : 'Auto sync',  // from v2.1.6 added 10.1.2016
			'moveUp'          : 'Move up',  // from v2.1.6 added 18.1.2016
			'getLink'         : 'Get URL link', // from v2.1.7 added 9.2.2016
			'selectedItems'   : 'Selected items ($1)', // from v2.1.7 added 2.19.2016
			'folderId'        : 'Folder ID', // from v2.1.10 added 3.25.2016
			'offlineAccess'   : 'Allow offline access', // from v2.1.10 added 3.25.2016
			'reAuth'          : 'To re-authenticate', // from v2.1.10 added 3.25.2016
			'nowLoading'      : 'Now loading...', // from v2.1.12 added 4.26.2016
			'openMulti'       : 'Open multiple files', // from v2.1.12 added 5.14.2016
			'openMultiConfirm': 'You are trying to open the $1 files. Are you sure you want to open in browser?', // from v2.1.12 added 5.14.2016
			'emptySearch'     : 'Search results is empty in search target.', // from v2.1.12 added 5.16.2016
			'editingFile'     : 'It is editing a file.', // from v2.1.13 added 6.3.2016
			'hasSelected'     : 'You have selected $1 items.', // from v2.1.13 added 6.3.2016
			'hasClipboard'    : 'You have $1 items in the clipboard.', // from v2.1.13 added 6.3.2016
			'incSearchOnly'   : 'Incremental search is only from the current view.', // from v2.1.13 added 6.30.2016
			'reinstate'       : 'Reinstate', // from v2.1.15 added 3.8.2016
			'complete'        : '$1 complete', // from v2.1.15 added 21.8.2016
			'contextmenu'     : 'Context menu', // from v2.1.15 added 9.9.2016
			'pageTurning'     : 'Page turning', // from v2.1.15 added 10.9.2016
			'volumeRoots'     : 'Volume roots', // from v2.1.16 added 16.9.2016
			'reset'           : 'Reset', // from v2.1.16 added 1.10.2016
			'bgcolor'         : 'Background color', // from v2.1.16 added 1.10.2016
			'colorPicker'     : 'Color picker', // from v2.1.16 added 1.10.2016
			'8pxgrid'         : '8px Grid', // from v2.1.16 added 4.10.2016
			'enabled'         : 'Enabled', // from v2.1.16 added 4.10.2016
			'disabled'        : 'Disabled', // from v2.1.16 added 4.10.2016
			'emptyIncSearch'  : 'Search results is empty in current view.\\A Press [Enter] to expand search target.', // from v2.1.16 added 5.10.2016
			'emptyLetSearch'  : 'First letter search results is empty in current view.', // from v2.1.23 added 24.3.2017
			'textLabel'       : 'Text label', // from v2.1.17 added 13.10.2016
			'minsLeft'        : '$1 mins left', // from v2.1.17 added 13.11.2016
			'openAsEncoding'  : 'Reopen with selected encoding', // from v2.1.19 added 2.12.2016
			'saveAsEncoding'  : 'Save with the selected encoding', // from v2.1.19 added 2.12.2016
			'selectFolder'    : 'Select folder', // from v2.1.20 added 13.12.2016
			'firstLetterSearch': 'First letter search', // from v2.1.23 added 24.3.2017
			'presets'         : 'Presets', // from v2.1.25 added 26.5.2017
			'tooManyToTrash'  : 'It\'s too many items so it can\'t into trash.', // from v2.1.25 added 9.6.2017
			'TextArea'        : 'TextArea', // from v2.1.25 added 14.6.2017
			'folderToEmpty'   : 'Empty the folder "$1".', // from v2.1.25 added 22.6.2017
			'filderIsEmpty'   : 'There are no items in a folder "$1".', // from v2.1.25 added 22.6.2017
			'preference'      : 'Preference', // from v2.1.26 added 28.6.2017
			'language'        : 'Language', // from v2.1.26 added 28.6.2017
			'clearBrowserData': 'Initialize the settings saved in this browser', // from v2.1.26 added 28.6.2017
			'toolbarPref'     : 'Toolbar settings', // from v2.1.27 added 2.8.2017
			'charsLeft'       : '... $1 chars left.',  // from v2.1.29 added 30.8.2017
			'sum'             : 'Sum', // from v2.1.29 added 28.9.2017
			'roughFileSize'   : 'Rough file size', // from v2.1.30 added 2.11.2017
			'autoFocusDialog' : 'Focus on the element of dialog with mouseover',  // from v2.1.30 added 2.11.2017
			'select'          : 'Select', // from v2.1.30 added 23.11.2017
			'selectAction'    : 'Action when select file', // from v2.1.30 added 23.11.2017
			'useStoredEditor' : 'Open with the editor used last time', // from v2.1.30 added 23.11.2017
			'selectinvert'    : 'Invert selection', // from v2.1.30 added 25.11.2017
			'renameMultiple'  : 'Are you sure you want to rename $1 selected items like $2?<br/>This cannot be undone!', // from v2.1.31 added 4.12.2017
			'batchRename'     : 'Batch rename', // from v2.1.31 added 8.12.2017
			'plusNumber'      : '+ Number', // from v2.1.31 added 8.12.2017
			'asPrefix'        : 'Add prefix', // from v2.1.31 added 8.12.2017
			'asSuffix'        : 'Add suffix', // from v2.1.31 added 8.12.2017
			'changeExtention' : 'Change extention', // from v2.1.31 added 8.12.2017
			'columnPref'      : 'Columns settings (List view)', // from v2.1.32 added 6.2.2018
			'reflectOnImmediate' : 'All changes will reflect immediately to the archive.', // from v2.1.33 added 2.3.2018
			'reflectOnUnmount'   : 'Any changes will not reflect until un-mount this volume.', // from v2.1.33 added 2.3.2018
			'unmountChildren' : 'The following volume(s) mounted on this volume also unmounted. Are you sure to unmount it?', // from v2.1.33 added 5.3.2018
			'selectionInfo'   : 'Selection Info', // from v2.1.33 added 7.3.2018
			'hashChecker'     : 'Algorithms to show the file hash', // from v2.1.33 added 10.3.2018
			'infoItems'       : 'Info Items (Selection Info Panel)', // from v2.1.38 added 28.3.2018
			'pressAgainToExit': 'Press again to exit.', // from v2.1.38 added 1.4.2018
			'toolbar'         : 'Toolbar', // from v2.1.38 added 4.4.2018
			'workspace'       : 'Work Space', // from v2.1.38 added 4.4.2018
			'dialog'          : 'Dialog', // from v2.1.38 added 4.4.2018
			'all'             : 'All', // from v2.1.38 added 4.4.2018
			'iconSize'        : 'Icon Size (Icons view)', // from v2.1.39 added 7.5.2018
			'editorMaximized' : 'Open the maximized editor window', // from v2.1.40 added 30.6.2018
			'editorConvNoApi' : 'Because conversion by API is not currently available, please convert on the website.', //from v2.1.40 added 8.7.2018
			'editorConvNeedUpload' : 'After conversion, you must be upload with the item URL or a downloaded file to save the converted file.', //from v2.1.40 added 8.7.2018
			'convertOn'       : 'Convert on the site of $1', // from v2.1.40 added 10.7.2018
			'integrations'    : 'Integrations', // from v2.1.40 added 11.7.2018
			'integrationWith' : 'This elFinder has the following external services integrated. Please check the terms of use, privacy policy, etc. before using it.', // from v2.1.40 added 11.7.2018
			'showHidden'      : 'Show hidden items', // from v2.1.41 added 24.7.2018
			'hideHidden'      : 'Hide hidden items', // from v2.1.41 added 24.7.2018
			'toggleHidden'    : 'Show/Hide hidden items', // from v2.1.41 added 24.7.2018
			'makefileTypes'   : 'File types to enable with "New file"', // from v2.1.41 added 7.8.2018
			'typeOfTextfile'  : 'Type of the Text file', // from v2.1.41 added 7.8.2018
			'add'             : 'Add', // from v2.1.41 added 7.8.2018
			'theme'           : 'Theme', // from v2.1.43 added 19.10.2018
			'default'         : 'Default', // from v2.1.43 added 19.10.2018
			'description'     : 'Description', // from v2.1.43 added 19.10.2018
			'website'         : 'Website', // from v2.1.43 added 19.10.2018
			'author'          : 'Author', // from v2.1.43 added 19.10.2018
			'email'           : 'Email', // from v2.1.43 added 19.10.2018
			'license'         : 'License', // from v2.1.43 added 19.10.2018
			'exportToSave'    : 'This item can\'t be saved. To avoid losing the edits you need to export to your PC.', // from v2.1.44 added 1.12.2018

			/********************************** mimetypes **********************************/
			'kindUnknown'     : 'Unknown',
			'kindRoot'        : 'Volume Root', // from v2.1.16 added 16.10.2016
			'kindFolder'      : 'Folder',
			'kindSelects'     : 'Selections', // from v2.1.29 added 29.8.2017
			'kindAlias'       : 'Alias',
			'kindAliasBroken' : 'Broken alias',
			// applications
			'kindApp'         : 'Application',
			'kindPostscript'  : 'Postscript document',
			'kindMsOffice'    : 'Microsoft Office document',
			'kindMsWord'      : 'Microsoft Word document',
			'kindMsExcel'     : 'Microsoft Excel document',
			'kindMsPP'        : 'Microsoft Powerpoint presentation',
			'kindOO'          : 'Open Office document',
			'kindAppFlash'    : 'Flash application',
			'kindPDF'         : 'Portable Document Format (PDF)',
			'kindTorrent'     : 'Bittorrent file',
			'kind7z'          : '7z archive',
			'kindTAR'         : 'TAR archive',
			'kindGZIP'        : 'GZIP archive',
			'kindBZIP'        : 'BZIP archive',
			'kindXZ'          : 'XZ archive',
			'kindZIP'         : 'ZIP archive',
			'kindRAR'         : 'RAR archive',
			'kindJAR'         : 'Java JAR file',
			'kindTTF'         : 'True Type font',
			'kindOTF'         : 'Open Type font',
			'kindRPM'         : 'RPM package',
			// texts
			'kindText'        : 'Text document',
			'kindTextPlain'   : 'Plain text',
			'kindPHP'         : 'PHP source',
			'kindCSS'         : 'Cascading style sheet',
			'kindHTML'        : 'HTML document',
			'kindJS'          : 'Javascript source',
			'kindRTF'         : 'Rich Text Format',
			'kindC'           : 'C source',
			'kindCHeader'     : 'C header source',
			'kindCPP'         : 'C++ source',
			'kindCPPHeader'   : 'C++ header source',
			'kindShell'       : 'Unix shell script',
			'kindPython'      : 'Python source',
			'kindJava'        : 'Java source',
			'kindRuby'        : 'Ruby source',
			'kindPerl'        : 'Perl script',
			'kindSQL'         : 'SQL source',
			'kindXML'         : 'XML document',
			'kindAWK'         : 'AWK source',
			'kindCSV'         : 'Comma separated values',
			'kindDOCBOOK'     : 'Docbook XML document',
			'kindMarkdown'    : 'Markdown text', // added 20.7.2015
			// images
			'kindImage'       : 'Image',
			'kindBMP'         : 'BMP image',
			'kindJPEG'        : 'JPEG image',
			'kindGIF'         : 'GIF Image',
			'kindPNG'         : 'PNG Image',
			'kindTIFF'        : 'TIFF image',
			'kindTGA'         : 'TGA image',
			'kindPSD'         : 'Adobe Photoshop image',
			'kindXBITMAP'     : 'X bitmap image',
			'kindPXM'         : 'Pixelmator image',
			// media
			'kindAudio'       : 'Audio media',
			'kindAudioMPEG'   : 'MPEG audio',
			'kindAudioMPEG4'  : 'MPEG-4 audio',
			'kindAudioMIDI'   : 'MIDI audio',
			'kindAudioOGG'    : 'Ogg Vorbis audio',
			'kindAudioWAV'    : 'WAV audio',
			'AudioPlaylist'   : 'MP3 playlist',
			'kindVideo'       : 'Video media',
			'kindVideoDV'     : 'DV movie',
			'kindVideoMPEG'   : 'MPEG movie',
			'kindVideoMPEG4'  : 'MPEG-4 movie',
			'kindVideoAVI'    : 'AVI movie',
			'kindVideoMOV'    : 'Quick Time movie',
			'kindVideoWM'     : 'Windows Media movie',
			'kindVideoFlash'  : 'Flash movie',
			'kindVideoMKV'    : 'Matroska movie',
			'kindVideoOGG'    : 'Ogg movie'
		}
	};
}



/*
 * File: /js/ui/button.js
 */

/**
 * @class  elFinder toolbar button widget.
 * If command has variants - create menu
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfinderbutton = function(cmd) {
		return this.each(function() {
		
		var c        = 'class',
			fm       = cmd.fm,
			disabled = fm.res(c, 'disabled'),
			active   = fm.res(c, 'active'),
			hover    = fm.res(c, 'hover'),
			item     = 'elfinder-button-menu-item',
			selected = 'elfinder-button-menu-item-selected',
			menu,
			text     = jQuery('<span class="elfinder-button-text">'+cmd.title+'</span>'),
			prvCname = 'elfinder-button-icon-' + (cmd.className? cmd.className : cmd.name),
			button   = jQuery(this).addClass('ui-state-default elfinder-button')
				.attr('title', cmd.title)
				.append('<span class="elfinder-button-icon ' + prvCname + '"/>', text)
				.on('mouseenter mouseleave', function(e) { !button.hasClass(disabled) && button[e.type == 'mouseleave' ? 'removeClass' : 'addClass'](hover);})
				.on('click', function(e) { 
					if (!button.hasClass(disabled)) {
						if (menu && cmd.variants.length >= 1) {
							// close other menus
							menu.is(':hidden') && fm.getUI().click();
							e.stopPropagation();
							menu.css(getMenuOffset()).slideToggle({
								duration: 100,
								done: function(e) {
									fm[menu.is(':visible')? 'toFront' : 'toHide'](menu);
								}
							});
						} else {
							fm.exec(cmd.name, getSelected(), {_userAction: true, _currentType: 'toolbar', _currentNode: button });
						}
						
					}
				}),
			hideMenu = function() {
				fm.toHide(menu);
			},
			getMenuOffset = function() {
				var fmNode = fm.getUI(),
					baseOffset = fmNode.offset(),
					buttonOffset = button.offset();
				return {
					top : buttonOffset.top - baseOffset.top,
					left : buttonOffset.left - baseOffset.left,
					maxHeight : fmNode.height() - 40
				};
			},
			getSelected = function() {
				var sel = fm.selected(),
					cwd;
				if (!sel.length) {
					if (cwd = fm.cwd()) {
						sel = [ fm.cwd().hash ];
					} else {
						sel = void(0);
					}
				}
				return sel;
			},
			tm;
			
		text.hide();
		
		// set self button object to cmd object
		cmd.button = button;
		
		// if command has variants create menu
		if (Array.isArray(cmd.variants)) {
			button.addClass('elfinder-menubutton');
			
			menu = jQuery('<div class="ui-front ui-widget ui-widget-content elfinder-button-menu ui-corner-all"/>')
				.hide()
				.appendTo(fm.getUI())
				.on('mouseenter mouseleave', '.'+item, function() { jQuery(this).toggleClass(hover); })
				.on('click', '.'+item, function(e) {
					var opts = jQuery(this).data('value');
					e.preventDefault();
					e.stopPropagation();
					button.removeClass(hover);
					fm.toHide(menu);
					if (typeof opts === 'undefined') {
						opts = {};
					}
					if (typeof opts === 'object') {
						opts._userAction = true;
					}
					fm.exec(cmd.name, getSelected(), opts);
				})
				.on('close', hideMenu);

			fm.bind('disable select', hideMenu).getUI().on('click', hideMenu);
			
			cmd.change(function() {
				menu.html('');
				jQuery.each(cmd.variants, function(i, variant) {
					menu.append(jQuery('<div class="'+item+'">'+variant[1]+'</div>').data('value', variant[0]).addClass(variant[0] == cmd.value ? selected : ''));
				});
			});
		}	
			
		cmd.change(function() {
			var cName;
			tm && cancelAnimationFrame(tm);
			tm = requestAnimationFrame(function() {
				if (cmd.disabled()) {
					button.removeClass(active+' '+hover).addClass(disabled);
				} else {
					button.removeClass(disabled);
					button[cmd.active() ? 'addClass' : 'removeClass'](active);
				}
				if (cmd.syncTitleOnChange) {
					cName = 'elfinder-button-icon-' + (cmd.className? cmd.className : cmd.name);
					if (prvCname !== cName) {
						button.children('.elfinder-button-icon').removeClass(prvCname).addClass(cName);
						prvCname = cName;
					}
					text.html(cmd.title);
					button.attr('title', cmd.title);
				}
			});
		})
		.change();
	});
};


/*
 * File: /js/ui/contextmenu.js
 */

/**
 * @class  elFinder contextmenu
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfindercontextmenu = function(fm) {
		return this.each(function() {
		var self   = jQuery(this),
			cmItem = 'elfinder-contextmenu-item',
			smItem = 'elfinder-contextsubmenu-item',
			exIcon = 'elfinder-contextmenu-extra-icon',
			cHover = fm.res('class', 'hover'),
			dragOpt = {
				distance: 8,
				start: function() {
					menu.data('drag', true).data('touching') && menu.find('.'+cHover).removeClass(cHover);
				},
				stop: function() {
					menu.data('draged', true).removeData('drag');
				}
			},
			menu = jQuery(this).addClass('touch-punch ui-helper-reset ui-front ui-widget ui-state-default ui-corner-all elfinder-contextmenu elfinder-contextmenu-'+fm.direction)
				.hide()
				.on('touchstart', function(e) {
					menu.data('touching', true).children().removeClass(cHover);
				})
				.on('touchend', function(e) {
					menu.removeData('touching');
				})
				.on('mouseenter mouseleave', '.'+cmItem, function(e) {
					jQuery(this).toggleClass(cHover, (e.type === 'mouseenter' || (! menu.data('draged') && menu.data('submenuKeep'))? true : false));
					if (menu.data('draged') && menu.data('submenuKeep')) {
						menu.find('.elfinder-contextmenu-sub:visible').parent().addClass(cHover);
					}
				})
				.on('mouseenter mouseleave', '.'+exIcon, function(e) {
					jQuery(this).parent().toggleClass(cHover, e.type === 'mouseleave');
				})
				.on('mouseenter mouseleave', '.'+cmItem+',.'+smItem, function(e) {
					var setIndex = function(target, sub) {
						jQuery.each(sub? subnodes : nodes, function(i, n) {
							if (target[0] === n) {
								(sub? subnodes : nodes)._cur = i;
								if (sub) {
									subselected = target;
								} else {
									selected = target;
								}
								return false;
							}
						});
					};
					if (e.originalEvent) {
						var target = jQuery(this),
							unHover = function() {
								if (selected && !selected.children('div.elfinder-contextmenu-sub:visible').length) {
									selected.removeClass(cHover);
								}
							};
						if (e.type === 'mouseenter') {
							// mouseenter
							if (target.hasClass(smItem)) {
								// submenu
								if (subselected) {
									subselected.removeClass(cHover);
								}
								if (selected) {
									subnodes = selected.find('div.'+smItem);
								}
								setIndex(target, true);
							} else {
								// menu
								unHover();
								setIndex(target);
							}
						} else {
							// mouseleave
							if (target.hasClass(smItem)) {
								//submenu
								subselected = null;
								subnodes = null;
							} else {
								// menu
								unHover();
								(function(sel) {
									setTimeout(function() {
										if (sel === selected) {
											selected = null;
										}
									}, 250);
								})(selected);
							}
						}
					}
				})
				.on('contextmenu', function(){return false;})
				.on('mouseup', function() {
					setTimeout(function() {
						menu.removeData('draged');
					}, 100);
				})
				.draggable(dragOpt),
			ltr = fm.direction === 'ltr',
			subpos = ltr? 'left' : 'right',
			types = Object.assign({}, fm.options.contextmenu),
			tpl     = '<div class="'+cmItem+'{className}"><span class="elfinder-button-icon {icon} elfinder-contextmenu-icon"{style}/><span>{label}</span></div>',
			item = function(label, icon, callback, opts) {
				var className = '',
					style = '',
					iconClass = '',
					v, pos;
				if (opts) {
					if (opts.className) {
						className = ' ' + opts.className;
					}
					if (opts.iconClass) {
						iconClass = opts.iconClass;
						icon = '';
					}
					if (opts.iconImg) {
						v = opts.iconImg.split(/ +/);
						pos = v[1] && v[2]? fm.escape(v[1] + 'px ' + v[2] + 'px') : '';
						style = ' style="background:url(\''+fm.escape(v[0])+'\') '+(pos? pos : '0 0')+' no-repeat;'+(pos? '' : 'posbackground-size:contain;')+'"';
					}
				}
				return jQuery(tpl.replace('{icon}', icon ? 'elfinder-button-icon-'+icon : (iconClass? iconClass : ''))
						.replace('{label}', label)
						.replace('{style}', style)
						.replace('{className}', className))
					.on('click', function(e) {
						e.stopPropagation();
						e.preventDefault();
						callback();
					});
			},
			urlIcon = function(iconUrl) {
				var v = iconUrl.split(/ +/),
					pos = v[1] && v[2]? (v[1] + 'px ' + v[2] + 'px') : '';
				return {
					backgroundImage: 'url("'+v[0]+'")',
					backgroundRepeat: 'no-repeat',
					backgroundPosition: pos? pos : '',
					backgroundSize: pos? '' : 'contain'
				};
			},
			base, cwd,
			nodes, selected, subnodes, subselected, autoSyncStop, subHoverTm,

			autoToggle = function() {
				var evTouchStart = 'touchstart.contextmenuAutoToggle';
				menu.data('hideTm') && clearTimeout(menu.data('hideTm'));
				if (menu.is(':visible')) {
					menu.on('touchstart', function(e) {
						if (e.originalEvent.touches.length > 1) {
							return;
						}
						menu.stop();
						fm.toFront(menu);
						menu.data('hideTm') && clearTimeout(menu.data('hideTm'));
					})
					.data('hideTm', setTimeout(function() {
						if (menu.is(':visible')) {
							cwd.find('.elfinder-cwd-file').off(evTouchStart);
							cwd.find('.elfinder-cwd-file.ui-selected')
							.one(evTouchStart, function(e) {
								if (e.originalEvent.touches.length > 1) {
									return;
								}
								var tgt = jQuery(e.target);
								if (menu.first().length && !tgt.is('input:checkbox') && !tgt.hasClass('elfinder-cwd-select')) {
									e.stopPropagation();
									//e.preventDefault();
									open(e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY);
									cwd.data('longtap', true)
									tgt.one('touchend', function() {
										setTimeout(function() {
											cwd.removeData('longtap');
										}, 80);
									});
									return;
								}
								cwd.find('.elfinder-cwd-file').off(evTouchStart);
							})
							.one('unselect.'+fm.namespace, function() {
								cwd.find('.elfinder-cwd-file').off(evTouchStart);
							});
							menu.fadeOut({
								duration: 300,
								fail: function() {
									menu.css('opacity', '1').show();
								},
								done: function() {
									fm.toHide(menu);
								}
							});
						}
					}, 4500));
				}
			},
			
			keyEvts = function(e) {
				var code = e.keyCode,
					ESC = jQuery.ui.keyCode.ESCAPE,
					ENT = jQuery.ui.keyCode.ENTER,
					LEFT = jQuery.ui.keyCode.LEFT,
					RIGHT = jQuery.ui.keyCode.RIGHT,
					UP = jQuery.ui.keyCode.UP,
					DOWN = jQuery.ui.keyCode.DOWN,
					subent = fm.direction === 'ltr'? RIGHT : LEFT,
					sublev = subent === RIGHT? LEFT : RIGHT;
				
				if (jQuery.inArray(code, [ESC, ENT, LEFT, RIGHT, UP, DOWN]) !== -1) {
					e.preventDefault();
					e.stopPropagation();
					e.stopImmediatePropagation();
					if (code == ESC || code === sublev) {
						if (selected && subnodes && subselected) {
							subselected.trigger('mouseleave').trigger('submenuclose');
							selected.addClass(cHover);
							subnodes = null;
							subselected = null;
						} else {
							code == ESC && close();
						}
					} else if (code == UP || code == DOWN) {
						if (subnodes) {
							if (subselected) {
								subselected.trigger('mouseleave');
							}
							if (code == DOWN && (! subselected || subnodes.length <= ++subnodes._cur)) {
								subnodes._cur = 0;
							} else if (code == UP && (! subselected || --subnodes._cur < 0)) {
								subnodes._cur = subnodes.length - 1;
							}
							subselected = subnodes.eq(subnodes._cur).trigger('mouseenter');
						} else {
							subnodes = null;
							if (selected) {
								selected.trigger('mouseleave');
							}
							if (code == DOWN && (! selected || nodes.length <= ++nodes._cur)) {
								nodes._cur = 0;
							} else if (code == UP && (! selected || --nodes._cur < 0)) {
								nodes._cur = nodes.length - 1;
							}
							selected = nodes.eq(nodes._cur).addClass(cHover);
						}
					} else if (selected && (code == ENT || code === subent)) {
						if (selected.hasClass('elfinder-contextmenu-group')) {
							if (subselected) {
								code == ENT && subselected.click();
							} else {
								selected.trigger('mouseenter');
								subnodes = selected.find('div.'+smItem);
								subnodes._cur = 0;
								subselected = subnodes.first().addClass(cHover);
							}
						} else {
							code == ENT && selected.click();
						}
					}
				}
			},
			
			open = function(x, y, css) {
				var width      = menu.outerWidth(),
					height     = menu.outerHeight(),
					bstyle     = base.attr('style'),
					bpos       = base.offset(),
					bwidth     = base.width(),
					bheight    = base.height(),
					mw         = fm.UA.Mobile? 40 : 2,
					mh         = fm.UA.Mobile? 20 : 2,
					x          = x - (bpos? bpos.left : 0),
					y          = y - (bpos? bpos.top : 0),
					css        = Object.assign(css || {}, {
						top  : Math.max(0, y + mh + height < bheight ? y + mh : y - (y + height - bheight)),
						left : Math.max(0, (x < width + mw || x + mw + width < bwidth)? x + mw : x - mw - width),
						opacity : '1'
					}),
					evts;

				autoSyncStop = true;
				fm.autoSync('stop');
				base.width(bwidth);
				menu.stop().removeAttr('style').css(css);
				fm.toFront(menu);
				menu.show();
				base.attr('style', bstyle);
				
				css[subpos] = parseInt(menu.width());
				menu.find('.elfinder-contextmenu-sub').css(css);
				if (fm.UA.iOS) {
					jQuery('div.elfinder div.overflow-scrolling-touch').css('-webkit-overflow-scrolling', 'auto');
				}
				
				selected = null;
				subnodes = null;
				subselected = null;
				jQuery(document).on('keydown.' + fm.namespace, keyEvts);
				evts = jQuery._data(document).events;
				if (evts && evts.keydown) {
					evts.keydown.unshift(evts.keydown.pop());
				}
				
				fm.UA.Mobile && autoToggle();
				
				requestAnimationFrame(function() {
					fm.getUI().one('click.' + fm.namespace, close);
				});
			},
			
			close = function() {
				fm.getUI().off('click.' + fm.namespace, close);
				jQuery(document).off('keydown.' + fm.namespace, keyEvts);

				currentType = currentTargets = null;
				
				if (menu.is(':visible') || menu.children().length) {
					fm.toHide(menu.removeAttr('style').empty().removeData('submenuKeep'));
					try {
						if (! menu.draggable('instance')) {
							menu.draggable(dragOpt);
						}
					} catch(e) {
						if (! menu.hasClass('ui-draggable')) {
							menu.draggable(dragOpt);
						}
					}
					if (menu.data('prevNode')) {
						menu.data('prevNode').after(menu);
						menu.removeData('prevNode');
					}
					fm.trigger('closecontextmenu');
					if (fm.UA.iOS) {
						jQuery('div.elfinder div.overflow-scrolling-touch').css('-webkit-overflow-scrolling', 'touch');
					}
				}
				
				autoSyncStop && fm.searchStatus.state < 1 && ! fm.searchStatus.ininc && fm.autoSync();
				autoSyncStop = false;
			},
			
			create = function(type, targets) {
				var sep    = false,
					insSep = false,
					disabled = [],
					isCwd = type === 'cwd',
					selcnt = 0,
					cmdMap;

				currentType = type;
				currentTargets = targets;
				
				// get current uiCmdMap option
				if (!(cmdMap = fm.option('uiCmdMap', isCwd? void(0) : targets[0]))) {
					cmdMap = {};
				}
				
				if (!isCwd) {
					disabled = fm.getDisabledCmds(targets);
				}
				
				selcnt = fm.selected().length;
				if (selcnt > 1) {
					menu.append('<div class="ui-corner-top ui-widget-header elfinder-contextmenu-header"><span>'
					 + fm.i18n('selectedItems', ''+selcnt)
					 + '</span></div>');
				}
				
				nodes = jQuery();
				jQuery.each(types[type]||[], function(i, name) {
					var cmd, cmdName, useMap, node, submenu, hover;
					
					if (name === '|') {
						if (sep) {
							insSep = true;
						}
						return;
					}
					
					if (cmdMap[name]) {
						cmdName = cmdMap[name];
						useMap = true;
					} else {
						cmdName = name;
					}
					cmd = fm.getCommand(cmdName);

					if (cmd && !isCwd && (!fm.searchStatus.state || !cmd.disableOnSearch)) {
						cmd.__disabled = cmd._disabled;
						cmd._disabled = !(cmd.alwaysEnabled || (fm._commands[cmdName] ? jQuery.inArray(name, disabled) === -1 && (!useMap || !disabled[cmdName]) : false));
						jQuery.each(cmd.linkedCmds, function(i, n) {
							var c;
							if (c = fm.getCommand(n)) {
								c.__disabled = c._disabled;
								c._disabled = !(c.alwaysEnabled || (fm._commands[n] ? !disabled[n] : false));
							}
						});
					}

					if (cmd && !cmd._disabled && cmd.getstate(targets) != -1) {
						if (cmd.variants) {
							if (!cmd.variants.length) {
								return;
							}
							node = item(cmd.title, cmd.className? cmd.className : cmd.name, function(){}, cmd.contextmenuOpts);
							
							submenu = jQuery('<div class="ui-front ui-corner-all elfinder-contextmenu-sub"/>')
								.hide()
								.css('max-height', fm.getUI().height() - 30)
								.appendTo(node.append('<span class="elfinder-contextmenu-arrow"/>'));
							
							hover = function(show){
								if (! show) {
									submenu.hide();
								} else {
									var bstyle = base.attr('style');
									base.width(base.width());
									// top: '-1000px' to prevent visible scrollbar of window with the elFinder option `height: '100%'`
									submenu.css({ top: '-1000px', left: 'auto', right: 'auto' });
									var nodeOffset = node.offset(),
										nodeleft   = nodeOffset.left,
										nodetop    = nodeOffset.top,
										nodewidth  = node.outerWidth(),
										width      = submenu.outerWidth(true),
										height     = submenu.outerHeight(true),
										baseOffset = base.offset(),
										wwidth     = baseOffset.left + base.width(),
										wheight    = baseOffset.top + base.height(),
										cltr       = ltr, 
										x          = nodewidth,
										y, over;
	
									if (ltr) {
										over = (nodeleft + nodewidth + width) - wwidth;
										if (over > 10) {
											if (nodeleft > width - 5) {
												x = x - 5;
												cltr = false;
											} else {
												if (!fm.UA.Mobile) {
													x = nodewidth - over;
												}
											}
										}
									} else {
										over = width - nodeleft;
										if (over > 0) {
											if ((nodeleft + nodewidth + width - 15) < wwidth) {
												x = x - 5;
												cltr = true;
											} else {
												if (!fm.UA.Mobile) {
													x = nodewidth - over;
												}
											}
										}
									}
									over = (nodetop + 5 + height) - wheight;
									y = (over > 0 && nodetop < wheight)? 5 - over : (over > 0? 30 - height : 5);
	
									menu.find('.elfinder-contextmenu-sub:visible').hide();
									submenu.css({
										top : y,
										left : cltr? x : 'auto',
										right: cltr? 'auto' : x,
										overflowY: 'auto'
									}).show();
									base.attr('style', bstyle);
								}
							};
							
							node.addClass('elfinder-contextmenu-group')
								.on('mouseleave', '.elfinder-contextmenu-sub', function(e) {
									if (! menu.data('draged')) {
										menu.removeData('submenuKeep');
									}
								})
								.on('submenuclose', '.elfinder-contextmenu-sub', function(e) {
									hover(false);
								})
								.on('click', '.'+smItem, function(e){
									var opts, $this;
									e.stopPropagation();
									if (! menu.data('draged')) {
										$this = jQuery(this);
										if (!cmd.keepContextmenu) {
											menu.hide();
										} else {
											$this.removeClass(cHover);
											node.addClass(cHover);
										}
										opts = $this.data('exec');
										if (typeof opts === 'undefined') {
											opts = {};
										}
										if (typeof opts === 'object') {
											opts._userAction = true;
											opts._currentType = type;
											opts._currentNode = $this;
										}
										!cmd.keepContextmenu && close();
										fm.exec(cmd.name, targets, opts);
									}
								})
								.on('touchend', function(e) {
									if (! menu.data('drag')) {
										hover(true);
										menu.data('submenuKeep', true);
									}
								})
								.on('mouseenter mouseleave', function(e){
									if (! menu.data('touching')) {
										if (node.data('timer')) {
											clearTimeout(node.data('timer'));
											node.removeData('timer');
										}
										if (!jQuery(e.target).closest('.elfinder-contextmenu-sub', menu).length) {
											if (e.type === 'mouseleave') {
												if (! menu.data('submenuKeep')) {
													node.data('timer', setTimeout(function() {
														node.removeData('timer');
														hover(false);
													}, 250));
												}
											} else {
												node.data('timer', setTimeout(function() {
													node.removeData('timer');
													hover(true);
												}, nodes.find('div.elfinder-contextmenu-sub:visible').length? 250 : 0));
											}
										}
									}
								});
							
							jQuery.each(cmd.variants, function(i, variant) {
								var item = variant === '|' ? '<div class="elfinder-contextmenu-separator"/>' :
									jQuery('<div class="'+cmItem+' '+smItem+'"><span>'+variant[1]+'</span></div>').data('exec', variant[0]),
									iconClass, icon;
								if (typeof variant[2] !== 'undefined') {
									icon = jQuery('<span/>').addClass('elfinder-button-icon elfinder-contextmenu-icon');
									if (! /\//.test(variant[2])) {
										icon.addClass('elfinder-button-icon-'+variant[2]);
									} else {
										icon.css(urlIcon(variant[2]));
									}
									item.prepend(icon).addClass(smItem+'-icon');
								}
								submenu.append(item);
							});
								
						} else {
							node = item(cmd.title, cmd.className? cmd.className : cmd.name, function() {
								if (! menu.data('draged')) {
									!cmd.keepContextmenu && close();
									fm.exec(cmd.name, targets, {_userAction: true, _currentType: type, _currentNode: node});
								}
							}, cmd.contextmenuOpts);
							if (cmd.extra && cmd.extra.node) {
								jQuery('<span class="elfinder-button-icon elfinder-button-icon-'+(cmd.extra.icon || '')+' '+exIcon+'"/>')
									.append(cmd.extra.node).appendTo(node);
								jQuery(cmd.extra.node).trigger('ready', {targets: targets});
							} else {
								node.remove('.'+exIcon);
							}
						}
						
						if (cmd.extendsCmd) {
							node.children('span.elfinder-button-icon').addClass('elfinder-button-icon-' + cmd.extendsCmd);
						}
						
						if (insSep) {
							menu.append('<div class="elfinder-contextmenu-separator"/>');
						}
						menu.append(node);
						sep = true;
						insSep = false;
					}
					
					if (cmd && typeof cmd.__disabled !== 'undefined') {
						cmd._disabled = cmd.__disabled;
						delete cmd.__disabled;
						jQuery.each(cmd.linkedCmds, function(i, n) {
							var c;
							if (c = fm.getCommand(n)) {
								c._disabled = c.__disabled;
								delete c.__disabled;
							}
						});
					}
				});
				nodes = menu.children('div.'+cmItem);
			},
			
			createFromRaw = function(raw) {
				currentType = 'raw';
				jQuery.each(raw, function(i, data) {
					var node;
					
					if (data === '|') {
						menu.append('<div class="elfinder-contextmenu-separator"/>');
					} else if (data.label && typeof data.callback == 'function') {
						node = item(data.label, data.icon, function() {
							if (! menu.data('draged')) {
								!data.remain && close();
								data.callback();
							}
						}, data.options || null);
						menu.append(node);
					}
				});
				nodes = menu.children('div.'+cmItem);
			},
			
			currentType = null,
			currentTargets = null;
		
		fm.one('load', function() {
			base = fm.getUI();
			cwd = fm.getUI('cwd');
			fm.bind('contextmenu', function(e) {
				var data = e.data,
					css = {},
					prevNode;

				if (data.type && data.type !== 'files') {
					cwd.trigger('unselectall');
				}
				close();

				if (data.type && data.targets) {
					fm.trigger('contextmenucreate', data);
					create(data.type, data.targets);
					fm.trigger('contextmenucreatedone', data);
				} else if (data.raw) {
					createFromRaw(data.raw);
				}

				if (menu.children().length) {
					prevNode = data.prevNode || null;
					if (prevNode) {
						menu.data('prevNode', menu.prev());
						prevNode.after(menu);
					}
					if (data.fitHeight) {
						css = {maxHeight: Math.min(fm.getUI().height(), jQuery(window).height()), overflowY: 'auto'};
						menu.draggable('destroy').removeClass('ui-draggable');
					}
					open(data.x, data.y, css);
					// call opened callback function
					if (data.opened && typeof data.opened === 'function') {
						data.opened.call(menu);
					}
				}
			})
			.one('destroy', function() { menu.remove(); })
			.bind('disable', close)
			.bind('select', function(e){
				(currentType === 'files' && (!e.data || e.data.selected.toString() !== currentTargets.toString())) && close();
			});
		})
		.shortcut({
			pattern     : fm.OS === 'mac' ? 'ctrl+m' : 'contextmenu shift+f10',
			description : 'contextmenu',
			callback    : function(e) {
				e.stopPropagation();
				e.preventDefault();
				jQuery(document).one('contextmenu.' + fm.namespace, function(e) {
					e.preventDefault();
					e.stopPropagation();
				});
				var sel = fm.selected(),
					type, targets, pos, elm;
				
				if (sel.length) {
					type = 'files';
					targets = sel;
					elm = fm.cwdHash2Elm(sel[0]);
				} else {
					type = 'cwd';
					targets = [ fm.cwd().hash ];
					pos = fm.getUI('workzone').offset();
				}
				if (! elm || ! elm.length) {
					elm = fm.getUI('workzone');
				}
				pos = elm.offset();
				pos.top += (elm.height() / 2);
				pos.left += (elm.width() / 2);
				fm.trigger('contextmenu', {
					'type'    : type,
					'targets' : targets,
					'x'       : pos.left,
					'y'       : pos.top
				});
			}
		});
		
	});
	
};


/*
 * File: /js/ui/cwd.js
 */

/**
 * elFinder current working directory ui.
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfindercwd = function(fm, options) {
		this.not('.elfinder-cwd').each(function() {
		// fm.time('cwdLoad');
		
		var mobile = fm.UA.Mobile,
			list = fm.viewType == 'list',

			undef = 'undefined',
			/**
			 * Select event full name
			 *
			 * @type String
			 **/
			evtSelect = 'select.'+fm.namespace,
			
			/**
			 * Unselect event full name
			 *
			 * @type String
			 **/
			evtUnselect = 'unselect.'+fm.namespace,
			
			/**
			 * Disable event full name
			 *
			 * @type String
			 **/
			evtDisable = 'disable.'+fm.namespace,
			
			/**
			 * Disable event full name
			 *
			 * @type String
			 **/
			evtEnable = 'enable.'+fm.namespace,
			
			c = 'class',
			/**
			 * File css class
			 *
			 * @type String
			 **/
			clFile       = fm.res(c, 'cwdfile'),
			
			/**
			 * Selected css class
			 *
			 * @type String
			 **/
			fileSelector = '.'+clFile,
			
			/**
			 * Selected css class
			 *
			 * @type String
			 **/
			clSelected = 'ui-selected',
			
			/**
			 * Disabled css class
			 *
			 * @type String
			 **/
			clDisabled = fm.res(c, 'disabled'),
			
			/**
			 * Draggable css class
			 *
			 * @type String
			 **/
			clDraggable = fm.res(c, 'draggable'),
			
			/**
			 * Droppable css class
			 *
			 * @type String
			 **/
			clDroppable = fm.res(c, 'droppable'),
			
			/**
			 * Hover css class
			 *
			 * @type String
			 **/
			clHover     = fm.res(c, 'hover'),

			/**
			 * Active css class
			 *
			 * @type String
			 **/
			clActive     = fm.res(c, 'active'),

			/**
			 * Hover css class
			 *
			 * @type String
			 **/
			clDropActive = fm.res(c, 'adroppable'),

			/**
			 * Css class for temporary nodes (for mkdir/mkfile) commands
			 *
			 * @type String
			 **/
			clTmp = clFile+'-tmp',

			/**
			 * Select checkbox css class
			 * 
			 * @type String
			 */
			clSelChk = 'elfinder-cwd-selectchk',

			/**
			 * Number of thumbnails to load in one request (new api only)
			 *
			 * @type Number
			 **/
			tmbNum = fm.options.loadTmbs > 0 ? fm.options.loadTmbs : 5,
			
			/**
			 * Current search query.
			 *
			 * @type String
			 */
			query = '',

			/**
			 * Currect clipboard(cut) hashes as object key
			 * 
			 * @type Object
			 */
			clipCuts = {},

			/**
			 * Parents hashes of cwd
			 *
			 * @type Array
			 */
			cwdParents = [],
			
			/**
			 * cwd current hashes
			 * 
			 * @type Array
			 */
			cwdHashes = [],

			/**
			 * incsearch current hashes
			 * 
			 * @type Array
			 */
			incHashes = void 0,

			/**
			 * Custom columns name and order
			 *
			 * @type Array
			 */
			customCols = [],

			/**
			 * Current clicked element id of first time for dblclick
			 * 
			 * @type String
			 */
			curClickId = '',

			/**
			 * Custom columns builder
			 *
			 * @type Function
			 */
			customColsBuild = function() {
				var cols = '';
				for (var i = 0; i < customCols.length; i++) {
					cols += '<td class="elfinder-col-'+customCols[i]+'">{' + customCols[i] + '}</td>';
				}
				return cols;
			},

			/**
			 * Make template.row from customCols
			 *
			 * @type Function
			 */
			makeTemplateRow = function() {
				return '<tr id="{id}" class="'+clFile+' {permsclass} {dirclass}" title="{tooltip}"{css}><td class="elfinder-col-name"><div class="elfinder-cwd-file-wrapper"><span class="elfinder-cwd-icon {mime}"{style}/>{marker}<span class="elfinder-cwd-filename">{name}</span></div>'+selectCheckbox+'</td>'+customColsBuild()+'</tr>';
			},
			
			selectCheckbox = (jQuery.map(options.showSelectCheckboxUA, function(t) {return (fm.UA[t] || t.match(/^all$/i))? true : null;}).length)? '<div class="elfinder-cwd-select"><input type="checkbox" class="'+clSelChk+'"></div>' : '',

			colResizing = false,
			
			colWidth = null,

			/**
			 * Table header height
			 */
			thHeight,

			/**
			 * File templates
			 *
			 * @type Object
			 **/
			templates = {
				icon : '<div id="{id}" class="'+clFile+' {permsclass} {dirclass} ui-corner-all" title="{tooltip}"><div class="elfinder-cwd-file-wrapper ui-corner-all"><div class="elfinder-cwd-icon {mime} ui-corner-all" unselectable="on"{style}/>{marker}</div><div class="elfinder-cwd-filename" title="{nametitle}">{name}</div>'+selectCheckbox+'</div>',
				row  : ''
			},
			
			permsTpl = fm.res('tpl', 'perms'),
			
			lockTpl = fm.res('tpl', 'lock'),
			
			symlinkTpl = fm.res('tpl', 'symlink'),
			
			/**
			 * Template placeholders replacement rules
			 *
			 * @type Object
			 **/
			replacement = {
				id : function(f) {
					return fm.cwdHash2Id(f.hash);
				},
				name : function(f) {
					var name = fm.escape(f.i18 || f.name);
					!list && (name = name.replace(/([_.])/g, '&#8203;$1'));
					return name;
				},
				nametitle : function(f) {
					return fm.escape(f.i18 || f.name);
				},
				permsclass : function(f) {
					return fm.perms2class(f);
				},
				perm : function(f) {
					return fm.formatPermissions(f);
				},
				dirclass : function(f) {
					var cName = f.mime == 'directory' ? 'directory' : '';
					f.isroot && (cName += ' isroot');
					f.csscls && (cName += ' ' + fm.escape(f.csscls));
					options.getClass && (cName += ' ' + options.getClass(f));
					return cName;
				},
				style : function(f) {
					return f.icon? fm.getIconStyle(f) : '';
				},
				mime : function(f) {
					var cName = fm.mime2class(f.mime);
					f.icon && (cName += ' elfinder-cwd-bgurl');
					return cName;
				},
				size : function(f) {
					return (f.mime === 'directory' && !f.size)? '-' : fm.formatSize(f.size);
				},
				date : function(f) {
					return fm.formatDate(f);
				},
				kind : function(f) {
					return fm.mime2kind(f);
				},
				mode : function(f) {
					return f.perm? fm.formatFileMode(f.perm) : '';
				},
				modestr : function(f) {
					return f.perm? fm.formatFileMode(f.perm, 'string') : '';
				},
				modeoct : function(f) {
					return f.perm? fm.formatFileMode(f.perm, 'octal') : '';
				},
				modeboth : function(f) {
					return f.perm? fm.formatFileMode(f.perm, 'both') : '';
				},
				marker : function(f) {
					return (f.alias || f.mime == 'symlink-broken' ? symlinkTpl : '')+(!f.read || !f.write ? permsTpl : '')+(f.locked ? lockTpl : '');
				},
				tooltip : function(f) {
					var title = fm.formatDate(f) + (f.size > 0 ? ' ('+fm.formatSize(f.size)+')' : ''),
						info  = '';
					if (query && f.path) {
						info = fm.escape(f.path.replace(/\/[^\/]*$/, ''));
					} else {
						info = f.tooltip? fm.escape(f.tooltip).replace(/\r/g, '&#13;') : '';
					}
					if (list) {
						info += (info? '&#13;' : '') + fm.escape(f.i18 || f.name);
					}
					return info? info + '&#13;' + title : title;
				}
			},
			
			/**
			 * Type badge CSS added flag
			 * 
			 * @type Object
			 */
			addedBadges = {},
			
			/**
			 * Type badge style sheet element
			 * 
			 * @type Object
			 */
			addBadgeStyleSheet,
			
			/**
			 * Add type badge CSS into 'head'
			 * 
			 * @type Fundtion
			 */
			addBadgeStyle = function(mime, name) {
				var sel, ext, type;
				if (mime && ! addedBadges[mime]) {
					if (typeof addBadgeStyleSheet === 'undefined') {
						if (jQuery('#elfinderAddBadgeStyle'+fm.namespace).length) {
							jQuery('#elfinderAddBadgeStyle'+fm.namespace).remove();
						}
						addBadgeStyleSheet = jQuery('<style id="addBadgeStyle'+fm.namespace+'"/>').insertBefore(jQuery('head').children(':first')).get(0).sheet || null;
					}
					if (addBadgeStyleSheet) {
						mime = mime.toLowerCase();
						type = mime.split('/');
						ext = fm.escape(fm.mimeTypes[mime] || (name.replace(/.bac?k$/i, '').match(/\.([^.]+)$/) || ['',''])[1]);
						if (ext) {
							sel = '.elfinder-cwd-icon-' + type[0].replace(/(\.|\+)/g, '-');
							if (typeof type[1] !== 'undefined') {
								sel += '.elfinder-cwd-icon-' + type[1].replace(/(\.|\+)/g, '-');
							}
							try {
								addBadgeStyleSheet.insertRule(sel + ':before{content:"' + ext.toLowerCase() + '"}', 0);
							} catch(e) {}
						}
						addedBadges[mime] = true;
					}
				}
			},
			
			/**
			 * Return file html
			 *
			 * @param  Object  file info
			 * @return String
			 **/
			itemhtml = function(f) {
				f.mime && f.mime !== 'directory' && !addedBadges[f.mime] && addBadgeStyle(f.mime, f.name);
				return templates[list ? 'row' : 'icon']
						.replace(/\{([a-z0-9_]+)\}/g, function(s, e) { 
							return replacement[e] ? replacement[e](f, fm) : (f[e] ? f[e] : ''); 
						});
			},
			
			/**
			 * jQueery node that will be selected next
			 * 
			 * @type Object jQuery node
			 */
			selectedNext = jQuery(),
			
			/**
			 * Flag. Required for msie to avoid unselect files on dragstart
			 *
			 * @type Boolean
			 **/
			selectLock = false,
			
			/**
			 * Move selection to prev/next file
			 *
			 * @param String  move direction
			 * @param Boolean append to current selection
			 * @return void
			 * @rise select			
			 */
			select = function(keyCode, append) {
				var code     = jQuery.ui.keyCode,
					prev     = keyCode == code.LEFT || keyCode == code.UP,
					sel      = cwd.find('[id].'+clSelected),
					selector = prev ? 'first:' : 'last',
					s, n, sib, top, left;

				function sibling(n, direction) {
					return n[direction+'All']('[id]:not(.'+clDisabled+'):not(.elfinder-cwd-parent):first');
				}
				
				if (sel.length) {
					s = sel.filter(prev ? ':first' : ':last');
					sib = sibling(s, prev ? 'prev' : 'next');
					
					if (!sib.length) {
						// there is no sibling on required side - do not move selection
						n = s;
					} else if (list || keyCode == code.LEFT || keyCode == code.RIGHT) {
						// find real prevoius file
						n = sib;
					} else {
						// find up/down side file in icons view
						top = s.position().top;
						left = s.position().left;

						n = s;
						if (prev) {
							do {
								n = n.prev('[id]');
							} while (n.length && !(n.position().top < top && n.position().left <= left));

							if (n.hasClass(clDisabled)) {
								n = sibling(n, 'next');
							}
						} else {
							do {
								n = n.next('[id]');
							} while (n.length && !(n.position().top > top && n.position().left >= left));
							
							if (n.hasClass(clDisabled)) {
								n = sibling(n, 'prev');
							}
							// there is row before last one - select last file
							if (!n.length) {
								sib = cwd.find('[id]:not(.'+clDisabled+'):last');
								if (sib.position().top > top) {
									n = sib;
								}
							}
						}
					}
					// !append && unselectAll();
				} else {
					if (selectedNext.length) {
						n = prev? selectedNext.prev() : selectedNext;
					} else {
						// there are no selected file - select first/last one
						n = cwd.find('[id]:not(.'+clDisabled+'):not(.elfinder-cwd-parent):'+(prev ? 'last' : 'first'));
					}
				}
				
				if (n && n.length && !n.hasClass('elfinder-cwd-parent')) {
					if (s && append) {
						// append new files to selected
						n = s.add(s[prev ? 'prevUntil' : 'nextUntil']('#'+n.attr('id'))).add(n);
					} else {
						// unselect selected files
						sel.trigger(evtUnselect);
					}
					// select file(s)
					n.trigger(evtSelect);
					// set its visible
					scrollToView(n.filter(prev ? ':first' : ':last'));
					// update cache/view
					trigger();
				}
			},
			
			selectedFiles = {},
			
			selectFile = function(hash) {
				fm.cwdHash2Elm(hash).trigger(evtSelect);
			},
			
			allSelected = false,
			
			selectAll = function() {
				var phash = fm.cwd().hash;

				selectCheckbox && selectAllCheckbox.find('input').prop('checked', true);
				fm.lazy(function() {
					var files;
					if (fm.maxTargets && (incHashes || cwdHashes).length > fm.maxTargets) {
						unselectAll({ notrigger: true });
						files = jQuery.map(incHashes || cwdHashes, function(hash) { return fm.file(hash) || null; });
						files = files.slice(0, fm.maxTargets);
						selectedFiles = {};
						jQuery.each(files, function(i, v) {
							selectedFiles[v.hash] = true;
							fm.cwdHash2Elm(v.hash).trigger(evtSelect);
						});
						fm.toast({mode: 'warning', msg: fm.i18n(['errMaxTargets', fm.maxTargets])});
					} else {
						cwd.find('[id]:not(.'+clSelected+'):not(.elfinder-cwd-parent)').trigger(evtSelect);
						selectedFiles = fm.arrayFlip(incHashes || cwdHashes, true);
					}
					trigger();
					selectCheckbox && selectAllCheckbox.data('pending', false);
				}, 0, {repaint: true});
			},
			
			/**
			 * Unselect all files
			 *
			 * @param  Object  options
			 * @return void
			 */
			unselectAll = function(opts) {
				var o = opts || {};
				selectCheckbox && selectAllCheckbox.find('input').prop('checked', false);
				if (Object.keys(selectedFiles).length) {
					selectLock = false;
					selectedFiles = {};
					cwd.find('[id].'+clSelected).trigger(evtUnselect);
					selectCheckbox && cwd.find('input:checkbox.'+clSelChk).prop('checked', false);
				}
				!o.notrigger && trigger();
				selectCheckbox && selectAllCheckbox.data('pending', false);
				cwd.removeClass('elfinder-cwd-allselected');
			},
			
			selectInvert = function() {
				var invHashes = {};
				if (allSelected) {
					unselectAll();
				} else if (! Object.keys(selectedFiles).length) {
					selectAll();
				} else {
					jQuery.each((incHashes || cwdHashes), function(i, h) {
						var itemNode = fm.cwdHash2Elm(h);
						if (! selectedFiles[h]) {
							invHashes[h] = true;
							itemNode.length && itemNode.trigger(evtSelect);
						} else {
							itemNode.length && itemNode.trigger(evtUnselect);
						}
					});
					selectedFiles = invHashes;
					trigger();
				}
			},
			
			/**
			 * Return selected files hashes list
			 *
			 * @return Array
			 */
			selected = function() {
				return Object.keys(selectedFiles);
			},
			
			/**
			 * Last selected node id
			 * 
			 * @type String|Void
			 */
			lastSelect = void 0,
			
			/**
			 * Fire elfinder "select" event and pass selected files to it
			 *
			 * @return void
			 */
			trigger = function() {
				var selected = Object.keys(selectedFiles),
					opts = {
						selected : selected,
						origin : 'cwd'
					};
				
				if (oldSchoolItem && (selected.length > 1 || selected[0] !== fm.cwdId2Hash(
					oldSchoolItem.attr('id'))) && oldSchoolItem.hasClass(clSelected)) {
					oldSchoolItem.trigger(evtUnselect);
				}
				allSelected = selected.length && (selected.length === (incHashes || cwdHashes).length) && (!fm.maxTargets || selected.length <= fm.maxTargets);
				if (selectCheckbox) {
					selectAllCheckbox.find('input').prop('checked', allSelected);
					cwd[allSelected? 'addClass' : 'removeClass']('elfinder-cwd-allselected');
				}
				if (allSelected) {
					opts.selectall = true;
				} else if (! selected.length) {
					opts.unselectall = true;
				}
				fm.trigger('select', opts);
			},
			
			/**
			 * Scroll file to set it visible
			 *
			 * @param DOMElement  file/dir node
			 * @return void
			 */
			scrollToView = function(o, blink) {
				if (! o.length) {
					return;
				}
				var ftop    = o.position().top,
					fheight = o.outerHeight(true),
					wtop    = wrapper.scrollTop(),
					wheight = wrapper.get(0).clientHeight,
					thheight = tableHeader? tableHeader.outerHeight(true) : 0;

				if (ftop + thheight + fheight > wtop + wheight) {
					wrapper.scrollTop(parseInt(ftop + thheight + fheight - wheight));
				} else if (ftop < wtop) {
					wrapper.scrollTop(ftop);
				}
				list && wrapper.scrollLeft(0);
				!!blink && fm.resources.blink(o, 'lookme');
			},
			
			/**
			 * Files we get from server but not show yet
			 *
			 * @type Array
			 **/
			buffer = [],
			
			/**
			 * Extra data of buffer
			 *
			 * @type Object
			 **/
			bufferExt = {},
			
			/**
			 * Return index of elements with required hash in buffer 
			 *
			 * @param String  file hash
			 * @return Number
			 */
			index = function(hash) {
				var l = buffer.length;
				
				while (l--) {
					if (buffer[l].hash == hash) {
						return l;
					}
				}
				return -1;
			},
			
			/**
			 * Scroll start event name
			 *
			 * @type String
			 **/
			scrollStartEvent = 'elfscrstart',
			
			/**
			 * Scroll stop event name
			 *
			 * @type String
			 **/
			scrollEvent = 'elfscrstop',
			
			scrolling = false,
			
			/**
			 * jQuery UI selectable option
			 * 
			 * @type Object
			 */
			selectableOption = {
				disabled   : true,
				filter     : '[id]:first',
				stop       : trigger,
				delay      : 250,
				appendTo   : 'body',
				autoRefresh: false,
				selected   : function(e, ui) { jQuery(ui.selected).trigger(evtSelect); },
				unselected : function(e, ui) { jQuery(ui.unselected).trigger(evtUnselect); }
			},
			
			/**
			 * hashes of items displayed in current view
			 * 
			 * @type Object  ItemHash => DomId
			 */
			inViewHashes = {},
			
			/**
			 * Processing when the current view is changed (On open, search, scroll, resize etc.)
			 * 
			 * @return void
			 */
			wrapperRepaint = function(init, recnt) {
				if (!bufferExt.renderd) {
					return;
				}
				var firstNode = (list? cwd.find('tbody:first') : cwd).children('[id]'+(options.oldSchool? ':not(.elfinder-cwd-parent)' : '')+':first');
				if (!firstNode.length) {
					return;
				}
				var selectable = cwd.data('selectable'),
					rec = (function() {
						var wos = wrapper.offset(),
							ww = wrapper.width(),
							w = jQuery(window),
							x = firstNode.width() / 2,
							l = Math.min(wos.left - w.scrollLeft() + (fm.direction === 'ltr'? x : ww - x), wos.left + ww - 10),
							t = wos.top - w.scrollTop() + 10 + (list? thHeight : 0);
						return {left: Math.max(0, Math.round(l)), top: Math.max(0, Math.round(t))};
					})(),
					tgt = init? firstNode : jQuery(document.elementFromPoint(rec.left , rec.top)),
					ids = {},
					tmbs = {},
					multi = 5,
					cnt = Math.ceil((bufferExt.hpi? Math.ceil((wz.data('rectangle').height / bufferExt.hpi) * 1.5) : showFiles) / multi),
					chk = function() {
						var id, hash, file, i;
						for (i = 0; i < multi; i++) {
							id = tgt.attr('id');
							if (id) {
								bufferExt.getTmbs = [];
								hash = fm.cwdId2Hash(id);
								inViewHashes[hash] = id;
								// for tmbs
								if (bufferExt.attachTmbs[hash]) {
									tmbs[hash] = bufferExt.attachTmbs[hash];
								}
								// for selectable
								selectable && (ids[id] = true);
							}
							// next node
							tgt = tgt.next();
							if (!tgt.length) {
								break;
							}
						}
					},
					done = function() {
						var idsArr;
						if (cwd.data('selectable')) {
							Object.assign(ids, selectedFiles);
							idsArr = Object.keys(ids);
							if (idsArr.length) {
								selectableOption.filter = '#'+idsArr.join(', #');
								cwd.selectable('enable').selectable('option', {filter : selectableOption.filter}).selectable('refresh');
							}
						}
						if (Object.keys(tmbs).length) {
							bufferExt.getTmbs = [];
							attachThumbnails(tmbs);
						}
					},
					setTarget = function() {
						if (!tgt.hasClass(clFile)) {
							tgt = tgt.closest(fileSelector);
						}
					},
					arr, widget;
				
				inViewHashes = {};
				selectable && cwd.selectable('option', 'disabled');
				
				if (tgt.length) {
					if (!tgt.hasClass(clFile) && !tgt.closest(fileSelector).length) {
						// dialog, serach button etc.
						widget = fm.getUI().find('.ui-dialog:visible,.ui-widget:visible');
						if (widget.length) {
							widget.hide();
							tgt = jQuery(document.elementFromPoint(rec.left , rec.top));
							widget.show();
						} else {
							widget = null;
						}
					}
					setTarget();
					if (!tgt.length) {
						// try search 5px down
						widget && widget.hide();
						tgt = jQuery(document.elementFromPoint(rec.left , rec.top + 5));
						widget && widget.show();
						setTarget();
					}
				}

				if (tgt.length) {
					if (tgt.attr('id')) {
						if (init) {
							for (var i = 0; i < cnt; i++) {
								chk();
								if (! tgt.length) {
									break;
								}
							}
							done();
						} else {
							bufferExt.repaintJob && bufferExt.repaintJob.state() === 'pending' && bufferExt.repaintJob.reject();
							arr = new Array(cnt);
							bufferExt.repaintJob = fm.asyncJob(function() {
								chk();
								if (! tgt.length) {
									done();
									bufferExt.repaintJob && bufferExt.repaintJob.state() === 'pending' && bufferExt.repaintJob.reject();
								}
							}, arr).done(done);
						}
					}
				} else if (init && bufferExt.renderd) {
					// In initial request, cwd DOM not renderd so doing lazy check
					recnt = recnt || 0;
					if (recnt < 10) { // Prevent infinite loop
						requestAnimationFrame(function() {
							wrapperRepaint(init, ++recnt);
						});
					}
				}
			},
			
			/**
			 * Item node of oldScholl ".."
			 */
			oldSchoolItem = null,

			/**
			 * display parent folder with ".." name
			 * 
			 * @param  String  phash
			 * @return void
			 */
			oldSchool = function(p) {
				var phash = fm.cwd().phash,
					pdir  = fm.file(phash) || null,
					set   = function(pdir) {
						if (pdir) {
							oldSchoolItem = jQuery(itemhtml(jQuery.extend(true, {}, pdir, {name : '..', i18 : '..', mime : 'directory'})))
								.addClass('elfinder-cwd-parent')
								.on('dblclick', function() {
									var hash = fm.cwdId2Hash(this.id);
									fm.trigger('select', {selected : [hash]}).exec('open', hash);
								});
							(list ? oldSchoolItem.children('td:first') : oldSchoolItem).children('.elfinder-cwd-select').remove();
							(list ? cwd.find('tbody') : cwd).prepend(oldSchoolItem);
							fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
						}
					};
				if (pdir) {
					set(pdir);
				} else {
					if (fm.getUI('tree').length) {
						fm.one('parents', function() {
							set(fm.file(phash) || null);
							wrapper.trigger(scrollEvent);
						});
					} else {
						fm.request({
							data : {cmd : 'parents', target : fm.cwd().hash},
							preventFail : true
						})
						.done(function(data) {
							set(fm.file(phash) || null);
							wrapper.trigger(scrollEvent);
						});
					}
				}
			},
			
			showFiles = fm.options.showFiles,
			
			/**
			 * Cwd scroll event handler.
			 * Lazy load - append to cwd not shown files
			 *
			 * @return void
			 */
			render = function() {
				if (bufferExt.rendering || (bufferExt.renderd && ! buffer.length)) {
					return;
				}
				var place = (list ? cwd.children('table').children('tbody') : cwd),
					phash,
					chk,
					// created document fragment for jQuery >= 1.12, 2.2, 3.0
					// see Studio-42/elFinder#1544 @ github
					docFlag = jQuery.htmlPrefilter? true : false,
					tempDom = docFlag? jQuery(document.createDocumentFragment()) : jQuery('<div/>'),
					go      = function(o){
						var over  = o || null,
							html  = [],
							dirs  = false,
							atmb  = {},
							stmb  = (fm.option('tmbUrl') === 'self'),
							init  = bufferExt.renderd? false : true,
							files, locks, selected;
						
						files = buffer.splice(0, showFiles + (over || 0) / (bufferExt.hpi || 1));
						bufferExt.renderd += files.length;
						if (! buffer.length) {
							bottomMarker.hide();
							wrapper.off(scrollEvent, render);
						}
						
						locks = [];
						html = jQuery.map(files, function(f) {
							if (f.hash && f.name) {
								if (f.mime == 'directory') {
									dirs = true;
								}
								if ((f.tmb && (f.tmb != 1 || f.size > 0)) || (stmb && f.mime.indexOf('image/') === 0)) {
									atmb[f.hash] = f.tmb || 'self';
								}
								clipCuts[f.hash] && locks.push(f.hash);
								return itemhtml(f);
							}
							return null;
						});

						// html into temp node
						tempDom.empty().append(html.join(''));
						
						// make directory droppable
						dirs && !mobile && makeDroppable(tempDom);
						
						// check selected items
						selected = [];
						if (Object.keys(selectedFiles).length) {
							tempDom.find('[id]:not(.'+clSelected+'):not(.elfinder-cwd-parent)').each(function() {
								selectedFiles[fm.cwdId2Hash(this.id)] && selected.push(jQuery(this));
							});
						}
						
						// append to cwd
						place.append(docFlag? tempDom : tempDom.children());
						
						// trigger select
						if (selected.length) {
							jQuery.each(selected, function(i, n) { n.trigger(evtSelect); });
							trigger();
						}
						
						locks.length && fm.trigger('lockfiles', {files: locks});
						!bufferExt.hpi && bottomMarkerShow(place, files.length);
						
						if (list) {
							// show thead
							cwd.find('thead').show();
							// fixed table header
							fixTableHeader({fitWidth: ! colWidth});
						}
						
						if (Object.keys(atmb).length) {
							Object.assign(bufferExt.attachTmbs, atmb);
						}
						
						if (init) {
							if (! mobile && ! cwd.data('selectable')) {
								// make files selectable
								cwd.selectable(selectableOption).data('selectable', true);
							}
						}

						! scrolling && wrapper.trigger(scrollEvent);
					};
				
				if (! bufferExt.renderd) {
					// first time to go()
					bufferExt.rendering = true;
					// scroll top on dir load to avoid scroll after page reload
					wrapper.scrollTop(0);
					phash = fm.cwd().phash;
					go();
					if (options.oldSchool) {
						if (phash && !query) {
							oldSchool(phash);
						} else {
							oldSchoolItem = jQuery();
						}
					}
					if (list) {
						colWidth && setColwidth();
						fixTableHeader({fitWidth: true});
					}
					bufferExt.itemH = (list? place.find('tr:first') : place.find('[id]:first')).outerHeight(true);
					fm.trigger('cwdrender');
					bufferExt.rendering = false;
					wrapperRepaint(true);
				}
				if (! bufferExt.rendering && buffer.length) {
					// next go()
					if ((chk = (wrapper.height() + wrapper.scrollTop() + fm.options.showThreshold + bufferExt.row) - (bufferExt.renderd * bufferExt.hpi)) > 0) {
						bufferExt.rendering = true;
						fm.lazy(function() {
							go(chk);
							bufferExt.rendering = false;
						});
					} else {
						!fm.enabled() && resize();
					}
				} else {
					resize();
				}
			},
			
			// fixed table header jQuery object
			tableHeader = null,

			// Is UA support CSS sticky
			cssSticky = fm.UA.CSS.positionSticky && fm.UA.CSS.widthMaxContent,
			
			// To fixed table header colmun
			fixTableHeader = function(optsArg) {
				thHeight = 0;
				if (! options.listView.fixedHeader) {
					return;
				}
				var setPos = function() {
					var val, pos;
					pos = (fm.direction === 'ltr')? 'left' : 'right';
					val = ((fm.direction === 'ltr')? wrapper.scrollLeft() : table.outerWidth(true) - wrapper.width() - wrapper.scrollLeft()) * -1;
					if (base.css(pos) !== val) {
						base.css(pos, val);
					}
				},
				opts = optsArg || {},
				cnt, base, table, htable, thead, tbody, hheight, htr, btr, htd, btd, htw, btw, init;
				
				tbody = cwd.find('tbody');
				btr = tbody.children('tr:first');
				if (btr.length && btr.is(':visible')) {
					table = tbody.parent();
					if (! tableHeader) {
						init = true;
						tbody.addClass('elfinder-cwd-fixheader');
						thead = cwd.find('thead').attr('id', fm.namespace+'-cwd-thead');
						htr = thead.children('tr:first');
						hheight = htr.outerHeight(true);
						cwd.css('margin-top', hheight - parseInt(table.css('padding-top')));
						if (cssSticky) {
							tableHeader = jQuery('<div class="elfinder-table-header-sticky"/>').addClass(cwd.attr('class')).append(jQuery('<table/>').append(thead));
							cwd.after(tableHeader);
							wrapper.on('resize.fixheader', function(e) {
								e.stopPropagation();
								fixTableHeader({fitWidth: true});
							});
						} else {
							base = jQuery('<div/>').addClass(cwd.attr('class')).append(jQuery('<table/>').append(thead));
							tableHeader = jQuery('<div/>').addClass(wrapper.attr('class') + ' elfinder-cwd-fixheader')
								.removeClass('ui-droppable native-droppable')
								.css(wrapper.position())
								.css({ height: hheight, width: cwd.outerWidth() })
								.append(base);
							if (fm.direction === 'rtl') {
								tableHeader.css('left', (wrapper.data('width') - wrapper.width()) + 'px');
							}
							setPos();
							wrapper.after(tableHeader)
								.on('scroll.fixheader resize.fixheader', function(e) {
									setPos();
									if (e.type === 'resize') {
										e.stopPropagation();
										tableHeader.css(wrapper.position());
										wrapper.data('width', wrapper.css('overflow', 'hidden').width());
										wrapper.css('overflow', 'auto');
										fixTableHeader();
									}
								});
						}
					} else {
						thead = jQuery('#'+fm.namespace+'-cwd-thead');
						htr = thead.children('tr:first');
					}
					
					if (init || opts.fitWidth || Math.abs(btr.outerWidth() - htr.outerWidth()) > 2) {
						cnt = customCols.length + 1;
						for (var i = 0; i < cnt; i++) {
							htd = htr.children('td:eq('+i+')');
							btd = btr.children('td:eq('+i+')');
							htw = htd.width();
							btw = btd.width();
							if (typeof htd.data('delta') === 'undefined') {
								htd.data('delta', (htd.outerWidth() - htw) - (btd.outerWidth() - btw));
							}
							btw -= htd.data('delta');
							if (! init && ! opts.fitWidth && htw === btw) {
								break;
							}
							htd.css('width', btw + 'px');
						}
					}
					
					if (!cssSticky) {
						tableHeader.data('widthTimer') && cancelAnimationFrame(tableHeader.data('widthTimer'));
						tableHeader.data('widthTimer', requestAnimationFrame(function() {
							if (tableHeader) {
								tableHeader.css('width', mBoard.width() + 'px');
								if (fm.direction === 'rtl') {
									tableHeader.css('left', (wrapper.data('width') - wrapper.width()) + 'px');
								}
							}
						}));
					}
					thHeight = thead.height();
				}
			},
			
			// Set colmun width
			setColwidth = function() {
				if (list && colWidth) {
					var cl = 'elfinder-cwd-colwidth',
					first = cwd.find('tr[id]:first'),
					former;
					if (! first.hasClass(cl)) {
						former = cwd.find('tr.'+cl);
						former.removeClass(cl).find('td').css('width', '');
						first.addClass(cl);
						cwd.find('table:first').css('table-layout', 'fixed');
						jQuery.each(jQuery.merge(['name'], customCols), function(i, k) {
							var w = colWidth[k] || first.find('td.elfinder-col-'+k).width();
							first.find('td.elfinder-col-'+k).width(w);
						});
					}
				}
			},
			
			/**
			 * Droppable options for cwd.
			 * Drop target is `wrapper`
			 * Do not add class on childs file over
			 *
			 * @type Object
			 */
			droppable = Object.assign({}, fm.droppable, {
				over : function(e, ui) {
					var dst    = jQuery(this),
						helper = ui.helper,
						ctr    = (e.shiftKey || e.ctrlKey || e.metaKey),
						hash, status, inParent;
					e.stopPropagation();
					helper.data('dropover', helper.data('dropover') + 1);
					dst.data('dropover', true);
					helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
					if (helper.data('namespace') !== fm.namespace || ! fm.insideWorkzone(e.pageX, e.pageY)) {
						dst.removeClass(clDropActive);
						//helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
						return;
					}
					if (dst.hasClass(fm.res(c, 'cwdfile'))) {
						hash = fm.cwdId2Hash(dst.attr('id'));
						dst.data('dropover', hash);
					} else {
						hash = fm.cwd().hash;
						fm.cwd().write && dst.data('dropover', hash);
					}
					inParent = (fm.file(helper.data('files')[0]).phash === hash);
					if (dst.data('dropover') === hash) {
						jQuery.each(helper.data('files'), function(i, h) {
							if (h === hash || (inParent && !ctr && !helper.hasClass('elfinder-drag-helper-plus'))) {
								dst.removeClass(clDropActive);
								return false; // break jQuery.each
							}
						});
					} else {
						dst.removeClass(clDropActive);
					}
					if (helper.data('locked') || inParent) {
						status = 'elfinder-drag-helper-plus';
					} else {
						status = 'elfinder-drag-helper-move';
						if (ctr) {
							status += ' elfinder-drag-helper-plus';
						}
					}
					dst.hasClass(clDropActive) && helper.addClass(status);
					requestAnimationFrame(function(){ dst.hasClass(clDropActive) && helper.addClass(status); });
				},
				out : function(e, ui) {
					var helper = ui.helper;
					e.stopPropagation();
					helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus').data('dropover', Math.max(helper.data('dropover') - 1, 0));
					jQuery(this).removeData('dropover')
					       .removeClass(clDropActive);
				},
				deactivate : function() {
					jQuery(this).removeData('dropover')
					       .removeClass(clDropActive);
				},
				drop : function(e, ui) {
					unselectAll({ notrigger: true });
					fm.droppable.drop.call(this, e, ui);
				}
			}),
			
			/**
			 * Make directory droppable
			 *
			 * @return void
			 */
			makeDroppable = function(place) {
				place = place? place : (list ? cwd.find('tbody') : cwd);
				var targets = place.children('.directory:not(.'+clDroppable+',.elfinder-na,.elfinder-ro)');

				if (fm.isCommandEnabled('paste')) {
					targets.droppable(droppable);
				}
				if (fm.isCommandEnabled('upload')) {
					targets.addClass('native-droppable');
				}
				
				place.children('.isroot').each(function(i, n) {
					var $n   = jQuery(n),
						hash = fm.cwdId2Hash(n.id);
					
					if (fm.isCommandEnabled('paste', hash)) {
						if (! $n.hasClass(clDroppable+',elfinder-na,elfinder-ro')) {
							$n.droppable(droppable);
						}
					} else {
						if ($n.hasClass(clDroppable)) {
							$n.droppable('destroy');
						}
					}
					if (fm.isCommandEnabled('upload', hash)) {
						if (! $n.hasClass('native-droppable,elfinder-na,elfinder-ro')) {
							$n.addClass('native-droppable');
						}
					} else {
						if ($n.hasClass('native-droppable')) {
							$n.removeClass('native-droppable');
						}
					}
				});
			},
			
			/**
			 * Preload required thumbnails and on load add css to files.
			 * Return false if required file is not visible yet (in buffer) -
			 * required for old api to stop loading thumbnails.
			 *
			 * @param  Object  file hash -> thumbnail map
			 * @param  Bool    reload
			 * @return void
			 */
			attachThumbnails = function(tmbs, reload) {
				var attach = function(node, tmb) {
						jQuery('<img/>')
							.on('load', function() {
								node.find('.elfinder-cwd-icon').addClass(tmb.className).css('background-image', "url('"+tmb.url+"')");
							})
							.attr('src', tmb.url);
					},
					chk  = function(hash, tmb) {
						var node = fm.cwdHash2Elm(hash),
							file, tmbObj, reloads = [];
	
						if (node.length) {
							if (tmb != '1') {
								file = fm.file(hash);
								if (file.tmb !== tmb) {
									file.tmb = tmb;
								}
								tmbObj = fm.tmb(file);
								if (reload) {
									node.find('.elfinder-cwd-icon').addClass(tmbObj.className).css('background-image', "url('"+tmbObj.url+"')");
								} else {
									attach(node, tmbObj);
								}
								delete bufferExt.attachTmbs[hash];
							} else {
								if (reload) {
									loadThumbnails([hash]);
								} else if (! bufferExt.tmbLoading[hash]) {
									bufferExt.getTmbs.push(hash);
								}
							}
						}
					};

				if (jQuery.isPlainObject(tmbs) && Object.keys(tmbs).length) {
					Object.assign(bufferExt.attachTmbs, tmbs);
					jQuery.each(tmbs, chk);
					if (! reload && bufferExt.getTmbs.length && ! Object.keys(bufferExt.tmbLoading).length) {
						loadThumbnails();
					}
				}
			},
			
			/**
			 * Load thumbnails from backend.
			 *
			 * @param  Array|void reloads  hashes list for reload thumbnail items
			 * @return void
			 */
			loadThumbnails = function(reloads) {
				var tmbs = [],
					reload = false;
				
				if (fm.oldAPI) {
					fm.request({
						data : {cmd : 'tmb', current : fm.cwd().hash},
						preventFail : true
					})
					.done(function(data) {
						if (data.images && Object.keys(data.images).length) {
							attachThumbnails(data.images);
						}
						if (data.tmb) {
							loadThumbnails();
						}
					});
					return;
				} 

				if (reloads) {
					reload = true;
					tmbs = reloads.splice(0, tmbNum);
				} else {
					tmbs = bufferExt.getTmbs.splice(0, tmbNum);
				}
				if (tmbs.length) {
					if (reload || inViewHashes[tmbs[0]] || inViewHashes[tmbs[tmbs.length-1]]) {
						jQuery.each(tmbs, function(i, h) {
							bufferExt.tmbLoading[h] = true;
						});
						fm.request({
							data : {cmd : 'tmb', targets : tmbs},
							preventFail : true
						})
						.done(function(data) {
							var errs = [],
								resLen;
							if (data.images) {
								if (resLen = Object.keys(data.images).length) {
									if (resLen < tmbs.length) {
										jQuery.each(tmbs, function(i, h) {
											if (! data.images[h]) {
												errs.push(h);
											}
										});
									}
									attachThumbnails(data.images, reload);
								} else {
									errs = tmbs;
								}
								// unset error items from bufferExt.attachTmbs
								if (errs.length) {
									jQuery.each(errs, function(i, h) {
										delete bufferExt.attachTmbs[h];
									});
								}
							}
							if (reload) {
								if (reloads.length) {
									loadThumbnails(reloads);
								}
							}
						})
						.always(function() {
							bufferExt.tmbLoading = {};
							if (! reload && bufferExt.getTmbs.length) {
								loadThumbnails();
							}
						});
					}
				}
			},
			
			/**
			 * Add new files to cwd/buffer
			 *
			 * @param  Array  new files
			 * @return void
			 */
			add = function(files, mode) {
				var place    = list ? cwd.find('tbody') : cwd,
					l        = files.length, 
					atmb     = {},
					findNode = function(file) {
						var pointer = cwd.find('[id]:first'), file2;

						while (pointer.length) {
							file2 = fm.file(fm.cwdId2Hash(pointer.attr('id')));
							if (!pointer.hasClass('elfinder-cwd-parent') && file2 && fm.compare(file, file2) < 0) {
								return pointer;
							}
							pointer = pointer.next('[id]');
						}
					},
					findIndex = function(file) {
						var l = buffer.length, i;
						
						for (i =0; i < l; i++) {
							if (fm.compare(file, buffer[i]) < 0) {
								return i;
							}
						}
						return l || -1;
					},
					// created document fragment for jQuery >= 1.12, 2.2, 3.0
					// see Studio-42/elFinder#1544 @ github
					docFlag = jQuery.htmlPrefilter? true : false,
					tempDom = docFlag? jQuery(document.createDocumentFragment()) : jQuery('<div/>'),
					file, hash, node, nodes, ndx, stmb;

				if (l > showFiles) {
					// re-render for performance tune
					content();
					selectedFiles = fm.arrayFlip(jQuery.map(files, function(f) { return f.hash; }), true);
					trigger();
				} else {
					// add the item immediately
					l && wz.removeClass('elfinder-cwd-wrapper-empty');
					
					// Self thumbnail
					stmb = (fm.option('tmbUrl') === 'self');
					
					while (l--) {
						file = files[l];
						hash = file.hash;
						
						if (fm.cwdHash2Elm(hash).length) {
							continue;
						}
						
						if ((node = findNode(file)) && ! node.length) {
							node = null;
						}
						if (! node && (ndx = findIndex(file)) >= 0) {
							buffer.splice(ndx, 0, file);
						} else {
							tempDom.empty().append(itemhtml(file));
							(file.mime === 'directory') && !mobile && makeDroppable(tempDom);
							nodes = docFlag? tempDom : tempDom.children();
							if (node) {
								node.before(nodes);
							} else {
								place.append(nodes);
							}
						}
						
						if (fm.cwdHash2Elm(hash).length) {
							if ((file.tmb && (file.tmb != 1 || file.size > 0)) || (stmb && file.mime.indexOf('image/') === 0)) {
								atmb[hash] = file.tmb || 'self';
							}
						}
					}
	
					if (list) {
						setColwidth();
						fixTableHeader({fitWidth: ! colWidth});
					}
					bottomMarkerShow(place);
					if (Object.keys(atmb).length) {
						Object.assign(bufferExt.attachTmbs, atmb);
					}
				}
			},
			
			/**
			 * Remove files from cwd/buffer
			 *
			 * @param  Array  files hashes
			 * @return void
			 */
			remove = function(files) {
				var l = files.length,
					inSearch = fm.searchStatus.state > 1,
					curCmd = fm.getCommand(fm.currentReqCmd) || {},
					hash, n, ndx, found;

				// removed cwd
				if (!fm.cwd().hash && !curCmd.noChangeDirOnRemovedCwd) {
					jQuery.each(cwdParents.reverse(), function(i, h) {
						if (fm.file(h)) {
							found = true;
							fm.one(fm.currentReqCmd + 'done', function() {
								!fm.cwd().hash && fm.exec('open', h);
							});
							return false;
						}
					});
					// fallback to fm.roots[0]
					!found && !fm.cwd().hash && fm.exec('open', fm.roots[Object.keys(fm.roots)[0]]);
					return;
				}
				
				while (l--) {
					hash = files[l];
					if ((n = fm.cwdHash2Elm(hash)).length) {
						try {
							n.remove();
							--bufferExt.renderd;
						} catch(e) {
							fm.debug('error', e);
						}
					} else if ((ndx = index(hash)) !== -1) {
						buffer.splice(ndx, 1);
					}
					selectedFiles[hash] && delete selectedFiles[hash];
					if (inSearch) {
						if ((ndx = jQuery.inArray(hash, cwdHashes)) !== -1) {
							cwdHashes.splice(ndx, 1);
						}
					}
				}
				
				inSearch && fm.trigger('cwdhasheschange', cwdHashes);
				
				if (list) {
					setColwidth();
					fixTableHeader({fitWidth: ! colWidth});
				}
			},
			
			customColsNameBuild = function() {
				var name = '',
				customColsName = '';
				for (var i = 0; i < customCols.length; i++) {
					name = fm.getColumnName(customCols[i]);
					customColsName +='<td class="elfinder-cwd-view-th-'+customCols[i]+' sortable-item">'+name+'</td>';
				}
				return customColsName;
			},
			
			setItemBoxSize = function(boxSize) {
				var place, elm;
				if (!boxSize.height) {
					place = (list ? cwd.find('tbody') : cwd);
					elm = place.find(list? 'tr:first' : '[id]:first');
					boxSize.height = elm.outerHeight(true);
					if (!list) {
						boxSize.width = elm.outerWidth(true);
					}
				}
			},

			bottomMarkerShow = function(cur, cnt) {
				var place = cur || (list ? cwd.find('tbody') : cwd),
					boxSize = itemBoxSize[fm.viewType],
					col = 1,
					row;

				if (buffer.length > 0) {
					if (!bufferExt.hpi) {
						setItemBoxSize(boxSize);
						if (! list) {
							col = Math.floor(place.width() / boxSize.width);
							bufferExt.row = boxSize.height;
							bufferExt.hpi = bufferExt.row / col;
						} else {
							bufferExt.row = bufferExt.hpi = boxSize.height;
						}
					} else if (!list) {
						col = Math.floor(place.width() / boxSize.width);
					}
					row = Math.ceil((buffer.length + (cnt || 0)) / col);
					if (list && tableHeader) {
						++row;
					}
					bottomMarker.css({top: (bufferExt.row * row) + 'px'}).show();
				}
			},
			
			wrapperContextMenu = {
				contextmenu : function(e) {
					e.preventDefault();
					if (cwd.data('longtap') !== void(0)) {
						e.stopPropagation();
						return;
					}
					fm.trigger('contextmenu', {
						'type'    : 'cwd',
						'targets' : [fm.cwd().hash],
						'x'       : e.pageX,
						'y'       : e.pageY
					});
				},
				touchstart : function(e) {
					if (e.originalEvent.touches.length > 1) {
						return;
					}
					if (cwd.data('longtap') !== false) {
						wrapper.data('touching', {x: e.originalEvent.touches[0].pageX, y: e.originalEvent.touches[0].pageY});
						cwd.data('tmlongtap', setTimeout(function(){
							// long tap
							cwd.data('longtap', true);
							fm.trigger('contextmenu', {
								'type'    : 'cwd',
								'targets' : [fm.cwd().hash],
								'x'       : wrapper.data('touching').x,
								'y'       : wrapper.data('touching').y
							});
						}, 500));
					}
					cwd.data('longtap', null);
				},
				touchend : function(e) {
					if (e.type === 'touchmove') {
						if (! wrapper.data('touching') ||
								( Math.abs(wrapper.data('touching').x - e.originalEvent.touches[0].pageX)
								+ Math.abs(wrapper.data('touching').y - e.originalEvent.touches[0].pageY)) > 4) {
							wrapper.data('touching', null);
						}
					} else {
						setTimeout(function() {
							cwd.removeData('longtap');
						}, 80);
					}
					clearTimeout(cwd.data('tmlongtap'));
				},
				click : function(e) {
					if (cwd.data('longtap')) {
						e.preventDefault();
						e.stopPropagation();
					}
				}
			},
			
			/**
			 * Update directory content
			 *
			 * @return void
			 */
			content = function() {
				fm.lazy(function() {
					var phash, emptyMethod, thtr;

					wz.append(selectAllCheckbox).removeClass('elfinder-cwd-wrapper-empty elfinder-search-result elfinder-incsearch-result elfinder-letsearch-result');
					if (fm.searchStatus.state > 1 || fm.searchStatus.ininc) {
						wz.addClass('elfinder-search-result' + (fm.searchStatus.ininc? ' elfinder-'+(query.substr(0,1) === '/' ? 'let':'inc')+'search-result' : ''));
					}
					
					// abort attachThumbJob
					bufferExt.attachThumbJob && bufferExt.attachThumbJob._abort();
					
					// destroy selectable for GC
					cwd.data('selectable') && cwd.selectable('disable').selectable('destroy').removeData('selectable');
					
					// notify cwd init
					fm.trigger('cwdinit');
					
					selectedNext = jQuery();
					try {
						// to avoid problem with draggable
						cwd.empty();
					} catch (e) {
						cwd.html('');
					}
					
					if (tableHeader) {
						wrapper.off('scroll.fixheader resize.fixheader');
						tableHeader.remove();
						tableHeader = null;
					}

					cwd.removeClass('elfinder-cwd-view-icons elfinder-cwd-view-list')
						.addClass('elfinder-cwd-view-'+(list ? 'list' :'icons'))
						.attr('style', '')
						.css('height', 'auto');
					bottomMarker.hide();

					wrapper[list ? 'addClass' : 'removeClass']('elfinder-cwd-wrapper-list')._padding = parseInt(wrapper.css('padding-top')) + parseInt(wrapper.css('padding-bottom'));
					if (fm.UA.iOS) {
						wrapper.removeClass('overflow-scrolling-touch').addClass('overflow-scrolling-touch');
					}

					if (list) {
						cwd.html('<table><thead/><tbody/></table>');
						thtr = jQuery('<tr class="ui-state-default"><td class="elfinder-cwd-view-th-name">'+fm.getColumnName('name')+'</td>'+customColsNameBuild()+'</tr>');
						cwd.find('thead').hide().append(thtr).find('td:first').append(selectAllCheckbox);
						if (jQuery.fn.sortable) {
							thtr.addClass('touch-punch touch-punch-keep-default')
								.sortable({
								axis: 'x',
								distance: 8,
								items: '> .sortable-item',
								start: function(e, ui) {
									jQuery(ui.item[0]).data('dragging', true);
									ui.placeholder
										.width(ui.helper.removeClass('ui-state-hover').width())
										.removeClass('ui-state-active')
										.addClass('ui-state-hover')
										.css('visibility', 'visible');
								},
								update: function(e, ui){
									var target = jQuery(ui.item[0]).attr('class').split(' ')[0].replace('elfinder-cwd-view-th-', ''),
										prev, done;
									customCols = jQuery.map(jQuery(this).children(), function(n) {
										var name = jQuery(n).attr('class').split(' ')[0].replace('elfinder-cwd-view-th-', '');
										if (! done) {
											if (target === name) {
												done = true;
											} else {
												prev = name;
											}
										}
										return (name === 'name')? null : name;
									});
									templates.row = makeTemplateRow();
									fm.storage('cwdCols', customCols);
									prev = '.elfinder-col-'+prev+':first';
									target = '.elfinder-col-'+target+':first';
									fm.lazy(function() {
										cwd.find('tbody tr').each(function() {
											var $this = jQuery(this);
											$this.children(prev).after($this.children(target));
										});
									});
								},
								stop: function(e, ui) {
									setTimeout(function() {
										jQuery(ui.item[0]).removeData('dragging');
									}, 100);
								}
							});
						}

						thtr.find('td').addClass('touch-punch').resizable({
							handles: fm.direction === 'ltr'? 'e' : 'w',
							start: function(e, ui) {
								var target = cwd.find('td.elfinder-col-'
									+ ui.element.attr('class').split(' ')[0].replace('elfinder-cwd-view-th-', '')
									+ ':first');
								
								ui.element
									.data('dragging', true)
									.data('resizeTarget', target)
									.data('targetWidth', target.width());
								colResizing = true;
								if (cwd.find('table').css('table-layout') !== 'fixed') {
									cwd.find('tbody tr:first td').each(function() {
										jQuery(this).width(jQuery(this).width());
									});
									cwd.find('table').css('table-layout', 'fixed');
								}
							},
							resize: function(e, ui) {
								ui.element.data('resizeTarget').width(ui.element.data('targetWidth') - (ui.originalSize.width - ui.size.width));
							},
							stop : function(e, ui) {
								colResizing = false;
								fixTableHeader({fitWidth: true});
								colWidth = {};
								cwd.find('tbody tr:first td').each(function() {
									var name = jQuery(this).attr('class').split(' ')[0].replace('elfinder-col-', '');
									colWidth[name] = jQuery(this).width();
								});
								fm.storage('cwdColWidth', colWidth);
								setTimeout(function() {
									ui.element.removeData('dragging');
								}, 100);
							}
						})
						.find('.ui-resizable-handle').addClass('ui-icon ui-icon-grip-dotted-vertical');
					}

					buffer = jQuery.map(incHashes || cwdHashes, function(hash) { return fm.file(hash) || null; });
					
					buffer = fm.sortFiles(buffer);
					
					if (incHashes) {
						incHashes = jQuery.map(buffer, function(f) { return f.hash; });
					} else {
						cwdHashes = jQuery.map(buffer, function(f) { return f.hash; });
					}
					
					bufferExt = {
						renderd: 0,
						attachTmbs: {},
						getTmbs: [],
						tmbLoading: {},
						lazyOpts: { tm : 0 }
					};
					
					wz[(buffer.length < 1) ? 'addClass' : 'removeClass']('elfinder-cwd-wrapper-empty');
					wrapper.off(scrollEvent, render).on(scrollEvent, render).trigger(scrollEvent);
					
					// set droppable
					if (!fm.cwd().write) {
						wrapper.removeClass('native-droppable')
						       .droppable('disable')
						       .removeClass('ui-state-disabled'); // for old jQueryUI see https://bugs.jqueryui.com/ticket/5974
					} else {
						wrapper[fm.isCommandEnabled('upload')? 'addClass' : 'removeClass']('native-droppable');
						wrapper.droppable(fm.isCommandEnabled('paste')? 'enable' : 'disable');
					}
				});
			},
			
			/**
			 * CWD node itself
			 *
			 * @type JQuery
			 **/
			cwd = jQuery(this)
				.addClass('ui-helper-clearfix elfinder-cwd')
				.attr('unselectable', 'on')
				// fix ui.selectable bugs and add shift+click support 
				.on('click.'+fm.namespace, fileSelector, function(e) {
					var p    = this.id ? jQuery(this) : jQuery(this).parents('[id]:first'),
						tgt  = jQuery(e.target),
						prev,
						next,
						pl,
						nl,
						sib;

					if (selectCheckbox && (tgt.is('input:checkbox.'+clSelChk) || tgt.hasClass('elfinder-cwd-select'))) {
						e.stopPropagation();
						e.preventDefault();
						p.trigger(p.hasClass(clSelected) ? evtUnselect : evtSelect);
						trigger();
						requestAnimationFrame(function() {
							tgt.prop('checked', p.hasClass(clSelected));
						});
						return;
					}

					if (cwd.data('longtap') || tgt.hasClass('elfinder-cwd-nonselect')) {
						e.stopPropagation();
						return;
					}

					if (!curClickId) {
						curClickId = p.attr('id');
						setTimeout(function() {
							curClickId = '';
						}, 500);
					}
					
					if (e.shiftKey) {
						prev = p.prevAll(lastSelect || '.'+clSelected+':first');
						next = p.nextAll(lastSelect || '.'+clSelected+':first');
						pl   = prev.length;
						nl   = next.length;
					}
					if (e.shiftKey && (pl || nl)) {
						sib = pl ? p.prevUntil('#'+prev.attr('id')) : p.nextUntil('#'+next.attr('id'));
						sib.add(p).trigger(evtSelect);
					} else if (e.ctrlKey || e.metaKey) {
						p.trigger(p.hasClass(clSelected) ? evtUnselect : evtSelect);
					} else {
						if (wrapper.data('touching') && p.hasClass(clSelected)) {
							wrapper.data('touching', null);
							fm.dblclick({file : fm.cwdId2Hash(this.id)});
							return;
						} else {
							unselectAll({ notrigger: true });
							p.trigger(evtSelect);
						}
					}

					trigger();
				})
				// call fm.open()
				.on('dblclick.'+fm.namespace, fileSelector, function(e) {
					if (curClickId) {
						var hash = fm.cwdId2Hash(curClickId);
						e.stopPropagation();
						if (this.id !== curClickId) {
							jQuery(this).trigger(evtUnselect);
							jQuery('#'+curClickId).trigger(evtSelect);
							trigger();
						}
						fm.dblclick({file : hash});
					}
				})
				// for touch device
				.on('touchstart.'+fm.namespace, fileSelector, function(e) {
					if (e.originalEvent.touches.length > 1) {
						return;
					}
					var p   = this.id ? jQuery(this) : jQuery(this).parents('[id]:first'),
						tgt = jQuery(e.target),
						nodeName = e.target.nodeName,
						sel;
					
					if ((nodeName === 'INPUT' && e.target.type === 'text') || nodeName === 'TEXTAREA' || tgt.hasClass('elfinder-cwd-nonselect')) {
						e.stopPropagation();
						return;
					}
					
					// now name editing
					if (p.find('input:text,textarea').length) {
						e.stopPropagation();
						e.preventDefault();
						return;
					}
					
					wrapper.data('touching', {x: e.originalEvent.touches[0].pageX, y: e.originalEvent.touches[0].pageY});
					if (selectCheckbox && (tgt.is('input:checkbox.'+clSelChk) || tgt.hasClass('elfinder-cwd-select'))) {
						return;
					}
					
					sel = p.prevAll('.'+clSelected+':first').length +
					      p.nextAll('.'+clSelected+':first').length;
					cwd.data('longtap', null);
					if (Object.keys(selectedFiles).length
						||
						(list && e.target.nodeName !== 'TD')
						||
						(!list && this !== e.target)
					) {
						cwd.data('longtap', false);
						p.addClass(clHover);
						p.data('tmlongtap', setTimeout(function(){
							// long tap
							cwd.data('longtap', true);
							p.trigger(evtSelect);
							trigger();
							fm.trigger('contextmenu', {
								'type'    : 'files',
								'targets' : fm.selected(),
								'x'       : e.originalEvent.touches[0].pageX,
								'y'       : e.originalEvent.touches[0].pageY
							});
						}, 500));
					}
				})
				.on('touchmove.'+fm.namespace+' touchend.'+fm.namespace, fileSelector, function(e) {
					var tgt = jQuery(e.target),
						p;
					if (selectCheckbox && (tgt.is('input:checkbox.'+clSelChk) || tgt.hasClass('elfinder-cwd-select'))) {
						return;
					}
					if (e.target.nodeName == 'INPUT' || e.target.nodeName == 'TEXTAREA') {
						e.stopPropagation();
						return;
					}
					p = this.id ? jQuery(this) : jQuery(this).parents('[id]:first');
					clearTimeout(p.data('tmlongtap'));
					if (e.type === 'touchmove') {
						wrapper.data('touching', null);
						p.removeClass(clHover);
					} else {
						if (wrapper.data('touching') && !cwd.data('longtap') && p.hasClass(clSelected)) {
							e.preventDefault();
							wrapper.data('touching', null);
							fm.dblclick({file : fm.cwdId2Hash(this.id)});
						}
						setTimeout(function() {
							cwd.removeData('longtap');
						}, 80);
					}
				})
				// attach draggable
				.on('mouseenter.'+fm.namespace, fileSelector, function(e) {
					if (scrolling) { return; }
					var $this = jQuery(this), helper = null;

					if (!mobile && !$this.data('dragRegisted') && !$this.hasClass(clTmp) && !$this.hasClass(clDraggable) && !$this.hasClass(clDisabled)) {
						$this.data('dragRegisted', true);
						if (!fm.isCommandEnabled('copy', fm.searchStatus.state > 1 || $this.hasClass('isroot')? fm.cwdId2Hash($this.attr('id')) : void 0)) {
							return;
						}
						$this.on('mousedown', function(e) {
							// shiftKey or altKey + drag start for HTML5 native drag function
							// Note: can no use shiftKey with the Google Chrome 
							var metaKey = e.shiftKey || e.altKey,
								disable = false;
							if (metaKey && !fm.UA.IE && cwd.data('selectable')) {
								// destroy jQuery-ui selectable while trigger native drag
								cwd.selectable('disable').selectable('destroy').removeData('selectable');
								requestAnimationFrame(function(){
									cwd.selectable(selectableOption).selectable('option', {disabled: false}).selectable('refresh').data('selectable', true);
								});
							}
							$this.removeClass('ui-state-disabled');
							if (metaKey) {
								$this.draggable('option', 'disabled', true).attr('draggable', 'true');
							} else {
								if (!$this.hasClass(clSelected)) {
									if (list) {
										disable = jQuery(e.target).closest('span,tr').is('tr');
									} else {
										disable = jQuery(e.target).hasClass('elfinder-cwd-file');
									}
								}
								if (disable) {
									$this.draggable('option', 'disabled', true);
								} else {
									$this.draggable('option', 'disabled', false)
										  .removeAttr('draggable')
									      .draggable('option', 'cursorAt', {left: 50 - parseInt(jQuery(e.currentTarget).css('margin-left')), top: 47});
								}
							}
						})
						.on('dragstart', function(e) {
							var dt = e.dataTransfer || e.originalEvent.dataTransfer || null;
							helper = null;
							if (dt && !fm.UA.IE) {
								var p = this.id ? jQuery(this) : jQuery(this).parents('[id]:first'),
									elm   = jQuery('<span>'),
									url   = '',
									durl  = null,
									murl  = null,
									files = [],
									icon  = function(f) {
										var mime = f.mime, i, tmb = fm.tmb(f);
										i = '<div class="elfinder-cwd-icon elfinder-cwd-icon-drag '+fm.mime2class(mime)+' ui-corner-all"/>';
										if (tmb) {
											i = jQuery(i).addClass(tmb.className).css('background-image', "url('"+tmb.url+"')").get(0).outerHTML;
										}
										return i;
									}, l, geturl = [];
								p.trigger(evtSelect);
								trigger();
								jQuery.each(selectedFiles, function(v){
									var file = fm.file(v),
										furl = file.url;
									if (file && file.mime !== 'directory') {
										if (!furl) {
											furl = fm.url(file.hash);
										} else if (furl == '1') {
											geturl.push(v);
											return true;
										}
										if (furl) {
											furl = fm.convAbsUrl(furl);
											files.push(v);
											jQuery('<a>').attr('href', furl).text(furl).appendTo(elm);
											url += furl + "\n";
											if (!durl) {
												durl = file.mime + ':' + file.name + ':' + furl;
											}
											if (!murl) {
												murl = furl + "\n" + file.name;
											}
										}
									}
								});
								if (geturl.length) {
									jQuery.each(geturl, function(i, v){
										var rfile = fm.file(v);
										rfile.url = '';
										fm.request({
											data : {cmd : 'url', target : v},
											notify : {type : 'url', cnt : 1},
											preventDefault : true
										})
										.always(function(data) {
											rfile.url = data.url? data.url : '1';
										});
									});
									return false;
								} else if (url) {
									if (dt.setDragImage) {
										helper = jQuery('<div class="elfinder-drag-helper html5-native"></div>').append(icon(fm.file(files[0]))).appendTo(jQuery(document.body));
										if ((l = files.length) > 1) {
											helper.append(icon(fm.file(files[l-1])) + '<span class="elfinder-drag-num">'+l+'</span>');
										}
										dt.setDragImage(helper.get(0), 50, 47);
									}
									dt.effectAllowed = 'copyLink';
									dt.setData('DownloadURL', durl);
									dt.setData('text/x-moz-url', murl);
									dt.setData('text/uri-list', url);
									dt.setData('text/plain', url);
									dt.setData('text/html', elm.html());
									dt.setData('elfinderfrom', window.location.href + fm.cwd().hash);
									dt.setData('elfinderfrom:' + dt.getData('elfinderfrom'), '');
								} else {
									return false;
								}
							}
						})
						.on('dragend', function(e){
							unselectAll({ notrigger: true });
							helper && helper.remove();
						})
						.draggable(fm.draggable);
					}
				})
				// add hover class to selected file
				.on(evtSelect, fileSelector, function(e) {
					var $this = jQuery(this),
						id    = fm.cwdId2Hash($this.attr('id'));
					
					if (!selectLock && !$this.hasClass(clDisabled)) {
						lastSelect = '#'+ this.id;
						$this.addClass(clSelected).children().addClass(clHover).find('input:checkbox.'+clSelChk).prop('checked', true);
						if (! selectedFiles[id]) {
							selectedFiles[id] = true;
						}
						// will be selected next
						selectedNext = cwd.find('[id].'+clSelected+':last').next();
					}
				})
				// remove hover class from unselected file
				.on(evtUnselect, fileSelector, function(e) {
					var $this = jQuery(this), 
						id    = fm.cwdId2Hash($this.attr('id'));
					
					if (!selectLock) {
						$this.removeClass(clSelected).children().removeClass(clHover).find('input:checkbox.'+clSelChk).prop('checked', false);
						if (cwd.hasClass('elfinder-cwd-allselected')) {
							selectCheckbox && selectAllCheckbox.children('input').prop('checked', false);
							cwd.removeClass('elfinder-cwd-allselected');
						}
						selectedFiles[id] && delete selectedFiles[id];
					}
					
				})
				// disable files wich removing or moving
				.on(evtDisable, fileSelector, function() {
					var $this  = jQuery(this).removeClass(clHover+' '+clSelected).addClass(clDisabled), 
						child  = $this.children(),
						target = (list ? $this : child.find('div.elfinder-cwd-file-wrapper,div.elfinder-cwd-filename'));
					
					child.removeClass(clHover+' '+clSelected);
					
					$this.hasClass(clDroppable) && $this.droppable('disable');
					target.hasClass(clDraggable) && target.draggable('disable');
				})
				// if any files was not removed/moved - unlock its
				.on(evtEnable, fileSelector, function() {
					var $this  = jQuery(this).removeClass(clDisabled), 
						target = list ? $this : $this.children('div.elfinder-cwd-file-wrapper,div.elfinder-cwd-filename');
					
					$this.hasClass(clDroppable) && $this.droppable('enable');	
					target.hasClass(clDraggable) && target.draggable('enable');
				})
				.on('scrolltoview', fileSelector, function(e, data) {
					scrollToView(jQuery(this), (data && typeof data.blink !== 'undefined')? data.blink : true);
				})
				.on('mouseenter.'+fm.namespace+' mouseleave.'+fm.namespace, fileSelector, function(e) {
					var enter = (e.type === 'mouseenter');
					if (enter && (scrolling || fm.UA.Mobile)) { return; }
					fm.trigger('hover', {hash : fm.cwdId2Hash(jQuery(this).attr('id')), type : e.type});
					jQuery(this).toggleClass(clHover, (e.type == 'mouseenter'));
				})
				// for file contextmenu
				.on('mouseenter.'+fm.namespace+' mouseleave.'+fm.namespace, '.elfinder-cwd-file-wrapper,.elfinder-cwd-filename', function(e) {
					var enter = (e.type === 'mouseenter');
					if (enter && scrolling) { return; }
					jQuery(this).closest(fileSelector).children('.elfinder-cwd-file-wrapper,.elfinder-cwd-filename').toggleClass(clActive, (e.type == 'mouseenter'));
				})
				.on('contextmenu.'+fm.namespace, function(e) {
					var file = jQuery(e.target).closest(fileSelector);
					
					if (file.get(0) === e.target && !selectedFiles[fm.cwdId2Hash(file.get(0).id)]) {
						return;
					}

					// now filename editing
					if (file.find('input:text,textarea').length) {
						e.stopPropagation();
						return;
					}
					
					if (file.length && (e.target.nodeName != 'TD' || selectedFiles[fm.cwdId2Hash(file.get(0).id)])) {
						e.stopPropagation();
						e.preventDefault();
						if (!file.hasClass(clDisabled) && !wrapper.data('touching')) {
							if (!file.hasClass(clSelected)) {
								unselectAll({ notrigger: true });
								file.trigger(evtSelect);
								trigger();
							}
							fm.trigger('contextmenu', {
								'type'    : 'files',
								'targets' : fm.selected(),
								'x'       : e.pageX,
								'y'       : e.pageY
							});

						}
						
					}
				})
				// unselect all on cwd click
				.on('click.'+fm.namespace, function(e) {
					if (e.target === this && ! cwd.data('longtap')) {
						!e.shiftKey && !e.ctrlKey && !e.metaKey && unselectAll();
					}
				})
				// prepend fake file/dir
				.on('create.'+fm.namespace, function(e, f) {
					var parent = list ? cwd.find('tbody') : cwd,
						p = parent.find('.elfinder-cwd-parent'),
						lock = f.move || false,
						file = jQuery(itemhtml(f)).addClass(clTmp),
						selected = fm.selected();
						
					if (selected.length) {
						lock && fm.trigger('lockfiles', {files: selected});
					} else {
						unselectAll();
					}

					if (p.length) {
						p.after(file);
					} else {
						parent.prepend(file);
					}
					
					setColwidth();
					wrapper.scrollTop(0).scrollLeft(0);
				})
				// unselect all selected files
				.on('unselectall', unselectAll)
				.on('selectfile', function(e, id) {
					fm.cwdHash2Elm(id).trigger(evtSelect);
					trigger();
				})
				.on('colwidth', function() {
					if (list) {
						cwd.find('table').css('table-layout', '')
							.find('td').css('width', '');
						fixTableHeader({fitWidth: true});
						fm.storage('cwdColWidth', colWidth = null);
					}
				})
				.on('iconpref', function(e, data) {
					cwd.removeClass(function(i, cName) {
						return (cName.match(/\belfinder-cwd-size\S+/g) || []).join(' ');
					});
					iconSize = data? (parseInt(data.size) || 0) : 0;
					if (!list) {
						if (iconSize > 0) {
							cwd.addClass('elfinder-cwd-size' + iconSize);
						}
						if (bufferExt.renderd) {
							requestAnimationFrame(function() {
								itemBoxSize.icons = {};
								bufferExt.hpi = null;
								bottomMarkerShow(cwd, bufferExt.renderd);
								wrapperRepaint();
							});
						}
					}
				})
				// Change icon size with mouse wheel event
				.on('onwheel' in document ? 'wheel' : 'mousewheel', function(e) {
					var tm, size, delta;
					if (!list && ((e.ctrlKey && !e.metaKey) || (!e.ctrlKey && e.metaKey))) {
						e.stopPropagation();
						e.preventDefault();
						tm = cwd.data('wheelTm');
						if (typeof tm !== 'undefined') {
							clearTimeout(tm);
							cwd.data('wheelTm', setTimeout(function() {
								cwd.removeData('wheelTm');
							}, 200));
						} else {
							cwd.data('wheelTm', false);
							size = iconSize || 0;
							delta = e.originalEvent.deltaY ? e.originalEvent.deltaY : -(e.originalEvent.wheelDelta);
							if (delta > 0) {
								if (iconSize > 0) {
									size = iconSize - 1;
								}
							} else {
								if (iconSize < options.iconsView.sizeMax) {
									size = iconSize + 1;
								}
							}
							if (size !== iconSize) {
								fm.storage('iconsize', size);
								cwd.trigger('iconpref', {size: size});
							}
						}
					}
				}),
			wrapper = jQuery('<div class="elfinder-cwd-wrapper"/>')
				// make cwd itself droppable for folders from nav panel
				.droppable(Object.assign({}, droppable, {autoDisable: false}))
				.on('contextmenu.'+fm.namespace, wrapperContextMenu.contextmenu)
				.on('touchstart.'+fm.namespace, wrapperContextMenu.touchstart)
				.on('touchmove.'+fm.namespace+' touchend.'+fm.namespace, wrapperContextMenu.touchend)
				.on('click.'+fm.namespace, wrapperContextMenu.click)
				.on('scroll.'+fm.namespace, function() {
					if (! scrolling) {
						cwd.data('selectable') && cwd.selectable('disable');
						wrapper.trigger(scrollStartEvent);
					}
					scrolling = true;
					bufferExt.scrtm && cancelAnimationFrame(bufferExt.scrtm);
					if (bufferExt.scrtm && Math.abs((bufferExt.scrolltop || 0) - (bufferExt.scrolltop = (this.scrollTop || jQuery(this).scrollTop()))) < 5) {
						bufferExt.scrtm = 0;
						wrapper.trigger(scrollEvent);
					}
					bufferExt.scrtm = requestAnimationFrame(function() {
						bufferExt.scrtm = 0;
						wrapper.trigger(scrollEvent);
					});
				})
				.on(scrollEvent, function() {
					scrolling = false;
					wrapperRepaint();
				}),
			
			bottomMarker = jQuery('<div>&nbsp;</div>')
				.css({position: 'absolute', width: '1px', height: '1px'})
				.hide(),
			
			selectAllCheckbox = selectCheckbox? jQuery('<div class="elfinder-cwd-selectall"><input type="checkbox"/></div>')
				.attr('title', fm.i18n('selectall'))
				.on('touchstart mousedown click', function(e) {
					e.stopPropagation();
					e.preventDefault();
					if (jQuery(this).data('pending') || e.type === 'click') {
						return false;
					}
					selectAllCheckbox.data('pending', true);
					if (cwd.hasClass('elfinder-cwd-allselected')) {
						selectAllCheckbox.find('input').prop('checked', false);
						requestAnimationFrame(function() {
							unselectAll();
						});
					} else {
						selectAll();
					}
				}) : jQuery(),
			
			restm = null,
			resize = function(init) {
				var initHeight = function() {
					if (typeof bufferExt.renderd !== 'undefined') {
						var h = 0;
						wrapper.siblings('div.elfinder-panel:visible').each(function() {
							h += jQuery(this).outerHeight(true);
						});
						wrapper.height(wz.height() - h - wrapper._padding);
					}
				};
				
				init && initHeight();
				
				restm && cancelAnimationFrame(restm);
				restm = requestAnimationFrame(function(){
					!init && initHeight();
					var wph, cwdoh;
					// fix cwd height if it less then wrapper
					cwd.css('height', 'auto');
					wph = wrapper[0].clientHeight - parseInt(wrapper.css('padding-top')) - parseInt(wrapper.css('padding-bottom')) - parseInt(cwd.css('margin-top')),
					cwdoh = cwd.outerHeight(true);
					if (cwdoh < wph) {
						cwd.height(wph);
					}
				});
				
				list && ! colResizing && (init? wrapper.trigger('resize.fixheader') : fixTableHeader());
				
				wrapperRepaint();
			},
			
			// elfinder node
			parent = jQuery(this).parent().on('resize', resize),
			
			// workzone node 
			wz = parent.children('.elfinder-workzone').append(wrapper.append(this).append(bottomMarker)),
			
			// message board
			mBoard = jQuery('<div class="elfinder-cwd-message-board"/>').insertAfter(cwd),

			// Volume expires
			vExpires = jQuery('<div class="elfinder-cwd-expires" />'),

			vExpiresTm,

			showVolumeExpires = function() {
				var remain, sec, int;
				vExpiresTm && clearTimeout(vExpiresTm);
				if (curVolId && fm.volumeExpires[curVolId]) {
					sec = fm.volumeExpires[curVolId] - ((+new Date()) / 1000);
					int = (sec % 60) + 0.1;
					remain = Math.floor(sec / 60);
					vExpires.html(fm.i18n(['minsLeft', remain])).show();
					if (remain) {
						vExpiresTm = setTimeout(showVolumeExpires, int * 1000);
					}
				}
			},

			// each item box size
			itemBoxSize = {
				icons : {},
				list : {}
			},

			// has UI tree
			hasUiTree,

			// Icon size of icons view
			iconSize,

			// Current volume id
			curVolId,
			
			winScrTm;

		// IE < 11 not support CSS `pointer-events: none`
		if (!fm.UA.ltIE10) {
			mBoard.append(jQuery('<div class="elfinder-cwd-trash" />').html(fm.i18n('volume_Trash')))
			      .append(vExpires);
		}

		// setup by options
		replacement = Object.assign(replacement, options.replacement || {});
		
		try {
			colWidth = fm.storage('cwdColWidth')? fm.storage('cwdColWidth') : null;
		} catch(e) {
			colWidth = null;
		}
		
		// setup costomCols
		fm.bind('columnpref', function(e) {
			var opts = e.data || {};
			if (customCols = fm.storage('cwdCols')) {
				customCols = jQuery.grep(customCols, function(n) {
					return (options.listView.columns.indexOf(n) !== -1)? true : false;
				});
				if (options.listView.columns.length > customCols.length) {
					jQuery.each(options.listView.columns, function(i, n) {
						if (customCols.indexOf(n) === -1) {
							customCols.push(n);
						}
					});
				}
			} else {
				customCols = options.listView.columns;
			}
			// column names array that hidden
			var columnhides = fm.storage('columnhides') || null;
			if (columnhides && Object.keys(columnhides).length)
			customCols = jQuery.grep(customCols, function(n) {
				return columnhides[n]? false : true;
			});
			// make template with customCols
			templates.row = makeTemplateRow();
			// repaint if need it
			list && opts.repaint && content();
		}).trigger('columnpref');

		if (mobile) {
			// for iOS5 bug
			jQuery('body').on('touchstart touchmove touchend', function(e){});
		}
		
		selectCheckbox && cwd.addClass('elfinder-has-checkbox');
		
		jQuery(window).on('scroll.'+fm.namespace, function() {
			winScrTm && cancelAnimationFrame(winScrTm);
			winScrTm = requestAnimationFrame(function() {
				wrapper.trigger(scrollEvent);
			});
		});
		
		jQuery(document).on('keydown.'+fm.namespace, function(e) {
			if (e.keyCode == jQuery.ui.keyCode.ESCAPE) {
				if (! fm.getUI().find('.ui-widget:visible').length) {
					unselectAll();
				}
			}
		});
		
		fm
			.one('init', function(){
				var style = document.createElement('style'),
				sheet, node, base, resizeTm, iconSize, i = 0;
				if (document.head) {
					document.head.appendChild(style);
					sheet = style.sheet;
					sheet.insertRule('.elfinder-cwd-wrapper-empty .elfinder-cwd:not(.elfinder-table-header-sticky):after{ content:"'+fm.i18n('emptyFolder')+'" }', i++);
					sheet.insertRule('.elfinder-cwd-wrapper-empty .native-droppable .elfinder-cwd:not(.elfinder-table-header-sticky):after{ content:"'+fm.i18n('emptyFolder'+(mobile? 'LTap' : 'Drop'))+'" }', i++);
					sheet.insertRule('.elfinder-cwd-wrapper-empty .ui-droppable-disabled .elfinder-cwd:not(.elfinder-table-header-sticky):after{ content:"'+fm.i18n('emptyFolder')+'" }', i++);
					sheet.insertRule('.elfinder-cwd-wrapper-empty.elfinder-search-result .elfinder-cwd:not(.elfinder-table-header-sticky):after{ content:"'+fm.i18n('emptySearch')+'" }', i++);
					sheet.insertRule('.elfinder-cwd-wrapper-empty.elfinder-search-result.elfinder-incsearch-result .elfinder-cwd:not(.elfinder-table-header-sticky):after{ content:"'+fm.i18n('emptyIncSearch')+'" }', i++);
					sheet.insertRule('.elfinder-cwd-wrapper-empty.elfinder-search-result.elfinder-letsearch-result .elfinder-cwd:not(.elfinder-table-header-sticky):after{ content:"'+fm.i18n('emptyLetSearch')+'" }', i++);
				}
				if (iconSize = fm.storage('iconsize') || 0) {
					cwd.trigger('iconpref', {size: iconSize});
				}
				if (! mobile) {
					fm.one('open', function() {
						sheet && fm.zIndex && sheet.insertRule('.ui-selectable-helper{z-index:'+fm.zIndex+';}', i++);
					});
					base = jQuery('<div style="position:absolute"/>');
					node = fm.getUI();
					node.on('resize', function(e, data) {
						var offset;
						e.preventDefault();
						e.stopPropagation();
						if (data && data.fullscreen) {
							offset = node.offset();
							if (data.fullscreen === 'on') {
								base.css({top:offset.top * -1 , left:offset.left * -1 }).appendTo(node);
								selectableOption.appendTo = base;
							} else {
								base.detach();
								selectableOption.appendTo = 'body';
							}
							cwd.data('selectable') && cwd.selectable('option', {appendTo : selectableOption.appendTo});
						}
					});
				}
				hasUiTree = fm.getUI('tree').length;
			})
			.bind('enable', function() {
				resize();
			})
			.bind('request.open', function() {
				bufferExt.getTmbs = [];
			})
			.one('open', function() {
				if (fm.maxTargets) {
					tmbNum = Math.min(fm.maxTargets, tmbNum);
				}
			})
			.bind('open add remove searchend', function() {
				var phash = fm.cwd().hash,
					type = this.type;
				if (type === 'open' || type === 'searchend' || fm.searchStatus.state < 2) {
					cwdHashes = jQuery.map(fm.files(phash), function(f) { return f.hash; });
					fm.trigger('cwdhasheschange', cwdHashes);
				}
				if (type === 'open') {
					var inTrash = function() {
							var isIn = false;
							jQuery.each(cwdParents, function(i, h) {
								if (fm.trashes[h]) {
									isIn = true;
									return false;
								}
							});
							return isIn;
						},
						req = phash?
							(! fm.file(phash) || hasUiTree?
								(! hasUiTree?
									fm.request({
										data: {
											cmd    : 'parents',
											target : fm.cwd().hash
										},
										preventFail : true
									}) : (function() {
										var dfd = jQuery.Deferred();
										fm.one('treesync', function(e) {
											e.data.always(function() {
												dfd.resolve();
											});
										});
										return dfd;
									})()
								) : null
							) : null,
						cwdObj = fm.cwd();
					// add/remove volume id class
					if (cwdObj.volumeid !== curVolId) {
						vExpires.empty().hide();
						if (curVolId) {
							wrapper.removeClass('elfinder-cwd-wrapper-' + curVolId);
						}
						curVolId = cwdObj.volumeid;
						showVolumeExpires();
						wrapper.addClass('elfinder-cwd-wrapper-' + curVolId);
					}
					// add/remove trash class
					jQuery.when(req).done(function() {
						cwdParents = fm.parents(cwdObj.hash);
						wrapper[inTrash()? 'addClass':'removeClass']('elfinder-cwd-wrapper-trash');
					});
					incHashes = void 0;
					unselectAll({ notrigger: true });
					content();
				}
			})
			.bind('search', function(e) {
				cwdHashes = jQuery.map(e.data.files, function(f) { return f.hash; });
				fm.trigger('cwdhasheschange', cwdHashes);
				incHashes = void 0;
				fm.searchStatus.ininc = false;
				content();
				fm.autoSync('stop');
			})
			.bind('searchend', function(e) {
				if (query || incHashes) {
					query = '';
					if (incHashes) {
						fm.trigger('incsearchend', e.data);
					} else {
						if (!e.data || !e.data.noupdate) {
							content();
						}
					}
				}
				fm.autoSync();
			})
			.bind('searchstart', function(e) {
				unselectAll();
				query = e.data.query;
			})
			.bind('incsearchstart', function(e) {
				selectedFiles = {};
				fm.lazy(function() {
					// incremental search
					var regex, q, fst = '';
					q = query = e.data.query || '';
					if (q) {
						if (q.substr(0,1) === '/') {
							q = q.substr(1);
							fst = '^';
						}
						regex = new RegExp(fst + q.replace(/([\\*\;\.\?\[\]\{\}\(\)\^\$\-\|])/g, '\\$1'), 'i');
						incHashes = jQuery.grep(cwdHashes, function(hash) {
							var file = fm.file(hash);
							return (file && (file.name.match(regex) || (file.i18 && file.i18.match(regex))))? true : false;
						});
						fm.trigger('incsearch', { hashes: incHashes, query: q })
							.searchStatus.ininc = true;
						content();
						fm.autoSync('stop');
					} else {
						fm.trigger('incsearchend');
					}
				});
			})
			.bind('incsearchend', function(e) {
				query = '';
				fm.searchStatus.ininc = false;
				incHashes = void 0;
				if (!e.data || !e.data.noupdate) {
					content();
				}
				fm.autoSync();
			})
			.bind('sortchange', function() {
				var lastScrollLeft = wrapper.scrollLeft(),
					allsel = cwd.hasClass('elfinder-cwd-allselected');
				
				content();
				fm.one('cwdrender', function() {
					wrapper.scrollLeft(lastScrollLeft);
					if (allsel) {
						selectedFiles = fm.arrayFlip(incHashes || cwdHashes, true);
					}
					(allsel || Object.keys(selectedFiles).length) && trigger();
				});
			})
			.bind('viewchange', function() {
				var l      = fm.storage('view') == 'list',
					allsel = cwd.hasClass('elfinder-cwd-allselected');
				
				if (l != list) {
					list = l;
					fm.viewType = list? 'list' : 'icons';
					if (iconSize) {
						fm.one('cwdinit', function() {
							cwd.trigger('iconpref', {size: iconSize});
						});
					}
					content();
					resize();

					if (allsel) {
						cwd.addClass('elfinder-cwd-allselected');
						selectAllCheckbox.find('input').prop('checked', true);
					}
					Object.keys(selectedFiles).length && trigger();
				}
			})
			.bind('wzresize', function() {
				var place = list ? cwd.find('tbody') : cwd,
					cwdOffset;
				resize(true);
				if (bufferExt.hpi) {
					bottomMarkerShow(place, place.find('[id]').length);
				}
				
				cwdOffset = cwd.offset();
				wz.data('rectangle', Object.assign(
					{
						width: wz.width(),
						height: wz.height(),
						cwdEdge: (fm.direction === 'ltr')? cwdOffset.left : cwdOffset.left + cwd.width()
					},
					wz.offset())
				);
				
				bufferExt.itemH = (list? place.find('tr:first') : place.find('[id]:first')).outerHeight(true);
			})
			.bind('changeclipboard', function(e) {
				clipCuts = {};
				if (e.data && e.data.clipboard && e.data.clipboard.length) {
					jQuery.each(e.data.clipboard, function(i, f) {
						if (f.cut) {
							clipCuts[f.hash] = true;
						}
					});
				}
			})
			.bind('resMixinMake', function() {
				setColwidth();
			})
			.bind('tmbreload', function(e) {
				var imgs = {},
					files = (e.data && e.data.files)? e.data.files : null;
				
				jQuery.each(files, function(i, f) {
					if (f.tmb && f.tmb != '1') {
						imgs[f.hash] = f.tmb;
					}
				});
				if (Object.keys(imgs).length) {
					attachThumbnails(imgs, true);
				}
			})
			.add(function(e) {
				var regex = query? new RegExp(query.replace(/([\\*\;\.\?\[\]\{\}\(\)\^\$\-\|])/g, '\\$1'), 'i') : null,
					mime  = fm.searchStatus.mime,
					inSearch = fm.searchStatus.state > 1,
					phash = inSearch && fm.searchStatus.target? fm.searchStatus.target : fm.cwd().hash,
					curPath = fm.path(phash),
					inTarget = function(f) {
						var res, parents, path;
						res = (f.phash === phash);
						if (!res && inSearch) {
							path = f.path || fm.path(f.hash);
							res = (curPath && path.indexOf(curPath) === 0);
							if (! res && fm.searchStatus.mixed) {
								res = jQuery.grep(fm.searchStatus.mixed, function(vid) { return f.hash.indexOf(vid) === 0? true : false; }).length? true : false;
							}
						}
						if (res && inSearch) {
							if (mime) {
								res = (f.mime.indexOf(mime) === 0);
							} else {
								res = (f.name.match(regex) || (f.i18 && f.i18.match(regex)))? true : false;
							}
						}
						return res;
					},
					files = jQuery.grep(e.data.added || [], function(f) { return inTarget(f)? true : false ;});
				add(files);
				if (fm.searchStatus.state === 2) {
					jQuery.each(files, function(i, f) {
						if (jQuery.inArray(f.hash, cwdHashes) === -1) {
							cwdHashes.push(f.hash);
						}
					});
					fm.trigger('cwdhasheschange', cwdHashes);
				}
				list && resize();
				wrapper.trigger(scrollEvent);
			})
			.change(function(e) {
				var phash = fm.cwd().hash,
					sel   = fm.selected(),
					files, added;

				if (query) {
					jQuery.each(e.data.changed || [], function(i, file) {
						if (fm.cwdHash2Elm(file.hash).length) {
							remove([file.hash]);
							add([file], 'change');
							jQuery.inArray(file.hash, sel) !== -1 && selectFile(file.hash);
							added = true;
						}
					});
				} else {
					jQuery.each(jQuery.grep(e.data.changed || [], function(f) { return f.phash == phash ? true : false; }), function(i, file) {
						if (fm.cwdHash2Elm(file.hash).length) {
							remove([file.hash]);
							add([file], 'change');
							jQuery.inArray(file.hash, sel) !== -1 && selectFile(file.hash);
							added = true;
						}
					});
				}
				
				if (added) {
					fm.trigger('cwdhasheschange', cwdHashes);
					list && resize();
					wrapper.trigger(scrollEvent);
				}
				
				trigger();
			})
			.remove(function(e) {
				var place = list ? cwd.find('tbody') : cwd;
				remove(e.data.removed || []);
				trigger();
				if (buffer.length < 1 && place.children(fileSelector).length < 1) {
					wz.addClass('elfinder-cwd-wrapper-empty');
					selectCheckbox && selectAllCheckbox.find('input').prop('checked', false);
					bottomMarker.hide();
					wrapper.off(scrollEvent, render);
					resize();
				} else {
					bottomMarkerShow(place);
					wrapper.trigger(scrollEvent);
				}
			})
			// select dragged file if no selected, disable selectable
			.dragstart(function(e) {
				var target = jQuery(e.data.target),
					oe     = e.data.originalEvent;

				if (target.hasClass(clFile)) {
					
					if (!target.hasClass(clSelected)) {
						!(oe.ctrlKey || oe.metaKey || oe.shiftKey) && unselectAll({ notrigger: true });
						target.trigger(evtSelect);
						trigger();
					}
				}
				
				cwd.removeClass(clDisabled).data('selectable') && cwd.selectable('disable');
				selectLock = true;
			})
			// enable selectable
			.dragstop(function() {
				cwd.data('selectable') && cwd.selectable('enable');
				selectLock = false;
			})
			.bind('lockfiles unlockfiles selectfiles unselectfiles', function(e) {
				var events = {
						lockfiles     : evtDisable ,
						unlockfiles   : evtEnable ,
						selectfiles   : evtSelect,
						unselectfiles : evtUnselect },
					event  = events[e.type],
					files  = e.data.files || [],
					l      = files.length,
					helper = e.data.helper || jQuery(),
					parents, ctr, add;

				if (l > 0) {
					parents = fm.parents(files[0]);
				}
				if (event === evtSelect || event === evtUnselect) {
					add  = (event === evtSelect),
					jQuery.each(files, function(i, hash) {
						var all = cwd.hasClass('elfinder-cwd-allselected');
						if (! selectedFiles[hash]) {
							add && (selectedFiles[hash] = true);
						} else {
							if (all) {
								selectCheckbox && selectAllCheckbox.children('input').prop('checked', false);
								cwd.removeClass('elfinder-cwd-allselected');
								all = false;
							}
							! add && delete selectedFiles[hash];
						}
					});
				}
				if (!helper.data('locked')) {
					while (l--) {
						try {
							fm.cwdHash2Elm(files[l]).trigger(event);
						} catch(e) {}
					}
					! e.data.inselect && trigger();
				}
				if (wrapper.data('dropover') && parents.indexOf(wrapper.data('dropover')) !== -1) {
					ctr = e.type !== 'lockfiles';
					helper.toggleClass('elfinder-drag-helper-plus', ctr);
					wrapper.toggleClass(clDropActive, ctr);
				}
			})
			// select new files after some actions
			.bind('mkdir mkfile duplicate upload rename archive extract paste multiupload', function(e) {
				if (e.type == 'upload' && e.data._multiupload) return;
				var phash = fm.cwd().hash, files;
				
				unselectAll({ notrigger: true });

				jQuery.each((e.data.added || []).concat(e.data.changed || []), function(i, file) { 
					file && file.phash == phash && selectFile(file.hash);
				});
				trigger();
			})
			.shortcut({
				pattern     :'ctrl+a', 
				description : 'selectall',
				callback    : selectAll
			})
			.shortcut({
				pattern     :'ctrl+shift+i', 
				description : 'selectinvert',
				callback    : selectInvert
			})
			.shortcut({
				pattern     : 'left right up down shift+left shift+right shift+up shift+down',
				description : 'selectfiles',
				type        : 'keydown' , //fm.UA.Firefox || fm.UA.Opera ? 'keypress' : 'keydown',
				callback    : function(e) { select(e.keyCode, e.shiftKey); }
			})
			.shortcut({
				pattern     : 'home',
				description : 'selectffile',
				callback    : function(e) { 
					unselectAll({ notrigger: true });
					scrollToView(cwd.find('[id]:first').trigger(evtSelect));
					trigger();
				}
			})
			.shortcut({
				pattern     : 'end',
				description : 'selectlfile',
				callback    : function(e) { 
					unselectAll({ notrigger: true });
					scrollToView(cwd.find('[id]:last').trigger(evtSelect)) ;
					trigger();
				}
			})
			.shortcut({
				pattern     : 'page_up',
				description : 'pageTurning',
				callback    : function(e) {
					if (bufferExt.itemH) {
						wrapper.scrollTop(
							Math.round(
								wrapper.scrollTop()
								- (Math.floor((wrapper.height() + (list? bufferExt.itemH * -1 : 16)) / bufferExt.itemH)) * bufferExt.itemH
							)
						);
					}
				}
			}).shortcut({
				pattern     : 'page_down',
				description : 'pageTurning',
				callback    : function(e) { 
					if (bufferExt.itemH) {
						wrapper.scrollTop(
							Math.round(
								wrapper.scrollTop()
								+ (Math.floor((wrapper.height() + (list? bufferExt.itemH * -1 : 16)) / bufferExt.itemH)) * bufferExt.itemH
							)
						);
					}
				}
			});
		
	});
	
	// fm.timeEnd('cwdLoad')
	
	return this;
};


/*
 * File: /js/ui/dialog.js
 */

/**
 * @class  elFinder dialog
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfinderdialog = function(opts, fm) {
		var platformWin = (window.navigator.platform.indexOf('Win') != -1),
		delta       = {},
		syncSize    = { enabled: false, width: false, height: false, defaultSize: null },
		fitSize     = function(dialog) {
			var opts, node;
			if (syncSize.enabled) {
				node = fm.options.dialogContained? elfNode : jQuery(window);
				opts = {
					maxWidth : syncSize.width?  node.width() - delta.width  : null,
					maxHeight: syncSize.height? node.height() - delta.height : null
				};
				Object.assign(restoreStyle, opts);
				dialog.css(opts).trigger('resize');
				if (dialog.data('hasResizable') && (dialog.resizable('option', 'maxWidth') < opts.maxWidth || dialog.resizable('option', 'maxHeight') < opts.maxHeight)) {
					dialog.resizable('option', opts);
				}
			}
		},
		syncFunc    = function(e) {
			var dialog = e.data;
			syncTm && cancelAnimationFrame(syncTm);
			syncTm = requestAnimationFrame(function() {
				var opts, offset;
				if (syncSize.enabled) {
					fitSize(dialog);
				}
			});
		},
		checkEditing = function() {
			var cldialog = 'elfinder-dialog',
				dialogs = elfNode.children('.' + cldialog + '.' + fm.res('class', 'editing') + ':visible');
			fm[dialogs.length? 'disable' : 'enable']();
		},
		propagationEvents = {},
		syncTm, dialog, elfNode, restoreStyle;
	
	if (fm && fm.ui) {
		elfNode = fm.getUI();
	} else {
		elfNode = this.closest('.elfinder');
		if (! fm) {
			fm = elfNode.elfinder('instance');
		}
	}
	
	if (typeof opts  === 'string') {
		if ((dialog = this.closest('.ui-dialog')).length) {
			if (opts === 'open') {
				if (dialog.css('display') === 'none') {
					// Need dialog.show() and hide() to detect elements size in open() callbacks
					dialog.trigger('posinit').show().trigger('open').hide();
					dialog.fadeIn(120, function() {
						fm.trigger('dialogopened', {dialog: dialog});
					});
				}
			} else if (opts === 'close' || opts === 'destroy') {
				dialog.stop(true);
				if (dialog.is(':visible') || elfNode.is(':hidden')) {
					dialog.trigger('close');
					fm.trigger('dialogclosed', {dialog: dialog});
				}
				if (opts === 'destroy') {
					dialog.remove();
					fm.trigger('dialogremoved', {dialog: dialog});
				}
			} else if (opts === 'toTop') {
				dialog.trigger('totop');
				fm.trigger('dialogtotoped', {dialog: dialog});
			} else if (opts === 'posInit') {
				dialog.trigger('posinit');
				fm.trigger('dialogposinited', {dialog: dialog});
			} else if (opts === 'tabstopsInit') {
				dialog.trigger('tabstopsInit');
				fm.trigger('dialogtabstopsinited', {dialog: dialog});
			} else if (opts === 'checkEditing') {
				checkEditing();
			}
		}
		return this;
	}
	
	opts = Object.assign({}, jQuery.fn.elfinderdialog.defaults, opts);
	
	if (opts.allowMinimize && opts.allowMinimize === 'auto') {
		opts.allowMinimize = this.find('textarea,input').length? true : false; 
	}
	opts.openMaximized = opts.allowMinimize && opts.openMaximized;
	if (opts.headerBtnPos && opts.headerBtnPos === 'auto') {
		opts.headerBtnPos = platformWin? 'right' : 'left';
	}
	if (opts.headerBtnOrder && opts.headerBtnOrder === 'auto') {
		opts.headerBtnOrder = platformWin? 'close:maximize:minimize' : 'close:minimize:maximize';
	}
	
	if (opts.modal && opts.allowMinimize) {
		opts.allowMinimize = false;
	}
	
	if (fm.options.dialogContained) {
		syncSize.width = syncSize.height = syncSize.enabled = true;
	} else {
		syncSize.width = (opts.maxWidth === 'window');
		syncSize.height = (opts.maxHeight === 'window');
		if (syncSize.width || syncSize.height) {
			syncSize.enabled = true;
		}
	}

	propagationEvents = fm.arrayFlip(opts.propagationEvents, true);
	
	this.filter(':not(.ui-dialog-content)').each(function() {
		var self       = jQuery(this).addClass('ui-dialog-content ui-widget-content'),
			clactive   = 'elfinder-dialog-active',
			cldialog   = 'elfinder-dialog',
			clnotify   = 'elfinder-dialog-notify',
			clhover    = 'ui-state-hover',
			cltabstop  = 'elfinder-tabstop',
			cl1stfocus = 'elfinder-focus',
			clmodal    = 'elfinder-dialog-modal',
			id         = parseInt(Math.random()*1000000),
			titlebar   = jQuery('<div class="ui-dialog-titlebar ui-widget-header ui-corner-top ui-helper-clearfix"><span class="elfinder-dialog-title">'+opts.title+'</span></div>'),
			buttonset  = jQuery('<div class="ui-dialog-buttonset"/>'),
			buttonpane = jQuery('<div class=" ui-helper-clearfix ui-dialog-buttonpane ui-widget-content"/>')
				.append(buttonset),
			btnWidth   = 0,
			btnCnt     = 0,
			tabstops   = jQuery(),
			evCover    = jQuery('<div style="width:100%;height:100%;position:absolute;top:0px;left:0px;"/>').hide(),
			numberToTel = function() {
				if (opts.optimizeNumber) {
					dialog.find('input[type=number]').each(function() {
						jQuery(this).attr('inputmode', 'numeric');
						jQuery(this).attr('pattern', '[0-9]*');
					});
				}
			},
			tabstopsInit = function() {
				tabstops = dialog.find('.'+cltabstop);
				if (tabstops.length) {
					tabstops.attr('tabindex', '-1');
					if (! tabstops.filter('.'+cl1stfocus).length) {
						buttonset.children('.'+cltabstop+':'+(platformWin? 'first' : 'last')).addClass(cl1stfocus);
					}
				}
			},
			tabstopNext = function(cur) {
				var elms = tabstops.filter(':visible:enabled'),
					node = cur? null : elms.filter('.'+cl1stfocus+':first');
					
				if (! node || ! node.length) {
					node = elms.first();
				}
				if (cur) {
					jQuery.each(elms, function(i, elm) {
						if (elm === cur && elms[i+1]) {
							node = elms.eq(i+1);
							return false;
						}
					});
				}
				return node;
			},
			tabstopPrev = function(cur) {
				var elms = tabstops.filter(':visible:enabled'),
					node = elms.last();
				jQuery.each(elms, function(i, elm) {
					if (elm === cur && elms[i-1]) {
						node = elms.eq(i-1);
						return false;
					}
				});
				return node;
			},
			makeHeaderBtn = function() {
				jQuery.each(opts.headerBtnOrder.split(':').reverse(), function(i, v) {
					headerBtns[v] && headerBtns[v]();
				});
				if (platformWin) {
					titlebar.children('.elfinder-titlebar-button').addClass('elfinder-titlebar-button-right');
				}
			},
			headerBtns = {
				close: function() {
					titlebar.prepend(jQuery('<span class="ui-widget-header ui-dialog-titlebar-close ui-corner-all elfinder-titlebar-button"><span class="ui-icon ui-icon-closethick"/></span>')
						.on('mousedown', function(e) {
							e.preventDefault();
							e.stopPropagation();
							self.elfinderdialog('close');
						})
					);
				},
				maximize: function() {
					if (opts.allowMaximize) {
						dialog.on('resize', function(e, data) {
							var full, elm;
							e.preventDefault();
							e.stopPropagation();
							if (data && data.maximize) {
								elm = titlebar.find('.elfinder-titlebar-full');
								full = (data.maximize === 'on');
								elm.children('span.ui-icon')
									.toggleClass('ui-icon-plusthick', ! full)
									.toggleClass('ui-icon-arrowreturnthick-1-s', full);
								if (full) {
									try {
										dialog.hasClass('ui-draggable') && dialog.draggable('disable');
										dialog.hasClass('ui-resizable') && dialog.resizable('disable');
									} catch(e) {}
									self.css('width', '100%').css('height', dialog.height() - dialog.children('.ui-dialog-titlebar').outerHeight(true) - buttonpane.outerHeight(true));
								} else {
									self.attr('style', elm.data('style'));
									elm.removeData('style');
									posCheck();
									try {
										dialog.hasClass('ui-draggable') && dialog.draggable('enable');
										dialog.hasClass('ui-resizable') && dialog.resizable('enable');
									} catch(e) {}
								}
								dialog.trigger('resize', {init: true});
							}
						});
						titlebar.prepend(jQuery('<span class="ui-widget-header ui-corner-all elfinder-titlebar-button elfinder-titlebar-full"><span class="ui-icon ui-icon-plusthick"/></span>')
							.on('mousedown', function(e) {
								var elm = jQuery(this);
								e.preventDefault();
								e.stopPropagation();
								if (!dialog.hasClass('elfinder-maximized') && typeof elm.data('style') === 'undefined') {
									self.height(self.height());
									elm.data('style', self.attr('style') || '');
								}
								fm.toggleMaximize(dialog);
								typeof(opts.maximize) === 'function' && opts.maximize.call(self[0]);
							})
						);
					}
					
				},
				minimize: function() {
					var btn, mnode, doffset;
					if (opts.allowMinimize) {
						btn = jQuery('<span class="ui-widget-header ui-corner-all elfinder-titlebar-button elfinder-titlebar-minimize"><span class="ui-icon ui-icon-minusthick"/></span>')
							.on('mousedown', function(e) {
								var $this = jQuery(this),
									tray = fm.getUI('bottomtray'),
									dumStyle = { width: 70, height: 24 },
									dum = jQuery('<div/>').css(dumStyle).addClass(dialog.get(0).className + ' elfinder-dialog-minimized'),
									pos = {};
								
								e.preventDefault();
								e.stopPropagation();
								if (!dialog.data('minimized')) {
									// minimize
									doffset = dialog.data('minimized', true).position();
									mnode = dialog.clone().on('mousedown', function() {
										$this.trigger('mousedown');
									}).removeClass('ui-draggable ui-resizable elfinder-frontmost');
									tray.append(dum);
									Object.assign(pos, dum.offset(), dumStyle);
									dum.remove();
									mnode.height(dialog.height()).children('.ui-dialog-content:first').empty();
									fm.toHide(dialog.before(mnode));
									mnode.children('.ui-dialog-content:first,.ui-dialog-buttonpane,.ui-resizable-handle').remove();
									mnode.find('.elfinder-titlebar-minimize,.elfinder-titlebar-full').remove();
									mnode.find('.ui-dialog-titlebar-close').on('mousedown', function(e) {
										e.stopPropagation();
										e.preventDefault();
										mnode.remove();
										dialog.show();
										self.elfinderdialog('close');
									});
									mnode.animate(pos, function() {
										mnode.attr('style', '')
										.css({ maxWidth: dialog.width() })
										.addClass('elfinder-dialog-minimized')
										.appendTo(tray);
										checkEditing();
										typeof(opts.minimize) === 'function' && opts.minimize.call(self[0]);
									});
								} else {
									//restore
									dialog.removeData('minimized').before(mnode.css(Object.assign({'position': 'absolute'}, mnode.offset())));
									fm.toFront(mnode);
									mnode.animate(Object.assign({ width: dialog.width(), height: dialog.height() }, doffset), function() {
										dialog.show();
										fm.toFront(dialog);
										mnode.remove();
										posCheck();
										checkEditing();
										dialog.trigger('resize', {init: true});
										typeof(opts.minimize) === 'function' && opts.minimize.call(self[0]);
									});
								}
							});
						titlebar.on('dblclick', function(e) {
							jQuery(this).children('.elfinder-titlebar-minimize').trigger('mousedown');
						}).prepend(btn);
						dialog.on('togleminimize', function() {
							btn.trigger('mousedown');
						});
					}
				}
			},
			dialog = jQuery('<div class="ui-front ui-dialog ui-widget ui-widget-content ui-corner-all ui-draggable std42-dialog touch-punch '+cldialog+' '+opts.cssClass+'"/>')
				.hide()
				.append(self)
				.appendTo(elfNode)
				.draggable({
					containment : fm.options.dialogContained? elfNode : null,
					handle : '.ui-dialog-titlebar',
					start : function() {
						evCover.show();
					},
					drag : function(e, ui) {
						var top = ui.offset.top,
							left = ui.offset.left;
						if (top < 0) {
							ui.position.top = ui.position.top - top;
						}
						if (left < 0) {
							ui.position.left = ui.position.left - left;
						}
						if (fm.options.dialogContained) {
							ui.position.top < 0 && (ui.position.top = 0);
							ui.position.left < 0 && (ui.position.left = 0);
						}
					},
					stop : function(e, ui) {
						evCover.hide();
						dialog.css({height : opts.height});
						self.data('draged', true);
					}
				})
				.css({
					width     : opts.width,
					height    : opts.height,
					minWidth  : opts.minWidth,
					minHeight : opts.minHeight,
					maxWidth  : opts.maxWidth,
					maxHeight : opts.maxHeight
				})
				.on('touchstart touchmove touchend click dblclick mouseup mouseenter mouseleave mouseout mouseover mousemove', function(e) {
					// stopPropagation of user action events
					!propagationEvents[e.type] && e.stopPropagation();
				})
				.on('mousedown', function(e) {
					!propagationEvents[e.type] && e.stopPropagation();
					requestAnimationFrame(function() {
						if (dialog.is(':visible') && !dialog.hasClass('elfinder-frontmost')) {
							toFocusNode = jQuery(':focus');
							if (!toFocusNode.length) {
								toFocusNode = void(0);
							}
							dialog.trigger('totop');
						}
					});
				})
				.on('open', function() {
					dialog.data('margin-y', self.outerHeight(true) - self.height());
					if (syncSize.enabled) {
						if (opts.height && opts.height !== 'auto') {
							dialog.trigger('resize', {init: true});
						}
						if (!syncSize.defaultSize) {
							syncSize.defaultSize = { width: self.width(), height: self.height() };
						}
						fitSize(dialog);
						dialog.trigger('resize').trigger('posinit');
						elfNode.on('resize.'+fm.namespace, dialog, syncFunc);
					}
					
					if (!dialog.hasClass(clnotify)) {
						elfNode.children('.'+cldialog+':visible:not(.'+clnotify+')').each(function() {
							var d     = jQuery(this),
								top   = parseInt(d.css('top')),
								left  = parseInt(d.css('left')),
								_top  = parseInt(dialog.css('top')),
								_left = parseInt(dialog.css('left')),
								ct    = Math.abs(top - _top) < 10,
								cl    = Math.abs(left - _left) < 10;

							if (d[0] != dialog[0] && (ct || cl)) {
								dialog.css({
									top  : ct ? (top + 10) : _top,
									left : cl ? (left + 10) : _left
								});
							}
						});
					} 
					
					if (dialog.data('modal')) {
						dialog.addClass(clmodal);
						fm.getUI('overlay').elfinderoverlay('show');
					}
					
					dialog.trigger('totop');
					
					opts.openMaximized && fm.toggleMaximize(dialog);

					fm.trigger('dialogopen', {dialog: dialog});

					typeof(opts.open) == 'function' && jQuery.proxy(opts.open, self[0])();
					
					if (opts.closeOnEscape) {
						jQuery(document).on('keydown.'+id, function(e) {
							if (e.keyCode == jQuery.ui.keyCode.ESCAPE && dialog.hasClass('elfinder-frontmost')) {
								self.elfinderdialog('close');
							}
						});
					}
					dialog.hasClass(fm.res('class', 'editing')) && checkEditing();
				})
				.on('close', function(e) {
					var dialogs, dfd;
					
					if (opts.beforeclose && typeof opts.beforeclose === 'function') {
						dfd = opts.beforeclose();
						if (!dfd || !dfd.promise) {
							dfd = !dfd? jQuery.Deferred().reject() : jQuery.Deferred().resolve();
						}
					} else {
						dfd = jQuery.Deferred().resolve();
					}
					
					dfd.done(function() {
						syncSize.enabled && elfNode.off('resize.'+fm.namespace, syncFunc);
						
						if (opts.closeOnEscape) {
							jQuery(document).off('keyup.'+id);
						}
						
						if (opts.allowMaximize) {
							fm.toggleMaximize(dialog, false);
						}
						
						fm.toHide(dialog);
						dialog.data('modal') && fm.getUI('overlay').elfinderoverlay('hide');
						
						if (typeof(opts.close) == 'function') {
							jQuery.proxy(opts.close, self[0])();
						}
						if (opts.destroyOnClose && dialog.parent().length) {
							dialog.hide().remove();
						}
						
						// get focus to next dialog
						dialogs = elfNode.children('.'+cldialog+':visible');
						
						dialog.hasClass(fm.res('class', 'editing')) && checkEditing();
					});
				})
				.on('totop frontmost', function() {
					var s = fm.storage('autoFocusDialog');
					
					dialog.data('focusOnMouseOver', s? (s > 0) : fm.options.uiOptions.dialog.focusOnMouseOver);
					
					if (dialog.data('minimized')) {
						titlebar.children('.elfinder-titlebar-minimize').trigger('mousedown');
					}
					
					if (!dialog.data('modal') && fm.getUI('overlay').is(':visible')) {
						fm.getUI('overlay').before(dialog);
					} else {
						fm.toFront(dialog);
					}
					elfNode.children('.'+cldialog+':not(.'+clmodal+')').removeClass(clactive);
					dialog.addClass(clactive);

					! fm.UA.Mobile && (toFocusNode || tabstopNext()).trigger('focus');

					toFocusNode = void(0);
				})
				.on('posinit', function() {
					var css = opts.position,
						nodeOffset, minTop, minLeft, outerSize, win, winSize, nodeFull;
					if (dialog.hasClass('elfinder-maximized')) {
						return;
					}
					if (! css && ! dialog.data('resizing')) {
						nodeFull = elfNode.hasClass('elfinder-fullscreen');
						dialog.css(nodeFull? {
							maxWidth  : '100%',
							maxHeight : '100%',
							overflow   : 'auto'
						} : restoreStyle);
						if (fm.UA.Mobile && !nodeFull && dialog.data('rotated') === fm.UA.Rotated) {
							return;
						}
						dialog.data('rotated', fm.UA.Rotated);
						win = jQuery(window);
						nodeOffset = elfNode.offset();
						outerSize = {
							width : dialog.outerWidth(true),
							height: dialog.outerHeight(true)
						};
						outerSize.right = nodeOffset.left + outerSize.width;
						outerSize.bottom = nodeOffset.top + outerSize.height;
						winSize = {
							scrLeft: win.scrollLeft(),
							scrTop : win.scrollTop(),
							width  : win.width(),
							height : win.height()
						};
						winSize.right = winSize.scrLeft + winSize.width;
						winSize.bottom = winSize.scrTop + winSize.height;
						
						if (fm.options.dialogContained || nodeFull) {
							minTop = 0;
							minLeft = 0;
						} else {
							minTop = nodeOffset.top * -1 + winSize.scrTop;
							minLeft = nodeOffset.left * -1 + winSize.scrLeft;
						}
						css = {
							top  : outerSize.height >= winSize.height? minTop  : Math.max(minTop, parseInt((elfNode.height() - outerSize.height)/2 - 42)),
							left : outerSize.width  >= winSize.width ? minLeft : Math.max(minLeft, parseInt((elfNode.width() - outerSize.width)/2))
						};
						if (outerSize.right + css.left > winSize.right) {
							css.left = Math.max(minLeft, winSize.right - outerSize.right);
						}
						if (outerSize.bottom + css.top > winSize.bottom) {
							css.top = Math.max(minTop, winSize.bottom - outerSize.bottom);
						}
					}
					if (opts.absolute) {
						css.position = 'absolute';
					}
					css && dialog.css(css);
				})
				.on('resize', function(e, data) {
					var oh = 0, init = data && data.init, h, minH;
					if ((data && (data.minimize || data.maxmize)) || dialog.data('minimized')) {
						return;
					}
					e.stopPropagation();
					e.preventDefault();
					dialog.children('.ui-widget-header,.ui-dialog-buttonpane').each(function() {
						oh += jQuery(this).outerHeight(true);
					});
					if (!init && syncSize.enabled && !e.originalEvent && !dialog.hasClass('elfinder-maximized')) {
						h = Math.min(syncSize.defaultSize.height, Math.max(parseInt(dialog.css('max-height')), parseInt(dialog.css('min-height'))) - oh - dialog.data('margin-y'));
					} else {
						h = dialog.height() - oh - dialog.data('margin-y');
					}
					self.height(h);
					if (init) {
						return;
					}
					posCheck();
					minH = self.height();
					minH = (h < minH)? (minH + oh + dialog.data('margin-y')) : opts.minHeight;
					dialog.css('min-height', minH);
					dialog.data('hasResizable') && dialog.resizable('option', { minHeight: minH });
					if (typeof(opts.resize) === 'function') {
						jQuery.proxy(opts.resize, self[0])(e, data);
					}
				})
				.on('tabstopsInit', tabstopsInit)
				.on('focus', '.'+cltabstop, function() {
					jQuery(this).addClass(clhover).parent('label').addClass(clhover);
					this.id && jQuery(this).parent().find('label[for='+this.id+']').addClass(clhover);
				})
				.on('click', 'select.'+cltabstop, function() {
					var node = jQuery(this);
					node.data('keepFocus')? node.removeData('keepFocus') : node.data('keepFocus', true);
				})
				.on('blur', '.'+cltabstop, function() {
					jQuery(this).removeClass(clhover).removeData('keepFocus').parent('label').removeClass(clhover);
					this.id && jQuery(this).parent().find('label[for='+this.id+']').removeClass(clhover);
				})
				.on('mouseenter mouseleave', '.'+cltabstop+',label', function(e) {
					var $this = jQuery(this), labelfor;
					if (this.nodeName === 'LABEL') {
						if (!$this.children('.'+cltabstop).length && (!(labelfor = $this.attr('for')) || !jQuery('#'+labelfor).hasClass(cltabstop))) {
							return;
						}
					}
					if (opts.btnHoverFocus && dialog.data('focusOnMouseOver')) {
						if (e.type === 'mouseenter' && ! jQuery(':focus').data('keepFocus')) {
							$this.trigger('focus');
						}
					} else {
						$this.toggleClass(clhover, e.type == 'mouseenter');
					}
				})
				.on('keydown', '.'+cltabstop, function(e) {
					var $this = jQuery(this),
						esc, move, moveTo;
					if ($this.is(':focus')) {
						esc = e.keyCode === jQuery.ui.keyCode.ESCAPE;
						if (e.keyCode === jQuery.ui.keyCode.ENTER) {
							e.preventDefault();
							$this.trigger('click');
						}  else if (((e.keyCode === jQuery.ui.keyCode.TAB) && e.shiftKey) || e.keyCode === jQuery.ui.keyCode.LEFT || e.keyCode == jQuery.ui.keyCode.UP) {
							move = 'prev';
						}  else if (e.keyCode === jQuery.ui.keyCode.TAB || e.keyCode == jQuery.ui.keyCode.RIGHT || e.keyCode == jQuery.ui.keyCode.DOWN) {
							move = 'next';
						}
						if (move
								&&
							(
								($this.is('textarea') && !(e.ctrlKey || e.metaKey))
									||
								($this.is('select,span.ui-slider-handle') && e.keyCode !== jQuery.ui.keyCode.TAB)
									||
								($this.is('input:not(:checkbox,:radio)') && (!(e.ctrlKey || e.metaKey) && e.keyCode === jQuery.ui.keyCode[move === 'prev'? 'LEFT':'RIGHT']))
							)
						) {
							e.stopPropagation();
							return;
						}
						if (!esc) {
							e.stopPropagation();
						} else if ($this.is('input:not(:checkbox,:radio),textarea')) {
							if ($this.val() !== '') {
								$this.val('');
								e.stopPropagation();
							}
						}
						if (move) {
							e.preventDefault();
							(move === 'prev'? tabstopPrev : tabstopNext)(this).trigger('focus');
						}
					}
				})
				.data({modal: opts.modal}),
			posCheck = function() {
				var node = fm.getUI(),
					pos;
				if (node.hasClass('elfinder-fullscreen')) {
					pos = dialog.position();
					dialog.css('top', Math.max(Math.min(Math.max(pos.top, 0), node.height() - 100), 0));
					dialog.css('left', Math.max(Math.min(Math.max(pos.left, 0), node.width() - 200), 0));
				}
			},
			maxSize, toFocusNode;
		
		dialog.prepend(titlebar);

		makeHeaderBtn();

		jQuery.each(opts.buttons, function(name, cb) {
			var button = jQuery('<button type="button" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only '
					+'elfinder-btncnt-'+(btnCnt++)+' '
					+cltabstop
					+'"><span class="ui-button-text">'+name+'</span></button>')
				.on('click', jQuery.proxy(cb, self[0]));
			if (cb._cssClass) {
				button.addClass(cb._cssClass);
			}
			if (platformWin) {
				buttonset.append(button);
			} else {
				buttonset.prepend(button);
			}
		});
		
		if (buttonset.children().length) {
			dialog.append(buttonpane);
			
			dialog.show();
			buttonpane.find('button').each(function(i, btn) {
				btnWidth += jQuery(btn).outerWidth(true);
			});
			dialog.hide();
			btnWidth += 20;
			
			if (dialog.width() < btnWidth) {
				dialog.width(btnWidth);
			}
		}
		
		dialog.append(evCover);
		
		if (syncSize.enabled) {
			delta.width = dialog.outerWidth(true) - dialog.width() + ((dialog.outerWidth() - dialog.width()) / 2);
			delta.height = dialog.outerHeight(true) - dialog.height() + ((dialog.outerHeight() - dialog.height()) / 2);
		}
		
		if (fm.options.dialogContained) {
			maxSize = {
				maxWidth: elfNode.width() - delta.width,
				maxHeight: elfNode.height() - delta.height
			};
			opts.maxWidth = opts.maxWidth? Math.min(maxSize.maxWidth, opts.maxWidth) : maxSize.maxWidth;
			opts.maxHeight = opts.maxHeight? Math.min(maxSize.maxHeight, opts.maxHeight) : maxSize.maxHeight;
			dialog.css(maxSize);
		}
		
		restoreStyle = {
			maxWidth  : dialog.css('max-width'),
			maxHeight : dialog.css('max-height'),
			overflow   : dialog.css('overflow')
		};
		
		if (opts.resizable) {
			dialog.resizable({
				minWidth   : opts.minWidth,
				minHeight  : opts.minHeight,
				maxWidth   : opts.maxWidth,
				maxHeight  : opts.maxHeight,
				start      : function() {
					evCover.show();
					if (dialog.data('resizing') !== true && dialog.data('resizing')) {
						clearTimeout(dialog.data('resizing'));
					}
					dialog.data('resizing', true);
				},
				stop       : function(e, ui) {
					evCover.hide();
					dialog.data('resizing', setTimeout(function() {
						dialog.data('resizing', false);
					}, 200));
					if (syncSize.enabled) {
						syncSize.defaultSize = { width: self.width(), height: self.height() };
					}
				}
			}).data('hasResizable', true);
		} 
		
		numberToTel();
		
		tabstopsInit();
		
		typeof(opts.create) == 'function' && jQuery.proxy(opts.create, this)();
		
		if (opts.autoOpen) {
			if (opts.open) {
				requestAnimationFrame(function() {
					self.elfinderdialog('open');
				});
			} else {
				self.elfinderdialog('open');
			}
		}

		if (opts.resize) {
			fm.bind('themechange', function() {
				setTimeout(function() {
					dialog.data('margin-y', self.outerHeight(true) - self.height());
					dialog.trigger('resize', {init: true});
				}, 300);
			});
		}
	});
	
	return this;
};

jQuery.fn.elfinderdialog.defaults = {
	cssClass  : '',
	title     : '',
	modal     : false,
	resizable : true,
	autoOpen  : true,
	closeOnEscape : true,
	destroyOnClose : false,
	buttons   : {},
	btnHoverFocus : true,
	position  : null,
	absolute  : false,
	width     : 320,
	height    : 'auto',
	minWidth  : 200,
	minHeight : 70,
	maxWidth  : null,
	maxHeight : null,
	allowMinimize : 'auto',
	allowMaximize : false,
	openMaximized : false,
	headerBtnPos : 'auto',
	headerBtnOrder : 'auto',
	optimizeNumber : true,
	propagationEvents : ['mousemove', 'mouseup']
};


/*
 * File: /js/ui/fullscreenbutton.js
 */

/**
 * @class  elFinder toolbar button to switch full scrren mode.
 *
 * @author Naoki Sawada
 **/

jQuery.fn.elfinderfullscreenbutton = function(cmd) {
		return this.each(function() {
		var button = jQuery(this).elfinderbutton(cmd),
			icon   = button.children('.elfinder-button-icon'),
			tm;
		cmd.change(function() {
			tm && cancelAnimationFrame(tm);
			tm = requestAnimationFrame(function() {
				var fullscreen = cmd.value;
				icon.addClass('elfinder-button-icon-fullscreen').toggleClass('elfinder-button-icon-unfullscreen', fullscreen);
				cmd.className = fullscreen? 'unfullscreen' : '';
			});
		});
	});
};


/*
 * File: /js/ui/navbar.js
 */

/**
 * @class elfindernav - elFinder container for diretories tree and places
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfindernavbar = function(fm, opts) {
		this.not('.elfinder-navbar').each(function() {
		var nav    = jQuery(this).hide().addClass('ui-state-default elfinder-navbar'),
			parent = nav.css('overflow', 'hidden').parent(),
			wz     = parent.children('.elfinder-workzone').append(nav),
			ltr    = fm.direction == 'ltr',
			delta, deltaW, handle, swipeHandle, autoHide, setWidth, navdock,
			setWzRect = function() {
				var cwd = fm.getUI('cwd'),
					wz  = fm.getUI('workzone'),
					wzRect = wz.data('rectangle'),
					cwdOffset = cwd.offset();
				wz.data('rectangle', Object.assign(wzRect, { cwdEdge: (fm.direction === 'ltr')? cwdOffset.left : cwdOffset.left + cwd.width() }));
			},
			setDelta = function() {
				nav.css('overflow', 'hidden');
				delta  = Math.round(nav.outerHeight() - nav.height());
				deltaW = Math.round(navdock.outerWidth() - navdock.innerWidth());
				nav.css('overflow', 'auto');
			};

		fm.one('init', function() {
			navdock = fm.getUI('navdock');
			var set = function() {
					setDelta();
					fm.bind('wzresize', function() {
						var navdockH = 0;
						navdock.width(nav.outerWidth() - deltaW);
						if (navdock.children().length > 1) {
							navdockH = navdock.outerHeight(true);
						}
						nav.height(wz.height() - navdockH - delta);
					}).trigger('wzresize');
				};
			if (fm.cssloaded) {
				set();
			} else {
				fm.one('cssloaded', set);
			}
		})
		.one('opendone',function() {
			handle && handle.trigger('resize');
			nav.css('overflow', 'auto');
		}).bind('themechange', setDelta);
		
		if (fm.UA.Touch) {
			autoHide = fm.storage('autoHide') || {};
			if (typeof autoHide.navbar === 'undefined') {
				autoHide.navbar = (opts.autoHideUA && opts.autoHideUA.length > 0 && jQuery.grep(opts.autoHideUA, function(v){ return fm.UA[v]? true : false; }).length);
				fm.storage('autoHide', autoHide);
			}
			
			if (autoHide.navbar) {
				fm.one('init', function() {
					if (nav.children().length) {
						fm.uiAutoHide.push(function(){ nav.stop(true, true).trigger('navhide', { duration: 'slow', init: true }); });
					}
				});
			}
			
			fm.bind('load', function() {
				if (nav.children().length) {
					swipeHandle = jQuery('<div class="elfinder-navbar-swipe-handle"/>').hide().appendTo(wz);
					if (swipeHandle.css('pointer-events') !== 'none') {
						swipeHandle.remove();
						swipeHandle = null;
					}
				}
			});
			
			nav.on('navshow navhide', function(e, data) {
				var mode     = (e.type === 'navshow')? 'show' : 'hide',
					duration = (data && data.duration)? data.duration : 'fast',
					handleW = (data && data.handleW)? data.handleW : Math.max(50, fm.getUI().width() / 10);
				nav.stop(true, true)[mode]({
					duration: duration,
					step    : function() {
						fm.trigger('wzresize');
					},
					complete: function() {
						if (swipeHandle) {
							if (mode === 'show') {
								swipeHandle.stop(true, true).hide();
							} else {
								swipeHandle.width(handleW? handleW : '');
								fm.resources.blink(swipeHandle, 'slowonce');
							}
						}
						fm.trigger('navbar'+ mode);
						data.init && fm.trigger('uiautohide');
						setWzRect();
					}
				});
				autoHide.navbar = (mode !== 'show');
				fm.storage('autoHide', Object.assign(fm.storage('autoHide'), {navbar: autoHide.navbar}));
			}).on('touchstart', function(e) {
				if (jQuery(this)['scroll' + (fm.direction === 'ltr'? 'Right' : 'Left')]() > 5) {
					e.originalEvent._preventSwipeX = true;
				}
			});
		}
		
		if (! fm.UA.Mobile) {
			handle = nav.resizable({
					handles : ltr ? 'e' : 'w',
					minWidth : opts.minWidth || 150,
					maxWidth : opts.maxWidth || 500,
					resize : function() {
						fm.trigger('wzresize');
					},
					stop : function(e, ui) {
						fm.storage('navbarWidth', ui.size.width);
						setWzRect();
					}
				})
				.on('resize scroll', function(e) {
					var $this = jQuery(this),
						tm = $this.data('posinit');
					e.preventDefault();
					e.stopPropagation();
					if (! ltr && e.type === 'resize') {
						nav.css('left', 0);
					}
					tm && cancelAnimationFrame(tm);
					$this.data('posinit', requestAnimationFrame(function() {
						var offset = (fm.UA.Opera && nav.scrollLeft())? 20 : 2;
						handle.css('top', 0).css({
							top  : parseInt(nav.scrollTop())+'px',
							left : ltr ? 'auto' : parseInt(nav.scrollRight() -  offset) * -1,
							right: ltr ? parseInt(nav.scrollLeft() - offset) * -1 : 'auto'
						});
						if (e.type === 'resize') {
							fm.getUI('cwd').trigger('resize');
						}
					}));
				})
				.children('.ui-resizable-handle').addClass('ui-front');
		}

		if (setWidth = fm.storage('navbarWidth')) {
			nav.width(setWidth);
		} else {
			if (fm.UA.Mobile) {
				fm.one('cssloaded', function() {
					var set = function() {
						setWidth = nav.parent().width() / 2;
						if (nav.data('defWidth') > setWidth) {
							nav.width(setWidth);
						} else {
							nav.width(nav.data('defWidth'));
						}
						nav.data('width', nav.width());
						fm.trigger('wzresize');
					};
					nav.data('defWidth', nav.width());
					jQuery(window).on('resize.' + fm.namespace, set);
					set();
				});
			}
		}

	});
	
	return this;
};


/*
 * File: /js/ui/navdock.js
 */

/**
 * @class elfindernavdock - elFinder container for preview etc at below the navbar
 *
 * @author Naoki Sawada
 **/
jQuery.fn.elfindernavdock = function(fm, opts) {
		this.not('.elfinder-navdock').each(function() {
		var self = jQuery(this).hide().addClass('ui-state-default elfinder-navdock touch-punch'),
			node = self.parent(),
			wz   = node.children('.elfinder-workzone').append(self),
			resize = function(to, h) {
				var curH = h || self.height(),
					diff = to - curH,
					len  = Object.keys(sizeSyncs).length,
					calc = len? diff / len : 0,
					ovf;
				if (diff) {
					ovf = self.css('overflow');
					self.css('overflow', 'hidden');
					self.height(to);
					jQuery.each(sizeSyncs, function(id, n) {
						n.height(n.height() + calc).trigger('resize.' + fm.namespace);
					});
					fm.trigger('wzresize');
					self.css('overflow', ovf);
				}
			},
			handle = jQuery('<div class="ui-front ui-resizable-handle ui-resizable-n"/>').appendTo(self),
			sizeSyncs = {},
			resizeFn = [],
			initMaxHeight = (parseInt(opts.initMaxHeight) || 50) / 100,
			maxHeight = (parseInt(opts.maxHeight) || 90) / 100,
			basicHeight, hasNode;
		
		
		self.data('addNode', function(cNode, opts) {
			var wzH = fm.getUI('workzone').height(),
				imaxH = wzH * initMaxHeight,
				curH, tH, mH;
			opts = Object.assign({
				first: false,
				sizeSync: true,
				init: false
			}, opts);
			if (!cNode.attr('id')) {
				cNode.attr('id', fm.namespace+'-navdock-' + (+new Date()));
			}
			opts.sizeSync && (sizeSyncs[cNode.attr('id')] = cNode);
			curH = self.height();
			tH = curH + cNode.outerHeight(true);
			
			if (opts.first) {
				handle.after(cNode);
			} else {
				self.append(cNode);
			}
			hasNode = true;
			self.resizable('enable').height(tH).show();
			
			fm.trigger('wzresize');
			
			if (opts.init) {
				mH = fm.storage('navdockHeight');
				if (mH) {
					tH = mH;
				} else {
					tH = tH > imaxH? imaxH : tH;
				}
				basicHeight = tH;
			}
			resize(Math.min(tH, wzH * maxHeight));
			
			return self;
		}).data('removeNode', function(nodeId, appendTo) {
			var cNode = jQuery('#'+nodeId);
			delete sizeSyncs[nodeId];
			self.height(self.height() - jQuery('#'+nodeId).outerHeight(true));
			if (appendTo) {
				if (appendTo === 'detach') {
					cNode = cNode.detach();
				} else {
					appendTo.append(cNode);
				}
			} else {
				cNode.remove();
			}
			if (self.children().length <= 1) {
				hasNode = false;
				self.resizable('disable').height(0).hide();
			}
			fm.trigger('wzresize');
			return cNode;
		});
		
		if (! opts.disabled) {
			fm.one('init', function() {
				var ovf;
				if (fm.getUI('navbar').children().not('.ui-resizable-handle').length) {
					self.data('dockEnabled', true);
					self.resizable({
						maxHeight: fm.getUI('workzone').height() * maxHeight,
						handles: { n: handle },
						start: function(e, ui) {
							ovf = self.css('overflow');
							self.css('overflow', 'hidden');
							fm.trigger('navdockresizestart', {event: e, ui: ui}, true);
						},
						resize: function(e, ui) {
							self.css('top', '');
							fm.trigger('wzresize', { inNavdockResize : true });
						},
						stop: function(e, ui) {
							fm.trigger('navdockresizestop', {event: e, ui: ui}, true);
							self.css('top', '');
							basicHeight = ui.size.height;
							fm.storage('navdockHeight', basicHeight);
							resize(basicHeight, ui.originalSize.height);
							self.css('overflow', ovf);
						}
					});
					fm.bind('wzresize', function(e) {
						var minH, maxH, h;
						if (self.is(':visible')) {
							maxH = fm.getUI('workzone').height() * maxHeight;
							if (! e.data || ! e.data.inNavdockResize) {
								h = self.height();
								if (maxH < basicHeight) {
									if (Math.abs(h - maxH) > 1) {
										resize(maxH);
									}
								} else {
									if (Math.abs(h - basicHeight) > 1) {
										resize(basicHeight);
									}
								}
							}
							self.resizable('option', 'maxHeight', maxH);
						}
					}).bind('themechange', function() {
						var oldH = Math.round(self.height());
						requestAnimationFrame(function() {
							var curH = Math.round(self.height()),
								diff = oldH - curH;
							if (diff !== 0) {
								resize(self.height(),  curH - diff);
							}
						});
					});
				}
				fm.bind('navbarshow navbarhide', function(e) {
					self[hasNode && e.type === 'navbarshow'? 'show' : 'hide']();
				});
			});
		}
	});
	return this;
};

/*
 * File: /js/ui/overlay.js
 */


jQuery.fn.elfinderoverlay = function(opts) {
		var fm = this.parent().elfinder('instance'),
		o, cnt, show, hide;
	
	this.filter(':not(.elfinder-overlay)').each(function() {
		opts = Object.assign({}, opts);
		jQuery(this).addClass('ui-front ui-widget-overlay elfinder-overlay')
			.hide()
			.on('mousedown', function(e) {
				e.preventDefault();
				e.stopPropagation();
			})
			.data({
				cnt  : 0,
				show : typeof(opts.show) == 'function' ? opts.show : function() { },
				hide : typeof(opts.hide) == 'function' ? opts.hide : function() { }
			});
	});
	
	if (opts == 'show') {
		o    = this.eq(0);
		cnt  = o.data('cnt') + 1;
		show = o.data('show');

		fm.toFront(o);
		o.data('cnt', cnt);

		if (o.is(':hidden')) {
			o.show();
			show();
		}
	} 
	
	if (opts == 'hide') {
		o    = this.eq(0);
		cnt  = o.data('cnt') - 1;
		hide = o.data('hide');
		
		o.data('cnt', cnt);
			
		if (cnt <= 0) {
			o.hide();
			hide();
		}
	}
	
	return this;
};


/*
 * File: /js/ui/panel.js
 */

jQuery.fn.elfinderpanel = function(fm) {
		return this.each(function() {
		var panel = jQuery(this).addClass('elfinder-panel ui-state-default ui-corner-all'),
			margin = 'margin-'+(fm.direction == 'ltr' ? 'left' : 'right');
		
		fm.one('load', function(e) {
			var navbar = fm.getUI('navbar');
			
			panel.css(margin, parseInt(navbar.outerWidth(true)));
			navbar.on('resize', function(e) {
				e.preventDefault();
				e.stopPropagation();
				panel.is(':visible') && panel.css(margin, parseInt(navbar.outerWidth(true)));
			});
		});
	});
};


/*
 * File: /js/ui/path.js
 */

/**
 * @class elFinder ui
 * Display current folder path in statusbar.
 * Click on folder name in path - open folder
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfinderpath = function(fm, options) {
		return this.each(function() {
		var query  = '',
			target = '',
			mimes  = [],
			place  = 'statusbar',
			clHover= fm.res('class', 'hover'),
			prefix = 'path' + (elFinder.prototype.uniqueid? elFinder.prototype.uniqueid : '') + '-',
			wzbase = jQuery('<div class="ui-widget-header ui-helper-clearfix elfinder-workzone-path"/>'),
			path   = jQuery(this).addClass('elfinder-path').html('&nbsp;')
				.on('mousedown', 'span.elfinder-path-dir', function(e) {
					var hash = jQuery(this).attr('id').substr(prefix.length);
					e.preventDefault();
					if (hash != fm.cwd().hash) {
						jQuery(this).addClass(clHover);
						if (query) {
							fm.exec('search', query, { target: hash, mime: mimes.join(' ') });
						} else {
							fm.trigger('select', {selected : [hash]}).exec('open', hash);
						}
					}
				})
				.prependTo(fm.getUI('statusbar').show()),
			roots = jQuery('<div class="elfinder-path-roots"/>').on('click', function(e) {
				e.stopPropagation();
				e.preventDefault();
				
				var roots = jQuery.map(fm.roots, function(h) { return fm.file(h); }),
				raw = [];

				jQuery.each(roots, function(i, f) {
					if (! f.phash && fm.root(fm.cwd().hash, true) !== f.hash) {
						raw.push({
							label    : fm.escape(f.i18 || f.name),
							icon     : 'home',
							callback : function() { fm.exec('open', f.hash); },
							options  : {
								iconClass : f.csscls || '',
								iconImg   : f.icon   || ''
							}
						});
					}
				});
				fm.trigger('contextmenu', {
					raw: raw,
					x: e.pageX,
					y: e.pageY
				});
			}).append('<span class="elfinder-button-icon elfinder-button-icon-menu" />').appendTo(wzbase),
			render = function(cwd) {
				var dirs = [],
					names = [];
				jQuery.each(fm.parents(cwd), function(i, hash) {
					var c = (cwd === hash)? 'elfinder-path-dir elfinder-path-cwd' : 'elfinder-path-dir',
						f = fm.file(hash),
						name = fm.escape(f.i18 || f.name);
					names.push(name);
					dirs.push('<span id="'+prefix+hash+'" class="'+c+'" title="'+names.join(fm.option('separator'))+'">'+name+'</span>');
				});
				return dirs.join('<span class="elfinder-path-other">'+fm.option('separator')+'</span>');
			},
			toWorkzone = function() {
				var prev;
				path.children('span.elfinder-path-dir').attr('style', '');
				prev = fm.direction === 'ltr'? jQuery('#'+prefix + fm.cwd().hash).prevAll('span.elfinder-path-dir:first') : jQuery();
				path.scrollLeft(prev.length? prev.position().left : 0);
			},
			fit = function() {
				if (fm.UA.CSS.flex) {
					return;
				}
				var dirs = path.children('span.elfinder-path-dir'),
					cnt  = dirs.length,
					m, bg = 0, ids;
				
				if (place === 'workzone' || cnt < 2) {
					dirs.attr('style', '');
					return;
				}
				path.width(path.css('max-width'));
				dirs.css({maxWidth: (100/cnt)+'%', display: 'inline-block'});
				m = path.width() - 9;
				path.children('span.elfinder-path-other').each(function() {
					m -= jQuery(this).width();
				});
				ids = [];
				dirs.each(function(i) {
					var dir = jQuery(this),
						w   = dir.width();
					m -= w;
					if (w < this.scrollWidth) {
						ids.push(i);
					}
				});
				path.width('');
				if (ids.length) {
					if (m > 0) {
						m = m / ids.length;
						jQuery.each(ids, function(i, k) {
							var d = jQuery(dirs[k]);
							d.css('max-width', d.width() + m);
						});
					}
					dirs.last().attr('style', '');
				} else {
					dirs.attr('style', '');
				}
			},
			hasUiTree, hasUiStat;

		fm.one('init', function() {
			hasUiTree = fm.getUI('tree').length;
			hasUiStat = fm.getUI('stat').length;
			if (! hasUiTree && options.toWorkzoneWithoutNavbar) {
				wzbase.append(path).insertBefore(fm.getUI('workzone'));
				place = 'workzone';
				fm.bind('open', toWorkzone)
				.one('opendone', function() {
					fm.getUI().trigger('resize');
				});
			}
		})
		.bind('open searchend parents', function() {
			var dirs = [];

			query  = '';
			target = '';
			mimes  = [];
			
			path.html(render(fm.cwd().hash));
			if (Object.keys(fm.roots).length > 1) {
				path.css('margin', '');
				roots.show();
			} else {
				path.css('margin', 0);
				roots.hide();
			}
			!hasUiStat && fit();
		})
		.bind('searchstart', function(e) {
			if (e.data) {
				query  = e.data.query || '';
				target = e.data.target || '';
				mimes  = e.data.mimes || [];
			}
		})
		.bind('search', function(e) {
			var dirs = [],
				html = '';
			if (target) {
				html = render(target);
			} else {
				html = fm.i18n('btnAll');
			}
			path.html('<span class="elfinder-path-other">'+fm.i18n('searcresult') + ': </span>' + html);
			fit();
		})
		// on swipe to navbar show/hide
		.bind('navbarshow navbarhide', function() {
			var wz = fm.getUI('workzone');
			if (this.type === 'navbarshow') {
				fm.unbind('open', toWorkzone);
				path.prependTo(fm.getUI('statusbar'));
				wzbase.detach();
				place = 'statusbar';
			} else {
				wzbase.append(path).insertBefore(wz);
				place = 'workzone';
				toWorkzone();
				fm.bind('open', toWorkzone);
			}
			fm.trigger('uiresize');
		})
		.bind('resize uistatchange', fit);
	});
};


/*
 * File: /js/ui/places.js
 */

/**
 * @class elFinder places/favorites ui
 *
 * @author Dmitry (dio) Levashov
 * @author Naoki Sawada
 **/
jQuery.fn.elfinderplaces = function(fm, opts) {
		return this.each(function() {
		var dirs      = {},
			c         = 'class',
			navdir    = fm.res(c, 'navdir'),
			collapsed = fm.res(c, 'navcollapse'),
			expanded  = fm.res(c, 'navexpand'),
			hover     = fm.res(c, 'hover'),
			clroot    = fm.res(c, 'treeroot'),
			dropover  = fm.res(c, 'adroppable'),
			tpl       = fm.res('tpl', 'placedir'),
			ptpl      = fm.res('tpl', 'perms'),
			spinner   = jQuery(fm.res('tpl', 'navspinner')),
			suffix    = opts.suffix? opts.suffix : '',
			key       = 'places' + suffix,
			menuTimer = null,
			/**
			 * Convert places dir node into dir hash
			 *
			 * @param  String  directory id
			 * @return String
			 **/
			id2hash   = function(id) { return id.substr(6);	},
			/**
			 * Convert places dir hash into dir node id
			 *
			 * @param  String  directory id
			 * @return String
			 **/
			hash2id   = function(hash) { return 'place-'+hash; },

			/**
			 * Convert places dir hash into dir node elment (jQuery object)
			 *
			 * @param  String  directory id
			 * @return Object
			 **/
			hash2elm  = function(hash) { return jQuery(document.getElementById(hash2id(hash))); },
			
			/**
			 * Save current places state
			 *
			 * @return void
			 **/
			save      = function() {
				var hashes = [], data = {};
				
				hashes = jQuery.map(subtree.children().find('[id]'), function(n) {
					return id2hash(n.id);
				});
				if (hashes.length) {
					jQuery.each(hashes.reverse(), function(i, h) {
						data[h] = dirs[h];
					});
				} else {
					data = null;
				}
				
				fm.storage(key, data);
			},
			/**
			 * Init dir at places
			 *
			 * @return void
			 **/
			init = function() {
				var dat, hashes;
				key = 'places'+(opts.suffix? opts.suffix : ''),
				dirs = {};
				dat = fm.storage(key);
				if (typeof dat === 'string') {
					// old data type elFinder <= 2.1.12
					dat = jQuery.grep(dat.split(','), function(hash) { return hash? true : false;});
					jQuery.each(dat, function(i, d) {
						var dir = d.split('#');
						dirs[dir[0]] = dir[1]? dir[1] : dir[0];
					});
				} else if (jQuery.isPlainObject(dat)) {
					dirs = dat;
				}
				// allow modify `dirs`
				/**
				 * example for preset places
				 * 
				 * elfinderInstance.bind('placesload', function(e, fm) {
				 * 	//if (fm.storage(e.data.storageKey) === null) { // for first time only
				 * 	if (!fm.storage(e.data.storageKey)) {           // for empty places
				 * 		e.data.dirs[targetHash] = fallbackName;     // preset folder
				 * 	}
				 * }
				 **/
				fm.trigger('placesload', {dirs: dirs, storageKey: key}, true);
				
				hashes = Object.keys(dirs);
				if (hashes.length) {
					root.prepend(spinner);
					
					fm.request({
						data : {cmd : 'info', targets : hashes},
						preventDefault : true
					})
					.done(function(data) {
						var exists = {};
						
						data.files && data.files.length && fm.cache(data.files);
						
						jQuery.each(data.files, function(i, f) {
							var hash = f.hash;
							exists[hash] = f;
						});
						jQuery.each(dirs, function(h, f) {
							add(exists[h] || Object.assign({notfound: true}, f));
						});
						if (fm.storage('placesState') > 0) {
							root.trigger('click');
						}
					})
					.always(function() {
						spinner.remove();
					});
				}
			},
			/**
			 * Return node for given dir object
			 *
			 * @param  Object  directory object
			 * @return jQuery
			 **/
			create    = function(dir, hash) {
				return jQuery(tpl.replace(/\{id\}/, hash2id(dir? dir.hash : hash))
						.replace(/\{name\}/, fm.escape(dir? dir.i18 || dir.name : hash))
						.replace(/\{cssclass\}/, dir? (fm.perms2class(dir) + (dir.notfound? ' elfinder-na' : '') + (dir.csscls? ' '+dir.csscls : '')) : '')
						.replace(/\{permissions\}/, (dir && (!dir.read || !dir.write || dir.notfound))? ptpl : '')
						.replace(/\{title\}/, (dir && dir.path)? fm.escape(dir.path) : '')
						.replace(/\{symlink\}/, '')
						.replace(/\{style\}/, (dir && dir.icon)? fm.getIconStyle(dir) : ''));
			},
			/**
			 * Add new node into places
			 *
			 * @param  Object  directory object
			 * @return void
			 **/
			add = function(dir) {
				var node, hash;

				if (dir.mime !== 'directory') {
					return false;
				}
				hash = dir.hash;
				if (!fm.files().hasOwnProperty(hash)) {
					// update cache
					fm.trigger('tree', {tree: [dir]});
				}
				
				node = create(dir, hash);
				
				dirs[hash] = dir;
				subtree.prepend(node);
				root.addClass(collapsed);
				sortBtn.toggle(subtree.children().length > 1);
				
				return true;
			},
			/**
			 * Remove dir from places
			 *
			 * @param  String  directory hash
			 * @return String  removed name
			 **/
			remove = function(hash) {
				var name = null, tgt, cnt;

				if (dirs[hash]) {
					delete dirs[hash];
					tgt = hash2elm(hash);
					if (tgt.length) {
						name = tgt.text();
						tgt.parent().remove();
						cnt = subtree.children().length;
						sortBtn.toggle(cnt > 1);
						if (! cnt) {
							root.removeClass(collapsed);
							places.removeClass(expanded);
							subtree.slideToggle(false);
						}
					}
				}
				
				return name;
			},
			/**
			 * Move up dir on places
			 *
			 * @param  String  directory hash
			 * @return void
			 **/
			moveup = function(hash) {
				var self = hash2elm(hash),
					tgt  = self.parent(),
					prev = tgt.prev('div'),
					cls  = 'ui-state-hover',
					ctm  = fm.getUI('contextmenu');
				
				menuTimer && clearTimeout(menuTimer);
				
				if (prev.length) {
					ctm.find(':first').data('placesHash', hash);
					self.addClass(cls);
					tgt.insertBefore(prev);
					prev = tgt.prev('div');
					menuTimer = setTimeout(function() {
						self.removeClass(cls);
						if (ctm.find(':first').data('placesHash') === hash) {
							ctm.hide().empty();
						}
					}, 1500);
				}
				
				if(!prev.length) {
					self.removeClass(cls);
					ctm.hide().empty();
				}
			},
			/**
			 * Update dir at places
			 *
			 * @param  Object   directory
			 * @param  String   previous hash
			 * @return Boolean
			 **/
			update = function(dir, preHash) {
				var hash = dir.hash,
					tgt  = hash2elm(preHash || hash),
					node = create(dir, hash);

				if (tgt.length > 0) {
					tgt.parent().replaceWith(node);
					dirs[hash] = dir;
					return true;
				} else {
					return false;
				}
			},
			/**
			 * Remove all dir from places
			 *
			 * @return void
			 **/
			clear = function() {
				subtree.empty();
				root.removeClass(collapsed);
				places.removeClass(expanded);
				subtree.slideToggle(false);
			},
			/**
			 * Sort places dirs A-Z
			 *
			 * @return void
			 **/
			sort = function() {
				jQuery.each(dirs, function(h, f) {
					var dir = fm.file(h) || f,
						node = create(dir, h),
						ret = null;
					if (!dir) {
						node.hide();
					}
					if (subtree.children().length) {
						jQuery.each(subtree.children(), function() {
							var current =  jQuery(this);
							if ((dir.i18 || dir.name).localeCompare(current.children('.'+navdir).text()) < 0) {
								ret = !node.insertBefore(current);
								return ret;
							}
						});
						if (ret !== null) {
							return true;
						}
					}
					!hash2elm(h).length && subtree.append(node);
				});
				save();
			},
			// sort button
			sortBtn = jQuery('<span class="elfinder-button-icon elfinder-button-icon-sort elfinder-places-root-icon" title="'+fm.i18n('cmdsort')+'"/>')
				.hide()
				.on('click', function(e) {
					e.stopPropagation();
					subtree.empty();
					sort();
				}
			),
			/**
			 * Node - wrapper for places root
			 *
			 * @type jQuery
			 **/
			wrapper = create({
					hash  : 'root-'+fm.namespace, 
					name  : fm.i18n(opts.name, 'places'),
					read  : true,
					write : true
				}),
			/**
			 * Places root node
			 *
			 * @type jQuery
			 **/
			root = wrapper.children('.'+navdir)
				.addClass(clroot)
				.on('click', function(e) {
					e.stopPropagation();
					if (root.hasClass(collapsed)) {
						places.toggleClass(expanded);
						subtree.slideToggle();
						fm.storage('placesState', places.hasClass(expanded)? 1 : 0);
					}
				})
				.append(sortBtn),
			/**
			 * Container for dirs
			 *
			 * @type jQuery
			 **/
			subtree = wrapper.children('.'+fm.res(c, 'navsubtree')),
			
			/**
			 * Main places container
			 *
			 * @type jQuery
			 **/
			places = jQuery(this).addClass(fm.res(c, 'tree')+' elfinder-places ui-corner-all')
				.hide()
				.append(wrapper)
				.appendTo(fm.getUI('navbar'))
				.on('mouseenter mouseleave', '.'+navdir, function(e) {
					jQuery(this).toggleClass('ui-state-hover', (e.type == 'mouseenter'));
				})
				.on('click', '.'+navdir, function(e) {
					var p = jQuery(this);
					if (p.data('longtap')) {
						e.stopPropagation();
						return;
					}
					! p.hasClass('elfinder-na') && fm.exec('open', p.attr('id').substr(6));
				})
				.on('contextmenu', '.'+navdir+':not(.'+clroot+')', function(e) {
					var self = jQuery(this),
						hash = self.attr('id').substr(6);
					
					e.preventDefault();

					fm.trigger('contextmenu', {
						raw : [{
							label    : fm.i18n('moveUp'),
							icon     : 'up',
							remain   : true,
							callback : function() { moveup(hash); save(); }
						},'|',{
							label    : fm.i18n('rmFromPlaces'),
							icon     : 'rm',
							callback : function() { remove(hash); save(); }
						}],
						'x'       : e.pageX,
						'y'       : e.pageY
					});
					
					self.addClass('ui-state-hover');
					
					fm.getUI('contextmenu').children().on('mouseenter', function() {
						self.addClass('ui-state-hover');
					});
					
					fm.bind('closecontextmenu', function() {
						self.removeClass('ui-state-hover');
					});
				})
				.droppable({
					tolerance  : 'pointer',
					accept     : '.elfinder-cwd-file-wrapper,.elfinder-tree-dir,.elfinder-cwd-file',
					hoverClass : fm.res('class', 'adroppable'),
					classes    : { // Deprecated hoverClass jQueryUI>=1.12.0
						'ui-droppable-hover': fm.res('class', 'adroppable')
					},
					over       : function(e, ui) {
						var helper = ui.helper,
							dir    = jQuery.grep(helper.data('files'), function(h) { return (fm.file(h).mime === 'directory' && !dirs[h])? true : false; });
						e.stopPropagation();
						helper.data('dropover', helper.data('dropover') + 1);
						if (fm.insideWorkzone(e.pageX, e.pageY)) {
							if (dir.length > 0) {
								helper.addClass('elfinder-drag-helper-plus');
								fm.trigger('unlockfiles', {files : helper.data('files'), helper: helper});
							} else {
								jQuery(this).removeClass(dropover);
							}
						}
					},
					out : function(e, ui) {
						var helper = ui.helper;
						e.stopPropagation();
						helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus').data('dropover', Math.max(helper.data('dropover') - 1, 0));
						jQuery(this).removeData('dropover')
						       .removeClass(dropover);
					},
					drop       : function(e, ui) {
						var helper  = ui.helper,
							resolve = true;
						
						jQuery.each(helper.data('files'), function(i, hash) {
							var dir = fm.file(hash);
							
							if (dir && dir.mime == 'directory' && !dirs[dir.hash]) {
								add(dir);
							} else {
								resolve = false;
							}
						});
						save();
						resolve && helper.hide();
					}
				})
				// for touch device
				.on('touchstart', '.'+navdir+':not(.'+clroot+')', function(e) {
					if (e.originalEvent.touches.length > 1) {
						return;
					}
					var hash = jQuery(this).attr('id').substr(6),
					p = jQuery(this)
					.addClass(hover)
					.data('longtap', null)
					.data('tmlongtap', setTimeout(function(){
						// long tap
						p.data('longtap', true);
						fm.trigger('contextmenu', {
							raw : [{
								label    : fm.i18n('rmFromPlaces'),
								icon     : 'rm',
								callback : function() { remove(hash); save(); }
							}],
							'x'       : e.originalEvent.touches[0].pageX,
							'y'       : e.originalEvent.touches[0].pageY
						});
					}, 500));
				})
				.on('touchmove touchend', '.'+navdir+':not(.'+clroot+')', function(e) {
					clearTimeout(jQuery(this).data('tmlongtap'));
					if (e.type == 'touchmove') {
						jQuery(this).removeClass(hover);
					}
				});

		if (jQuery.fn.sortable) {
			subtree.addClass('touch-punch')
			.sortable({
				appendTo : fm.getUI(),
				revert   : false,
				helper   : function(e) {
					var dir = jQuery(e.target).parent();
						
					dir.children().removeClass('ui-state-hover');
					
					return jQuery('<div class="ui-widget elfinder-place-drag elfinder-'+fm.direction+'"/>')
							.append(jQuery('<div class="elfinder-navbar"/>').show().append(dir.clone()));

				},
				stop     : function(e, ui) {
					var target = jQuery(ui.item[0]),
						top    = places.offset().top,
						left   = places.offset().left,
						width  = places.width(),
						height = places.height(),
						x      = e.pageX,
						y      = e.pageY;
					
					if (!(x > left && x < left+width && y > top && y < y+height)) {
						remove(id2hash(target.children(':first').attr('id')));
						save();
					}
				},
				update   : function(e, ui) {
					save();
				}
			});
		}

		// "on regist" for command exec
		jQuery(this).on('regist', function(e, files){
			var added = false;
			jQuery.each(files, function(i, dir) {
				if (dir && dir.mime == 'directory' && !dirs[dir.hash]) {
					if (add(dir)) {
						added = true;
					}
				}
			});
			added && save();
		});
	

		// on fm load - show places and load files from backend
		fm.one('load', function() {
			var dat, hashes;
			
			if (fm.oldAPI) {
				return;
			}
			
			places.show().parent().show();

			init();

			fm.change(function(e) {
				var changed = false;
				jQuery.each(e.data.changed, function(i, file) {
					if (dirs[file.hash]) {
						if (file.mime !== 'directory') {
							if (remove(file.hash)) {
								changed = true;
							}
						} else {
							if (update(file)) {
								changed = true;
							}
						}
					}
				});
				changed && save();
			})
			.bind('rename', function(e) {
				var changed = false;
				if (e.data.removed) {
					jQuery.each(e.data.removed, function(i, hash) {
						if (e.data.added[i]) {
							if (update(e.data.added[i], hash)) {
								changed = true;
							}
						}
					});
				}
				changed && save();
			})
			.bind('rm paste', function(e) {
				var names = [],
					changed = false;
				if (e.data.removed) {
					jQuery.each(e.data.removed, function(i, hash) {
						var name = remove(hash);
						name && names.push(name);
					});
				}
				if (names.length) {
					changed = true;
				}
				if (e.data.added && names.length) {
					jQuery.each(e.data.added, function(i, file) {
						if (jQuery.inArray(file.name, names) !== 1) {
							file.mime == 'directory' && add(file);
						}
					});
				}
				changed && save();
			})
			.bind('sync netmount', function() {
				var ev = this,
					opSuffix = opts.suffix? opts.suffix : '',
					hashes;
				
				if (ev.type === 'sync') {
					// check is change of opts.suffix
					if (suffix !== opSuffix) {
						suffix = opSuffix;
						clear();
						init();
						return;
					}
				}
				
				hashes = Object.keys(dirs);
				if (hashes.length) {
					root.prepend(spinner);

					fm.request({
						data : {cmd : 'info', targets : hashes},
						preventDefault : true
					})
					.done(function(data) {
						var exists  = {},
							updated = false,
							cwd     = fm.cwd().hash;
						jQuery.each(data.files || [], function(i, file) {
							var hash = file.hash;
							exists[hash] = file;
							if (!fm.files().hasOwnProperty(file.hash)) {
								// update cache
								fm.trigger('tree', {tree: [file]});
							}
						});
						jQuery.each(dirs, function(h, f) {
							if (f.notfound === Boolean(exists[h])) {
								if ((f.phash === cwd && ev.type !== 'netmount') || (exists[h] && exists[h].mime !== 'directory')) {
									if (remove(h)) {
										updated = true;
									}
								} else {
									if (update(exists[h] || Object.assign({notfound: true}, f))) {
										updated = true;
									}
								}
							} else if (exists[h] && exists[h].phash != cwd) {
								// update permission of except cwd
								update(exists[h]);
							}
						});
						updated && save();
					})
					.always(function() {
						spinner.remove();
					});
				}
			});
			
		});
		
	});
};


/*
 * File: /js/ui/searchbutton.js
 */

/**
 * @class  elFinder toolbar search button widget.
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfindersearchbutton = function(cmd) {
		return this.each(function() {
		var result = false,
			fm     = cmd.fm,
			disabled = fm.res('class', 'disabled'),
			isopts = cmd.options.incsearch || { enable: false },
			sTypes = cmd.options.searchTypes,
			id     = function(name){return fm.namespace + fm.escape(name);},
			toolbar= fm.getUI('toolbar'),
			btnCls = fm.res('class', 'searchbtn'),
			button = jQuery(this)
				.hide()
				.addClass('ui-widget-content elfinder-button '+btnCls)
				.on('click', function(e) {
					e.stopPropagation();
				}),
			getMenuOffset = function() {
				var fmNode = fm.getUI(),
					baseOffset = fmNode.offset(),
					buttonOffset = button.offset();
				return {
					top : buttonOffset.top - baseOffset.top,
					maxHeight : fmNode.height() - 40
				};
			},
			search = function() {
				input.data('inctm') && clearTimeout(input.data('inctm'));
				var val = jQuery.trim(input.val()),
					from = !jQuery('#' + id('SearchFromAll')).prop('checked'),
					mime = jQuery('#' + id('SearchMime')).prop('checked'),
					type = '';
				if (from) {
					if (jQuery('#' + id('SearchFromVol')).prop('checked')) {
						from = fm.root(fm.cwd().hash);
					} else {
						from = fm.cwd().hash;
					}
				}
				if (mime) {
					mime = val;
					val = '.';
				}
				if (typeSet) {
					type = typeSet.children('input:checked').val();
				}
				if (val) {
					input.trigger('focus');
					cmd.exec(val, from, mime, type).done(function() {
						result = true;
					}).fail(function() {
						abort();
					});
					
				} else {
					fm.trigger('searchend');
				}
			},
			abort = function() {
				input.data('inctm') && clearTimeout(input.data('inctm'));
				input.val('').trigger('blur');
				if (result || incVal) {
					result = false;
					incVal = '';
					fm.lazy(function() {
						fm.trigger('searchend');
					});
				}
			},
			incVal = '',
			input  = jQuery('<input type="text" size="42"/>')
				.on('focus', function() {
					// close other menus
					!button.hasClass('ui-state-active') && fm.getUI().click();
					inFocus = true;
					incVal = '';
					button.addClass('ui-state-active');
					fm.trigger('uiresize');
					opts && opts.css(getMenuOffset()).slideDown(function() {
						// Care for on browser window re-active
						button.addClass('ui-state-active');
						fm.toFront(opts);
					});
				})
				.on('blur', function() {
					inFocus = false;
					if (opts) {
						if (!opts.data('infocus')) {
							opts.slideUp(function() {
								button.removeClass('ui-state-active');
								fm.trigger('uiresize');
								fm.toHide(opts);
							});
						} else {
							opts.data('infocus', false);
						}
					} else {
						button.removeClass('ui-state-active');
					}
				})
				.appendTo(button)
				// to avoid fm shortcuts on arrows
				.on('keypress', function(e) {
					e.stopPropagation();
				})
				.on('keydown', function(e) {
					e.stopPropagation();
					if (e.keyCode === jQuery.ui.keyCode.ENTER) {
						search();
					} else if (e.keyCode === jQuery.ui.keyCode.ESCAPE) {
						e.preventDefault();
						abort();
					}
				}),
			opts, typeSet, cwdReady, inFocus;
		
		if (isopts.enable) {
			isopts.minlen = isopts.minlen || 2;
			isopts.wait = isopts.wait || 500;
			input
				.attr('title', fm.i18n('incSearchOnly'))
				.on('compositionstart', function() {
					input.data('composing', true);
				})
				.on('compositionend', function() {
					input.removeData('composing');
					input.trigger('input'); // for IE, edge
				})
				.on('input', function() {
					if (! input.data('composing')) {
						input.data('inctm') && clearTimeout(input.data('inctm'));
						input.data('inctm', setTimeout(function() {
							var val = input.val();
							if (val.length === 0 || val.length >= isopts.minlen) {
								(incVal !== val) && fm.trigger('incsearchstart', { query: val });
								incVal = val;
								if (val === '' && fm.searchStatus.state > 1 && fm.searchStatus.query) {
									input.val(fm.searchStatus.query).trigger('select');
								} 
							}
						}, isopts.wait));
					}
				});
			
			if (fm.UA.ltIE8) {
				input.on('keydown', function(e) {
						if (e.keyCode === 229) {
							input.data('imetm') && clearTimeout(input.data('imetm'));
							input.data('composing', true);
							input.data('imetm', setTimeout(function() {
								input.removeData('composing');
							}, 100));
						}
					})
					.on('keyup', function(e) {
						input.data('imetm') && clearTimeout(input.data('imetm'));
						if (input.data('composing')) {
							e.keyCode === jQuery.ui.keyCode.ENTER && input.trigger('compositionend');
						} else {
							input.trigger('input');
						}
					});
			}
		}
		
		jQuery('<span class="ui-icon ui-icon-search" title="'+cmd.title+'"/>')
			.appendTo(button)
			.on('mousedown', function(e) {
				e.stopPropagation();
				e.preventDefault();
				if (button.hasClass('ui-state-active')) {
					search();
				} else {
					input.trigger('focus');
				}
			});
		
		jQuery('<span class="ui-icon ui-icon-close"/>')
			.appendTo(button)
			.on('mousedown', function(e) {
				e.stopPropagation();
				e.preventDefault();
				if (input.val() === '' && !button.hasClass('ui-state-active')) {
					input.trigger('focus');
				} else {
					abort();
				}
			});
		
		// wait when button will be added to DOM
		fm.bind('toolbarload', function(){
			var parent = button.parent();
			if (parent.length) {
				toolbar.prepend(button.show());
				parent.remove();
				// position icons for ie7
				if (fm.UA.ltIE7) {
					var icon = button.children(fm.direction == 'ltr' ? '.ui-icon-close' : '.ui-icon-search');
					icon.css({
						right : '',
						left  : parseInt(button.width())-icon.outerWidth(true)
					});
				}
			}
		});
		
		fm
			.one('init', function() {
				fm.getUI('cwd').on('touchstart click', function() {
					inFocus && input.trigger('blur');
				});
			})
			.one('open', function() {
				opts = (fm.api < 2.1)? null : jQuery('<div class="ui-front ui-widget ui-widget-content elfinder-button-menu elfinder-button-search-menu ui-corner-all"/>')
					.append(
						jQuery('<div class="buttonset"/>')
							.append(
								jQuery('<input id="'+id('SearchFromCwd')+'" name="serchfrom" type="radio" checked="checked"/><label for="'+id('SearchFromCwd')+'">'+fm.i18n('btnCwd')+'</label>'),
								jQuery('<input id="'+id('SearchFromVol')+'" name="serchfrom" type="radio"/><label for="'+id('SearchFromVol')+'">'+fm.i18n('btnVolume')+'</label>'),
								jQuery('<input id="'+id('SearchFromAll')+'" name="serchfrom" type="radio"/><label for="'+id('SearchFromAll')+'">'+fm.i18n('btnAll')+'</label>')
							),
						jQuery('<div class="buttonset elfinder-search-type"/>')
							.append(
								jQuery('<input id="'+id('SearchName')+'" name="serchcol" type="radio" checked="checked" value="SearchName"/><label for="'+id('SearchName')+'">'+fm.i18n('btnFileName')+'</label>')
							)
					)
					.hide()
					.appendTo(fm.getUI());
				if (opts) {
					if (sTypes) {
						typeSet = opts.find('.elfinder-search-type');
						jQuery.each(cmd.options.searchTypes, function(i, v) {
							typeSet.append(jQuery('<input id="'+id(i)+'" name="serchcol" type="radio" value="'+fm.escape(i)+'"/><label for="'+id(i)+'">'+fm.i18n(v.name)+'</label>'));
						});
					}
					opts.find('div.buttonset').buttonset();
					jQuery('#'+id('SearchFromAll')).next('label').attr('title', fm.i18n('searchTarget', fm.i18n('btnAll')));
					if (sTypes) {
						jQuery.each(sTypes, function(i, v) {
							if (v.title) {
								jQuery('#'+id(i)).next('label').attr('title', fm.i18n(v.title));
							}
						});
					}
					opts.on('mousedown', 'div.buttonset', function(e){
							e.stopPropagation();
							opts.data('infocus', true);
						})
						.on('click', 'input', function(e) {
							e.stopPropagation();
							jQuery.trim(input.val())? search() : input.trigger('focus');
						})
						.on('close', function() {
							input.trigger('blur');
						});
				}
			})
			.bind('searchend', function() {
				input.val('');
			})
			.bind('open parents', function() {
				var dirs    = [],
					volroot = fm.file(fm.root(fm.cwd().hash));
				
				if (volroot) {
					jQuery.each(fm.parents(fm.cwd().hash), function(i, hash) {
						dirs.push(fm.file(hash).name);
					});
		
					jQuery('#'+id('SearchFromCwd')).next('label').attr('title', fm.i18n('searchTarget', dirs.join(fm.option('separator'))));
					jQuery('#'+id('SearchFromVol')).next('label').attr('title', fm.i18n('searchTarget', volroot.name));
				}
			})
			.bind('open', function() {
				incVal && abort();
			})
			.bind('cwdinit', function() {
				cwdReady = false;
			})
			.bind('cwdrender',function() {
				cwdReady = true;
			})
			.bind('keydownEsc', function() {
				if (incVal && incVal.substr(0, 1) === '/') {
					incVal = '';
					input.val('');
					fm.trigger('searchend');
				}
			})
			.shortcut({
				pattern     : 'ctrl+f f3',
				description : cmd.title,
				callback    : function() { 
					input.trigger('select').trigger('focus');
				}
			})
			.shortcut({
				pattern     : 'a b c d e f g h i j k l m n o p q r s t u v w x y z dig0 dig1 dig2 dig3 dig4 dig5 dig6 dig7 dig8 dig9 num0 num1 num2 num3 num4 num5 num6 num7 num8 num9',
				description : fm.i18n('firstLetterSearch'),
				callback    : function(e) { 
					if (! cwdReady) { return; }
					
					var code = e.originalEvent.keyCode,
						next = function() {
							var sel = fm.selected(),
								key = jQuery.ui.keyCode[(!sel.length || fm.cwdHash2Elm(sel[0]).next('[id]').length)? 'RIGHT' : 'HOME'];
							jQuery(document).trigger(jQuery.Event('keydown', { keyCode: key, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
						},
						val;
					if (code >= 96 && code <= 105) {
						code -= 48;
					}
					val = '/' + String.fromCharCode(code);
					if (incVal !== val) {
						input.val(val);
						incVal = val;
						fm
							.trigger('incsearchstart', { query: val })
							.one('cwdrender', next);
					} else{
						next();
					}
				}
			});

	});
};


/*
 * File: /js/ui/sortbutton.js
 */

/**
 * @class  elFinder toolbar button menu with sort variants.
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfindersortbutton = function(cmd) {
		return this.each(function() {
		var fm       = cmd.fm,
			name     = cmd.name,
			c        = 'class',
			disabled = fm.res(c, 'disabled'),
			hover    = fm.res(c, 'hover'),
			item     = 'elfinder-button-menu-item',
			selected = item+'-selected',
			asc      = selected+'-asc',
			desc     = selected+'-desc',
			text     = jQuery('<span class="elfinder-button-text">'+cmd.title+'</span>'),
			button   = jQuery(this).addClass('ui-state-default elfinder-button elfinder-menubutton elfiner-button-'+name)
				.attr('title', cmd.title)
				.append('<span class="elfinder-button-icon elfinder-button-icon-'+name+'"/>', text)
				.on('mouseenter mouseleave', function(e) { !button.hasClass(disabled) && button.toggleClass(hover, e.type === 'mouseenter'); })
				.on('click', function(e) {
					if (!button.hasClass(disabled)) {
						e.stopPropagation();
						menu.is(':hidden') && fm.getUI().click();
						menu.css(getMenuOffset()).slideToggle({
							duration: 100,
							done: function(e) {
								fm[menu.is(':visible')? 'toFront' : 'toHide'](menu);
							}
						});
					}
				}),
			hide = function() { fm.toHide(menu); },
			menu = jQuery('<div class="ui-front ui-widget ui-widget-content elfinder-button-menu ui-corner-all"/>')
				.hide()
				.appendTo(fm.getUI())
				.on('mouseenter mouseleave', '.'+item, function(e) { jQuery(this).toggleClass(hover, e.type === 'mouseenter'); })
				.on('click', function(e) {
					e.preventDefault();
					e.stopPropagation();
				})
				.on('close', hide),
			update = function() {
				menu.children('[rel]').removeClass(selected+' '+asc+' '+desc)
					.filter('[rel="'+fm.sortType+'"]')
					.addClass(selected+' '+(fm.sortOrder == 'asc' ? asc : desc));

				menu.children('.elfinder-sort-stick').toggleClass(selected, fm.sortStickFolders);
				menu.children('.elfinder-sort-tree').toggleClass(selected, fm.sortAlsoTreeview);
			},
			getMenuOffset = function() {
				var baseOffset = fm.getUI().offset(),
					buttonOffset = button.offset();
				return {
					top : buttonOffset.top - baseOffset.top,
					left : buttonOffset.left - baseOffset.left
				};
			},
			tm;
			
		text.hide();
		
		jQuery.each(fm.sortRules, function(name, value) {
			menu.append(jQuery('<div class="'+item+'" rel="'+name+'"><span class="ui-icon ui-icon-arrowthick-1-n"/><span class="ui-icon ui-icon-arrowthick-1-s"/>'+fm.i18n('sort'+name)+'</div>').data('type', name));
		});
		
		menu.children().on('click', function(e) {
			cmd.exec([], jQuery(this).removeClass(hover).attr('rel'));
		});
		
		jQuery('<div class="'+item+' '+item+'-separated elfinder-sort-ext elfinder-sort-stick"><span class="ui-icon ui-icon-check"/>'+fm.i18n('sortFoldersFirst')+'</div>')
			.appendTo(menu)
			.on('click', function() {
				cmd.exec([], 'stick');
			});

		fm.one('init', function() {
			if (fm.ui.tree && fm.options.sortAlsoTreeview !== null) {
				jQuery('<div class="'+item+' '+item+'-separated elfinder-sort-ext elfinder-sort-tree"><span class="ui-icon ui-icon-check"/>'+fm.i18n('sortAlsoTreeview')+'</div>')
				.appendTo(menu)
				.on('click', function() {
					cmd.exec([], 'tree');
				});
			}
		})
		.bind('disable select', hide)
		.bind('open', function() {
			menu.children('[rel]').each(function() {
				var $this = jQuery(this);
				$this.toggle(fm.sorters[$this.attr('rel')]);
			});
		}).bind('sortchange', update).getUI().on('click', hide);
		
		if (menu.children().length > 1) {
			cmd.change(function() {
					tm && cancelAnimationFrame(tm);
					tm = requestAnimationFrame(function() {
						button.toggleClass(disabled, cmd.disabled());
						update();
					});
				})
				.change();
		} else {
			button.addClass(disabled);
		}

	});
	
};


/*
 * File: /js/ui/stat.js
 */

/**
 * @class elFinder ui
 * Display number of files/selected files and its size in statusbar
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfinderstat = function(fm) {
		return this.each(function() {
		var size       = jQuery(this).addClass('elfinder-stat-size'),
			sel        = jQuery('<div class="elfinder-stat-selected"/>')
				.on('click', 'a', function(e) {
					var hash = jQuery(this).data('hash');
					e.preventDefault();
					fm.exec('opendir', [ hash ]);
				}),
			titleitems = fm.i18n('items'),
			titlesel   = fm.i18n('selected'),
			titlesize  = fm.i18n('size'),
			setstat    = function(files) {
				var c = 0, 
					s = 0,
					cwd = fm.cwd(),
					calc = true,
					hasSize = true;

				if (cwd.sizeInfo || cwd.size) {
					s = cwd.size;
					calc = false;
				}
				jQuery.each(files, function(i, file) {
					c++;
					if (calc) {
						s += parseInt(file.size) || 0;
						if (hasSize === true && file.mime === 'directory' && !file.sizeInfo) {
							hasSize = false;
						}
					}
				});
				size.html(titleitems+': <span class="elfinder-stat-incsearch"></span>'+c+',&nbsp;<span class="elfinder-stat-size'+(hasSize? ' elfinder-stat-size-recursive' : '')+'">'+fm.i18n(hasSize? 'sum' : 'size')+': '+fm.formatSize(s)+'</span>')
					.attr('title', size.text());
				fm.trigger('uistatchange');
			},
			setIncsearchStat = function(data) {
				size.find('span.elfinder-stat-incsearch').html(data? data.hashes.length + ' / ' : '');
				size.attr('title', size.text());
				fm.trigger('uistatchange');
			},
			setSelect = function(files) {
				var s = 0,
					c = 0,
					dirs = [],
					path, file;

				if (files.length === 1) {
					file = files[0];
					s = file.size;
					if (fm.searchStatus.state === 2) {
						path = fm.escape(file.path? file.path.replace(/\/[^\/]*$/, '') : '..');
						dirs.push('<a href="#elf_'+file.phash+'" data-hash="'+file.hash+'" title="'+path+'">'+path+'</a>');
					}
					dirs.push(fm.escape(file.i18 || file.name));
					sel.html(dirs.join('/') + (s > 0 ? ', '+fm.formatSize(s) : ''));
				} else if (files.length) {
					jQuery.each(files, function(i, file) {
						c++;
						s += parseInt(file.size)||0;
					});
					sel.html(c ? titlesel+': '+c+', '+titlesize+': '+fm.formatSize(s) : '&nbsp;');
				} else {
					sel.html('');
				}
				sel.attr('title', sel.text());
				fm.trigger('uistatchange');
			};

		fm.getUI('statusbar').prepend(size).append(sel).show();
		if (fm.UA.Mobile && jQuery.fn.tooltip) {
			fm.getUI('statusbar').tooltip({
				classes: {
					'ui-tooltip': 'elfinder-ui-tooltip ui-widget-shadow'
				},
				tooltipClass: 'elfinder-ui-tooltip ui-widget-shadow',
				track: true
			});
		}
		
		fm
		.bind('cwdhasheschange', function(e) {
			setstat(jQuery.map(e.data, function(h) { return fm.file(h); }));
		})
		.change(function(e) {
			var files = e.data.changed || [],
				cwdHash = fm.cwd().hash;
			jQuery.each(files, function() {
				if (this.hash === cwdHash) {
					if (this.size) {
						size.children('.elfinder-stat-size').addClass('elfinder-stat-size-recursive').html(fm.i18n('sum')+': '+fm.formatSize(this.size));
						size.attr('title', size.text());
					}
					return false;
				}
			});
		})
		.select(function() {
			setSelect(fm.selectedFiles());
		})
		.bind('open', function() {
			setSelect([]);
		})
		.bind('incsearch', function(e) {
			setIncsearchStat(e.data);
		})
		.bind('incsearchend', function() {
			setIncsearchStat();
		})
		;
	});
};


/*
 * File: /js/ui/toast.js
 */

/**
 * @class  elFinder toast
 * 
 * This was created inspired by the toastr. Thanks to developers of toastr.
 * CodeSeven/toastr: http://johnpapa.net <https://github.com/CodeSeven/toastr>
 *
 * @author Naoki Sawada
 **/
jQuery.fn.elfindertoast = function(opts, fm) {
		var defOpts = Object.assign({
		mode: 'success', // or 'info', 'warning' and 'error'
		msg: '',
		showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
		showDuration: 300,
		showEasing: 'swing', //swing and linear are built into jQuery
		onShown: undefined,
		hideMethod: 'fadeOut',
		hideDuration: 1500,
		hideEasing: 'swing',
		onHidden: undefined,
		timeOut: 3000,
		extNode: undefined,
		button: undefined,
		width: undefined
	}, jQuery.isPlainObject(fm.options.uiOptions.toast.defaults)? fm.options.uiOptions.toast.defaults : {});
	return this.each(function() {
		opts = Object.assign({}, defOpts, opts || {});
		
		var self = jQuery(this),
			show = function(notm) {
				self.stop();
				fm.toFront(self);
				self[opts.showMethod]({
					duration: opts.showDuration,
					easing: opts.showEasing,
					complete: function() {
						opts.onShown && opts.onShown();
						if (!notm && opts.timeOut) {
							rmTm = setTimeout(rm, opts.timeOut);
						}
					}
				});
			},
			rm = function() {
				self[opts.hideMethod]({
					duration: opts.hideDuration,
					easing: opts.hideEasing,
					complete: function() {
						opts.onHidden && opts.onHidden();
						self.remove();
					}
				});
			},
			rmTm;
		
		self.on('click', function(e) {
			e.stopPropagation();
			e.preventDefault();
			rmTm && clearTimeout(rmTm);
			opts.onHidden && opts.onHidden();
			self.stop().remove();
		}).on('mouseenter mouseleave', function(e) {
			if (opts.timeOut) {
				rmTm && clearTimeout(rmTm);
				rmTm = null;
				if (e.type === 'mouseenter') {
					show(true);
				} else {
					rmTm = setTimeout(rm, opts.timeOut);
				}
			}
		}).hide().addClass('toast-' + opts.mode).append(jQuery('<div class="elfinder-toast-msg"/>').html(opts.msg.replace(/%([a-zA-Z0-9]+)%/g, function(m, m1) {
			return fm.i18n(m1);
		})));
		
		if (opts.extNode) {
			self.append(opts.extNode);
		}

		if (opts.button) {
			self.append(
				jQuery('<button class="ui-button ui-widget ui-state-default ui-corner-all elfinder-tabstop"/>')
				.append(jQuery('<span class="ui-button-text"/>').text(fm.i18n(opts.button.text)))
				.on('mouseenter mouseleave', function(e) { 
					jQuery(this).toggleClass('ui-state-hover', e.type == 'mouseenter');
				})
				.on('click', opts.button.click || function(){})
			);
		}

		if (opts.width) {
			self.css('max-width', opts.width);
		}
		
		show();
	});
};

/*
 * File: /js/ui/toolbar.js
 */

/**
 * @class  elFinder toolbar
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfindertoolbar = function(fm, opts) {
		this.not('.elfinder-toolbar').each(function() {
		var commands = fm._commands,
			self     = jQuery(this).addClass('ui-helper-clearfix ui-widget-header elfinder-toolbar'),
			options  = {
				// default options
				displayTextLabel: false,
				labelExcludeUA: ['Mobile'],
				autoHideUA: ['Mobile'],
				showPreferenceButton: 'none'
			},
			filter   = function(opts) {
				return jQuery.grep(opts, function(v) {
					if (jQuery.isPlainObject(v)) {
						options = Object.assign(options, v);
						return false;
					}
					return true;
				});
			},
			render = function(disabled){
				var name,cmdPref;
				
				jQuery.each(buttons, function(i, b) { b.detach(); });
				self.empty();
				l = panels.length;
				while (l--) {
					if (panels[l]) {
						panel = jQuery('<div class="ui-widget-content ui-corner-all elfinder-buttonset"/>');
						i = panels[l].length;
						while (i--) {
							name = panels[l][i];
							if ((!disabled || !disabled[name]) && (cmd = commands[name])) {
								button = 'elfinder'+cmd.options.ui;
								if (! buttons[name] && jQuery.fn[button]) {
									buttons[name] = jQuery('<div/>')[button](cmd);
								}
								if (buttons[name]) {
									buttons[name].children('.elfinder-button-text')[textLabel? 'show' : 'hide']();
									panel.prepend(buttons[name]);
								}
							}
						}
						
						panel.children().length && self.prepend(panel);
						panel.children(':gt(0)').before('<span class="ui-widget-content elfinder-toolbar-button-separator"/>');

					}
				}
				
				if (cmdPref = commands['preference']) {
					//cmdPref.state = !self.children().length? 0 : -1;
					if (options.showPreferenceButton === 'always' || (!self.children().length && options.showPreferenceButton === 'auto')) {
						//cmdPref.state = 0;
						panel = jQuery('<div class="ui-widget-content ui-corner-all elfinder-buttonset"/>');
						name = 'preference';
						button = 'elfinder'+cmd.options.ui;
						buttons[name] = jQuery('<div/>')[button](cmdPref);
						buttons[name].children('.elfinder-button-text')[textLabel? 'show' : 'hide']();
						panel.prepend(buttons[name]);
						self.append(panel);
					}
				}
				
				(! self.data('swipeClose') && self.children().length)? self.show() : self.hide();
				prevHeight = self[0].clientHeight;
				fm.trigger('toolbarload').trigger('uiresize');
			},
			buttons = {},
			panels   = filter(opts || []),
			dispre   = null,
			uiCmdMapPrev = '',
			prevHeight = 0,
			contextRaw = [],
			l, i, cmd, panel, button, swipeHandle, autoHide, textLabel, resizeTm;
		
		// normalize options
		options.showPreferenceButton = options.showPreferenceButton.toLowerCase();
		
		if (options.displayTextLabel !== 'none') {
			// correction of options.displayTextLabel
			textLabel = fm.storage('toolbarTextLabel');
			if (textLabel === null) {
				textLabel = (options.displayTextLabel && (! options.labelExcludeUA || ! options.labelExcludeUA.length || ! jQuery.grep(options.labelExcludeUA, function(v){ return fm.UA[v]? true : false; }).length));
			} else {
				textLabel = (textLabel == 1);
			}
			contextRaw.push({
				label    : fm.i18n('textLabel'),
				icon     : 'text',
				callback : function() {
					textLabel = ! textLabel;
					self.css('height', '').find('.elfinder-button-text')[textLabel? 'show':'hide']();
					fm.trigger('uiresize').storage('toolbarTextLabel', textLabel? '1' : '0');
				},
			});
		}

		if (options.preferenceInContextmenu && commands['preference']) {
			contextRaw.push({
				label    : fm.i18n('toolbarPref'),
				icon     : 'preference',
				callback : function() {
					fm.exec('preference', void(0), {tab: 'toolbar'});
				}
			});
		}

		// add contextmenu
		if (contextRaw.length) {
			self.on('contextmenu', function(e) {
					e.stopPropagation();
					e.preventDefault();
					fm.trigger('contextmenu', {
						raw: contextRaw,
						x: e.pageX,
						y: e.pageY
					});
				}).on('touchstart', function(e) {
					if (e.originalEvent.touches.length > 1) {
						return;
					}
					self.data('tmlongtap') && clearTimeout(self.data('tmlongtap'));
					self.removeData('longtap')
						.data('longtap', {x: e.originalEvent.touches[0].pageX, y: e.originalEvent.touches[0].pageY})
						.data('tmlongtap', setTimeout(function() {
							self.removeData('longtapTm')
								.trigger({
									type: 'contextmenu',
									pageX: self.data('longtap').x,
									pageY: self.data('longtap').y
								})
								.data('longtap', {longtap: true});
						}, 500));
				}).on('touchmove touchend', function(e) {
					if (self.data('tmlongtap')) {
						if (e.type === 'touchend' ||
								( Math.abs(self.data('longtap').x - e.originalEvent.touches[0].pageX)
								+ Math.abs(self.data('longtap').y - e.originalEvent.touches[0].pageY)) > 4)
						clearTimeout(self.data('tmlongtap'));
						self.removeData('longtapTm');
					}
				}).on('click', function(e) {
					if (self.data('longtap') && self.data('longtap').longtap) {
						e.stopImmediatePropagation();
						e.preventDefault();
					}
				}).on('touchend click', '.elfinder-button', function(e) {
					if (self.data('longtap') && self.data('longtap').longtap) {
						e.stopImmediatePropagation();
						e.preventDefault();
					}
				}
			);
		}

		self.prev().length && self.parent().prepend(this);
		
		render();
		
		fm.bind('open sync select toolbarpref', function() {
			var disabled = Object.assign({}, fm.option('disabledFlip')),
				userHides = fm.storage('toolbarhides'),
				doRender, sel, disabledKeys;
			
			if (! userHides && Array.isArray(options.defaultHides)) {
				userHides = {};
				jQuery.each(options.defaultHides, function() {
					userHides[this] = true;
				});
				fm.storage('toolbarhides', userHides);
			}
			if (this.type === 'select') {
				if (fm.searchStatus.state < 2) {
					return;
				}
				sel = fm.selected();
				if (sel.length) {
					disabled = fm.getDisabledCmds(sel, true);
				}
			}
			
			jQuery.each(userHides, function(n) {
				if (!disabled[n]) {
					disabled[n] = true;
				}
			});
			
			if (Object.keys(fm.commandMap).length) {
				jQuery.each(fm.commandMap, function(from, to){
					if (to === 'hidden') {
						disabled[from] = true;
					}
				});
			}
			
			disabledKeys = Object.keys(disabled);
			if (!dispre || dispre.toString() !== disabledKeys.sort().toString()) {
				render(disabledKeys.length? disabled : null);
				doRender = true;
			}
			dispre = disabledKeys.sort();

			if (doRender || uiCmdMapPrev !== JSON.stringify(fm.commandMap)) {
				uiCmdMapPrev = JSON.stringify(fm.commandMap);
				if (! doRender) {
					// reset toolbar
					jQuery.each(jQuery('div.elfinder-button'), function(){
						var origin = jQuery(this).data('origin');
						if (origin) {
							jQuery(this).after(origin).detach();
						}
					});
				}
				if (Object.keys(fm.commandMap).length) {
					jQuery.each(fm.commandMap, function(from, to){
						var cmd = fm._commands[to],
							button = cmd? 'elfinder'+cmd.options.ui : null,
							btn;
						if (button && jQuery.fn[button]) {
							btn = buttons[from];
							if (btn) {
								if (! buttons[to] && jQuery.fn[button]) {
									buttons[to] = jQuery('<div/>')[button](cmd);
									if (buttons[to]) {
										buttons[to].children('.elfinder-button-text')[textLabel? 'show' : 'hide']();
										if (cmd.extendsCmd) {
											buttons[to].children('span.elfinder-button-icon').addClass('elfinder-button-icon-' + cmd.extendsCmd);
										}
									}
								}
								if (buttons[to]) {
									btn.after(buttons[to]);
									buttons[to].data('origin', btn.detach());
								}
							}
						}
					});
				}
			}
		}).bind('resize', function(e) {
			resizeTm && cancelAnimationFrame(resizeTm);
			resizeTm = requestAnimationFrame(function() {
				var h = self[0].clientHeight;
				if (prevHeight !== h) {
					prevHeight = h;
					fm.trigger('uiresize');
				}
			});
		});
		
		if (fm.UA.Touch) {
			autoHide = fm.storage('autoHide') || {};
			if (typeof autoHide.toolbar === 'undefined') {
				autoHide.toolbar = (options.autoHideUA && options.autoHideUA.length > 0 && jQuery.grep(options.autoHideUA, function(v){ return fm.UA[v]? true : false; }).length);
				fm.storage('autoHide', autoHide);
			}
			
			if (autoHide.toolbar) {
				fm.one('init', function() {
					fm.uiAutoHide.push(function(){ self.stop(true, true).trigger('toggle', { duration: 500, init: true }); });
				});
			}
			
			fm.bind('load', function() {
				swipeHandle = jQuery('<div class="elfinder-toolbar-swipe-handle"/>').hide().appendTo(fm.getUI());
				if (swipeHandle.css('pointer-events') !== 'none') {
					swipeHandle.remove();
					swipeHandle = null;
				}
			});
			
			self.on('toggle', function(e, data) {
				var wz    = fm.getUI('workzone'),
					toshow= self.is(':hidden'),
					wzh   = wz.height(),
					h     = self.height(),
					tbh   = self.outerHeight(true),
					delta = tbh - h,
					opt   = Object.assign({
						step: function(now) {
							wz.height(wzh + (toshow? (now + delta) * -1 : h - now));
							fm.trigger('resize');
						},
						always: function() {
							requestAnimationFrame(function() {
								self.css('height', '');
								fm.trigger('uiresize');
								if (swipeHandle) {
									if (toshow) {
										swipeHandle.stop(true, true).hide();
									} else {
										swipeHandle.height(data.handleH? data.handleH : '');
										fm.resources.blink(swipeHandle, 'slowonce');
									}
								}
								toshow && self.scrollTop('0px');
								data.init && fm.trigger('uiautohide');
							});
						}
					}, data);
				self.data('swipeClose', ! toshow).stop(true, true).animate({height : 'toggle'}, opt);
				autoHide.toolbar = !toshow;
				fm.storage('autoHide', Object.assign(fm.storage('autoHide'), {toolbar: autoHide.toolbar}));
			}).on('touchstart', function(e) {
				if (self.scrollBottom() > 5) {
					e.originalEvent._preventSwipeY = true;
				}
			});
		}
	});
	
	return this;
};


/*
 * File: /js/ui/tree.js
 */

/**
 * @class  elFinder folders tree
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfindertree = function(fm, opts) {
		var treeclass = fm.res('class', 'tree');
	
	this.not('.'+treeclass).each(function() {

		var c = 'class', mobile = fm.UA.Mobile,
			
			/**
			 * Root directory class name
			 *
			 * @type String
			 */
			root      = fm.res(c, 'treeroot'),

			/**
			 * Open root dir if not opened yet
			 *
			 * @type Boolean
			 */
			openRoot  = opts.openRootOnLoad,

			/**
			 * Open current work dir if not opened yet
			 *
			 * @type Boolean
			 */
			openCwd   = opts.openCwdOnOpen,

			
			/**
			 * Auto loading current directory parents and do expand their node
			 *
			 * @type Boolean
			 */
			syncTree  = openCwd || opts.syncTree,
			
			/**
			 * Subtree class name
			 *
			 * @type String
			 */
			subtree   = fm.res(c, 'navsubtree'),
			
			/**
			 * Directory class name
			 *
			 * @type String
			 */
			navdir    = fm.res(c, 'treedir'),
			
			/**
			 * Directory CSS selector
			 *
			 * @type String
			 */
			selNavdir = 'span.' + navdir,
			
			/**
			 * Collapsed arrow class name
			 *
			 * @type String
			 */
			collapsed = fm.res(c, 'navcollapse'),
			
			/**
			 * Expanded arrow class name
			 *
			 * @type String
			 */
			expanded  = fm.res(c, 'navexpand'),
			
			/**
			 * Class name to mark arrow for directory with already loaded children
			 *
			 * @type String
			 */
			loaded    = 'elfinder-subtree-loaded',
			
			/**
			 * Class name to mark need subdirs request
			 *
			 * @type String
			 */
			chksubdir = 'elfinder-subtree-chksubdir',
			
			/**
			 * Arraw class name
			 *
			 * @type String
			 */
			arrow = fm.res(c, 'navarrow'),
			
			/**
			 * Current directory class name
			 *
			 * @type String
			 */
			active    = fm.res(c, 'active'),
			
			/**
			 * Droppable dirs dropover class
			 *
			 * @type String
			 */
			dropover = fm.res(c, 'adroppable'),
			
			/**
			 * Hover class name
			 *
			 * @type String
			 */
			hover    = fm.res(c, 'hover'),
			
			/**
			 * Disabled dir class name
			 *
			 * @type String
			 */
			disabled = fm.res(c, 'disabled'),
			
			/**
			 * Draggable dir class name
			 *
			 * @type String
			 */
			draggable = fm.res(c, 'draggable'),
			
			/**
			 * Droppable dir  class name
			 *
			 * @type String
			 */
			droppable = fm.res(c, 'droppable'),
			
			/**
			 * root wrapper class
			 * 
			 * @type String
			 */
			wrapperRoot = 'elfinder-navbar-wrapper-root',

			/**
			 * Un-disabled cmd `paste` volume's root wrapper class
			 * 
			 * @type String
			 */
			pastable = 'elfinder-navbar-wrapper-pastable',
			
			/**
			 * Un-disabled cmd `upload` volume's root wrapper class
			 * 
			 * @type String
			 */
			uploadable = 'elfinder-navbar-wrapper-uploadable',
			
			/**
			 * Is position x inside Navbar
			 * 
			 * @param x Numbar
			 * 
			 * @return
			 */
			insideNavbar = function(x) {
				var left = navbar.offset().left;
					
				return left <= x && x <= left + navbar.width();
			},
			
			/**
			 * To call subdirs elements queue
			 * 
			 * @type Object
			 */
			subdirsQue = {},
			
			/**
			 * To exec subdirs elements ids
			 * 
			 */
			subdirsExecQue = [],
			
			/**
			 * Request subdirs to backend
			 * 
			 * @param id String
			 * 
			 * @return Deferred
			 */
			subdirs = function(ids) {
				var targets = [];
				jQuery.each(ids, function(i, id) {
					subdirsQue[id] && targets.push(fm.navId2Hash(id));
					delete subdirsQue[id];
				});
				if (targets.length) {
					return fm.request({
						data: {
							cmd: 'subdirs',
							targets: targets,
							preventDefault : true
						}
					}).done(function(res) {
						if (res && res.subdirs) {
							jQuery.each(res.subdirs, function(hash, subdirs) {
								var elm = fm.navHash2Elm(hash);
								elm.removeClass(chksubdir);
								elm[subdirs? 'addClass' : 'removeClass'](collapsed);
							});
						}
					});
				}
			},
			
			subdirsJobRes = null,
			
			/**
			 * To check target element is in window of subdirs
			 * 
			 * @return void
			 */
			checkSubdirs = function() {
				var ids = Object.keys(subdirsQue);
				if (ids.length) {
					subdirsJobRes && subdirsJobRes._abort();
					execSubdirsTm && clearTimeout(execSubdirsTm);
					subdirsExecQue = [];
					subdirsJobRes = fm.asyncJob(function(id) {
						return fm.isInWindow(jQuery('#'+id))? id : null;
					}, ids, { numPerOnce: 200 })
					.done(function(arr) {
						if (arr.length) {
							subdirsExecQue = arr;
							execSubdirs();
						}
					});
				}
			},
			
			subdirsPending = 0,
			execSubdirsTm,
			
			/**
			 * Exec subdirs as batch request
			 * 
			 * @return void
			 */
			execSubdirs = function() {
				var cnt = opts.subdirsMaxConn - subdirsPending,
					atOnce = fm.maxTargets? Math.min(fm.maxTargets, opts.subdirsAtOnce) : opts.subdirsAtOnce,
					i, ids;
				execSubdirsTm && cancelAnimationFrame(execSubdirsTm);
				if (subdirsExecQue.length) {
					if (cnt > 0) {
						for (i = 0; i < cnt; i++) {
							if (subdirsExecQue.length) {
								subdirsPending++;
								subdirs(subdirsExecQue.splice(0, atOnce)).always(function() {
									subdirsPending--;
									execSubdirs();
								});
							}
						}
					} else {
						execSubdirsTm = requestAnimationFrame(function() {
							subdirsExecQue.length && execSubdirs();
						});
					}
				}
			},
			
			drop = fm.droppable.drop,
			
			/**
			 * Droppable options
			 *
			 * @type Object
			 */
			droppableopts = jQuery.extend(true, {}, fm.droppable, {
				// show subfolders on dropover
				over : function(e, ui) {
					var dst    = jQuery(this),
						helper = ui.helper,
						cl     = hover+' '+dropover,
						hash, status;
					e.stopPropagation();
					helper.data('dropover', helper.data('dropover') + 1);
					dst.data('dropover', true);
					if (ui.helper.data('namespace') !== fm.namespace || ! fm.insideWorkzone(e.pageX, e.pageY)) {
						dst.removeClass(cl);
						helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
						return;
					}
					if (! insideNavbar(e.clientX)) {
						dst.removeClass(cl);
						return;
					}
					helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
					dst.addClass(hover);
					if (dst.is('.'+collapsed+':not(.'+expanded+')')) {
						dst.data('expandTimer', setTimeout(function() {
							dst.is('.'+collapsed+'.'+hover) && dst.children('.'+arrow).trigger('click');
						}, 500));
					}
					if (dst.is('.elfinder-ro,.elfinder-na')) {
						dst.removeClass(dropover);
						//helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
						return;
					}
					hash = fm.navId2Hash(dst.attr('id'));
					dst.data('dropover', hash);
					jQuery.each(ui.helper.data('files'), function(i, h) {
						if (h === hash || (fm.file(h).phash === hash && !ui.helper.hasClass('elfinder-drag-helper-plus'))) {
							dst.removeClass(cl);
							return false; // break jQuery.each
						}
					});
					if (helper.data('locked')) {
						status = 'elfinder-drag-helper-plus';
					} else {
						status = 'elfinder-drag-helper-move';
						if (e.shiftKey || e.ctrlKey || e.metaKey) {
							status += ' elfinder-drag-helper-plus';
						}
					}
					dst.hasClass(dropover) && helper.addClass(status);
					requestAnimationFrame(function(){ dst.hasClass(dropover) && helper.addClass(status); });
				},
				out : function(e, ui) {
					var dst    = jQuery(this),
						helper = ui.helper;
					e.stopPropagation();
					if (insideNavbar(e.clientX)) {
						helper.removeClass('elfinder-drag-helper-move elfinder-drag-helper-plus');
					}
					helper.data('dropover', Math.max(helper.data('dropover') - 1, 0));
					dst.data('expandTimer') && clearTimeout(dst.data('expandTimer'));
					dst.removeData('dropover')
					   .removeClass(hover+' '+dropover);
				},
				deactivate : function() {
					jQuery(this).removeData('dropover')
					       .removeClass(hover+' '+dropover);
				},
				drop : function(e, ui) {
					insideNavbar(e.clientX) && drop.call(this, e, ui);
				}
			}),
			
			spinner = jQuery(fm.res('tpl', 'navspinner')),
			
			/**
			 * Directory html template
			 *
			 * @type String
			 */
			tpl = fm.res('tpl', 'navdir'),
			
			/**
			 * Permissions marker html template
			 *
			 * @type String
			 */
			ptpl = fm.res('tpl', 'perms'),
			
			/**
			 * Lock marker html template
			 *
			 * @type String
			 */
			ltpl = fm.res('tpl', 'lock'),
			
			/**
			 * Symlink marker html template
			 *
			 * @type String
			 */
			stpl = fm.res('tpl', 'symlink'),
			
			/**
			 * Directory hashes that has more pages
			 * 
			 * @type Object
			 */
			hasMoreDirs = {},
			
			/**
			 * Html template replacement methods
			 *
			 * @type Object
			 */
			replace = {
				id          : function(dir) { return fm.navHash2Id(dir.hash); },
				name        : function(dir) { return fm.escape(dir.i18 || dir.name); },
				cssclass    : function(dir) {
					var cname = (dir.phash && ! dir.isroot ? '' : root)+' '+navdir+' '+fm.perms2class(dir);
					dir.dirs && !dir.link && (cname += ' ' + collapsed) && dir.dirs == -1 && (cname += ' ' + chksubdir);
					opts.getClass && (cname += ' ' + opts.getClass(dir));
					dir.csscls && (cname += ' ' + fm.escape(dir.csscls));
					return cname;
				},
				root        : function(dir) {
					var cls = '';
					if (!dir.phash || dir.isroot) {
						cls += ' '+wrapperRoot;
						if (!dir.disabled || dir.disabled.length < 1) {
							cls += ' '+pastable+' '+uploadable;
						} else {
							if (jQuery.inArray('paste', dir.disabled) === -1) {
								cls += ' '+pastable;
							}
							if (jQuery.inArray('upload', dir.disabled) === -1) {
								cls += ' '+uploadable;
							}
						}
						return cls;
					} else {
						return '';
					}
				},
				permissions : function(dir) { return !dir.read || !dir.write ? ptpl : ''; },
				symlink     : function(dir) { return dir.alias ? stpl : ''; },
				style       : function(dir) { return dir.icon ? fm.getIconStyle(dir) : ''; }
			},
			
			/**
			 * Return html for given dir
			 *
			 * @param  Object  directory
			 * @return String
			 */
			itemhtml = function(dir) {
				return tpl.replace(/(?:\{([a-z]+)\})/ig, function(m, key) {
					var res = replace[key] ? replace[key](dir) : (dir[key] || '');
					if (key === 'id' && dir.dirs == -1) {
						subdirsQue[res] = res;
					}
					return res;
				});
			},
			
			/**
			 * Return only dirs from files list
			 *
			 * @param  Array   files list
			 * @param  Boolean do check exists
			 * @return Array
			 */
			filter = function(files, checkExists) {
				return jQuery.map(files || [], function(f) {
					return (f.mime === 'directory' && (!checkExists || fm.navHash2Elm(f.hash).length)) ? f : null;
				});
			},
			
			/**
			 * Find parent subtree for required directory
			 *
			 * @param  String  dir hash
			 * @return jQuery
			 */
			findSubtree = function(hash) {
				return hash ? fm.navHash2Elm(hash).next('.'+subtree) : tree;
			},
			
			/**
			 * Find directory (wrapper) in required node
			 * before which we can insert new directory
			 *
			 * @param  jQuery  parent directory
			 * @param  Object  new directory
			 * @return jQuery
			 */
			findSibling = function(subtree, dir) {
				var node = subtree.children(':first'),
					info;

				while (node.length) {
					info = fm.file(fm.navId2Hash(node.children('[id]').attr('id')));
					
					if ((info = fm.file(fm.navId2Hash(node.children('[id]').attr('id')))) 
					&& compare(dir, info) < 0) {
						return node;
					}
					node = node.next();
				}
				return subtree.children('button.elfinder-navbar-pager-next');
			},
			
			/**
			 * Add new dirs in tree
			 *
			 * @param  Array  dirs list
			 * @return void
			 */
			updateTree = function(dirs) {
				var length  = dirs.length,
					orphans = [],
					i = length,
					tgts = jQuery(),
					done = {},
					cwd = fm.cwd(),
					append = function(parent, dirs, start, direction) {
						var hashes = {},
							curStart = 0,
							max = fm.newAPI? Math.min(10000, Math.max(10, opts.subTreeMax)) : 10000,
							setHashes = function() {
								hashes = {};
								jQuery.each(dirs, function(i, d) {
									hashes[d.hash] = i;
								});
							},
							change = function(mode) {
								if (mode === 'prepare') {
									jQuery.each(dirs, function(i, d) {
										d.node && parent.append(d.node.hide());
									});
								} else if (mode === 'done') {
									jQuery.each(dirs, function(i, d) {
										d.node && d.node.detach().show();
									});
								}
							},
							update = function(e, data) {
								var i, changed;
								e.stopPropagation();
								
								if (data.select) {
									render(getStart(data.select));
									return;
								}
								
								if (data.change) {
									change(data.change);
									return;
								}
								
								if (data.removed && data.removed.length) {
									dirs = jQuery.grep(dirs, function(d) {
										if (data.removed.indexOf(d.hash) === -1) {
											return true;
										} else {
											!changed && (changed = true);
											return false;
										}
									});
								}
								
								if (data.added && data.added.length) {
									dirs = dirs.concat(jQuery.grep(data.added, function(d) {
										if (hashes[d.hash] === void(0)) {
											!changed && (changed = true);
											return true;
										} else {
											return false;
										}
									}));
								}
								if (changed) {
									dirs.sort(compare);
									setHashes();
									render(curStart);
								}
							},
							getStart = function(target) {
								if (hashes[target] !== void(0)) {
									return Math.floor(hashes[target] / max) * max;
								}
								return void(0);
							},
							target = fm.navId2Hash(parent.prev('[id]').attr('id')),
							render = function(start, direction) {
								var html = [],
									nodes = {},
									total, page, s, parts, prev, next, prevBtn, nextBtn;
								delete hasMoreDirs[target];
								curStart = start;
								parent.off('update.'+fm.namespace, update);
								if (dirs.length > max) {
									parent.on('update.'+fm.namespace, update);
									if (start === void(0)) {
										s = 0;
										setHashes();
										start = getStart(cwd.hash);
										if (start === void(0)) {
											start = 0;
										}
									}
									parts = dirs.slice(start, start + max);
									hasMoreDirs[target] = parent;
									prev = start? Math.max(-1, start - max) : -1;
									next = (start + max >= dirs.length)? 0 : start + max;
									total = Math.ceil(dirs.length/max);
									page = Math.ceil(start/max);
								}
								jQuery.each(parts || dirs, function(i, d) {
									html.push(itemhtml(d));
									if (d.node) {
										nodes[d.hash] = d.node;
									}
								});
								if (prev > -1) {
									prevBtn = jQuery('<button class="elfinder-navbar-pager elfinder-navbar-pager-prev"/>')
										.text(fm.i18n('btnPrevious', page, total))
										.button({
											icons: {
												primary: "ui-icon-caret-1-n"
											}
										})
										.on('click', function(e) {
											e.preventDefault();
											e.stopPropagation();
											render(prev, 'up');
										});
								} else {
									prevBtn = jQuery();
								}
								if (next) {
									nextBtn = jQuery('<button class="elfinder-navbar-pager elfinder-navbar-pager-next"/>')
										.text(fm.i18n('btnNext', page + 2, total))
										.button({
											icons: {
												primary: "ui-icon-caret-1-s"
											}
										})
										.on('click', function(e) {
											e.preventDefault();
											e.stopPropagation();
											render(next, 'down');
										});
								} else {
									nextBtn = jQuery();
								}
								detach();
								parent.empty()[parts? 'addClass' : 'removeClass']('elfinder-navbar-hasmore').append(prevBtn, html.join(''), nextBtn);
								jQuery.each(nodes, function(h, n) {
									fm.navHash2Elm(h).parent().replaceWith(n);
								});
								if (direction) {
									autoScroll(fm.navHash2Id(parts[direction === 'up'? parts.length - 1 : 0].hash));
								}
								! mobile && fm.lazy(function() { updateDroppable(null, parent); });
							},
							detach = function() {
								jQuery.each(parent.children('.elfinder-navbar-wrapper'), function(i, elm) {
									var n = jQuery(elm),
										ch = n.children('[id]:first'),
										h, c;
									if (ch.hasClass(loaded)) {
										h = fm.navId2Hash(ch.attr('id'));
										if (h && (c = hashes[h]) !== void(0)) {
											dirs[c].node = n.detach();
										}
									}
								});
							};
						
						render();
					},
					dir, html, parent, sibling, init, atonce = {}, updates = [], base, node,
					firstVol = true; // check for netmount volume
				
				while (i--) {
					dir = dirs[i];

					if (done[dir.hash] || fm.navHash2Elm(dir.hash).length) {
						continue;
					}
					done[dir.hash] = true;
					
					if ((parent = findSubtree(dir.phash)).length) {
						if (dir.phash && ((init = !parent.children().length) || parent.hasClass('elfinder-navbar-hasmore') || (sibling = findSibling(parent, dir)).length)) {
							if (init) {
								if (!atonce[dir.phash]) {
									atonce[dir.phash] = [];
								}
								atonce[dir.phash].push(dir);
							} else {
								if (sibling) {
									node = itemhtml(dir);
									sibling.before(node);
									! mobile && (tgts = tgts.add(node));
								} else {
									updates.push(dir);
								}
							}
						} else {
							node = itemhtml(dir);
							parent[firstVol || dir.phash ? 'append' : 'prepend'](node);
							firstVol = false;
							if (!dir.phash || dir.isroot) {
								base = fm.navHash2Elm(dir.hash).parent();
							}
							! mobile && updateDroppable(null, base);
						}
					} else {
						orphans.push(dir);
					}
				}

				// When init, html append at once
				if (Object.keys(atonce).length){
					jQuery.each(atonce, function(p, dirs){
						var parent = findSubtree(p),
						    html   = [];
						dirs.sort(compare);
						append(parent, dirs);
					});
				}
				
				if (updates.length) {
					parent.trigger('update.' + fm.namespace, { added : updates });
				}
				
				if (orphans.length && orphans.length < length) {
					updateTree(orphans);
					return;
				} 
				
				! mobile && tgts.length && fm.lazy(function() { updateDroppable(tgts); });
				
			},
			
			/**
			 * sort function by dir.name
			 * 
			 */
			compare = function(dir1, dir2) {
				if (! fm.sortAlsoTreeview) {
					return fm.sortRules.name(dir1, dir2);
				} else {
					var asc   = fm.sortOrder == 'asc',
						type  = fm.sortType,
						rules = fm.sortRules,
						res;
					
					res = asc? rules[fm.sortType](dir1, dir2) : rules[fm.sortType](dir2, dir1);
					
					return type !== 'name' && res === 0
						? res = asc ? rules.name(dir1, dir2) : rules.name(dir2, dir1)
						: res;
				}
			},

			/**
			 * Timer ID of autoScroll
			 * 
			 * @type  Integer
			 */
			autoScrTm,

			/**
			 * Auto scroll to cwd
			 *
			 * @return Object  jQuery Deferred
			 */
			autoScroll = function(target) {
				var dfrd = jQuery.Deferred(),
					current, parent, top, treeH, bottom, tgtTop;
				autoScrTm && clearTimeout(autoScrTm);
				autoScrTm = setTimeout(function() {
					current = jQuery(document.getElementById((target || fm.navHash2Id(fm.cwd().hash))));
					if (current.length) {
						// expand parents directory
						(openCwd? current : current.parent()).parents('.elfinder-navbar-wrapper').children('.'+loaded).addClass(expanded).next('.'+subtree).show();
						
						parent = tree.parent().stop(false, true);
						top = parent.offset().top;
						treeH = parent.height();
						bottom = top + treeH - current.outerHeight();
						tgtTop = current.offset().top;
						
						if (tgtTop < top || tgtTop > bottom) {
							parent.animate({
								scrollTop : parent.scrollTop() + tgtTop - top - treeH / 3
							}, {
								duration : opts.durations.autoScroll,
								complete : function() {	dfrd.resolve(); }
							});
						} else {
							dfrd.resolve();
						}
					} else {
						dfrd.reject();
					}
				}, 100);
				return dfrd;
			},
			/**
			 * Get hashes array of items of the bottom of the leaf root back from the target
			 * 
			 * @param Object elFinder item(directory) object
			 * @return Array hashes
			 */
			getEnds = function(d) {
				var cur = d || fm.cwd(),
					res = cur.hash? [ cur.hash ] : [],
					phash, root, dir;
				
				root = fm.root(cur.hash);
				dir = fm.file(root);
				while (dir && (phash = dir.phash)) {
					res.unshift(phash);
					root = fm.root(phash);
					dir = fm.file(root);
					if (fm.navHash2Elm(dir.hash).hasClass(loaded)) {
						break;
					}
				}
				
				return res;
			},
			
			/**
			 * Select pages back in order to display the target
			 * 
			 * @param Object elFinder item(directory) object
			 * @return Object jQuery node object of target node
			 */
			selectPages = function(current) {
				var cur = current || fm.cwd(),
					curHash = cur.hash,
					node = fm.navHash2Elm(curHash);
			
				if (!node.length) {
					while(cur && cur.phash) {
						if (hasMoreDirs[cur.phash] && !fm.navHash2Elm(cur.hash).length) {
							hasMoreDirs[cur.phash].trigger('update.'+fm.namespace, { select : cur.hash });
						}
						cur = fm.file(cur.phash);
					}
					node = fm.navHash2Elm(curHash);
				}
				
				return node;
			},
			
			/**
			 * Flag indicating that synchronization is currently in progress
			 * 
			 * @type Boolean
			 */
			syncing,

			/**
			 * Mark current directory as active
			 * If current directory is not in tree - load it and its parents
			 *
			 * @param Array directory objects of cwd
			 * @param Boolean do auto scroll
			 * @return Object jQuery Deferred
			 */
			sync = function(cwdDirs, aScr) {
				var cwd     = fm.cwd(),
					cwdhash = cwd.hash,
					autoScr = aScr === void(0)? syncTree : aScr,
					loadParents = function(dir) {
						var dfd  = jQuery.Deferred(),
							reqs = [],
							ends = getEnds(dir),
							makeReq = function(cmd, h, until) {
								var data = {
										cmd    : cmd,
										target : h
									};
								if (until) {
									data.until = until;
								}
								return fm.request({
									data : data,
									preventFail : true
								});
							},
							baseHash, baseId;
						
						reqs = jQuery.map(ends, function(h) {
							var d = fm.file(h),
								isRoot = d? fm.isRoot(d) : false,
								node = fm.navHash2Elm(h),
								getPhash = function(h, dep) {
									var d, ph,
										depth = dep || 1;
									ph = (d = fm.file(h))? d.phash : false;
									if (ph && depth > 1) {
										return getPhash(ph, --depth);
									}
									return ph;
								},
								until,
								closest = (function() {
									var phash = getPhash(h);
									until = phash;
									while (phash) {
										if (fm.navHash2Elm(phash).hasClass(loaded)) {
											break;
										}
										until = phash;
										phash = getPhash(phash);
									}
									if (!phash) {
										until = void(0);
										phash = fm.root(h);
									}
									return phash;
								})(),
								cmd;
							
							if (!node.hasClass(loaded) && (isRoot || !d || !fm.navHash2Elm(d.phash).hasClass(loaded))) {
								if (isRoot || closest === getPhash(h) || closest === getPhash(h, 2)) {
									until = void(0);
									cmd = 'tree';
									if (!isRoot) {
										h = getPhash(h);
									}
								} else {
									cmd = 'parents';
								}
								if (!baseHash) {
									baseHash = (cmd === 'tree')? h : closest;
								}
								return makeReq(cmd, h, until);
							}
							return null;
						});
						
						if (reqs.length) {
							selectPages(fm.file(baseHash));
							baseId = fm.navHash2Id(baseHash);
							autoScr && autoScroll(baseId);
							baseNode = jQuery('#'+baseId);
							spinner = jQuery(fm.res('tpl', 'navspinner')).insertBefore(baseNode.children('.'+arrow));
							baseNode.removeClass(collapsed);
							
							jQuery.when.apply($, reqs)
							.done(function() {
								var res = {},data, treeDirs, dirs, argLen, i;
								argLen = arguments.length;
								if (argLen > 0) {
									for (i = 0; i < argLen; i++) {
										data = arguments[i].tree || [];
										res[ends[i]] = Object.assign([], filter(data));
									}
								}
								dfd.resolve(res);
							})
							.fail(function() {
								dfd.reject();
							});
							
							return dfd;
						} else {
							return dfd.resolve();
						}
					},
					done= function(res, dfrd) {
						var open = function() {
								if (openRoot && baseNode) {
									findSubtree(baseNode.hash).show().prev(selNavdir).addClass(expanded);
									openRoot = false;
								}
								if (autoScr) {
									autoScroll().done(checkSubdirs);
								} else {
									checkSubdirs();
								}
							},
							current;
						
						if (res) {
							jQuery.each(res, function(endHash, dirs) {
								dirs && updateTree(dirs);
								selectPages(fm.file(endHash));
								dirs && updateArrows(dirs, loaded);
							});
						}
						
						if (cwdDirs) {
							(fm.api < 2.1) && cwdDirs.push(cwd);
							updateTree(cwdDirs);
						}
						
						// set current node
						current = selectPages();
						
						if (!current.hasClass(active)) {
							tree.find(selNavdir+'.'+active).removeClass(active);
							current.addClass(active);
						}
						
						// mark as loaded to cwd parents
						current.parents('.elfinder-navbar-wrapper').children('.'+navdir).addClass(loaded);
						
						if (res) {
							fm.lazy(open).done(function() {
								dfrd.resolve();
							});
						} else {
							open();
							dfrd.resolve();
						}
					},
					rmSpinner = function(fail) {
						if (baseNode) {
							spinner.remove();
							baseNode.addClass(collapsed + (fail? '' : (' ' + loaded)));
						}
					},
					dfrd = jQuery.Deferred(),
					baseNode, spinner;
				
				if (!fm.navHash2Elm(cwdhash).length) {
					syncing = true;
					loadParents()
					.done(function(res) {
						done(res, dfrd);
						rmSpinner();
					})
					.fail(function() { 
						rmSpinner(true);
						dfrd.reject();
					})
					.always(function() {
						syncing = false;
					});
				} else {
					done(void(0), dfrd);
				}
				
				// trigger 'treesync' with my jQuery.Deferred
				fm.trigger('treesync', dfrd);

				return dfrd;
			},
			
			/**
			 * Make writable and not root dirs droppable
			 *
			 * @return void
			 */
			updateDroppable = function(target, node) {
				var limit = 100,
					next;
				
				if (!target) {
					if (!node || node.closest('div.'+wrapperRoot).hasClass(uploadable)) {
						(node || tree.find('div.'+uploadable)).find(selNavdir+':not(.elfinder-ro,.elfinder-na)').addClass('native-droppable');
					}
					if (!node || node.closest('div.'+wrapperRoot).hasClass(pastable)) {
						target = (node || tree.find('div.'+pastable)).find(selNavdir+':not(.'+droppable+')');
					} else {
						target = jQuery();
					}
					if (node) {
						// check leaf roots
						node.children('div.'+wrapperRoot).each(function() {
							updateDroppable(null, jQuery(this));
						});
					}
				}
				
				// make droppable on async
				if (target.length) {
					fm.asyncJob(function(elm) {
						jQuery(elm).droppable(droppableopts);
					}, jQuery.makeArray(target), {
						interval : 20,
						numPerOnce : 100
					});
				}
			},
			
			/**
			 * Check required folders for subfolders and update arrow classes
			 *
			 * @param  Array  folders to check
			 * @param  String css class 
			 * @return void
			 */
			updateArrows = function(dirs, cls) {
				var sel = cls == loaded
						? '.'+collapsed+':not(.'+loaded+')'
						: ':not(.'+collapsed+')';
				
				jQuery.each(dirs, function(i, dir) {
					fm.navHash2Elm(dir.phash).filter(sel)
						.filter(function() { return jQuery.grep(jQuery(this).next('.'+subtree).children(), function(n) {
							return (jQuery(n).children().hasClass(root))? false : true;
						}).length > 0; })
						.addClass(cls);
				});
			},
			
			
			
			/**
			 * Navigation tree
			 *
			 * @type JQuery
			 */
			tree = jQuery(this).addClass(treeclass)
				// make dirs draggable and toggle hover class
				.on('mouseenter mouseleave', selNavdir, function(e) {
					var enter = (e.type === 'mouseenter');
					if (enter && scrolling) { return; }
					var link  = jQuery(this); 
					
					if (!link.hasClass(dropover+' '+disabled)) {
						if (!mobile && enter && !link.data('dragRegisted') && !link.hasClass(root+' '+draggable+' elfinder-na elfinder-wo')) {
							link.data('dragRegisted', true);
							if (fm.isCommandEnabled('copy', fm.navId2Hash(link.attr('id')))) {
								link.draggable(fm.draggable);
							}
						}
						link.toggleClass(hover, enter);
					}
				})
				// native drag enter
				.on('dragenter', selNavdir, function(e) {
					if (e.originalEvent.dataTransfer) {
						var dst = jQuery(this);
						dst.addClass(hover);
						if (dst.is('.'+collapsed+':not(.'+expanded+')')) {
							dst.data('expandTimer', setTimeout(function() {
								dst.is('.'+collapsed+'.'+hover) && dst.children('.'+arrow).trigger('click');
							}, 500));
						}
					}
				})
				// native drag leave
				.on('dragleave', selNavdir, function(e) {
					if (e.originalEvent.dataTransfer) {
						var dst = jQuery(this);
						dst.data('expandTimer') && clearTimeout(dst.data('expandTimer'));
						dst.removeClass(hover);
					}
				})
				// open dir or open subfolders in tree
				.on('click', selNavdir, function(e) {
					var link = jQuery(this),
						hash = fm.navId2Hash(link.attr('id')),
						file = fm.file(hash);
					
					if (link.data('longtap')) {
						link.removeData('longtap');
						e.stopPropagation();
						return;
					}
					
					if (!link.hasClass(active)) {
						tree.find(selNavdir+'.'+active).removeClass(active);
						link.addClass(active);
					}
					if (hash != fm.cwd().hash && !link.hasClass(disabled)) {
						fm.exec('open', hash).done(function() {
							fm.one('opendone', function() {
								fm.select({selected: [hash], origin: 'navbar'});
							});
						});
					} else {
						if (link.hasClass(collapsed)) {
							link.children('.'+arrow).trigger('click');
						}
						fm.select({selected: [hash], origin: 'navbar'});
					}
				})
				// for touch device
				.on('touchstart', selNavdir, function(e) {
					if (e.originalEvent.touches.length > 1) {
						return;
					}
					var evt = e.originalEvent,
						p;
					
					if (e.target.nodeName === 'INPUT') {
						e.stopPropagation();
						return;
					}
					
					p = jQuery(this).addClass(hover)
					.removeData('longtap')
					.data('tmlongtap', setTimeout(function(e){
						// long tap
						p.data('longtap', true);
						fm.trigger('contextmenu', {
							'type'    : 'navbar',
							'targets' : [fm.navId2Hash(p.attr('id'))],
							'x'       : evt.touches[0].pageX,
							'y'       : evt.touches[0].pageY
						});
					}, 500));
				})
				.on('touchmove touchend', selNavdir, function(e) {
					if (e.target.nodeName === 'INPUT') {
						e.stopPropagation();
						return;
					}
					clearTimeout(jQuery(this).data('tmlongtap'));
					if (e.type == 'touchmove') {
						jQuery(this).removeClass(hover);
					}
				})
				// toggle subfolders in tree
				.on('click', selNavdir+'.'+collapsed+' .'+arrow, function(e) {
					var arrow = jQuery(this),
						link  = arrow.parent(selNavdir),
						stree = link.next('.'+subtree),
						dfrd  = jQuery.Deferred(),
						slideTH = 30, cnt;

					e.stopPropagation();

					if (link.hasClass(loaded)) {
						link.toggleClass(expanded);
						fm.lazy(function() {
							cnt = link.hasClass(expanded)? stree.children().length + stree.find('div.elfinder-navbar-subtree[style*=block]').children().length : stree.find('div:visible').length;
							if (cnt > slideTH) {
								stree.toggle();
								fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
								checkSubdirs();
							} else {
								stree.stop(true, true)[link.hasClass(expanded)? 'slideDown' : 'slideUp'](opts.durations.slideUpDown, function(){
									fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
									checkSubdirs();
								});
							}
						}).always(function() {
							dfrd.resolve();
						});
					} else {
						spinner.insertBefore(arrow);
						link.removeClass(collapsed);

						fm.request({cmd : 'tree', target : fm.navId2Hash(link.attr('id'))})
							.done(function(data) { 
								updateTree(Object.assign([], filter(data.tree))); 
								
								if (stree.children().length) {
									link.addClass(collapsed+' '+expanded);
									if (stree.children().length > slideTH) {
										stree.show();
										fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
										checkSubdirs();
									} else {
										stree.stop(true, true).slideDown(opts.durations.slideUpDown, function(){
											fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
											checkSubdirs();
										});
									}
								} 
							})
							.always(function(data) {
								spinner.remove();
								link.addClass(loaded);
								fm.one('treedone', function() {
									dfrd.resolve();
								});
							});
					}
					arrow.data('dfrd', dfrd);
				})
				.on('contextmenu', selNavdir, function(e) {
					var self = jQuery(this);
					
					// now dirname editing
					if (self.find('input:text').length) {
						e.stopPropagation();
						return;
					}
					
					e.preventDefault();

					fm.trigger('contextmenu', {
						'type'    : 'navbar',
						'targets' : [fm.navId2Hash(jQuery(this).attr('id'))],
						'x'       : e.pageX,
						'y'       : e.pageY
					});
					
					self.addClass('ui-state-hover');
					
					fm.getUI('contextmenu').children().on('mouseenter', function() {
						self.addClass('ui-state-hover');
					});
					
					fm.bind('closecontextmenu', function() {
						self.removeClass('ui-state-hover');
					});
				})
				.on('scrolltoview', selNavdir, function(e, data) {
					var self = jQuery(this);
					autoScroll(self.attr('id')).done(function() {
						if (!data || data.blink === 'undefined' || data.blink) {
							fm.resources.blink(self, 'lookme');
						}
					});
				})
				// prepend fake dir
				.on('create.'+fm.namespace, function(e, item) {
					var pdir = findSubtree(item.phash),
						lock = item.move || false,
						dir  = jQuery(itemhtml(item)).addClass('elfinder-navbar-wrapper-tmp'),
						selected = fm.selected();
						
					lock && selected.length && fm.trigger('lockfiles', {files: selected});
					pdir.prepend(dir);
				}),
			scrolling = false,
			navbarScrTm,
			// move tree into navbar
			navbar = fm.getUI('navbar').append(tree).show().on('scroll', function() {
				scrolling = true;
				navbarScrTm && cancelAnimationFrame(navbarScrTm);
				navbarScrTm = requestAnimationFrame(function() {
					scrolling = false;
					checkSubdirs();
				});
			}),
			
			prevSortTreeview = fm.sortAlsoTreeview;
			
		fm.open(function(e) {
			var data = e.data,
				dirs = filter(data.files),
				contextmenu = fm.getUI('contextmenu');

			data.init && tree.empty();

			if (fm.UA.iOS) {
				navbar.removeClass('overflow-scrolling-touch').addClass('overflow-scrolling-touch');
			}

			if (dirs.length) {
				fm.lazy(function() {
					if (!contextmenu.data('cmdMaps')) {
						contextmenu.data('cmdMaps', {});
					}
					updateTree(dirs);
					updateArrows(dirs, loaded);
					sync(dirs);
				});
			} else {
				sync();
			}
		})
		// add new dirs
		.add(function(e) {
			var dirs = filter(e.data.added);

			if (dirs.length) {
				updateTree(dirs);
				updateArrows(dirs, collapsed);
			}
		})
		// update changed dirs
		.change(function(e) {
			// do ot perfome while syncing
			if (syncing) {
				return;
			}

			var dirs = filter(e.data.changed, true),
				length = dirs.length,
				l    = length,
				tgts = jQuery(),
				changed = {},
				dir, phash, node, tmp, realParent, reqParent, realSibling, reqSibling, isExpanded, isLoaded, parent, subdirs;
			
			jQuery.each(hasMoreDirs, function(h, node) {
				node.trigger('update.'+fm.namespace, { change: 'prepare' });
			});
			
			while (l--) {
				dir = dirs[l];
				phash = dir.phash;
				if ((node = fm.navHash2Elm(dir.hash)).length) {
					parent = node.parent();
					if (phash) {
						realParent  = node.closest('.'+subtree);
						reqParent   = findSubtree(phash);
						realSibling = node.parent().next();
						reqSibling  = findSibling(reqParent, dir);
						
						if (!reqParent.length) {
							continue;
						}
						
						if (reqParent[0] !== realParent[0] || realSibling.get(0) !== reqSibling.get(0)) {
							reqSibling.length ? reqSibling.before(parent) : reqParent.append(parent);
						}
					}
					isExpanded = node.hasClass(expanded);
					isLoaded   = node.hasClass(loaded);
					tmp        = jQuery(itemhtml(dir));
					node.replaceWith(tmp.children(selNavdir));
					! mobile && updateDroppable(null, parent);
					
					if (dir.dirs
					&& (isExpanded || isLoaded) 
					&& (node = fm.navHash2Elm(dir.hash))
					&& node.next('.'+subtree).children().length) {
						isExpanded && node.addClass(expanded);
						isLoaded && node.addClass(loaded);
					}
					
					subdirs |= dir.dirs == -1;
				}
			}
			
			// to check subdirs
			if (subdirs) {
				checkSubdirs();
			}
			
			jQuery.each(hasMoreDirs, function(h, node) {
				node.trigger('update.'+fm.namespace, { change: 'done' });
			});
			
			length && sync(void(0), false);
		})
		// remove dirs
		.remove(function(e) {
			var dirs = e.data.removed,
				l    = dirs.length,
				node, stree, removed;
			
			jQuery.each(hasMoreDirs, function(h, node) {
				node.trigger('update.'+fm.namespace, { removed : dirs });
				node.trigger('update.'+fm.namespace, { change: 'prepare' });
			});

			while (l--) {
				if ((node = fm.navHash2Elm(dirs[l])).length) {
					removed = true;
					stree = node.closest('.'+subtree);
					node.parent().detach();
					if (!stree.children().length) {
						stree.hide().prev(selNavdir).removeClass(collapsed+' '+expanded+' '+loaded);
					}
				}
			}
			
			removed && fm.getUI('navbar').children('.ui-resizable-handle').trigger('resize');
			
			jQuery.each(hasMoreDirs, function(h, node) {
				node.trigger('update.'+fm.namespace, { change: 'done' });
			});
		})
		// lock/unlock dirs while moving
		.bind('lockfiles unlockfiles', function(e) {
			var lock = e.type == 'lockfiles',
				helperLocked = e.data.helper? e.data.helper.data('locked') : false,
				act  = (lock && !helperLocked) ? 'disable' : 'enable',
				dirs = jQuery.grep(e.data.files||[], function(h) {  
					var dir = fm.file(h);
					return dir && dir.mime == 'directory' ? true : false;
				});
				
			jQuery.each(dirs, function(i, hash) {
				var dir = fm.navHash2Elm(hash);
				
				if (dir.length && !helperLocked) {
					dir.hasClass(draggable) && dir.draggable(act);
					dir.hasClass(droppable) && dir.droppable(act);
					dir[lock ? 'addClass' : 'removeClass'](disabled);
				}
			});
		})
		.bind('sortchange', function() {
			if (fm.sortAlsoTreeview || prevSortTreeview !== fm.sortAlsoTreeview) {
				var dirs,
					ends = [],
					endsMap = {},
					endsVid = {},
					topVid = '',
					single = false,
					current;
				
				fm.lazy(function() {
					dirs = filter(fm.files());
					prevSortTreeview = fm.sortAlsoTreeview;
					
					tree.empty();
					
					// append volume roots at first
					updateTree(jQuery.map(fm.roots, function(h) {
						var dir = fm.file(h);
						return dir && !dir.phash? dir : null;
					}));
					
					if (!Object.keys(hasMoreDirs).length) {
						updateTree(dirs);
						current = selectPages();
						updateArrows(dirs, loaded);
					} else {
						ends = getEnds();
						if (ends.length > 1) {
							jQuery.each(ends, function(i, end) {
								var vid = fm.file(fm.root(end)).volumeid; 
								if (i === 0) {
									topVid = vid;
								}
								endsVid[vid] = end;
								endsMap[end] = [];
							});
							jQuery.each(dirs, function(i, d) {
								if (!d.volumeid) {
									single = true;
									return false;
								}
								endsMap[endsVid[d.volumeid] || endsVid[topVid]].push(d);
							});
						} else {
							single = true;
						}
						if (single) {
							jQuery.each(ends, function(i, endHash) {
								updateTree(dirs);
								current = selectPages(fm.file(endHash));
								updateArrows(dirs, loaded);
							});
						} else {
							jQuery.each(endsMap, function(endHash, dirs) {
								updateTree(dirs);
								current = selectPages(fm.file(endHash));
								updateArrows(dirs, loaded);
							});
						}
					}
					
					sync();
				}, 100);
			}
		});

	});
	
	return this;
};


/*
 * File: /js/ui/uploadButton.js
 */

/**
 * @class  elFinder toolbar's button tor upload file
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfinderuploadbutton = function(cmd) {
		return this.each(function() {
		var fm = cmd.fm,
			button = jQuery(this).elfinderbutton(cmd)
				.off('click'), 
			form = jQuery('<form/>').appendTo(button),
			input = jQuery('<input type="file" multiple="true" title="'+cmd.fm.i18n('selectForUpload')+'"/>')
				.on('change', function() {
					var _input = jQuery(this);
					if (_input.val()) {
						fm.exec('upload', {input : _input.remove()[0]}, void(0), fm.cwd().hash);
						input.clone(true).appendTo(form);
					} 
				})
				.on('dragover', function(e) {
					e.originalEvent.dataTransfer.dropEffect = 'copy';
				}),
			tm;

		form.append(input.clone(true));
				
		cmd.change(function() {
			tm && cancelAnimationFrame(tm);
			tm = requestAnimationFrame(function() {
				var toShow = cmd.disabled();
				if (form.is('visible')) {
					!toShow && form.hide();
				} else {
					toShow && form.show();
				}
			});
		})
		.change();
	});
};


/*
 * File: /js/ui/viewbutton.js
 */

/**
 * @class  elFinder toolbar button to switch current directory view.
 *
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfinderviewbutton = function(cmd) {
		return this.each(function() {
		var button = jQuery(this).elfinderbutton(cmd),
			icon   = button.children('.elfinder-button-icon'),
			text   = button.children('.elfinder-button-text'),
			tm;

		cmd.change(function() {
			tm && cancelAnimationFrame(tm);
			tm = requestAnimationFrame(function() {
				var icons = cmd.value == 'icons';

				icon.toggleClass('elfinder-button-icon-view-list', icons);
				cmd.className = icons? 'view-list' : '';
				cmd.title = cmd.fm.i18n(icons ? 'viewlist' : 'viewicons');
				button.attr('title', cmd.title);
				text.html(cmd.title);
			});
		});
	});
};


/*
 * File: /js/ui/workzone.js
 */

/**
 * @class elfinderworkzone - elFinder container for nav and current directory
 * @author Dmitry (dio) Levashov
 **/
jQuery.fn.elfinderworkzone = function(fm) {
		var cl = 'elfinder-workzone';
	
	this.not('.'+cl).each(function() {
		var wz     = jQuery(this).addClass(cl),
			prevH  = Math.round(wz.height()),
			parent = wz.parent(),
			setDelta = function() {
				wdelta = wz.outerHeight(true) - wz.height();
			},
			fitsize = function(e) {
				var height = parent.height() - wdelta,
					style  = parent.attr('style'),
					curH   = Math.round(wz.height());
	
				if (e) {
					e.preventDefault();
					e.stopPropagation();
				}
				
				parent.css('overflow', 'hidden')
					.children(':visible:not(.'+cl+')').each(function() {
						var ch = jQuery(this);
		
						if (ch.css('position') != 'absolute' && ch.css('position') != 'fixed') {
							height -= ch.outerHeight(true);
						}
					});
				parent.attr('style', style || '');
				
				height = Math.max(0, Math.round(height));
				if (prevH !== height || curH !== height) {
					prevH  = Math.round(wz.height());
					wz.height(height);
					fm.trigger('wzresize');
				}
			},
			cssloaded = function() {
				wdelta = wz.outerHeight(true) - wz.height();
				fitsize();
			},
			wdelta;
		
		setDelta();
		parent.on('resize.' + fm.namespace, fitsize);
		fm.one('cssloaded', cssloaded)
		  .bind('uiresize', fitsize)
		  .bind('themechange', setDelta);
	});
	return this;
};


/*
 * File: /js/commands/archive.js
 */

/**
 * @class  elFinder command "archive"
 * Archive selected files
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.archive = function() {
		var self  = this,
		fm    = self.fm,
		mimes = [],
		dfrd;
		
	this.variants = [];
	
	this.disableOnSearch = false;
	
	this.nextAction = {};
	
	/**
	 * Update mimes on open/reload
	 *
	 * @return void
	 **/
	fm.bind('open reload', function() {
		self.variants = [];
		jQuery.each((mimes = fm.option('archivers')['create'] || []), function(i, mime) {
			self.variants.push([mime, fm.mime2kind(mime)]);
		});
		self.change();
	});
	
	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length,
			chk = (cnt && ! fm.isRoot(sel[0]) && (fm.file(sel[0].phash) || {}).write && ! jQuery.grep(sel, function(f){ return f.read ? false : true; }).length),
			cwdId;
		
		if (chk && fm.searchStatus.state > 1) {
			cwdId = fm.cwd().volumeid;
			chk = (cnt === jQuery.grep(sel, function(f) { return f.read && f.hash.indexOf(cwdId) === 0 ? true : false; }).length);
		}
		
		return chk && !this._disabled && mimes.length && (cnt || (dfrd && dfrd.state() == 'pending')) ? 0 : -1;
	};
	
	this.exec = function(hashes, type) {
		var files = this.files(hashes),
			cnt   = files.length,
			mime  = type || mimes[0],
			cwd   = fm.file(files[0].phash) || null,
			error = ['errArchive', 'errPerm', 'errCreatingTempDir', 'errFtpDownloadFile', 'errFtpUploadFile', 'errFtpMkdir', 'errArchiveExec', 'errExtractExec', 'errRm'],
			i, open;

		dfrd = jQuery.Deferred().fail(function(error) {
			error && fm.error(error);
		});

		if (! (cnt && mimes.length && jQuery.inArray(mime, mimes) !== -1)) {
			return dfrd.reject();
		}
		
		if (!cwd.write) {
			return dfrd.reject(error);
		}
		
		for (i = 0; i < cnt; i++) {
			if (!files[i].read) {
				return dfrd.reject(error);
			}
		}

		self.mime   = mime;
		self.prefix = ((cnt > 1)? 'Archive' : files[0].name) + (fm.option('archivers')['createext']? '.' + fm.option('archivers')['createext'][mime] : '');
		self.data   = {targets : self.hashes(hashes), type : mime};
		
		if (fm.cwd().hash !== cwd.hash) {
			open = fm.exec('open', cwd.hash).done(function() {
				fm.one('cwdrender', function() {
					fm.selectfiles({files : hashes});
					dfrd = jQuery.proxy(fm.res('mixin', 'make'), self)();
				});
			});
		} else {
			fm.selectfiles({files : hashes});
			dfrd = jQuery.proxy(fm.res('mixin', 'make'), self)();
		}
		
		return dfrd;
	};

};


/*
 * File: /js/commands/back.js
 */

/**
 * @class  elFinder command "back"
 * Open last visited folder
 *
 * @author Dmitry (dio) Levashov
 **/
(elFinder.prototype.commands.back = function() {
		this.alwaysEnabled  = true;
	this.updateOnSelect = false;
	this.shortcuts      = [{
		pattern     : 'ctrl+left backspace'
	}];
	
	this.getstate = function() {
		return this.fm.history.canBack() ? 0 : -1;
	};
	
	this.exec = function() {
		return this.fm.history.back();
	};

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/chmod.js
 */

/**
 * @class elFinder command "chmod".
 * Chmod files.
 *
 * @type  elFinder.command
 * @author Naoki Sawada
 */
elFinder.prototype.commands.chmod = function() {
		this.updateOnSelect = false;
	var fm  = this.fm,
		level = {
			0 : 'owner',
			1 : 'group',
			2 : 'other'
		},
		msg = {
			read     : fm.i18n('read'),
			write    : fm.i18n('write'),
			execute  : fm.i18n('execute'),
			perm     : fm.i18n('perm'),
			kind     : fm.i18n('kind'),
			files    : fm.i18n('files')
		},
		isPerm = function(perm){
			return (!isNaN(parseInt(perm, 8) && parseInt(perm, 8) <= 511) || perm.match(/^([r-][w-][x-]){3}$/i));
		};

	this.tpl = {
		main       : '<div class="ui-helper-clearfix elfinder-info-title"><span class="elfinder-cwd-icon {class} ui-corner-all"/>{title}</div>'
					+'{dataTable}',
		itemTitle  : '<strong>{name}</strong><span id="elfinder-info-kind">{kind}</span>',
		groupTitle : '<strong>{items}: {num}</strong>',
		dataTable  : '<table id="{id}-table-perm"><tr><td>{0}</td><td>{1}</td><td>{2}</td></tr></table>'
					+'<div class="">'+msg.perm+': <input class="elfinder-tabstop elfinder-focus" id="{id}-perm" type="text" size="4" maxlength="3" value="{value}"></div>',
		fieldset   : '<fieldset id="{id}-fieldset-{level}"><legend>{f_title}{name}</legend>'
					+'<input type="checkbox" value="4" class="elfinder-tabstop" id="{id}-read-{level}-perm"{checked-r}> <label for="{id}-read-{level}-perm">'+msg.read+'</label><br>'
					+'<input type="checkbox" value="6" class="elfinder-tabstop" id="{id}-write-{level}-perm"{checked-w}> <label for="{id}-write-{level}-perm">'+msg.write+'</label><br>'
					+'<input type="checkbox" value="5" class="elfinder-tabstop" id="{id}-execute-{level}-perm"{checked-x}> <label for="{id}-execute-{level}-perm">'+msg.execute+'</label><br>'
	};

	this.shortcuts = [{
		//pattern     : 'ctrl+p'
	}];

	this.getstate = function(sel) {
		var fm = this.fm;
		sel = sel || fm.selected();
		if (sel.length == 0) {
			sel = [ fm.cwd().hash ];
		}
		return this.checkstate(this.files(sel)) ? 0 : -1;
	};
	
	this.checkstate = function(sel) {
		var cnt = sel.length;
		if (!cnt) return false;
		var chk = jQuery.grep(sel, function(f) {
			return (f.isowner && f.perm && isPerm(f.perm) && (cnt == 1 || f.mime != 'directory')) ? true : false;
		}).length;
		return (cnt == chk)? true : false;
	};

	this.exec = function(select) {
		var hashes  = this.hashes(select),
			files   = this.files(hashes);
		if (! files.length) {
			hashes = [ this.fm.cwd().hash ];
			files   = this.files(hashes);
		}
		var fm  = this.fm,
		dfrd    = jQuery.Deferred().always(function() {
			fm.enable();
		}),
		tpl     = this.tpl,
		cnt     = files.length,
		file    = files[0],
		id = fm.namespace + '-perm-' + file.hash,
		view    = tpl.main,
		checked = ' checked="checked"',
		buttons = function() {
			var buttons = {};
			buttons[fm.i18n('btnApply')] = save;
			buttons[fm.i18n('btnCancel')] = function() { dialog.elfinderdialog('close'); };
			return buttons;
		},
		save = function() {
			var perm = jQuery.trim(jQuery('#'+id+'-perm').val()),
				reqData;
			
			if (!isPerm(perm)) return false;
			
			dialog.elfinderdialog('close');
			
			reqData = {
				cmd     : 'chmod',
				targets : hashes,
				mode    : perm
			};
			fm.request({
				data : reqData,
				notify : {type : 'chmod', cnt : cnt}
			})
			.fail(function(error) {
				dfrd.reject(error);
			})
			.done(function(data) {
				if (data.changed && data.changed.length) {
					data.undo = {
						cmd : 'chmod',
						callback : function() {
							var reqs = [];
							jQuery.each(prevVals, function(perm, hashes) {
								reqs.push(fm.request({
									data : {cmd : 'chmod', targets : hashes, mode : perm},
									notify : {type : 'undo', cnt : hashes.length}
								}));
							});
							return jQuery.when.apply(null, reqs);
						}
					};
					data.redo = {
						cmd : 'chmod',
						callback : function() {
							return fm.request({
								data : reqData,
								notify : {type : 'redo', cnt : hashes.length}
							});
						}
					};
				}
				dfrd.resolve(data);
			});
		},
		setperm = function() {
			var perm = '';
			var _perm;
			for (var i = 0; i < 3; i++){
				_perm = 0;
				if (jQuery("#"+id+"-read-"+level[i]+'-perm').is(':checked')) {
					_perm = (_perm | 4);
				}
				if (jQuery("#"+id+"-write-"+level[i]+'-perm').is(':checked')) {
					_perm = (_perm | 2);
				}
				if (jQuery("#"+id+"-execute-"+level[i]+'-perm').is(':checked')) {
					_perm = (_perm | 1);
				}
				perm += _perm.toString(8);
			}
			jQuery('#'+id+'-perm').val(perm);
		},
		setcheck = function(perm) {
			var _perm;
			for (var i = 0; i < 3; i++){
				_perm = parseInt(perm.slice(i, i+1), 8);
				jQuery("#"+id+"-read-"+level[i]+'-perm').prop("checked", false);
				jQuery("#"+id+"-write-"+level[i]+'-perm').prop("checked", false);
				jQuery("#"+id+"-execute-"+level[i]+'-perm').prop("checked", false);
				if ((_perm & 4) == 4) {
					jQuery("#"+id+"-read-"+level[i]+'-perm').prop("checked", true);
				}
				if ((_perm & 2) == 2) {
					jQuery("#"+id+"-write-"+level[i]+'-perm').prop("checked", true);
				}
				if ((_perm & 1) == 1) {
					jQuery("#"+id+"-execute-"+level[i]+'-perm').prop("checked", true);
				}
			}
			setperm();
		},
		makeperm = function(files) {
			var perm = '777', ret = '', chk, _chk, _perm;
			var len = files.length;
			for (var i2 = 0; i2 < len; i2++) {
				chk = getPerm(files[i2].perm);
				if (! prevVals[chk]) {
					prevVals[chk] = [];
				}
				prevVals[chk].push(files[i2].hash);
				ret = '';
				for (var i = 0; i < 3; i++){
					_chk = parseInt(chk.slice(i, i+1), 8);
					_perm = parseInt(perm.slice(i, i+1), 8);
					if ((_chk & 4) != 4 && (_perm & 4) == 4) {
						_perm -= 4;
					}
					if ((_chk & 2) != 2 && (_perm & 2) == 2) {
						_perm -= 2;
					}
					if ((_chk & 1) != 1 && (_perm & 1) == 1) {
						_perm -= 1;
					}
					ret += _perm.toString(8);
				}
				perm = ret;
			}
			return perm;
		},
		makeName = function(name) {
			return name? ':'+name : '';
		},
		makeDataTable = function(perm, f) {
			var _perm, fieldset;
			var value = '';
			var dataTable = tpl.dataTable;
			for (var i = 0; i < 3; i++){
				_perm = parseInt(perm.slice(i, i+1), 8);
				value += _perm.toString(8);
				fieldset = tpl.fieldset.replace('{f_title}', fm.i18n(level[i])).replace('{name}', makeName(f[level[i]])).replace(/\{level\}/g, level[i]);
				dataTable = dataTable.replace('{'+i+'}', fieldset)
				                     .replace('{checked-r}', ((_perm & 4) == 4)? checked : '')
				                     .replace('{checked-w}', ((_perm & 2) == 2)? checked : '')
				                     .replace('{checked-x}', ((_perm & 1) == 1)? checked : '');
			}
			dataTable = dataTable.replace('{value}', value).replace('{valueCaption}', msg['perm']);
			return dataTable;
		},
		getPerm = function(perm){
			if (isNaN(parseInt(perm, 8))) {
				var mode_array = perm.split('');
				var a = [];

				for (var i = 0, l = mode_array.length; i < l; i++) {
					if (i === 0 || i === 3 || i === 6) {
						if (mode_array[i].match(/[r]/i)) {
							a.push(1);
						} else if (mode_array[i].match(/[-]/)) {
							a.push(0);
						}
					} else if ( i === 1 || i === 4 || i === 7) {
						 if (mode_array[i].match(/[w]/i)) {
							a.push(1);
						} else if (mode_array[i].match(/[-]/)) {
							a.push(0);
						}
					} else {
						if (mode_array[i].match(/[x]/i)) {
							a.push(1);
						} else if (mode_array[i].match(/[-]/)) {
							a.push(0);
						}
					}
				}
			
				a.splice(3, 0, ",");
				a.splice(7, 0, ",");

				var b = a.join("");
				var b_array = b.split(",");
				var c = [];
			
				for (var j = 0, m = b_array.length; j < m; j++) {
					var p = parseInt(b_array[j], 2).toString(8);
					c.push(p);
				}

				perm = c.join('');
			} else {
				perm = parseInt(perm, 8).toString(8);
			}
			return perm;
		},
		opts    = {
			title : this.title,
			width : 'auto',
			buttons : buttons(),
			close : function() { jQuery(this).elfinderdialog('destroy'); }
		},
		dialog = fm.getUI().find('#'+id),
		prevVals = {},
		tmb = '', title, dataTable;

		if (dialog.length) {
			dialog.elfinderdialog('toTop');
			return jQuery.Deferred().resolve();
		}

		view  = view.replace('{class}', cnt > 1 ? 'elfinder-cwd-icon-group' : fm.mime2class(file.mime));
		if (cnt > 1) {
			title = tpl.groupTitle.replace('{items}', fm.i18n('items')).replace('{num}', cnt);
		} else {
			title = tpl.itemTitle.replace('{name}', file.name).replace('{kind}', fm.mime2kind(file));
			tmb = fm.tmb(file);
		}

		dataTable = makeDataTable(makeperm(files), files.length == 1? files[0] : {});

		view = view.replace('{title}', title).replace('{dataTable}', dataTable).replace(/{id}/g, id);

		dialog = this.fmDialog(view, opts);
		dialog.attr('id', id);

		// load thumbnail
		if (tmb) {
			jQuery('<img/>')
				.on('load', function() { dialog.find('.elfinder-cwd-icon').addClass(tmb.className).css('background-image', "url('"+tmb.url+"')"); })
				.attr('src', tmb.url);
		}

		jQuery('#' + id + '-table-perm :checkbox').on('click', function(){setperm('perm');});
		jQuery('#' + id + '-perm').on('keydown', function(e) {
			var c = e.keyCode;
			if (c == jQuery.ui.keyCode.ENTER) {
				e.stopPropagation();
				save();
				return;
			}
		}).on('focus', function(e){
			jQuery(this).trigger('select');
		}).on('keyup', function(e) {
			if (jQuery(this).val().length == 3) {
				jQuery(this).trigger('select');
				setcheck(jQuery(this).val());
			}
		});
		
		return dfrd;
	};
};


/*
 * File: /js/commands/colwidth.js
 */

/**
 * @class  elFinder command "colwidth"
 * CWD list table columns width to auto
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.colwidth = function() {
		this.alwaysEnabled = true;
	this.updateOnSelect = false;
	
	this.getstate = function() {
		return this.fm.getUI('cwd').find('table').css('table-layout') === 'fixed' ? 0 : -1;
	};
	
	this.exec = function() {
		this.fm.getUI('cwd').trigger('colwidth');
		return jQuery.Deferred().resolve();
	};
	
};

/*
 * File: /js/commands/copy.js
 */

/**
 * @class elFinder command "copy".
 * Put files in filemanager clipboard.
 *
 * @type  elFinder.command
 * @author  Dmitry (dio) Levashov
 */
elFinder.prototype.commands.copy = function() {
		this.shortcuts = [{
		pattern     : 'ctrl+c ctrl+insert'
	}];
	
	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length;

		return cnt && jQuery.grep(sel, function(f) { return f.read ? true : false; }).length == cnt ? 0 : -1;
	};
	
	this.exec = function(hashes) {
		var fm   = this.fm,
			dfrd = jQuery.Deferred()
				.fail(function(error) {
					fm.error(error);
				});

		jQuery.each(this.files(hashes), function(i, file) {
			if (! file.read) {
				return !dfrd.reject(['errCopy', file.name, 'errPerm']);
			}
		});
		
		return dfrd.state() == 'rejected' ? dfrd : dfrd.resolve(fm.clipboard(this.hashes(hashes)));
	};

};


/*
 * File: /js/commands/cut.js
 */

/**
 * @class elFinder command "copy".
 * Put files in filemanager clipboard.
 *
 * @type  elFinder.command
 * @author  Dmitry (dio) Levashov
 */
elFinder.prototype.commands.cut = function() {
		var fm = this.fm;
	
	this.shortcuts = [{
		pattern     : 'ctrl+x shift+insert'
	}];
	
	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length;
		
		return cnt && jQuery.grep(sel, function(f) { return f.read && ! f.locked && ! fm.isRoot(f) ? true : false; }).length == cnt ? 0 : -1;
	};
	
	this.exec = function(hashes) {
		var dfrd = jQuery.Deferred()
				.fail(function(error) {
					fm.error(error);
				});

		jQuery.each(this.files(hashes), function(i, file) {
			if (!(file.read && ! file.locked && ! fm.isRoot(file)) ) {
				return !dfrd.reject(['errCopy', file.name, 'errPerm']);
			}
			if (file.locked) {
				return !dfrd.reject(['errLocked', file.name]);
			}
		});
		
		return dfrd.state() == 'rejected' ? dfrd : dfrd.resolve(fm.clipboard(this.hashes(hashes), true));
	};

};


/*
 * File: /js/commands/download.js
 */

/**
 * @class elFinder command "download". 
 * Download selected files.
 * Only for new api
 *
 * @author Dmitry (dio) Levashov, dio@std42.ru
 **/
elFinder.prototype.commands.zipdl = function() {};
elFinder.prototype.commands.download = function() {
		var self   = this,
		fm     = this.fm,
		czipdl = null,
		zipOn  = false,
		mixed  = false,
		dlntf  = false,
		cpath  = window.location.pathname || '/',
		filter = function(hashes, inExec) {
			var volumeid, mixedCmd;
			
			if (czipdl !== null) {
				if (fm.searchStatus.state > 1) {
					mixed = fm.searchStatus.mixed;
				} else if (fm.leafRoots[fm.cwd().hash]) {
					volumeid = fm.cwd().volumeid;
					jQuery.each(hashes, function(i, h) {
						if (h.indexOf(volumeid) !== 0) {
							mixed = true;
							return false;
						}
					});
				}
				zipOn = (fm.isCommandEnabled('zipdl', hashes[0]));
			}

			if (mixed) {
				mixedCmd = czipdl? 'zipdl' : 'download';
				hashes = jQuery.grep(hashes, function(h) {
					var f = fm.file(h),
						res = (! f || (! czipdl && f.mime === 'directory') || ! fm.isCommandEnabled(mixedCmd, h))? false : true;
					if (f && inExec && ! res) {
						fm.cwdHash2Elm(f.hash).trigger('unselect');
					}
					return res;
				});
				if (! hashes.length) {
					return [];
				}
			} else {
				if (!fm.isCommandEnabled('download', hashes[0])) {
					return [];
				}
			}
			
			return jQuery.grep(self.files(hashes), function(f) { 
				var res = (! f.read || (! zipOn && f.mime == 'directory')) ? false : true;
				if (inExec && ! res) {
					fm.cwdHash2Elm(f.hash).trigger('unselect');
				}
				return res;
			});
		};
	
	this.linkedCmds = ['zipdl'];
	
	this.shortcuts = [{
		pattern     : 'shift+enter'
	}];
	
	this.getstate = function(select) {
		var sel    = this.hashes(select),
			cnt    = sel.length,
			maxReq = this.options.maxRequests || 10,
			mixed  = false,
			croot  = '';
		
		if (cnt < 1) {
			return -1;
		}
		cnt = filter(sel).length;
		
		return  (cnt && (zipOn || (cnt <= maxReq && ((!fm.UA.IE && !fm.UA.Mobile) || cnt == 1))) ? 0 : -1);
	};
	
	fm.bind('contextmenu', function(e){
		var fm = self.fm,
			helper = null,
			targets, file, link,
			getExtra = function(file) {
				var link = file.url || fm.url(file.hash);
				return {
					icon: 'link',
					node: jQuery('<a/>')
						.attr({href: link, target: '_blank', title: fm.i18n('link')})
						.text(file.name)
						.on('mousedown click touchstart touchmove touchend contextmenu', function(e){
							e.stopPropagation();
						})
						.on('dragstart', function(e) {
							var dt = e.dataTransfer || e.originalEvent.dataTransfer || null;
							helper = null;
							if (dt) {
								var icon  = function(f) {
										var mime = f.mime, i, tmb = fm.tmb(f);
										i = '<div class="elfinder-cwd-icon '+fm.mime2class(mime)+' ui-corner-all"/>';
										if (tmb) {
											i = jQuery(i).addClass(tmb.className).css('background-image', "url('"+tmb.url+"')").get(0).outerHTML;
										}
										return i;
									};
								dt.effectAllowed = 'copyLink';
								if (dt.setDragImage) {
									helper = jQuery('<div class="elfinder-drag-helper html5-native">').append(icon(file)).appendTo(jQuery(document.body));
									dt.setDragImage(helper.get(0), 50, 47);
								}
								if (!fm.UA.IE) {
									dt.setData('elfinderfrom', window.location.href + file.phash);
									dt.setData('elfinderfrom:' + dt.getData('elfinderfrom'), '');
								}
							}
						})
						.on('dragend', function(e) {
							helper && helper.remove();
						})
				};
			};
		self.extra = null;
		if (e.data) {
			targets = e.data.targets || [];
			if (targets.length === 1 && (file = fm.file(targets[0])) && file.mime !== 'directory') {
				if (file.url != '1') {
					self.extra = getExtra(file);
				} else {
					// Get URL ondemand
					var node;
					self.extra = {
						icon: 'link',
						node: jQuery('<a/>')
							.attr({href: '#', title: fm.i18n('getLink'), draggable: 'false'})
							.text(file.name)
							.on('click touchstart', function(e){
								if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
									return;
								}
								var parent = node.parent();
								e.stopPropagation();
								e.preventDefault();
								parent.removeClass('ui-state-disabled').addClass('elfinder-button-icon-spinner');
								fm.request({
									data : {cmd : 'url', target : file.hash},
									preventDefault : true
								})
								.always(function(data) {
									parent.removeClass('elfinder-button-icon-spinner');
									if (data.url) {
										var rfile = fm.file(file.hash);
										rfile.url = data.url;
										node.replaceWith(getExtra(file).node);
									} else {
										parent.addClass('ui-state-disabled');
									}
								});

							})
					};
					node = self.extra.node;
					node.ready(function(){
						requestAnimationFrame(function(){
							node.parent().addClass('ui-state-disabled').css('pointer-events', 'auto');
						});
					});
				}
			}
		}
	}).one('open', function() {
		if (fm.api >= 2.1012) {
			czipdl = fm.getCommand('zipdl');
		}
		dlntf = fm.api > 2.1038 && !fm.isCORS;
	});
	
	this.exec = function(select) {
		var hashes  = this.hashes(select),
			fm      = this.fm,
			base    = fm.options.url,
			files   = filter(hashes, true),
			dfrd    = jQuery.Deferred(),
			iframes = '',
			cdata   = '',
			targets = {},
			i, url,
			linkdl  = false,
			getTask = function(hashes) {
				return function() {
					var dfd = jQuery.Deferred(),
						root = fm.file(fm.root(hashes[0])),
						single = (hashes.length === 1),
						volName = root? (root.i18 || root.name) : null,
						dir, dlName, phash;
					if (single) {
						if (dir = fm.file(hashes[0])) {
							dlName = (dir.i18 || dir.name);
						}
					} else {
						jQuery.each(hashes, function() {
							var d = fm.file(this);
							if (d && (!phash || phash === d.phash)) {
								phash = d.phash;
							} else {
								phash = null;
								return false;
							}
						});
						if (phash && (dir = fm.file(phash))) {
							dlName = (dir.i18 || dir.name) + '-' + hashes.length;
						}
					}
					if (dlName) {
						volName = dlName;
					}
					volName && (volName = ' (' + volName + ')');
					fm.request({
						data : {cmd : 'zipdl', targets : hashes},
						notify : {type : 'zipdl', cnt : 1, hideCnt : true, msg : fm.i18n('ntfzipdl') + volName},
						cancel : true,
						eachCancel : true,
						preventDefault : true
					}).done(function(e) {
						var zipdl, dialog, btn = {}, dllink, form, iframe, m,
							uniq = 'dlw' + (+new Date());
						if (e.error) {
							fm.error(e.error);
							dfd.resolve();
						} else if (e.zipdl) {
							zipdl = e.zipdl;
							if (dlName) {
								m = fm.splitFileExtention(zipdl.name || '');
								dlName += m[1]? ('.' + m[1]) : '.zip';
							} else {
								dlName = zipdl.name;
							}
							if ((html5dl && (!fm.UA.Safari || fm.isSameOrigin(fm.options.url))) || linkdl) {
								url = fm.options.url + (fm.options.url.indexOf('?') === -1 ? '?' : '&')
								+ 'cmd=zipdl&download=1';
								jQuery.each([hashes[0], zipdl.file, dlName, zipdl.mime], function(key, val) {
									url += '&targets%5B%5D='+encodeURIComponent(val);
								});
								jQuery.each(fm.customData, function(key, val) {
									url += '&'+encodeURIComponent(key)+'='+encodeURIComponent(val);
								});
								url += '&'+encodeURIComponent(dlName);
								dllink = jQuery('<a/>')
									.attr('href', url)
									.attr('download', fm.escape(dlName))
									.on('click', function() {
										dfd.resolve();
										dialog && dialog.elfinderdialog('destroy');
									});
								if (linkdl) {
									dllink.attr('target', '_blank')
										.append('<span class="elfinder-button-icon elfinder-button-icon-download"></span>'+fm.escape(dlName));
									btn[fm.i18n('btnCancel')] = function() {
										dialog.elfinderdialog('destroy');
									};
									dialog = self.fmDialog(dllink, {
										title: fm.i18n('link'),
										buttons: btn,
										width: '200px',
										destroyOnClose: true,
										close: function() {
											(dfd.state() !== 'resolved') && dfd.resolve();
										}
									});
								} else {
									click(dllink.hide().appendTo('body').get(0));
									dllink.remove();
								}
							} else {
								form = jQuery('<form action="'+fm.options.url+'" method="post" target="'+uniq+'" style="display:none"/>')
								.append('<input type="hidden" name="cmd" value="zipdl"/>')
								.append('<input type="hidden" name="download" value="1"/>');
								jQuery.each([hashes[0], zipdl.file, dlName, zipdl.mime], function(key, val) {
									form.append('<input type="hidden" name="targets[]" value="'+fm.escape(val)+'"/>');
								});
								jQuery.each(fm.customData, function(key, val) {
									form.append('<input type="hidden" name="'+key+'" value="'+fm.escape(val)+'"/>');
								});
								form.attr('target', uniq).appendTo('body');
								iframe = jQuery('<iframe style="display:none" name="'+uniq+'">')
									.appendTo('body')
									.ready(function() {
										form.submit().remove();
										dfd.resolve();
										setTimeout(function() {
											iframe.remove();
										}, 20000); // give 20 sec file to be saved
									});
							}
						}
					}).fail(function(error) {
						error && fm.error(error);
						dfd.resolve();
					});
					return dfd.promise();
				};
			},
			// use MouseEvent to click element for Safari etc
			click = function(a) {
				var clickEv;
				if (typeof MouseEvent === 'function') {
					clickEv = new MouseEvent('click');
				} else {
					clickEv = document.createEvent('MouseEvents');
					clickEv.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
				}
				a.dispatchEvent(clickEv);
			},
			checkCookie = function(id) {
				var name = 'elfdl' + id,
					parts;
				parts = document.cookie.split(name + "=");
				if (parts.length === 2) {
					ntftm && clearTimeout(ntftm);
					document.cookie = name + '=; path=' + cpath + '; max-age=0';
					closeNotify();
				} else {
					setTimeout(function() { checkCookie(id); }, 200);
				}
			},
			closeNotify = function() {
				if (fm.ui.notify.children('.elfinder-notify-download').length) {
					fm.notify({
						type : 'download',
						cnt : -1
					});
				}
			},
			reqids = [],
			link, html5dl, fileCnt, clickEv, cid, ntftm, reqid;
			
		if (!files.length) {
			return dfrd.reject();
		}
		
		fileCnt = jQuery.grep(files, function(f) { return f.mime === 'directory'? false : true; }).length;
		link = jQuery('<a>').hide().appendTo('body');
		html5dl = (typeof link.get(0).download === 'string');
		
		if (zipOn && (fileCnt !== files.length || fileCnt >= (this.options.minFilesZipdl || 1))) {
			link.remove();
			linkdl = (!html5dl && fm.UA.Mobile);
			if (mixed) {
				targets = {};
				jQuery.each(files, function(i, f) {
					var p = f.hash.split('_', 2);
					if (! targets[p[0]]) {
						targets[p[0]] = [ f.hash ];
					} else {
						targets[p[0]].push(f.hash);
					}
				});
				if (!linkdl && fm.UA.Mobile && Object.keys(targets).length > 1) {
					linkdl = true;
				}
			} else {
				targets = [ jQuery.map(files, function(f) { return f.hash; }) ];
			}
			dfrd = fm.sequence(jQuery.map(targets, function(t) { return getTask(t); })).always(
				function() {
					fm.trigger('download', {files : files});
				}
			);
			return dfrd;
		} else {
			reqids = [];
			for (i = 0; i < files.length; i++) {
				url = fm.openUrl(files[i].hash, true);
				if (dlntf && url.substr(0, fm.options.url.length) === fm.options.url) {
					reqid = fm.getRequestId();
					reqids.push(reqid);
					url += '&cpath=' + cpath + '&reqid=' + reqid;
					ntftm = setTimeout(function() {
						fm.notify({
							type : 'download',
							cnt : 1,
							cancel : (fm.UA.IE || fm.UA.Edge)? void(0) : function() {
								if (reqids.length) {
									jQuery.each(reqids, function() {
										fm.request({
											data: {
												cmd: 'abort',
												id: this
											},
											preventDefault: true
										});
									});
								}
								reqids = [];
							}
						});
					}, fm.notifyDelay);
					checkCookie(reqid);
				}
				if (html5dl && (!fm.UA.Safari || fm.isSameOrigin(url))) {
					click(link.attr('href', url)
						.attr('download', fm.escape(files[i].name))
						.get(0)
					);
				} else {
					if (fm.UA.Mobile) {
						setTimeout(function(){
							if (! window.open(url)) {
								fm.error('errPopup');
								ntftm && cleaerTimeout(ntftm);
								closeNotify();
							}
						}, 100);
					} else {
						iframes += '<iframe class="downloader" id="downloader-' + files[i].hash+'" style="display:none" src="'+url+'"/>';
					}
				}
			}
			link.remove();
			jQuery(iframes)
				.appendTo('body')
				.ready(function() {
					setTimeout(function() {
						jQuery(iframes).each(function() {
							jQuery('#' + jQuery(this).attr('id')).remove();
						});
					}, 20000 + (10000 * i)); // give 20 sec + 10 sec for each file to be saved
				});
			fm.trigger('download', {files : files});
			return dfrd.resolve();
		}
	};

};


/*
 * File: /js/commands/duplicate.js
 */

/**
 * @class elFinder command "duplicate"
 * Create file/folder copy with suffix "copy Number"
 *
 * @type  elFinder.command
 * @author  Dmitry (dio) Levashov
 */
elFinder.prototype.commands.duplicate = function() {
		var fm = this.fm;
	
	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length;

		return cnt && fm.cwd().write && jQuery.grep(sel, function(f) { return f.read && f.phash === fm.cwd().hash && ! fm.isRoot(f)? true : false; }).length == cnt ? 0 : -1;
	};
	
	this.exec = function(hashes) {
		var fm     = this.fm,
			files  = this.files(hashes),
			cnt    = files.length,
			dfrd   = jQuery.Deferred()
				.fail(function(error) {
					error && fm.error(error);
				}), 
			args = [];
			
		if (! cnt) {
			return dfrd.reject();
		}
		
		jQuery.each(files, function(i, file) {
			if (!file.read || !fm.file(file.phash).write) {
				return !dfrd.reject(['errCopy', file.name, 'errPerm']);
			}
		});
		
		if (dfrd.state() == 'rejected') {
			return dfrd;
		}
		
		return fm.request({
			data   : {cmd : 'duplicate', targets : this.hashes(hashes)},
			notify : {type : 'copy', cnt : cnt},
			navigate : {
				toast : {
					inbuffer : {msg: fm.i18n(['complete', fm.i18n('cmdduplicate')])}
				}
			}
		});
		
	};

};


/*
 * File: /js/commands/edit.js
 */

/**
 * @class elFinder command "edit". 
 * Edit text file in dialog window
 *
 * @author Dmitry (dio) Levashov, dio@std42.ru
 **/
elFinder.prototype.commands.edit = function() {
		var self  = this,
		fm    = this.fm,
		clsEditing = fm.res('class', 'editing'),
		mimesSingle = [],
		mimes = [],
		allowAll = false,
		rtrim = function(str){
			return str.replace(/\s+$/, '');
		},
		getEncSelect = function(heads) {
			var sel = jQuery('<select class="ui-corner-all"/>'),
				hval;
			if (heads) {
				jQuery.each(heads, function(i, head) {
					hval = fm.escape(head.value);
					sel.append('<option value="'+hval+'">'+(head.caption? fm.escape(head.caption) : hval)+'</option>');
				});
			}
			jQuery.each(self.options.encodings, function(i, v) {
				sel.append('<option value="'+v+'">'+v+'</option>');
			});
			return sel;
		},
		getDlgWidth = function() {
			var m, width;
			if (typeof self.options.dialogWidth === 'string' && (m = self.options.dialogWidth.match(/(\d+)%/))) {
				width = parseInt(fm.getUI().width() * (m[1] / 100));
			} else {
				width = parseInt(self.options.dialogWidth || 650);
			}
			return Math.min(width, jQuery(window).width());
		},

		/**
		 * Return files acceptable to edit
		 *
		 * @param  Array  files hashes
		 * @return Array
		 **/
		filter = function(files) {
			var cnt = files.length,
				mime, ext, skip;
			
			if (cnt > 1) {
				mime = files[0].mime;
				ext = files[0].name.replace(/^.*(\.[^.]+)$/, '$1');
			}
			return jQuery.grep(files, function(file) {
				var res;
				if (skip || file.mime === 'directory') {
					return false;
				}
				res = file.read
					&& (allowAll || fm.mimeIsText(file.mime) || jQuery.inArray(file.mime, cnt === 1? mimesSingle : mimes) !== -1) 
					&& (!self.onlyMimes.length || jQuery.inArray(file.mime, self.onlyMimes) !== -1)
					&& (cnt === 1 || (file.mime === mime && file.name.substr(ext.length * -1) === ext))
					&& (fm.uploadMimeCheck(file.mime, file.phash)? true : false)
					&& setEditors(file, cnt)
					&& Object.keys(editors).length;
				if (!res) {
					skip = true;
				}
				return res;
			});
		},

		fileSync = function(hash) {
			var old = fm.file(hash),
				f;
			fm.request({
				cmd: 'info',
				targets: [hash],
				preventDefault: true
			}).done(function(data) {
				var changed;
				if (data && data.files && data.files.length) {
					f = data.files[0];
					if (old.ts != f.ts || old.size != f.size) {
						changed = { changed: [ f ] };
						fm.updateCache(changed);
						fm.change(changed);
					}
				}
			});
		},

		/**
		 * Open dialog with textarea to edit file
		 *
		 * @param  String  id       dialog id
		 * @param  Object  file     file object
		 * @param  String  content  file content
		 * @return jQuery.Deferred
		 **/
		dialog = function(id, file, content, encoding, editor) {

			var dfrd = jQuery.Deferred(),
				_loaded = false,
				loaded = function() {
					if (!_loaded) {
						fm.toast({
							mode: 'warning',
							msg: fm.i18n('nowLoading')
						});
						return false;
					}
					return true;
				},
				save = function() {
					var encord = selEncoding? selEncoding.val():void(0),
						saveDfd = jQuery.Deferred().fail(function(err) {
							dialogNode.show().find('button.elfinder-btncnt-0,button.elfinder-btncnt-1').hide();
						}),
						conf, res;
					if (!loaded()) {
						return saveDfd.resolve();
					}
					if (ta.editor) {
						ta.editor.save(ta[0], ta.editor.instance);
						conf = ta.editor.confObj;
						if (conf.info && (conf.info.schemeContent || conf.info.arrayBufferContent)) {
							encord = 'scheme';
						}
					}
					res = getContent();
					setOld(res);
					if (res.promise) {
						res.done(function(data) {
							dfrd.notifyWith(ta, [encord, ta.data('hash'), old, saveDfd]);
						}).fail(function(err) {
							saveDfd.reject(err);
						});
					} else {
						dfrd.notifyWith(ta, [encord, ta.data('hash'), old, saveDfd]);
					}
					return saveDfd;
				},
				saveon = function() {
					if (!loaded()) { return; }
					save().fail(function(err) {
						err && fm.error(err);
					});
				},
				cancel = function() {
					ta.elfinderdialog('close');
				},
				savecl = function() {
					if (!loaded()) { return; }
					save().done(function() {
						_loaded = false;
						dialogNode.show();
						cancel();
					}).fail(function(err) {
						dialogNode.show();
						err && fm.error(err);
					});
					dialogNode.hide();
				},
				saveAs = function() {
					if (!loaded()) { return; }
					var prevOld = old,
						phash = fm.file(file.phash)? file.phash : fm.cwd().hash,
						fail = function(err) {
							dialogs.addClass(clsEditing).fadeIn(function() {
								err && fm.error(err);
							});
							old = prevOld;
							fm.disable();
						},
						make = function() {
							self.mime = saveAsFile.mime || file.mime;
							self.prefix = (saveAsFile.name || file.name).replace(/ \d+(\.[^.]+)?$/, '$1');
							self.requestCmd = 'mkfile';
							self.nextAction = {};
							self.data = {target : phash};
							jQuery.proxy(fm.res('mixin', 'make'), self)()
								.done(function(data) {
									if (data.added && data.added.length) {
										ta.data('hash', data.added[0].hash);
										save().done(function() {
											_loaded = false;
											dialogNode.show();
											cancel();
											dialogs.fadeIn();
										}).fail(fail);
									} else {
										fail();
									}
								})
								.progress(function(err) {
									if (err && err === 'errUploadMime') {
										ta.trigger('saveAsFail');
									}
								})
								.fail(fail)
								.always(function() {
									delete self.mime;
									delete self.prefix;
									delete self.nextAction;
									delete self.data;
								});
							fm.trigger('unselectfiles', { files: [ file.hash ] });
						},
						reqOpen = null,
						dialogs = fm.getUI().children('.' + self.dialogClass + ':visible');
						if (dialogNode.is(':hidden')) {
							dialogs = dialogs.add(dialogNode);
						}
						dialogs.removeClass(clsEditing).fadeOut();
					
					fm.enable();
					
					if (fm.searchStatus.state < 2 && phash !== fm.cwd().hash) {
						reqOpen = fm.exec('open', [phash], {thash: phash});
					}
					
					jQuery.when([reqOpen]).done(function() {
						reqOpen? fm.one('cwdrender', make) : make();
					}).fail(fail);
				},
				changed = function() {
					var dfd = jQuery.Deferred(),
						res, tm;
					if (!_loaded) {
						return dfd.resolve(false);
					}
					ta.editor && ta.editor.save(ta[0], ta.editor.instance);
					res = getContent();
					if (res && res.promise) {
						tm = setTimeout(function() {
							fm.notify({
								type : 'chkcontent',
								cnt : 1,
								hideCnt: true
							});
						}, 100);
						res.always(function() {
							tm && clearTimeout(tm);
							fm.notify({ type : 'chkcontent', cnt: -1 });
						}).done(function(d) {
							dfd.resolve(old !== d);
						}).fail(function(err) {
							dfd.resolve(err || true);
						});
					} else {
						dfd.resolve(old !== res);
					}
					return dfd;
				},
				opts = {
					title   : fm.escape(file.name),
					width   : getDlgWidth(),
					buttons : {},
					cssClass  : clsEditing,
					maxWidth  : 'window',
					maxHeight : 'window',
					allowMinimize : true,
					allowMaximize : true,
					openMaximized : editorMaximized() || (editor && editor.info && editor.info.openMaximized),
					btnHoverFocus : false,
					closeOnEscape : false,
					propagationEvents : ['mousemove', 'mouseup', 'click'],
					minimize : function() {
						var conf;
						if (ta.editor && dialogNode.closest('.ui-dialog').is(':hidden')) {
							conf = ta.editor.confObj;
							if (conf.info && conf.info.syncInterval) {
								fileSync(file.hash);
							}
						}
					},
					close   : function() {
						var close = function() {
								var conf;
								dfrd.resolve();
								if (ta.editor) {
									ta.editor.close(ta[0], ta.editor.instance);
									conf = ta.editor.confObj;
									if (conf.info && conf.info.syncInterval) {
										fileSync(file.hash);
									}
								}
								ta.elfinderdialog('destroy');
							},
							onlySaveAs = (typeof saveAsFile.name !== 'undefined'),
							accept = onlySaveAs? {
								label    : 'btnSaveAs',
								callback : function() {
									requestAnimationFrame(saveAs);
								}
							} : {
								label    : 'btnSaveClose',
								callback : function() {
									save().done(function() {
										close();
									});
								}
							};
						changed().done(function(change) {
							var msgs = ['confirmNotSave'];
							if (change) {
								if (typeof change === 'string') {
									msgs.unshift(change);
								}
								fm.confirm({
									title  : self.title,
									text   : msgs,
									accept : accept,
									cancel : {
										label    : 'btnClose',
										callback : close
									},
									buttons : onlySaveAs? null : [{
										label    : 'btnSaveAs',
										callback : function() {
											requestAnimationFrame(saveAs);
										}
									}]
								});
							} else {
								close();
							}
						});
					},
					open    : function() {
						var loadRes, conf, interval;
						ta.initEditArea.call(ta, id, file, content, fm);
						if (ta.editor) {
							loadRes = ta.editor.load(ta[0]) || null;
							if (loadRes && loadRes.done) {
								loadRes.always(function() {
									_loaded = true;
								}).done(function(instance) {
									ta.editor.instance = instance;
									ta.editor.focus(ta[0], ta.editor.instance);
									setOld(getContent());
									requestAnimationFrame(function() {
										dialogNode.trigger('resize');
									});
								}).fail(function(error) {
									error && fm.error(error);
									ta.elfinderdialog('destroy');
									return;
								});
							} else {
								_loaded = true;
								if (loadRes && (typeof loadRes === 'string' || Array.isArray(loadRes))) {
									fm.error(loadRes);
									ta.elfinderdialog('destroy');
									return;
								}
								ta.editor.instance = loadRes;
								ta.editor.focus(ta[0], ta.editor.instance);
								setOld(getContent());
								requestAnimationFrame(function() {
									dialogNode.trigger('resize');
								});
							}
							conf = ta.editor.confObj;
							if (conf.info && conf.info.syncInterval) {
								if (interval = parseInt(conf.info.syncInterval)) {
									setTimeout(function() {
										autoSync(interval);
									}, interval);
								}
							}
						} else {
							_loaded = true;
							setOld(getContent());
						}
					},
					resize : function(e, data) {
						ta.editor && ta.editor.resize(ta[0], ta.editor.instance, e, data || {});
					}
				},
				getContent = function() {
					return ta.getContent.call(ta, ta[0]);
				},
				setOld = function(res) {
					if (res && res.promise) {
						res.done(function(d) {
							old = d;
						});
					} else {
						old = res;
					}
				},
				autoSync = function(interval) {
					if (dialogNode.is(':visible')) {
						fileSync(file.hash);
						setTimeout(function() {
							autoSync(interval);
						}, interval);
					}
				},
				saveAsFile = {},
				ta, old, dialogNode, selEncoding, extEditor, maxW, syncInterval;
				
			if (editor) {
				if (editor.html) {
					ta = jQuery(editor.html);
				}
				extEditor = {
					init     : editor.init || null,
					load     : editor.load,
					getContent : editor.getContent || null,
					save     : editor.save,
					beforeclose : typeof editor.beforeclose == 'function' ? editor.beforeclose : void 0,
					close    : typeof editor.close == 'function' ? editor.close : function() {},
					focus    : typeof editor.focus == 'function' ? editor.focus : function() {},
					resize   : typeof editor.resize == 'function' ? editor.resize : function() {},
					instance : null,
					doSave   : saveon,
					doCancel : cancel,
					doClose  : savecl,
					file     : file,
					fm       : fm,
					confObj  : editor,
					trigger  : function(evName, data) {
						fm.trigger('editEditor' + evName, Object.assign({}, editor.info || {}, data));
					}
				};
			}
			
			if (!ta) {
				if (!fm.mimeIsText(file.mime)) {
					return dfrd.reject('errEditorNotFound');
				}
				(function() {
					var stateChange = function() {
							if (selEncoding) {
								changed().done(function(change) {
									if (change) {
										selEncoding.attr('title', fm.i18n('saveAsEncoding')).addClass('elfinder-edit-changed');
									} else {
										selEncoding.attr('title', fm.i18n('openAsEncoding')).removeClass('elfinder-edit-changed');
									}
								});
							}
						};
						
					ta = jQuery('<textarea class="elfinder-file-edit" rows="20" id="'+id+'-ta"></textarea>')
						.on('input propertychange', stateChange);
					
					if (!ta.editor || !ta.editor.info || ta.editor.info.useTextAreaEvent) {
						ta.on('keydown', function(e) {
							var code = e.keyCode,
								value, start;
							
							e.stopPropagation();
							if (code == jQuery.ui.keyCode.TAB) {
								e.preventDefault();
								// insert tab on tab press
								if (this.setSelectionRange) {
									value = this.value;
									start = this.selectionStart;
									this.value = value.substr(0, start) + "\t" + value.substr(this.selectionEnd);
									start += 1;
									this.setSelectionRange(start, start);
								}
							}
							
							if (e.ctrlKey || e.metaKey) {
								// close on ctrl+w/q
								if (code == 'Q'.charCodeAt(0) || code == 'W'.charCodeAt(0)) {
									e.preventDefault();
									cancel();
								}
								if (code == 'S'.charCodeAt(0)) {
									e.preventDefault();
									saveon();
								}
							}
							
						})
						.on('mouseenter', function(){this.focus();});
					}

					ta.initEditArea = function(id, file, content) {
						var heads = (encoding && encoding !== 'unknown')? [{value: encoding}] : [],
							wfake = jQuery('<select/>').hide(),
							setSelW = function(init) {
								init && wfake.appendTo(selEncoding.parent());
								wfake.empty().append(jQuery('<option/>').text(selEncoding.val()));
								selEncoding.width(wfake.width());
							};
						// ta.hide() for performance tune. Need ta.show() in `load()` if use textarea node.
						ta.hide().val(content);
						if (content === '' || ! encoding || encoding !== 'UTF-8') {
							heads.push({value: 'UTF-8'});
						}
						selEncoding = getEncSelect(heads).on('touchstart', function(e) {
							// for touch punch event handler
							e.stopPropagation();
						}).on('change', function() {
							// reload to change encoding if not edited
							changed().done(function(change) {
								if (! change && getContent() !== '') {
									cancel();
									edit(file, selEncoding.val(), editor).fail(function(err) { err && fm.error(err); });
								}
							});
							setSelW();
						}).on('mouseover', stateChange);
						ta.parent().next().prepend(jQuery('<div class="ui-dialog-buttonset elfinder-edit-extras"/>').append(selEncoding));
						setSelW(true);
					};
				})();
			}
			
			ta.data('hash', file.hash);
			
			if (extEditor) {
				ta.editor = extEditor;
				
				if (typeof extEditor.beforeclose === 'function') {
					opts.beforeclose = function() {
						return extEditor.beforeclose(ta[0], extEditor.instance);
					};
				}
				
				if (typeof extEditor.init === 'function') {
					ta.initEditArea = extEditor.init;
				}
				
				if (typeof extEditor.getContent === 'function') {
					ta.getContent = extEditor.getContent;
				}
			}
			
			if (! ta.initEditArea) {
				ta.initEditArea = function() {};
			}
			
			if (! ta.getContent) {
				ta.getContent = function() {
					return rtrim(ta.val());
				};
			}
			
			if (!editor || !editor.info || !editor.info.preventGet) {
				opts.buttons[fm.i18n('btnSave')]      = saveon;
				opts.buttons[fm.i18n('btnSaveClose')] = savecl;
				opts.buttons[fm.i18n('btnSaveAs')]    = saveAs;
				opts.buttons[fm.i18n('btnCancel')]    = cancel;
			}
			
			if (editor && typeof editor.prepare === 'function') {
				editor.prepare(ta, opts, file);
			}
			
			dialogNode = self.fmDialog(ta, opts)
				.attr('id', id)
				.on('keydown keyup keypress', function(e) {
					e.stopPropagation();
				})
				.css({ overflow: 'hidden', minHeight: '7em' })
				.addClass('elfinder-edit-editor')
				.closest('.ui-dialog')
				.on('changeType', function(e, data) {
					if (data.extention && data.mime) {
						var ext = data.extention,
							mime = data.mime,
							btnSet = jQuery(this).children('.ui-dialog-buttonpane').children('.ui-dialog-buttonset');
						btnSet.children('.elfinder-btncnt-0,.elfinder-btncnt-1').hide();
						saveAsFile.name = fm.splitFileExtention(file.name)[0] + '.' + data.extention;
						saveAsFile.mime = data.mime;
						if (!data.keepEditor) {
							btnSet.children('.elfinder-btncnt-2').trigger('click');
						}
					}
				});
			
			// care to viewport scale change with mobile devices
			maxW = (fm.options.dialogContained? elfNode : jQuery(window)).width();
			(dialogNode.width() > maxW) && dialogNode.width(maxW);
			
			return dfrd.promise();
		},
		
		/**
		 * Get file content and
		 * open dialog with textarea to edit file content
		 *
		 * @param  String  file hash
		 * @return jQuery.Deferred
		 **/
		edit = function(file, convert, editor) {
			var hash   = file.hash,
				opts   = fm.options,
				dfrd   = jQuery.Deferred(), 
				id     = 'edit-'+fm.namespace+'-'+file.hash,
				d      = fm.getUI().find('#'+id),
				conv   = !convert? 0 : convert,
				req, error, res;
			
			
			if (d.length) {
				d.elfinderdialog('toTop');
				return dfrd.resolve();
			}
			
			if (!file.read || (!file.write && (!editor.info || !editor.info.converter))) {
				error = ['errOpen', file.name, 'errPerm'];
				return dfrd.reject(error);
			}
			
			if (editor && editor.info) {
				if (typeof editor.info.edit === 'function') {
					res = editor.info.edit.call(fm, file, editor);
					if (res.promise) {
						res.done(function() {
							dfrd.resolve();
						}).fail(function(error) {
							dfrd.reject(error);
						});
					} else {
						res? dfrd.resolve() : dfrd.reject();
					}
					return dfrd;
				}

				if (editor.info.urlAsContent || editor.info.preventGet || editor.info.noContent) {
					req = jQuery.Deferred();
					if (editor.info.urlAsContent) {
						fm.url(hash, { async: true, onetime: true, temporary: true }).done(function(url) {
							req.resolve({content: url});
						});
					} else {
						req.resolve({});
					}
				} else {
					req = fm.request({
						data           : {cmd : 'get', target : hash, conv : conv, _t : file.ts},
						options        : {type: 'get', cache : true},
						notify         : {type : 'file', cnt : 1},
						preventDefault : true
					});
				}

				req.done(function(data) {
					var selEncoding, reg, m, res;
					if (data.doconv) {
						fm.confirm({
							title  : self.title,
							text   : data.doconv === 'unknown'? 'confirmNonUTF8' : 'confirmConvUTF8',
							accept : {
								label    : 'btnConv',
								callback : function() {  
									dfrd = edit(file, selEncoding.val(), editor);
								}
							},
							cancel : {
								label    : 'btnCancel',
								callback : function() { dfrd.reject(); }
							},
							optionsCallback : function(options) {
								options.create = function() {
									var base = jQuery('<div class="elfinder-dialog-confirm-encoding"/>'),
										head = {value: data.doconv},
										detected;
									
									if (data.doconv === 'unknown') {
										head.caption = '-';
									}
									selEncoding = getEncSelect([head]);
									jQuery(this).next().find('.ui-dialog-buttonset')
										.prepend(base.append(jQuery('<label>'+fm.i18n('encoding')+' </label>').append(selEncoding)));
								};
							}
						});
					} else {
						if ((!editor || !editor.info || !editor.info.preventGet) && fm.mimeIsText(file.mime)) {
							reg = new RegExp('^(data:'+file.mime.replace(/([.+])/g, '\\$1')+';base64,)', 'i');
							if (!editor.info.dataScheme) {
								if (window.atob && (m = data.content.match(reg))) {
									data.content = atob(data.content.substr(m[1].length));
								}
							} else {
								if (window.btoa && !data.content.match(reg)) {
									data.content = 'data:'+file.mime+';base64,'+btoa(data.content);
								}
							}
						}
						dialog(id, file, data.content, data.encoding, editor)
							.done(function(data) {
								dfrd.resolve(data);
							})
							.progress(function(encoding, newHash, data, saveDfd) {
								var ta = this;
								if (newHash) {
									hash = newHash;
								}
								fm.request({
									options : {type : 'post'},
									data : {
										cmd     : 'put',
										target  : hash,
										encoding : encoding || data.encoding,
										content : data
									},
									notify : {type : 'save', cnt : 1},
									syncOnFail : true,
									preventFail : true,
									navigate : {
										target : 'changed',
										toast : {
											inbuffer : {msg: fm.i18n(['complete', fm.i18n('btnSave')])}
										}
									}
								})
								.fail(function(error) {
									dfrd.reject(error);
									saveDfd.reject();
								})
								.done(function(data) {
									requestAnimationFrame(function(){
										ta.trigger('focus');
										ta.editor && ta.editor.focus(ta[0], ta.editor.instance);
									});
									saveDfd.resolve();
								});
							})
							.fail(function(error) {
								dfrd.reject(error);
							});
					}
				})
				.fail(function(error) {
					var err = fm.parseError(error);
					err = Array.isArray(err)? err[0] : err;
					(err !== 'errConvUTF8') && fm.sync();
					dfrd.reject(error);
				});
			}

			return dfrd.promise();
		},
		
		/**
		 * Current editors of selected files
		 * 
		 * @type Object
		 */
		editors = {},
		
		/**
		 * Fallback editor (Simple text editor)
		 * 
		 * @type Object
		 */
		fallbackEditor = {
			// Simple Text (basic textarea editor)
			info : {
				id : 'textarea',
				name : 'TextArea',
				useTextAreaEvent : true
			},
			load : function(textarea) {
				// trigger event 'editEditorPrepare'
				this.trigger('Prepare', {
					node: textarea,
					editorObj: void(0),
					instance: void(0),
					opts: {}
				});
				textarea.setSelectionRange && textarea.setSelectionRange(0, 0);
				jQuery(textarea).trigger('focus').show();
			},
			save : function(){}
		},

		/**
		 * Set current editors
		 * 
		 * @param  Object  file object
		 * @param  Number  cnt  count of selected items
		 * @return Void
		 */
		setEditors = function(file, cnt) {
			var mimeMatch = function(fileMime, editorMimes){
					if (!editorMimes) {
						return fm.mimeIsText(fileMime);
					} else {
						if (editorMimes[0] === '*' || jQuery.inArray(fileMime, editorMimes) !== -1) {
							return true;
						}
						var i, l;
						l = editorMimes.length;
						for (i = 0; i < l; i++) {
							if (fileMime.indexOf(editorMimes[i]) === 0) {
								return true;
							}
						}
						return false;
					}
				},
				extMatch = function(fileName, editorExts){
					if (!editorExts || !editorExts.length) {
						return true;
					}
					var ext = fileName.replace(/^.+\.([^.]+)|(.+)$/, '$1$2').toLowerCase(),
					i, l;
					l = editorExts.length;
					for (i = 0; i < l; i++) {
						if (ext === editorExts[i].toLowerCase()) {
							return true;
						}
					}
					return false;
				},
				optEditors = self.options.editors || [],
				cwdWrite = fm.cwd().write;
			
			stored = fm.storage('storedEditors') || {};
			editors = {};
			if (!optEditors.length) {
				optEditors = [fallbackEditor];
			}
			jQuery.each(optEditors, function(i, editor) {
				var name;
				if ((cnt === 1 || !editor.info.single)
						&& ((!editor.info || !editor.info.converter)? file.write : cwdWrite)
						&& (file.size > 0 || (!editor.info.converter && (editor.info.canMakeEmpty || (editor.info.canMakeEmpty !== false && fm.mimeIsText(file.mime)))))
						&& (!editor.info.maxSize || file.size <= editor.info.maxSize)
						&& mimeMatch(file.mime, editor.mimes || null)
						&& extMatch(file.name, editor.exts || null)
						&& typeof editor.load == 'function'
						&& typeof editor.save == 'function') {
					
					name = editor.info.name? editor.info.name : ('Code Editor');
					editor.id = editor.info.id? editor.info.id : ('editor' + i),
					editor.name = name;
					editor.i18n = fm.i18n(name);
					editors[editor.id] = editor;
				}
			});
			return Object.keys(editors).length? true : false;
		},
		store = function(mime, editor) {
			if (mime && editor) {
				if (!jQuery.isPlainObject(stored)) {
					stored = {};
				}
				stored[mime] = editor.id;
				fm.storage('storedEditors', stored);
				fm.trigger('selectfiles', {files : fm.selected()});
			}
		},
		useStoredEditor = function() {
			var d = fm.storage('useStoredEditor');
			return d? (d > 0) : self.options.useStoredEditor;
		},
		editorMaximized = function() {
			var d = fm.storage('editorMaximized');
			return d? (d > 0) : self.options.editorMaximized;
		},
		getSubMenuRaw = function(files, callback) {
			var subMenuRaw = [];
			jQuery.each(editors, function(id, ed) {
				subMenuRaw.push(
					{
						label    : fm.escape(ed.i18n),
						icon     : ed.info && ed.info.icon? ed.info.icon : 'edit',
						options  : { iconImg: ed.info && ed.info.iconImg? fm.baseUrl + ed.info.iconImg : void(0) },
						callback : function() {
							store(files[0].mime, ed);
							callback && callback.call(ed);
						}
					}		
				);
			});
			return subMenuRaw;
		},
		getStoreId = function(name) {
			// for compatibility to previous version
			return name.toLowerCase().replace(/ +/g, '');
		},
		getStoredEditor = function(mime) {
			var name = stored[mime];
			return name && Object.keys(editors).length? editors[getStoreId(name)] : void(0);
		},
		infoRequest = function() {

		},
		stored;
	
	
	this.shortcuts = [{
		pattern     : 'ctrl+e'
	}];
	
	this.init = function() {
		var self = this,
			fm   = this.fm,
			opts = this.options,
			cmdChecks = [],
			ccData, dfd;
		
		this.onlyMimes = this.options.mimes || [];
		
		fm.one('open', function() {
			// editors setup
			if (opts.editors && Array.isArray(opts.editors)) {
				fm.trigger('canMakeEmptyFile', {mimes: Object.keys(fm.storage('mkfileTextMimes') || {}).concat(opts.makeTextMimes || ['text/plain'])});
				jQuery.each(opts.editors, function(i, editor) {
					if (editor.info && editor.info.cmdCheck) {
						cmdChecks.push(editor.info.cmdCheck);
					}
				});
				if (cmdChecks.length) {
					if (fm.api >= 2.1030) {
						dfd = fm.request({
							data : {
								cmd: 'editor',
								name: cmdChecks,
								method: 'enabled'
							},
							preventDefault : true
						}).done(function(d) {
							ccData = d;
						}).fail(function() {
							ccData = {};
						});
					} else {
						ccData = {};
						dfd = jQuery.Deferred().resolve();
					}
				} else {
					dfd = jQuery.Deferred().resolve();
				}
				
				dfd.always(function() {
					if (ccData) {
						opts.editors = jQuery.grep(opts.editors, function(e) {
							if (e.info && e.info.cmdCheck) {
								return ccData[e.info.cmdCheck]? true : false;
							} else {
								return true;
							}
						});
					}
					jQuery.each(opts.editors, function(i, editor) {
						if (editor.setup && typeof editor.setup === 'function') {
							editor.setup.call(editor, opts, fm);
						}
						if (!editor.disabled) {
							if (editor.mimes && Array.isArray(editor.mimes)) {
								mimesSingle = mimesSingle.concat(editor.mimes);
								if (!editor.info || !editor.info.single) {
									mimes = mimes.concat(editor.mimes);
								}
							}
							if (!allowAll && editor.mimes && editor.mimes[0] === '*') {
								allowAll = true;
							}
							if (!editor.info) {
								editor.info = {};
							}
							if (editor.info.integrate) {
								fm.trigger('helpIntegration', Object.assign({cmd: 'edit'}, editor.info.integrate));
							}
							if (editor.info.canMakeEmpty) {
								fm.trigger('canMakeEmptyFile', {mimes: editor.mimes});
							}
						}
					});
					
					mimesSingle = (jQuery.uniqueSort || jQuery.unique)(mimesSingle);
					mimes = (jQuery.uniqueSort || jQuery.unique)(mimes);
					
					opts.editors = jQuery.grep(opts.editors, function(e) {
						return e.disabled? false : true;
					});
				});
			}
		})
		.bind('select', function() {
			editors = null;
		})
		.bind('contextmenucreate', function(e) {
			var file, editor,
				single = function(editor) {
					var title = self.title;
					fm.one('contextmenucreatedone', function() {
						self.title = title;
					});
					self.title = fm.escape(editor.i18n);
					if (editor.info && editor.info.iconImg) {
						self.contextmenuOpts = {
							iconImg: fm.baseUrl + editor.info.iconImg
						};
					}
					delete self.variants;
				};
			
			self.contextmenuOpts = void(0);
			if (e.data.type === 'files' && self.enabled()) {
				file = fm.file(e.data.targets[0]);
				if (setEditors(file, e.data.targets.length)) {
					if (Object.keys(editors).length > 1) {
						if (!useStoredEditor() || !(editor = getStoredEditor(file.mime))) {
							delete self.extra;
							self.variants = [];
							jQuery.each(editors, function(id, editor) {
								self.variants.push([{ editor: editor }, editor.i18n, editor.info && editor.info.iconImg? fm.baseUrl + editor.info.iconImg : 'edit']);
							});
						} else {
							single(editor);
							self.extra = {
								icon: 'menu',
								node: jQuery('<span/>')
									.attr({title: fm.i18n('select')})
									.on('click touchstart', function(e){
										if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
											return;
										}
										var node = jQuery(this);
										e.stopPropagation();
										e.preventDefault();
										fm.trigger('contextmenu', {
											raw: getSubMenuRaw(fm.selectedFiles(), function() {
												var hashes = fm.selected();
												fm.exec('edit', hashes, {editor: this});
												fm.trigger('selectfiles', {files : hashes});
											}),
											x: node.offset().left,
											y: node.offset().top
										});
									})
							};
						}
					} else {
						single(editors[Object.keys(editors)[0]]);
						delete self.extra;
					}
				}
			}
		})
		.bind('canMakeEmptyFile', function(e) {
			if (e.data && e.data.resetTexts) {
				var defs = fm.arrayFlip(self.options.makeTextMimes || ['text/plain']),
					hides = fm.storage('mkfileHides') || {};

				jQuery.each((fm.storage('mkfileTextMimes') || {}), function(mime, type) {
					if (!defs[mime]) {
						delete fm.mimesCanMakeEmpty[mime];
						delete hides[mime];
					}
				});
				fm.storage('mkfileTextMimes', null);
				if (Object.keys(hides).length) {
					fm.storage('mkfileHides', hides);
				} else {
					fm.storage('mkfileHides', null);
				}
			}
		});
	};
	
	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length;

		return cnt && filter(sel).length == cnt ? 0 : -1;
	};
	
	this.exec = function(select, opts) {
		var fm    = this.fm, 
			files = filter(this.files(select)),
			hashes = jQuery.map(files, function(f) { return f.hash; }),
			list  = [],
			editor = opts && opts.editor? opts.editor : null,
			node = jQuery(opts && opts._currentNode? opts._currentNode : fm.cwdHash2Elm(hashes[0])),
			getEditor = function() {
				var dfd = jQuery.Deferred(),
					storedId;
				
				if (!editor && Object.keys(editors).length > 1) {
					if (useStoredEditor() && (editor = getStoredEditor(files[0].mime))) {
						return dfd.resolve(editor);
					}
					fm.trigger('contextmenu', {
						raw: getSubMenuRaw(files, function() {
							dfd.resolve(this);
						}),
						x: node.offset().left,
						y: node.offset().top + 22,
						opened: function() {
							fm.one('closecontextmenu',function() {
								requestAnimationFrame(function() {
									if (dfd.state() === 'pending') {
										dfd.reject();
									}
								});
							});
						}
					});
					
					fm.trigger('selectfiles', {files : hashes});
					
					return dfd;
				} else {
					Object.keys(editors).length > 1 && editor && store(files[0].mime, editor);
					return dfd.resolve(editor? editor : (Object.keys(editors).length? editors[Object.keys(editors)[0]] : null));
				}
			},
			dfrd = jQuery.Deferred(),
			file;

		if (editors === null) {
			setEditors(files[0], hashes.length);
		}
		
		if (!node.length) {
			node = fm.getUI('cwd');
		}
		
		getEditor().done(function(editor) {
			while ((file = files.shift())) {
				list.push(edit(file, void(0), editor).fail(function(error) {
					error && fm.error(error);
				}));
			}
			
			if (list.length) { 
				jQuery.when.apply(null, list).done(function() {
					dfrd.resolve();
				}).fail(function() {
					dfrd.reject();
				});
			} else {
				dfrd.reject();
			}
		}).fail(function() {
			dfrd.reject();
		});
		
		return dfrd;
	};

};


/*
 * File: /js/commands/empty.js
 */

/**
 * @class elFinder command "empty".
 * Empty the folder
 *
 * @type  elFinder.command
 * @author  Naoki Sawada
 */
elFinder.prototype.commands.empty = function() {
		var self, fm,
		selFiles = function(select) {
			var sel = self.files(select);
			if (!sel.length) {
				sel = [ fm.cwd() ];
			}
			return sel;
		};
	
	this.linkedCmds = ['rm'];
	
	this.init = function() {
		// lazy assign to make possible to become superclass
		self = this;
		fm = this.fm;
	};

	this.getstate = function(select) {
		var sel = selFiles(select),
			cnt;
		
		cnt = sel.length;
		return jQuery.grep(sel, function(f) { return f.read && f.write && f.mime === 'directory' ? true : false; }).length == cnt ? 0 : -1;
	};
	
	this.exec = function(hashes) {
		var dirs = selFiles(hashes),
			cnt  = dirs.length,
			dfrd = jQuery.Deferred()
				.done(function() {
					var data = {changed: {}};
					fm.toast({msg: fm.i18n(['"'+success.join('", ')+'"', 'complete', fm.i18n('cmdempty')])});
					jQuery.each(dirs, function(i, dir) {
						data.changed[dir.hash] = dir;
					});
					fm.change(data);
				})
				.always(function() {
					var cwd = fm.cwd().hash;
					fm.trigger('selectfiles', {files: jQuery.map(dirs, function(d) { return cwd === d.phash? d.hash : null; })});
				}),
			success = [],
			done = function(res) {
				if (typeof res === 'number') {
					success.push(dirs[res].name);
					delete dirs[res].dirs;
				} else {
					res && fm.error(res);
				}
				(--cnt < 1) && dfrd[success.length? 'resolve' : 'reject']();
			};

		jQuery.each(dirs, function(i, dir) {
			var tm;
			if (!(dir.write && dir.mime === 'directory')) {
				done(['errEmpty', dir.name, 'errPerm']);
				return null;
			}
			if (!fm.isCommandEnabled('rm', dir.hash)) {
				done(['errCmdNoSupport', '"rm"']);
				return null;
			}
			tm = setTimeout(function() {
				fm.notify({type : 'search', cnt : 1, hideCnt : cnt > 1? false : true});
			}, fm.notifyDelay);
			fm.request({
				data : {cmd  : 'open', target : dir.hash},
				preventDefault : true,
				asNotOpen : true
			}).done(function(data) {
				var targets = [];
				tm && clearTimeout(tm);
				if (fm.ui.notify.children('.elfinder-notify-search').length) {
					fm.notify({type : 'search', cnt : -1, hideCnt : cnt > 1? false : true});
				}
				if (data && data.files && data.files.length) {
					if (data.files.length > fm.maxTargets) {
						done(['errEmpty', dir.name, 'errMaxTargets', fm.maxTargets]);
					} else {
						fm.updateCache(data);
						jQuery.each(data.files, function(i, f) {
							if (!f.write || f.locked) {
								done(['errEmpty', dir.name, 'errRm', f.name, 'errPerm']);
								targets = [];
								return false;
							}
							targets.push(f.hash);
						});
						if (targets.length) {
							fm.exec('rm', targets, { _userAction : true, addTexts : [ fm.i18n('folderToEmpty', dir.name) ] })
							.fail(function(error) {
								fm.trigger('unselectfiles', {files: fm.selected()});
								done(fm.parseError(error) || '');
							})
							.done(function() { done(i); });
						}
					}
				} else {
					fm.toast({ mode: 'warning', msg: fm.i18n('filderIsEmpty', dir.name)});
					done('');
				}
			}).fail(function(error) {
				done(fm.parseError(error) || '');
			});
		});
		
		return dfrd;
	};

};


/*
 * File: /js/commands/extract.js
 */

/**
 * @class  elFinder command "extract"
 * Extract files from archive
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.extract = function() {
		var self    = this,
		fm      = self.fm,
		mimes   = [],
		filter  = function(files) {
			return jQuery.grep(files, function(file) { 
				return file.read && jQuery.inArray(file.mime, mimes) !== -1 ? true : false;
			});
		};
	
	this.variants = [];
	this.disableOnSearch = true;
	
	// Update mimes list on open/reload
	fm.bind('open reload', function() {
		mimes = fm.option('archivers')['extract'] || [];
		if (fm.api > 2) {
			self.variants = [[{makedir: true}, fm.i18n('cmdmkdir')], [{}, fm.i18n('btnCwd')]];
		} else {
			self.variants = [[{}, fm.i18n('btnCwd')]];
		}
		self.change();
	});
	
	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length;
		
		return cnt && this.fm.cwd().write && filter(sel).length == cnt ? 0 : -1;
	};
	
	this.exec = function(hashes, opts) {
		var files    = this.files(hashes),
			dfrd     = jQuery.Deferred(),
			cnt      = files.length,
			makedir  = opts && opts.makedir ? 1 : 0,
			i, error,
			decision;

		var overwriteAll = false;
		var omitAll = false;
		var mkdirAll = 0;

		var names = jQuery.map(fm.files(hashes), function(file) { return file.name; });
		var map = {};
		jQuery.grep(fm.files(hashes), function(file) {
			map[file.name] = file;
			return false;
		});
		
		var decide = function(decision) {
			switch (decision) {
				case 'overwrite_all' :
					overwriteAll = true;
					break;
				case 'omit_all':
					omitAll = true;
					break;
			}
		};

		var unpack = function(file) {
			if (!(file.read && fm.file(file.phash).write)) {
				error = ['errExtract', file.name, 'errPerm'];
				fm.error(error);
				dfrd.reject(error);
			} else if (jQuery.inArray(file.mime, mimes) === -1) {
				error = ['errExtract', file.name, 'errNoArchive'];
				fm.error(error);
				dfrd.reject(error);
			} else {
				fm.request({
					data:{cmd:'extract', target:file.hash, makedir:makedir},
					notify:{type:'extract', cnt:1},
					syncOnFail:true,
					navigate:{
						toast : makedir? {
							incwd    : {msg: fm.i18n(['complete', fm.i18n('cmdextract')]), action: {cmd: 'open', msg: 'cmdopen'}},
							inbuffer : {msg: fm.i18n(['complete', fm.i18n('cmdextract')]), action: {cmd: 'open', msg: 'cmdopen'}}
						} : {
							inbuffer : {msg: fm.i18n(['complete', fm.i18n('cmdextract')])}
						}
					}
				})
				.fail(function (error) {
					if (dfrd.state() != 'rejected') {
						dfrd.reject(error);
					}
				})
				.done(function () {
				});
			}
		};
		
		var confirm = function(files, index) {
			var file = files[index],
			name = fm.splitFileExtention(file.name)[0],
			existed = (jQuery.inArray(name, names) >= 0),
			next = function(){
				if((index+1) < cnt) {
					confirm(files, index+1);
				} else {
					dfrd.resolve();
				}
			};
			if (!makedir && existed && map[name].mime != 'directory') {
				fm.confirm(
					{
						title : fm.i18n('ntfextract'),
						text  : ['errExists', name, 'confirmRepl'],
						accept:{
							label : 'btnYes',
							callback:function (all) {
								decision = all ? 'overwrite_all' : 'overwrite';
								decide(decision);
								if(!overwriteAll && !omitAll) {
									if('overwrite' == decision) {
										unpack(file);
									}
									if((index+1) < cnt) {
										confirm(files, index+1);
									} else {
										dfrd.resolve();
									}
								} else if(overwriteAll) {
									for (i = index; i < cnt; i++) {
										unpack(files[i]);
									}
									dfrd.resolve();
								}
							}
						},
						reject : {
							label : 'btnNo',
							callback:function (all) {
								decision = all ? 'omit_all' : 'omit';
								decide(decision);
								if(!overwriteAll && !omitAll && (index+1) < cnt) {
									confirm(files, index+1);
								} else if (omitAll) {
									dfrd.resolve();
								}
							}
						},
						cancel : {
							label : 'btnCancel',
							callback:function () {
								dfrd.resolve();
							}
						},
						all : ((index+1) < cnt)
					}
				);
			} else if (!makedir) {
				if (mkdirAll == 0) {
					fm.confirm({
						title : fm.i18n('cmdextract'),
						text  : [fm.i18n('cmdextract')+' "'+file.name+'"', 'confirmRepl'],
						accept:{
							label : 'btnYes',
							callback:function (all) {
								all && (mkdirAll = 1);
								unpack(file);
								next();
							}
						},
						reject : {
							label : 'btnNo',
							callback:function (all) {
								all && (mkdirAll = -1);
								next();
							}
						},
						cancel : {
							label : 'btnCancel',
							callback:function () {
								dfrd.resolve();
							}
						},
						all : ((index+1) < cnt)
					});
				} else {
					(mkdirAll > 0) && unpack(file);
					next();
				}
			} else {
				unpack(file);
				next();
			}
		};
		
		if (!(this.enabled() && cnt && mimes.length)) {
			return dfrd.reject();
		}
		
		if(cnt > 0) {
			confirm(files, 0);
		}

		return dfrd;
	};

};


/*
 * File: /js/commands/forward.js
 */

/**
 * @class  elFinder command "forward"
 * Open next visited folder
 *
 * @author Dmitry (dio) Levashov
 **/
(elFinder.prototype.commands.forward = function() {
		this.alwaysEnabled = true;
	this.updateOnSelect = true;
	this.shortcuts = [{
		pattern     : 'ctrl+right'
	}];
	
	this.getstate = function() {
		return this.fm.history.canForward() ? 0 : -1;
	};
	
	this.exec = function() {
		return this.fm.history.forward();
	};
	
}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/fullscreen.js
 */

/**
 * @class  elFinder command "fullscreen"
 * elFinder node to full scrren mode
 *
 * @author Naoki Sawada
 **/

elFinder.prototype.commands.fullscreen = function() {
		var self   = this,
		fm     = this.fm,
		update = function(e, data) {
			e.preventDefault();
			e.stopPropagation();
			if (data && data.fullscreen) {
				self.update(void(0), (data.fullscreen === 'on'));
			}
		};

	this.alwaysEnabled  = true;
	this.updateOnSelect = false;
	this.syncTitleOnChange = true;
	this.value = false;

	this.options = {
		ui : 'fullscreenbutton'
	};

	this.getstate = function() {
		return 0;
	};
	
	this.exec = function() {
		var node = fm.getUI().get(0),
			full = (node === fm.toggleFullscreen(node));
		self.title = fm.i18n(full ? 'reinstate' : 'cmdfullscreen');
		self.update(void(0), full);
		return jQuery.Deferred().resolve();
	};
	
	fm.bind('init', function() {
		fm.getUI().off('resize.' + fm.namespace, update).on('resize.' + fm.namespace, update);
	});
};


/*
 * File: /js/commands/getfile.js
 */

/**
 * @class elFinder command "getfile". 
 * Return selected files info into outer callback.
 * For use elFinder with wysiwyg editors etc.
 *
 * @author Dmitry (dio) Levashov, dio@std42.ru
 **/
(elFinder.prototype.commands.getfile = function() {
		var self   = this,
		fm     = this.fm,
		filter = function(files) {
			var o = self.options;

			files = jQuery.grep(files, function(file) {
				return (file.mime != 'directory' || o.folders) && file.read ? true : false;
			});

			return o.multiple || files.length == 1 ? files : [];
		};
	
	this.alwaysEnabled = true;
	this.callback      = fm.options.getFileCallback;
	this._disabled     = typeof(this.callback) == 'function';
	
	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length;
			
		return this.callback && cnt && filter(sel).length == cnt ? 0 : -1;
	};
	
	this.exec = function(hashes) {
		var fm    = this.fm,
			opts  = this.options,
			files = this.files(hashes),
			cnt   = files.length,
			url   = fm.option('url'),
			tmb   = fm.option('tmbUrl'),
			dfrd  = jQuery.Deferred()
				.done(function(data) {
					var res,
						done = function() {
							if (opts.oncomplete == 'close') {
								fm.hide();
							} else if (opts.oncomplete == 'destroy') {
								fm.destroy();
							}
						},
						fail = function(error) {
							if (opts.onerror == 'close') {
								fm.hide();
							} else if (opts.onerror == 'destroy') {
								fm.destroy();
							} else {
								error && fm.error(error);
							}
						};
					
					fm.trigger('getfile', {files : data});
					
					try {
						res = self.callback(data, fm);
					} catch(e) {
						fail(['Error in `getFileCallback`.', e.message]);
						return;
					}
					
					if (typeof res === 'object' && typeof res.done === 'function') {
						res.done(done).fail(fail);
					} else {
						done();
					}
				}),
			result = function(file) {
				return opts.onlyURL
					? opts.multiple ? jQuery.map(files, function(f) { return f.url; }) : files[0].url
					: opts.multiple ? files : files[0];
			},
			req = [], 
			i, file, dim;

		for (i = 0; i < cnt; i++) {
			file = files[i];
			if (file.mime == 'directory' && !opts.folders) {
				return dfrd.reject();
			}
			file.baseUrl = url;
			if (file.url == '1') {
				req.push(fm.request({
					data : {cmd : 'url', target : file.hash},
					notify : {type : 'url', cnt : 1, hideCnt : true},
					preventDefault : true
				})
				.done(function(data) {
					if (data.url) {
						var rfile = fm.file(this.hash);
						rfile.url = this.url = data.url;
					}
				}.bind(file)));
			} else {
				file.url = fm.url(file.hash);
			}
			if (! opts.onlyURL) {
				if (opts.getPath) {
					file.path = fm.path(file.hash);
					if (file.path === '' && file.phash) {
						// get parents
						(function() {
							var dfd  = jQuery.Deferred();
							req.push(dfd);
							fm.path(file.hash, false, {})
								.done(function(path) {
									file.path = path;
								})
								.fail(function() {
									file.path = '';
								})
								.always(function() {
									dfd.resolve();
								});
						})();
					}
				}
				if (file.tmb && file.tmb != 1) {
					file.tmb = tmb + file.tmb;
				}
				if (!file.width && !file.height) {
					if (file.dim) {
						dim = file.dim.split('x');
						file.width = dim[0];
						file.height = dim[1];
					} else if (opts.getImgSize && file.mime.indexOf('image') !== -1) {
						req.push(fm.request({
							data : {cmd : 'dim', target : file.hash},
							notify : {type : 'dim', cnt : 1, hideCnt : true},
							preventDefault : true
						})
						.done(function(data) {
							if (data.dim) {
								var dim = data.dim.split('x');
								var rfile = fm.file(this.hash);
								rfile.width = this.width = dim[0];
								rfile.height = this.height = dim[1];
							}
						}.bind(file)));
					}
				}
			}
		}
		
		if (req.length) {
			jQuery.when.apply(null, req).always(function() {
				dfrd.resolve(result(files));
			});
			return dfrd;
		}
		
		return dfrd.resolve(result(files));
	};

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/help.js
 */

/**
 * @class  elFinder command "help"
 * "About" dialog
 *
 * @author Dmitry (dio) Levashov
 **/
(elFinder.prototype.commands.help = function() {
		var fm   = this.fm,
		self = this,
		linktpl = '<div class="elfinder-help-link"> <a href="{url}">{link}</a></div>',
		linktpltgt = '<div class="elfinder-help-link"> <a href="{url}" target="_blank">{link}</a></div>',
		atpl    = '<div class="elfinder-help-team"><div>{author}</div>{work}</div>',
		url     = /\{url\}/,
		link    = /\{link\}/,
		author  = /\{author\}/,
		work    = /\{work\}/,
		r       = 'replace',
		prim    = 'ui-priority-primary',
		sec     = 'ui-priority-secondary',
		lic     = 'elfinder-help-license',
		tab     = '<li class="' + fm.res('class', 'tabstab') + ' elfinder-help-tab-{id}"><a href="#'+fm.namespace+'-help-{id}" class="ui-tabs-anchor">{title}</a></li>',
		html    = ['<div class="ui-tabs ui-widget ui-widget-content ui-corner-all elfinder-help">', 
				'<ul class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-top">'],
		stpl    = '<div class="elfinder-help-shortcut"><div class="elfinder-help-shortcut-pattern">{pattern}</div> {descrip}</div>',
		sep     = '<div class="elfinder-help-separator"/>',
		selfUrl = jQuery('base').length? document.location.href.replace(/#.*$/, '') : '',
		clTabActive = fm.res('class', 'tabsactive'),
		
		getTheme = function() {
			var src;
			if (fm.theme && fm.theme.author) {
				src = atpl[r]('elfinder-help-team', 'elfinder-help-team elfinder-help-term-theme')[r](author, fm.i18n(fm.theme.author) + (fm.theme.email? ' &lt;'+fm.theme.email+'&gt;' : ''))[r](work, fm.i18n('theme') + ' ('+fm.i18n(fm.theme.name)+')');
			} else {
				src = '<div class="elfinder-help-team elfinder-help-term-theme" style="display:none"></div>';
			}
			return src;
		},

		about = function() {
			html.push('<div id="'+fm.namespace+'-help-about" class="ui-tabs-panel ui-widget-content ui-corner-bottom"><div class="elfinder-help-logo"/>');
			html.push('<h3>elFinder</h3>');
			html.push('<div class="'+prim+'">'+fm.i18n('webfm')+'</div>');
			html.push('<div class="'+sec+'">'+fm.i18n('ver')+': '+fm.version+'</div>');
			html.push('<div class="'+sec+'">'+fm.i18n('protocolver')+': <span class="apiver"></span></div>');
			html.push('<div class="'+sec+'">jQuery/jQuery UI: '+jQuery().jquery+'/'+jQuery.ui.version+'</div>');

			html.push(sep);
			
			html.push(linktpltgt[r](url, 'https://studio-42.github.io/elFinder/')[r](link, fm.i18n('homepage')));
			html.push(linktpltgt[r](url, 'https://github.com/Studio-42/elFinder/wiki')[r](link, fm.i18n('docs')));
			html.push(linktpltgt[r](url, 'https://github.com/Studio-42/elFinder')[r](link, fm.i18n('github')));
			//html.push(linktpltgt[r](url, 'http://twitter.com/elrte_elfinder')[r](link, fm.i18n('twitter')));
			
			html.push(sep);
			
			html.push('<div class="'+prim+'">'+fm.i18n('team')+'</div>');
			
			html.push(atpl[r](author, 'Dmitry "dio" Levashov &lt;dio@std42.ru&gt;')[r](work, fm.i18n('chiefdev')));
			html.push(atpl[r](author, 'Naoki Sawada &lt;hypweb+elfinder@gmail.com&gt;')[r](work, fm.i18n('developer')));
			html.push(atpl[r](author, 'Troex Nevelin &lt;troex@fury.scancode.ru&gt;')[r](work, fm.i18n('maintainer')));
			html.push(atpl[r](author, 'Alexey Sukhotin &lt;strogg@yandex.ru&gt;')[r](work, fm.i18n('contributor')));
			
			if (fm.i18[fm.lang].translator) {
				jQuery.each(fm.i18[fm.lang].translator.split(', '), function() {
					html.push(atpl[r](author, jQuery.trim(this))[r](work, fm.i18n('translator')+' ('+fm.i18[fm.lang].language+')'));
				});	
			}
			
			html.push(getTheme());

			html.push(sep);
			html.push('<div class="'+lic+'">'+fm.i18n('icons')+': Pixelmixer, <a href="http://p.yusukekamiyamane.com" target="_blank">Fugue</a>, <a href="https://icons8.com" target="_blank">Icons8</a></div>');
			
			html.push(sep);
			html.push('<div class="'+lic+'">Licence: 3-clauses BSD Licence</div>');
			html.push('<div class="'+lic+'">Copyright © 2009-2019, Studio 42</div>');
			html.push('<div class="'+lic+'">„ …'+fm.i18n('dontforget')+' ”</div>');
			html.push('</div>');
		},
		shortcuts = function() {
			var sh = fm.shortcuts();
			// shortcuts tab
			html.push('<div id="'+fm.namespace+'-help-shortcuts" class="ui-tabs-panel ui-widget-content ui-corner-bottom">');
			
			if (sh.length) {
				html.push('<div class="ui-widget-content elfinder-help-shortcuts">');
				jQuery.each(sh, function(i, s) {
					html.push(stpl.replace(/\{pattern\}/, s[0]).replace(/\{descrip\}/, s[1]));
				});
			
				html.push('</div>');
			} else {
				html.push('<div class="elfinder-help-disabled">'+fm.i18n('shortcutsof')+'</div>');
			}
			
			
			html.push('</div>');
			
		},
		help = function() {
			// help tab
			html.push('<div id="'+fm.namespace+'-help-help" class="ui-tabs-panel ui-widget-content ui-corner-bottom">');
			html.push('<a href="https://github.com/Studio-42/elFinder/wiki" target="_blank" class="elfinder-dont-panic"><span>DON\'T PANIC</span></a>');
			html.push('</div>');
			// end help
		},
		useInteg = false,
		integrations = function() {
			useInteg = true;
			html.push('<div id="'+fm.namespace+'-help-integrations" class="ui-tabs-panel ui-widget-content ui-corner-bottom"/>');
		},
		useDebug = false,
		debug = function() {
			useDebug = true;
			// debug tab
			html.push('<div id="'+fm.namespace+'-help-debug" class="ui-tabs-panel ui-widget-content ui-corner-bottom">');
			html.push('<div class="ui-widget-content elfinder-help-debug"><ul></ul></div>');
			html.push('</div>');
			// end debug
		},
		debugRender = function() {
			var render = function(elm, obj) {
				jQuery.each(obj, function(k, v) {
					elm.append(jQuery('<dt/>').text(k));
					if (typeof v === 'undefined') {
						elm.append(jQuery('<dd/>').append(jQuery('<span/>').text('undfined')));
					} else if (typeof v === 'object' && !v) {
						elm.append(jQuery('<dd/>').append(jQuery('<span/>').text('null')));
					} else if (typeof v === 'object' && (jQuery.isPlainObject(v) || v.length)) {
						elm.append( jQuery('<dd/>').append(render(jQuery('<dl/>'), v)));
					} else {
						elm.append(jQuery('<dd/>').append(jQuery('<span/>').text((v && typeof v === 'object')? '[]' : (v? v : '""'))));
					}
				});
				return elm;
			},
			cnt = debugUL.children('li').length,
			targetL, target, tabId,
			info, lastUL, lastDIV;
			
			if (self.debug.options || self.debug.debug) {
				if (cnt >= 5) {
					lastUL = debugUL.children('li:last');
					lastDIV = debugDIV.children('div:last');
					if (lastDIV.is(':hidden')) {
						lastUL.remove();
						lastDIV.remove();
					} else {
						lastUL.prev().remove();
						lastDIV.prev().remove();
					}
				}
				
				tabId = fm.namespace + '-help-debug-' + (+new Date());
				targetL = jQuery('<li/>').html('<a href="'+selfUrl+'#'+tabId+'">'+self.debug.debug.cmd+'</a>').prependTo(debugUL);
				target = jQuery('<div id="'+tabId+'"/>').data('debug', self.debug);
				
				targetL.on('click.debugrender', function() {
					var debug = target.data('debug');
					target.removeData('debug');
					if (debug) {
						target.hide();
						if (debug.debug) {
							info = jQuery('<fieldset>').append(jQuery('<legend/>').text('debug'), render(jQuery('<dl/>'), debug.debug));
							target.append(info);
						}
						if (debug.options) {
							info = jQuery('<fieldset>').append(jQuery('<legend/>').text('options'), render(jQuery('<dl/>'), debug.options));
							target.append(info);
						}
						target.show();
					}
					targetL.off('click.debugrender');
				});
				
				debugUL.after(target);
				
				opened && debugDIV.tabs('refresh');
			}
		},
		content = '',
		opened, tabInteg, integDIV, tabDebug, debugDIV, debugUL;
	
	this.alwaysEnabled  = true;
	this.updateOnSelect = false;
	this.state = -1;
	
	this.shortcuts = [{
		pattern     : 'f1',
		description : this.title
	}];
	
	fm.bind('load', function() {
		var parts = self.options.view || ['about', 'shortcuts', 'help', 'integrations', 'debug'],
			i, helpSource, tabBase, tabNav, tabs, delta;
		
		// remove 'preference' tab, it moved to command 'preference'
		if ((i = jQuery.inArray('preference', parts)) !== -1) {
			parts.splice(i, 1);
		}
		
		// debug tab require jQueryUI Tabs Widget
		if (! jQuery.fn.tabs) {
			if ((i = jQuery.inArray(parts, 'debug')) !== -1) {
				parts.splice(i, 1);
			}
		}
		
		jQuery.each(parts, function(i, title) {
			html.push(tab[r](/\{id\}/g, title)[r](/\{title\}/, fm.i18n(title)));
		});
		
		html.push('</ul>');

		jQuery.inArray('about', parts) !== -1 && about();
		jQuery.inArray('shortcuts', parts) !== -1 && shortcuts();
		if (jQuery.inArray('help', parts) !== -1) {
			helpSource = fm.i18nBaseUrl + 'help/%s.html.js';
			help();
		}
		jQuery.inArray('integrations', parts) !== -1 && integrations();
		jQuery.inArray('debug', parts) !== -1 && debug();
		
		html.push('</div>');
		content = jQuery(html.join(''));
		
		content.find('.ui-tabs-nav li')
			.on('mouseenter mouseleave', function(e) {
				jQuery(this).toggleClass('ui-state-hover', e.type === 'mouseenter');
			})
			.on('focus blur', 'a', function(e) {
				jQuery(e.delegateTarget).toggleClass('ui-state-focus', e.type === 'focusin');
			})
			.children()
			.on('click', function(e) {
				var link = jQuery(this);
				
				e.preventDefault();
				e.stopPropagation();
				
				link.parent().addClass(clTabActive).siblings().removeClass(clTabActive);
				content.children('.ui-tabs-panel').hide().filter(link.attr('href')).show();
			})
			.filter(':first').trigger('click');
		
		if (useInteg) {
			tabInteg = content.find('.elfinder-help-tab-integrations').hide();
			integDIV = content.find('#'+fm.namespace+'-help-integrations').hide().append(jQuery('<div class="elfinder-help-integrations-desc"/>').html(fm.i18n('integrationWith')));
			fm.bind('helpIntegration', function(e) {
				var ul = integDIV.children('ul:first'),
					data, elm, cmdUL, cmdCls;
				if (e.data) {
					if (jQuery.isPlainObject(e.data)) {
						data = Object.assign({
							link: '',
							title: '',
							banner: ''
						}, e.data);
						if (data.title || data.link) {
							if (!data.title) {
								data.title = data.link;
							}
							if (data.link) {
								elm = jQuery('<a/>').attr('href', data.link).attr('target', '_blank').text(data.title);
							} else {
								elm = jQuery('<span/>').text(data.title);
							}
							if (data.banner) {
								elm = jQuery('<span/>').append(jQuery('<img/>').attr(data.banner), elm);
							}
						}
					} else {
						elm = jQuery(e.data);
						elm.filter('a').each(function() {
							var tgt = jQuery(this);
							if (!tgt.attr('target')) {
								tgt.attr('target', '_blank');;
							}
						});
					}
					if (elm) {
						tabInteg.show();
						if (!ul.length) {
							ul = jQuery('<ul class="elfinder-help-integrations"/>').appendTo(integDIV);
						}
						if (data && data.cmd) {
							cmdCls = 'elfinder-help-integration-' + data.cmd;
							cmdUL = ul.find('ul.' + cmdCls);
							if (!cmdUL.length) {
								cmdUL = jQuery('<ul class="'+cmdCls+'"/>');
								ul.append(jQuery('<li/>').append(jQuery('<span/>').html(fm.i18n('cmd'+data.cmd))).append(cmdUL));
							}
							elm = cmdUL.append(jQuery('<li/>').append(elm));
						} else {
							ul.append(jQuery('<li/>').append(elm));
						}
					}
				}
			}).bind('themechange', function() {
				content.find('div.elfinder-help-term-theme').replaceWith(getTheme());
			});
		}

		// debug
		if (useDebug) {
			tabDebug = content.find('.elfinder-help-tab-debug').hide();
			debugDIV = content.find('#'+fm.namespace+'-help-debug').children('div:first');
			debugUL = debugDIV.children('ul:first').on('click', function(e) {
				e.preventDefault();
				e.stopPropagation();
			});

			self.debug = {};
	
			fm.bind('backenddebug', function(e) {
				// CAUTION: DO NOT TOUCH `e.data`
				if (useDebug && e.data && e.data.debug) {
					self.debug = { options : e.data.options, debug : Object.assign({ cmd : fm.currentReqCmd }, e.data.debug) };
					if (self.dialog) {
						debugRender();
					}
				}
			});
		}

		content.find('#'+fm.namespace+'-help-about').find('.apiver').text(fm.api);
		self.dialog = self.fmDialog(content, {
				title : self.title,
				width : 530,
				maxWidth: 'window',
				maxHeight: 'window',
				autoOpen : false,
				destroyOnClose : false,
				close : function() {
					if (useDebug) {
						tabDebug.hide();
						debugDIV.tabs('destroy');
					}
					opened = false;
				}
			})
			.on('click', function(e) {
				e.stopPropagation();
			})
			.css({
				overflow: 'hidden'
			});
		
		tabBase = self.dialog.children('.ui-tabs');
		tabNav = tabBase.children('.ui-tabs-nav:first');
		tabs = tabBase.children('.ui-tabs-panel');
		delta = self.dialog.outerHeight(true) - self.dialog.height();
		self.dialog.closest('.ui-dialog').on('resize', function() {
			tabs.height(self.dialog.height() - delta - tabNav.outerHeight(true) - 20);
		});
		
		if (helpSource) {
			self.dialog.one('initContents', function() {
				jQuery.ajax({
					url: self.options.helpSource? self.options.helpSource : helpSource.replace('%s', fm.lang),
					dataType: 'html'
				}).done(function(source) {
					jQuery('#'+fm.namespace+'-help-help').html(source);
				}).fail(function() {
					jQuery.ajax({
						url: helpSource.replace('%s', 'en'),
						dataType: 'html'
					}).done(function(source) {
						jQuery('#'+fm.namespace+'-help-help').html(source);
					});
				});
			});
		}
		
		self.state = 0;

		fm.trigger('helpBuilded', self.dialog);
	}).one('open', function() {
		var debug = false;
		fm.one('backenddebug', function() {
			debug =true;
		}).one('opendone', function() {
			requestAnimationFrame(function() {
				if (! debug && useDebug) {
					useDebug = false;
					tabDebug.hide();
					debugDIV.hide();
					debugUL.hide();
				}
			});
		});
	});
	
	this.getstate = function() {
		return 0;
	};
	
	this.exec = function(sel, opts) {
		var tab = opts? opts.tab : void(0),
			debugShow = function() {
				if (useDebug) {
					debugDIV.tabs();
					debugUL.find('a:first').trigger('click');
					tabDebug.show();
					opened = true;
				}
			};
		debugShow();
		this.dialog.trigger('initContents').elfinderdialog('open').find((tab? '.elfinder-help-tab-'+tab : '.ui-tabs-nav li') + ' a:first').trigger('click');
		return jQuery.Deferred().resolve();
	};

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/hidden.js
 */

/**
 * @class  elFinder command "hidden"
 * Always hidden command for uiCmdMap
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.hidden = function() {
		this.hidden = true;
	this.updateOnSelect = false;
	this.getstate = function() {
		return -1;
	};
};

/*
 * File: /js/commands/hide.js
 */

/**
 * @class elFinder command "hide".
 * folders/files to hide as personal setting.
 *
 * @type  elFinder.command
 * @author  Naoki Sawada
 */
elFinder.prototype.commands.hide = function() {
	
	var self = this,
		nameCache = {},
		hideData, hideCnt, cMenuType, sOrigin;

	this.syncTitleOnChange = true;

	this.shortcuts = [{
		pattern : 'ctrl+shift+dot',
		description : this.fm.i18n('toggleHidden')
	}];

	this.init = function() {
		var fm = this.fm;
		
		hideData = fm.storage('hide') || {items: {}};
		hideCnt = Object.keys(hideData.items).length;

		this.title = fm.i18n(hideData.show? 'hideHidden' : 'showHidden');
		self.update(void(0), self.title);
	};

	this.fm.bind('select contextmenucreate closecontextmenu', function(e, fm) {
		var sel = (e.data? (e.data.selected || e.data.targets) : null) || fm.selected();
		if (e.type === 'select' && e.data) {
			sOrigin = e.data.origin;
		} else if (e.type === 'contextmenucreate') {
			cMenuType = e.data.type;
		}
		if (!sel.length || (((e.type !== 'contextmenucreate' && sOrigin !== 'navbar') || cMenuType === 'cwd') && sel[0] === fm.cwd().hash)) {
			self.title = fm.i18n(hideData.show? 'hideHidden' : 'showHidden');
		} else {
			self.title = fm.i18n('cmdhide');
		}
		if (e.type !== 'closecontextmenu') {
			self.update(cMenuType === 'cwd'? (hideCnt? 0 : -1) : void(0), self.title);
		} else {
			cMenuType = '';
			requestAnimationFrame(function() {
				self.update(void(0), self.title);
			});
		}
	});

	this.getstate = function(sel) {
		return (cMenuType !== 'cwd' && (sel || this.fm.selected()).length) || hideCnt? 0 : -1;
	};

	this.exec = function(hashes, opts) {
		var fm = this.fm,
			dfrd = jQuery.Deferred()
				.done(function() {
					fm.trigger('hide', {items: items, opts: opts});
				})
				.fail(function(error) {
					fm.error(error);
				}),
			o = opts || {},
			items = o.targets? o.targets : (hashes || fm.selected()),
			added = [],
			removed = [],
			notifyto, files, res;

		hideData = fm.storage('hide') || {};
		if (!jQuery.isPlainObject(hideData)) {
			hideData = {};
		}
		if (!jQuery.isPlainObject(hideData.items)) {
			hideData.items = {};
		}
		if (opts._currentType === 'shortcut' || !items.length || (opts._currentType !== 'navbar' && sOrigin !=='navbar' && items[0] === fm.cwd().hash)) {
			if (hideData.show) {
				o.hide = true;
			} else if (Object.keys(hideData.items).length) {
				o.show = true;
			}
		}
		if (o.reset) {
			o.show = true;
			hideCnt = 0;
		}
		if (o.show || o.hide) {
			if (o.show) {
				hideData.show = true;
			} else {
				delete hideData.show;
			}
			if (o.show) {
				fm.storage('hide', o.reset? null : hideData);
				self.title = fm.i18n('hideHidden');
				self.update(o.reset? -1 : void(0), self.title);
				jQuery.each(hideData.items, function(h) {
					var f = fm.file(h, true);
					if (f && (fm.searchStatus.state || !f.phash || fm.file(f.phash))) {
						added.push(f);
					}
				});
				if (added.length) {
					fm.updateCache({added: added});
					fm.add({added: added});
				}
				if (o.reset) {
					hideData = {items: {}};
				}
				return dfrd.resolve();
			}
			items = Object.keys(hideData.items);
		}

		if (items.length) {
			jQuery.each(items, function(i, h) {
				var f;
				if (!hideData.items[h]) {
					f = fm.file(h);
					if (f) {
						nameCache[h] = f.i18 || f.name;
					}
					hideData.items[h] = nameCache[h]? nameCache[h] : h;
				}
			});
			hideCnt = Object.keys(hideData.items).length;
			files = this.files(items);
			fm.storage('hide', hideData);
			fm.remove({removed: items});
			if (hideData.show) {
				this.exec(void(0), {hide: true});
			}
			if (!o.hide) {
				res = {};
				res.undo = {
					cmd : 'hide',
					callback : function() {
						var nData = fm.storage('hide');
						if (nData) {
							jQuery.each(items, function(i, h) {
								delete nData.items[h];
							});
							hideCnt = Object.keys(nData.items).length;
							fm.storage('hide', nData);
							fm.trigger('hide', {items: items, opts: {}});
							self.update(hideCnt? 0 : -1);
						}
						fm.updateCache({added: files});
						fm.add({added: files});
					}
				};
				res.redo = {
					cmd : 'hide',
					callback : function() {
						return fm.exec('hide', void(0), {targets: items});
					}
				};
			}
		}

		return dfrd.state() == 'rejected' ? dfrd : dfrd.resolve(res);
	};
};


/*
 * File: /js/commands/home.js
 */

(elFinder.prototype.commands.home = function() {
		this.title = 'Home';
	this.alwaysEnabled  = true;
	this.updateOnSelect = false;
	this.shortcuts = [{
		pattern     : 'ctrl+home ctrl+shift+up',
		description : 'Home'
	}];
	
	this.getstate = function() {
		var root = this.fm.root(),
			cwd  = this.fm.cwd().hash;
			
		return root && cwd && root != cwd ? 0: -1;
	};
	
	this.exec = function() {
		return this.fm.exec('open', this.fm.root());
	};
	

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/info.js
 */

/**
 * @class elFinder command "info". 
 * Display dialog with file properties.
 *
 * @author Dmitry (dio) Levashov, dio@std42.ru
 **/
(elFinder.prototype.commands.info = function() {
		var m   = 'msg',
		fm  = this.fm,
		spclass = 'elfinder-spinner',
		btnclass = 'elfinder-info-button',
		msg = {
			calc     : fm.i18n('calc'),
			size     : fm.i18n('size'),
			unknown  : fm.i18n('unknown'),
			path     : fm.i18n('path'),
			aliasfor : fm.i18n('aliasfor'),
			modify   : fm.i18n('modify'),
			perms    : fm.i18n('perms'),
			locked   : fm.i18n('locked'),
			dim      : fm.i18n('dim'),
			kind     : fm.i18n('kind'),
			files    : fm.i18n('files'),
			folders  : fm.i18n('folders'),
			roots    : fm.i18n('volumeRoots'),
			items    : fm.i18n('items'),
			yes      : fm.i18n('yes'),
			no       : fm.i18n('no'),
			link     : fm.i18n('link'),
			owner    : fm.i18n('owner'),
			group    : fm.i18n('group'),
			perm     : fm.i18n('perm'),
			getlink  : fm.i18n('getLink')
		},
		applyZWSP = function(str, remove) {
			if (remove) {
				return str.replace(/\u200B/g, '');
			} else {
				return str.replace(/(\/|\\)/g, "$1\u200B");
			}
		};
	
	this.items = ['size', 'aliasfor', 'path', 'link', 'dim', 'modify', 'perms', 'locked', 'owner', 'group', 'perm'];
	if (this.options.custom && Object.keys(this.options.custom).length) {
		jQuery.each(this.options.custom, function(name, details) {
			details.label && this.items.push(details.label);
		});
	}

	this.tpl = {
		main       : '<div class="ui-helper-clearfix elfinder-info-title {dirclass}"><span class="elfinder-cwd-icon {class} ui-corner-all"{style}/>{title}</div><table class="elfinder-info-tb">{content}</table>',
		itemTitle  : '<strong>{name}</strong><span class="elfinder-info-kind">{kind}</span>',
		groupTitle : '<strong>{items}: {num}</strong>',
		row        : '<tr><td class="elfinder-info-label">{label} : </td><td class="{class}">{value}</td></tr>',
		spinner    : '<span>{text}</span> <span class="'+spclass+' '+spclass+'-{name}"/>'
	};
	
	this.alwaysEnabled = true;
	this.updateOnSelect = false;
	this.shortcuts = [{
		pattern     : 'ctrl+i'
	}];
	
	this.init = function() {
		jQuery.each(msg, function(k, v) {
			msg[k] = fm.i18n(v);
		});
	};
	
	this.getstate = function() {
		return 0;
	};
	
	this.exec = function(hashes) {
		var files   = this.files(hashes);
		if (! files.length) {
			files   = this.files([ this.fm.cwd().hash ]);
		}
		var self    = this,
			fm      = this.fm,
			o       = this.options,
			tpl     = this.tpl,
			row     = tpl.row,
			cnt     = files.length,
			content = [],
			view    = tpl.main,
			l       = '{label}',
			v       = '{value}',
			reqs    = [],
			reqDfrd = null,
			opts    = {
				title : fm.i18n('selectionInfo'),
				width : 'auto',
				close : function() {
					jQuery(this).elfinderdialog('destroy');
					if (reqDfrd && reqDfrd.state() === 'pending') {
						reqDfrd.reject();
					}
					jQuery.grep(reqs, function(r) {
						r && r.state() === 'pending' && r.reject();
					});
				}
			},
			count = [],
			replSpinner = function(msg, name, className) {
				dialog.find('.'+spclass+'-'+name).parent().html(msg).addClass(className || '');
			},
			id = fm.namespace+'-info-'+jQuery.map(files, function(f) { return f.hash; }).join('-'),
			dialog = fm.getUI().find('#'+id),
			customActions = [],
			style = '',
			hashClass = 'elfinder-font-mono elfinder-info-hash',
			size, tmb, file, title, dcnt, rdcnt, path, getHashAlgorisms, hideItems;
			
		if (!cnt) {
			return jQuery.Deferred().reject();
		}
			
		if (dialog.length) {
			dialog.elfinderdialog('toTop');
			return jQuery.Deferred().resolve();
		}
		
		hideItems = fm.storage('infohides') || fm.arrayFlip(o.hideItems, true);

		if (cnt === 1) {
			file = files[0];
			
			if (file.icon) {
				style = ' '+fm.getIconStyle(file);
			}
			
			view  = view.replace('{dirclass}', file.csscls? fm.escape(file.csscls) : '').replace('{class}', fm.mime2class(file.mime)).replace('{style}', style);
			title = tpl.itemTitle.replace('{name}', fm.escape(file.i18 || file.name)).replace('{kind}', '<span title="'+fm.escape(file.mime)+'">'+fm.mime2kind(file)+'</span>');

			tmb = fm.tmb(file);
			
			if (!file.read) {
				size = msg.unknown;
			} else if (file.mime != 'directory' || file.alias) {
				size = fm.formatSize(file.size);
			} else {
				size = tpl.spinner.replace('{text}', msg.calc).replace('{name}', 'size');
				count.push(file.hash);
			}
			
			!hideItems.size && content.push(row.replace(l, msg.size).replace(v, size));
			!hideItems.aleasfor && file.alias && content.push(row.replace(l, msg.aliasfor).replace(v, file.alias));
			if (!hideItems.path) {
				if (path = fm.path(file.hash, true)) {
					content.push(row.replace(l, msg.path).replace(v, applyZWSP(fm.escape(path))).replace('{class}', 'elfinder-info-path'));
				} else {
					content.push(row.replace(l, msg.path).replace(v, tpl.spinner.replace('{text}', msg.calc).replace('{name}', 'path')).replace('{class}', 'elfinder-info-path'));
					reqs.push(fm.path(file.hash, true, {notify: null})
					.fail(function() {
						replSpinner(msg.unknown, 'path');
					})
					.done(function(path) {
						replSpinner(applyZWSP(path), 'path');
					}));
				}
			}
			if (!hideItems.link && file.read) {
				var href,
				name_esc = fm.escape(file.name);
				if (file.url == '1') {
					content.push(row.replace(l, msg.link).replace(v, '<button class="'+btnclass+' '+spclass+'-url">'+msg.getlink+'</button>'));
				} else {
					if (file.url) {
						href = file.url;
					} else if (file.mime === 'directory') {
						if (o.nullUrlDirLinkSelf && file.url === null) {
							var loc = window.location;
							href = loc.pathname + loc.search + '#elf_' + file.hash;
						} else if (file.url !== '' && fm.option('url', (!fm.isRoot(file) && file.phash) || file.hash)) {
							href = fm.url(file.hash);
						}
					} else {
						href = fm.url(file.hash);
					}
					/*href && content.push(row.replace(l, msg.link).replace(v,  '<a href="'+href+'" target="_blank">'+name_esc+'</a>'));*/
					href && content.push(row.replace(l, msg.link).replace(v,  '<a href="'+href+'" target="_blank">'+name_esc+'</a> <a href="mailto:?Subject=WP File Manager Share '+name_esc+'&amp;Body='+href+'" class="mk_elfinder_share_button" title="Share"><button class="button button-primary">Share</button></a>'));	
				}
			}
			
			if (!hideItems.dim) {
				if (file.dim) { // old api
					content.push(row.replace(l, msg.dim).replace(v, file.dim));
				} else if (file.mime.indexOf('image') !== -1) {
					if (file.width && file.height) {
						content.push(row.replace(l, msg.dim).replace(v, file.width+'x'+file.height));
					} else {
						content.push(row.replace(l, msg.dim).replace(v, tpl.spinner.replace('{text}', msg.calc).replace('{name}', 'dim')));
						reqs.push(fm.request({
							data : {cmd : 'dim', target : file.hash},
							preventDefault : true
						})
						.fail(function() {
							replSpinner(msg.unknown, 'dim');
						})
						.done(function(data) {
							replSpinner(data.dim || msg.unknown, 'dim');
							if (data.dim) {
								var dim = data.dim.split('x');
								var rfile = fm.file(file.hash);
								rfile.width = dim[0];
								rfile.height = dim[1];
							}
						}));
					}
				}
			}
			
			!hideItems.modify && content.push(row.replace(l, msg.modify).replace(v, fm.formatDate(file)));
			!hideItems.perms && content.push(row.replace(l, msg.perms).replace(v, fm.formatPermissions(file)));
			!hideItems.locked && content.push(row.replace(l, msg.locked).replace(v, file.locked ? msg.yes : msg.no));
			!hideItems.owner && file.owner && content.push(row.replace(l, msg.owner).replace(v, file.owner));
			!hideItems.group && file.group && content.push(row.replace(l, msg.group).replace(v, file.group));
			!hideItems.perm && file.perm && content.push(row.replace(l, msg.perm).replace(v, fm.formatFileMode(file.perm)));
			
			// Get MD5 hash
			if (window.ArrayBuffer && (fm.options.cdns.sparkmd5 || fm.options.cdns.jssha) && file.mime !== 'directory' && file.size > 0 && (!o.showHashMaxsize || file.size <= o.showHashMaxsize)) {
				getHashAlgorisms = [];
				jQuery.each(fm.storage('hashchekcer') || o.showHashAlgorisms, function(i, n) {
					if (!file[n]) {
					content.push(row.replace(l, fm.i18n(n)).replace(v, tpl.spinner.replace('{text}', msg.calc).replace('{name}', n)));
						getHashAlgorisms.push(n);
					} else {
						content.push(row.replace(l, fm.i18n(n)).replace(v, file[n]).replace('{class}', hashClass));
					}
				});

				reqs.push(
					fm.getContentsHashes(file.hash, getHashAlgorisms).progress(function(hashes) {
						jQuery.each(getHashAlgorisms, function(i, n) {
							if (hashes[n]) {
								replSpinner(hashes[n], n, hashClass);
							}
						});
					}).always(function() {
						jQuery.each(getHashAlgorisms, function(i, n) {
							replSpinner(msg.unknown, n);
						});
					})
				);
			}
			
			// Add custom info fields
			if (o.custom) {
				jQuery.each(o.custom, function(name, details) {
					if (
					  !hideItems[details.label]
					    &&
					  (!details.mimes || jQuery.grep(details.mimes, function(m){return (file.mime === m || file.mime.indexOf(m+'/') === 0)? true : false;}).length)
					    &&
					  (!details.hashRegex || file.hash.match(details.hashRegex))
					) {
						// Add to the content
						content.push(row.replace(l, fm.i18n(details.label)).replace(v , details.tpl.replace('{id}', id)));
						// Register the action
						if (details.action && (typeof details.action == 'function')) {
							customActions.push(details.action);
						}
					}
				});
			}
		} else {
			view  = view.replace('{class}', 'elfinder-cwd-icon-group');
			title = tpl.groupTitle.replace('{items}', msg.items).replace('{num}', cnt);
			dcnt  = jQuery.grep(files, function(f) { return f.mime == 'directory' ? true : false ; }).length;
			if (!dcnt) {
				size = 0;
				jQuery.each(files, function(h, f) { 
					var s = parseInt(f.size);
					
					if (s >= 0 && size >= 0) {
						size += s;
					} else {
						size = 'unknown';
					}
				});
				content.push(row.replace(l, msg.kind).replace(v, msg.files));
				!hideItems.size && content.push(row.replace(l, msg.size).replace(v, fm.formatSize(size)));
			} else {
				rdcnt = jQuery.grep(files, function(f) { return f.mime === 'directory' && (! f.phash || f.isroot)? true : false ; }).length;
				dcnt -= rdcnt;
				content.push(row.replace(l, msg.kind).replace(v, (rdcnt === cnt || dcnt === cnt)? msg[rdcnt? 'roots' : 'folders'] : jQuery.map({roots: rdcnt, folders: dcnt, files: cnt - rdcnt - dcnt}, function(c, t) { return c? msg[t]+' '+c : null; }).join(', ')));
				!hideItems.size && content.push(row.replace(l, msg.size).replace(v, tpl.spinner.replace('{text}', msg.calc).replace('{name}', 'size')));
				count = jQuery.map(files, function(f) { return f.hash; });
				
			}
		}
		
		view = view.replace('{title}', title).replace('{content}', content.join('').replace(/{class}/g, ''));
		
		dialog = self.fmDialog(view, opts);
		dialog.attr('id', id).one('mousedown', '.elfinder-info-path', function() {
			jQuery(this).html(applyZWSP(jQuery(this).html(), true));
		});

		if (fm.UA.Mobile && jQuery.fn.tooltip) {
			dialog.children('.ui-dialog-content .elfinder-info-title').tooltip({
				classes: {
					'ui-tooltip': 'elfinder-ui-tooltip ui-widget-shadow'
				},
				tooltipClass: 'elfinder-ui-tooltip ui-widget-shadow',
				track: true
			});
		}

		if (file && file.url == '1') {
			dialog.on('click', '.'+spclass+'-url', function(){
				jQuery(this).parent().html(tpl.spinner.replace('{text}', fm.i18n('ntfurl')).replace('{name}', 'url'));
				fm.request({
					data : {cmd : 'url', target : file.hash},
					preventDefault : true
				})
				.fail(function() {
					replSpinner(name_esc, 'url');
				})
				.done(function(data) {
					if (data.url) {
						replSpinner('<a href="'+data.url+'" target="_blank">'+name_esc+'</a>' || name_esc, 'url');
						var rfile = fm.file(file.hash);
						rfile.url = data.url;
					} else {
						replSpinner(name_esc, 'url');
					}
				});
			});
		}

		// load thumbnail
		if (tmb) {
			jQuery('<img/>')
				.on('load', function() { dialog.find('.elfinder-cwd-icon').addClass(tmb.className).css('background-image', "url('"+tmb.url+"')"); })
				.attr('src', tmb.url);
		}
		
		// send request to count total size
		if (count.length) {
			reqDfrd = fm.getSize(count).done(function(data) {
				replSpinner(data.formated, 'size');
			}).fail(function() {
				replSpinner(msg.unknown, 'size');
			});
		}
		
		// call custom actions
		if (customActions.length) {
			jQuery.each(customActions, function(i, action) {
				try {
					action(file, fm, dialog);
				} catch(e) {
					fm.debug('error', e);
				}
			});
		}
		
		return jQuery.Deferred().resolve();
	};
	
}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/mkdir.js
 */

/**
 * @class  elFinder command "mkdir"
 * Create new folder
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.mkdir = function() {
		var fm   = this.fm,
		self = this,
		curOrg;
	
	this.value           = '';
	this.disableOnSearch = true;
	this.updateOnSelect  = false;
	this.syncTitleOnChange = true;
	this.mime            = 'directory';
	this.prefix          = 'untitled folder';
	this.exec            = function(select, cOpts) {
		var onCwd;

		if (select && select.length && cOpts && cOpts._currentType && cOpts._currentType === 'navbar') {
			this.origin = cOpts._currentType;
			this.data = {
				target: select[0]
			};
		} else {
			onCwd = fm.cwd().hash === select[0];
			this.origin = curOrg && !onCwd? curOrg : 'cwd';
			delete this.data;
		}
		if (! select && ! this.options.intoNewFolderToolbtn) {
			fm.getUI('cwd').trigger('unselectall');
		}
		//this.move = (!onCwd && curOrg !== 'navbar' && fm.selected().length)? true : false;
		this.move = this.value === fm.i18n('cmdmkdirin');
		return jQuery.proxy(fm.res('mixin', 'make'), self)();
	};
	
	this.shortcuts = [{
		pattern     : 'ctrl+shift+n'
	}];

	this.init = function() {
		if (this.options.intoNewFolderToolbtn) {
			this.syncTitleOnChange = true;
		}
	};
	
	fm.bind('select contextmenucreate closecontextmenu', function(e) {
		var sel = (e.data? (e.data.selected || e.data.targets) : null) || fm.selected();
		
		self.className = 'mkdir';
		curOrg = e.data && sel.length? (e.data.origin || e.data.type || '') : '';
		if (!self.options.intoNewFolderToolbtn && curOrg === '') {
			curOrg = 'cwd';
		}
		if (sel.length && curOrg !== 'navbar' && curOrg !== 'cwd' && fm.cwd().hash !== sel[0]) {
			self.title = fm.i18n('cmdmkdirin');
			self.className += ' elfinder-button-icon-mkdirin';
		} else {
			self.title = fm.i18n('cmdmkdir');
		}
		if (e.type !== 'closecontextmenu') {
			self.update(void(0), self.title);
		} else {
			requestAnimationFrame(function() {
				self.update(void(0), self.title);
			});
		}
	});
	
	this.getstate = function(select) {
		var cwd = fm.cwd(),
			sel = (curOrg === 'navbar' || (select && select[0] !== cwd.hash))? this.files(select || fm.selected()) : [],
			cnt = sel.length;

		if (curOrg === 'navbar') {
			return cnt && sel[0].write && sel[0].read? 0 : -1;  
		} else {
			return cwd.write && (!cnt || jQuery.grep(sel, function(f) { return f.read && ! f.locked? true : false; }).length == cnt)? 0 : -1;
		}
	};

};


/*
 * File: /js/commands/mkfile.js
 */

/**
 * @class  elFinder command "mkfile"
 * Create new empty file
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.mkfile = function() {
		var self = this;

	this.disableOnSearch = true;
	this.updateOnSelect  = false;
	this.mime            = 'text/plain';
	this.prefix          = 'untitled file.txt';
	this.variants        = [];

	this.getTypeName = function(mime, type) {
		var fm = self.fm,
			name;
		if (name = fm.messages['kind' + fm.kinds[mime]]) {
			name = fm.i18n(['extentiontype', type.toUpperCase(), name]);
		} else {
			name = fm.i18n(['extentionfile', type.toUpperCase()]);
		}
		return name;
	};

	this.fm.bind('open reload canMakeEmptyFile', function() {
		var fm = self.fm,
			hides = fm.storage('mkfileHides') || {};
		self.variants = [];
		if (fm.mimesCanMakeEmpty) {
			jQuery.each(fm.mimesCanMakeEmpty, function(mime, type) {
				type && !hides[mime] && fm.uploadMimeCheck(mime) && self.variants.push([mime, self.getTypeName(mime, type)]);
			});
		}
		self.change();
	});

	this.getstate = function() {
		return this.fm.cwd().write ? 0 : -1;
	};

	this.exec = function(_dum, mime) {
		var fm = self.fm,
			type, err;
		if (type = fm.mimesCanMakeEmpty[mime]) {
			if (fm.uploadMimeCheck(mime)) {
				this.mime = mime;
				this.prefix = fm.i18n(['untitled file', type]);
				return jQuery.proxy(fm.res('mixin', 'make'), self)();
			}
			err = ['errMkfile', self.getTypeName(mime, type)];
		}
		return jQuery.Deferred().reject(err);
	};
};


/*
 * File: /js/commands/netmount.js
 */

/**
 * @class  elFinder command "netmount"
 * Mount network volume with user credentials.
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.netmount = function() {
		var self = this,
		hasMenus = false,
		content;

	this.alwaysEnabled  = true;
	this.updateOnSelect = false;

	this.drivers = [];
	
	this.handlers = {
		load : function() {
			var fm = self.fm;
			self.drivers = fm.netDrivers;
			if (self.drivers.length) {
				requestAnimationFrame(function() {
					jQuery.each(self.drivers, function() {
						var d = self.options[this];
						if (d) {
							hasMenus = true;
							if (d.integrateInfo) {
								fm.trigger('helpIntegration', Object.assign({cmd: 'netmount'}, d.integrateInfo));
							}
						}
					});
				});
			}
		}
	};

	this.getstate = function() {
		return hasMenus ? 0 : -1;
	};
	
	this.exec = function() {
		var fm = self.fm,
			dfrd = jQuery.Deferred(),
			o = self.options,
			create = function() {
				var winFocus = function() {
						inputs.protocol.trigger('change', 'winfocus');
					},
					inputs = {
						protocol : jQuery('<select/>')
						.on('change', function(e, data){
							var protocol = this.value;
							content.find('.elfinder-netmount-tr').hide();
							content.find('.elfinder-netmount-tr-'+protocol).show();
							dialogNode && dialogNode.children('.ui-dialog-buttonpane:first').find('button').show();
							if (typeof o[protocol].select == 'function') {
								o[protocol].select(fm, e, data);
							}
							requestAnimationFrame(function() {
								content.find('input:text.elfinder-tabstop:visible:first').trigger('focus');
							});
						})
						.addClass('ui-corner-all')
					},
					opts = {
						title          : fm.i18n('netMountDialogTitle'),
						resizable      : false,
						modal          : true,
						destroyOnClose : false,
						open           : function() {
							jQuery(window).on('focus.'+fm.namespace, winFocus);
							inputs.protocol.trigger('change');
						},
						close          : function() { 
							dfrd.state() == 'pending' && dfrd.reject();
							jQuery(window).off('focus.'+fm.namespace, winFocus);
						},
						buttons        : {}
					},
					doMount = function() {
						var protocol = inputs.protocol.val(),
							data = {cmd : 'netmount', protocol: protocol},
							cur = o[protocol];
						jQuery.each(content.find('input.elfinder-netmount-inputs-'+protocol), function(name, input) {
							var val, elm;
							elm = jQuery(input);
							if (elm.is(':radio,:checkbox')) {
								if (elm.is(':checked')) {
									val = jQuery.trim(elm.val());
								}
							} else {
								val = jQuery.trim(elm.val());
							}
							if (val) {
								data[input.name] = val;
							}
						});
	
						if (!data.host) {
							return fm.trigger('error', {error : 'errNetMountHostReq', opts : {modal: true}});
						}
	
						fm.request({data : data, notify : {type : 'netmount', cnt : 1, hideCnt : true}})
							.done(function(data) {
								var pdir;
								if (data.added && data.added.length) {
									if (data.added[0].phash) {
										if (pdir = fm.file(data.added[0].phash)) {
											if (! pdir.dirs) {
												pdir.dirs = 1;
												fm.change({ changed: [ pdir ] });
											}
										}
									}
									fm.one('netmountdone', function() {
										fm.exec('open', data.added[0].hash);
									});
								}
								dfrd.resolve();
							})
							.fail(function(error) {
								if (cur.fail && typeof cur.fail == 'function') {
									cur.fail(fm, fm.parseError(error));
								}
								dfrd.reject(error);
							});
	
						self.dialog.elfinderdialog('close');
					},
					form = jQuery('<form autocomplete="off"/>').on('keydown', 'input', function(e) {
						var comp = true,
							next;
						if (e.keyCode === jQuery.ui.keyCode.ENTER) {
							jQuery.each(form.find('input:visible:not(.elfinder-input-optional)'), function() {
								if (jQuery(this).val() === '') {
									comp = false;
									next = jQuery(this);
									return false;
								}
							});
							if (comp) {
								doMount();
							} else {
								next.trigger('focus');
							}
						}
					}),
					hidden  = jQuery('<div/>'),
					dialog;

				content = jQuery('<table class="elfinder-info-tb elfinder-netmount-tb"/>')
					.append(jQuery('<tr/>').append(jQuery('<td>'+fm.i18n('protocol')+'</td>')).append(jQuery('<td/>').append(inputs.protocol)));

				jQuery.each(self.drivers, function(i, protocol) {
					if (o[protocol]) {
						inputs.protocol.append('<option value="'+protocol+'">'+fm.i18n(o[protocol].name || protocol)+'</option>');
						jQuery.each(o[protocol].inputs, function(name, input) {
							input.attr('name', name);
							if (input.attr('type') != 'hidden') {
								input.addClass('ui-corner-all elfinder-netmount-inputs-'+protocol);
								content.append(jQuery('<tr/>').addClass('elfinder-netmount-tr elfinder-netmount-tr-'+protocol).append(jQuery('<td>'+fm.i18n(name)+'</td>')).append(jQuery('<td/>').append(input)));
							} else {
								input.addClass('elfinder-netmount-inputs-'+protocol);
								hidden.append(input);
							}
						});
						o[protocol].protocol = inputs.protocol;
					}
				});
				
				content.append(hidden);
				
				content.find('.elfinder-netmount-tr').hide();

				opts.buttons[fm.i18n('btnMount')] = doMount;

				opts.buttons[fm.i18n('btnCancel')] = function() {
					self.dialog.elfinderdialog('close');
				};
				
				content.find('select,input').addClass('elfinder-tabstop');
				
				dialog = self.fmDialog(form.append(content), opts);
				dialogNode = dialog.closest('.ui-dialog');
				dialog.ready(function(){
					inputs.protocol.trigger('change');
					dialog.elfinderdialog('posInit');
				});
				return dialog;
			},
			dialogNode;
		
		if (!self.dialog) {
			self.dialog = create();
		} else {
			self.dialog.elfinderdialog('open');
		}

		return dfrd.promise();
	};

	self.fm.bind('netmount', function(e) {
		var d = e.data || null,
			o = self.options;
		if (d && d.protocol) {
			if (o[d.protocol] && typeof o[d.protocol].done == 'function') {
				o[d.protocol].done(self.fm, d);
				content.find('select,input').addClass('elfinder-tabstop');
				self.dialog.elfinderdialog('tabstopsInit');
			}
		}
	});

};

elFinder.prototype.commands.netunmount = function() {
	var self = this;

	this.alwaysEnabled  = true;
	this.updateOnSelect = false;

	this.drivers = [];
	
	this.handlers = {
		load : function() {
			this.drivers = this.fm.netDrivers;
		}
	};

	this.getstate = function(sel) {
		var fm = this.fm,
			file;
		return !!sel && this.drivers.length && !this._disabled && (file = fm.file(sel[0])) && file.netkey ? 0 : -1;
	};
	
	this.exec = function(hashes) {
		var self   = this,
			fm     = this.fm,
			dfrd   = jQuery.Deferred()
				.fail(function(error) {
					error && fm.error(error);
				}),
			drive  = fm.file(hashes[0]),
			childrenRoots = function(hash) {
				var roots = [],
					work;
				if (fm.leafRoots) {
					work = [];
					jQuery.each(fm.leafRoots, function(phash, hashes) {
						var parents = fm.parents(phash),
							idx, deep;
						if ((idx = jQuery.inArray(hash, parents)) !== -1) {
							idx = parents.length - idx;
							jQuery.each(hashes, function(i, h) {
								work.push({i: idx, hash: h});
							});
						}
					});
					if (work.length) {
						work.sort(function(a, b) { return a.i < b.i; });
						jQuery.each(work, function(i, o) {
							roots.push(o.hash);
						});
					}
				}
				return roots;
			};

		if (this._disabled) {
			return dfrd.reject();
		}

		if (dfrd.state() == 'pending') {
			fm.confirm({
				title  : self.title,
				text   : fm.i18n('confirmUnmount', drive.name),
				accept : {
					label    : 'btnUnmount',
					callback : function() {  
						var target =  drive.hash,
							roots = childrenRoots(target),
							requests = [],
							removed = [],
							doUmount = function() {
								jQuery.when(requests).done(function() {
									fm.request({
										data   : {cmd  : 'netmount', protocol : 'netunmount', host: drive.netkey, user : target, pass : 'dum'}, 
										notify : {type : 'netunmount', cnt : 1, hideCnt : true},
										preventFail : true
									})
									.fail(function(error) {
										dfrd.reject(error);
									})
									.done(function(data) {
										drive.volumeid && delete fm.volumeExpires[drive.volumeid];
										dfrd.resolve();
									});
								}).fail(function(error) {
									if (removed.length) {
										fm.remove({ removed: removed });
									}
									dfrd.reject(error);
								});
							};
						
						if (roots.length) {
							fm.confirm({
								title : self.title,
								text  : (function() {
									var msgs = ['unmountChildren'];
									jQuery.each(roots, function(i, hash) {
										msgs.push([fm.file(hash).name]);
									});
									return msgs;
								})(),
								accept : {
									label : 'btnUnmount',
									callback : function() {
										jQuery.each(roots, function(i, hash) {
											var d = fm.file(hash);
											if (d.netkey) {
												requests.push(fm.request({
													data   : {cmd  : 'netmount', protocol : 'netunmount', host: d.netkey, user : d.hash, pass : 'dum'}, 
													notify : {type : 'netunmount', cnt : 1, hideCnt : true},
													preventDefault : true
												}).done(function(data) {
													if (data.removed) {
														d.volumeid && delete fm.volumeExpires[d.volumeid];
														removed = removed.concat(data.removed);
													}
												}));
											}
										});
										doUmount();
									}
								},
								cancel : {
									label : 'btnCancel',
									callback : function() {
										dfrd.reject();
									}
								}
							});
						} else {
							requests = null;
							doUmount();
						}
					}
				},
				cancel : {
					label    : 'btnCancel',
					callback : function() { dfrd.reject(); }
				}
			});
		}
			
		return dfrd;
	};

};


/*
 * File: /js/commands/open.js
 */

/**
 * @class  elFinder command "open"
 * Enter folder or open files in new windows
 *
 * @author Dmitry (dio) Levashov
 **/  
(elFinder.prototype.commands.open = function() {
		var fm = this.fm;
	this.alwaysEnabled = true;
	this.noChangeDirOnRemovedCwd = true;
	
	this._handlers = {
		dblclick : function(e) { e.preventDefault(); fm.exec('open', e.data && e.data.file? [ e.data.file ]: void(0)); },
		'select enable disable reload' : function(e) { this.update(e.type == 'disable' ? -1 : void(0));  }
	};
	
	this.shortcuts = [{
		pattern     : 'ctrl+down numpad_enter'+(fm.OS != 'mac' && ' enter')
	}];

	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length;
		
		return cnt == 1 
			? (sel[0].read? 0 : -1) 
			: (cnt && !fm.UA.Mobile) ? (jQuery.grep(sel, function(file) { return file.mime == 'directory' || ! file.read ? false : true;}).length == cnt ? 0 : -1) : -1;
	};
	
	this.exec = function(hashes, cOpts) {
		var dfrd  = jQuery.Deferred().fail(function(error) { error && fm.error(error); }),
			files = this.files(hashes),
			cnt   = files.length,
			thash = (typeof cOpts == 'object')? cOpts.thash : false,
			opts  = this.options,
			into  = opts.into || 'window',
			file, url, s, w, imgW, imgH, winW, winH, reg, link, html5dl, inline,
			selAct, cmd;

		if (!cnt && !thash) {
			{
				return dfrd.reject();
			}
		}

		// open folder
		if (thash || (cnt == 1 && (file = files[0]) && file.mime == 'directory')) {
			if (!thash && file && !file.read) {
				return dfrd.reject(['errOpen', file.name, 'errPerm']);
			} else {
				if (fm.keyState.ctrlKey && (fm.keyState.shiftKey || typeof fm.options.getFileCallback !== 'function')) {
					if (fm.getCommand('opennew')) {
						return fm.exec('opennew', [thash? thash : file.hash]);
					}
				}

				return fm.request({
					data   : {cmd  : 'open', target : thash || file.hash},
					notify : {type : 'open', cnt : 1, hideCnt : true},
					syncOnFail : true,
					lazy : false
				});
			}
		}
		
		files = jQuery.grep(files, function(file) { return file.mime != 'directory' ? true : false; });
		
		// nothing to open or files and folders selected - do nothing
		if (cnt != files.length) {
			return dfrd.reject();
		}
		
		var doOpen = function() {
			var wnd, target, getOnly;
			
			try {
				reg = new RegExp(fm.option('dispInlineRegex'), 'i');
			} catch(e) {
				reg = false;
			}
	
			// open files
			link     = jQuery('<a>').hide().appendTo(jQuery('body')),
			html5dl  = (typeof link.get(0).download === 'string');
			cnt = files.length;
			while (cnt--) {
				target = 'elf_open_window';
				file = files[cnt];
				
				if (!file.read) {
					return dfrd.reject(['errOpen', file.name, 'errPerm']);
				}
				
				inline = (reg && file.mime.match(reg));
				url = fm.openUrl(file.hash, !inline);
				if (fm.UA.Mobile || !inline) {
					if (html5dl) {
						if (!inline) {
							link.attr('download', file.name);
						} else {
							link.attr('target', '_blank');
						}
						link.attr('href', url).get(0).click();
					} else {
						wnd = window.open(url);
						if (!wnd) {
							return dfrd.reject('errPopup');
						}
					}
				} else {
					getOnly = (typeof opts.method === 'string' && opts.method.toLowerCase() === 'get');
					if (!getOnly
						&& url.indexOf(fm.options.url) === 0
						&& fm.customData
						&& Object.keys(fm.customData).length
						// Since playback by POST request can not be done in Chrome, media allows GET request
						&& !file.mime.match(/^(?:video|audio)/)
					) {
						// Send request as 'POST' method to hide custom data at location bar
						url = '';
					}
					if (into === 'window') {
						// set window size for image if set
						imgW = winW = Math.round(2 * screen.availWidth / 3);
						imgH = winH = Math.round(2 * screen.availHeight / 3);
						if (parseInt(file.width) && parseInt(file.height)) {
							imgW = parseInt(file.width);
							imgH = parseInt(file.height);
						} else if (file.dim) {
							s = file.dim.split('x');
							imgW = parseInt(s[0]);
							imgH = parseInt(s[1]);
						}
						if (winW >= imgW && winH >= imgH) {
							winW = imgW;
							winH = imgH;
						} else {
							if ((imgW - winW) > (imgH - winH)) {
								winH = Math.round(imgH * (winW / imgW));
							} else {
								winW = Math.round(imgW * (winH / imgH));
							}
						}
						w = 'width='+winW+',height='+winH;
						wnd = window.open(url, target, w + ',top=50,left=50,scrollbars=yes,resizable=yes,titlebar=no');
					} else {
						if (into === 'tabs') {
							target = file.hash;
						}
						wnd = window.open('about:blank', target);
					}
					
					if (!wnd) {
						return dfrd.reject('errPopup');
					}
					
					if (url === '') {
						var form = document.createElement("form");
						form.action = fm.options.url;
						form.method = 'POST';
						form.target = target;
						form.style.display = 'none';
						var params = Object.assign({}, fm.customData, {
							cmd: 'file',
							target: file.hash,
							_t: file.ts || parseInt(+new Date()/1000)
						});
						jQuery.each(params, function(key, val)
						{
							var input = document.createElement("input");
							input.name = key;
							input.value = val;
							form.appendChild(input);
						});
						
						document.body.appendChild(form);
						form.submit();
					} else if (into !== 'window') {
						wnd.location = url;
					}
					jQuery(wnd).trigger('focus');
				}
			}
			link.remove();
			return dfrd.resolve(hashes);
		};
		
		if (cnt > 1) {
			fm.confirm({
				title: 'openMulti',
				text : ['openMultiConfirm', cnt + ''],
				accept : {
					label : 'cmdopen',
					callback : function() { doOpen(); }
				},
				cancel : {
					label : 'btnCancel',
					callback : function() { 
						dfrd.reject();
					}
				},
				buttons : (fm.getCommand('zipdl') && fm.isCommandEnabled('zipdl', fm.cwd().hash))? [
					{
						label : 'cmddownload',
						callback : function() {
							fm.exec('download', hashes);
							dfrd.reject();
						}
					}
				] : []
			});
		} else {
			selAct = fm.storage('selectAction') || opts.selectAction;
			if (selAct) {
				jQuery.each(selAct.split('/'), function() {
					var cmdName = this.valueOf();
					if (cmdName !== 'open' && (cmd = fm.getCommand(cmdName)) && cmd.enabled()) {
						return false;
					}
					cmd = null;
				});
				if (cmd) {
					return fm.exec(cmd.name);
				}
			}
			doOpen();
		}
		
		return dfrd;
	};

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/opendir.js
 */

/**
 * @class  elFinder command "opendir"
 * Enter parent folder
 *
 * @author Naoki Sawada
 **/  
elFinder.prototype.commands.opendir = function() {
		this.alwaysEnabled = true;
	
	this.getstate = function() {
		var sel = this.fm.selected(),
			cnt = sel.length,
			wz;
		if (cnt !== 1) {
			return -1;
		}
		wz = this.fm.getUI('workzone');
		return wz.hasClass('elfinder-search-result')? 0 : -1;
	};
	
	this.exec = function(hashes) {
		var fm    = this.fm,
			dfrd  = jQuery.Deferred(),
			files = this.files(hashes),
			cnt   = files.length,
			hash, pcheck = null;

		if (!cnt || !files[0].phash) {
			return dfrd.reject();
		}

		hash = files[0].phash;
		fm.trigger('searchend', { noupdate: true });
		fm.request({
			data   : {cmd  : 'open', target : hash},
			notify : {type : 'open', cnt : 1, hideCnt : true},
			syncOnFail : false
		});
		
		return dfrd;
	};

};


/*
 * File: /js/commands/opennew.js
 */

/**
 * @class  elFinder command "opennew"
 * Open folder in new window
 *
 * @author Naoki Sawada
 **/  
elFinder.prototype.commands.opennew = function() {
		var fm = this.fm;

	this.shortcuts = [{
		pattern  : (typeof(fm.options.getFileCallback) === 'function'? 'shift+' : '') + 'ctrl+enter'
	}];

	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length;
		
		return cnt === 1 
			? (sel[0].mime === 'directory' && sel[0].read? 0 : -1) 
			: -1;
	};
	
	this.exec = function(hashes) {
		var dfrd  = jQuery.Deferred(),
			files = this.files(hashes),
			cnt   = files.length,
			opts  = this.options,
			file, loc, url, win;

		// open folder to new tab (window)
		if (cnt === 1 && (file = files[0]) && file.mime === 'directory') {
			loc = window.location;
			if (opts.url) {
				url = opts.url;
			} else {
				url = loc.pathname;
			}
			if (opts.useOriginQuery) {
				if (!url.match(/\?/)) {
					url += loc.search;
				} else if (loc.search) {
					url += '&' + loc.search.substr(1);
				}
			}
			url += '#elf_' + file.hash;
			win = window.open(url, '_blank');
			setTimeout(function() {
				win.focus();
			}, 1000);
			return dfrd.resolve();
		} else {
			return dfrd.reject();
		}
	};
};


/*
 * File: /js/commands/paste.js
 */

/**
 * @class  elFinder command "paste"
 * Paste filesfrom clipboard into directory.
 * If files pasted in its parent directory - files duplicates will created
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.paste = function() {
		this.updateOnSelect  = false;
	
	this.handlers = {
		changeclipboard : function() { this.update(); }
	};

	this.shortcuts = [{
		pattern     : 'ctrl+v shift+insert'
	}];
	
	this.getstate = function(dst) {
		if (this._disabled) {
			return -1;
		}
		if (dst) {
			if (Array.isArray(dst)) {
				if (dst.length != 1) {
					return -1;
				}
				dst = this.fm.file(dst[0]);
			}
		} else {
			dst = this.fm.cwd();
		}

		return this.fm.clipboard().length && dst.mime == 'directory' && dst.write ? 0 : -1;
	};
	
	this.exec = function(select, cOpts) {
		var self   = this,
			fm     = self.fm,
			opts   = cOpts || {},
			dst    = select ? this.files(select)[0] : fm.cwd(),
			files  = fm.clipboard(),
			cnt    = files.length,
			cut    = cnt ? files[0].cut : false,
			cmd    = opts._cmd? opts._cmd : (cut? 'move' : 'copy'),
			error  = 'err' + cmd.charAt(0).toUpperCase() + cmd.substr(1),
			fpaste = [],
			fcopy  = [],
			dfrd   = jQuery.Deferred()
				.fail(function(error) {
					error && fm.error(error);
				})
				.always(function() {
					fm.unlockfiles({files : jQuery.map(files, function(f) { return f.hash; })});
				}),
			copy  = function(files) {
				return files.length && fm._commands.duplicate
					? fm.exec('duplicate', files)
					: jQuery.Deferred().resolve();
			},
			paste = function(files) {
				var dfrd      = jQuery.Deferred(),
					existed   = [],
					hashes  = {},
					intersect = function(files, names) {
						var ret = [], 
							i   = files.length;

						while (i--) {
							jQuery.inArray(files[i].name, names) !== -1 && ret.unshift(i);
						}
						return ret;
					},
					confirm   = function(ndx) {
						var i    = existed[ndx],
							file = files[i],
							last = ndx == existed.length-1;

						if (!file) {
							return;
						}

						fm.confirm({
							title  : fm.i18n(cmd + 'Files'),
							text   : ['errExists', file.name, cmd === 'restore'? 'confirmRest' : 'confirmRepl'], 
							all    : !last,
							accept : {
								label    : 'btnYes',
								callback : function(all) {
									!last && !all
										? confirm(++ndx)
										: paste(files);
								}
							},
							reject : {
								label    : 'btnNo',
								callback : function(all) {
									var i;

									if (all) {
										i = existed.length;
										while (ndx < i--) {
											files[existed[i]].remove = true;
										}
									} else {
										files[existed[ndx]].remove = true;
									}

									!last && !all
										? confirm(++ndx)
										: paste(files);
								}
							},
							cancel : {
								label    : 'btnCancel',
								callback : function() {
									dfrd.resolve();
								}
							},
							buttons : [
								{
									label : 'btnBackup',
									callback : function(all) {
										var i;
										if (all) {
											i = existed.length;
											while (ndx < i--) {
												files[existed[i]].rename = true;
											}
										} else {
											files[existed[ndx]].rename = true;
										}
										!last && !all
											? confirm(++ndx)
											: paste(files);
									}
								}
							]
						});
					},
					valid     = function(names) {
						var exists = {}, existedArr;
						if (names) {
							if (Array.isArray(names)) {
								if (names.length) {
									if (typeof names[0] == 'string') {
										// elFinder <= 2.1.6 command `is` results
										existed = intersect(files, names);
									} else {
										jQuery.each(names, function(i, v) {
											exists[v.name] = v.hash;
										});
										existed = intersect(files, jQuery.map(exists, function(h, n) { return n; }));
										jQuery.each(files, function(i, file) {
											if (exists[file.name]) {
												hashes[exists[file.name]] = file.name;
											}
										});
									}
								}
							} else {
								existedArr = [];
								existed = jQuery.map(names, function(n) {
									if (typeof n === 'string') {
										return n;
									} else {
										// support to >=2.1.11 plugin Normalizer, Sanitizer
										existedArr = existedArr.concat(n);
										return false;
									}
								});
								if (existedArr.length) {
									existed = existed.concat(existedArr);
								}
								existed = intersect(files, existed);
								hashes = names;
							}
						}
						existed.length ? confirm(0) : paste(files);
					},
					paste     = function(selFiles) {
						var renames = [],
							files  = jQuery.grep(selFiles, function(file) { 
								if (file.rename) {
									renames.push(file.name);
								}
								return !file.remove ? true : false;
							}),
							cnt    = files.length,
							groups = {},
							args   = [],
							targets, reqData;

						if (!cnt) {
							return dfrd.resolve();
						}

						targets = jQuery.map(files, function(f) { return f.hash; });
						
						reqData = {cmd : 'paste', dst : dst.hash, targets : targets, cut : cut ? 1 : 0, renames : renames, hashes : hashes, suffix : fm.options.backupSuffix};
						if (fm.api < 2.1) {
							reqData.src = files[0].phash;
						}
						
						fm.request({
								data   : reqData,
								notify : {type : cmd, cnt : cnt},
								navigate : { 
									toast  : opts.noToast? {} : {
										inbuffer : {msg: fm.i18n(['complete', fm.i18n('cmd' + cmd)]), action: {
											cmd: 'open',
											msg: 'cmdopendir',
											data: [dst.hash],
											done: 'select',
											cwdNot: dst.hash
										}}
									}
								}
							})
							.done(function(data) {
								var dsts = {},
									added = data.added && data.added.length? data.added : null;
								if (cut && added) {
									// undo/redo
									jQuery.each(files, function(i, f) {
										var phash = f.phash,
											srcHash = function(name) {
												var hash;
												jQuery.each(added, function(i, f) {
													if (f.name === name) {
														hash = f.hash;
														return false;
													}
												});
												return hash;
											},
											shash = srcHash(f.name);
										if (shash) {
											if (dsts[phash]) {
												dsts[phash].push(shash);
											} else {
												dsts[phash] = [ shash ];
											}
										}
									});
									if (Object.keys(dsts).length) {
										data.undo = {
											cmd : 'move',
											callback : function() {
												var reqs = [];
												jQuery.each(dsts, function(dst, targets) {
													reqs.push(fm.request({
														data : {cmd : 'paste', dst : dst, targets : targets, cut : 1},
														notify : {type : 'undo', cnt : targets.length}
													}));
												});
												return jQuery.when.apply(null, reqs);
											}
										};
										data.redo = {
											cmd : 'move',
											callback : function() {
												return fm.request({
													data : reqData,
													notify : {type : 'redo', cnt : cnt}
												});
											}
										};
									}
								}
								dfrd.resolve(data);
							})
							.fail(function() {
								dfrd.reject();
							})
							.always(function() {
								fm.unlockfiles({files : files});
							});
					},
					internames;

				if (!fm.isCommandEnabled(self.name, dst.hash) || !files.length) {
					return dfrd.resolve();
				}
				
				if (fm.oldAPI) {
					paste(files);
				} else {
					
					if (!fm.option('copyOverwrite', dst.hash)) {
						paste(files);
					} else {
						internames = jQuery.map(files, function(f) { return f.name; });
						dst.hash == fm.cwd().hash
							? valid(jQuery.map(fm.files(), function(file) { return file.phash == dst.hash ? {hash: file.hash, name: file.name} : null; }))
							: fm.request({
								data : {cmd : 'ls', target : dst.hash, intersect : internames},
								notify : {type : 'prepare', cnt : 1, hideCnt : true},
								preventFail : true
							})
							.always(function(data) {
								valid(data.list);
							});
					}
				}
				
				return dfrd;
			},
			parents, fparents;


		if (!cnt || !dst || dst.mime != 'directory') {
			return dfrd.reject();
		}
			
		if (!dst.write)	{
			return dfrd.reject([error, files[0].name, 'errPerm']);
		}
		
		parents = fm.parents(dst.hash);
		
		jQuery.each(files, function(i, file) {
			if (!file.read) {
				return !dfrd.reject([error, file.name, 'errPerm']);
			}
			
			if (cut && file.locked) {
				return !dfrd.reject(['errLocked', file.name]);
			}
			
			if (jQuery.inArray(file.hash, parents) !== -1) {
				return !dfrd.reject(['errCopyInItself', file.name]);
			}
			
			if (file.mime && file.mime !== 'directory' && ! fm.uploadMimeCheck(file.mime, dst.hash)) {
				return !dfrd.reject([error, file.name, 'errUploadMime']);
			}
			
			fparents = fm.parents(file.hash);
			fparents.pop();
			if (jQuery.inArray(dst.hash, fparents) !== -1) {
				
				if (jQuery.grep(fparents, function(h) { var d = fm.file(h); return d.phash == dst.hash && d.name == file.name ? true : false; }).length) {
					return !dfrd.reject(['errReplByChild', file.name]);
				}
			}
			
			if (file.phash == dst.hash) {
				fcopy.push(file.hash);
			} else {
				fpaste.push({
					hash  : file.hash,
					phash : file.phash,
					name  : file.name
				});
			}
		});

		if (dfrd.state() == 'rejected') {
			return dfrd;
		}

		jQuery.when(
			copy(fcopy),
			paste(fpaste)
		)
		.done(function(cr, pr) {
			dfrd.resolve(pr && pr.undo? pr : void(0));
		})
		.fail(function() {
			dfrd.reject();
		})
		.always(function() {
			cut && fm.clipboard([]);
		});
		
		return dfrd;
	};

};


/*
 * File: /js/commands/places.js
 */

/**
 * @class  elFinder command "places"
 * Regist to Places
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.places = function() {
		var self   = this,
	fm     = this.fm,
	filter = function(hashes) {
		return jQuery.grep(self.files(hashes), function(f) { return f.mime == 'directory' ? true : false; });
	},
	places = null;
	
	this.getstate = function(select) {
		var sel = this.hashes(select),
		cnt = sel.length;
		
		return  places && cnt && cnt == filter(sel).length ? 0 : -1;
	};
	
	this.exec = function(hashes) {
		var files = this.files(hashes);
		places.trigger('regist', [ files ]);
		return jQuery.Deferred().resolve();
	};
	
	fm.one('load', function(){
		places = fm.ui.places;
	});

};


/*
 * File: /js/commands/preference.js
 */

/**
 * @class  elFinder command "preference"
 * "Preference" dialog
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.preference = function() {
	var self    = this,
		fm      = this.fm,
		r       = 'replace',
		tab     = '<li class="' + fm.res('class', 'tabstab') + ' elfinder-preference-tab-{id}"><a href="#'+fm.namespace+'-preference-{id}" id="'+fm.namespace+'-preference-tab-{id}" class="ui-tabs-anchor {class}">{title}</a></li>',
		base    = jQuery('<div class="ui-tabs ui-widget ui-widget-content ui-corner-all elfinder-preference">'), 
		ul      = jQuery('<ul class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-top">'),
		tabs    = jQuery('<div class="elfinder-preference-tabs ui-tabs-panel ui-widget-content ui-corner-bottom"/>'),
		sep     = '<div class="elfinder-preference-separator"/>',
		selfUrl = jQuery('base').length? document.location.href.replace(/#.*$/, '') : '',
		selectTab = function(tab) {
			jQuery('#'+fm.namespace+'-preference-tab-'+tab).trigger('mouseover').trigger('click');
			openTab = tab;
		},
		clTabActive = fm.res('class', 'tabsactive'),
		build   = function() {
			var cats = self.options.categories || {
					'language' : ['language'],
					'theme' : ['theme'],
					'toolbar' : ['toolbarPref'],
					'workspace' : ['iconSize','columnPref', 'selectAction', 'makefileTypes', 'useStoredEditor', 'editorMaximized', 'showHidden'],
					'dialog' : ['autoFocusDialog'],
					'selectionInfo' : ['infoItems', 'hashChecker'],
					'reset' : ['clearBrowserData'],
					'all' : true
				},
				forms = self.options.prefs || ['language', 'theme', 'toolbarPref', 'iconSize', 'columnPref', 'selectAction', 'makefileTypes', 'useStoredEditor', 'editorMaximized', 'showHidden', 'infoItems', 'hashChecker', 'autoFocusDialog', 'clearBrowserData'];
			
			forms = fm.arrayFlip(forms, true);
			
			if (fm.options.getFileCallback) {
				delete forms.selectAction;
			}
			
			forms.language && (forms.language = (function() {
				var langSel = jQuery('<select/>').on('change', function() {
						var lang = jQuery(this).val();
						fm.storage('lang', lang);
						jQuery('#'+fm.id).elfinder('reload');
					}),
					optTags = [],
					langs = self.options.langs || {
						ar: 'اللغة العربية',
						bg: 'Български',
						ca: 'Català',
						cs: 'Čeština',
						da: 'Dansk',
						de: 'Deutsch',
						el: 'Ελληνικά',
						en: 'English',
						es: 'Español',
						fa: 'فارسی',
						fo: 'Føroyskt',
						fr: 'Français',
						he: 'עברית',
						hr: 'Hrvatski',
						hu: 'Magyar',
						id: 'Bahasa Indonesia',
						it: 'Italiano',
						ja: '日本語',
						ko: '한국어',
						nl: 'Nederlands',
						no: 'Norsk',
						pl: 'Polski',
						pt_BR: 'Português',
						ro: 'Română',
						ru: 'Pусский',
						si: 'සිංහල',
						sk: 'Slovenčina',
						sl: 'Slovenščina',
						sr: 'Srpski',
						sv: 'Svenska',
						tr: 'Türkçe',
						ug_CN: 'ئۇيغۇرچە',
						uk: 'Український',
						vi: 'Tiếng Việt',
						zh_CN: '简体中文',
						zh_TW: '正體中文'
					};
				jQuery.each(langs, function(lang, name) {
					optTags.push('<option value="'+lang+'">'+name+'</option>');
				});
				return langSel.append(optTags.join('')).val(fm.lang);
			})());
			
			forms.theme && (forms.theme = (function() {
				var cnt = fm.options.themes? Object.keys(fm.options.themes).length : 0;
				if (cnt === 0 || (cnt === 1 && fm.options.themes.default)) {
					return null;
				}
				var themeSel = jQuery('<select/>').on('change', function() {
						var theme = jQuery(this).val();
						fm.changeTheme(theme).storage('theme', theme);
					}),
					optTags = [],
					tpl = {
						image: '<img class="elfinder-preference-theme elfinder-preference-theme-image" src="$2" />',
						link: '<a href="$1" target="_blank" title="$3">$2</a>',
						data: '<dt>$1</dt><dd><span class="elfinder-preference-theme elfinder-preference-theme-$0">$2</span></dd>'
					},
					items = ['image', 'description', 'author', 'email', 'license'],
					render = function(key, data) {
					},
					defBtn = jQuery('<button class="ui-button ui-corner-all ui-widget elfinder-preference-theme-default"/>').text(fm.i18n('default')).on('click', function(e) {
						themeSel.val('default').trigger('change');
					}),
					list = jQuery('<div class="elfinder-reference-hide-taball"/>').on('click', 'button', function() {
							var val = jQuery(this).data('themeid');
							themeSel.val(val).trigger('change');
					});

				if (!fm.options.themes.default) {
					themeSel.append('<option value="default">'+fm.i18n('default')+'</option>');
				}
				jQuery.each(fm.options.themes, function(id, val) {
					var opt = jQuery('<option class="elfinder-theme-option-'+id+'" value="'+id+'">'+fm.i18n(id)+'</option>'),
						dsc = jQuery('<fieldset class="ui-widget ui-widget-content ui-corner-all elfinder-theme-list-'+id+'"><legend>'+fm.i18n(id)+'</legend><div><span class="elfinder-spinner"/></div></fieldset>'),
						tm;
					themeSel.append(opt);
					list.append(dsc);
					tm = setTimeout(function() {
						dsc.find('span.elfinder-spinner').replaceWith(fm.i18n(['errRead', id]));
					}, 10000);
					fm.getTheme(id).always(function() {
						tm && clearTimeout(tm);
					}).done(function(data) {
						var link, val = jQuery(), dl = jQuery('<dl/>');
						link = data.link? tpl.link.replace(/\$1/g, data.link).replace(/\$3/g, fm.i18n('website')) : '$2';
						if (data.name) {
							opt.html(fm.i18n(data.name));
						}
						dsc.children('legend').html(link.replace(/\$2/g, fm.i18n(data.name) || id));
						jQuery.each(items, function(i, key) {
							var t = tpl[key] || tpl.data,
								elm;
							if (data[key]) {
								elm = t.replace(/\$0/g, fm.escape(key)).replace(/\$1/g, fm.i18n(key)).replace(/\$2/g, fm.i18n(data[key]));
								if (key === 'image' && data.link) {
									elm = jQuery(elm).on('click', function() {
										themeSel.val(id).trigger('change');
									}).attr('title', fm.i18n('select'));
								}
								dl.append(elm);
							}
						});
						val = val.add(dl);
						val = val.add(jQuery('<div class="elfinder-preference-theme-btn"/>').append(jQuery('<button class="ui-button ui-corner-all ui-widget"/>').data('themeid', id).html(fm.i18n('select'))));
						dsc.find('span.elfinder-spinner').replaceWith(val);
					}).fail(function() {
						dsc.find('span.elfinder-spinner').replaceWith(fm.i18n(['errRead', id]));
					});
				});
				return jQuery('<div/>').append(themeSel.val(fm.theme && fm.theme.id? fm.theme.id : 'default'), defBtn, list);
			})());

			forms.toolbarPref && (forms.toolbarPref = (function() {
				var pnls = jQuery.map(fm.options.uiOptions.toolbar, function(v) {
						return jQuery.isArray(v)? v : null;
					}),
					tags = [],
					hides = fm.storage('toolbarhides') || {};
				jQuery.each(pnls, function() {
					var cmd = this,
						name = fm.i18n('cmd'+cmd);
					if (name === 'cmd'+cmd) {
						name = fm.i18n(cmd);
					}
					tags.push('<span class="elfinder-preference-toolbar-item"><label><input type="checkbox" value="'+cmd+'" '+(hides[cmd]? '' : 'checked')+'/>'+name+'</label></span>');
				});
				return jQuery(tags.join(' ')).on('change', 'input', function() {
					var v = jQuery(this).val(),
						o = jQuery(this).is(':checked');
					if (!o && !hides[v]) {
						hides[v] = true;
					} else if (o && hides[v]) {
						delete hides[v];
					}
					fm.storage('toolbarhides', hides);
					fm.trigger('toolbarpref');
				});
			})());
			
			forms.iconSize && (forms.iconSize = (function() {
				var max = fm.options.uiOptions.cwd.iconsView.sizeMax || 3,
					size = fm.storage('iconsize') || 0,
					sld = jQuery('<div class="touch-punch"/>').slider({
						classes: {
							'ui-slider-handle': 'elfinder-tabstop',
						},
						value: size,
						max: max,
						slide: function(e, ui) {
							fm.getUI('cwd').trigger('iconpref', {size: ui.value});
						},
						change: function(e, ui) {
							fm.storage('iconsize', ui.value);
						}
					});
				fm.getUI('cwd').on('iconpref', function(e, data) {
					sld.slider('option', 'value', data.size);
				});
				return sld;
			})());

			forms.columnPref && (forms.columnPref = (function() {
				var cols = fm.options.uiOptions.cwd.listView.columns,
					tags = [],
					hides = fm.storage('columnhides') || {};
				jQuery.each(cols, function() {
					var key = this,
						name = fm.getColumnName(key);
					tags.push('<span class="elfinder-preference-column-item"><label><input type="checkbox" value="'+key+'" '+(hides[key]? '' : 'checked')+'/>'+name+'</label></span>');
				});
				return jQuery(tags.join(' ')).on('change', 'input', function() {
					var v = jQuery(this).val(),
						o = jQuery(this).is(':checked');
					if (!o && !hides[v]) {
						hides[v] = true;
					} else if (o && hides[v]) {
						delete hides[v];
					}
					fm.storage('columnhides', hides);
					fm.trigger('columnpref', { repaint: true });
				});
			})());
			
			forms.selectAction && (forms.selectAction = (function() {
				var actSel = jQuery('<select/>').on('change', function() {
						var act = jQuery(this).val();
						fm.storage('selectAction', act === 'default'? null : act);
					}),
					optTags = [],
					acts = self.options.selectActions,
					defAct = fm.getCommand('open').options.selectAction || 'open';
				
				if (jQuery.inArray(defAct, acts) === -1) {
					acts.unshift(defAct);
				}
				jQuery.each(acts, function(i, act) {
					var names = jQuery.map(act.split('/'), function(cmd) {
						var name = fm.i18n('cmd'+cmd);
						if (name === 'cmd'+cmd) {
							name = fm.i18n(cmd);
						}
						return name;
					});
					optTags.push('<option value="'+act+'">'+names.join('/')+'</option>');
				});
				return actSel.append(optTags.join('')).val(fm.storage('selectAction') || defAct);
			})());
			
			forms.makefileTypes && (forms.makefileTypes = (function() {
				var hides = fm.storage('mkfileHides') || {},
					getTag = function() {
						var tags = [];
						// re-assign hides
						hides = fm.storage('mkfileHides') || {};
						jQuery.each(fm.mimesCanMakeEmpty, function(mime, type) {
							var name = fm.getCommand('mkfile').getTypeName(mime, type);
							tags.push('<span class="elfinder-preference-column-item" title="'+fm.escape(name)+'"><label><input type="checkbox" value="'+mime+'" '+(hides[mime]? '' : 'checked')+'/>'+type+'</label></span>');
						});
						return tags.join(' ');
					},
					elm = jQuery('<div/>').on('change', 'input', function() {
						var v = jQuery(this).val(),
							o = jQuery(this).is(':checked');
						if (!o && !hides[v]) {
							hides[v] = true;
						} else if (o && hides[v]) {
							delete hides[v];
						}
						fm.storage('mkfileHides', hides);
						fm.trigger('canMakeEmptyFile');
					}).append(getTag()),
					add = jQuery('<div/>').append(
						jQuery('<input type="text" placeholder="'+fm.i18n('typeOfTextfile')+'"/>').on('keydown', function(e) {
							(e.keyCode === jQuery.ui.keyCode.ENTER) && jQuery(this).next().trigger('click');
						}),
						jQuery('<button class="ui-button"/>').html(fm.i18n('add')).on('click', function() {
							var input = jQuery(this).prev(),
								val = input.val(),
								uiToast = fm.getUI('toast'),
								err = function() {
									uiToast.appendTo(input.closest('.ui-dialog'));
									fm.toast({
										msg: fm.i18n('errUsupportType'),
										mode: 'warning',
										onHidden: function() {
											uiToast.children().length === 1 && uiToast.appendTo(fm.getUI());
										}
									});
									input.trigger('focus');
									return false;
								},
								tmpMimes;
							if (!val.match(/\//)) {
								val = fm.arrayFlip(fm.mimeTypes)[val];
								if (!val) {
									return err();
								}
								input.val(val);
							}
							if (!fm.mimeIsText(val) || !fm.mimeTypes[val]) {
								return err();
							}
							fm.trigger('canMakeEmptyFile', {mimes: [val], unshift: true});
							tmpMimes = {};
							tmpMimes[val] = fm.mimeTypes[val];
							fm.storage('mkfileTextMimes', Object.assign(tmpMimes, fm.storage('mkfileTextMimes') || {}));
							input.val('');
							uiToast.appendTo(input.closest('.ui-dialog'));
							fm.toast({
								msg: fm.i18n(['complete', val + ' (' + tmpMimes[val] + ')']),
								onHidden: function() {
									uiToast.children().length === 1 && uiToast.appendTo(fm.getUI());
								}
							});
						}),
						jQuery('<button class="ui-button"/>').html(fm.i18n('reset')).on('click', function() {
							fm.one('canMakeEmptyFile', {done: function() {
								elm.empty().append(getTag());
							}});
							fm.trigger('canMakeEmptyFile', {resetTexts: true});
						})
					),
					tm;
				fm.bind('canMakeEmptyFile', {done: function(e) {
					if (e.data && e.data.mimes && e.data.mimes.length) {
						elm.empty().append(getTag());
					}
				}});
				return jQuery('<div/>').append(elm, add);
			})());

			forms.useStoredEditor && (forms.useStoredEditor = jQuery('<input type="checkbox"/>').prop('checked', (function() {
				var s = fm.storage('useStoredEditor');
				return s? (s > 0) : fm.options.commandsOptions.edit.useStoredEditor;
			})()).on('change', function(e) {
				fm.storage('useStoredEditor', jQuery(this).is(':checked')? 1 : -1);
			}));

			forms.editorMaximized && (forms.editorMaximized = jQuery('<input type="checkbox"/>').prop('checked', (function() {
				var s = fm.storage('editorMaximized');
				return s? (s > 0) : fm.options.commandsOptions.edit.editorMaximized;
			})()).on('change', function(e) {
				fm.storage('editorMaximized', jQuery(this).is(':checked')? 1 : -1);
			}));

			if (forms.showHidden) {
				(function() {
					var setTitle = function() {
							var s = fm.storage('hide'),
								t = [],
								v;
							if (s && s.items) {
								jQuery.each(s.items, function(h, n) {
									t.push(fm.escape(n));
								});
							}
							elms.prop('disabled', !t.length)[t.length? 'removeClass' : 'addClass']('ui-state-disabled');
							v = t.length? t.join('\n') : '';
							forms.showHidden.attr('title',v);
							useTooltip && forms.showHidden.tooltip('option', 'content', v.replace(/\n/g, '<br>')).tooltip('close');
						},
						chk = jQuery('<input type="checkbox"/>').prop('checked', (function() {
							var s = fm.storage('hide');
							return s && s.show;
						})()).on('change', function(e) {
							var o = {};
							o[jQuery(this).is(':checked')? 'show' : 'hide'] = true;
							fm.exec('hide', void(0), o);
						}),
						btn = jQuery('<button class="ui-button ui-corner-all ui-widget"/>').append(fm.i18n('reset')).on('click', function() {
							fm.exec('hide', void(0), {reset: true});
							jQuery(this).parent().find('input:first').prop('checked', false);
							setTitle();
						}),
						elms = jQuery().add(chk).add(btn),
						useTooltip;
					
					forms.showHidden = jQuery('<div/>').append(chk, btn);
					fm.bind('hide', function(e) {
						var d = e.data;
						if (!d.opts || (!d.opts.show && !d.opts.hide)) {
							setTitle();
						}
					});
					if (fm.UA.Mobile && jQuery.fn.tooltip) {
						useTooltip = true;
						forms.showHidden.tooltip({
							classes: {
								'ui-tooltip': 'elfinder-ui-tooltip ui-widget-shadow'
							},
							tooltipClass: 'elfinder-ui-tooltip ui-widget-shadow',
							track: true
						}).css('user-select', 'none');
						btn.css('user-select', 'none');
					}
					setTitle();
				})();
			}
			
			forms.infoItems && (forms.infoItems = (function() {
				var items = fm.getCommand('info').items,
					tags = [],
					hides = fm.storage('infohides') || fm.arrayFlip(fm.options.commandsOptions.info.hideItems, true);
				jQuery.each(items, function() {
					var key = this,
						name = fm.i18n(key);
					tags.push('<span class="elfinder-preference-info-item"><label><input type="checkbox" value="'+key+'" '+(hides[key]? '' : 'checked')+'/>'+name+'</label></span>');
				});
				return jQuery(tags.join(' ')).on('change', 'input', function() {
					var v = jQuery(this).val(),
						o = jQuery(this).is(':checked');
					if (!o && !hides[v]) {
						hides[v] = true;
					} else if (o && hides[v]) {
						delete hides[v];
					}
					fm.storage('infohides', hides);
					fm.trigger('infopref', { repaint: true });
				});
			})());
			
			forms.hashChecker && fm.hashCheckers.length && (forms.hashChecker = (function() {
				var tags = [],
					enabled = fm.arrayFlip(fm.storage('hashchekcer') || fm.options.commandsOptions.info.showHashAlgorisms, true);
				jQuery.each(fm.hashCheckers, function() {
					var cmd = this,
						name = fm.i18n(cmd);
					tags.push('<span class="elfinder-preference-hashchecker-item"><label><input type="checkbox" value="'+cmd+'" '+(enabled[cmd]? 'checked' : '')+'/>'+name+'</label></span>');
				});
				return jQuery(tags.join(' ')).on('change', 'input', function() {
					var v = jQuery(this).val(),
						o = jQuery(this).is(':checked');
					if (o) {
						enabled[v] = true;
					} else if (enabled[v]) {
						delete enabled[v];
					}
					fm.storage('hashchekcer', jQuery.grep(fm.hashCheckers, function(v) {
						return enabled[v];
					}));
				});
			})());

			forms.autoFocusDialog && (forms.autoFocusDialog = jQuery('<input type="checkbox"/>').prop('checked', (function() {
				var s = fm.storage('autoFocusDialog');
				return s? (s > 0) : fm.options.uiOptions.dialog.focusOnMouseOver;
			})()).on('change', function(e) {
				fm.storage('autoFocusDialog', jQuery(this).is(':checked')? 1 : -1);
			}));
			
			forms.clearBrowserData && (forms.clearBrowserData = jQuery('<button/>').text(fm.i18n('reset')).button().on('click', function(e) {
				e.preventDefault();
				fm.storage();
				jQuery('#'+fm.id).elfinder('reload');
			}));
			
			jQuery.each(cats, function(id, prefs) {
				var dls, found;
				if (prefs === true) {
					found = 1;
				} else if (prefs) {
					dls = jQuery();
					jQuery.each(prefs, function(i, n) {
						var f, title, chks = '', cbox;
						if (f = forms[n]) {
							found = 2;
							title = fm.i18n(n);
							cbox = jQuery(f).filter('input[type="checkbox"]');
							if (!cbox.length) {
								cbox = jQuery(f).find('input[type="checkbox"]');
							}
							if (cbox.length === 1) {
								if (!cbox.attr('id')) {
									cbox.attr('id', 'elfinder-preference-'+n+'-checkbox');
								}
								title = '<label for="'+cbox.attr('id')+'">'+title+'</label>';
							} else if (cbox.length > 1) {
								chks = ' elfinder-preference-checkboxes';
							}
							dls = dls.add(jQuery('<dt class="elfinder-preference-'+n+chks+'">'+title+'</dt>')).add(jQuery('<dd class="elfinder-preference-'+n+chks+'"/>').append(f));
						}
					});
				}
				if (found) {
					ul.append(tab[r](/\{id\}/g, id)[r](/\{title\}/, fm.i18n(id))[r](/\{class\}/, openTab === id? 'elfinder-focus' : ''));
					if (found === 2) {
						tabs.append(
							jQuery('<div id="'+fm.namespace+'-preference-'+id+'" class="elfinder-preference-content"/>')
							.hide()
							.append(jQuery('<dl/>').append(dls))
						);
					}
				}
			});

			ul.on('click', 'a', function(e) {
				var t = jQuery(e.target),
					h = t.attr('href');
				e.preventDefault();
				e.stopPropagation();

				ul.children().removeClass(clTabActive);
				t.removeClass('ui-state-hover').parent().addClass(clTabActive);

				if (h.match(/all$/)) {
					tabs.addClass('elfinder-preference-taball').children().show();
				} else {
					tabs.removeClass('elfinder-preference-taball').children().hide();
					jQuery(h).show();
				}
			}).on('focus blur', 'a', function(e) {
				jQuery(this).parent().toggleClass('ui-state-focus', e.type === 'focusin');
			}).on('mouseenter mouseleave', 'li', function(e) {
				jQuery(this).toggleClass('ui-state-hover', e.type === 'mouseenter');
			});

			tabs.find('a,input,select,button').addClass('elfinder-tabstop');
			base.append(ul, tabs);

			dialog = self.fmDialog(base, {
				title : self.title,
				width : self.options.width || 600,
				height: self.options.height || 400,
				maxWidth: 'window',
				maxHeight: 'window',
				autoOpen : false,
				destroyOnClose : false,
				allowMinimize : false,
				open : function() {
					openTab && selectTab(openTab);
					openTab = null;
				},
				resize : function() {
					tabs.height(dialog.height() - ul.outerHeight(true) - (tabs.outerHeight(true) - tabs.height()) - 5);
				}
			})
			.on('click', function(e) {
				e.stopPropagation();
			})
			.css({
				overflow: 'hidden'
			});

			dialog.closest('.ui-dialog')
			.css({
				overflow: 'hidden'
			})
			.addClass('elfinder-bg-translucent');
			
			openTab = 'all';
		},
		dialog, openTab;

	this.shortcuts = [{
		pattern     : 'ctrl+comma',
		description : this.title
	}];

	this.alwaysEnabled  = true;
	
	this.getstate = function() {
		return 0;
	};
	
	this.exec = function(sel, cOpts) {
		!dialog && build();
		if (cOpts) {
			if (cOpts.tab) {
				selectTab(cOpts.tab);
			} else if (cOpts._currentType === 'cwd') {
				selectTab('workspace');
			}
		}
		dialog.elfinderdialog('open');
		return jQuery.Deferred().resolve();
	};

};

/*
 * File: /js/commands/quicklook.js
 */

/**
 * @class  elFinder command "quicklook"
 * Fast preview for some files types
 *
 * @author Dmitry (dio) Levashov
 **/
(elFinder.prototype.commands.quicklook = function() {
		var self       = this,
		fm         = self.fm,
		/**
		 * window closed state
		 *
		 * @type Number
		 **/
		closed     = 0,
		/**
		 * window animated state
		 *
		 * @type Number
		 **/
		animated   = 1,
		/**
		 * window opened state
		 *
		 * @type Number
		 **/
		opened     = 2,
		/**
		 * window docked state
		 *
		 * @type Number
		 **/
		docked     = 3,
		/**
		 * window docked and hidden state
		 *
		 * @type Number
		 **/
		dockedhidden = 4,
		/**
		 * window state
		 *
		 * @type Number
		 **/
		state      = closed,
		/**
		 * Event name of update
		 * for fix conflicts with Prototype.JS
		 * 
		 * `@see https://github.com/Studio-42/elFinder/pull/2346
		 * @type String
		 **/
		evUpdate = Element.update? 'quicklookupdate' : 'update',
		/**
		 * navbar icon class
		 *
		 * @type String
		 **/
		navicon    = 'elfinder-quicklook-navbar-icon',
		/**
		 * navbar "fullscreen" icon class
		 *
		 * @type String
		 **/
		fullscreen = 'elfinder-quicklook-fullscreen',
		/**
		 * info wrapper class
		 * 
		 * @type String
		 */
		infocls    = 'elfinder-quicklook-info-wrapper',
		/**
		 * Triger keydown/keypress event with left/right arrow key code
		 *
		 * @param  Number  left/right arrow key code
		 * @return void
		 **/
		navtrigger = function(code) {
			jQuery(document).trigger(jQuery.Event('keydown', { keyCode: code, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
		},
		/**
		 * Return css for closed window
		 *
		 * @param  jQuery  file node in cwd
		 * @return void
		 **/
		closedCss = function(node) {
			var elf = fm.getUI().offset(),
				base = (function() {
					var target = node.find('.elfinder-cwd-file-wrapper');
					return target.length? target : node;
				})(),
				baseOffset = base.offset() || { top: 0, left: 0 };
			return {
				opacity : 0,
				width   : base.width(),
				height  : base.height() - 30,
				top     : baseOffset.top - elf.top,
				left    : baseOffset.left  - elf.left
			};
		},
		/**
		 * Return css for opened window
		 *
		 * @return void
		 **/
		openedCss = function() {
			var contain = self.options.contain,
				win = contain? fm.getUI() : jQuery(window),
				elf = fm.getUI().offset(),
				w = Math.min(width, win.width()-10),
				h = Math.min(height, win.height()-80);
			return {
				opacity : 1,
				width  : w,
				height : h,
				top    : parseInt((win.height() - h - 60) / 2 + (contain? 0 : win.scrollTop() - elf.top)),
				left   : parseInt((win.width() - w) / 2 + (contain? 0 : win.scrollLeft() - elf.left))
			};
		},
		
		mediaNode = {},
		support = function(codec, name) {
			var node  = name || codec.substr(0, codec.indexOf('/')),
				media = mediaNode[node]? mediaNode[node] : (mediaNode[node] = document.createElement(node)),
				value = false;
			
			try {
				value = media.canPlayType && media.canPlayType(codec);
			} catch(e) {}
			
			return (value && value !== '' && value != 'no')? true : false;
		},
		
		platformWin = (window.navigator.platform.indexOf('Win') != -1),
		
		/**
		 * Opened window width (from config)
		 *
		 * @type Number
		 **/
		width, 
		/**
		 * Opened window height (from config)
		 *
		 * @type Number
		 **/
		height, 
		/**
		 * Previous style before docked
		 *
		 * @type String
		 **/
		prevStyle,
		/**
		 * elFinder node
		 *
		 * @type jQuery
		 **/
		parent, 
		/**
		 * elFinder current directory node
		 *
		 * @type jQuery
		 **/
		cwd, 
		/**
		 * Current directory hash
		 *
		 * @type String
		 **/
		cwdHash,
		dockEnabled = false,
		navdrag = false,
		navmove = false,
		navtm   = null,
		leftKey = jQuery.ui.keyCode.LEFT,
		rightKey = jQuery.ui.keyCode.RIGHT,
		coverEv = 'mousemove touchstart ' + ('onwheel' in document? 'wheel' : 'onmousewheel' in document? 'mousewheel' : 'DOMMouseScroll'),
		title   = jQuery('<span class="elfinder-dialog-title elfinder-quicklook-title"/>'),
		icon    = jQuery('<div/>'),
		info    = jQuery('<div class="elfinder-quicklook-info"/>'),//.hide(),
		cover   = jQuery('<div class="ui-front elfinder-quicklook-cover"/>'),
		fsicon  = jQuery('<div class="'+navicon+' '+navicon+'-fullscreen"/>')
			.on('click touchstart', function(e) {
				if (navmove) {
					return;
				}
				
				var win     = self.window,
					full    = win.hasClass(fullscreen),
					$window = jQuery(window),
					resize  = function() { self.preview.trigger('changesize'); };
					
				e.stopPropagation();
				e.preventDefault();
				
				if (full) {
					navStyle = '';
					navShow();
					win.toggleClass(fullscreen)
					.css(win.data('position'));
					$window.trigger(self.resize).off(self.resize, resize);
					navbar.off('mouseenter mouseleave');
					cover.off(coverEv);
				} else {
					win.toggleClass(fullscreen)
					.data('position', {
						left   : win.css('left'), 
						top    : win.css('top'), 
						width  : win.width(), 
						height : win.height(),
						display: 'block'
					})
					.removeAttr('style');

					jQuery(window).on(self.resize, resize)
					.trigger(self.resize);

					cover.on(coverEv, function(e) {
						if (! navdrag) {
							if (e.type === 'mousemove' || e.type === 'touchstart') {
								navShow();
								navtm = setTimeout(function() {
									if (fm.UA.Mobile || navbar.parent().find('.elfinder-quicklook-navbar:hover').length < 1) {
										navbar.fadeOut('slow', function() {
											cover.show();
										});
									}
								}, 3000);
							}
							if (cover.is(':visible')) {
								coverHide();
								cover.data('tm', setTimeout(function() {
									cover.show();
								}, 3000));
							}
						}
					}).show().trigger('mousemove');
					
					navbar.on('mouseenter mouseleave', function(e) {
						if (! navdrag) {
							if (e.type === 'mouseenter') {
								navShow();
							} else {
								cover.trigger('mousemove');
							}
						}
					});
				}
				if (fm.zIndex) {
					win.css('z-index', fm.zIndex + 1);
				}
				if (fm.UA.Mobile) {
					navbar.attr('style', navStyle);
				} else {
					navbar.attr('style', navStyle).draggable(full ? 'destroy' : {
						start: function() {
							navdrag = true;
							navmove = true;
							cover.show();
							navShow();
						},
						stop: function() {
							navdrag = false;
							navStyle = self.navbar.attr('style');
							requestAnimationFrame(function() {
								navmove = false;
							});
						}
					});
				}
				jQuery(this).toggleClass(navicon+'-fullscreen-off');
				var collection = win;
				if (parent.is('.ui-resizable')) {
					collection = collection.add(parent);
				}
				collection.resizable(full ? 'enable' : 'disable').removeClass('ui-state-disabled');

				win.trigger('viewchange');
			}
		),
		
		updateOnSel = function() {
			self.update(void(0), (function() {
				var fm = self.fm,
					files = fm.selectedFiles(),
					cnt = files.length,
					inDock = self.docked(),
					getInfo = function() {
						var ts = 0;
						jQuery.each(files, function(i, f) {
							var t = parseInt(f.ts);
							if (ts >= 0) {
								if (t > ts) {
									ts = t;
								}
							} else {
								ts = 'unknown';
							}
						});
						return {
							hash : files[0].hash  + '/' + (+new Date()),
							name : fm.i18n('items') + ': ' + cnt,
							mime : 'group',
							size : spinner,
							ts   : ts,
							files : jQuery.map(files, function(f) { return f.hash; }),
							getSize : true
						};
					};
				if (! cnt) {
					cnt = 1;
					files = [fm.cwd()];
				}
				return (cnt === 1)? files[0] : getInfo();
			})());
		},
		
		navShow = function() {
			if (self.window.hasClass(fullscreen)) {
				navtm && clearTimeout(navtm);
				navtm = null;
				// if use `show()` it make infinite loop with old jQuery (jQuery/jQuery UI: 1.8.0/1.9.0)
				// see #1478 https://github.com/Studio-42/elFinder/issues/1478
				navbar.stop(true, true).css('display', 'block');
				coverHide();
			}
		},
		
		coverHide = function() {
			cover.data('tm') && clearTimeout(cover.data('tm'));
			cover.removeData('tm');
			cover.hide();
		},
			
		prev = jQuery('<div class="'+navicon+' '+navicon+'-prev"/>').on('click touchstart', function(e) { ! navmove && navtrigger(leftKey); return false; }),
		next = jQuery('<div class="'+navicon+' '+navicon+'-next"/>').on('click touchstart', function(e) { ! navmove && navtrigger(rightKey); return false; }),
		navbar  = jQuery('<div class="elfinder-quicklook-navbar"/>')
			.append(prev)
			.append(fsicon)
			.append(next)
			.append('<div class="elfinder-quicklook-navbar-separator"/>')
			.append(jQuery('<div class="'+navicon+' '+navicon+'-close"/>').on('click touchstart', function(e) { ! navmove && self.window.trigger('close'); return false; }))
		,
		titleClose = jQuery('<span class="ui-front ui-icon elfinder-icon-close ui-icon-closethick"/>').on('mousedown', function(e) {
			e.stopPropagation();
			self.window.trigger('close');
		}),
		titleDock = jQuery('<span class="ui-front ui-icon elfinder-icon-minimize ui-icon-minusthick"/>').on('mousedown', function(e) {
			e.stopPropagation();
			if (! self.docked()) {
				self.window.trigger('navdockin');
			} else {
				self.window.trigger('navdockout');
			}
		}),
		spinner = '<span class="elfinder-spinner-text">' + fm.i18n('calc') + '</span>' + '<span class="elfinder-spinner"/>',
		navStyle = '',
		init = true,
		dockHeight,	getSize, tm4cwd, dockedNode, selectTm;

	this.cover = cover;
	this.evUpdate = evUpdate;
	(this.navbar = navbar)._show = navShow;
	this.resize = 'resize.'+fm.namespace;
	this.info = jQuery('<div/>').addClass(infocls)
		.append(icon)
		.append(info);
	this.autoPlay = function() {
		if (self.opened()) {
			return !! self.options[self.docked()? 'dockAutoplay' : 'autoplay'];
		}
		return false;
	};
	this.preview = jQuery('<div class="elfinder-quicklook-preview ui-helper-clearfix"/>')
		// clean info/icon
		.on('change', function() {
			navShow();
			navbar.attr('style', navStyle);
			self.docked() && navbar.hide();
			self.preview.attr('style', '').removeClass('elfinder-overflow-auto');
			self.info.attr('style', '').hide();
			self.cover.removeClass('elfinder-quicklook-coverbg');
			icon.removeAttr('class').attr('style', '');
			info.html('');
		})
		// update info/icon
		.on(evUpdate, function(e) {
			var preview = self.preview,
				file    = e.file,
				tpl     = '<div class="elfinder-quicklook-info-data">{value}</div>',
				update  = function() {
					var win = self.window.css('overflow', 'hidden');
					name = fm.escape(file.i18 || file.name);
					!file.read && e.stopImmediatePropagation();
					self.window.data('hash', file.hash);
					self.preview.off('changesize').trigger('change').children().remove();
					title.html(name);
					
					prev.css('visibility', '');
					next.css('visibility', '');
					if (file.hash === fm.cwdId2Hash(cwd.find('[id]:not(.elfinder-cwd-parent):first').attr('id'))) {
						prev.css('visibility', 'hidden');
					}
					if (file.hash === fm.cwdId2Hash(cwd.find('[id]:last').attr('id'))) {
						next.css('visibility', 'hidden');
					}
					
					if (file.mime === 'directory') {
						getSizeHashes = [ file.hash ];
					} else if (file.mime === 'group' && file.getSize) {
						getSizeHashes = file.files;
					}
					
					info.html(
						tpl.replace(/\{value\}/, name)
						+ tpl.replace(/\{value\}/, fm.mime2kind(file))
						+ tpl.replace(/\{value\}/, getSizeHashes.length ? spinner : fm.formatSize(file.size))
						+ tpl.replace(/\{value\}/, fm.i18n('modify')+': '+ fm.formatDate(file))
					);
					
					if (getSizeHashes.length) {
						getSize = fm.getSize(getSizeHashes).done(function(data) {
							info.find('span.elfinder-spinner').parent().html(data.formated);
						}).fail(function() {
							info.find('span.elfinder-spinner').parent().html(fm.i18n('unknown'));
						}).always(function() {
							getSize = null;
						});
						getSize._hash = file.hash;
					}
					
					icon.addClass('elfinder-cwd-icon ui-corner-all '+fm.mime2class(file.mime));
					
					if (file.icon) {
						icon.css(fm.getIconStyle(file, true));
					}
					
					self.info.attr('class', infocls);
					if (file.csscls) {
						self.info.addClass(file.csscls);
					}
	
					if (file.read && (tmb = fm.tmb(file))) {
						jQuery('<img/>')
							.hide()
							.appendTo(self.preview)
							.on('load', function() {
								icon.addClass(tmb.className).css('background-image', "url('"+tmb.url+"')");
								jQuery(this).remove();
							})
							.attr('src', tmb.url);
					}
					self.info.delay(100).fadeIn(10);
					if (self.window.hasClass(fullscreen)) {
						cover.trigger('mousemove');
					}
					win.css('overflow', '');
				},
				tmb, name, getSizeHashes = [];

			if (file && ! Object.keys(file).length) {
				file = fm.cwd();
			}
			if (file && getSize && getSize.state() === 'pending' && getSize._hash !== file.hash) {
				getSize.reject();
			}
			if (file && (e.forceUpdate || self.window.data('hash') !== file.hash)) {
				update();
			} else { 
				e.stopImmediatePropagation();
			}
		});

	this.window = jQuery('<div class="ui-front ui-helper-reset ui-widget elfinder-quicklook touch-punch" style="position:absolute"/>')
		.hide()
		.addClass(fm.UA.Touch? 'elfinder-touch' : '')
		.on('click', function(e) {
			var win = this;
			e.stopPropagation();
			if (state === opened) {
				requestAnimationFrame(function() {
					state === opened && fm.toFront(win);
				});
			}
		})
		.append(
			jQuery('<div class="ui-dialog-titlebar ui-widget-header ui-corner-top ui-helper-clearfix elfinder-quicklook-titlebar"/>')
			.append(
				jQuery('<span class="ui-widget-header ui-dialog-titlebar-close ui-corner-all elfinder-titlebar-button elfinder-quicklook-titlebar-icon'+(platformWin? ' elfinder-titlebar-button-right' : '')+'"/>').append(
					titleClose, titleDock
				),
				title
			),
			this.preview,
			self.info.hide(),
			cover.hide(),
			navbar
		)
		.draggable({handle : 'div.elfinder-quicklook-titlebar'})
		.on('open', function(e, clcss) {
			var win  = self.window, 
				file = self.value,
				node = fm.getUI('cwd'),
				open = function(status) {
					state = status;
					self.update(1, self.value);
					self.change();
					win.trigger('resize.' + fm.namespace);
				};

			if (!init && state === closed) {
				if (file && file.hash !== cwdHash) {
					node = fm.cwdHash2Elm(file.hash.split('/', 2)[0]);
				}
				navStyle = '';
				navbar.attr('style', '');
				state = animated;
				node.trigger('scrolltoview');
				coverHide();
				win.css(clcss || closedCss(node))
					.show()
					.animate(openedCss(), 550, function() {
						open(opened);
						navShow();
					});
				fm.toFront(win);
			} else if (state === dockedhidden) {
				fm.getUI('navdock').data('addNode')(dockedNode);
				open(docked);
				self.preview.trigger('changesize');
				fm.storage('previewDocked', '1');
				if (fm.getUI('navdock').width() === 0) {
					win.trigger('navdockout');
				}
			}
		})
		.on('close', function(e, dfd) {
			var win     = self.window,
				preview = self.preview.trigger('change'),
				file    = self.value,
				hash    = (win.data('hash') || '').split('/', 2)[0],
				close   = function(status, winhide) {
					state = status;
					winhide && fm.toHide(win);
					preview.children().remove();
					self.update(0, self.value);
					win.data('hash', '');
					dfd && dfd.resolve();
				},
				node;
				
			if (self.opened()) {
				getSize && getSize.state() === 'pending' && getSize.reject();
				if (! self.docked()) {
					state = animated;
					win.hasClass(fullscreen) && fsicon.click();
					(hash && (node = cwd.find('#'+hash)).length)
						? win.animate(closedCss(node), 500, function() { close(closed, true); })
						: close(closed, true);
				} else {
					dockedNode = fm.getUI('navdock').data('removeNode')(self.window.attr('id'), 'detach');
					close(dockedhidden);
					fm.storage('previewDocked', '2');
				}
			}
		})
		.on('navdockin', function(e, data) {
			var w      = self.window,
				box    = fm.getUI('navdock'),
				height = dockHeight || box.width(),
				opts   = data || {};
			
			if (init) {
				opts.init = true;
			}
			state = docked;
			prevStyle = w.attr('style');
			w.toggleClass('ui-front').removeClass('ui-widget').draggable('disable').resizable('disable').removeAttr('style').css({
				width: '100%',
				height: height,
				boxSizing: 'border-box',
				paddingBottom: 0,
				zIndex: 'unset'
			});
			navbar.hide();
			titleDock.toggleClass('ui-icon-plusthick ui-icon-minusthick elfinder-icon-full elfinder-icon-minimize');
			
			fm.toHide(w, true);
			box.data('addNode')(w, opts);
			
			self.preview.trigger('changesize');
			
			fm.storage('previewDocked', '1');
		})
		.on('navdockout', function(e) {
			var w   = self.window,
				box = fm.getUI('navdock'),
				dfd = jQuery.Deferred(),
				clcss = closedCss(self.preview);
			
			dockHeight = w.outerHeight();
			box.data('removeNode')(w.attr('id'), fm.getUI());
			w.toggleClass('ui-front').addClass('ui-widget').draggable('enable').resizable('enable').attr('style', prevStyle);
			titleDock.toggleClass('ui-icon-plusthick ui-icon-minusthick elfinder-icon-full elfinder-icon-minimize');
			
			state = closed;
			w.trigger('open', clcss);
			
			fm.storage('previewDocked', '0');
		})
		.on('resize.' + fm.namespace, function() {
			self.preview.trigger('changesize'); 
		});

	/**
	 * This command cannot be disable by backend
	 *
	 * @type Boolean
	 **/
	this.alwaysEnabled = true;
	
	/**
	 * Selected file
	 *
	 * @type Object
	 **/
	this.value = null;
	
	this.handlers = {
		// save selected file
		select : function(e, d) {
			selectTm && cancelAnimationFrame(selectTm);
			if (! e.data || ! e.data.selected || ! e.data.selected.length) {
				selectTm = requestAnimationFrame(function() {
					self.opened() && updateOnSel();
				});
			} else {
				self.opened() && updateOnSel();
			}
		},
		error  : function() { self.window.is(':visible') && self.window.trigger('close'); },
		'searchshow searchhide' : function() { this.opened() && this.window.trigger('close'); },
		navbarshow : function() {
			requestAnimationFrame(function() {
				self.docked() && self.preview.trigger('changesize');
			});
		},
		destroy : function() { self.window.remove(); }
	};
	
	this.shortcuts = [{
		pattern     : 'space'
	}];
	
	this.support = {
		audio : {
			ogg : support('audio/ogg;'),
			webm: support('audio/webm;'),
			mp3 : support('audio/mpeg;'),
			wav : support('audio/wav;'),
			m4a : support('audio/mp4;') || support('audio/x-m4a;') || support('audio/aac;'),
			flac: support('audio/flac;'),
			amr : support('audio/amr;')
		},
		video : {
			ogg  : support('video/ogg;'),
			webm : support('video/webm;'),
			mp4  : support('video/mp4;'),
			mkv  : support('video/x-matroska;') || support('video/webm;'),
			'3gp': support('video/3gpp;') || support('video/mp4;'), // try as mp4
			m3u8 : support('application/x-mpegURL', 'video') || support('application/vnd.apple.mpegURL', 'video'),
			mpd  : support('application/dash+xml', 'video')
		}
	};
	// for GC
	mediaNode = {};
	
	/**
	 * Return true if quickLoock window is hiddenReturn true if quickLoock window is visible and not animated
	 *
	 * @return Boolean
	 **/
	this.closed = function() {
		return (state == closed || state == dockedhidden);
	};
	
	/**
	 * Return true if quickLoock window is visible and not animated
	 *
	 * @return Boolean
	 **/
	this.opened = function() {
		return state == opened || state == docked;
	};
	
	/**
	 * Return true if quickLoock window is in NavDock
	 *
	 * @return Boolean
	 **/
	this.docked = function() {
		return state == docked;
	};
	
	/**
	 * Adds an integration into help dialog.
	 *
	 * @param Object opts  options
	 */
	this.addIntegration = function(opts) {
		requestAnimationFrame(function() {
			fm.trigger('helpIntegration', Object.assign({cmd: 'quicklook'}, opts));
		});
	};

	/**
	 * Init command.
	 * Add default plugins and init other plugins
	 *
	 * @return Object
	 **/
	this.init = function() {
		var o       = this.options, 
			win     = this.window,
			preview = this.preview,
			i, p, cwdDispInlineRegex;
		
		width  = o.width  > 0 ? parseInt(o.width)  : 450;	
		height = o.height > 0 ? parseInt(o.height) : 300;
		if (o.dockHeight !== 'auto') {
			dockHeight = parseInt(o.dockHeight);
			if (! dockHeight) {
				dockHeight = void(0);
			}
		}

		fm.one('load', function() {
			
			dockEnabled = fm.getUI('navdock').data('dockEnabled');
			
			! dockEnabled && titleDock.hide();
			
			parent = fm.getUI();
			cwd    = fm.getUI('cwd');

			if (fm.zIndex) {
				win.css('z-index', fm.zIndex + 1);
			}
			
			win.appendTo(parent);
			
			// close window on escape
			jQuery(document).on('keydown.'+fm.namespace, function(e) {
				e.keyCode == jQuery.ui.keyCode.ESCAPE && self.opened() && ! self.docked() && win.hasClass('elfinder-frontmost') && win.trigger('close');
			});
			
			win.resizable({ 
				handles   : 'se', 
				minWidth  : 350, 
				minHeight : 120, 
				resize    : function() { 
					// use another event to avoid recursion in fullscreen mode
					// may be there is clever solution, but i cant find it :(
					preview.trigger('changesize'); 
				}
			});
			
			self.change(function() {
				if (self.opened()) {
					if (self.value) {
						if (self.value.tmb && self.value.tmb == 1) {
							// try re-get file object
							self.value = Object.assign({}, fm.file(self.value.hash));
						}
						preview.trigger(jQuery.Event(evUpdate, {file : self.value}));
					}
				}
			});
			
			preview.on(evUpdate, function(e) {
				var file, hash, serach;
				
				if (file = e.file) {
					hash = file.hash;
					serach = (fm.searchStatus.mixed && fm.searchStatus.state > 1);
				
					if (file.mime !== 'directory') {
						if (parseInt(file.size) || file.mime.match(o.mimeRegexNotEmptyCheck)) {
							// set current dispInlineRegex
							self.dispInlineRegex = cwdDispInlineRegex;
							if (serach || fm.optionsByHashes[hash]) {
								try {
									self.dispInlineRegex = new RegExp(fm.option('dispInlineRegex', hash), 'i');
								} catch(e) {
									try {
										self.dispInlineRegex = new RegExp(!fm.isRoot(file)? fm.option('dispInlineRegex', file.phash) : fm.options.dispInlineRegex, 'i');
									} catch(e) {
										self.dispInlineRegex = /^$/;
									}
								}
							}
						} else {
							//  do not preview of file that size = 0
							e.stopImmediatePropagation();
						}
					} else {
						self.dispInlineRegex = /^$/;
					}
					
					self.info.show();
				} else {
					e.stopImmediatePropagation();
				}
			});

			jQuery.each(fm.commands.quicklook.plugins || [], function(i, plugin) {
				if (typeof(plugin) == 'function') {
					new plugin(self);
				}
			});
		}).one('open', function() {
			var dock = Number(fm.storage('previewDocked') || o.docked),
				win;
			if (dockEnabled && dock >= 1) {
				win = self.window;
				self.exec();
				win.trigger('navdockin', { init : true });
				if (dock === 2) {
					win.trigger('close');
				} else {
					self.update(void(0), fm.cwd());
					self.change();
				}
			}
			init = false;
		}).bind('open', function() {
			cwdHash = fm.cwd().hash;
			self.value = fm.cwd();
			// set current volume dispInlineRegex
			try {
				cwdDispInlineRegex = new RegExp(fm.option('dispInlineRegex'), 'i');
			} catch(e) {
				cwdDispInlineRegex = /^$/;
			}
		}).bind('change', function(e) {
			if (e.data && e.data.changed && self.opened()) {
				jQuery.each(e.data.changed, function() {
					if (self.window.data('hash') === this.hash) {
						self.window.data('hash', null);
						self.preview.trigger(evUpdate);
						return false;
					}
				});
			}
		}).bind('navdockresizestart navdockresizestop', function(e) {
			cover[e.type === 'navdockresizestart'? 'show' : 'hide']();
		});
	};
	
	this.getstate = function() {
		return self.opened()? 1 : 0;
	};
	
	this.exec = function() {
		self.closed() && updateOnSel();
		self.enabled() && self.window.trigger(self.opened() ? 'close' : 'open');
		return jQuery.Deferred().resolve();
	};

	this.hideinfo = function() {
		this.info.stop(true, true).hide();
	};

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/quicklook.plugins.js
 */

elFinder.prototype.commands.quicklook.plugins = [
	
	/**
	 * Images preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var mimes   = ['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/x-ms-bmp'],
			preview = ql.preview,
			WebP, flipMime;
		
		// webp support
		WebP = new Image();
		WebP.onload = WebP.onerror = function() {
			if (WebP.height == 2) {
				mimes.push('image/webp');
			}
		};
		WebP.src='data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
		
		// what kind of images we can display
		jQuery.each(navigator.mimeTypes, function(i, o) {
			var mime = o.type;
			
			if (mime.indexOf('image/') === 0 && jQuery.inArray(mime, mimes)) {
				mimes.push(mime);
			} 
		});
			
		preview.on(ql.evUpdate, function(e) {
			var fm   = ql.fm,
				file = e.file,
				showed = false,
				dimreq = null,
				setdim  = function(dim) {
					var rfile = fm.file(file.hash);
					rfile.width = dim[0];
					rfile.height = dim[1];
				},
				show = function() {
					var elm, varelm, memSize, width, height, prop;
					
					dimreq && dimreq.state && dimreq.state() === 'pending' && dimreq.reject();
					if (showed) {
						return;
					}
					showed = true;
					
					elm = img.get(0);
					memSize = file.width && file.height? {w: file.width, h: file.height} : (elm.naturalWidth? null : {w: img.width(), h: img.height()});
				
					memSize && img.removeAttr('width').removeAttr('height');
					
					width  = file.width || elm.naturalWidth || elm.width || img.width();
					height = file.height || elm.naturalHeight || elm.height || img.height();
					if (!file.width || !file.height) {
						setdim([width, height]);
					}
					
					memSize && img.width(memSize.w).height(memSize.h);

					prop = (width/height).toFixed(2);
					preview.on('changesize', function() {
						var pw = parseInt(preview.width()),
							ph = parseInt(preview.height()),
							w, h;
					
						if (prop < (pw/ph).toFixed(2)) {
							h = ph;
							w = Math.floor(h * prop);
						} else {
							w = pw;
							h = Math.floor(w/prop);
						}
						img.width(w).height(h).css('margin-top', h < ph ? Math.floor((ph - h)/2) : 0);
					
					})
					.trigger('changesize');
					
					//show image
					img.fadeIn(100);
				},
				hideInfo = function() {
					loading.remove();
					// hide info/icon
					ql.hideinfo();
				},
				url, img, loading, m;

			if (!flipMime) {
				flipMime = fm.arrayFlip(mimes);
			}
			if (flipMime[file.mime] && ql.dispInlineRegex.test(file.mime)) {
				// this is our file - stop event propagation
				e.stopImmediatePropagation();

				loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));

				url = fm.openUrl(file.hash);
				
				img = jQuery('<img/>')
					.hide()
					.appendTo(preview)
					.on('load', function() {
						hideInfo();
						show();
					})
					.on('error', function() {
						loading.remove();
					})
					.attr('src', url);
				
				if (file.width && file.height) {
					show();
				} else if (file.size > (ql.options.getDimThreshold || 0)) {
					dimreq = fm.request({
						data : {cmd : 'dim', target : file.hash},
						preventDefault : true
					})
					.done(function(data) {
						if (data.dim) {
							var dim = data.dim.split('x');
							file.width = dim[0];
							file.height = dim[1];
							setdim(dim);
							show();
						}
					});
				}
			}
			
		});
	},
	
	/**
	 * PSD(Adobe Photoshop data) preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mimes   = fm.arrayFlip(['image/vnd.adobe.photoshop', 'image/x-photoshop']),
			preview = ql.preview,
			load    = function(url, img, loading) {
				try {
					fm.replaceXhrSend();
					PSD.fromURL(url).then(function(psd) {
						var prop;
						img.attr('src', psd.image.toBase64());
						requestAnimationFrame(function() {
							prop = (img.width()/img.height()).toFixed(2);
							preview.on('changesize', function() {
								var pw = parseInt(preview.width()),
									ph = parseInt(preview.height()),
									w, h;
							
								if (prop < (pw/ph).toFixed(2)) {
									h = ph;
									w = Math.floor(h * prop);
								} else {
									w = pw;
									h = Math.floor(w/prop);
								}
								img.width(w).height(h).css('margin-top', h < ph ? Math.floor((ph - h)/2) : 0);
							}).trigger('changesize');
							
							loading.remove();
							// hide info/icon
							ql.hideinfo();
							//show image
							img.fadeIn(100);
						});
					}, function() {
						loading.remove();
						img.remove();
					});
					fm.restoreXhrSend();
				} catch(e) {
					fm.restoreXhrSend();
					loading.remove();
					img.remove();
				}
			},
			PSD;
		
		preview.on(ql.evUpdate, function(e) {
			var file = e.file,
				url, img, loading, m,
				_define, _require;

			if (mimes[file.mime] && fm.options.cdns.psd && ! fm.UA.ltIE10 && ql.dispInlineRegex.test(file.mime)) {
				// this is our file - stop event propagation
				e.stopImmediatePropagation();

				loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));
				url = fm.openUrl(file.hash);
				if (!fm.isSameOrigin(url)) {
					url = fm.openUrl(file.hash, true);
				}
				img = jQuery('<img/>').hide().appendTo(preview);
				
				if (PSD) {
					load(url, img, loading);
				} else {
					_define = window.define;
					_require = window.require;
					window.require = null;
					window.define = null;
					fm.loadScript(
						[ fm.options.cdns.psd ],
						function() {
							PSD = require('psd');
							_define? (window.define = _define) : (delete window.define);
							_require? (window.require = _require) : (delete window.require);
							load(url, img, loading);
						}
					);
				}
			}
		});
	},
	
	/**
	 * HTML preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mimes   = fm.arrayFlip(['text/html', 'application/xhtml+xml']),
			preview = ql.preview;
			
		preview.on(ql.evUpdate, function(e) {
			var file = e.file, jqxhr, loading;
			
			if (mimes[file.mime] && ql.dispInlineRegex.test(file.mime) && (!ql.options.getSizeMax || file.size <= ql.options.getSizeMax)) {
				e.stopImmediatePropagation();

				loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));

				// stop loading on change file if not loaded yet
				preview.one('change', function() {
					jqxhr.state() == 'pending' && jqxhr.reject();
				}).addClass('elfinder-overflow-auto');
				
				jqxhr = fm.request({
					data           : {cmd : 'get', target : file.hash, conv : 1, _t : file.ts},
					options        : {type: 'get', cache : true},
					preventDefault : true
				})
				.done(function(data) {
					ql.hideinfo();
					var doc = jQuery('<iframe class="elfinder-quicklook-preview-html"/>').appendTo(preview)[0].contentWindow.document;
					doc.open();
					doc.write(data.content);
					doc.close();
				})
				.always(function() {
					loading.remove();
				});
			}
		});
	},
	
	/**
	 * MarkDown preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mimes   = fm.arrayFlip(['text/x-markdown']),
			preview = ql.preview,
			marked  = null,
			show = function(data, loading) {
				ql.hideinfo();
				var doc = jQuery('<iframe class="elfinder-quicklook-preview-html"/>').appendTo(preview)[0].contentWindow.document;
				doc.open();
				doc.write(marked(data.content));
				doc.close();
				loading.remove();
			},
			error = function(loading) {
				marked = false;
				loading.remove();
			};
			
		preview.on(ql.evUpdate, function(e) {
			var file = e.file, jqxhr, loading;
			
			if (mimes[file.mime] && fm.options.cdns.marked && marked !== false && ql.dispInlineRegex.test(file.mime) && (!ql.options.getSizeMax || file.size <= ql.options.getSizeMax)) {
				e.stopImmediatePropagation();

				loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));

				// stop loading on change file if not loaded yet
				preview.one('change', function() {
					jqxhr.state() == 'pending' && jqxhr.reject();
				}).addClass('elfinder-overflow-auto');
				
				jqxhr = fm.request({
					data           : {cmd : 'get', target : file.hash, conv : 1, _t : file.ts},
					options        : {type: 'get', cache : true},
					preventDefault : true
				})
				.done(function(data) {
					if (marked || window.marked) {
						if (!marked) {
							marked = window.marked;
						}
						show(data, loading);
					} else {
						fm.loadScript([fm.options.cdns.marked],
							function(res) { 
								marked = res || window.marked || false;
								delete window.marked;
								if (marked) {
									show(data, loading);
								} else {
									error(loading);
								}
							},
							{
								tryRequire: true,
								error: function() {
									error(loading);
								}
							}
						);
					}
				})
				.fail(function() {
					error(loading);
				});
			}
		});
	},

	/**
	 * PDF/ODT/ODS/ODP preview with ViewerJS
	 * 
	 * @param elFinder.commands.quicklook
	 */
	 function(ql) {
		if (ql.options.viewerjs) {
			var fm      = ql.fm,
				preview = ql.preview,
				opts    = ql.options.viewerjs,
				mimes   = opts.url? fm.arrayFlip(opts.mimes || []) : [];

			if (opts.url) {
				preview.on('update', function(e) {
					var win  = ql.window,
						file = e.file, node, loading;

					if (mimes[file.mime]) {
						var url = fm.openUrl(file.hash);
						if (url && fm.isSameOrigin(url)) {
							e.stopImmediatePropagation();

							loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));

							node = jQuery('<iframe class="elfinder-quicklook-preview-iframe"/>')
								.css('background-color', 'transparent')
								.on('load', function() {
									ql.hideinfo();
									loading.remove();
									node.css('background-color', '#fff');
								})
								.on('error', function() {
									loading.remove();
									node.remove();
								})
								.appendTo(preview)
								.attr('src', opts.url + '#' + url);

							preview.one('change', function() {
								loading.remove();
								node.off('load').remove();
							});
						}
					}
				});
			}
		}
	},

	/**
	 * PDF preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mime    = 'application/pdf',
			preview = ql.preview,
			active  = false,
			urlhash = '',
			firefox, toolbar;
			
		if ((fm.UA.Safari && fm.OS === 'mac' && !fm.UA.iOS) || fm.UA.IE || fm.UA.Firefox) {
			active = true;
		} else {
			jQuery.each(navigator.plugins, function(i, plugins) {
				jQuery.each(plugins, function(i, plugin) {
					if (plugin.type === mime) {
						return !(active = true);
					}
				});
			});
		}

		if (active) {
			if (typeof ql.options.pdfToolbar !== 'undefined' && !ql.options.pdfToolbar) {
				urlhash = '#toolbar=0';
			}
			preview.on(ql.evUpdate, function(e) {
				var file = e.file;
				
				if (active && file.mime === mime && ql.dispInlineRegex.test(file.mime)) {
					e.stopImmediatePropagation();
					ql.hideinfo();
					ql.cover.addClass('elfinder-quicklook-coverbg');
					jQuery('<object class="elfinder-quicklook-preview-pdf" data="'+fm.openUrl(file.hash)+urlhash+'" type="application/pdf" />')
						.on('error', function(e) {
							active = false;
							ql.update(void(0), fm.cwd());
							ql.update(void(0), file);
						})
						.appendTo(preview);
				}
				
			});
		}
	},
	
	/**
	 * Flash preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mime    = 'application/x-shockwave-flash',
			preview = ql.preview,
			active  = false;

		jQuery.each(navigator.plugins, function(i, plugins) {
			jQuery.each(plugins, function(i, plugin) {
				if (plugin.type === mime) {
					return !(active = true);
				}
			});
		});
		
		active && preview.on(ql.evUpdate, function(e) {
			var file = e.file,
				node;
				
			if (file.mime === mime && ql.dispInlineRegex.test(file.mime)) {
				e.stopImmediatePropagation();
				ql.hideinfo();
				node = jQuery('<embed class="elfinder-quicklook-preview-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" src="'+fm.openUrl(file.hash)+'" quality="high" type="application/x-shockwave-flash" wmode="transparent" />')
					.appendTo(preview);
			}
		});
	},
	
	/**
	 * HTML5 audio preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm       = ql.fm,
			preview  = ql.preview,
			mimes    = {
				'audio/mpeg'    : 'mp3',
				'audio/mpeg3'   : 'mp3',
				'audio/mp3'     : 'mp3',
				'audio/x-mpeg3' : 'mp3',
				'audio/x-mp3'   : 'mp3',
				'audio/x-wav'   : 'wav',
				'audio/wav'     : 'wav',
				'audio/x-m4a'   : 'm4a',
				'audio/aac'     : 'm4a',
				'audio/mp4'     : 'm4a',
				'audio/x-mp4'   : 'm4a',
				'audio/ogg'     : 'ogg',
				'audio/webm'    : 'webm',
				'audio/flac'    : 'flac',
				'audio/x-flac'  : 'flac',
				'audio/amr'     : 'amr'
			},
			node, curHash,
			win  = ql.window,
			navi = ql.navbar,
			AMR, autoplay,
			controlsList = typeof ql.options.mediaControlsList === 'string' && ql.options.mediaControlsList? ' controlsList="' + fm.escape(ql.options.mediaControlsList) + '"' : '',
			setNavi = function() {
				navi.css('bottom', win.hasClass('elfinder-quicklook-fullscreen')? '50px' : '');
			},
			getNode = function(src, hash) {
				return jQuery('<audio class="elfinder-quicklook-preview-audio ui-front" controls' + controlsList + ' preload="auto" autobuffer><source src="'+src+'" /></audio>')
					.on('change', function(e) {
						// Firefox fire change event on seek or volume change
						e.stopPropagation();
					})
					.on('error', function(e) {
						node && node.data('hash') === hash && reset();
					})
					.data('hash', hash)
					.appendTo(preview);
			},
			amrToWavUrl = function(hash) {
				var dfd = jQuery.Deferred(),
					loader = jQuery.Deferred().done(function() {
						fm.getContents(hash).done(function(data) {
							try {
								var buffer = AMR.toWAV(new Uint8Array(data));
								if (buffer) {
									dfd.resolve(URL.createObjectURL(new Blob([buffer], { type: 'audio/x-wav' })));
								} else {
									dfd.reject();
								}
							} catch(e) {
								dfd.reject();
							}
						}).fail(function() {
							dfd.reject();
						});
					}).fail(function() {
						AMR = false;
						dfd.reject();
					}),
					_AMR;
				if (window.TextEncoder && window.URL && URL.createObjectURL && typeof AMR === 'undefined') {
					// previous window.AMR
					_AMR = window.AMR;
					delete window.AMR;
					fm.loadScript(
						[ fm.options.cdns.amr ],
						function() { 
							AMR = window.AMR? window.AMR : false;
							// restore previous window.AMR
							window.AMR = _AMR;
							loader[AMR? 'resolve':'reject']();
						},
						{
							error: function() {
								loader.reject();
							}
						}
					);
				} else {
					loader[AMR? 'resolve':'reject']();
				}
				return dfd;
			},
			play = function(player) {
				var hash = node.data('hash'),
					playPromise;
				autoplay && (playPromise = player.play());
				// uses "playPromise['catch']" instead "playPromise.catch" to support Old IE
				if (playPromise && playPromise['catch']) {
					playPromise['catch'](function(e) {
						if (!player.paused) {
							node && node.data('hash') === hash && reset();
						}
					});
				}
			},
			reset = function() {
				if (node && node.parent().length) {
					var elm = node[0],
						url = node.children('source').attr('src');
					win.off('viewchange.audio');
					try {
						elm.pause();
						node.empty();
						if (url.match(/^blob:/)) {
							URL.revokeObjectURL(url);
						}
						elm.src = '';
						elm.load();
					} catch(e) {}
					node.remove();
					node = null;
				}
			};

		preview.on(ql.evUpdate, function(e) {
			var file = e.file,
				type = mimes[file.mime],
				html5, srcUrl;

			if (mimes[file.mime] && ql.dispInlineRegex.test(file.mime) && ((html5 = ql.support.audio[type]) || (type === 'amr'))) {
				autoplay = ql.autoPlay();
				curHash = file.hash;
				srcUrl = html5? fm.openUrl(curHash) : '';
				if (!html5) {
					if (fm.options.cdns.amr && type === 'amr' && AMR !== false) {
						e.stopImmediatePropagation();
						node = getNode(srcUrl, curHash);
						amrToWavUrl(file.hash).done(function(url) {
							if (curHash === file.hash) {
								var elm = node[0];
								try {
									node.children('source').attr('src', url);
									elm.pause();
									elm.load();
									play(elm);
									win.on('viewchange.audio', setNavi);
									setNavi();
								} catch(e) {
									URL.revokeObjectURL(url);
									node.remove();
								}
							} else {
								URL.revokeObjectURL(url);
							}
						}).fail(function() {
							node.remove();
						});
					}
				} else {
					e.stopImmediatePropagation();
					node = getNode(srcUrl, curHash);
					play(node[0]);
					win.on('viewchange.audio', setNavi);
					setNavi();
				}
			}
		}).on('change', reset);
	},
	
	/**
	 * HTML5 video preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm       = ql.fm,
			preview  = ql.preview,
			mimes    = {
				'video/mp4'       : 'mp4',
				'video/x-m4v'     : 'mp4',
				'video/quicktime' : 'mp4',
				'video/ogg'       : 'ogg',
				'application/ogg' : 'ogg',
				'video/webm'      : 'webm',
				'video/x-matroska': 'mkv',
				'video/3gpp'      : '3gp',
				'application/vnd.apple.mpegurl' : 'm3u8',
				'application/x-mpegurl' : 'm3u8',
				'application/dash+xml'  : 'mpd',
				'video/x-flv'     : 'flv'
			},
			node,
			win  = ql.window,
			navi = ql.navbar,
			cHls, cDash, pDash, cFlv, autoplay, tm,
			controlsList = typeof ql.options.mediaControlsList === 'string' && ql.options.mediaControlsList? ' controlsList="' + fm.escape(ql.options.mediaControlsList) + '"' : '',
			setNavi = function() {
				if (fm.UA.iOS) {
					if (win.hasClass('elfinder-quicklook-fullscreen')) {
						preview.css('height', '-webkit-calc(100% - 50px)');
						navi._show();
					} else {
						preview.css('height', '');
					}
				} else {
					navi.css('bottom', win.hasClass('elfinder-quicklook-fullscreen')? '50px' : '');
				}
			},
			render = function(file, opts) {
				var errTm = function(e) {
						if (err > 1) {
							tm && clearTimeout(tm);
							tm = setTimeout(function() {
								!canPlay && reset(true);
							}, 800);
						}
					},
					err = 0, 
					canPlay;
				//reset();
				pDash = null;
				opts = opts || {};
				ql.hideinfo();
				node = jQuery('<video class="elfinder-quicklook-preview-video" controls' + controlsList + ' preload="auto" autobuffer playsinline>'
						+'</video>')
					.on('change', function(e) {
						// Firefox fire change event on seek or volume change
						e.stopPropagation();
					})
					.on('timeupdate progress', errTm)
					.on('canplay', function() {
						canPlay = true;
					})
					.data('hash', file.hash);
				// can not handling error event with jQuery `on` event handler
				node[0].addEventListener('error', function(e) {
					if (opts.src && fm.convAbsUrl(opts.src) === fm.convAbsUrl(e.target.src)) {
						++err;
						errTm();
					}
				}, true);

				if (opts.src) {
					node.append('<source src="'+opts.src+'" type="'+file.mime+'"/><source src="'+opts.src+'"/>');
				}
				
				node.appendTo(preview);

				win.on('viewchange.video', setNavi);
				setNavi();
			},
			loadHls = function(file) {
				var hls;
				render(file);
				hls = new cHls();
				hls.loadSource(fm.openUrl(file.hash));
				hls.attachMedia(node[0]);
				if (autoplay) {
					hls.on(cHls.Events.MANIFEST_PARSED, function() {
						play(node[0]);
					});
				}
			},
			loadDash = function(file) {
				render(file);
				pDash = window.dashjs.MediaPlayer().create();
				pDash.getDebug().setLogToBrowserConsole(false);
				pDash.initialize(node[0], fm.openUrl(file.hash), autoplay);
				pDash.on('error', function(e) {
					reset(true);
				});
			},
			loadFlv = function(file) {
				if (!cFlv.isSupported()) {
					cFlv = false;
					return;
				}
				var player = cFlv.createPlayer({
					type: 'flv',
					url: fm.openUrl(file.hash)
				});
				render(file);
				player.on(cFlv.Events.ERROR, function() {
					player.destroy();
					reset(true);
				});
				player.attachMediaElement(node[0]);
				player.load();
				play(player);
			},
			play = function(player) {
				var hash = node.data('hash'),
					playPromise;
				autoplay && (playPromise = player.play());
				// uses "playPromise['catch']" instead "playPromise.catch" to support Old IE
				if (playPromise && playPromise['catch']) {
					playPromise['catch'](function(e) {
						if (!player.paused) {
							node && node.data('hash') === hash && reset(true);
						}
					});
				}
			},
			reset = function(showInfo) {
				tm && clearTimeout(tm);
				if (node && node.parent().length) {
					var elm = node[0];
					win.off('viewchange.video');
					pDash && pDash.reset();
					try {
						elm.pause();
						node.empty();
						elm.src = '';
						elm.load();
					} catch(e) {}
					node.remove();
					node = null;
				}
				showInfo && ql.info.show();
			};

		preview.on(ql.evUpdate, function(e) {
			var file = e.file,
				mime = file.mime.toLowerCase(),
				type = mimes[mime],
				stock, playPromise;
			
			if (mimes[mime] && ql.dispInlineRegex.test(file.mime) && (((type === 'm3u8' || (type === 'mpd' && !fm.UA.iOS) || type === 'flv') && !fm.UA.ltIE10) || ql.support.video[type])) {
				autoplay = ql.autoPlay();
				if (ql.support.video[type] && (type !== 'm3u8' || fm.UA.Safari)) {
					e.stopImmediatePropagation();
					render(file, { src: fm.openUrl(file.hash) });
					play(node[0]);
				} else {
					if (cHls !== false && fm.options.cdns.hls && type === 'm3u8') {
						e.stopImmediatePropagation();
						if (cHls) {
							loadHls(file);
						} else {
							stock = window.Hls;
							delete window.Hls;
							fm.loadScript(
								[ fm.options.cdns.hls ],
								function(res) { 
									cHls = res || window.Hls || false;
									window.Hls = stock;
									cHls && loadHls(file);
								},
								{
									tryRequire: true,
									error : function() {
										cHls = false;
									}
								}
							);
						}
					} else if (cDash !== false && fm.options.cdns.dash && type === 'mpd') {
						e.stopImmediatePropagation();
						if (cDash) {
							loadDash(file);
						} else {
							fm.loadScript(
								[ fm.options.cdns.dash ],
								function() {
									// dashjs require window.dashjs in global scope
									cDash = window.dashjs? true : false;
									cDash && loadDash(file);
								},
								{
									tryRequire: true,
									error : function() {
										cDash = false;
									}
								}
							);
						}
					} else if (cFlv !== false && fm.options.cdns.flv && type === 'flv') {
						e.stopImmediatePropagation();
						if (cFlv) {
							loadFlv(file);
						} else {
							stock = window.flvjs;
							delete window.flvjs;
							fm.loadScript(
								[ fm.options.cdns.flv ],
								function(res) { 
									cFlv = res || window.flvjs || false;
									window.flvjs = stock;
									cFlv && loadFlv(file);
								},
								{
									tryRequire: true,
									error : function() {
										cFlv = false;
									}
								}
							);
						}
					}
				}
			}
		}).on('change', reset);
	},
	
	/**
	 * Audio/video preview plugin using browser plugins
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var preview = ql.preview,
			mimes   = [],
			node,
			win  = ql.window,
			navi = ql.navbar;
			
		jQuery.each(navigator.plugins, function(i, plugins) {
			jQuery.each(plugins, function(i, plugin) {
				(plugin.type.indexOf('audio/') === 0 || plugin.type.indexOf('video/') === 0) && mimes.push(plugin.type);
			});
		});
		mimes = ql.fm.arrayFlip(mimes);
		
		preview.on(ql.evUpdate, function(e) {
			var file  = e.file,
				mime  = file.mime,
				video,
				setNavi = function() {
					navi.css('bottom', win.hasClass('elfinder-quicklook-fullscreen')? '50px' : '');
				};
			
			if (mimes[file.mime] && ql.dispInlineRegex.test(file.mime)) {
				e.stopImmediatePropagation();
				(video = mime.indexOf('video/') === 0) && ql.hideinfo();
				node = jQuery('<embed src="'+ql.fm.openUrl(file.hash)+'" type="'+mime+'" class="elfinder-quicklook-preview-'+(video ? 'video' : 'audio')+'"/>')
					.appendTo(preview);
				
				win.on('viewchange.embed', setNavi);
				setNavi();
			}
		}).on('change', function() {
			if (node && node.parent().length) {
				win.off('viewchange.embed');
				node.remove();
				node= null;
			}
		});
		
	},

	/**
	 * Archive(zip|gzip|tar) preview plugin using https://github.com/imaya/zlib.js
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mimes   = fm.arrayFlip(['application/zip', 'application/x-gzip', 'application/x-tar']),
			preview = ql.preview,
			unzipFiles = function() {
				/** @type {Array.<string>} */
				var filenameList = [];
				/** @type {number} */
				var i;
				/** @type {number} */
				var il;
				/** @type {Array.<Zlib.Unzip.FileHeader>} */
				var fileHeaderList;
				// need check this.Y when update cdns.zlibUnzip
				this.Y();
				fileHeaderList = this.i;
				for (i = 0, il = fileHeaderList.length; i < il; ++i) {
					// need check fileHeaderList[i].J when update cdns.zlibUnzip
					filenameList[i] = fileHeaderList[i].filename + (fileHeaderList[i].J? ' (' + fm.formatSize(fileHeaderList[i].J) + ')' : '');
				}
				return filenameList;
			},
			tarFiles = function(tar) {
				var filenames = [],
					tarlen = tar.length,
					offset = 0,
					toStr = function(arr) {
						return String.fromCharCode.apply(null, arr).replace(/\0+$/, '');
					},
					h, name, prefix, size, dbs;
				while (offset < tarlen && tar[offset] !== 0) {
					h = tar.subarray(offset, offset + 512);
					name = toStr(h.subarray(0, 100));
					if (prefix = toStr(h.subarray(345, 500))) {
						name = prefix + name;
					}
					size = parseInt(toStr(h.subarray(124, 136)), 8);
					dbs = Math.ceil(size / 512) * 512;
					if (name === '././@LongLink') {
						name = toStr(tar.subarray(offset + 512, offset + 512 + dbs));
					}
					(name !== 'pax_global_header') && filenames.push(name + (size? ' (' + fm.formatSize(size) + ')': ''));
					offset = offset + 512 + dbs;
				}
				return filenames;
			},
			Zlib;

		if (window.Uint8Array && window.DataView && fm.options.cdns.zlibUnzip && fm.options.cdns.zlibGunzip) {
			preview.on(ql.evUpdate, function(e) {
				var file  = e.file,
					isTar = (file.mime === 'application/x-tar');
				if (mimes[file.mime] && (
						isTar
						|| ((typeof Zlib === 'undefined' || Zlib) && (file.mime === 'application/zip' || file.mime === 'application/x-gzip'))
					)) {
					var jqxhr, loading, url,
						req = function() {
							url = fm.openUrl(file.hash);
							if (!fm.isSameOrigin(url)) {
								url = fm.openUrl(file.hash, true);
							}
							jqxhr = fm.request({
								data    : {cmd : 'get'},
								options : {
									url: url,
									type: 'get',
									cache : true,
									dataType : 'binary',
									responseType :'arraybuffer',
									processData: false
								}
							})
							.fail(function() {
								loading.remove();
							})
							.done(function(data) {
								var unzip, filenames;
								try {
									if (file.mime === 'application/zip') {
										unzip = new Zlib.Unzip(new Uint8Array(data));
										//filenames = unzip.getFilenames();
										filenames = unzipFiles.call(unzip);
									} else if (file.mime === 'application/x-gzip') {
										unzip = new Zlib.Gunzip(new Uint8Array(data));
										filenames = tarFiles(unzip.decompress());
									} else if (file.mime === 'application/x-tar') {
										filenames = tarFiles(new Uint8Array(data));
									}
									makeList(filenames);
								} catch (e) {
									loading.remove();
									fm.debug('error', e);
								}
							});
						},
						makeList = function(filenames) {
							var header, doc;
							if (filenames && filenames.length) {
								filenames = jQuery.map(filenames, function(str) {
									return fm.decodeRawString(str);
								});
								filenames.sort();
								loading.remove();
								header = '<strong>'+fm.escape(file.mime)+'</strong> ('+fm.formatSize(file.size)+')'+'<hr/>';
								doc = jQuery('<div class="elfinder-quicklook-preview-archive-wrapper">'+header+'<pre class="elfinder-quicklook-preview-text">'+fm.escape(filenames.join("\n"))+'</pre></div>')
									.on('touchstart', function(e) {
										if (jQuery(this)['scroll' + (fm.direction === 'ltr'? 'Right' : 'Left')]() > 5) {
											e.originalEvent._preventSwipeX = true;
										}
									})
									.appendTo(preview);
								ql.hideinfo();
							}
						},
						_Zlib;

					// this is our file - stop event propagation
					e.stopImmediatePropagation();
					
					loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));
					
					// stop loading on change file if not loaded yet
					preview.one('change', function() {
						jqxhr.state() === 'pending' && jqxhr.reject();
						loading.remove();
					});
					
					if (Zlib) {
						req();
					} else {
						if (window.Zlib) {
							_Zlib = window.Zlib;
							delete window.Zlib;
						}
						fm.loadScript(
							[ fm.options.cdns.zlibUnzip, fm.options.cdns.zlibGunzip ],
							function() {
								if (window.Zlib && (Zlib = window.Zlib)) {
									if (_Zlib) {
										window.Zlib = _Zlib;
									} else {
										delete window.Zlib;
									}
									req();
								} else {
									error();
								}
							}
						);
					}
				}
			});
		}
	},

	/**
	 * RAR Archive preview plugin using https://github.com/43081j/rar.js
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mimes   = fm.arrayFlip(['application/x-rar']),
			preview = ql.preview,
			RAR;

		if (window.DataView) {
			preview.on(ql.evUpdate, function(e) {
				var file = e.file;
				if (mimes[file.mime] && fm.options.cdns.rar && RAR !== false) {
					var loading, url, archive, abort,
						getList = function(url) {
							if (abort) {
								loading.remove();
								return;
							}
							try {
								archive = RAR({
									file: url,
									type: 2,
									xhrHeaders: fm.customHeaders,
									xhrFields: fm.xhrFields
								}, function(err) {
									loading.remove();
									var filenames = [],
										header, doc;
									if (abort || err) {
										// An error occurred (not a rar, read error, etc)
										err && fm.debug('error', err);
										return;
									}
									jQuery.each(archive.entries, function() {
										filenames.push(this.path + (this.size? ' (' + fm.formatSize(this.size) + ')' : ''));
									});
									if (filenames.length) {
										filenames = jQuery.map(filenames, function(str) {
											return fm.decodeRawString(str);
										});
										filenames.sort();
										header = '<strong>'+fm.escape(file.mime)+'</strong> ('+fm.formatSize(file.size)+')'+'<hr/>';
										doc = jQuery('<div class="elfinder-quicklook-preview-archive-wrapper">'+header+'<pre class="elfinder-quicklook-preview-text">'+fm.escape(filenames.join("\n"))+'</pre></div>')
											.on('touchstart', function(e) {
												if (jQuery(this)['scroll' + (fm.direction === 'ltr'? 'Right' : 'Left')]() > 5) {
													e.originalEvent._preventSwipeX = true;
												}
											})
											.appendTo(preview);
										ql.hideinfo();
									}
								});
							} catch(e) {
								loading.remove();
							}
						},
						error = function() {
							RAR = false;
							loading.remove();
						},
						_RAR;

					// this is our file - stop event propagation
					e.stopImmediatePropagation();
					
					loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));
					
					// stop loading on change file if not loaded yet
					preview.one('change', function() {
						archive && (archive.abort = true);
						loading.remove();
						abort = true;
					});
					
					url = fm.openUrl(file.hash);
					if (!fm.isSameOrigin(url)) {
						url = fm.openUrl(file.hash, true);
					}
					if (RAR) {
						getList(url);
					} else {
						if (window.RarArchive) {
							_RAR = window.RarArchive;
							delete window.RarArchive;
						}
						fm.loadScript(
							[ fm.options.cdns.rar ],
							function() {
								if (fm.hasRequire) {
									require(['rar'], function(RarArchive) {
										RAR = RarArchive;
										getList(url);
									}, error);
								} else {
									if (RAR = window.RarArchive) {
										if (_RAR) {
											window.RarArchive = _RAR;
										} else {
											delete window.RarArchive;
										}
										getList(url);
									} else {
										error();
									}
								}
							},
							{
								tryRequire: true,
								error : error
							}
						);
					}
				}
			});
		}
	},

	/**
	 * CAD-Files and 3D-Models online viewer on sharecad.org
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mimes   = fm.arrayFlip(ql.options.sharecadMimes || []),
			preview = ql.preview,
			win     = ql.window,
			node;
			
		if (ql.options.sharecadMimes.length) {
			ql.addIntegration({
				title: 'ShareCAD.org CAD and 3D-Models viewer',
				link: 'https://sharecad.org/DWGOnlinePlugin'
			});
		}

		preview.on(ql.evUpdate, function(e) {
			var file = e.file;
			if (mimes[file.mime.toLowerCase()] && !fm.option('onetimeUrl', file.hash)) {
				var win     = ql.window,
					loading, url;
				
				e.stopImmediatePropagation();
				if (file.url == '1') {
					preview.hide();
					jQuery('<div class="elfinder-quicklook-info-data"><button class="elfinder-info-button">'+fm.i18n('getLink')+'</button></div>').appendTo(ql.info.find('.elfinder-quicklook-info'))
					.on('click', function() {
						var self = jQuery(this);
						self.html('<span class="elfinder-spinner">');
						fm.request({
							data : {cmd : 'url', target : file.hash},
							preventDefault : true
						})
						.always(function() {
							self.html('');
						})
						.done(function(data) {
							var rfile = fm.file(file.hash);
							file.url = rfile.url = data.url || '';
							if (file.url) {
								preview.trigger({
									type: ql.evUpdate,
									file: file,
									forceUpdate: true
								});
							}
						});
					});
				}
				if (file.url !== '' && file.url != '1') {
					preview.one('change', function() {
						loading.remove();
						node.off('load').remove();
						node = null;
					}).addClass('elfinder-overflow-auto');
					
					loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));
					
					url = fm.convAbsUrl(fm.url(file.hash));
					node = jQuery('<iframe class="elfinder-quicklook-preview-iframe" scrolling="no"/>')
						.css('background-color', 'transparent')
						.appendTo(preview)
						.on('load', function() {
							ql.hideinfo();
							loading.remove();
							ql.preview.after(ql.info);
							jQuery(this).css('background-color', '#fff').show();
						})
						.on('error', function() {
							loading.remove();
							ql.preview.after(ql.info);
						})
						.attr('src', '//sharecad.org/cadframe/load?url=' + encodeURIComponent(url));
					
					ql.info.after(ql.preview);
				}
			}
			
		});
	},

	/**
	 * KML preview with GoogleMaps API
	 *
	 * @param elFinder.commands.quicklook
	 */
	function(ql) {
				var fm      = ql.fm,
			mimes   = {
				'application/vnd.google-earth.kml+xml' : true,
				'application/vnd.google-earth.kmz' : true
			},
			preview = ql.preview,
			gMaps, loadMap, wGmfail, fail, mapScr;

		if (ql.options.googleMapsApiKey) {
			ql.addIntegration({
				title: 'Google Maps',
				link: 'https://www.google.com/intl/' + fm.lang.replace('_', '-') + '/help/terms_maps.html'
			});
			gMaps = (window.google && google.maps);
			// start load maps
			loadMap = function(file, node) {
				var mapsOpts = ql.options.googleMapsOpts.maps;
				fm.forExternalUrl(file.hash).done(function(url) {
					if (url) {
						try {
							new gMaps.KmlLayer(url, Object.assign({
								map: new gMaps.Map(node.get(0), mapsOpts)
							}, ql.options.googleMapsOpts.kml));
							ql.hideinfo();
						} catch(e) {
							fail();
						}
					} else {
						fail();
					}
				});
			};
			// keep stored error handler if exists
			wGmfail = window.gm_authFailure;
			// on error function
			fail = function() {
				mapScr = null;
			};
			// API script url
			mapScr = 'https://maps.googleapis.com/maps/api/js?key=' + ql.options.googleMapsApiKey;
			// error handler
			window.gm_authFailure = function() {
				fail();
				wGmfail && wGmfail();
			};

			preview.on(ql.evUpdate, function(e) {
				var file = e.file;
				if (mapScr && mimes[file.mime.toLowerCase()]) {
					var win     = ql.window,
						getLink = (file.url == '1' && !fm.option('onetimeUrl', file.hash)),
						loading, url, node;
				
					e.stopImmediatePropagation();
					if (getLink) {
						preview.hide();
						jQuery('<div class="elfinder-quicklook-info-data"><button class="elfinder-info-button">'+fm.i18n('getLink')+'</button></div>').appendTo(ql.info.find('.elfinder-quicklook-info'))
						.on('click', function() {
							var self = jQuery(this);
							self.html('<span class="elfinder-spinner">');
							fm.request({
								data : {cmd : 'url', target : file.hash},
								preventDefault : true
							})
							.always(function() {
								self.html('');
							})
							.done(function(data) {
								var rfile = fm.file(file.hash);
								file.url = rfile.url = data.url || '';
								if (file.url) {
									preview.trigger({
										type: ql.evUpdate,
										file: file,
										forceUpdate: true
									});
								}
							});
						});
					}
					if (file.url !== '' && !getLink) {
						node = jQuery('<div style="width:100%;height:100%;"/>').appendTo(preview);
						preview.one('change', function() {
							node.remove();
							node = null;
						});
						if (!gMaps) {
							fm.loadScript([mapScr], function() {
								gMaps = window.google && google.maps;
								gMaps && loadMap(file, node);
							});
						} else {
							loadMap(file, node);
						}
					}
				}
			});
		}
	},

	/**
	 * Any supported files preview plugin using (Google docs | MS Office) online viewer
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			mimes   = Object.assign(fm.arrayFlip(ql.options.googleDocsMimes || [], 'g'), fm.arrayFlip(ql.options.officeOnlineMimes || [], 'm')),
			preview = ql.preview,
			win     = ql.window,
			navi    = ql.navbar,
			urls    = {
				g: 'docs.google.com/gview?embedded=true&url=',
				m: 'view.officeapps.live.com/op/embed.aspx?wdStartOn=0&src='
			},
			navBottom = {
				g: '56px',
				m: '24px'
			},
			mLimits = {
				xls  : 5242880, // 5MB
				xlsb : 5242880,
				xlsx : 5242880,
				xlsm : 5242880,
				other: 10485760 // 10MB
			},
			node, enable;
		
		if (ql.options.googleDocsMimes.length) {
			enable = true;
			ql.addIntegration({
				title: 'Google Docs Viewer',
				link: 'https://docs.google.com/'
			});
		}
		if (ql.options.officeOnlineMimes.length) {
			enable = true;
			ql.addIntegration({
				title: 'MS Online Doc Viewer',
				link: 'https://products.office.com/office-online/view-office-documents-online'
			});
		}

		if (enable) {
			preview.on(ql.evUpdate, function(e) {
				var file = e.file,
					type;
				// 25MB is maximum filesize of Google Docs prevew
				if (file.size <= 26214400 && (type = mimes[file.mime])) {
					var win     = ql.window,
						setNavi = function() {
							navi.css('bottom', win.hasClass('elfinder-quicklook-fullscreen')? navBottom[type] : '');
						},
						ext     = fm.mimeTypes[file.mime],
						getLink = (file.url == '1' && !fm.option('onetimeUrl', file.hash)),
						loading, url;
					
					if (type === 'm') {
						if ((mLimits[ext] && file.size > mLimits[ext]) || file.size > mLimits.other) {
							type = 'g';
						}
					}
					if (getLink) {
						preview.hide();
						jQuery('<div class="elfinder-quicklook-info-data"><button class="elfinder-info-button">'+fm.i18n('getLink')+'</button></div>').appendTo(ql.info.find('.elfinder-quicklook-info'))
						.on('click', function() {
							var self = jQuery(this);
							self.html('<span class="elfinder-spinner">');
							fm.request({
								data : {cmd : 'url', target : file.hash},
								preventDefault : true
							})
							.always(function() {
								self.html('');
							})
							.done(function(data) {
								var rfile = fm.file(file.hash);
								file.url = rfile.url = data.url || '';
								if (file.url) {
									preview.trigger({
										type: ql.evUpdate,
										file: file,
										forceUpdate: true
									});
								}
							});
						});
					}
					if (file.url !== '' && !getLink) {
						e.stopImmediatePropagation();
						preview.one('change', function() {
							win.off('viewchange.googledocs');
							loading.remove();
							node.off('load').remove();
							node = null;
						}).addClass('elfinder-overflow-auto');
						
						loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));
						
						node = jQuery('<iframe class="elfinder-quicklook-preview-iframe"/>')
							.css('background-color', 'transparent')
							.appendTo(preview);

						fm.forExternalUrl(file.hash).done(function(url) {
							if (url) {
								if (file.ts) {
									url += (url.match(/\?/)? '&' : '?') + '_t=' + file.ts;
								}
								node.on('load', function() {
									ql.hideinfo();
									loading.remove();
									ql.preview.after(ql.info);
									jQuery(this).css('background-color', '#fff').show();
								})
								.on('error', function() {
									loading.remove();
									ql.preview.after(ql.info);
								}).attr('src', 'https://' + urls[type] + encodeURIComponent(url));
							} else {
								loading.remove();
								node.remove();
							}
						});

						win.on('viewchange.googledocs', setNavi);
						setNavi();
						ql.info.after(ql.preview);
					}
				}
				
			});
		}
	},

	/**
	 * Texts preview plugin
	 *
	 * @param elFinder.commands.quicklook
	 **/
	function(ql) {
				var fm      = ql.fm,
			preview = ql.preview,
			textMaxlen = parseInt(ql.options.textMaxlen) || 2000,
			prettify = function() {
				if (fm.options.cdns.prettify) {
					fm.loadScript([fm.options.cdns.prettify + (fm.options.cdns.prettify.match(/\?/)? '&' : '?') + 'autorun=false']);
					prettify = function() { return true; };
				} else {
					prettify = function() { return false; };
				}
			},
			PRcheck = function(node, cnt) {
				if (prettify()) {
					if (typeof window.PR === 'undefined' && cnt--) {
						setTimeout(function() { PRcheck(node, cnt); }, 100);
					} else {
						if (typeof window.PR === 'object') {
							node.css('cursor', 'wait');
							requestAnimationFrame(function() {
								PR.prettyPrint && PR.prettyPrint(null, node.get(0));
								node.css('cursor', '');
							});
						} else {
							prettify = function() { return false; };
						}
					}
				}
			};
		
		preview.on(ql.evUpdate, function(e) {
			var file = e.file,
				mime = file.mime,
				jqxhr, loading;
			
			if (fm.mimeIsText(file.mime) && (!ql.options.getSizeMax || file.size <= ql.options.getSizeMax)) {
				e.stopImmediatePropagation();
				
				(typeof window.PR === 'undefined') && prettify();
				
				loading = jQuery('<div class="elfinder-quicklook-info-data"><span class="elfinder-spinner-text">'+fm.i18n('nowLoading')+'</span><span class="elfinder-spinner"/></div>').appendTo(ql.info.find('.elfinder-quicklook-info'));

				// stop loading on change file if not loadin yet
				preview.one('change', function() {
					jqxhr.state() == 'pending' && jqxhr.reject();
				});
				
				jqxhr = fm.request({
					data           : {cmd : 'get', target : file.hash, conv : 1, _t : file.ts},
					options        : {type: 'get', cache : true},
					preventDefault : true
				})
				.done(function(data) {
					var reg = new RegExp('^(data:'+file.mime.replace(/([.+])/g, '\\$1')+';base64,)', 'i'),
						text = data.content,
						part, more, node, m;
					ql.hideinfo();
					if (window.atob && (m = text.match(reg))) {
						text = atob(text.substr(m[1].length));
					}
					
					more = text.length - textMaxlen;
					if (more > 100) {
						part = text.substr(0, textMaxlen) + '...';
					} else {
						more = 0;
					}
					
					node = jQuery('<div class="elfinder-quicklook-preview-text-wrapper"><pre class="elfinder-quicklook-preview-text prettyprint"></pre></div>');
					
					if (more) {
						node.append(jQuery('<div class="elfinder-quicklook-preview-charsleft"><hr/><span>' + fm.i18n('charsLeft', fm.toLocaleString(more)) + '</span></div>')
							.on('click', function() {
								var top = node.scrollTop();
								jQuery(this).remove();
								node.children('pre').removeClass('prettyprinted').text(text).scrollTop(top);
								PRcheck(node, 100);
							})
						);
					}
					node.children('pre').text(part || text);
					
					node.on('touchstart', function(e) {
						if (jQuery(this)['scroll' + (fm.direction === 'ltr'? 'Right' : 'Left')]() > 5) {
							e.originalEvent._preventSwipeX = true;
						}
					}).appendTo(preview);
					
					PRcheck(node, 100);
				})
				.always(function() {
					loading.remove();
				});
			}
		});
	}
];


/*
 * File: /js/commands/reload.js
 */

/**
 * @class  elFinder command "reload"
 * Sync files and folders
 *
 * @author Dmitry (dio) Levashov
 **/
(elFinder.prototype.commands.reload = function() {
	"use strict";
	var self   = this,
		search = false;
	
	this.alwaysEnabled = true;
	this.updateOnSelect = true;
	
	this.shortcuts = [{
		pattern     : 'ctrl+shift+r f5'
	}];
	
	this.getstate = function() {
		return 0;
	};
	
	this.init = function() {
		this.fm.bind('search searchend', function() {
			search = this.type == 'search';
		});
	};
	
	this.fm.bind('contextmenu', function(){
		var fm = self.fm;
		if (fm.options.sync >= 1000) {
			self.extra = {
				icon: 'accept',
				node: jQuery('<span/>')
					.attr({title: fm.i18n('autoSync')})
					.on('click touchstart', function(e){
						if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
							return;
						}
						e.stopPropagation();
						e.preventDefault();
						jQuery(this).parent()
							.toggleClass('ui-state-disabled', fm.options.syncStart)
							.parent().removeClass('ui-state-hover');
						fm.options.syncStart = !fm.options.syncStart;
						fm.autoSync(fm.options.syncStart? null : 'stop');
					}).on('ready', function(){
						jQuery(this).parent().toggleClass('ui-state-disabled', !fm.options.syncStart).css('pointer-events', 'auto');
					})
			};
		}
	});
	
	this.exec = function() {
		var fm = this.fm;
		if (!search) {
			var dfrd    = fm.sync(),
				timeout = setTimeout(function() {
					fm.notify({type : 'reload', cnt : 1, hideCnt : true});
					dfrd.always(function() { fm.notify({type : 'reload', cnt  : -1}); });
				}, fm.notifyDelay);
				
			return dfrd.always(function() { 
				clearTimeout(timeout); 
				fm.trigger('reload');
			});
		} else {
			jQuery('div.elfinder-toolbar > div.'+fm.res('class', 'searchbtn') + ' > span.ui-icon-search').click();
		}
	};

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/rename.js
 */

/**
 * @class elFinder command "rename". 
 * Rename selected file.
 *
 * @author Dmitry (dio) Levashov, dio@std42.ru
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.rename = function() {
	"use strict";

	// set alwaysEnabled to allow root rename on client size
	this.alwaysEnabled = true;

	this.syncTitleOnChange = true;

	var self = this,
		fm = self.fm,
		request = function(dfrd, targtes, file, name) {
			var sel = targtes? [file.hash].concat(targtes) : [file.hash],
				cnt = sel.length,
				data = {}, rootNames;
			
			fm.lockfiles({files : sel});
			
			if (fm.isRoot(file)) {
				if (!(rootNames = fm.storage('rootNames'))) {
					rootNames = {};
				}
				if (name === '') {
					if (rootNames[file.hash]) {
						file.name = file._name;
						file.i18 = file._i18;
						delete rootNames[file.hash];
						delete file._name;
						delete file._i18;
					} else {
						dfrd && dfrd.reject();
						fm.unlockfiles({files : sel}).trigger('selectfiles', {files : sel});
						return;
					}
				} else {
					if (typeof file._name === 'undefined') {
						file._name = file.name;
						file._i18 = file.i18;
					}
					file.name = rootNames[file.hash] = name;
					delete file.i18;
				}
				fm.storage('rootNames', rootNames);
				data = { changed: [file] };
				fm.updateCache(data);
				fm.change(data);
				dfrd && dfrd.resolve(data);
				fm.unlockfiles({files : sel}).trigger('selectfiles', {files : sel});
				return;
			}

			data = {
				cmd : 'rename',
				name : name,
				target : file.hash
			};

			if (cnt > 1) {
				data['targets'] = targtes;
				if (name.match(/\*/)) {
					data['q'] = name;
				}
			}
			
			fm.request({
					data   : data,
					notify : {type : 'rename', cnt : cnt},
					navigate : {}
				})
				.fail(function(error) {
					var err = fm.parseError(error);
					dfrd && dfrd.reject();
					if (! err || ! Array.isArray(err) || err[0] !== 'errRename') {
						fm.sync();
					}
				})
				.done(function(data) {
					var cwdHash;
					if (data.added && data.added.length && cnt === 1) {
						data.undo = {
							cmd : 'rename',
							callback : function() {
								return fm.request({
									data   : {cmd : 'rename', target : data.added[0].hash, name : file.name},
									notify : {type : 'undo', cnt : 1}
								});
							}
						};
						data.redo = {
							cmd : 'rename',
							callback : function() {
								return fm.request({
									data   : {cmd : 'rename', target : file.hash, name : name},
									notify : {type : 'rename', cnt : 1}
								});
							}
						};
					}
					dfrd && dfrd.resolve(data);
					if (!(cwdHash = fm.cwd().hash) || cwdHash === file.hash) {
						fm.exec('open', jQuery.map(data.added, function(f) {
							return (f.mime === 'directory')? f.hash : null;
						})[0]);
					}
				})
				.always(function() {
					fm.unlockfiles({files : sel}).trigger('selectfiles', {files : sel});
				}
			);
		},
		getHint = function(name, target) {
			var sel = target || fm.selected(),
				splits = fm.splitFileExtention(name),
				f1 = fm.file(sel[0]),
				f2 = fm.file(sel[1]),
				ext, hint, add;
			
			ext = splits[1]? ('.' + splits[1]) : '';
			if (splits[1] && splits[0] === '*') {
				// change extention
				hint =  '"' + fm.splitFileExtention(f1.name)[0] + ext + '", ';
				hint += '"' + fm.splitFileExtention(f2.name)[0] + ext + '"';
			} else if (splits[0].length > 1) {
				if (splits[0].substr(-1) === '*') {
					// add prefix
					add = splits[0].substr(0, splits[0].length - 1);
					hint =  '"' + add + f1.name+'", ';
					hint += '"' + add + f2.name+'"';
				} else if (splits[0].substr(0, 1) === '*') {
					// add suffix
					add = splits[0].substr(1);
					hint =  '"'+fm.splitFileExtention(f1.name)[0] + add + ext + '", ';
					hint += '"'+fm.splitFileExtention(f2.name)[0] + add + ext + '"';
				}
			}
			if (!hint) {
				hint = '"'+splits[0] + '1' + ext + '", "' + splits[0] + '2' + ext + '"';
			}
			if (sel.length > 2) {
				hint += ' ...';
			}
			return hint;
		},
		batchRename = function() {
			var sel = fm.selected(),
				tplr = '<input name="type" type="radio" class="elfinder-tabstop">',
				mkChk = function(node, label) {
					return jQuery('<label class="elfinder-rename-batch-checks">' + fm.i18n(label) + '</label>').prepend(node);
				},
				name = jQuery('<input type="text" class="ui-corner-all elfinder-tabstop">'),
				num  = jQuery(tplr),
				prefix  = jQuery(tplr),
				suffix  = jQuery(tplr),
				extention  = jQuery(tplr),
				checks = jQuery('<div/>').append(
					mkChk(num, 'plusNumber'),
					mkChk(prefix, 'asPrefix'),
					mkChk(suffix, 'asSuffix'),
					mkChk(extention, 'changeExtention')
				),
				preview = jQuery('<div class="elfinder-rename-batch-preview"/>'),
				node = jQuery('<div class="elfinder-rename-batch"/>').append(
						jQuery('<div class="elfinder-rename-batch-name"/>').append(name),
						jQuery('<div class="elfinder-rename-batch-type"/>').append(checks),
						preview
					),
				opts = {
					title : fm.i18n('batchRename'),
					modal : true,
					destroyOnClose : true,
					width: Math.min(380, fm.getUI().width() - 20),
					buttons : {},
					open : function() {
						name.on('input', mkPrev).trigger('focus');
					}
				},
				getName = function() {
					var vName = name.val(),
						ext = fm.splitFileExtention(fm.file(sel[0]).name)[1];
					if (vName !== '' || num.is(':checked')) {
						if (prefix.is(':checked')) {
							vName += '*';
						} else if (suffix.is(':checked')) {
							vName = '*' + vName + '.' + ext;
						} else if (extention.is(':checked')) {
							vName = '*.' + vName;
						} else if (ext) {
							vName += '.' + ext;
						}
					}
					return vName;
				},
				mkPrev = function() {
					var vName = getName();
					if (vName !== '') {
						preview.html(fm.i18n(['renameMultiple', sel.length, getHint(vName)]));
					} else {
						preview.empty();
					}
				},
				radios = checks.find('input:radio').on('change', mkPrev),
				dialog;
			
			opts.buttons[fm.i18n('btnApply')] = function() {
				var vName = getName(),
					file, targets;
				if (vName !== '') {
					dialog.elfinderdialog('close');
					targets = sel;
					file = fm.file(targets.shift());
					request(void(0), targets, file, vName);
				}
			};
			opts.buttons[fm.i18n('btnCancel')] = function() {
				dialog.elfinderdialog('close');
			};
			if (jQuery.fn.checkboxradio) {
				radios.checkboxradio({
					create: function(e, ui) {
						if (this === num.get(0)) {
							num.prop('checked', true).change();
						}
					}
				});
			} else {
				checks.buttonset({
					create: function(e, ui) {
						num.prop('checked', true).change();
					}
				});
			}
			dialog = self.fmDialog(node, opts);
		};
	
	this.noChangeDirOnRemovedCwd = true;
	
	this.shortcuts = [{
		pattern : 'f2' + (fm.OS == 'mac' ? ' enter' : '')
	}, {
		pattern : 'shift+f2',
		description : 'batchRename',
		callback : function() {
			fm.selected().length > 1 && batchRename();
		}
	}];
	
	this.getstate = function(select) {
		var sel = this.files(select),
			cnt = sel.length,
			phash, ext, mime, brk, state, isRoot;
		
		if (!cnt) {
			return -1;
		}
		
		if (cnt > 1 && sel[0].phash) {
			phash = sel[0].phash;
			ext = fm.splitFileExtention(sel[0].name)[1].toLowerCase();
			mime = sel[0].mime;
		}
		if (cnt === 1) {
			isRoot = fm.isRoot(sel[0]);
		}

		state = (cnt === 1 && (isRoot || !sel[0].locked) || (fm.api > 2.1030 && cnt === jQuery.grep(sel, function(f) {
			if (!brk && !f.locked && f.phash === phash && !fm.isRoot(f) && (mime === f.mime || ext === fm.splitFileExtention(f.name)[1].toLowerCase())) {
				return true;
			} else {
				brk && (brk = true);
				return false;
			}
		}).length)) ? 0 : -1;
		
		// because alwaysEnabled = true, it need check disabled on connector 
		if (!isRoot && state === 0 && fm.option('disabledFlip', sel[0].hash)['rename']) {
			state = -1;
		}

		if (state !== -1 && cnt > 1) {
			self.extra = {
				icon: 'preference',
				node: jQuery('<span/>')
					.attr({title: fm.i18n('batchRename')})
					.on('click touchstart', function(e){
						if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
							return;
						}
						e.stopPropagation();
						e.preventDefault();
						fm.getUI().trigger('click'); // to close the context menu immediately
						batchRename();
					})
			};
		} else {
			delete self.extra;
		}
			
		return state;
	};
	
	this.exec = function(hashes, cOpts) {
		var cwd      = fm.getUI('cwd'),
			sel      = hashes || (fm.selected().length? fm.selected() : false) || [fm.cwd().hash],
			cnt      = sel.length,
			file     = fm.file(sel.shift()),
			filename = '.elfinder-cwd-filename',
			opts     = cOpts || {},
			incwd    = (fm.cwd().hash == file.hash),
			type     = (opts._currentType === 'navbar' || opts._currentType === 'files')? opts._currentType : (incwd? 'navbar' : 'files'),
			navbar   = (type !== 'files'),
			target   = fm[navbar? 'navHash2Elm' : 'cwdHash2Elm'](file.hash),
			tarea    = (!navbar && fm.storage('view') != 'list'),
			split    = function(name) {
				var ext = fm.splitFileExtention(name)[1];
				return [name.substr(0, name.length - ext.length - 1), ext];
			},
			unselect = function() {
				requestAnimationFrame(function() {
					input && input.trigger('blur');
				});
			},
			rest     = function(){
				if (!overlay.is(':hidden')) {
					overlay.elfinderoverlay('hide').off('click close', cancel);
				}
				pnode.removeClass('ui-front')
					.css('position', '')
					.off('unselect.'+fm.namespace, unselect);
				if (tarea) {
					node && node.css('max-height', '');
				} else if (!navbar) {
					pnode.css('width', '')
						.parent('td').css('overflow', '');
				}
			}, colwidth,
			dfrd     = jQuery.Deferred()
				.fail(function(error) {
					var parent = input.parent(),
						name   = fm.escape(file.i18 || file.name);

					input.off();
					if (tarea) {
						name = name.replace(/([_.])/g, '&#8203;$1');
					}
					requestAnimationFrame(function() {
						if (navbar) {
							input.replaceWith(name);
						} else {
							if (parent.length) {
								input.remove();
								parent.html(name);
							} else {
								target.find(filename).html(name);
							}
						}
					});
					error && fm.error(error);
				})
				.always(function() {
					rest();
					fm.unbind('resize', resize);
					fm.enable();
				}),
			blur = function(e) {
				var name   = jQuery.trim(input.val()),
				splits = fm.splitFileExtention(name),
				valid  = true,
				req = function() {
					input.off();
					rest();
					if (navbar) {
						input.replaceWith(fm.escape(name));
					} else {
						node.html(fm.escape(name));
					}
					request(dfrd, sel, file, name);
				};

				if (!overlay.is(':hidden')) {
					pnode.css('z-index', '');
				}
				if (name === '') {
					if (!fm.isRoot(file)) {
						return cancel();
					}
					if (navbar) {
						input.replaceWith(fm.escape(file.name));
					} else {
						node.html(fm.escape(file.name));
					}
				}
				if (!inError && pnode.length) {
					
					input.off('blur');
					
					if (cnt === 1 && name === file.name) {
						return dfrd.reject();
					}
					if (fm.options.validName && fm.options.validName.test) {
						try {
							valid = fm.options.validName.test(name);
						} catch(e) {
							valid = false;
						}
					}
					if (name === '.' || name === '..' || !valid) {
						inError = true;
						fm.error(file.mime === 'directory'? 'errInvDirname' : 'errInvName', {modal: true, close: function(){setTimeout(select, 120);}});
						return false;
					}
					if (cnt === 1 && fm.fileByName(name, file.phash)) {
						inError = true;
						fm.error(['errExists', name], {modal: true, close: function(){setTimeout(select, 120);}});
						return false;
					}
					
					if (cnt === 1) {
						req();
					} else {
						fm.confirm({
							title : 'cmdrename',
							text  : ['renameMultiple', cnt, getHint(name, [file.hash].concat(sel))],
							accept : {
								label : 'btnYes',
								callback : req
							},
							cancel : {
								label : 'btnCancel',
								callback : function() {
									setTimeout(function() {
										inError = true;
										select();
									}, 120);
								}
							}
						});
						setTimeout(function() {
							fm.trigger('unselectfiles', {files: fm.selected()})
								.trigger('selectfiles', {files : [file.hash].concat(sel)});
						}, 120);
					}
				}
			},
			input = jQuery(tarea? '<textarea/>' : '<input type="text"/>')
				.on('keyup text', function(){
					if (tarea) {
						this.style.height = '1px';
						this.style.height = this.scrollHeight + 'px';
					} else if (colwidth) {
						this.style.width = colwidth + 'px';
						if (this.scrollWidth > colwidth) {
							this.style.width = this.scrollWidth + 10 + 'px';
						}
					}
				})
				.on('keydown', function(e) {
					e.stopImmediatePropagation();
					if (e.keyCode == jQuery.ui.keyCode.ESCAPE) {
						dfrd.reject();
					} else if (e.keyCode == jQuery.ui.keyCode.ENTER) {
						e.preventDefault();
						input.trigger('blur');
					}
				})
				.on('mousedown click dblclick', function(e) {
					e.stopPropagation();
					if (e.type === 'dblclick') {
						e.preventDefault();
					}
				})
				.on('blur', blur)
				.on('dragenter dragleave dragover drop', function(e) {
					// stop bubbling to prevent upload with native drop event
					e.stopPropagation();
				}),
			select = function() {
				var name = fm.splitFileExtention(input.val())[0];
				if (!inError && fm.UA.Mobile && !fm.UA.iOS) { // since iOS has a bug? (z-index not effect) so disable it
					overlay.on('click close', cancel).elfinderoverlay('show');
					pnode.css('z-index', overlay.css('z-index') + 1);
				}
				! fm.enabled() && fm.enable();
				if (inError) {
					inError = false;
					input.on('blur', blur);
				}
				input.trigger('focus').trigger('select');
				input[0].setSelectionRange && input[0].setSelectionRange(0, name.length);
			},
			node = navbar? target.contents().filter(function(){ return this.nodeType==3 && jQuery(this).parent().attr('id') === fm.navHash2Id(file.hash); })
					: target.find(filename),
			pnode = node.parent(),
			overlay = fm.getUI('overlay'),
			cancel = function(e) { 
				if (!overlay.is(':hidden')) {
					pnode.css('z-index', '');
				}
				if (! inError) {
					dfrd.reject();
					if (e) {
						e.stopPropagation();
						e.preventDefault();
					}
				}
			},
			resize = function() {
				target.trigger('scrolltoview', {blink : false});
			},
			inError = false;
		
		pnode.addClass('ui-front')
			.css('position', 'relative')
			.on('unselect.'+fm.namespace, unselect);
		fm.bind('resize', resize);
		if (navbar) {
			node.replaceWith(input.val(file.name));
		} else {
			if (tarea) {
				node.css('max-height', 'none');
			} else if (!navbar) {
				colwidth = pnode.width();
				pnode.width(colwidth - 15)
					.parent('td').css('overflow', 'visible');
			}
			node.empty().append(input.val(file.name));
		}
		
		if (cnt > 1 && fm.api <= 2.1030) {
			return dfrd.reject();
		}
		
		if (!file || !node.length) {
			return dfrd.reject('errCmdParams', this.title);
		}
		
		if (file.locked && !fm.isRoot(file)) {
			return dfrd.reject(['errLocked', file.name]);
		}
		
		fm.one('select', function() {
			input.parent().length && file && jQuery.inArray(file.hash, fm.selected()) === -1 && input.trigger('blur');
		});
		
		input.trigger('keyup');
		
		select();
		
		return dfrd;
	};

	fm.bind('select contextmenucreate closecontextmenu', function(e) {
		var sel = (e.data? (e.data.selected || e.data.targets) : null) || fm.selected(),
			file;
		if (sel && sel.length === 1 && (file = fm.file(sel[0])) && fm.isRoot(file)) {
			self.title = fm.i18n('kindAlias') + ' (' + fm.i18n('preference') + ')';
		} else {
			self.title = fm.i18n('cmdrename');
		}
		if (e.type !== 'closecontextmenu') {
			self.update(void(0), self.title);
		} else {
			requestAnimationFrame(function() {
				self.update(void(0), self.title);
			});
		}
	}).remove(function(e) {
		var rootNames;
		if (e.data && e.data.removed && (rootNames = fm.storage('rootNames'))) {
			jQuery.each(e.data.removed, function(i, h) {
				if (rootNames[h]) {
					delete rootNames[h];
				}
			});
			fm.storage('rootNames', rootNames);
		}
	});
};


/*
 * File: /js/commands/resize.js
 */

/**
 * @class  elFinder command "resize"
 * Open dialog to resize image
 *
 * @author Dmitry (dio) Levashov
 * @author Alexey Sukhotin
 * @author Naoki Sawada
 * @author Sergio Jovani
 **/
elFinder.prototype.commands.resize = function() {
	"use strict";
	var losslessRotate = 0,
		getBounceBox = function(w, h, theta) {
			var srcPts = [
					{x: w/2, y: h/2},
					{x: -w/2, y: h/2},
					{x: -w/2, y: -h/2},
					{x: w/2, y: -h/2}
				],
				dstPts = [],
				min = {x: Number.MAX_VALUE, y: Number.MAX_VALUE},
				max = {x: Number.MIN_VALUE, y: Number.MIN_VALUE};
			jQuery.each(srcPts, function(i, srcPt){
				dstPts.push({
					x: srcPt.x * Math.cos(theta) - srcPt.y * Math.sin(theta),
					y: srcPt.x * Math.sin(theta) + srcPt.y * Math.cos(theta)
				});
			});
			jQuery.each(dstPts, function(i, pt) {
				min.x = Math.min(min.x, pt.x);
				min.y = Math.min(min.y, pt.y);
				max.x = Math.max(max.x, pt.x);
				max.y = Math.max(max.y, pt.y);
			});
			return {
				width: max.x - min.x, height: max.y - min.y
			};
		};
	
	this.updateOnSelect = false;
	
	this.getstate = function() {
		var sel = this.fm.selectedFiles();
		return sel.length == 1 && sel[0].read && sel[0].write && sel[0].mime.indexOf('image/') !== -1 ? 0 : -1;
	};
	
	this.resizeRequest = function(data, f, dfrd) {
		var fm   = this.fm,
			file = f || fm.file(data.target),
			tmb  = file? file.tmb : null,
			enabled = fm.isCommandEnabled('resize', data.target);
		
		if (enabled && (! file || (file && file.read && file.write && file.mime.indexOf('image/') !== -1 ))) {
			return fm.request({
				data : Object.assign(data, {
					cmd : 'resize'
				}),
				notify : {type : 'resize', cnt : 1}
			})
			.fail(function(error) {
				if (dfrd) {
					dfrd.reject(error);
				}
			})
			.done(function() {
				if (data.quality) {
					fm.storage('jpgQuality', data.quality === fm.option('jpgQuality')? null : data.quality);
				}
				dfrd && dfrd.resolve();
			});
		} else {
			var error;
			
			if (file) {
				if (file.mime.indexOf('image/') === -1) {
					error = ['errResize', file.name, 'errUsupportType'];
				} else {
					error = ['errResize', file.name, 'errPerm'];
				}
			} else {
				error = ['errResize', data.target, 'errPerm'];
			}
			
			if (dfrd) {
				dfrd.reject(error);
			} else {
				fm.error(error);
			}
			return jQuery.Deferred().reject(error);
		}
	};
	
	this.exec = function(hashes) {
		var self  = this,
			fm    = this.fm,
			files = this.files(hashes),
			dfrd  = jQuery.Deferred(),
			api2  = (fm.api > 1),
			options = this.options,
			dialogWidth = 650,
			fmnode = fm.getUI(),
			ctrgrup = jQuery().controlgroup? 'controlgroup' : 'buttonset',
			grid8Def = typeof options.grid8px === 'undefined' || options.grid8px !== 'disable'? true : false,
			presetSize = Array.isArray(options.presetSize)? options.presetSize : [],
			clactive = 'elfinder-dialog-active',
			clsediting = fm.res('class', 'editing'),
			open = function(file, id) {
				var isJpeg   = (file.mime === 'image/jpeg'),
					dialog   = jQuery('<div class="elfinder-resize-container"/>'),
					input    = '<input type="number" class="ui-corner-all"/>',
					row      = '<div class="elfinder-resize-row"/>',
					label    = '<div class="elfinder-resize-label"/>',
					changeTm = null,
					operate  = false,
					opStart  = function() { operate = true; },
					opStop   = function() {
						if (operate) {
							operate = false;
							control.trigger('change');
						}
					},
					control  = jQuery('<div class="elfinder-resize-control"/>')
						.on('focus', 'input[type=text],input[type=number]', function() {
							jQuery(this).trigger('select');
						})
						.on('change', function() {
							changeTm && cancelAnimationFrame(changeTm);
							changeTm = requestAnimationFrame(function() {
								var panel, quty, canvas, ctx, img, sx, sy, sw, sh, deg, theta, bb;
								if (sizeImg && ! operate && (canvas = sizeImg.data('canvas'))) {
									panel = control.children('div.elfinder-resize-control-panel:visible');
									quty = panel.find('input.elfinder-resize-quality');
									if (quty.is(':visible')) {
										ctx = sizeImg.data('ctx');
										img = sizeImg.get(0);
										if (panel.hasClass('elfinder-resize-uiresize')) {
											// resize
											sw = canvas.width = width.val();
											sh = canvas.height = height.val();
											ctx.drawImage(img, 0, 0, sw, sh);
										} else if (panel.hasClass('elfinder-resize-uicrop')) {
											// crop
											sx = pointX.val();
											sy = pointY.val();
											sw = offsetX.val();
											sh = offsetY.val();
											canvas.width = sw;
											canvas.height = sh;
											ctx.drawImage(img, sx, sy, sw, sh, 0, 0, sw, sh);
										} else {
											// rotate
											deg = degree.val();
											theta = (degree.val() * Math.PI) / 180;
											bb = getBounceBox(owidth, oheight, theta);
											sw = canvas.width = bb.width;
											sh = canvas.height = bb.height;
											ctx.save();
											if (deg % 90 !== 0) {
												ctx.fillStyle = bg.val() || '#FFF';
												ctx.fillRect(0, 0, sw, sh);
											}
											ctx.translate(sw / 2, sh / 2);
											ctx.rotate(theta);
											ctx.drawImage(img, -img.width/2, -img.height/2, owidth, oheight);
											ctx.restore();
										}
										canvas.toBlob(function(blob) {
											blob && quty.next('span').text(' (' + fm.formatSize(blob.size) + ')');
										}, 'image/jpeg', Math.max(Math.min(quty.val(), 100), 1) / 100);
									}
								}
							});
						})
						.on('mouseup', 'input', function(e) {
							jQuery(e.target).trigger('change');
						}),
					preview  = jQuery('<div class="elfinder-resize-preview"/>')
						.on('touchmove', function(e) {
							if (jQuery(e.target).hasClass('touch-punch')) {
								e.stopPropagation();
								e.preventDefault();
							}
						}),
					spinner  = jQuery('<div class="elfinder-resize-loading">'+fm.i18n('ntfloadimg')+'</div>'),
					rhandle  = jQuery('<div class="elfinder-resize-handle touch-punch"/>'),
					rhandlec = jQuery('<div class="elfinder-resize-handle touch-punch"/>'),
					uiresize = jQuery('<div class="elfinder-resize-uiresize elfinder-resize-control-panel"/>'),
					uicrop   = jQuery('<div class="elfinder-resize-uicrop elfinder-resize-control-panel"/>'),
					uirotate = jQuery('<div class="elfinder-resize-rotate elfinder-resize-control-panel"/>'),
					uideg270 = jQuery('<button/>').attr('title',fm.i18n('rotate-cw')).append(jQuery('<span class="elfinder-button-icon elfinder-button-icon-rotate-l"/>')),
					uideg90  = jQuery('<button/>').attr('title',fm.i18n('rotate-ccw')).append(jQuery('<span class="elfinder-button-icon elfinder-button-icon-rotate-r"/>')),
					uiprop   = jQuery('<span />'),
					reset    = jQuery('<button class="elfinder-resize-reset">').text(fm.i18n('reset'))
						.on('click', function() {
							resetView();
						})
						.button({
							icons: {
								primary: 'ui-icon-arrowrefresh-1-n'
							},
							text: false
						}),
					uitype   = jQuery('<div class="elfinder-resize-type"/>')
						.append('<input type="radio" name="type" id="'+id+'-resize" value="resize" checked="checked" /><label for="'+id+'-resize">'+fm.i18n('resize')+'</label>',
						'<input class="api2" type="radio" name="type" id="'+id+'-crop" value="crop" /><label class="api2" for="'+id+'-crop">'+fm.i18n('crop')+'</label>',
						'<input class="api2" type="radio" name="type" id="'+id+'-rotate" value="rotate" /><label class="api2" for="'+id+'-rotate">'+fm.i18n('rotate')+'</label>'),
					mode     = 'resize',
					type     = uitype[ctrgrup]()[ctrgrup]('disable').find('input')
						.on('change', function() {
							mode = jQuery(this).val();
							
							resetView();
							resizable(true);
							croppable(true);
							rotateable(true);
							
							if (mode == 'resize') {
								uiresize.show();
								uirotate.hide();
								uicrop.hide();
								resizable();
								isJpeg && grid8px.insertAfter(uiresize.find('.elfinder-resize-grid8'));
							}
							else if (mode == 'crop') {
								uirotate.hide();
								uiresize.hide();
								uicrop.show();
								croppable();
								isJpeg && grid8px.insertAfter(uicrop.find('.elfinder-resize-grid8'));
							} else if (mode == 'rotate') {
								uiresize.hide();
								uicrop.hide();
								uirotate.show();
								rotateable();
							}
						}),
					width   = jQuery(input)
						.on('change', function() {
							var w = round(parseInt(width.val())),
								h = round(cratio ? w/ratio : parseInt(height.val()));

							if (w > 0 && h > 0) {
								resize.updateView(w, h);
								width.val(w);
								height.val(h);
							}
						}).addClass('elfinder-focus'),
					height  = jQuery(input)
						.on('change', function() {
							var h = round(parseInt(height.val())),
								w = round(cratio ? h*ratio : parseInt(width.val()));

							if (w > 0 && h > 0) {
								resize.updateView(w, h);
								width.val(w);
								height.val(h);
							}
						}),
					pointX  = jQuery(input).on('change', function(){crop.updateView();}),
					pointY  = jQuery(input).on('change', function(){crop.updateView();}),
					offsetX = jQuery(input).on('change', function(){crop.updateView('w');}),
					offsetY = jQuery(input).on('change', function(){crop.updateView('h');}),
					quality = isJpeg && api2?
						jQuery(input).val(fm.storage('jpgQuality') > 0? fm.storage('jpgQuality') : fm.option('jpgQuality'))
							.addClass('elfinder-resize-quality')
							.attr('min', '1').attr('max', '100').attr('title', '1 - 100')
							.on('blur', function(){
								var q = Math.min(100, Math.max(1, parseInt(this.value)));
								control.find('input.elfinder-resize-quality').val(q);
							})
						: null,
					degree = jQuery('<input type="number" class="ui-corner-all" maxlength="3" value="0" />')
						.on('change', function() {
							rotate.update();
						}),
					uidegslider = jQuery('<div class="elfinder-resize-rotate-slider touch-punch"/>')
						.slider({
							min: 0,
							max: 360,
							value: degree.val(),
							animate: true,
							start: opStart,
							stop: opStop,
							change: function(event, ui) {
								if (ui.value != uidegslider.slider('value')) {
									rotate.update(ui.value);
								}
							},
							slide: function(event, ui) {
								rotate.update(ui.value, false);
							}
						}).find('.ui-slider-handle')
							.addClass('elfinder-tabstop')
							.off('keydown')
							.on('keydown', function(e) {
								if (e.keyCode == jQuery.ui.keyCode.LEFT || e.keyCode == jQuery.ui.keyCode.RIGHT) {
									e.stopPropagation();
									e.preventDefault();
									rotate.update(Number(degree.val()) + (e.keyCode == jQuery.ui.keyCode.RIGHT? 1 : -1), false);
								}
							})
						.end(),
					pickimg,
					pickcanv,
					pickctx,
					pickc = {},
					pick = function(e) {
						var color, r, g, b, h, s, l;

						try {
							color = pickc[Math.round(e.offsetX)][Math.round(e.offsetY)];
						} catch(e) {}
						if (!color) return;

						r = color[0]; g = color[1]; b = color[2];
						h = color[3]; s = color[4]; l = color[5];

						setbg(r, g, b, (e.type === 'click'));
					},
					palpick = function(e) {
						setbg(jQuery(this).css('backgroundColor'), '', '', (e.type === 'click'));
					},
					setbg = function(r, g, b, off) {
						var s, m, cc;
						if (typeof r === 'string') {
							g = '';
							if (r && (s = jQuery('<span>').css('backgroundColor', r).css('backgroundColor')) && (m = s.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i))) {
								r = Number(m[1]);
								g = Number(m[2]);
								b = Number(m[3]);
							}
						}
						cc = (g === '')? r : '#' + getColorCode(r, g, b);
						bg.val(cc).css({ backgroundColor: cc, backgroundImage: 'none', color: (r+g+b < 384? '#fff' : '#000') });
						preview.css('backgroundColor', cc);
						if (off) {
							imgr.off('.picker').removeClass('elfinder-resize-picking');
							pallet.off('.picker').removeClass('elfinder-resize-picking');
						}
					},
					getColorCode = function(r, g, b) {
						return jQuery.map([r,g,b], function(c){return ('0'+parseInt(c).toString(16)).slice(-2);}).join('');
					},
					picker = jQuery('<button>').text(fm.i18n('colorPicker'))
					.on('click', function() { 
						imgr.on('mousemove.picker click.picker', pick).addClass('elfinder-resize-picking');
						pallet.on('mousemove.picker click.picker', 'span', palpick).addClass('elfinder-resize-picking');
					})
					.button({
						icons: {
							primary: 'ui-icon-pin-s'
						},
						text: false
					}),
					reseter = jQuery('<button>').text(fm.i18n('reset'))
						.on('click', function() { 
							setbg('', '', '', true);
						})
						.button({
							icons: {
								primary: 'ui-icon-arrowrefresh-1-n'
							},
							text: false
						}),
					bg = jQuery('<input class="ui-corner-all elfinder-resize-bg" type="text">')
						.on('focus', function() {
							jQuery(this).attr('style', '');
						})
						.on('blur', function() {
							setbg(jQuery(this).val());
						}),
					pallet  = jQuery('<div class="elfinder-resize-pallet">').on('click', 'span', function() {
						setbg(jQuery(this).css('backgroundColor'));
					}),
					ratio   = 1,
					prop    = 1,
					owidth  = 0,
					oheight = 0,
					cratio  = true,
					cratioc = false,
					pwidth  = 0,
					pheight = 0,
					rwidth  = 0,
					rheight = 0,
					rdegree = 0,
					grid8   = isJpeg? grid8Def : false,
					constr  = jQuery('<button>').html(fm.i18n('aspectRatio'))
						.on('click', function() {
							cratio = ! cratio;
							constr.button('option', {
								icons : { primary: cratio? 'ui-icon-locked' : 'ui-icon-unlocked'}
							});
							resize.fixHeight();
							rhandle.resizable('option', 'aspectRatio', cratio).data('uiResizable')._aspectRatio = cratio;
						})
						.button({
							icons : {
								primary: cratio? 'ui-icon-locked' : 'ui-icon-unlocked'
							},
							text: false
						}),
					constrc = jQuery('<button>').html(fm.i18n('aspectRatio'))
						.on('click', function() {
							cratioc = ! cratioc;
							constrc.button('option', {
								icons : { primary: cratioc? 'ui-icon-locked' : 'ui-icon-unlocked'}
							});
							rhandlec.resizable('option', 'aspectRatio', cratioc).data('uiResizable')._aspectRatio = cratioc;
						})
						.button({
							icons : {
								primary: cratioc? 'ui-icon-locked' : 'ui-icon-unlocked'
							},
							text: false
						}),
					grid8px = jQuery('<button>').html(fm.i18n(grid8? 'enabled' : 'disabled')).toggleClass('ui-state-active', grid8)
						.on('click', function() {
							grid8 = ! grid8;
							grid8px.html(fm.i18n(grid8? 'enabled' : 'disabled')).toggleClass('ui-state-active', grid8);
							setStep8();
						})
						.button(),
					setStep8 = function() {
						var step = grid8? 8 : 1;
						jQuery.each([width, height, offsetX, offsetY, pointX, pointY], function() {
							this.attr('step', step);
						});
						if (grid8) {
							width.val(round(width.val()));
							height.val(round(height.val()));
							offsetX.val(round(offsetX.val()));
							offsetY.val(round(offsetY.val()));
							pointX.val(round(pointX.val()));
							pointY.val(round(pointY.val()));
							if (uiresize.is(':visible')) {
								resize.updateView(width.val(), height.val());
							} else if (uicrop.is(':visible')) {
								crop.updateView();
							}
						}
					},
					setuprimg = function() {
						var r_scale,
							fail = function() {
								bg.parent().hide();
								pallet.hide();
							};
						r_scale = Math.min(pwidth, pheight) / Math.sqrt(Math.pow(owidth, 2) + Math.pow(oheight, 2));
						rwidth = Math.ceil(owidth * r_scale);
						rheight = Math.ceil(oheight * r_scale);
						imgr.width(rwidth)
							.height(rheight)
							.css('margin-top', (pheight-rheight)/2 + 'px')
							.css('margin-left', (pwidth-rwidth)/2 + 'px');
						if (imgr.is(':visible') && bg.is(':visible')) {
							if (file.mime !== 'image/png') {
								preview.css('backgroundColor', bg.val());
								pickimg = jQuery('<img>');
								if (fm.isCORS) {
									pickimg.attr('crossorigin', 'use-credentials');
								}
								pickimg.on('load', function() {
									if (pickcanv && pickcanv.width !== rwidth) {
										setColorData();
									}
								})
								.on('error', fail)
								.attr('src', canvSrc);
							} else {
								fail();
							}
						}
					},
					setupimg = function() {
						resize.updateView(owidth, oheight);
						setuprimg();
						basec
							.width(img.width())
							.height(img.height());
						imgc
							.width(img.width())
							.height(img.height());
						crop.updateView();
						jpgCalc();
					},
					setColorData = function() {
						if (pickctx) {
							var n, w, h, r, g, b, a, s, l, hsl, hue,
								data, scale, tx1, tx2, ty1, ty2, rgb,
								domi = {},
								domic = [],
								domiv, palc,
								rgbToHsl = function (r, g, b) {
									var h, s, l,
										max = Math.max(Math.max(r, g), b),
										min = Math.min(Math.min(r, g), b);
		
									// Hue, 0 ~ 359
									if (max === min) {
										h = 0;
									} else if (r === max) {
										h = ((g - b) / (max - min) * 60 + 360) % 360;
									} else if (g === max) {
										h = (b - r) / (max - min) * 60 + 120;
									} else if (b === max) {
										h = (r - g) / (max - min) * 60 + 240;
									}
									// Saturation, 0 ~ 1
									s = (max - min) / max;
									// Lightness, 0 ~ 1
									l = (r *  0.3 + g * 0.59 + b * 0.11) / 255;
		
									return [h, s, l, 'hsl'];
								},
								rgbRound = function(c) {
									return Math.round(c / 8) * 8;
								};
							
							calc:
							try {
								w = pickcanv.width = imgr.width();
								h = pickcanv.height = imgr.height();
								scale = w / owidth;
								pickctx.scale(scale, scale);
								pickctx.drawImage(pickimg.get(0), 0, 0);
			
								data = pickctx.getImageData(0, 0, w, h).data;
			
								// Range to detect the dominant color
								tx1 = w * 0.1;
								tx2 = w * 0.9;
								ty1 = h * 0.1;
								ty2 = h * 0.9;
			
								for (var y = 0; y < h - 1; y++) {
									for (var x = 0; x < w - 1; x++) {
										n = x * 4 + y * w * 4;
										// RGB
										r = data[n]; g = data[n + 1]; b = data[n + 2]; a = data[n + 3];
										// check alpha ch
										if (a !== 255) {
											bg.parent().hide();
											pallet.hide();
											break calc;
										}
										// HSL
										hsl = rgbToHsl(r, g, b);
										hue = Math.round(hsl[0]); s = Math.round(hsl[1] * 100); l = Math.round(hsl[2] * 100);
										if (! pickc[x]) {
											pickc[x] = {};
										}
										// set pickc
										pickc[x][y] = [r, g, b, hue, s, l];
										// detect the dominant color
										if ((x < tx1 || x > tx2) && (y < ty1 || y > ty2)) {
											rgb = rgbRound(r) + ',' + rgbRound(g) + ',' + rgbRound(b);
											if (! domi[rgb]) {
												domi[rgb] = 1;
											} else {
												++domi[rgb];
											}
										}
									}
								}
								
								if (! pallet.children(':first').length) {
									palc = 1;
									jQuery.each(domi, function(c, v) {
										domic.push({c: c, v: v});
									});
									jQuery.each(domic.sort(function(a, b) {
										return (a.v > b.v)? -1 : 1;
									}), function() {
										if (this.v < 2 || palc > 10) {
											return false;
										}
										pallet.append(jQuery('<span style="width:20px;height:20px;display:inline-block;background-color:rgb('+this.c+');">'));
										++palc;
									});
								}
							} catch(e) {
								picker.hide();
								pallet.hide();
							}
						}
					},
					setupPicker = function() {
						try {
							pickcanv = document.createElement('canvas');
							pickctx = pickcanv.getContext('2d');
						} catch(e) {
							picker.hide();
							pallet.hide();
						}
					},
					setupPreset = function() {
						preset.on('click', 'span.elfinder-resize-preset', function() {
							var btn = jQuery(this),
								w = btn.data('s')[0],
								h = btn.data('s')[1],
								r = owidth / oheight;
							btn.data('s', [h, w]).text(h + 'x' + w);
							if (owidth > w || oheight > h) {
								if (owidth <= w) {
									w = round(h * r);
								} else if (oheight <= h) {
									h = round(w / r);
								} else {
									if (owidth - w > oheight - h) {
										h = round(w / r);
									} else {
										w = round(h * r);
									}
								}
							} else {
								w = owidth;
								h = oheight;
							}
							width.val(w);
							height.val(h);
							resize.updateView(w, h);
							jpgCalc();
						});
						presetc.on('click', 'span.elfinder-resize-preset', function() {
							var btn = jQuery(this),
								w = btn.data('s')[0],
								h = btn.data('s')[1],
								x = pointX.val(),
								y = pointY.val();
							
							btn.data('s', [h, w]).text(h + 'x' + w);
							if (owidth >= w && oheight >= h) {
								if (owidth - w - x < 0) {
									x = owidth - w;
								}
								if (oheight - h - y < 0) {
									y = oheight - h;
								}
								pointX.val(x);
								pointY.val(y);
								offsetX.val(w);
								offsetY.val(h);
								crop.updateView();
								jpgCalc();
							}
						});
						presetc.children('span.elfinder-resize-preset').each(function() {
							var btn = jQuery(this),
								w = btn.data('s')[0],
								h = btn.data('s')[1];
							
							btn[(owidth >= w && oheight >= h)? 'show' : 'hide']();
						});
					},
					dimreq  = null,
					inited  = false,
					setdim  = function(dim) {
						var rfile = fm.file(file.hash);
						rfile.width = dim[0];
						rfile.height = dim[1];
					},
					init    = function() {
						var elm, memSize, r_scale, imgRatio;
						
						if (inited) {
							return;
						}
						inited = true;
						dimreq && dimreq.state && dimreq.state() === 'pending' && dimreq.reject();
						
						// check lossless rotete
						if (fm.api >= 2.1030) {
							if (losslessRotate === 0) {
								fm.request({
									data: {
										cmd    : 'resize',
										target : file.hash,
										degree : 0,
										mode   : 'rotate'
									},
									preventDefault : true
								}).done(function(data) {
									losslessRotate = data.losslessRotate? 1 : -1;
									if (losslessRotate === 1 && (degree.val() % 90 === 0)) {
										uirotate.children('div.elfinder-resize-quality').hide();
									}
								}).fail(function() {
									losslessRotate = -1;
								});
							}
						} else {
							losslessRotate = -1;
						}
						
						elm = img.get(0);
						memSize = file.width && file.height? {w: file.width, h: file.height} : (elm.naturalWidth? null : {w: img.width(), h: img.height()});
					
						memSize && img.removeAttr('width').removeAttr('height');
						
						owidth  = file.width || elm.naturalWidth || elm.width || img.width();
						oheight = file.height || elm.naturalHeight || elm.height || img.height();
						if (!file.width || !file.height) {
							setdim([owidth, oheight]);
						}
						
						memSize && img.width(memSize.w).height(memSize.h);
						
						dMinBtn.show();
	
						imgRatio = oheight / owidth;
						
						if (imgRatio < 1 && preview.height() > preview.width() * imgRatio) {
							preview.height(preview.width() * imgRatio);
						}
						
						if (preview.height() > img.height() + 20) {
							preview.height(img.height() + 20);
						}
						
						pheight = preview.height() - (rhandle.outerHeight() - rhandle.height());
						
						spinner.remove();
						
						ratio = owidth/oheight;
	
						rhandle.append(img.show()).show();
						width.val(owidth);
						height.val(oheight);
	
						setupPicker();
						setupPreset();
						setupimg();
						
						uitype[ctrgrup]('enable');
						control.find('input,select').prop('disabled', false)
							.filter(':text').on('keydown', function(e) {
								var cOpts;
								if (e.keyCode == jQuery.ui.keyCode.ENTER) {
									e.stopPropagation();
									e.preventDefault();
									cOpts = {
										title  : jQuery('input:checked', uitype).val(),
										text   : 'confirmReq',
										accept : {
											label    : 'btnApply',
											callback : function() {  
												save();
											}
										},
										cancel : {
											label    : 'btnCancel',
											callback : function(){
												jQuery(this).trigger('focus');
											}
										}
									};
										
									if (useSaveAs) {
										cOpts['buttons'] = [{
											label    : 'btnSaveAs',
											callback : function() {
												requestAnimationFrame(saveAs);
											}
										}];
									}
									fm.confirm(cOpts);
									return;
								}
							})
							.on('keyup', function() {
								var $this = jQuery(this);
								if (! $this.hasClass('elfinder-resize-bg')) {
									requestAnimationFrame(function() {
										$this.val($this.val().replace(/[^0-9]/g, ''));
									});
								}
							})
							.filter(':first');
						
						setStep8();
						!fm.UA.Mobile && width.trigger('focus');
						resizable();
					},
					img     = jQuery('<img/>')
						.on('load', init)
						.on('error', function() {
							spinner.text('Unable to load image').css('background', 'transparent');
						}),
					basec = jQuery('<div/>'),
					imgc = jQuery('<img/>'),
					coverc = jQuery('<div/>'),
					imgr = jQuery('<img class="elfinder-resize-imgrotate" />'),
					round = function(v, max) {
						v = grid8? Math.round(v/8)*8 : Math.round(v);
						v = Math.max(0, v);
						if (max && v > max) {
							v = grid8? Math.floor(max/8)*8 : max;
						}
						return v;
					},
					resetView = function() {
						width.val(owidth);
						height.val(oheight);
						resize.updateView(owidth, oheight);
						pointX.val(0);
						pointY.val(0);
						offsetX.val(owidth);
						offsetY.val(oheight);
						crop.updateView();
						jpgCalc();
					},
					resize = {
						update : function() {
							width.val(round(img.width()/prop));
							height.val(round(img.height()/prop));
							jpgCalc();
						},
						
						updateView : function(w, h) {
							if (w > pwidth || h > pheight) {
								if (w / pwidth > h / pheight) {
									prop = pwidth / w;
									img.width(pwidth).height(round(h*prop));
								} else {
									prop = pheight / h;
									img.height(pheight).width(round(w*prop));
								}
							} else {
								img.width(round(w)).height(round(h));
							}
							
							prop = img.width()/w;
							uiprop.text('1 : '+(1/prop).toFixed(2));
							resize.updateHandle();
						},
						
						updateHandle : function() {
							rhandle.width(img.width()).height(img.height());
						},
						fixHeight : function() {
							var w, h;
							if (cratio) {
								w = width.val();
								h = round(w/ratio);
								resize.updateView(w, h);
								height.val(h);
							}
						}
					},
					crop = {
						update : function(change) {
							pointX.val(round(((rhandlec.data('x')||rhandlec.position().left))/prop, owidth));
							pointY.val(round(((rhandlec.data('y')||rhandlec.position().top))/prop, oheight));
							if (change !== 'xy') {
								offsetX.val(round((rhandlec.data('w')||rhandlec.width())/prop, owidth - pointX.val()));
								offsetY.val(round((rhandlec.data('h')||rhandlec.height())/prop, oheight - pointY.val()));
							}
							jpgCalc();
						},
						updateView : function(change) {
							var r, x, y, w, h;
							
							pointX.val(round(pointX.val(), owidth - (grid8? 8 : 1)));
							pointY.val(round(pointY.val(), oheight - (grid8? 8 : 1)));
							offsetX.val(round(offsetX.val(), owidth - pointX.val()));
							offsetY.val(round(offsetY.val(), oheight - pointY.val()));
							
							if (cratioc) {
								r = coverc.width() / coverc.height();
								if (change === 'w') {
									offsetY.val(round(parseInt(offsetX.val()) / r));
								} else if (change === 'h') {
									offsetX.val(round(parseInt(offsetY.val()) * r));
								}
							}
							x = Math.round(parseInt(pointX.val()) * prop);
							y = Math.round(parseInt(pointY.val()) * prop);
							if (change !== 'xy') {
								w = Math.round(parseInt(offsetX.val()) * prop);
								h = Math.round(parseInt(offsetY.val()) * prop);
							} else {
								w = rhandlec.data('w');
								h = rhandlec.data('h');
							}
							rhandlec.data({x: x, y: y, w: w, h: h})
								.width(w)
								.height(h)
								.css({left: x, top: y});
							coverc.width(w)
								.height(h);
						},
						resize_update : function(e, ui) {
							rhandlec.data({x: ui.position.left, y: ui.position.top, w: ui.size.width, h: ui.size.height});
							crop.update();
							crop.updateView();
						},
						drag_update : function(e, ui) {
							rhandlec.data({x: ui.position.left, y: ui.position.top});
							crop.update('xy');
						}
					},
					rotate = {
						mouseStartAngle : 0,
						imageStartAngle : 0,
						imageBeingRotated : false,
						
						setQuality : function() {
							uirotate.children('div.elfinder-resize-quality')[(losslessRotate > 0 && (degree.val() % 90) === 0)? 'hide' : 'show']();
						},
						
						update : function(value, animate) {
							if (typeof value == 'undefined') {
								rdegree = value = parseInt(degree.val());
							}
							if (typeof animate == 'undefined') {
								animate = true;
							}
							if (! animate || fm.UA.Opera || fm.UA.ltIE8) {
								imgr.rotate(value);
							} else {
								imgr.animate({rotate: value + 'deg'});
							}
							value = value % 360;
							if (value < 0) {
								value += 360;
							}
							degree.val(parseInt(value));

							uidegslider.slider('value', degree.val());
							
							rotate.setQuality();
						},
						
						execute : function ( e ) {
							
							if ( !rotate.imageBeingRotated ) return;
							
							var imageCentre = rotate.getCenter( imgr );
							var ev = e.originalEvent.touches? e.originalEvent.touches[0] : e;
							var mouseXFromCentre = ev.pageX - imageCentre[0];
							var mouseYFromCentre = ev.pageY - imageCentre[1];
							var mouseAngle = Math.atan2( mouseYFromCentre, mouseXFromCentre );
							
							var rotateAngle = mouseAngle - rotate.mouseStartAngle + rotate.imageStartAngle;
							rotateAngle = Math.round(parseFloat(rotateAngle) * 180 / Math.PI);
							
							if ( e.shiftKey ) {
								rotateAngle = Math.round((rotateAngle + 6)/15) * 15;
							}
							
							imgr.rotate(rotateAngle);
							
							rotateAngle = rotateAngle % 360;
							if (rotateAngle < 0) {
								rotateAngle += 360;
							}
							degree.val(rotateAngle);

							uidegslider.slider('value', degree.val());
							
							rotate.setQuality();
							
							return false;
						},
						
						start : function ( e ) {
							if (imgr.hasClass('elfinder-resize-picking')) {
								return;
							}
							
							opStart();
							rotate.imageBeingRotated = true;
							
							var imageCentre = rotate.getCenter( imgr );
							var ev = e.originalEvent.touches? e.originalEvent.touches[0] : e;
							var mouseStartXFromCentre = ev.pageX - imageCentre[0];
							var mouseStartYFromCentre = ev.pageY - imageCentre[1];
							rotate.mouseStartAngle = Math.atan2( mouseStartYFromCentre, mouseStartXFromCentre );
							
							rotate.imageStartAngle = parseFloat(imgr.rotate()) * Math.PI / 180.0;
							
							jQuery(document).on('mousemove', rotate.execute);
							imgr.on('touchmove', rotate.execute);
							
							return false;
						},
							
						stop : function ( e ) {
							
							if ( !rotate.imageBeingRotated ) return;
							
							jQuery(document).off('mousemove', rotate.execute);
							imgr.off('touchmove', rotate.execute);
							
							requestAnimationFrame(function() { rotate.imageBeingRotated = false; });
							opStop();
							
							return false;
						},
						
						getCenter : function ( image ) {
							
							var currentRotation = imgr.rotate();
							imgr.rotate(0);
							
							var imageOffset = imgr.offset();
							var imageCentreX = imageOffset.left + imgr.width() / 2;
							var imageCentreY = imageOffset.top + imgr.height() / 2;
							
							imgr.rotate(currentRotation);
							
							return Array( imageCentreX, imageCentreY );
						}
					},
					resizable = function(destroy) {
						if (destroy) {
							rhandle.filter(':ui-resizable').resizable('destroy');
							rhandle.hide();
						}
						else {
							rhandle.show();
							rhandle.resizable({
								alsoResize  : img,
								aspectRatio : cratio,
								resize      : resize.update,
								start       : opStart,
								stop        : function(e) {
									resize.fixHeight;
									resize.updateView(width.val(), height.val());
									opStop();
								}
							});
							dinit();
						}
					},
					croppable = function(destroy) {
						if (destroy) {
							rhandlec.filter(':ui-resizable').resizable('destroy')
								.filter(':ui-draggable').draggable('destroy');
							basec.hide();
						}
						else {
							basec.show();
							
							rhandlec
								.resizable({
									containment : basec,
									aspectRatio : cratioc,
									resize      : crop.resize_update,
									start       : opStart,
									stop        : opStop,
									handles     : 'all'
								})
								.draggable({
									handle      : coverc,
									containment : imgc,
									drag        : crop.drag_update,
									start       : opStart,
									stop        : function() {
										crop.updateView('xy');
										opStop();
									}
								});
							
							dinit();
							crop.update();
						}
					},
					rotateable = function(destroy) {
						if (destroy) {
							imgr.hide();
						}
						else {
							imgr.show();
							dinit();
						}
					},
					checkVals = function() {
						var w, h, x, y, d, q, b = '';
						
						if (mode == 'resize') {
							w = parseInt(width.val()) || 0;
							h = parseInt(height.val()) || 0;
						} else if (mode == 'crop') {
							w = parseInt(offsetX.val()) || 0;
							h = parseInt(offsetY.val()) || 0;
							x = parseInt(pointX.val()) || 0;
							y = parseInt(pointY.val()) || 0;
						} else if (mode == 'rotate') {
							w = owidth;
							h = oheight;
							d = parseInt(degree.val()) || 0;
							if (d < 0 || d > 360) {
								fm.error('Invalid rotate degree');
								return false;
							}
							if (d == 0 || d == 360) {
								fm.error('errResizeNoChange');
								return false;
							}
							b = bg.val();
						}
						q = quality? parseInt(quality.val()) : 0;
						
						if (mode != 'rotate') {
							if (w <= 0 || h <= 0) {
								fm.error('Invalid image size');
								return false;
							}
							if (w == owidth && h == oheight) {
								fm.error('errResizeNoChange');
								return false;
							}
						}
						
						return {w: w, h: h, x: x, y: y, d: d, q: q, b: b};
					},
					save = function() {
						var vals;
						
						if (vals = checkVals()) {
							dialog.elfinderdialog('close');
							self.resizeRequest({
								target : file.hash,
								width  : vals.w,
								height : vals.h,
								x      : vals.x,
								y      : vals.y,
								degree : vals.d,
								quality: vals.q,
								bg     : vals.b,
								mode   : mode
							}, file, dfrd);
						}
					},
					saveAs = function() {
						var fail = function() {
								dialogs.addClass(clsediting).fadeIn(function() {
									base.addClass(clactive);
								});
								fm.disable();
							},
							make = function() {
								self.mime = file.mime;
								self.prefix = file.name.replace(/ \d+(\.[^.]+)?$/, '$1');
								self.requestCmd = 'mkfile';
								self.nextAction = {};
								self.data = {target : file.phash};
								jQuery.proxy(fm.res('mixin', 'make'), self)()
									.done(function(data) {
										var hash, dfd;
										if (data.added && data.added.length) {
											hash = data.added[0].hash;
											dfd = fm.api < 2.1032? fm.url(file.hash, { async: true, temporary: true }) : null;
											jQuery.when(dfd).done(function(url) {
												fm.request({
													options : {type : 'post'},
													data : {
														cmd     : 'put',
														target  : hash,
														encoding: dfd? 'scheme' : 'hash', 
														content : dfd? fm.convAbsUrl(url) : file.hash
													},
													notify : {type : 'copy', cnt : 1},
													syncOnFail : true
												})
												.fail(fail)
												.done(function(data) {
													data = fm.normalize(data);
													fm.updateCache(data);
													file = fm.file(hash);
													data.changed && data.changed.length && fm.change(data);
													base.show().find('.elfinder-dialog-title').html(fm.escape(file.name));
													save();
													dialogs.fadeIn();
												});
											}).fail(fail);
										} else {
											fail();
										}
									})
									.fail(fail)
									.always(function() {
										delete self.mime;
										delete self.prefix;
										delete self.nextAction;
										delete self.data;
									});
								fm.trigger('unselectfiles', { files: [ file.hash ] });
							},
							reqOpen = null,
							dialogs;
						
						if (checkVals()) {
							dialogs = fmnode.children('.' + self.dialogClass + ':visible').removeClass(clsediting).fadeOut();
							base.removeClass(clactive);
							fm.enable();
							if (fm.searchStatus.state < 2 && file.phash !== fm.cwd().hash) {
								reqOpen = fm.exec('open', [file.phash], {thash: file.phash});
							}
							
							jQuery.when([reqOpen]).done(function() {
								reqOpen? fm.one('cwdrender', make) : make();
							}).fail(fail);
						}
					},
					buttons = {},
					hline   = 'elfinder-resize-handle-hline',
					vline   = 'elfinder-resize-handle-vline',
					rpoint  = 'elfinder-resize-handle-point',
					src     = fm.openUrl(file.hash),
					canvSrc = fm.openUrl(file.hash, !fm.isSameOrigin(src)),
					sizeImg = quality? jQuery('<img>').attr('crossorigin', fm.isCORS? 'use-credentials' : '').attr('src', canvSrc).on('load', function() {
						try {
							var canv = document.createElement('canvas');
							sizeImg.data('canvas', canv).data('ctx', canv.getContext('2d'));
							jpgCalc();
						} catch(e) {
							sizeImg.removeData('canvas').removeData('ctx');
						}
					}) : null,
					jpgCalc = function() {
						control.find('input.elfinder-resize-quality:visible').trigger('change');
					},
					dinit   = function(e) {
						if (base.hasClass('elfinder-dialog-minimized') || base.is(':hidden')) {
							return;
						}
						
						preset.hide();
						presetc.hide();
						
						var win   = fm.options.dialogContained? fmnode : jQuery(window),
							winH  = win.height(),
							winW  = win.width(),
							presW = 'auto',
							presIn = true,
							dw, ctrW, prvW;
						
						base.width(Math.min(dialogWidth, winW - 30));
						preview.attr('style', '');
						if (owidth && oheight) {
							pwidth  = preview.width()  - (rhandle.outerWidth()  - rhandle.width());
							pheight = preview.height() - (rhandle.outerHeight() - rhandle.height());
							resize.updateView(owidth, oheight);
						}
						ctrW  = dialog.find('div.elfinder-resize-control').width();
						prvW  = preview.width();
						
						dw = dialog.width() - 20;
						if (prvW > dw) {
							preview.width(dw);
							presIn = false;
						} else if ((dw - prvW) < ctrW) {
							if (winW > winH) {
								preview.width(dw - ctrW - 20);
							} else {
								preview.css({ float: 'none', marginLeft: 'auto', marginRight: 'auto'});
								presIn = false;
							}
						}
						if (presIn) {
							presW = ctrW;
						}
						pwidth  = preview.width()  - (rhandle.outerWidth()  - rhandle.width());
						if (fmnode.hasClass('elfinder-fullscreen')) {
							if (base.height() > winH) {
								winH -= 2;
								preview.height(winH - base.height() + preview.height());
								base.css('top', 0 - fmnode.offset().top);
							}
						} else {
							winH -= 30;
							(preview.height() > winH) && preview.height(winH);
						}
						pheight = preview.height() - (rhandle.outerHeight() - rhandle.height());
						if (owidth && oheight) {
							setupimg();
						}
						if (img.height() && preview.height() > img.height() + 20) {
							preview.height(img.height() + 20);
							pheight = preview.height() - (rhandle.outerHeight() - rhandle.height());
							setuprimg();
						}
						
						preset.css('width', presW).show();
						presetc.css('width', presW).show();
						if (!presetc.children('span.elfinder-resize-preset:visible').length) {
							presetc.hide();
						}
					},
					preset = (function() {
						var sets = jQuery('<fieldset class="elfinder-resize-preset-container">').append(jQuery('<legend>').html(fm.i18n('presets'))).hide(),
							hasC;
						jQuery.each(presetSize, function(i, s) {
							if (s.length === 2) {
								hasC = true;
								sets.append(jQuery('<span class="elfinder-resize-preset"/>')
									.data('s', s)
									.text(s[0]+'x'+s[1])
									.button()
								);
							}
						});
						if (!hasC) {
							return jQuery();
						} else {
							return sets;
						}
					})(),
					presetc = preset.clone(true),
					useSaveAs = fm.uploadMimeCheck(file.mime, file.phash),
					dMinBtn, base;
				
				uiresize.append(
					jQuery(row).append(jQuery(label).text(fm.i18n('width')), width),
					jQuery(row).append(jQuery(label).text(fm.i18n('height')), height, jQuery('<div class="elfinder-resize-whctrls">').append(constr, reset)),
					(quality? jQuery(row).append(jQuery(label).text(fm.i18n('quality')), quality, jQuery('<span/>')) : jQuery()),
					(isJpeg? jQuery(row).append(jQuery(label).text(fm.i18n('8pxgrid')).addClass('elfinder-resize-grid8'), grid8px) : jQuery()),
					jQuery(row).append(jQuery(label).text(fm.i18n('scale')), uiprop),
					jQuery(row).append(preset)
				);

				if (api2) {
					uicrop.append(
						jQuery(row).append(jQuery(label).text('X'), pointX),
						jQuery(row).append(jQuery(label).text('Y')).append(pointY),
						jQuery(row).append(jQuery(label).text(fm.i18n('width')), offsetX),
						jQuery(row).append(jQuery(label).text(fm.i18n('height')), offsetY, jQuery('<div class="elfinder-resize-whctrls">').append(constrc, reset.clone(true))),
						(quality? jQuery(row).append(jQuery(label).text(fm.i18n('quality')), quality.clone(true), jQuery('<span/>')) : jQuery()),
						(isJpeg? jQuery(row).append(jQuery(label).text(fm.i18n('8pxgrid')).addClass('elfinder-resize-grid8')) : jQuery()),
						jQuery(row).append(presetc)
					);
					
					uirotate.append(
						jQuery(row).addClass('elfinder-resize-degree').append(
							jQuery(label).text(fm.i18n('rotate')),
							degree,
							jQuery('<span/>').text(fm.i18n('degree')),
							jQuery('<div/>').append(uideg270, uideg90)[ctrgrup]()
						),
						jQuery(row).css('height', '20px').append(uidegslider),
						((quality)? jQuery(row)[losslessRotate < 1? 'show' : 'hide']().addClass('elfinder-resize-quality').append(
							jQuery(label).text(fm.i18n('quality')),
							quality.clone(true),
							jQuery('<span/>')) : jQuery()
						),
						jQuery(row).append(jQuery(label).text(fm.i18n('bgcolor')), bg, picker, reseter),
						jQuery(row).css('height', '20px').append(pallet)
					);
					uideg270.on('click', function() {
						rdegree = rdegree - 90;
						rotate.update(rdegree);
					});
					uideg90.on('click', function(){
						rdegree = rdegree + 90;
						rotate.update(rdegree);
					});
				}
				
				dialog.append(uitype).on('resize', function(e){
					e.stopPropagation();
				});

				if (api2) {
					control.append(/*jQuery(row), */uiresize, uicrop.hide(), uirotate.hide());
				} else {
					control.append(/*jQuery(row), */uiresize);
				}
				
				rhandle.append('<div class="'+hline+' '+hline+'-top"/>',
					'<div class="'+hline+' '+hline+'-bottom"/>',
					'<div class="'+vline+' '+vline+'-left"/>',
					'<div class="'+vline+' '+vline+'-right"/>',
					'<div class="'+rpoint+' '+rpoint+'-e"/>',
					'<div class="'+rpoint+' '+rpoint+'-se"/>',
					'<div class="'+rpoint+' '+rpoint+'-s"/>');
					
				preview.append(spinner).append(rhandle.hide()).append(img.hide());

				if (api2) {
					rhandlec.css('position', 'absolute')
						.append('<div class="'+hline+' '+hline+'-top"/>',
						'<div class="'+hline+' '+hline+'-bottom"/>',
						'<div class="'+vline+' '+vline+'-left"/>',
						'<div class="'+vline+' '+vline+'-right"/>',
						'<div class="'+rpoint+' '+rpoint+'-n"/>',
						'<div class="'+rpoint+' '+rpoint+'-e"/>',
						'<div class="'+rpoint+' '+rpoint+'-s"/>',
						'<div class="'+rpoint+' '+rpoint+'-w"/>',
						'<div class="'+rpoint+' '+rpoint+'-ne"/>',
						'<div class="'+rpoint+' '+rpoint+'-se"/>',
						'<div class="'+rpoint+' '+rpoint+'-sw"/>',
						'<div class="'+rpoint+' '+rpoint+'-nw"/>');

					preview.append(basec.css('position', 'absolute').hide().append(imgc, rhandlec.append(coverc)));
					
					preview.append(imgr.hide());
				}
				
				preview.css('overflow', 'hidden');
				
				dialog.append(preview, control);
				
				buttons[fm.i18n('btnApply')] = save;
				if (useSaveAs) {
					buttons[fm.i18n('btnSaveAs')] = function() { requestAnimationFrame(saveAs); };
				}
				buttons[fm.i18n('btnCancel')] = function() { dialog.elfinderdialog('close'); };
				
				dialog.find('input,button').addClass('elfinder-tabstop');
				
				base = self.fmDialog(dialog, {
					title          : fm.escape(file.name),
					width          : dialogWidth,
					resizable      : false,
					buttons        : buttons,
					open           : function() {
						var substituteImg = (fm.option('substituteImg', file.hash) && file.size > options.dimSubImgSize)? true : false,
							hasSize = (file.width && file.height)? true : false;
						dialog.parent().css('overflow', 'hidden');
						dMinBtn = base.find('.ui-dialog-titlebar .elfinder-titlebar-minimize').hide();
						fm.bind('resize', dinit);
						img.attr('src', src);
						imgc.attr('src', src);
						imgr.attr('src', src);
						if (api2) {
							imgr.on('mousedown touchstart', rotate.start)
								.on('touchend', rotate.stop);
							base.on('mouseup', rotate.stop);
						}
						if (hasSize && !substituteImg) {
							return init();
						}
						if (file.size > (options.getDimThreshold || 0)) {
							dimreq = fm.request({
								data : {cmd : 'dim', target : file.hash, substitute : (substituteImg? 400 : '')},
								preventDefault : true
							})
							.done(function(data) {
								if (data.dim) {
									var dim = data.dim.split('x');
									file.width = dim[0];
									file.height = dim[1];
									setdim(dim);
									if (data.url) {
										img.attr('src', data.url);
										imgc.attr('src', data.url);
										imgr.attr('src', data.url);
									}
									return init();
								}
							});
						} else if (hasSize) {
							return init();
						}
					},
					close          : function() {
						if (api2) {
							imgr.off('mousedown touchstart', rotate.start)
								.off('touchend', rotate.stop);
							jQuery(document).off('mouseup', rotate.stop);
						}
						fm.unbind('resize', dinit);
						jQuery(this).elfinderdialog('destroy');
					},
					resize         : function(e, data) {
						if (data && data.minimize === 'off') {
							dinit();
						}
					}
				}).attr('id', id).closest('.ui-dialog').addClass(clsediting);
				
				// for IE < 9 dialog mising at open second+ time.
				if (fm.UA.ltIE8) {
					jQuery('.elfinder-dialog').css('filter', '');
				}
				
				coverc.css({ 'opacity': 0.2, 'background-color': '#fff', 'position': 'absolute'}),
				rhandlec.css('cursor', 'move');
				rhandlec.find('.elfinder-resize-handle-point').css({
					'background-color' : '#fff',
					'opacity': 0.5,
					'border-color':'#000'
				});

				if (! api2) {
					uitype.find('.api2').remove();
				}
				
				control.find('input,select').prop('disabled', true);
				control.find('input.elfinder-resize-quality')
					.next('span').addClass('elfinder-resize-jpgsize').attr('title', fm.i18n('roughFileSize'));

			},
			
			id, dialog
			;
			

		if (!files.length || files[0].mime.indexOf('image/') === -1) {
			return dfrd.reject();
		}
		
		id = 'resize-'+fm.namespace+'-'+files[0].hash;
		dialog = fmnode.find('#'+id);
		
		if (dialog.length) {
			dialog.elfinderdialog('toTop');
			return dfrd.resolve();
		}
		
		open(files[0], id);
			
		return dfrd;
	};

};

(function ($) {
	
	var findProperty = function (styleObject, styleArgs) {
		var i = 0 ;
		for( i in styleArgs) {
	        if (typeof styleObject[styleArgs[i]] != 'undefined') 
	        	return styleArgs[i];
		}
		styleObject[styleArgs[i]] = '';
	    return styleArgs[i];
	};
	
	jQuery.cssHooks.rotate = {
		get: function(elem, computed, extra) {
			return jQuery(elem).rotate();
		},
		set: function(elem, value) {
			jQuery(elem).rotate(value);
			return value;
		}
	};
	jQuery.cssHooks.transform = {
		get: function(elem, computed, extra) {
			var name = findProperty( elem.style , 
				['WebkitTransform', 'MozTransform', 'OTransform' , 'msTransform' , 'transform'] );
			return elem.style[name];
		},
		set: function(elem, value) {
			var name = findProperty( elem.style , 
				['WebkitTransform', 'MozTransform', 'OTransform' , 'msTransform' , 'transform'] );
			elem.style[name] = value;
			return value;
		}
	};
	
	jQuery.fn.rotate = function(val) {
		var r;
		if (typeof val == 'undefined') {
			if (!!window.opera) {
				r = this.css('transform').match(/rotate\((.*?)\)/);
				return  ( r && r[1])?
					Math.round(parseFloat(r[1]) * 180 / Math.PI) : 0;
			} else {
				r = this.css('transform').match(/rotate\((.*?)\)/);
				return  ( r && r[1])? parseInt(r[1]) : 0;
			}
		}
		this.css('transform', 
			this.css('transform').replace(/none|rotate\(.*?\)/, '') + 'rotate(' + parseInt(val) + 'deg)');
		return this;
	};

	jQuery.fx.step.rotate  = function(fx) {
		if ( fx.state == 0 ) {
			fx.start = jQuery(fx.elem).rotate();
			fx.now = fx.start;
		}
		jQuery(fx.elem).rotate(fx.now);
	};

	if (typeof window.addEventListener == "undefined" && typeof document.getElementsByClassName == "undefined") { // IE & IE<9
		var GetAbsoluteXY = function(element) {
			var pnode = element;
			var x = pnode.offsetLeft;
			var y = pnode.offsetTop;
			
			while ( pnode.offsetParent ) {
				pnode = pnode.offsetParent;
				if (pnode != document.body && pnode.currentStyle['position'] != 'static') {
					break;
				}
				if (pnode != document.body && pnode != document.documentElement) {
					x -= pnode.scrollLeft;
					y -= pnode.scrollTop;
				}
				x += pnode.offsetLeft;
				y += pnode.offsetTop;
			}
			
			return { x: x, y: y };
		};
		
		var StaticToAbsolute = function (element) {
			if ( element.currentStyle['position'] != 'static') {
				return ;
			}

			var xy = GetAbsoluteXY(element);
			element.style.position = 'absolute' ;
			element.style.left = xy.x + 'px';
			element.style.top = xy.y + 'px';
		};

		var IETransform = function(element,transform){

			var r;
			var m11 = 1;
			var m12 = 1;
			var m21 = 1;
			var m22 = 1;

			if (typeof element.style['msTransform'] != 'undefined'){
				return true;
			}

			StaticToAbsolute(element);

			r = transform.match(/rotate\((.*?)\)/);
			var rotate =  ( r && r[1])	?	parseInt(r[1])	:	0;

			rotate = rotate % 360;
			if (rotate < 0) rotate = 360 + rotate;

			var radian= rotate * Math.PI / 180;
			var cosX =Math.cos(radian);
			var sinY =Math.sin(radian);

			m11 *= cosX;
			m12 *= -sinY;
			m21 *= sinY;
			m22 *= cosX;

			element.style.filter =  (element.style.filter || '').replace(/progid:DXImageTransform\.Microsoft\.Matrix\([^)]*\)/, "" ) +
				("progid:DXImageTransform.Microsoft.Matrix(" + 
					 "M11=" + m11 + 
					",M12=" + m12 + 
					",M21=" + m21 + 
					",M22=" + m22 + 
					",FilterType='bilinear',sizingMethod='auto expand')") 
				;

	  		var ow = parseInt(element.style.width || element.width || 0 );
	  		var oh = parseInt(element.style.height || element.height || 0 );

			radian = rotate * Math.PI / 180;
			var absCosX =Math.abs(Math.cos(radian));
			var absSinY =Math.abs(Math.sin(radian));

			var dx = (ow - (ow * absCosX + oh * absSinY)) / 2;
			var dy = (oh - (ow * absSinY + oh * absCosX)) / 2;

			element.style.marginLeft = Math.floor(dx) + "px";
			element.style.marginTop  = Math.floor(dy) + "px";

			return(true);
		};
		
		var transform_set = jQuery.cssHooks.transform.set;
		jQuery.cssHooks.transform.set = function(elem, value) {
			transform_set.apply(this, [elem, value] );
			IETransform(elem,value);
			return value;
		};
	}

})(jQuery);


/*
 * File: /js/commands/restore.js
 */

/**
 * @class  elFinder command "restore"
 * Restore items from the trash
 *
 * @author Naoki Sawada
 **/
(elFinder.prototype.commands.restore = function() {
	"use strict";
	var self = this,
		fm = this.fm,
		fakeCnt = 0,
		getFilesRecursively = function(files) {
			var dfd = jQuery.Deferred(),
				dirs = [],
				results = [],
				reqs = [],
				phashes = [],
				getFile;
			
			dfd._xhrReject = function() {
				jQuery.each(reqs, function() {
					this && this.reject && this.reject();
				});
				getFile && getFile._xhrReject();
			};
			
			jQuery.each(files, function(i, f) {
				f.mime === 'directory'? dirs.push(f) : results.push(f);
			});
			
			if (dirs.length) {
				jQuery.each(dirs, function(i, d) {
					reqs.push(fm.request({
						data : {cmd  : 'open', target : d.hash},
						preventDefault : true,
						asNotOpen : true
					}));
					phashes[i] = d.hash;
				});
				jQuery.when.apply($, reqs).fail(function() {
					dfd.reject();
				}).done(function() {
					var items = [];
					jQuery.each(arguments, function(i, r) {
						var files;
						if (r.files) {
							if (r.files.length) {
								items = items.concat(r.files);
							} else {
								items.push({
									hash: 'fakefile_' + (fakeCnt++),
									phash: phashes[i],
									mime: 'fakefile',
									name: 'fakefile',
									ts: 0
								});
							}
						}
					});
					fm.cache(items);
					getFile = getFilesRecursively(items).done(function(res) {
						results = results.concat(res);
						dfd.resolve(results);
					});
				});
			} else {
				dfd.resolve(results);
			}
			
			return dfd;
		},
		restore = function(dfrd, files, targets, ops) {
			var rHashes = {},
				others = [],
				found = false,
				dirs = [],
				opts = ops || {},
				id = +new Date(),
				tm, getFile;
			
			fm.lockfiles({files : targets});
			
			dirs = jQuery.map(files, function(f) {
				return f.mime === 'directory'? f.hash : null;
			});
			
			dfrd.done(function() {
				dirs && fm.exec('rm', dirs, {forceRm : true, quiet : true});
			}).always(function() {
				fm.unlockfiles({files : targets});
			});
			
			tm = setTimeout(function() {
				fm.notify({type : 'search', id : id, cnt : 1, hideCnt : true, cancel : function() {
					getFile && getFile._xhrReject();
					dfrd.reject();
				}});
			}, fm.notifyDelay);

			fakeCnt = 0;
			getFile = getFilesRecursively(files).always(function() {
				tm && clearTimeout(tm);
				fm.notify({type : 'search', id: id, cnt : -1, hideCnt : true});
			}).fail(function() {
				dfrd.reject('errRestore', 'errFileNotFound');
			}).done(function(res) {
				var errFolderNotfound = ['errRestore', 'errFolderNotFound'],
					dirTop = '';
				
				if (res.length) {
					jQuery.each(res, function(i, f) {
						var phash = f.phash,
							pfile,
							srcRoot, tPath;
						while(phash) {
							if (srcRoot = fm.trashes[phash]) {
								if (! rHashes[srcRoot]) {
									if (found) {
										// Keep items of other trash
										others.push(f.hash);
										return null; // continue jQuery.each
									}
									rHashes[srcRoot] = {};
									found = true;
								}
		
								tPath = fm.path(f.hash).substr(fm.path(phash).length).replace(/\\/g, '/');
								tPath = tPath.replace(/\/[^\/]+?$/, '');
								if (tPath === '') {
									tPath = '/';
								}
								if (!rHashes[srcRoot][tPath]) {
									rHashes[srcRoot][tPath] = [];
								}
								if (f.mime === 'fakefile') {
									fm.updateCache({removed:[f.hash]});
								} else {
									rHashes[srcRoot][tPath].push(f.hash);
								}
								if (!dirTop || dirTop.length > tPath.length) {
									dirTop = tPath;
								}
								break;
							}
							
							// Go up one level for next check
							pfile = fm.file(phash);
							
							if (!pfile) {
								phash = false;
								// Detection method for search results
								jQuery.each(fm.trashes, function(ph) {
									var file = fm.file(ph),
										filePath = fm.path(ph);
									if ((!file.volumeid || f.hash.indexOf(file.volumeid) === 0) && fm.path(f.hash).indexOf(filePath) === 0) {
										phash = ph;
										return false;
									}
								});
							} else {
								phash = pfile.phash;
							}
						}
					});
					if (found) {
						jQuery.each(rHashes, function(src, dsts) {
							var dirs = Object.keys(dsts),
								cnt = dirs.length;
							fm.request({
								data   : {cmd  : 'mkdir', target : src, dirs : dirs}, 
								notify : {type : 'chkdir', cnt : cnt},
								preventFail : true
							}).fail(function(error) {
								dfrd.reject(error);
								fm.unlockfiles({files : targets});
							}).done(function(data) {
								var cmdPaste, hashes;
								
								if (hashes = data.hashes) {
									cmdPaste = fm.getCommand('paste');
									if (cmdPaste) {
										// wait until file cache made
										fm.one('mkdirdone', function() {
											var hasErr = false;
											jQuery.each(dsts, function(dir, files) {
												if (hashes[dir]) {
													if (files.length) {
														if (fm.file(hashes[dir])) {
															fm.clipboard(files, true);
															fm.exec('paste', [ hashes[dir] ], {_cmd : 'restore', noToast : (opts.noToast || dir !== dirTop)})
															.done(function(data) {
																if (data && (data.error || data.warning)) {
																	hasErr = true;
																}
															})
															.fail(function() {
																hasErr = true;
															})
															.always(function() {
																if (--cnt < 1) {
																	dfrd[hasErr? 'reject' : 'resolve']();
																	if (others.length) {
																		// Restore items of other trash
																		fm.exec('restore', others);
																	}
																}
															});
														} else {
															dfrd.reject(errFolderNotfound);
														}
													} else {
														if (--cnt < 1) {
															dfrd.resolve();
															if (others.length) {
																// Restore items of other trash
																fm.exec('restore', others);
															}
														}
													}
												}
											});
										});
									} else {
										dfrd.reject(['errRestore', 'errCmdNoSupport', '(paste)']);
									}
								} else {
									dfrd.reject(errFolderNotfound);
								}
							});
						});
					} else {
						dfrd.reject(errFolderNotfound);
					}
				} else {
					dfrd.reject('errFileNotFound');
					dirs && fm.exec('rm', dirs, {forceRm : true, quiet : true});
				}
			});
		};
	
	// for to be able to overwrite
	this.restore = restore;

	this.linkedCmds = ['copy', 'paste', 'mkdir', 'rm'];
	this.updateOnSelect = false;
	
	this.init = function() {
		// re-assign for extended command
		self = this;
		fm = this.fm;
	};

	this.getstate = function(sel, e) {
		sel = sel || fm.selected();
		return sel.length && jQuery.grep(sel, function(h) {var f = fm.file(h); return f && ! f.locked && ! fm.isRoot(f)? true : false; }).length == sel.length
			? 0 : -1;
	};
	
	this.exec = function(hashes, opts) {
		var dfrd   = jQuery.Deferred()
				.fail(function(error) {
					error && fm.error(error);
				}),
			files  = self.files(hashes);

		if (! files.length) {
			return dfrd.reject();
		}
		
		jQuery.each(files, function(i, file) {
			if (fm.isRoot(file)) {
				return !dfrd.reject(['errRestore', file.name]);
			}
			if (file.locked) {
				return !dfrd.reject(['errLocked', file.name]);
			}
		});

		if (dfrd.state() === 'pending') {
			this.restore(dfrd, files, hashes, opts);
		}
			
		return dfrd;
	};

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/rm.js
 */

/**
 * @class  elFinder command "rm"
 * Delete files
 *
 * @author Dmitry (dio) Levashov
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.rm = function() {
	"use strict";
	var self = this,
		fm = this.fm,
		tpl = '<div class="ui-helper-clearfix elfinder-rm-title"><span class="elfinder-cwd-icon {class} ui-corner-all"/>{title}<div class="elfinder-rm-desc">{desc}</div></div>',
		confirm = function(dfrd, targets, files, tHash, addTexts) {
			var cnt = targets.length,
				cwd = fm.cwd().hash,
				descs = [],
				spinner = fm.i18n('calc') + '<span class="elfinder-spinner"/>',
				dialog, text, tmb, size, f, fname;
			
			if (cnt > 1) {
				size = 0;
				jQuery.each(files, function(h, f) { 
					if (f.size && f.size != 'unknown' && f.mime !== 'directory') {
						var s = parseInt(f.size);
						if (s >= 0 && size >= 0) {
							size += s;
						}
					} else {
						size = 'unknown';
						return false;
					}
				});
				getSize = (size === 'unknown');
				descs.push(fm.i18n('size')+': '+(getSize? spinner : fm.formatSize(size)));
				text = [jQuery(tpl.replace('{class}', 'elfinder-cwd-icon-group').replace('{title}', '<strong>' + fm.i18n('items')+ ': ' + cnt + '</strong>').replace('{desc}', descs.join('<br>')))];
			} else {
				f = files[0];
				tmb = fm.tmb(f);
				getSize = (f.mime === 'directory');
				descs.push(fm.i18n('size')+': '+(getSize? spinner : fm.formatSize(f.size)));
				descs.push(fm.i18n('modify')+': '+fm.formatDate(f));
				fname = fm.escape(f.i18 || f.name).replace(/([_.])/g, '&#8203;$1');
				text = [jQuery(tpl.replace('{class}', fm.mime2class(f.mime)).replace('{title}', '<strong>' + fname + '</strong>').replace('{desc}', descs.join('<br>')))];
			}
			
			if (addTexts) {
				text = text.concat(addTexts);
			}
			
			text.push(tHash? 'confirmTrash' : 'confirmRm');
			
			dialog = fm.confirm({
				title  : self.title,
				text   : text,
				accept : {
					label    : 'btnRm',
					callback : function() {  
						if (tHash) {
							self.toTrash(dfrd, targets, tHash);
						} else {
							remove(dfrd, targets);
						}
					}
				},
				cancel : {
					label    : 'btnCancel',
					callback : function() {
						fm.unlockfiles({files : targets});
						if (targets.length === 1 && fm.file(targets[0]).phash !== cwd) {
							fm.select({selected : targets});
						} else {
							fm.selectfiles({files : targets});
						}
						dfrd.reject();
					}
				}
			});
			// load thumbnail
			if (tmb) {
				jQuery('<img/>')
					.on('load', function() { dialog.find('.elfinder-cwd-icon').addClass(tmb.className).css('background-image', "url('"+tmb.url+"')"); })
					.attr('src', tmb.url);
			}
			
			if (getSize) {
				getSize = fm.getSize(jQuery.map(files, function(f) { return f.mime === 'directory'? f.hash : null; })).done(function(data) {
					dialog.find('span.elfinder-spinner').parent().html(fm.i18n('size')+': '+data.formated);
				}).fail(function() {
					dialog.find('span.elfinder-spinner').parent().html(fm.i18n('size')+': '+fm.i18n('unknown'));
				}).always(function() {
					getSize = false;
				});
			}
		},
		toTrash = function(dfrd, targets, tHash) {
			var dsts = {},
				itemCnt = targets.length,
				maxCnt = self.options.toTrashMaxItems,
				checkDirs = [],
				reqDfd = jQuery.Deferred(),
				req, dirs, cnt;
			
			if (itemCnt > maxCnt) {
				self.confirm(dfrd, targets, self.files(targets), null, [fm.i18n('tooManyToTrash')]);
				return;
			}
			
			// Directory preparation preparation and directory enumeration
			jQuery.each(targets, function(i, h) {
				var file = fm.file(h),
					path = fm.path(h).replace(/\\/g, '/'),
					m = path.match(/^[^\/]+?(\/(?:[^\/]+?\/)*)[^\/]+?$/);
				
				if (file) {
					if (m) {
						m[1] = m[1].replace(/(^\/.*?)\/?$/, '$1');
						if (! dsts[m[1]]) {
							dsts[m[1]] = [];
						}
						dsts[m[1]].push(h);
					}
					if (file.mime === 'directory') {
						checkDirs.push(h);
					}
				}
			});
			
			// Check directory information
			if (checkDirs.length) {
				req = fm.request({
					data : {cmd : 'size', targets : checkDirs},
					notify : {type: 'readdir', cnt: 1, hideCnt: true},
					preventDefault : true
				}).done(function(data) {
					var cnt = 0;
					data.fileCnt && (cnt += parseInt(data.fileCnt));
					data.dirCnt && (cnt += parseInt(data.dirCnt));
					reqDfd[cnt > maxCnt ? 'reject' : 'resolve']();
				}).fail(function() {
					reqDfd.reject();
				});
				setTimeout(function() {
					var xhr = (req && req.xhr)? req.xhr : null;
					if (xhr && xhr.state() == 'pending') {
						req.syncOnFail(false);
						req.reject();
						reqDfd.reject();
					}
				}, self.options.infoCheckWait * 1000);
			} else {
				reqDfd.resolve();
			}
			
			// Directory creation and paste command execution
			reqDfd.done(function() {
				dirs = Object.keys(dsts);
				cnt = dirs.length;
				if (cnt) {
					fm.request({
						data   : {cmd  : 'mkdir', target : tHash, dirs : dirs}, 
						notify : {type : 'chkdir', cnt : cnt},
						preventFail : true
					})
					.fail(function(error) {
						dfrd.reject(error);
						fm.unlockfiles({files : targets});
					})
					.done(function(data) {
						var margeRes = function(data, phash, reqData) {
								var undo, prevUndo, redo, prevRedo;
								jQuery.each(data, function(k, v) {
									if (Array.isArray(v)) {
										if (res[k]) {
											res[k] = res[k].concat(v);
										} else {
											res[k] = v;
										}
									}
								});
								if (data.sync) {
									res.sync = 1;
								}
								if (data.added && data.added.length) {
									undo = function() {
										var targets = [],
											dirs    = jQuery.map(data.added, function(f) { return f.mime === 'directory'? f.hash : null; });
										jQuery.each(data.added, function(i, f) {
											if (jQuery.inArray(f.phash, dirs) === -1) {
												targets.push(f.hash);
											}
										});
										return fm.exec('restore', targets, {noToast: true});
									};
									redo = function() {
										return fm.request({
											data   : reqData,
											notify : {type : 'redo', cnt : targets.length}
										});
									};
									if (res.undo) {
										prevUndo = res.undo;
										res.undo = function() {
											undo();
											prevUndo();
										};
									} else {
										res.undo = undo;
									}
									if (res.redo) {
										prevRedo = res.redo;
										res.redo = function() {
											redo();
											prevRedo();
										};
									} else {
										res.redo = redo;
									}
								}
							},
							err = ['errTrash'],
							res = {},
							hasNtf = function() {
								return fm.ui.notify.children('.elfinder-notify-trash').length;
							},
							hashes, tm, prg, prgSt;
						
						if (hashes = data.hashes) {
							prg = 1 / cnt * 100;
							prgSt = cnt === 1? 100 : 5;
							tm = setTimeout(function() {
								fm.notify({type : 'trash', cnt : 1, hideCnt : true, progress : prgSt});
							}, fm.notifyDelay);
							jQuery.each(dsts, function(dir, files) {
								var phash = fm.file(files[0]).phash,
									reqData;
								if (hashes[dir]) {
									reqData = {cmd : 'paste', dst : hashes[dir], targets : files, cut : 1};
									fm.request({
										data : reqData,
										preventDefault : true
									})
									.fail(function(error) {
										if (error) {
											err = err.concat(error);
										}
									})
									.done(function(data) {
										data = fm.normalize(data);
										fm.updateCache(data);
										margeRes(data, phash, reqData);
										if (data.warning) {
											err = err.concat(data.warning);
											delete data.warning;
										}
										// fire some event to update cache/ui
										data.removed && data.removed.length && fm.remove(data);
										data.added   && data.added.length   && fm.add(data);
										data.changed && data.changed.length && fm.change(data);
										// fire event with command name
										fm.trigger('paste', data);
										// fire event with command name + 'done'
										fm.trigger('pastedone');
										// force update content
										data.sync && fm.sync();
									})
									.always(function() {
										var hashes = [], addTexts, end = 2;
										if (hasNtf()) {
											fm.notify({type : 'trash', cnt : 0, hideCnt : true, progress : prg});
										} else {
											prgSt+= prg;
										}
										if (--cnt < 1) {
											tm && clearTimeout(tm);
											hasNtf() && fm.notify({type : 'trash', cnt  : -1});
											fm.unlockfiles({files : targets});
											if (Object.keys(res).length) {
												if (err.length > 1) {
													if (res.removed || res.removed.length) {
														hashes = jQuery.grep(targets, function(h) {
															return jQuery.inArray(h, res.removed) === -1? true : false;
														});
													}
													if (hashes.length) {
														if (err.length > end) {
															end = (fm.messages[err[end-1]] || '').indexOf('$') === -1? end : end + 1;
														}
														dfrd.reject();
														fm.exec('rm', hashes, { addTexts: err.slice(0, end), forceRm: true });
													} else {
														fm.error(err);
													}
												}
												res._noSound = true;
												if (res.undo && res.redo) {
													res.undo = {
														cmd : 'trash',
														callback : res.undo,
													};
													res.redo = {
														cmd : 'trash',
														callback : res.redo
													};
												}
												dfrd.resolve(res);
											} else {
												dfrd.reject(err);
											}
										}
									});
								}
							});
						} else {
							dfrd.reject('errFolderNotFound');
							fm.unlockfiles({files : targets});
						}
					});
				} else {
					dfrd.reject(['error', 'The folder hierarchy to be deleting can not be determined.']);
					fm.unlockfiles({files : targets});
				}
			}).fail(function() {
				self.confirm(dfrd, targets, self.files(targets), null, [fm.i18n('tooManyToTrash')]);
			});
		},
		remove = function(dfrd, targets, quiet) {
			var notify = quiet? {} : {type : 'rm', cnt : targets.length};
			fm.request({
				data   : {cmd  : 'rm', targets : targets}, 
				notify : notify,
				preventFail : true
			})
			.fail(function(error) {
				dfrd.reject(error);
			})
			.done(function(data) {
				if (data.error || data.warning) {
					data.sync = true;
				}
				dfrd.resolve(data);
			})
			.always(function() {
				fm.unlockfiles({files : targets});
			});
		},
		getTHash = function(targets) {
			var thash = null,
				root1st;
			
			if (targets && targets.length) {
				if (targets.length > 1 && fm.searchStatus.state === 2) {
					root1st = fm.file(fm.root(targets[0])).volumeid;
					if (!jQuery.grep(targets, function(h) { return h.indexOf(root1st) !== 0? true : false ; }).length) {
						thash = fm.option('trashHash', targets[0]);
					}
				} else {
					thash = fm.option('trashHash', targets[0]);
				}
			}
			return thash;
		},
		getSize = false;
	
	// for to be able to overwrite
	this.confirm = confirm;
	this.toTrash = toTrash;
	this.remove = remove;

	this.syncTitleOnChange = true;
	this.updateOnSelect = false;
	this.shortcuts = [{
		pattern     : 'delete ctrl+backspace shift+delete'
	}];
	this.value = 'rm';
	
	this.init = function() {
		// re-assign for extended command
		self = this;
		fm = this.fm;
		// bind function of change
		self.change(function() {
			var targets;
			delete self.extra;
			self.title = fm.i18n('cmd' + self.value);
			self.className = self.value;
			self.button && self.button.children('span.elfinder-button-icon')[self.value === 'trash'? 'addClass' : 'removeClass']('elfinder-button-icon-trash');
			if (self.value === 'trash') {
				self.extra = {
					icon: 'rm',
					node: jQuery('<span/>')
						.attr({title: fm.i18n('cmdrm')})
						.on('ready', function(e, data) {
							targets = data.targets;
						})
						.on('click touchstart', function(e){
							if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
								return;
							}
							e.stopPropagation();
							e.preventDefault();
							fm.getUI().trigger('click'); // to close the context menu immediately
							fm.exec('rm', targets, {_userAction: true, forceRm : true});
						})
				};
			}
		});
	};
	
	this.getstate = function(select) {
		var sel   = this.hashes(select);
		
		return sel.length && jQuery.grep(sel, function(h) { var f = fm.file(h); return f && ! f.locked && ! fm.isRoot(f)? true : false; }).length == sel.length
			? 0 : -1;
	};
	
	this.exec = function(hashes, cOpts) {
		var opts   = cOpts || {},
			dfrd   = jQuery.Deferred()
				.always(function() {
					if (getSize && getSize.state && getSize.state() === 'pending') {
						getSize.reject();
					}
				})
				.fail(function(error) {
					error && fm.error(error);
				}).done(function(data) {
					!opts.quiet && !data._noSound && data.removed && data.removed.length && fm.trigger('playsound', {soundFile : 'rm.wav'});
				}),
			files  = self.files(hashes),
			cnt    = files.length,
			tHash  = null,
			addTexts = opts.addTexts? opts.addTexts : null,
			forceRm = opts.forceRm,
			quiet = opts.quiet,
			targets;

		if (! cnt) {
			return dfrd.reject();
		}
		
		jQuery.each(files, function(i, file) {
			if (fm.isRoot(file)) {
				return !dfrd.reject(['errRm', file.name, 'errPerm']);
			}
			if (file.locked) {
				return !dfrd.reject(['errLocked', file.name]);
			}
		});

		if (dfrd.state() === 'pending') {
			targets = self.hashes(hashes);
			cnt     = files.length;
			
			if (forceRm || (self.event && self.event.originalEvent && self.event.originalEvent.shiftKey)) {
				tHash = '';
				self.title = fm.i18n('cmdrm');
			}
			
			if (tHash === null) {
				tHash = getTHash(targets);
			}
			
			fm.lockfiles({files : targets});
			
			if (tHash && self.options.quickTrash) {
				self.toTrash(dfrd, targets, tHash);
			} else {
				if (quiet) {
					remove(dfrd, targets, quiet);
				} else {
					self.confirm(dfrd, targets, files, tHash, addTexts);
				}
			}
		}
			
		return dfrd;
	};

	fm.bind('select contextmenucreate closecontextmenu', function(e) {
		var targets = (e.data? (e.data.selected || e.data.targets) : null) || fm.selected();
		if (targets && targets.length) {
			self.update(void(0), (targets? getTHash(targets) : fm.option('trashHash'))? 'trash' : 'rm');
		}
	});

};


/*
 * File: /js/commands/search.js
 */

/**
 * @class  elFinder command "search"
 * Find files
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.search = function() {
	"use strict";
	this.title          = 'Find files';
	this.options        = {ui : 'searchbutton'};
	this.alwaysEnabled  = true;
	this.updateOnSelect = false;
	
	/**
	 * Return command status.
	 * Search does not support old api.
	 *
	 * @return Number
	 **/
	this.getstate = function() {
		return 0;
	};
	
	/**
	 * Send search request to backend.
	 *
	 * @param  String  search string
	 * @return jQuery.Deferred
	 **/
	this.exec = function(q, target, mime, type) {
		var fm = this.fm,
			reqDef = [],
			sType = type || '',
			onlyMimes = fm.options.onlyMimes,
			phash, targetVolids = [],
			setType = function(data) {
				if (sType && sType !== 'SearchName' && sType !== 'SearchMime') {
					data.type = sType;
				}
				return data;
			};
		
		if (typeof q == 'string' && q) {
			if (typeof target == 'object') {
				mime = target.mime || '';
				target = target.target || '';
			}
			target = target? target : '';
			if (mime) {
				mime = jQuery.trim(mime).replace(',', ' ').split(' ');
				if (onlyMimes.length) {
					mime = jQuery.map(mime, function(m){ 
						m = jQuery.trim(m);
						return m && (jQuery.inArray(m, onlyMimes) !== -1
									|| jQuery.grep(onlyMimes, function(om) { return m.indexOf(om) === 0? true : false; }).length
									)? m : null;
					});
				}
			} else {
				mime = [].concat(onlyMimes);
			}

			fm.trigger('searchstart', setType({query : q, target : target, mimes : mime}));
			
			if (! onlyMimes.length || mime.length) {
				if (target === '' && fm.api >= 2.1) {
					jQuery.each(fm.roots, function(id, hash) {
						reqDef.push(fm.request({
							data   : setType({cmd : 'search', q : q, target : hash, mimes : mime}),
							notify : {type : 'search', cnt : 1, hideCnt : (reqDef.length? false : true)},
							cancel : true,
							preventDone : true
						}));
					});
				} else {
					reqDef.push(fm.request({
						data   : setType({cmd : 'search', q : q, target : target, mimes : mime}),
						notify : {type : 'search', cnt : 1, hideCnt : true},
						cancel : true,
						preventDone : true
					}));
					if (target !== '' && fm.api >= 2.1 && Object.keys(fm.leafRoots).length) {
						jQuery.each(fm.leafRoots, function(hash, roots) {
							phash = hash;
							while(phash) {
								if (target === phash) {
									jQuery.each(roots, function() {
										var f = fm.file(this);
										f && f.volumeid && targetVolids.push(f.volumeid);
										reqDef.push(fm.request({
											data   : setType({cmd : 'search', q : q, target : this, mimes : mime}),
											notify : {type : 'search', cnt : 1, hideCnt : false},
											cancel : true,
											preventDone : true
										}));
									});
								}
								phash = (fm.file(phash) || {}).phash;
							}
						});
					}
				}
			} else {
				reqDef = [jQuery.Deferred().resolve({files: []})];
			}
			
			fm.searchStatus.mixed = (reqDef.length > 1)? targetVolids : false;
			
			return jQuery.when.apply($, reqDef).done(function(data) {
				var argLen = arguments.length,
					i;
				
				data.warning && fm.error(data.warning);
				
				if (argLen > 1) {
					data.files = (data.files || []);
					for(i = 1; i < argLen; i++) {
						arguments[i].warning && fm.error(arguments[i].warning);
						
						if (arguments[i].files) {
							data.files.push.apply(data.files, arguments[i].files);
						}
					}
				}
				
				// because "preventDone : true" so update files cache
				data.files && data.files.length && fm.cache(data.files);
				
				fm.lazy(function() {
					fm.trigger('search', data);
				}).then(function() {
					// fire event with command name + 'done'
					return fm.lazy(function() {
						fm.trigger('searchdone');
					});
				}).then(function() {
					// force update content
					data.sync && fm.sync();
				});
			});
		}
		fm.getUI('toolbar').find('.'+fm.res('class', 'searchbtn')+' :text').trigger('focus');
		return jQuery.Deferred().reject();
	};

};


/*
 * File: /js/commands/selectall.js
 */

/**
 * @class  elFinder command "selectall"
 * Select ALL of cwd items
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.selectall = function() {
	"use strict";
	var self = this,
		state = 0;
	
	this.fm.bind('select', function(e) {
		state = (e.data && e.data.selectall)? -1 : 0;
	});
	
	this.state = 0;
	this.updateOnSelect = false;
	
	this.getstate = function() {
		return state;
	};
	
	this.exec = function() {
		jQuery(document).trigger(jQuery.Event('keydown', { keyCode: 65, ctrlKey : true, shiftKey : false, altKey : false, metaKey : false }));
		return jQuery.Deferred().resolve();
	};
};


/*
 * File: /js/commands/selectinvert.js
 */

/**
 * @class  elFinder command "selectinvert"
 * Invert Selection of cwd items
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.selectinvert = function() {
	"use strict";
	this.updateOnSelect = false;
	
	this.getstate = function() {
		return 0;
	};
	
	this.exec = function() {
		jQuery(document).trigger(jQuery.Event('keydown', { keyCode: 73, ctrlKey : true, shiftKey : true, altKey : false, metaKey : false }));
		return jQuery.Deferred().resolve();
	};

};


/*
 * File: /js/commands/selectnone.js
 */

/**
 * @class  elFinder command "selectnone"
 * Unselect ALL of cwd items
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.selectnone = function() {
	"use strict";
	var self = this,
		fm = this.fm,
		state = -1;
	
	fm.bind('select', function(e) {
		state = (e.data && e.data.unselectall)? -1 : 0;
	});
	
	this.state = -1;
	this.updateOnSelect = false;
	
	this.getstate = function() {
		return state;
	};
	
	this.exec = function() {
		fm.getUI('cwd').trigger('unselectall');
		return jQuery.Deferred().resolve();
	};
};


/*
 * File: /js/commands/sort.js
 */

/**
 * @class  elFinder command "sort"
 * Change sort files rule
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.sort = function() {
	"use strict";
	var self  = this,
		fm    = self.fm,
		setVar = function() {
			self.variants = [];
			jQuery.each(fm.sortRules, function(name, value) {
				if (fm.sorters[name]) {
					var arr = (name === fm.sortType)? (fm.sortOrder === 'asc'? 'n' : 's') : '';
					self.variants.push([name, (arr? '<span class="ui-icon ui-icon-arrowthick-1-'+arr+'"></span>' : '') + '&nbsp;' + fm.i18n('sort'+name)]);
				}
			});
			self.variants.push('|');
			self.variants.push([
				'stick',
				(fm.sortStickFolders? '<span class="ui-icon ui-icon-check"/>' : '') + '&nbsp;' + fm.i18n('sortFoldersFirst')
			]);
			if (fm.ui.tree && fm.options.sortAlsoTreeview !== null) {
				self.variants.push('|');
				self.variants.push([
					'tree',
					(fm.sortAlsoTreeview? '<span class="ui-icon ui-icon-check"/>' : '') + '&nbsp;' + fm.i18n('sortAlsoTreeview')
				]);
			}
			updateContextmenu();
		},
		updateContextmenu = function() {
			var cm = fm.getUI('contextmenu'),
				icon, sub;
			if (cm.is(':visible')) {
				icon = cm.find('span.elfinder-button-icon-sort');
				sub = icon.siblings('div.elfinder-contextmenu-sub');
				sub.find('span.ui-icon').remove();
				sub.children('div.elfinder-contextsubmenu-item').each(function() {
					var tgt = jQuery(this).children('span'),
						name = tgt.text().trim(),
						arr;
					if (name === (i18Name.stick || (i18Name.stick = fm.i18n('sortFoldersFirst')))) {
						if (fm.sortStickFolders) {
							tgt.prepend('<span class="ui-icon ui-icon-check"/>');
						}
					} else if (name === (i18Name.tree || (i18Name.tree = fm.i18n('sortAlsoTreeview')))) {
						if (fm.sortAlsoTreeview) {
							tgt.prepend('<span class="ui-icon ui-icon-check"/>');
						}
					} else if (name === (i18Name[fm.sortType] || (i18Name[fm.sortType] = fm.i18n('sort' + fm.sortType)))) {
						arr = fm.sortOrder === 'asc'? 'n' : 's';
						tgt.prepend('<span class="ui-icon ui-icon-arrowthick-1-'+arr+'"></span>');
					}
				});
			}
		},
		i18Name = {};
	
	/**
	 * Command options
	 *
	 * @type  Object
	 */
	this.options = {ui : 'sortbutton'};
	
	this.keepContextmenu = true;

	fm.bind('sortchange', setVar)
	.bind('sorterupdate', function() {
		setVar();
		fm.getUI('toolbar').find('.elfiner-button-sort .elfinder-button-menu .elfinder-button-menu-item').each(function() {
			var tgt = jQuery(this),
				rel = tgt.attr('rel');
			tgt.toggle(! rel || fm.sorters[rel]);
		});
	})
	.bind('cwdrender', function() {
		var cols = jQuery(fm.cwd).find('div.elfinder-cwd-wrapper-list table');
		if (cols.length) {
			jQuery.each(fm.sortRules, function(name, value) {
				var td = cols.find('thead tr td.elfinder-cwd-view-th-'+name);
				if (td.length) {
					var current = ( name == fm.sortType),
					sort = {
						type  : name,
						order : current ? fm.sortOrder == 'asc' ? 'desc' : 'asc' : fm.sortOrder
					},arr;
					if (current) {
						td.addClass('ui-state-active');
						arr = fm.sortOrder == 'asc' ? 'n' : 's';
						jQuery('<span class="ui-icon ui-icon-triangle-1-'+arr+'"/>').appendTo(td);
					}
					jQuery(td).on('click', function(e){
						if (! jQuery(this).data('dragging')) {
							e.stopPropagation();
							if (! fm.getUI('cwd').data('longtap')) {
								fm.exec('sort', [], sort);
							}
						}
					})
					.on('mouseenter mouseleave', function(e) {
						jQuery(this).toggleClass('ui-state-hover', e.type === 'mouseenter');
					});
				}
				
			});
		}
	});
	
	this.getstate = function() {
		return 0;
	};
	
	this.exec = function(hashes, cOpt) {
		var fm = this.fm,
			sortopt = jQuery.isPlainObject(cOpt)? cOpt : (function() {
				cOpt += '';
				var sOpts = {};
				if (cOpt === 'stick') {
					sOpts.stick = !fm.sortStickFolders;
				} else if (cOpt === 'tree') {
					sOpts.tree = !fm.sortAlsoTreeview;
				} else if (fm.sorters[cOpt]) {
					if (fm.sortType === cOpt) {
						sOpts.order = fm.sortOrder === 'asc'? 'desc' : 'asc';
					} else {
						sOpts.type = cOpt;
					}
				}
				return sOpts;
			})(),
			sort = Object.assign({
				type  : fm.sortType,
				order : fm.sortOrder,
				stick : fm.sortStickFolders,
				tree  : fm.sortAlsoTreeview
			}, sortopt);

		return fm.lazy(function() {
			fm.setSort(sort.type, sort.order, sort.stick, sort.tree);
			this.resolve();
		});
	};

};


/*
 * File: /js/commands/undo.js
 */

/**
 * @class  elFinder command "undo"
 * Undo previous commands
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.undo = function() {
	"use strict";
	var self = this,
		fm = this.fm,
		setTitle = function(undo) {
			if (undo) {
				self.title = fm.i18n('cmdundo') + ' ' + fm.i18n('cmd'+undo.cmd);
				self.state = 0;
			} else {
				self.title = fm.i18n('cmdundo');
				self.state = -1;
			}
			self.change();
		},
		cmds = [];
	
	this.alwaysEnabled  = true;
	this.updateOnSelect = false;
	this.shortcuts      = [{
		pattern     : 'ctrl+z'
	}];
	this.syncTitleOnChange = true;
	
	this.getstate = function() {
		return cmds.length? 0 : -1;
	};
	
	this.setUndo = function(undo, redo) {
		var _undo = {};
		if (undo) {
			if (jQuery.isPlainObject(undo) && undo.cmd && undo.callback) {
				Object.assign(_undo, undo);
				if (redo) {
					delete redo.undo;
					_undo.redo = redo;
				} else {
					fm.getCommand('redo').setRedo(null);
				}
				cmds.push(_undo);
				setTitle(_undo);
			}
		}
	};
	
	this.exec = function() {
		var redo = fm.getCommand('redo'),
			dfd = jQuery.Deferred(),
			undo, res, _redo = {};
		if (cmds.length) {
			undo = cmds.pop();
			if (undo.redo) {
				Object.assign(_redo, undo.redo);
				delete undo.redo;
			} else {
				_redo = null;
			} 
			dfd.done(function() {
				if (_redo) {
					redo.setRedo(_redo, undo);
				}
			});
			
			setTitle(cmds.length? cmds[cmds.length-1] : void(0));
			
			res = undo.callback();
			
			if (res && res.done) {
				res.done(function() {
					dfd.resolve();
				}).fail(function() {
					dfd.reject();
				});
			} else {
				dfd.resolve();
			}
			if (cmds.length) {
				this.update(0, cmds[cmds.length - 1].name);
			} else {
				this.update(-1, '');
			}
		} else {
			dfd.reject();
		}
		return dfd;
	};
	
	fm.bind('exec', function(e) {
		var data = e.data || {};
		if (data.opts && data.opts._userAction) {
			if (data.dfrd && data.dfrd.done) {
				data.dfrd.done(function(res) {
					if (res && res.undo && res.redo) {
						res.undo.redo = res.redo;
						self.setUndo(res.undo);
					}
				});
			}
		}
	});
};

/**
 * @class  elFinder command "redo"
 * Redo previous commands
 *
 * @author Naoki Sawada
 **/
elFinder.prototype.commands.redo = function() {
	"use strict";
	var self = this,
		fm   = this.fm,
		setTitle = function(redo) {
			if (redo && redo.callback) {
				self.title = fm.i18n('cmdredo') + ' ' + fm.i18n('cmd'+redo.cmd);
				self.state = 0;
			} else {
				self.title = fm.i18n('cmdredo');
				self.state = -1;
			}
			self.change();
		},
		cmds = [];
	
	this.alwaysEnabled  = true;
	this.updateOnSelect = false;
	this.shortcuts      = [{
		pattern     : 'shift+ctrl+z ctrl+y'
	}];
	this.syncTitleOnChange = true;
	
	this.getstate = function() {
		return cmds.length? 0 : -1;
	};
	
	this.setRedo = function(redo, undo) {
		if (redo === null) {
			cmds = [];
			setTitle();
		} else {
			if (redo && redo.cmd && redo.callback) {
				if (undo) {
					redo.undo = undo;
				}
				cmds.push(redo);
				setTitle(redo);
			}
		}
	};
	
	this.exec = function() {
		var undo = fm.getCommand('undo'),
			dfd = jQuery.Deferred(),
			redo, res, _undo = {}, _redo = {};
		if (cmds.length) {
			redo = cmds.pop();
			if (redo.undo) {
				Object.assign(_undo, redo.undo);
				Object.assign(_redo, redo);
				delete _redo.undo;
				dfd.done(function() {
					undo.setUndo(_undo, _redo);
				});
			}
			
			setTitle(cmds.length? cmds[cmds.length-1] : void(0));
			
			res = redo.callback();
			
			if (res && res.done) {
				res.done(function() {
					dfd.resolve();
				}).fail(function() {
					dfd.reject();
				});
			} else {
				dfd.resolve();
			}
			return dfd;
		} else {
			return dfd.reject();
		}
	};
};


/*
 * File: /js/commands/up.js
 */

/**
 * @class  elFinder command "up"
 * Go into parent directory
 *
 * @author Dmitry (dio) Levashov
 **/
(elFinder.prototype.commands.up = function() {
	"use strict";
	this.alwaysEnabled = true;
	this.updateOnSelect = false;
	
	this.shortcuts = [{
		pattern     : 'ctrl+up'
	}];
	
	this.getstate = function() {
		return this.fm.cwd().phash ? 0 : -1;
	};
	
	this.exec = function() {
		var fm = this.fm,
			cwdhash = fm.cwd().hash;
		return this.fm.cwd().phash ? this.fm.exec('open', this.fm.cwd().phash).done(function() {
			fm.one('opendone', function() {
				fm.selectfiles({files : [cwdhash]});
			});
		}) : jQuery.Deferred().reject();
	};

}).prototype = { forceLoad : true }; // this is required command


/*
 * File: /js/commands/upload.js
 */

/**
 * @class elFinder command "upload"
 * Upload files using iframe or XMLHttpRequest & FormData.
 * Dialog allow to send files using drag and drop
 *
 * @type  elFinder.command
 * @author  Dmitry (dio) Levashov
 */
elFinder.prototype.commands.upload = function() {
	"use strict";
	var hover = this.fm.res('class', 'hover');
	
	this.disableOnSearch = true;
	this.updateOnSelect  = false;
	
	// Shortcut opens dialog
	this.shortcuts = [{
		pattern     : 'ctrl+u'
	}];
	
	/**
	 * Return command state
	 *
	 * @return Number
	 **/
	this.getstate = function(select) {
		var fm = this.fm, f,
		sel = (select || [fm.cwd().hash]);
		if (!this._disabled && sel.length == 1) {
			f = fm.file(sel[0]);
		}
		return (f && f.mime == 'directory' && f.write)? 0 : -1;
	};
	
	
	this.exec = function(data) {
		var fm = this.fm,
			cwdHash = fm.cwd().hash,
			getTargets = function() {
				var tgts = data && (data instanceof Array)? data : null,
					sel;
				if (! data || data instanceof Array) {
					if (! tgts && (sel = fm.selected()).length === 1 && fm.file(sel[0]).mime === 'directory') {
						tgts = sel;
					} else if (!tgts || tgts.length !== 1 || fm.file(tgts[0]).mime !== 'directory') {
						tgts = [ cwdHash ];
					}
				}
				return tgts;
			},
			targets = getTargets(),
			check = targets? targets[0] : (data && data.target? data.target : null),
			targetDir = check? fm.file(check) : fm.cwd(),
			fmUpload = function(data) {
				fm.upload(data)
					.fail(function(error) {
						dfrd.reject(error);
					})
					.done(function(data) {
						var cwd = fm.getUI('cwd'),
							node;
						dfrd.resolve(data);
						if (data && data.added && data.added[0] && ! fm.ui.notify.children('.elfinder-notify-upload').length) {
							var newItem = fm.findCwdNodes(data.added);
							if (newItem.length) {
								newItem.trigger('scrolltoview');
							} else {
								if (targetDir.hash !== cwdHash) {
									node = jQuery('<div/>').append(
										jQuery('<button type="button" class="ui-button ui-widget ui-state-default ui-corner-all elfinder-tabstop"><span class="ui-button-text">'+fm.i18n('cmdopendir')+'</span></button>')
										.on('mouseenter mouseleave', function(e) { 
											jQuery(this).toggleClass('ui-state-hover', e.type == 'mouseenter');
										}).on('click', function() {
											fm.exec('open', check).done(function() {
												fm.one('opendone', function() {
													fm.trigger('selectfiles', {files : jQuery.map(data.added, function(f) {return f.hash;})});
												});
											});
										})
									);
								} else {
									fm.trigger('selectfiles', {files : jQuery.map(data.added, function(f) {return f.hash;})});
								}
								fm.toast({msg: fm.i18n(['complete', fm.i18n('cmdupload')]), extNode: node});
							}
						}
					})
					.progress(function() {
						dfrd.notifyWith(this, Array.from(arguments));
					});
			},
			upload = function(data) {
				dialog.elfinderdialog('close');
				if (targets) {
					data.target = targets[0];
				}
				fmUpload(data);
			},
			getSelector = function() {
				var hash = targetDir.hash,
					dirs = jQuery.map(fm.files(hash), function(f) {
						return (f.mime === 'directory' && f.write)? f : null; 
					});
				
				if (! dirs.length) {
					return jQuery();
				}
				
				return jQuery('<div class="elfinder-upload-dirselect elfinder-tabstop" title="' + fm.i18n('folders') + '"/>')
				.on('click', function(e) {
					e.stopPropagation();
					e.preventDefault();
					dirs = fm.sortFiles(dirs);
					var $this  = jQuery(this),
						cwd    = fm.cwd(),
						base   = dialog.closest('div.ui-dialog'),
						getRaw = function(f, icon) {
							return {
								label    : fm.escape(f.i18 || f.name),
								icon     : icon,
								remain   : false,
								callback : function() {
									var title = base.children('.ui-dialog-titlebar:first').find('span.elfinder-upload-target');
									targets = [ f.hash ];
									title.html(' - ' + fm.escape(f.i18 || f.name));
									$this.trigger('focus');
								},
								options  : {
									className : (targets && targets.length && f.hash === targets[0])? 'ui-state-active' : '',
									iconClass : f.csscls || '',
									iconImg   : f.icon   || ''
								}
							};
						},
						raw = [ getRaw(targetDir, 'opendir'), '|' ];
					jQuery.each(dirs, function(i, f) {
						raw.push(getRaw(f, 'dir'));
					});
					$this.trigger('blur');
					fm.trigger('contextmenu', {
						raw: raw,
						x: e.pageX || jQuery(this).offset().left,
						y: e.pageY || jQuery(this).offset().top,
						prevNode: base,
						fitHeight: true
					});
				}).append('<span class="elfinder-button-icon elfinder-button-icon-dir" />');
			},
			inputButton = function(type, caption) {
				var button,
					input = jQuery('<input type="file" ' + type + '/>')
					.on('click', function() {
						// for IE's bug
						if (fm.UA.IE) {
							setTimeout(function() {
								form.css('display', 'none').css('position', 'relative');
								requestAnimationFrame(function() {
									form.css('display', '').css('position', '');
								});
							}, 100);
						}
					})
					.on('change', function() {
						upload({input : input.get(0), type : 'files'});
					})
					.on('dragover', function(e) {
						e.originalEvent.dataTransfer.dropEffect = 'copy';
					}),
					form = jQuery('<form/>').append(input).on('click', function(e) {
						e.stopPropagation();
					});

				return jQuery('<div class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only elfinder-tabstop elfinder-focus"><span class="ui-button-text">'+fm.i18n(caption)+'</span></div>')
					.append(form)
					.on('click', function(e) {
						e.stopPropagation();
						e.preventDefault();
						input.trigger('click');
					})
					.on('mouseenter mouseleave', function(e) {
						jQuery(this).toggleClass(hover, e.type === 'mouseenter');
					});
			},
			dfrd = jQuery.Deferred(),
			dialog, dropbox, pastebox, dropUpload, paste, dirs, spinner, uidialog;
		
		dropUpload = function(e) {
			e.stopPropagation();
			e.preventDefault();
			var file = false,
				type = '',
				elfFrom = null,
				mycwd = '',
				data = null,
				target = e._target || null,
				trf = e.dataTransfer || null,
				kind = (trf.items && trf.items.length && trf.items[0].kind)? trf.items[0].kind : '',
				errors;
			
			if (trf) {
				try {
					elfFrom = trf.getData('elfinderfrom');
					if (elfFrom) {
						mycwd = window.location.href + fm.cwd().hash;
						if ((!target && elfFrom === mycwd) || target === mycwd) {
							dfrd.reject();
							return;
						}
					}
				} catch(e) {}
				
				if (kind === 'file' && (trf.items[0].getAsEntry || trf.items[0].webkitGetAsEntry)) {
					file = trf;
					type = 'data';
				} else if (kind !== 'string' && trf.files && trf.files.length && jQuery.inArray('Text', trf.types) === -1) {
					file = trf.files;
					type = 'files';
				} else {
					try {
						if ((data = trf.getData('text/html')) && data.match(/<(?:img|a)/i)) {
							file = [ data ];
							type = 'html';
						}
					} catch(e) {}
					if (! file) {
						if (data = trf.getData('text')) {
							file = [ data ];
							type = 'text';
						} else if (trf && trf.files) {
							// maybe folder uploading but this UA dose not support it
							kind = 'file';
						}
					}
				}
			}
			if (file) {
				fmUpload({files : file, type : type, target : target, dropEvt : e});
			} else {
				errors = ['errUploadNoFiles'];
				if (kind === 'file') {
					errors.push('errFolderUpload');
				}
				fm.error(errors);
				dfrd.reject();
			}
		};
		
		if (!targets && data) {
			if (data.input || data.files) {
				data.type = 'files';
				fmUpload(data);
			} else if (data.dropEvt) {
				dropUpload(data.dropEvt);
			}
			return dfrd;
		}
		
		paste = function(ev) {
			var e = ev.originalEvent || ev;
			var files = [], items = [];
			var file;
			if (e.clipboardData) {
				if (e.clipboardData.items && e.clipboardData.items.length){
					items = e.clipboardData.items;
					for (var i=0; i < items.length; i++) {
						if (e.clipboardData.items[i].kind == 'file') {
							file = e.clipboardData.items[i].getAsFile();
							files.push(file);
						}
					}
				} else if (e.clipboardData.files && e.clipboardData.files.length) {
					files = e.clipboardData.files;
				}
				if (files.length) {
					upload({files : files, type : 'files', clipdata : true});
					return;
				}
			}
			var my = e.target || e.srcElement;
			requestAnimationFrame(function() {
				var type = 'text',
					src;
				if (my.innerHTML) {
					jQuery(my).find('img').each(function(i, v){
						if (v.src.match(/^webkit-fake-url:\/\//)) {
							// For Safari's bug.
							// ref. https://bugs.webkit.org/show_bug.cgi?id=49141
							//      https://dev.ckeditor.com/ticket/13029
							jQuery(v).remove();
						}
					});
					
					if (jQuery(my).find('a,img').length) {
						type = 'html';
					}
					src = my.innerHTML;
					my.innerHTML = '';
					upload({files : [ src ], type : type});
				}
			});
		};
		
		dialog = jQuery('<div class="elfinder-upload-dialog-wrapper"/>')
			.append(inputButton('multiple', 'selectForUpload'));
		
		if (! fm.UA.Mobile && (function(input) {
			return (typeof input.webkitdirectory !== 'undefined' || typeof input.directory !== 'undefined');})(document.createElement('input'))) {
			dialog.append(inputButton('multiple webkitdirectory directory', 'selectFolder'));
		}
		
		if (targetDir.dirs) {
			
			if (targetDir.hash === cwdHash || fm.navHash2Elm(targetDir.hash).hasClass('elfinder-subtree-loaded')) {
				getSelector().appendTo(dialog);
			} else {
				spinner = jQuery('<div class="elfinder-upload-dirselect" title="' + fm.i18n('nowLoading') + '"/>')
					.append('<span class="elfinder-button-icon elfinder-button-icon-spinner" />')
					.appendTo(dialog);
				fm.request({cmd : 'tree', target : targetDir.hash})
					.done(function() { 
						fm.one('treedone', function() {
							spinner.replaceWith(getSelector());
							uidialog.elfinderdialog('tabstopsInit');
						});
					})
					.fail(function() {
						spinner.remove();
					});
			}
		}
		
		if (fm.dragUpload) {
			dropbox = jQuery('<div class="ui-corner-all elfinder-upload-dropbox elfinder-tabstop" contenteditable="true" data-ph="'+fm.i18n('dropPasteFiles')+'"></div>')
				.on('paste', function(e){
					paste(e);
				})
				.on('mousedown click', function(){
					jQuery(this).trigger('focus');
				})
				.on('focus', function(){
					this.innerHTML = '';
				})
				.on('mouseover', function(){
					jQuery(this).addClass(hover);
				})
				.on('mouseout', function(){
					jQuery(this).removeClass(hover);
				})
				.on('dragenter', function(e) {
					e.stopPropagation();
				  	e.preventDefault();
				  	jQuery(this).addClass(hover);
				})
				.on('dragleave', function(e) {
					e.stopPropagation();
				  	e.preventDefault();
				  	jQuery(this).removeClass(hover);
				})
				.on('dragover', function(e) {
					e.stopPropagation();
				  	e.preventDefault();
					e.originalEvent.dataTransfer.dropEffect = 'copy';
					jQuery(this).addClass(hover);
				})
				.on('drop', function(e) {
					dialog.elfinderdialog('close');
					targets && (e.originalEvent._target = targets[0]);
					dropUpload(e.originalEvent);
				})
				.prependTo(dialog)
				.after('<div class="elfinder-upload-dialog-or">'+fm.i18n('or')+'</div>')[0];
			
		} else {
			pastebox = jQuery('<div class="ui-corner-all elfinder-upload-dropbox" contenteditable="true">'+fm.i18n('dropFilesBrowser')+'</div>')
				.on('paste drop', function(e){
					paste(e);
				})
				.on('mousedown click', function(){
					jQuery(this).trigger('focus');
				})
				.on('focus', function(){
					this.innerHTML = '';
				})
				.on('dragenter mouseover', function(){
					jQuery(this).addClass(hover);
				})
				.on('dragleave mouseout', function(){
					jQuery(this).removeClass(hover);
				})
				.prependTo(dialog)
				.after('<div class="elfinder-upload-dialog-or">'+fm.i18n('or')+'</div>')[0];
			
		}
		
		uidialog = this.fmDialog(dialog, {
			title          : this.title + '<span class="elfinder-upload-target">' + (targetDir? ' - ' + fm.escape(targetDir.i18 || targetDir.name) : '') + '</span>',
			modal          : true,
			resizable      : false,
			destroyOnClose : true,
			propagationEvents : ['mousemove', 'mouseup', 'click'],
			close          : function() {
				var cm = fm.getUI('contextmenu');
				if (cm.is(':visible')) {
					cm.click();
				}
			}
		});
		
		return dfrd;
	};

};


/*
 * File: /js/commands/view.js
 */

/**
 * @class  elFinder command "view"
 * Change current directory view (icons/list)
 *
 * @author Dmitry (dio) Levashov
 **/
elFinder.prototype.commands.view = function() {
	"use strict";
	var self = this,
		fm = this.fm,
		subMenuRaw;
	this.value          = fm.viewType;
	this.alwaysEnabled  = true;
	this.updateOnSelect = false;

	this.options = { ui : 'viewbutton'};
	
	this.getstate = function() {
		return 0;
	};
	
	this.extra = {
		icon: 'menu',
		node: jQuery('<span/>')
			.attr({title: fm.i18n('viewtype')})
			.on('click touchstart', function(e){
				if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
					return;
				}
				var node = jQuery(this);
				e.stopPropagation();
				e.preventDefault();
				fm.trigger('contextmenu', {
					raw: getSubMenuRaw(),
					x: node.offset().left,
					y: node.offset().top
				});
			})
	};

	this.exec = function() {
		var self  = this,
			value = fm.storage('view', this.value == 'list' ? 'icons' : 'list');
		return fm.lazy(function() {
			fm.viewchange();
			self.update(void(0), value);
			this.resolve();
		});
	};

	fm.bind('init', function() {
		subMenuRaw = (function() {
			var cwd = fm.getUI('cwd'),
				raws = [],
				sizeNames = fm.options.uiOptions.cwd.iconsView.sizeNames,
				max = fm.options.uiOptions.cwd.iconsView.sizeMax,
				i, size;
			for (i = 0; i <= max; i++) {
				raws.push(
					{
						label    : fm.i18n(sizeNames[i] || ('Size-' + i + ' icons')),
						icon     : 'view',
						callback : (function(s) {
							return function() {
								cwd.trigger('iconpref', {size: s});
								fm.storage('iconsize', s);
								if (self.value === 'list') {
									self.exec();
								}
							};
						})(i)
					}
				);
			}
			raws.push('|');
			raws.push(
				{
					label    : fm.i18n('viewlist'),
					icon     : 'view-list',
					callback : function() {
						if (self.value !== 'list') {
							self.exec();
						}
					}
				}		
			);
			return raws;
		})();
	}).bind('contextmenucreate', function() {
		self.extra = {
			icon: 'menu',
			node: jQuery('<span/>')
				.attr({title: fm.i18n('cmdview')})
				.on('click touchstart', function(e){
					if (e.type === 'touchstart' && e.originalEvent.touches.length > 1) {
						return;
					}
					var node = jQuery(this),
						raw = subMenuRaw.concat(),
						idx, i;
					if (self.value === 'list') {
						idx = subMenuRaw.length - 1;
					} else {
						idx = parseInt(fm.storage('iconsize') || 0);
					}
					for (i = 0; i < subMenuRaw.length; i++) {
						if (subMenuRaw[i] !== '|') {
							subMenuRaw[i].options = (i === idx? {'className': 'ui-state-active'} : void(0))
							;
						}
					}
					e.stopPropagation();
					e.preventDefault();
					fm.trigger('contextmenu', {
						raw: subMenuRaw,
						x: node.offset().left,
						y: node.offset().top
					});
				})
		};
	});

};

return elFinder;
}));

Youez - 2016 - github.com/yon3zu
LinuXploit