· 10 years ago · Dec 05, 2015, 11:42 AM
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 **/
21window.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 : ' ',
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 */
2131elFinder.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(/&/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, '"').replace(/'/g, ''');
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 */
4118if (!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 **/
4162elFinder.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(){
4172var 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 */
4245elFinder.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 */
4936elFinder.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 */
5052elFinder.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 **/
5332elFinder.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 */
5622if (elFinder && elFinder.prototype && typeof(elFinder.prototype.i18) == 'object') {
5623 elFinder.prototype.i18.en = {
5624 translator : 'Troex Nevelin <troex@fury.scancode.ru>',
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, '​$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, ' ') + ' ' + 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> </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(' ')
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) : ' ');
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 **/
9694elFinder.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 **/
9788elFinder.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 */
9816elFinder.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 */
10113elFinder.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 */
10155elFinder.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
10189elFinder.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 **/
10276elFinder.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 */
10382elFinder.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 **/
10435elFinder.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 **/
10778elFinder.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 **/
10978elFinder.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 **/
11006elFinder.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 **/
11130elFinder.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 <dio@std42.ru>')[r](work, fm.i18n('chiefdev')));
11170 html.push(atpl[r](author, 'Troex Nevelin <troex@fury.scancode.ru>')[r](work, fm.i18n('maintainer')));
11171 html.push(atpl[r](author, 'Alexey Sukhotin <strogg@yandex.ru>')[r](work, fm.i18n('contributor')));
11172 html.push(atpl[r](author, 'Naoki Sawada <hypweb@gmail.com>')[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
11281elFinder.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 **/
11314elFinder.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 **/
11604elFinder.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 **/
11632elFinder.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 **/
11656elFinder.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
11784elFinder.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 **/
11871elFinder.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¤t='+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 **/
12055elFinder.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 **/
12294elFinder.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 **/
12330elFinder.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
12790elFinder.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 **/
13135elFinder.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 **/
13186elFinder.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, '​$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 **/
13401elFinder.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 **/
14223elFinder.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 **/
14329elFinder.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 **/
14381elFinder.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>' : '') + ' ' + 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 **/
14467elFinder.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 */
14497elFinder.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 **/
14734elFinder.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);