@oat-sa/tao-core-ui 3.23.0 → 3.24.1

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 (31) hide show
  1. package/dist/figure/FigureStateActive.js +64 -32
  2. package/dist/image/ImgStateActive/initHelper.js +64 -32
  3. package/dist/image/ImgStateActive.js +64 -32
  4. package/dist/previewer.js +1 -1
  5. package/dist/resourcemgr/assetSearch.js +793 -0
  6. package/dist/resourcemgr/assetSearchContract.js +477 -0
  7. package/dist/resourcemgr/css/resourcemgr.css +1230 -97
  8. package/dist/resourcemgr/css/resourcemgr.css.map +1 -1
  9. package/dist/resourcemgr/fileBrowser.js +815 -83
  10. package/dist/resourcemgr/filePreview.js +82 -7
  11. package/dist/resourcemgr/fileSelector.js +377 -92
  12. package/dist/resourcemgr.js +314 -57
  13. package/dist/searchModal/advancedSearch.js +233 -31
  14. package/package.json +15 -6
  15. package/src/image/ImgStateActive/initHelper.js +69 -31
  16. package/src/previewer.js +1 -1
  17. package/src/resourcemgr/assetSearch.js +847 -0
  18. package/src/resourcemgr/assetSearchContract.js +489 -0
  19. package/src/resourcemgr/css/resourcemgr.css +1230 -97
  20. package/src/resourcemgr/css/resourcemgr.css.map +1 -1
  21. package/src/resourcemgr/fileBrowser.js +872 -92
  22. package/src/resourcemgr/filePreview.js +89 -8
  23. package/src/resourcemgr/fileSelector.js +356 -46
  24. package/src/resourcemgr/scss/resourcemgr.scss +1450 -89
  25. package/src/resourcemgr/tpl/advanced-search.tpl +10 -0
  26. package/src/resourcemgr/tpl/fileSelect.tpl +25 -28
  27. package/src/resourcemgr/tpl/folder.tpl +4 -1
  28. package/src/resourcemgr/tpl/layout.tpl +125 -54
  29. package/src/resourcemgr/tpl/rootFolder.tpl +3 -1
  30. package/src/resourcemgr.js +294 -47
  31. package/src/searchModal/advancedSearch.js +242 -31
@@ -1,11 +1,11 @@
1
- define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/handlebars/helpers'], function ($$1, _, request, paginationComponent, Handlebars, Helpers0) { 'use strict';
1
+ define(['jquery', 'lodash', 'ui/pagination', 'handlebars', 'lib/handlebars/helpers', 'core/logger', 'ui/resourcemgr/assetSearchContract'], function ($$1, _, paginationComponent, Handlebars, Helpers0, loggerFactory, assetSearchContract) { 'use strict';
2
2
 
3
3
  $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
4
4
  _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
5
- request = request && Object.prototype.hasOwnProperty.call(request, 'default') ? request['default'] : request;
6
5
  paginationComponent = paginationComponent && Object.prototype.hasOwnProperty.call(paginationComponent, 'default') ? paginationComponent['default'] : paginationComponent;
7
6
  Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
8
7
  Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
8
+ loggerFactory = loggerFactory && Object.prototype.hasOwnProperty.call(loggerFactory, 'default') ? loggerFactory['default'] : loggerFactory;
9
9
 
10
10
  if (!Helpers0.__initialized) {
11
11
  Helpers0(Handlebars);
@@ -14,12 +14,15 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
14
14
  var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
15
15
  this.compilerInfo = [4,'>= 1.0.0'];
16
16
  helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
17
- var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
17
+ var buffer = "", stack1, self=this, functionType="function", escapeExpression=this.escapeExpression;
18
18
 
19
19
  function program1(depth0,data) {
20
20
 
21
21
  var buffer = "", stack1, helper;
22
- buffer += "\n <li class=\"root\">\n <a\n class=\"root-folder active\"\n href=\"#\"\n data-path=\"";
22
+ buffer += "\n <li class=\"root\">\n <a\n class=\"root-folder";
23
+ stack1 = helpers.unless.call(depth0, (depth0 && depth0.showToggle), {hash:{},inverse:self.noop,fn:self.program(2, program2, data),data:data});
24
+ if(stack1 || stack1 === 0) { buffer += stack1; }
25
+ buffer += "\"\n href=\"#\"\n data-path=\"";
23
26
  if (helper = helpers.path) { stack1 = helper.call(depth0, {hash:{},data:data}); }
24
27
  else { helper = (depth0 && depth0.path); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
25
28
  buffer += escapeExpression(stack1)
@@ -31,13 +34,27 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
31
34
  if (helper = helpers.childrenLimit) { stack1 = helper.call(depth0, {hash:{},data:data}); }
32
35
  else { helper = (depth0 && depth0.childrenLimit); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
33
36
  buffer += escapeExpression(stack1)
34
- + "\"\n data-level=\"0\"\n style=\"--tree-level: 0;\">\n ";
37
+ + "\"\n data-level=\"0\"\n style=\"--tree-level: 0;\">\n <span class=\"tree-toggle";
38
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.showToggle), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
39
+ if(stack1 || stack1 === 0) { buffer += stack1; }
40
+ buffer += "\" aria-hidden=\"true\"></span>\n <span class=\"tree-folder icon-folder\" aria-hidden=\"true\"></span>\n ";
35
41
  if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
36
42
  else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
37
43
  buffer += escapeExpression(stack1)
38
44
  + "\n </a>\n <ul></ul>\n </li>\n";
39
45
  return buffer;
40
46
  }
47
+ function program2(depth0,data) {
48
+
49
+
50
+ return " is-leaf";
51
+ }
52
+
53
+ function program4(depth0,data) {
54
+
55
+
56
+ return " icon-right";
57
+ }
41
58
 
42
59
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.permissions)),stack1 == null || stack1 === false ? stack1 : stack1.read), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
43
60
  if(stack1 || stack1 === 0) { buffer += stack1; }
@@ -81,13 +98,30 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
81
98
  if (helper = helpers.level) { stack1 = helper.call(depth0, {hash:{},data:data}); }
82
99
  else { helper = (depth0 && depth0.level); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
83
100
  buffer += escapeExpression(stack1)
84
- + ";\"\n href=\"#\">\n ";
101
+ + ";\"\n href=\"#\"\n ";
102
+ stack1 = helpers.unless.call(depth0, (depth0 && depth0.showToggle), {hash:{},inverse:self.noop,fn:self.program(2, program2, data),data:data});
103
+ if(stack1 || stack1 === 0) { buffer += stack1; }
104
+ buffer += ">\n <span class=\"tree-toggle";
105
+ stack1 = helpers['if'].call(depth0, (depth0 && depth0.showToggle), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
106
+ if(stack1 || stack1 === 0) { buffer += stack1; }
107
+ buffer += "\" aria-hidden=\"true\"></span>\n <span class=\"tree-folder icon-folder\" aria-hidden=\"true\"></span>\n ";
85
108
  if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }
86
109
  else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
87
110
  buffer += escapeExpression(stack1)
88
111
  + "\n </a>\n </li>\n";
89
112
  return buffer;
90
113
  }
114
+ function program2(depth0,data) {
115
+
116
+
117
+ return "class=\"is-leaf\"";
118
+ }
119
+
120
+ function program4(depth0,data) {
121
+
122
+
123
+ return " icon-right";
124
+ }
91
125
 
92
126
  stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.permissions)),stack1 == null || stack1 === false ? stack1 : stack1.read), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
93
127
  if(stack1 || stack1 === 0) { buffer += stack1; }
@@ -164,48 +198,171 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
164
198
  *
165
199
  * You should have received a copy of the GNU General Public License
166
200
  * along with this program; if not, write to the Free Software
167
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
201
+ * Foundation, Inc., 31 Milk St # 960789 Boston, MA 02196 USA.
168
202
  *
169
- * Copyright (c) 2018-2020 (original work) Open Assessment Technologies SA;
203
+ * Copyright (c) 2018-2026 (original work) Open Assessment Technologies SA;
170
204
  */
171
- const ns = 'resourcemgr';
205
+ const NS = 'resourcemgr';
206
+ const LOGGER = loggerFactory(`ui/${NS}`);
207
+ const DEFAULT_AJAX_TIMEOUT_MS = 30000;
208
+ const TREE_LOADING_REF_KEY = `${NS}TreeLoadingRefCount`;
209
+
210
+ /**
211
+ * @param {Object} data
212
+ * @returns {Array}
213
+ */
214
+ function folderChildrenOnly(data) {
215
+ return _.filter(data && data.children, function (child) {
216
+ return child && child.path && !child.uri;
217
+ });
218
+ }
172
219
  function fileBrowser (options) {
220
+ if (!options.browseUrl && options.url) {
221
+ options.browseUrl = options.url;
222
+ }
173
223
  const root = options.root || 'local';
174
224
  const rootPath = options.path || '/';
225
+ const initialPath = options.initialPath || rootPath;
175
226
  const $container = options.$target;
176
227
  const $fileBrowser = $$1('.file-browser .file-browser-wrapper', $container);
177
228
  const $divContainer = $$1(`.${root}`, $fileBrowser);
178
229
  const $folderContainer = $$1('.folders', $divContainer);
230
+ const $paginationContainer = $$1('.pagination-bottom', $container);
179
231
  const fileTree = {};
180
232
  // for pagination
181
233
  let selectedClass = {
182
234
  path: rootPath,
183
- childrenLimit: 10,
235
+ childrenLimit: 11,
184
236
  total: 0,
185
237
  page: 1
186
238
  };
239
+ let searchMode = false;
240
+ let sort = Object.assign({}, assetSearchContract.DEFAULT_SORT);
241
+ const splitBrowse = options.splitBrowse !== false;
242
+ const ajaxTimeoutMs = Number.isFinite(Number(options.ajaxTimeoutMs)) && Number(options.ajaxTimeoutMs) > 0 ? Number(options.ajaxTimeoutMs) : DEFAULT_AJAX_TIMEOUT_MS;
243
+ const $treeLoadingOverlay = $$1('.file-browser-tree-loading', $fileBrowser);
244
+ const $treeLoadingStatus = $$1('.file-browser-tree-loading-status', $treeLoadingOverlay);
245
+
246
+ /**
247
+ * Ref-count in-flight browse requests and toggle the tree loading overlay.
248
+ * @param {Boolean} increment - true when a load starts, false when it finishes
249
+ */
250
+ function setTreeLoading(increment) {
251
+ let treeLoadingRefCount = Number($fileBrowser.data(TREE_LOADING_REF_KEY)) || 0;
252
+ treeLoadingRefCount += increment ? 1 : -1;
253
+ if (treeLoadingRefCount < 0) {
254
+ treeLoadingRefCount = 0;
255
+ }
256
+ $fileBrowser.data(TREE_LOADING_REF_KEY, treeLoadingRefCount);
257
+ const loading = treeLoadingRefCount > 0;
258
+ $fileBrowser.toggleClass('is-tree-loading', loading);
259
+ $fileBrowser.attr('aria-busy', loading ? 'true' : 'false');
260
+ $treeLoadingOverlay.toggleClass('hidden', !loading).attr('aria-hidden', loading ? 'false' : 'true');
261
+ $treeLoadingStatus.attr('aria-busy', loading ? 'true' : 'false');
262
+ }
263
+
264
+ /**
265
+ * Show or hide the asset table loading overlay (browse mode only).
266
+ * @param {Boolean} loading
267
+ */
268
+ function setFilesLoading(loading) {
269
+ if (searchMode) {
270
+ return;
271
+ }
272
+ $container.trigger(`filesloading.${NS}`, [!!loading]);
273
+ }
274
+ $container.on(`searchmode.${NS}`, function (e, enabled) {
275
+ searchMode = !!enabled;
276
+ if (searchMode) {
277
+ $container.trigger(`filesloading.${NS}`, [false]);
278
+ }
279
+ });
280
+ $container.on(`sortchange.${NS}`, function (e, nextSort) {
281
+ sort = Object.assign({}, assetSearchContract.DEFAULT_SORT, nextSort || {});
282
+ selectedClass.page = 1;
283
+ invalidateFolderFiles(selectedClass.path);
284
+ if (searchMode || !isActiveBrowser()) {
285
+ return;
286
+ }
287
+ reloadSortedFolder();
288
+ });
289
+ $container.on(`searchclear.${NS}`, function (e, path) {
290
+ const targetPath = path || selectedClass.path;
291
+ selectedClass.page = 1;
292
+ invalidateFolderFiles(targetPath);
293
+ if (!isActiveBrowser()) {
294
+ return;
295
+ }
296
+ const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath) || fileTree;
297
+ if (!searchMode) {
298
+ setFilesLoading(true);
299
+ }
300
+ getFolderContent(subTree, targetPath, function (content) {
301
+ if (content) {
302
+ selectFolder(content, targetPath);
303
+ } else {
304
+ setFilesLoading(false);
305
+ }
306
+ });
307
+ });
308
+
309
+ // Reopen with resolved parent (AC3 edit/change): leave search, open folder again.
310
+ $container.on(`applycontext.${NS}`, function (e, ctx) {
311
+ const path = ctx && ctx.path || rootPath;
312
+ const pathIsTaomedia = path && String(path).indexOf('taomedia://') === 0;
313
+ if (pathIsTaomedia && root === 'local') {
314
+ return;
315
+ }
316
+ if (!pathIsTaomedia && root !== 'local') {
317
+ return;
318
+ }
319
+ $container.data('activeFileBrowserRoot', root);
320
+ if (searchMode) {
321
+ $container.trigger(`requestexitsearch.${NS}`);
322
+ }
323
+ selectedClass.page = 1;
324
+ openInitialPath(path);
325
+ });
187
326
 
188
327
  //load the content of the ROOT
328
+ const showInitialListLoading = !searchMode && root === 'local';
329
+ if (showInitialListLoading) {
330
+ setFilesLoading(true);
331
+ }
189
332
  getFolderContent(fileTree, rootPath, function (content) {
333
+ if (!content) {
334
+ if (showInitialListLoading) {
335
+ setFilesLoading(false);
336
+ }
337
+ return;
338
+ }
190
339
  indexTree(content);
191
340
 
192
341
  //create the tree node for the ROOT folder by default once the initial content loaded
193
- $folderContainer.append(rootFolderTpl(content));
342
+ $folderContainer.append(rootFolderTpl(Object.assign({}, content, {
343
+ showToggle: hasNestedFolderChildren(content) !== false
344
+ })));
194
345
  const $rootNode = $$1('.root-folder', $folderContainer);
195
346
  //create an inner list and append found elements
196
347
  const $innerList = $$1('.root ul', $folderContainer);
197
- if (content.children) {
348
+ if (hasNestedFolderChildren(content)) {
198
349
  $rootNode.addClass('opened');
350
+ } else if (hasNestedFolderChildren(content) === false) {
351
+ setFolderToggleState($rootNode, false);
199
352
  }
200
353
  updateFolders(content, $innerList);
201
- if (content.permissions.read && !options.hasAlreadySelected) {
202
- $$1('.file-browser').find('li.active').removeClass('active');
203
- updateSelectedClass(content.path, content.total, content.childrenLimit);
204
- $container.trigger('folderselect.'.concat(ns), [content.label, getPage(content.children), content.path, content]);
205
- renderPagination();
354
+ if (content.permissions && content.permissions.read && !options.hasAlreadySelected) {
355
+ if (initialPath && initialPath !== rootPath) {
356
+ openInitialPath(initialPath);
357
+ } else {
358
+ selectFolder(content, content.path);
359
+ syncTreeActiveFolder(content.path || rootPath);
360
+ }
206
361
  if (root !== 'local') {
207
362
  options.hasAlreadySelected = true;
208
363
  }
364
+ } else if (showInitialListLoading) {
365
+ setFilesLoading(false);
209
366
  }
210
367
  });
211
368
 
@@ -213,9 +370,16 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
213
370
  $divContainer.off('click', '.folders a').on('click', '.folders a', function (e) {
214
371
  e.preventDefault();
215
372
  const $selected = $$1(this);
216
- const $folders = $$1('.folders li', $fileBrowser);
217
373
  const fullPath = $selected.data('path');
218
- const subTree = getByPath(fileTree, fullPath);
374
+ const subTree = getByExactPath(fileTree, fullPath);
375
+ const openingFolder = !searchMode && fullPath !== selectedClass.path;
376
+ if (openingFolder) {
377
+ selectedClass.page = 1;
378
+ }
379
+ if (!searchMode) {
380
+ setFilesLoading(true);
381
+ }
382
+ invalidateFolderFiles(fullPath);
219
383
 
220
384
  //get the folder content
221
385
  getFolderContent(subTree, fullPath, function (content) {
@@ -223,10 +387,12 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
223
387
  if (content) {
224
388
  //either create the inner list of the content is new or just show it
225
389
  let $innerList = $selected.siblings('ul');
226
- if (!$innerList.length && content.children && _.find(content.children, 'path') && !content.empty) {
390
+ const nested = hasNestedFolderChildren(content);
391
+ if (!$innerList.length && nested) {
227
392
  $innerList = $$1('<ul></ul>').insertAfter($selected);
228
393
  updateFolders(content, $innerList);
229
394
  $selected.addClass('opened');
395
+ setFolderToggleState($selected, true);
230
396
  } else if ($innerList.length) {
231
397
  if ($innerList.css('display') === 'none') {
232
398
  $innerList.show();
@@ -235,106 +401,460 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
235
401
  $innerList.hide();
236
402
  $selected.removeClass('opened');
237
403
  }
404
+ } else if (nested === false) {
405
+ // Leaf folder: keep alignment spacer, hide expand chevron.
406
+ setFolderToggleState($selected, false);
238
407
  }
239
-
240
- //toggle active element
241
- $folders.removeClass('active');
242
- $selected.parent('li').addClass('active');
408
+ syncTreeActiveFolder(fullPath, $selected);
243
409
 
244
410
  //internal event to set the file-selector content
245
- updateSelectedClass(fullPath, content.total, content.childrenLimit);
246
- $container.trigger(`folderselect.${ns}`, [content.label, getPage(content.children), content.path, content]);
247
- renderPagination();
411
+ selectFolder(content, fullPath);
412
+ } else if (!searchMode) {
413
+ setFilesLoading(false);
248
414
  }
249
415
  });
250
416
  });
251
- $container.on(`filenew.${ns}`, function (e, file, path) {
252
- const subTree = getByPath(fileTree, path);
253
- if (subTree) {
254
- if (!subTree.children) {
255
- subTree.children = [];
256
- }
257
- if (root !== 'local' || !_.find(subTree.children, {
258
- name: file.name
259
- })) {
260
- updatePermissions(file);
261
- const childrenFilesOnly = _.filter(subTree.children, function (child) {
262
- // Only file object has link property
263
- return Object.prototype.hasOwnProperty.call(child, 'link');
264
- });
265
- if (childrenFilesOnly.length === subTree.total) {
266
- // all children loaded new file can be pushed to the end of tree
267
- // if not all, new file will be loaded with next page
268
- subTree.children.push(file);
269
- }
270
- subTree.total = Number.isFinite(Number(subTree.total)) ? Number(subTree.total) + 1 : 1;
271
- if (selectedClass.path === path) {
272
- selectedClass.total = subTree.total;
273
- }
274
- $container.trigger(`folderselect.${ns}`, [subTree.label, getPage(subTree.children), path, subTree]);
275
- renderPagination();
276
- }
417
+ $container.on(`filenew.${NS}`, function (e, file, path) {
418
+ const targetPath = path || selectedClass.path;
419
+ if (!targetPath || searchMode) {
420
+ return;
277
421
  }
422
+ const activeRoot = $container.data('activeFileBrowserRoot');
423
+ if (activeRoot && activeRoot !== root) {
424
+ return;
425
+ }
426
+ const subTree = getByExactPath(fileTree, targetPath) || getByPath(fileTree, targetPath);
427
+ if (subTree && root === 'local' && file && file.name && _.find(subTree.children, {
428
+ name: file.name
429
+ })) {
430
+ return;
431
+ }
432
+ if (targetPath !== selectedClass.path) {
433
+ selectedClass.path = targetPath;
434
+ }
435
+ selectedClass.page = 1;
436
+ invalidateFolderFiles(targetPath);
437
+ const pendingUpload = file && !file.error && (file.uri || file.name) ? file : null;
438
+ reloadSortedFolder(pendingUpload ? [pendingUpload] : []);
278
439
  });
279
- $container.on(`filedelete.${ns}`, function (e, path) {
280
- if (removeFromPath(fileTree, path)) {
281
- selectedClass.total--;
282
- loadPage();
440
+ $container.on(`filedelete.${NS}`, function (e, path) {
441
+ if (searchMode) {
442
+ return;
443
+ }
444
+ const activeRoot = $container.data('activeFileBrowserRoot');
445
+ if (activeRoot && activeRoot !== root) {
446
+ return;
447
+ }
448
+ if (path) {
449
+ removeFromPath(fileTree, path);
283
450
  }
451
+ invalidateFolderFiles(selectedClass.path);
452
+ reloadSortedFolder();
284
453
  });
454
+
455
+ /**
456
+ * Open and select an initial folder path after the root tree is available.
457
+ * @param {String} path
458
+ */
459
+ function findFolderLink(path) {
460
+ return $folderContainer.find('a').filter(function () {
461
+ return $$1(this).data('path') === path;
462
+ });
463
+ }
464
+
465
+ /**
466
+ * Render directory children under an expanded folder anchor (replace / reopen).
467
+ * @param {String} parentPath
468
+ * @param {Object} parentContent
469
+ */
470
+ function mountFolderBranch(parentPath, parentContent) {
471
+ const $parentLink = findFolderLink(parentPath);
472
+ if (!$parentLink.length || !parentContent) {
473
+ return;
474
+ }
475
+ let $innerList = $parentLink.siblings('ul');
476
+ if (!$innerList.length) {
477
+ $innerList = $$1('<ul></ul>').insertAfter($parentLink);
478
+ }
479
+ updateFolders(parentContent, $innerList);
480
+ $parentLink.addClass('opened');
481
+ if ($innerList.css('display') === 'none') {
482
+ $innerList.show();
483
+ }
484
+ setFolderToggleState($parentLink, hasNestedFolderChildren(parentContent) !== false);
485
+ }
486
+ function markFolderActive(path) {
487
+ const $targetLink = findFolderLink(path);
488
+ if ($targetLink.length) {
489
+ $targetLink.parents('li').each(function () {
490
+ const $li = $$1(this);
491
+ const $anchor = $li.children('a');
492
+ const $list = $li.children('ul');
493
+ $anchor.addClass('opened');
494
+ if ($list.length) {
495
+ $list.show();
496
+ }
497
+ });
498
+ syncTreeActiveFolder(path, $targetLink);
499
+ } else if (path === rootPath) {
500
+ syncTreeActiveFolder(rootPath);
501
+ }
502
+ }
503
+ function openInitialPath(path) {
504
+ getFolderContent(fileTree, path, function (content) {
505
+ indexTree(fileTree);
506
+ if (!content) {
507
+ const rootContent = getByPath(fileTree, rootPath) || fileTree;
508
+ const fallbackPath = rootContent.path || rootPath;
509
+ selectFolder(rootContent, fallbackPath);
510
+ syncTreeActiveFolder(fallbackPath);
511
+ return;
512
+ }
513
+ function finishOpen() {
514
+ markFolderActive(path);
515
+ selectFolder(content, path);
516
+ }
517
+ if (findFolderLink(path).length) {
518
+ finishOpen();
519
+ return;
520
+ }
521
+ const parentFolderPath = content.parentFolderPath;
522
+ if (parentFolderPath && parentFolderPath !== path) {
523
+ getFolderContent(fileTree, parentFolderPath, function (parentContent) {
524
+ mountFolderBranch(parentFolderPath, parentContent);
525
+ finishOpen();
526
+ });
527
+ return;
528
+ }
529
+ finishOpen();
530
+ });
531
+ }
532
+
533
+ /**
534
+ * Mark the folder row that matches path as active (open folder icon for leaves).
535
+ * @param {String} path
536
+ * @param {jQuery} [$link]
537
+ */
538
+ function syncTreeActiveFolder(path, $link) {
539
+ $$1('.folders li', $fileBrowser).removeClass('active');
540
+ const $target = $link && $link.length ? $link : $folderContainer.find('a').filter(function () {
541
+ return $$1(this).data('path') === path;
542
+ });
543
+ if ($target.length) {
544
+ $target.parent('li').addClass('active');
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Whether this media source currently owns the file table.
550
+ * @returns {Boolean}
551
+ */
552
+ function isActiveBrowser() {
553
+ return $container.data('activeFileBrowserRoot') === root;
554
+ }
555
+
556
+ /**
557
+ * Select a folder and publish its page of files to the selector.
558
+ * @param {Object} content
559
+ * @param {String} path
560
+ */
561
+ function selectFolder(content, path) {
562
+ if (!content) {
563
+ return;
564
+ }
565
+ $container.data('activeFileBrowserRoot', root);
566
+ if (searchMode) {
567
+ $container.trigger(`folderpath.${NS}`, [path, content.label]);
568
+ $container.trigger(`folderselect.${NS}`, [content.label, getFilesForDisplay(content), path, content]);
569
+ return;
570
+ }
571
+ updateSelectedClass(path, content.total, content.childrenLimit);
572
+ $container.trigger(`folderpath.${NS}`, [path, content.label]);
573
+ $container.trigger(`folderselect.${NS}`, [content.label, getFilesForDisplay(content), path, content]);
574
+ renderPagination();
575
+ if (!searchMode) {
576
+ setFilesLoading(false);
577
+ }
578
+ }
579
+
285
580
  /**
286
581
  * Get files for page
287
582
  * @param {Array} children
288
583
  * @returns {Array} files for this page
289
584
  */
290
585
  function getPage(children) {
291
- const files = _.filter(children, function (item) {
586
+ const files = assetSearchContract.sortAssetItems(_.filter(children, function (item) {
292
587
  return !!item.uri;
293
- });
588
+ }), sort);
294
589
  if (selectedClass.childrenLimit) {
295
590
  return files.slice((selectedClass.page - 1) * selectedClass.childrenLimit, selectedClass.page * selectedClass.childrenLimit);
296
591
  }
297
592
  return files;
298
593
  }
594
+
595
+ /**
596
+ * Files for the table (split list payload or legacy children slice).
597
+ * @param {Object} content
598
+ * @returns {Array}
599
+ */
600
+ function getFilesForDisplay(content) {
601
+ if (!content) {
602
+ return [];
603
+ }
604
+ if (splitBrowse) {
605
+ return content.listItems || [];
606
+ }
607
+ return getPage(content.children || []);
608
+ }
609
+ function listCacheKey() {
610
+ return `${sort.field}:${sort.direction}:${selectedClass.page}`;
611
+ }
612
+
613
+ /**
614
+ * Snapshot list request identity (folder + page/sort) when a browse starts.
615
+ * @returns {{path: String, cacheKey: String}}
616
+ */
617
+ function captureListRequestContext() {
618
+ return {
619
+ path: selectedClass.path,
620
+ cacheKey: listCacheKey()
621
+ };
622
+ }
623
+
624
+ /**
625
+ * Whether navigation changed before an in-flight list response should apply.
626
+ * @param {{path: String, cacheKey: String}} captured
627
+ * @returns {Boolean}
628
+ */
629
+ function isListRequestStale(captured) {
630
+ return !captured || captured.path !== selectedClass.path || captured.cacheKey !== listCacheKey();
631
+ }
632
+
633
+ /**
634
+ * @param {Object} response
635
+ * @returns {Object}
636
+ */
637
+ function unwrapBrowseResponse(response) {
638
+ if (response && response.success === false) {
639
+ return Promise.reject(response);
640
+ }
641
+ let data = response && response.data ? response.data : response;
642
+ data = updatePermissions(data);
643
+ if (data.children && data.children.length > 0) {
644
+ data.children = data.children.map(function (child) {
645
+ return updatePermissions(child);
646
+ });
647
+ }
648
+ if (data.items && data.items.length > 0) {
649
+ data.items = data.items.map(function (item) {
650
+ return updatePermissions(item);
651
+ });
652
+ }
653
+ return data;
654
+ }
655
+
656
+ /**
657
+ * @param {String} path
658
+ * @param {Object} extraData
659
+ * @returns {Promise}
660
+ */
661
+ function browseRequest(path, extraData) {
662
+ const parameters = {};
663
+ parameters[options.pathParam || 'path'] = path;
664
+ return Promise.resolve($$1.ajax({
665
+ url: options.browseUrl,
666
+ method: 'GET',
667
+ dataType: 'json',
668
+ timeout: ajaxTimeoutMs,
669
+ data: _.merge(parameters, options.params, extraData)
670
+ })).then(unwrapBrowseResponse);
671
+ }
672
+
673
+ /**
674
+ * @param {Object} tree
675
+ * @param {String} path
676
+ * @param {Object} data
677
+ */
678
+ function applyTreePayload(tree, path, data) {
679
+ const folderData = Object.assign({}, data, {
680
+ children: folderChildrenOnly(data)
681
+ });
682
+ if (!tree.path) {
683
+ _.merge(tree, folderData);
684
+ } else if (folderData.children) {
685
+ setToPath(tree, path, folderData);
686
+ if (!_.find(folderData.children, 'path')) {
687
+ markFolderEmptyAtPath(tree, path);
688
+ }
689
+ } else {
690
+ setToPath(tree, path, folderData);
691
+ markFolderEmptyAtPath(tree, path);
692
+ }
693
+ }
694
+
695
+ /**
696
+ * @param {Object} tree
697
+ * @param {String} path
698
+ * @param {Object} listData
699
+ * @param {String} cacheKey
700
+ */
701
+ function applyListPayload(tree, path, listData, cacheKey) {
702
+ const node = getByExactPath(tree, path) || getByPath(tree, path);
703
+ if (!node || !listData) {
704
+ return;
705
+ }
706
+ node.listItems = listData.items || [];
707
+ node.total = listData.total;
708
+ node.childrenLimit = listData.pageSize || listData.childrenLimit || selectedClass.childrenLimit;
709
+ node.listPage = listData.page;
710
+ node.totalIsApproximate = listData.totalIsApproximate;
711
+ node.listCacheKey = cacheKey;
712
+ }
713
+
714
+ /**
715
+ * Ensure upload responses appear in the table before Elasticsearch catches up.
716
+ * @param {String} path
717
+ * @param {Array<Object>} uploads
718
+ */
719
+ function mergePendingUploads(path, uploads) {
720
+ if (!uploads || !uploads.length) {
721
+ return;
722
+ }
723
+ const node = getByExactPath(fileTree, path) || getByPath(fileTree, path);
724
+ if (!node) {
725
+ return;
726
+ }
727
+ if (!Array.isArray(node.listItems)) {
728
+ node.listItems = [];
729
+ }
730
+ let added = 0;
731
+ uploads.forEach(function (uploaded) {
732
+ if (!uploaded || uploaded.error) {
733
+ return;
734
+ }
735
+ const normalized = updatePermissions(uploaded);
736
+ if (!normalized.uri && !normalized.name) {
737
+ return;
738
+ }
739
+ const exists = _.some(node.listItems, function (item) {
740
+ return normalized.uri && item.uri === normalized.uri || normalized.name && item.name === normalized.name;
741
+ });
742
+ if (exists) {
743
+ return;
744
+ }
745
+ node.listItems.push(normalized);
746
+ added++;
747
+ });
748
+ if (!added) {
749
+ return;
750
+ }
751
+ if (splitBrowse) {
752
+ node.listItems = assetSearchContract.sortAssetItems(node.listItems, sort);
753
+ }
754
+ const total = Number(node.total);
755
+ if (Number.isFinite(total) && total >= 0) {
756
+ node.total = total + added;
757
+ }
758
+ }
759
+
299
760
  /**
300
761
  * Get the content of a folder, either in the model or load it
301
762
  * @param {Object} tree - the tree model
302
763
  * @param {String} path - the folder path (relative to the root)
303
764
  * @param {Function} cb - called back with the content in 1st parameter
304
765
  */
766
+ function markFolderEmptyAtPath(tree, path) {
767
+ const node = getByExactPath(tree, path);
768
+ if (node) {
769
+ node.empty = true;
770
+ }
771
+ }
305
772
  function getFolderContent(tree, path, cb) {
773
+ if (splitBrowse) {
774
+ const existing = getByExactPath(tree, path) || getByPath(tree, path);
775
+ const needTree = !existing || !existing.children && !existing.empty;
776
+ const cacheKey = listCacheKey();
777
+ const needList = !existing || existing.listCacheKey !== cacheKey;
778
+ const treePromise = needTree ? (setTreeLoading(true), browseRequest(path, {
779
+ part: 'tree',
780
+ depth: 1,
781
+ sortBy: sort.field,
782
+ sortDir: sort.direction
783
+ }).then(function (data) {
784
+ setTreeLoading(false);
785
+ return data;
786
+ }, function (error) {
787
+ setTreeLoading(false);
788
+ return Promise.reject(error);
789
+ })) : Promise.resolve(null);
790
+ const listPromise = needList ? browseRequest(path, {
791
+ part: 'list',
792
+ page: selectedClass.page,
793
+ pageSize: selectedClass.childrenLimit || 11,
794
+ sortBy: sort.field,
795
+ sortDir: sort.direction
796
+ }) : Promise.resolve(null);
797
+ Promise.all([treePromise, listPromise]).then(function (_ref) {
798
+ let [treeData, listData] = _ref;
799
+ if (treeData) {
800
+ applyTreePayload(tree, path, treeData);
801
+ }
802
+ if (listData) {
803
+ applyListPayload(tree, path, listData, cacheKey);
804
+ }
805
+ cb(getByExactPath(tree, path) || getByPath(tree, path));
806
+ }).catch(function () {
807
+ cb(null);
808
+ });
809
+ return;
810
+ }
306
811
  let content = getByPath(tree, path);
307
812
  if (!content || !content.children && !content.empty) {
308
813
  loadContent(path).then(function (data) {
309
814
  if (!tree.path) {
310
815
  tree = _.merge(tree, data);
311
816
  } else if (data.children) {
817
+ setToPath(tree, path, data);
312
818
  if (!_.find(data.children, 'path')) {
313
819
  // no subfolders inside folder
314
- tree.empty = true;
820
+ markFolderEmptyAtPath(tree, path);
315
821
  }
316
- setToPath(tree, path, data);
317
822
  } else {
318
- tree.empty = true;
823
+ setToPath(tree, path, data);
824
+ markFolderEmptyAtPath(tree, path);
319
825
  }
320
826
  cb(data);
827
+ }).catch(function () {
828
+ cb(null);
321
829
  });
322
830
  } else if (content.children) {
323
831
  const files = _.filter(content.children, function (item) {
324
832
  return !!item.uri;
325
833
  });
326
- // if files less then total and need toload this page
327
- if (files.length < selectedClass.total && files.length < selectedClass.page * selectedClass.childrenLimit) {
834
+ // Use folder total (not selectedClass): openInitialPath runs before selectFolder.
835
+ const expectedTotal = Number(content.total);
836
+ const pageSize = Number(content.childrenLimit) || selectedClass.childrenLimit || 11;
837
+ const page = selectedClass.page || 1;
838
+ // Missing/NaN total → unknown size; refetch so invalidated cache is not treated as complete.
839
+ if (!Number.isFinite(expectedTotal) || files.length < expectedTotal && files.length < page * pageSize) {
328
840
  loadContent(path).then(function (data) {
329
841
  const loadedFiles = _.filter(data.children, function (item) {
330
842
  return !!item.uri;
331
843
  });
844
+ const node = getByExactPath(tree, path);
845
+ if (node && !Number.isFinite(expectedTotal)) {
846
+ node.children = _.filter(node.children, function (item) {
847
+ return !item.uri;
848
+ });
849
+ }
332
850
  setToPath(tree, path, {
333
851
  children: loadedFiles,
334
852
  total: data.total,
335
853
  childrenLimit: data.childrenLimit
336
854
  });
337
- content = getByPath(tree, path);
855
+ content = getByExactPath(tree, path) || getByPath(tree, path);
856
+ cb(content);
857
+ }).catch(function () {
338
858
  cb(content);
339
859
  });
340
860
  } else {
@@ -384,6 +904,29 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
384
904
  return match;
385
905
  }
386
906
 
907
+ /**
908
+ * Get a subtree by exact path equality.
909
+ * @param {Object} tree
910
+ * @param {String} path
911
+ * @returns {Object|undefined}
912
+ */
913
+ function getByExactPath(tree, path) {
914
+ let match;
915
+ if (tree) {
916
+ if (tree.path === path) {
917
+ match = tree;
918
+ } else if (tree.children) {
919
+ _.forEach(tree.children, function (child) {
920
+ match = getByExactPath(child, path);
921
+ if (match) {
922
+ return false;
923
+ }
924
+ });
925
+ }
926
+ }
927
+ return match;
928
+ }
929
+
387
930
  /**
388
931
  * Merge data into at into the subtree
389
932
  * @param {Object} tree - the tree model
@@ -427,8 +970,11 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
427
970
  return child.path === path || child.name && tree.path + child.name === path || child.uri === path;
428
971
  });
429
972
  done = removed.length > 0;
430
- tree.total--;
431
- if (!done) {
973
+ if (done) {
974
+ if (Number.isFinite(Number(tree.total))) {
975
+ tree.total = Math.max(0, Number(tree.total) - removed.length);
976
+ }
977
+ } else {
432
978
  _.forEach(tree.children, function (child) {
433
979
  done = removeFromPath(child, path);
434
980
  if (done) {
@@ -440,31 +986,136 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
440
986
  return done;
441
987
  }
442
988
 
989
+ /**
990
+ * Drop cached file rows for a folder so the next load hits the service
991
+ * with the current sort. Nested folder nodes are kept.
992
+ * @param {String} path
993
+ */
994
+ function invalidateFolderFiles(path) {
995
+ const content = getByExactPath(fileTree, path);
996
+ if (!content) {
997
+ return;
998
+ }
999
+ if (splitBrowse) {
1000
+ delete content.listItems;
1001
+ delete content.listCacheKey;
1002
+ delete content.listPage;
1003
+ return;
1004
+ }
1005
+ if (Array.isArray(content.children)) {
1006
+ content.children = content.children.filter(function (child) {
1007
+ return child.path && !child.uri;
1008
+ });
1009
+ }
1010
+ }
1011
+
1012
+ /**
1013
+ * Replace a folder node with a freshly loaded payload (files + folders).
1014
+ * @param {String} path
1015
+ * @param {Object} data
1016
+ */
1017
+ function replaceFolderContent(path, data, cacheKey) {
1018
+ const content = getByExactPath(fileTree, path);
1019
+ if (!content || !data) {
1020
+ return;
1021
+ }
1022
+ if (splitBrowse && Object.prototype.hasOwnProperty.call(data, 'items')) {
1023
+ applyListPayload(fileTree, path, data, cacheKey || listCacheKey());
1024
+ return;
1025
+ }
1026
+ if (Object.prototype.hasOwnProperty.call(data, 'children')) {
1027
+ content.children = data.children;
1028
+ } else {
1029
+ content.children = [];
1030
+ content.empty = true;
1031
+ content.total = 0;
1032
+ }
1033
+ if (Object.prototype.hasOwnProperty.call(data, 'total')) {
1034
+ content.total = data.total;
1035
+ }
1036
+ if (Object.prototype.hasOwnProperty.call(data, 'childrenLimit')) {
1037
+ content.childrenLimit = data.childrenLimit;
1038
+ }
1039
+ if (data.label) {
1040
+ content.label = data.label;
1041
+ }
1042
+ if (data.path) {
1043
+ content.path = data.path;
1044
+ }
1045
+ }
1046
+
443
1047
  /**
444
1048
  * Get the content of a folder
445
1049
  * @param {String} path - the folder path
446
- * @returns {jQuery.Deferred} the defferred object to run done/complete/fail
1050
+ * @returns {Promise} resolves with folder content
447
1051
  */
448
1052
  function loadContent(path) {
449
1053
  const parameters = {};
450
- parameters[options.pathParam] = path;
451
- return request({
1054
+ parameters[options.pathParam || 'path'] = path;
1055
+ setTreeLoading(true);
1056
+ return Promise.resolve($$1.ajax({
452
1057
  url: options.browseUrl,
453
1058
  method: 'GET',
454
1059
  dataType: 'json',
1060
+ timeout: ajaxTimeoutMs,
455
1061
  data: _.merge(parameters, options.params, {
456
- childrenOffset: (selectedClass.page - 1) * selectedClass.childrenLimit
457
- }),
458
- noToken: true
459
- }).then(response => response.data).then(response => {
460
- response = updatePermissions(response);
461
- if (response.children && response.children.length > 0) {
462
- response.children.map(responseChildren => updatePermissions(responseChildren));
463
- }
464
- return response;
1062
+ // depth=2 so each rendered child folder includes its own dir children,
1063
+ // allowing leaf folders to hide the expand chevron without an extra click.
1064
+ depth: 2,
1065
+ childrenOffset: (selectedClass.page - 1) * selectedClass.childrenLimit,
1066
+ sortBy: sort.field,
1067
+ sortDir: sort.direction
1068
+ })
1069
+ })).then(function (response) {
1070
+ if (response && response.success === false) {
1071
+ return Promise.reject(response);
1072
+ }
1073
+ let data = response && response.data ? response.data : response;
1074
+ data = updatePermissions(data);
1075
+ if (data.children && data.children.length > 0) {
1076
+ data.children.map(responseChildren => updatePermissions(responseChildren));
1077
+ }
1078
+ return data;
1079
+ }).then(function (data) {
1080
+ setTreeLoading(false);
1081
+ return data;
1082
+ }, function (error) {
1083
+ setTreeLoading(false);
1084
+ return Promise.reject(error);
465
1085
  });
466
1086
  }
467
1087
 
1088
+ /**
1089
+ * Whether a folder node has nested directory children.
1090
+ * @param {Object} node
1091
+ * @returns {boolean|null} true/false when known; null when children were not loaded yet
1092
+ */
1093
+ function hasNestedFolderChildren(node) {
1094
+ if (!node || node.empty === true) {
1095
+ return false;
1096
+ }
1097
+ if (!Array.isArray(node.children)) {
1098
+ return null;
1099
+ }
1100
+ return Boolean(_.find(node.children, 'path'));
1101
+ }
1102
+
1103
+ /**
1104
+ * Show or hide the expand chevron on a folder anchor (keep spacer width).
1105
+ * @param {jQuery} $anchor
1106
+ * @param {boolean} hasNested
1107
+ */
1108
+ function setFolderToggleState($anchor, hasNested) {
1109
+ const $toggle = $anchor.children('.tree-toggle');
1110
+ if (hasNested) {
1111
+ $toggle.addClass('icon-right').removeClass('is-leaf');
1112
+ $anchor.removeClass('is-leaf');
1113
+ } else {
1114
+ $toggle.removeClass('icon-right').addClass('is-leaf');
1115
+ $anchor.removeClass('opened').addClass('is-leaf');
1116
+ }
1117
+ }
1118
+
468
1119
  /**
469
1120
  * Update the HTML Tree
470
1121
  * @param {Object} data - the tree data
@@ -476,7 +1127,10 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
476
1127
  if (typeof data.relPath === 'undefined') {
477
1128
  data.relPath = data.path;
478
1129
  }
1130
+ // Unknown nested state (lazy depth) keeps the chevron until the folder is opened.
1131
+ data.showToggle = hasNestedFolderChildren(data) !== false;
479
1132
  $parent.append(folderTpl(data));
1133
+ return;
480
1134
  }
481
1135
  if (data && data.children && _.isArray(data.children) && !data.empty) {
482
1136
  _.forEach(data.children, function (child) {
@@ -497,7 +1151,7 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
497
1151
  selectedClass = {
498
1152
  path,
499
1153
  total: Number.isFinite(normalizedTotal) && normalizedTotal >= 0 ? normalizedTotal : 0,
500
- childrenLimit: Number.isFinite(normalizedChildrenLimit) && normalizedChildrenLimit > 0 ? normalizedChildrenLimit : selectedClass.childrenLimit || 10,
1154
+ childrenLimit: Number.isFinite(normalizedChildrenLimit) && normalizedChildrenLimit > 0 ? normalizedChildrenLimit : selectedClass.childrenLimit || 11,
501
1155
  page: 1
502
1156
  };
503
1157
  }
@@ -505,7 +1159,11 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
505
1159
  * Render pagination
506
1160
  */
507
1161
  function renderPagination() {
508
- const $paginationContainer = $$1('.pagination-bottom', $container);
1162
+ if (searchMode) {
1163
+ // Drop browse pagination controls/handlers when search owns the table.
1164
+ $paginationContainer.empty();
1165
+ return;
1166
+ }
509
1167
  const total = Number(selectedClass.total);
510
1168
  const childrenLimit = Number(selectedClass.childrenLimit);
511
1169
  $paginationContainer.empty();
@@ -527,18 +1185,92 @@ define(['jquery', 'lodash', 'core/request', 'ui/pagination', 'handlebars', 'lib/
527
1185
  }).render($paginationContainer);
528
1186
  }
529
1187
  }
1188
+ /**
1189
+ * Re-fetch the current folder with the active sort and publish files.
1190
+ */
1191
+ function reloadSortedFolder(pendingUploads) {
1192
+ const listRequest = captureListRequestContext();
1193
+ const path = listRequest.path;
1194
+ if (!searchMode && isActiveBrowser()) {
1195
+ setFilesLoading(true);
1196
+ }
1197
+ const loadPromise = splitBrowse ? browseRequest(path, {
1198
+ part: 'list',
1199
+ page: selectedClass.page,
1200
+ pageSize: selectedClass.childrenLimit || 11,
1201
+ sortBy: sort.field,
1202
+ sortDir: sort.direction
1203
+ }) : loadContent(path);
1204
+ loadPromise.then(function (data) {
1205
+ if (!data) {
1206
+ setFilesLoading(false);
1207
+ return;
1208
+ }
1209
+ if (isListRequestStale(listRequest)) {
1210
+ setFilesLoading(false);
1211
+ return;
1212
+ }
1213
+ replaceFolderContent(path, data, listRequest.cacheKey);
1214
+ mergePendingUploads(path, pendingUploads);
1215
+ const content = getByExactPath(fileTree, path) || data;
1216
+ selectFolder(content, content.path || path);
1217
+ }).catch(function (error) {
1218
+ LOGGER.error(error);
1219
+ setFilesLoading(false);
1220
+ });
1221
+ }
1222
+
530
1223
  /**
531
1224
  * Load page
532
1225
  */
533
1226
  function loadPage() {
534
- const subTree = getByPath(fileTree, selectedClass.path);
1227
+ const listRequest = captureListRequestContext();
1228
+ if (!searchMode && isActiveBrowser()) {
1229
+ setFilesLoading(true);
1230
+ }
1231
+ if (splitBrowse) {
1232
+ const path = listRequest.path;
1233
+ browseRequest(path, {
1234
+ part: 'list',
1235
+ page: selectedClass.page,
1236
+ pageSize: selectedClass.childrenLimit || 11,
1237
+ sortBy: sort.field,
1238
+ sortDir: sort.direction
1239
+ }).then(function (data) {
1240
+ if (isListRequestStale(listRequest)) {
1241
+ setFilesLoading(false);
1242
+ return;
1243
+ }
1244
+ applyListPayload(fileTree, path, data, listRequest.cacheKey);
1245
+ const content = getByExactPath(fileTree, path);
1246
+ if (content) {
1247
+ $container.trigger(`folderselect.${NS}`, [content.label, getFilesForDisplay(content), content.path, content]);
1248
+ renderPagination();
1249
+ }
1250
+ setFilesLoading(false);
1251
+ }).catch(function (error) {
1252
+ LOGGER.error(error);
1253
+ setFilesLoading(false);
1254
+ });
1255
+ return;
1256
+ }
1257
+ const subTree = getByPath(fileTree, selectedClass.path) || fileTree;
535
1258
 
536
1259
  //get the folder content
537
1260
  getFolderContent(subTree, selectedClass.path, function (content) {
1261
+ if (isListRequestStale(listRequest)) {
1262
+ if (!searchMode) {
1263
+ setFilesLoading(false);
1264
+ }
1265
+ return;
1266
+ }
538
1267
  indexTree(fileTree);
539
1268
  if (content) {
540
1269
  //internal event to set the file-selector content
541
- $container.trigger(`folderselect.${ns}`, [content.label, getPage(content.children), content.path]);
1270
+ $container.trigger(`folderselect.${NS}`, [content.label, getFilesForDisplay(content), content.path, content]);
1271
+ }
1272
+ if (!searchMode) {
1273
+ setFilesLoading(false);
542
1274
  }
543
1275
  });
544
1276
  }