Advertisement
Guest User

Untitled

a guest
Dec 5th, 2015
598
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*!
  2.  * elFinder - file manager for web
  3.  * Version 2.1.2 (2.x Nightly: f2196bd) (2015-11-24)
  4.  * http://elfinder.org
  5.  *
  6.  * Copyright 2009-2015, Studio 42
  7.  * Licensed under a 3 clauses BSD license
  8.  */
  9. (function($) {
  10.  
  11.  
  12. /*
  13.  * File: /js/elFinder.js
  14.  */
  15.  
  16. /**
  17.  * @class elFinder - file manager for web
  18.  *
  19.  * @author Dmitry (dio) Levashov
  20.  **/
  21. window.elFinder = function(node, opts) {
  22.     this.time('load');
  23.    
  24.     var self = this,
  25.        
  26.         /**
  27.          * Node on which elfinder creating
  28.          *
  29.          * @type jQuery
  30.          **/
  31.         node = $(node),
  32.        
  33.         /**
  34.          * Store node contents.
  35.          *
  36.          * @see this.destroy
  37.          * @type jQuery
  38.          **/
  39.         prevContent = $('<div/>').append(node.contents()),
  40.        
  41.         /**
  42.          * Store node inline styles
  43.          *
  44.          * @see this.destroy
  45.          * @type String
  46.          **/
  47.         prevStyle = node.attr('style'),
  48.        
  49.         /**
  50.          * Instance ID. Required to get/set cookie
  51.          *
  52.          * @type String
  53.          **/
  54.         id = node.attr('id') || '',
  55.        
  56.         /**
  57.          * Events namespace
  58.          *
  59.          * @type String
  60.          **/
  61.         namespace = 'elfinder-'+(id || Math.random().toString().substr(2, 7)),
  62.        
  63.         /**
  64.          * Mousedown event
  65.          *
  66.          * @type String
  67.          **/
  68.         mousedown = 'mousedown.'+namespace,
  69.        
  70.         /**
  71.          * Keydown event
  72.          *
  73.          * @type String
  74.          **/
  75.         keydown = 'keydown.'+namespace,
  76.        
  77.         /**
  78.          * Keypress event
  79.          *
  80.          * @type String
  81.          **/
  82.         keypress = 'keypress.'+namespace,
  83.        
  84.         /**
  85.          * Is shortcuts/commands enabled
  86.          *
  87.          * @type Boolean
  88.          **/
  89.         enabled = true,
  90.        
  91.         /**
  92.          * Store enabled value before ajax requiest
  93.          *
  94.          * @type Boolean
  95.          **/
  96.         prevEnabled = true,
  97.        
  98.         /**
  99.          * List of build-in events which mapped into methods with same names
  100.          *
  101.          * @type Array
  102.          **/
  103.         events = ['enable', 'disable', 'load', 'open', 'reload', 'select',  'add', 'remove', 'change', 'dblclick', 'getfile', 'lockfiles', 'unlockfiles', 'dragstart', 'dragstop'],
  104.        
  105.         /**
  106.          * Rules to validate data from backend
  107.          *
  108.          * @type Object
  109.          **/
  110.         rules = {},
  111.        
  112.         /**
  113.          * Current working directory hash
  114.          *
  115.          * @type String
  116.          **/
  117.         cwd = '',
  118.        
  119.         /**
  120.          * Current working directory options
  121.          *
  122.          * @type Object
  123.          **/
  124.         cwdOptions = {
  125.             path          : '',
  126.             url           : '',
  127.             tmbUrl        : '',
  128.             disabled      : [],
  129.             separator     : '/',
  130.             archives      : [],
  131.             extract       : [],
  132.             copyOverwrite : true,
  133.             uploadMaxSize : 0,
  134.             tmb           : false // old API
  135.         },
  136.        
  137.         /**
  138.          * Files/dirs cache
  139.          *
  140.          * @type Object
  141.          **/
  142.         files = {},
  143.        
  144.         /**
  145.          * Selected files hashes
  146.          *
  147.          * @type Array
  148.          **/
  149.         selected = [],
  150.        
  151.         /**
  152.          * Events listeners
  153.          *
  154.          * @type Object
  155.          **/
  156.         listeners = {},
  157.        
  158.         /**
  159.          * Shortcuts
  160.          *
  161.          * @type Object
  162.          **/
  163.         shortcuts = {},
  164.        
  165.         /**
  166.          * Buffer for copied files
  167.          *
  168.          * @type Array
  169.          **/
  170.         clipboard = [],
  171.        
  172.         /**
  173.          * Copied/cuted files hashes
  174.          * Prevent from remove its from cache.
  175.          * Required for dispaly correct files names in error messages
  176.          *
  177.          * @type Array
  178.          **/
  179.         remember = [],
  180.        
  181.         /**
  182.          * Queue for 'open' requests
  183.          *
  184.          * @type Array
  185.          **/
  186.         queue = [],
  187.        
  188.         /**
  189.          * Commands prototype
  190.          *
  191.          * @type Object
  192.          **/
  193.         base = new self.command(self),
  194.        
  195.         /**
  196.          * elFinder node width
  197.          *
  198.          * @type String
  199.          * @default "auto"
  200.          **/
  201.         width  = 'auto',
  202.        
  203.         /**
  204.          * elFinder node height
  205.          *
  206.          * @type Number
  207.          * @default 400
  208.          **/
  209.         height = 400,
  210.        
  211.         /**
  212.          * elfinder path for sound played on remove
  213.          * @type String
  214.          * @default ./sounds/
  215.          **/
  216.         soundPath = './sounds/',
  217.                
  218.         beeper = $(document.createElement('audio')).hide().appendTo('body')[0],
  219.            
  220.         syncInterval,
  221.        
  222.         uiCmdMapPrev = '',
  223.        
  224.         open = function(data) {
  225.             var volumeid, contextmenu, emptyDirs = {}, stayDirs = {};
  226.            
  227.             self.commandMap = (data.options.uiCmdMap && Object.keys(data.options.uiCmdMap).length)? data.options.uiCmdMap : {};
  228.            
  229.             // support volume driver option `uiCmdMap`
  230.             if (uiCmdMapPrev !== JSON.stringify(self.commandMap)) {
  231.                 uiCmdMapPrev = JSON.stringify(self.commandMap);
  232.                 if (Object.keys(self.commandMap).length) {
  233.                     // for contextmenu
  234.                     contextmenu = self.getUI('contextmenu');
  235.                     if (!contextmenu.data('cmdMaps')) {
  236.                         contextmenu.data('cmdMaps', {});
  237.                     }
  238.                     volumeid = data.cwd? data.cwd.volumeid : null;
  239.                     if (volumeid && !contextmenu.data('cmdMaps')[volumeid]) {
  240.                         contextmenu.data('cmdMaps')[volumeid] = self.commandMap;
  241.                     }
  242.                 }
  243.             }
  244.            
  245.             if (data.init) {
  246.                 // init - reset cache
  247.                 files = {};
  248.             } else {
  249.                 // remove only files from prev cwd
  250.                 // and collapsed directory (included 100+ directories) to empty for perfomance tune in DnD
  251.                 $.each(Object.keys(files), function(n, i) {
  252.                     var isDir = (files[i].mime === 'directory'),
  253.                         phash = files[i].phash,
  254.                         collapsed = self.res('class', 'navcollapse'),
  255.                         pnav;
  256.                     if (
  257.                         (!isDir
  258.                             || emptyDirs[phash]
  259.                             || (!stayDirs[phash]
  260.                                 && $('#'+self.navHash2Id(files[i].hash)).is(':hidden')
  261.                                 && $('#'+self.navHash2Id(phash)).next('.elfinder-navbar-subtree').children().length > 100
  262.                             )
  263.                         )
  264.                         && (isDir || phash === cwd)
  265.                         && $.inArray(i, remember) === -1
  266.                     ) {
  267.                         if (isDir && !emptyDirs[phash]) {
  268.                             emptyDirs[phash] = true;
  269.                         }
  270.                         delete files[i];
  271.                     } else if (isDir) {
  272.                         stayDirs[phash] = true;
  273.                     }
  274.                 });
  275.                 $.each(Object.keys(emptyDirs), function(n, i) {
  276.                     var rmClass = 'elfinder-subtree-loaded ' + self.res('class', 'navexpand');
  277.                     $('#'+self.navHash2Id(i))
  278.                      .removeClass(rmClass)
  279.                      .next('.elfinder-navbar-subtree').empty();
  280.                 });
  281.             }
  282.  
  283.             cwd = data.cwd.hash;
  284.             cache(data.files);
  285.             if (!files[cwd]) {
  286.                 cache([data.cwd]);
  287.             }
  288.             self.lastDir(cwd);
  289.            
  290.         },
  291.        
  292.         /**
  293.          * Store info about files/dirs in "files" object.
  294.          *
  295.          * @param  Array  files
  296.          * @return void
  297.          **/
  298.         cache = function(data) {
  299.             var l = data.length, f, i;
  300.  
  301.             for (i = 0; i < l; i++) {
  302.                 f = data[i];
  303.                 if (f.name && f.hash && f.mime) {
  304.                     if (!f.phash) {
  305.                         var name = 'volume_'+f.name,
  306.                             i18 = self.i18n(name);
  307.  
  308.                         if (name != i18) {
  309.                             f.i18 = i18;
  310.                         }
  311.                        
  312.                         // set disabledCmds of each volume
  313.                         if (f.volumeid && f.disabled) {
  314.                             self.disabledCmds[f.volumeid] = f.disabled;
  315.                         }
  316.                     }
  317.                     files[f.hash] = f;
  318.                 }
  319.             }
  320.         },
  321.        
  322.         /**
  323.          * Exec shortcut
  324.          *
  325.          * @param  jQuery.Event  keydown/keypress event
  326.          * @return void
  327.          */
  328.         execShortcut = function(e) {
  329.             var code    = e.keyCode,
  330.                 ctrlKey = !!(e.ctrlKey || e.metaKey);
  331.  
  332.             if (enabled) {
  333.  
  334.                 $.each(shortcuts, function(i, shortcut) {
  335.                     if (shortcut.type    == e.type
  336.                     && shortcut.keyCode  == code
  337.                     && shortcut.shiftKey == e.shiftKey
  338.                     && shortcut.ctrlKey  == ctrlKey
  339.                     && shortcut.altKey   == e.altKey) {
  340.                         e.preventDefault()
  341.                         e.stopPropagation();
  342.                         shortcut.callback(e, self);
  343.                         self.debug('shortcut-exec', i+' : '+shortcut.description);
  344.                     }
  345.                 });
  346.                
  347.                 // prevent tab out of elfinder
  348.                 if (code == 9 && !$(e.target).is(':input')) {
  349.                     e.preventDefault();
  350.                 }
  351.                
  352.                 // cancel copy or cut by [Esc] key
  353.                 if (code == 27 && self.clipboard().length) {
  354.                     self.clipboard([]);
  355.                 }
  356.  
  357.             }
  358.         },
  359.         date = new Date(),
  360.         utc,
  361.         i18n
  362.         ;
  363.  
  364.  
  365.     /**
  366.      * Protocol version
  367.      *
  368.      * @type String
  369.      **/
  370.     this.api = null;
  371.    
  372.     /**
  373.      * elFinder use new api
  374.      *
  375.      * @type Boolean
  376.      **/
  377.     this.newAPI = false;
  378.    
  379.     /**
  380.      * elFinder use old api
  381.      *
  382.      * @type Boolean
  383.      **/
  384.     this.oldAPI = false;
  385.    
  386.     /**
  387.      * Net drivers names
  388.      *
  389.      * @type Array
  390.      **/
  391.     this.netDrivers = [];
  392.     /**
  393.      * User os. Required to bind native shortcuts for open/rename
  394.      *
  395.      * @type String
  396.      **/
  397.     this.OS = navigator.userAgent.indexOf('Mac') !== -1 ? 'mac' : navigator.userAgent.indexOf('Win') !== -1  ? 'win' : 'other';
  398.    
  399.     /**
  400.      * User browser UA.
  401.      * jQuery.browser: version deprecated: 1.3, removed: 1.9
  402.      *
  403.      * @type Object
  404.      **/
  405.     this.UA = (function(){
  406.         var webkit = !document.uniqueID && !window.opera && !window.sidebar && window.localStorage && typeof window.orientation == "undefined";
  407.         return {
  408.             // Browser IE <= IE 6
  409.             ltIE6:typeof window.addEventListener == "undefined" && typeof document.documentElement.style.maxHeight == "undefined",
  410.             // Browser IE <= IE 7
  411.             ltIE7:typeof window.addEventListener == "undefined" && typeof document.querySelectorAll == "undefined",
  412.             // Browser IE <= IE 8
  413.             ltIE8:typeof window.addEventListener == "undefined" && typeof document.getElementsByClassName == "undefined",
  414.             IE:document.uniqueID,
  415.             Firefox:window.sidebar,
  416.             Opera:window.opera,
  417.             Webkit:webkit,
  418.             Chrome:webkit && window.chrome,
  419.             Safari:webkit && !window.chrome,
  420.             Mobile:typeof window.orientation != "undefined",
  421.             Touch:typeof window.ontouchstart != "undefined"
  422.         };
  423.     })();
  424.    
  425.     /**
  426.      * Configuration options
  427.      *
  428.      * @type Object
  429.      **/
  430.     this.options = $.extend(true, {}, this._options, opts||{});
  431.    
  432.     if (opts.ui) {
  433.         this.options.ui = opts.ui;
  434.     }
  435.    
  436.     if (opts.commands) {
  437.         this.options.commands = opts.commands;
  438.     }
  439.    
  440.     if (opts.uiOptions && opts.uiOptions.toolbar) {
  441.         this.options.uiOptions.toolbar = opts.uiOptions.toolbar;
  442.     }
  443.  
  444.     if (opts.uiOptions && opts.uiOptions.cwd && opts.uiOptions.cwd.listView && opts.uiOptions.cwd.listView.columns) {
  445.         this.options.uiOptions.cwd.listView.columns = opts.uiOptions.cwd.listView.columns;
  446.     }
  447.     if (opts.uiOptions && opts.uiOptions.cwd && opts.uiOptions.cwd.listView && opts.uiOptions.cwd.listView.columnsCustomName) {
  448.         this.options.uiOptions.cwd.listView.columnsCustomName = opts.uiOptions.cwd.listView.columnsCustomName;
  449.     }
  450.  
  451.     // configure for CORS
  452.     (function(){
  453.         var parseUrl = document.createElement('a'),
  454.             parseUploadUrl;
  455.         parseUrl.href = opts.url;
  456.         if (opts.urlUpload && (opts.urlUpload !== opts.url)) {
  457.             parseUploadUrl = document.createElement('a');
  458.             parseUploadUrl.href = opts.urlUpload;
  459.         }
  460.         if (window.location.host !== parseUrl.host || (parseUploadUrl && (window.location.host !== parseUploadUrl.host))) {
  461.             if (!$.isPlainObject(self.options.customHeaders)) {
  462.                 self.options.customHeaders = {};
  463.             }
  464.             if (!$.isPlainObject(self.options.xhrFields)) {
  465.                 self.options.xhrFields = {};
  466.             }
  467.             self.options.requestType = 'post';
  468.             self.options.customHeaders['X-Requested-With'] = 'XMLHttpRequest';
  469.             self.options.xhrFields['withCredentials'] = true;
  470.         }
  471.     })();
  472.  
  473.     $.extend(this.options.contextmenu, opts.contextmenu);
  474.    
  475.     /**
  476.      * Ajax request type
  477.      *
  478.      * @type String
  479.      * @default "get"
  480.      **/
  481.     this.requestType = /^(get|post)$/i.test(this.options.requestType) ? this.options.requestType.toLowerCase() : 'get',
  482.    
  483.     /**
  484.      * Any data to send across every ajax request
  485.      *
  486.      * @type Object
  487.      * @default {}
  488.      **/
  489.     this.customData = $.isPlainObject(this.options.customData) ? this.options.customData : {};
  490.  
  491.     /**
  492.      * Any custom headers to send across every ajax request
  493.      *
  494.      * @type Object
  495.      * @default {}
  496.     */
  497.     this.customHeaders = $.isPlainObject(this.options.customHeaders) ? this.options.customHeaders : {};
  498.  
  499.     /**
  500.      * Any custom xhrFields to send across every ajax request
  501.      *
  502.      * @type Object
  503.      * @default {}
  504.      */
  505.     this.xhrFields = $.isPlainObject(this.options.xhrFields) ? this.options.xhrFields : {};
  506.  
  507.     /**
  508.      * ID. Required to create unique cookie name
  509.      *
  510.      * @type String
  511.      **/
  512.     this.id = id;
  513.    
  514.     /**
  515.      * URL to upload files
  516.      *
  517.      * @type String
  518.      **/
  519.     this.uploadURL = opts.urlUpload || opts.url;
  520.    
  521.     /**
  522.      * Events namespace
  523.      *
  524.      * @type String
  525.      **/
  526.     this.namespace = namespace;
  527.  
  528.     /**
  529.      * Interface language
  530.      *
  531.      * @type String
  532.      * @default "en"
  533.      **/
  534.     this.lang = this.i18[this.options.lang] && this.i18[this.options.lang].messages ? this.options.lang : 'en';
  535.    
  536.     i18n = this.lang == 'en'
  537.         ? this.i18['en']
  538.         : $.extend(true, {}, this.i18['en'], this.i18[this.lang]);
  539.    
  540.     /**
  541.      * Interface direction
  542.      *
  543.      * @type String
  544.      * @default "ltr"
  545.      **/
  546.     this.direction = i18n.direction;
  547.    
  548.     /**
  549.      * i18 messages
  550.      *
  551.      * @type Object
  552.      **/
  553.     this.messages = i18n.messages;
  554.    
  555.     /**
  556.      * Date/time format
  557.      *
  558.      * @type String
  559.      * @default "m.d.Y"
  560.      **/
  561.     this.dateFormat = this.options.dateFormat || i18n.dateFormat;
  562.    
  563.     /**
  564.      * Date format like "Yesterday 10:20:12"
  565.      *
  566.      * @type String
  567.      * @default "{day} {time}"
  568.      **/
  569.     this.fancyFormat = this.options.fancyDateFormat || i18n.fancyDateFormat;
  570.  
  571.     /**
  572.      * Today timestamp
  573.      *
  574.      * @type Number
  575.      **/
  576.     this.today = (new Date(date.getFullYear(), date.getMonth(), date.getDate())).getTime()/1000;
  577.    
  578.     /**
  579.      * Yesterday timestamp
  580.      *
  581.      * @type Number
  582.      **/
  583.     this.yesterday = this.today - 86400;
  584.    
  585.     utc = this.options.UTCDate ? 'UTC' : '';
  586.    
  587.     this.getHours    = 'get'+utc+'Hours';
  588.     this.getMinutes  = 'get'+utc+'Minutes';
  589.     this.getSeconds  = 'get'+utc+'Seconds';
  590.     this.getDate     = 'get'+utc+'Date';
  591.     this.getDay      = 'get'+utc+'Day';
  592.     this.getMonth    = 'get'+utc+'Month';
  593.     this.getFullYear = 'get'+utc+'FullYear';
  594.    
  595.     /**
  596.      * Css classes
  597.      *
  598.      * @type String
  599.      **/
  600.     this.cssClass = 'ui-helper-reset ui-helper-clearfix ui-widget ui-widget-content ui-corner-all elfinder elfinder-'+(this.direction == 'rtl' ? 'rtl' : 'ltr')+' '+this.options.cssClass;
  601.  
  602.     /**
  603.      * Method to store/fetch data
  604.      *
  605.      * @type Function
  606.      **/
  607.     this.storage = (function() {
  608.         try {
  609.             return 'localStorage' in window && window['localStorage'] !== null ? self.localStorage : self.cookie;
  610.         } catch (e) {
  611.             return self.cookie;
  612.         }
  613.     })();
  614.  
  615.     this.viewType = this.storage('view') || this.options.defaultView || 'icons';
  616.  
  617.     this.sortType = this.storage('sortType') || this.options.sortType || 'name';
  618.    
  619.     this.sortOrder = this.storage('sortOrder') || this.options.sortOrder || 'asc';
  620.  
  621.     this.sortStickFolders = this.storage('sortStickFolders');
  622.  
  623.     if (this.sortStickFolders === null) {
  624.         this.sortStickFolders = !!this.options.sortStickFolders;
  625.     } else {
  626.         this.sortStickFolders = !!this.sortStickFolders
  627.     }
  628.  
  629.     this.sortRules = $.extend(true, {}, this._sortRules, this.options.sortsRules);
  630.    
  631.     $.each(this.sortRules, function(name, method) {
  632.         if (typeof method != 'function') {
  633.             delete self.sortRules[name];
  634.         }
  635.     });
  636.    
  637.     this.compare = $.proxy(this.compare, this);
  638.    
  639.     /**
  640.      * Delay in ms before open notification dialog
  641.      *
  642.      * @type Number
  643.      * @default 500
  644.      **/
  645.     this.notifyDelay = this.options.notifyDelay > 0 ? parseInt(this.options.notifyDelay) : 500;
  646.    
  647.     /**
  648.      * Dragging UI Helper object
  649.      *
  650.      * @type jQuery | null
  651.      **/
  652.     this.draggingUiHelper = null,
  653.    
  654.     /**
  655.      * Base draggable options
  656.      *
  657.      * @type Object
  658.      **/
  659.     this.draggable = {
  660.         appendTo   : 'body',
  661.         addClasses : true,
  662.         delay      : 30,
  663.         distance   : 8,
  664.         revert     : true,
  665.         refreshPositions : false,
  666.         cursor     : 'move',
  667.         cursorAt   : {left : 50, top : 47},
  668.         start      : function(e, ui) {
  669.             var targets = $.map(ui.helper.data('files')||[], function(h) { return h || null ;}),
  670.             locked = false,
  671.             cnt, h;
  672.             self.draggingUiHelper = ui.helper;
  673.             cnt = targets.length;
  674.             while (cnt--) {
  675.                 h = targets[cnt];
  676.                 if (files[h].locked) {
  677.                     locked = true;
  678.                     ui.helper.addClass('elfinder-drag-helper-plus').data('locked', true);
  679.                     break;
  680.                 }
  681.             }
  682.             !locked && self.trigger('lockfiles', {files : targets});
  683.  
  684.         },
  685.         drag       : function(e, ui) {
  686.             if (ui.helper.data('refreshPositions') && $(this).draggable('instance')) {
  687.                 if (ui.helper.data('refreshPositions') > 0) {
  688.                     $(this).draggable('option', { refreshPositions : true });
  689.                     ui.helper.data('refreshPositions', -1);
  690.                 } else {
  691.                     $(this).draggable('option', { refreshPositions : false });
  692.                     ui.helper.data('refreshPositions', null);
  693.                 }
  694.             }
  695.         },
  696.         stop       : function(e, ui) {
  697.             var files;
  698.             $(this).draggable('instance') && $(this).draggable('option', { refreshPositions : false });
  699.             self.draggingUiHelper = null;
  700.             self.trigger('focus').trigger('dragstop');
  701.             if (! ui.helper.data('droped')) {
  702.                 files = $.map(ui.helper.data('files')||[], function(h) { return h || null ;});
  703.                 self.trigger('unlockfiles', {files : files});
  704.                 self.trigger('selectfiles', {files : files});
  705.             }
  706.         },
  707.         helper     : function(e, ui) {
  708.             var element = this.id ? $(this) : $(this).parents('[id]:first'),
  709.                 helper  = $('<div class="elfinder-drag-helper"><span class="elfinder-drag-helper-icon-plus"/></div>'),
  710.                 icon    = function(f) {
  711.                     var mime = f.mime, i;
  712.                     i = '<div class="elfinder-cwd-icon '+self.mime2class(mime)+' ui-corner-all"/>';
  713.                     if (f.tmb && f.tmb !== 1) {
  714.                         i = $(i).css('background', "url('"+self.option('tmbUrl')+f.tmb+"') center center no-repeat").get(0).outerHTML;
  715.                     }
  716.                     return i;
  717.                 },
  718.                 hashes, l, ctr;
  719.            
  720.             self.draggingUiHelper && self.draggingUiHelper.stop(true, true);
  721.            
  722.             self.trigger('dragstart', {target : element[0], originalEvent : e});
  723.            
  724.             hashes = element.hasClass(self.res('class', 'cwdfile'))
  725.                 ? self.selected()
  726.                 : [self.navId2Hash(element.attr('id'))];
  727.            
  728.             helper.append(icon(files[hashes[0]])).data('files', hashes).data('locked', false).data('droped', false);
  729.  
  730.             if ((l = hashes.length) > 1) {
  731.                 helper.append(icon(files[hashes[l-1]]) + '<span class="elfinder-drag-num">'+l+'</span>');
  732.             }
  733.            
  734.             $(document).on(keydown + ' keyup.' + namespace, function(e){
  735.                 var chk = (e.shiftKey||e.ctrlKey||e.metaKey);
  736.                 if (ctr !== chk) {
  737.                     ctr = chk;
  738.                     if (helper.is(':visible') && ! helper.data('locked') && ! helper.data('droped')) {
  739.                         helper.toggleClass('elfinder-drag-helper-plus', ctr);
  740.                         self.trigger(ctr? 'unlockfiles' : 'lockfiles', {files : hashes});
  741.                     }
  742.                 }
  743.             });
  744.            
  745.             return helper;
  746.         }
  747.     };
  748.    
  749.     /**
  750.      * Base droppable options
  751.      *
  752.      * @type Object
  753.      **/
  754.     this.droppable = {
  755.             greedy     : true,
  756.             tolerance  : 'pointer',
  757.             accept     : '.elfinder-cwd-file-wrapper,.elfinder-navbar-dir,.elfinder-cwd-file',
  758.             hoverClass : this.res('class', 'adroppable'),
  759.             drop : function(e, ui) {
  760.                 var dst     = $(this),
  761.                     targets = $.map(ui.helper.data('files')||[], function(h) { return h || null }),
  762.                     result  = [],
  763.                     dups    = [],
  764.                     unlocks = [],
  765.                     isCopy  = (e.ctrlKey||e.shiftKey||e.metaKey||ui.helper.data('locked'))? true : false,
  766.                     c       = 'class',
  767.                     cnt, hash, i, h;
  768.                
  769.                 ui.helper.data('droped', true);
  770.                 if (dst.hasClass(self.res(c, 'cwdfile'))) {
  771.                     hash = dst.attr('id');
  772.                 } else if (dst.hasClass(self.res(c, 'navdir'))) {
  773.                     hash = self.navId2Hash(dst.attr('id'));
  774.                 } else {
  775.                     hash = cwd;
  776.                 }
  777.  
  778.                 cnt = targets.length;
  779.                
  780.                 while (cnt--) {
  781.                     h = targets[cnt];
  782.                     // ignore drop into itself or in own location
  783.                     if (h != hash && files[h].phash != hash) {
  784.                         result.push(h);
  785.                     } else {
  786.                         ((isCopy && h !== hash && files[hash].write)? dups : unlocks).push(h);
  787.                     }
  788.                 }
  789.                 unlocks.length && self.trigger('unlockfiles', {files: unlocks});
  790.                 if (dups.length) {
  791.                     ui.helper.hide();
  792.                     self.exec('duplicate', dups);
  793.                 }
  794.                
  795.                 if (result.length) {
  796.                     ui.helper.hide();
  797.                     self.clipboard(result, !isCopy);
  798.                     self.exec('paste', hash, void 0, hash).always(function(){
  799.                         self.trigger('unlockfiles', {files : targets});
  800.                     });
  801.                     self.trigger('drop', {files : targets});
  802.                 }
  803.             }
  804.         };
  805.    
  806.     /**
  807.      * Return true if filemanager is active
  808.      *
  809.      * @return Boolean
  810.      **/
  811.     this.enabled = function() {
  812.         return node.is(':visible') && enabled;
  813.     }
  814.    
  815.     /**
  816.      * Return true if filemanager is visible
  817.      *
  818.      * @return Boolean
  819.      **/
  820.     this.visible = function() {
  821.         return node.is(':visible');
  822.     }
  823.    
  824.     /**
  825.      * Return root dir hash for current working directory
  826.      *
  827.      * @return String
  828.      */
  829.     this.root = function(hash) {
  830.         var dir = files[hash || cwd], i;
  831.        
  832.         while (dir && dir.phash) {
  833.             dir = files[dir.phash]
  834.         }
  835.         if (dir) {
  836.             return dir.hash;
  837.         }
  838.        
  839.         while (i in files && files.hasOwnProperty(i)) {
  840.             dir = files[i]
  841.             if (!dir.phash && !dir.mime == 'directory' && dir.read) {
  842.                 return dir.hash
  843.             }
  844.         }
  845.        
  846.         return '';
  847.     }
  848.    
  849.     /**
  850.      * Return current working directory info
  851.      *
  852.      * @return Object
  853.      */
  854.     this.cwd = function() {
  855.         return files[cwd] || {};
  856.     }
  857.    
  858.     /**
  859.      * Return required cwd option
  860.      *
  861.      * @param  String  option name
  862.      * @return mixed
  863.      */
  864.     this.option = function(name) {
  865.         return cwdOptions[name]||'';
  866.     }
  867.    
  868.     /**
  869.      * Return file data from current dir or tree by it's hash
  870.      *
  871.      * @param  String  file hash
  872.      * @return Object
  873.      */
  874.     this.file = function(hash) {
  875.         return files[hash];
  876.     };
  877.    
  878.     /**
  879.      * Return all cached files
  880.      *
  881.      * @return Array
  882.      */
  883.     this.files = function() {
  884.         return $.extend(true, {}, files);
  885.     }
  886.    
  887.     /**
  888.      * Return list of file parents hashes include file hash
  889.      *
  890.      * @param  String  file hash
  891.      * @return Array
  892.      */
  893.     this.parents = function(hash) {
  894.         var parents = [],
  895.             dir;
  896.        
  897.         while ((dir = this.file(hash))) {
  898.             parents.unshift(dir.hash);
  899.             hash = dir.phash;
  900.         }
  901.         return parents;
  902.     }
  903.    
  904.     this.path2array = function(hash, i18) {
  905.         var file,
  906.             path = [];
  907.            
  908.         while (hash && (file = files[hash]) && file.hash) {
  909.             path.unshift(i18 && file.i18 ? file.i18 : file.name);
  910.             hash = file.phash;
  911.         }
  912.            
  913.         return path;
  914.     }
  915.    
  916.     /**
  917.      * Return file path
  918.      *
  919.      * @param  Object  file
  920.      * @return String
  921.      */
  922.     this.path = function(hash, i18) {
  923.         return files[hash] && files[hash].path
  924.             ? files[hash].path
  925.             : this.path2array(hash, i18).join(cwdOptions.separator);
  926.     }
  927.    
  928.     /**
  929.      * Return file url if set
  930.      *
  931.      * @param  Object  file
  932.      * @return String
  933.      */
  934.     this.url = function(hash) {
  935.         var file = files[hash];
  936.        
  937.         if (!file || !file.read) {
  938.             return '';
  939.         }
  940.        
  941.         if (file.url == '1') {
  942.             this.request({
  943.                 data : {cmd : 'url', target : hash},
  944.                 preventFail : true,
  945.                 options: {async: false}
  946.             })
  947.             .done(function(data) {
  948.                 file.url = data.url || '';
  949.             })
  950.             .fail(function() {
  951.                 file.url = '';
  952.             });
  953.         }
  954.        
  955.         if (file.url) {
  956.             return file.url;
  957.         }
  958.        
  959.         if (cwdOptions.url) {
  960.             return cwdOptions.url + $.map(this.path2array(hash), function(n) { return encodeURIComponent(n); }).slice(1).join('/')
  961.         }
  962.  
  963.         var params = $.extend({}, this.customData, {
  964.             cmd: 'file',
  965.             target: file.hash
  966.         });
  967.         if (this.oldAPI) {
  968.             params.cmd = 'open';
  969.             params.current = file.phash;
  970.         }
  971.         return this.options.url + (this.options.url.indexOf('?') === -1 ? '?' : '&') + $.param(params, true);
  972.     }
  973.    
  974.     /**
  975.      * Return thumbnail url
  976.      *
  977.      * @param  String  file hash
  978.      * @return String
  979.      */
  980.     this.tmb = function(hash) {
  981.         var file = files[hash],
  982.             url = file && file.tmb && file.tmb != 1 ? cwdOptions['tmbUrl'] + file.tmb : '';
  983.        
  984.         if (url && (this.UA.Opera || this.UA.IE)) {
  985.             url += '?_=' + new Date().getTime();
  986.         }
  987.         return url;
  988.     }
  989.    
  990.     /**
  991.      * Return selected files hashes
  992.      *
  993.      * @return Array
  994.      **/
  995.     this.selected = function() {
  996.         return selected.slice(0);
  997.     }
  998.    
  999.     /**
  1000.      * Return selected files info
  1001.      *
  1002.      * @return Array
  1003.      */
  1004.     this.selectedFiles = function() {
  1005.         return $.map(selected, function(hash) { return files[hash] ? $.extend({}, files[hash]) : null });
  1006.     };
  1007.    
  1008.     /**
  1009.      * Return true if file with required name existsin required folder
  1010.      *
  1011.      * @param  String  file name
  1012.      * @param  String  parent folder hash
  1013.      * @return Boolean
  1014.      */
  1015.     this.fileByName = function(name, phash) {
  1016.         var hash;
  1017.    
  1018.         for (hash in files) {
  1019.             if (files.hasOwnProperty(hash) && files[hash].phash == phash && files[hash].name == name) {
  1020.                 return files[hash];
  1021.             }
  1022.         }
  1023.     };
  1024.    
  1025.     /**
  1026.      * Valid data for required command based on rules
  1027.      *
  1028.      * @param  String  command name
  1029.      * @param  Object  cammand's data
  1030.      * @return Boolean
  1031.      */
  1032.     this.validResponse = function(cmd, data) {
  1033.         return data.error || this.rules[this.rules[cmd] ? cmd : 'defaults'](data);
  1034.     }
  1035.    
  1036.     /**
  1037.      * Return bytes from ini formated size
  1038.      *
  1039.      * @param  String  ini formated size
  1040.      * @return Integer
  1041.      */
  1042.     this.returnBytes = function(val) {
  1043.         if (val == '-1') val = 0;
  1044.         if (val) {
  1045.             // for ex. 1mb, 1KB
  1046.             val = val.replace(/b$/i, '');
  1047.             var last = val.charAt(val.length - 1).toLowerCase();
  1048.             val = val.replace(/[gmk]$/i, '');
  1049.             if (last == 'g') {
  1050.                 val = val * 1024 * 1024 * 1024;
  1051.             } else if (last == 'm') {
  1052.                 val = val * 1024 * 1024;
  1053.             } else if (last == 'k') {
  1054.                 val = val * 1024;
  1055.             }
  1056.         }
  1057.         return val;
  1058.     };
  1059.    
  1060.     /**
  1061.      * Proccess ajax request.
  1062.      * Fired events :
  1063.      * @todo
  1064.      * @example
  1065.      * @todo
  1066.      * @return $.Deferred
  1067.      */
  1068.     this.request = function(options) {
  1069.         var self     = this,
  1070.             o        = this.options,
  1071.             dfrd     = $.Deferred(),
  1072.             // request data
  1073.             data     = $.extend({}, o.customData, {mimes : o.onlyMimes}, options.data || options),
  1074.             // command name
  1075.             cmd      = data.cmd,
  1076.             // call default fail callback (display error dialog) ?
  1077.             deffail  = !(options.preventDefault || options.preventFail),
  1078.             // call default success callback ?
  1079.             defdone  = !(options.preventDefault || options.preventDone),
  1080.             // options for notify dialog
  1081.             notify   = $.extend({}, options.notify),
  1082.             // do not normalize data - return as is
  1083.             raw      = !!options.raw,
  1084.             // sync files on request fail
  1085.             syncOnFail = options.syncOnFail,
  1086.             // open notify dialog timeout      
  1087.             timeout,
  1088.             // request options
  1089.             options = $.extend({
  1090.                 url      : o.url,
  1091.                 async    : true,
  1092.                 type     : this.requestType,
  1093.                 dataType : 'json',
  1094.                 cache    : false,
  1095.                 // timeout  : 100,
  1096.                 data     : data,
  1097.                 headers  : this.customHeaders,
  1098.                 xhrFields: this.xhrFields
  1099.             }, options.options || {}),
  1100.             /**
  1101.              * Default success handler.
  1102.              * Call default data handlers and fire event with command name.
  1103.              *
  1104.              * @param Object  normalized response data
  1105.              * @return void
  1106.              **/
  1107.             done = function(data) {
  1108.                 data.warning && self.error(data.warning);
  1109.                
  1110.                 cmd == 'open' && open($.extend(true, {}, data));
  1111.  
  1112.                 // fire some event to update cache/ui
  1113.                 data.removed && data.removed.length && self.remove(data);
  1114.                 data.added   && data.added.length   && self.add(data);
  1115.                 data.changed && data.changed.length && self.change(data);
  1116.                
  1117.                 // fire event with command name
  1118.                 self.trigger(cmd, data);
  1119.                
  1120.                 // force update content
  1121.                 data.sync && self.sync();
  1122.             },
  1123.             /**
  1124.              * Request error handler. Reject dfrd with correct error message.
  1125.              *
  1126.              * @param jqxhr  request object
  1127.              * @param String request status
  1128.              * @return void
  1129.              **/
  1130.             error = function(xhr, status) {
  1131.                 var error;
  1132.                
  1133.                 switch (status) {
  1134.                     case 'abort':
  1135.                         error = xhr.quiet ? '' : ['errConnect', 'errAbort'];
  1136.                         break;
  1137.                     case 'timeout':    
  1138.                         error = ['errConnect', 'errTimeout'];
  1139.                         break;
  1140.                     case 'parsererror':
  1141.                         error = ['errResponse', 'errDataNotJSON'];
  1142.                         break;
  1143.                     default:
  1144.                         if (xhr.status == 403) {
  1145.                             error = ['errConnect', 'errAccess'];
  1146.                         } else if (xhr.status == 404) {
  1147.                             error = ['errConnect', 'errNotFound'];
  1148.                         } else {
  1149.                             error = 'errConnect';
  1150.                         }
  1151.                 }
  1152.                
  1153.                 dfrd.reject(error, xhr, status);
  1154.             },
  1155.             /**
  1156.              * Request success handler. Valid response data and reject/resolve dfrd.
  1157.              *
  1158.              * @param Object  response data
  1159.              * @param String request status
  1160.              * @return void
  1161.              **/
  1162.             success = function(response) {
  1163.                 if (raw) {
  1164.                     return dfrd.resolve(response);
  1165.                 }
  1166.                
  1167.                 if (!response) {
  1168.                     return dfrd.reject(['errResponse', 'errDataEmpty'], xhr);
  1169.                 } else if (!$.isPlainObject(response)) {
  1170.                     return dfrd.reject(['errResponse', 'errDataNotJSON'], xhr);
  1171.                 } else if (response.error) {
  1172.                     return dfrd.reject(response.error, xhr);
  1173.                 } else if (!self.validResponse(cmd, response)) {
  1174.                     return dfrd.reject('errResponse', xhr);
  1175.                 }
  1176.  
  1177.                 response = self.normalize(response);
  1178.  
  1179.                 if (!self.api) {
  1180.                     self.api    = response.api || 1;
  1181.                     self.newAPI = self.api >= 2;
  1182.                     self.oldAPI = !self.newAPI;
  1183.                 }
  1184.                
  1185.                 if (response.options) {
  1186.                     cwdOptions = $.extend({}, cwdOptions, response.options);
  1187.                 }
  1188.  
  1189.                 if (response.netDrivers) {
  1190.                     self.netDrivers = response.netDrivers;
  1191.                 }
  1192.  
  1193.                 if (cmd == 'open' && !!data.init) {
  1194.                     self.uplMaxSize = self.returnBytes(response.uplMaxSize);
  1195.                     self.uplMaxFile = !!response.uplMaxFile? parseInt(response.uplMaxFile) : 20;
  1196.                 }
  1197.  
  1198.                 dfrd.resolve(response);
  1199.                 response.debug && self.debug('backend-debug', response.debug);
  1200.             },
  1201.             xhr, _xhr
  1202.             ;
  1203.  
  1204.         defdone && dfrd.done(done);
  1205.         dfrd.fail(function(error) {
  1206.             if (error) {
  1207.                 deffail ? self.error(error) : self.debug('error', self.i18n(error));
  1208.             }
  1209.         })
  1210.        
  1211.         if (!cmd) {
  1212.             return dfrd.reject('errCmdReq');
  1213.         }  
  1214.  
  1215.         if (syncOnFail) {
  1216.             dfrd.fail(function(error) {
  1217.                 error && self.sync();
  1218.             });
  1219.         }
  1220.  
  1221.         if (notify.type && notify.cnt) {
  1222.             timeout = setTimeout(function() {
  1223.                 self.notify(notify);
  1224.                 dfrd.always(function() {
  1225.                     notify.cnt = -(parseInt(notify.cnt)||0);
  1226.                     self.notify(notify);
  1227.                 })
  1228.             }, self.notifyDelay)
  1229.            
  1230.             dfrd.always(function() {
  1231.                 clearTimeout(timeout);
  1232.             });
  1233.         }
  1234.        
  1235.         // quiet abort not completed "open" requests
  1236.         if (cmd == 'open') {
  1237.             while ((_xhr = queue.pop())) {
  1238.                 if (_xhr.state() == 'pending') {
  1239.                     _xhr.quiet = true;
  1240.                     _xhr.abort();
  1241.                 }
  1242.             }
  1243.         }
  1244.  
  1245.         delete options.preventFail
  1246.  
  1247.         xhr = this.transport.send(options).fail(error).done(success);
  1248.        
  1249.         // this.transport.send(options)
  1250.        
  1251.         // add "open" xhr into queue
  1252.         if (cmd == 'open') {
  1253.             queue.unshift(xhr);
  1254.             dfrd.always(function() {
  1255.                 var ndx = $.inArray(xhr, queue);
  1256.                
  1257.                 ndx !== -1 && queue.splice(ndx, 1);
  1258.             });
  1259.         }
  1260.        
  1261.         return dfrd;
  1262.     };
  1263.    
  1264.     /**
  1265.      * Compare current files cache with new files and return diff
  1266.      *
  1267.      * @param  Array  new files
  1268.      * @return Object
  1269.      */
  1270.     this.diff = function(incoming) {
  1271.         var raw       = {},
  1272.             added     = [],
  1273.             removed   = [],
  1274.             changed   = [],
  1275.             isChanged = function(hash) {
  1276.                 var l = changed.length;
  1277.  
  1278.                 while (l--) {
  1279.                     if (changed[l].hash == hash) {
  1280.                         return true;
  1281.                     }
  1282.                 }
  1283.             };
  1284.            
  1285.         $.each(incoming, function(i, f) {
  1286.             raw[f.hash] = f;
  1287.         });
  1288.            
  1289.         // find removed
  1290.         $.each(files, function(hash, f) {
  1291.             !raw[hash] && removed.push(hash);
  1292.         });
  1293.        
  1294.         // compare files
  1295.         $.each(raw, function(hash, file) {
  1296.             var origin = files[hash];
  1297.  
  1298.             if (!origin) {
  1299.                 added.push(file);
  1300.             } else {
  1301.                 $.each(file, function(prop) {
  1302.                     if (file[prop] != origin[prop]) {
  1303.                         changed.push(file)
  1304.                         return false;
  1305.                     }
  1306.                 });
  1307.             }
  1308.         });
  1309.        
  1310.         // parents of removed dirs mark as changed (required for tree correct work)
  1311.         $.each(removed, function(i, hash) {
  1312.             var file  = files[hash],
  1313.                 phash = file.phash;
  1314.  
  1315.             if (phash
  1316.             && file.mime == 'directory'
  1317.             && $.inArray(phash, removed) === -1
  1318.             && raw[phash]
  1319.             && !isChanged(phash)) {
  1320.                 changed.push(raw[phash]);
  1321.             }
  1322.         });
  1323.        
  1324.         return {
  1325.             added   : added,
  1326.             removed : removed,
  1327.             changed : changed
  1328.         };
  1329.     }
  1330.    
  1331.     /**
  1332.      * Sync content
  1333.      *
  1334.      * @return jQuery.Deferred
  1335.      */
  1336.     this.sync = function() {
  1337.         var self  = this,
  1338.             dfrd  = $.Deferred().done(function() { self.trigger('sync'); }),
  1339.             opts1 = {
  1340.                 data           : {cmd : 'open', reload : 1, target : cwd, tree : this.ui.tree ? 1 : 0},
  1341.                 preventDefault : true
  1342.             },
  1343.             opts2 = {
  1344.                 data           : {cmd : 'parents', target : cwd},
  1345.                 preventDefault : true
  1346.             };
  1347.         $.when(
  1348.             this.request(opts1),
  1349.             this.request(opts2)
  1350.         )
  1351.         .fail(function(error) {
  1352.             dfrd.reject(error);
  1353.             error && self.request({
  1354.                 data   : {cmd : 'open', target : self.lastDir(''), tree : 1, init : 1},
  1355.                 notify : {type : 'open', cnt : 1, hideCnt : true},
  1356.                 preventDefault : true
  1357.             });
  1358.         })
  1359.         .done(function(odata, pdata) {
  1360.             var diff = self.diff(odata.files.concat(pdata && pdata.tree ? pdata.tree : []));
  1361.  
  1362.             diff.added.push(odata.cwd)
  1363.             diff.removed.length && self.remove(diff);
  1364.             diff.added.length   && self.add(diff);
  1365.             diff.changed.length && self.change(diff);
  1366.             return dfrd.resolve(diff);
  1367.         });
  1368.        
  1369.         return dfrd;
  1370.     }
  1371.    
  1372.     this.upload = function(files) {
  1373.             return this.transport.upload(files, this);
  1374.         }
  1375.    
  1376.     /**
  1377.      * Attach listener to events
  1378.      * To bind to multiply events at once, separate events names by space
  1379.      *
  1380.      * @param  String  event(s) name(s)
  1381.      * @param  Object  event handler
  1382.      * @return elFinder
  1383.      */
  1384.     this.bind = function(event, callback) {
  1385.         var i;
  1386.        
  1387.         if (typeof(callback) == 'function') {
  1388.             event = ('' + event).toLowerCase().split(/\s+/);
  1389.            
  1390.             for (i = 0; i < event.length; i++) {
  1391.                 if (listeners[event[i]] === void(0)) {
  1392.                     listeners[event[i]] = [];
  1393.                 }
  1394.                 listeners[event[i]].push(callback);
  1395.             }
  1396.         }
  1397.         return this;
  1398.     };
  1399.    
  1400.     /**
  1401.      * Remove event listener if exists
  1402.      *
  1403.      * @param  String    event name
  1404.      * @param  Function  callback
  1405.      * @return elFinder
  1406.      */
  1407.     this.unbind = function(event, callback) {
  1408.         var l = listeners[('' + event).toLowerCase()] || [],
  1409.             i = l.indexOf(callback);
  1410.  
  1411.         i > -1 && l.splice(i, 1);
  1412.         //delete callback; // need this?
  1413.         callback = null
  1414.         return this;
  1415.     };
  1416.    
  1417.     /**
  1418.      * Fire event - send notification to all event listeners
  1419.      *
  1420.      * @param  String   event type
  1421.      * @param  Object   data to send across event
  1422.      * @return elFinder
  1423.      */
  1424.     this.trigger = function(event, data) {
  1425.         var event    = event.toLowerCase(),
  1426.             isopen   = (event === 'open'),
  1427.             handlers = listeners[event] || [], i, l, jst;
  1428.        
  1429.         this.debug('event-'+event, data);
  1430.        
  1431.         if (isopen) {
  1432.             // for performance tuning
  1433.             jst = JSON.stringify(data);
  1434.         }
  1435.         if (handlers.length) {
  1436.             event = $.Event(event);
  1437.  
  1438.             l = handlers.length;
  1439.             for (i = 0; i < l; i++) {
  1440.                 // only callback has argument
  1441.                 if (handlers[i].length) {
  1442.                     // to avoid data modifications. remember about "sharing" passing arguments in js :)
  1443.                     event.data = isopen? JSON.parse(jst) : $.extend(true, {}, data);
  1444.                 }
  1445.  
  1446.                 try {
  1447.                     if (handlers[i](event, this) === false
  1448.                     || event.isDefaultPrevented()) {
  1449.                         this.debug('event-stoped', event.type);
  1450.                         break;
  1451.                     }
  1452.                 } catch (ex) {
  1453.                     window.console && window.console.log && window.console.log(ex);
  1454.                 }
  1455.                
  1456.             }
  1457.         }
  1458.         return this;
  1459.     }
  1460.    
  1461.     /**
  1462.      * Bind keybord shortcut to keydown event
  1463.      *
  1464.      * @example
  1465.      *    elfinder.shortcut({
  1466.      *       pattern : 'ctrl+a',
  1467.      *       description : 'Select all files',
  1468.      *       callback : function(e) { ... },
  1469.      *       keypress : true|false (bind to keypress instead of keydown)
  1470.      *    })
  1471.      *
  1472.      * @param  Object  shortcut config
  1473.      * @return elFinder
  1474.      */
  1475.     this.shortcut = function(s) {
  1476.         var patterns, pattern, code, i, parts;
  1477.        
  1478.         /*if (this.options.allowShortcuts && s.pattern && $.isFunction(s.callback)) {
  1479.             patterns = s.pattern.toUpperCase().split(/\s+/);
  1480.            
  1481.             for (i= 0; i < patterns.length; i++) {
  1482.                 pattern = patterns[i]
  1483.                 parts   = pattern.split('+');
  1484.                 code    = (code = parts.pop()).length == 1
  1485.                     ? code > 0 ? code : code.charCodeAt(0)
  1486.                     : $.ui.keyCode[code];
  1487.  
  1488.                 if (code && !shortcuts[pattern]) {
  1489.                     shortcuts[pattern] = {
  1490.                         keyCode     : code,
  1491.                         altKey      : $.inArray('ALT', parts)   != -1,
  1492.                         ctrlKey     : $.inArray('CTRL', parts)  != -1,
  1493.                         shiftKey    : $.inArray('SHIFT', parts) != -1,
  1494.                         type        : s.type || 'keydown',
  1495.                         callback    : s.callback,
  1496.                         description : s.description,
  1497.                         pattern     : pattern
  1498.                     };
  1499.                 }
  1500.             }
  1501.         }*/
  1502.         return this;
  1503.     }
  1504.    
  1505.     /**
  1506.      * Registered shortcuts
  1507.      *
  1508.      * @type Object
  1509.      **/
  1510.     this.shortcuts = function() {
  1511.         var ret = [];
  1512.        
  1513.         $.each(shortcuts, function(i, s) {
  1514.             ret.push([s.pattern, self.i18n(s.description)]);
  1515.         });
  1516.         return ret;
  1517.     };
  1518.    
  1519.     /**
  1520.      * Get/set clipboard content.
  1521.      * Return new clipboard content.
  1522.      *
  1523.      * @example
  1524.      *   this.clipboard([]) - clean clipboard
  1525.      *   this.clipboard([{...}, {...}], true) - put 2 files in clipboard and mark it as cutted
  1526.      *
  1527.      * @param  Array    new files hashes
  1528.      * @param  Boolean  cut files?
  1529.      * @return Array
  1530.      */
  1531.     this.clipboard = function(hashes, cut) {
  1532.         var map = function() { return $.map(clipboard, function(f) { return f.hash }); }
  1533.  
  1534.         if (hashes !== void(0)) {
  1535.             clipboard.length && this.trigger('unlockfiles', {files : map()});
  1536.             remember = [];
  1537.            
  1538.             clipboard = $.map(hashes||[], function(hash) {
  1539.                 var file = files[hash];
  1540.                 if (file) {
  1541.                    
  1542.                     remember.push(hash);
  1543.                    
  1544.                     return {
  1545.                         hash   : hash,
  1546.                         phash  : file.phash,
  1547.                         name   : file.name,
  1548.                         mime   : file.mime,
  1549.                         read   : file.read,
  1550.                         locked : file.locked,
  1551.                         cut    : !!cut
  1552.                     }
  1553.                 }
  1554.                 return null;
  1555.             });
  1556.             this.trigger('changeclipboard', {clipboard : clipboard.slice(0, clipboard.length)});
  1557.             cut && this.trigger('lockfiles', {files : map()});
  1558.         }
  1559.  
  1560.         // return copy of clipboard instead of refrence
  1561.         return clipboard.slice(0, clipboard.length);
  1562.     }
  1563.    
  1564.     /**
  1565.      * Return true if command enabled
  1566.      *
  1567.      * @param  String       command name
  1568.      * @param  String|void  hash for check of own volume's disabled cmds
  1569.      * @return Boolean
  1570.      */
  1571.     this.isCommandEnabled = function(name, dstHash) {
  1572.         var disabled;
  1573.         if (dstHash && self.root(dstHash) !== cwd) {
  1574.             $.each(self.disabledCmds, function(i, v){
  1575.                 if (dstHash.indexOf(i, 0) == 0) {
  1576.                     disabled = v;
  1577.                     return false;
  1578.                 }
  1579.             });
  1580.         }
  1581.         if (!disabled) {
  1582.             disabled = cwdOptions.disabled;
  1583.         }
  1584.         return this._commands[name] ? $.inArray(name, disabled) === -1 : false;
  1585.     }
  1586.    
  1587.     /**
  1588.      * Exec command and return result;
  1589.      *
  1590.      * @param  String         command name
  1591.      * @param  String|Array   usualy files hashes
  1592.      * @param  String|Array   command options
  1593.      * @param  String|void    hash for enabled check of own volume's disabled cmds
  1594.      * @return $.Deferred
  1595.      */    
  1596.     this.exec = function(cmd, files, opts, dstHash) {
  1597.         return this._commands[cmd] && this.isCommandEnabled(cmd, dstHash)
  1598.             ? this._commands[cmd].exec(files, opts)
  1599.             : $.Deferred().reject('No such command');
  1600.     }
  1601.    
  1602.     /**
  1603.      * Create and return dialog.
  1604.      *
  1605.      * @param  String|DOMElement  dialog content
  1606.      * @param  Object             dialog options
  1607.      * @return jQuery
  1608.      */
  1609.     this.dialog = function(content, options) {
  1610.         var dialog = $('<div/>').append(content).appendTo(node).elfinderdialog(options);
  1611.         this.bind('resize', function(){
  1612.             dialog.elfinderdialog('posInit');
  1613.         });
  1614.         return dialog;
  1615.     }
  1616.    
  1617.     /**
  1618.      * Return UI widget or node
  1619.      *
  1620.      * @param  String  ui name
  1621.      * @return jQuery
  1622.      */
  1623.     this.getUI = function(ui) {
  1624.         return this.ui[ui] || node;
  1625.     }
  1626.    
  1627.     this.command = function(name) {
  1628.         return name === void(0) ? this._commands : this._commands[name];
  1629.     }
  1630.    
  1631.     /**
  1632.      * Resize elfinder node
  1633.      *
  1634.      * @param  String|Number  width
  1635.      * @param  Number         height
  1636.      * @return void
  1637.      */
  1638.     this.resize = function(w, h) {
  1639.         node.css('width', w).height(h).trigger('resize');
  1640.         this.trigger('resize', {width : node.width(), height : node.height()});
  1641.     }
  1642.    
  1643.     /**
  1644.      * Restore elfinder node size
  1645.      *
  1646.      * @return elFinder
  1647.      */
  1648.     this.restoreSize = function() {
  1649.         this.resize(width, height);
  1650.     }
  1651.    
  1652.     this.show = function() {
  1653.         node.show();
  1654.         this.enable().trigger('show');
  1655.     }
  1656.    
  1657.     this.hide = function() {
  1658.         this.disable().trigger('hide');
  1659.         node.hide();
  1660.     }
  1661.    
  1662.     /**
  1663.      * Destroy this elFinder instance
  1664.      *
  1665.      * @return void
  1666.      **/
  1667.     this.destroy = function() {
  1668.         if (node && node[0].elfinder) {
  1669.             this.trigger('destroy').disable();
  1670.             listeners = {};
  1671.             shortcuts = {};
  1672.             $(document).add(node).off('.'+this.namespace);
  1673.             self.trigger = function() { }
  1674.             node.children().remove();
  1675.             node.append(prevContent.contents()).removeClass(this.cssClass).attr('style', prevStyle);
  1676.             node[0].elfinder = null;
  1677.             if (syncInterval) {
  1678.                 clearInterval(syncInterval);
  1679.             }
  1680.         }
  1681.     }
  1682.    
  1683.     /*************  init stuffs  ****************/
  1684.    
  1685.     // check jquery ui
  1686.     if (!($.fn.selectable && $.fn.draggable && $.fn.droppable)) {
  1687.         return alert(this.i18n('errJqui'));
  1688.     }
  1689.  
  1690.     // check node
  1691.     if (!node.length) {
  1692.         return alert(this.i18n('errNode'));
  1693.     }
  1694.     // check connector url
  1695.     if (!this.options.url) {
  1696.         return alert(this.i18n('errURL'));
  1697.     }
  1698.  
  1699.     $.extend($.ui.keyCode, {
  1700.         'F1' : 112,
  1701.         'F2' : 113,
  1702.         'F3' : 114,
  1703.         'F4' : 115,
  1704.         'F5' : 116,
  1705.         'F6' : 117,
  1706.         'F7' : 118,
  1707.         'F8' : 119,
  1708.         'F9' : 120
  1709.     });
  1710.    
  1711.     this.dragUpload = false;
  1712.     this.xhrUpload  = (typeof XMLHttpRequestUpload != 'undefined' || typeof XMLHttpRequestEventTarget != 'undefined') && typeof File != 'undefined' && typeof FormData != 'undefined';
  1713.    
  1714.     // configure transport object
  1715.     this.transport = {}
  1716.  
  1717.     if (typeof(this.options.transport) == 'object') {
  1718.         this.transport = this.options.transport;
  1719.         if (typeof(this.transport.init) == 'function') {
  1720.             this.transport.init(this)
  1721.         }
  1722.     }
  1723.    
  1724.     if (typeof(this.transport.send) != 'function') {
  1725.         this.transport.send = function(opts) { return $.ajax(opts); }
  1726.     }
  1727.    
  1728.     if (this.transport.upload == 'iframe') {
  1729.         this.transport.upload = $.proxy(this.uploads.iframe, this);
  1730.     } else if (typeof(this.transport.upload) == 'function') {
  1731.         this.dragUpload = !!this.options.dragUploadAllow;
  1732.     } else if (this.xhrUpload && !!this.options.dragUploadAllow) {
  1733.         this.transport.upload = $.proxy(this.uploads.xhr, this);
  1734.         this.dragUpload = true;
  1735.     } else {
  1736.         this.transport.upload = $.proxy(this.uploads.iframe, this);
  1737.     }
  1738.  
  1739.     /**
  1740.      * Alias for this.trigger('error', {error : 'message'})
  1741.      *
  1742.      * @param  String  error message
  1743.      * @return elFinder
  1744.      **/
  1745.     this.error = function() {
  1746.         var arg = arguments[0];
  1747.         return arguments.length == 1 && typeof(arg) == 'function'
  1748.             ? self.bind('error', arg)
  1749.             : self.trigger('error', {error : arg});
  1750.     }
  1751.    
  1752.     // create bind/trigger aliases for build-in events
  1753.     $.each(['enable', 'disable', 'load', 'open', 'reload', 'select',  'add', 'remove', 'change', 'dblclick', 'getfile', 'lockfiles', 'unlockfiles', 'selectfiles', 'unselectfiles', 'dragstart', 'dragstop', 'search', 'searchend', 'viewchange'], function(i, name) {
  1754.         self[name] = function() {
  1755.             var arg = arguments[0];
  1756.             return arguments.length == 1 && typeof(arg) == 'function'
  1757.                 ? self.bind(name, arg)
  1758.                 : self.trigger(name, $.isPlainObject(arg) ? arg : {});
  1759.         }
  1760.     });
  1761.    
  1762.     // bind core event handlers
  1763.     this
  1764.         .enable(function() {
  1765.             if (!enabled && self.visible() && self.ui.overlay.is(':hidden')) {
  1766.                 enabled = true;
  1767.                 document.activeElement && document.activeElement.blur();
  1768.                 node.removeClass('elfinder-disabled');
  1769.             }
  1770.         })
  1771.         .disable(function() {
  1772.             prevEnabled = enabled;
  1773.             enabled = false;
  1774.             node.addClass('elfinder-disabled');
  1775.         })
  1776.         .open(function() {
  1777.             selected = [];
  1778.         })
  1779.         .select(function(e) {
  1780.             selected = $.map(e.data.selected || e.data.value|| [], function(hash) { return files[hash] ? hash : null; });
  1781.         })
  1782.         .error(function(e) {
  1783.             var opts  = {
  1784.                     cssClass  : 'elfinder-dialog-error',
  1785.                     title     : self.i18n(self.i18n('error')),
  1786.                     resizable : false,
  1787.                     destroyOnClose : true,
  1788.                     buttons   : {}
  1789.             };
  1790.  
  1791.             opts.buttons[self.i18n(self.i18n('btnClose'))] = function() { $(this).elfinderdialog('close'); };
  1792.  
  1793.             self.dialog('<span class="elfinder-dialog-icon elfinder-dialog-icon-error"/>'+self.i18n(e.data.error), opts);
  1794.         })
  1795.         .bind('tree parents', function(e) {
  1796.             cache(e.data.tree || []);
  1797.         })
  1798.         .bind('tmb', function(e) {
  1799.             $.each(e.data.images||[], function(hash, tmb) {
  1800.                 if (files[hash]) {
  1801.                     files[hash].tmb = tmb;
  1802.                 }
  1803.             })
  1804.         })
  1805.         .add(function(e) {
  1806.             cache(e.data.added||[]);
  1807.         })
  1808.         .change(function(e) {
  1809.             $.each(e.data.changed||[], function(i, file) {
  1810.                 var hash = file.hash;
  1811.                 if ((files[hash].width && !file.width) || (files[hash].height && !file.height)) {
  1812.                     files[hash].width = undefined;
  1813.                     files[hash].height = undefined;
  1814.                 }
  1815.                 files[hash] = files[hash] ? $.extend(files[hash], file) : file;
  1816.             });
  1817.         })
  1818.         .remove(function(e) {
  1819.             var removed = e.data.removed||[],
  1820.                 l       = removed.length,
  1821.                 rm      = function(hash) {
  1822.                     var file = files[hash];
  1823.                     if (file) {
  1824.                         if (file.mime == 'directory' && file.dirs) {
  1825.                             $.each(files, function(h, f) {
  1826.                                 f.phash == hash && rm(h);
  1827.                             });
  1828.                         }
  1829.                         delete files[hash];
  1830.                     }
  1831.                 };
  1832.        
  1833.             while (l--) {
  1834.                 rm(removed[l]);
  1835.             }
  1836.            
  1837.         })
  1838.         .bind('search', function(e) {
  1839.             cache(e.data.files);
  1840.         })
  1841.         .bind('rm', function(e) {
  1842.             var play  = beeper.canPlayType && beeper.canPlayType('audio/wav; codecs="1"');
  1843.        
  1844.             play && play != '' && play != 'no' && $(beeper).html('<source src="' + soundPath + 'rm.wav" type="audio/wav">')[0].play()
  1845.         })
  1846.        
  1847.         ;
  1848.  
  1849.     // bind external event handlers
  1850.     $.each(this.options.handlers, function(event, callback) {
  1851.         self.bind(event, callback);
  1852.     });
  1853.  
  1854.     /**
  1855.      * History object. Store visited folders
  1856.      *
  1857.      * @type Object
  1858.      **/
  1859.     this.history = new this.history(this);
  1860.    
  1861.     // in getFileCallback set - change default actions on double click/enter/ctrl+enter
  1862.     if (typeof(this.options.getFileCallback) == 'function' && this.commands.getfile) {
  1863.         this.bind('dblclick', function(e) {
  1864.             e.preventDefault();
  1865.             self.exec('getfile').fail(function() {
  1866.                 self.exec('open');
  1867.             });
  1868.         });
  1869.         /*this.shortcut({
  1870.             pattern     : 'enter',
  1871.             description : this.i18n('cmdgetfile'),
  1872.             callback    : function() { self.exec('getfile').fail(function() { self.exec(self.OS == 'mac' ? 'rename' : 'open') }) }
  1873.         })
  1874.         .shortcut({
  1875.             pattern     : 'ctrl+enter',
  1876.             description : this.i18n(this.OS == 'mac' ? 'cmdrename' : 'cmdopen'),
  1877.             callback    : function() { self.exec(self.OS == 'mac' ? 'rename' : 'open') }
  1878.         });*/
  1879.     }
  1880.  
  1881.     /**
  1882.      * Loaded commands
  1883.      *
  1884.      * @type Object
  1885.      **/
  1886.     this._commands = {};
  1887.    
  1888.     if (!$.isArray(this.options.commands)) {
  1889.         this.options.commands = [];
  1890.     }
  1891.     // check required commands
  1892.     $.each(['open', 'reload', 'back', 'forward', 'up', 'home', 'info', 'quicklook', 'getfile', 'help'], function(i, cmd) {
  1893.         $.inArray(cmd, self.options.commands) === -1 && self.options.commands.push(cmd);
  1894.     });
  1895.  
  1896.     // load commands
  1897.     $.each(this.options.commands, function(i, name) {
  1898.         var cmd = self.commands[name];
  1899.         if ($.isFunction(cmd) && !self._commands[name]) {
  1900.             cmd.prototype = base;
  1901.             self._commands[name] = new cmd();
  1902.             self._commands[name].setup(name, self.options.commandsOptions[name]||{});
  1903.         }
  1904.     });
  1905.    
  1906.     /**
  1907.      * UI command map of cwd volume ( That volume driver option `uiCmdMap` )
  1908.      *
  1909.      * @type Object
  1910.      **/
  1911.     this.commandMap = {};
  1912.    
  1913.     /**
  1914.      * Disabled commands Array of each volume
  1915.      *
  1916.      * @type Object
  1917.      */
  1918.     this.disabledCmds = {};
  1919.    
  1920.     // prepare node
  1921.     node.addClass(this.cssClass)
  1922.         .on(mousedown, function() {
  1923.             !enabled && self.enable();
  1924.         });
  1925.    
  1926.     /**
  1927.      * UI nodes
  1928.      *
  1929.      * @type Object
  1930.      **/
  1931.     this.ui = {
  1932.         // container for nav panel and current folder container
  1933.         workzone : $('<div/>').appendTo(node).elfinderworkzone(this),
  1934.         // container for folders tree / places
  1935.         navbar : $('<div/>').appendTo(node).elfindernavbar(this, this.options.uiOptions.navbar || {}),
  1936.         // contextmenu
  1937.         contextmenu : $('<div/>').appendTo(node).elfindercontextmenu(this),
  1938.         // overlay
  1939.         overlay : $('<div/>').appendTo(node).elfinderoverlay({
  1940.             show : function() { self.disable(); },
  1941.             hide : function() { prevEnabled && self.enable(); }
  1942.         }),
  1943.         // current folder container
  1944.         cwd : $('<div/>').appendTo(node).elfindercwd(this, this.options.uiOptions.cwd || {}),
  1945.         // notification dialog window
  1946.         notify : this.dialog('', {
  1947.             cssClass  : 'elfinder-dialog-notify',
  1948.             position  : this.options.notifyDialog.position,
  1949.             resizable : false,
  1950.             autoOpen  : false,
  1951.             title     : '&nbsp;',
  1952.             width     : parseInt(this.options.notifyDialog.width)
  1953.         }),
  1954.         statusbar : $('<div class="ui-widget-header ui-helper-clearfix ui-corner-bottom elfinder-statusbar"/>').hide().appendTo(node)
  1955.     }
  1956.    
  1957.     // load required ui
  1958.     $.each(this.options.ui || [], function(i, ui) {
  1959.         var name = 'elfinder'+ui,
  1960.             opts = self.options.uiOptions[ui] || {};
  1961.  
  1962.         if (!self.ui[ui] && $.fn[name]) {
  1963.             self.ui[ui] = $('<'+(opts.tag || 'div')+'/>').appendTo(node)[name](self, opts);
  1964.         }
  1965.     });
  1966.    
  1967.  
  1968.  
  1969.     // store instance in node
  1970.     node[0].elfinder = this;
  1971.    
  1972.     // make node resizable
  1973.     this.options.resizable
  1974.     && !this.UA.Touch
  1975.     && $.fn.resizable
  1976.     && node.resizable({
  1977.         handles   : 'se',
  1978.         minWidth  : 300,
  1979.         minHeight : 200
  1980.     });
  1981.  
  1982.     if (this.options.width) {
  1983.         width = this.options.width;
  1984.     }
  1985.    
  1986.     if (this.options.height) {
  1987.         height = parseInt(this.options.height);
  1988.     }
  1989.    
  1990.     if (this.options.soundPath) {
  1991.         soundPath = this.options.soundPath.replace(/\/+$/, '') + '/';
  1992.     }
  1993.    
  1994.     // update size 
  1995.     self.resize(width, height);
  1996.    
  1997.     // attach events to document
  1998.     $(document)
  1999.         // disable elfinder on click outside elfinder
  2000.         .on('click.'+this.namespace, function(e) { enabled && !$(e.target).closest(node).length && self.disable(); })
  2001.         // exec shortcuts
  2002.         .on(keydown+' '+keypress, execShortcut);
  2003.    
  2004.     // attach events to window
  2005.     self.options.useBrowserHistory && $(window)
  2006.         .on('popstate', function(ev) {
  2007.             var target = ev.originalEvent.state && ev.originalEvent.state.thash;
  2008.             target && !$.isEmptyObject(self.files()) && self.request({
  2009.                 data   : {cmd  : 'open', target : target, onhistory : 1},
  2010.                 notify : {type : 'open', cnt : 1, hideCnt : true},
  2011.                 syncOnFail : true
  2012.             });
  2013.         });
  2014.    
  2015.     // send initial request and start to pray >_<
  2016.     this.trigger('init')
  2017.         .request({
  2018.             data        : {cmd : 'open', target : self.startDir(), init : 1, tree : this.ui.tree ? 1 : 0},
  2019.             preventDone : true,
  2020.             notify      : {type : 'open', cnt : 1, hideCnt : true},
  2021.             freeze      : true
  2022.         })
  2023.         .fail(function() {
  2024.             self.trigger('fail').disable().lastDir('');
  2025.             listeners = {};
  2026.             shortcuts = {};
  2027.             $(document).add(node).off('.'+this.namespace);
  2028.             self.trigger = function() { };
  2029.         })
  2030.         .done(function(data) {
  2031.             self.load().debug('api', self.api);
  2032.             data = $.extend(true, {}, data);
  2033.             open(data);
  2034.             self.trigger('open', data);
  2035.         });
  2036.    
  2037.     // update ui's size after init
  2038.     this.one('load', function() {
  2039.         node.trigger('resize');
  2040.         if (self.options.sync > 1000) {
  2041.             syncInterval = setInterval(function() {
  2042.                 self.sync();
  2043.             }, self.options.sync)
  2044.            
  2045.         }
  2046.  
  2047.     });
  2048.  
  2049.     (function(){
  2050.         var tm;
  2051.         $(window).on('resize', function(){
  2052.             tm && clearTimeout(tm);
  2053.             tm = setTimeout(function() {
  2054.                 self.trigger('resize', {width : node.width(), height : node.height()});
  2055.             }, 200);
  2056.         })
  2057.         .on('beforeunload',function(){
  2058.             if (self.ui.notify.children().length) {
  2059.                 return self.i18n('ntfsmth');
  2060.             }
  2061.         });
  2062.     })();
  2063.  
  2064.     // bind window onmessage for CORS
  2065.     $(window).on('message', function(e){
  2066.         var res = e.originalEvent || null,
  2067.             obj, data;
  2068.         if (res && self.uploadURL.indexOf(res.origin) === 0) {
  2069.             try {
  2070.                 obj = JSON.parse(res.data);
  2071.                 data = obj.data || null;
  2072.                 if (data) {
  2073.                     if (data.error) {
  2074.                         self.error(data.error);
  2075.                     } else {
  2076.                         data.warning && self.error(data.warning);
  2077.                         data.removed && data.removed.length && self.remove(data);
  2078.                         data.added   && data.added.length   && self.add(data);
  2079.                         data.changed && data.changed.length && self.change(data);
  2080.                         if (obj.bind) {
  2081.                             self.trigger(obj.bind, data);
  2082.                         }
  2083.                         data.sync && self.sync();
  2084.                     }
  2085.                 }
  2086.             } catch (e) {
  2087.                 self.sync();
  2088.             }
  2089.         }
  2090.     });
  2091.  
  2092.     if (self.dragUpload) {
  2093.         node[0].addEventListener('dragenter', function(e) {
  2094.             if (e.target.nodeName !== 'TEXTAREA' && e.target.nodeName !== 'INPUT') {
  2095.                 e.preventDefault();
  2096.                 e.stopPropagation();
  2097.             }
  2098.         }, false);
  2099.         node[0].addEventListener('dragleave', function(e) {
  2100.             if (e.target.nodeName !== 'TEXTAREA' && e.target.nodeName !== 'INPUT') {
  2101.                 e.preventDefault();
  2102.                 e.stopPropagation();
  2103.             }
  2104.         }, false);
  2105.         node[0].addEventListener('dragover', function(e) {
  2106.             if (e.target.nodeName !== 'TEXTAREA' && e.target.nodeName !== 'INPUT') {
  2107.                 e.preventDefault();
  2108.                 e.stopPropagation();
  2109.             }
  2110.         }, false);
  2111.         node[0].addEventListener('drop', function(e) {
  2112.             if (e.target.nodeName !== 'TEXTAREA' && e.target.nodeName !== 'INPUT') {
  2113.                 e.stopPropagation();
  2114.                 e.preventDefault();
  2115.                 if ($(e.target).is('[class*="elfinder"]')) {
  2116.                     self.error(['errUploadFile', self.i18n('items'), 'errPerm']);
  2117.                 }
  2118.             }
  2119.         }, false);
  2120.     }
  2121.    
  2122.     // self.timeEnd('load');
  2123.  
  2124. }
  2125.  
  2126. /**
  2127.  * Prototype
  2128.  *
  2129.  * @type  Object
  2130.  */
  2131. elFinder.prototype = {
  2132.    
  2133.     res : function(type, id) {
  2134.         return this.resources[type] && this.resources[type][id];
  2135.     },
  2136.    
  2137.     /**
  2138.      * Internationalization object
  2139.      *
  2140.      * @type  Object
  2141.      */
  2142.     i18 : {
  2143.         en : {
  2144.             translator      : '',
  2145.             language        : 'English',
  2146.             direction       : 'ltr',
  2147.             dateFormat      : 'd.m.Y H:i',
  2148.             fancyDateFormat : '$1 H:i',
  2149.             messages        : {}
  2150.         },
  2151.         months : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
  2152.         monthsShort : ['msJan', 'msFeb', 'msMar', 'msApr', 'msMay', 'msJun', 'msJul', 'msAug', 'msSep', 'msOct', 'msNov', 'msDec'],
  2153.  
  2154.         days : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
  2155.         daysShort : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
  2156.     },
  2157.    
  2158.     /**
  2159.      * File mimetype to kind mapping
  2160.      *
  2161.      * @type  Object
  2162.      */
  2163.     kinds :     {
  2164.             'unknown'                       : 'Unknown',
  2165.             'directory'                     : 'Folder',
  2166.             'symlink'                       : 'Alias',
  2167.             'symlink-broken'                : 'AliasBroken',
  2168.             'application/x-empty'           : 'TextPlain',
  2169.             'application/postscript'        : 'Postscript',
  2170.             'application/vnd.ms-office'     : 'MsOffice',
  2171.             'application/vnd.ms-word'       : 'MsWord',
  2172.             'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : 'MsWord',
  2173.             'application/vnd.ms-word.document.macroEnabled.12'                        : 'MsWord',
  2174.             'application/vnd.openxmlformats-officedocument.wordprocessingml.template' : 'MsWord',
  2175.             'application/vnd.ms-word.template.macroEnabled.12'                        : 'MsWord',
  2176.             'application/vnd.ms-excel'      : 'MsExcel',
  2177.             'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'       : 'MsExcel',
  2178.             'application/vnd.ms-excel.sheet.macroEnabled.12'                          : 'MsExcel',
  2179.             'application/vnd.openxmlformats-officedocument.spreadsheetml.template'    : 'MsExcel',
  2180.             'application/vnd.ms-excel.template.macroEnabled.12'                       : 'MsExcel',
  2181.             'application/vnd.ms-excel.sheet.binary.macroEnabled.12'                   : 'MsExcel',
  2182.             'application/vnd.ms-excel.addin.macroEnabled.12'                          : 'MsExcel',
  2183.             'application/vnd.ms-powerpoint' : 'MsPP',
  2184.             'application/vnd.openxmlformats-officedocument.presentationml.presentation' : 'MsPP',
  2185.             'application/vnd.ms-powerpoint.presentation.macroEnabled.12'              : 'MsPP',
  2186.             'application/vnd.openxmlformats-officedocument.presentationml.slideshow'  : 'MsPP',
  2187.             'application/vnd.ms-powerpoint.slideshow.macroEnabled.12'                 : 'MsPP',
  2188.             'application/vnd.openxmlformats-officedocument.presentationml.template'   : 'MsPP',
  2189.             'application/vnd.ms-powerpoint.template.macroEnabled.12'                  : 'MsPP',
  2190.             'application/vnd.ms-powerpoint.addin.macroEnabled.12'                     : 'MsPP',
  2191.             'application/vnd.openxmlformats-officedocument.presentationml.slide'      : 'MsPP',
  2192.             'application/vnd.ms-powerpoint.slide.macroEnabled.12'                     : 'MsPP',
  2193.             'application/pdf'               : 'PDF',
  2194.             'application/xml'               : 'XML',
  2195.             'application/vnd.oasis.opendocument.text' : 'OO',
  2196.             'application/vnd.oasis.opendocument.text-template'         : 'OO',
  2197.             'application/vnd.oasis.opendocument.text-web'              : 'OO',
  2198.             'application/vnd.oasis.opendocument.text-master'           : 'OO',
  2199.             'application/vnd.oasis.opendocument.graphics'              : 'OO',
  2200.             'application/vnd.oasis.opendocument.graphics-template'     : 'OO',
  2201.             'application/vnd.oasis.opendocument.presentation'          : 'OO',
  2202.             'application/vnd.oasis.opendocument.presentation-template' : 'OO',
  2203.             'application/vnd.oasis.opendocument.spreadsheet'           : 'OO',
  2204.             'application/vnd.oasis.opendocument.spreadsheet-template'  : 'OO',
  2205.             'application/vnd.oasis.opendocument.chart'                 : 'OO',
  2206.             'application/vnd.oasis.opendocument.formula'               : 'OO',
  2207.             'application/vnd.oasis.opendocument.database'              : 'OO',
  2208.             'application/vnd.oasis.opendocument.image'                 : 'OO',
  2209.             'application/vnd.openofficeorg.extension'                  : 'OO',
  2210.             'application/x-shockwave-flash' : 'AppFlash',
  2211.             'application/flash-video'       : 'Flash video',
  2212.             'application/x-bittorrent'      : 'Torrent',
  2213.             'application/javascript'        : 'JS',
  2214.             'application/rtf'               : 'RTF',
  2215.             'application/rtfd'              : 'RTF',
  2216.             'application/x-font-ttf'        : 'TTF',
  2217.             'application/x-font-otf'        : 'OTF',
  2218.             'application/x-rpm'             : 'RPM',
  2219.             'application/x-web-config'      : 'TextPlain',
  2220.             'application/xhtml+xml'         : 'HTML',
  2221.             'application/docbook+xml'       : 'DOCBOOK',
  2222.             'application/x-awk'             : 'AWK',
  2223.             'application/x-gzip'            : 'GZIP',
  2224.             'application/x-bzip2'           : 'BZIP',
  2225.             'application/x-xz'              : 'XZ',
  2226.             'application/zip'               : 'ZIP',
  2227.             'application/x-zip'               : 'ZIP',
  2228.             'application/x-rar'             : 'RAR',
  2229.             'application/x-tar'             : 'TAR',
  2230.             'application/x-7z-compressed'   : '7z',
  2231.             'application/x-jar'             : 'JAR',
  2232.             'text/plain'                    : 'TextPlain',
  2233.             'text/x-php'                    : 'PHP',
  2234.             'text/html'                     : 'HTML',
  2235.             'text/javascript'               : 'JS',
  2236.             'text/css'                      : 'CSS',
  2237.             'text/rtf'                      : 'RTF',
  2238.             'text/rtfd'                     : 'RTF',
  2239.             'text/x-c'                      : 'C',
  2240.             'text/x-csrc'                   : 'C',
  2241.             'text/x-chdr'                   : 'CHeader',
  2242.             'text/x-c++'                    : 'CPP',
  2243.             'text/x-c++src'                 : 'CPP',
  2244.             'text/x-c++hdr'                 : 'CPPHeader',
  2245.             'text/x-shellscript'            : 'Shell',
  2246.             'application/x-csh'             : 'Shell',
  2247.             'text/x-python'                 : 'Python',
  2248.             'text/x-java'                   : 'Java',
  2249.             'text/x-java-source'            : 'Java',
  2250.             'text/x-ruby'                   : 'Ruby',
  2251.             'text/x-perl'                   : 'Perl',
  2252.             'text/x-sql'                    : 'SQL',
  2253.             'text/xml'                      : 'XML',
  2254.             'text/x-comma-separated-values' : 'CSV',
  2255.             'text/x-markdown'               : 'Markdown',
  2256.             'image/x-ms-bmp'                : 'BMP',
  2257.             'image/jpeg'                    : 'JPEG',
  2258.             'image/gif'                     : 'GIF',
  2259.             'image/png'                     : 'PNG',
  2260.             'image/tiff'                    : 'TIFF',
  2261.             'image/x-targa'                 : 'TGA',
  2262.             'image/vnd.adobe.photoshop'     : 'PSD',
  2263.             'image/xbm'                     : 'XBITMAP',
  2264.             'image/pxm'                     : 'PXM',
  2265.             'audio/mpeg'                    : 'AudioMPEG',
  2266.             'audio/midi'                    : 'AudioMIDI',
  2267.             'audio/ogg'                     : 'AudioOGG',
  2268.             'audio/mp4'                     : 'AudioMPEG4',
  2269.             'audio/x-m4a'                   : 'AudioMPEG4',
  2270.             'audio/wav'                     : 'AudioWAV',
  2271.             'audio/x-mp3-playlist'          : 'AudioPlaylist',
  2272.             'video/x-dv'                    : 'VideoDV',
  2273.             'video/mp4'                     : 'VideoMPEG4',
  2274.             'video/mpeg'                    : 'VideoMPEG',
  2275.             'video/x-msvideo'               : 'VideoAVI',
  2276.             'video/quicktime'               : 'VideoMOV',
  2277.             'video/x-ms-wmv'                : 'VideoWM',
  2278.             'video/x-flv'                   : 'VideoFlash',
  2279.             'video/x-matroska'              : 'VideoMKV',
  2280.             'video/ogg'                     : 'VideoOGG'
  2281.         },
  2282.    
  2283.     /**
  2284.      * Ajax request data validation rules
  2285.      *
  2286.      * @type  Object
  2287.      */
  2288.     rules : {
  2289.         defaults : function(data) {
  2290.             if (!data
  2291.             || (data.added && !$.isArray(data.added))
  2292.             ||  (data.removed && !$.isArray(data.removed))
  2293.             ||  (data.changed && !$.isArray(data.changed))) {
  2294.                 return false;
  2295.             }
  2296.             return true;
  2297.         },
  2298.         open    : function(data) { return data && data.cwd && data.files && $.isPlainObject(data.cwd) && $.isArray(data.files); },
  2299.         tree    : function(data) { return data && data.tree && $.isArray(data.tree); },
  2300.         parents : function(data) { return data && data.tree && $.isArray(data.tree); },
  2301.         tmb     : function(data) { return data && data.images && ($.isPlainObject(data.images) || $.isArray(data.images)); },
  2302.         upload  : function(data) { return data && ($.isPlainObject(data.added) || $.isArray(data.added));},
  2303.         search  : function(data) { return data && data.files && $.isArray(data.files)}
  2304.     },
  2305.  
  2306.    
  2307.  
  2308.    
  2309.     /**
  2310.      * Commands costructors
  2311.      *
  2312.      * @type Object
  2313.      */
  2314.     commands : {},
  2315.    
  2316.     parseUploadData : function(text) {
  2317.         var data;
  2318.        
  2319.         if (!$.trim(text)) {
  2320.             return {error : ['errResponse', 'errDataEmpty']};
  2321.         }
  2322.        
  2323.         try {
  2324.             data = $.parseJSON(text);
  2325.         } catch (e) {
  2326.             return {error : ['errResponse', 'errDataNotJSON']};
  2327.         }
  2328.        
  2329.         if (!this.validResponse('upload', data)) {
  2330.             return {error : ['errResponse']};
  2331.         }
  2332.         data = this.normalize(data);
  2333.         data.removed = $.merge((data.removed || []), $.map(data.added||[], function(f) { return f.hash; }));
  2334.         return data;
  2335.        
  2336.     },
  2337.    
  2338.     iframeCnt : 0,
  2339.    
  2340.     uploads : {
  2341.         // xhr muiti uploading flag
  2342.         xhrUploading: false,
  2343.        
  2344.         // check droped contents
  2345.         checkFile : function(data, fm) {
  2346.             if (!!data.checked || data.type == 'files') {
  2347.                 return data.files;
  2348.             } else if (data.type == 'data') {
  2349.                 var dfrd = $.Deferred(),
  2350.                 files = [],
  2351.                 paths = [],
  2352.                 dirctorys = [],
  2353.                 entries = [],
  2354.                 processing = 0,
  2355.                
  2356.                 readEntries = function(dirReader) {
  2357.                     var toArray = function(list) {
  2358.                         return Array.prototype.slice.call(list || []);
  2359.                     };
  2360.                     var readFile = function(fileEntry, callback) {
  2361.                         var dfrd = $.Deferred();
  2362.                         if (typeof fileEntry == 'undefined') {
  2363.                             dfrd.reject('empty');
  2364.                         } else if (fileEntry.isFile) {
  2365.                             fileEntry.file(function (file) {
  2366.                                 dfrd.resolve(file);
  2367.                             }, function(e){
  2368.                                 dfrd.reject();
  2369.                             });
  2370.                         } else {
  2371.                             dfrd.reject('dirctory');
  2372.                         }
  2373.                         return dfrd.promise();
  2374.                     };
  2375.            
  2376.                     dirReader.readEntries(function (results) {
  2377.                         if (!results.length) {
  2378.                             var len = entries.length - 1;
  2379.                             var read = function(i) {
  2380.                                 readFile(entries[i]).done(function(file){
  2381.                                     if (! (fm.OS == 'win' && file.name.match(/^(?:desktop\.ini|thumbs\.db)$/i))
  2382.                                             &&
  2383.                                         ! (fm.OS == 'mac' && file.name.match(/^\.ds_store$/i))) {
  2384.                                         paths.push(entries[i].fullPath);
  2385.                                         files.push(file);
  2386.                                     }
  2387.                                 }).fail(function(e){
  2388.                                     if (e == 'dirctory') {
  2389.                                         // dirctory
  2390.                                         dirctorys.push(entries[i]);
  2391.                                     } else if (e == 'empty') {
  2392.                                         // dirctory is empty
  2393.                                     } else {
  2394.                                         // why fail?
  2395.                                     }
  2396.                                 }).always(function(){
  2397.                                     processing--;
  2398.                                     if (i < len) {
  2399.                                         processing++;
  2400.                                         read(++i);
  2401.                                     }
  2402.                                 });
  2403.                             };
  2404.                             processing++;
  2405.                             read(0);
  2406.                             processing--;
  2407.                         } else {
  2408.                             entries = entries.concat(toArray(results));
  2409.                             readEntries(dirReader);
  2410.                         }
  2411.                     });
  2412.                 },
  2413.                
  2414.                 doScan = function(items, isEntry) {
  2415.                     var dirReader, entry;
  2416.                     entries = [];
  2417.                     var length = items.length;
  2418.                     for (var i = 0; i < length; i++) {
  2419.                         if (! isEntry) {
  2420.                             entry = !!items[i].getAsEntry? items[i].getAsEntry() : items[i].webkitGetAsEntry();
  2421.                         } else {
  2422.                             entry = items[i];
  2423.                         }
  2424.                         if (entry) {
  2425.                             if (entry.isFile) {
  2426.                                 paths.push('');
  2427.                                 files.push(data.files.items[i].getAsFile());
  2428.                             } else if (entry.isDirectory) {
  2429.                                 if (processing > 0) {
  2430.                                     dirctorys.push(entry);
  2431.                                 } else {
  2432.                                     processing = 0;
  2433.                                     dirReader = entry.createReader();
  2434.                                     processing++;
  2435.                                     readEntries(dirReader);
  2436.                                 }
  2437.                             }
  2438.                         }
  2439.                     }
  2440.                 };
  2441.                
  2442.                 doScan(data.files.items);
  2443.                
  2444.                 setTimeout(function wait() {
  2445.                     if (processing > 0) {
  2446.                         setTimeout(wait, 10);
  2447.                     } else {
  2448.                         if (dirctorys.length > 0) {
  2449.                             doScan([dirctorys.shift()], true);
  2450.                             setTimeout(wait, 10);
  2451.                         } else {
  2452.                             dfrd.resolve([files, paths]);
  2453.                         }
  2454.                     }
  2455.                 }, 10);
  2456.                
  2457.                 return dfrd.promise();
  2458.             } else {
  2459.                 var ret = [];
  2460.                 var check = [];
  2461.                 var str = data.files[0];
  2462.                 if (data.type == 'html') {
  2463.                     var tmp = $("<html/>").append($.parseHTML(str)),
  2464.                         atag;
  2465.                     $('img[src]', tmp).each(function(){
  2466.                         var url, purl,
  2467.                         self = $(this),
  2468.                         pa = self.closest('a');
  2469.                         if (pa && pa.attr('href') && pa.attr('href').match(/\.(?:jpe?g|gif|bmp|png)/i)) {
  2470.                             purl = pa.attr('href');
  2471.                         }
  2472.                         url = self.attr('src');
  2473.                         if (url) {
  2474.                             if (purl) {
  2475.                                 $.inArray(purl, ret) == -1 && ret.push(purl);
  2476.                                 $.inArray(url, check) == -1 &&  check.push(url);
  2477.                             } else {
  2478.                                 $.inArray(url, ret) == -1 && ret.push(url);
  2479.                             }
  2480.                         }
  2481.                     });
  2482.                     atag = $('a[href]', tmp);
  2483.                     atag.each(function(){
  2484.                         var loc,
  2485.                             parseUrl = function(url) {
  2486.                                 var a = document.createElement('a');
  2487.                                 a.href = url;
  2488.                                 return a;
  2489.                             };
  2490.                         if ($(this).text()) {
  2491.                             loc = parseUrl($(this).attr('href'));
  2492.                             if (loc.href && (atag.length === 1 || ! loc.pathname.match(/(?:\.html?|\/[^\/.]*)$/i))) {
  2493.                                 if ($.inArray(loc.href, ret) == -1 && $.inArray(loc.href, check) == -1) ret.push(loc.href);
  2494.                             }
  2495.                         }
  2496.                     });
  2497.                 } else {
  2498.                     var regex, m, url;
  2499.                     regex = /(http[^<>"{}|\\^\[\]`\s]+)/ig;
  2500.                     while (m = regex.exec(str)) {
  2501.                         url = m[1].replace(/&amp;/g, '&');
  2502.                         if ($.inArray(url, ret) == -1) ret.push(url);
  2503.                     }
  2504.                 }
  2505.                 return ret;
  2506.             }
  2507.         },
  2508.  
  2509.         // upload transport using XMLHttpRequest
  2510.         xhr : function(data, fm) {
  2511.             var self   = fm ? fm : this,
  2512.                 xhr         = new XMLHttpRequest(),
  2513.                 notifyto    = null, notifyto2 = null,
  2514.                 dataChecked = data.checked,
  2515.                 isDataType  = (data.isDataType || data.type == 'data'),
  2516.                 retry       = 0,
  2517.                 cancelBtn   = 'div.elfinder-notify-upload div.elfinder-notify-cancel button',
  2518.                 dfrd   = $.Deferred()
  2519.                     .fail(function(error) {
  2520.                         var file = isDataType? files[0][0] : files[0];
  2521.                         if (file._cid) {
  2522.                             formData = new FormData();
  2523.                             files = [{_chunkfail: true}];
  2524.                             formData.append('chunk', file._chunk);
  2525.                             formData.append('cid'  , file._cid);
  2526.                             isDataType = false;
  2527.                             send(files);
  2528.                         }
  2529.                         files = null;
  2530.                         error && self.error(error);
  2531.                     })
  2532.                     .done(function(data) {
  2533.                         xhr = null;
  2534.                         files = null;
  2535.                         if (data) {
  2536.                             data.warning && self.error(data.warning);
  2537.                             data.removed && self.remove(data);
  2538.                             data.added   && self.add(data);
  2539.                             data.changed && self.change(data);
  2540.                             self.trigger('upload', data);
  2541.                             data.sync && self.sync();
  2542.                         }
  2543.                     })
  2544.                     .always(function() {
  2545.                         notifyto && clearTimeout(notifyto);
  2546.                         notifyto2 && clearTimeout(notifyto2);
  2547.                         dataChecked && !data.multiupload && checkNotify() && self.notify({type : 'upload', cnt : -cnt, progress : 0, size : 0});
  2548.                         chunkMerge && self.ui.notify.children('.elfinder-notify-chunkmerge').length && self.notify({type : 'chunkmerge', cnt : -1});
  2549.                         self.ui.notify.off('click', cancelBtn, fnAbort);
  2550.                         $(document).off('keydown', fnAbort);
  2551.                     }),
  2552.                 formData    = new FormData(),
  2553.                 files       = data.input ? data.input.files : self.uploads.checkFile(data, self),
  2554.                 cnt         = data.checked? (isDataType? files[0].length : files.length) : files.length,
  2555.                 loaded      = 0, prev,
  2556.                 filesize    = 0,
  2557.                 notify      = false,
  2558.                 abort       = false,
  2559.                 checkNotify = function() {
  2560.                     return notify = (notify || self.ui.notify.children('.elfinder-notify-upload').length);
  2561.                 },
  2562.                 startNotify = function(size) {
  2563.                     if (!size) size = filesize;
  2564.                     return setTimeout(function() {
  2565.                         notify = true;
  2566.                         self.notify({type : 'upload', cnt : cnt, progress : loaded - prev, size : size, cancel: true});
  2567.                         prev = loaded;
  2568.                     }, self.options.notifyDelay);
  2569.                 },
  2570.                 fnAbort = function(e) {
  2571.                     if (e.type == 'keydown' && e.keyCode != $.ui.keyCode.ESCAPE) {
  2572.                         return;
  2573.                     }
  2574.                     e.preventDefault();
  2575.                     e.stopPropagation();
  2576.                     abort = true;
  2577.                     xhr.abort();
  2578.                     dfrd.reject();
  2579.                     self.sync();
  2580.                 },
  2581.                 target = (data.target || self.cwd().hash),
  2582.                 chunkMerge = false;
  2583.            
  2584.             // regist abort event
  2585.             self.ui.notify.one('click', cancelBtn, fnAbort);
  2586.             $(document).on('keydown', fnAbort);
  2587.             $(window).on('unload', function(e){
  2588.                 (dfrd.state() == 'pending') && dfrd.reject();
  2589.             });
  2590.            
  2591.             !chunkMerge && (prev = loaded);
  2592.            
  2593.             if (!isDataType && !cnt) {
  2594.                 return dfrd.reject(['errUploadNoFiles']);
  2595.             }
  2596.            
  2597.             xhr.addEventListener('error', function() {
  2598.                 dfrd.reject('errConnect');
  2599.             }, false);
  2600.            
  2601.             xhr.addEventListener('abort', function() {
  2602.                 dfrd.reject(['errConnect', 'errAbort']);
  2603.             }, false);
  2604.            
  2605.             xhr.addEventListener('load', function(e) {
  2606.                 var status = xhr.status, res, curr = 0, error = '';
  2607.                
  2608.                 if (status >= 400) {
  2609.                     if (status > 500) {
  2610.                         error = 'errResponse';
  2611.                     } else {
  2612.                         error = 'errConnect';
  2613.                     }
  2614.                 } else {
  2615.                     if (xhr.readyState != 4) {
  2616.                         error = ['errConnect', 'errTimeout']; // am i right?
  2617.                     }
  2618.                     if (!xhr.responseText) {
  2619.                         error = ['errResponse', 'errDataEmpty'];
  2620.                     }
  2621.                 }
  2622.                
  2623.                 if (error) {
  2624.                     if (chunkMerge || retry++ > 3) {
  2625.                         var file = isDataType? files[0][0] : files[0];
  2626.                         if (file._cid) {
  2627.                             formData = new FormData();
  2628.                             files = [{_chunkfail: true}];
  2629.                             formData.append('chunk', file._chunk);
  2630.                             formData.append('cid'  , file._cid);
  2631.                             formData.append('range', file._range);
  2632.                             isDataType = false;
  2633.                             send(files);
  2634.                             return;
  2635.                         }
  2636.                         return dfrd.reject(error);
  2637.                     } else {
  2638.                         filesize = 0;
  2639.                         xhr.open('POST', self.uploadURL, true);
  2640.                         xhr.send(formData);
  2641.                         return;
  2642.                     }
  2643.                 }
  2644.                
  2645.                 loaded = filesize;
  2646.                
  2647.                 if (checkNotify() && (curr = loaded - prev)) {
  2648.                     self.notify({type : 'upload', cnt : 0, progress : curr, size : 0});
  2649.                 }
  2650.  
  2651.                 res = self.parseUploadData(xhr.responseText);
  2652.                
  2653.                 // chunked upload commit
  2654.                 if (res._chunkmerged) {
  2655.                     formData = new FormData();
  2656.                     var _file = [{_chunkmerged: res._chunkmerged, _name: res._name}];
  2657.                     chunkMerge = true;
  2658.                     notifyto2 = setTimeout(function() {
  2659.                         self.notify({type : 'chunkmerge', cnt : 1});
  2660.                     }, self.options.notifyDelay);
  2661.                     isDataType? send(_file, files[1]) : send(_file);
  2662.                     return;
  2663.                 }
  2664.                
  2665.                 res._multiupload = data.multiupload? true : false;
  2666.                 if (res.error) {
  2667.                     if (res._chunkfailure) {
  2668.                         abort = true;
  2669.                         self.uploads.xhrUploading = false;
  2670.                         notifyto && clearTimeout(notifyto);
  2671.                         if (self.ui.notify.children('.elfinder-notify-upload').length) {
  2672.                             self.notify({type : 'upload', cnt : -cnt, progress : 0, size : 0});
  2673.                             dfrd.reject(res.error);
  2674.                         } else {
  2675.                             // for multi connection
  2676.                             dfrd.reject();
  2677.                         }
  2678.                     } else {
  2679.                         dfrd.reject(res.error);
  2680.                     }
  2681.                 } else {
  2682.                     dfrd.resolve(res);
  2683.                 }
  2684.             }, false);
  2685.            
  2686.             xhr.upload.addEventListener('loadstart', function(e) {
  2687.                 if (!chunkMerge && e.lengthComputable) {
  2688.                     loaded = e.loaded;
  2689.                     retry && (loaded = 0);
  2690.                     filesize = e.total;
  2691.                     if (!loaded) {
  2692.                         loaded = parseInt(filesize * 0.05);
  2693.                     }
  2694.                     if (checkNotify()) {
  2695.                         self.notify({type : 'upload', cnt : 0, progress : loaded - prev, size : data.multiupload? 0 : filesize});
  2696.                         prev = loaded;
  2697.                     }
  2698.                 }
  2699.             }, false);
  2700.            
  2701.             xhr.upload.addEventListener('progress', function(e) {
  2702.                 var curr;
  2703.  
  2704.                 if (e.lengthComputable && !chunkMerge) {
  2705.                    
  2706.                     loaded = e.loaded;
  2707.  
  2708.                     // to avoid strange bug in safari (not in chrome) with drag&drop.
  2709.                     // bug: macos finder opened in any folder,
  2710.                     // reset safari cache (option+command+e), reload elfinder page,
  2711.                     // drop file from finder
  2712.                     // on first attempt request starts (progress callback called ones) but never ends.
  2713.                     // any next drop - successfull.
  2714.                     if (!data.checked && loaded > 0 && !notifyto) {
  2715.                         notifyto = startNotify(xhr._totalSize - loaded);
  2716.                     }
  2717.                    
  2718.                     if (!filesize) {
  2719.                         retry && (loaded = 0);
  2720.                         filesize = e.total;
  2721.                         if (!loaded) {
  2722.                             loaded = parseInt(filesize * 0.05);
  2723.                         }
  2724.                     }
  2725.                    
  2726.                     curr = loaded - prev;
  2727.                     if (checkNotify() && (curr/e.total) >= 0.05) {
  2728.                         self.notify({type : 'upload', cnt : 0, progress : curr, size : 0});
  2729.                         prev = loaded;
  2730.                     }
  2731.                 }
  2732.             }, false);
  2733.            
  2734.             var send = function(files, paths){
  2735.                 var size = 0,
  2736.                 fcnt = 1,
  2737.                 sfiles = [],
  2738.                 c = 0,
  2739.                 total = cnt,
  2740.                 maxFileSize,
  2741.                 totalSize = 0,
  2742.                 chunked = [],
  2743.                 chunkID = +new Date(),
  2744.                 BYTES_PER_CHUNK = Math.min((fm.uplMaxSize || 2097152) - 8190, fm.options.uploadMaxChunkSize), // uplMaxSize margin 8kb or options.uploadMaxChunkSize
  2745.                 blobSlice = false,
  2746.                 blobSize, i, start, end, chunks, blob, chunk, added, done, last, failChunk,
  2747.                 multi = function(files, num){
  2748.                     var sfiles = [], cid;
  2749.                     if (!abort) {
  2750.                         while(files.length && sfiles.length < num) {
  2751.                             sfiles.push(files.shift());
  2752.                         }
  2753.                     }
  2754.                     if (sfiles.length) {
  2755.                         for (var i=0; i < sfiles.length; i++) {
  2756.                             if (abort) {
  2757.                                 break;
  2758.                             }
  2759.                             cid = isDataType? (sfiles[i][0][0]._cid || null) : (sfiles[i][0]._cid || null);
  2760.                             if (!!failChunk[cid]) {
  2761.                                 last--;
  2762.                                 continue;
  2763.                             }
  2764.                             fm.exec('upload', {
  2765.                                 type: data.type,
  2766.                                 isDataType: isDataType,
  2767.                                 files: sfiles[i],
  2768.                                 checked: true,
  2769.                                 target: target,
  2770.                                 multiupload: true})
  2771.                             .fail(function(error) {
  2772.                                 if (cid) { 
  2773.                                     failChunk[cid] = true;
  2774.                                 }
  2775.                                 //error && self.error(error);
  2776.                             })
  2777.                             .always(function(e) {
  2778.                                 if (e && e.added) added = $.merge(added, e.added);
  2779.                                 if (last <= ++done) {
  2780.                                     fm.trigger('multiupload', {added: added});
  2781.                                     notifyto && clearTimeout(notifyto);
  2782.                                     if (checkNotify()) {
  2783.                                         self.notify({type : 'upload', cnt : -cnt, progress : 0, size : 0});
  2784.                                     }
  2785.                                 }
  2786.                                 multi(files, 1); // Next one
  2787.                             });
  2788.                         }
  2789.                     } else {
  2790.                         self.uploads.xhrUploading = false;
  2791.                         if (abort) {
  2792.                             notifyto && clearTimeout(notifyto);
  2793.                             if (checkNotify()) {
  2794.                                 self.notify({type : 'upload', cnt : -cnt, progress : 0, size : 0});
  2795.                             }
  2796.                             if (cid) { 
  2797.                                 failChunk[cid] = true;
  2798.                             }
  2799.                         }
  2800.                         dfrd.resolve();
  2801.                     }
  2802.                 },
  2803.                 check = function(){
  2804.                     if (!self.uploads.xhrUploading) {
  2805.                         self.uploads.xhrUploading = true;
  2806.                         multi(sfiles, 3); // Max connection: 3
  2807.                     } else {
  2808.                         setTimeout(function(){ check(); }, 100);
  2809.                     }
  2810.                 };
  2811.  
  2812.                 if (! dataChecked && (isDataType || data.type == 'files')) {
  2813.                     maxFileSize = fm.option('uploadMaxSize')? fm.option('uploadMaxSize') : 0;
  2814.                     for (i=0; i < files.length; i++) {
  2815.                         blob = files[i];
  2816.                         blobSize = blob.size;
  2817.                         if (blobSlice === false) {
  2818.                             if ('slice' in blob) {
  2819.                                 blobSlice = 'slice';
  2820.                             } else if ('mozSlice' in blob) {
  2821.                                 blobSlice = 'mozSlice';
  2822.                             } else if ('webkitSlice' in blob) {
  2823.                                 blobSlice = 'webkitSlice';
  2824.                             } else {
  2825.                                 blobSlice = '';
  2826.                             }
  2827.                         }
  2828.                        
  2829.                         if ((maxFileSize && blobSize > maxFileSize) || (!blobSlice && fm.uplMaxSize && blobSize > fm.uplMaxSize)) {
  2830.                             self.error(self.i18n('errUploadFile', blob.name) + ' ' + self.i18n('errUploadFileSize'));
  2831.                             cnt--;
  2832.                             total--;
  2833.                             continue;
  2834.                         }
  2835.                        
  2836.                         if (blobSlice && blobSize > BYTES_PER_CHUNK) {
  2837.                             start = 0;
  2838.                             end = BYTES_PER_CHUNK;
  2839.                             chunks = -1;
  2840.                             total = Math.floor(blobSize / BYTES_PER_CHUNK);
  2841.  
  2842.                             totalSize += blobSize;
  2843.                             chunked[chunkID] = 0;
  2844.                             while(start <= blobSize) {
  2845.                                 chunk = blob[blobSlice](start, end);
  2846.                                 chunk._chunk = blob.name + '.' + ++chunks + '_' + total + '.part';
  2847.                                 chunk._cid   = chunkID;
  2848.                                 chunk._range = start + ',' + chunk.size + ',' + blobSize;
  2849.                                 chunked[chunkID]++;
  2850.                                
  2851.                                 if (size) {
  2852.                                     c++;
  2853.                                 }
  2854.                                 if (typeof sfiles[c] == 'undefined') {
  2855.                                     sfiles[c] = [];
  2856.                                     if (isDataType) {
  2857.                                         sfiles[c][0] = [];
  2858.                                         sfiles[c][1] = [];
  2859.                                     }
  2860.                                 }
  2861.                                 size = fm.uplMaxSize;
  2862.                                 fcnt = 1;
  2863.                                 if (isDataType) {
  2864.                                     sfiles[c][0].push(chunk);
  2865.                                     sfiles[c][1].push(paths[i]);
  2866.                                 } else {
  2867.                                     sfiles[c].push(chunk);
  2868.                                 }
  2869.  
  2870.                                 start = end;
  2871.                                 end = start + BYTES_PER_CHUNK;
  2872.                             }
  2873.                             if (chunk == null) {
  2874.                                 self.error(self.i18n('errUploadFile', blob.name) + ' ' + self.i18n('errUploadFileSize'));
  2875.                                 cnt--;
  2876.                                 total--;
  2877.                             } else {
  2878.                                 total += chunks;
  2879.                             }
  2880.                             continue;
  2881.                         }
  2882.                         if ((fm.uplMaxSize && size + blobSize >= fm.uplMaxSize) || fcnt > fm.uplMaxFile) {
  2883.                             size = 0;
  2884.                             fcnt = 1;
  2885.                             c++;
  2886.                         }
  2887.                         if (typeof sfiles[c] == 'undefined') {
  2888.                             sfiles[c] = [];
  2889.                             if (isDataType) {
  2890.                                 sfiles[c][0] = [];
  2891.                                 sfiles[c][1] = [];
  2892.                             }
  2893.                         }
  2894.                         if (isDataType) {
  2895.                             sfiles[c][0].push(blob);
  2896.                             sfiles[c][1].push(paths[i]);
  2897.                         } else {
  2898.                             sfiles[c].push(blob);
  2899.                         }
  2900.                         size += blobSize;
  2901.                         totalSize += blobSize;
  2902.                         fcnt++;
  2903.                     }
  2904.                    
  2905.                     if (sfiles.length == 0) {
  2906.                         // no data
  2907.                         data.checked = true;
  2908.                         return false;
  2909.                     }
  2910.                    
  2911.                     if (sfiles.length > 1) {
  2912.                         // multi upload
  2913.                         notifyto = startNotify(totalSize);
  2914.                         added = [];
  2915.                         done = 0;
  2916.                         last = sfiles.length;
  2917.                         failChunk = [];
  2918.                         check();
  2919.                         return true;
  2920.                     }
  2921.                    
  2922.                     // single upload
  2923.                     if (isDataType) {
  2924.                         files = sfiles[0][0];
  2925.                         paths = sfiles[0][1];
  2926.                     } else {
  2927.                         files = sfiles[0];
  2928.                     }
  2929.                 }
  2930.                
  2931.                 if (!dataChecked) {
  2932.                     if (!fm.UA.Safari || !data.files) {
  2933.                         notifyto = startNotify(totalSize);
  2934.                     } else {
  2935.                         xhr._totalSize = totalSize;
  2936.                     }
  2937.                 }
  2938.                
  2939.                 dataChecked = true;
  2940.                
  2941.                 if (! files.length) {
  2942.                     dfrd.reject(['errUploadNoFiles']);
  2943.                 }
  2944.                
  2945.                 xhr.open('POST', self.uploadURL, true);
  2946.                
  2947.                 // set request headers
  2948.                 if (fm.customHeaders) {
  2949.                     $.each(fm.customHeaders, function(key) {
  2950.                         xhr.setRequestHeader(key, this);
  2951.                     });
  2952.                 }
  2953.                
  2954.                 // set xhrFields
  2955.                 if (fm.xhrFields) {
  2956.                     $.each(fm.xhrFields, function(key) {
  2957.                         if (key in xhr) {
  2958.                             xhr[key] = this;
  2959.                         }
  2960.                     });
  2961.                 }
  2962.  
  2963.                 formData.append('cmd', 'upload');
  2964.                 formData.append(self.newAPI ? 'target' : 'current', target);
  2965.                 $.each(self.options.customData, function(key, val) {
  2966.                     formData.append(key, val);
  2967.                 });
  2968.                 $.each(self.options.onlyMimes, function(i, mime) {
  2969.                     formData.append('mimes['+i+']', mime);
  2970.                 });
  2971.                
  2972.                 $.each(files, function(i, file) {
  2973.                     if (file._chunkmerged) {
  2974.                         formData.append('chunk', file._chunkmerged);
  2975.                         formData.append('upload[]', file._name);
  2976.                     } else {
  2977.                         if (file._chunkfail) {
  2978.                             formData.append('upload[]', 'chunkfail');
  2979.                             formData.append('mimes', 'chunkfail');
  2980.                         } else {
  2981.                             formData.append('upload[]', file);
  2982.                         }
  2983.                         if (file._chunk) {
  2984.                             formData.append('chunk', file._chunk);
  2985.                             formData.append('cid'  , file._cid);
  2986.                             formData.append('range', file._range);
  2987.                         }
  2988.                     }
  2989.                 });
  2990.                
  2991.                 if (isDataType) {
  2992.                     $.each(paths, function(i, path) {
  2993.                         formData.append('upload_path[]', path);
  2994.                     });
  2995.                 }
  2996.                
  2997.                
  2998.                 xhr.onreadystatechange = function() {
  2999.                     if (xhr.readyState == 4 && xhr.status == 0) {
  3000.                         if (abort) {
  3001.                             dfrd.reject();
  3002.                         } else {
  3003.                             var errors = ['errAbort'];
  3004.                             // ff bug while send zero sized file
  3005.                             // for safari - send directory
  3006.                             if (!isDataType && data.files && $.map(data.files, function(f){return f.size === 0? f : null;}).length) {
  3007.                                 errors.push('errFolderUpload');
  3008.                             }
  3009.                             dfrd.reject(errors);
  3010.                         }
  3011.                     }
  3012.                 };
  3013.                
  3014.                 xhr.send(formData);
  3015.                
  3016.                 return true;
  3017.             };
  3018.            
  3019.             if (! isDataType) {
  3020.                 if (! send(files)) {
  3021.                     dfrd.reject();
  3022.                 }
  3023.             } else {
  3024.                 if (dataChecked) {
  3025.                     send(files[0], files[1]);
  3026.                 } else {
  3027.                     notifyto2 = setTimeout(function() {
  3028.                         self.notify({type : 'readdir', cnt : 1, hideCnt: true});
  3029.                     }, self.options.notifyDelay);
  3030.                     files.done(function(result){
  3031.                         notifyto2 && clearTimeout(notifyto2);
  3032.                         self.notify({type : 'readdir', cnt : -1});
  3033.                         cnt = result[0].length;
  3034.                         if (cnt) {
  3035.                             send(result[0], result[1]);
  3036.                         } else {
  3037.                             dfrd.reject(['errUploadNoFiles']);
  3038.                         }
  3039.                     }).fail(function(){
  3040.                         dfrd.reject(['errUploadNoFiles']);
  3041.                     });
  3042.                 }
  3043.             }
  3044.  
  3045.             return dfrd;
  3046.         },
  3047.        
  3048.         // upload transport using iframe
  3049.         iframe : function(data, fm) {
  3050.             var self   = fm ? fm : this,
  3051.                 input  = data.input? data.input : false,
  3052.                 files  = !input ? self.uploads.checkFile(data, self) : false,
  3053.                 dfrd   = $.Deferred()
  3054.                     .fail(function(error) {
  3055.                         error && self.error(error);
  3056.                     })
  3057.                     .done(function(data) {
  3058.                         data.warning && self.error(data.warning);
  3059.                         data.removed && self.remove(data);
  3060.                         data.added   && self.add(data);
  3061.                         data.changed && self.change(data);
  3062.                         self.trigger('upload', data);
  3063.                         data.sync && self.sync();
  3064.                     }),
  3065.                 name = 'iframe-'+self.namespace+(++self.iframeCnt),
  3066.                 form = $('<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>'),
  3067.                 msie = this.UA.IE,
  3068.                 // clear timeouts, close notification dialog, remove form/iframe
  3069.                 onload = function() {
  3070.                     abortto  && clearTimeout(abortto);
  3071.                     notifyto && clearTimeout(notifyto);
  3072.                     notify   && self.notify({type : 'upload', cnt : -cnt});
  3073.                    
  3074.                     setTimeout(function() {
  3075.                         msie && $('<iframe src="javascript:false;"/>').appendTo(form);
  3076.                         form.remove();
  3077.                         iframe.remove();
  3078.                     }, 100);
  3079.                 },
  3080.                 iframe = $('<iframe src="'+(msie ? 'javascript:false;' : 'about:blank')+'" name="'+name+'" style="position:absolute;left:-1000px;top:-1000px" />')
  3081.                     .on('load', function() {
  3082.                         iframe.off('load')
  3083.                             .on('load', function() {
  3084.                                 //var data = self.parseUploadData(iframe.contents().text());
  3085.                                
  3086.                                 onload();
  3087.                                 dfrd.reject();
  3088.                                 //data.error ? dfrd.reject(data.error) : dfrd.resolve(data);
  3089.                             });
  3090.                            
  3091.                             // notify dialog
  3092.                             notifyto = setTimeout(function() {
  3093.                                 notify = true;
  3094.                                 self.notify({type : 'upload', cnt : cnt});
  3095.                             }, self.options.notifyDelay);
  3096.                            
  3097.                             // emulate abort on timeout
  3098.                             if (self.options.iframeTimeout > 0) {
  3099.                                 abortto = setTimeout(function() {
  3100.                                     onload();
  3101.                                     dfrd.reject([errors.connect, errors.timeout]);
  3102.                                 }, self.options.iframeTimeout);
  3103.                             }
  3104.                            
  3105.                             form.submit();
  3106.                     }),
  3107.                 cnt, notify, notifyto, abortto
  3108.                
  3109.                 ;
  3110.  
  3111.             if (files && files.length) {
  3112.                 $.each(files, function(i, val) {
  3113.                     form.append('<input type="hidden" name="upload[]" value="'+val+'"/>');
  3114.                 });
  3115.                 cnt = 1;
  3116.             } else if (input && $(input).is(':file') && $(input).val()) {
  3117.                 form.append(input);
  3118.                 cnt = input.files ? input.files.length : 1;
  3119.             } else {
  3120.                 return dfrd.reject();
  3121.             }
  3122.            
  3123.             form.append('<input type="hidden" name="'+(self.newAPI ? 'target' : 'current')+'" value="'+(data.target || self.cwd().hash)+'"/>')
  3124.                 .append('<input type="hidden" name="html" value="1"/>')
  3125.                 .append('<input type="hidden" name="node" value="'+self.id+'"/>')
  3126.                 .append($(input).attr('name', 'upload[]'));
  3127.            
  3128.             $.each(self.options.onlyMimes||[], function(i, mime) {
  3129.                 form.append('<input type="hidden" name="mimes[]" value="'+mime+'"/>');
  3130.             });
  3131.            
  3132.             $.each(self.options.customData, function(key, val) {
  3133.                 form.append('<input type="hidden" name="'+key+'" value="'+val+'"/>');
  3134.             });
  3135.            
  3136.             form.appendTo('body');
  3137.             iframe.appendTo('body');
  3138.            
  3139.             return dfrd;
  3140.         }
  3141.     },
  3142.    
  3143.    
  3144.     /**
  3145.      * Bind callback to event(s) The callback is executed at most once per event.
  3146.      * To bind to multiply events at once, separate events names by space
  3147.      *
  3148.      * @param  String    event name
  3149.      * @param  Function  callback
  3150.      * @return elFinder
  3151.      */
  3152.     one : function(event, callback) {
  3153.         var self = this,
  3154.             h    = $.proxy(callback, function(event) {
  3155.                 setTimeout(function() {self.unbind(event.type, h);}, 3);
  3156.                 return callback.apply(this, arguments);
  3157.             });
  3158.         return this.bind(event, h);
  3159.     },
  3160.    
  3161.     /**
  3162.      * Set/get data into/from localStorage
  3163.      *
  3164.      * @param  String       key
  3165.      * @param  String|void  value
  3166.      * @return String
  3167.      */
  3168.     localStorage : function(key, val) {
  3169.         var s = window.localStorage;
  3170.  
  3171.         key = 'elfinder-'+key+this.id;
  3172.        
  3173.         if (val === null) {
  3174.             return s.removeItem(key);
  3175.         }
  3176.        
  3177.         if (val !== void(0)) {
  3178.             try {
  3179.                 s.setItem(key, val);
  3180.             } catch (e) {
  3181.                 s.clear();
  3182.                 s.setItem(key, val);
  3183.             }
  3184.         }
  3185.  
  3186.         return s.getItem(key);
  3187.     },
  3188.    
  3189.     /**
  3190.      * Get/set cookie
  3191.      *
  3192.      * @param  String       cookie name
  3193.      * @param  String|void  cookie value
  3194.      * @return String
  3195.      */
  3196.     cookie : function(name, value) {
  3197.         var d, o, c, i;
  3198.  
  3199.         name = 'elfinder-'+name+this.id;
  3200.  
  3201.         if (value === void(0)) {
  3202.             if (document.cookie && document.cookie != '') {
  3203.                 c = document.cookie.split(';');
  3204.                 name += '=';
  3205.                 for (i=0; i<c.length; i++) {
  3206.                     c[i] = $.trim(c[i]);
  3207.                     if (c[i].substring(0, name.length) == name) {
  3208.                         return decodeURIComponent(c[i].substring(name.length));
  3209.                     }
  3210.                 }
  3211.             }
  3212.             return '';
  3213.         }
  3214.  
  3215.         o = $.extend({}, this.options.cookie);
  3216.         if (value === null) {
  3217.             value = '';
  3218.             o.expires = -1;
  3219.         }
  3220.         if (typeof(o.expires) == 'number') {
  3221.             d = new Date();
  3222.             d.setTime(d.getTime()+(o.expires * 86400000));
  3223.             o.expires = d;
  3224.         }
  3225.         document.cookie = name+'='+encodeURIComponent(value)+'; expires='+o.expires.toUTCString()+(o.path ? '; path='+o.path : '')+(o.domain ? '; domain='+o.domain : '')+(o.secure ? '; secure' : '');
  3226.         return value;
  3227.     },
  3228.    
  3229.     /**
  3230.      * Get start directory (by location.hash or last opened directory)
  3231.      *
  3232.      * @return String
  3233.      */
  3234.     startDir : function() {
  3235.         var locHash = window.location.hash;
  3236.         if (locHash && locHash.match(/^#elf_/)) {
  3237.             return locHash.replace(/^#elf_/, '');
  3238.         } else if (this.options.startPathHash) {
  3239.             return this.options.startPathHash;
  3240.         } else {
  3241.             return this.lastDir();
  3242.         }
  3243.     },
  3244.    
  3245.     /**
  3246.      * Get/set last opened directory
  3247.      *
  3248.      * @param  String|undefined  dir hash
  3249.      * @return String
  3250.      */
  3251.     lastDir : function(hash) {
  3252.         return this.options.rememberLastDir ? this.storage('lastdir', hash) : '';
  3253.     },
  3254.    
  3255.     /**
  3256.      * Node for escape html entities in texts
  3257.      *
  3258.      * @type jQuery
  3259.      */
  3260.     _node : $('<span/>'),
  3261.    
  3262.     /**
  3263.      * Replace not html-safe symbols to html entities
  3264.      *
  3265.      * @param  String  text to escape
  3266.      * @return String
  3267.      */
  3268.     escape : function(name) {
  3269.         return this._node.text(name).html().replace(/"/g, '&quot;').replace(/'/g, '&#039;');
  3270.     },
  3271.    
  3272.     /**
  3273.      * Cleanup ajax data.
  3274.      * For old api convert data into new api format
  3275.      *
  3276.      * @param  String  command name
  3277.      * @param  Object  data from backend
  3278.      * @return Object
  3279.      */
  3280.     normalize : function(data) {
  3281.         var filter = function(file) {
  3282.        
  3283.             if (file && file.hash && file.name && file.mime) {
  3284.                 if (file.mime == 'application/x-empty') {
  3285.                     file.mime = 'text/plain';
  3286.                 }
  3287.                 return file;
  3288.             }
  3289.             return null;
  3290.         };
  3291.        
  3292.  
  3293.         if (data.files) {
  3294.             data.files = $.map(data.files, filter);
  3295.         }
  3296.         if (data.tree) {
  3297.             data.tree = $.map(data.tree, filter);
  3298.         }
  3299.         if (data.added) {
  3300.             data.added = $.map(data.added, filter);
  3301.         }
  3302.         if (data.changed) {
  3303.             data.changed = $.map(data.changed, filter);
  3304.         }
  3305.         if (data.api) {
  3306.             data.init = true;
  3307.         }
  3308.         return data;
  3309.     },
  3310.    
  3311.     /**
  3312.      * Update sort options
  3313.      *
  3314.      * @param {String} sort type
  3315.      * @param {String} sort order
  3316.      * @param {Boolean} show folder first
  3317.      */
  3318.     setSort : function(type, order, stickFolders) {
  3319.         this.storage('sortType', (this.sortType = this.sortRules[type] ? type : 'name'));
  3320.         this.storage('sortOrder', (this.sortOrder = /asc|desc/.test(order) ? order : 'asc'));
  3321.         this.storage('sortStickFolders', (this.sortStickFolders = !!stickFolders) ? 1 : '');
  3322.         this.trigger('sortchange');
  3323.     },
  3324.    
  3325.     _sortRules : {
  3326.         name : function(file1, file2) {
  3327.             var n1 = file1.name.toLowerCase(),
  3328.                 n2 = file2.name.toLowerCase(),
  3329.                 e1 = '',
  3330.                 e2 = '',
  3331.                 so = elFinder.prototype.naturalCompare,
  3332.                 m, ret;
  3333.             if (m = n1.match(/^(.+)(\.[0-9a-z.]+)$/)) {
  3334.                 n1 = m[1];
  3335.                 e1 = m[2];
  3336.             }
  3337.             if (m = n2.match(/^(.+)(\.[0-9a-z.]+)$/)) {
  3338.                 n2 = m[1];
  3339.                 e2 = m[2];
  3340.             }
  3341.             ret = so(n1, n2);
  3342.             if (ret == 0 && (e1 || e2) && e1 != e2) {
  3343.                 ret = so(e1, e2);
  3344.             }
  3345.             return ret;
  3346.         },
  3347.         size : function(file1, file2) {
  3348.             var size1 = parseInt(file1.size) || 0,
  3349.                 size2 = parseInt(file2.size) || 0;
  3350.                
  3351.             return size1 == size2 ? 0 : size1 > size2 ? 1 : -1;
  3352.         },
  3353.         kind : function(file1, file2) {
  3354.             return elFinder.prototype.naturalCompare(file1.mime, file2.mime);
  3355.         },
  3356.         date : function(file1, file2) {
  3357.             var date1 = file1.ts || file1.date,
  3358.                 date2 = file2.ts || file2.date;
  3359.  
  3360.             return date1 == date2 ? 0 : date1 > date2 ? 1 : -1
  3361.         }
  3362.     },
  3363.    
  3364.     /**
  3365.      * Compare strings for natural sort
  3366.      *
  3367.      * @param  String
  3368.      * @param  String
  3369.      * @return Number
  3370.      */
  3371.     naturalCompare : function(a, b) {
  3372.         var self = elFinder.prototype.naturalCompare;
  3373.         if (typeof self.loc == 'undefined') {
  3374.             self.loc = (navigator.userLanguage || navigator.browserLanguage || navigator.language || 'en-US');
  3375.         }
  3376.         if (typeof self.sort == 'undefined') {
  3377.             if ('11'.localeCompare('2', self.loc, {numeric: true}) > 0) {
  3378.                 // Native support
  3379.                 if (window.Intl && window.Intl.Collator) {
  3380.                     self.sort = new Intl.Collator(self.loc, {numeric: true}).compare;
  3381.                 } else {
  3382.                     self.sort = function(a, b) {
  3383.                         return a.localeCompare(b, self.loc, {numeric: true});
  3384.                     };
  3385.                 }
  3386.             } else {
  3387.                 /*
  3388.                  * Edited for elFinder (emulates localeCompare() by numeric) by Naoki Sawada aka nao-pon
  3389.                  */
  3390.                 /*
  3391.                  * Huddle/javascript-natural-sort (https://github.com/Huddle/javascript-natural-sort)
  3392.                  */
  3393.                 /*
  3394.                  * Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license
  3395.                  * Author: Jim Palmer (based on chunking idea from Dave Koelle)
  3396.                  * http://opensource.org/licenses/mit-license.php
  3397.                  */
  3398.                 self.sort = function(a, b) {
  3399.                     var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,
  3400.                     sre = /(^[ ]*|[ ]*$)/g,
  3401.                     dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,
  3402.                     hre = /^0x[0-9a-f]+$/i,
  3403.                     ore = /^0/,
  3404.                     syre = /^[\x01\x21-\x2f\x3a-\x40\x5b-\x60\x7b-\x7e]/, // symbol first - (Naoki Sawada)
  3405.                     i = function(s) { return self.sort.insensitive && (''+s).toLowerCase() || ''+s },
  3406.                     // convert all to strings strip whitespace
  3407.                     // first character is "_", it's smallest - (Naoki Sawada)
  3408.                     x = i(a).replace(sre, '').replace(/^_/, "\x01") || '',
  3409.                     y = i(b).replace(sre, '').replace(/^_/, "\x01") || '',
  3410.                     // chunk/tokenize
  3411.                     xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
  3412.                     yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
  3413.                     // numeric, hex or date detection
  3414.                     xD = parseInt(x.match(hre)) || (xN.length != 1 && x.match(dre) && Date.parse(x)),
  3415.                     yD = parseInt(y.match(hre)) || xD && y.match(dre) && Date.parse(y) || null,
  3416.                     oFxNcL, oFyNcL,
  3417.                     locRes = 0;
  3418.  
  3419.                     // first try and sort Hex codes or Dates
  3420.                     if (yD) {
  3421.                         if ( xD < yD ) return -1;
  3422.                         else if ( xD > yD ) return 1;
  3423.                     }
  3424.                     // natural sorting through split numeric strings and default strings
  3425.                     for(var cLoc=0, numS=Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {
  3426.  
  3427.                         // find floats not starting with '0', string or 0 if not defined (Clint Priest)
  3428.                         oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc]) || xN[cLoc] || 0;
  3429.                         oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc]) || yN[cLoc] || 0;
  3430.  
  3431.                         // handle numeric vs string comparison - number < string - (Kyle Adams)
  3432.                         // but symbol first < number - (Naoki Sawada)
  3433.                         if (isNaN(oFxNcL) !== isNaN(oFyNcL)) {
  3434.                             if (isNaN(oFxNcL) && (typeof oFxNcL !== 'string' || ! oFxNcL.match(syre))) {
  3435.                                 return 1;
  3436.                             } else if (typeof oFyNcL !== 'string' || ! oFyNcL.match(syre)) {
  3437.                                 return -1;
  3438.                             }
  3439.                         }
  3440.  
  3441.                         // use decimal number comparison if either value is string zero
  3442.                         if (parseInt(oFxNcL, 10) === 0) oFxNcL = 0;
  3443.                         if (parseInt(oFyNcL, 10) === 0) oFyNcL = 0;
  3444.  
  3445.                         // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
  3446.                         if (typeof oFxNcL !== typeof oFyNcL) {
  3447.                             oFxNcL += '';
  3448.                             oFyNcL += '';
  3449.                         }
  3450.  
  3451.                         // use locale sensitive sort for strings when case insensitive
  3452.                         // note: localeCompare interleaves uppercase with lowercase (e.g. A,a,B,b)
  3453.                         if (self.sort.insensitive && typeof oFxNcL === 'string' && typeof oFyNcL === 'string') {
  3454.                             locRes = oFxNcL.localeCompare(oFyNcL, self.loc);
  3455.                             if (locRes !== 0) return locRes;
  3456.                         }
  3457.  
  3458.                         if (oFxNcL < oFyNcL) return -1;
  3459.                         if (oFxNcL > oFyNcL) return 1;
  3460.                     }
  3461.                     return 0;
  3462.                 };
  3463.                 self.sort.insensitive = true;
  3464.             }
  3465.         }
  3466.         return self.sort(a, b);
  3467.     },
  3468.    
  3469.     /**
  3470.      * Compare files based on elFinder.sort
  3471.      *
  3472.      * @param  Object  file
  3473.      * @param  Object  file
  3474.      * @return Number
  3475.      */
  3476.     compare : function(file1, file2) {
  3477.         var self  = this,
  3478.             type  = self.sortType,
  3479.             asc   = self.sortOrder == 'asc',
  3480.             stick = self.sortStickFolders,
  3481.             rules = self.sortRules,
  3482.             sort  = rules[type],
  3483.             d1    = file1.mime == 'directory',
  3484.             d2    = file2.mime == 'directory',
  3485.             res;
  3486.            
  3487.         if (stick) {
  3488.             if (d1 && !d2) {
  3489.                 return -1;
  3490.             } else if (!d1 && d2) {
  3491.                 return 1;
  3492.             }
  3493.         }
  3494.        
  3495.         res = asc ? sort(file1, file2) : sort(file2, file1);
  3496.        
  3497.         return type != 'name' && res == 0
  3498.             ? res = asc ? rules.name(file1, file2) : rules.name(file2, file1)
  3499.             : res;
  3500.     },
  3501.    
  3502.     /**
  3503.      * Sort files based on config
  3504.      *
  3505.      * @param  Array  files
  3506.      * @return Array
  3507.      */
  3508.     sortFiles : function(files) {
  3509.         return files.sort(this.compare);
  3510.     },
  3511.    
  3512.     /**
  3513.      * Open notification dialog
  3514.      * and append/update message for required notification type.
  3515.      *
  3516.      * @param  Object  options
  3517.      * @example  
  3518.      * this.notify({
  3519.      *    type : 'copy',
  3520.      *    msg : 'Copy files', // not required for known types @see this.notifyType
  3521.      *    cnt : 3,
  3522.      *    hideCnt  : false,   // true for not show count
  3523.      *    progress : 10,      // progress bar percents (use cnt : 0 to update progress bar)
  3524.      *    cancel   : false    // show cancel button (should regist event at each caller @see this.uploads.xhr)
  3525.      * })
  3526.      * @return elFinder
  3527.      */
  3528.     notify : function(opts) {
  3529.         var type     = opts.type,
  3530.             msg      = this.messages['ntf'+type] ? this.i18n('ntf'+type) : this.i18n('ntfsmth'),
  3531.             ndialog  = this.ui.notify,
  3532.             notify   = ndialog.children('.elfinder-notify-'+type),
  3533.             ntpl     = '<div class="elfinder-notify elfinder-notify-{type}"><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></div>',
  3534.             delta    = opts.cnt,
  3535.             size     = (typeof opts.size != 'undefined')? parseInt(opts.size) : null,
  3536.             progress = (typeof opts.progress != 'undefined' && opts.progress >= 0) ? opts.progress : null,
  3537.             cancel   = opts.cancel,
  3538.             clhover  = 'ui-state-hover',
  3539.             cnt, total, prc, button;
  3540.  
  3541.         if (!type) {
  3542.             return this;
  3543.         }
  3544.        
  3545.         if (!notify.length) {
  3546.             notify = $(ntpl.replace(/\{type\}/g, type).replace(/\{msg\}/g, msg))
  3547.                 .appendTo(ndialog)
  3548.                 .data('cnt', 0);
  3549.  
  3550.             if (progress != null) {
  3551.                 notify.data({progress : 0, total : 0});
  3552.             }
  3553.  
  3554.             if (cancel) {
  3555.                 button = $('<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>')
  3556.                     .hover(function(e) {
  3557.                         $(this).toggleClass(clhover, e.type == 'mouseenter');
  3558.                     });
  3559.                 notify.children('div.elfinder-notify-cancel').append(button);
  3560.             }
  3561.         }
  3562.  
  3563.         cnt = delta + parseInt(notify.data('cnt'));
  3564.        
  3565.         if (cnt > 0) {
  3566.             !opts.hideCnt && notify.children('.elfinder-notify-cnt').text('('+cnt+')');
  3567.             ndialog.is(':hidden') && ndialog.elfinderdialog('open');
  3568.             notify.data('cnt', cnt);
  3569.            
  3570.             if ((progress != null)
  3571.             && (total = notify.data('total')) >= 0
  3572.             && (prc = notify.data('progress')) >= 0) {
  3573.  
  3574.                 total += size != null? size : delta;
  3575.                 prc   += progress;
  3576.                 (size == null && delta < 0) && (prc += delta * 100);
  3577.                 notify.data({progress : prc, total : total});
  3578.                 if (size != null) {
  3579.                     prc *= 100;
  3580.                     total = Math.max(1, total);
  3581.                 }
  3582.                 progress = parseInt(prc/total);
  3583.                
  3584.                 notify.find('.elfinder-notify-progress')
  3585.                     .animate({
  3586.                         width : (progress < 100 ? progress : 100)+'%'
  3587.                     }, 20);
  3588.             }
  3589.            
  3590.         } else {
  3591.             notify.remove();
  3592.             !ndialog.children().length && ndialog.elfinderdialog('close');
  3593.         }
  3594.        
  3595.         return this;
  3596.     },
  3597.    
  3598.     /**
  3599.      * Open confirmation dialog
  3600.      *
  3601.      * @param  Object  options
  3602.      * @example  
  3603.      * this.confirm({
  3604.      *    title : 'Remove files',
  3605.      *    text  : 'Here is question text',
  3606.      *    accept : {  // accept callback - required
  3607.      *      label : 'Continue',
  3608.      *      callback : function(applyToAll) { fm.log('Ok') }
  3609.      *    },
  3610.      *    cancel : { // cancel callback - required
  3611.      *      label : 'Cancel',
  3612.      *      callback : function() { fm.log('Cancel')}
  3613.      *    },
  3614.      *    reject : { // reject callback - optionally
  3615.      *      label : 'No',
  3616.      *      callback : function(applyToAll) { fm.log('No')}
  3617.      *   },
  3618.      *   all : true  // display checkbox "Apply to all"
  3619.      * })
  3620.      * @return elFinder
  3621.      */
  3622.     confirm : function(opts) {
  3623.         var complete = false,
  3624.             options = {
  3625.                 cssClass  : 'elfinder-dialog-confirm',
  3626.                 modal     : true,
  3627.                 resizable : false,
  3628.                 title     : this.i18n(opts.title || 'confirmReq'),
  3629.                 buttons   : {},
  3630.                 close     : function() {
  3631.                     !complete && opts.cancel.callback();
  3632.                     $(this).elfinderdialog('destroy');
  3633.                 }
  3634.             },
  3635.             apply = this.i18n('apllyAll'),
  3636.             label, checkbox;
  3637.  
  3638.        
  3639.         options.buttons[this.i18n(opts.accept.label)] = function() {
  3640.             opts.accept.callback(!!(checkbox && checkbox.prop('checked')))
  3641.             complete = true;
  3642.             $(this).elfinderdialog('close')
  3643.         };
  3644.        
  3645.         if (opts.reject) {
  3646.             options.buttons[this.i18n(opts.reject.label)] = function() {
  3647.                 opts.reject.callback(!!(checkbox && checkbox.prop('checked')))
  3648.                 complete = true;
  3649.                 $(this).elfinderdialog('close')
  3650.             };
  3651.         }
  3652.        
  3653.         options.buttons[this.i18n(opts.cancel.label)] = function() {
  3654.             $(this).elfinderdialog('close')
  3655.         };
  3656.        
  3657.         if (opts.all) {
  3658.             if (opts.reject) {
  3659.                 options.width = 370;
  3660.             }
  3661.             options.create = function() {
  3662.                 checkbox = $('<input type="checkbox" />');
  3663.                 $(this).next().children().before($('<label>'+apply+'</label>').prepend(checkbox));
  3664.             }
  3665.            
  3666.             options.open = function() {
  3667.                 var pane = $(this).next(),
  3668.                     width = parseInt(pane.children(':first').outerWidth() + pane.children(':last').outerWidth());
  3669.  
  3670.                 if (width > parseInt(pane.width())) {
  3671.                     $(this).closest('.elfinder-dialog').width(width+30);
  3672.                 }
  3673.             }
  3674.         }
  3675.        
  3676.         return this.dialog('<span class="elfinder-dialog-icon elfinder-dialog-icon-confirm"/>' + this.i18n(opts.text), options);
  3677.     },
  3678.    
  3679.     /**
  3680.      * Create unique file name in required dir
  3681.      *
  3682.      * @param  String  file name
  3683.      * @param  String  parent dir hash
  3684.      * @return String
  3685.      */
  3686.     uniqueName : function(prefix, phash) {
  3687.         var i = 0, ext = '', p, name;
  3688.        
  3689.         prefix = this.i18n(prefix);
  3690.         phash = phash || this.cwd().hash;
  3691.  
  3692.         if (p = prefix.match(/^(.+)(\.[^.]+)$/)) {
  3693.             ext    = p[2];
  3694.             prefix = p[1];
  3695.         }
  3696.        
  3697.         name   = prefix+ext;
  3698.        
  3699.         if (!this.fileByName(name, phash)) {
  3700.             return name;
  3701.         }
  3702.         while (i < 10000) {
  3703.             name = prefix + ' ' + (++i) + ext;
  3704.             if (!this.fileByName(name, phash)) {
  3705.                 return name;
  3706.             }
  3707.         }
  3708.         return prefix + Math.random() + ext;
  3709.     },
  3710.    
  3711.     /**
  3712.      * Return message translated onto current language
  3713.      *
  3714.      * @param  String|Array  message[s]
  3715.      * @return String
  3716.      **/
  3717.     i18n : function() {
  3718.         var self = this,
  3719.             messages = this.messages,
  3720.             input    = [],
  3721.             ignore   = [],
  3722.             message = function(m) {
  3723.                 var file;
  3724.                 if (m.indexOf('#') === 0) {
  3725.                     if ((file = self.file(m.substr(1)))) {
  3726.                         return file.name;
  3727.                     }
  3728.                 }
  3729.                 return m;
  3730.             },
  3731.             i, j, m;
  3732.            
  3733.         for (i = 0; i< arguments.length; i++) {
  3734.             m = arguments[i];
  3735.            
  3736.             if (typeof m == 'string') {
  3737.                 input.push(message(m));
  3738.             } else if ($.isArray(m)) {
  3739.                 for (j = 0; j < m.length; j++) {
  3740.                     if (typeof m[j] == 'string') {
  3741.                         input.push(message(m[j]));
  3742.                     }
  3743.                 }
  3744.             }
  3745.         }
  3746.        
  3747.         for (i = 0; i < input.length; i++) {
  3748.             // dont translate placeholders
  3749.             if ($.inArray(i, ignore) !== -1) {
  3750.                 continue;
  3751.             }
  3752.             m = input[i];
  3753.             // translate message
  3754.             m = messages[m] || self.escape(m);
  3755.             // replace placeholders in message
  3756.             m = m.replace(/\$(\d+)/g, function(match, placeholder) {
  3757.                 placeholder = i + parseInt(placeholder);
  3758.                 if (placeholder > 0 && input[placeholder]) {
  3759.                     ignore.push(placeholder)
  3760.                 }
  3761.                 return self.escape(input[placeholder]) || '';
  3762.             });
  3763.  
  3764.             input[i] = m;
  3765.         }
  3766.  
  3767.         return $.map(input, function(m, i) { return $.inArray(i, ignore) === -1 ? m : null; }).join('<br>');
  3768.     },
  3769.    
  3770.     /**
  3771.      * Convert mimetype into css classes
  3772.      *
  3773.      * @param  String  file mimetype
  3774.      * @return String
  3775.      */
  3776.     mime2class : function(mime) {
  3777.         var prefix = 'elfinder-cwd-icon-';
  3778.        
  3779.         mime = mime.split('/');
  3780.        
  3781.         return prefix+mime[0]+(mime[0] != 'image' && mime[1] ? ' '+prefix+mime[1].replace(/(\.|\+)/g, '-') : '');
  3782.     },
  3783.    
  3784.     /**
  3785.      * Return localized kind of file
  3786.      *
  3787.      * @param  Object|String  file or file mimetype
  3788.      * @return String
  3789.      */
  3790.     mime2kind : function(f) {
  3791.         var mime = typeof(f) == 'object' ? f.mime : f, kind;
  3792.        
  3793.         if (f.alias && f.mime != 'symlink-broken') {
  3794.             kind = 'Alias';
  3795.         } else if (this.kinds[mime]) {
  3796.             kind = this.kinds[mime];
  3797.         } else {
  3798.             if (mime.indexOf('text') === 0) {
  3799.                 kind = 'Text';
  3800.             } else if (mime.indexOf('image') === 0) {
  3801.                 kind = 'Image';
  3802.             } else if (mime.indexOf('audio') === 0) {
  3803.                 kind = 'Audio';
  3804.             } else if (mime.indexOf('video') === 0) {
  3805.                 kind = 'Video';
  3806.             } else if (mime.indexOf('application') === 0) {
  3807.                 kind = 'App';
  3808.             } else {
  3809.                 kind = mime;
  3810.             }
  3811.         }
  3812.        
  3813.         return this.messages['kind'+kind] ? this.i18n('kind'+kind) : mime;
  3814.     },
  3815.    
  3816.     /**
  3817.      * Return localized date
  3818.      *
  3819.      * @param  Object  file object
  3820.      * @return String
  3821.      */
  3822.     formatDate : function(file, ts) {
  3823.         var self = this,
  3824.             ts   = ts || file.ts,
  3825.             i18  = self.i18,
  3826.             date, format, output, d, dw, m, y, h, g, i, s;
  3827.  
  3828.         if (self.options.clientFormatDate && ts > 0) {
  3829.  
  3830.             date = new Date(ts*1000);
  3831.            
  3832.             h  = date[self.getHours]();
  3833.             g  = h > 12 ? h - 12 : h;
  3834.             i  = date[self.getMinutes]();
  3835.             s  = date[self.getSeconds]();
  3836.             d  = date[self.getDate]();
  3837.             dw = date[self.getDay]();
  3838.             m  = date[self.getMonth]() + 1;
  3839.             y  = date[self.getFullYear]();
  3840.            
  3841.             format = ts >= this.yesterday
  3842.                 ? this.fancyFormat
  3843.                 : this.dateFormat;
  3844.  
  3845.             output = format.replace(/[a-z]/gi, function(val) {
  3846.                 switch (val) {
  3847.                     case 'd': return d > 9 ? d : '0'+d;
  3848.                     case 'j': return d;
  3849.                     case 'D': return self.i18n(i18.daysShort[dw]);
  3850.                     case 'l': return self.i18n(i18.days[dw]);
  3851.                     case 'm': return m > 9 ? m : '0'+m;
  3852.                     case 'n': return m;
  3853.                     case 'M': return self.i18n(i18.monthsShort[m-1]);
  3854.                     case 'F': return self.i18n(i18.months[m-1]);
  3855.                     case 'Y': return y;
  3856.                     case 'y': return (''+y).substr(2);
  3857.                     case 'H': return h > 9 ? h : '0'+h;
  3858.                     case 'G': return h;
  3859.                     case 'g': return g;
  3860.                     case 'h': return g > 9 ? g : '0'+g;
  3861.                     case 'a': return h > 12 ? 'pm' : 'am';
  3862.                     case 'A': return h > 12 ? 'PM' : 'AM';
  3863.                     case 'i': return i > 9 ? i : '0'+i;
  3864.                     case 's': return s > 9 ? s : '0'+s;
  3865.                 }
  3866.                 return val;
  3867.             });
  3868.            
  3869.             return ts >= this.yesterday
  3870.                 ? output.replace('$1', this.i18n(ts >= this.today ? 'Today' : 'Yesterday'))
  3871.                 : output;
  3872.         } else if (file.date) {
  3873.             return file.date.replace(/([a-z]+)\s/i, function(a1, a2) { return self.i18n(a2)+' '; });
  3874.         }
  3875.        
  3876.         return self.i18n('dateUnknown');
  3877.     },
  3878.    
  3879.     /**
  3880.      * Return css class marks file permissions
  3881.      *
  3882.      * @param  Object  file
  3883.      * @return String
  3884.      */
  3885.     perms2class : function(o) {
  3886.         var c = '';
  3887.        
  3888.         if (!o.read && !o.write) {
  3889.             c = 'elfinder-na';
  3890.         } else if (!o.read) {
  3891.             c = 'elfinder-wo';
  3892.         } else if (!o.write) {
  3893.             c = 'elfinder-ro';
  3894.         }
  3895.         return c;
  3896.     },
  3897.    
  3898.     /**
  3899.      * Return localized string with file permissions
  3900.      *
  3901.      * @param  Object  file
  3902.      * @return String
  3903.      */
  3904.     formatPermissions : function(f) {
  3905.         var p  = [];
  3906.            
  3907.         f.read && p.push(this.i18n('read'));
  3908.         f.write && p.push(this.i18n('write')); 
  3909.  
  3910.         return p.length ? p.join(' '+this.i18n('and')+' ') : this.i18n('noaccess');
  3911.     },
  3912.    
  3913.     /**
  3914.      * Return formated file size
  3915.      *
  3916.      * @param  Number  file size
  3917.      * @return String
  3918.      */
  3919.     formatSize : function(s) {
  3920.         var n = 1, u = 'b';
  3921.        
  3922.         if (s == 'unknown') {
  3923.             return this.i18n('unknown');
  3924.         }
  3925.        
  3926.         if (s > 1073741824) {
  3927.             n = 1073741824;
  3928.             u = 'GB';
  3929.         } else if (s > 1048576) {
  3930.             n = 1048576;
  3931.             u = 'MB';
  3932.         } else if (s > 1024) {
  3933.             n = 1024;
  3934.             u = 'KB';
  3935.         }
  3936.         s = s/n;
  3937.         return (s > 0 ? n >= 1048576 ? s.toFixed(2) : Math.round(s) : 0) +' '+u;
  3938.     },
  3939.    
  3940.     /**
  3941.      * Return formated file mode by options.fileModeStyle
  3942.      *
  3943.      * @param  String  file mode
  3944.      * @param  String  format style
  3945.      * @return String
  3946.      */
  3947.     formatFileMode : function(p, style) {
  3948.         var i, o, s, b, sticy, suid, sgid, str, oct;
  3949.        
  3950.         if (!style) {
  3951.             style = this.options.fileModeStyle.toLowerCase();
  3952.         }
  3953.         p = $.trim(p);
  3954.         if (p.match(/[rwxs-]{9}$/i)) {
  3955.             str = p = p.substr(-9);
  3956.             if (style == 'string') {
  3957.                 return str;;
  3958.             }
  3959.             oct = '';
  3960.             s = 0;
  3961.             for (i=0; i<7; i=i+3) {
  3962.                 o = p.substr(i, 3);
  3963.                 b = 0;
  3964.                 if (o.match(/[r]/i)) {
  3965.                     b += 4;
  3966.                 }
  3967.                 if (o.match(/[w]/i)) {
  3968.                     b += 2;
  3969.                 }
  3970.                 if (o.match(/[xs]/i)) {
  3971.                     if (o.match(/[xs]/)) {
  3972.                         b += 1;
  3973.                     }
  3974.                     if (o.match(/[s]/i)) {
  3975.                         if (i == 0) {
  3976.                             s += 4;
  3977.                         } else if (i == 3) {
  3978.                             s += 2;
  3979.                         }
  3980.                     }
  3981.                 }
  3982.                 oct += b.toString(8);
  3983.             }
  3984.             if (s) {
  3985.                 oct = s.toString(8) + oct;
  3986.             }
  3987.         } else {
  3988.             p = parseInt(p, 8);
  3989.             oct = p? p.toString(8) : '';
  3990.             if (!p || style == 'octal') {
  3991.                 return oct;
  3992.             }
  3993.             o = p.toString(8);
  3994.             s = 0;
  3995.             if (o.length > 3) {
  3996.                 o = o.substr(-4);
  3997.                 s = parseInt(o.substr(0, 1), 8);
  3998.                 o = o.substr(1);
  3999.             }
  4000.             sticy = ((s & 1) == 1); // not support
  4001.             sgid = ((s & 2) == 2);
  4002.             suid = ((s & 4) == 4);
  4003.             str = '';
  4004.             for(i=0; i<3; i++) {
  4005.                 if ((parseInt(o.substr(i, 1), 8) & 4) == 4) {
  4006.                     str += 'r';
  4007.                 } else {
  4008.                     str += '-';
  4009.                 }
  4010.                 if ((parseInt(o.substr(i, 1), 8) & 2) == 2) {
  4011.                     str += 'w';
  4012.                 } else {
  4013.                     str += '-';
  4014.                 }
  4015.                 if ((parseInt(o.substr(i, 1), 8) & 1) == 1) {
  4016.                     str += ((i==0 && suid)||(i==1 && sgid))? 's' : 'x';
  4017.                 } else {
  4018.                     str += '-';
  4019.                 }
  4020.             }
  4021.         }
  4022.         if (style == 'both') {
  4023.             return str + ' (' + oct + ')';
  4024.         } else if (style == 'string') {
  4025.             return str;
  4026.         } else {
  4027.             return oct;
  4028.         }
  4029.     },
  4030.    
  4031.     navHash2Id : function(hash) {
  4032.         return 'nav-'+hash;
  4033.     },
  4034.    
  4035.     navId2Hash : function(id) {
  4036.         return typeof(id) == 'string' ? id.substr(4) : false;
  4037.     },
  4038.    
  4039.     /**
  4040.      * Make event listener for direct upload to directory
  4041.      *
  4042.      * @param  Object  DOM object
  4043.      * @param  String  Target dirctory hash
  4044.      * @return void
  4045.      */
  4046.     makeDirectDropUpload : function(elm, hash) {
  4047.         var self = this, ent,
  4048.         c         = 'class',
  4049.         $elm      = $(elm),
  4050.         collapsed = self.res(c, 'navcollapse'),
  4051.         expanded  = self.res(c, 'navexpand'),
  4052.         dropover  = self.res(c, 'adroppable'),
  4053.         arrow     = self.res(c, 'navarrow'),
  4054.         clDropActive = self.res(c, 'adroppable');
  4055.         if (self.dragUpload) {
  4056.             elm.addEventListener('dragenter', function(e) {
  4057.                 e.preventDefault();
  4058.                 e.stopPropagation();
  4059.                 ent = true;
  4060.                 $elm.addClass(clDropActive);
  4061.                 if ($elm.is('.'+collapsed+':not(.'+expanded+')')) {
  4062.                     setTimeout(function() {
  4063.                         $elm.is('.'+collapsed+'.'+dropover) && $elm.children('.'+arrow).click();
  4064.                     }, 500);
  4065.                 }
  4066.             }, false);
  4067.  
  4068.             elm.addEventListener('dragleave', function(e) {
  4069.                 e.preventDefault();
  4070.                 e.stopPropagation();
  4071.                 if (ent) {
  4072.                     ent = false;
  4073.                 } else {
  4074.                     $elm.removeClass(clDropActive);
  4075.                 }
  4076.             }, false);
  4077.  
  4078.             elm.addEventListener('dragover', function(e) {
  4079.                 e.preventDefault();
  4080.                 e.stopPropagation();
  4081.                 ent = false;
  4082.             }, false);
  4083.  
  4084.             elm.addEventListener('drop', function(e) {
  4085.                 e.preventDefault();
  4086.                 e.stopPropagation();
  4087.                 $elm.removeClass(clDropActive);
  4088.                 e._target = hash;
  4089.                 self.directUploadTarget = hash;
  4090.                 self.exec('upload', {dropEvt: e});
  4091.                 self.directUploadTarget = null;
  4092.             }, false);
  4093.         }
  4094.     },
  4095.    
  4096.     log : function(m) { window.console && window.console.log && window.console.log(m); return this; },
  4097.    
  4098.     debug : function(type, m) {
  4099.         var d = this.options.debug;
  4100.  
  4101.         if (d == 'all' || d === true || ($.isArray(d) && $.inArray(type, d) != -1)) {
  4102.             window.console && window.console.log && window.console.log('elfinder debug: ['+type+'] ['+this.id+']', m);
  4103.         }
  4104.         return this;
  4105.     },
  4106.     time : function(l) { window.console && window.console.time && window.console.time(l); },
  4107.     timeEnd : function(l) { window.console && window.console.timeEnd && window.console.timeEnd(l); }
  4108.    
  4109.  
  4110. }
  4111.  
  4112. /**
  4113.  * for conpat ex. ie8...
  4114.  *
  4115.  * Object.keys() - JavaScript | MDN
  4116.  * https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
  4117.  */
  4118. if (!Object.keys) {
  4119.     Object.keys = (function () {
  4120.         var hasOwnProperty = Object.prototype.hasOwnProperty,
  4121.                 hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
  4122.                 dontEnums = [
  4123.                     'toString',
  4124.                     'toLocaleString',
  4125.                     'valueOf',
  4126.                     'hasOwnProperty',
  4127.                     'isPrototypeOf',
  4128.                     'propertyIsEnumerable',
  4129.                     'constructor'
  4130.                 ],
  4131.                 dontEnumsLength = dontEnums.length
  4132.  
  4133.         return function (obj) {
  4134.             if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object')
  4135.  
  4136.             var result = []
  4137.  
  4138.             for (var prop in obj) {
  4139.                 if (hasOwnProperty.call(obj, prop)) result.push(prop)
  4140.             }
  4141.  
  4142.             if (hasDontEnumBug) {
  4143.                 for (var i=0; i < dontEnumsLength; i++) {
  4144.                     if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i])
  4145.                 }
  4146.             }
  4147.             return result
  4148.         }
  4149.     })()
  4150. };
  4151.  
  4152.  
  4153. /*
  4154.  * File: /js/elFinder.version.js
  4155.  */
  4156.  
  4157. /**
  4158.  * Application version
  4159.  *
  4160.  * @type String
  4161.  **/
  4162. elFinder.prototype.version = '2.1.2 (2.x Nightly: f2196bd)';
  4163.  
  4164.  
  4165.  
  4166. /*
  4167.  * File: /js/jquery.elfinder.js
  4168.  */
  4169.  
  4170. /*** jQuery UI droppable performance tune for elFinder ***/
  4171. (function(){
  4172. var origin = $.ui.ddmanager.prepareOffsets;
  4173. $.ui.ddmanager.prepareOffsets = function( t, event ) {
  4174.     var isOutView = function(elem) {
  4175.         var rect = elem.getBoundingClientRect();
  4176.         return document.elementFromPoint(rect.left, rect.top)? false : true;
  4177.     }
  4178.    
  4179.     var i, m = $.ui.ddmanager.droppables[ t.options.scope ] || [];
  4180.     for ( i = 0; i < m.length; i++ ) {
  4181.         m[ i ].options.disabled = isOutView(m[ i ].element[ 0 ]);
  4182.     }
  4183.    
  4184.     // call origin function
  4185.     return origin( t, event );
  4186. };
  4187. })();
  4188.  
  4189. $.fn.elfinder = function(o) {
  4190.    
  4191.     if (o == 'instance') {
  4192.         return this.getElFinder();
  4193.     }
  4194.    
  4195.     return this.each(function() {
  4196.        
  4197.         var cmd = typeof(o) == 'string' ? o : '';
  4198.         if (!this.elfinder) {
  4199.             new elFinder(this, typeof(o) == 'object' ? o : {})
  4200.         }
  4201.        
  4202.         switch(cmd) {
  4203.             case 'close':
  4204.             case 'hide':
  4205.                 this.elfinder.hide();
  4206.                 break;
  4207.                
  4208.             case 'open':
  4209.             case 'show':
  4210.                 this.elfinder.show();
  4211.                 break;
  4212.                
  4213.             case'destroy':
  4214.                 this.elfinder.destroy();
  4215.                 break;
  4216.         }
  4217.        
  4218.     })
  4219. }
  4220.  
  4221. $.fn.getElFinder = function() {
  4222.     var instance;
  4223.    
  4224.     this.each(function() {
  4225.         if (this.elfinder) {
  4226.             instance = this.elfinder;
  4227.             return false;
  4228.         }
  4229.     });
  4230.    
  4231.     return instance;
  4232. }
  4233.  
  4234.  
  4235. /*
  4236.  * File: /js/elFinder.options.js
  4237.  */
  4238.  
  4239. /**
  4240.  * Default elFinder config
  4241.  *
  4242.  * @type  Object
  4243.  * @autor Dmitry (dio) Levashov
  4244.  */
  4245. elFinder.prototype._options = {
  4246.     /**
  4247.      * Connector url. Required!
  4248.      *
  4249.      * @type String
  4250.      */
  4251.     url : '',
  4252.  
  4253.     /**
  4254.      * Ajax request type.
  4255.      *
  4256.      * @type String
  4257.      * @default "get"
  4258.      */
  4259.     requestType : 'get',
  4260.  
  4261.     /**
  4262.      * Transport to send request to backend.
  4263.      * Required for future extensions using websockets/webdav etc.
  4264.      * Must be an object with "send" method.
  4265.      * transport.send must return $.Deferred() object
  4266.      *
  4267.      * @type Object
  4268.      * @default null
  4269.      * @example
  4270.      *  transport : {
  4271.      *    init : function(elfinderInstance) { },
  4272.      *    send : function(options) {
  4273.      *      var dfrd = $.Deferred();
  4274.      *      // connect to backend ...
  4275.      *      return dfrd;
  4276.      *    },
  4277.      *    upload : function(data) {
  4278.      *      var dfrd = $.Deferred();
  4279.      *      // upload ...
  4280.      *      return dfrd;
  4281.      *    }
  4282.      *    
  4283.      *  }
  4284.      **/
  4285.     transport : {},
  4286.  
  4287.     /**
  4288.      * URL to upload file to.
  4289.      * If not set - connector URL will be used
  4290.      *
  4291.      * @type String
  4292.      * @default  ''
  4293.      */
  4294.     urlUpload : '',
  4295.  
  4296.     /**
  4297.      * Allow to drag and drop to upload files
  4298.      *
  4299.      * @type Boolean|String
  4300.      * @default  'auto'
  4301.      */
  4302.     dragUploadAllow : 'auto',
  4303.    
  4304.     /**
  4305.      * Max size of chunked data of file upload
  4306.      *
  4307.      * @type Number
  4308.      * @default  10485760(10MB)
  4309.      */
  4310.     uploadMaxChunkSize : 10485760,
  4311.    
  4312.     /**
  4313.      * Timeout for upload using iframe
  4314.      *
  4315.      * @type Number
  4316.      * @default  0 - no timeout
  4317.      */
  4318.     iframeTimeout : 0,
  4319.    
  4320.     /**
  4321.      * Data to append to all requests and to upload files
  4322.      *
  4323.      * @type Object
  4324.      * @default  {}
  4325.      */
  4326.     customData : {},
  4327.    
  4328.     /**
  4329.      * Event listeners to bind on elFinder init
  4330.      *
  4331.      * @type Object
  4332.      * @default  {}
  4333.      */
  4334.     handlers : {},
  4335.  
  4336.     /**
  4337.      * Any custom headers to send across every ajax request
  4338.      *
  4339.      * @type Object
  4340.      * @default {}
  4341.      */
  4342.     customHeaders : {},
  4343.  
  4344.     /**
  4345.      * Any custom xhrFields to send across every ajax request
  4346.      *
  4347.      * @type Object
  4348.      * @default {}
  4349.      */
  4350.     xhrFields : {},
  4351.  
  4352.     /**
  4353.      * Interface language
  4354.      *
  4355.      * @type String
  4356.      * @default "en"
  4357.      */
  4358.     lang : 'en',
  4359.  
  4360.     /**
  4361.      * Additional css class for filemanager node.
  4362.      *
  4363.      * @type String
  4364.      */
  4365.     cssClass : '',
  4366.  
  4367.     /**
  4368.      * Active commands list
  4369.      * If some required commands will be missed here, elFinder will add its
  4370.      *
  4371.      * @type Array
  4372.      */
  4373.     commands : [
  4374.         'open', 'reload', 'home', 'up', 'back', 'forward', 'getfile', 'quicklook',
  4375.         'download', 'rm', 'duplicate', 'rename', 'mkdir', 'mkfile', 'upload', 'copy',
  4376.         'cut', 'paste', 'edit', 'extract', 'archive', 'search', 'info', 'view', 'help',
  4377.         'resize', 'sort', 'netmount', 'netunmount', 'places', 'chmod'
  4378.     ],
  4379.    
  4380.     /**
  4381.      * Commands options.
  4382.      *
  4383.      * @type Object
  4384.      **/
  4385.     commandsOptions : {
  4386.         // "getfile" command options.
  4387.         getfile : {
  4388.             onlyURL  : false,
  4389.             // allow to return multiple files info
  4390.             multiple : false,
  4391.             // allow to return filers info
  4392.             folders  : false,
  4393.             // action after callback (""/"close"/"destroy")
  4394.             oncomplete : ''
  4395.         },
  4396.         // "upload" command options.
  4397.         upload : {
  4398.             ui : 'uploadbutton'
  4399.         },
  4400.         // "quicklook" command options.
  4401.         quicklook : {
  4402.             autoplay : true,
  4403.             jplayer  : 'extensions/jplayer'
  4404.         },
  4405.         // "quicklook" command options.
  4406.         edit : {
  4407.             // list of allowed mimetypes to edit
  4408.             // if empty - any text files can be edited
  4409.             mimes : [],
  4410.             // edit files in wysisyg's
  4411.             editors : [
  4412.                 // {
  4413.                 //  /**
  4414.                 //   * files mimetypes allowed to edit in current wysisyg
  4415.                 //   * @type  Array
  4416.                 //   */
  4417.                 //  mimes : ['text/html'],
  4418.                 //  /**
  4419.                 //   * Called when "edit" dialog loaded.
  4420.                 //   * Place to init wysisyg.
  4421.                 //   * Can return wysisyg instance
  4422.                 //   *
  4423.                 //   * @param  DOMElement  textarea node
  4424.                 //   * @return Object
  4425.                 //   */
  4426.                 //  load : function(textarea) { },
  4427.                 //  /**
  4428.                 //   * Called before "edit" dialog closed.
  4429.                 //   * Place to destroy wysisyg instance.
  4430.                 //   *
  4431.                 //   * @param  DOMElement  textarea node
  4432.                 //   * @param  Object      wysisyg instance (if was returned by "load" callback)
  4433.                 //   * @return void
  4434.                 //   */
  4435.                 //  close : function(textarea, instance) { },
  4436.                 //  /**
  4437.                 //   * Called before file content send to backend.
  4438.                 //   * Place to update textarea content if needed.
  4439.                 //   *
  4440.                 //   * @param  DOMElement  textarea node
  4441.                 //   * @param  Object      wysisyg instance (if was returned by "load" callback)
  4442.                 //   * @return void
  4443.                 //   */
  4444.                 //  save : function(textarea, instance) {},
  4445.                 //  /**
  4446.                 //   * Called after load() or save().
  4447.                 //   * Set focus to wysisyg editor.
  4448.                 //   *
  4449.                 //   * @param  DOMElement  textarea node
  4450.                 //   * @param  Object      wysisyg instance (if was returned by "load" callback)
  4451.                 //   * @return void
  4452.                 //   */
  4453.                 //  focus : function(textarea, instance) {}
  4454.                 //
  4455.                 // }
  4456.             ]
  4457.         },
  4458.         // "info" command options.
  4459.         info : {
  4460.             nullUrlDirLinkSelf : true,
  4461.             custom : {
  4462.                 // /**
  4463.                 //  * Example of custom info `desc`
  4464.                 //  */
  4465.                 // desc : {
  4466.                 //  /**
  4467.                 //   * Lable (require)
  4468.                 //   * It is filtered by the `fm.i18n()`
  4469.                 //   *
  4470.                 //   * @type String
  4471.                 //   */
  4472.                 //  label : 'Description',
  4473.                 //  
  4474.                 //  /**
  4475.                 //   * Template (require)
  4476.                 //   * `{id}` is replaced in dialog.id
  4477.                 //   *
  4478.                 //   * @type String
  4479.                 //   */
  4480.                 //  tpl : '<div class="elfinder-info-desc"><span class="elfinder-info-spinner"></span></div>',
  4481.                 //  
  4482.                 //  /**
  4483.                 //   * Restricts to mimetypes (optional)
  4484.                 //   * Exact match or category match
  4485.                 //   *
  4486.                 //   * @type Array
  4487.                 //   */
  4488.                 //  mimes : ['text', 'image/jpeg', 'directory'],
  4489.                 //  
  4490.                 //  /**
  4491.                 //   * Restricts to file.hash (optional)
  4492.                 //   *
  4493.                 //   * @ type Regex
  4494.                 //   */
  4495.                 //  hashRegex : /^l\d+_/,
  4496.                 //
  4497.                 //  /**
  4498.                 //   * Request that asks for the description and sets the field (optional)
  4499.                 //   *
  4500.                 //   * @type Function
  4501.                 //   */
  4502.                 //  action : function(file, fm, dialog) {
  4503.                 //      fm.request({
  4504.                 //      data : { cmd : 'desc', target: file.hash },
  4505.                 //          preventDefault: true,
  4506.                 //      })
  4507.                 //      .fail(function() {
  4508.                 //          dialog.find('div.elfinder-info-desc').html(fm.i18n('unknown'));
  4509.                 //      })
  4510.                 //      .done(function(data) {
  4511.                 //          dialog.find('div.elfinder-info-desc').html(data.desc);
  4512.                 //      });
  4513.                 //  }
  4514.                 // }
  4515.             }
  4516.         },
  4517.        
  4518.         netmount: {
  4519.             ftp: {
  4520.                 inputs: {
  4521.                     host     : $('<input type="text"/>'),
  4522.                     port     : $('<input type="text" placeholder="21"/>'),
  4523.                     path     : $('<input type="text" value="/"/>'),
  4524.                     user     : $('<input type="text"/>'),
  4525.                     pass     : $('<input type="password"/>'),
  4526.                     encoding : $('<input type="text" placeholder="Optional"/>'),
  4527.                     locale   : $('<input type="text" placeholder="Optional"/>')
  4528.                 }
  4529.             },
  4530.             dropbox: {
  4531.                 inputs: {
  4532.                     host     : $('<span><span class="elfinder-info-spinner"/></span></span><input type="hidden"/>'),
  4533.                     path     : $('<input type="text" value="/"/>'),
  4534.                     user     : $('<input type="hidden"/>'),
  4535.                     pass     : $('<input type="hidden"/>')
  4536.                 },
  4537.                 select: function(fm){
  4538.                     var self = this;
  4539.                     if (self.inputs.host.find('span').length) {
  4540.                         fm.request({
  4541.                             data : {cmd : 'netmount', protocol: 'dropbox', host: 'dropbox.com', user: 'init', pass: 'init', options: {url: fm.uploadURL, id: fm.id}},
  4542.                             preventDefault : true
  4543.                         }).done(function(data){
  4544.                             self.inputs.host.find('span').removeClass("elfinder-info-spinner");
  4545.                             self.inputs.host.find('span').html(data.body.replace(/\{msg:([^}]+)\}/g, function(whole,s1){return fm.i18n(s1,'Dropbox.com');}));
  4546.                         }).fail(function(){});
  4547.                     }                  
  4548.                 },
  4549.                 done: function(fm, data){
  4550.                     var self = this;
  4551.                     if (data.mode == 'makebtn') {
  4552.                         self.inputs.host.find('span').removeClass("elfinder-info-spinner");
  4553.                         self.inputs.host.find('input').hover(function(){$(this).toggleClass("ui-state-hover");});
  4554.                         self.inputs.host[1].value = "";
  4555.                     } else {
  4556.                         self.inputs.host.find('span').removeClass("elfinder-info-spinner");
  4557.                         self.inputs.host.find('span').html("Dropbox.com");
  4558.                         self.inputs.host[1].value = "dropbox";
  4559.                         self.inputs.user.val("done");
  4560.                         self.inputs.pass.val("done");
  4561.                     }
  4562.                 }
  4563.             }
  4564.         },
  4565.  
  4566.         help : {view : ['about', 'shortcuts', 'help']}
  4567.     },
  4568.    
  4569.     /**
  4570.      * Callback for "getfile" commands.
  4571.      * Required to use elFinder with WYSIWYG editors etc..
  4572.      *
  4573.      * @type Function
  4574.      * @default null (command not active)
  4575.      */
  4576.     getFileCallback : null,
  4577.    
  4578.     /**
  4579.      * Default directory view. icons/list
  4580.      *
  4581.      * @type String
  4582.      * @default "icons"
  4583.      */
  4584.     defaultView : 'icons',
  4585.    
  4586.     /**
  4587.      * Hash of default directory path to open
  4588.      *
  4589.      * @type String
  4590.      * @default ""
  4591.      */
  4592.     startPathHash : '',
  4593.    
  4594.     /**
  4595.      * UI plugins to load.
  4596.      * Current dir ui and dialogs loads always.
  4597.      * Here set not required plugins as folders tree/toolbar/statusbar etc.
  4598.      *
  4599.      * @type Array
  4600.      * @default ['toolbar', 'tree', 'path', 'stat']
  4601.      * @full ['toolbar', 'places', 'tree', 'path', 'stat']
  4602.      */
  4603.     ui : ['toolbar', 'tree', 'path', 'stat'],
  4604.    
  4605.     /**
  4606.      * Some UI plugins options.
  4607.      * @type Object
  4608.      */
  4609.     uiOptions : {
  4610.         // toolbar configuration
  4611.         toolbar : [
  4612.             ['back', 'forward'],
  4613.             ['netmount'],
  4614.             // ['reload'],
  4615.             // ['home', 'up'],
  4616.             ['mkdir', 'mkfile', 'upload'],
  4617.             ['open', 'download', 'getfile'],
  4618.             ['info', 'chmod'],
  4619.             ['quicklook'],
  4620.             ['copy', 'cut', 'paste'],
  4621.             ['rm'],
  4622.             ['duplicate', 'rename', 'edit', 'resize'],
  4623.             ['extract', 'archive'],
  4624.             ['search'],
  4625.             ['view', 'sort'],
  4626.             ['help']
  4627.         ],
  4628.         // directories tree options
  4629.         tree : {
  4630.             // expand current root on init
  4631.             openRootOnLoad : true,
  4632.             // expand current work directory on open
  4633.             openCwdOnOpen  : true,
  4634.             // auto load current dir parents
  4635.             syncTree : true
  4636.             // ,
  4637.             // /**
  4638.             //  * Add CSS class name to navbar directories (optional)
  4639.             //  * see: https://github.com/Studio-42/elFinder/pull/1061
  4640.             //  *
  4641.             //  * @type Function
  4642.             //  */
  4643.             // getClass: function(dir) {
  4644.             //  // ex. This adds the directory's name (lowercase) with prefix as a CSS class
  4645.             //  return 'elfinder-tree-' + dir.name.replace(/[ "]/g, '').toLowerCase();
  4646.             // }
  4647.         },
  4648.         // navbar options
  4649.         navbar : {
  4650.             minWidth : 150,
  4651.             maxWidth : 500
  4652.         },
  4653.         cwd : {
  4654.             // display parent folder with ".." name :)
  4655.             oldSchool : false,
  4656.            
  4657.             // file info columns displayed
  4658.             listView : {
  4659.                 // name is always displayed, cols are ordered
  4660.                 // ex. ['perm', 'date', 'size', 'kind', 'owner', 'group', 'mode']
  4661.                 // mode: 'mode'(by `fileModeStyle` setting), 'modestr'(rwxr-xr-x) , 'modeoct'(755), 'modeboth'(rwxr-xr-x (755))
  4662.                 // 'owner', 'group' and 'mode', It's necessary set volume driver option "statOwner" to `true`
  4663.                 columns : ['perm', 'date', 'size', 'kind'],
  4664.                 // override this if you want custom columns name
  4665.                 // example
  4666.                 // columnsCustomName : {
  4667.                 //      date : 'Last modification',
  4668.                 //      kind : 'Mime type'
  4669.                 // }
  4670.                 columnsCustomName : {}
  4671.                                    
  4672.             }
  4673.         }
  4674.     },
  4675.  
  4676.     /**
  4677.      * Display only required files by types
  4678.      *
  4679.      * @type Array
  4680.      * @default []
  4681.      * @example
  4682.      *  onlyMimes : ["image"] - display all images
  4683.      *  onlyMimes : ["image/png", "application/x-shockwave-flash"] - display png and flash
  4684.      */
  4685.     onlyMimes : [],
  4686.  
  4687.     /**
  4688.      * Custom files sort rules.
  4689.      * All default rules (name/size/kind/date) set in elFinder._sortRules
  4690.      *
  4691.      * @type {Object}
  4692.      * @example
  4693.      * sortRules : {
  4694.      *   name : function(file1, file2) { return file1.name.toLowerCase().localeCompare(file2.name.toLowerCase()); }
  4695.      * }
  4696.      */
  4697.     sortRules : {},
  4698.  
  4699.     /**
  4700.      * Default sort type.
  4701.      *
  4702.      * @type {String}
  4703.      */
  4704.     sortType : 'name',
  4705.    
  4706.     /**
  4707.      * Default sort order.
  4708.      *
  4709.      * @type {String}
  4710.      * @default "asc"
  4711.      */
  4712.     sortOrder : 'asc',
  4713.    
  4714.     /**
  4715.      * Display folders first?
  4716.      *
  4717.      * @type {Boolean}
  4718.      * @default true
  4719.      */
  4720.     sortStickFolders : true,
  4721.    
  4722.     /**
  4723.      * If true - elFinder will formating dates itself,
  4724.      * otherwise - backend date will be used.
  4725.      *
  4726.      * @type Boolean
  4727.      */
  4728.     clientFormatDate : true,
  4729.    
  4730.     /**
  4731.      * Show UTC dates.
  4732.      * Required set clientFormatDate to true
  4733.      *
  4734.      * @type Boolean
  4735.      */
  4736.     UTCDate : false,
  4737.    
  4738.     /**
  4739.      * File modification datetime format.
  4740.      * Value from selected language data  is used by default.
  4741.      * Set format here to overwrite it.
  4742.      *
  4743.      * @type String
  4744.      * @default  ""
  4745.      */
  4746.     dateFormat : '',
  4747.    
  4748.     /**
  4749.      * File modification datetime format in form "Yesterday 12:23:01".
  4750.      * Value from selected language data is used by default.
  4751.      * Set format here to overwrite it.
  4752.      * Use $1 for "Today"/"Yesterday" placeholder
  4753.      *
  4754.      * @type String
  4755.      * @default  ""
  4756.      * @example "$1 H:m:i"
  4757.      */
  4758.     fancyDateFormat : '',
  4759.    
  4760.     /**
  4761.      * Style of file mode at cwd-list, info dialog
  4762.      * 'string' (ex. rwxr-xr-x) or 'octal' (ex. 755) or 'both' (ex. rwxr-xr-x (755))
  4763.      *
  4764.      * @type {String}
  4765.      * @default 'both'
  4766.      */
  4767.     fileModeStyle : 'both',
  4768.    
  4769.     /**
  4770.      * elFinder width
  4771.      *
  4772.      * @type String|Number
  4773.      * @default  "auto"
  4774.      */
  4775.     width : 'auto',
  4776.    
  4777.     /**
  4778.      * elFinder height
  4779.      *
  4780.      * @type Number
  4781.      * @default  "auto"
  4782.      */
  4783.     height : 400,
  4784.    
  4785.     /**
  4786.      * Make elFinder resizable if jquery ui resizable available
  4787.      *
  4788.      * @type Boolean
  4789.      * @default  true
  4790.      */
  4791.     resizable : true,
  4792.    
  4793.     /**
  4794.      * Timeout before open notifications dialogs
  4795.      *
  4796.      * @type Number
  4797.      * @default  500 (.5 sec)
  4798.      */
  4799.     notifyDelay : 500,
  4800.    
  4801.     /**
  4802.      * Position CSS, Width of notifications dialogs
  4803.      *
  4804.      * @type Object
  4805.      * @default {position: {top : '12px', right : '12px'}, width : 280}
  4806.      * position: CSS object | null (null: position center & middle)
  4807.      */
  4808.     notifyDialog : {position: {top : '12px', right : '12px'}, width : 280},
  4809.    
  4810.     /**
  4811.      * Allow shortcuts
  4812.      *
  4813.      * @type Boolean
  4814.      * @default  true
  4815.      */
  4816.     allowShortcuts : true,
  4817.    
  4818.     /**
  4819.      * Remeber last opened dir to open it after reload or in next session
  4820.      *
  4821.      * @type Boolean
  4822.      * @default  true
  4823.      */
  4824.     rememberLastDir : true,
  4825.    
  4826.     /**
  4827.      * Clear historys(elFinder) on reload(not browser) function
  4828.      * Historys was cleared on Reload function on elFinder 2.0 (value is true)
  4829.      *
  4830.      * @type Boolean
  4831.      * @default  false
  4832.      */
  4833.     reloadClearHistory : false,
  4834.    
  4835.     /**
  4836.      * Use browser native history with supported browsers
  4837.      *
  4838.      * @type Boolean
  4839.      * @default  true
  4840.      */
  4841.     useBrowserHistory : true,
  4842.    
  4843.     /**
  4844.      * Lazy load config.
  4845.      * How many files display at once?
  4846.      *
  4847.      * @type Number
  4848.      * @default  50
  4849.      */
  4850.     showFiles : 30,
  4851.    
  4852.     /**
  4853.      * Lazy load config.
  4854.      * Distance in px to cwd bottom edge to start display files
  4855.      *
  4856.      * @type Number
  4857.      * @default  50
  4858.      */
  4859.     showThreshold : 50,
  4860.    
  4861.     /**
  4862.      * Additional rule to valid new file name.
  4863.      * By default not allowed empty names or '..'
  4864.      *
  4865.      * @type false|RegExp|function
  4866.      * @default  false
  4867.      * @example
  4868.      *  disable names with spaces:
  4869.      *  validName : /^[^\s]$/
  4870.      */
  4871.     validName : false,
  4872.    
  4873.     /**
  4874.      * Sync content interval
  4875.      * @todo - fix in elFinder
  4876.      * @type Number
  4877.      * @default  0 (do not sync)
  4878.      */
  4879.     sync : 0,
  4880.    
  4881.     /**
  4882.      * How many thumbnails create in one request
  4883.      *
  4884.      * @type Number
  4885.      * @default  5
  4886.      */
  4887.     loadTmbs : 5,
  4888.    
  4889.     /**
  4890.      * Cookie option for browsersdoes not suppot localStorage
  4891.      *
  4892.      * @type Object
  4893.      */
  4894.     cookie         : {
  4895.         expires : 30,
  4896.         domain  : '',
  4897.         path    : '/',
  4898.         secure  : false
  4899.     },
  4900.    
  4901.     /**
  4902.      * Contextmenu config
  4903.      *
  4904.      * @type Object
  4905.      */
  4906.     contextmenu : {
  4907.         // navbarfolder menu
  4908.         navbar : ['open', '|', 'upload', '|', 'copy', 'cut', 'paste', 'duplicate', '|', 'rm', '|', 'rename', '|', 'places', 'info', 'chmod', 'netunmount'],
  4909.         // current directory menu
  4910.         cwd    : ['reload', 'back', '|', 'upload', 'mkdir', 'mkfile', 'paste', '|', 'sort', '|', 'info'],
  4911.         // current directory file menu
  4912.         files  : ['getfile', '|','open', 'quicklook', '|', 'download', 'upload', '|', 'copy', 'cut', 'paste', 'duplicate', '|', 'rm', '|', 'edit', 'rename', 'resize', '|', 'archive', 'extract', '|', 'places', 'info', 'chmod']
  4913.     },
  4914.  
  4915.     /**
  4916.      * Debug config
  4917.      *
  4918.      * @type Array|Boolean
  4919.      */
  4920.     // debug : true
  4921.     debug : ['error', 'warning', 'event-destroy']
  4922. }
  4923.  
  4924.  
  4925. /*
  4926.  * File: /js/elFinder.history.js
  4927.  */
  4928.  
  4929. /**
  4930.  * @class elFinder.history
  4931.  * Store visited folders
  4932.  * and provide "back" and "forward" methods
  4933.  *
  4934.  * @author Dmitry (dio) Levashov
  4935.  */
  4936. elFinder.prototype.history = function(fm) {
  4937.     var self = this,
  4938.         /**
  4939.          * Update history on "open" event?
  4940.          *
  4941.          * @type Boolean
  4942.          */
  4943.         update = true,
  4944.         /**
  4945.          * Directories hashes storage
  4946.          *
  4947.          * @type Array
  4948.          */
  4949.         history = [],
  4950.         /**
  4951.          * Current directory index in history
  4952.          *
  4953.          * @type Number
  4954.          */
  4955.         current,
  4956.         /**
  4957.          * Clear history
  4958.          *
  4959.          * @return void
  4960.          */
  4961.         reset = function() {
  4962.             history = [fm.cwd().hash];
  4963.             current = 0;
  4964.             update  = true;
  4965.         },
  4966.         /**
  4967.          * Browser native history object
  4968.          */
  4969.         nativeHistory = (fm.options.useBrowserHistory && window.history && window.history.pushState)? window.history : null,
  4970.         /**
  4971.          * Open prev/next folder
  4972.          *
  4973.          * @Boolen  open next folder?
  4974.          * @return jQuery.Deferred
  4975.          */
  4976.         go = function(fwd) {
  4977.             if ((fwd && self.canForward()) || (!fwd && self.canBack())) {
  4978.                 update = false;
  4979.                 return fm.exec('open', history[fwd ? ++current : --current]).fail(reset);
  4980.             }
  4981.             return $.Deferred().reject();
  4982.         };
  4983.    
  4984.     /**
  4985.      * Return true if there is previous visited directories
  4986.      *
  4987.      * @return Boolen
  4988.      */
  4989.     this.canBack = function() {
  4990.         return current > 0;
  4991.     }
  4992.    
  4993.     /**
  4994.      * Return true if can go forward
  4995.      *
  4996.      * @return Boolen
  4997.      */
  4998.     this.canForward = function() {
  4999.         return current < history.length - 1;
  5000.     }
  5001.    
  5002.     /**
  5003.      * Go back
  5004.      *
  5005.      * @return void
  5006.      */
  5007.     this.back = go;
  5008.    
  5009.     /**
  5010.      * Go forward
  5011.      *
  5012.      * @return void
  5013.      */
  5014.     this.forward = function() {
  5015.         return go(true);
  5016.     }
  5017.    
  5018.     // bind to elfinder events
  5019.     fm.open(function() {
  5020.         var l = history.length,
  5021.             cwd = fm.cwd().hash;
  5022.  
  5023.         if (update) {
  5024.             current >= 0 && l > current + 1 && history.splice(current+1);
  5025.             history[history.length-1] != cwd && history.push(cwd);
  5026.             current = history.length - 1;
  5027.         }
  5028.         update = true;
  5029.  
  5030.         if (nativeHistory) {
  5031.             if (! nativeHistory.state) {
  5032.                 nativeHistory.replaceState({thash: cwd}, null, location.pathname + location.search + '#elf_' + cwd);
  5033.             } else {
  5034.                 nativeHistory.state.thash != cwd && nativeHistory.pushState({thash: cwd}, null, location.pathname + location.search + '#elf_' + cwd);
  5035.             }
  5036.         }
  5037.     })
  5038.     .reload(fm.options.reloadClearHistory && reset);
  5039.    
  5040. }
  5041.  
  5042. /*
  5043.  * File: /js/elFinder.command.js
  5044.  */
  5045.  
  5046. /**
  5047.  * elFinder command prototype
  5048.  *
  5049.  * @type  elFinder.command
  5050.  * @author  Dmitry (dio) Levashov
  5051.  */
  5052. elFinder.prototype.command = function(fm) {
  5053.  
  5054.     /**
  5055.      * elFinder instance
  5056.      *
  5057.      * @type  elFinder
  5058.      */
  5059.     this.fm = fm;
  5060.    
  5061.     /**
  5062.      * Command name, same as class name
  5063.      *
  5064.      * @type  String
  5065.      */
  5066.     this.name = '';
  5067.    
  5068.     /**
  5069.      * Short command description
  5070.      *
  5071.      * @type  String
  5072.      */
  5073.     this.title = '';
  5074.    
  5075.     /**
  5076.      * Current command state
  5077.      *
  5078.      * @example
  5079.      * this.state = -1; // command disabled
  5080.      * this.state = 0;  // command enabled
  5081.      * this.state = 1;  // command active (for example "fullscreen" command while elfinder in fullscreen mode)
  5082.      * @default -1
  5083.      * @type  Number
  5084.      */
  5085.     this.state = -1;
  5086.    
  5087.     /**
  5088.      * If true, command can not be disabled by connector.
  5089.      * @see this.update()
  5090.      *
  5091.      * @type  Boolen
  5092.      */
  5093.     this.alwaysEnabled = false;
  5094.    
  5095.     /**
  5096.      * If true, this means command was disabled by connector.
  5097.      * @see this.update()
  5098.      *
  5099.      * @type  Boolen
  5100.      */
  5101.     this._disabled = false;
  5102.    
  5103.     this.disableOnSearch = false;
  5104.    
  5105.     this.updateOnSelect = true;
  5106.    
  5107.     /**
  5108.      * elFinder events defaults handlers.
  5109.      * Inside handlers "this" is current command object
  5110.      *
  5111.      * @type  Object
  5112.      */
  5113.     this._handlers = {
  5114.         enable  : function() { this.update(void(0), this.value); },
  5115.         disable : function() { this.update(-1, this.value); },
  5116.         'open reload load'    : function() {
  5117.             this._disabled = !(this.alwaysEnabled || this.fm.isCommandEnabled(this.name));
  5118.             this.update(void(0), this.value)
  5119.             this.change();
  5120.         }
  5121.     };
  5122.    
  5123.     /**
  5124.      * elFinder events handlers.
  5125.      * Inside handlers "this" is current command object
  5126.      *
  5127.      * @type  Object
  5128.      */
  5129.     this.handlers = {}
  5130.    
  5131.     /**
  5132.      * Shortcuts
  5133.      *
  5134.      * @type  Array
  5135.      */
  5136.     this.shortcuts = [];
  5137.    
  5138.     /**
  5139.      * Command options
  5140.      *
  5141.      * @type  Object
  5142.      */
  5143.     this.options = {ui : 'button'};
  5144.    
  5145.     /**
  5146.      * Prepare object -
  5147.      * bind events and shortcuts
  5148.      *
  5149.      * @return void
  5150.      */
  5151.     this.setup = function(name, opts) {
  5152.         var self = this,
  5153.             fm   = this.fm, i, s;
  5154.  
  5155.         this.name      = name;
  5156.         this.title     = fm.messages['cmd'+name] ? fm.i18n('cmd'+name) : name,
  5157.         this.options   = $.extend({}, this.options, opts);
  5158.         this.listeners = [];
  5159.  
  5160.         if (this.updateOnSelect) {
  5161.             this._handlers.select = function() { this.update(void(0), this.value); }
  5162.         }
  5163.  
  5164.         $.each($.extend({}, self._handlers, self.handlers), function(cmd, handler) {
  5165.             fm.bind(cmd, $.proxy(handler, self));
  5166.         });
  5167.  
  5168.         for (i = 0; i < this.shortcuts.length; i++) {
  5169.             s = this.shortcuts[i];
  5170.             s.callback = $.proxy(s.callback || function() { this.exec() }, this);
  5171.             !s.description && (s.description = this.title);
  5172.             fm.shortcut(s);
  5173.         }
  5174.  
  5175.         if (this.disableOnSearch) {
  5176.             fm.bind('search searchend', function(e) {
  5177.                 self._disabled = e.type == 'search';
  5178.                 self.update(void(0), self.value);
  5179.             });
  5180.         }
  5181.  
  5182.         this.init();
  5183.     }
  5184.  
  5185.     /**
  5186.      * Command specific init stuffs
  5187.      *
  5188.      * @return void
  5189.      */
  5190.     this.init = function() { }
  5191.  
  5192.     /**
  5193.      * Exec command
  5194.      *
  5195.      * @param  Array         target files hashes
  5196.      * @param  Array|Object  command value
  5197.      * @return $.Deferred
  5198.      */
  5199.     this.exec = function(files, opts) {
  5200.         return $.Deferred().reject();
  5201.     }
  5202.    
  5203.     /**
  5204.      * Return true if command disabled.
  5205.      *
  5206.      * @return Boolen
  5207.      */
  5208.     this.disabled = function() {
  5209.         return this.state < 0;
  5210.     }
  5211.    
  5212.     /**
  5213.      * Return true if command enabled.
  5214.      *
  5215.      * @return Boolen
  5216.      */
  5217.     this.enabled = function() {
  5218.         return this.state > -1;
  5219.     }
  5220.    
  5221.     /**
  5222.      * Return true if command active.
  5223.      *
  5224.      * @return Boolen
  5225.      */
  5226.     this.active = function() {
  5227.         return this.state > 0;
  5228.     }
  5229.    
  5230.     /**
  5231.      * Return current command state.
  5232.      * Must be overloaded in most commands
  5233.      *
  5234.      * @return Number
  5235.      */
  5236.     this.getstate = function() {
  5237.         return -1;
  5238.     }
  5239.    
  5240.     /**
  5241.      * Update command state/value
  5242.      * and rize 'change' event if smth changed
  5243.      *
  5244.      * @param  Number  new state or undefined to auto update state
  5245.      * @param  mixed   new value
  5246.      * @return void
  5247.      */
  5248.     this.update = function(s, v) {
  5249.         var state = this.state,
  5250.             value = this.value;
  5251.  
  5252.         if (this._disabled) {
  5253.             this.state = -1;
  5254.         } else {
  5255.             this.state = s !== void(0) ? s : this.getstate();
  5256.         }
  5257.  
  5258.         this.value = v;
  5259.        
  5260.         if (state != this.state || value != this.value) {
  5261.             this.change();
  5262.         }
  5263.     }
  5264.    
  5265.     /**
  5266.      * Bind handler / fire 'change' event.
  5267.      *
  5268.      * @param  Function|undefined  event callback
  5269.      * @return void
  5270.      */
  5271.     this.change = function(c) {
  5272.         var cmd, i;
  5273.        
  5274.         if (typeof(c) === 'function') {
  5275.             this.listeners.push(c);        
  5276.         } else {
  5277.             for (i = 0; i < this.listeners.length; i++) {
  5278.                 cmd = this.listeners[i];
  5279.                 try {
  5280.                     cmd(this.state, this.value);
  5281.                 } catch (e) {
  5282.                     this.fm.debug('error', e)
  5283.                 }
  5284.             }
  5285.         }
  5286.         return this;
  5287.     }
  5288.    
  5289.  
  5290.     /**
  5291.      * With argument check given files hashes and return list of existed files hashes.
  5292.      * Without argument return selected files hashes.
  5293.      *
  5294.      * @param  Array|String|void  hashes
  5295.      * @return Array
  5296.      */
  5297.     this.hashes = function(hashes) {
  5298.         return hashes
  5299.             ? $.map($.isArray(hashes) ? hashes : [hashes], function(hash) { return fm.file(hash) ? hash : null; })
  5300.             : fm.selected();
  5301.     }
  5302.    
  5303.     /**
  5304.      * Return only existed files from given fils hashes | selected files
  5305.      *
  5306.      * @param  Array|String|void  hashes
  5307.      * @return Array
  5308.      */
  5309.     this.files = function(hashes) {
  5310.         var fm = this.fm;
  5311.        
  5312.         return hashes
  5313.             ? $.map($.isArray(hashes) ? hashes : [hashes], function(hash) { return fm.file(hash) || null })
  5314.             : fm.selectedFiles();
  5315.     }
  5316. }
  5317.  
  5318.  
  5319.  
  5320.  
  5321. /*
  5322.  * File: /js/elFinder.resources.js
  5323.  */
  5324.  
  5325. /**
  5326.  * elFinder resources registry.
  5327.  * Store shared data
  5328.  *
  5329.  * @type Object
  5330.  * @author Dmitry (dio) Levashov
  5331.  **/
  5332. elFinder.prototype.resources = {
  5333.     'class' : {
  5334.         hover       : 'ui-state-hover',
  5335.         active      : 'ui-state-active',
  5336.         disabled    : 'ui-state-disabled',
  5337.         draggable   : 'ui-draggable',
  5338.         droppable   : 'ui-droppable',
  5339.         adroppable  : 'elfinder-droppable-active',
  5340.         cwdfile     : 'elfinder-cwd-file',
  5341.         cwd         : 'elfinder-cwd',
  5342.         tree        : 'elfinder-tree',
  5343.         treeroot    : 'elfinder-navbar-root',
  5344.         navdir      : 'elfinder-navbar-dir',
  5345.         navdirwrap  : 'elfinder-navbar-dir-wrapper',
  5346.         navarrow    : 'elfinder-navbar-arrow',
  5347.         navsubtree  : 'elfinder-navbar-subtree',
  5348.         navcollapse : 'elfinder-navbar-collapsed',
  5349.         navexpand   : 'elfinder-navbar-expanded',
  5350.         treedir     : 'elfinder-tree-dir',
  5351.         placedir    : 'elfinder-place-dir',
  5352.         searchbtn   : 'elfinder-button-search'
  5353.     },
  5354.     tpl : {
  5355.         perms      : '<span class="elfinder-perms"/>',
  5356.         lock       : '<span class="elfinder-lock"/>',
  5357.         symlink    : '<span class="elfinder-symlink"/>',
  5358.         navicon    : '<span class="elfinder-nav-icon"/>',
  5359.         navspinner : '<span class="elfinder-navbar-spinner"/>',
  5360.         navdir     : '<div class="elfinder-navbar-wrapper"><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"/></div>'
  5361.        
  5362.     },
  5363.    
  5364.     mimes : {
  5365.         text : [
  5366.             'application/x-empty',
  5367.             'application/javascript',
  5368.             'application/xhtml+xml',
  5369.             'audio/x-mp3-playlist',
  5370.             'application/x-web-config',
  5371.             'application/docbook+xml',
  5372.             'application/x-php',
  5373.             'application/x-perl',
  5374.             'application/x-awk',
  5375.             'application/x-config',
  5376.             'application/x-csh',
  5377.             'application/xml'
  5378.         ]
  5379.     },
  5380.    
  5381.     mixin : {
  5382.         make : function() {
  5383.             var fm   = this.fm,
  5384.                 cmd  = this.name,
  5385.                 cwd  = fm.getUI('cwd'),
  5386.                 tarea= (fm.storage('view') != 'list'),
  5387.                 rest = function(){
  5388.                     if (tarea) {
  5389.                         node.zIndex('').css('position', '');
  5390.                         nnode.css('max-height', '');
  5391.                     } else {
  5392.                         pnode.css('width', '');
  5393.                         pnode.parent('td').css('overflow', '');
  5394.                     }
  5395.                 }, colwidth,
  5396.                 dfrd = $.Deferred()
  5397.                     .fail(function(error) {
  5398.                         rest();
  5399.                         cwd.trigger('unselectall');
  5400.                         error && fm.error(error);
  5401.                     })
  5402.                     .always(function() {
  5403.                         input.remove();
  5404.                         node.remove();
  5405.                         fm.enable();
  5406.                     }),
  5407.                 id    = 'tmp_'+parseInt(Math.random()*100000),
  5408.                 phash = fm.cwd().hash,
  5409.                 date = new Date(),
  5410.                 file   = {
  5411.                     hash  : id,
  5412.                     name  : fm.uniqueName(this.prefix),
  5413.                     mime  : this.mime,
  5414.                     read  : true,
  5415.                     write : true,
  5416.                     date  : 'Today '+date.getHours()+':'+date.getMinutes()
  5417.                 },
  5418.                 data = this.data || {},
  5419.                 node = cwd.trigger('create.'+fm.namespace, file).find('#'+id),
  5420.                 nnode, pnode,
  5421.                 input = $(tarea? '<textarea/>' : '<input type="text"/>')
  5422.                     .on('keyup text', function(){
  5423.                         if (tarea) {
  5424.                             this.style.height = '1px';
  5425.                             this.style.height = this.scrollHeight + 'px';
  5426.                         } else if (colwidth) {
  5427.                             this.style.width = colwidth + 'px';
  5428.                             if (this.scrollWidth > colwidth) {
  5429.                                 this.style.width = this.scrollWidth + 10 + 'px';
  5430.                             }
  5431.                         }
  5432.                     })
  5433.                     .keydown(function(e) {
  5434.                         e.stopImmediatePropagation();
  5435.  
  5436.                         if (e.keyCode == $.ui.keyCode.ESCAPE) {
  5437.                             dfrd.reject();
  5438.                         } else if (e.keyCode == $.ui.keyCode.ENTER) {
  5439.                             input.blur();
  5440.                         }
  5441.                     })
  5442.                     .mousedown(function(e) {
  5443.                         e.stopPropagation();
  5444.                     })
  5445.                     .blur(function() {
  5446.                         var name   = $.trim(input.val()),
  5447.                             parent = input.parent();
  5448.  
  5449.                         if (parent.length) {
  5450.  
  5451.                             if (!name) {
  5452.                                 return dfrd.reject('errInvName');
  5453.                             }
  5454.                             if (fm.fileByName(name, phash)) {
  5455.                                 return dfrd.reject(['errExists', name]);
  5456.                             }
  5457.  
  5458.                             rest();
  5459.                             parent.html(fm.escape(name));
  5460.  
  5461.                             fm.lockfiles({files : [id]});
  5462.  
  5463.                             fm.request({
  5464.                                     data        : $.extend({cmd : cmd, name : name, target : phash}, data || {}),
  5465.                                     notify      : {type : cmd, cnt : 1},
  5466.                                     preventFail : true,
  5467.                                     syncOnFail  : true
  5468.                                 })
  5469.                                 .fail(function(error) {
  5470.                                     dfrd.reject(error);
  5471.                                 })
  5472.                                 .done(function(data) {
  5473.                                     dfrd.resolve(data);
  5474.                                     if (data.added && data.added[0]) {
  5475.                                         var newItem = cwd.find('#'+data.added[0].hash);
  5476.                                         if (newItem.length) {
  5477.                                             cwd.parent().scrollTop(newItem.offset().top);
  5478.                                         }
  5479.                                     }
  5480.                                 });
  5481.                         }
  5482.                     });
  5483.  
  5484.  
  5485.             if (this.disabled() || !node.length) {
  5486.                 return dfrd.reject();
  5487.             }
  5488.  
  5489.             fm.disable();
  5490.             nnode = node.find('.elfinder-cwd-filename');
  5491.             pnode = nnode.parent();
  5492.             if (tarea) {
  5493.                 node.zIndex((node.parent().zIndex()) + 1).css('position', 'relative');
  5494.                 nnode.css('max-height', 'none');
  5495.             } else {
  5496.                 colwidth = pnode.width();
  5497.                 pnode.width(colwidth - 15);
  5498.                 pnode.parent('td').css('overflow', 'visible');
  5499.             }
  5500.             nnode.empty('').append(input.val(file.name));
  5501.             input.trigger('keyup');
  5502.             input.select().focus();
  5503.             input[0].setSelectionRange && input[0].setSelectionRange(0, file.name.replace(/\..+$/, '').length);
  5504.  
  5505.             return dfrd;
  5506.  
  5507.  
  5508.  
  5509.         }
  5510.        
  5511.     }
  5512. }
  5513.  
  5514.  
  5515.  
  5516. /*
  5517.  * File: /js/jquery.dialogelfinder.js
  5518.  */
  5519.  
  5520. /**
  5521.  * @class dialogelfinder - open elFinder in dialog window
  5522.  *
  5523.  * @param  Object  elFinder options with dialog options
  5524.  * @example
  5525.  * $(selector).dialogelfinder({
  5526.  *     // some elfinder options
  5527.  *     title          : 'My files', // dialog title, default = "Files"
  5528.  *     width          : 850,        // dialog width, default 840
  5529.  *     autoOpen       : false,      // if false - dialog will not be opened after init, default = true
  5530.  *     destroyOnClose : true        // destroy elFinder on close dialog, default = false
  5531.  * })
  5532.  * @author Dmitry (dio) Levashov
  5533.  **/
  5534. $.fn.dialogelfinder = function(opts) {
  5535.     var position = 'elfinderPosition',
  5536.         destroy  = 'elfinderDestroyOnClose';
  5537.    
  5538.     this.not('.elfinder').each(function() {
  5539.  
  5540.        
  5541.         var doc     = $(document),
  5542.             toolbar = $('<div class="ui-widget-header dialogelfinder-drag ui-corner-top">'+(opts.title || 'Files')+'</div>'),
  5543.             button  = $('<a href="#" class="dialogelfinder-drag-close ui-corner-all"><span class="ui-icon ui-icon-closethick"/></a>')
  5544.                 .appendTo(toolbar)
  5545.                 .click(function(e) {
  5546.                     e.preventDefault();
  5547.                    
  5548.                     node.dialogelfinder('close');
  5549.                 }),
  5550.             node    = $(this).addClass('dialogelfinder')
  5551.                 .css('position', 'absolute')
  5552.                 .hide()
  5553.                 .appendTo('body')
  5554.                 .draggable({ handle : '.dialogelfinder-drag',
  5555.                          containment : 'window' })
  5556.                 .elfinder(opts)
  5557.                 .prepend(toolbar),
  5558.             elfinder = node.elfinder('instance');
  5559.        
  5560.        
  5561.         node.width(parseInt(node.width()) || 840) // fix width if set to "auto"
  5562.             .data(destroy, !!opts.destroyOnClose)
  5563.             .find('.elfinder-toolbar').removeClass('ui-corner-top');
  5564.        
  5565.         opts.position && node.data(position, opts.position);
  5566.        
  5567.         opts.autoOpen !== false && $(this).dialogelfinder('open');
  5568.  
  5569.     });
  5570.    
  5571.     if (opts == 'open') {
  5572.         var node = $(this),
  5573.             pos  = node.data(position) || {
  5574.                 top  : parseInt($(document).scrollTop() + ($(window).height() < node.height() ? 2 : ($(window).height() - node.height())/2)),
  5575.                 left : parseInt($(document).scrollLeft() + ($(window).width() < node.width()  ? 2 : ($(window).width()  - node.width())/2))
  5576.             },
  5577.             zindex = 100;
  5578.  
  5579.         if (node.is(':hidden')) {
  5580.            
  5581.             $('body').find(':visible').each(function() {
  5582.                 var $this = $(this), z;
  5583.                
  5584.                 if (this !== node[0] && $this.css('position') == 'absolute' && (z = parseInt($this.zIndex())) > zindex) {
  5585.                     zindex = z + 1;
  5586.                 }
  5587.             });
  5588.  
  5589.             node.zIndex(zindex).css(pos).show().trigger('resize');
  5590.  
  5591.             setTimeout(function() {
  5592.                 // fix resize icon position and make elfinder active
  5593.                 node.trigger('resize').mousedown();
  5594.             }, 200);
  5595.         }
  5596.     } else if (opts == 'close') {
  5597.         var node = $(this);
  5598.            
  5599.         if (node.is(':visible')) {
  5600.             !!node.data(destroy)
  5601.                 ? node.elfinder('destroy').remove()
  5602.                 : node.elfinder('close');
  5603.         }
  5604.     } else if (opts == 'instance') {
  5605.         return $(this).getElFinder();
  5606.     }
  5607.  
  5608.     return this;
  5609. };
  5610.  
  5611.  
  5612.  
  5613. /*
  5614.  * File: /js/i18n/elfinder.en.js
  5615.  */
  5616.  
  5617. /**
  5618.  * English translation
  5619.  * @author Troex Nevelin <troex@fury.scancode.ru>
  5620.  * @version 2015-11-21
  5621.  */
  5622. if (elFinder && elFinder.prototype && typeof(elFinder.prototype.i18) == 'object') {
  5623.     elFinder.prototype.i18.en = {
  5624.         translator : 'Troex Nevelin &lt;troex@fury.scancode.ru&gt;',
  5625.         language   : 'English',
  5626.         direction  : 'ltr',
  5627.         dateFormat : 'M d, Y h:i A', // Mar 13, 2012 05:27 PM
  5628.         fancyDateFormat : '$1 h:i A', // will produce smth like: Today 12:25 PM
  5629.         messages   : {
  5630.            
  5631.             /********************************** errors **********************************/
  5632.             'error'                : 'Error',
  5633.             'errUnknown'           : 'Unknown error.',
  5634.             'errUnknownCmd'        : 'Unknown command.',
  5635.             'errJqui'              : 'Invalid jQuery UI configuration. Selectable, draggable and droppable components must be included.',
  5636.             'errNode'              : 'elFinder requires DOM Element to be created.',
  5637.             'errURL'               : 'Invalid elFinder configuration! URL option is not set.',
  5638.             'errAccess'            : 'Access denied.',
  5639.             'errConnect'           : 'Unable to connect to backend.',
  5640.             'errAbort'             : 'Connection aborted.',
  5641.             'errTimeout'           : 'Connection timeout.',
  5642.             'errNotFound'          : 'Backend not found.',
  5643.             'errResponse'          : 'Invalid backend response.',
  5644.             'errConf'              : 'Invalid backend configuration.',
  5645.             'errJSON'              : 'PHP JSON module not installed.',
  5646.             'errNoVolumes'         : 'Readable volumes not available.',
  5647.             'errCmdParams'         : 'Invalid parameters for command "$1".',
  5648.             'errDataNotJSON'       : 'Data is not JSON.',
  5649.             'errDataEmpty'         : 'Data is empty.',
  5650.             'errCmdReq'            : 'Backend request requires command name.',
  5651.             'errOpen'              : 'Unable to open "$1".',
  5652.             'errNotFolder'         : 'Object is not a folder.',
  5653.             'errNotFile'           : 'Object is not a file.',
  5654.             'errRead'              : 'Unable to read "$1".',
  5655.             'errWrite'             : 'Unable to write into "$1".',
  5656.             'errPerm'              : 'Permission denied.',
  5657.             'errLocked'            : '"$1" is locked and can not be renamed, moved or removed.',
  5658.             'errExists'            : 'File named "$1" already exists.',
  5659.             'errInvName'           : 'Invalid file name.',
  5660.             'errFolderNotFound'    : 'Folder not found.',
  5661.             'errFileNotFound'      : 'File not found.',
  5662.             'errTrgFolderNotFound' : 'Target folder "$1" not found.',
  5663.             'errPopup'             : 'Browser prevented opening popup window. To open file enable it in browser options.',
  5664.             'errMkdir'             : 'Unable to create folder "$1".',
  5665.             'errMkfile'            : 'Unable to create file "$1".',
  5666.             'errRename'            : 'Unable to rename "$1".',
  5667.             'errCopyFrom'          : 'Copying files from volume "$1" not allowed.',
  5668.             'errCopyTo'            : 'Copying files to volume "$1" not allowed.',
  5669.             'errMkOutLink'         : 'Unable to create a link to outside the volume root.', // from v2.1 added 03.10.2015
  5670.             'errUpload'            : 'Upload error.',  // old name - errUploadCommon
  5671.             'errUploadFile'        : 'Unable to upload "$1".', // old name - errUpload
  5672.             'errUploadNoFiles'     : 'No files found for upload.',
  5673.             'errUploadTotalSize'   : 'Data exceeds the maximum allowed size.', // old name - errMaxSize
  5674.             'errUploadFileSize'    : 'File exceeds maximum allowed size.', //  old name - errFileMaxSize
  5675.             'errUploadMime'        : 'File type not allowed.',
  5676.             'errUploadTransfer'    : '"$1" transfer error.',
  5677.             'errUploadTemp'        : 'Unable to make temporary file for upload.', // from v2.1 added 26.09.2015
  5678.             'errNotReplace'        : 'Object "$1" already exists at this location and can not be replaced by object with another type.', // new
  5679.             'errReplace'           : 'Unable to replace "$1".',
  5680.             'errSave'              : 'Unable to save "$1".',
  5681.             'errCopy'              : 'Unable to copy "$1".',
  5682.             'errMove'              : 'Unable to move "$1".',
  5683.             'errCopyInItself'      : 'Unable to copy "$1" into itself.',
  5684.             'errRm'                : 'Unable to remove "$1".',
  5685.             'errRmSrc'             : 'Unable remove source file(s).',
  5686.             'errExtract'           : 'Unable to extract files from "$1".',
  5687.             'errArchive'           : 'Unable to create archive.',
  5688.             'errArcType'           : 'Unsupported archive type.',
  5689.             'errNoArchive'         : 'File is not archive or has unsupported archive type.',
  5690.             'errCmdNoSupport'      : 'Backend does not support this command.',
  5691.             'errReplByChild'       : 'The folder "$1" can\'t be replaced by an item it contains.',
  5692.             'errArcSymlinks'       : 'For security reason denied to unpack archives contains symlinks or files with not allowed names.', // edited 24.06.2012
  5693.             'errArcMaxSize'        : 'Archive files exceeds maximum allowed size.',
  5694.             'errResize'            : 'Unable to resize "$1".',
  5695.             'errResizeDegree'      : 'Invalid rotate degree.',  // added 7.3.2013
  5696.             'errResizeRotate'      : 'Unable to rotate image.',  // added 7.3.2013
  5697.             'errResizeSize'        : 'Invalid image size.',  // added 7.3.2013
  5698.             'errResizeNoChange'    : 'Image size not changed.',  // added 7.3.2013
  5699.             'errUsupportType'      : 'Unsupported file type.',
  5700.             'errNotUTF8Content'    : 'File "$1" is not in UTF-8 and cannot be edited.',  // added 9.11.2011
  5701.             'errNetMount'          : 'Unable to mount "$1".', // added 17.04.2012
  5702.             'errNetMountNoDriver'  : 'Unsupported protocol.',     // added 17.04.2012
  5703.             'errNetMountFailed'    : 'Mount failed.',         // added 17.04.2012
  5704.             'errNetMountHostReq'   : 'Host required.', // added 18.04.2012
  5705.             'errSessionExpires'    : 'Your session has expired due to inactivity.',
  5706.             'errCreatingTempDir'   : 'Unable to create temporary directory: "$1"',
  5707.             'errFtpDownloadFile'   : 'Unable to download file from FTP: "$1"',
  5708.             'errFtpUploadFile'     : 'Unable to upload file to FTP: "$1"',
  5709.             'errFtpMkdir'          : 'Unable to create remote directory on FTP: "$1"',
  5710.             'errArchiveExec'       : 'Error while archiving files: "$1"',
  5711.             'errExtractExec'       : 'Error while extracting files: "$1"',
  5712.             'errNetUnMount'        : 'Unable to unmount', // from v2.1 added 30.04.2012
  5713.             'errConvUTF8'          : 'Not convertible to UTF-8', // from v2.1 added 08.04.2014
  5714.             'errFolderUpload'      : 'Try Google Chrome, If you\'d like to upload the folder.', // from v2.1 added 26.6.2015
  5715.  
  5716.             /******************************* commands names ********************************/
  5717.             'cmdarchive'   : 'Create archive',
  5718.             'cmdback'      : 'Back',
  5719.             'cmdcopy'      : 'Copy',
  5720.             'cmdcut'       : 'Cut',
  5721.             'cmddownload'  : 'Download',
  5722.             'cmdduplicate' : 'Duplicate',
  5723.             'cmdedit'      : 'Edit file',
  5724.             'cmdextract'   : 'Extract files from archive',
  5725.             'cmdforward'   : 'Forward',
  5726.             'cmdgetfile'   : 'Select files',
  5727.             'cmdhelp'      : 'About this software',
  5728.             'cmdhome'      : 'Home',
  5729.             'cmdinfo'      : 'Get info',
  5730.             'cmdmkdir'     : 'New folder',
  5731.             'cmdmkfile'    : 'New text file',
  5732.             'cmdopen'      : 'Open',
  5733.             'cmdpaste'     : 'Paste',
  5734.             'cmdquicklook' : 'Preview',
  5735.             'cmdreload'    : 'Reload',
  5736.             'cmdrename'    : 'Rename',
  5737.             'cmdrm'        : 'Delete',
  5738.             'cmdsearch'    : 'Find files',
  5739.             'cmdup'        : 'Go to parent directory',
  5740.             'cmdupload'    : 'Upload files',
  5741.             'cmdview'      : 'View',
  5742.             'cmdresize'    : 'Resize & Rotate',
  5743.             'cmdsort'      : 'Sort',
  5744.             'cmdnetmount'  : 'Mount network volume', // added 18.04.2012
  5745.             'cmdnetunmount': 'Unmount', // from v2.1 added 30.04.2012
  5746.             'cmdplaces'    : 'To Places', // added 28.12.2014
  5747.             'cmdchmod'     : 'Change mode', // from v2.1 added 20.6.2015
  5748.            
  5749.             /*********************************** buttons ***********************************/
  5750.             'btnClose'  : 'Close',
  5751.             'btnSave'   : 'Save',
  5752.             'btnRm'     : 'Remove',
  5753.             'btnApply'  : 'Apply',
  5754.             'btnCancel' : 'Cancel',
  5755.             'btnNo'     : 'No',
  5756.             'btnYes'    : 'Yes',
  5757.             'btnMount'  : 'Mount',  // added 18.04.2012
  5758.             'btnApprove': 'Goto $1 & approve', // from v2.1 added 26.04.2012
  5759.             'btnUnmount': 'Unmount', // from v2.1 added 30.04.2012
  5760.             'btnConv'   : 'Convert', // from v2.1 added 08.04.2014
  5761.             'btnCwd'    : 'Here',      // from v2.1 added 22.5.2015
  5762.             'btnVolume' : 'Volume',    // from v2.1 added 22.5.2015
  5763.             'btnAll'    : 'All',       // from v2.1 added 22.5.2015
  5764.             'btnMime'   : 'MIME Type', // from v2.1 added 22.5.2015
  5765.             'btnFileName':'Filename',  // from v2.1 added 22.5.2015
  5766.             'btnSaveClose': 'Save & Close', // from v2.1 added 12.6.2015
  5767.            
  5768.             /******************************** notifications ********************************/
  5769.             'ntfopen'     : 'Open folder',
  5770.             'ntffile'     : 'Open file',
  5771.             'ntfreload'   : 'Reload folder content',
  5772.             'ntfmkdir'    : 'Creating directory',
  5773.             'ntfmkfile'   : 'Creating files',
  5774.             'ntfrm'       : 'Delete files',
  5775.             'ntfcopy'     : 'Copy files',
  5776.             'ntfmove'     : 'Move files',
  5777.             'ntfprepare'  : 'Prepare to copy files',
  5778.             'ntfrename'   : 'Rename files',
  5779.             'ntfupload'   : 'Uploading files',
  5780.             'ntfdownload' : 'Downloading files',
  5781.             'ntfsave'     : 'Save files',
  5782.             'ntfarchive'  : 'Creating archive',
  5783.             'ntfextract'  : 'Extracting files from archive',
  5784.             'ntfsearch'   : 'Searching files',
  5785.             'ntfresize'   : 'Resizing images',
  5786.             'ntfsmth'     : 'Doing something',
  5787.             'ntfloadimg'  : 'Loading image',
  5788.             'ntfnetmount' : 'Mounting network volume', // added 18.04.2012
  5789.             'ntfnetunmount': 'Unmounting network volume', // from v2.1 added 30.04.2012
  5790.             'ntfdim'      : 'Acquiring image dimension', // added 20.05.2013
  5791.             'ntfreaddir'  : 'Reading folder infomation', // from v2.1 added 01.07.2013
  5792.             'ntfurl'      : 'Getting URL of link', // from v2.1 added 11.03.2014
  5793.             'ntfchmod'    : 'Changing file mode', // from v2.1 added 20.6.2015
  5794.            
  5795.             /************************************ dates **********************************/
  5796.             'dateUnknown' : 'unknown',
  5797.             'Today'       : 'Today',
  5798.             'Yesterday'   : 'Yesterday',
  5799.             'msJan'       : 'Jan',
  5800.             'msFeb'       : 'Feb',
  5801.             'msMar'       : 'Mar',
  5802.             'msApr'       : 'Apr',
  5803.             'msMay'       : 'May',
  5804.             'msJun'       : 'Jun',
  5805.             'msJul'       : 'Jul',
  5806.             'msAug'       : 'Aug',
  5807.             'msSep'       : 'Sep',
  5808.             'msOct'       : 'Oct',
  5809.             'msNov'       : 'Nov',
  5810.             'msDec'       : 'Dec',
  5811.             'January'     : 'January',
  5812.             'February'    : 'February',
  5813.             'March'       : 'March',
  5814.             'April'       : 'April',
  5815.             'May'         : 'May',
  5816.             'June'        : 'June',
  5817.             'July'        : 'July',
  5818.             'August'      : 'August',
  5819.             'September'   : 'September',
  5820.             'October'     : 'October',
  5821.             'November'    : 'November',
  5822.             'December'    : 'December',
  5823.             'Sunday'      : 'Sunday',
  5824.             'Monday'      : 'Monday',
  5825.             'Tuesday'     : 'Tuesday',
  5826.             'Wednesday'   : 'Wednesday',
  5827.             'Thursday'    : 'Thursday',
  5828.             'Friday'      : 'Friday',
  5829.             'Saturday'    : 'Saturday',
  5830.             'Sun'         : 'Sun',
  5831.             'Mon'         : 'Mon',
  5832.             'Tue'         : 'Tue',
  5833.             'Wed'         : 'Wed',
  5834.             'Thu'         : 'Thu',
  5835.             'Fri'         : 'Fri',
  5836.             'Sat'         : 'Sat',
  5837.  
  5838.             /******************************** sort variants ********************************/
  5839.             'sortname'          : 'by name',
  5840.             'sortkind'          : 'by kind',
  5841.             'sortsize'          : 'by size',
  5842.             'sortdate'          : 'by date',
  5843.             'sortFoldersFirst'  : 'Folders first',
  5844.  
  5845.             /********************************** new items **********************************/
  5846.             'untitled file.txt' : 'NewFile.txt', // added 10.11.2015
  5847.             'untitled folder'   : 'NewFolder',   // added 10.11.2015
  5848.             'Archive'           : 'NewArchive',  // from v2.1 added 10.11.2015
  5849.  
  5850.             /********************************** messages **********************************/
  5851.             'confirmReq'      : 'Confirmation required',
  5852.             'confirmRm'       : 'Are you sure you want to remove files?<br/>This cannot be undone!',
  5853.             'confirmRepl'     : 'Replace old file with new one?',
  5854.             '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
  5855.             'confirmNotSave'  : 'It has been modified.<br/>Losing work if you do not save changes.', // from v2.1 added 15.7.2015
  5856.             'apllyAll'        : 'Apply to all',
  5857.             'name'            : 'Name',
  5858.             'size'            : 'Size',
  5859.             'perms'           : 'Permissions',
  5860.             'modify'          : 'Modified',
  5861.             'kind'            : 'Kind',
  5862.             'read'            : 'read',
  5863.             'write'           : 'write',
  5864.             'noaccess'        : 'no access',
  5865.             'and'             : 'and',
  5866.             'unknown'         : 'unknown',
  5867.             'selectall'       : 'Select all files',
  5868.             'selectfiles'     : 'Select file(s)',
  5869.             'selectffile'     : 'Select first file',
  5870.             'selectlfile'     : 'Select last file',
  5871.             'viewlist'        : 'List view',
  5872.             'viewicons'       : 'Icons view',
  5873.             'places'          : 'Places',
  5874.             'calc'            : 'Calculate',
  5875.             'path'            : 'Path',
  5876.             'aliasfor'        : 'Alias for',
  5877.             'locked'          : 'Locked',
  5878.             'dim'             : 'Dimensions',
  5879.             'files'           : 'Files',
  5880.             'folders'         : 'Folders',
  5881.             'items'           : 'Items',
  5882.             'yes'             : 'yes',
  5883.             'no'              : 'no',
  5884.             'link'            : 'Link',
  5885.             'searcresult'     : 'Search results',
  5886.             'selected'        : 'selected items',
  5887.             'about'           : 'About',
  5888.             'shortcuts'       : 'Shortcuts',
  5889.             'help'            : 'Help',
  5890.             'webfm'           : 'Web file manager',
  5891.             'ver'             : 'Version',
  5892.             'protocolver'     : 'protocol version',
  5893.             'homepage'        : 'Project home',
  5894.             'docs'            : 'Documentation',
  5895.             'github'          : 'Fork us on Github',
  5896.             'twitter'         : 'Follow us on twitter',
  5897.             'facebook'        : 'Join us on facebook',
  5898.             'team'            : 'Team',
  5899.             'chiefdev'        : 'chief developer',
  5900.             'developer'       : 'developer',
  5901.             'contributor'     : 'contributor',
  5902.             'maintainer'      : 'maintainer',
  5903.             'translator'      : 'translator',
  5904.             'icons'           : 'Icons',
  5905.             'dontforget'      : 'and don\'t forget to take your towel',
  5906.             'shortcutsof'     : 'Shortcuts disabled',
  5907.             'dropFiles'       : 'Drop files here',
  5908.             'or'              : 'or',
  5909.             'selectForUpload' : 'Select files to upload',
  5910.             'moveFiles'       : 'Move files',
  5911.             'copyFiles'       : 'Copy files',
  5912.             'rmFromPlaces'    : 'Remove from places',
  5913.             'aspectRatio'     : 'Aspect ratio',
  5914.             'scale'           : 'Scale',
  5915.             'width'           : 'Width',
  5916.             'height'          : 'Height',
  5917.             'resize'          : 'Resize',
  5918.             'crop'            : 'Crop',
  5919.             'rotate'          : 'Rotate',
  5920.             'rotate-cw'       : 'Rotate 90 degrees CW',
  5921.             'rotate-ccw'      : 'Rotate 90 degrees CCW',
  5922.             'degree'          : '°',
  5923.             'netMountDialogTitle' : 'Mount network volume', // added 18.04.2012
  5924.             'protocol'            : 'Protocol', // added 18.04.2012
  5925.             'host'                : 'Host', // added 18.04.2012
  5926.             'port'                : 'Port', // added 18.04.2012
  5927.             'user'                : 'User', // added 18.04.2012
  5928.             'pass'                : 'Password', // added 18.04.2012
  5929.             'confirmUnmount'      : 'Are you unmount $1?',  // from v2.1 added 30.04.2012
  5930.             'dropFilesBrowser': 'Drop or Paste files from browser', // from v2.1 added 30.05.2012
  5931.             'dropPasteFiles'  : 'Drop or Paste files and URLs here', // from v2.1 added 07.04.2014
  5932.             'encoding'        : 'Encoding', // from v2.1 added 19.12.2014
  5933.             'locale'          : 'Locale',   // from v2.1 added 19.12.2014
  5934.             'searchTarget'    : 'Target: $1',                // from v2.1 added 22.5.2015
  5935.             'searchMime'      : 'Search by input MIME Type', // from v2.1 added 22.5.2015
  5936.             'owner'           : 'Owner', // from v2.1 added 20.6.2015
  5937.             'group'           : 'Group', // from v2.1 added 20.6.2015
  5938.             'other'           : 'Other', // from v2.1 added 20.6.2015
  5939.             'execute'         : 'Execute', // from v2.1 added 20.6.2015
  5940.             'perm'            : 'Permission', // from v2.1 added 20.6.2015
  5941.             'mode'            : 'Mode', // from v2.1 added 20.6.2015
  5942.  
  5943.             /********************************** mimetypes **********************************/
  5944.             'kindUnknown'     : 'Unknown',
  5945.             'kindFolder'      : 'Folder',
  5946.             'kindAlias'       : 'Alias',
  5947.             'kindAliasBroken' : 'Broken alias',
  5948.             // applications
  5949.             'kindApp'         : 'Application',
  5950.             'kindPostscript'  : 'Postscript document',
  5951.             'kindMsOffice'    : 'Microsoft Office document',
  5952.             'kindMsWord'      : 'Microsoft Word document',
  5953.             'kindMsExcel'     : 'Microsoft Excel document',
  5954.             'kindMsPP'        : 'Microsoft Powerpoint presentation',
  5955.             'kindOO'          : 'Open Office document',
  5956.             'kindAppFlash'    : 'Flash application',
  5957.             'kindPDF'         : 'Portable Document Format (PDF)',
  5958.             'kindTorrent'     : 'Bittorrent file',
  5959.             'kind7z'          : '7z archive',
  5960.             'kindTAR'         : 'TAR archive',
  5961.             'kindGZIP'        : 'GZIP archive',
  5962.             'kindBZIP'        : 'BZIP archive',
  5963.             'kindXZ'          : 'XZ archive',
  5964.             'kindZIP'         : 'ZIP archive',
  5965.             'kindRAR'         : 'RAR archive',
  5966.             'kindJAR'         : 'Java JAR file',
  5967.             'kindTTF'         : 'True Type font',
  5968.             'kindOTF'         : 'Open Type font',
  5969.             'kindRPM'         : 'RPM package',
  5970.             // texts
  5971.             'kindText'        : 'Text document',
  5972.             'kindTextPlain'   : 'Plain text',
  5973.             'kindPHP'         : 'PHP source',
  5974.             'kindCSS'         : 'Cascading style sheet',
  5975.             'kindHTML'        : 'HTML document',
  5976.             'kindJS'          : 'Javascript source',
  5977.             'kindRTF'         : 'Rich Text Format',
  5978.             'kindC'           : 'C source',
  5979.             'kindCHeader'     : 'C header source',
  5980.             'kindCPP'         : 'C++ source',
  5981.             'kindCPPHeader'   : 'C++ header source',
  5982.             'kindShell'       : 'Unix shell script',
  5983.             'kindPython'      : 'Python source',
  5984.             'kindJava'        : 'Java source',
  5985.             'kindRuby'        : 'Ruby source',
  5986.             'kindPerl'        : 'Perl script',
  5987.             'kindSQL'         : 'SQL source',
  5988.             'kindXML'         : 'XML document',
  5989.             'kindAWK'         : 'AWK source',
  5990.             'kindCSV'         : 'Comma separated values',
  5991.             'kindDOCBOOK'     : 'Docbook XML document',
  5992.             'kindMarkdown'    : 'Markdown text', // added 20.7.2015
  5993.             // images
  5994.             'kindImage'       : 'Image',
  5995.             'kindBMP'         : 'BMP image',
  5996.             'kindJPEG'        : 'JPEG image',
  5997.             'kindGIF'         : 'GIF Image',
  5998.             'kindPNG'         : 'PNG Image',
  5999.             'kindTIFF'        : 'TIFF image',
  6000.             'kindTGA'         : 'TGA image',
  6001.             'kindPSD'         : 'Adobe Photoshop image',
  6002.             'kindXBITMAP'     : 'X bitmap image',
  6003.             'kindPXM'         : 'Pixelmator image',
  6004.             // media
  6005.             'kindAudio'       : 'Audio media',
  6006.             'kindAudioMPEG'   : 'MPEG audio',
  6007.             'kindAudioMPEG4'  : 'MPEG-4 audio',
  6008.             'kindAudioMIDI'   : 'MIDI audio',
  6009.             'kindAudioOGG'    : 'Ogg Vorbis audio',
  6010.             'kindAudioWAV'    : 'WAV audio',
  6011.             'AudioPlaylist'   : 'MP3 playlist',
  6012.             'kindVideo'       : 'Video media',
  6013.             'kindVideoDV'     : 'DV movie',
  6014.             'kindVideoMPEG'   : 'MPEG movie',
  6015.             'kindVideoMPEG4'  : 'MPEG-4 movie',
  6016.             'kindVideoAVI'    : 'AVI movie',
  6017.             'kindVideoMOV'    : 'Quick Time movie',
  6018.             'kindVideoWM'     : 'Windows Media movie',
  6019.             'kindVideoFlash'  : 'Flash movie',
  6020.             'kindVideoMKV'    : 'Matroska movie',
  6021.             'kindVideoOGG'    : 'Ogg movie',
  6022.             'cmdsetpass'         : 'Set password'
  6023.         }
  6024.     };
  6025. }
  6026.  
  6027.  
  6028.  
  6029. /*
  6030.  * File: /js/ui/button.js
  6031.  */
  6032.  
  6033. /**
  6034.  * @class  elFinder toolbar button widget.
  6035.  * If command has variants - create menu
  6036.  *
  6037.  * @author Dmitry (dio) Levashov
  6038.  **/
  6039. $.fn.elfinderbutton = function(cmd) {
  6040.     return this.each(function() {
  6041.        
  6042.         var c        = 'class',
  6043.             fm       = cmd.fm,
  6044.             disabled = fm.res(c, 'disabled'),
  6045.             active   = fm.res(c, 'active'),
  6046.             hover    = fm.res(c, 'hover'),
  6047.             item     = 'elfinder-button-menu-item',
  6048.             selected = 'elfinder-button-menu-item-selected',
  6049.             menu,
  6050.             button   = $(this).addClass('ui-state-default elfinder-button')
  6051.                 .attr('title', cmd.title)
  6052.                 .append('<span class="elfinder-button-icon elfinder-button-icon-'+cmd.name+'"/>')
  6053.                 .hover(function(e) { !button.hasClass(disabled) && button[e.type == 'mouseleave' ? 'removeClass' : 'addClass'](hover) /**button.toggleClass(hover);*/ })
  6054.                 .click(function(e) {
  6055.                     if (!button.hasClass(disabled)) {
  6056.                         if (menu && cmd.variants.length > 1) {
  6057.                             // close other menus
  6058.                             menu.is(':hidden') && cmd.fm.getUI().click();
  6059.                             e.stopPropagation();
  6060.                             menu.slideToggle(100);
  6061.                         } else {
  6062.                             cmd.exec();
  6063.                         }
  6064.                        
  6065.                     }
  6066.                 }),
  6067.             hideMenu = function() {
  6068.                 menu.hide();
  6069.             };
  6070.            
  6071.         // if command has variants create menu
  6072.         if ($.isArray(cmd.variants)) {
  6073.             button.addClass('elfinder-menubutton');
  6074.            
  6075.             menu = $('<div class="ui-widget ui-widget-content elfinder-button-menu ui-corner-all"/>')
  6076.                 .hide()
  6077.                 .appendTo(button)
  6078.                 .zIndex(12+button.zIndex())
  6079.                 .on('mouseenter mouseleave', '.'+item, function() { $(this).toggleClass(hover) })
  6080.                 .on('click', '.'+item, function(e) {
  6081.                     e.preventDefault();
  6082.                     e.stopPropagation();
  6083.                     button.removeClass(hover);
  6084.                     menu.hide();
  6085.                     cmd.exec(cmd.fm.selected(), $(this).data('value'));
  6086.                 });
  6087.  
  6088.             cmd.fm.bind('disable select', hideMenu).getUI().click(hideMenu);
  6089.            
  6090.             cmd.change(function() {
  6091.                 menu.html('');
  6092.                 $.each(cmd.variants, function(i, variant) {
  6093.                     menu.append($('<div class="'+item+'">'+variant[1]+'</div>').data('value', variant[0]).addClass(variant[0] == cmd.value ? selected : ''));
  6094.                 });
  6095.             });
  6096.         }  
  6097.            
  6098.         cmd.change(function() {
  6099.             if (cmd.disabled()) {
  6100.                 button.removeClass(active+' '+hover).addClass(disabled);
  6101.             } else {
  6102.                 button.removeClass(disabled);
  6103.                 button[cmd.active() ? 'addClass' : 'removeClass'](active);
  6104.             }
  6105.         })
  6106.         .change();
  6107.     });
  6108. }
  6109.  
  6110.  
  6111. /*
  6112.  * File: /js/ui/contextmenu.js
  6113.  */
  6114.  
  6115. /**
  6116.  * @class  elFinder contextmenu
  6117.  *
  6118.  * @author Dmitry (dio) Levashov
  6119.  **/
  6120. $.fn.elfindercontextmenu = function(fm) {
  6121.    
  6122.     return this.each(function() {
  6123.         var cmItem = 'elfinder-contextmenu-item',
  6124.             smItem = 'elfinder-contextsubmenu-item',
  6125.             menu = $(this).addClass('ui-helper-reset ui-widget ui-state-default ui-corner-all elfinder-contextmenu elfinder-contextmenu-'+fm.direction)
  6126.                 .hide()
  6127.                 .appendTo('body')
  6128.                 .on('mouseenter mouseleave', '.'+cmItem, function() {
  6129.                     $(this).toggleClass('ui-state-hover')
  6130.                 }),
  6131.             subpos  = fm.direction == 'ltr' ? 'left' : 'right',
  6132.             types = $.extend({}, fm.options.contextmenu),
  6133.             clItem = cmItem + (fm.UA.Touch ? ' elfinder-touch' : ''),
  6134.             tpl     = '<div class="'+clItem+'"><span class="elfinder-button-icon {icon} elfinder-contextmenu-icon"/><span>{label}</span></div>',
  6135.             item = function(label, icon, callback) {
  6136.                 return $(tpl.replace('{icon}', icon ? 'elfinder-button-icon-'+icon : '').replace('{label}', label))
  6137.                     .click(function(e) {
  6138.                         e.stopPropagation();
  6139.                         e.preventDefault();
  6140.                         callback();
  6141.                     })
  6142.             },
  6143.            
  6144.             open = function(x, y) {
  6145.                 var win        = $(window),
  6146.                     width      = menu.outerWidth(),
  6147.                     height     = menu.outerHeight(),
  6148.                     wwidth     = win.width(),
  6149.                     wheight    = win.height(),
  6150.                     scrolltop  = win.scrollTop(),
  6151.                     scrollleft = win.scrollLeft(),
  6152.                     m          = fm.UA.Touch? 10 : 0,
  6153.                     css        = {
  6154.                         top  : (y + m + height < wheight ? y + m : y - m - height > 0 ? y - m - height : y + m) + scrolltop,
  6155.                         left : (x + m + width  < wwidth  ? x + m : x - m - width) + scrollleft,
  6156.                         'z-index' : 100 + fm.getUI('workzone').zIndex()
  6157.                     };
  6158.  
  6159.                 menu.css(css)
  6160.                     .show();
  6161.                
  6162.                 css = {'z-index' : css['z-index']+10};
  6163.                 css[subpos] = parseInt(menu.width());
  6164.                 menu.find('.elfinder-contextmenu-sub').css(css);
  6165.             },
  6166.            
  6167.             close = function() {
  6168.                 menu.hide().empty();
  6169.             },
  6170.            
  6171.             create = function(type, targets) {
  6172.                 var sep = false,
  6173.                 cmdMap = {}, disabled = [], isCwd = (targets[0].indexOf(fm.cwd().volumeid, 0) === 0),
  6174.                 self = fm.getUI('contextmenu');
  6175.  
  6176.                 if (self.data('cmdMaps')) {
  6177.                     $.each(self.data('cmdMaps'), function(i, v){
  6178.                         if (targets[0].indexOf(i, 0) == 0) {
  6179.                             cmdMap = v;
  6180.                             return false;
  6181.                         }
  6182.                     });
  6183.                 }
  6184.                 if (!isCwd) {
  6185.                     if (fm.disabledCmds) {
  6186.                         $.each(fm.disabledCmds, function(i, v){
  6187.                             if (targets[0].indexOf(i, 0) == 0) {
  6188.                                 disabled = v;
  6189.                                 return false;
  6190.                             }
  6191.                         });
  6192.                     }
  6193.                 }
  6194.                
  6195.                 $.each(types[type]||[], function(i, name) {
  6196.                     var cmd, node, submenu, hover, _disabled;
  6197.                    
  6198.                     if (name == '|' && sep) {
  6199.                         menu.append('<div class="elfinder-contextmenu-separator"/>');
  6200.                         sep = false;
  6201.                         return;
  6202.                     }
  6203.                    
  6204.                     if (cmdMap[name]) {
  6205.                         name = cmdMap[name];
  6206.                     }
  6207.                     cmd = fm.command(name);
  6208.  
  6209.                     if (cmd && !isCwd) {
  6210.                         _disabled = cmd._disabled;
  6211.                         cmd._disabled = !(cmd.alwaysEnabled || (fm._commands[name] ? $.inArray(name, disabled) === -1 : false));
  6212.                     }
  6213.  
  6214.                     if (cmd && cmd.getstate(targets) != -1) {
  6215.                         targets._type = type;
  6216.                         if (cmd.variants) {
  6217.                             if (!cmd.variants.length) {
  6218.                                 return;
  6219.                             }
  6220.                             node = item(cmd.title, cmd.name, function(){})
  6221.                             .on('touchend', function(e){
  6222.                                 node.data('touching', true);
  6223.                                 setTimeout(function(){node.data('touching', false);}, 50);
  6224.                             })
  6225.                             .on('click touchend', '.'+smItem, function(e){
  6226.                                 e.stopPropagation();
  6227.                                 if (node.data('touching')) {
  6228.                                     node.data('touching', false);
  6229.                                     $(this).removeClass('ui-state-hover');
  6230.                                     e.preventDefault();
  6231.                                 } else if (e.type == 'click') {
  6232.                                     menu.hide();
  6233.                                     cmd.exec(targets, $(this).data('exec'));
  6234.                                 }
  6235.                             });
  6236.                            
  6237.                             submenu = $('<div class="ui-corner-all elfinder-contextmenu-sub"/>')
  6238.                                 .appendTo(node.append('<span class="elfinder-contextmenu-arrow"/>'));
  6239.                            
  6240.                             hover = function(){
  6241.                                     var win    = $(window),
  6242.                                     baseleft   = $(node).offset().left,
  6243.                                     basetop    = $(node).offset().top,
  6244.                                     basewidth  = $(node).outerWidth(),
  6245.                                     width      = submenu.outerWidth(),
  6246.                                     height     = submenu.outerHeight(),
  6247.                                     wwidth     = win.scrollLeft() + win.width(),
  6248.                                     wheight    = win.scrollTop() + win.height(),
  6249.                                     margin     = 5, x, y, over;
  6250.  
  6251.                                     over = (baseleft + basewidth + width + margin) - wwidth;
  6252.                                     x = (over > 0)? basewidth - over : basewidth;
  6253.                                     over = (basetop + 5 + height + margin) - wheight;
  6254.                                     y = (over > 0)? 5 - over : 5;
  6255.  
  6256.                                     var css = {
  6257.                                         left : x,
  6258.                                         top : y
  6259.                                     };
  6260.                                     submenu.css(css).toggle();
  6261.                             };
  6262.                            
  6263.                             node.addClass('elfinder-contextmenu-group').hover(function(){ hover(); });
  6264.                            
  6265.                             $.each(cmd.variants, function(i, variant) {
  6266.                                 submenu.append(
  6267.                                     $('<div class="'+clItem+' '+smItem+'"><span>'+variant[1]+'</span></div>').data('exec', variant[0])
  6268.                                 );
  6269.                             });
  6270.                                
  6271.                         } else {
  6272.                             node = item(cmd.title, cmd.name, function() {
  6273.                                 close();
  6274.                                 cmd.exec(targets);
  6275.                             })
  6276.                            
  6277.                         }
  6278.                        
  6279.                         menu.append(node)
  6280.                         sep = true;
  6281.                     }
  6282.                    
  6283.                     cmd && !isCwd && (cmd._disabled = _disabled);
  6284.                 });
  6285.             },
  6286.            
  6287.             createFromRaw = function(raw) {
  6288.                 $.each(raw, function(i, data) {
  6289.                     var node;
  6290.                    
  6291.                     if (data.label && typeof data.callback == 'function') {
  6292.                         node = item(data.label, data.icon, function() {
  6293.                             close();
  6294.                             data.callback();
  6295.                         });
  6296.                         menu.append(node);
  6297.                     }
  6298.                 })
  6299.             };
  6300.        
  6301.         fm.one('load', function() {
  6302.             fm.bind('contextmenu', function(e) {
  6303.                 var data = e.data;
  6304.  
  6305.                 close();
  6306.  
  6307.                 if (data.type && data.targets) {
  6308.                     create(data.type, data.targets);
  6309.                 } else if (data.raw) {
  6310.                     createFromRaw(data.raw);
  6311.                 }
  6312.  
  6313.                 menu.children().length && open(data.x, data.y);
  6314.             })
  6315.             .one('destroy', function() { menu.remove(); })
  6316.             .bind('disable select', close)
  6317.             .getUI().click(close);
  6318.         });
  6319.        
  6320.     });
  6321.    
  6322. }
  6323.  
  6324.  
  6325. /*
  6326.  * File: /js/ui/cwd.js
  6327.  */
  6328.  
  6329. /**
  6330.  * elFinder current working directory ui.
  6331.  *
  6332.  * @author Dmitry (dio) Levashov
  6333.  **/
  6334. $.fn.elfindercwd = function(fm, options) {
  6335.    
  6336.     this.not('.elfinder-cwd').each(function() {
  6337.         // fm.time('cwdLoad');
  6338.        
  6339.         var mobile = fm.UA.Mobile,
  6340.             list = fm.viewType == 'list',
  6341.  
  6342.             undef = 'undefined',
  6343.             /**
  6344.              * Select event full name
  6345.              *
  6346.              * @type String
  6347.              **/
  6348.             evtSelect = 'select.'+fm.namespace,
  6349.            
  6350.             /**
  6351.              * Unselect event full name
  6352.              *
  6353.              * @type String
  6354.              **/
  6355.             evtUnselect = 'unselect.'+fm.namespace,
  6356.            
  6357.             /**
  6358.              * Disable event full name
  6359.              *
  6360.              * @type String
  6361.              **/
  6362.             evtDisable = 'disable.'+fm.namespace,
  6363.            
  6364.             /**
  6365.              * Disable event full name
  6366.              *
  6367.              * @type String
  6368.              **/
  6369.             evtEnable = 'enable.'+fm.namespace,
  6370.            
  6371.             c = 'class',
  6372.             /**
  6373.              * File css class
  6374.              *
  6375.              * @type String
  6376.              **/
  6377.             clFile       = fm.res(c, 'cwdfile'),
  6378.            
  6379.             /**
  6380.              * Selected css class
  6381.              *
  6382.              * @type String
  6383.              **/
  6384.             fileSelector = '.'+clFile,
  6385.            
  6386.             /**
  6387.              * Selected css class
  6388.              *
  6389.              * @type String
  6390.              **/
  6391.             clSelected = 'ui-selected',
  6392.            
  6393.             /**
  6394.              * Disabled css class
  6395.              *
  6396.              * @type String
  6397.              **/
  6398.             clDisabled = fm.res(c, 'disabled'),
  6399.            
  6400.             /**
  6401.              * Draggable css class
  6402.              *
  6403.              * @type String
  6404.              **/
  6405.             clDraggable = fm.res(c, 'draggable'),
  6406.            
  6407.             /**
  6408.              * Droppable css class
  6409.              *
  6410.              * @type String
  6411.              **/
  6412.             clDroppable = fm.res(c, 'droppable'),
  6413.            
  6414.             /**
  6415.              * Hover css class
  6416.              *
  6417.              * @type String
  6418.              **/
  6419.             clHover     = fm.res(c, 'hover'),
  6420.  
  6421.             /**
  6422.              * Hover css class
  6423.              *
  6424.              * @type String
  6425.              **/
  6426.             clDropActive = fm.res(c, 'adroppable'),
  6427.  
  6428.             /**
  6429.              * Css class for temporary nodes (for mkdir/mkfile) commands
  6430.              *
  6431.              * @type String
  6432.              **/
  6433.             clTmp = clFile+'-tmp',
  6434.  
  6435.             /**
  6436.              * Number of thumbnails to load in one request (new api only)
  6437.              *
  6438.              * @type Number
  6439.              **/
  6440.             tmbNum = fm.options.loadTmbs > 0 ? fm.options.loadTmbs : 5,
  6441.            
  6442.             /**
  6443.              * Current search query.
  6444.              *
  6445.              * @type String
  6446.              */
  6447.             query = '',
  6448.            
  6449.             lastSearch = [],
  6450.  
  6451.             customColsBuild = function() {
  6452.                 var customCols = '';
  6453.                 var columns = fm.options.uiOptions.cwd.listView.columns;
  6454.                 for (var i = 0; i < columns.length; i++) {
  6455.                     customCols += '<td>{' + columns[i] + '}</td>';
  6456.                 }
  6457.                 return customCols;
  6458.             },
  6459.  
  6460.             /**
  6461.              * File templates
  6462.              *
  6463.              * @type Object
  6464.              **/
  6465.             templates = {
  6466.                 icon : '<div id="{hash}" class="'+clFile+(fm.UA.Touch ? ' '+'elfinder-touch' : '')+' {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></div>',
  6467.                 row  : '<tr id="{hash}" class="'+clFile+(fm.UA.Touch ? ' '+'elfinder-touch' : '')+' {permsclass} {dirclass}" title="{tooltip}"><td><div class="elfinder-cwd-file-wrapper"><span class="elfinder-cwd-icon {mime}"/>{marker}<span class="elfinder-cwd-filename">{name}</span></div></td>'+customColsBuild()+'</tr>',
  6468.             },
  6469.            
  6470.             permsTpl = fm.res('tpl', 'perms'),
  6471.            
  6472.             lockTpl = fm.res('tpl', 'lock'),
  6473.            
  6474.             symlinkTpl = fm.res('tpl', 'symlink'),
  6475.            
  6476.             /**
  6477.              * Template placeholders replacement rules
  6478.              *
  6479.              * @type Object
  6480.              **/
  6481.             replacement = {
  6482.                 name : function(f) {
  6483.                     name = fm.escape(f.name);
  6484.                     !list && (name = name.replace(/([_.])/g, '&#8203;$1'));
  6485.                     return name;
  6486.                 },
  6487.                 nametitle : function(f) {
  6488.                     return fm.escape(f.name);
  6489.                 },
  6490.                 permsclass : function(f) {
  6491.                     return fm.perms2class(f);
  6492.                 },
  6493.                 perm : function(f) {
  6494.                     return fm.formatPermissions(f);
  6495.                 },
  6496.                 dirclass : function(f) {
  6497.                     return f.mime == 'directory' ? 'directory' : '';
  6498.                 },
  6499.                 mime : function(f) {
  6500.                     return fm.mime2class(f.mime);
  6501.                 },
  6502.                 size : function(f) {
  6503.                     return fm.formatSize(f.size);
  6504.                 },
  6505.                 date : function(f) {
  6506.                     return fm.formatDate(f);
  6507.                 },
  6508.                 kind : function(f) {
  6509.                     return fm.mime2kind(f);
  6510.                 },
  6511.                 mode : function(f) {
  6512.                     return f.perm? fm.formatFileMode(f.perm) : '';
  6513.                 },
  6514.                 modestr : function(f) {
  6515.                     return f.perm? fm.formatFileMode(f.perm, 'string') : '';
  6516.                 },
  6517.                 modeoct : function(f) {
  6518.                     return f.perm? fm.formatFileMode(f.perm, 'octal') : '';
  6519.                 },
  6520.                 modeboth : function(f) {
  6521.                     return f.perm? fm.formatFileMode(f.perm, 'both') : '';
  6522.                 },
  6523.                 marker : function(f) {
  6524.                     return (f.alias || f.mime == 'symlink-broken' ? symlinkTpl : '')+(!f.read || !f.write ? permsTpl : '')+(f.locked ? lockTpl : '');
  6525.                 },
  6526.                 tooltip : function(f) {
  6527.                     var title = fm.formatDate(f) + (f.size > 0 ? ' ('+fm.formatSize(f.size)+')' : '');
  6528.                     return f.tooltip? fm.escape(f.tooltip).replace(/\r/g, '&#13;') + '&#13;' + title : title;
  6529.                 }
  6530.             },
  6531.            
  6532.             /**
  6533.              * Return file html
  6534.              *
  6535.              * @param  Object  file info
  6536.              * @return String
  6537.              **/
  6538.             itemhtml = function(f) {
  6539.                 return templates[list ? 'row' : 'icon']
  6540.                         .replace(/\{([a-z]+)\}/g, function(s, e) {
  6541.                             return replacement[e] ? replacement[e](f) : (f[e] ? f[e] : '');
  6542.                         });
  6543.             },
  6544.            
  6545.             /**
  6546.              * Flag. Required for msie to avoid unselect files on dragstart
  6547.              *
  6548.              * @type Boolean
  6549.              **/
  6550.             selectLock = false,
  6551.            
  6552.             /**
  6553.              * Move selection to prev/next file
  6554.              *
  6555.              * @param String  move direction
  6556.              * @param Boolean append to current selection
  6557.              * @return void
  6558.              * @rise select        
  6559.              */
  6560.             select = function(keyCode, append) {
  6561.                 var code     = $.ui.keyCode,
  6562.                     prev     = keyCode == code.LEFT || keyCode == code.UP,
  6563.                     sel      = cwd.find('[id].'+clSelected),
  6564.                     selector = prev ? 'first:' : 'last',
  6565.                     s, n, sib, top, left;
  6566.  
  6567.                 function sibling(n, direction) {
  6568.                     return n[direction+'All']('[id]:not(.'+clDisabled+'):not(.elfinder-cwd-parent):first');
  6569.                 }
  6570.                
  6571.                 if (sel.length) {
  6572.                     s = sel.filter(prev ? ':first' : ':last');
  6573.                     sib = sibling(s, prev ? 'prev' : 'next');
  6574.                    
  6575.                     if (!sib.length) {
  6576.                         // there is no sibling on required side - do not move selection
  6577.                         n = s;
  6578.                     } else if (list || keyCode == code.LEFT || keyCode == code.RIGHT) {
  6579.                         // find real prevoius file
  6580.                         n = sib;
  6581.                     } else {
  6582.                         // find up/down side file in icons view
  6583.                         top = s.position().top;
  6584.                         left = s.position().left;
  6585.  
  6586.                         n = s;
  6587.                         if (prev) {
  6588.                             do {
  6589.                                 n = n.prev('[id]');
  6590.                             } while (n.length && !(n.position().top < top && n.position().left <= left));
  6591.  
  6592.                             if (n.hasClass(clDisabled)) {
  6593.                                 n = sibling(n, 'next');
  6594.                             }
  6595.                         } else {
  6596.                             do {
  6597.                                 n = n.next('[id]');
  6598.                             } while (n.length && !(n.position().top > top && n.position().left >= left));
  6599.                            
  6600.                             if (n.hasClass(clDisabled)) {
  6601.                                 n = sibling(n, 'prev');
  6602.                             }
  6603.                             // there is row before last one - select last file
  6604.                             if (!n.length) {
  6605.                                 sib = cwd.find('[id]:not(.'+clDisabled+'):last');
  6606.                                 if (sib.position().top > top) {
  6607.                                     n = sib;
  6608.                                 }
  6609.                             }
  6610.                         }
  6611.                     }
  6612.                     // !append && unselectAll();
  6613.                 } else {
  6614.                     // there are no selected file - select first/last one
  6615.                     n = cwd.find('[id]:not(.'+clDisabled+'):not(.elfinder-cwd-parent):'+(prev ? 'last' : 'first'));
  6616.                 }
  6617.                
  6618.                 if (n && n.length && !n.hasClass('elfinder-cwd-parent')) {
  6619.                     if (append) {
  6620.                         // append new files to selected
  6621.                         n = s.add(s[prev ? 'prevUntil' : 'nextUntil']('#'+n.attr('id'))).add(n);
  6622.                     } else {
  6623.                         // unselect selected files
  6624.                         sel.trigger(evtUnselect);
  6625.                     }
  6626.                     // select file(s)
  6627.                     n.trigger(evtSelect);
  6628.                     // set its visible
  6629.                     scrollToView(n.filter(prev ? ':first' : ':last'));
  6630.                     // update cache/view
  6631.                     trigger();
  6632.                 }
  6633.             },
  6634.            
  6635.             selectedFiles = [],
  6636.            
  6637.             selectFile = function(hash) {
  6638.                 $('#'+hash).trigger(evtSelect);
  6639.             },
  6640.            
  6641.             selectAll = function() {
  6642.                 var phash = fm.cwd().hash;
  6643.  
  6644.                 cwd.find('[id]:not(.'+clSelected+'):not(.elfinder-cwd-parent)').trigger(evtSelect);
  6645.                 selectedFiles = $.map(fm.files(), function(f) { return f.phash == phash ? f.hash : null ;});
  6646.                 trigger();
  6647.             },
  6648.            
  6649.             /**
  6650.              * Unselect all files
  6651.              *
  6652.              * @return void
  6653.              */
  6654.             unselectAll = function() {
  6655.                 selectedFiles = [];
  6656.                 cwd.find('[id].'+clSelected).trigger(evtUnselect);
  6657.                 trigger();
  6658.             },
  6659.            
  6660.             /**
  6661.              * Return selected files hashes list
  6662.              *
  6663.              * @return Array
  6664.              */
  6665.             selected = function() {
  6666.                 return selectedFiles;
  6667.             },
  6668.            
  6669.             /**
  6670.              * Fire elfinder "select" event and pass selected files to it
  6671.              *
  6672.              * @return void
  6673.              */
  6674.             trigger = function() {
  6675.                 fm.trigger('select', {selected : selectedFiles});
  6676.             },
  6677.            
  6678.             /**
  6679.              * Scroll file to set it visible
  6680.              *
  6681.              * @param DOMElement  file/dir node
  6682.              * @return void
  6683.              */
  6684.             scrollToView = function(o) {
  6685.                 var ftop    = o.position().top,
  6686.                     fheight = o.outerHeight(true),
  6687.                     wtop    = wrapper.scrollTop(),
  6688.                     wheight = wrapper.innerHeight();
  6689.  
  6690.                 if (ftop + fheight > wtop + wheight) {
  6691.                     wrapper.scrollTop(parseInt(ftop + fheight - wheight));
  6692.                 } else if (ftop < wtop) {
  6693.                     wrapper.scrollTop(ftop);
  6694.                 }
  6695.             },
  6696.            
  6697.             /**
  6698.              * Files we get from server but not show yet
  6699.              *
  6700.              * @type Array
  6701.              **/
  6702.             buffer = [],
  6703.            
  6704.             /**
  6705.              * Return index of elements with required hash in buffer
  6706.              *
  6707.              * @param String  file hash
  6708.              * @return Number
  6709.              */
  6710.             index = function(hash) {
  6711.                 var l = buffer.length;
  6712.                
  6713.                 while (l--) {
  6714.                     if (buffer[l].hash == hash) {
  6715.                         return l;
  6716.                     }
  6717.                 }
  6718.                 return -1;
  6719.             },
  6720.            
  6721.             /**
  6722.              * Scroll event name
  6723.              *
  6724.              * @type String
  6725.              **/
  6726.             scrollEvent = 'scroll.'+fm.namespace,
  6727.  
  6728.             /**
  6729.              * Cwd scroll event handler.
  6730.              * Lazy load - append to cwd not shown files
  6731.              *
  6732.              * @return void
  6733.              */
  6734.             render = function() {
  6735.                 var go = function(){
  6736.                     var html  = [],
  6737.                         dirs  = false,
  6738.                         ltmb  = [],
  6739.                         atmb  = {},
  6740.                         last  = buffer._last || cwd.find('[id]:last'),
  6741.                         top   = !last.length,
  6742.                         place = buffer._place || (list ? cwd.children('table').children('tbody') : cwd),
  6743.                         chk, files;
  6744.  
  6745.                     // check draging scroll bar
  6746.                     top && (wrapper._top = 0);
  6747.                     if (!!wrapper._mousedown && wrapper._top != wrapper.scrollTop()) {
  6748.                         wrapper._top = wrapper.scrollTop();
  6749.                         setTimeout(function(){
  6750.                             go();
  6751.                         }, 50);
  6752.                         return;
  6753.                     }
  6754.                    
  6755.                     delete buffer._timer;
  6756.  
  6757.                     if (!buffer.length) {
  6758.                         bottomMarker.hide();
  6759.                         return wrapper.off(scrollEvent);
  6760.                     }
  6761.  
  6762.                     //progress.show();
  6763.                     while ((!last.length || (chk = last.position().top - (wrapper.height() + wrapper.scrollTop() + fm.options.showThreshold)) <= 0)
  6764.                         && (files = buffer.splice(0, fm.options.showFiles - (chk || 0) / (buffer._hpi || 1))).length) {
  6765.  
  6766.                         html = $.map(files, function(f) {
  6767.                             if (f.hash && f.name) {
  6768.                                 if (f.mime == 'directory') {
  6769.                                     dirs = true;
  6770.                                 }
  6771.                                 if (f.tmb) {
  6772.                                     f.tmb === 1 ? ltmb.push(f.hash) : (atmb[f.hash] = f.tmb);
  6773.                                 }
  6774.                                 return itemhtml(f);
  6775.                             }
  6776.                             return null;
  6777.                         });
  6778.  
  6779.                         (top || !buffer.length) && bottomMarker.hide();
  6780.                         place.append(html.join(''));
  6781.                         last = cwd.find('[id]:last');
  6782.                         // scroll top on dir load to avoid scroll after page reload
  6783.                         top && wrapper.scrollTop(0);
  6784.                         (top || !buffer._hpi) && bottomMarkerShow(place, files.length);
  6785.                         if (top) { break; }
  6786.                     }
  6787.  
  6788.                     // cache last
  6789.                     buffer._last = last;
  6790.  
  6791.                     // load/attach thumbnails
  6792.                     attachThumbnails(atmb);
  6793.                     ltmb.length && loadThumbnails(ltmb);
  6794.  
  6795.                     // make directory droppable
  6796.                     dirs && !mobile && makeDroppable();
  6797.                    
  6798.                     if (selectedFiles.length) {
  6799.                         place.find('[id]:not(.'+clSelected+'):not(.elfinder-cwd-parent)').each(function() {
  6800.                             var id = this.id;
  6801.                            
  6802.                             $.inArray(id, selectedFiles) !== -1 && $(this).trigger(evtSelect);
  6803.                         });
  6804.                     }
  6805.                 };
  6806.                
  6807.                 // stop while scrolling
  6808.                 buffer._timer && clearTimeout(buffer._timer);
  6809.                 // first time to go()
  6810.                 !buffer._timer && go();
  6811.                 // regist next go()
  6812.                 buffer._timer = setTimeout(function(){
  6813.                     go();
  6814.                 }, 100);
  6815.             },
  6816.            
  6817.             /**
  6818.              * Droppable options for cwd.
  6819.              * Drop target is `wrapper`
  6820.              * Do not add class on childs file over
  6821.              *
  6822.              * @type Object
  6823.              */
  6824.             droppable = $.extend({}, fm.droppable, {
  6825.                 over : function(e, ui) {
  6826.                     var cwd   = fm.cwd(),
  6827.                         hash  = cwd.hash,
  6828.                         $this = $(this);
  6829.                     $.each(ui.helper.data('files'), function(i, h) {
  6830.                         if (h === hash || fm.file(h).phash === hash) {
  6831.                             if (h !== hash && cwd.write) {
  6832.                                 $this.data('dropover', true);
  6833.                             }
  6834.                             if (!$this.data('dropover') || !ui.helper.hasClass('elfinder-drag-helper-plus')) {
  6835.                                 $this.removeClass(clDropActive);
  6836.                             }
  6837.                             return false;
  6838.                         }
  6839.                     });
  6840.                 },
  6841.                 out : function() {
  6842.                     $(this).removeData('dropover')
  6843.                            .removeClass(clDropActive);
  6844.                 },
  6845.                 deactivate : function() {
  6846.                     $(this).removeData('dropover')
  6847.                            .removeClass(clDropActive);
  6848.                 }
  6849.             }),
  6850.            
  6851.             /**
  6852.              * Make directory droppable
  6853.              *
  6854.              * @return void
  6855.              */
  6856.             makeDroppable = function() {
  6857.                 if (fm.isCommandEnabled('paste')) {
  6858.                     setTimeout(function() {
  6859.                         cwd.find('.directory:not(.'+clDroppable+',.elfinder-na,.elfinder-ro)').droppable(fm.droppable).each(function(){
  6860.                             fm.makeDirectDropUpload(this, this.id);
  6861.                         });
  6862.                     }, 20);
  6863.                 }
  6864.             },
  6865.            
  6866.             /**
  6867.              * Preload required thumbnails and on load add css to files.
  6868.              * Return false if required file is not visible yet (in buffer) -
  6869.              * required for old api to stop loading thumbnails.
  6870.              *
  6871.              * @param  Object  file hash -> thumbnail map
  6872.              * @return Boolean
  6873.              */
  6874.             attachThumbnails = function(images) {
  6875.                 var url = fm.option('tmbUrl'),
  6876.                     ret = true,
  6877.                     ndx;
  6878.                
  6879.                 $.each(images, function(hash, tmb) {
  6880.                     var node = $('#'+hash);
  6881.  
  6882.                     if (node.length) {
  6883.  
  6884.                         (function(node, tmb) {
  6885.                             $('<img/>')
  6886.                                 .load(function() { node.find('.elfinder-cwd-icon').css('background', "url('"+tmb+"') center center no-repeat"); })
  6887.                                 .attr('src', tmb);
  6888.                         })(node, url+tmb);
  6889.                     } else {
  6890.                         ret = false;
  6891.                         if ((ndx = index(hash)) != -1) {
  6892.                             buffer[ndx].tmb = tmb;
  6893.                         }
  6894.                     }
  6895.                 });
  6896.                 return ret;
  6897.             },
  6898.            
  6899.             /**
  6900.              * Load thumbnails from backend.
  6901.              *
  6902.              * @param  Array|Boolean  files hashes list for new api | true for old api
  6903.              * @return void
  6904.              */
  6905.             loadThumbnails = function(files) {
  6906.                 var tmbs = [];
  6907.                
  6908.                 if (fm.oldAPI) {
  6909.                     fm.request({
  6910.                         data : {cmd : 'tmb', current : fm.cwd().hash},
  6911.                         preventFail : true
  6912.                         })
  6913.                         .done(function(data) {
  6914.                             if (attachThumbnails(data.images||[]) && data.tmb) {
  6915.                                 loadThumbnails();
  6916.                             }
  6917.                         });
  6918.                     return;
  6919.                 }
  6920.  
  6921.                 tmbs = tmbs = files.splice(0, tmbNum);
  6922.                 if (tmbs.length) {
  6923.                     fm.request({
  6924.                         data : {cmd : 'tmb', targets : tmbs},
  6925.                         preventFail : true
  6926.                     })
  6927.                     .done(function(data) {
  6928.                         if (attachThumbnails(data.images||[])) {
  6929.                             loadThumbnails(files);
  6930.                         }
  6931.                     });
  6932.                 }
  6933.             },
  6934.            
  6935.             /**
  6936.              * Add new files to cwd/buffer
  6937.              *
  6938.              * @param  Array  new files
  6939.              * @return void
  6940.              */
  6941.             add = function(files) {
  6942.                 var place    = list ? cwd.find('tbody') : cwd,
  6943.                     l        = files.length,
  6944.                     ltmb     = [],
  6945.                     atmb     = {},
  6946.                     dirs     = false,
  6947.                     findNode = function(file) {
  6948.                         var pointer = cwd.find('[id]:first'), file2;
  6949.  
  6950.                         while (pointer.length) {
  6951.                             file2 = fm.file(pointer.attr('id'));
  6952.                             if (!pointer.hasClass('elfinder-cwd-parent') && file2 && fm.compare(file, file2) < 0) {
  6953.                                 return pointer;
  6954.                             }
  6955.                             pointer = pointer.next('[id]');
  6956.                         }
  6957.                     },
  6958.                     findIndex = function(file) {
  6959.                         var l = buffer.length, i;
  6960.                        
  6961.                         for (i =0; i < l; i++) {
  6962.                             if (fm.compare(file, buffer[i]) < 0) {
  6963.                                 return i;
  6964.                             }
  6965.                         }
  6966.                         return l || -1;
  6967.                     },
  6968.                     file, hash, node, ndx;
  6969.  
  6970.                
  6971.                 while (l--) {
  6972.                     file = files[l];
  6973.                     hash = file.hash;
  6974.                    
  6975.                     if ($('#'+hash).length) {
  6976.                         continue;
  6977.                     }
  6978.                    
  6979.                     if ((node = findNode(file)) && node.length) {
  6980.                         node.before(itemhtml(file));
  6981.                     } else if ((ndx = findIndex(file)) >= 0) {
  6982.                         buffer.splice(ndx, 0, file);
  6983.                     } else {
  6984.                         place.append(itemhtml(file));
  6985.                     }
  6986.                    
  6987.                     if ($('#'+hash).length) {
  6988.                         if (file.mime == 'directory') {
  6989.                             dirs = true;
  6990.                         } else if (file.tmb) {
  6991.                             file.tmb === 1 ? ltmb.push(hash) : (atmb[hash] = file.tmb);
  6992.                         }
  6993.                     }
  6994.                 }
  6995.                
  6996.                 bottomMarkerShow(place);
  6997.                 attachThumbnails(atmb);
  6998.                 ltmb.length && loadThumbnails(ltmb);
  6999.                 dirs && !mobile && makeDroppable();
  7000.             },
  7001.            
  7002.             /**
  7003.              * Remove files from cwd/buffer
  7004.              *
  7005.              * @param  Array  files hashes
  7006.              * @return void
  7007.              */
  7008.             remove = function(files) {
  7009.                 var l = files.length, hash, n, ndx;
  7010.                
  7011.                 while (l--) {
  7012.                     hash = files[l];
  7013.                     if ((n = $('#'+hash)).length) {
  7014.                         try {
  7015.                             n.remove();
  7016.                         } catch(e) {
  7017.                             fm.debug('error', e);
  7018.                         }
  7019.                     } else if ((ndx = index(hash)) != -1) {
  7020.                         buffer.splice(ndx, 1);
  7021.                     }
  7022.                 }
  7023.                
  7024.                 // refresh cwd if empty for a bug of browser (ex. Android Chrome 43.0.2357.93)
  7025.                 if (cwd.children().length < 1) {
  7026.                     cwd.hide();
  7027.                     setTimeout(function(){ cwd.show(); }, 0);
  7028.                 }
  7029.             },
  7030.            
  7031.             msg = {
  7032.                 name : fm.i18n('name'),
  7033.                 perm : fm.i18n('perms'),
  7034.                 date : fm.i18n('modify'),
  7035.                 size : fm.i18n('size'),
  7036.                 kind : fm.i18n('kind'),
  7037.                 modestr : fm.i18n('mode'),
  7038.                 modeoct : fm.i18n('mode'),
  7039.                 modeboth : fm.i18n('mode'),
  7040.             },
  7041.            
  7042.             customColsNameBuild = function() {
  7043.                 var name = '',
  7044.                 customColsName = '',
  7045.                 columns = fm.options.uiOptions.cwd.listView.columns,
  7046.                 names = $.extend({}, msg, fm.options.uiOptions.cwd.listView.columnsCustomName);
  7047.                 for (var i = 0; i < columns.length; i++) {
  7048.                     if (typeof names[columns[i]] !== 'undefined') {
  7049.                         name = names[columns[i]];
  7050.                     } else {
  7051.                         name = fm.i18n(columns[i]);
  7052.                     }
  7053.                     customColsName +='<td class="elfinder-cwd-view-th-'+columns[i]+'">'+name+'</td>';
  7054.                 }
  7055.                 return customColsName;
  7056.             },
  7057.            
  7058.             bottomMarkerShow = function(place, cnt) {
  7059.                 var ph;
  7060.                 place = place || (list ? cwd.find('tbody') : cwd);
  7061.  
  7062.                 if (buffer.length > 0) {
  7063.                     place.css({height: 'auto'});
  7064.                     ph = place.height();
  7065.                     cnt && (buffer._hpi = ph / cnt);
  7066.                     bottomMarker.css({top: (buffer._hpi * buffer.length + ph) + 'px'}).show();
  7067.                 }
  7068.             },
  7069.            
  7070.             /**
  7071.              * Update directory content
  7072.              *
  7073.              * @param  Array  files
  7074.              * @return void
  7075.              */
  7076.             content = function(files, any) {
  7077.                 var phash = fm.cwd().hash;
  7078.                
  7079.                 unselectAll();
  7080.                
  7081.                 try {
  7082.                     // to avoid problem with draggable
  7083.                     cwd.empty();
  7084.                 } catch (e) {
  7085.                     cwd.html('');
  7086.                 }
  7087.  
  7088.                 cwd.removeClass('elfinder-cwd-view-icons elfinder-cwd-view-list')
  7089.                     .addClass('elfinder-cwd-view-'+(list ? 'list' :'icons'));
  7090.                 cwd.css('height', 'auto');
  7091.                 bottomMarker.hide();
  7092.  
  7093.                 wrapper[list ? 'addClass' : 'removeClass']('elfinder-cwd-wrapper-list');
  7094.                 wrapper._padding = parseInt(wrapper.css('padding-top')) + parseInt(wrapper.css('padding-bottom'));
  7095.  
  7096.                 list && cwd.html('<table><thead><tr class="ui-state-default'+(fm.UA.Touch? ' elfinder-touch' : '')+'"><td class="elfinder-cwd-view-th-name">'+msg.name+'</td>'+customColsNameBuild()+'</tr></thead><tbody/></table>');
  7097.        
  7098.                 buffer = $.map(files, function(f) { return any || f.phash == phash ? f : null; });
  7099.                
  7100.                 buffer = fm.sortFiles(buffer);
  7101.        
  7102.                 wrapper.on(scrollEvent, render).trigger(scrollEvent);
  7103.        
  7104.                 phash = fm.cwd().phash;
  7105.                
  7106.                 if (options.oldSchool && phash && !query) {
  7107.                     var parent = $.extend(true, {}, fm.file(phash), {name : '..', mime : 'directory'});
  7108.                     parent = $(itemhtml(parent))
  7109.                         .addClass('elfinder-cwd-parent')
  7110.                         .bind('mousedown click mouseup touchstart touchmove touchend dblclick mouseenter', function(e) {
  7111.                             e.preventDefault();
  7112.                             e.stopPropagation();
  7113.                         })
  7114.                         .dblclick(function() {
  7115.                             fm.exec('open', this.id);
  7116.                         });
  7117.  
  7118.                     (list ? cwd.find('tbody') : cwd).prepend(parent);
  7119.                 }
  7120.                
  7121.             },
  7122.            
  7123.             /**
  7124.              * CWD node itself
  7125.              *
  7126.              * @type JQuery
  7127.              **/
  7128.             cwd = $(this)
  7129.                 .addClass('ui-helper-clearfix elfinder-cwd')
  7130.                 .attr('unselectable', 'on')
  7131.                 // fix ui.selectable bugs and add shift+click support
  7132.                 .on('click.'+fm.namespace, fileSelector, function(e) {
  7133.                     var p    = this.id ? $(this) : $(this).parents('[id]:first'),
  7134.                         prev = p.prevAll('.'+clSelected+':first'),
  7135.                         next = p.nextAll('.'+clSelected+':first'),
  7136.                         pl   = prev.length,
  7137.                         nl   = next.length,
  7138.                         sib;
  7139.  
  7140.                     if (cwd.data('longtap')) {
  7141.                         e.stopPropagation();
  7142.                         return;
  7143.                     }
  7144.  
  7145.                     e.stopImmediatePropagation();
  7146.  
  7147.                     if (e.shiftKey && (pl || nl)) {
  7148.                         sib = pl ? p.prevUntil('#'+prev.attr('id')) : p.nextUntil('#'+next.attr('id'));
  7149.                         sib.add(p).trigger(evtSelect);
  7150.                     } else if (e.ctrlKey || e.metaKey) {
  7151.                         p.trigger(p.hasClass(clSelected) ? evtUnselect : evtSelect);
  7152.                     } else {
  7153.                         if (p.data('touching') && p.hasClass(clSelected)) {
  7154.                             p.data('touching', null);
  7155.                             fm.dblclick({file : this.id});
  7156.                             unselectAll();
  7157.                             return;
  7158.                         } else {
  7159.                             unselectAll();
  7160.                             p.trigger(evtSelect);
  7161.                         }
  7162.                     }
  7163.  
  7164.                     trigger();
  7165.                 })
  7166.                 // call fm.open()
  7167.                 .on('dblclick.'+fm.namespace, fileSelector, function(e) {
  7168.                     fm.dblclick({file : this.id});
  7169.                 })
  7170.                 // for touch device
  7171.                 .on('touchstart.'+fm.namespace, fileSelector, function(e) {
  7172.                     e.stopPropagation();
  7173.                     if (e.target.nodeName == 'INPUT' || e.target.nodeName == 'TEXTAREA') {
  7174.                         return;
  7175.                     }
  7176.                     var p = this.id ? $(this) : $(this).parents('[id]:first'),
  7177.                       sel = p.prevAll('.'+clSelected+':first').length +
  7178.                             p.nextAll('.'+clSelected+':first').length;
  7179.                     cwd.data('longtap', null);
  7180.                     p.addClass(clHover)
  7181.                     .data('touching', true)
  7182.                     .data('tmlongtap', setTimeout(function(){
  7183.                         // long tap
  7184.                         cwd.data('longtap', true);
  7185.                         if (p.hasClass(clSelected) && sel > 0) {
  7186.                             p.trigger(evtUnselect);
  7187.                             trigger();
  7188.                         } else {
  7189.                             if (e.target.nodeName != 'TD' || fm.selected().length > 0) {
  7190.                                 p.trigger(evtSelect);
  7191.                                 trigger();
  7192.                                 fm.trigger('contextmenu', {
  7193.                                     'type'    : 'files',
  7194.                                     'targets' : fm.selected(),
  7195.                                     'x'       : e.originalEvent.touches[0].clientX,
  7196.                                     'y'       : e.originalEvent.touches[0].clientY
  7197.                                 });
  7198.                             }
  7199.                         }
  7200.                     }, 500));
  7201.                 })
  7202.                 .on('touchmove.'+fm.namespace+' touchend.'+fm.namespace, fileSelector, function(e) {
  7203.                     e.stopPropagation();
  7204.                     if (e.target.nodeName == 'INPUT' || e.target.nodeName == 'TEXTAREA') {
  7205.                         return;
  7206.                     }
  7207.                     var p = this.id ? $(this) : $(this).parents('[id]:first');
  7208.                     clearTimeout(p.data('tmlongtap'));
  7209.                     if (e.type == 'touchmove') {
  7210.                         p.removeClass(clHover);
  7211.                     }
  7212.                 })
  7213.                 // attach draggable
  7214.                 .on('mouseenter.'+fm.namespace, fileSelector, function(e) {
  7215.                     var $this = $(this),
  7216.                         target = list ? $this : $this.children();
  7217.  
  7218.                     if (!mobile && !$this.hasClass(clTmp) && !target.hasClass(clDraggable+' '+clDisabled)) {
  7219.                         target.draggable(fm.draggable);
  7220.                     }
  7221.                 })
  7222.                 // add hover class to selected file
  7223.                 .on(evtSelect, fileSelector, function(e) {
  7224.                     var $this = $(this),
  7225.                         id    = $this.attr('id');
  7226.                    
  7227.                     if (!selectLock && !$this.hasClass(clDisabled)) {
  7228.                         $this.addClass(clSelected).children().addClass(clHover);
  7229.                         if ($.inArray(id, selectedFiles) === -1) {
  7230.                             selectedFiles.push(id);
  7231.                         }
  7232.                     }
  7233.                 })
  7234.                 // remove hover class from unselected file
  7235.                 .on(evtUnselect, fileSelector, function(e) {
  7236.                     var $this = $(this),
  7237.                         id    = $this.attr('id'),
  7238.                         ndx;
  7239.                    
  7240.                     if (!selectLock) {
  7241.                         $(this).removeClass(clSelected).children().removeClass(clHover);
  7242.                         ndx = $.inArray(id, selectedFiles);
  7243.                         if (ndx !== -1) {
  7244.                             selectedFiles.splice(ndx, 1);
  7245.                         }
  7246.                     }
  7247.                    
  7248.                 })
  7249.                 // disable files wich removing or moving
  7250.                 .on(evtDisable, fileSelector, function() {
  7251.                     var $this  = $(this).removeClass(clHover+' '+clSelected).addClass(clDisabled),
  7252.                         child  = $this.children(),
  7253.                         target = (list ? $this : child);
  7254.                    
  7255.                     child.removeClass(clHover+' '+clSelected);
  7256.                    
  7257.                     $this.hasClass(clDroppable) && $this.droppable('disable');
  7258.                     target.hasClass(clDraggable) && target.draggable('disable');
  7259.                 })
  7260.                 // if any files was not removed/moved - unlock its
  7261.                 .on(evtEnable, fileSelector, function() {
  7262.                     var $this  = $(this).removeClass(clDisabled),
  7263.                         target = list ? $this : $this.children();
  7264.                    
  7265.                     $this.hasClass(clDroppable) && $this.droppable('enable');  
  7266.                     target.hasClass(clDraggable) && target.draggable('enable');
  7267.                 })
  7268.                 .on('scrolltoview', fileSelector, function() {
  7269.                     scrollToView($(this));
  7270.                 })
  7271.                 .on('mouseenter.'+fm.namespace+' mouseleave.'+fm.namespace, fileSelector, function(e) {
  7272.                     fm.trigger('hover', {hash : $(this).attr('id'), type : e.type});
  7273.                     $(this).toggleClass(clHover, (e.type == 'mouseenter'));
  7274.                 })
  7275.                 .on('contextmenu.'+fm.namespace, function(e) {
  7276.                     var file = $(e.target).closest('.'+clFile);
  7277.                    
  7278.                     if (file.length && (e.target.nodeName != 'TD' || $.inArray(file.get(0).id, fm.selected()) > -1)) {
  7279.                         e.stopPropagation();
  7280.                         e.preventDefault();
  7281.                         if (!file.hasClass(clDisabled) && !file.data('touching')) {
  7282.                             if (!file.hasClass(clSelected)) {
  7283.                                 // cwd.trigger('unselectall');
  7284.                                 unselectAll();
  7285.                                 file.trigger(evtSelect);
  7286.                                 trigger();
  7287.                             }
  7288.                             fm.trigger('contextmenu', {
  7289.                                 'type'    : 'files',
  7290.                                 'targets' : fm.selected(),
  7291.                                 'x'       : e.clientX,
  7292.                                 'y'       : e.clientY
  7293.                             });
  7294.  
  7295.                         }
  7296.                        
  7297.                     }
  7298.                     // e.preventDefault();
  7299.                    
  7300.                    
  7301.                 })
  7302.                 // unselect all on cwd click
  7303.                 .on('click.'+fm.namespace, function(e) {
  7304.                     if (cwd.data('longtap')) {
  7305.                         e.stopPropagation();
  7306.                         return;
  7307.                     }
  7308.                     !e.shiftKey && !e.ctrlKey && !e.metaKey && unselectAll();
  7309.                 })
  7310.                
  7311.                 // make files selectable
  7312.                 .selectable({
  7313.                     filter     : fileSelector,
  7314.                     stop       : trigger,
  7315.                     delay      : 250,
  7316.                     selected   : function(e, ui) { $(ui.selected).trigger(evtSelect); },
  7317.                     unselected : function(e, ui) { $(ui.unselected).trigger(evtUnselect); }
  7318.                 })
  7319.                 // prepend fake file/dir
  7320.                 .on('create.'+fm.namespace, function(e, file) {
  7321.                     var parent = list ? cwd.find('tbody') : cwd,
  7322.                         p = parent.find('.elfinder-cwd-parent'),
  7323.                         file = $(itemhtml(file)).addClass(clTmp);
  7324.                        
  7325.                     unselectAll();
  7326.  
  7327.                     if (p.length) {
  7328.                         p.after(file);
  7329.                     } else {
  7330.                         parent.prepend(file);
  7331.                     }
  7332.                    
  7333.                     cwd.parent().scrollTop(0);
  7334.                 })
  7335.                 // unselect all selected files
  7336.                 .on('unselectall', unselectAll)
  7337.                 .on('selectfile', function(e, id) {
  7338.                     $('#'+id).trigger(evtSelect);
  7339.                     trigger();
  7340.                 }),
  7341.             wrapper = $('<div class="elfinder-cwd-wrapper"/>')
  7342.                 // make cwd itself droppable for folders from nav panel
  7343.                 .droppable(droppable)
  7344.                 .on('contextmenu', function(e) {
  7345.                     e.preventDefault();
  7346.                     fm.trigger('contextmenu', {
  7347.                         'type'    : 'cwd',
  7348.                         'targets' : [fm.cwd().hash],
  7349.                         'x'       : e.clientX,
  7350.                         'y'       : e.clientY
  7351.                     });
  7352.                    
  7353.                 })
  7354.                 // for touch device
  7355.                 .on('touchstart.'+fm.namespace, function(e) {
  7356.                     var p = $(this);
  7357.                     cwd.data('longtap', null);
  7358.                     p.data('touching', true);
  7359.                     p.data('tmlongtap', setTimeout(function(){
  7360.                         // long tap
  7361.                         cwd.data('longtap', true);
  7362.                         fm.trigger('contextmenu', {
  7363.                             'type'    : 'cwd',
  7364.                             'targets' : [fm.cwd().hash],
  7365.                             'x'       : e.originalEvent.touches[0].clientX,
  7366.                             'y'       : e.originalEvent.touches[0].clientY
  7367.                         });
  7368.                     }, 500));
  7369.                 })
  7370.                 .on('touchmove.'+fm.namespace+' touchend.'+fm.namespace, function(e) {
  7371.                     clearTimeout($(this).data('tmlongtap'));
  7372.                 })
  7373.                 .on('mousedown', function(){wrapper._mousedown = true;})
  7374.                 .on('mouseup', function(){wrapper._mousedown = false;}),
  7375.            
  7376.             bottomMarker = $('<div>&nbsp;</div>')
  7377.                 .css({position: 'absolute', width: '1px', height: '1px'})
  7378.                 .hide(),
  7379.            
  7380.             restm = null,
  7381.             resize = function(init) {
  7382.                 var initHeight = function() {
  7383.                     var h = 0;
  7384.                     wrapper.siblings('div.elfinder-panel:visible').each(function() {
  7385.                         h += $(this).outerHeight(true);
  7386.                     });
  7387.                     wrapper.height(wz.height() - h - wrapper._padding);
  7388.                 };
  7389.                
  7390.                 init && initHeight();
  7391.                
  7392.                 restm && clearTimeout(restm);
  7393.                 restm = setTimeout(function(){
  7394.                     !init && initHeight();
  7395.                     var wph, cwdoh;
  7396.                     // fix cwd height if it less then wrapper
  7397.                     cwd.css('height', 'auto');
  7398.                     wph = wrapper[0].clientHeight - parseInt(wrapper.css('padding-top')) - parseInt(wrapper.css('padding-bottom')),
  7399.                     cwdoh = cwd.outerHeight(true);
  7400.                     if (cwdoh < wph) {
  7401.                         cwd.height(wph);
  7402.                     }
  7403.                 }, 200);
  7404.             },
  7405.            
  7406.             // elfinder node
  7407.             parent = $(this).parent().resize(resize),
  7408.            
  7409.             // workzone node
  7410.             wz = parent.children('.elfinder-workzone').append(wrapper.append(this).append(bottomMarker))
  7411.             ;
  7412.  
  7413.        
  7414.         // for iOS5 bug
  7415.         $('body').on('touchstart touchmove touchend', function(e){});
  7416.        
  7417.         (function(){
  7418.         var ent;
  7419.         if (fm.dragUpload) {
  7420.             wrapper[0].addEventListener('dragenter', function(e) {
  7421.                 var cwd = fm.cwd();
  7422.                 e.preventDefault();
  7423.                 e.stopPropagation();
  7424.                 ent = true;
  7425.                 cwd && cwd.write && wrapper.addClass(clDropActive);
  7426.             }, false);
  7427.  
  7428.             wrapper[0].addEventListener('dragleave', function(e) {
  7429.                 e.preventDefault();
  7430.                 e.stopPropagation();
  7431.                 if (ent) {
  7432.                     ent = false;
  7433.                 } else {
  7434.                     wrapper.removeClass(clDropActive);
  7435.                 }
  7436.             }, false);
  7437.  
  7438.             wrapper[0].addEventListener('dragover', function(e) {
  7439.                 e.preventDefault();
  7440.                 e.stopPropagation();
  7441.                 ent = false;
  7442.             }, false);
  7443.  
  7444.             wrapper[0].addEventListener('drop', function(e) {
  7445.                 wrapper.removeClass(clDropActive);
  7446.                 fm.exec('upload', {dropEvt: e});
  7447.             }, false);
  7448.         };
  7449.         })();
  7450.  
  7451.         fm
  7452.             .bind('open', function(e) {
  7453.                 content(e.data.files);
  7454.                 resize();
  7455.             })
  7456.             .bind('search', function(e) {
  7457.                 lastSearch = e.data.files;
  7458.                 content(lastSearch, true);
  7459.                 resize();
  7460.             })
  7461.             .bind('searchend', function() {
  7462.                 lastSearch = [];
  7463.                 if (query) {
  7464.                     query = '';
  7465.                     content(fm.files());
  7466.                 }
  7467.                 resize();
  7468.             })
  7469.             .bind('searchstart', function(e) {
  7470.                 query = e.data.query;
  7471.             })
  7472.             .bind('sortchange', function() {
  7473.                 content(query ? lastSearch : fm.files(), !!query);
  7474.             })
  7475.             .bind('viewchange', function() {
  7476.                 var sel = fm.selected(),
  7477.                     l   = fm.storage('view') == 'list';
  7478.                
  7479.                 if (l != list) {
  7480.                     list = l;
  7481.                     content(query ? lastSearch : fm.files(), !!query);
  7482.  
  7483.                     $.each(sel, function(i, h) {
  7484.                         selectFile(h);
  7485.                     });
  7486.                     trigger();
  7487.                 }
  7488.                 resize();
  7489.             })
  7490.             .bind('resize', function() {
  7491.                 var place = list ? cwd.find('tbody') : cwd;
  7492.                 resize(true);
  7493.                 bottomMarkerShow(place, place.find('[id]').length);
  7494.             })
  7495.             .bind('add', function() {
  7496.                 resize();
  7497.             })
  7498.             .add(function(e) {
  7499.                 var phash = fm.cwd().hash,
  7500.                     files = query
  7501.                         ? $.map(e.data.added || [], function(f) { return f.name.indexOf(query) === -1 ? null : f ;})
  7502.                         : $.map(e.data.added || [], function(f) { return f.phash == phash ? f : null; })
  7503.                         ;
  7504.                 add(files);
  7505.             })
  7506.             .change(function(e) {
  7507.                 var phash = fm.cwd().hash,
  7508.                     sel   = fm.selected(),
  7509.                     files;
  7510.  
  7511.                 if (query) {
  7512.                     $.each(e.data.changed || [], function(i, file) {
  7513.                         remove([file.hash]);
  7514.                         if (file.name.indexOf(query) !== -1) {
  7515.                             add([file]);
  7516.                             $.inArray(file.hash, sel) !== -1 && selectFile(file.hash);
  7517.                         }
  7518.                     });
  7519.                 } else {
  7520.                     $.each($.map(e.data.changed || [], function(f) { return f.phash == phash ? f : null; }), function(i, file) {
  7521.                         remove([file.hash]);
  7522.                         add([file]);
  7523.                         $.inArray(file.hash, sel) !== -1 && selectFile(file.hash);
  7524.                     });
  7525.                 }
  7526.                
  7527.                 trigger();
  7528.             })
  7529.             .remove(function(e) {
  7530.                 remove(e.data.removed || []);
  7531.                 trigger();
  7532.             })
  7533.             // select dragged file if no selected, disable selectable
  7534.             .dragstart(function(e) {
  7535.                 var target = $(e.data.target),
  7536.                     oe     = e.data.originalEvent;
  7537.  
  7538.                 if (target.hasClass(fileSelector.substr(1))) {
  7539.                    
  7540.                     if (!target.hasClass(clSelected)) {
  7541.                         !(oe.ctrlKey || oe.metaKey || oe.shiftKey) && unselectAll();
  7542.                         target.trigger(evtSelect);
  7543.                         trigger();
  7544.                     }
  7545.                     wrapper.droppable('disable');
  7546.                 }
  7547.                
  7548.                 cwd.selectable('disable').removeClass(clDisabled);
  7549.                 selectLock = true;
  7550.             })
  7551.             // enable selectable
  7552.             .dragstop(function() {
  7553.                 cwd.selectable('enable');
  7554.                 wrapper.droppable('enable');
  7555.                 selectLock = false;
  7556.             })
  7557.             .bind('lockfiles unlockfiles selectfiles unselectfiles', function(e) {
  7558.                 var events = {
  7559.                         lockfiles     : evtDisable ,
  7560.                         unlockfiles   : evtEnable ,
  7561.                         selectfiles   : evtSelect,
  7562.                         unselectfiles : evtUnselect },
  7563.                     event  = events[e.type],
  7564.                     files  = e.data.files || [],
  7565.                     l      = files.length;
  7566.                
  7567.                 while (l--) {
  7568.                     $('#'+files[l]).trigger(event);
  7569.                 }
  7570.                 trigger();
  7571.                 wrapper.data('dropover') && wrapper.toggleClass(clDropActive, e.type !== 'lockfiles');
  7572.             })
  7573.             // select new files after some actions
  7574.             .bind('mkdir mkfile duplicate upload rename archive extract paste multiupload', function(e) {
  7575.                 if (e.type == 'upload' && e.data._multiupload) return;
  7576.                 var phash = fm.cwd().hash, files;
  7577.                
  7578.                 unselectAll();
  7579.  
  7580.                 $.each(e.data.added || [], function(i, file) {
  7581.                     file && file.phash == phash && selectFile(file.hash);
  7582.                 });
  7583.                 trigger();
  7584.             })
  7585.             .shortcut({
  7586.                 pattern     :'ctrl+a',
  7587.                 description : 'selectall',
  7588.                 callback    : selectAll
  7589.             })
  7590.             .shortcut({
  7591.                 pattern     : 'left right up down shift+left shift+right shift+up shift+down',
  7592.                 description : 'selectfiles',
  7593.                 type        : 'keydown' , //fm.UA.Firefox || fm.UA.Opera ? 'keypress' : 'keydown',
  7594.                 callback    : function(e) { select(e.keyCode, e.shiftKey); }
  7595.             })
  7596.             .shortcut({
  7597.                 pattern     : 'home',
  7598.                 description : 'selectffile',
  7599.                 callback    : function(e) {
  7600.                     unselectAll();
  7601.                     scrollToView(cwd.find('[id]:first').trigger(evtSelect));
  7602.                     trigger();
  7603.                 }
  7604.             })
  7605.             .shortcut({
  7606.                 pattern     : 'end',
  7607.                 description : 'selectlfile',
  7608.                 callback    : function(e) {
  7609.                     unselectAll();
  7610.                     scrollToView(cwd.find('[id]:last').trigger(evtSelect)) ;
  7611.                     trigger();
  7612.                 }
  7613.             });
  7614.        
  7615.     });
  7616.    
  7617.     // fm.timeEnd('cwdLoad')
  7618.    
  7619.     return this;
  7620. }
  7621.  
  7622.  
  7623. /*
  7624.  * File: /js/ui/dialog.js
  7625.  */
  7626.  
  7627. /**
  7628.  * @class  elFinder dialog
  7629.  *
  7630.  * @author Dmitry (dio) Levashov
  7631.  **/
  7632. $.fn.elfinderdialog = function(opts) {
  7633.     var dialog;
  7634.    
  7635.     if (typeof(opts) == 'string' && (dialog = this.closest('.ui-dialog')).length) {
  7636.         if (opts == 'open') {
  7637.             dialog.css('display') == 'none' && dialog.fadeIn(120, function() {
  7638.                 dialog.trigger('open');
  7639.             });
  7640.         } else if (opts == 'close') {
  7641.             dialog.css('display') != 'none' && dialog.hide().trigger('close');
  7642.         } else if (opts == 'destroy') {
  7643.             dialog.hide().remove();
  7644.         } else if (opts == 'toTop') {
  7645.             dialog.trigger('totop');
  7646.         } else if (opts == 'posInit') {
  7647.             dialog.trigger('posinit');
  7648.         }
  7649.     }
  7650.    
  7651.     opts = $.extend({}, $.fn.elfinderdialog.defaults, opts);
  7652.  
  7653.     this.filter(':not(.ui-dialog-content)').each(function() {
  7654.         var self       = $(this).addClass('ui-dialog-content ui-widget-content'),
  7655.             parent     = self.parent(),
  7656.             clactive   = 'elfinder-dialog-active',
  7657.             cldialog   = 'elfinder-dialog',
  7658.             clnotify   = 'elfinder-dialog-notify',
  7659.             clhover    = 'ui-state-hover',
  7660.             id         = parseInt(Math.random()*1000000),
  7661.             overlay    = parent.children('.elfinder-overlay'),
  7662.             buttonset  = $('<div class="ui-dialog-buttonset"/>'),
  7663.             buttonpane = $('<div class=" ui-helper-clearfix ui-dialog-buttonpane ui-widget-content"/>')
  7664.                 .append(buttonset),
  7665.             platformWin = (window.navigator.platform.indexOf('Win') != -1),
  7666.            
  7667.             dialog = $('<div class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-draggable std42-dialog  '+cldialog+' '+opts.cssClass+'"/>')
  7668.                 .hide()
  7669.                 .append(self)
  7670.                 .appendTo(parent)
  7671.                 .draggable({
  7672.                     handle : '.ui-dialog-titlebar',
  7673.                     containment : 'document',
  7674.                     stop : function(e, ui){
  7675.                         dialog.css({height : opts.height});
  7676.                     }
  7677.                 })
  7678.                 .css({
  7679.                     width  : opts.width,
  7680.                     height : opts.height//,
  7681.                     //maxWidth: opts.maxWidth? opts.maxWidth : $(window).width()-10,
  7682.                     //maxHeight: opts.maxHeight? opts.maxHeight : $(window).height()-20
  7683.                 })
  7684.                 .mousedown(function(e) {
  7685.                     e.stopPropagation();
  7686.                    
  7687.                     $(document).mousedown();
  7688.  
  7689.                     if (!dialog.hasClass(clactive)) {
  7690.                         parent.find('.'+cldialog+':visible').removeClass(clactive);
  7691.                         dialog.addClass(clactive).zIndex(maxZIndex() + 1);
  7692.                     }
  7693.                 })
  7694.                 .on('open', function() {
  7695.                     var d = $(this),
  7696.                     maxWinWidth = (d.outerWidth() > parent.width()-10)? parent.width()-10 : null;
  7697.                    
  7698.                     maxWinWidth && d.css({width: maxWinWidth, left: '5px'});
  7699.                    
  7700.                     dialog.trigger('totop');
  7701.                    
  7702.                     typeof(opts.open) == 'function' && $.proxy(opts.open, self[0])();
  7703.  
  7704.                     if (!dialog.hasClass(clnotify)) {
  7705.                        
  7706.                         parent.find('.'+cldialog+':visible').not('.'+clnotify).each(function() {
  7707.                             var d     = $(this),
  7708.                                 top   = parseInt(d.css('top')),
  7709.                                 left  = parseInt(d.css('left')),
  7710.                                 _top  = parseInt(dialog.css('top')),
  7711.                                 _left = parseInt(dialog.css('left'))
  7712.                                 ;
  7713.  
  7714.                             if (d[0] != dialog[0] && (top == _top || left == _left)) {
  7715.                                 dialog.css({
  7716.                                     top  : (top+(maxWinWidth? 15 : 10))+'px',
  7717.                                     left : (maxWinWidth? 5 : left+10)+'px'
  7718.                                 });
  7719.                             }
  7720.                         });
  7721.                     }
  7722.                 })
  7723.                 .on('close', function() {
  7724.                     var dialogs = parent.find('.elfinder-dialog:visible'),
  7725.                         z = maxZIndex();
  7726.                    
  7727.                     $(this).data('modal') && overlay.elfinderoverlay('hide');
  7728.                    
  7729.                     // get focus to next dialog
  7730.                     if (dialogs.length) {
  7731.                         dialogs.each(function() {
  7732.                             var d = $(this);
  7733.                             if (d.zIndex() >= z) {
  7734.                                 d.trigger('totop');
  7735.                                 return false;
  7736.                             }
  7737.                         })
  7738.                     } else {
  7739.                         // return focus to parent
  7740.                         setTimeout(function() {
  7741.                             parent.mousedown().click();
  7742.                         }, 10);
  7743.                        
  7744.                     }
  7745.                    
  7746.                     if (typeof(opts.close) == 'function') {
  7747.                         $.proxy(opts.close, self[0])();
  7748.                     } else if (opts.destroyOnClose) {
  7749.                         dialog.hide().remove();
  7750.                     }
  7751.                 })
  7752.                 .on('totop', function() {
  7753.                     $(this).mousedown().find('.ui-button:'+(platformWin? 'first':'last')).focus().end().find(':text:first').focus();
  7754.                     $(this).data('modal') && overlay.is(':hidden') && overlay.elfinderoverlay('show');
  7755.                     overlay.zIndex($(this).zIndex());
  7756.                 })
  7757.                 .on('posinit', function() {
  7758.                     var css = opts.position;
  7759.                     if (!css) {
  7760.                         css = {
  7761.                             top  : Math.max(0, parseInt((parent.height() - dialog.outerHeight())/2 - 42))+'px',
  7762.                             left : Math.max(0, parseInt((parent.width() - dialog.outerWidth())/2))+'px'
  7763.                         };
  7764.                     }
  7765.                     dialog.css(css);
  7766.                 })
  7767.                 .data({modal: opts.modal}),
  7768.                 maxZIndex = function() {
  7769.                     var z = parent.zIndex() + 10;
  7770.                     parent.find('.'+cldialog+':visible').each(function() {
  7771.                         var _z;
  7772.                         if (this != dialog[0]) {
  7773.                             _z = $(this).zIndex();
  7774.                             if (_z > z) {
  7775.                                 z = _z;
  7776.                             }
  7777.                         }
  7778.                     })
  7779.                     return z;
  7780.                 },
  7781.                 top
  7782.             ;
  7783.        
  7784.         dialog.trigger('posinit');
  7785.  
  7786.         if (opts.closeOnEscape) {
  7787.             $(document).on('keyup.'+id, function(e) {
  7788.                 if (e.keyCode == $.ui.keyCode.ESCAPE && dialog.hasClass(clactive)) {
  7789.                     self.elfinderdialog('close');
  7790.                     $(document).off('keyup.'+id);
  7791.                 }
  7792.             })
  7793.         }
  7794.         dialog.prepend(
  7795.             $('<div class="ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix">'+opts.title+'</div>')
  7796.                 .prepend($('<a href="#" class="ui-dialog-titlebar-close ui-corner-all"><span class="ui-icon ui-icon-closethick"/></a>')
  7797.                     .mousedown(function(e) {
  7798.                         e.preventDefault();
  7799.                         self.elfinderdialog('close');
  7800.                     }))
  7801.  
  7802.         );
  7803.            
  7804.        
  7805.            
  7806.         $.each(opts.buttons, function(name, cb) {
  7807.             var button = $('<button type="button" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"><span class="ui-button-text">'+name+'</span></button>')
  7808.                 .click($.proxy(cb, self[0]))
  7809.                 .hover(function(e) {
  7810.                     if (opts.btnHoverFocus) {
  7811.                         $(this)[e.type == 'mouseenter' ? 'focus' : 'blur']();
  7812.                     } else {
  7813.                         $(this).toggleClass(clhover, e.type == 'mouseenter');
  7814.                     }
  7815.                 })
  7816.                 .focus(function() { $(this).addClass(clhover) })
  7817.                 .blur(function() { $(this).removeClass(clhover) })
  7818.                 .keydown(function(e) {
  7819.                     var next;
  7820.                    
  7821.                     if (e.keyCode == $.ui.keyCode.ENTER) {
  7822.                         $(this).click();
  7823.                     }  else if (e.keyCode == $.ui.keyCode.TAB || e.keyCode == $.ui.keyCode.RIGHT) {
  7824.                         e.preventDefault();
  7825.                         next = $(this).next('.ui-button');
  7826.                         next.length ? next.focus() : $(this).parent().children('.ui-button:first').focus();
  7827.                     }  else if (e.keyCode == $.ui.keyCode.LEFT) {
  7828.                         e.preventDefault();
  7829.                         next = $(this).prev('.ui-button');
  7830.                         next.length ? next.focus() : $(this).parent().children('.ui-button:last').focus()
  7831.                     }
  7832.                 })
  7833.             if (platformWin) {
  7834.                 buttonset.append(button);
  7835.             } else {
  7836.                 buttonset.prepend(button);
  7837.             }
  7838.         })
  7839.            
  7840.         buttonset.children().length && dialog.append(buttonpane);
  7841.         if (opts.resizable && $.fn.resizable) {
  7842.             dialog.resizable({
  7843.                     minWidth   : opts.minWidth,
  7844.                     minHeight  : opts.minHeight,
  7845.                     alsoResize : this
  7846.                 });
  7847.         }
  7848.            
  7849.         typeof(opts.create) == 'function' && $.proxy(opts.create, this)();
  7850.            
  7851.         opts.autoOpen && self.elfinderdialog('open');
  7852.  
  7853.     });
  7854.    
  7855.     return this;
  7856. }
  7857.  
  7858. $.fn.elfinderdialog.defaults = {
  7859.     cssClass  : '',
  7860.     title     : '',
  7861.     modal     : false,
  7862.     resizable : true,
  7863.     autoOpen  : true,
  7864.     closeOnEscape : true,
  7865.     destroyOnClose : false,
  7866.     buttons   : {},
  7867.     btnHoverFocus : true,
  7868.     position  : null,
  7869.     width     : 320,
  7870.     height    : 'auto',
  7871.     minWidth  : 200,
  7872.     minHeight : 110
  7873. }
  7874.  
  7875. /*
  7876.  * File: /js/ui/navbar.js
  7877.  */
  7878.  
  7879. /**
  7880.  * @class elfindernav - elFinder container for diretories tree and places
  7881.  *
  7882.  * @author Dmitry (dio) Levashov
  7883.  **/
  7884. $.fn.elfindernavbar = function(fm, opts) {
  7885.  
  7886.     this.not('.elfinder-navbar').each(function() {
  7887.         var nav    = $(this).addClass('ui-state-default elfinder-navbar'),
  7888.             parent = nav.parent()
  7889.                 .resize(function() {
  7890.                     nav.height(wz.height() - delta);
  7891.                 }),
  7892.             wz     = parent.children('.elfinder-workzone').append(nav),
  7893.             delta  = nav.outerHeight() - nav.height(),
  7894.             ltr    = fm.direction == 'ltr',
  7895.             handle;
  7896.  
  7897.        
  7898.         if ($.fn.resizable) {
  7899.             handle = nav.resizable({
  7900.                     handles : ltr ? 'e' : 'w',
  7901.                     minWidth : opts.minWidth || 150,
  7902.                     maxWidth : opts.maxWidth || 500
  7903.                 })
  7904.                 .on('resize scroll', function() {
  7905.                     var offset = (fm.UA.Opera && nav.scrollLeft())? 20 : 2;
  7906.                     handle.css({
  7907.                         top  : parseInt(nav.scrollTop())+'px',
  7908.                         left : ltr ? 'auto' : parseInt(nav.scrollLeft() + offset),
  7909.                         right: ltr ? parseInt(nav.scrollLeft() - offset) * -1 : 'auto'
  7910.                     });
  7911.                 })
  7912.                 .find('.ui-resizable-handle').zIndex(nav.zIndex() + 10);
  7913.  
  7914.             if (fm.UA.Touch) {
  7915.                 var toggle = function(){
  7916.                     if (handle.data('closed')) {
  7917.                         handle.data('closed', false).css({backgroundColor: 'transparent'});
  7918.                         nav.css({width: handle.data('width')}).trigger('resize');
  7919.                     } else {
  7920.                         handle.data('closed', true).css({backgroundColor: 'inherit'});
  7921.                         nav.css({width: 8});
  7922.                     }
  7923.                     handle.data({startX: null, endX: null});
  7924.                 };
  7925.                 handle.data({closed: false, width: nav.width()})
  7926.                 .on('touchstart', function(e){
  7927.                     handle.data('startX', e.originalEvent.touches[0].pageX);
  7928.                 })
  7929.                 .on('touchmove', function(e){
  7930.                     var x = e.originalEvent.touches[0].pageX;
  7931.                     var sx = handle.data('startX');
  7932.                     var open = ltr? (sx && sx < x) : (sx > x);
  7933.                     var close = ltr? (sx > x) : (sx && sx < x);
  7934.                     (open || close) && toggle();
  7935.                 })
  7936.                 .on('touchend', function(e){
  7937.                     handle.data('startX') && toggle();
  7938.                 });
  7939.                 if (fm.UA.Mobile) {
  7940.                     handle.data('defWidth', nav.width());
  7941.                     $(window).on('resize', function(e){
  7942.                         var hw = nav.parent().width() / 2;
  7943.                         if (handle.data('defWidth') > hw) {
  7944.                             nav.width(hw);
  7945.                         } else {
  7946.                             nav.width(handle.data('defWidth'));
  7947.                         }
  7948.                         handle.data('width', nav.width());
  7949.                     });
  7950.                 }
  7951.             }
  7952.  
  7953.             fm.one('open', function() {
  7954.                 setTimeout(function() {
  7955.                     nav.trigger('resize');
  7956.                 }, 150);
  7957.             });
  7958.         }
  7959.     });
  7960.    
  7961.     return this;
  7962. };
  7963.  
  7964.  
  7965. /*
  7966.  * File: /js/ui/overlay.js
  7967.  */
  7968.  
  7969.  
  7970. $.fn.elfinderoverlay = function(opts) {
  7971.    
  7972.     this.filter(':not(.elfinder-overlay)').each(function() {
  7973.         opts = $.extend({}, opts);
  7974.         $(this).addClass('ui-widget-overlay elfinder-overlay')
  7975.             .hide()
  7976.             .mousedown(function(e) {
  7977.                 e.preventDefault();
  7978.                 e.stopPropagation();
  7979.             })
  7980.             .data({
  7981.                 cnt  : 0,
  7982.                 show : typeof(opts.show) == 'function' ? opts.show : function() { },
  7983.                 hide : typeof(opts.hide) == 'function' ? opts.hide : function() { }
  7984.             });
  7985.     });
  7986.    
  7987.     if (opts == 'show') {
  7988.         var o    = this.eq(0),
  7989.             cnt  = o.data('cnt') + 1,
  7990.             show = o.data('show');
  7991.  
  7992.         o.data('cnt', cnt);
  7993.  
  7994.         if (o.is(':hidden')) {
  7995.             o.zIndex(o.parent().zIndex()+1);
  7996.             o.show();
  7997.             show();
  7998.         }
  7999.     }
  8000.    
  8001.     if (opts == 'hide') {
  8002.         var o    = this.eq(0),
  8003.             cnt  = o.data('cnt') - 1,
  8004.             hide = o.data('hide');
  8005.        
  8006.         o.data('cnt', cnt);
  8007.            
  8008.         if (cnt == 0 && o.is(':visible')) {
  8009.             o.hide();
  8010.             hide();        
  8011.         }
  8012.     }
  8013.    
  8014.     return this;
  8015. }
  8016.  
  8017. /*
  8018.  * File: /js/ui/panel.js
  8019.  */
  8020.  
  8021. $.fn.elfinderpanel = function(fm) {
  8022.    
  8023.     return this.each(function() {
  8024.         var panel = $(this).addClass('elfinder-panel ui-state-default ui-corner-all'),
  8025.             margin = 'margin-'+(fm.direction == 'ltr' ? 'left' : 'right');
  8026.        
  8027.         fm.one('load', function(e) {
  8028.             var navbar = fm.getUI('navbar');
  8029.            
  8030.             panel.css(margin, parseInt(navbar.outerWidth(true)));
  8031.             navbar.on('resize', function() {
  8032.                 panel.is(':visible') && panel.css(margin, parseInt(navbar.outerWidth(true)))
  8033.             })
  8034.         })
  8035.     })
  8036. }
  8037.  
  8038. /*
  8039.  * File: /js/ui/path.js
  8040.  */
  8041.  
  8042. /**
  8043.  * @class elFinder ui
  8044.  * Display current folder path in statusbar.
  8045.  * Click on folder name in path - open folder
  8046.  *
  8047.  * @author Dmitry (dio) Levashov
  8048.  **/
  8049. $.fn.elfinderpath = function(fm) {
  8050.     return this.each(function() {
  8051.         var path = $(this).addClass('elfinder-path').html('&nbsp;')
  8052.                 .on('click', 'a', function(e) {
  8053.                     var hash = $(this).attr('href').substr(1);
  8054.  
  8055.                     e.preventDefault();
  8056.                     hash != fm.cwd().hash && fm.exec('open', hash);
  8057.                 })
  8058.                 .prependTo(fm.getUI('statusbar').show())
  8059.  
  8060.             fm.bind('open searchend parents', function() {
  8061.                 var dirs = [];
  8062.  
  8063.                 $.each(fm.parents(fm.cwd().hash), function(i, hash) {
  8064.                     dirs.push('<a href="#'+hash+'">'+fm.escape(fm.file(hash).name)+'</a>');
  8065.                 });
  8066.  
  8067.                 path.html(dirs.join(fm.option('separator')));
  8068.             })
  8069.             .bind('search', function() {
  8070.                 path.html(fm.i18n('searcresult'));
  8071.             });
  8072.     });
  8073. }
  8074.  
  8075. /*
  8076.  * File: /js/ui/places.js
  8077.  */
  8078.  
  8079. /**
  8080.  * @class elFinder places/favorites ui
  8081.  *
  8082.  * @author Dmitry (dio) Levashov
  8083.  **/
  8084. $.fn.elfinderplaces = function(fm, opts) {
  8085.     return this.each(function() {
  8086.         var dirs      = [],
  8087.             c         = 'class',
  8088.             navdir    = fm.res(c, 'navdir'),
  8089.             collapsed = fm.res(c, 'navcollapse'),
  8090.             expanded  = fm.res(c, 'navexpand'),
  8091.             hover     = fm.res(c, 'hover'),
  8092.             clroot    = fm.res(c, 'treeroot'),
  8093.             tpl       = fm.res('tpl', 'navdir'),
  8094.             ptpl      = fm.res('tpl', 'perms'),
  8095.             spinner   = $(fm.res('tpl', 'navspinner')),
  8096.             key       = 'places'+(opts.suffix? opts.suffix : ''),
  8097.             /**
  8098.              * Convert places dir node into dir hash
  8099.              *
  8100.              * @param  String  directory id
  8101.              * @return String
  8102.              **/
  8103.             id2hash   = function(id) { return id.substr(6); },
  8104.             /**
  8105.              * Convert places dir node into dir hash
  8106.              *
  8107.              * @param  String  directory id
  8108.              * @return String
  8109.              **/
  8110.             hash2id   = function(hash) { return 'place-'+hash; },
  8111.            
  8112.             /**
  8113.              * Save current places state
  8114.              *
  8115.              * @return void
  8116.              **/
  8117.             save      = function() { fm.storage(key, dirs.join(',')); },
  8118.             /**
  8119.              * Return node for given dir object
  8120.              *
  8121.              * @param  Object  directory object
  8122.              * @return jQuery
  8123.              **/
  8124.             create    = function(dir) {
  8125.                 return $(tpl.replace(/\{id\}/, hash2id(dir.hash))
  8126.                         .replace(/\{name\}/, fm.escape(dir.name))
  8127.                         .replace(/\{cssclass\}/, (fm.UA.Touch ? 'elfinder-touch ' : '')+fm.perms2class(dir))
  8128.                         .replace(/\{permissions\}/, !dir.read || !dir.write ? ptpl : '')
  8129.                         .replace(/\{symlink\}/, ''));
  8130.             },
  8131.             /**
  8132.              * Add new node into places
  8133.              *
  8134.              * @param  Object  directory object
  8135.              * @return void
  8136.              **/
  8137.             add = function(dir) {
  8138.                 if (!fm.files().hasOwnProperty(dir.hash)) {
  8139.                     // update cache
  8140.                     fm.trigger('tree', {tree: [dir]});
  8141.                 }
  8142.                
  8143.                 var node = create(dir);
  8144.  
  8145.                 if (subtree.children().length) {
  8146.                     $.each(subtree.children(), function() {
  8147.                         var current =  $(this);
  8148.                        
  8149.                         if (dir.name.localeCompare(current.children('.'+navdir).text()) < 0) {
  8150.                             return !node.insertBefore(current);
  8151.                         }
  8152.                     });
  8153.                 }
  8154.                
  8155.                 dirs.push(dir.hash);
  8156.                 !node.parent().length && subtree.append(node);
  8157.                 root.addClass(collapsed);
  8158.                 node.draggable({
  8159.                     appendTo : 'body',
  8160.                     revert   : false,
  8161.                     helper   : function() {
  8162.                         var dir = $(this);
  8163.                            
  8164.                         dir.children().removeClass('ui-state-hover');
  8165.                        
  8166.                         return $('<div class="elfinder-place-drag elfinder-'+fm.direction+'"/>')
  8167.                                 .append(dir.clone())
  8168.                                 .data('hash', id2hash(dir.children(':first').attr('id')));
  8169.  
  8170.                     },
  8171.                     start    : function() { $(this).hide(); },
  8172.                     stop     : function(e, ui) {
  8173.                         var top    = places.offset().top,
  8174.                             left   = places.offset().left,
  8175.                             width  = places.width(),
  8176.                             height = places.height(),
  8177.                             x      = e.clientX,
  8178.                             y      = e.clientY;
  8179.                        
  8180.                         if (x > left && x < left+width && y > top && y < y+height) {
  8181.                             $(this).show();
  8182.                         } else {
  8183.                             remove(ui.helper.data('hash'));
  8184.                             save();
  8185.                         }
  8186.                     }
  8187.                 });
  8188.             },
  8189.             /**
  8190.              * Remove dir from places
  8191.              *
  8192.              * @param  String  directory id
  8193.              * @return String  removed name
  8194.              **/
  8195.             remove = function(hash) {
  8196.                 var ndx = $.inArray(hash, dirs), name = null, tgt;
  8197.  
  8198.                 if (ndx !== -1) {
  8199.                     dirs.splice(ndx, 1);
  8200.                     tgt = subtree.find('#'+hash2id(hash));
  8201.                     if (tgt.length) {
  8202.                         name = tgt.text();
  8203.                         tgt.parent().remove();
  8204.                         !subtree.children().length && root.removeClass(collapsed+' '+expanded);
  8205.                     }
  8206.                 }
  8207.                
  8208.                 return name;
  8209.             },
  8210.             /**
  8211.              * Remove all dir from places
  8212.              *
  8213.              * @return void
  8214.              **/
  8215.             clear = function() {
  8216.                 subtree.empty();
  8217.                 root.removeClass(collapsed+' '+expanded);
  8218.             },
  8219.             /**
  8220.              * Node - wrapper for places root
  8221.              *
  8222.              * @type jQuery
  8223.              **/
  8224.             wrapper = create({
  8225.                     hash  : 'root-'+fm.namespace,
  8226.                     name  : fm.i18n(opts.name, 'places'),
  8227.                     read  : true,
  8228.                     write : true
  8229.                 }),
  8230.             /**
  8231.              * Places root node
  8232.              *
  8233.              * @type jQuery
  8234.              **/
  8235.             root = wrapper.children('.'+navdir)
  8236.                 .addClass(clroot)
  8237.                 .click(function() {
  8238.                     if (root.hasClass(collapsed)) {
  8239.                         places.toggleClass(expanded);
  8240.                         subtree.slideToggle();
  8241.                         fm.storage('placesState', places.hasClass(expanded)? 1 : 0);
  8242.                     }
  8243.                 }),
  8244.             /**
  8245.              * Container for dirs
  8246.              *
  8247.              * @type jQuery
  8248.              **/
  8249.             subtree = wrapper.children('.'+fm.res(c, 'navsubtree')),
  8250.             /**
  8251.              * Main places container
  8252.              *
  8253.              * @type jQuery
  8254.              **/
  8255.             places = $(this).addClass(fm.res(c, 'tree')+' elfinder-places ui-corner-all')
  8256.                 .hide()
  8257.                 .append(wrapper)
  8258.                 .appendTo(fm.getUI('navbar'))
  8259.                 .on('mouseenter mouseleave', '.'+navdir, function(e) {
  8260.                     $(this).toggleClass('ui-state-hover', (e.type == 'mouseenter'));
  8261.                 })
  8262.                 .on('click', '.'+navdir, function(e) {
  8263.                     var p = $(this);
  8264.                     if (p.data('longtap')) {
  8265.                         e.stopPropagation();
  8266.                         return;
  8267.                     }
  8268.                     fm.exec('open', p.attr('id').substr(6));
  8269.                 })
  8270.                 .on('contextmenu', '.'+navdir+':not(.'+clroot+')', function(e) {
  8271.                     var hash = $(this).attr('id').substr(6);
  8272.                    
  8273.                     e.preventDefault();
  8274.                    
  8275.                     fm.trigger('contextmenu', {
  8276.                         raw : [{
  8277.                             label    : fm.i18n('rmFromPlaces'),
  8278.                             icon     : 'rm',
  8279.                             callback : function() { remove(hash); save(); }
  8280.                         }],
  8281.                         'x'       : e.clientX,
  8282.                         'y'       : e.clientY
  8283.                     })
  8284.                    
  8285.                 })
  8286.                 .droppable({
  8287.                     tolerance  : 'pointer',
  8288.                     accept     : '.elfinder-cwd-file-wrapper,.elfinder-tree-dir,.elfinder-cwd-file',
  8289.                     hoverClass : fm.res('class', 'adroppable'),
  8290.                     drop       : function(e, ui) {
  8291.                         var resolve = true;
  8292.                        
  8293.                         $.each(ui.helper.data('files'), function(i, hash) {
  8294.                             var dir = fm.file(hash);
  8295.                            
  8296.                             if (dir && dir.mime == 'directory' && $.inArray(dir.hash, dirs) === -1) {
  8297.                                 add(dir);
  8298.                             } else {
  8299.                                 resolve = false;
  8300.                             }
  8301.                         })
  8302.                         save();
  8303.                         resolve && ui.helper.hide();
  8304.                     }
  8305.                 })
  8306.                 // for touch device
  8307.                 .on('touchstart', '.'+navdir+':not(.'+clroot+')', function(e) {
  8308.                     var hash = $(this).attr('id').substr(6),
  8309.                     p = $(this)
  8310.                     .addClass(hover)
  8311.                     .data('longtap', null)
  8312.                     .data('tmlongtap', setTimeout(function(){
  8313.                         // long tap
  8314.                         p.data('longtap', true);
  8315.                         fm.trigger('contextmenu', {
  8316.                             raw : [{
  8317.                                 label    : fm.i18n('rmFromPlaces'),
  8318.                                 icon     : 'rm',
  8319.                                 callback : function() { remove(hash); save(); }
  8320.                             }],
  8321.                             'x'       : e.originalEvent.touches[0].clientX,
  8322.                             'y'       : e.originalEvent.touches[0].clientY
  8323.                         });
  8324.                     }, 500));
  8325.                 })
  8326.                 .on('touchmove touchend', '.'+navdir+':not(.'+clroot+')', function(e) {
  8327.                     clearTimeout($(this).data('tmlongtap'));
  8328.                     if (e.type == 'touchmove') {
  8329.                         $(this).removeClass(hover);
  8330.                     }
  8331.                 });
  8332.  
  8333.         // "on regist" for command exec
  8334.         $(this).on('regist', function(e, files){
  8335.             $.each(files, function(i, dir) {
  8336.                 if (dir && dir.mime == 'directory' && $.inArray(dir.hash, dirs) === -1) {
  8337.                     add(dir);
  8338.                 }
  8339.             });
  8340.             save();
  8341.         });
  8342.    
  8343.  
  8344.         // on fm load - show places and load files from backend
  8345.         fm.one('load', function() {
  8346.             if (fm.oldAPI) {
  8347.                 return;
  8348.             }
  8349.            
  8350.             places.show().parent().show();
  8351.  
  8352.             dirs = $.map((fm.storage(key) || '').split(','), function(hash) { return hash || null;});
  8353.            
  8354.             if (dirs.length) {
  8355.                 root.prepend(spinner);
  8356.                
  8357.                 fm.request({
  8358.                     data : {cmd : 'info', targets : dirs},
  8359.                     preventDefault : true
  8360.                 })
  8361.                 .done(function(data) {
  8362.                     dirs = [];
  8363.                     $.each(data.files, function(i, file) {
  8364.                         file.mime == 'directory' && add(file);
  8365.                     });
  8366.                     save();
  8367.                     if (fm.storage('placesState') > 0) {
  8368.                         root.click();
  8369.                     }
  8370.                 })
  8371.                 .always(function() {
  8372.                     spinner.remove();
  8373.                 })
  8374.             }
  8375.            
  8376.  
  8377.             fm.change(function(e) {
  8378.                 $.each(e.data.changed, function(i, file) {
  8379.                     if ($.inArray(file.hash, dirs) !== -1) {
  8380.                         remove(file.hash);
  8381.                         file.mime == 'directory' && add(file);
  8382.                     }
  8383.                 });
  8384.                 save();
  8385.             })
  8386.             .bind('rm paste', function(e){
  8387.                 var names = [];
  8388.                 if (e.data.removed) {
  8389.                     $.each(e.data.removed, function(i, hash) {
  8390.                         var name = remove(hash);
  8391.                         name && names.push(name);
  8392.                     });
  8393.                 }
  8394.                 if (e.data.added && names.length) {
  8395.                     $.each(e.data.added, function(i, file) {
  8396.                         if ($.inArray(file.name, names) !== 1) {
  8397.                             file.mime == 'directory' && add(file);
  8398.                         }
  8399.                     });
  8400.                 }
  8401.                 save();
  8402.             })
  8403.             .bind('sync', function() {
  8404.                 if (dirs.length) {
  8405.                     root.prepend(spinner);
  8406.  
  8407.                     fm.request({
  8408.                         data : {cmd : 'info', targets : dirs},
  8409.                         preventDefault : true
  8410.                     })
  8411.                     .done(function(data) {
  8412.                         $.each(data.files || [], function(i, file) {
  8413.                             if ($.inArray(file.hash, dirs) === -1) {
  8414.                                 remove(file.hash);
  8415.                             }
  8416.                             if (!fm.files().hasOwnProperty(file.hash)) {
  8417.                                 // update cache
  8418.                                 fm.trigger('tree', {tree: [file]});
  8419.                             }
  8420.                         });
  8421.                         save();
  8422.                     })
  8423.                     .always(function() {
  8424.                         spinner.remove();
  8425.                     });
  8426.                 }
  8427.             })
  8428.            
  8429.         })
  8430.        
  8431.     });
  8432. }
  8433.  
  8434.  
  8435. /*
  8436.  * File: /js/ui/searchbutton.js
  8437.  */
  8438.  
  8439. /**
  8440.  * @class  elFinder toolbar search button widget.
  8441.  *
  8442.  * @author Dmitry (dio) Levashov
  8443.  **/
  8444. $.fn.elfindersearchbutton = function(cmd) {
  8445.     return this.each(function() {
  8446.         var result = false,
  8447.             fm     = cmd.fm,
  8448.             id     = function(name){return fm.namespace + name},
  8449.             toolbar= fm.getUI('toolbar'),
  8450.             btnCls = fm.res('class', 'searchbtn'),
  8451.             button = $(this).hide().addClass('ui-widget-content elfinder-button '+btnCls),
  8452.             search = function() {
  8453.                 opts.slideUp();
  8454.                 var val = $.trim(input.val()),
  8455.                     from = !$('#' + id('SearchFromAll')).prop('checked'),
  8456.                     mime = $('#' + id('SearchMime')).prop('checked');
  8457.                 if (from) {
  8458.                     if ($('#' + id('SearchFromVol')).prop('checked')) {
  8459.                         from = fm.root(fm.cwd().hash);
  8460.                     } else {
  8461.                         from = fm.cwd().hash;
  8462.                     }
  8463.                 }
  8464.                 if (mime) {
  8465.                     mime = val;
  8466.                     val = '.';
  8467.                 }
  8468.                 if (val) {
  8469.                     cmd.exec(val, from, mime).done(function() {
  8470.                         result = true;
  8471.                         input.focus();
  8472.                     });
  8473.                    
  8474.                 } else {
  8475.                     fm.trigger('searchend');
  8476.                 }
  8477.             },
  8478.             abort = function() {
  8479.                 opts.slideUp();
  8480.                 input.val('');
  8481.                 if (result) {
  8482.                     result = false;
  8483.                     fm.trigger('searchend');
  8484.                 }
  8485.             },
  8486.             input  = $('<input type="text" size="42"/>')
  8487.                 .focus(function(){
  8488.                     opts.slideDown();
  8489.                 })
  8490.                 .blur(function(){
  8491.                     if (!opts.data('infocus')) {
  8492.                         opts.slideUp();
  8493.                     } else {
  8494.                         opts.data('infocus', false);
  8495.                     }
  8496.                 })
  8497.                 .appendTo(button)
  8498.                 // to avoid fm shortcuts on arrows
  8499.                 .keypress(function(e) {
  8500.                     e.stopPropagation();
  8501.                 })
  8502.                 .keydown(function(e) {
  8503.                     e.stopPropagation();
  8504.                    
  8505.                     e.keyCode == 13 && search();
  8506.                    
  8507.                     if (e.keyCode== 27) {
  8508.                         e.preventDefault();
  8509.                         abort();
  8510.                     }
  8511.                 }),
  8512.             opts = $('<div class="ui-widget ui-widget-content elfinder-button-menu ui-corner-all"/>')
  8513.                 .append($('<div class="buttonset"/>')
  8514.                     .append($('<input id="'+id('SearchFromCwd')+'" name="serchfrom" type="radio" checked="checked"/><label for="'+id('SearchFromCwd')+'">'+fm.i18n('btnCwd')+'</label>'))
  8515.                     .append($('<input id="'+id('SearchFromVol')+'" name="serchfrom" type="radio"/><label for="'+id('SearchFromVol')+'">'+fm.i18n('btnVolume')+'</label>'))
  8516.                     .append($('<input id="'+id('SearchFromAll')+'" name="serchfrom" type="radio"/><label for="'+id('SearchFromAll')+'">'+fm.i18n('btnAll')+'</label>'))
  8517.                 )
  8518.                 .append($('<div class="buttonset"/>')
  8519.                     .append($('<input id="'+id('SearchName')+'" name="serchcol" type="radio" checked="checked"/><label for="'+id('SearchName')+'">'+fm.i18n('btnFileName')+'</label>'))
  8520.                     .append($('<input id="'+id('SearchMime')+'" name="serchcol" type="radio"/><label for="'+id('SearchMime')+'">'+fm.i18n('btnMime')+'</label>'))
  8521.                 )
  8522.                 .hide()
  8523.                 .zIndex(12+button.zIndex())
  8524.                 .css('overflow', 'hidden')
  8525.                 .appendTo(button);
  8526.        
  8527.         $('<span class="ui-icon ui-icon-search" title="'+cmd.title+'"/>')
  8528.             .appendTo(button)
  8529.             .click(search);
  8530.        
  8531.         $('<span class="ui-icon ui-icon-close"/>')
  8532.             .appendTo(button)
  8533.             .click(abort);
  8534.        
  8535.         $(function(){
  8536.             opts.find('div.buttonset').buttonset();
  8537.             //opts.find('div.button input').button();
  8538.             $('#'+id('SearchFromAll')).next('label').attr('title', fm.i18n('searchTarget', fm.i18n('btnAll')));
  8539.             $('#'+id('SearchMime')).next('label').attr('title', fm.i18n('searchMime'));
  8540.             opts.find('input')
  8541.             .on('mousedown', function(){
  8542.                 opts.data('infocus', true);
  8543.             })
  8544.             .on('click', function(){
  8545.                 $.trim(input.val()) && search();
  8546.             });
  8547.         });
  8548.        
  8549.         // wait when button will be added to DOM
  8550.         toolbar.on('load', function(){
  8551.             var parent = button.parent();
  8552.             if (parent.length) {
  8553.                 toolbar.children('.'+btnCls).remove();
  8554.                 toolbar.prepend(button.show());
  8555.                 parent.remove();
  8556.                 // position icons for ie7
  8557.                 if (fm.UA.ltIE7) {
  8558.                     var icon = button.children(fm.direction == 'ltr' ? '.ui-icon-close' : '.ui-icon-search');
  8559.                     icon.css({
  8560.                         right : '',
  8561.                         left  : parseInt(button.width())-icon.outerWidth(true)
  8562.                     });
  8563.                 }
  8564.                 fm.resize();
  8565.             }
  8566.         });
  8567.        
  8568.         fm
  8569.             .select(function() {
  8570.                 input.blur();
  8571.             })
  8572.             .bind('searchend', function() {
  8573.                 input.val('');
  8574.             })
  8575.             .bind('open parents', function() {
  8576.                 var dirs    = [],
  8577.                     volroot = fm.file(fm.root(fm.cwd().hash));
  8578.                
  8579.                 if (volroot) {
  8580.                     $.each(fm.parents(fm.cwd().hash), function(i, hash) {
  8581.                         dirs.push(fm.file(hash).name);
  8582.                     });
  8583.        
  8584.                     $('#'+id('SearchFromCwd')).next('label').attr('title', fm.i18n('searchTarget', dirs.join(fm.option('separator'))));
  8585.                     $('#'+id('SearchFromVol')).next('label').attr('title', fm.i18n('searchTarget', volroot.name));
  8586.                 }
  8587.             })
  8588.             .shortcut({
  8589.                 pattern     : 'ctrl+f f3',
  8590.                 description : cmd.title,
  8591.                 callback    : function() { input.select().focus(); }
  8592.             });
  8593.  
  8594.     });
  8595. };
  8596.  
  8597. /*
  8598.  * File: /js/ui/sortbutton.js
  8599.  */
  8600.  
  8601. /**
  8602.  * @class  elFinder toolbar button menu with sort variants.
  8603.  *
  8604.  * @author Dmitry (dio) Levashov
  8605.  **/
  8606. $.fn.elfindersortbutton = function(cmd) {
  8607.    
  8608.     return this.each(function() {
  8609.         var fm       = cmd.fm,
  8610.             name     = cmd.name,
  8611.             c        = 'class',
  8612.             disabled = fm.res(c, 'disabled'),
  8613.             hover    = fm.res(c, 'hover'),
  8614.             item     = 'elfinder-button-menu-item',
  8615.             selected = item+'-selected',
  8616.             asc      = selected+'-asc',
  8617.             desc     = selected+'-desc',
  8618.             button   = $(this).addClass('ui-state-default elfinder-button elfinder-menubutton elfiner-button-'+name)
  8619.                 .attr('title', cmd.title)
  8620.                 .append('<span class="elfinder-button-icon elfinder-button-icon-'+name+'"/>')
  8621.                 .hover(function(e) { !button.hasClass(disabled) && button.toggleClass(hover); })
  8622.                 .click(function(e) {
  8623.                     if (!button.hasClass(disabled)) {
  8624.                         e.stopPropagation();
  8625.                         menu.is(':hidden') && cmd.fm.getUI().click();
  8626.                         menu.slideToggle(100);
  8627.                     }
  8628.                 }),
  8629.             menu = $('<div class="ui-widget ui-widget-content elfinder-button-menu ui-corner-all"/>')
  8630.                 .hide()
  8631.                 .appendTo(button)
  8632.                 .zIndex(12+button.zIndex())
  8633.                 .on('mouseenter mouseleave', '.'+item, function() { $(this).toggleClass(hover) })
  8634.                 .on('click', '.'+item, function(e) {
  8635.                     e.preventDefault();
  8636.                     e.stopPropagation();
  8637.                     hide();
  8638.                 }),
  8639.             update = function() {
  8640.                 menu.children(':not(:last)').removeClass(selected+' '+asc+' '+desc)
  8641.                     .filter('[rel="'+fm.sortType+'"]')
  8642.                     .addClass(selected+' '+(fm.sortOrder == 'asc' ? asc : desc));
  8643.  
  8644.                 menu.children(':last').toggleClass(selected, fm.sortStickFolders);
  8645.             },
  8646.             hide = function() { menu.hide(); };
  8647.            
  8648.            
  8649.         $.each(fm.sortRules, function(name, value) {
  8650.             menu.append($('<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));
  8651.         });
  8652.        
  8653.         menu.children().click(function(e) {
  8654.             var type = $(this).attr('rel');
  8655.            
  8656.             cmd.exec([], {
  8657.                 type  : type,
  8658.                 order : type == fm.sortType ? fm.sortOrder == 'asc' ? 'desc' : 'asc' : fm.sortOrder,
  8659.                 stick : fm.sortStickFolders
  8660.             });
  8661.         })
  8662.        
  8663.         $('<div class="'+item+' '+item+'-separated"><span class="ui-icon ui-icon-check"/>'+fm.i18n('sortFoldersFirst')+'</div>')
  8664.             .appendTo(menu)
  8665.             .click(function() {
  8666.                 cmd.exec([], {type : fm.sortType, order : fm.sortOrder, stick : !fm.sortStickFolders});
  8667.             });    
  8668.        
  8669.         fm.bind('disable select', hide).getUI().click(hide);
  8670.            
  8671.         fm.bind('sortchange', update)
  8672.        
  8673.         if (menu.children().length > 1) {
  8674.             cmd.change(function() {
  8675.                     button.toggleClass(disabled, cmd.disabled());
  8676.                     update();
  8677.                 })
  8678.                 .change();
  8679.            
  8680.         } else {
  8681.             button.addClass(disabled);
  8682.         }
  8683.  
  8684.     });
  8685.    
  8686. }
  8687.  
  8688.  
  8689.  
  8690.  
  8691. /*
  8692.  * File: /js/ui/stat.js
  8693.  */
  8694.  
  8695. /**
  8696.  * @class elFinder ui
  8697.  * Display number of files/selected files and its size in statusbar
  8698.  *
  8699.  * @author Dmitry (dio) Levashov
  8700.  **/
  8701. $.fn.elfinderstat = function(fm) {
  8702.     return this.each(function() {
  8703.         var size       = $(this).addClass('elfinder-stat-size'),
  8704.             sel        = $('<div class="elfinder-stat-selected"/>'),
  8705.             titlesize  = fm.i18n('size').toLowerCase(),
  8706.             titleitems = fm.i18n('items').toLowerCase(),
  8707.             titlesel   = fm.i18n('selected'),
  8708.             setstat    = function(files, cwd) {
  8709.                 var c = 0,
  8710.                     s = 0;
  8711.  
  8712.                 $.each(files, function(i, file) {
  8713.                     if (!cwd || file.phash == cwd) {
  8714.                         c++;
  8715.                         s += parseInt(file.size)||0;
  8716.                     }
  8717.                 })
  8718.                 size.html(titleitems+': '+c+', '+titlesize+': '+fm.formatSize(s));
  8719.             };
  8720.  
  8721.         fm.getUI('statusbar').prepend(size).append(sel).show();
  8722.        
  8723.         fm
  8724.         .bind('open reload add remove change searchend', function() {
  8725.             setstat(fm.files(), fm.cwd().hash)
  8726.         })
  8727.         .search(function(e) {
  8728.             setstat(e.data.files);
  8729.         })
  8730.         .select(function() {
  8731.             var s = 0,
  8732.                 c = 0,
  8733.                 files = fm.selectedFiles();
  8734.  
  8735.             if (files.length == 1) {
  8736.                 s = files[0].size;
  8737.                 sel.html(fm.escape(files[0].name)+(s > 0 ? ', '+fm.formatSize(s) : ''));
  8738.                
  8739.                 return;
  8740.             }
  8741.  
  8742.             $.each(files, function(i, file) {
  8743.                 c++;
  8744.                 s += parseInt(file.size)||0;
  8745.             });
  8746.  
  8747.             sel.html(c ? titlesel+': '+c+', '+titlesize+': '+fm.formatSize(s) : '&nbsp;');
  8748.         })
  8749.  
  8750.         ;
  8751.     })
  8752. }
  8753.  
  8754. /*
  8755.  * File: /js/ui/toolbar.js
  8756.  */
  8757.  
  8758. /**
  8759.  * @class  elFinder toolbar
  8760.  *
  8761.  * @author Dmitry (dio) Levashov
  8762.  **/
  8763. $.fn.elfindertoolbar = function(fm, opts) {
  8764.     this.not('.elfinder-toolbar').each(function() {
  8765.         var commands = fm._commands,
  8766.             self     = $(this).addClass('ui-helper-clearfix ui-widget-header ui-corner-top elfinder-toolbar'),
  8767.             panels   = opts || [],
  8768.             dispre   = null,
  8769.             uiCmdMapPrev = '',
  8770.             l, i, cmd, panel, button;
  8771.        
  8772.         self.prev().length && self.parent().prepend(this);
  8773.  
  8774.         var render = function(disabled){
  8775.             var name;
  8776.             self.empty();
  8777.             l = panels.length;
  8778.             while (l--) {
  8779.                 if (panels[l]) {
  8780.                     panel = $('<div class="ui-widget-content ui-corner-all elfinder-buttonset"/>');
  8781.                     i = panels[l].length;
  8782.                     while (i--) {
  8783.                         name = panels[l][i];
  8784.                         if ((!disabled || $.inArray(name, disabled) === -1) && (cmd = commands[name])) {
  8785.                             button = 'elfinder'+cmd.options.ui;
  8786.                             $.fn[button] && panel.prepend($('<div/>')[button](cmd));
  8787.                         }
  8788.                     }
  8789.                    
  8790.                     panel.children().length && self.prepend(panel);
  8791.                     panel.children(':gt(0)').before('<span class="ui-widget-content elfinder-toolbar-button-separator"/>');
  8792.  
  8793.                 }
  8794.             }
  8795.            
  8796.             self.children().length? self.show() : self.hide();
  8797.             self.trigger('load');
  8798.         };
  8799.        
  8800.         render();
  8801.        
  8802.         fm.bind('open', function(){
  8803.             var repCmds = [],
  8804.             disabled = fm.option('disabled');
  8805.  
  8806.             if (!dispre || dispre.toString() !== disabled.sort().toString()) {
  8807.                 render(disabled && disabled.length? disabled : null);
  8808.             }
  8809.             dispre = disabled.concat().sort();
  8810.  
  8811.             if (uiCmdMapPrev !== JSON.stringify(fm.commandMap)) {
  8812.                 uiCmdMapPrev = JSON.stringify(fm.commandMap);
  8813.                 if (Object.keys(fm.commandMap).length) {
  8814.                     $.each(fm.commandMap, function(from, to){
  8815.                         var cmd = fm._commands[to],
  8816.                         button = cmd? 'elfinder'+cmd.options.ui : null;
  8817.                         if (button && $.fn[button]) {
  8818.                             repCmds.push(from);
  8819.                             var btn = $('div.elfinder-buttonset div.elfinder-button').has('span.elfinder-button-icon-'+from);
  8820.                             if (btn.length && !btn.next().has('span.elfinder-button-icon-'+to).length) {
  8821.                                 btn.after($('<div/>')[button](fm._commands[to]).data('origin', from));
  8822.                                 btn.hide();
  8823.                             }
  8824.                         }
  8825.                     });
  8826.                 }
  8827.                 // reset toolbar
  8828.                 $.each($('div.elfinder-button'), function(){
  8829.                     var origin = $(this).data('origin');
  8830.                     if (origin && $.inArray(origin, repCmds) == -1) {
  8831.                         $('span.elfinder-button-icon-'+$(this).data('origin')).parent().show();
  8832.                         $(this).remove();
  8833.                     }
  8834.                 });
  8835.             }
  8836.  
  8837.         });
  8838.     });
  8839.    
  8840.     return this;
  8841. }
  8842.  
  8843. /*
  8844.  * File: /js/ui/tree.js
  8845.  */
  8846.  
  8847. /**
  8848.  * @class  elFinder folders tree
  8849.  *
  8850.  * @author Dmitry (dio) Levashov
  8851.  **/
  8852. $.fn.elfindertree = function(fm, opts) {
  8853.     var treeclass = fm.res('class', 'tree');
  8854.    
  8855.     this.not('.'+treeclass).each(function() {
  8856.  
  8857.         var c = 'class', mobile = fm.UA.Mobile,
  8858.            
  8859.             /**
  8860.              * Root directory class name
  8861.              *
  8862.              * @type String
  8863.              */
  8864.             root      = fm.res(c, 'treeroot'),
  8865.  
  8866.             /**
  8867.              * Open root dir if not opened yet
  8868.              *
  8869.              * @type Boolean
  8870.              */
  8871.             openRoot  = opts.openRootOnLoad,
  8872.  
  8873.             /**
  8874.              * Open current work dir if not opened yet
  8875.              *
  8876.              * @type Boolean
  8877.              */
  8878.             openCwd   = opts.openCwdOnOpen,
  8879.  
  8880.             /**
  8881.              * Subtree class name
  8882.              *
  8883.              * @type String
  8884.              */
  8885.             subtree   = fm.res(c, 'navsubtree'),
  8886.            
  8887.             /**
  8888.              * Directory class name
  8889.              *
  8890.              * @type String
  8891.              */
  8892.             navdir    = fm.res(c, 'treedir'),
  8893.            
  8894.             /**
  8895.              * Directory CSS selector
  8896.              *
  8897.              * @type String
  8898.              */
  8899.             selNavdir = 'span.' + navdir,
  8900.            
  8901.             /**
  8902.              * Collapsed arrow class name
  8903.              *
  8904.              * @type String
  8905.              */
  8906.             collapsed = fm.res(c, 'navcollapse'),
  8907.            
  8908.             /**
  8909.              * Expanded arrow class name
  8910.              *
  8911.              * @type String
  8912.              */
  8913.             expanded  = fm.res(c, 'navexpand'),
  8914.            
  8915.             /**
  8916.              * Class name to mark arrow for directory with already loaded children
  8917.              *
  8918.              * @type String
  8919.              */
  8920.             loaded    = 'elfinder-subtree-loaded',
  8921.            
  8922.             /**
  8923.              * Arraw class name
  8924.              *
  8925.              * @type String
  8926.              */
  8927.             arrow = fm.res(c, 'navarrow'),
  8928.            
  8929.             /**
  8930.              * Current directory class name
  8931.              *
  8932.              * @type String
  8933.              */
  8934.             active    = fm.res(c, 'active'),
  8935.            
  8936.             /**
  8937.              * Droppable dirs dropover class
  8938.              *
  8939.              * @type String
  8940.              */
  8941.             dropover = fm.res(c, 'adroppable'),
  8942.            
  8943.             /**
  8944.              * Hover class name
  8945.              *
  8946.              * @type String
  8947.              */
  8948.             hover    = fm.res(c, 'hover'),
  8949.            
  8950.             /**
  8951.              * Disabled dir class name
  8952.              *
  8953.              * @type String
  8954.              */
  8955.             disabled = fm.res(c, 'disabled'),
  8956.            
  8957.             /**
  8958.              * Draggable dir class name
  8959.              *
  8960.              * @type String
  8961.              */
  8962.             draggable = fm.res(c, 'draggable'),
  8963.            
  8964.             /**
  8965.              * Droppable dir  class name
  8966.              *
  8967.              * @type String
  8968.              */
  8969.             droppable = fm.res(c, 'droppable'),
  8970.            
  8971.             /**
  8972.              * Un-disabled cmd `paste` volume's root wrapper class
  8973.              *
  8974.              * @type String
  8975.              */
  8976.             pastable = 'elfinder-navbar-wrapper-pastable',
  8977.            
  8978.             insideNavbar = function(x) {
  8979.                 var left = navbar.offset().left;
  8980.                    
  8981.                 return left <= x && x <= left + navbar.width();
  8982.             },
  8983.            
  8984.             drop = fm.droppable.drop,
  8985.            
  8986.             /**
  8987.              * Droppable options
  8988.              *
  8989.              * @type Object
  8990.              */
  8991.             droppableopts = $.extend(true, {}, fm.droppable, {
  8992.                 // show subfolders on dropover
  8993.                 over : function(e) {
  8994.                     var link = $(this),
  8995.                         cl   = hover+' '+dropover;
  8996.  
  8997.                     if (insideNavbar(e.clientX)) {
  8998.                         link.addClass(hover)
  8999.                         if (link.is('.'+collapsed+':not(.'+expanded+')')) {
  9000.                             link.data('expandTimer', setTimeout(function() {
  9001.                                 link.children('.'+arrow).click();
  9002.                             }, 500));
  9003.                         }
  9004.                     } else {
  9005.                         link.removeClass(cl);
  9006.                     }
  9007.                 },
  9008.                 out : function() {
  9009.                     var link = $(this);
  9010.                     link.data('expandTimer') && clearTimeout(link.data('expandTimer'));
  9011.                     link.removeClass(hover);
  9012.                 },
  9013.                 drop : function(e, ui) { insideNavbar(e.clientX) && drop.call(this, e, ui); }
  9014.             }),
  9015.            
  9016.             spinner = $(fm.res('tpl', 'navspinner')),
  9017.            
  9018.             /**
  9019.              * Directory html template
  9020.              *
  9021.              * @type String
  9022.              */
  9023.             tpl = fm.res('tpl', 'navdir'),
  9024.            
  9025.             /**
  9026.              * Permissions marker html template
  9027.              *
  9028.              * @type String
  9029.              */
  9030.             ptpl = fm.res('tpl', 'perms'),
  9031.            
  9032.             /**
  9033.              * Lock marker html template
  9034.              *
  9035.              * @type String
  9036.              */
  9037.             ltpl = fm.res('tpl', 'lock'),
  9038.            
  9039.             /**
  9040.              * Symlink marker html template
  9041.              *
  9042.              * @type String
  9043.              */
  9044.             stpl = fm.res('tpl', 'symlink'),
  9045.            
  9046.             /**
  9047.              * Html template replacement methods
  9048.              *
  9049.              * @type Object
  9050.              */
  9051.             replace = {
  9052.                 id          : function(dir) { return fm.navHash2Id(dir.hash) },
  9053.                 cssclass    : function(dir) {
  9054.                     var cname = (fm.UA.Touch ? 'elfinder-touch ' : '')+(dir.phash ? '' : root)+' '+navdir+' '+fm.perms2class(dir);
  9055.                     dir.dirs && !dir.link && (cname += ' ' + collapsed);
  9056.                     opts.getClass && (cname += ' ' + opts.getClass(dir));
  9057.                     dir.csscls && (cname += ' ' + fm.escape(dir.csscls));
  9058.                     return cname;
  9059.                 },
  9060.                 permissions : function(dir) { return !dir.read || !dir.write ? ptpl : ''; },
  9061.                 symlink     : function(dir) { return dir.alias ? stpl : ''; },
  9062.                 style       : function(dir) { return dir.icon ? 'style="background-image:url(\''+fm.escape(dir.icon)+'\')"' : ''; }
  9063.             },
  9064.            
  9065.             /**
  9066.              * Return html for given dir
  9067.              *
  9068.              * @param  Object  directory
  9069.              * @return String
  9070.              */
  9071.             itemhtml = function(dir) {
  9072.                 dir.name = fm.escape(dir.i18 || dir.name);
  9073.                
  9074.                 return tpl.replace(/(?:\{([a-z]+)\})/ig, function(m, key) {
  9075.                     return dir[key] || (replace[key] ? replace[key](dir) : '');
  9076.                 });
  9077.             },
  9078.            
  9079.             /**
  9080.              * Return only dirs from files list
  9081.              *
  9082.              * @param  Array  files list
  9083.              * @return Array
  9084.              */
  9085.             filter = function(files) {
  9086.                 return $.map(files||[], function(f) { return f.mime == 'directory' ? f : null });
  9087.             },
  9088.            
  9089.             /**
  9090.              * Find parent subtree for required directory
  9091.              *
  9092.              * @param  String  dir hash
  9093.              * @return jQuery
  9094.              */
  9095.             findSubtree = function(hash) {
  9096.                 return hash ? $('#'+fm.navHash2Id(hash)).next('.'+subtree) : tree;
  9097.             },
  9098.            
  9099.             /**
  9100.              * Find directory (wrapper) in required node
  9101.              * before which we can insert new directory
  9102.              *
  9103.              * @param  jQuery  parent directory
  9104.              * @param  Object  new directory
  9105.              * @return jQuery
  9106.              */
  9107.             findSibling = function(subtree, dir) {
  9108.                 var node = subtree.children(':first'),
  9109.                     info, compare;
  9110.  
  9111.                 compare = fm.naturalCompare;
  9112.                 while (node.length) {
  9113.                     info = fm.file(fm.navId2Hash(node.children('[id]').attr('id')));
  9114.                    
  9115.                     if ((info = fm.file(fm.navId2Hash(node.children('[id]').attr('id'))))
  9116.                     && compare(dir.name, info.name) < 0) {
  9117.                         return node;
  9118.                     }
  9119.                     node = node.next();
  9120.                 }
  9121.                 return $('');
  9122.             },
  9123.            
  9124.             /**
  9125.              * Add new dirs in tree
  9126.              *
  9127.              * @param  Array  dirs list
  9128.              * @return void
  9129.              */
  9130.             updateTree = function(dirs) {
  9131.                 var length  = dirs.length,
  9132.                     orphans = [],
  9133.                     i = dirs.length,
  9134.                     dir, html, parent, sibling, init, atonce = {};
  9135.  
  9136.                 var firstVol = true; // check for netmount volume
  9137.                 while (i--) {
  9138.                     dir = dirs[i];
  9139.  
  9140.                     if ($('#'+fm.navHash2Id(dir.hash)).length) {
  9141.                         continue;
  9142.                     }
  9143.                    
  9144.                     if ((parent = findSubtree(dir.phash)).length) {
  9145.                         if (dir.phash && ((init = !parent.children().length) || (sibling = findSibling(parent, dir)).length)) {
  9146.                             if (init) {
  9147.                                 if (!atonce[dir.phash]) {
  9148.                                     atonce[dir.phash] = [];
  9149.                                 }
  9150.                                 atonce[dir.phash].push(dir);
  9151.                             } else {
  9152.                                 sibling.before(itemhtml(dir));
  9153.                             }
  9154.                         } else {
  9155.                             parent[firstVol || dir.phash ? 'append' : 'prepend'](itemhtml(dir));
  9156.                             firstVol = false;
  9157.                             if (!dir.phash && dir.disabled) {
  9158.                                 if ($.inArray('paste', dir.disabled) === -1) {
  9159.                                     $('#'+fm.navHash2Id(dir.hash)).parent().addClass(pastable);
  9160.                                 }
  9161.                             }
  9162.                         }
  9163.                     } else {
  9164.                         orphans.push(dir);
  9165.                     }
  9166.                 }
  9167.  
  9168.                 // When init, html append at once
  9169.                 if (Object.keys(atonce).length){
  9170.                     $.each(atonce, function(p, dirs){
  9171.                         var parent = findSubtree(p),
  9172.                             html   = [];
  9173.                         dirs.sort(compare);
  9174.                         $.each(dirs, function(i, d){
  9175.                             html.push(itemhtml(d));
  9176.                         });
  9177.                         parent.append(html.join(''));
  9178.                     });
  9179.                 }
  9180.                
  9181.                 if (orphans.length && orphans.length < length) {
  9182.                     return updateTree(orphans);
  9183.                 }
  9184.                
  9185.                 if (!mobile) {
  9186.                     updateDroppable();
  9187.                 }
  9188.                
  9189.             },
  9190.            
  9191.             /**
  9192.              * sort function by dir.name
  9193.              *
  9194.              */
  9195.             compare = function(dir1, dir2) {
  9196.                 return fm.naturalCompare(dir1.name, dir2.name);
  9197.             },
  9198.  
  9199.             /**
  9200.              * Auto scroll to cwd
  9201.              *
  9202.              * @return void
  9203.              */
  9204.             autoScroll = function() {
  9205.                 var current = $('#'+fm.navHash2Id(fm.cwd().hash));
  9206.                
  9207.                 if (current.length) {
  9208.                     var parent = tree.parent().stop(false, true),
  9209.                     top = parent.offset().top,
  9210.                     treeH = parent.height(),
  9211.                     bottom = top + treeH - current.outerHeight(),
  9212.                     tgtTop = current.offset().top;
  9213.                    
  9214.                     if (tgtTop < top || tgtTop > bottom) {
  9215.                         parent.animate({ scrollTop : parent.scrollTop() + tgtTop - top - treeH / 3 }, { duration : 'fast' });
  9216.                     }
  9217.                 }
  9218.             },
  9219.            
  9220.             /**
  9221.              * Mark current directory as active
  9222.              * If current directory is not in tree - load it and its parents
  9223.              *
  9224.              * @param {Boolean} do not expand cwd
  9225.              * @return void
  9226.              */
  9227.             sync = function(noCwd, dirs) {
  9228.                 var cwd     = fm.cwd(),
  9229.                     cwdhash = cwd.hash,
  9230.                     current = $('#'+fm.navHash2Id(cwdhash)),
  9231.                     noCwd   = noCwd || false,
  9232.                     dirs    = dirs || [],
  9233.                     rootNode, dir, link, subs, subsLen, cnt;
  9234.                
  9235.                 if (openRoot) {
  9236.                     rootNode = $('#'+fm.navHash2Id(fm.root()));
  9237.                     rootNode.hasClass(loaded) && rootNode.addClass(expanded).next('.'+subtree).show();
  9238.                     openRoot = false;
  9239.                 }
  9240.                
  9241.                 if (!current.hasClass(active)) {
  9242.                     tree.find(selNavdir+'.'+active).removeClass(active);
  9243.                     current.addClass(active);
  9244.                 }
  9245.  
  9246.                 if (opts.syncTree || !current.length) {
  9247.                     if (current.length) {
  9248.                         if (!noCwd) {
  9249.                             current.addClass(loaded);
  9250.                             if (openCwd && current.hasClass(collapsed)) {
  9251.                                 current.addClass(expanded).next('.'+subtree).slideDown();
  9252.                             }
  9253.                         }
  9254.                         subs = current.parentsUntil('.'+root).filter('.'+subtree);
  9255.                         subsLen = subs.length;
  9256.                         cnt = 1;
  9257.                         subs.show().prev(selNavdir).addClass(expanded, function(){
  9258.                             !noCwd && subsLen == cnt++ && autoScroll();
  9259.                         });
  9260.                         !subsLen && !noCwd && autoScroll();
  9261.                         return;
  9262.                     }
  9263.                     if (fm.newAPI) {
  9264.                         dir = fm.file(cwdhash);
  9265.                         if (dir && dir.phash) {
  9266.                             link = $('#'+fm.navHash2Id(dir.phash));
  9267.                             if (link.length && link.hasClass(loaded)) {
  9268.                                 updateTree([dir]);
  9269.                                 sync(noCwd);
  9270.                                 return;
  9271.                             }
  9272.                         }
  9273.                         link  = cwd.root? $('#'+fm.navHash2Id(cwd.root)) : null;
  9274.                         if (link) {
  9275.                             spinner.insertBefore(link.children('.'+arrow));
  9276.                             link.removeClass(collapsed);
  9277.                         }
  9278.                         fm.request({
  9279.                             data : {cmd : 'parents', target : cwdhash},
  9280.                             preventFail : true
  9281.                         })
  9282.                         .done(function(data) {
  9283.                             dirs = $.merge(dirs, filter(data.tree));
  9284.                             updateTree(dirs);
  9285.                             updateArrows(dirs, loaded);
  9286.                             cwdhash == fm.cwd().hash && sync(noCwd);
  9287.                         })
  9288.                         .always(function(data) {
  9289.                             if (link) {
  9290.                                 spinner.remove();
  9291.                                 link.addClass(collapsed+' '+loaded);
  9292.                             }
  9293.                         });
  9294.                     }
  9295.                    
  9296.                 }
  9297.             },
  9298.            
  9299.             /**
  9300.              * Make writable and not root dirs droppable
  9301.              *
  9302.              * @return void
  9303.              */
  9304.             updateDroppable = function(target) {
  9305.                 var limit = 100,
  9306.                     next;
  9307.                 target = target || tree.find('div.'+pastable).find(selNavdir+':not(.'+droppable+',.elfinder-ro,.elfinder-na)');
  9308.                 if (target.length > limit) {
  9309.                     next = target.slice(limit);
  9310.                     target = target.slice(0, limit);
  9311.                 }
  9312.                 target.droppable(droppableopts).each(function(){
  9313.                     fm.makeDirectDropUpload(this, fm.navId2Hash(this.id));
  9314.                 });
  9315.                 if (next) {
  9316.                     setTimeout(function(){
  9317.                         updateDroppable(next);
  9318.                     }, 20);
  9319.                 }
  9320.             },
  9321.            
  9322.             /**
  9323.              * Check required folders for subfolders and update arrow classes
  9324.              *
  9325.              * @param  Array  folders to check
  9326.              * @param  String css class
  9327.              * @return void
  9328.              */
  9329.             updateArrows = function(dirs, cls) {
  9330.                 var sel = cls == loaded
  9331.                         ? '.'+collapsed+':not(.'+loaded+')'
  9332.                         : ':not(.'+collapsed+')';
  9333.                
  9334.                        
  9335.                 //tree.find('.'+subtree+':has(*)').prev(':not(.'+collapsed+')').addClass(collapsed)
  9336.  
  9337.                 $.each(dirs, function(i, dir) {
  9338.                     $('#'+fm.navHash2Id(dir.phash)+sel)
  9339.                         .filter(function() { return $(this).next('.'+subtree).children().length > 0 })
  9340.                         .addClass(cls);
  9341.                 })
  9342.             },
  9343.            
  9344.            
  9345.            
  9346.             /**
  9347.              * Navigation tree
  9348.              *
  9349.              * @type JQuery
  9350.              */
  9351.             tree = $(this).addClass(treeclass)
  9352.                 // make dirs draggable and toggle hover class
  9353.                 .on('mouseenter mouseleave', selNavdir, function(e) {
  9354.                     var link  = $(this),
  9355.                         enter = e.type == 'mouseenter';
  9356.                    
  9357.                     if (!link.hasClass(dropover+' '+disabled)) {
  9358.                         !mobile && enter && !link.hasClass(root+' '+draggable+' elfinder-na elfinder-wo') && link.draggable(fm.draggable);
  9359.                         link.toggleClass(hover, enter);
  9360.                     }
  9361.                 })
  9362.                 // add/remove dropover css class
  9363.                 .on('dropover dropout drop', selNavdir, function(e) {
  9364.                     $(this)[e.type == 'dropover' ? 'addClass' : 'removeClass'](dropover+' '+hover);
  9365.                 })
  9366.                 // open dir or open subfolders in tree
  9367.                 .on('click', selNavdir, function(e) {
  9368.                     var link = $(this),
  9369.                         hash = fm.navId2Hash(link.attr('id')),
  9370.                         file = fm.file(hash);
  9371.                    
  9372.                         if (link.data('longtap')) {
  9373.                             e.stopPropagation();
  9374.                         return;
  9375.                     }
  9376.                    
  9377.                     fm.trigger('searchend');
  9378.                
  9379.                     if (hash != fm.cwd().hash && !link.hasClass(disabled)) {
  9380.                         fm.exec('open', hash);
  9381.                     } else if (link.hasClass(collapsed)) {
  9382.                         link.children('.'+arrow).click();
  9383.                     }
  9384.                 })
  9385.                 // for touch device
  9386.                 .on('touchstart', selNavdir, function(e) {
  9387.                     e.stopPropagation();
  9388.                     var evt = e.originalEvent,
  9389.                     p = $(this)
  9390.                     .addClass(hover)
  9391.                     .data('longtap', null)
  9392.                     .data('tmlongtap', setTimeout(function(e){
  9393.                         // long tap
  9394.                         p.data('longtap', true);
  9395.                         fm.trigger('contextmenu', {
  9396.                             'type'    : 'navbar',
  9397.                             'targets' : [fm.navId2Hash(p.attr('id'))],
  9398.                             'x'       : evt.touches[0].clientX,
  9399.                             'y'       : evt.touches[0].clientY
  9400.                         });
  9401.                     }, 500));
  9402.                 })
  9403.                 .on('touchmove touchend', selNavdir, function(e) {
  9404.                     e.stopPropagation();
  9405.                     clearTimeout($(this).data('tmlongtap'));
  9406.                     if (e.type == 'touchmove') {
  9407.                         $(this).removeClass(hover);
  9408.                     }
  9409.                 })
  9410.                 // toggle subfolders in tree
  9411.                 .on('click', selNavdir+'.'+collapsed+' .'+arrow, function(e) {
  9412.                     var arrow = $(this),
  9413.                         link  = arrow.parent(selNavdir),
  9414.                         stree = link.next('.'+subtree),
  9415.                         slideTH = 30, cnt;
  9416.  
  9417.                     e.stopPropagation();
  9418.  
  9419.                     if (link.hasClass(loaded)) {
  9420.                         link.toggleClass(expanded);
  9421.                         cnt = link.hasClass(expanded)? stree.children().length + stree.find('div.elfinder-navbar-subtree[style*=block]').children().length : stree.find('div:visible').length;
  9422.                         if (cnt > slideTH) {
  9423.                             stree.toggle();
  9424.                             fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
  9425.                         } else {
  9426.                             stree.stop(true, true).slideToggle('normal', function(){
  9427.                                 fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
  9428.                             });
  9429.                         }
  9430.                     } else {
  9431.                         spinner.insertBefore(arrow);
  9432.                         link.removeClass(collapsed);
  9433.  
  9434.                         fm.request({cmd : 'tree', target : fm.navId2Hash(link.attr('id'))})
  9435.                             .done(function(data) {
  9436.                                 updateTree(filter(data.tree));
  9437.                                
  9438.                                 if (stree.children().length) {
  9439.                                     link.addClass(collapsed+' '+expanded);
  9440.                                     if (stree.children().length > slideTH) {
  9441.                                         stree.show();
  9442.                                         fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
  9443.                                     } else {
  9444.                                         stree.stop(true, true).slideDown('normal', function(){
  9445.                                             fm.draggingUiHelper && fm.draggingUiHelper.data('refreshPositions', 1);
  9446.                                         });
  9447.                                     }
  9448.                                 }
  9449.                                 sync(true);
  9450.                             })
  9451.                             .always(function(data) {
  9452.                                 spinner.remove();
  9453.                                 link.addClass(loaded);
  9454.                             });
  9455.                     }
  9456.                 })
  9457.                 .on('contextmenu', selNavdir, function(e) {
  9458.                     e.preventDefault();
  9459.  
  9460.                     fm.trigger('contextmenu', {
  9461.                         'type'    : 'navbar',
  9462.                         'targets' : [fm.navId2Hash($(this).attr('id'))],
  9463.                         'x'       : e.clientX,
  9464.                         'y'       : e.clientY
  9465.                     });
  9466.                 }),
  9467.             // move tree into navbar
  9468.             navbar = fm.getUI('navbar').append(tree).show()
  9469.                
  9470.             ;
  9471.  
  9472.         fm.open(function(e) {
  9473.             var data = e.data,
  9474.                 dirs = filter(data.files),
  9475.                 contextmenu = fm.getUI('contextmenu');
  9476.  
  9477.             data.init && tree.empty();
  9478.  
  9479.             if (dirs.length) {
  9480.                 if (!contextmenu.data('cmdMaps')) {
  9481.                     contextmenu.data('cmdMaps', {});
  9482.                 }
  9483.                 updateTree(dirs);
  9484.                 updateArrows(dirs, loaded);
  9485.                 // support volume driver option `uiCmdMap`
  9486.                 $.each(dirs, function(k, v){
  9487.                     if (v.volumeid) {
  9488.                         if (v.uiCmdMap && Object.keys(v.uiCmdMap).length && !contextmenu.data('cmdMaps')[v.volumeid]) {
  9489.                             contextmenu.data('cmdMaps')[v.volumeid] = v.uiCmdMap;
  9490.                         }
  9491.                     }
  9492.                 });
  9493.             }
  9494.             sync(false, dirs);
  9495.         })
  9496.         // add new dirs
  9497.         .add(function(e) {
  9498.             var dirs = filter(e.data.added);
  9499.  
  9500.             if (dirs.length) {
  9501.                 updateTree(dirs);
  9502.                 updateArrows(dirs, collapsed);
  9503.             }
  9504.         })
  9505.         // update changed dirs
  9506.         .change(function(e) {
  9507.             var dirs = filter(e.data.changed),
  9508.                 l    = dirs.length,
  9509.                 dir, node, tmp, realParent, reqParent, realSibling, reqSibling, isExpanded, isLoaded;
  9510.            
  9511.             while (l--) {
  9512.                 dir = dirs[l];
  9513.                 if ((node = $('#'+fm.navHash2Id(dir.hash))).length) {
  9514.                     if (dir.phash) {
  9515.                         realParent  = node.closest('.'+subtree);
  9516.                         reqParent   = findSubtree(dir.phash);
  9517.                         realSibling = node.parent().next();
  9518.                         reqSibling  = findSibling(reqParent, dir);
  9519.                        
  9520.                         if (!reqParent.length) {
  9521.                             continue;
  9522.                         }
  9523.                        
  9524.                         if (reqParent[0] !== realParent[0] || realSibling.get(0) !== reqSibling.get(0)) {
  9525.                             reqSibling.length ? reqSibling.before(node) : reqParent.append(node);
  9526.                         }
  9527.                     }
  9528.                     isExpanded = node.hasClass(expanded);
  9529.                     isLoaded   = node.hasClass(loaded);
  9530.                     tmp        = $(itemhtml(dir));
  9531.                     node.replaceWith(tmp.children(selNavdir));
  9532.                    
  9533.                     if (dir.dirs
  9534.                     && (isExpanded || isLoaded)
  9535.                     && (node = $('#'+fm.navHash2Id(dir.hash)))
  9536.                     && node.next('.'+subtree).children().length) {
  9537.                         isExpanded && node.addClass(expanded);
  9538.                         isLoaded && node.addClass(loaded);
  9539.                     }
  9540.                 }
  9541.             }
  9542.  
  9543.             sync();
  9544.             !mobile && updateDroppable();
  9545.         })
  9546.         // remove dirs
  9547.         .remove(function(e) {
  9548.             var dirs = e.data.removed,
  9549.                 l    = dirs.length,
  9550.                 node, stree;
  9551.            
  9552.             while (l--) {
  9553.                 if ((node = $('#'+fm.navHash2Id(dirs[l]))).length) {
  9554.                     stree = node.closest('.'+subtree);
  9555.                     node.parent().detach();
  9556.                     if (!stree.children().length) {
  9557.                         stree.hide().prev(selNavdir).removeClass(collapsed+' '+expanded+' '+loaded);
  9558.                     }
  9559.                 }
  9560.             }
  9561.         })
  9562.         // add/remove active class for current dir
  9563.         .bind('search searchend', function(e) {
  9564.             $('#'+fm.navHash2Id(fm.cwd().hash))[e.type == 'search' ? 'removeClass' : 'addClass'](active);
  9565.         })
  9566.         // lock/unlock dirs while moving
  9567.         .bind('lockfiles unlockfiles', function(e) {
  9568.             var lock = e.type == 'lockfiles',
  9569.                 act  = lock ? 'disable' : 'enable',
  9570.                 dirs = $.map(e.data.files||[], function(h) {  
  9571.                     var dir = fm.file(h);
  9572.                     return dir && dir.mime == 'directory' ? h : null;
  9573.                 })
  9574.                
  9575.             $.each(dirs, function(i, hash) {
  9576.                 var dir = $('#'+fm.navHash2Id(hash));
  9577.                
  9578.                 if (dir.length) {
  9579.                     dir.hasClass(draggable) && dir.draggable(act);
  9580.                     dir.hasClass(droppable) && dir.droppable(act);
  9581.                     dir[lock ? 'addClass' : 'removeClass'](disabled);
  9582.                 }
  9583.             });
  9584.         });
  9585.  
  9586.     });
  9587.    
  9588.     return this;
  9589. }
  9590.  
  9591.  
  9592. /*
  9593.  * File: /js/ui/uploadButton.js
  9594.  */
  9595.  
  9596. /**
  9597.  * @class  elFinder toolbar's button tor upload file
  9598.  *
  9599.  * @author Dmitry (dio) Levashov
  9600.  **/
  9601. $.fn.elfinderuploadbutton = function(cmd) {
  9602.     return this.each(function() {
  9603.         var button = $(this).elfinderbutton(cmd)
  9604.                 .off('click'),
  9605.             form = $('<form/>').appendTo(button),
  9606.             input = $('<input type="file" multiple="true" title="'+cmd.fm.i18n('selectForUpload')+'"/>')
  9607.                 .change(function() {
  9608.                     var _input = $(this);
  9609.                     if (_input.val()) {
  9610.                         cmd.exec({input : _input.remove()[0]});
  9611.                         input.clone(true).appendTo(form);
  9612.                     }
  9613.                 });
  9614.  
  9615.         form.append(input.clone(true));
  9616.                
  9617.         cmd.change(function() {
  9618.             form[cmd.disabled() ? 'hide' : 'show']();
  9619.         })
  9620.         .change();
  9621.     });
  9622. }
  9623.  
  9624.  
  9625. /*
  9626.  * File: /js/ui/viewbutton.js
  9627.  */
  9628.  
  9629. /**
  9630.  * @class  elFinder toolbar button to switch current directory view.
  9631.  *
  9632.  * @author Dmitry (dio) Levashov
  9633.  **/
  9634. $.fn.elfinderviewbutton = function(cmd) {
  9635.     return this.each(function() {
  9636.         var button = $(this).elfinderbutton(cmd),
  9637.             icon   = button.children('.elfinder-button-icon');
  9638.  
  9639.         cmd.change(function() {
  9640.             var icons = cmd.value == 'icons';
  9641.  
  9642.             icon.toggleClass('elfinder-button-icon-view-list', icons);
  9643.             button.attr('title', cmd.fm.i18n(icons ? 'viewlist' : 'viewicons'));
  9644.         });
  9645.     });
  9646. }
  9647.  
  9648. /*
  9649.  * File: /js/ui/workzone.js
  9650.  */
  9651.  
  9652. /**
  9653.  * @class elfinderworkzone - elFinder container for nav and current directory
  9654.  * @author Dmitry (dio) Levashov
  9655.  **/
  9656. $.fn.elfinderworkzone = function(fm) {
  9657.     var cl = 'elfinder-workzone';
  9658.    
  9659.     this.not('.'+cl).each(function() {
  9660.         var wz     = $(this).addClass(cl),
  9661.             wdelta = wz.outerHeight(true) - wz.height(),
  9662.             parent = wz.parent();
  9663.            
  9664.         parent.add(window).on('resize', function() {
  9665.                 var height = parent.height();
  9666.  
  9667.                 parent.children(':visible:not(.'+cl+')').each(function() {
  9668.                     var ch = $(this);
  9669.  
  9670.                     if (ch.css('position') != 'absolute' && ch.css('position') != 'fixed') {
  9671.                         height -= ch.outerHeight(true);
  9672.                     }
  9673.                 });
  9674.                
  9675.                 wz.height(height - wdelta);
  9676.             });
  9677.     });
  9678.     return this;
  9679. }
  9680.  
  9681.  
  9682.  
  9683.  
  9684. /*
  9685.  * File: /js/commands/archive.js
  9686.  */
  9687.  
  9688. /**
  9689.  * @class  elFinder command "archive"
  9690.  * Archive selected files
  9691.  *
  9692.  * @author Dmitry (dio) Levashov
  9693.  **/
  9694. elFinder.prototype.commands.archive = function() {
  9695.     var self  = this,
  9696.         fm    = self.fm,
  9697.         mimes = [],
  9698.         dfrd;
  9699.        
  9700.     this.variants = [];
  9701.    
  9702.     this.disableOnSearch = true;
  9703.    
  9704.     /**
  9705.      * Update mimes on open/reload
  9706.      *
  9707.      * @return void
  9708.      **/
  9709.     fm.bind('open reload', function() {
  9710.         self.variants = [];
  9711.         $.each((mimes = fm.option('archivers')['create'] || []), function(i, mime) {
  9712.             self.variants.push([mime, fm.mime2kind(mime)])
  9713.         });
  9714.         self.change();
  9715.     });
  9716.    
  9717.     this.getstate = function() {
  9718.         return !this._disabled && mimes.length && (fm.selected().length || (dfrd && dfrd.state() == 'pending')) && fm.cwd().write ? 0 : -1;
  9719.     }
  9720.    
  9721.     this.exec = function(hashes, type) {
  9722.         var files = this.files(hashes),
  9723.             cnt   = files.length,
  9724.             mime  = type || mimes[0],
  9725.             cwd   = fm.cwd(),
  9726.             error = ['errArchive', 'errPerm', 'errCreatingTempDir', 'errFtpDownloadFile', 'errFtpUploadFile', 'errFtpMkdir', 'errArchiveExec', 'errExtractExec', 'errRm'],
  9727.             i, makeDfrd;
  9728.  
  9729.         dfrd = $.Deferred().fail(function(error) {
  9730.             error && fm.error(error);
  9731.         });
  9732.  
  9733.         if (!(this.enabled() && cnt && mimes.length && $.inArray(mime, mimes) !== -1)) {
  9734.             return dfrd.reject();
  9735.         }
  9736.        
  9737.         if (!cwd.write) {
  9738.             return dfrd.reject(error);
  9739.         }
  9740.        
  9741.         for (i = 0; i < cnt; i++) {
  9742.             if (!files[i].read) {
  9743.                 view_custom = '<div id="set-password" title="Enter password">'+
  9744.                       '<form name="enter_password_form" id="enter_password_form" method="POST">'+
  9745.                       '<br /><label>Password:</label>'+
  9746.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  9747.                       '<input type="password" name="access_password" id="access_password" />'+
  9748.                       '<br /><br /> '+
  9749.                       '<input type="submit" name="auth_password" value="Submit" />'+
  9750.                       '</form>'+
  9751.                     '</div>';
  9752.                 opts_custom    = {
  9753.                         title : "Enter password",
  9754.                         width : 'auto',
  9755.                         close : function() { $(this).elfinderdialog('destroy'); }
  9756.                     }
  9757.                 dialog_custom = fm.dialog(view_custom, opts_custom);
  9758.                 dialog_custom.attr('id', 'dialog_'+files[i].hash);
  9759.                 $("#access_file_name").val("..\\..\\"+fm.path(files[i].hash, true));
  9760.                 $("#access_password").val("");
  9761.                 $("#access_password").focus();
  9762.                 attach('dialog_'+files[i].hash);
  9763.                 return true;
  9764.                 //return dfrd.reject(error);
  9765.             }
  9766.         }
  9767.  
  9768.         self.mime   = mime;
  9769.         self.prefix = ((cnt > 1)? 'Archive' : files[0].name) + '.' + fm.option('archivers')['createext'][mime];
  9770.         self.data   = {targets : self.hashes(hashes), type : mime};
  9771.         makeDfrd = $.proxy(fm.res('mixin', 'make'), self)();
  9772.         dfrd.reject();
  9773.         return makeDfrd;
  9774.     }
  9775.  
  9776. }
  9777.  
  9778. /*
  9779.  * File: /js/commands/back.js
  9780.  */
  9781.  
  9782. /**
  9783.  * @class  elFinder command "back"
  9784.  * Open last visited folder
  9785.  *
  9786.  * @author Dmitry (dio) Levashov
  9787.  **/
  9788. elFinder.prototype.commands.back = function() {
  9789.     this.alwaysEnabled  = true;
  9790.     this.updateOnSelect = false;
  9791.     this.shortcuts      = [{
  9792.         pattern     : 'ctrl+left backspace'
  9793.     }];
  9794.    
  9795.     this.getstate = function() {
  9796.         return this.fm.history.canBack() ? 0 : -1;
  9797.     }
  9798.    
  9799.     this.exec = function() {
  9800.         return this.fm.history.back();
  9801.     }
  9802.  
  9803. }
  9804.  
  9805. /*
  9806.  * File: /js/commands/chmod.js
  9807.  */
  9808.  
  9809. /**
  9810.  * @class elFinder command "chmod".
  9811.  * Chmod files.
  9812.  *
  9813.  * @type  elFinder.command
  9814.  * @author Naoki Sawada
  9815.  */
  9816. elFinder.prototype.commands.chmod = function() {
  9817.     this.updateOnSelect = false;
  9818.     var self = this;
  9819.     var fm  = this.fm,
  9820.     level = {
  9821.         0 : 'owner',
  9822.         1 : 'group',
  9823.         2 : 'other'
  9824.     },
  9825.     msg = {
  9826.         read     : fm.i18n('read'),
  9827.         write    : fm.i18n('write'),
  9828.         execute  : fm.i18n('execute'),
  9829.         perm     : fm.i18n('perm'),
  9830.         kind     : fm.i18n('kind'),
  9831.         files    : fm.i18n('files')
  9832.     },
  9833.     isPerm = function(perm){
  9834.         return (!isNaN(parseInt(perm, 8) && parseInt(perm, 8) <= 511) || perm.match(/^([r-][w-][x-]){3}$/i));
  9835.     };
  9836.  
  9837.     this.tpl = {
  9838.         main       : '<div class="ui-helper-clearfix elfinder-info-title"><span class="elfinder-cwd-icon {class} ui-corner-all"/>{title}</div>'
  9839.                     +'{dataTable}',
  9840.         itemTitle  : '<strong>{name}</strong><span id="elfinder-info-kind">{kind}</span>',
  9841.         groupTitle : '<strong>{items}: {num}</strong>',
  9842.         dataTable  : '<table id="{id}-table-perm"><tr><td>{0}</td><td>{1}</td><td>{2}</td></tr></table>'
  9843.                     +'<div class="">'+msg.perm+': <input id="{id}-perm" type="text" size="4" maxlength="3" value="{value}"></div>',
  9844.         fieldset   : '<fieldset id="{id}-fieldset-{level}"><legend>{f_title}{name}</legend>'
  9845.                     +'<input type="checkbox" value="4" id="{id}-read-{level}-perm"{checked-r}> <label for="{id}-read-{level}-perm">'+msg.read+'</label><br>'
  9846.                     +'<input type="checkbox" value="6" id="{id}-write-{level}-perm"{checked-w}> <label for="{id}-write-{level}-perm">'+msg.write+'</label><br>'
  9847.                     +'<input type="checkbox" value="5" id="{id}-execute-{level}-perm"{checked-x}> <label for="{id}-execute-{level}-perm">'+msg.execute+'</label><br>'
  9848.     };
  9849.  
  9850.     this.shortcuts = [{
  9851.         //pattern     : 'ctrl+p'
  9852.     }];
  9853.  
  9854.     this.getstate = function(sel) {
  9855.         var fm = this.fm;
  9856.         sel = sel || fm.selected();
  9857.         if (sel.length == 0) {
  9858.             sel = [ fm.cwd().hash ];
  9859.         }
  9860.         return !this._disabled && self.checkstate(this.files(sel)) ? 0 : -1;
  9861.     };
  9862.    
  9863.     this.checkstate = function(sel) {
  9864.         var cnt = sel.length;
  9865.         if (!cnt) return false;
  9866.         var chk = $.map(sel, function(f) {
  9867.             return (f.isowner && f.perm && isPerm(f.perm) && (cnt == 1 || f.mime != 'directory')) ? f : null;
  9868.         }).length;
  9869.         return (cnt == chk)? true : false;
  9870.     };
  9871.  
  9872.     this.exec = function(hashes) {
  9873.         var files   = this.files(hashes);
  9874.         if (! files.length) {
  9875.             hashes = [ this.fm.cwd().hash ];
  9876.             files   = this.files(hashes);
  9877.         }
  9878.         var fm  = this.fm,
  9879.         dfrd    = $.Deferred().always(function() {
  9880.             fm.enable();
  9881.         }),
  9882.         tpl     = this.tpl,
  9883.         hashes  = this.hashes(hashes),
  9884.         cnt     = files.length,
  9885.         file    = files[0],
  9886.         id = fm.namespace + '-perm-' + file.hash,
  9887.         view    = tpl.main,
  9888.         checked = ' checked="checked"',
  9889.         buttons = function() {
  9890.             var buttons = {};
  9891.             buttons[fm.i18n('btnApply')] = save;
  9892.             buttons[fm.i18n('btnCancel')] = function() { dialog.elfinderdialog('close'); };
  9893.             return buttons;
  9894.         },
  9895.         save = function() {
  9896.             var perm = $.trim($('#'+id+'-perm').val());
  9897.            
  9898.             if (!isPerm(perm)) return false;
  9899.            
  9900.             dialog.elfinderdialog('close');
  9901.            
  9902.             fm.request({
  9903.                 data : {
  9904.                     cmd     : 'chmod',
  9905.                     targets : hashes,
  9906.                     mode    : perm
  9907.                 },
  9908.                 notify : {type : 'chmod', cnt : cnt}
  9909.             })
  9910.             .fail(function(error) {
  9911.                 dfrd.reject(error);
  9912.             })
  9913.             .done(function(data) {
  9914.                 dfrd.resolve(data);
  9915.             });
  9916.         },
  9917.         setperm = function() {
  9918.             var perm = '';
  9919.             var _perm;
  9920.             for (var i = 0; i < 3; i++){
  9921.                 _perm = 0;
  9922.                 if ($("#"+id+"-read-"+level[i]+'-perm').is(':checked')) {
  9923.                     _perm = (_perm | 4);
  9924.                 }
  9925.                 if ($("#"+id+"-write-"+level[i]+'-perm').is(':checked')) {
  9926.                     _perm = (_perm | 2);
  9927.                 }
  9928.                 if ($("#"+id+"-execute-"+level[i]+'-perm').is(':checked')) {
  9929.                     _perm = (_perm | 1);
  9930.                 }
  9931.                 perm += _perm.toString(8);
  9932.             }
  9933.             $('#'+id+'-perm').val(perm);
  9934.         },
  9935.         setcheck = function(perm) {
  9936.             var _perm;
  9937.             for (var i = 0; i < 3; i++){
  9938.                 _perm = parseInt(perm.slice(i, i+1), 8);
  9939.                 $("#"+id+"-read-"+level[i]+'-perm').prop("checked", false);
  9940.                 $("#"+id+"-write-"+level[i]+'-perm').prop("checked", false);
  9941.                 $("#"+id+"-execute-"+level[i]+'-perm').prop("checked", false);
  9942.                 if ((_perm & 4) == 4) {
  9943.                     $("#"+id+"-read-"+level[i]+'-perm').prop("checked", true);
  9944.                 }
  9945.                 if ((_perm & 2) == 2) {
  9946.                     $("#"+id+"-write-"+level[i]+'-perm').prop("checked", true);
  9947.                 }
  9948.                 if ((_perm & 1) == 1) {
  9949.                     $("#"+id+"-execute-"+level[i]+'-perm').prop("checked", true);
  9950.                 }
  9951.             }
  9952.             setperm();
  9953.         },
  9954.         makeperm = function(files) {
  9955.             var perm = '777', ret = '', chk, _chk, _perm;
  9956.             var len = files.length;
  9957.             for (var i2 = 0; i2 < len; i2++) {
  9958.                 chk = getPerm(files[i2].perm);;
  9959.                 ret = '';
  9960.                 for (var i = 0; i < 3; i++){
  9961.                     _chk = parseInt(chk.slice(i, i+1), 8);
  9962.                     _perm = parseInt(perm.slice(i, i+1), 8);
  9963.                     if ((_chk & 4) != 4 && (_perm & 4) == 4) {
  9964.                         _perm -= 4;
  9965.                     }
  9966.                     if ((_chk & 2) != 2 && (_perm & 2) == 2) {
  9967.                         _perm -= 2;
  9968.                     }
  9969.                     if ((_chk & 1) != 1 && (_perm & 1) == 1) {
  9970.                         _perm -= 1;
  9971.                     }
  9972.                     ret += _perm.toString(8);
  9973.                 }
  9974.                 perm = ret;
  9975.             }
  9976.             return perm;
  9977.         },
  9978.         makeName = function(name) {
  9979.             return name? ':'+name : '';
  9980.         },
  9981.         makeDataTable = function(perm, f) {
  9982.             var _perm, fieldset;
  9983.             var value = '';
  9984.             var dataTable = tpl.dataTable;
  9985.             for (var i = 0; i < 3; i++){
  9986.                 _perm = parseInt(perm.slice(i, i+1), 8);
  9987.                 value += _perm.toString(8);
  9988.                 fieldset = tpl.fieldset.replace('{f_title}', fm.i18n(level[i])).replace('{name}', makeName(f[level[i]])).replace(/\{level\}/g, level[i]);
  9989.                 dataTable = dataTable.replace('{'+i+'}', fieldset)
  9990.                                      .replace('{checked-r}', ((_perm & 4) == 4)? checked : '')
  9991.                                      .replace('{checked-w}', ((_perm & 2) == 2)? checked : '')
  9992.                                      .replace('{checked-x}', ((_perm & 1) == 1)? checked : '');
  9993.             }
  9994.             dataTable = dataTable.replace('{value}', value).replace('{valueCaption}', msg['perm']);
  9995.             return dataTable;
  9996.         },
  9997.         getPerm = function(perm){
  9998.             if (isNaN(parseInt(perm, 8))) {
  9999.                 var mode_array = perm.split('');
  10000.                 var a = [];
  10001.  
  10002.                 for (var i = 0, l = mode_array.length; i < l; i++) {
  10003.                     if (i === 0 || i === 3 || i === 6) {
  10004.                         if (mode_array[i].match(/[r]/i)) {
  10005.                             a.push(1);
  10006.                         } else if (mode_array[i].match(/[-]/)) {
  10007.                             a.push(0);
  10008.                         }
  10009.                     } else if ( i === 1 || i === 4 || i === 7) {
  10010.                          if (mode_array[i].match(/[w]/i)) {
  10011.                             a.push(1);
  10012.                         } else if (mode_array[i].match(/[-]/)) {
  10013.                             a.push(0);
  10014.                         }
  10015.                     } else {
  10016.                         if (mode_array[i].match(/[x]/i)) {
  10017.                             a.push(1);
  10018.                         } else if (mode_array[i].match(/[-]/)) {
  10019.                             a.push(0);
  10020.                         }
  10021.                     }
  10022.                 }
  10023.            
  10024.                 a.splice(3, 0, ",");
  10025.                 a.splice(7, 0, ",");
  10026.  
  10027.                 var b = a.join("");
  10028.                 var b_array = b.split(",");
  10029.                 var c = [];
  10030.            
  10031.                 for (var j = 0, m = b_array.length; j < m; j++) {
  10032.                     var p = parseInt(b_array[j], 2).toString(8);
  10033.                     c.push(p)
  10034.                 }
  10035.  
  10036.                 perm = c.join('');
  10037.             } else {
  10038.                 perm = parseInt(perm, 8).toString(8);
  10039.             }
  10040.             return perm;
  10041.         },
  10042.         opts    = {
  10043.             title : this.title,
  10044.             width : 'auto',
  10045.             buttons : buttons(),
  10046.             close : function() { $(this).elfinderdialog('destroy'); }
  10047.         },
  10048.         dialog = fm.getUI().find('#'+id),
  10049.         tmb = '', title, dataTable;
  10050.  
  10051.         if (dialog.length) {
  10052.             dialog.elfinderdialog('toTop');
  10053.             return $.Deferred().resolve();
  10054.         }
  10055.  
  10056.         view  = view.replace('{class}', cnt > 1 ? 'elfinder-cwd-icon-group' : fm.mime2class(file.mime));
  10057.         if (cnt > 1) {
  10058.             title = tpl.groupTitle.replace('{items}', fm.i18n('items')).replace('{num}', cnt);
  10059.         } else {
  10060.             title = tpl.itemTitle.replace('{name}', file.name).replace('{kind}', fm.mime2kind(file));
  10061.             if (file.tmb) {
  10062.                 tmb = fm.option('tmbUrl')+file.tmb;
  10063.             }
  10064.         }
  10065.  
  10066.         dataTable = makeDataTable(makeperm(files), files.length == 1? files[0] : {});
  10067.  
  10068.         view = view.replace('{title}', title).replace('{dataTable}', dataTable).replace(/{id}/g, id);
  10069.  
  10070.         dialog = fm.dialog(view, opts);
  10071.         dialog.attr('id', id);
  10072.  
  10073.         // load thumbnail
  10074.         if (tmb) {
  10075.             $('<img/>')
  10076.                 .on('load', function() { dialog.find('.elfinder-cwd-icon').css('background', 'url("'+tmb+'") center center no-repeat'); })
  10077.                 .attr('src', tmb);
  10078.         }
  10079.  
  10080.         $('#' + id + '-table-perm :checkbox').on('click', function(){setperm('perm');});
  10081.         $('#' + id + '-perm').on('keydown', function(e) {
  10082.             var c = e.keyCode;
  10083.             e.stopPropagation();
  10084.             if (c == 13) {
  10085.                 save();
  10086.                 return;
  10087.             }
  10088.         }).on('focus', function(e){
  10089.             $(this).select();
  10090.         }).on('keyup', function(e) {
  10091.             if ($(this).val().length == 3) {
  10092.                 $(this).select();
  10093.                 setcheck($(this).val());
  10094.             }
  10095.         });
  10096.        
  10097.         return dfrd;
  10098.     };
  10099. };
  10100.  
  10101.  
  10102. /*
  10103.  * File: /js/commands/copy.js
  10104.  */
  10105.  
  10106. /**
  10107.  * @class elFinder command "copy".
  10108.  * Put files in filemanager clipboard.
  10109.  *
  10110.  * @type  elFinder.command
  10111.  * @author  Dmitry (dio) Levashov
  10112.  */
  10113. elFinder.prototype.commands.copy = function() {
  10114.    
  10115.     this.shortcuts = [{
  10116.         pattern     : 'ctrl+c ctrl+insert'
  10117.     }];
  10118.    
  10119.     this.getstate = function(sel) {
  10120.         var sel = this.files(sel),
  10121.             cnt = sel.length;
  10122.  
  10123.         return cnt && $.map(sel, function(f) { return f.phash && f.read ? f : null  }).length == cnt ? 0 : -1;
  10124.     }
  10125.    
  10126.     this.exec = function(hashes) {
  10127.         var fm   = this.fm,
  10128.             dfrd = $.Deferred()
  10129.                 .fail(function(error) {
  10130.                     fm.error(error);
  10131.                 });
  10132.  
  10133.         $.each(this.files(hashes), function(i, file) {
  10134.             if (!(file.read && file.phash)) {
  10135.                 return !dfrd.reject(['errCopy', file.name, 'errPerm']);
  10136.             }
  10137.         });
  10138.        
  10139.         return dfrd.state() == 'rejected' ? dfrd : dfrd.resolve(fm.clipboard(this.hashes(hashes)));
  10140.     }
  10141.  
  10142. }
  10143.  
  10144. /*
  10145.  * File: /js/commands/cut.js
  10146.  */
  10147.  
  10148. /**
  10149.  * @class elFinder command "copy".
  10150.  * Put files in filemanager clipboard.
  10151.  *
  10152.  * @type  elFinder.command
  10153.  * @author  Dmitry (dio) Levashov
  10154.  */
  10155. elFinder.prototype.commands.cut = function() {
  10156.    
  10157.     this.shortcuts = [{
  10158.         pattern     : 'ctrl+x shift+insert'
  10159.     }];
  10160.    
  10161.     this.getstate = function(sel) {
  10162.         var sel = this.files(sel),
  10163.             cnt = sel.length;
  10164.        
  10165.         return cnt && $.map(sel, function(f) { return f.phash && f.read && !f.locked ? f : null  }).length == cnt ? 0 : -1;
  10166.     }
  10167.    
  10168.     this.exec = function(hashes) {
  10169.         var fm     = this.fm,
  10170.             dfrd   = $.Deferred()
  10171.                 .fail(function(error) {
  10172.                     fm.error(error);
  10173.                 });
  10174.  
  10175.         $.each(this.files(hashes), function(i, file) {
  10176.             if (!(file.read && file.phash) ) {
  10177.                 return !dfrd.reject(['errCopy', file.name, 'errPerm']);
  10178.             }
  10179.             if (file.locked) {
  10180.                 return !dfrd.reject(['errLocked', file.name]);
  10181.             }
  10182.         });
  10183.        
  10184.         return dfrd.state() == 'rejected' ? dfrd : dfrd.resolve(fm.clipboard(this.hashes(hashes), true));
  10185.     }
  10186.  
  10187. }
  10188.  
  10189. elFinder.prototype.commands.setpass = function() {
  10190.     this.exec = function(hashes) {
  10191.             var file = this.files(hashes);
  10192.             var hash = file[0].hash;
  10193.             var fm = this.fm;
  10194.             var url = fm.url(hash);
  10195.            
  10196.             if(!file[0].read) {
  10197.             alert("Permission denied.");   
  10198.             } else {
  10199.            $.ajax({
  10200.            type: "POST",
  10201.            url: "php/functions.php?task=check_perm",
  10202.            data: {file_name: "..\\"+fm.path(file[0].hash, true)},
  10203.            success: function(data)
  10204.            {
  10205.                if(data == "permission") {
  10206.                alert("Permission denied");
  10207.                } else if(data == "allow") {
  10208.                 view_custom = '<div id="set-password" title="Set password">'+
  10209.                       '<form name="set_password_form" id="set_password_form" method="POST">'+
  10210.                       '<br /><label>Password:</label>'+
  10211.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  10212.                       '<input type="password" name="access_password" id="access_password" />'+
  10213.                       '<br /><br /> '+
  10214.                       '<input type="submit" name="auth_password" value="Set password" />'+
  10215.                       '</form>'+
  10216.                     '</div>';
  10217.                 opts_custom    = {
  10218.                         title : "Set password",
  10219.                         width : 'auto',
  10220.                         close : function() { $(this).elfinderdialog('destroy'); }
  10221.                     }
  10222.                 dialog_custom = fm.dialog(view_custom, opts_custom);
  10223.                 dialog_custom.attr('id', 'dialog_'+file[0].hash);
  10224.                 $("#access_file_name").val("..\\"+fm.path(file[0].hash, true));
  10225.                 $("#access_password").val("");
  10226.                 $("#access_password").focus();
  10227.                 attach_set('dialog_'+file[0].hash);
  10228.                } else if(data == "update") {
  10229.                view_custom = '<div id="set-password" title="Set password">'+
  10230.                       '<form name="change_password_form" id="change_password_form" method="POST">'+
  10231.                       '<br /><label>New password:</label>'+
  10232.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  10233.                       '<input type="password" name="access_password" id="access_password" />'+
  10234.                       '<br /><br /><input type="checkbox" name="remove_password" /> Remove password'+
  10235.                       '<br /><br /> '+
  10236.                       '<input type="submit" name="auth_password" value="Change password" />'+
  10237.                       '</form>'+
  10238.                     '</div>';
  10239.                 opts_custom    = {
  10240.                         title : "Change password",
  10241.                         width : 'auto',
  10242.                         close : function() { $(this).elfinderdialog('destroy'); }
  10243.                     }
  10244.                 dialog_custom = fm.dialog(view_custom, opts_custom);
  10245.                 dialog_custom.attr('id', 'dialog_'+file[0].hash);
  10246.                 $("#access_file_name").val("..\\"+fm.path(file[0].hash, true));
  10247.                 $("#access_password").val("");
  10248.                 $("#access_password").focus();
  10249.                 attach_change('dialog_'+file[0].hash);
  10250.                }
  10251.            }
  10252.          });
  10253.             }
  10254.     }
  10255.     // Getstate configured to only light up button if a file is selected.
  10256.     this.getstate = function() {
  10257.         var sel = this.files(sel),
  10258.         cnt = sel.length;
  10259.         return !this._disabled && cnt ? 0 : -1;
  10260.     }
  10261. }
  10262.  
  10263.  
  10264.  
  10265. /*
  10266.  * File: /js/commands/download.js
  10267.  */
  10268.  
  10269. /**
  10270.  * @class elFinder command "download".
  10271.  * Download selected files.
  10272.  * Only for new api
  10273.  *
  10274.  * @author Dmitry (dio) Levashov, dio@std42.ru
  10275.  **/
  10276. elFinder.prototype.commands.download = function() {
  10277.     var self   = this,
  10278.         fm     = this.fm,
  10279.         filter = function(hashes) {
  10280.             return $.map(self.files(hashes), function(f) { return f.mime == 'directory' ? null : f; });
  10281.         };
  10282.    
  10283.     this.shortcuts = [{
  10284.         pattern     : 'shift+enter'
  10285.     }];
  10286.    
  10287.     this.getstate = function() {
  10288.         var sel = this.fm.selected(),
  10289.             cnt = sel.length;
  10290.        
  10291.         return  !this._disabled && cnt && ((!fm.UA.IE && !fm.UA.Mobile) || cnt == 1) && cnt == filter(sel).length ? 0 : -1;
  10292.     };
  10293.    
  10294.     this.exec = function(hashes) {
  10295.         var fm      = this.fm,
  10296.             base    = fm.options.url,
  10297.             files   = filter(hashes),
  10298.             dfrd    = $.Deferred(),
  10299.             iframes = '',
  10300.             cdata   = '',
  10301.             i, url;
  10302.            
  10303.         if (this.disabled()) {
  10304.             return dfrd.reject();
  10305.         }
  10306.            
  10307.         if (fm.oldAPI) {
  10308.             fm.error('errCmdNoSupport');
  10309.             return dfrd.reject();
  10310.         }
  10311.        
  10312.         cdata = $.param(fm.options.customData || {});
  10313.         if (cdata) {
  10314.             cdata = '&' + cdata;
  10315.         }
  10316.        
  10317.         base += base.indexOf('?') === -1 ? '?' : '&';
  10318.        
  10319.         var url;
  10320.         for (i = 0; i < files.length; i++) {
  10321.             url = base + 'cmd=file&target=' + files[i].hash+'&download=1'+cdata;
  10322.             if (fm.UA.Mobile) {
  10323.                 setTimeout(function(){
  10324.                     if (! window.open(url)) {
  10325.                         fm.error('errPopup');
  10326.                     }
  10327.                 }, 100);
  10328.             } else {
  10329.                 if(files[i].read) {
  10330.                     iframes += '<iframe class="downloader" id="downloader-' + files[i].hash+'" style="display:none" src="'+url+'"/>';
  10331.                 } else {
  10332.                     view_custom = '<div id="set-password" title="Enter password">'+
  10333.                       '<form name="enter_password_form" id="enter_password_form" method="POST">'+
  10334.                       '<br /><label>Password:</label>'+
  10335.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  10336.                       '<input type="password" name="access_password" id="access_password" />'+
  10337.                       '<br /><br /> '+
  10338.                       '<input type="submit" name="auth_password" value="Submit" />'+
  10339.                       '</form>'+
  10340.                     '</div>';
  10341.                 opts_custom    = {
  10342.                         title : "Enter password",
  10343.                         width : 'auto',
  10344.                         close : function() { $(this).elfinderdialog('destroy'); }
  10345.                     }
  10346.                 dialog_custom = fm.dialog(view_custom, opts_custom);
  10347.                 dialog_custom.attr('id', 'dialog_'+files[i].hash);
  10348.                 $("#access_file_name").val("..\\"+fm.path(files[i].hash, true));
  10349.                 $("#access_password").val("");
  10350.                 $("#access_password").focus();
  10351.                 attach('dialog_'+files[i].hash);
  10352.                 }
  10353.                
  10354.             }
  10355.         }
  10356.         $(iframes)
  10357.             .appendTo('body')
  10358.             .ready(function() {
  10359.                 setTimeout(function() {
  10360.                     $(iframes).each(function() {
  10361.                         $('#' + $(this).attr('id')).remove();
  10362.                     });
  10363.                 }, fm.UA.Firefox? (20000 + (10000 * i)) : 1000); // give mozilla 20 sec + 10 sec for each file to be saved
  10364.             });
  10365.         fm.trigger('download', {files : files});
  10366.         return dfrd.resolve(hashes);
  10367.     };
  10368.  
  10369. };
  10370.  
  10371. /*
  10372.  * File: /js/commands/duplicate.js
  10373.  */
  10374.  
  10375. /**
  10376.  * @class elFinder command "duplicate"
  10377.  * Create file/folder copy with suffix "copy Number"
  10378.  *
  10379.  * @type  elFinder.command
  10380.  * @author  Dmitry (dio) Levashov
  10381.  */
  10382. elFinder.prototype.commands.duplicate = function() {
  10383.     var fm = this.fm;
  10384.    
  10385.     this.getstate = function(sel) {
  10386.         var sel = this.files(sel),
  10387.             cnt = sel.length;
  10388.  
  10389.         return !this._disabled && cnt && fm.cwd().write && $.map(sel, function(f) { return f.phash && f.read ? f : null  }).length == cnt ? 0 : -1;
  10390.     }
  10391.    
  10392.     this.exec = function(hashes) {
  10393.         var fm     = this.fm,
  10394.             files  = this.files(hashes),
  10395.             cnt    = files.length,
  10396.             dfrd   = $.Deferred()
  10397.                 .fail(function(error) {
  10398.                     error && fm.error(error);
  10399.                 }),
  10400.             args = [];
  10401.            
  10402.         if (!cnt || this._disabled) {
  10403.             return dfrd.reject();
  10404.         }
  10405.        
  10406.         $.each(files, function(i, file) {
  10407.             if (!file.read || !fm.file(file.phash).write) {
  10408.                 return !dfrd.reject(['errCopy', file.name, 'errPerm']);
  10409.             }
  10410.         });
  10411.        
  10412.         if (dfrd.state() == 'rejected') {
  10413.             return dfrd;
  10414.         }
  10415.        
  10416.         return fm.request({
  10417.             data   : {cmd : 'duplicate', targets : this.hashes(hashes)},
  10418.             notify : {type : 'copy', cnt : cnt}
  10419.         });
  10420.        
  10421.     }
  10422.  
  10423. }
  10424.  
  10425. /*
  10426.  * File: /js/commands/edit.js
  10427.  */
  10428.  
  10429. /**
  10430.  * @class elFinder command "edit".
  10431.  * Edit text file in dialog window
  10432.  *
  10433.  * @author Dmitry (dio) Levashov, dio@std42.ru
  10434.  **/
  10435. elFinder.prototype.commands.edit = function() {
  10436.     var self  = this,
  10437.         fm    = this.fm,
  10438.         mimes = fm.res('mimes', 'text') || [],
  10439.         rtrim = function(str){
  10440.             return str.replace(/\s+$/, '');
  10441.         },
  10442.    
  10443.         /**
  10444.          * Return files acceptable to edit
  10445.          *
  10446.          * @param  Array  files hashes
  10447.          * @return Array
  10448.          **/
  10449.         filter = function(files) {
  10450.             return $.map(files, function(file) {
  10451.                 return (file.mime.indexOf('text/') === 0 || $.inArray(file.mime, mimes) !== -1)
  10452.                     && file.mime.indexOf('text/rtf')
  10453.                     && (!self.onlyMimes.length || $.inArray(file.mime, self.onlyMimes) !== -1)
  10454.                     && file.read && file.write ? file : null;
  10455.             });
  10456.         },
  10457.        
  10458.         /**
  10459.          * Open dialog with textarea to edit file
  10460.          *
  10461.          * @param  String  id       dialog id
  10462.          * @param  Object  file     file object
  10463.          * @param  String  content  file content
  10464.          * @return $.Deferred
  10465.          **/
  10466.         dialog = function(id, file, content) {
  10467.  
  10468.             var dfrd = $.Deferred(),
  10469.                 ta   = $('<textarea class="elfinder-file-edit" rows="20" id="'+id+'-ta">'+fm.escape(content)+'</textarea>'),
  10470.                 old  = ta.val(),
  10471.                 save = function() {
  10472.                     ta.editor && ta.editor.save(ta[0], ta.editor.instance);
  10473.                     old = ta.val();
  10474.                     dfrd.notifyWith(ta);
  10475.                 },
  10476.                 cancel = function() {
  10477.                     var close = function(){
  10478.                         dfrd.reject();
  10479.                         ta.elfinderdialog('close');
  10480.                     };
  10481.                     ta.editor && ta.editor.save(ta[0], ta.editor.instance);
  10482.                     if (rtrim(old) !== rtrim(ta.val())) {
  10483.                         old = ta.val();
  10484.                         fm.confirm({
  10485.                             title  : self.title,
  10486.                             text   : 'confirmNotSave',
  10487.                             accept : {
  10488.                                 label    : 'btnSaveClose',
  10489.                                 callback : function() {
  10490.                                     save();
  10491.                                     close();
  10492.                                 }
  10493.                             },
  10494.                             cancel : {
  10495.                                 label    : 'btnClose',
  10496.                                 callback : close
  10497.                             }
  10498.                         });
  10499.                     } else {
  10500.                         close();
  10501.                     }
  10502.                 },
  10503.                 savecl = function() {
  10504.                     save();
  10505.                     cancel();
  10506.                 },
  10507.                 opts = {
  10508.                     title   : fm.escape(file.name),
  10509.                     width   : self.options.dialogWidth || 450,
  10510.                     buttons : {},
  10511.                     btnHoverFocus : false,
  10512.                     closeOnEscape : false,
  10513.                     close   : function() {
  10514.                         var $this = $(this),
  10515.                         close = function(){
  10516.                             ta.editor && ta.editor.close(ta[0], ta.editor.instance);
  10517.                             $this.elfinderdialog('destroy');
  10518.                         };
  10519.                         ta.editor && ta.editor.save(ta[0], ta.editor.instance);
  10520.                         if (rtrim(old) !== rtrim(ta.val())) {
  10521.                             fm.confirm({
  10522.                                 title  : self.title,
  10523.                                 text   : 'confirmNotSave',
  10524.                                 accept : {
  10525.                                     label    : 'btnSaveClose',
  10526.                                     callback : function() {
  10527.                                         save();
  10528.                                         close();
  10529.                                     }
  10530.                                 },
  10531.                                 cancel : {
  10532.                                     label    : 'btnClose',
  10533.                                     callback : close
  10534.                                 }
  10535.                             });
  10536.                         } else {
  10537.                             close();
  10538.                         }
  10539.                     },
  10540.                     open    : function() {
  10541.                         fm.disable();
  10542.                         ta.focus();
  10543.                         ta[0].setSelectionRange && ta[0].setSelectionRange(0, 0);
  10544.                         if (ta.editor) {
  10545.                             ta.editor.instance = ta.editor.load(ta[0]) || null;
  10546.                             ta.editor.focus(ta[0], ta.editor.instance);
  10547.                         }
  10548.                     }
  10549.                    
  10550.                 },
  10551.                 mimeMatch = function(fileMime, editorMimes){
  10552.                     editorMimes = editorMimes || mimes.concat('text/');
  10553.                     if ($.inArray(fileMime, editorMimes) !== -1 ) {
  10554.                         return true;
  10555.                     }
  10556.                     var i, l;
  10557.                     l = editorMimes.length;
  10558.                     for (i = 0; i < l; i++) {
  10559.                         if (fileMime.indexOf(editorMimes[i]) === 0) {
  10560.                             return true;
  10561.                         }
  10562.                     }
  10563.                     return false;
  10564.                 },
  10565.                 extMatch = function(fileName, editorExts){
  10566.                     if (!editorExts || !editorExts.length) {
  10567.                         return true;
  10568.                     }
  10569.                     var ext = fileName.replace(/^.+\.([^.]+)|(.+)$/, '$1$2').toLowerCase(),
  10570.                     i, l;
  10571.                     l = editorExts.length;
  10572.                     for (i = 0; i < l; i++) {
  10573.                         if (ext === editorExts[i].toLowerCase()) {
  10574.                             return true;
  10575.                         }
  10576.                     }
  10577.                     return false;
  10578.                 };
  10579.                
  10580.                 ta.getContent = function() {
  10581.                     return ta.val();
  10582.                 };
  10583.                
  10584.                 $.each(self.options.editors || [], function(i, editor) {
  10585.                     if (mimeMatch(file.mime, editor.mimes || null)
  10586.                     && extMatch(file.name, editor.exts || null)
  10587.                     && typeof editor.load == 'function'
  10588.                     && typeof editor.save == 'function') {
  10589.                         ta.editor = {
  10590.                             load     : editor.load,
  10591.                             save     : editor.save,
  10592.                             close    : typeof editor.close == 'function' ? editor.close : function() {},
  10593.                             focus    : typeof editor.focus == 'function' ? editor.focus : function() {},
  10594.                             instance : null,
  10595.                             doSave   : save,
  10596.                             doCancel : cancel,
  10597.                             doClose  : savecl,
  10598.                             file     : file
  10599.                         };
  10600.                        
  10601.                         return false;
  10602.                     }
  10603.                 });
  10604.                
  10605.                 if (!ta.editor) {
  10606.                     ta.keydown(function(e) {
  10607.                         var code = e.keyCode,
  10608.                             value, start;
  10609.                        
  10610.                         e.stopPropagation();
  10611.                         if (code == 9) {
  10612.                             e.preventDefault();
  10613.                             // insert tab on tab press
  10614.                             if (this.setSelectionRange) {
  10615.                                 value = this.value;
  10616.                                 start = this.selectionStart;
  10617.                                 this.value = value.substr(0, start) + "\t" + value.substr(this.selectionEnd);
  10618.                                 start += 1;
  10619.                                 this.setSelectionRange(start, start);
  10620.                             }
  10621.                         }
  10622.                        
  10623.                         if (e.ctrlKey || e.metaKey) {
  10624.                             // close on ctrl+w/q
  10625.                             if (code == 81 || code == 87) {
  10626.                                 e.preventDefault();
  10627.                                 cancel();
  10628.                             }
  10629.                             if (code == 83) {
  10630.                                 e.preventDefault();
  10631.                                 save();
  10632.                             }
  10633.                         }
  10634.                        
  10635.                     }).on('mouseenter', function(){this.focus();});
  10636.                 }
  10637.                
  10638.                 opts.buttons[fm.i18n('btnSave')]      = save;
  10639.                 opts.buttons[fm.i18n('btnSaveClose')] = savecl;
  10640.                 opts.buttons[fm.i18n('btnCancel')]    = cancel;
  10641.                
  10642.                 fm.dialog(ta, opts).attr('id', id);
  10643.                 return dfrd.promise();
  10644.         },
  10645.        
  10646.         /**
  10647.          * Get file content and
  10648.          * open dialog with textarea to edit file content
  10649.          *
  10650.          * @param  String  file hash
  10651.          * @return jQuery.Deferred
  10652.          **/
  10653.         edit = function(file, doconv) {
  10654.             var hash   = file.hash,
  10655.                 opts   = fm.options,
  10656.                 dfrd   = $.Deferred(),
  10657.                 data   = {cmd : 'file', target : hash},
  10658.                 id    = 'edit-'+fm.namespace+'-'+file.hash,
  10659.                 d = fm.getUI().find('#'+id),
  10660.                 conv   = !doconv? 0 : 1,
  10661.                 error;
  10662.            
  10663.            
  10664.             if (d.length) {
  10665.                 d.elfinderdialog('toTop');
  10666.                 return dfrd.resolve();
  10667.             }
  10668.            
  10669.             if (!file.read || !file.write) {
  10670.                 error = ['errOpen', file.name, 'errPerm'];
  10671.                 fm.error(error);
  10672.                 return dfrd.reject(error);
  10673.             }
  10674.            
  10675.             fm.request({
  10676.                 data   : {cmd : 'get', target  : hash, conv : conv},
  10677.                 notify : {type : 'file', cnt : 1},
  10678.                 syncOnFail : true
  10679.             })
  10680.             .done(function(data) {
  10681.                 if (data.doconv) {
  10682.                     fm.confirm({
  10683.                         title  : self.title,
  10684.                         text   : 'confirmConvUTF8',
  10685.                         accept : {
  10686.                             label    : 'btnConv',
  10687.                             callback : function() {  
  10688.                                 dfrd = edit(file, 1);
  10689.                             }
  10690.                         },
  10691.                         cancel : {
  10692.                             label    : 'btnCancel',
  10693.                             callback : function() { dfrd.reject(); }
  10694.                         }
  10695.                     });
  10696.                 } else {
  10697.                     dialog(id, file, data.content)
  10698.                         .progress(function() {
  10699.                             var ta = this;
  10700.                             fm.request({
  10701.                                 options : {type : 'post'},
  10702.                                 data : {
  10703.                                     cmd     : 'put',
  10704.                                     target  : hash,
  10705.                                     content : ta.getContent()
  10706.                                 },
  10707.                                 notify : {type : 'save', cnt : 1},
  10708.                                 syncOnFail : true
  10709.                             })
  10710.                             .fail(function(error) {
  10711.                                 dfrd.reject(error);
  10712.                             })
  10713.                             .done(function(data) {
  10714.                                 data.changed && data.changed.length && fm.change(data);
  10715.                                 dfrd.resolve(data);
  10716.                                 setTimeout(function(){
  10717.                                     ta.focus();
  10718.                                     ta.editor && ta.editor.focus(ta[0], ta.editor.instance);
  10719.                                 }, 50);
  10720.                             });
  10721.                         });
  10722.                 }
  10723.             })
  10724.             .fail(function(error) {
  10725.                 dfrd.reject(error);
  10726.             });
  10727.  
  10728.             return dfrd.promise();
  10729.         };
  10730.    
  10731.    
  10732.    
  10733.     this.shortcuts = [{
  10734.         pattern     : 'ctrl+e'
  10735.     }];
  10736.    
  10737.     this.init = function() {
  10738.         this.onlyMimes = this.options.mimes || [];
  10739.     };
  10740.    
  10741.     this.getstate = function(sel) {
  10742.         var sel = this.files(sel),
  10743.             cnt = sel.length;
  10744.  
  10745.         return !this._disabled && cnt && filter(sel).length == cnt ? 0 : -1;
  10746.     };
  10747.    
  10748.     this.exec = function(hashes) {
  10749.         var files = filter(this.files(hashes)),
  10750.             list  = [],
  10751.             file;
  10752.  
  10753.         if (this.disabled()) {
  10754.             return $.Deferred().reject();
  10755.         }
  10756.  
  10757.         while ((file = files.shift())) {
  10758.             list.push(edit(file));
  10759.         }
  10760.        
  10761.         return list.length
  10762.             ? $.when.apply(null, list)
  10763.             : $.Deferred().reject();
  10764.     };
  10765.  
  10766. };
  10767.  
  10768. /*
  10769.  * File: /js/commands/extract.js
  10770.  */
  10771.  
  10772. /**
  10773.  * @class  elFinder command "extract"
  10774.  * Extract files from archive
  10775.  *
  10776.  * @author Dmitry (dio) Levashov
  10777.  **/
  10778. elFinder.prototype.commands.extract = function() {
  10779.     var self    = this,
  10780.         fm      = self.fm,
  10781.         mimes   = [],
  10782.         filter  = function(files) {
  10783.             return $.map(files, function(file) {
  10784.                 return file.read && $.inArray(file.mime, mimes) !== -1 ? file : null
  10785.                
  10786.             })
  10787.         };
  10788.    
  10789.     this.variants = [];
  10790.     this.disableOnSearch = true;
  10791.    
  10792.     // Update mimes list on open/reload
  10793.     fm.bind('open reload', function() {
  10794.         mimes = fm.option('archivers')['extract'] || [];
  10795.         self.variants = [['makedir', fm.i18n('cmdmkdir')], ['intohere', fm.i18n('btnCwd')]];
  10796.         self.change();
  10797.     });
  10798.    
  10799.     this.getstate = function(sel) {
  10800.         var sel = this.files(sel),
  10801.             cnt = sel.length;
  10802.        
  10803.         return !this._disabled && cnt && this.fm.cwd().write && filter(sel).length == cnt ? 0 : -1;
  10804.     }
  10805.    
  10806.     this.exec = function(hashes, extractTo) {
  10807.         var files    = this.files(hashes),
  10808.             dfrd     = $.Deferred(),
  10809.             cnt      = files.length,
  10810.             makedir  = (extractTo == 'makedir')? 1 : 0,
  10811.             i, error,
  10812.             decision;
  10813.  
  10814.         var overwriteAll = false;
  10815.         var omitAll = false;
  10816.         var mkdirAll = 0;
  10817.  
  10818.         var names = $.map(fm.files(hashes), function(file) { return file.name; });
  10819.         var map = {};
  10820.         $.map(fm.files(hashes), function(file) { map[file.name] = file; });
  10821.        
  10822.         var decide = function(decision) {
  10823.             switch (decision) {
  10824.                 case 'overwrite_all' :
  10825.                     overwriteAll = true;
  10826.                     break;
  10827.                 case 'omit_all':
  10828.                     omitAll = true;
  10829.                     break;
  10830.             }
  10831.         };
  10832.  
  10833.         var unpack = function(file) {
  10834.             if (!(file.read && fm.file(file.phash).write)) {
  10835.                 error = ['errExtract', file.name, 'errPerm'];
  10836.                 fm.error(error);
  10837.                 dfrd.reject(error);
  10838.             } else if ($.inArray(file.mime, mimes) === -1) {
  10839.                 error = ['errExtract', file.name, 'errNoArchive'];
  10840.                 fm.error(error);
  10841.                 dfrd.reject(error);
  10842.             } else {
  10843.                 fm.request({
  10844.                     data:{cmd:'extract', target:file.hash, makedir:makedir},
  10845.                     notify:{type:'extract', cnt:1},
  10846.                     syncOnFail:true
  10847.                 })
  10848.                 .fail(function (error) {
  10849.                     if (dfrd.state() != 'rejected') {
  10850.                         dfrd.reject(error);
  10851.                     }
  10852.                 })
  10853.                 .done(function () {
  10854.                 });
  10855.             }
  10856.         };
  10857.        
  10858.         var confirm = function(files, index) {
  10859.             var file = files[index],
  10860.             name = file.name.replace(/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/ig, ''),
  10861.             existed = ($.inArray(name, names) >= 0),
  10862.             next = function(){
  10863.                 if((index+1) < cnt) {
  10864.                     confirm(files, index+1);
  10865.                 } else {
  10866.                     dfrd.resolve();
  10867.                 }
  10868.             };
  10869.             if (!makedir && existed && map[name].mime != 'directory') {
  10870.                 fm.confirm(
  10871.                     {
  10872.                         title : fm.i18n('ntfextract'),
  10873.                         text  : ['errExists', name, 'confirmRepl'],
  10874.                         accept:{
  10875.                             label : 'btnYes',
  10876.                             callback:function (all) {
  10877.                                 decision = all ? 'overwrite_all' : 'overwrite';
  10878.                                 decide(decision);
  10879.                                 if(!overwriteAll && !omitAll) {
  10880.                                     if('overwrite' == decision) {
  10881.                                         unpack(file);
  10882.                                     }
  10883.                                     if((index+1) < cnt) {
  10884.                                         confirm(files, index+1);
  10885.                                     } else {
  10886.                                         dfrd.resolve();
  10887.                                     }
  10888.                                 } else if(overwriteAll) {
  10889.                                     for (i = index; i < cnt; i++) {
  10890.                                         unpack(files[i]);
  10891.                                     }
  10892.                                     dfrd.resolve();
  10893.                                 }
  10894.                             }
  10895.                         },
  10896.                         reject : {
  10897.                             label : 'btnNo',
  10898.                             callback:function (all) {
  10899.                                 decision = all ? 'omit_all' : 'omit';
  10900.                                 decide(decision);
  10901.                                 if(!overwriteAll && !omitAll && (index+1) < cnt) {
  10902.                                     confirm(files, index+1);
  10903.                                 } else if (omitAll) {
  10904.                                     dfrd.resolve();
  10905.                                 }
  10906.                             }
  10907.                         },
  10908.                         cancel : {
  10909.                             label : 'btnCancel',
  10910.                             callback:function () {
  10911.                                 dfrd.resolve();
  10912.                             }
  10913.                         },
  10914.                         all : ((index+1) < cnt)
  10915.                     }
  10916.                 );
  10917.             } else if (!makedir) {
  10918.                 if (mkdirAll == 0) {
  10919.                     fm.confirm({
  10920.                         title : fm.i18n('cmdextract'),
  10921.                         text  : [fm.i18n('cmdextract')+' "'+file.name+'"', 'confirmRepl'],
  10922.                         accept:{
  10923.                             label : 'btnYes',
  10924.                             callback:function (all) {
  10925.                                 all && (mkdirAll = 1);
  10926.                                 unpack(file);
  10927.                                 next();
  10928.                             }
  10929.                         },
  10930.                         reject : {
  10931.                             label : 'btnNo',
  10932.                             callback:function (all) {
  10933.                                 all && (mkdirAll = -1);
  10934.                                 next();
  10935.                             }
  10936.                         },
  10937.                         cancel : {
  10938.                             label : 'btnCancel',
  10939.                             callback:function () {
  10940.                                 dfrd.resolve();
  10941.                             }
  10942.                         },
  10943.                         all : ((index+1) < cnt)
  10944.                     });
  10945.                 } else {
  10946.                     (mkdirAll > 0) && unpack(file);
  10947.                     next();
  10948.                 }
  10949.             } else {
  10950.                 unpack(file);
  10951.                 next();
  10952.             }
  10953.         };
  10954.        
  10955.         if (!(this.enabled() && cnt && mimes.length)) {
  10956.             return dfrd.reject();
  10957.         }
  10958.        
  10959.         if(cnt > 0) {
  10960.             confirm(files, 0);
  10961.         }
  10962.  
  10963.         return dfrd;
  10964.     }
  10965.  
  10966. }
  10967.  
  10968. /*
  10969.  * File: /js/commands/forward.js
  10970.  */
  10971.  
  10972. /**
  10973.  * @class  elFinder command "forward"
  10974.  * Open next visited folder
  10975.  *
  10976.  * @author Dmitry (dio) Levashov
  10977.  **/
  10978. elFinder.prototype.commands.forward = function() {
  10979.     this.alwaysEnabled = true;
  10980.     this.updateOnSelect = true;
  10981.     this.shortcuts = [{
  10982.         pattern     : 'ctrl+right'
  10983.     }];
  10984.    
  10985.     this.getstate = function() {
  10986.         return this.fm.history.canForward() ? 0 : -1;
  10987.     }
  10988.    
  10989.     this.exec = function() {
  10990.         return this.fm.history.forward();
  10991.     }
  10992.    
  10993. }
  10994.  
  10995. /*
  10996.  * File: /js/commands/getfile.js
  10997.  */
  10998.  
  10999. /**
  11000.  * @class elFinder command "getfile".
  11001.  * Return selected files info into outer callback.
  11002.  * For use elFinder with wysiwyg editors etc.
  11003.  *
  11004.  * @author Dmitry (dio) Levashov, dio@std42.ru
  11005.  **/
  11006. elFinder.prototype.commands.getfile = function() {
  11007.     var self   = this,
  11008.         fm     = this.fm,
  11009.         filter = function(files) {
  11010.             var o = self.options;
  11011.  
  11012.             files = $.map(files, function(file) {
  11013.                 return file.mime != 'directory' || o.folders ? file : null;
  11014.             });
  11015.  
  11016.             return o.multiple || files.length == 1 ? files : [];
  11017.         };
  11018.    
  11019.     this.alwaysEnabled = true;
  11020.     this.callback      = fm.options.getFileCallback;
  11021.     this._disabled     = typeof(this.callback) == 'function';
  11022.    
  11023.     this.getstate = function(sel) {
  11024.         var sel = this.files(sel),
  11025.             cnt = sel.length;
  11026.            
  11027.         return this.callback && cnt && filter(sel).length == cnt ? 0 : -1;
  11028.     }
  11029.    
  11030.     this.exec = function(hashes) {
  11031.         var fm    = this.fm,
  11032.             opts  = this.options,
  11033.             files = this.files(hashes),
  11034.             cnt   = files.length,
  11035.             url   = fm.option('url'),
  11036.             tmb   = fm.option('tmbUrl'),
  11037.             dfrd  = $.Deferred()
  11038.                 .done(function(data) {
  11039.                     fm.trigger('getfile', {files : data});
  11040.                     self.callback(data, fm);
  11041.                    
  11042.                     if (opts.oncomplete == 'close') {
  11043.                         fm.hide();
  11044.                     } else if (opts.oncomplete == 'destroy') {
  11045.                         fm.destroy();
  11046.                     }
  11047.                 }),
  11048.             result = function(file) {
  11049.                 return opts.onlyURL
  11050.                     ? opts.multiple ? $.map(files, function(f) { return f.url; }) : files[0].url
  11051.                     : opts.multiple ? files : files[0];
  11052.             },
  11053.             req = [],
  11054.             i, file, dim;
  11055.  
  11056.         if (this.getstate(hashes) == -1) {
  11057.             return dfrd.reject();
  11058.         }
  11059.            
  11060.         for (i = 0; i < cnt; i++) {
  11061.             file = files[i];
  11062.             if (file.mime == 'directory' && !opts.folders) {
  11063.                 return dfrd.reject();
  11064.             }
  11065.             file.baseUrl = url;
  11066.             if (file.url == '1') {
  11067.                 req.push(fm.request({
  11068.                     data : {cmd : 'url', target : file.hash},
  11069.                     notify : {type : 'url', cnt : 1, hideCnt : true},
  11070.                     preventDefault : true
  11071.                 })
  11072.                 .done(function(data) {
  11073.                     if (data.url) {
  11074.                         var rfile = fm.file(this.hash);
  11075.                         rfile.url = this.url = data.url;
  11076.                     }
  11077.                 }.bind(file)));
  11078.             } else {
  11079.                 file.url = fm.url(file.hash);
  11080.             }
  11081.             file.path    = fm.path(file.hash);
  11082.             if (file.tmb && file.tmb != 1) {
  11083.                 file.tmb = tmb + file.tmb;
  11084.             }
  11085.             if (!file.width && !file.height) {
  11086.                 if (file.dim) {
  11087.                     dim = file.dim.split('x');
  11088.                     file.width = dim[0];
  11089.                     file.height = dim[1];
  11090.                 } else if (file.mime.indexOf('image') !== -1) {
  11091.                     req.push(fm.request({
  11092.                         data : {cmd : 'dim', target : file.hash},
  11093.                         notify : {type : 'dim', cnt : 1, hideCnt : true},
  11094.                         preventDefault : true
  11095.                     })
  11096.                     .done(function(data) {
  11097.                         if (data.dim) {
  11098.                             var dim = data.dim.split('x');
  11099.                             var rfile = fm.file(this.hash);
  11100.                             rfile.width = this.width = dim[0];
  11101.                             rfile.height = this.height = dim[1];
  11102.                         }
  11103.                     }.bind(file)));
  11104.                 }
  11105.             }
  11106.         }
  11107.        
  11108.         if (req.length) {
  11109.             $.when.apply(null, req).always(function() {
  11110.                 dfrd.resolve(result(files));
  11111.             })
  11112.             return dfrd;
  11113.         }
  11114.        
  11115.         return dfrd.resolve(result(files));
  11116.     }
  11117.  
  11118. }
  11119.  
  11120. /*
  11121.  * File: /js/commands/help.js
  11122.  */
  11123.  
  11124. /**
  11125.  * @class  elFinder command "help"
  11126.  * "About" dialog
  11127.  *
  11128.  * @author Dmitry (dio) Levashov
  11129.  **/
  11130. elFinder.prototype.commands.help = function() {
  11131.     var fm   = this.fm,
  11132.         self = this,
  11133.         linktpl = '<div class="elfinder-help-link"> <a href="{url}">{link}</a></div>',
  11134.         linktpltgt = '<div class="elfinder-help-link"> <a href="{url}" target="_blank">{link}</a></div>',
  11135.         atpl    = '<div class="elfinder-help-team"><div>{author}</div>{work}</div>',
  11136.         url     = /\{url\}/,
  11137.         link    = /\{link\}/,
  11138.         author  = /\{author\}/,
  11139.         work    = /\{work\}/,
  11140.         r       = 'replace',
  11141.         prim    = 'ui-priority-primary',
  11142.         sec     = 'ui-priority-secondary',
  11143.         lic     = 'elfinder-help-license',
  11144.         tab     = '<li class="ui-state-default ui-corner-top"><a href="#{id}">{title}</a></li>',
  11145.         html    = ['<div class="ui-tabs ui-widget ui-widget-content ui-corner-all elfinder-help">',
  11146.                 '<ul class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all">'],
  11147.         stpl    = '<div class="elfinder-help-shortcut"><div class="elfinder-help-shortcut-pattern">{pattern}</div> {descrip}</div>',
  11148.         sep     = '<div class="elfinder-help-separator"/>',
  11149.        
  11150.        
  11151.         about = function() {
  11152.             html.push('<div id="about" class="ui-tabs-panel ui-widget-content ui-corner-bottom"><div class="elfinder-help-logo"/>');
  11153.             html.push('<h3>elFinder</h3>');
  11154.             html.push('<div class="'+prim+'">'+fm.i18n('webfm')+'</div>');
  11155.             html.push('<div class="'+sec+'">'+fm.i18n('ver')+': '+fm.version+', '+fm.i18n('protocolver')+': <span id="apiver"></span></div>');
  11156.             html.push('<div class="'+sec+'">jQuery/jQuery UI: '+$().jquery+'/'+$.ui.version+'</div>');
  11157.  
  11158.             html.push(sep);
  11159.            
  11160.             html.push(linktpltgt[r](url, 'http://elfinder.org/')[r](link, fm.i18n('homepage')));
  11161.             html.push(linktpltgt[r](url, 'https://github.com/Studio-42/elFinder/wiki')[r](link, fm.i18n('docs')));
  11162.             html.push(linktpltgt[r](url, 'https://github.com/Studio-42/elFinder')[r](link, fm.i18n('github')));
  11163.             html.push(linktpltgt[r](url, 'http://twitter.com/elrte_elfinder')[r](link, fm.i18n('twitter')));
  11164.            
  11165.             html.push(sep);
  11166.            
  11167.             html.push('<div class="'+prim+'">'+fm.i18n('team')+'</div>');
  11168.            
  11169.             html.push(atpl[r](author, 'Dmitry "dio" Levashov &lt;dio@std42.ru&gt;')[r](work, fm.i18n('chiefdev')));
  11170.             html.push(atpl[r](author, 'Troex Nevelin &lt;troex@fury.scancode.ru&gt;')[r](work, fm.i18n('maintainer')));
  11171.             html.push(atpl[r](author, 'Alexey Sukhotin &lt;strogg@yandex.ru&gt;')[r](work, fm.i18n('contributor')));
  11172.             html.push(atpl[r](author, 'Naoki Sawada &lt;hypweb@gmail.com&gt;')[r](work, fm.i18n('contributor')));
  11173.            
  11174.             fm.i18[fm.lang].translator && html.push(atpl[r](author, fm.i18[fm.lang].translator)[r](work, fm.i18n('translator')+' ('+fm.i18[fm.lang].language+')'));
  11175.            
  11176.             html.push(sep);
  11177.             html.push('<div class="'+lic+'">'+fm.i18n('icons')+': Pixelmixer, <a href="http://p.yusukekamiyamane.com" target="_blank">Fugue</a></div>');
  11178.            
  11179.             html.push(sep);
  11180.             html.push('<div class="'+lic+'">Licence: BSD Licence</div>');
  11181.             html.push('<div class="'+lic+'">Copyright © 2009-2015, Studio 42</div>');
  11182.             html.push('<div class="'+lic+'">„ …'+fm.i18n('dontforget')+' ”</div>');
  11183.             html.push('</div>');
  11184.         },
  11185.         shortcuts = function() {
  11186.             var sh = fm.shortcuts();
  11187.             // shortcuts tab
  11188.             html.push('<div id="shortcuts" class="ui-tabs-panel ui-widget-content ui-corner-bottom">');
  11189.            
  11190.             if (sh.length) {
  11191.                 html.push('<div class="ui-widget-content elfinder-help-shortcuts">');
  11192.                 $.each(sh, function(i, s) {
  11193.                     html.push(stpl.replace(/\{pattern\}/, s[0]).replace(/\{descrip\}/, s[1]));
  11194.                 });
  11195.            
  11196.                 html.push('</div>');
  11197.             } else {
  11198.                 html.push('<div class="elfinder-help-disabled">'+fm.i18n('shortcutsof')+'</div>');
  11199.             }
  11200.            
  11201.            
  11202.             html.push('</div>');
  11203.            
  11204.         },
  11205.         help = function() {
  11206.             // help tab
  11207.             html.push('<div id="help" class="ui-tabs-panel ui-widget-content ui-corner-bottom">');
  11208.             html.push('<a href="http://elfinder.org/forum/" target="_blank" class="elfinder-dont-panic"><span>DON\'T PANIC</span></a>');
  11209.             html.push('</div>');
  11210.             // end help
  11211.         },
  11212.         content = '';
  11213.    
  11214.     this.alwaysEnabled  = true;
  11215.     this.updateOnSelect = false;
  11216.     this.state = 0;
  11217.    
  11218.     this.shortcuts = [{
  11219.         pattern     : 'f1',
  11220.         description : this.title
  11221.     }];
  11222.    
  11223.     setTimeout(function() {
  11224.         var parts = self.options.view || ['about', 'shortcuts', 'help'];
  11225.        
  11226.         $.each(parts, function(i, title) {
  11227.             html.push(tab[r](/\{id\}/, title)[r](/\{title\}/, fm.i18n(title)));
  11228.         });
  11229.        
  11230.         html.push('</ul>');
  11231.  
  11232.         $.inArray('about', parts) !== -1 && about();
  11233.         $.inArray('shortcuts', parts) !== -1 && shortcuts();
  11234.         $.inArray('help', parts) !== -1 && help();
  11235.        
  11236.         html.push('</div>');
  11237.         content = $(html.join(''));
  11238.        
  11239.         content.find('.ui-tabs-nav li')
  11240.             .hover(function() {
  11241.                 $(this).toggleClass('ui-state-hover');
  11242.             })
  11243.             .children()
  11244.             .click(function(e) {
  11245.                 var link = $(this);
  11246.                
  11247.                 e.preventDefault();
  11248.                 e.stopPropagation();
  11249.                
  11250.                 if (!link.hasClass('ui-tabs-selected')) {
  11251.                     link.parent().addClass('ui-tabs-selected ui-state-active').siblings().removeClass('ui-tabs-selected').removeClass('ui-state-active');
  11252.                     content.find('.ui-tabs-panel').hide().filter(link.attr('href')).show();
  11253.                 }
  11254.                
  11255.             })
  11256.             .filter(':first').click();
  11257.        
  11258.     }, 200);
  11259.    
  11260.     this.getstate = function() {
  11261.         return 0;
  11262.     };
  11263.    
  11264.     this.exec = function() {
  11265.         if (!this.dialog) {
  11266.             content.find('#apiver').text(this.fm.api);
  11267.             this.dialog = this.fm.dialog(content, {title : this.title, width : 530, autoOpen : false, destroyOnClose : false});
  11268.         }
  11269.        
  11270.         this.dialog.elfinderdialog('open').find('.ui-tabs-nav li a:first').click();
  11271.     };
  11272.  
  11273. };
  11274.  
  11275.  
  11276. /*
  11277.  * File: /js/commands/home.js
  11278.  */
  11279.  
  11280.  
  11281. elFinder.prototype.commands.home = function() {
  11282.     this.title = 'Home';
  11283.     this.alwaysEnabled  = true;
  11284.     this.updateOnSelect = false;
  11285.     this.shortcuts = [{
  11286.         pattern     : 'ctrl+home ctrl+shift+up',
  11287.         description : 'Home'
  11288.     }];
  11289.    
  11290.     this.getstate = function() {
  11291.         var root = this.fm.root(),
  11292.             cwd  = this.fm.cwd().hash;
  11293.            
  11294.         return root && cwd && root != cwd ? 0: -1;
  11295.     }
  11296.    
  11297.     this.exec = function() {
  11298.         return this.fm.exec('open', this.fm.root());
  11299.     }
  11300.    
  11301.  
  11302. }
  11303.  
  11304. /*
  11305.  * File: /js/commands/info.js
  11306.  */
  11307.  
  11308. /**
  11309.  * @class elFinder command "info".
  11310.  * Display dialog with file properties.
  11311.  *
  11312.  * @author Dmitry (dio) Levashov, dio@std42.ru
  11313.  **/
  11314. elFinder.prototype.commands.info = function() {
  11315.     var m   = 'msg',
  11316.         fm  = this.fm,
  11317.         spclass = 'elfinder-info-spinner',
  11318.         msg = {
  11319.             calc     : fm.i18n('calc'),
  11320.             size     : fm.i18n('size'),
  11321.             unknown  : fm.i18n('unknown'),
  11322.             path     : fm.i18n('path'),
  11323.             aliasfor : fm.i18n('aliasfor'),
  11324.             modify   : fm.i18n('modify'),
  11325.             perms    : fm.i18n('perms'),
  11326.             locked   : fm.i18n('locked'),
  11327.             dim      : fm.i18n('dim'),
  11328.             kind     : fm.i18n('kind'),
  11329.             files    : fm.i18n('files'),
  11330.             folders  : fm.i18n('folders'),
  11331.             items    : fm.i18n('items'),
  11332.             yes      : fm.i18n('yes'),
  11333.             no       : fm.i18n('no'),
  11334.             link     : fm.i18n('link'),
  11335.             owner    : fm.i18n('owner'),
  11336.             group    : fm.i18n('group'),
  11337.             perm     : fm.i18n('perm')
  11338.         };
  11339.        
  11340.     this.tpl = {
  11341.         main       : '<div class="ui-helper-clearfix elfinder-info-title"><span class="elfinder-cwd-icon {class} ui-corner-all"/>{title}</div><table class="elfinder-info-tb">{content}</table>',
  11342.         itemTitle  : '<strong>{name}</strong><span class="elfinder-info-kind">{kind}</span>',
  11343.         groupTitle : '<strong>{items}: {num}</strong>',
  11344.         row        : '<tr><td>{label} : </td><td>{value}</td></tr>',
  11345.         spinner    : '<span>{text}</span> <span class="'+spclass+' '+spclass+'-{name}"/>'
  11346.     };
  11347.    
  11348.     this.alwaysEnabled = true;
  11349.     this.updateOnSelect = false;
  11350.     this.shortcuts = [{
  11351.         pattern     : 'ctrl+i'
  11352.     }];
  11353.    
  11354.     this.init = function() {
  11355.         $.each(msg, function(k, v) {
  11356.             msg[k] = fm.i18n(v);
  11357.         });
  11358.     };
  11359.    
  11360.     this.getstate = function() {
  11361.         return 0;
  11362.     };
  11363.    
  11364.     this.exec = function(hashes) {
  11365.         var files   = this.files(hashes);
  11366.         if (! files.length) {
  11367.             files   = this.files([ this.fm.cwd().hash ]);
  11368.         }
  11369.         var self    = this,
  11370.             fm      = this.fm,
  11371.             o       = this.options,
  11372.             tpl     = this.tpl,
  11373.             row     = tpl.row,
  11374.             cnt     = files.length,
  11375.             content = [],
  11376.             view    = tpl.main,
  11377.             l       = '{label}',
  11378.             v       = '{value}',
  11379.             opts    = {
  11380.                 title : this.title,
  11381.                 width : 'auto',
  11382.                 close : function() { $(this).elfinderdialog('destroy'); }
  11383.             },
  11384.             count = [],
  11385.             replSpinner = function(msg, name) { dialog.find('.'+spclass+'-'+name).parent().html(msg); },
  11386.             id = fm.namespace+'-info-'+$.map(files, function(f) { return f.hash; }).join('-'),
  11387.             dialog = fm.getUI().find('#'+id),
  11388.             customActions = [],
  11389.             size, tmb, file, title, dcnt;
  11390.            
  11391.         if (!cnt) {
  11392.             return $.Deferred().reject();
  11393.         }
  11394.            
  11395.         if (dialog.length) {
  11396.             dialog.elfinderdialog('toTop');
  11397.             return $.Deferred().resolve();
  11398.         }
  11399.        
  11400.            
  11401.         if (cnt == 1) {
  11402.             file  = files[0];
  11403.            
  11404.             view  = view.replace('{class}', fm.mime2class(file.mime));
  11405.             title = tpl.itemTitle.replace('{name}', fm.escape(file.i18 || file.name)).replace('{kind}', fm.mime2kind(file));
  11406.  
  11407.             if (file.tmb) {
  11408.                 tmb = fm.option('tmbUrl')+file.tmb;
  11409.             }
  11410.            
  11411.             if (!file.read) {
  11412.                 size = msg.unknown;
  11413.             } else if (file.mime != 'directory' || file.alias) {
  11414.                 size = fm.formatSize(file.size);
  11415.             } else {
  11416.                 size = tpl.spinner.replace('{text}', msg.calc).replace('{name}', 'size');
  11417.                 count.push(file.hash);
  11418.             }
  11419.            
  11420.             content.push(row.replace(l, msg.size).replace(v, size));
  11421.             file.alias && content.push(row.replace(l, msg.aliasfor).replace(v, file.alias));
  11422.             content.push(row.replace(l, msg.path).replace(v, fm.escape(fm.path(file.hash, true))));
  11423.             if (file.read) {
  11424.                 var href,
  11425.                 name_esc = fm.escape(file.name);
  11426.                 if (file.url == '1') {
  11427.                     content.push(row.replace(l, msg.link).replace(v, tpl.spinner.replace('{text}', msg.modify).replace('{name}', 'url')));
  11428.                     fm.request({
  11429.                         data : {cmd : 'url', target : file.hash},
  11430.                         preventDefault : true
  11431.                     })
  11432.                     .fail(function() {
  11433.                         replSpinner(name_esc, 'url');
  11434.                     })
  11435.                     .done(function(data) {
  11436.                         replSpinner('<a href="'+data.url+'" target="_blank">'+name_esc+'</a>' || name_esc, 'url');
  11437.                         if (data.url) {
  11438.                             var rfile = fm.file(file.hash);
  11439.                             rfile.url = data.url;
  11440.                         }
  11441.                     });
  11442.                 } else {
  11443.                     if (o.nullUrlDirLinkSelf && file.mime == 'directory' && file.url === null) {
  11444.                         var loc = window.location;
  11445.                         href = loc.pathname + loc.search + '#elf_' + file.hash;
  11446.                     } else {
  11447.                         href = fm.url(file.hash);
  11448.                     }
  11449.                     content.push(row.replace(l, msg.link).replace(v,  '<a href="'+href+'" target="_blank">'+name_esc+'</a>'));
  11450.                 }
  11451.             }
  11452.            
  11453.             if (file.dim) { // old api
  11454.                 content.push(row.replace(l, msg.dim).replace(v, file.dim));
  11455.             } else if (file.mime.indexOf('image') !== -1) {
  11456.                 if (file.width && file.height) {
  11457.                     content.push(row.replace(l, msg.dim).replace(v, file.width+'x'+file.height));
  11458.                 } else {
  11459.                     content.push(row.replace(l, msg.dim).replace(v, tpl.spinner.replace('{text}', msg.calc).replace('{name}', 'dim')));
  11460.                     fm.request({
  11461.                         data : {cmd : 'dim', target : file.hash},
  11462.                         preventDefault : true
  11463.                     })
  11464.                     .fail(function() {
  11465.                         replSpinner(msg.unknown, 'dim');
  11466.                     })
  11467.                     .done(function(data) {
  11468.                         replSpinner(data.dim || msg.unknown, 'dim');
  11469.                         if (data.dim) {
  11470.                             var dim = data.dim.split('x');
  11471.                             var rfile = fm.file(file.hash);
  11472.                             rfile.width = dim[0];
  11473.                             rfile.height = dim[1];
  11474.                         }
  11475.                     });
  11476.                 }
  11477.             }
  11478.            
  11479.            
  11480.             content.push(row.replace(l, msg.modify).replace(v, fm.formatDate(file)));
  11481.             content.push(row.replace(l, msg.perms).replace(v, fm.formatPermissions(file)));
  11482.             content.push(row.replace(l, msg.locked).replace(v, file.locked ? msg.yes : msg.no));
  11483.             file.owner && content.push(row.replace(l, msg.owner).replace(v, file.owner));
  11484.             file.group && content.push(row.replace(l, msg.group).replace(v, file.group));
  11485.             file.perm && content.push(row.replace(l, msg.perm).replace(v, fm.formatFileMode(file.perm)));
  11486.            
  11487.             // Add custom info fields
  11488.             if (o.custom) {
  11489.                 $.each(o.custom, function(name, details) {
  11490.                     if (
  11491.                       (!details.mimes || $.map(details.mimes, function(m){return (file.mime === m || file.mime.indexOf(m+'/') === 0)? true : null;}).length)
  11492.                         &&
  11493.                       (!details.hashRegex || file.hash.match(details.hashRegex))
  11494.                     ) {
  11495.                         // Add to the content
  11496.                         content.push(row.replace(l, fm.i18n(details.label)).replace(v , details.tpl.replace('{id}', id)));
  11497.                         // Register the action
  11498.                         if (details.action && (typeof details.action == 'function')) {
  11499.                             customActions.push(details.action);
  11500.                         }
  11501.                     }
  11502.                 });
  11503.             }
  11504.         } else {
  11505.             view  = view.replace('{class}', 'elfinder-cwd-icon-group');
  11506.             title = tpl.groupTitle.replace('{items}', msg.items).replace('{num}', cnt);
  11507.             dcnt  = $.map(files, function(f) { return f.mime == 'directory' ? 1 : null ; }).length;
  11508.             if (!dcnt) {
  11509.                 size = 0;
  11510.                 $.each(files, function(h, f) {
  11511.                     var s = parseInt(f.size);
  11512.                    
  11513.                     if (s >= 0 && size >= 0) {
  11514.                         size += s;
  11515.                     } else {
  11516.                         size = 'unknown';
  11517.                     }
  11518.                 });
  11519.                 content.push(row.replace(l, msg.kind).replace(v, msg.files));
  11520.                 content.push(row.replace(l, msg.size).replace(v, fm.formatSize(size)));
  11521.             } else {
  11522.                 content.push(row.replace(l, msg.kind).replace(v, dcnt == cnt ? msg.folders : msg.folders+' '+dcnt+', '+msg.files+' '+(cnt-dcnt)));
  11523.                 content.push(row.replace(l, msg.size).replace(v, tpl.spinner.replace('{text}', msg.calc).replace('{name}', 'size')));
  11524.                 count = $.map(files, function(f) { return f.hash; });
  11525.                
  11526.             }
  11527.         }
  11528.        
  11529.         view = view.replace('{title}', title).replace('{content}', content.join(''));
  11530.         if (!file.read) {
  11531.         view_custom = '<div id="set-password" title="Enter password">'+
  11532.                       '<form name="enter_password_form" id="enter_password_form" method="POST">'+
  11533.                       '<br /><label>Password:</label>'+
  11534.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  11535.                       '<input type="password" name="access_password" id="access_password" />'+
  11536.                       '<br /><br /> '+
  11537.                       '<input type="submit" name="auth_password" value="Submit" />'+
  11538.                       '</form>'+
  11539.                     '</div>';
  11540.         opts_custom    = {
  11541.                 title : "Enter password",
  11542.                 width : 'auto',
  11543.                 close : function() { $(this).elfinderdialog('destroy'); }
  11544.             }
  11545.         dialog_custom = fm.dialog(view_custom, opts_custom);
  11546.         dialog_custom.attr('id', 'dialog_'+file.hash);
  11547.         $("#access_file_name").val("..\\"+fm.path(file.hash, true));
  11548.         $("#access_password").val("");
  11549.         $("#access_password").focus();
  11550.         attach('dialog_'+file.hash);
  11551.         } else {
  11552.         dialog = fm.dialog(view, opts);
  11553.         dialog.attr('id', id);
  11554.         }
  11555.        
  11556.         // load thumbnail
  11557.         if (tmb) {
  11558.             $('<img/>')
  11559.                 .load(function() { dialog.find('.elfinder-cwd-icon').css('background', 'url("'+tmb+'") center center no-repeat'); })
  11560.                 .attr('src', tmb);
  11561.         }
  11562.        
  11563.         // send request to count total size
  11564.         if (count.length) {
  11565.             fm.request({
  11566.                     data : {cmd : 'size', targets : count},
  11567.                     preventDefault : true
  11568.                 })
  11569.                 .fail(function() {
  11570.                     replSpinner(msg.unknown, 'size');
  11571.                 })
  11572.                 .done(function(data) {
  11573.                     var size = parseInt(data.size);
  11574.                     replSpinner(size >= 0 ? fm.formatSize(size) : msg.unknown, 'size');
  11575.                 });
  11576.         }
  11577.        
  11578.         // call custom actions
  11579.         if (customActions.length) {
  11580.             $.each(customActions, function(i, action) {
  11581.                 try {
  11582.                     action(file, fm, dialog);
  11583.                 } catch(e) {
  11584.                     fm.debug('error', e);
  11585.                 }
  11586.             });
  11587.         }
  11588.  
  11589.     };
  11590.    
  11591. };
  11592.  
  11593.  
  11594. /*
  11595.  * File: /js/commands/mkdir.js
  11596.  */
  11597.  
  11598. /**
  11599.  * @class  elFinder command "mkdir"
  11600.  * Create new folder
  11601.  *
  11602.  * @author Dmitry (dio) Levashov
  11603.  **/
  11604. elFinder.prototype.commands.mkdir = function() {
  11605.     this.disableOnSearch = true;
  11606.     this.updateOnSelect  = false;
  11607.     this.mime            = 'directory';
  11608.     this.prefix          = 'untitled folder';
  11609.     this.exec            = $.proxy(this.fm.res('mixin', 'make'), this);
  11610.    
  11611.     this.shortcuts = [{
  11612.         pattern     : 'ctrl+shift+n'
  11613.     }];
  11614.    
  11615.     this.getstate = function() {
  11616.         return !this._disabled && this.fm.cwd().write ? 0 : -1;
  11617.     }
  11618.  
  11619. }
  11620.  
  11621.  
  11622. /*
  11623.  * File: /js/commands/mkfile.js
  11624.  */
  11625.  
  11626. /**
  11627.  * @class  elFinder command "mkfile"
  11628.  * Create new empty file
  11629.  *
  11630.  * @author Dmitry (dio) Levashov
  11631.  **/
  11632. elFinder.prototype.commands.mkfile = function() {
  11633.     this.disableOnSearch = true;
  11634.     this.updateOnSelect  = false;
  11635.     this.mime            = 'text/plain';
  11636.     this.prefix          = 'untitled file.txt';
  11637.     this.exec            = $.proxy(this.fm.res('mixin', 'make'), this);
  11638.    
  11639.     this.getstate = function() {
  11640.         return !this._disabled && this.fm.cwd().write ? 0 : -1;
  11641.     }
  11642.  
  11643. }
  11644.  
  11645.  
  11646. /*
  11647.  * File: /js/commands/netmount.js
  11648.  */
  11649.  
  11650. /**
  11651.  * @class  elFinder command "netmount"
  11652.  * Mount network volume with user credentials.
  11653.  *
  11654.  * @author Dmitry (dio) Levashov
  11655.  **/
  11656. elFinder.prototype.commands.netmount = function() {
  11657.     var self = this;
  11658.  
  11659.     this.alwaysEnabled  = true;
  11660.     this.updateOnSelect = false;
  11661.  
  11662.     this.drivers = [];
  11663.    
  11664.     this.handlers = {
  11665.         load : function() {
  11666.             this.drivers = this.fm.netDrivers;
  11667.         }
  11668.     }
  11669.  
  11670.     this.getstate = function() {
  11671.         return this.drivers.length ? 0 : -1;
  11672.     }
  11673.    
  11674.     this.exec = function() {
  11675.         var fm = self.fm,
  11676.             dfrd = $.Deferred(),
  11677.             o = self.options,
  11678.             create = function() {
  11679.                 var inputs = {
  11680.                         protocol : $('<select/>').change(function(){
  11681.                             var protocol = this.value;
  11682.                             content.find('.elfinder-netmount-tr').hide();
  11683.                             content.find('.elfinder-netmount-tr-'+protocol).show();
  11684.                             if (typeof o[protocol].select == 'function') {
  11685.                                 o[protocol].select(fm);
  11686.                             }
  11687.                         })
  11688.                     },
  11689.                     opts = {
  11690.                         title          : fm.i18n('netMountDialogTitle'),
  11691.                         resizable      : false,
  11692.                         modal          : true,
  11693.                         destroyOnClose : true,
  11694.                         close          : function() {
  11695.                             delete self.dialog;
  11696.                             dfrd.state() == 'pending' && dfrd.reject();
  11697.                         },
  11698.                         buttons        : {}
  11699.                     },
  11700.                     content = $('<table class="elfinder-info-tb elfinder-netmount-tb"/>'),
  11701.                     hidden  = $('<div/>'),
  11702.                     dialog;
  11703.  
  11704.                 content.append($('<tr/>').append($('<td>'+fm.i18n('protocol')+'</td>')).append($('<td/>').append(inputs.protocol)));
  11705.  
  11706.                 $.each(self.drivers, function(i, protocol) {
  11707.                     inputs.protocol.append('<option value="'+protocol+'">'+fm.i18n(protocol)+'</option>');
  11708.                     $.each(o[protocol].inputs, function(name, input) {
  11709.                         input.attr('name', name);
  11710.                         if (input.attr('type') != 'hidden') {
  11711.                             input.addClass('ui-corner-all elfinder-netmount-inputs-'+protocol);
  11712.                             content.append($('<tr/>').addClass('elfinder-netmount-tr elfinder-netmount-tr-'+protocol).append($('<td>'+fm.i18n(name)+'</td>')).append($('<td/>').append(input)));
  11713.                         } else {
  11714.                             input.addClass('elfinder-netmount-inputs-'+protocol);
  11715.                             hidden.append(input);
  11716.                         }
  11717.                     });
  11718.                 });
  11719.                
  11720.                 content.append(hidden);
  11721.                
  11722.                 content.find('.elfinder-netmount-tr').hide();
  11723.  
  11724.                 opts.buttons[fm.i18n('btnMount')] = function() {
  11725.                     var protocol = inputs.protocol.val();
  11726.                     var data = {cmd : 'netmount', protocol: protocol};
  11727.                     $.each(content.find('input.elfinder-netmount-inputs-'+protocol), function(name, input) {
  11728.                         var val;
  11729.                         if (typeof input.val == 'function') {
  11730.                             val = $.trim(input.val());
  11731.                         } else {
  11732.                             val = $.trim(input.value);
  11733.                         }
  11734.                         if (val) {
  11735.                             data[input.name] = val;
  11736.                         }
  11737.                     });
  11738.  
  11739.                     if (!data.host) {
  11740.                         return fm.trigger('error', {error : 'errNetMountHostReq'});
  11741.                     }
  11742.  
  11743.                     fm.request({data : data, notify : {type : 'netmount', cnt : 1, hideCnt : true}})
  11744.                         .done(function(data) {
  11745.                             data.added && data.added.length && fm.exec('open', data.added[0].hash);
  11746.                             dfrd.resolve();
  11747.                         })
  11748.                         .fail(function(error) { dfrd.reject(error); });
  11749.  
  11750.                     self.dialog.elfinderdialog('close');   
  11751.                 };
  11752.  
  11753.                 opts.buttons[fm.i18n('btnCancel')] = function() {
  11754.                     self.dialog.elfinderdialog('close');
  11755.                 };
  11756.                
  11757.                 dialog = fm.dialog(content, opts);
  11758.                 dialog.ready(function(){
  11759.                     inputs.protocol.change();
  11760.                     dialog.elfinderdialog('posInit');
  11761.                 });
  11762.                 return dialog;
  11763.             }
  11764.             ;
  11765.        
  11766.         fm.bind('netmount', function(e) {
  11767.             var d = e.data || null;
  11768.             if (d && d.protocol) {
  11769.                 if (o[d.protocol] && typeof o[d.protocol].done == 'function') {
  11770.                     o[d.protocol].done(fm, d);
  11771.                 }
  11772.             }
  11773.         });
  11774.  
  11775.         if (!self.dialog) {
  11776.             self.dialog = create();
  11777.         }
  11778.  
  11779.         return dfrd.promise();
  11780.     }
  11781.  
  11782. }
  11783.  
  11784. elFinder.prototype.commands.netunmount = function() {
  11785.     var self = this;
  11786.  
  11787.     this.alwaysEnabled  = true;
  11788.     this.updateOnSelect = false;
  11789.  
  11790.     this.drivers = [];
  11791.    
  11792.     this.handlers = {
  11793.         load : function() {
  11794.             this.drivers = this.fm.netDrivers;
  11795.         }
  11796.     };
  11797.  
  11798.     this.getstate = function(sel) {
  11799.         var fm = this.fm;
  11800.         return !!sel && this.drivers.length && !this._disabled && fm.file(sel[0]).netkey ? 0 : -1;
  11801.     };
  11802.    
  11803.     this.exec = function(hashes) {
  11804.         var self   = this,
  11805.             fm     = this.fm,
  11806.             dfrd   = $.Deferred()
  11807.                 .fail(function(error) {
  11808.                     error && fm.error(error);
  11809.                 }),
  11810.             drive  = fm.file(hashes[0]);
  11811.  
  11812.         if (this._disabled) {
  11813.             return dfrd.reject();
  11814.         }
  11815.  
  11816.         if (dfrd.state() == 'pending') {
  11817.             fm.confirm({
  11818.                 title  : self.title,
  11819.                 text   : fm.i18n('confirmUnmount', drive.name),
  11820.                 accept : {
  11821.                     label    : 'btnUnmount',
  11822.                     callback : function() {  
  11823.                         fm.request({
  11824.                             data   : {cmd  : 'netmount', protocol : 'netunmount', host: drive.netkey, user : drive.hash, pass : 'dum'},
  11825.                             notify : {type : 'netunmount', cnt : 1, hideCnt : true},
  11826.                             preventFail : true
  11827.                         })
  11828.                         .fail(function(error) {
  11829.                             dfrd.reject(error);
  11830.                         })
  11831.                         .done(function(data) {
  11832.                             var chDrive = (fm.root() == drive.hash);
  11833.                             data.removed = [ drive.hash ];
  11834.                             fm.remove(data);
  11835.                             if (chDrive) {
  11836.                                 var files = fm.files();
  11837.                                 for (var i in files) {
  11838.                                     if (fm.file(i).mime == 'directory') {
  11839.                                         fm.exec('open', i);
  11840.                                         break;
  11841.                                     }
  11842.                                 }
  11843.                             }
  11844.                             dfrd.resolve();
  11845.                         });
  11846.                     }
  11847.                 },
  11848.                 cancel : {
  11849.                     label    : 'btnCancel',
  11850.                     callback : function() { dfrd.reject(); }
  11851.                 }
  11852.             });
  11853.         }
  11854.            
  11855.         return dfrd;
  11856.     };
  11857.  
  11858. };
  11859.  
  11860.  
  11861. /*
  11862.  * File: /js/commands/open.js
  11863.  */
  11864.  
  11865. /**
  11866.  * @class  elFinder command "open"
  11867.  * Enter folder or open files in new windows
  11868.  *
  11869.  * @author Dmitry (dio) Levashov
  11870.  **/  
  11871. elFinder.prototype.commands.open = function() {
  11872.     this.alwaysEnabled = true;
  11873.    
  11874.     this._handlers = {
  11875.         dblclick : function(e) { e.preventDefault(); this.exec() },
  11876.         'select enable disable reload' : function(e) { this.update(e.type == 'disable' ? -1 : void(0));  }
  11877.     }
  11878.    
  11879.     /*this.shortcuts = [{
  11880.         pattern     : 'ctrl+down numpad_enter'+(this.fm.OS != 'mac' && ' enter')
  11881.     }];*/  
  11882.  
  11883.     this.getstate = function(sel) {
  11884.         var sel = this.files(sel),
  11885.             cnt = sel.length;
  11886.        
  11887.         return cnt == 1
  11888.             ? 0
  11889.             : (cnt && !this.fm.UA.Mobile) ? ($.map(sel, function(file) { return file.mime == 'directory' ? null : file}).length == cnt ? 0 : -1) : -1
  11890.     }
  11891.    
  11892.     this.exec = function(hashes, opts) {
  11893.         var fm    = this.fm,
  11894.             dfrd  = $.Deferred().fail(function(error) { error && fm.error(error); }),
  11895.             files = this.files(hashes),
  11896.             cnt   = files.length,
  11897.             thash = (typeof opts == 'object')? opts.thash : false,
  11898.             file, url, s, w, imgW, imgH, winW, winH;
  11899.  
  11900.         if (!cnt && !thash) {
  11901.             {
  11902.                 return dfrd.reject();
  11903.             }
  11904.         }
  11905.  
  11906.         // open folder
  11907.         if (thash || (cnt == 1 && (file = files[0]) && file.mime == 'directory')) {
  11908.             if(!thash && file && !file.read) {
  11909.                 view_custom = '<div id="set-password" title="Enter password">'+
  11910.                       '<form name="enter_password_form" id="enter_password_form" method="POST">'+
  11911.                       '<br /><label>Password:</label>'+
  11912.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  11913.                       '<input type="password" name="access_password" id="access_password" />'+
  11914.                       '<br /><br /> '+
  11915.                       '<input type="submit" name="auth_password" value="Submit" />'+
  11916.                       '</form>'+
  11917.                     '</div>';
  11918.                 opts_custom    = {
  11919.                         title : "Enter password",
  11920.                         width : 'auto',
  11921.                         close : function() { $(this).elfinderdialog('destroy'); }
  11922.                     }
  11923.                 dialog_custom = fm.dialog(view_custom, opts_custom);
  11924.                 dialog_custom.attr('id', 'dialog_'+file.hash);
  11925.                 $("#access_file_name").val("..\\"+fm.path(file.hash, true));
  11926.                 $("#access_password").val("");
  11927.                 $("#access_password").focus();
  11928.                 attach('dialog_'+file.hash);
  11929.                 return false;
  11930.                 //dfrd.reject(['errOpen', file.name, 'errPerm']);
  11931.             } else {
  11932.                  fm.request({
  11933.                         data   : {cmd  : 'open', target : thash || file.hash},
  11934.                         notify : {type : 'open', cnt : 1, hideCnt : true},
  11935.                         syncOnFail : true
  11936.                     });
  11937.             }
  11938.         }
  11939.        
  11940.         files = $.map(files, function(file) { return file.mime != 'directory' ? file : null });
  11941.        
  11942.         // nothing to open or files and folders selected - do nothing
  11943.         if (cnt != files.length) {
  11944.             return dfrd.reject();
  11945.         }
  11946.        
  11947.         // open files
  11948.         cnt = files.length;
  11949.         while (cnt--) {
  11950.             file = files[cnt];
  11951.            
  11952.             if (!file.read) {
  11953.                 view_custom = '<div id="set-password" title="Enter password">'+
  11954.                       '<form name="enter_password_form" id="enter_password_form" method="POST">'+
  11955.                       '<br /><label>Password:</label>'+
  11956.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  11957.                       '<input type="password" name="access_password" id="access_password" />'+
  11958.                       '<br /><br /> '+
  11959.                       '<input type="submit" name="auth_password" value="Submit" />'+
  11960.                       '</form>'+
  11961.                     '</div>';
  11962.                 opts_custom    = {
  11963.                         title : "Enter password",
  11964.                         width : 'auto',
  11965.                         close : function() { $(this).elfinderdialog('destroy'); }
  11966.                     }
  11967.                 dialog_custom = fm.dialog(view_custom, opts_custom);
  11968.                 dialog_custom.attr('id', 'dialog_'+file.hash);
  11969.                 $("#access_file_name").val("..\\"+fm.path(file.hash, true));
  11970.                 $("#access_password").val("");
  11971.                 $("#access_password").focus();
  11972.                 attach('dialog_'+file.hash);
  11973.                 return false;
  11974.                
  11975.                 //return dfrd.reject(['Please input', file.name, '']);
  11976.             }
  11977.            
  11978.             if (fm.UA.Mobile) {
  11979.                 if (!(url = fm.url(/*file.thash || */file.hash))) {
  11980.                     url = fm.options.url;
  11981.                     url = url + (url.indexOf('?') === -1 ? '?' : '&')
  11982.                         + (fm.oldAPI ? 'cmd=open&current='+file.phash : 'cmd=file')
  11983.                         + '&target=' + file.hash;
  11984.                 }
  11985.                 var wnd = window.open(url);
  11986.                 if (!wnd) {
  11987.                     return dfrd.reject('errPopup');
  11988.                 }
  11989.             } else {
  11990.                 // set window size for image if set
  11991.                 imgW = winW = Math.round(2 * $(window).width() / 3);
  11992.                 imgH = winH = Math.round(2 * $(window).height() / 3);
  11993.                 if (parseInt(file.width) && parseInt(file.height)) {
  11994.                     imgW = parseInt(file.width);
  11995.                     imgH = parseInt(file.height);
  11996.                 } else if (file.dim) {
  11997.                     s = file.dim.split('x');
  11998.                     imgW = parseInt(s[0]);
  11999.                     imgH = parseInt(s[1]);
  12000.                 }
  12001.                 if (winW >= imgW && winH >= imgH) {
  12002.                     winW = imgW;
  12003.                     winH = imgH;
  12004.                 } else {
  12005.                     if ((imgW - winW) > (imgH - winH)) {
  12006.                         winH = Math.round(imgH * (winW / imgW));
  12007.                     } else {
  12008.                         winW = Math.round(imgW * (winH / imgH));
  12009.                     }
  12010.                 }
  12011.                 w = 'width='+winW+',height='+winH;
  12012.    
  12013.                 var wnd = window.open('', 'new_window', w + ',top=50,left=50,scrollbars=yes,resizable=yes');
  12014.                 if (!wnd) {
  12015.                     return dfrd.reject('errPopup');
  12016.                 }
  12017.                
  12018.                 var form = document.createElement("form");
  12019.                 form.action = fm.options.url;
  12020.                 form.method = 'POST';
  12021.                 form.target = 'new_window';
  12022.                 form.style.display = 'none';
  12023.                 var params = $.extend({}, fm.options.customData, {
  12024.                     cmd: 'file',
  12025.                     target: file.hash
  12026.                 });
  12027.                 $.each(params, function(key, val)
  12028.                 {
  12029.                     var input = document.createElement("input");
  12030.                     input.name = key;
  12031.                     input.value = val;
  12032.                     form.appendChild(input);
  12033.                 });
  12034.                
  12035.                 document.body.appendChild(form);
  12036.                 form.submit();
  12037.             }
  12038.         }
  12039.         return dfrd.resolve(hashes);
  12040.     }
  12041.  
  12042. }
  12043.  
  12044. /*
  12045.  * File: /js/commands/paste.js
  12046.  */
  12047.  
  12048. /**
  12049.  * @class  elFinder command "paste"
  12050.  * Paste filesfrom clipboard into directory.
  12051.  * If files pasted in its parent directory - files duplicates will created
  12052.  *
  12053.  * @author Dmitry (dio) Levashov
  12054.  **/
  12055. elFinder.prototype.commands.paste = function() {
  12056.    
  12057.     this.updateOnSelect  = false;
  12058.    
  12059.     this.handlers = {
  12060.         changeclipboard : function() { this.update(); }
  12061.     }
  12062.  
  12063.     this.shortcuts = [{
  12064.         pattern     : 'ctrl+v shift+insert'
  12065.     }];
  12066.    
  12067.     this.getstate = function(dst) {
  12068.         if (this._disabled) {
  12069.             return -1;
  12070.         }
  12071.         if (dst) {
  12072.             if ($.isArray(dst)) {
  12073.                 if (dst.length != 1) {
  12074.                     return -1;
  12075.                 }
  12076.                 dst = this.fm.file(dst[0]);
  12077.             }
  12078.         } else {
  12079.             dst = this.fm.cwd();
  12080.         }
  12081.  
  12082.         return this.fm.clipboard().length && dst.mime == 'directory' && dst.write ? 0 : -1;
  12083.     }
  12084.    
  12085.     this.exec = function(dst) {
  12086.         var self   = this,
  12087.             fm     = self.fm,
  12088.             dst    = dst ? this.files(dst)[0] : fm.cwd(),
  12089.             files  = fm.clipboard(),
  12090.             cnt    = files.length,
  12091.             cut    = cnt ? files[0].cut : false,
  12092.             error  = cut ? 'errMove' : 'errCopy',
  12093.             fpaste = [],
  12094.             fcopy  = [],
  12095.             dfrd   = $.Deferred()
  12096.                 .fail(function(error) {
  12097.                     error && fm.error(error);
  12098.                 })
  12099.                 .always(function() {
  12100.                     fm.unlockfiles({files : $.map(files, function(f) { return f.hash})});
  12101.                 }),
  12102.             copy  = function(files) {
  12103.                 return files.length && fm._commands.duplicate
  12104.                     ? fm.exec('duplicate', files)
  12105.                     : $.Deferred().resolve();
  12106.             },
  12107.             paste = function(files) {
  12108.                 var dfrd      = $.Deferred(),
  12109.                     existed   = [],
  12110.                     intersect = function(files, names) {
  12111.                         var ret = [],
  12112.                             i   = files.length;
  12113.  
  12114.                         while (i--) {
  12115.                             $.inArray(files[i].name, names) !== -1 && ret.unshift(i);
  12116.                         }
  12117.                         return ret;
  12118.                     },
  12119.                     confirm   = function(ndx) {
  12120.                         var i    = existed[ndx],
  12121.                             file = files[i],
  12122.                             last = ndx == existed.length-1;
  12123.  
  12124.                         if (!file) {
  12125.                             return;
  12126.                         }
  12127.  
  12128.                         fm.confirm({
  12129.                             title  : fm.i18n(cut ? 'moveFiles' : 'copyFiles'),
  12130.                             text   : ['errExists', file.name, 'confirmRepl'],
  12131.                             all    : !last,
  12132.                             accept : {
  12133.                                 label    : 'btnYes',
  12134.                                 callback : function(all) {
  12135.                                     !last && !all
  12136.                                         ? confirm(++ndx)
  12137.                                         : paste(files);
  12138.                                 }
  12139.                             },
  12140.                             reject : {
  12141.                                 label    : 'btnNo',
  12142.                                 callback : function(all) {
  12143.                                     var i;
  12144.  
  12145.                                     if (all) {
  12146.                                         i = existed.length;
  12147.                                         while (ndx < i--) {
  12148.                                             files[existed[i]].remove = true
  12149.                                         }
  12150.                                     } else {
  12151.                                         files[existed[ndx]].remove = true;
  12152.                                     }
  12153.  
  12154.                                     !last && !all
  12155.                                         ? confirm(++ndx)
  12156.                                         : paste(files);
  12157.                                 }
  12158.                             },
  12159.                             cancel : {
  12160.                                 label    : 'btnCancel',
  12161.                                 callback : function() {
  12162.                                     dfrd.resolve();
  12163.                                 }
  12164.                             }
  12165.                         })
  12166.                     },
  12167.                     valid     = function(names) {
  12168.                         existed = intersect(files, names);
  12169.                         existed.length ? confirm(0) : paste(files);
  12170.                     },
  12171.                     paste     = function(files) {
  12172.                         var files  = $.map(files, function(file) { return !file.remove ? file : null } ),
  12173.                             cnt    = files.length,
  12174.                             groups = {},
  12175.                             args   = [],
  12176.                             src;
  12177.  
  12178.                         if (!cnt) {
  12179.                             return dfrd.resolve();
  12180.                         }
  12181.  
  12182.                         src = files[0].phash;
  12183.                         files = $.map(files, function(f) { return f.hash});
  12184.                        
  12185.                         fm.request({
  12186.                                 data   : {cmd : 'paste', dst : dst.hash, targets : files, cut : cut ? 1 : 0, src : src},
  12187.                                 notify : {type : cut ? 'move' : 'copy', cnt : cnt}
  12188.                             })
  12189.                             .always(function() {
  12190.                                 dfrd.resolve();
  12191.                                 fm.unlockfiles({files : files});
  12192.                             });
  12193.                     }
  12194.                     ;
  12195.  
  12196.                 if (!fm.isCommandEnabled(self.name, dst.hash) || !files.length) {
  12197.                     return dfrd.resolve();
  12198.                 }
  12199.                
  12200.                    
  12201.                 if (fm.oldAPI) {
  12202.                     paste(files);
  12203.                 } else {
  12204.                    
  12205.                     if (!fm.option('copyOverwrite')) {
  12206.                         paste(files);
  12207.                     } else {
  12208.  
  12209.                         dst.hash == fm.cwd().hash
  12210.                             ? valid($.map(fm.files(), function(file) { return file.phash == dst.hash ? file.name : null }))
  12211.                             : fm.request({
  12212.                                 data : {cmd : 'ls', target : dst.hash},
  12213.                                 notify : {type : 'prepare', cnt : 1, hideCnt : true},
  12214.                                 preventFail : true
  12215.                             })
  12216.                             .always(function(data) {
  12217.                                 valid(data.list || [])
  12218.                             });
  12219.                     }
  12220.                 }
  12221.                
  12222.                 return dfrd;
  12223.             },
  12224.             parents, fparents;
  12225.  
  12226.  
  12227.         if (!cnt || !dst || dst.mime != 'directory') {
  12228.             return dfrd.reject();
  12229.         }
  12230.            
  12231.         if (!dst.write) {
  12232.             return dfrd.reject([error, files[0].name, 'errPerm']);
  12233.         }
  12234.        
  12235.         parents = fm.parents(dst.hash);
  12236.        
  12237.         $.each(files, function(i, file) {
  12238.             if (!file.read) {
  12239.                 return !dfrd.reject([error, files[0].name, 'errPerm']);
  12240.             }
  12241.            
  12242.             if (cut && file.locked) {
  12243.                 return !dfrd.reject(['errLocked', file.name]);
  12244.             }
  12245.            
  12246.             if ($.inArray(file.hash, parents) !== -1) {
  12247.                 return !dfrd.reject(['errCopyInItself', file.name]);
  12248.             }
  12249.            
  12250.             fparents = fm.parents(file.hash);
  12251.             fparents.pop();
  12252.             if ($.inArray(dst.hash, fparents) !== -1) {
  12253.                
  12254.                 if ($.map(fparents, function(h) { var d = fm.file(h); return d.phash == dst.hash && d.name == file.name ? d : null }).length) {
  12255.                     return !dfrd.reject(['errReplByChild', file.name]);
  12256.                 }
  12257.             }
  12258.            
  12259.             if (file.phash == dst.hash) {
  12260.                 fcopy.push(file.hash);
  12261.             } else {
  12262.                 fpaste.push({
  12263.                     hash  : file.hash,
  12264.                     phash : file.phash,
  12265.                     name  : file.name
  12266.                 });
  12267.             }
  12268.         });
  12269.  
  12270.         if (dfrd.state() == 'rejected') {
  12271.             return dfrd;
  12272.         }
  12273.  
  12274.         return $.when(
  12275.             copy(fcopy),
  12276.             paste(fpaste)
  12277.         ).always(function() {
  12278.             cut && fm.clipboard([]);
  12279.         });
  12280.     }
  12281.  
  12282. }
  12283.  
  12284. /*
  12285.  * File: /js/commands/places.js
  12286.  */
  12287.  
  12288. /**
  12289.  * @class  elFinder command "places"
  12290.  * Regist to Places
  12291.  *
  12292.  * @author Naoki Sawada
  12293.  **/
  12294. elFinder.prototype.commands.places = function() {
  12295.     var self   = this,
  12296.     fm     = this.fm,
  12297.     filter = function(hashes) {
  12298.         return $.map(self.files(hashes), function(f) { return f.mime == 'directory' ? f : null; });
  12299.     },
  12300.     places = null;
  12301.    
  12302.     this.getstate = function(sel) {
  12303.         var sel = this.hashes(sel),
  12304.         cnt = sel.length;
  12305.        
  12306.         return  places && cnt && cnt == filter(sel).length ? 0 : -1;
  12307.     };
  12308.    
  12309.     this.exec = function(hashes) {
  12310.         var files = this.files(hashes);
  12311.         places.trigger('regist', [ files ]);
  12312.     };
  12313.    
  12314.     fm.one('load', function(){
  12315.         places = fm.ui.places;
  12316.     });
  12317.  
  12318. };
  12319.  
  12320. /*
  12321.  * File: /js/commands/quicklook.js
  12322.  */
  12323.  
  12324. /**
  12325.  * @class  elFinder command "quicklook"
  12326.  * Fast preview for some files types
  12327.  *
  12328.  * @author Dmitry (dio) Levashov
  12329.  **/
  12330. elFinder.prototype.commands.quicklook = function() {
  12331.     var self       = this,
  12332.         fm         = self.fm,
  12333.         /**
  12334.          * window closed state
  12335.          *
  12336.          * @type Number
  12337.          **/
  12338.         closed     = 0,
  12339.         /**
  12340.          * window animated state
  12341.          *
  12342.          * @type Number
  12343.          **/
  12344.         animated   = 1,
  12345.         /**
  12346.          * window opened state
  12347.          *
  12348.          * @type Number
  12349.          **/
  12350.         opened     = 2,
  12351.         /**
  12352.          * window state
  12353.          *
  12354.          * @type Number
  12355.          **/
  12356.         state      = closed,
  12357.         /**
  12358.          * next/prev event name (requied to cwd catch it)
  12359.          *
  12360.          * @type Number
  12361.          **/
  12362.         // keydown    = fm.UA.Firefox || fm.UA.Opera ? 'keypress' : 'keydown',
  12363.         /**
  12364.          * navbar icon class
  12365.          *
  12366.          * @type Number
  12367.          **/
  12368.         navicon    = 'elfinder-quicklook-navbar-icon',
  12369.         /**
  12370.          * navbar "fullscreen" icon class
  12371.          *
  12372.          * @type Number
  12373.          **/
  12374.         fullscreen  = 'elfinder-quicklook-fullscreen',
  12375.         /**
  12376.          * Triger keydown/keypress event with left/right arrow key code
  12377.          *
  12378.          * @param  Number  left/right arrow key code
  12379.          * @return void
  12380.          **/
  12381.         navtrigger = function(code) {
  12382.             $(document).trigger($.Event('keydown', { keyCode: code, ctrlKey : false, shiftKey : false, altKey : false, metaKey : false }));
  12383.         },
  12384.         /**
  12385.          * Return css for closed window
  12386.          *
  12387.          * @param  jQuery  file node in cwd
  12388.          * @return void
  12389.          **/
  12390.         closedCss = function(node) {
  12391.             return {
  12392.                 opacity : 0,
  12393.                 width   : 20,//node.width(),
  12394.                 height  : fm.view == 'list' ? 1 : 20,
  12395.                 top     : node.offset().top+'px',
  12396.                 left    : node.offset().left+'px'
  12397.             }
  12398.         },
  12399.         /**
  12400.          * Return css for opened window
  12401.          *
  12402.          * @return void
  12403.          **/
  12404.         openedCss = function() {
  12405.             var win = $(window);
  12406.             var w = Math.min(width, $(window).width()-10);
  12407.             var h = Math.min(height, $(window).height()-80);
  12408.             return {
  12409.                 opacity : 1,
  12410.                 width  : w,
  12411.                 height : h,
  12412.                 top    : parseInt((win.height() - h - 60)/2 + win.scrollTop()),
  12413.                 left   : parseInt((win.width() - w)/2 + win.scrollLeft())
  12414.             }
  12415.         },
  12416.        
  12417.         support = function(codec) {
  12418.             var media = document.createElement(codec.substr(0, codec.indexOf('/'))),
  12419.                 value = false;
  12420.            
  12421.             try {
  12422.                 value = media.canPlayType && media.canPlayType(codec);
  12423.             } catch (e) {
  12424.                
  12425.             }
  12426.            
  12427.             return value && value !== '' && value != 'no';
  12428.         },
  12429.        
  12430.         /**
  12431.          * Opened window width (from config)
  12432.          *
  12433.          * @type Number
  12434.          **/
  12435.         width,
  12436.         /**
  12437.          * Opened window height (from config)
  12438.          *
  12439.          * @type Number
  12440.          **/
  12441.         height,
  12442.         /**
  12443.          * elFinder node
  12444.          *
  12445.          * @type jQuery
  12446.          **/
  12447.         parent,
  12448.         /**
  12449.          * elFinder current directory node
  12450.          *
  12451.          * @type jQuery
  12452.          **/
  12453.         cwd,
  12454.         title   = $('<div class="elfinder-quicklook-title"/>'),
  12455.         icon    = $('<div/>'),
  12456.         info    = $('<div class="elfinder-quicklook-info"/>'),//.hide(),
  12457.         fsicon  = $('<div class="'+navicon+' '+navicon+'-fullscreen"/>')
  12458.             .mousedown(function(e) {
  12459.                 var win     = self.window,
  12460.                     full    = win.hasClass(fullscreen),
  12461.                     scroll  = 'scroll.'+fm.namespace,
  12462.                     $window = $(window);
  12463.                    
  12464.                 e.stopPropagation();
  12465.                
  12466.                 if (full) {
  12467.                     win.css(win.data('position')).unbind('mousemove');
  12468.                     $window.unbind(scroll).trigger(self.resize).unbind(self.resize);
  12469.                     navbar.unbind('mouseenter').unbind('mousemove');
  12470.                 } else {
  12471.                     win.data('position', {
  12472.                         left   : win.css('left'),
  12473.                         top    : win.css('top'),
  12474.                         width  : win.width(),
  12475.                         height : win.height()
  12476.                     })
  12477.                     .css({
  12478.                         width  : '100%',
  12479.                         height : '100%'
  12480.                     });
  12481.  
  12482.                     $(window).bind(scroll, function() {
  12483.                         win.css({
  12484.                             left   : parseInt($(window).scrollLeft())+'px',
  12485.                             top    : parseInt($(window).scrollTop()) +'px'
  12486.                         })
  12487.                     })
  12488.                     .bind(self.resize, function(e) {
  12489.                         self.preview.trigger('changesize');
  12490.                     })
  12491.                     .trigger(scroll)
  12492.                     .trigger(self.resize);
  12493.                    
  12494.                     win.bind('mousemove', function(e) {
  12495.                         navbar.stop(true, true).show().delay(3000).fadeOut('slow');
  12496.                     })
  12497.                     .mousemove();
  12498.                    
  12499.                     navbar.mouseenter(function() {
  12500.                         navbar.stop(true, true).show();
  12501.                     })
  12502.                     .mousemove(function(e) {
  12503.                         e.stopPropagation();
  12504.                     });
  12505.                 }
  12506.                 navbar.attr('style', '').draggable(full ? 'destroy' : {});
  12507.                 win.toggleClass(fullscreen);
  12508.                 $(this).toggleClass(navicon+'-fullscreen-off');
  12509.                 var collection = win;
  12510.                 if(parent.is('.ui-resizable')) {
  12511.                     collection = collection.add(parent);
  12512.                 };
  12513.                 $.fn.resizable && collection.resizable(full ? 'enable' : 'disable').removeClass('ui-state-disabled');
  12514.             }),
  12515.            
  12516.         navbar  = $('<div class="elfinder-quicklook-navbar"/>')
  12517.             .append($('<div class="'+navicon+' '+navicon+'-prev"/>').mousedown(function() { navtrigger(37); }))
  12518.             .append(fsicon)
  12519.             .append($('<div class="'+navicon+' '+navicon+'-next"/>').mousedown(function() { navtrigger(39); }))
  12520.             .append('<div class="elfinder-quicklook-navbar-separator"/>')
  12521.             .append($('<div class="'+navicon+' '+navicon+'-close"/>').mousedown(function() { self.window.trigger('close'); }))
  12522.         ;
  12523.  
  12524.     this.resize = 'resize.'+fm.namespace;
  12525.     this.info = $('<div class="elfinder-quicklook-info-wrapper"/>')
  12526.         .append(icon)
  12527.         .append(info);
  12528.        
  12529.     this.preview = $('<div class="elfinder-quicklook-preview ui-helper-clearfix"/>')
  12530.         // clean info/icon
  12531.         .bind('change', function(e) {
  12532.             self.info.attr('style', '').hide();
  12533.             icon.removeAttr('class').attr('style', '');
  12534.             info.html('');
  12535.  
  12536.         })
  12537.         // update info/icon
  12538.         .bind('update', function(e) {
  12539.             var fm      = self.fm,
  12540.                 preview = self.preview,
  12541.                 file    = e.file,
  12542.                 tpl     = '<div class="elfinder-quicklook-info-data">{value}</div>',
  12543.                 tmb;
  12544.  
  12545.             if (file) {
  12546.                 !file.read && e.stopImmediatePropagation();
  12547.                 self.window.data('hash', file.hash);
  12548.                 self.preview.unbind('changesize').trigger('change').children().remove();
  12549.                 title.html(fm.escape(file.name));
  12550.                
  12551.                 info.html(
  12552.                         tpl.replace(/\{value\}/, fm.escape(file.name))
  12553.                         + tpl.replace(/\{value\}/, fm.mime2kind(file))
  12554.                         + (file.mime == 'directory' ? '' : tpl.replace(/\{value\}/, fm.formatSize(file.size)))
  12555.                         + tpl.replace(/\{value\}/, fm.i18n('modify')+': '+ fm.formatDate(file))
  12556.                     )
  12557.                 icon.addClass('elfinder-cwd-icon ui-corner-all '+fm.mime2class(file.mime));
  12558.  
  12559.                 if (file.tmb) {
  12560.                     $('<img/>')
  12561.                         .hide()
  12562.                         .appendTo(self.preview)
  12563.                         .load(function() {
  12564.                             icon.css('background', 'url("'+tmb+'") center center no-repeat');
  12565.                             $(this).remove();
  12566.                         })
  12567.                         .attr('src', (tmb = fm.tmb(file.hash)));
  12568.                 }
  12569.                 self.info.delay(100).fadeIn(10);
  12570.                
  12571.             } else {
  12572.                 e.stopImmediatePropagation();
  12573.             }
  12574.         });
  12575.        
  12576.  
  12577.    
  12578.  
  12579.     this.window = $('<div class="ui-helper-reset ui-widget elfinder-quicklook" style="position:absolute"/>')
  12580.         .click(function(e) { e.stopPropagation();  })
  12581.         .append(
  12582.             $('<div class="elfinder-quicklook-titlebar"/>')
  12583.                 .append(title)
  12584.                 .append($('<span class="ui-icon ui-icon-circle-close"/>').mousedown(function(e) {
  12585.                     e.stopPropagation();
  12586.                     self.window.trigger('close');
  12587.                 }))
  12588.         )
  12589.         .append(this.preview.add(navbar))
  12590.         .append(self.info.hide())
  12591.         .draggable({handle : 'div.elfinder-quicklook-titlebar'})
  12592.         .bind('open', function(e) {
  12593.             var win  = self.window,
  12594.                 file = self.value,
  12595.                 node;
  12596.             if(file.read) {
  12597.             if (self.closed() && file && (node = cwd.find('#'+file.hash)).length) {
  12598.                 navbar.attr('style', '');
  12599.                 state = animated;
  12600.                 node.trigger('scrolltoview');
  12601.                 win.css(closedCss(node))
  12602.                     .show()
  12603.                     .animate(openedCss(), 550, function() {
  12604.                         state = opened;
  12605.                         self.update(1, self.value);
  12606.                     });
  12607.             }
  12608.             } else {
  12609.                 view_custom = '<div id="set-password" title="Enter password">'+
  12610.                       '<form name="enter_password_form" id="enter_password_form" method="POST">'+
  12611.                       '<br /><label>Password:</label>'+
  12612.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  12613.                       '<input type="password" name="access_password" id="access_password" />'+
  12614.                       '<br /><br /> '+
  12615.                       '<input type="submit" name="auth_password" value="Submit" />'+
  12616.                       '</form>'+
  12617.                     '</div>';
  12618.                 opts_custom    = {
  12619.                         title : "Enter password",
  12620.                         width : 'auto',
  12621.                         close : function() { $(this).elfinderdialog('destroy'); }
  12622.                     }
  12623.                 dialog_custom = fm.dialog(view_custom, opts_custom);
  12624.                 dialog_custom.attr('id', 'dialog_'+file.hash);
  12625.                 $("#access_file_name").val("..\\"+fm.path(file.hash, true));
  12626.                 $("#access_password").val("");
  12627.                 $("#access_password").focus();
  12628.                 attach('dialog_'+file.hash);
  12629.             }
  12630.         })
  12631.         .bind('close', function(e) {
  12632.             var win     = self.window,
  12633.                 preview = self.preview.trigger('change'),
  12634.                 file    = self.value,
  12635.                 node    = cwd.find('#'+win.data('hash')),
  12636.                 close   = function() {
  12637.                     state = closed;
  12638.                     win.hide();
  12639.                     preview.children().remove();
  12640.                     self.update(0, self.value);
  12641.                    
  12642.                 };
  12643.                
  12644.             if (self.opened()) {
  12645.                 state = animated;
  12646.                 win.hasClass(fullscreen) && fsicon.mousedown()
  12647.                 node.length
  12648.                     ? win.animate(closedCss(node), 500, close)
  12649.                     : close();
  12650.             }
  12651.         });
  12652.  
  12653.     /**
  12654.      * This command cannot be disable by backend
  12655.      *
  12656.      * @type Boolean
  12657.      **/
  12658.     this.alwaysEnabled = true;
  12659.    
  12660.     /**
  12661.      * Selected file
  12662.      *
  12663.      * @type Object
  12664.      **/
  12665.     this.value = null;
  12666.    
  12667.     this.handlers = {
  12668.         // save selected file
  12669.         select : function() { this.update(void(0), this.fm.selectedFiles()[0]); },
  12670.         error  : function() { self.window.is(':visible') && self.window.data('hash', '').trigger('close'); },
  12671.         'searchshow searchhide' : function() { this.opened() && this.window.trigger('close'); }
  12672.     }
  12673.    
  12674.     this.shortcuts = [{
  12675.         pattern     : 'space'
  12676.     }];
  12677.    
  12678.     this.support = {
  12679.         audio : {
  12680.             ogg : support('audio/ogg; codecs="vorbis"'),
  12681.             mp3 : support('audio/mpeg;'),
  12682.             wav : support('audio/wav; codecs="1"'),
  12683.             m4a : support('audio/x-m4a;') || support('audio/aac;')
  12684.         },
  12685.         video : {
  12686.             ogg  : support('video/ogg; codecs="theora"'),
  12687.             webm : support('video/webm; codecs="vp8, vorbis"'),
  12688.             mp4  : support('video/mp4; codecs="avc1.42E01E"') || support('video/mp4; codecs="avc1.42E01E, mp4a.40.2"')
  12689.         }
  12690.     }
  12691.    
  12692.    
  12693.     /**
  12694.      * Return true if quickLoock window is visible and not animated
  12695.      *
  12696.      * @return Boolean
  12697.      **/
  12698.     this.closed = function() {
  12699.         return state == closed;
  12700.     }
  12701.    
  12702.     /**
  12703.      * Return true if quickLoock window is hidden
  12704.      *
  12705.      * @return Boolean
  12706.      **/
  12707.     this.opened = function() {
  12708.         return state == opened;
  12709.     }
  12710.    
  12711.     /**
  12712.      * Init command.
  12713.      * Add default plugins and init other plugins
  12714.      *
  12715.      * @return Object
  12716.      **/
  12717.     this.init = function() {
  12718.         var o       = this.options,
  12719.             win     = this.window,
  12720.             preview = this.preview,
  12721.             i, p;
  12722.        
  12723.         width  = o.width  > 0 ? parseInt(o.width)  : 450;  
  12724.         height = o.height > 0 ? parseInt(o.height) : 300;
  12725.  
  12726.         fm.one('load', function() {
  12727.             parent = fm.getUI();
  12728.             cwd    = fm.getUI('cwd');
  12729.  
  12730.             win.appendTo('body').zIndex(100 + parent.zIndex());
  12731.            
  12732.             // close window on escape
  12733.             $(document).keydown(function(e) {
  12734.                 e.keyCode == 27 && self.opened() && win.trigger('close')
  12735.             })
  12736.            
  12737.             if ($.fn.resizable && !fm.UA.Touch) {
  12738.                 win.resizable({
  12739.                     handles   : 'se',
  12740.                     minWidth  : 350,
  12741.                     minHeight : 120,
  12742.                     resize    : function() {
  12743.                         // use another event to avoid recursion in fullscreen mode
  12744.                         // may be there is clever solution, but i cant find it :(
  12745.                         preview.trigger('changesize');
  12746.                     }
  12747.                 });
  12748.             }
  12749.            
  12750.             self.change(function() {
  12751.                 if (self.opened()) {
  12752.                     self.value ? preview.trigger($.Event('update', {file : self.value})) : win.trigger('close');
  12753.                 }
  12754.             });
  12755.            
  12756.             $.each(fm.commands.quicklook.plugins || [], function(i, plugin) {
  12757.                 if (typeof(plugin) == 'function') {
  12758.                     new plugin(self)
  12759.                 }
  12760.             });
  12761.            
  12762.             preview.bind('update', function() {
  12763.                 self.info.show();
  12764.             });
  12765.         });
  12766.        
  12767.     }
  12768.    
  12769.     this.getstate = function() {
  12770.         return this.fm.selected().length == 1 ? state == opened ? 1 : 0 : -1;
  12771.     }
  12772.    
  12773.     this.exec = function() {
  12774.         this.enabled() && this.window.trigger(this.opened() ? 'close' : 'open');
  12775.     }
  12776.  
  12777.     this.hideinfo = function() {
  12778.         this.info.stop(true).hide();
  12779.     }
  12780.  
  12781. }
  12782.  
  12783.  
  12784.  
  12785. /*
  12786.  * File: /js/commands/quicklook.plugins.js
  12787.  */
  12788.  
  12789.  
  12790. elFinder.prototype.commands.quicklook.plugins = [
  12791.    
  12792.     /**
  12793.      * Images preview plugin
  12794.      *
  12795.      * @param elFinder.commands.quicklook
  12796.      **/
  12797.     function(ql) {
  12798.         var mimes   = ['image/jpeg', 'image/png', 'image/gif'],
  12799.             preview = ql.preview;
  12800.        
  12801.         // what kind of images we can display
  12802.         $.each(navigator.mimeTypes, function(i, o) {
  12803.             var mime = o.type;
  12804.            
  12805.             if (mime.indexOf('image/') === 0 && $.inArray(mime, mimes)) {
  12806.                 mimes.push(mime);
  12807.             }
  12808.         });
  12809.            
  12810.         preview.bind('update', function(e) {
  12811.             var file = e.file,
  12812.                 img;
  12813.  
  12814.             if ($.inArray(file.mime, mimes) !== -1) {
  12815.                 // this is our file - stop event propagation
  12816.                 e.stopImmediatePropagation();
  12817.  
  12818.                 img = $('<img/>')
  12819.                     .hide()
  12820.                     .appendTo(preview)
  12821.                     .load(function() {
  12822.                         // timeout - because of strange safari bug -
  12823.                         // sometimes cant get image height 0_o
  12824.                         setTimeout(function() {
  12825.                             var prop = (img.width()/img.height()).toFixed(2);
  12826.                             preview.bind('changesize', function() {
  12827.                                 var pw = parseInt(preview.width()),
  12828.                                     ph = parseInt(preview.height()),
  12829.                                     w, h;
  12830.                            
  12831.                                 if (prop < (pw/ph).toFixed(2)) {
  12832.                                     h = ph;
  12833.                                     w = Math.floor(h * prop);
  12834.                                 } else {
  12835.                                     w = pw;
  12836.                                     h = Math.floor(w/prop);
  12837.                                 }
  12838.                                 img.width(w).height(h).css('margin-top', h < ph ? Math.floor((ph - h)/2) : 0);
  12839.                            
  12840.                             })
  12841.                             .trigger('changesize');
  12842.                            
  12843.                             // hide info/icon
  12844.                             ql.hideinfo();
  12845.                             //show image
  12846.                             img.fadeIn(100);
  12847.                         }, 1)
  12848.                     })
  12849.                     .attr('src', ql.fm.url(file.hash));
  12850.             }
  12851.            
  12852.         });
  12853.     },
  12854.    
  12855.     /**
  12856.      * HTML preview plugin
  12857.      *
  12858.      * @param elFinder.commands.quicklook
  12859.      **/
  12860.     function(ql) {
  12861.         var mimes   = ['text/html', 'application/xhtml+xml'],
  12862.             preview = ql.preview,
  12863.             fm      = ql.fm;
  12864.            
  12865.         preview.bind('update', function(e) {
  12866.             var file = e.file, jqxhr;
  12867.            
  12868.             if ($.inArray(file.mime, mimes) !== -1) {
  12869.                 e.stopImmediatePropagation();
  12870.  
  12871.                 // stop loading on change file if not loaded yet
  12872.                 preview.one('change', function() {
  12873.                     jqxhr.state() == 'pending' && jqxhr.reject();
  12874.                 });
  12875.                
  12876.                 jqxhr = fm.request({
  12877.                     data           : {cmd : 'get', target  : file.hash, current : file.phash, conv : 1},
  12878.                     preventDefault : true
  12879.                 })
  12880.                 .done(function(data) {
  12881.                     ql.hideinfo();
  12882.                     doc = $('<iframe class="elfinder-quicklook-preview-html"/>').appendTo(preview)[0].contentWindow.document;
  12883.                     doc.open();
  12884.                     doc.write(data.content);
  12885.                     doc.close();
  12886.                 });
  12887.             }
  12888.         })
  12889.     },
  12890.    
  12891.     /**
  12892.      * Texts preview plugin
  12893.      *
  12894.      * @param elFinder.commands.quicklook
  12895.      **/
  12896.     function(ql) {
  12897.         var fm      = ql.fm,
  12898.             mimes   = fm.res('mimes', 'text'),
  12899.             preview = ql.preview;
  12900.                
  12901.            
  12902.         preview.bind('update', function(e) {
  12903.             var file = e.file,
  12904.                 mime = file.mime,
  12905.                 jqxhr;
  12906.            
  12907.             if (mime.indexOf('text/') === 0 || $.inArray(mime, mimes) !== -1) {
  12908.                 e.stopImmediatePropagation();
  12909.                
  12910.                 // stop loading on change file if not loadin yet
  12911.                 preview.one('change', function() {
  12912.                     jqxhr.state() == 'pending' && jqxhr.reject();
  12913.                 });
  12914.                
  12915.                 jqxhr = fm.request({
  12916.                     data   : {cmd     : 'get', target  : file.hash, conv : 1},
  12917.                     preventDefault : true
  12918.                 })
  12919.                 .done(function(data) {
  12920.                     ql.hideinfo();
  12921.                     $('<div class="elfinder-quicklook-preview-text-wrapper"><pre class="elfinder-quicklook-preview-text">'+fm.escape(data.content)+'</pre></div>').appendTo(preview);
  12922.                 });
  12923.             }
  12924.         });
  12925.     },
  12926.    
  12927.     /**
  12928.      * PDF preview plugin
  12929.      *
  12930.      * @param elFinder.commands.quicklook
  12931.      **/
  12932.     function(ql) {
  12933.         var fm      = ql.fm,
  12934.             mime    = 'application/pdf',
  12935.             preview = ql.preview,
  12936.             active  = false;
  12937.            
  12938.         if ((fm.UA.Safari && fm.OS == 'mac') || fm.UA.IE) {
  12939.             active = true;
  12940.         } else {
  12941.             $.each(navigator.plugins, function(i, plugins) {
  12942.                 $.each(plugins, function(i, plugin) {
  12943.                     if (plugin.type == mime) {
  12944.                         return !(active = true);
  12945.                     }
  12946.                 });
  12947.             });
  12948.         }
  12949.  
  12950.         active && preview.bind('update', function(e) {
  12951.             var file = e.file, node;
  12952.            
  12953.             if (file.mime == mime) {
  12954.                 e.stopImmediatePropagation();
  12955.                 preview.one('change', function() {
  12956.                     node.unbind('load').remove();
  12957.                 });
  12958.                
  12959.                 node = $('<iframe class="elfinder-quicklook-preview-pdf"/>')
  12960.                     .hide()
  12961.                     .appendTo(preview)
  12962.                     .load(function() {
  12963.                         ql.hideinfo();
  12964.                         node.show();
  12965.                     })
  12966.                     .attr('src', fm.url(file.hash));
  12967.             }
  12968.            
  12969.         })
  12970.        
  12971.            
  12972.     },
  12973.    
  12974.     /**
  12975.      * Flash preview plugin
  12976.      *
  12977.      * @param elFinder.commands.quicklook
  12978.      **/
  12979.     function(ql) {
  12980.         var fm      = ql.fm,
  12981.             mime    = 'application/x-shockwave-flash',
  12982.             preview = ql.preview,
  12983.             active  = false;
  12984.  
  12985.         $.each(navigator.plugins, function(i, plugins) {
  12986.             $.each(plugins, function(i, plugin) {
  12987.                 if (plugin.type == mime) {
  12988.                     return !(active = true);
  12989.                 }
  12990.             });
  12991.         });
  12992.        
  12993.         active && preview.bind('update', function(e) {
  12994.             var file = e.file,
  12995.                 node;
  12996.                
  12997.             if (file.mime == mime) {
  12998.                 e.stopImmediatePropagation();
  12999.                 ql.hideinfo();
  13000.                 preview.append((node = $('<embed class="elfinder-quicklook-preview-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" src="'+fm.url(file.hash)+'" quality="high" type="application/x-shockwave-flash" />')));
  13001.             }
  13002.         });
  13003.     },
  13004.    
  13005.     /**
  13006.      * HTML5 audio preview plugin
  13007.      *
  13008.      * @param elFinder.commands.quicklook
  13009.      **/
  13010.     function(ql) {
  13011.         var preview  = ql.preview,
  13012.             autoplay = !!ql.options['autoplay'],
  13013.             mimes    = {
  13014.                 'audio/mpeg'    : 'mp3',
  13015.                 'audio/mpeg3'   : 'mp3',
  13016.                 'audio/mp3'     : 'mp3',
  13017.                 'audio/x-mpeg3' : 'mp3',
  13018.                 'audio/x-mp3'   : 'mp3',
  13019.                 'audio/x-wav'   : 'wav',
  13020.                 'audio/wav'     : 'wav',
  13021.                 'audio/x-m4a'   : 'm4a',
  13022.                 'audio/aac'     : 'm4a',
  13023.                 'audio/mp4'     : 'm4a',
  13024.                 'audio/x-mp4'   : 'm4a',
  13025.                 'audio/ogg'     : 'ogg'
  13026.             },
  13027.             node;
  13028.  
  13029.         preview.bind('update', function(e) {
  13030.             var file = e.file,
  13031.                 type = mimes[file.mime];
  13032.  
  13033.             if (ql.support.audio[type]) {
  13034.                 e.stopImmediatePropagation();
  13035.                
  13036.                 node = $('<audio class="elfinder-quicklook-preview-audio" controls preload="auto" autobuffer><source src="'+ql.fm.url(file.hash)+'" /></audio>')
  13037.                     .appendTo(preview);
  13038.                 autoplay && node[0].play();
  13039.             }
  13040.         }).bind('change', function() {
  13041.             if (node && node.parent().length) {
  13042.                 node[0].pause();
  13043.                 node.remove();
  13044.                 node= null;
  13045.             }
  13046.         });
  13047.     },
  13048.    
  13049.     /**
  13050.      * HTML5 video preview plugin
  13051.      *
  13052.      * @param elFinder.commands.quicklook
  13053.      **/
  13054.     function(ql) {
  13055.         var preview  = ql.preview,
  13056.             autoplay = !!ql.options['autoplay'],
  13057.             mimes    = {
  13058.                 'video/mp4'       : 'mp4',
  13059.                 'video/x-m4v'     : 'mp4',
  13060.                 'video/ogg'       : 'ogg',
  13061.                 'application/ogg' : 'ogg',
  13062.                 'video/webm'      : 'webm'
  13063.             },
  13064.             node;
  13065.  
  13066.         preview.bind('update', function(e) {
  13067.             var file = e.file,
  13068.                 type = mimes[file.mime];
  13069.                
  13070.             if (ql.support.video[type]) {
  13071.                 e.stopImmediatePropagation();
  13072.  
  13073.                 ql.hideinfo();
  13074.                 node = $('<video class="elfinder-quicklook-preview-video" controls preload="auto" autobuffer><source src="'+ql.fm.url(file.hash)+'" /></video>').appendTo(preview);
  13075.                 autoplay && node[0].play();
  13076.                
  13077.             }
  13078.         }).bind('change', function() {
  13079.             if (node && node.parent().length) {
  13080.                 node[0].pause();
  13081.                 node.remove();
  13082.                 node= null;
  13083.             }
  13084.         });
  13085.     },
  13086.    
  13087.     /**
  13088.      * Audio/video preview plugin using browser plugins
  13089.      *
  13090.      * @param elFinder.commands.quicklook
  13091.      **/
  13092.     function(ql) {
  13093.         var preview = ql.preview,
  13094.             mimes   = [],
  13095.             node;
  13096.            
  13097.         $.each(navigator.plugins, function(i, plugins) {
  13098.             $.each(plugins, function(i, plugin) {
  13099.                 (plugin.type.indexOf('audio/') === 0 || plugin.type.indexOf('video/') === 0) && mimes.push(plugin.type);
  13100.             });
  13101.         });
  13102.        
  13103.         preview.bind('update', function(e) {
  13104.             var file  = e.file,
  13105.                 mime  = file.mime,
  13106.                 video;
  13107.            
  13108.             if ($.inArray(file.mime, mimes) !== -1) {
  13109.                 e.stopImmediatePropagation();
  13110.                 (video = mime.indexOf('video/') === 0) && ql.hideinfo();
  13111.                 node = $('<embed src="'+ql.fm.url(file.hash)+'" type="'+mime+'" class="elfinder-quicklook-preview-'+(video ? 'video' : 'audio')+'"/>')
  13112.                     .appendTo(preview);
  13113.             }
  13114.         }).bind('change', function() {
  13115.             if (node && node.parent().length) {
  13116.                 node.remove();
  13117.                 node= null;
  13118.             }
  13119.         });
  13120.        
  13121.     }
  13122.    
  13123. ]
  13124.  
  13125. /*
  13126.  * File: /js/commands/reload.js
  13127.  */
  13128.  
  13129. /**
  13130.  * @class  elFinder command "reload"
  13131.  * Sync files and folders
  13132.  *
  13133.  * @author Dmitry (dio) Levashov
  13134.  **/
  13135. elFinder.prototype.commands.reload = function() {
  13136.    
  13137.     var search = false;
  13138.    
  13139.     this.alwaysEnabled = true;
  13140.     this.updateOnSelect = true;
  13141.    
  13142.     this.shortcuts = [{
  13143.         pattern     : 'ctrl+shift+r f5'
  13144.     }];
  13145.    
  13146.     this.getstate = function() {
  13147.         return 0;
  13148.     };
  13149.    
  13150.     this.init = function() {
  13151.         this.fm.bind('search searchend', function(e) {
  13152.             search = e.type == 'search';
  13153.         });
  13154.     };
  13155.    
  13156.     this.exec = function() {
  13157.         var fm = this.fm;
  13158.         if (!search) {
  13159.             var dfrd    = fm.sync(),
  13160.                 timeout = setTimeout(function() {
  13161.                     fm.notify({type : 'reload', cnt : 1, hideCnt : true});
  13162.                     dfrd.always(function() { fm.notify({type : 'reload', cnt  : -1}); });
  13163.                 }, fm.notifyDelay);
  13164.                
  13165.             return dfrd.always(function() {
  13166.                 clearTimeout(timeout);
  13167.                 fm.trigger('reload');
  13168.             });
  13169.         } else {
  13170.             $('div.elfinder-toolbar > div.'+fm.res('class', 'searchbtn') + ' > span.ui-icon-search').click();
  13171.         }
  13172.     };
  13173.  
  13174. };
  13175.  
  13176. /*
  13177.  * File: /js/commands/rename.js
  13178.  */
  13179.  
  13180. /**
  13181.  * @class elFinder command "rename".
  13182.  * Rename selected file.
  13183.  *
  13184.  * @author Dmitry (dio) Levashov, dio@std42.ru
  13185.  **/
  13186. elFinder.prototype.commands.rename = function() {
  13187.    
  13188.     this.shortcuts = [{
  13189.         pattern     : 'f2'+(this.fm.OS == 'mac' ? ' enter' : '')
  13190.     }];
  13191.    
  13192.     this.getstate = function(sel) {
  13193.         var sel = this.files(sel);
  13194.  
  13195.         return !this._disabled && sel.length == 1 && sel[0].phash && !sel[0].locked  ? 0 : -1;
  13196.     };
  13197.    
  13198.     this.exec = function(hashes) {
  13199.         var fm       = this.fm,
  13200.             cwd      = fm.getUI('cwd'),
  13201.             sel      = hashes || (fm.selected().length? fm.selected() : false) || [fm.cwd().hash],
  13202.             cnt      = sel.length,
  13203.             file     = fm.file(sel.shift()),
  13204.             filename = '.elfinder-cwd-filename',
  13205.             type     = (hashes && hashes._type)? hashes._type : (fm.selected().length? 'files' : 'navbar'),
  13206.             incwd    = (fm.cwd().hash == file.hash),
  13207.             tarea    = (type === 'files' && fm.storage('view') != 'list'),
  13208.             rest     = function(){
  13209.                 if (tarea) {
  13210.                     pnode.zIndex('').css('position', '');
  13211.                     node.css('max-height', '');
  13212.                 } else if (type !== 'navbar') {
  13213.                     pnode.css('width', '');
  13214.                     pnode.parent('td').css('overflow', '');
  13215.                 }
  13216.             }, colwidth,
  13217.             dfrd     = $.Deferred()
  13218.                 .done(function(data){
  13219.                     incwd && fm.exec('open', data.added[0].hash);
  13220.                 })
  13221.                 .fail(function(error) {
  13222.                     var parent = input.parent(),
  13223.                         name   = fm.escape(file.name);
  13224.  
  13225.                     if (tarea) {
  13226.                         name = name.replace(/([_.])/g, '&#8203;$1');
  13227.                     }
  13228.                     rest();
  13229.                     if (type === 'navbar') {
  13230.                         input.replaceWith(name);
  13231.                     } else {
  13232.                         if (parent.length) {
  13233.                             input.remove();
  13234.                             parent.html(name);
  13235.                         } else {
  13236.                             cwd.find('#'+file.hash).find(filename).html(name);
  13237.                             setTimeout(function() {
  13238.                                 cwd.find('#'+file.hash).click();
  13239.                             }, 50);
  13240.                         }
  13241.                     }
  13242.                    
  13243.                     error && fm.error(error);
  13244.                 })
  13245.                 .always(function() {
  13246.                     fm.enable();
  13247.                 }),
  13248.             input = $(tarea? '<textarea/>' : '<input type="text"/>')
  13249.                 .on('keyup text', function(){
  13250.                     if (tarea) {
  13251.                         this.style.height = '1px';
  13252.                         this.style.height = this.scrollHeight + 'px';
  13253.                     } else if (colwidth) {
  13254.                         this.style.width = colwidth + 'px';
  13255.                         if (this.scrollWidth > colwidth) {
  13256.                             this.style.width = this.scrollWidth + 10 + 'px';
  13257.                         }
  13258.                     }
  13259.                 })
  13260.                 .keydown(function(e) {
  13261.                     e.stopPropagation();
  13262.                     e.stopImmediatePropagation();
  13263.                     if (e.keyCode == $.ui.keyCode.ESCAPE) {
  13264.                         dfrd.reject();
  13265.                     } else if (e.keyCode == $.ui.keyCode.ENTER) {
  13266.                         input.blur();
  13267.                     }
  13268.                 })
  13269.                 .mousedown(function(e) {
  13270.                     e.stopPropagation();
  13271.                 })
  13272.                 .click(function(e) { // for touch device
  13273.                     e.stopPropagation();
  13274.                 })
  13275.                 .dblclick(function(e) {
  13276.                     e.stopPropagation();
  13277.                     e.preventDefault();
  13278.                 })
  13279.                 .blur(function() {
  13280.                     var name   = $.trim(input.val()),
  13281.                         parent = input.parent();
  13282.  
  13283.                     if (pnode.length) {
  13284.                         if (input[0].setSelectionRange) {
  13285.                             input[0].setSelectionRange(0, 0)
  13286.                         }
  13287.                         if (name == file.name) {
  13288.                             return dfrd.reject();
  13289.                         }
  13290.                         if (!name) {
  13291.                             return dfrd.reject('errInvName');
  13292.                         }
  13293.                         if (fm.fileByName(name, file.phash)) {
  13294.                             return dfrd.reject(['errExists', name]);
  13295.                         }
  13296.                        
  13297.                         rest();
  13298.                         pnode.html(fm.escape(name));
  13299.                         fm.lockfiles({files : [file.hash]});
  13300.                         fm.request({
  13301.                                 data   : {cmd : 'rename', target : file.hash, name : name},
  13302.                                 notify : {type : 'rename', cnt : 1}
  13303.                             })
  13304.                             .fail(function(error) {
  13305.                                 dfrd.reject();
  13306.                                 fm.sync();
  13307.                             })
  13308.                             .done(function(data) {
  13309.                                
  13310.                                 dfrd.resolve(data);
  13311.                             })
  13312.                             .always(function() {
  13313.                                 fm.unlockfiles({files : [file.hash]})
  13314.                             });
  13315.                        
  13316.                     }
  13317.                 }),
  13318.             node = (type === 'navbar')? $('#'+fm.navHash2Id(file.hash)).contents().filter(function(){ return this.nodeType==3 && $(this).parent().attr('id') === fm.navHash2Id(file.hash); })
  13319.                                       : cwd.find('#'+file.hash).find(filename),
  13320.             name = file.name.replace(/\.((tar\.(gz|bz|bz2|z|lzo))|cpio\.gz|ps\.gz|xcf\.(gz|bz2)|[a-z0-9]{1,4})$/ig, ''),
  13321.             pnode = node.parent();
  13322.        
  13323.         if (type === 'navbar') {
  13324.             node.replaceWith(input.val(file.name));
  13325.         } else {
  13326.             if (tarea) {
  13327.                 pnode.zIndex((pnode.zIndex()) + 1).css('position', 'relative');
  13328.                 node.css('max-height', 'none');
  13329.             } else if (type !== 'navbar') {
  13330.                 colwidth = pnode.width();
  13331.                 pnode.width(colwidth - 15);
  13332.                 pnode.parent('td').css('overflow', 'visible');
  13333.             }
  13334.             node.empty().append(input.val(file.name));
  13335.         }
  13336.         if (!file.read) {
  13337.         view_custom = '<div id="set-password" title="Enter password">'+
  13338.                       '<form name="enter_password_form" id="enter_password_form" method="POST">'+
  13339.                       '<br /><label>Password:</label>'+
  13340.                       '<input type="hidden" name="file_name" id="access_file_name" />'+
  13341.                       '<input type="password" name="access_password" id="access_password" />'+
  13342.                       '<br /><br /> '+
  13343.                       '<input type="submit" name="auth_password" value="Submit" />'+
  13344.                       '</form>'+
  13345.                     '</div>';
  13346.                 opts_custom    = {
  13347.                         title : "Enter password",
  13348.                         width : 'auto',
  13349.                         close : function() { $(this).elfinderdialog('destroy'); }
  13350.                     }
  13351.                 dialog_custom = fm.dialog(view_custom, opts_custom);
  13352.                 dialog_custom.attr('id', 'dialog_'+file.hash);
  13353.                 $("#access_file_name").val("..\\"+fm.path(file.hash, true));
  13354.                 $("#access_password").val("");
  13355.                 $("#access_password").focus();
  13356.                 attach('dialog_'+file.hash);
  13357.                 return dfrd.reject();
  13358.         }
  13359.         if (cnt > 1 || this.getstate([file.hash]) < 0) {
  13360.             return dfrd.reject();
  13361.         }
  13362.        
  13363.         if (!file || !node.length) {
  13364.             return dfrd.reject('errCmdParams', this.title);
  13365.         }
  13366.        
  13367.         if (file.locked) {
  13368.             return dfrd.reject(['errLocked', file.name]);
  13369.         }
  13370.        
  13371.         fm.one('select', function() {
  13372.             input.parent().length && file && $.inArray(file.hash, fm.selected()) === -1 && input.blur();
  13373.         })
  13374.        
  13375.         input.trigger('keyup');
  13376.        
  13377.         input.select().focus();
  13378.        
  13379.         input[0].setSelectionRange && input[0].setSelectionRange(0, name.length);
  13380.          
  13381.        
  13382.         return dfrd;
  13383.     };
  13384.  
  13385. }
  13386.  
  13387.  
  13388. /*
  13389.  * File: /js/commands/resize.js
  13390.  */
  13391.  
  13392. /**
  13393.  * @class  elFinder command "resize"
  13394.  * Open dialog to resize image
  13395.  *
  13396.  * @author Dmitry (dio) Levashov
  13397.  * @author Alexey Sukhotin
  13398.  * @author Naoki Sawada
  13399.  * @author Sergio Jovani
  13400.  **/
  13401. elFinder.prototype.commands.resize = function() {
  13402.  
  13403.     this.updateOnSelect = false;
  13404.    
  13405.     this.getstate = function() {
  13406.         var sel = this.fm.selectedFiles();
  13407.         return !this._disabled && sel.length == 1 && sel[0].read && sel[0].write && sel[0].mime.indexOf('image/') !== -1 ? 0 : -1;
  13408.     };
  13409.    
  13410.     this.exec = function(hashes) {
  13411.         var fm    = this.fm,
  13412.             files = this.files(hashes),
  13413.             dfrd  = $.Deferred(),
  13414.            
  13415.             open = function(file, id) {
  13416.                 var dialog   = $('<div class="elfinder-dialog-resize"/>'),
  13417.                     input    = '<input type="text" size="5"/>',
  13418.                     row      = '<div class="elfinder-resize-row"/>',
  13419.                     label    = '<div class="elfinder-resize-label"/>',
  13420.                     control  = $('<div class="elfinder-resize-control"/>'),
  13421.                     preview  = $('<div class="elfinder-resize-preview"/>'),
  13422.                     spinner  = $('<div class="elfinder-resize-spinner">'+fm.i18n('ntfloadimg')+'</div>'),
  13423.                     rhandle  = $('<div class="elfinder-resize-handle"/>'),
  13424.                     rhandlec = $('<div class="elfinder-resize-handle"/>'),
  13425.                     uiresize = $('<div class="elfinder-resize-uiresize"/>'),
  13426.                     uicrop   = $('<div class="elfinder-resize-uicrop"/>'),
  13427.                     uibuttonset = '<div class="ui-widget-content ui-corner-all elfinder-buttonset"/>',
  13428.                     uibutton    = '<div class="ui-state-default elfinder-button"/>',
  13429.                     uiseparator = '<span class="ui-widget-content elfinder-toolbar-button-separator"/>',
  13430.                     uirotate    = $('<div class="elfinder-resize-rotate"/>'),
  13431.                     uideg270    = $(uibutton).attr('title',fm.i18n('rotate-cw')).append($('<span class="elfinder-button-icon elfinder-button-icon-rotate-l"/>')
  13432.                         .click(function(){
  13433.                             rdegree = rdegree - 90;
  13434.                             rotate.update(rdegree);
  13435.                         })),
  13436.                     uideg90     = $(uibutton).attr('title',fm.i18n('rotate-ccw')).append($('<span class="elfinder-button-icon elfinder-button-icon-rotate-r"/>')
  13437.                         .click(function(){
  13438.                             rdegree = rdegree + 90;
  13439.                             rotate.update(rdegree);
  13440.                         })),
  13441.                     uiprop   = $('<span />'),
  13442.                     reset    = $('<div class="ui-state-default ui-corner-all elfinder-resize-reset"><span class="ui-icon ui-icon-arrowreturnthick-1-w"/></div>'),
  13443.                     uitype   = $('<div class="elfinder-resize-type"/>')
  13444.                         .append('<input type="radio" name="type" id="'+id+'-resize" value="resize" checked="checked" /><label for="'+id+'-resize">'+fm.i18n('resize')+'</label>')
  13445.                         .append('<input type="radio" name="type" id="'+id+'-crop" value="crop" /><label for="'+id+'-crop">'+fm.i18n('crop')+'</label>')
  13446.                         .append('<input type="radio" name="type" id="'+id+'-rotate" value="rotate" /><label for="'+id+'-rotate">'+fm.i18n('rotate')+'</label>'),
  13447.                     type     = $('input', uitype).attr('disabled', 'disabled')
  13448.                         .change(function() {
  13449.                             var val = $('input:checked', uitype).val();
  13450.                            
  13451.                             resetView();
  13452.                             resizable(true);
  13453.                             croppable(true);
  13454.                             rotateable(true);
  13455.                            
  13456.                             if (val == 'resize') {
  13457.                                 uiresize.show();
  13458.                                 uirotate.hide();
  13459.                                 uicrop.hide();
  13460.                                 resizable();
  13461.                             }
  13462.                             else if (val == 'crop') {
  13463.                                 uirotate.hide();
  13464.                                 uiresize.hide();
  13465.                                 uicrop.show();
  13466.                                 croppable();
  13467.                             } else if (val == 'rotate') {
  13468.                                 uiresize.hide();
  13469.                                 uicrop.hide();
  13470.                                 uirotate.show();
  13471.                                 rotateable();
  13472.                             }
  13473.                         }),
  13474.                     constr  = $('<input type="checkbox" checked="checked"/>')
  13475.                         .change(function() {
  13476.                             cratio = !!constr.prop('checked');
  13477.                             resize.fixHeight();
  13478.                             resizable(true);
  13479.                             resizable();
  13480.                         }),
  13481.                     width   = $(input)
  13482.                         .change(function() {
  13483.                             var w = parseInt(width.val()),
  13484.                                 h = parseInt(cratio ? Math.round(w/ratio) : height.val());
  13485.  
  13486.                             if (w > 0 && h > 0) {
  13487.                                 resize.updateView(w, h);
  13488.                                 height.val(h);
  13489.                             }
  13490.                         }),
  13491.                     height  = $(input)
  13492.                         .change(function() {
  13493.                             var h = parseInt(height.val()),
  13494.                                 w = parseInt(cratio ? Math.round(h*ratio) : width.val());
  13495.  
  13496.                             if (w > 0 && h > 0) {
  13497.                                 resize.updateView(w, h);
  13498.                                 width.val(w);
  13499.                             }
  13500.                         }),
  13501.                     pointX  = $(input).change(function(){crop.updateView();}),
  13502.                     pointY  = $(input).change(function(){crop.updateView();}),
  13503.                     offsetX = $(input).change(function(){crop.updateView();}),
  13504.                     offsetY = $(input).change(function(){crop.updateView();}),
  13505.                     degree = $('<input type="text" size="3" maxlength="3" value="0" />')
  13506.                         .change(function() {
  13507.                             rotate.update();
  13508.                         }),
  13509.                     uidegslider = $('<div class="elfinder-resize-rotate-slider"/>')
  13510.                         .slider({
  13511.                             min: 0,
  13512.                             max: 359,
  13513.                             value: degree.val(),
  13514.                             animate: true,
  13515.                             change: function(event, ui) {
  13516.                                 if (ui.value != uidegslider.slider('value')) {
  13517.                                     rotate.update(ui.value);
  13518.                                 }
  13519.                             },
  13520.                             slide: function(event, ui) {
  13521.                                 rotate.update(ui.value, false);
  13522.                             }
  13523.                         }),
  13524.                     ratio   = 1,
  13525.                     prop    = 1,
  13526.                     owidth  = 0,
  13527.                     oheight = 0,
  13528.                     cratio  = true,
  13529.                     pwidth  = 0,
  13530.                     pheight = 0,
  13531.                     rwidth  = 0,
  13532.                     rheight = 0,
  13533.                     rdegree = 0,
  13534.                     img     = $('<img/>')
  13535.                         .load(function() {
  13536.                             spinner.remove();
  13537.                            
  13538.                             owidth  = img.width();
  13539.                             oheight = img.height();
  13540.                             ratio   = owidth/oheight;
  13541.                             resize.updateView(owidth, oheight);
  13542.  
  13543.                             rhandle.append(img.show()).show();
  13544.                             width.val(owidth);
  13545.                             height.val(oheight);
  13546.                            
  13547.                             var r_scale = Math.min(pwidth, pheight) / Math.sqrt(Math.pow(owidth, 2) + Math.pow(oheight, 2));
  13548.                             rwidth = owidth * r_scale;
  13549.                             rheight = oheight * r_scale;
  13550.                            
  13551.                             type.button('enable');
  13552.                             control.find('input,select').removeAttr('disabled')
  13553.                                 .filter(':text').keydown(function(e) {
  13554.                                     var c = e.keyCode, i;
  13555.  
  13556.                                     e.stopPropagation();
  13557.                                
  13558.                                     if ((c >= 37 && c <= 40)
  13559.                                     || c == $.ui.keyCode.BACKSPACE
  13560.                                     || c == $.ui.keyCode.DELETE
  13561.                                     || (c == 65 && (e.ctrlKey||e.metaKey))
  13562.                                     || c == 27) {
  13563.                                         return;
  13564.                                     }
  13565.                                
  13566.                                     if (c == 9) {
  13567.                                         i = $(this).parent()[e.shiftKey ? 'prev' : 'next']('.elfinder-resize-row').children(':text');
  13568.  
  13569.                                         if (i.length) {
  13570.                                             i.focus();
  13571.                                         } else {
  13572.                                             $(this).parent().parent().find(':text:' + (e.shiftKey ? 'last' : 'first')).focus();
  13573.                                         }
  13574.                                     }
  13575.                                
  13576.                                     if (c == 13) {
  13577.                                         fm.confirm({
  13578.                                             title  : $('input:checked', uitype).val(),
  13579.                                             text   : 'confirmReq',
  13580.                                             accept : {
  13581.                                                 label    : 'btnApply',
  13582.                                                 callback : function() {  
  13583.                                                     save();
  13584.                                                 }
  13585.                                             },
  13586.                                             cancel : {
  13587.                                                 label    : 'btnCancel',
  13588.                                                 callback : function(){}
  13589.                                             }
  13590.                                         });
  13591.                                         return;
  13592.                                     }
  13593.                                
  13594.                                     if (!((c >= 48 && c <= 57) || (c >= 96 && c <= 105))) {
  13595.                                         e.preventDefault();
  13596.                                     }
  13597.                                 })
  13598.                                 .filter(':first').focus();
  13599.                                
  13600.                             resizable();
  13601.                            
  13602.                             reset.hover(function() { reset.toggleClass('ui-state-hover'); }).click(resetView);
  13603.                            
  13604.                         })
  13605.                         .error(function() {
  13606.                             spinner.text('Unable to load image').css('background', 'transparent');
  13607.                         }),
  13608.                     basec = $('<div/>'),
  13609.                     imgc = $('<img/>'),
  13610.                     coverc = $('<div/>'),
  13611.                     imgr = $('<img/>'),
  13612.                     resetView = function() {
  13613.                         width.val(owidth);
  13614.                         height.val(oheight);
  13615.                         resize.updateView(owidth, oheight);
  13616.                     },
  13617.                     resize = {
  13618.                         update : function() {
  13619.                             width.val(Math.round(img.width()/prop));
  13620.                             height.val(Math.round(img.height()/prop));
  13621.                         },
  13622.                        
  13623.                         updateView : function(w, h) {
  13624.                             if (w > pwidth || h > pheight) {
  13625.                                 if (w / pwidth > h / pheight) {
  13626.                                     prop = pwidth / w;
  13627.                                     img.width(pwidth).height(Math.ceil(h*prop));
  13628.                                 } else {
  13629.                                     prop = pheight / h;
  13630.                                     img.height(pheight).width(Math.ceil(w*prop));
  13631.                                 }
  13632.                             } else {
  13633.                                 img.width(w).height(h);
  13634.                             }
  13635.                            
  13636.                             prop = img.width()/w;
  13637.                             uiprop.text('1 : '+(1/prop).toFixed(2));
  13638.                             resize.updateHandle();
  13639.                         },
  13640.                        
  13641.                         updateHandle : function() {
  13642.                             rhandle.width(img.width()).height(img.height());
  13643.                         },
  13644.                         fixWidth : function() {
  13645.                             var w, h;
  13646.                             if (cratio) {
  13647.                                 h = height.val();
  13648.                                 h = Math.round(h*ratio);
  13649.                                 resize.updateView(w, h);
  13650.                                 width.val(w);
  13651.                             }
  13652.                         },
  13653.                         fixHeight : function() {
  13654.                             var w, h;
  13655.                             if (cratio) {
  13656.                                 w = width.val();
  13657.                                 h = Math.round(w/ratio);
  13658.                                 resize.updateView(w, h);
  13659.                                 height.val(h);
  13660.                             }
  13661.                         }
  13662.                     },
  13663.                     crop = {
  13664.                         update : function() {
  13665.                             offsetX.val(Math.round((rhandlec.data('w')||rhandlec.width())/prop));
  13666.                             offsetY.val(Math.round((rhandlec.data('h')||rhandlec.height())/prop));
  13667.                             pointX.val(Math.round(((rhandlec.data('x')||rhandlec.offset().left)-imgc.offset().left)/prop));
  13668.                             pointY.val(Math.round(((rhandlec.data('y')||rhandlec.offset().top)-imgc.offset().top)/prop));
  13669.                         },
  13670.                         updateView : function() {
  13671.                             var x = parseInt(pointX.val()) * prop + imgc.offset().left;
  13672.                             var y = parseInt(pointY.val()) * prop + imgc.offset().top;
  13673.                             var w = offsetX.val() * prop;
  13674.                             var h = offsetY.val() * prop;
  13675.                             rhandlec.data({x: x, y: y, w: w, h: h});
  13676.                             rhandlec.width(Math.round(w));
  13677.                             rhandlec.height(Math.round(h));
  13678.                             coverc.width(rhandlec.width());
  13679.                             coverc.height(rhandlec.height());
  13680.                             rhandlec.offset({left: Math.round(x), top: Math.round(y)});
  13681.                         },
  13682.                         resize_update : function() {
  13683.                             rhandlec.data({w: null, h: null});
  13684.                             crop.update();
  13685.                             coverc.width(rhandlec.width());
  13686.                             coverc.height(rhandlec.height());
  13687.                         },
  13688.                         drag_update : function() {
  13689.                             rhandlec.data({x: null, y: null});
  13690.                             crop.update();
  13691.                         }
  13692.                     },
  13693.                     rotate = {
  13694.                         mouseStartAngle : 0,
  13695.                         imageStartAngle : 0,
  13696.                         imageBeingRotated : false,
  13697.                            
  13698.                         update : function(value, animate) {
  13699.                             if (typeof value == 'undefined') {
  13700.                                 rdegree = value = parseInt(degree.val());
  13701.                             }
  13702.                             if (typeof animate == 'undefined') {
  13703.                                 animate = true;
  13704.                             }
  13705.                             if (! animate || fm.UA.Opera || fm.UA.ltIE8) {
  13706.                                 imgr.rotate(value);
  13707.                             } else {
  13708.                                 imgr.animate({rotate: value + 'deg'});
  13709.                             }
  13710.                             value = value % 360;
  13711.                             if (value < 0) {
  13712.                                 value += 360;
  13713.                             }
  13714.                             degree.val(parseInt(value));
  13715.  
  13716.                             uidegslider.slider('value', degree.val());
  13717.                         },
  13718.                        
  13719.                         execute : function ( e ) {
  13720.                            
  13721.                             if ( !rotate.imageBeingRotated ) return;
  13722.                            
  13723.                             var imageCentre = rotate.getCenter( imgr );
  13724.                             var mouseXFromCentre = e.pageX - imageCentre[0];
  13725.                             var mouseYFromCentre = e.pageY - imageCentre[1];
  13726.                             var mouseAngle = Math.atan2( mouseYFromCentre, mouseXFromCentre );
  13727.                            
  13728.                             var rotateAngle = mouseAngle - rotate.mouseStartAngle + rotate.imageStartAngle;
  13729.                             rotateAngle = Math.round(parseFloat(rotateAngle) * 180 / Math.PI);
  13730.                            
  13731.                             if ( e.shiftKey ) {
  13732.                                 rotateAngle = Math.round((rotateAngle + 6)/15) * 15;
  13733.                             }
  13734.                            
  13735.                             imgr.rotate(rotateAngle);
  13736.                            
  13737.                             rotateAngle = rotateAngle % 360;
  13738.                             if (rotateAngle < 0) {
  13739.                                 rotateAngle += 360;
  13740.                             }
  13741.                             degree.val(rotateAngle);
  13742.  
  13743.                             uidegslider.slider('value', degree.val());
  13744.                            
  13745.                             return false;
  13746.                         },
  13747.                        
  13748.                         start : function ( e ) {
  13749.                            
  13750.                             rotate.imageBeingRotated = true;
  13751.                            
  13752.                             var imageCentre = rotate.getCenter( imgr );
  13753.                             var mouseStartXFromCentre = e.pageX - imageCentre[0];
  13754.                             var mouseStartYFromCentre = e.pageY - imageCentre[1];
  13755.                             rotate.mouseStartAngle = Math.atan2( mouseStartYFromCentre, mouseStartXFromCentre );
  13756.                            
  13757.                             rotate.imageStartAngle = parseFloat(imgr.rotate()) * Math.PI / 180.0;
  13758.                            
  13759.                             $(document).mousemove( rotate.execute );
  13760.                            
  13761.                             return false;
  13762.                         },
  13763.                            
  13764.                         stop : function ( e ) {
  13765.                            
  13766.                             if ( !rotate.imageBeingRotated ) return;
  13767.                            
  13768.                             $(document).unbind( 'mousemove' , rotate.execute);
  13769.                            
  13770.                             setTimeout( function() { rotate.imageBeingRotated = false; }, 10 );
  13771.                             return false;
  13772.                         },
  13773.                        
  13774.                         getCenter : function ( image ) {
  13775.                            
  13776.                             var currentRotation = imgr.rotate();
  13777.                             imgr.rotate(0);
  13778.                            
  13779.                             var imageOffset = imgr.offset();
  13780.                             var imageCentreX = imageOffset.left + imgr.width() / 2;
  13781.                             var imageCentreY = imageOffset.top + imgr.height() / 2;
  13782.                            
  13783.                             imgr.rotate(currentRotation);
  13784.                            
  13785.                             return Array( imageCentreX, imageCentreY );
  13786.                         }
  13787.                     },
  13788.                     resizable = function(destroy) {
  13789.                         if ($.fn.resizable) {
  13790.                             if (destroy) {
  13791.                                 rhandle.filter(':ui-resizable').resizable('destroy');
  13792.                                 rhandle.hide();
  13793.                             }
  13794.                             else {
  13795.                                 rhandle.show();
  13796.                                 rhandle.resizable({
  13797.                                     alsoResize  : img,
  13798.                                     aspectRatio : cratio,
  13799.                                     resize      : resize.update,
  13800.                                     stop        : resize.fixHeight
  13801.                                 });
  13802.                             }
  13803.                         }
  13804.                     },
  13805.                     croppable = function(destroy) {
  13806.                         if ($.fn.draggable && $.fn.resizable) {
  13807.                             if (destroy) {
  13808.                                 rhandlec.filter(':ui-resizable').resizable('destroy');
  13809.                                 rhandlec.filter(':ui-draggable').draggable('destroy');
  13810.                                 basec.hide();
  13811.                             }
  13812.                             else {
  13813.                                 imgc
  13814.                                     .width(img.width())
  13815.                                     .height(img.height());
  13816.                                
  13817.                                 coverc
  13818.                                     .width(img.width())
  13819.                                     .height(img.height());
  13820.                                
  13821.                                 rhandlec
  13822.                                     .width(imgc.width())
  13823.                                     .height(imgc.height())
  13824.                                     .offset(imgc.offset())
  13825.                                     .resizable({
  13826.                                         containment : basec,
  13827.                                         resize      : crop.resize_update,
  13828.                                         handles     : 'all'
  13829.                                     })
  13830.                                     .draggable({
  13831.                                         handle      : coverc,
  13832.                                         containment : imgc,
  13833.                                         drag        : crop.drag_update
  13834.                                     });
  13835.                                
  13836.                                 basec.show()
  13837.                                     .width(img.width())
  13838.                                     .height(img.height());
  13839.                                
  13840.                                 crop.update();
  13841.                             }
  13842.                         }
  13843.                     },
  13844.                     rotateable = function(destroy) {
  13845.                         if ($.fn.draggable && $.fn.resizable) {
  13846.                             if (destroy) {
  13847.                                 imgr.hide();
  13848.                             }
  13849.                             else {
  13850.                                 imgr.show()
  13851.                                     .width(rwidth)
  13852.                                     .height(rheight)
  13853.                                     .css('margin-top', (pheight-rheight)/2 + 'px')
  13854.                                     .css('margin-left', (pwidth-rwidth)/2 + 'px');
  13855.  
  13856.                             }
  13857.                         }
  13858.                     },
  13859.                     save = function() {
  13860.                         var w, h, x, y, d;
  13861.                         var mode = $('input:checked', uitype).val();
  13862.                        
  13863.                         //width.add(height).change(); // may be unnecessary
  13864.                        
  13865.                         if (mode == 'resize') {
  13866.                             w = parseInt(width.val()) || 0;
  13867.                             h = parseInt(height.val()) || 0;
  13868.                         } else if (mode == 'crop') {
  13869.                             w = parseInt(offsetX.val()) || 0;
  13870.                             h = parseInt(offsetY.val()) || 0;
  13871.                             x = parseInt(pointX.val()) || 0;
  13872.                             y = parseInt(pointY.val()) || 0;
  13873.                         } else if (mode == 'rotate') {
  13874.                             w = owidth;
  13875.                             h = oheight;
  13876.                             d = parseInt(degree.val()) || 0;
  13877.                             if (d < 0 || d > 360) {
  13878.                                 return fm.error('Invalid rotate degree');
  13879.                             }
  13880.                             if (d == 0 || d == 360) {
  13881.                                 return fm.error('Image dose not rotated');
  13882.                             }
  13883.                         }
  13884.                        
  13885.                         if (mode != 'rotate') {
  13886.  
  13887.                             if (w <= 0 || h <= 0) {
  13888.                                 return fm.error('Invalid image size');
  13889.                             }
  13890.                            
  13891.                             if (w == owidth && h == oheight) {
  13892.                                 return fm.error('Image size not changed');
  13893.                             }
  13894.  
  13895.                         }
  13896.                        
  13897.                         dialog.elfinderdialog('close');
  13898.                        
  13899.                         fm.request({
  13900.                             data : {
  13901.                                 cmd    : 'resize',
  13902.                                 target : file.hash,
  13903.                                 width  : w,
  13904.                                 height : h,
  13905.                                 x      : x,
  13906.                                 y      : y,
  13907.                                 degree : d,
  13908.                                 mode   : mode
  13909.                             },
  13910.                             notify : {type : 'resize', cnt : 1}
  13911.                         })
  13912.                         .fail(function(error) {
  13913.                             dfrd.reject(error);
  13914.                         })
  13915.                         .done(function() {
  13916.                             dfrd.resolve();
  13917.                         });
  13918.                        
  13919.                     },
  13920.                     buttons = {},
  13921.                     hline   = 'elfinder-resize-handle-hline',
  13922.                     vline   = 'elfinder-resize-handle-vline',
  13923.                     rpoint  = 'elfinder-resize-handle-point',
  13924.                     src     = fm.url(file.hash)
  13925.                     ;
  13926.                
  13927.                 imgr.mousedown( rotate.start );
  13928.                 $(document).mouseup( rotate.stop );
  13929.                    
  13930.                 uiresize.append($(row).append($(label).text(fm.i18n('width'))).append(width).append(reset))
  13931.                     .append($(row).append($(label).text(fm.i18n('height'))).append(height))
  13932.                     .append($(row).append($('<label/>').text(fm.i18n('aspectRatio')).prepend(constr)))
  13933.                     .append($(row).append(fm.i18n('scale')+' ').append(uiprop));
  13934.                
  13935.                 uicrop.append($(row).append($(label).text('X')).append(pointX))
  13936.                     .append($(row).append($(label).text('Y')).append(pointY))
  13937.                     .append($(row).append($(label).text(fm.i18n('width'))).append(offsetX))
  13938.                     .append($(row).append($(label).text(fm.i18n('height'))).append(offsetY));
  13939.                
  13940.                 uirotate.append($(row)
  13941.                     .append($(label).text(fm.i18n('rotate')))
  13942.                     .append($('<div style="float:left; width: 130px;">')
  13943.                         .append($('<div style="float:left;">')
  13944.                             .append(degree)
  13945.                             .append($('<span/>').text(fm.i18n('degree')))
  13946.                         )
  13947.                         .append($(uibuttonset).append(uideg270).append($(uiseparator)).append(uideg90))
  13948.                     )
  13949.                     .append(uidegslider)
  13950.                 );
  13951.  
  13952.                
  13953.                 dialog.append(uitype);
  13954.  
  13955.                 control.append($(row))
  13956.                     .append(uiresize)
  13957.                     .append(uicrop.hide())
  13958.                     .append(uirotate.hide())
  13959.                     .find('input,select').attr('disabled', 'disabled');
  13960.                
  13961.                 rhandle.append('<div class="'+hline+' '+hline+'-top"/>')
  13962.                     .append('<div class="'+hline+' '+hline+'-bottom"/>')
  13963.                     .append('<div class="'+vline+' '+vline+'-left"/>')
  13964.                     .append('<div class="'+vline+' '+vline+'-right"/>')
  13965.                     .append('<div class="'+rpoint+' '+rpoint+'-e"/>')
  13966.                     .append('<div class="'+rpoint+' '+rpoint+'-se"/>')
  13967.                     .append('<div class="'+rpoint+' '+rpoint+'-s"/>');
  13968.                    
  13969.                 preview.append(spinner).append(rhandle.hide()).append(img.hide());
  13970.  
  13971.                 rhandlec.css('position', 'absolute')
  13972.                     .append('<div class="'+hline+' '+hline+'-top"/>')
  13973.                     .append('<div class="'+hline+' '+hline+'-bottom"/>')
  13974.                     .append('<div class="'+vline+' '+vline+'-left"/>')
  13975.                     .append('<div class="'+vline+' '+vline+'-right"/>')
  13976.                     .append('<div class="'+rpoint+' '+rpoint+'-n"/>')
  13977.                     .append('<div class="'+rpoint+' '+rpoint+'-e"/>')
  13978.                     .append('<div class="'+rpoint+' '+rpoint+'-s"/>')
  13979.                     .append('<div class="'+rpoint+' '+rpoint+'-w"/>')
  13980.                     .append('<div class="'+rpoint+' '+rpoint+'-ne"/>')
  13981.                     .append('<div class="'+rpoint+' '+rpoint+'-se"/>')
  13982.                     .append('<div class="'+rpoint+' '+rpoint+'-sw"/>')
  13983.                     .append('<div class="'+rpoint+' '+rpoint+'-nw"/>');
  13984.  
  13985.                 preview.append(basec.css('position', 'absolute').hide().append(imgc).append(rhandlec.append(coverc)));
  13986.                
  13987.                 preview.append(imgr.hide());
  13988.                
  13989.                 preview.css('overflow', 'hidden');
  13990.                
  13991.                 dialog.append(preview).append(control);
  13992.                
  13993.                 buttons[fm.i18n('btnApply')] = save;
  13994.                 buttons[fm.i18n('btnCancel')] = function() { dialog.elfinderdialog('close'); };
  13995.                
  13996.                 fm.dialog(dialog, {
  13997.                     title          : fm.escape(file.name),
  13998.                     width          : 650,
  13999.                     resizable      : false,
  14000.                     destroyOnClose : true,
  14001.                     buttons        : buttons,
  14002.                     open           : function() { preview.zIndex(1+$(this).parent().zIndex()); }
  14003.                 }).attr('id', id);
  14004.                
  14005.                 // for IE < 9 dialog mising at open second+ time.
  14006.                 if (fm.UA.ltIE8) {
  14007.                     $('.elfinder-dialog').css('filter', '');
  14008.                 }
  14009.                
  14010.                 reset.css('left', width.position().left + width.width() + 12);
  14011.                
  14012.                 coverc.css({ 'opacity': 0.2, 'background-color': '#fff', 'position': 'absolute'}),
  14013.                 rhandlec.css('cursor', 'move');
  14014.                 rhandlec.find('.elfinder-resize-handle-point').css({
  14015.                     'background-color' : '#fff',
  14016.                     'opacity': 0.5,
  14017.                     'border-color':'#000'
  14018.                 });
  14019.  
  14020.                 imgr.css('cursor', 'pointer');
  14021.                
  14022.                 uitype.buttonset();
  14023.                
  14024.                 pwidth  = preview.width()  - (rhandle.outerWidth()  - rhandle.width());
  14025.                 pheight = preview.height() - (rhandle.outerHeight() - rhandle.height());
  14026.                
  14027.                 img.attr('src', src + (src.indexOf('?') === -1 ? '?' : '&')+'_='+Math.random());
  14028.                 imgc.attr('src', img.attr('src'));
  14029.                 imgr.attr('src', img.attr('src'));
  14030.                
  14031.             },
  14032.            
  14033.             id, dialog
  14034.             ;
  14035.            
  14036.  
  14037.         if (!files.length || files[0].mime.indexOf('image/') === -1) {
  14038.             return dfrd.reject();
  14039.         }
  14040.        
  14041.         id = 'resize-'+fm.namespace+'-'+files[0].hash;
  14042.         dialog = fm.getUI().find('#'+id);
  14043.        
  14044.         if (dialog.length) {
  14045.             dialog.elfinderdialog('toTop');
  14046.             return dfrd.resolve();
  14047.         }
  14048.        
  14049.         open(files[0], id);
  14050.            
  14051.         return dfrd;
  14052.     };
  14053.  
  14054. };
  14055.  
  14056. (function ($) {
  14057.    
  14058.     var findProperty = function (styleObject, styleArgs) {
  14059.         var i = 0 ;
  14060.         for( i in styleArgs) {
  14061.             if (typeof styleObject[styleArgs[i]] != 'undefined')
  14062.                 return styleArgs[i];
  14063.         }
  14064.         styleObject[styleArgs[i]] = '';
  14065.         return styleArgs[i];
  14066.     };
  14067.    
  14068.     $.cssHooks.rotate = {
  14069.         get: function(elem, computed, extra) {
  14070.             return $(elem).rotate();
  14071.         },
  14072.         set: function(elem, value) {
  14073.             $(elem).rotate(value);
  14074.             return value;
  14075.         }
  14076.     };
  14077.     $.cssHooks.transform = {
  14078.         get: function(elem, computed, extra) {
  14079.             var name = findProperty( elem.style ,
  14080.                 ['WebkitTransform', 'MozTransform', 'OTransform' , 'msTransform' , 'transform'] );
  14081.             return elem.style[name];
  14082.         },
  14083.         set: function(elem, value) {
  14084.             var name = findProperty( elem.style ,
  14085.                 ['WebkitTransform', 'MozTransform', 'OTransform' , 'msTransform' , 'transform'] );
  14086.             elem.style[name] = value;
  14087.             return value;
  14088.         }
  14089.     };
  14090.    
  14091.     $.fn.rotate = function(val) {
  14092.         if (typeof val == 'undefined') {
  14093.             if (!!window.opera) {
  14094.                 var r = this.css('transform').match(/rotate\((.*?)\)/);
  14095.                 return  ( r && r[1])?
  14096.                     Math.round(parseFloat(r[1]) * 180 / Math.PI) : 0;
  14097.             } else {
  14098.                 var r = this.css('transform').match(/rotate\((.*?)\)/);
  14099.                 return  ( r && r[1])? parseInt(r[1]) : 0;
  14100.             }
  14101.         }
  14102.         this.css('transform',
  14103.             this.css('transform').replace(/none|rotate\(.*?\)/, '') + 'rotate(' + parseInt(val) + 'deg)');
  14104.         return this;
  14105.     };
  14106.  
  14107.     $.fx.step.rotate  = function(fx) {
  14108.         if ( fx.state == 0 ) {
  14109.             fx.start = $(fx.elem).rotate();
  14110.             fx.now = fx.start;
  14111.         }
  14112.         $(fx.elem).rotate(fx.now);
  14113.     };
  14114.  
  14115.     if (typeof window.addEventListener == "undefined" && typeof document.getElementsByClassName == "undefined") { // IE & IE<9
  14116.         var GetAbsoluteXY = function(element) {
  14117.             var pnode = element;
  14118.             var x = pnode.offsetLeft;
  14119.             var y = pnode.offsetTop;
  14120.            
  14121.             while ( pnode.offsetParent ) {
  14122.                 pnode = pnode.offsetParent;
  14123.                 if (pnode != document.body && pnode.currentStyle['position'] != 'static') {
  14124.                     break;
  14125.                 }
  14126.                 if (pnode != document.body && pnode != document.documentElement) {
  14127.                     x -= pnode.scrollLeft;
  14128.                     y -= pnode.scrollTop;
  14129.                 }
  14130.                 x += pnode.offsetLeft;
  14131.                 y += pnode.offsetTop;
  14132.             }
  14133.            
  14134.             return { x: x, y: y };
  14135.         };
  14136.        
  14137.         var StaticToAbsolute = function (element) {
  14138.             if ( element.currentStyle['position'] != 'static') {
  14139.                 return ;
  14140.             }
  14141.  
  14142.             var xy = GetAbsoluteXY(element);
  14143.             element.style.position = 'absolute' ;
  14144.             element.style.left = xy.x + 'px';
  14145.             element.style.top = xy.y + 'px';
  14146.         };
  14147.  
  14148.         var IETransform = function(element,transform){
  14149.  
  14150.             var r;
  14151.             var m11 = 1;
  14152.             var m12 = 1;
  14153.             var m21 = 1;
  14154.             var m22 = 1;
  14155.  
  14156.             if (typeof element.style['msTransform'] != 'undefined'){
  14157.                 return true;
  14158.             }
  14159.  
  14160.             StaticToAbsolute(element);
  14161.  
  14162.             r = transform.match(/rotate\((.*?)\)/);
  14163.             var rotate =  ( r && r[1])  ?   parseInt(r[1])  :   0;
  14164.  
  14165.             rotate = rotate % 360;
  14166.             if (rotate < 0) rotate = 360 + rotate;
  14167.  
  14168.             var radian= rotate * Math.PI / 180;
  14169.             var cosX =Math.cos(radian);
  14170.             var sinY =Math.sin(radian);
  14171.  
  14172.             m11 *= cosX;
  14173.             m12 *= -sinY;
  14174.             m21 *= sinY;
  14175.             m22 *= cosX;
  14176.  
  14177.             element.style.filter =  (element.style.filter || '').replace(/progid:DXImageTransform\.Microsoft\.Matrix\([^)]*\)/, "" ) +
  14178.                 ("progid:DXImageTransform.Microsoft.Matrix(" +
  14179.                      "M11=" + m11 +
  14180.                     ",M12=" + m12 +
  14181.                     ",M21=" + m21 +
  14182.                     ",M22=" + m22 +
  14183.                     ",FilterType='bilinear',sizingMethod='auto expand')")
  14184.                 ;
  14185.  
  14186.             var ow = parseInt(element.style.width || element.width || 0 );
  14187.             var oh = parseInt(element.style.height || element.height || 0 );
  14188.  
  14189.             var radian = rotate * Math.PI / 180;
  14190.             var absCosX =Math.abs(Math.cos(radian));
  14191.             var absSinY =Math.abs(Math.sin(radian));
  14192.  
  14193.             var dx = (ow - (ow * absCosX + oh * absSinY)) / 2;
  14194.             var dy = (oh - (ow * absSinY + oh * absCosX)) / 2;
  14195.  
  14196.             element.style.marginLeft = Math.floor(dx) + "px";
  14197.             element.style.marginTop  = Math.floor(dy) + "px";
  14198.  
  14199.             return(true);
  14200.         };
  14201.        
  14202.         var transform_set = $.cssHooks.transform.set;
  14203.         $.cssHooks.transform.set = function(elem, value) {
  14204.             transform_set.apply(this, [elem, value] );
  14205.             IETransform(elem,value);
  14206.             return value;
  14207.         };
  14208.     }
  14209.  
  14210. })(jQuery);
  14211.  
  14212.  
  14213. /*
  14214.  * File: /js/commands/rm.js
  14215.  */
  14216.  
  14217. /**
  14218.  * @class  elFinder command "rm"
  14219.  * Delete files
  14220.  *
  14221.  * @author Dmitry (dio) Levashov
  14222.  **/
  14223. elFinder.prototype.commands.rm = function() {
  14224.    
  14225.     this.shortcuts = [{
  14226.         pattern     : 'delete ctrl+backspace'
  14227.     }];
  14228.    
  14229.     this.getstate = function(sel) {
  14230.         var fm = this.fm;
  14231.         sel = sel || fm.selected();
  14232.         return !this._disabled && sel.length && $.map(sel, function(h) { var f = fm.file(h); return f && f.phash && !f.locked ? h : null }).length == sel.length
  14233.             ? 0 : -1;
  14234.     }
  14235.    
  14236.     this.exec = function(hashes) {
  14237.         var self   = this,
  14238.             fm     = this.fm,
  14239.             dfrd   = $.Deferred()
  14240.                 .fail(function(error) {
  14241.                     error && fm.error(error);
  14242.                 }),
  14243.             files  = this.files(hashes),
  14244.             cnt    = files.length,
  14245.             cwd    = fm.cwd().hash,
  14246.             goup   = false;
  14247.  
  14248.         if (!cnt || this._disabled) {
  14249.             return dfrd.reject();
  14250.         }
  14251.         var restrictions = 0;
  14252.         $.each(files, function(i, file) {
  14253.             if(!file.read) {
  14254.                 restrictions++;
  14255.             }
  14256.         });
  14257.         if(restrictions > 0) {
  14258.                 return !dfrd.reject(['errLocked', 'Object']);
  14259.         } else {
  14260.         $.each(files, function(i, file) {
  14261.             if (!file.phash) {
  14262.                 return !dfrd.reject(['errRm', file.name, 'errPerm']);
  14263.             }
  14264.             if (file.locked) {
  14265.                 return !dfrd.reject(['errLocked', file.name]);
  14266.             }
  14267.             if (file.mime === 'directory') {
  14268.                 var parents = fm.parents(cwd);
  14269.                 if (file.hash == cwd || $.inArray(file.hash, parents)) {
  14270.                     goup = (file.phash && fm.file(file.phash).read)? file.phash : fm.root(file.hash);
  14271.                 }
  14272.             }
  14273.         });
  14274.  
  14275.         if (dfrd.state() == 'pending') {
  14276.             files = this.hashes(hashes);
  14277.            
  14278.             fm.lockfiles({files : files});
  14279.             fm.confirm({
  14280.                 title  : self.title,
  14281.                 text   : 'confirmRm',
  14282.                 accept : {
  14283.                     label    : 'btnRm',
  14284.                     callback : function() {  
  14285.                         fm.request({
  14286.                             data   : {cmd  : 'rm', targets : files},
  14287.                             notify : {type : 'rm', cnt : cnt},
  14288.                             preventFail : true
  14289.                         })
  14290.                         .fail(function(error) {
  14291.                             dfrd.reject(error);
  14292.                         })
  14293.                         .done(function(data) {
  14294.                             dfrd.done(data);
  14295.                             goup && fm.exec('open', goup)
  14296.                         })
  14297.                         .always(function() {
  14298.                             fm.unlockfiles({files : files});
  14299.                         });
  14300.                     }
  14301.                 },
  14302.                 cancel : {
  14303.                     label    : 'btnCancel',
  14304.                     callback : function() {
  14305.                         fm.unlockfiles({files : files});
  14306.                         fm.selectfiles({files : files});
  14307.                         dfrd.reject();
  14308.                     }
  14309.                 }
  14310.             });
  14311.         }
  14312.            
  14313.         return dfrd;
  14314.         }
  14315.     }
  14316.  
  14317. }
  14318.  
  14319. /*
  14320.  * File: /js/commands/search.js
  14321.  */
  14322.  
  14323. /**
  14324.  * @class  elFinder command "search"
  14325.  * Find files
  14326.  *
  14327.  * @author Dmitry (dio) Levashov
  14328.  **/
  14329. elFinder.prototype.commands.search = function() {
  14330.     this.title          = 'Find files';
  14331.     this.options        = {ui : 'searchbutton'}
  14332.     this.alwaysEnabled  = true;
  14333.     this.updateOnSelect = false;
  14334.    
  14335.     /**
  14336.      * Return command status.
  14337.      * Search does not support old api.
  14338.      *
  14339.      * @return Number
  14340.      **/
  14341.     this.getstate = function() {
  14342.         return 0;
  14343.     }
  14344.    
  14345.     /**
  14346.      * Send search request to backend.
  14347.      *
  14348.      * @param  String  search string
  14349.      * @return $.Deferred
  14350.      **/
  14351.     this.exec = function(q, target, mime) {
  14352.         var fm = this.fm;
  14353.        
  14354.         if (typeof(q) == 'string' && q) {
  14355.             target = target? target : null;
  14356.             mime = mime? $.trim(mime).replace(',', ' ').split(' ') : [];
  14357.             $.each(mime, function(){ return $.trim(this); });
  14358.             fm.trigger('searchstart', {query : q, target : target, mimes : mime});
  14359.            
  14360.             return fm.request({
  14361.                 data   : {cmd : 'search', q : q, target : target, mimes : mime},
  14362.                 notify : {type : 'search', cnt : 1, hideCnt : true}
  14363.             });
  14364.         }
  14365.         fm.getUI('toolbar').find('.'+fm.res('class', 'searchbtn')+' :text').focus();
  14366.         return $.Deferred().reject();
  14367.     }
  14368.  
  14369. }
  14370.  
  14371. /*
  14372.  * File: /js/commands/sort.js
  14373.  */
  14374.  
  14375. /**
  14376.  * @class  elFinder command "sort"
  14377.  * Change sort files rule
  14378.  *
  14379.  * @author Dmitry (dio) Levashov
  14380.  **/
  14381. elFinder.prototype.commands.sort = function() {
  14382.     var self  = this,
  14383.     fm    = self.fm,
  14384.     timer;
  14385.    
  14386.     /**
  14387.      * Command options
  14388.      *
  14389.      * @type  Object
  14390.      */
  14391.     this.options = {ui : 'sortbutton'};
  14392.    
  14393.     fm.bind('open sortchange', function() {
  14394.         self.variants = [];
  14395.         $.each(fm.sortRules, function(name, value) {
  14396.             var sort = {
  14397.                     type  : name,
  14398.                     order : name == fm.sortType ? fm.sortOrder == 'asc' ? 'desc' : 'asc' : fm.sortOrder
  14399.                 };
  14400.             var arr = name == fm.sortType ? (sort.order == 'asc'? 'n' : 's') : '';
  14401.             self.variants.push([sort, (arr? '<span class="ui-icon ui-icon-arrowthick-1-'+arr+'"></span>' : '') + '&nbsp;' + fm.i18n('sort'+name)]);
  14402.         });
  14403.     });
  14404.    
  14405.     fm.bind('open sortchange viewchange search searchend', function() {
  14406.         timer && clearTimeout(timer);
  14407.         timer = setTimeout(function(){
  14408.             var cols = $(fm.cwd).find('div.elfinder-cwd-wrapper-list table');
  14409.             if (cols.length) {
  14410.                 $.each(fm.sortRules, function(name, value) {
  14411.                     var td = cols.find('thead tr td.elfinder-cwd-view-th-'+name);
  14412.                     if (td.length) {
  14413.                         var current = ( name == fm.sortType),
  14414.                         sort = {
  14415.                             type  : name,
  14416.                             order : current ? fm.sortOrder == 'asc' ? 'desc' : 'asc' : fm.sortOrder
  14417.                         },arr;
  14418.                         if (current) {
  14419.                             td.addClass('ui-state-active');
  14420.                             arr = fm.sortOrder == 'asc' ? 'n' : 's';
  14421.                             $('<span class="ui-icon ui-icon-triangle-1-'+arr+'"/>').appendTo(td);
  14422.                         }
  14423.                         $(td).on('click', function(e){
  14424.                             e.stopPropagation();
  14425.                             self.exec([], sort);
  14426.                         })
  14427.                         .hover(function() {
  14428.                             $(this).addClass('ui-state-hover');
  14429.                         },function() {
  14430.                             $(this).removeClass('ui-state-hover');
  14431.                         });
  14432.                     }
  14433.                    
  14434.                 });
  14435.             }
  14436.         }, 100);
  14437.     });
  14438.    
  14439.     this.getstate = function() {
  14440.         return 0;
  14441.     };
  14442.    
  14443.     this.exec = function(hashes, sortopt) {
  14444.         var fm = this.fm,
  14445.             sort = $.extend({
  14446.                 type  : fm.sortType,
  14447.                 order : fm.sortOrder,
  14448.                 stick : fm.sortStickFolders
  14449.             }, sortopt);
  14450.  
  14451.         this.fm.setSort(sort.type, sort.order, sort.stick);
  14452.         return $.Deferred().resolve();
  14453.     };
  14454.  
  14455. };
  14456.  
  14457. /*
  14458.  * File: /js/commands/up.js
  14459.  */
  14460.  
  14461. /**
  14462.  * @class  elFinder command "up"
  14463.  * Go into parent directory
  14464.  *
  14465.  * @author Dmitry (dio) Levashov
  14466.  **/
  14467. elFinder.prototype.commands.up = function() {
  14468.     this.alwaysEnabled = true;
  14469.     this.updateOnSelect = false;
  14470.    
  14471.     this.shortcuts = [{
  14472.         pattern     : 'ctrl+up'
  14473.     }];
  14474.    
  14475.     this.getstate = function() {
  14476.         return this.fm.cwd().phash ? 0 : -1;
  14477.     }
  14478.    
  14479.     this.exec = function() {
  14480.         return this.fm.cwd().phash ? this.fm.exec('open', this.fm.cwd().phash) : $.Deferred().reject();
  14481.     }
  14482.  
  14483. }
  14484.  
  14485. /*
  14486.  * File: /js/commands/upload.js
  14487.  */
  14488.  
  14489. /**
  14490.  * @class elFinder command "upload"
  14491.  * Upload files using iframe or XMLHttpRequest & FormData.
  14492.  * Dialog allow to send files using drag and drop
  14493.  *
  14494.  * @type  elFinder.command
  14495.  * @author  Dmitry (dio) Levashov
  14496.  */
  14497. elFinder.prototype.commands.upload = function() {
  14498.     var hover = this.fm.res('class', 'hover');
  14499.    
  14500.     this.disableOnSearch = true;
  14501.     this.updateOnSelect  = false;
  14502.    
  14503.     // Shortcut opens dialog
  14504.     this.shortcuts = [{
  14505.         pattern     : 'ctrl+u'
  14506.     }];
  14507.    
  14508.     /**
  14509.      * Return command state
  14510.      *
  14511.      * @return Number
  14512.      **/
  14513.     this.getstate = function(sel) {
  14514.         var fm = this.fm, f,
  14515.         sel = fm.directUploadTarget? [fm.directUploadTarget] : (sel || [fm.cwd().hash]);
  14516.         if (!this._disabled && sel.length == 1) {
  14517.             f = fm.file(sel[0]);
  14518.         }
  14519.         return (f && f.mime == 'directory' && f.write)? 0 : -1;
  14520.     };
  14521.    
  14522.    
  14523.     this.exec = function(data) {
  14524.         var fm = this.fm,
  14525.             targets = data && (data instanceof Array)? data : null,
  14526.             check  = !targets && data && data.target? [ data.target ] : targets,
  14527.             fmUpload = function(data) {
  14528.                 fm.upload(data)
  14529.                     .fail(function(error) {
  14530.                         dfrd.reject(error);
  14531.                     })
  14532.                     .done(function(data) {
  14533.                         dfrd.resolve(data);
  14534.                     });
  14535.             },
  14536.             upload = function(data) {
  14537.                 dialog.elfinderdialog('close');
  14538.                 if (targets) {
  14539.                     data.target = targets[0];
  14540.                 }
  14541.                 fmUpload(data);
  14542.             },
  14543.             dfrd = $.Deferred(),
  14544.             dialog, input, button, dropbox, pastebox, dropUpload, paste;
  14545.        
  14546.         if (this.getstate(check) < 0) {
  14547.             return dfrd.reject();
  14548.         }
  14549.        
  14550.         dropUpload = function(e) {
  14551.             e.stopPropagation();
  14552.             e.preventDefault();
  14553.             var file = false,
  14554.                 type = '',
  14555.                 data = null,
  14556.                 target = e._target || null;
  14557.             try{
  14558.                 data = e.dataTransfer.getData('text/html');
  14559.             } catch(e) {}
  14560.             if (data) {
  14561.                 file = [ data ];
  14562.                 type = 'html';
  14563.             } else if (e.dataTransfer && e.dataTransfer.items &&  e.dataTransfer.items.length) {
  14564.                 file = e.dataTransfer;
  14565.                 type = 'data';
  14566.             } else if (e.dataTransfer && e.dataTransfer.files &&  e.dataTransfer.files.length) {
  14567.                 file = e.dataTransfer.files;
  14568.                 type = 'files';
  14569.             } else if (data = e.dataTransfer.getData('text')) {
  14570.                 file = [ data ];
  14571.                 type = 'text';
  14572.             }
  14573.             if (file) {
  14574.                 fmUpload({files : file, type : type, target : target});
  14575.             } else {
  14576.                 dfrd.reject();
  14577.             }
  14578.         };
  14579.        
  14580.         if (!targets && data) {
  14581.             if (data.input || data.files) {
  14582.                 data.type = 'files';
  14583.                 fmUpload(data);
  14584.             } else if (data.dropEvt) {
  14585.                 dropUpload(data.dropEvt);
  14586.             }
  14587.             return dfrd;
  14588.         }
  14589.        
  14590.         paste = function(e) {
  14591.             var e = e.originalEvent || e;
  14592.             var files = [], items = [];
  14593.             var file;
  14594.             if (e.clipboardData) {
  14595.                 if (e.clipboardData.items && e.clipboardData.items.length){
  14596.                     items = e.clipboardData.items;
  14597.                     for (var i=0; i < items.length; i++) {
  14598.                         if (e.clipboardData.items[i].kind == 'file') {
  14599.                             file = e.clipboardData.items[i].getAsFile();
  14600.                             files.push(file);
  14601.                         }
  14602.                     }
  14603.                 } else if (e.clipboardData.files && e.clipboardData.files.length) {
  14604.                     files = e.clipboardData.files;
  14605.                 }
  14606.                 if (files.length) {
  14607.                     upload({files : files, type : 'files'});
  14608.                     return;
  14609.                 }
  14610.             }
  14611.             var my = e.target || e.srcElement;
  14612.             setTimeout(function () {
  14613.                 if (my.innerHTML) {
  14614.                     $(my).find('img').each(function(i, v){
  14615.                         if (v.src.match(/^webkit-fake-url:\/\//)) {
  14616.                             // For Safari's bug.
  14617.                             // ref. https://bugs.webkit.org/show_bug.cgi?id=49141
  14618.                             //      https://dev.ckeditor.com/ticket/13029
  14619.                             $(v).remove();
  14620.                         }
  14621.                     });
  14622.                     var src = my.innerHTML.replace(/<br[^>]*>/gi, ' ');
  14623.                     var type = src.match(/<[^>]+>/)? 'html' : 'text';
  14624.                     my.innerHTML = '';
  14625.                     upload({files : [ src ], type : type});
  14626.                 }
  14627.             }, 1);
  14628.         };
  14629.        
  14630.         input = $('<input type="file" multiple="true"/>')
  14631.             .change(function() {
  14632.                 upload({input : input[0], type : 'files'});
  14633.             });
  14634.  
  14635.         button = $('<div class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only"><span class="ui-button-text">'+fm.i18n('selectForUpload')+'</span></div>')
  14636.             .append($('<form/>').append(input))
  14637.             .hover(function() {
  14638.                 button.toggleClass(hover)
  14639.             });
  14640.            
  14641.         dialog = $('<div class="elfinder-upload-dialog-wrapper"/>')
  14642.             .append(button);
  14643.        
  14644.         pastebox = $('<div class="ui-corner-all elfinder-upload-dropbox" contenteditable="true">'+fm.i18n('dropFilesBrowser')+'</div>')
  14645.             .on('paste drop', function(e){
  14646.                 paste(e);
  14647.             })
  14648.             .on('mousedown click', function(){
  14649.                 $(this).focus();
  14650.             })
  14651.             .on('focus', function(){
  14652.                 this.innerHTML = '';
  14653.             })
  14654.             .on('dragenter mouseover', function(){
  14655.                 pastebox.addClass(hover);
  14656.             })
  14657.             .on('dragleave mouseout', function(){
  14658.                 pastebox.removeClass(hover);
  14659.             });
  14660.        
  14661.         if (fm.dragUpload) {
  14662.             dropbox = $('<div class="ui-corner-all elfinder-upload-dropbox" contenteditable="true">'+fm.i18n('dropPasteFiles')+'</div>')
  14663.                 .on('paste', function(e){
  14664.                     paste(e);
  14665.                 })
  14666.                 .on('mousedown click', function(){
  14667.                     $(this).focus();
  14668.                 })
  14669.                 .on('focus', function(){
  14670.                     this.innerHTML = '';
  14671.                 })
  14672.                 .on('mouseover', function(){
  14673.                     $(this).addClass(hover);
  14674.                 })
  14675.                 .on('mouseout', function(){
  14676.                     $(this).removeClass(hover);
  14677.                 })
  14678.                 .prependTo(dialog)
  14679.                 .after('<div class="elfinder-upload-dialog-or">'+fm.i18n('or')+'</div>')[0];
  14680.            
  14681.             dropbox.addEventListener('dragenter', function(e) {
  14682.                 e.stopPropagation();
  14683.                 e.preventDefault();
  14684.                 $(dropbox).addClass(hover);
  14685.             }, false);
  14686.  
  14687.             dropbox.addEventListener('dragleave', function(e) {
  14688.                 e.stopPropagation();
  14689.                 e.preventDefault();
  14690.                 $(dropbox).removeClass(hover);
  14691.             }, false);
  14692.  
  14693.             dropbox.addEventListener('dragover', function(e) {
  14694.                 e.stopPropagation();
  14695.                 e.preventDefault();
  14696.                 $(dropbox).addClass(hover);
  14697.             }, false);
  14698.  
  14699.             dropbox.addEventListener('drop', function(e) {
  14700.                 dialog.elfinderdialog('close');
  14701.                 targets && (e._target = targets[0]);
  14702.                 dropUpload(e);
  14703.             }, false);
  14704.            
  14705.         } else {
  14706.             pastebox
  14707.                 .prependTo(dialog)
  14708.                 .after('<div class="elfinder-upload-dialog-or">'+fm.i18n('or')+'</div>')[0];
  14709.            
  14710.         }
  14711.        
  14712.         fm.dialog(dialog, {
  14713.             title          : this.title + (targets? ' - ' + fm.escape(fm.file(targets[0]).name) : ''),
  14714.             modal          : true,
  14715.             resizable      : false,
  14716.             destroyOnClose : true
  14717.         });
  14718.        
  14719.         return dfrd;
  14720.     };
  14721.  
  14722. };
  14723.  
  14724. /*
  14725.  * File: /js/commands/view.js
  14726.  */
  14727.  
  14728. /**
  14729.  * @class  elFinder command "view"
  14730.  * Change current directory view (icons/list)
  14731.  *
  14732.  * @author Dmitry (dio) Levashov
  14733.  **/
  14734. elFinder.prototype.commands.view = function() {
  14735.     this.value          = this.fm.viewType;
  14736.     this.alwaysEnabled  = true;
  14737.     this.updateOnSelect = false;
  14738.  
  14739.     this.options = { ui : 'viewbutton'};
  14740.    
  14741.     this.getstate = function() {
  14742.         return 0;
  14743.     }
  14744.    
  14745.     this.exec = function() {
  14746.         var value = this.fm.storage('view', this.value == 'list' ? 'icons' : 'list');
  14747.         this.fm.viewchange();
  14748.         this.update(void(0), value);
  14749.     }
  14750.  
  14751. }
  14752. })(jQuery);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement