UNPKG

elfinder-dotneet

Version:
2,105 lines (1,894 loc) 859 kB
/*! * elFinder - file manager for web * Version 2.1.29 (2017-10-31) * http://elfinder.org * * Copyright 2009-2017, 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(node, 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'], /** * Node on which elfinder creating * * @type jQuery **/ node = $(node), /** * Object of events originally registered in this node * * @type Object */ prevEvents = $.extend(true, {}, $._data(node.get(0), 'events')), /** * Store node contents. * * @see this.destroy * @type jQuery **/ prevContent = $('<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, /** * Is shortcuts/commands enabled * * @type Boolean **/ enabled = true, /** * Store enabled value before ajax requiest * * @type Boolean **/ prevEnabled = true, /** * 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 = {}, /** * 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 | $(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/', beeper = $(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; 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.navHash2Id(files[i].hash)).is(':hidden') && $('#'+self.navHash2Id(phash)).next('.elfinder-navbar-subtree').children().length > 100 ) ) && (isDir || phash !== cwd) && ! remember[i] ) { if (isDir && !emptyDirs[phash]) { emptyDirs[phash] = true; $('#'+self.navHash2Id(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 }); } }; 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]); } 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.length === 0), l = data.length, setSorter = function(f) { var f = f || {}; self.sorters = []; $.each(self.sortRules, function(key) { if (defsorter[key] || typeof f[key] !== 'undefined' || (key === 'mode' && typeof f.perm !== 'undefined')) { self.sorters.push(key); } }); }, keeps = ['sizeInfo'], changedParents = {}, f, i, keepProp, parents; for (i = 0; i < l; i++) { f = Object.assign({}, data[i]); if (f.name && f.hash && f.mime) { if (sorterChk && f.phash === cwd) { setSorter(f); sorterChk = false; } // make or update of leaf roots cache if (f.isroot && f.phash) { if (! self.leafRoots[f.phash]) { self.leafRoots[f.phash] = [ f.hash ]; } else { if ($.inArray(f.hash, self.leafRoots[f.phash]) === -1) { self.leafRoots[f.phash].push(f.hash); } } if (files[f.phash]) { if (! files[f.phash].dirs) { files[f.phash].dirs = 1; } if (f.ts && (files[f.phash].ts || 0) < f.ts) { files[f.phash].ts = f.ts; } } } if (f.phash && (type === 'add' || type === 'change')) { if (parents = self.parents(f.phash)) { $.each(parents, function() { changedParents[this] = true; }); } } if (files[f.hash]) { $.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); } 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 $.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]]; } if (self.searchStatus.state < 2) { $.each(files, function(h, f) { f.phash == hash && rm(h); }); } } if (file.phash) { if (parents = self.parents(file.phash)) { $.each(parents, function() { changedParents[this] = true; }); } } deleteCache(files[hash]); } }, changedParents = {}, parents; $.each(self.roots, function(k, v) { roots[v] = k; }); while (l--) { rm(removed[l]); } // delete sizeInfo cache $.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) { $.each(changed, function(i, file) { var hash = file.hash; if (files[hash]) { $.each(['locked', 'hidden', 'width', 'height'], function(i, v){ if (files[hash][v] && !file[v]) { 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), ddm; if (enabled) { $.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 == $.ui.keyCode.TAB && !$(e.target).is(':input')) { e.preventDefault(); } // cancel any actions by [Esc] key if (e.type === 'keydown' && code == $.ui.keyCode.ESCAPE) { // copy or cut if (! node.find('.ui-widget:visible').length) { self.clipboard().length && self.clipboard([]); } // dragging if ($.ui.ddmanager) { ddm = $.ui.ddmanager.current; ddm && ddm.helper && ddm.cancel(); } // button menus node.find('.ui-widget.elfinder-button-menu').hide(); // trigger keydownEsc self.trigger('keydownEsc', e); } } }, date = new Date(), utc, i18n, inFrame = (window.parent !== window), parentIframe = (function() { var pifm, ifms; if (inFrame) { try { ifms = $('iframe', window.parent.document); if (ifms.length) { $.each(ifms, function(i, ifm) { if (ifm.contentWindow === window) { pifm = $(ifm); return false; } }); } } catch(e) {} } return pifm; })(), /** * elFinder boot up function * * @type Function */ bootUp, /** * Original function of XMLHttpRequest.prototype.send * * @type Function */ savedXhrSend; // 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 = ''; /** * Is elFinder CSS loaded * * @type Boolean */ this.cssloaded = false; /** * Callback function at boot up that option specified at elFinder starting * * @type Function */ this.bootCallback; /** * Configuration options * * @type Object **/ this.options = $.extend(true, {}, this._options, opts||{}); // set fm.baseUrl this.baseUrl = (function() { var myTag, myCss, base, baseUrl; if (self.options.baseUrl) { return self.options.baseUrl; } else { baseUrl = ''; myTag = $('head > script[src$="js/elfinder.min.js"],script[src$="js/elfinder.full.js"]:first'); if (myTag.length) { myCss = $('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 = $('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; } })(); // set dispInlineRegex cwdOptionsDefault['dispInlineRegex'] = this.options.dispInlineRegex; // auto load required CSS if (this.options.cssAutoLoad) { (function() { var baseUrl = self.baseUrl; if (self.cssloaded === null) { // hide elFinder node while css loading node.data('cssautoloadHide', $('<style>.elfinder{visibility:hidden;overflow:hidden}</style>')); $('head').append(node.data('cssautoloadHide')); // load CSS self.loadCss([baseUrl+'css/elfinder.min.css', baseUrl+'css/theme.css']); // additional CSS files if (Array.isArray(self.options.cssAutoLoad)) { self.loadCss(self.options.cssAutoLoad); } } self.options.cssAutoLoad = false; })(); } /** * 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 (opts.ui) { this.options.ui = opts.ui; } if (opts.commands) { this.options.commands = opts.commands; } if (opts.uiOptions) { if (opts.uiOptions.toolbar && Array.isArray(opts.uiOptions.toolbar)) { if ($.isPlainObject(opts.uiOptions.toolbar[opts.uiOptions.toolbar.length - 1])) { Object.assign(this.options.uiOptions.toolbarExtra, opts.uiOptions.toolbar.pop()); } this.options.uiOptions.toolbar = opts.uiOptions.toolbar; } if (opts.uiOptions.toolbarExtra && $.isPlainObject(opts.uiOptions.toolbarExtra)) { Object.assign(this.options.uiOptions.toolbarExtra, opts.uiOptions.toolbarExtra); } if (opts.uiOptions.cwd && opts.uiOptions.cwd.listView) { if (opts.uiOptions.cwd.listView.columns) { this.options.uiOptions.cwd.listView.columns = opts.uiOptions.cwd.listView.columns; } if (opts.uiOptions.cwd.listView.columnsCustomName) { this.options.uiOptions.cwd.listView.columnsCustomName = opts.uiOptions.cwd.listView.columnsCustomName; } } } // join toolbarExtra to toolbar this.options.uiOptions.toolbar.push(this.options.uiOptions.toolbarExtra); delete this.options.uiOptions.toolbarExtra; if (opts.contextmenu) { Object.assign(this.options.contextmenu, opts.contextmenu); } if (! inFrame && ! this.options.enableAlways && $('body').children().length === 2) { // only node and beeper this.options.enableAlways = true; } if (this.baseUrl === '') { this.baseUrl = this.options.baseUrl? this.options.baseUrl : ''; } // make options.debug if (this.options.debug === true) { this.options.debug = 'all'; } else if (Array.isArray(this.options.debug)) { (function() { var d = {}; $.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() { $.each(conflictChecks, function(i, p) { if ($.fn[p] && typeof $.fn[p].noConflict === 'function') { self.noConflicts[p] = $.fn[p].noConflict(); } }); } // do check conflict this.noConflict(); /** * Is elFinder over CORS * * @type Boolean **/ this.isCORS = false; // configure for CORS (function(){ var parseUrl = document.createElement('a'), parseUploadUrl; parseUrl.href = opts.url; if (opts.urlUpload && (opts.urlUpload !== opts.url)) { parseUploadUrl = document.createElement('a'); parseUploadUrl.href = opts.urlUpload; } if (window.location.host !== parseUrl.host || (parseUploadUrl && (window.location.host !== parseUploadUrl.host))) { self.isCORS = true; if (!$.isPlainObject(self.options.customHeaders)) { self.options.customHeaders = {}; } if (!$.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); /** * Any data to send across every ajax request * * @type Object * @default {} **/ this.customData = $.isPlainObject(this.options.customData) ? this.options.customData : {}; /** * Any custom headers to send across every ajax request * * @type Object * @default {} */ this.customHeaders = $.isPlainObject(this.options.customHeaders) ? this.options.customHeaders : {}; /** * Any custom xhrFields to send across every ajax request * * @type Object * @default {} */ this.xhrFields = $.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) { $.each(self.customHeaders, function(key) { xhr.setRequestHeader(key, this); }); } // set xhrFields if (self.xhrFields) { $.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() { 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']; /** * ID. Required to create unique cookie name * * @type String **/ this.id = id; /** * 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 }; /** * 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; } })(); /** * Interface language * * @type String * @default "en" **/ this.lang = this.storage('lang') || this.options.lang; 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.sortAlsoTreeview = !!this.options.sortAlsoTreeview; } else { this.sortAlsoTreeview = !!this.sortAlsoTreeview } this.sortRules = $.extend(true, {}, this._sortRules, this.options.sortRules); $.each(this.sortRules, function(name, method) { if (typeof method != 'function') { delete self.sortRules[name]; } }); this.compare = $.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 = $(this), targets = $.map(ui.helper.data('files')||[], function(h) { return h || null }), 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) { $.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.phash && !dir.mime == 'directory' && 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; target = target || cwd; if (self.optionsByHashes[target] && typeof self.optionsByHashes[target][name] !== 'undefined') { return self.optionsByHashes[target][name]; } if (cwd !== target) { res = ''; $.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) { var disabled = ['hidden']; if (! Array.isArray(targets)) { targets = [ targets ]; } $.each(targets, function(i, h) { var disCmds = self.option('disabled', h); if (disCmds) { $.each(disCmds, function(i, cmd) { if ($.inArray(cmd, disabled) === -1) { disabled.push(cmd); } }); } }); return disabled; } /** * Return file data from current dir or tree by it's hash * * @param String file hash * @return Object */ this.file = function(hash) { return hash? files[hash] : void(0); }; /** * Return all cached files * * @param String parent hash * @return Object */ this.files = function(phash) { var items = {}; if (phash) { if (!ownFiles[phash]) { return {}; } $.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 ((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 = $.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 */ this.url = function(hash, opts) { var file = files[hash], opts = opts || {}, async = opts.async || false, temp = opts.temporary || false, dfrd = async? $.Deferred() : null, getUrl = function(url) { if (url) { return url; } if (file.url) { return file.url; } baseUrl = (file.hash.indexOf(self.cwd().volumeid) === 0)? cwdOptions.url : self.option('url', file.hash); if (baseUrl) { return baseUrl + $.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 self.options.url + (self.options.url.indexOf('?') === -1 ? '?' : '&') + $.param(params, true); }, baseUrl, res; if (!file || !file.read) { return async? dfrd.resolve('') : ''; } if (file.url == '1') { 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 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 + $.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'; } $.each(this.options.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 ($.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; } if (url) { if (file.ts) { 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 $.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; }; /** * Proccess ajax request. * Fired events : * @todo * @example * @todo * @return $.Deferred */ this.request = function(opts) { var self = this, o = this.options, dfrd = $.Deferred(), // request ID reqId = (+ new Date()).toString(16) + Math.floor(1000 * Math.random()).toString(16), // request data data = Object.assign({}, o.customData, {mimes : o.onlyMimes}, opts.data || opts), // command name cmd = data.cmd, // current cmd is "open" isOpen = (!opts.asNotOpen && cmd === 'open'), // call default fail callback (display error dialog) ? deffail = !(opts.preventDefault || opts.preventFail), // call default success callback ? defdone = !(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 = !!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() { // 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, 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' && !d['backend-error']))) { if (!d) { self.options.debug = {}; } self.options.debug['backend-error'] = true } if (raw) { self.abortXHR(xhr); response && response.debug && self.debug('backend-debug', response); return dfrd.resolve(response); } if (!response) { return dfrd.reject(['errResponse', 'errDataEmpty'], xhr, response); } else if (!$.isPlainObject(response)) { return dfrd.reject(['errResponse', 'errDataNotJSON'], xhr, response); } else if (response.error) { return dfrd.reject(response.error, xhr, response); } var resolve = function() { var pushLeafRoots = function(name) { if (self.leafRoots[data.target] && response[name]) { $.each(self.leafRoots[data.target], function(i, h) { var root; if (root = self.file(h)) { response[name].push(root); } }); } }, setTextMimes = function() { self.textMimes = {}; $.each(self.resources.mimes.text, function() { self.textMimes[this] = true; }); }, actionTarget; if (isOpen) { pushLeafRoots('files'); } else if (cmd === 'tree') { pushLeafRoots('tree'); } response = self.normalize(response); if (!self.validResponse(cmd, response)) { return dfrd.reject((response.norError || 'errResponse'), xhr, response); } 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 (isOpen && !!data.init) { self.uplMaxSize = self.returnBytes(response.uplMaxSize); self.uplMaxFile = !!response.uplMaxFile? parseInt(response.uplMaxFile) : 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 $.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 = $('<div/>') .append( $('<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) { $(this).toggleClass('ui-state-hover', e.type == 'mouseenter'); }) .on('click', function() { self.exec(cmd, data || hashes, {_userAction: true, _currentType: 'toast', _currentNode: $(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 ? $.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 ($.isPlainObject(navigate.toast.incwd)) { self.toast(makeToast(navigate.toast.incwd)); } } else { if ($.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