hfs 0.1.6 → 0.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE.txt +674 -0
  2. package/README.md +102 -8
  3. package/admin/assets/index.dcc78777.css +1 -0
  4. package/admin/assets/index.f056db34.js +282 -0
  5. package/admin/assets/sha512.3c0e384c.js +8 -0
  6. package/admin/index.html +17 -0
  7. package/admin/logo.svg +36 -0
  8. package/frontend/assets/index.55c710c2.js +85 -0
  9. package/frontend/assets/index.ee805a6c.css +1 -0
  10. package/frontend/assets/sha512.634b743e.js +8 -0
  11. package/frontend/fontello.css +77 -0
  12. package/frontend/fontello.woff2 +0 -0
  13. package/frontend/index.html +18 -0
  14. package/package.json +93 -28
  15. package/plugins/antibrute/plugin.js +38 -0
  16. package/plugins/download-counter/plugin.js +47 -0
  17. package/plugins/download-counter/public/hits.js +5 -0
  18. package/plugins/updater-disabled/plugin.js +44 -0
  19. package/plugins/vhosting/plugin.js +42 -0
  20. package/src/QuickZipStream.js +285 -0
  21. package/src/ThrottledStream.js +93 -0
  22. package/src/adminApis.js +169 -0
  23. package/src/api.accounts.js +59 -0
  24. package/src/api.auth.js +130 -0
  25. package/src/api.file_list.js +103 -0
  26. package/src/api.helpers.js +32 -0
  27. package/src/api.monitor.js +102 -0
  28. package/src/api.plugins.js +125 -0
  29. package/src/api.vfs.js +164 -0
  30. package/src/apiMiddleware.js +136 -0
  31. package/src/block.js +33 -0
  32. package/src/commands.js +105 -0
  33. package/src/config.js +172 -0
  34. package/src/connections.js +57 -0
  35. package/src/const.js +83 -0
  36. package/src/crypt.js +21 -0
  37. package/src/debounceAsync.js +48 -0
  38. package/src/events.js +9 -0
  39. package/src/frontEndApis.js +38 -0
  40. package/src/github.js +102 -0
  41. package/src/index.js +53 -0
  42. package/src/listen.js +226 -0
  43. package/src/log.js +137 -0
  44. package/src/middlewares.js +154 -0
  45. package/src/misc.js +160 -0
  46. package/src/pbkdf2.js +74 -0
  47. package/src/perm.js +176 -0
  48. package/src/plugins.js +338 -0
  49. package/src/serveFile.js +104 -0
  50. package/src/serveGuiFiles.js +113 -0
  51. package/src/sse.js +29 -0
  52. package/src/throttler.js +91 -0
  53. package/src/update.js +69 -0
  54. package/src/util-files.js +141 -0
  55. package/src/util-generators.js +30 -0
  56. package/src/util-http.js +30 -0
  57. package/src/vfs.js +227 -0
  58. package/src/watchLoad.js +73 -0
  59. package/src/zip.js +69 -0
  60. package/.npmignore +0 -19
  61. package/admin-server.js +0 -212
  62. package/cli.js +0 -33
  63. package/file-server.js +0 -100
  64. package/lib/common.js +0 -10
  65. package/lib/extending.js +0 -158
  66. package/lib/mime.js +0 -19
  67. package/lib/misc.js +0 -75
  68. package/lib/serving.js +0 -81
  69. package/lib/vfs.js +0 -403
  70. package/main.js +0 -24
  71. package/note.txt +0 -104
  72. package/speedtest.js +0 -21
  73. package/static/backend.css +0 -14
  74. package/static/backend.html +0 -32
  75. package/static/backend.js +0 -694
  76. package/static/extending.js +0 -187
  77. package/static/frontend.css +0 -29
  78. package/static/frontend.html +0 -23
  79. package/static/frontend.js +0 -230
  80. package/static/icons/files/archive.png +0 -0
  81. package/static/icons/files/audio.png +0 -0
  82. package/static/icons/files/file.png +0 -0
  83. package/static/icons/files/folder.png +0 -0
  84. package/static/icons/files/image.png +0 -0
  85. package/static/icons/files/link.png +0 -0
  86. package/static/icons/files/video.png +0 -0
  87. package/static/jquery.js +0 -4
  88. package/static/jquery.rule-1.0.2.js +0 -273
  89. package/static/misc.js +0 -194
  90. package/static/tpl.js +0 -17
  91. package/todo.txt +0 -25
package/static/backend.js DELETED
@@ -1,694 +0,0 @@
1
- var socket = io.connect(window.location.origin);
2
-
3
- var tpl = {
4
- item: "<li>"
5
- +"<span class='expansion-button'></span>"
6
- +"<span class='icon'></span>"
7
- +"<span class='label'></span>"
8
- +"</li>",
9
- noChildren: "<span class='no-children'>nothing</span>",
10
- };
11
-
12
- var virtualFocus = 'vfs';
13
-
14
- $(function(){ // dom ready
15
- $(tpl.item).addClass('item').appendTo($('<ul>').appendTo('#vfs')); // create the root element
16
-
17
- // hide expansion button
18
- var style = document.styleSheets[0];
19
- style.addRule('#vfs .expansion-button','opacity:0');
20
- expansionCss = style.rules[style.rules.length-1].style;
21
-
22
- vfsUpdateButtons();
23
- setupEventHandlers();
24
- socket.on('connect', function(){ // socket ready
25
- socket.emit('info.get', {}, function(data){
26
- serverInfo = data||{};
27
- reloadVFS();
28
- });
29
- });
30
- socket.on('vfs.changed', function(data){
31
- if (!log('vfs.changed',data)) return; // something wrong
32
- var folder = data.uri.substr(0, data.uri.lastIndexOf('/')+1);
33
- var it = getItemFromURI(folder);
34
- if (!it) return; // not in the visible tree: ignore
35
- if (!isExpanded(it)) return; // not expanded, we don't see its content, no need to reload
36
- reloadVFS(it);
37
- });
38
- });
39
-
40
- function sameFileName(a,b) {
41
- return serverInfo.caseSensitiveFileNames ? a === b : a.same(b);
42
- } // sameFileName
43
-
44
- /* display a dialog for input.
45
- Currently just a wrapper of prompt().
46
- Arguments:
47
- text, callback
48
- */
49
- function inputBox() {
50
- var a = arguments, text = a[0], def, cb;
51
- switch (typeof a[1]) {
52
- case 'function':
53
- cb = a[1];
54
- break;
55
- case 'string':
56
- def = a[1];
57
- cb = a[2];
58
- break;
59
- default:
60
- return false;
61
- }
62
- cb(prompt(text, def));
63
- } // inputBox
64
-
65
- function msgBox(message) { alert(message) }
66
-
67
- function bindItem() {
68
- var it = getFirstSelectedFolder();
69
- if (!it) return;
70
- inputBox('Enter path', function(s){
71
- if (!s) return;
72
- socket.emit('vfs.set', { uri:getURI(it), resource:s }, function(result){
73
- result.ok
74
- ? reloadVFS(it)
75
- : msgBox(result.error);
76
- });
77
- });
78
- } // bindItem
79
-
80
- function renameItem() {
81
- var it = getFirstSelectedItem();
82
- if (!it || isRoot(it) || isDeleted(it)) return;
83
- inputBox('Enter new name', it.name, function(s){
84
- s = $.trim(s);
85
- if (!s || s == it.name) return; // no change
86
- socket.emit('vfs.set', { uri:getURI(it), name:s }, function(result){
87
- if (!result.ok) {
88
- msgBox(result.error);
89
- return;
90
- }
91
- it.name = s; // update object
92
- bindItemToDOM(it, it.element); // update GUI
93
- });
94
- });
95
- } // renameItem
96
-
97
- function deleteItem() {
98
- // who's gonna be deleted?
99
- var li = getFirstSelected();
100
- if (isDeleted(li)) { // this a deleted one, what we really want is restore it
101
- restoreItem();
102
- return;
103
- }
104
- // we're not going to delete items that are root, or we are already deleting, or that are not! (special items)
105
- var it = asItem(li);
106
- if (!it || isRoot(it) || it.deleting) return;
107
- it.deleting = true; // mark it, so we avoid overlapping operations
108
- var parent = getParent(li);
109
- var pit = asItem(parent);
110
- var oldNumber = pit.deletedItems.length; // we'll use this to see if the number has changed at the end
111
- li.attr('deleting',1).fadeTo(100, 0.5); // mark it visually and at DOM level
112
- vfsSelectNearby(li); // renew selection
113
- // server, please do it
114
- socket.emit('vfs.delete', { uri:getURI(it) }, function(result){
115
- if (!log(result).ok) { // something went wrong
116
- // ugh, forget it.... (unmark)
117
- it.deleting = false;
118
- li.removeAttr('deleting');
119
- // let the user know
120
- msgBox(result.error);
121
- return;
122
- }
123
- // if this number has changed, then we need to do a little extra work: the item became a deleted item.
124
- if (oldNumber !== result.folderDeletedCount) {
125
- pit.deletedItems.push(result.dynamicItem);
126
- updateDeletedItems(parent, result.dynamicItem);
127
- }
128
- // GUI refresh
129
- li.fadeOut(100, function(){
130
- li.remove();
131
- if (!getFirstChild(parent).size()) {
132
- parent.find('ul:first').append(tpl.noChildren); // deleted last item of a folder
133
- }
134
- })
135
- });
136
- } // deleteItem
137
-
138
- function restoreItem(it) {
139
- if (it === undefined)
140
- it = getFirstSelectedItem();
141
- if (!it || !isDeleted(it)) return;
142
- var li = $(it.element);
143
- var folder = li.closest('li.item');
144
- vfsSelectNearby(li);
145
- socket.emit('vfs.restore', { uri:getURI(folder), resource:it.name }, function(result){
146
- if (!result.ok) return;
147
- // do the job locally: remove the element from the array
148
- var a = asItem(folder).deletedItems;
149
- var i = a.length;
150
- while (i--) {
151
- if (sameFileName(a[i].excludeTrailing('/'), it.name)) {
152
- a.splice(i,1);
153
- }
154
- }
155
- // refresh GUI
156
- li.remove();
157
- updateDeletedItems(folder);
158
- addItemUnder(folder, result.item);
159
- // we are not going to automatically select the restored item, so it's easier to restore more files
160
- });
161
- } // restoreItem
162
-
163
- function addItem() {
164
- var it = getFirstSelectedFolder() || getRootItem();
165
- inputBox('Enter name or path', function(s){
166
- if (!s) return;
167
- socket.emit('vfs.add', { uri:getURI(it), resource:s }, function(result){
168
- if (!log(result).ok) {
169
- msgBox(result.error);
170
- return;
171
- }
172
- if (result.item.nodeKind === 'temp') { // this is a dynamic element, was actually restored from the "deleted" list
173
- updateDeletedItems(it);
174
- }
175
- setExpanded(it);
176
- addItemUnder(it, result.item);
177
- vfsSelect(result.item);
178
- });
179
- });
180
- } // addItem
181
-
182
- function vfsSelectNearby(li) {
183
- var selector = 'li:not([deleting])';
184
- var go = li.next(selector);
185
- if (!go.size()) {
186
- go = li.prev(selector);
187
- }
188
- if (!go.size()) {
189
- go = getParent(li);
190
- }
191
- vfsSelect(go);
192
- } // vfsSelectNearby
193
-
194
- function hideExpansionButtons() { animate(expansionCss, 'opacity', 0) }
195
-
196
- function showExpansionButtons(state /** optional */) {
197
- if (typeof state !== 'undefined' && !state) {
198
- hideExpansionButtons();
199
- return;
200
- }
201
- animate(expansionCss, 'opacity', 1, {duration:0.2});
202
- } // showExpansionButtons
203
-
204
- function expandOrCollapseFolder(li) {
205
- isExpanded(li) ? setExpanded(li, false) : expandAndLoad(li);
206
- }
207
-
208
- function setupEventHandlers() {
209
-
210
- $('#vfs').click(function(){
211
- vfsSelect(null); // deselect
212
- });
213
- $('#vfs li').live({
214
- click: function(ev){
215
- ev.stopImmediatePropagation();
216
- removeBrowserSelection();
217
- vfsSelect(this);
218
- },
219
- dblclick: function(ev){
220
- ev.stopImmediatePropagation();
221
- removeBrowserSelection();
222
- var it = getFirstSelected();
223
- if (isFolder(it)) {
224
- expandOrCollapseFolder(it);
225
- }
226
- },
227
- mouseover: function(ev){
228
- ev.stopImmediatePropagation();
229
- $('#vfs li.hovered').removeClass('hovered');
230
- $(this).addClass('hovered');
231
- $('#vfs .expansion-button').fadeIn();
232
- },
233
- mouseout: function(ev){
234
- ev.stopImmediatePropagation();
235
- $(this).removeClass('hovered');
236
- }
237
- });
238
- $('#vfs .expansion-button').live({
239
- click: function(ev){
240
- ev.stopImmediatePropagation();
241
- removeBrowserSelection();
242
- var li = $(ev.target).closest('li');
243
- expandOrCollapseFolder(li);
244
- },
245
- mouseover: function(ev){
246
- $('#vfs .expansion-button.hovered').removeClass('hovered');
247
- $(this).addClass('hovered');
248
- },
249
- mouseout: function(ev){
250
- $(this).removeClass('hovered');
251
- }
252
- });
253
- $('#vfs').hover(showExpansionButtons, hideExpansionButtons);
254
- $('body').keydown(function(ev){
255
- if (!(ev.target instanceof HTMLBodyElement)) return; // focused elsewhere, but the event propagated till here
256
- if (virtualFocusEventHandler(ev) === false) {
257
- ev.stopImmediatePropagation();
258
- return false;
259
- }
260
- });
261
- $('#bindItem').click(bindItem);
262
- $('#addItem').click(addItem);
263
- $('#renameItem').click(renameItem);
264
- $('#deleteItem').click(deleteItem);
265
- } // setupEventHandlers
266
-
267
- function virtualFocusEventHandler(ev) {
268
- // there's a couple of possible event handlers to be called
269
- var v = 'eventHandler_'+virtualFocus;
270
- var fns = [v+'_'+ev.type, v];
271
-
272
- for (var i=0, l=fns.length; i<l; ++i) {
273
- var fn = window[fns[i]]; // try to get it
274
- if (typeof fn == 'function') { // if it exists, run it
275
- if (fn(ev) === false) return false; // it can decide to interrupt the event handling
276
- }
277
- }
278
- } // virtualFocusEventHandler
279
-
280
- function eventHandler_vfs_keydown(ev) {
281
- var sel = getFirstSelected();
282
- var go;
283
- switch (ev.keyCode) {
284
- case 38: // up
285
- go = sel.prev();
286
- if (go.size()) break;
287
- go = getParent(sel);
288
- break;
289
- case 40: // down
290
- if (!sel.size()) { // initialize with root
291
- go = getRoot();
292
- break;
293
- }
294
- if (isExpanded(sel)) { // try first child
295
- go = getFirstChild(sel);
296
- if (go.size()) break;
297
- }
298
- go = sel.next() // try the sibling
299
- if (go.size()) break;
300
- // try the parent's sibling
301
- var v = sel;
302
- while ((v = getParent(v)).size()) {
303
- if (v.next().size()) {
304
- go = v.next();
305
- break;
306
- }
307
- }
308
- break;
309
- case 37: // left
310
- isFolder(sel) && isExpanded(sel)
311
- ? setExpanded(sel, false)
312
- : go = getParent(sel);
313
- break;
314
- case 39: // right
315
- if (!isFolder(sel)) break;
316
- isExpanded(sel)
317
- ? go = getFirstChild(sel)
318
- : expandAndLoad(sel);
319
- break;
320
- case 36: // home
321
- if (sel.size() && !isRoot(sel)) {
322
- go = getFirstChild(getParent(sel));
323
- if (go.size() && !go.is(sel)) break;
324
- }
325
- go = getRoot();
326
- break;
327
- case 35: // end
328
- go = getLastChild(getParent(sel));
329
- if (go.size() && !go.is(sel)) break;
330
- go = getRoot();
331
- // go down till it's possible
332
- while ((v = getLastChild(go)).size()) {
333
- go = v;
334
- }
335
- break;
336
- case 113: // F2
337
- renameItem();
338
- break;
339
- case 46: // delete
340
- deleteItem();
341
- break;
342
- default:
343
- //log(ev.keyCode);
344
- return; // unsupported key, don't handle
345
- }
346
- if (go && go.size()) {
347
- showExpansionButtons();
348
- vfsSelect(go);
349
- }
350
- return false;
351
- } // eventHandler_vfs_keydown
352
-
353
- function vfsSelect(el) {
354
- $('#vfs .selected').removeClass('selected');
355
- asLI(el).addClass('selected').scrollToMe({duration:'fast'});
356
- vfsUpdateButtons();
357
- } // vfsSelect
358
-
359
- function getSelectedItems() {
360
- var res = [];
361
- $('#vfs .selected').each(function(i,e){
362
- var it = $(e).data('item');
363
- res.push(it);
364
- });
365
- return res;
366
- } // getSelectedItems
367
-
368
- function getSelected() { return $('#vfs .selected') }
369
-
370
- function getFirstSelected() { return $('#vfs .selected:first') }
371
-
372
- function getFirstSelectedItem() { return getFirstSelected().data('item') }
373
-
374
- function getFirstSelectedFolder() {
375
- var res = getSelectedItems();
376
- for (var i=0, l=res.length; i<l; ++i) {
377
- if (isFolder(res[i])) {
378
- return res[i];
379
- }
380
- }
381
- return false;
382
- } // getFirstSelectedFolder
383
-
384
- function getRoot() { return $('#vfs li:first'); }
385
-
386
- function getRootItem() { return getRoot().data('item'); }
387
-
388
- /** return children in the same format of the parameter: jQuery, array of items or array of HTMLElements */
389
- function getChildren(x) {
390
- if (!x) return false;
391
- var item = !!x.element;
392
- var children = $(x.element || x).find('ul:first>li');
393
- if (x instanceof $) return children;
394
- var res = [];
395
- children.each(function(idx,el){
396
- res.push(item ? asItem(el) : el);
397
- });
398
- return res;
399
- } // getChildren
400
-
401
- /** return first child of "parent" matching "pattern" */
402
- function getFirstChild(parent, pattern /** optional */) {
403
- var inputType = getType(parent);
404
- parent = asLI(parent);
405
- assert(parent.size(), 'parent');
406
- assert(!pattern || typeof pattern == 'object', 'pattern');
407
- var res = false;
408
- parent.find('ul:first>li').each(function(idx,el){
409
- var child = asItem(el)
410
- if (pattern) { // do we have a pattern?
411
- if (!child) return;
412
- // does it match?
413
- for (var k in pattern) {
414
- if (!pattern.hasOwnProperty(k)) continue;
415
- var match = (k === 'name')
416
- ? sameFileName(pattern[k], child[k])
417
- : pattern[k] === child[k];
418
- if (!match) return; // ...nope, skip
419
- }
420
- }
421
- res = el; // found! mark it
422
- return false; // stop it
423
- });
424
- return toType(inputType, res); // convert output to the same type of the input
425
- } // getFirstChild
426
-
427
- function getLastChild(parent) {
428
- var e = asLI(parent).find('ul:first>li:last');
429
- return toType(getType(parent), e);
430
- } // getLastChild
431
-
432
- /** determine the type of the element */
433
- function getType(x) {
434
- if (!x) return false;
435
- if (x.element) return 'item';
436
- if (x instanceof HTMLElement) return 'html';
437
- if (x instanceof $) return 'jquery';
438
- return false;
439
- } // getType
440
-
441
- /** convert the element to the specified type */
442
- function toType(to, x) {
443
- if (!to) return false; // bad
444
- var from = getType(x);
445
- if (from == to) return x; // no need for conversion
446
- switch (from) {
447
- case false:
448
- return (to == 'jquery') ? $('') : false;
449
- case 'item':
450
- if (to == 'html') return x.element;
451
- // to == 'jquery'
452
- return $(x.element);
453
- case 'html':
454
- if (to == 'jquery') return $(x).closest('li');
455
- // to == 'item'
456
- return $(x).closest('li').data('item');
457
- case 'jquery':
458
- if (to == 'html') return x[0];
459
- // to == 'item'
460
- return x.data('item');
461
- default:
462
- assert('unsupported type: '+from);
463
- }
464
- } // setType
465
-
466
- function getParent(it) {
467
- var p = asLI(it).parent().closest('li');
468
- return toType(getType(it), p);
469
- } // getParent
470
-
471
- function isFolder(it) {
472
- if (asLI(it).hasClass('deleted-items')) {
473
- return true;
474
- }
475
- it = asItem(it);
476
- return it && it.itemKind.endsBy('folder');
477
- } // isFolder
478
-
479
- function isRoot(it) { return getURI(it) == '/' }
480
-
481
- function isExpanded(x) { return asLI(x).hasClass('expanded') }
482
-
483
- function isDeleted(x) { return asLI(x).closest('.deleted-items').size() }
484
-
485
- function getURI(item) {
486
- item = asItem(item);
487
- if (!item) return false;
488
- var p = getParent(item);
489
- return (p ? getURI(p) : '') // recursion
490
- + encodeURI(item.name)
491
- + (p && isFolder(item) ? '/' : '');
492
- } // getURI
493
-
494
- /** get the item from the uri, but only if it's currently in our tree */
495
- function getItemFromURI(uri, from) {
496
- var run = asItem(from) || getRootItem();
497
- for (var i=0, a=uri.split('/'), l=a.length; i<l; ++i) {
498
- var name = a[i];
499
- if (!name) continue;
500
- run = getFirstChild(run, {name:name});
501
- if (!run) return false;
502
- }
503
- return run;
504
- } // getItemFromURI
505
-
506
- function vfsUpdateButtons() {
507
- var li = getFirstSelected();
508
- var it = asItem(li);
509
- enableButton('addItem', !li.size() || it && !it.deleted);
510
- enableButton('bindItem', it && it.itemKind === 'virtual folder');
511
- enableButton('renameItem', it && !isRoot(it) && !isDeleted(it));
512
- enableButton('deleteItem', it && !isRoot(it));
513
- } // vfsUpdateButtons
514
-
515
- function enableButton(name, condition) {
516
- $('#'+name).attr({disabled: !condition});
517
- } // enableButton
518
-
519
- function reloadVFS(item, cb) {
520
- var e = item ? asLI(item) : getRoot();
521
- e.children('ul').empty().append(tpl.noChildren); // remove possible children
522
- socket.emit('vfs.get', { uri:item ? getURI(item) : '/', depth:1 }, function(data){
523
- if (!log('vfs.get',data)) return;
524
- bindItemToDOM(data, e);
525
- setExpanded(e);
526
- updateDeletedItems(e);
527
- data.children.forEach(function(it){
528
- addItemUnder(e, it);
529
- });
530
- if (cb) cb();
531
- });
532
- } // reloadVFS
533
-
534
- /** util function for those functions who want to accept several types but work with the $(LI) */
535
- function asLI(x) { return toType('jquery', x).closest('li') }
536
-
537
- /** util function for those functions who want to accept several types but work with the $(LI) */
538
- function asItem(x) { return toType('item', x) }
539
-
540
- function setExpanded(item, state) {
541
- if (state == undefined) state = true;
542
- var li = asLI(item);
543
- if (!li.size()) return;
544
- item = asItem(item);
545
- var button = li.find('.expansion-button:first');
546
- button.text(state ? '▲' : '▷');
547
- if (!isFolder(li)
548
- || item && item.deleted) { // deleted folders are not expandable
549
- button.css({visibility:'hidden'});
550
- return;
551
- }
552
- if (isExpanded(li) == state) return;
553
- li.addClass(state ? 'expanded' : 'collapsed')
554
- .removeClass(!state ? 'expanded' : 'collapsed');
555
- // deal with the container of children
556
- var ul = li.find('ul:first');
557
- if (state) {
558
- if (!ul.size())
559
- ul = $('<ul>').appendTo(li);
560
- }
561
- else {
562
- ul.remove();
563
- }
564
- return true;
565
- } // setExpanded
566
-
567
- function expandAndLoad(li) {
568
- li = asLI(li);
569
- setExpanded(li);
570
- if (li.hasClass('item'))
571
- return reloadVFS(li);
572
- if (li.hasClass('deleted-items')) {
573
- asItem(getParent(li)).deletedItems.forEach(function(name){
574
- addItemUnder(li, name);
575
- });
576
- }
577
- } // expandAndLoad
578
-
579
- function bindItemToDOM(item, element) {
580
- var li = asLI(element);
581
- item.element = li[0]; // bind the item to the html element, so we can get to it
582
- if (isFolder(item) && !item.deletedItems) {
583
- item.deletedItems = []; // having this always present will ease the rest of the code
584
- }
585
- li.data({item:item});
586
- li.find('.label:first').text(item.name);
587
- var icon = isFolder(item) ? 'folder' : item.itemKind;
588
- if (icon=='file') {
589
- icon = nameToType(item.name) || icon;
590
- }
591
- li.find('.icon:first').html("<img src='"+getIconURI(icon)+"' />");
592
- if (isFolder(item)) {
593
- updateDeletedItems(item);
594
- }
595
- setExpanded(li, false);
596
- return element;
597
- } // bindItemToDOM
598
-
599
- /** updates the pseudo-folder containing removed temp items */
600
- function updateDeletedItems(it, adding) {
601
- var folder = asLI(it);
602
- var ul = folder.children('ul');
603
- it = asItem(it);
604
- if (!it.deletedItems || !it.deletedItems.length) { // no items?
605
- // remove the pseudo-folder
606
- if (!ul.size()) return;
607
- var li = ul.children('li.deleted-items');
608
- if (!li.size()) return;
609
- if (getFirstSelected().is(li)) {
610
- vfsSelectNearby(li);
611
- }
612
- li.remove();
613
- // if there is no more children in the containing folder, remove the UL as well
614
- if (!ul.children().size()) {
615
- ul.remove();
616
- }
617
- return;
618
- }
619
- // ensure there's a UL for the containing folder
620
- if (!ul.size()) {
621
- ul = $('<ul>').appendTo(folder);
622
- }
623
- // ensure there's a LI of the pseudo-folder
624
- var li = ul.children('li.deleted-items');
625
- if (!li.size()) {
626
- li = $(tpl.item).addClass('deleted-items').prependTo(ul);
627
- li.find('.icon:first').html("<img src='"+getIconURI('folder')+"' />");
628
- setExpanded(li, false);
629
- }
630
- // update the label
631
- li.find('.label:first').text('Deleted: {0} item(s)'.format(it.deletedItems.length));
632
- // eventually add the item
633
- if (adding && isExpanded(li)) {
634
- addItemUnder(li, adding);
635
- }
636
- } // updateDeletedItems
637
-
638
- /** put a new element under $under with bound object $item
639
- * @position {undefined|string} admitted values: 'sorted' (default), 'top', 'bottom'
640
- */
641
- function addItemUnder(under, item, position) {
642
- if (!item) return false;
643
- if (position === undefined) position = 'sorted';
644
- under = asLI(under);
645
- if (under.hasClass('deleted-items')
646
- && typeof item == 'string') { // automatic construction of the special item
647
- item = {
648
- deleted: true,
649
- itemKind: item.endsBy('/') ? 'folder' : 'file',
650
- name: item.excludeTrailing('/'),
651
- };
652
- }
653
-
654
- // the UL element is the place for children, have one or create it
655
- var x = under.children('ul');
656
- under = x.size() ? x : $('<ul>').appendTo(under);
657
-
658
-
659
- under.children('span.no-children').remove(); // remove any place holder
660
- var el = $(tpl.item).addClass(item.deleted ? 'deleted' : 'item');
661
- var beforeThis; // where to put the new item
662
- if (position.isIn('top','sorted')) { // both require to put the item after special items (so skip them)
663
- beforeThis = getFirstChild(under); // go down one level
664
- // skip special items
665
- while (beforeThis.size() && !beforeThis.hasClass('item')) {
666
- beforeThis = beforeThis.next();
667
- }
668
- }
669
- if (position === 'sorted') {
670
- // skip elements until we find one that has a "higher" name (case-insensitively)
671
- var name = item.name.low();
672
- while (beforeThis.size()
673
- && beforeThis.hasClass('item')
674
- && beforeThis.find('.label').text().low() < name) {
675
- beforeThis = beforeThis.next();
676
- }
677
- }
678
- // add the item at the calculated position, or at the end
679
- (beforeThis && beforeThis.size())
680
- ? el.insertBefore(beforeThis)
681
- : el.appendTo(under);
682
- // do the final setup
683
- return bindItemToDOM(item, el);
684
- } // addItemUnder
685
-
686
- function removeBrowserSelection() {
687
- if (window.getSelection) { // all browsers, except IE before version 9
688
- return getSelection().removeAllRanges();
689
- }
690
- if (document.selection.createRange) { // Internet Explorer
691
- var range = document.selection.createRange();
692
- document.selection.empty();
693
- }
694
- } // removeBrowserSelection