@jupyterlab/filebrowser 4.0.0-alpha.9 → 4.0.0-beta.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.
package/lib/listing.js CHANGED
@@ -6,7 +6,7 @@ import { isValidFileName, renameFile } from '@jupyterlab/docmanager';
6
6
  import { DocumentRegistry } from '@jupyterlab/docregistry';
7
7
  import { nullTranslator } from '@jupyterlab/translation';
8
8
  import { caretDownIcon, caretUpIcon, classes, LabIcon } from '@jupyterlab/ui-components';
9
- import { ArrayExt, ArrayIterator, each, filter, find, StringExt, toArray } from '@lumino/algorithm';
9
+ import { ArrayExt, filter, StringExt } from '@lumino/algorithm';
10
10
  import { MimeData, PromiseDelegate } from '@lumino/coreutils';
11
11
  import { ElementExt } from '@lumino/domutils';
12
12
  import { Drag } from '@lumino/dragdrop';
@@ -54,6 +54,10 @@ const ITEM_ICON_CLASS = 'jp-DirListing-itemIcon';
54
54
  * The class name added to the listing item modified cell.
55
55
  */
56
56
  const ITEM_MODIFIED_CLASS = 'jp-DirListing-itemModified';
57
+ /**
58
+ * The class name added to the listing item file size cell.
59
+ */
60
+ const ITEM_FILE_SIZE_CLASS = 'jp-DirListing-itemFileSize';
57
61
  /**
58
62
  * The class name added to the label element that wraps each item's checkbox and
59
63
  * the header's check-all checkbox.
@@ -71,6 +75,10 @@ const NAME_ID_CLASS = 'jp-id-name';
71
75
  * The class name added to the modified column header cell.
72
76
  */
73
77
  const MODIFIED_ID_CLASS = 'jp-id-modified';
78
+ /**
79
+ * The class name added to the file size column header cell.
80
+ */
81
+ const FILE_SIZE_ID_CLASS = 'jp-id-filesize';
74
82
  /**
75
83
  * The class name added to the narrow column header cell.
76
84
  */
@@ -79,6 +87,10 @@ const NARROW_ID_CLASS = 'jp-id-narrow';
79
87
  * The class name added to the modified column header cell and modified item cell when hidden.
80
88
  */
81
89
  const MODIFIED_COLUMN_HIDDEN = 'jp-LastModified-hidden';
90
+ /**
91
+ * The class name added to the size column header cell and size item cell when hidden.
92
+ */
93
+ const FILE_SIZE_COLUMN_HIDDEN = 'jp-FileSize-hidden';
82
94
  /**
83
95
  * The mime type for a contents drag object.
84
96
  */
@@ -180,6 +192,8 @@ export class DirListing extends Widget {
180
192
  this._manager = this._model.manager;
181
193
  this._renderer = options.renderer || DirListing.defaultRenderer;
182
194
  const headerNode = DOMUtils.findElement(this.node, HEADER_CLASS);
195
+ // hide the file size column by default
196
+ this._hiddenColumns.add('file_size');
183
197
  this._renderer.populateHeaderNode(headerNode, this.translator, this._hiddenColumns);
184
198
  this._manager.activateRequested.connect(this._onActivateRequested, this);
185
199
  }
@@ -253,7 +267,7 @@ export class DirListing extends Widget {
253
267
  * @returns A new iterator over the listing's sorted items.
254
268
  */
255
269
  sortedItems() {
256
- return new ArrayIterator(this._sortedItems);
270
+ return this._sortedItems[Symbol.iterator]();
257
271
  }
258
272
  /**
259
273
  * Sort the items using a sort condition.
@@ -297,9 +311,10 @@ export class DirListing extends Widget {
297
311
  }
298
312
  const basePath = this._model.path;
299
313
  const promises = [];
300
- each(this._clipboard, path => {
314
+ for (const path of this._clipboard) {
301
315
  if (this._isCut) {
302
- const parts = path.split('/');
316
+ const localPath = this._manager.services.contents.localPath(path);
317
+ const parts = localPath.split('/');
303
318
  const name = parts[parts.length - 1];
304
319
  const newPath = PathExt.join(basePath, name);
305
320
  promises.push(this._model.manager.rename(path, newPath));
@@ -307,11 +322,11 @@ export class DirListing extends Widget {
307
322
  else {
308
323
  promises.push(this._model.manager.copy(path, basePath));
309
324
  }
310
- });
325
+ }
311
326
  // Remove any cut modifiers.
312
- each(this._items, item => {
327
+ for (const item of this._items) {
313
328
  item.classList.remove(CUT_CLASS);
314
- });
329
+ }
315
330
  this._clipboard.length = 0;
316
331
  this._isCut = false;
317
332
  this.removeClass(CLIPBOARD_CLASS);
@@ -359,11 +374,11 @@ export class DirListing extends Widget {
359
374
  duplicate() {
360
375
  const basePath = this._model.path;
361
376
  const promises = [];
362
- each(this.selectedItems(), item => {
377
+ for (const item of this.selectedItems()) {
363
378
  if (item.type !== 'directory') {
364
379
  promises.push(this._model.manager.copy(item.path, basePath));
365
380
  }
366
- });
381
+ }
367
382
  return Promise.all(promises)
368
383
  .then(() => {
369
384
  return undefined;
@@ -376,7 +391,7 @@ export class DirListing extends Widget {
376
391
  * Download the currently selected item(s).
377
392
  */
378
393
  async download() {
379
- await Promise.all(toArray(this.selectedItems())
394
+ await Promise.all(Array.from(this.selectedItems())
380
395
  .filter(item => item.type !== 'directory')
381
396
  .map(item => this._model.download(item.path)));
382
397
  }
@@ -389,7 +404,7 @@ export class DirListing extends Widget {
389
404
  const model = this._model;
390
405
  const items = this._sortedItems;
391
406
  const paths = items.map(item => item.path);
392
- const promises = toArray(this._model.sessions())
407
+ const promises = Array.from(this._model.sessions())
393
408
  .filter(session => {
394
409
  const index = ArrayExt.firstIndexOf(paths, session.path);
395
410
  return this.selection[items[index].path];
@@ -490,7 +505,7 @@ export class DirListing extends Widget {
490
505
  */
491
506
  isSelected(name) {
492
507
  const items = this._sortedItems;
493
- return (toArray(filter(items, item => item.name === name && this.selection[item.path])).length !== 0);
508
+ return (Array.from(filter(items, item => item.name === name && this.selection[item.path])).length !== 0);
494
509
  }
495
510
  /**
496
511
  * Find a model given a click.
@@ -658,6 +673,7 @@ export class DirListing extends Widget {
658
673
  * A handler invoked on an `'update-request'` message.
659
674
  */
660
675
  onUpdateRequest(msg) {
676
+ var _a;
661
677
  this._isDirty = false;
662
678
  // Fetch common variables.
663
679
  const items = this._sortedItems;
@@ -692,7 +708,7 @@ export class DirListing extends Widget {
692
708
  const checkAllCheckbox = renderer.getCheckboxNode(this.headerNode);
693
709
  if (checkAllCheckbox) {
694
710
  const totalSelected = Object.keys(this.selection).length;
695
- const allSelected = totalSelected === items.length;
711
+ const allSelected = items.length > 0 && totalSelected === items.length;
696
712
  const someSelected = !allSelected && totalSelected > 0;
697
713
  checkAllCheckbox.checked = allSelected;
698
714
  checkAllCheckbox.indeterminate = someSelected;
@@ -702,21 +718,20 @@ export class DirListing extends Widget {
702
718
  // next value).
703
719
  checkAllCheckbox.dataset.checked = String(allSelected);
704
720
  checkAllCheckbox.dataset.indeterminate = String(someSelected);
721
+ const trans = this.translator.load('jupyterlab');
722
+ checkAllCheckbox === null || checkAllCheckbox === void 0 ? void 0 : checkAllCheckbox.setAttribute('aria-label', allSelected || someSelected
723
+ ? trans.__('Deselect all files and directories')
724
+ : trans.__('Select all files and directories'));
705
725
  }
706
726
  // Add extra classes to item nodes based on widget state.
707
727
  items.forEach((item, i) => {
708
728
  const node = nodes[i];
709
729
  const ft = this._manager.registry.getFileTypeForModel(item);
710
- renderer.updateItemNode(node, item, ft, this.translator, this._hiddenColumns);
711
- if (this.selection[item.path]) {
712
- node.classList.add(SELECTED_CLASS);
713
- if (this._isCut && this._model.path === this._prevPath) {
714
- node.classList.add(CUT_CLASS);
715
- }
716
- const checkbox = renderer.getCheckboxNode(node);
717
- if (checkbox) {
718
- checkbox.checked = true;
719
- }
730
+ renderer.updateItemNode(node, item, ft, this.translator, this._hiddenColumns, this.selection[item.path]);
731
+ if (this.selection[item.path] &&
732
+ this._isCut &&
733
+ this._model.path === this._prevPath) {
734
+ node.classList.add(CUT_CLASS);
720
735
  }
721
736
  // add metadata to the node
722
737
  node.setAttribute('data-isdir', item.type === 'directory' ? 'true' : 'false');
@@ -731,8 +746,7 @@ export class DirListing extends Widget {
731
746
  }
732
747
  // Handle file session statuses.
733
748
  const paths = items.map(item => item.path);
734
- each(this._model.sessions(), session => {
735
- var _a;
749
+ for (const session of this._model.sessions()) {
736
750
  const index = ArrayExt.firstIndexOf(paths, session.path);
737
751
  const node = nodes[index];
738
752
  // Node may have been filtered out.
@@ -742,11 +756,11 @@ export class DirListing extends Widget {
742
756
  node.classList.add(RUNNING_CLASS);
743
757
  if (specs && name) {
744
758
  const spec = specs.kernelspecs[name];
745
- name = spec ? spec.display_name : 'unknown'; // FIXME-TRANS: Is this localizable?
759
+ name = spec ? spec.display_name : this._trans.__('unknown');
746
760
  }
747
761
  node.title = this._trans.__('%1\nKernel: %2', node.title, name);
748
762
  }
749
- });
763
+ }
750
764
  this._prevPath = this._model.path;
751
765
  }
752
766
  onResize(msg) {
@@ -1159,8 +1173,8 @@ export class DirListing extends Widget {
1159
1173
  }
1160
1174
  else {
1161
1175
  const path = selectedPaths[0];
1162
- item = find(items, value => value.path === path);
1163
- selectedItems = toArray(this.selectedItems());
1176
+ item = items.find(value => value.path === path);
1177
+ selectedItems = this.selectedItems();
1164
1178
  }
1165
1179
  if (!item) {
1166
1180
  return;
@@ -1337,9 +1351,9 @@ export class DirListing extends Widget {
1337
1351
  */
1338
1352
  _copy() {
1339
1353
  this._clipboard.length = 0;
1340
- each(this.selectedItems(), item => {
1354
+ for (const item of this.selectedItems()) {
1341
1355
  this._clipboard.push(item.path);
1342
- });
1356
+ }
1343
1357
  }
1344
1358
  /**
1345
1359
  * Delete the files with the given paths.
@@ -1427,12 +1441,12 @@ export class DirListing extends Widget {
1427
1441
  // Update the selection.
1428
1442
  const existing = Object.keys(this.selection);
1429
1443
  this.clearSelectedItems();
1430
- each(this._model.items(), item => {
1444
+ for (const item of this._model.items()) {
1431
1445
  const path = item.path;
1432
1446
  if (existing.indexOf(path) !== -1) {
1433
1447
  this.selection[path] = true;
1434
1448
  }
1435
- });
1449
+ }
1436
1450
  if (this.isVisible) {
1437
1451
  // Update the sorted items.
1438
1452
  this.sort(this.sortState);
@@ -1508,18 +1522,19 @@ export class DirListing extends Widget {
1508
1522
  * @param node - The header node to populate.
1509
1523
  */
1510
1524
  populateHeaderNode(node, translator, hiddenColumns) {
1511
- var _a, _b;
1512
1525
  translator = translator || nullTranslator;
1513
1526
  const trans = translator.load('jupyterlab');
1514
1527
  const name = this.createHeaderItemNode(trans.__('Name'));
1515
1528
  const narrow = document.createElement('div');
1516
1529
  const modified = this.createHeaderItemNode(trans.__('Last Modified'));
1530
+ const fileSize = this.createHeaderItemNode(trans.__('File Size'));
1517
1531
  name.classList.add(NAME_ID_CLASS);
1518
1532
  name.classList.add(SELECTED_CLASS);
1519
1533
  modified.classList.add(MODIFIED_ID_CLASS);
1534
+ fileSize.classList.add(FILE_SIZE_ID_CLASS);
1520
1535
  narrow.classList.add(NARROW_ID_CLASS);
1521
1536
  narrow.textContent = '...';
1522
- if (!((_a = hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has) === null || _a === void 0 ? void 0 : _a.call(hiddenColumns, 'is_selected'))) {
1537
+ if (!(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('is_selected'))) {
1523
1538
  const checkboxWrapper = this.createCheckboxWrapperNode({
1524
1539
  alwaysVisible: true
1525
1540
  });
@@ -1528,12 +1543,19 @@ export class DirListing extends Widget {
1528
1543
  node.appendChild(name);
1529
1544
  node.appendChild(narrow);
1530
1545
  node.appendChild(modified);
1531
- if ((_b = hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has) === null || _b === void 0 ? void 0 : _b.call(hiddenColumns, 'last_modified')) {
1546
+ node.appendChild(fileSize);
1547
+ if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified')) {
1532
1548
  modified.classList.add(MODIFIED_COLUMN_HIDDEN);
1533
1549
  }
1534
1550
  else {
1535
1551
  modified.classList.remove(MODIFIED_COLUMN_HIDDEN);
1536
1552
  }
1553
+ if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size')) {
1554
+ fileSize.classList.add(FILE_SIZE_COLUMN_HIDDEN);
1555
+ }
1556
+ else {
1557
+ fileSize.classList.remove(FILE_SIZE_COLUMN_HIDDEN);
1558
+ }
1537
1559
  // set the initial caret icon
1538
1560
  Private.updateCaret(DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS), 'right', 'up');
1539
1561
  }
@@ -1549,11 +1571,13 @@ export class DirListing extends Widget {
1549
1571
  handleHeaderClick(node, event) {
1550
1572
  const name = DOMUtils.findElement(node, NAME_ID_CLASS);
1551
1573
  const modified = DOMUtils.findElement(node, MODIFIED_ID_CLASS);
1574
+ const fileSize = DOMUtils.findElement(node, FILE_SIZE_ID_CLASS);
1552
1575
  const state = { direction: 'ascending', key: 'name' };
1553
1576
  const target = event.target;
1577
+ const modifiedIcon = DOMUtils.findElement(modified, HEADER_ITEM_ICON_CLASS);
1578
+ const fileSizeIcon = DOMUtils.findElement(fileSize, HEADER_ITEM_ICON_CLASS);
1579
+ const nameIcon = DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS);
1554
1580
  if (name.contains(target)) {
1555
- const modifiedIcon = DOMUtils.findElement(modified, HEADER_ITEM_ICON_CLASS);
1556
- const nameIcon = DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS);
1557
1581
  if (name.classList.contains(SELECTED_CLASS)) {
1558
1582
  if (!name.classList.contains(DESCENDING_CLASS)) {
1559
1583
  state.direction = 'descending';
@@ -1572,12 +1596,13 @@ export class DirListing extends Widget {
1572
1596
  name.classList.add(SELECTED_CLASS);
1573
1597
  modified.classList.remove(SELECTED_CLASS);
1574
1598
  modified.classList.remove(DESCENDING_CLASS);
1599
+ fileSize.classList.remove(SELECTED_CLASS);
1600
+ fileSize.classList.remove(DESCENDING_CLASS);
1575
1601
  Private.updateCaret(modifiedIcon, 'left');
1602
+ Private.updateCaret(fileSizeIcon, 'left');
1576
1603
  return state;
1577
1604
  }
1578
1605
  if (modified.contains(target)) {
1579
- const modifiedIcon = DOMUtils.findElement(modified, HEADER_ITEM_ICON_CLASS);
1580
- const nameIcon = DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS);
1581
1606
  state.key = 'last_modified';
1582
1607
  if (modified.classList.contains(SELECTED_CLASS)) {
1583
1608
  if (!modified.classList.contains(DESCENDING_CLASS)) {
@@ -1597,7 +1622,36 @@ export class DirListing extends Widget {
1597
1622
  modified.classList.add(SELECTED_CLASS);
1598
1623
  name.classList.remove(SELECTED_CLASS);
1599
1624
  name.classList.remove(DESCENDING_CLASS);
1625
+ fileSize.classList.remove(SELECTED_CLASS);
1626
+ fileSize.classList.remove(DESCENDING_CLASS);
1627
+ Private.updateCaret(nameIcon, 'right');
1628
+ Private.updateCaret(fileSizeIcon, 'left');
1629
+ return state;
1630
+ }
1631
+ if (fileSize.contains(target)) {
1632
+ state.key = 'file_size';
1633
+ if (fileSize.classList.contains(SELECTED_CLASS)) {
1634
+ if (!fileSize.classList.contains(DESCENDING_CLASS)) {
1635
+ state.direction = 'descending';
1636
+ fileSize.classList.add(DESCENDING_CLASS);
1637
+ Private.updateCaret(fileSizeIcon, 'left', 'down');
1638
+ }
1639
+ else {
1640
+ fileSize.classList.remove(DESCENDING_CLASS);
1641
+ Private.updateCaret(fileSizeIcon, 'left', 'up');
1642
+ }
1643
+ }
1644
+ else {
1645
+ fileSize.classList.remove(DESCENDING_CLASS);
1646
+ Private.updateCaret(fileSizeIcon, 'left', 'up');
1647
+ }
1648
+ fileSize.classList.add(SELECTED_CLASS);
1649
+ name.classList.remove(SELECTED_CLASS);
1650
+ name.classList.remove(DESCENDING_CLASS);
1651
+ modified.classList.remove(SELECTED_CLASS);
1652
+ modified.classList.remove(DESCENDING_CLASS);
1600
1653
  Private.updateCaret(nameIcon, 'right');
1654
+ Private.updateCaret(modifiedIcon, 'left');
1601
1655
  return state;
1602
1656
  }
1603
1657
  return state;
@@ -1608,32 +1662,40 @@ export class DirListing extends Widget {
1608
1662
  * @returns A new DOM node to use as a content item.
1609
1663
  */
1610
1664
  createItemNode(hiddenColumns) {
1611
- var _a, _b;
1612
1665
  const node = document.createElement('li');
1613
1666
  const icon = document.createElement('span');
1614
1667
  const text = document.createElement('span');
1615
1668
  const modified = document.createElement('span');
1669
+ const fileSize = document.createElement('span');
1616
1670
  icon.className = ITEM_ICON_CLASS;
1617
1671
  text.className = ITEM_TEXT_CLASS;
1618
1672
  modified.className = ITEM_MODIFIED_CLASS;
1619
- if (!((_a = hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has) === null || _a === void 0 ? void 0 : _a.call(hiddenColumns, 'is_selected'))) {
1673
+ fileSize.className = ITEM_FILE_SIZE_CLASS;
1674
+ if (!(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('is_selected'))) {
1620
1675
  const checkboxWrapper = this.createCheckboxWrapperNode();
1621
1676
  node.appendChild(checkboxWrapper);
1622
1677
  }
1623
1678
  node.appendChild(icon);
1624
1679
  node.appendChild(text);
1625
1680
  node.appendChild(modified);
1681
+ node.appendChild(fileSize);
1626
1682
  // Make the text note focusable so that it receives keyboard events;
1627
1683
  // text node was specifically chosen to receive shortcuts because
1628
1684
  // text element gets substituted with input area during file name edits
1629
1685
  // which conveniently deactivate irrelevant shortcuts.
1630
1686
  text.tabIndex = 0;
1631
- if ((_b = hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has) === null || _b === void 0 ? void 0 : _b.call(hiddenColumns, 'last_modified')) {
1687
+ if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified')) {
1632
1688
  modified.classList.add(MODIFIED_COLUMN_HIDDEN);
1633
1689
  }
1634
1690
  else {
1635
1691
  modified.classList.remove(MODIFIED_COLUMN_HIDDEN);
1636
1692
  }
1693
+ if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size')) {
1694
+ fileSize.classList.add(FILE_SIZE_COLUMN_HIDDEN);
1695
+ }
1696
+ else {
1697
+ fileSize.classList.remove(FILE_SIZE_COLUMN_HIDDEN);
1698
+ }
1637
1699
  return node;
1638
1700
  }
1639
1701
  /**
@@ -1680,9 +1742,10 @@ export class DirListing extends Widget {
1680
1742
  * @param fileType - The file type of the item, if applicable.
1681
1743
  *
1682
1744
  */
1683
- updateItemNode(node, model, fileType, translator, hiddenColumns) {
1684
- var _a, _b;
1685
- translator = translator || nullTranslator;
1745
+ updateItemNode(node, model, fileType, translator, hiddenColumns, selected) {
1746
+ if (selected) {
1747
+ node.classList.add(SELECTED_CLASS);
1748
+ }
1686
1749
  fileType =
1687
1750
  fileType || DocumentRegistry.getDefaultTextFileType(translator);
1688
1751
  const { icon, iconClass, name } = fileType;
@@ -1691,8 +1754,9 @@ export class DirListing extends Widget {
1691
1754
  const iconContainer = DOMUtils.findElement(node, ITEM_ICON_CLASS);
1692
1755
  const text = DOMUtils.findElement(node, ITEM_TEXT_CLASS);
1693
1756
  const modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
1757
+ const fileSize = DOMUtils.findElement(node, ITEM_FILE_SIZE_CLASS);
1694
1758
  const checkboxWrapper = DOMUtils.findElement(node, CHECKBOX_WRAPPER_CLASS);
1695
- const showFileCheckboxes = !((_a = hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has) === null || _a === void 0 ? void 0 : _a.call(hiddenColumns, 'is_selected'));
1759
+ const showFileCheckboxes = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('is_selected'));
1696
1760
  if (checkboxWrapper && !showFileCheckboxes) {
1697
1761
  node.removeChild(checkboxWrapper);
1698
1762
  }
@@ -1700,12 +1764,18 @@ export class DirListing extends Widget {
1700
1764
  const checkboxWrapper = this.createCheckboxWrapperNode();
1701
1765
  node.insertBefore(checkboxWrapper, iconContainer);
1702
1766
  }
1703
- if ((_b = hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has) === null || _b === void 0 ? void 0 : _b.call(hiddenColumns, 'last_modified')) {
1767
+ if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified')) {
1704
1768
  modified.classList.add(MODIFIED_COLUMN_HIDDEN);
1705
1769
  }
1706
1770
  else {
1707
1771
  modified.classList.remove(MODIFIED_COLUMN_HIDDEN);
1708
1772
  }
1773
+ if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size')) {
1774
+ fileSize.classList.add(FILE_SIZE_COLUMN_HIDDEN);
1775
+ }
1776
+ else {
1777
+ fileSize.classList.remove(FILE_SIZE_COLUMN_HIDDEN);
1778
+ }
1709
1779
  // render the file item's icon
1710
1780
  LabIcon.resolveElement({
1711
1781
  icon,
@@ -1717,8 +1787,13 @@ export class DirListing extends Widget {
1717
1787
  let hoverText = trans.__('Name: %1', model.name);
1718
1788
  // add file size to pop up if its available
1719
1789
  if (model.size !== null && model.size !== undefined) {
1790
+ const fileSizeText = Private.formatFileSize(model.size, 1, 1024);
1791
+ fileSize.textContent = fileSizeText;
1720
1792
  hoverText += trans.__('\nSize: %1', Private.formatFileSize(model.size, 1, 1024));
1721
1793
  }
1794
+ else {
1795
+ fileSize.textContent = '';
1796
+ }
1722
1797
  if (model.path) {
1723
1798
  const dirname = PathExt.dirname(model.path);
1724
1799
  if (dirname) {
@@ -1729,10 +1804,10 @@ export class DirListing extends Widget {
1729
1804
  }
1730
1805
  }
1731
1806
  if (model.created) {
1732
- hoverText += trans.__('\nCreated: %1', Time.format(new Date(model.created), 'YYYY-MM-DD HH:mm:ss'));
1807
+ hoverText += trans.__('\nCreated: %1', Time.format(new Date(model.created)));
1733
1808
  }
1734
1809
  if (model.last_modified) {
1735
- hoverText += trans.__('\nModified: %1', Time.format(new Date(model.last_modified), 'YYYY-MM-DD HH:mm:ss'));
1810
+ hoverText += trans.__('\nModified: %1', Time.format(new Date(model.last_modified)));
1736
1811
  }
1737
1812
  hoverText += trans.__('\nWritable: %1', model.writable);
1738
1813
  node.title = hoverText;
@@ -1744,16 +1819,33 @@ export class DirListing extends Widget {
1744
1819
  node.removeAttribute('data-is-dot');
1745
1820
  }
1746
1821
  // If an item is being edited currently, its text node is unavailable.
1822
+ const indices = !model.indices ? [] : model.indices;
1823
+ let highlightedName = StringExt.highlight(model.name, indices, h.mark);
1747
1824
  if (text) {
1748
- const indices = !model.indices ? [] : model.indices;
1749
- let highlightedName = StringExt.highlight(model.name, indices, h.mark);
1750
1825
  VirtualDOM.render(h.span(highlightedName), text);
1751
1826
  }
1827
+ // Adds an aria-label to the checkbox element.
1828
+ const checkbox = checkboxWrapper === null || checkboxWrapper === void 0 ? void 0 : checkboxWrapper.querySelector('input[type="checkbox"]');
1829
+ if (checkbox) {
1830
+ let ariaLabel;
1831
+ if (fileType.contentType === 'directory') {
1832
+ ariaLabel = selected
1833
+ ? trans.__('Deselect directory "%1"', highlightedName)
1834
+ : trans.__('Select directory "%1"', highlightedName);
1835
+ }
1836
+ else {
1837
+ ariaLabel = selected
1838
+ ? trans.__('Deselect file "%1"', highlightedName)
1839
+ : trans.__('Select file "%1"', highlightedName);
1840
+ }
1841
+ checkbox.setAttribute('aria-label', ariaLabel);
1842
+ checkbox.checked = selected !== null && selected !== void 0 ? selected : false;
1843
+ }
1752
1844
  let modText = '';
1753
1845
  let modTitle = '';
1754
1846
  if (model.last_modified) {
1755
1847
  modText = Time.formatHuman(new Date(model.last_modified));
1756
- modTitle = Time.format(new Date(model.last_modified), 'lll');
1848
+ modTitle = Time.format(new Date(model.last_modified));
1757
1849
  }
1758
1850
  modified.textContent = modText;
1759
1851
  modified.title = modTitle;
@@ -1896,7 +1988,7 @@ var Private;
1896
1988
  * Sort a list of items by sort state as a new array.
1897
1989
  */
1898
1990
  function sort(items, state) {
1899
- const copy = toArray(items);
1991
+ const copy = Array.from(items);
1900
1992
  const reverse = state.direction === 'descending' ? 1 : -1;
1901
1993
  if (state.key === 'last_modified') {
1902
1994
  // Sort by last modified (grouping directories first)
@@ -1908,6 +2000,15 @@ var Private;
1908
2000
  return t1 - t2 || (valA - valB) * reverse;
1909
2001
  });
1910
2002
  }
2003
+ else if (state.key === 'file_size') {
2004
+ // Sort by size (grouping directories first)
2005
+ copy.sort((a, b) => {
2006
+ var _a, _b;
2007
+ const t1 = a.type === 'directory' ? 0 : 1;
2008
+ const t2 = b.type === 'directory' ? 0 : 1;
2009
+ return t1 - t2 || (((_a = a.size) !== null && _a !== void 0 ? _a : 0) - ((_b = b.size) !== null && _b !== void 0 ? _b : 0)) * reverse;
2010
+ });
2011
+ }
1911
2012
  else {
1912
2013
  // Sort by name (grouping directories first)
1913
2014
  copy.sort((a, b) => {
@@ -1933,10 +2034,10 @@ var Private;
1933
2034
  function formatFileSize(bytes, decimalPoint, k) {
1934
2035
  // https://www.codexworld.com/how-to/convert-file-size-bytes-kb-mb-gb-javascript/
1935
2036
  if (bytes === 0) {
1936
- return '0 Bytes';
2037
+ return '0 B';
1937
2038
  }
1938
2039
  const dm = decimalPoint || 2;
1939
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
2040
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
1940
2041
  const i = Math.floor(Math.log(bytes) / Math.log(k));
1941
2042
  if (i >= 0 && i < sizes.length) {
1942
2043
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];