@jupyterlab/filebrowser 4.6.0-alpha.4 → 4.6.0-alpha.5
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/browser.d.ts +14 -0
- package/lib/browser.js +48 -1
- package/lib/browser.js.map +1 -1
- package/lib/crumbs.d.ts +76 -0
- package/lib/crumbs.js +362 -81
- package/lib/crumbs.js.map +1 -1
- package/lib/listing.d.ts +84 -11
- package/lib/listing.js +276 -99
- package/lib/listing.js.map +1 -1
- package/lib/model.d.ts +25 -0
- package/lib/model.js +48 -2
- package/lib/model.js.map +1 -1
- package/lib/opendialog.d.ts +7 -0
- package/lib/opendialog.js +5 -4
- package/lib/opendialog.js.map +1 -1
- package/lib/pathnavigator.d.ts +108 -0
- package/lib/pathnavigator.js +389 -0
- package/lib/pathnavigator.js.map +1 -0
- package/package.json +11 -11
- package/src/browser.ts +50 -1
- package/src/crumbs.ts +432 -87
- package/src/listing.ts +418 -119
- package/src/model.ts +63 -2
- package/src/opendialog.ts +13 -0
- package/src/pathnavigator.ts +456 -0
- package/style/base.css +64 -35
- package/style/pathnavigator.css +72 -0
package/src/listing.ts
CHANGED
|
@@ -96,6 +96,11 @@ const ITEM_MODIFIED_CLASS = 'jp-DirListing-itemModified';
|
|
|
96
96
|
*/
|
|
97
97
|
const ITEM_FILE_SIZE_CLASS = 'jp-DirListing-itemFileSize';
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The class name added to the listing item created cell.
|
|
101
|
+
*/
|
|
102
|
+
const ITEM_CREATED_CLASS = 'jp-DirListing-itemCreated';
|
|
103
|
+
|
|
99
104
|
/**
|
|
100
105
|
* The class name added to the label element that wraps each item's checkbox and
|
|
101
106
|
* the header's check-all checkbox.
|
|
@@ -122,6 +127,11 @@ const MODIFIED_ID_CLASS = 'jp-id-modified';
|
|
|
122
127
|
*/
|
|
123
128
|
const FILE_SIZE_ID_CLASS = 'jp-id-filesize';
|
|
124
129
|
|
|
130
|
+
/**
|
|
131
|
+
* The class name added to the created column header cell.
|
|
132
|
+
*/
|
|
133
|
+
const CREATED_ID_CLASS = 'jp-id-created';
|
|
134
|
+
|
|
125
135
|
/**
|
|
126
136
|
* The mime type for a contents drag object.
|
|
127
137
|
*/
|
|
@@ -241,10 +251,14 @@ export class DirListing extends Widget {
|
|
|
241
251
|
|
|
242
252
|
// Get the width of the "modified" column
|
|
243
253
|
this._updateModifiedSize(this.node);
|
|
254
|
+
// Get the width of the "created" column
|
|
255
|
+
this._updateCreatedSize(this.node);
|
|
244
256
|
|
|
245
257
|
const headerNode = DOMUtils.findElement(this.node, HEADER_CLASS);
|
|
246
258
|
// hide the file size column by default
|
|
247
259
|
this._hiddenColumns.add('file_size');
|
|
260
|
+
// hide the date created column by default
|
|
261
|
+
this._hiddenColumns.add('date_created');
|
|
248
262
|
this._renderer.populateHeaderNode(
|
|
249
263
|
headerNode,
|
|
250
264
|
this.translator,
|
|
@@ -355,6 +369,7 @@ export class DirListing extends Widget {
|
|
|
355
369
|
);
|
|
356
370
|
this._sortState = state;
|
|
357
371
|
this.update();
|
|
372
|
+
this._saveState();
|
|
358
373
|
}
|
|
359
374
|
|
|
360
375
|
/**
|
|
@@ -575,19 +590,62 @@ export class DirListing extends Widget {
|
|
|
575
590
|
| Record<DirListing.IColumn['id'], number | null>
|
|
576
591
|
| undefined;
|
|
577
592
|
|
|
578
|
-
|
|
579
|
-
|
|
593
|
+
const sortState = (columns as ReadonlyJSONObject)[
|
|
594
|
+
'sortState'
|
|
595
|
+
] as unknown as DirListing.ISortState | undefined;
|
|
596
|
+
|
|
597
|
+
// Set sort state before updating column sizes so that any
|
|
598
|
+
// state save triggered by _updateColumnSizes includes the
|
|
599
|
+
// correct sort state rather than the default.
|
|
600
|
+
if (sortState) {
|
|
601
|
+
this._sortState = sortState;
|
|
580
602
|
}
|
|
581
|
-
|
|
582
|
-
|
|
603
|
+
|
|
604
|
+
if (sizes) {
|
|
605
|
+
for (const [key, size] of Object.entries(sizes)) {
|
|
606
|
+
this._columnSizes[key as DirListing.IColumn['id']] = size;
|
|
607
|
+
}
|
|
608
|
+
this._updateColumnSizes();
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Sort items and rebuild the header to reflect the restored
|
|
612
|
+
// sort state without calling sort() to avoid a redundant save.
|
|
613
|
+
if (sortState) {
|
|
614
|
+
this._sortedItems = Private.sort(
|
|
615
|
+
this.model.items(),
|
|
616
|
+
sortState,
|
|
617
|
+
this._sortNotebooksFirst,
|
|
618
|
+
this._sortFileNamesNaturally,
|
|
619
|
+
this.translator
|
|
620
|
+
);
|
|
621
|
+
this.headerNode.innerHTML = '';
|
|
622
|
+
this._renderer.populateHeaderNode(
|
|
623
|
+
this.headerNode,
|
|
624
|
+
this.translator,
|
|
625
|
+
this._hiddenColumns,
|
|
626
|
+
this._columnSizes,
|
|
627
|
+
sortState
|
|
628
|
+
);
|
|
629
|
+
this.update();
|
|
583
630
|
}
|
|
584
|
-
this._updateColumnSizes();
|
|
585
631
|
} catch (error) {
|
|
586
632
|
await state.remove(key);
|
|
587
633
|
}
|
|
588
634
|
}
|
|
589
635
|
private _stateColumnsKey: string;
|
|
590
636
|
|
|
637
|
+
/**
|
|
638
|
+
* Save the current column sizes and sort state to the state database.
|
|
639
|
+
*/
|
|
640
|
+
private _saveState(): void {
|
|
641
|
+
if (this._state && this._stateColumnsKey) {
|
|
642
|
+
void this._state.save(this._stateColumnsKey, {
|
|
643
|
+
sizes: this._columnSizes,
|
|
644
|
+
sortState: { ...this._sortState }
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
591
649
|
/**
|
|
592
650
|
* Shut down kernels on the applicable currently selected items.
|
|
593
651
|
*
|
|
@@ -739,6 +797,39 @@ export class DirListing extends Widget {
|
|
|
739
797
|
}
|
|
740
798
|
}
|
|
741
799
|
|
|
800
|
+
/**
|
|
801
|
+
* Move focus to selected item, or to the first item if no item is selected.
|
|
802
|
+
*/
|
|
803
|
+
private _focusContent(): void {
|
|
804
|
+
const items = this._sortedItems;
|
|
805
|
+
if (items.length === 0) {
|
|
806
|
+
this._focusItem(0);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
let index =
|
|
811
|
+
this._focusPath !== ''
|
|
812
|
+
? ArrayExt.findFirstIndex(
|
|
813
|
+
items,
|
|
814
|
+
value => value.path === this._focusPath
|
|
815
|
+
)
|
|
816
|
+
: -1;
|
|
817
|
+
|
|
818
|
+
// Fallbacks if path is missing/not found.
|
|
819
|
+
if (index === -1) {
|
|
820
|
+
index = Math.min(Math.max(this._focusIndex, 0), items.length - 1);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
this._selectItem(index, false, true);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Handle `'activate-request'` messages.
|
|
828
|
+
*/
|
|
829
|
+
protected onActivateRequest(msg: Message): void {
|
|
830
|
+
this._focusContent();
|
|
831
|
+
}
|
|
832
|
+
|
|
742
833
|
/**
|
|
743
834
|
* Select an item by name.
|
|
744
835
|
*
|
|
@@ -944,21 +1035,42 @@ export class DirListing extends Widget {
|
|
|
944
1035
|
}
|
|
945
1036
|
|
|
946
1037
|
/**
|
|
947
|
-
* Update the modified column's size
|
|
1038
|
+
* Update the 'modified' column's size
|
|
948
1039
|
*/
|
|
949
1040
|
private _updateModifiedSize(node: HTMLElement) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1041
|
+
this._modifiedStyle = this._computeDateColumnStyle(
|
|
1042
|
+
node,
|
|
1043
|
+
MODIFIED_ID_CLASS,
|
|
1044
|
+
'last_modified'
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Update the 'date created' column's size
|
|
1050
|
+
*/
|
|
1051
|
+
private _updateCreatedSize(node: HTMLElement) {
|
|
1052
|
+
this._createdStyle = this._computeDateColumnStyle(
|
|
1053
|
+
node,
|
|
1054
|
+
CREATED_ID_CLASS,
|
|
1055
|
+
'date_created'
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Compute the Intl.RelativeTimeFormat style for a date column based on
|
|
1061
|
+
* its pixel width: < 100px → 'narrow', 100–120px → 'short', > 120px → 'long'.
|
|
1062
|
+
*/
|
|
1063
|
+
private _computeDateColumnStyle(
|
|
1064
|
+
node: HTMLElement,
|
|
1065
|
+
columnClassName: string,
|
|
1066
|
+
columnName: 'last_modified' | 'date_created'
|
|
1067
|
+
): Time.HumanStyle {
|
|
1068
|
+
const columnElement = DOMUtils.findElement(node, columnClassName);
|
|
1069
|
+
const width =
|
|
1070
|
+
this._columnSizes[columnName] ??
|
|
1071
|
+
columnElement?.getBoundingClientRect().width ??
|
|
955
1072
|
83;
|
|
956
|
-
|
|
957
|
-
this._modifiedWidth < 100
|
|
958
|
-
? 'narrow'
|
|
959
|
-
: this._modifiedWidth > 120
|
|
960
|
-
? 'long'
|
|
961
|
-
: 'short';
|
|
1073
|
+
return width < 100 ? 'narrow' : width > 120 ? 'long' : 'short';
|
|
962
1074
|
}
|
|
963
1075
|
|
|
964
1076
|
/**
|
|
@@ -966,13 +1078,23 @@ export class DirListing extends Widget {
|
|
|
966
1078
|
*/
|
|
967
1079
|
private _updateModifiedStyleAndSize() {
|
|
968
1080
|
const oldModifiedStyle = this._modifiedStyle;
|
|
969
|
-
// Update both size and style
|
|
970
1081
|
this._updateModifiedSize(this.node);
|
|
971
1082
|
if (oldModifiedStyle !== this._modifiedStyle) {
|
|
972
1083
|
this.updateModified(this._sortedItems, this._items);
|
|
973
1084
|
}
|
|
974
1085
|
}
|
|
975
1086
|
|
|
1087
|
+
/**
|
|
1088
|
+
* Rerender item nodes' created dates, if the created style has changed.
|
|
1089
|
+
*/
|
|
1090
|
+
private _updateCreatedStyleAndSize() {
|
|
1091
|
+
const oldCreatedStyle = this._createdStyle;
|
|
1092
|
+
this._updateCreatedSize(this.node);
|
|
1093
|
+
if (oldCreatedStyle !== this._createdStyle) {
|
|
1094
|
+
this.updateCreated(this._sortedItems, this._items);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
976
1098
|
/**
|
|
977
1099
|
* Update only the modified dates.
|
|
978
1100
|
*/
|
|
@@ -981,6 +1103,9 @@ export class DirListing extends Widget {
|
|
|
981
1103
|
const node = nodes[i];
|
|
982
1104
|
if (node && item.last_modified) {
|
|
983
1105
|
const modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
|
|
1106
|
+
if (!modified) {
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
984
1109
|
if (this.renderer.updateItemModified !== undefined) {
|
|
985
1110
|
this.renderer.updateItemModified(
|
|
986
1111
|
modified,
|
|
@@ -998,6 +1123,34 @@ export class DirListing extends Widget {
|
|
|
998
1123
|
});
|
|
999
1124
|
}
|
|
1000
1125
|
|
|
1126
|
+
/**
|
|
1127
|
+
* Update only the created dates.
|
|
1128
|
+
*/
|
|
1129
|
+
protected updateCreated(items: Contents.IModel[], nodes: HTMLElement[]) {
|
|
1130
|
+
items.forEach((item, i) => {
|
|
1131
|
+
const node = nodes[i];
|
|
1132
|
+
if (node && item.created) {
|
|
1133
|
+
const created = DOMUtils.findElement(node, ITEM_CREATED_CLASS);
|
|
1134
|
+
if (!created) {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
if (this.renderer.updateItemCreated !== undefined) {
|
|
1138
|
+
this.renderer.updateItemCreated(
|
|
1139
|
+
created,
|
|
1140
|
+
item.created,
|
|
1141
|
+
this._createdStyle
|
|
1142
|
+
);
|
|
1143
|
+
} else {
|
|
1144
|
+
DirListing.defaultRenderer.updateItemCreated(
|
|
1145
|
+
created,
|
|
1146
|
+
item.created,
|
|
1147
|
+
this._createdStyle
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1001
1154
|
// Update item nodes based on widget state.
|
|
1002
1155
|
protected updateNodes(
|
|
1003
1156
|
items: Contents.IModel[],
|
|
@@ -1015,7 +1168,8 @@ export class DirListing extends Widget {
|
|
|
1015
1168
|
node,
|
|
1016
1169
|
item,
|
|
1017
1170
|
this._modifiedStyle,
|
|
1018
|
-
this._columnSizes
|
|
1171
|
+
this._columnSizes,
|
|
1172
|
+
this._createdStyle
|
|
1019
1173
|
);
|
|
1020
1174
|
}
|
|
1021
1175
|
const ft = this._manager.registry.getFileTypeForModel(item);
|
|
@@ -1027,7 +1181,8 @@ export class DirListing extends Widget {
|
|
|
1027
1181
|
this._hiddenColumns,
|
|
1028
1182
|
this.selection[item.path],
|
|
1029
1183
|
this._modifiedStyle,
|
|
1030
|
-
this._columnSizes
|
|
1184
|
+
this._columnSizes,
|
|
1185
|
+
this._createdStyle
|
|
1031
1186
|
);
|
|
1032
1187
|
if (
|
|
1033
1188
|
this.selection[item.path] &&
|
|
@@ -1195,7 +1350,8 @@ export class DirListing extends Widget {
|
|
|
1195
1350
|
this.headerNode,
|
|
1196
1351
|
this.translator,
|
|
1197
1352
|
this._hiddenColumns,
|
|
1198
|
-
this._columnSizes
|
|
1353
|
+
this._columnSizes,
|
|
1354
|
+
this._sortState
|
|
1199
1355
|
);
|
|
1200
1356
|
|
|
1201
1357
|
this._updateColumnSizes();
|
|
@@ -1286,6 +1442,7 @@ export class DirListing extends Widget {
|
|
|
1286
1442
|
column.element.style.width = size === null ? '' : size + 'px';
|
|
1287
1443
|
}
|
|
1288
1444
|
this._updateModifiedStyleAndSize();
|
|
1445
|
+
this._updateCreatedStyleAndSize();
|
|
1289
1446
|
|
|
1290
1447
|
// Refresh sizes on the per item widths
|
|
1291
1448
|
if (this.isVisible) {
|
|
@@ -1295,11 +1452,7 @@ export class DirListing extends Widget {
|
|
|
1295
1452
|
}
|
|
1296
1453
|
}
|
|
1297
1454
|
|
|
1298
|
-
|
|
1299
|
-
void this._state.save(this._stateColumnsKey, {
|
|
1300
|
-
sizes: this._columnSizes
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1455
|
+
this._saveState();
|
|
1303
1456
|
}
|
|
1304
1457
|
|
|
1305
1458
|
private get _visibleColumns() {
|
|
@@ -1729,6 +1882,10 @@ export class DirListing extends Widget {
|
|
|
1729
1882
|
if (model.path === model.rootPath) {
|
|
1730
1883
|
return;
|
|
1731
1884
|
}
|
|
1885
|
+
// Check if we're at the navigation root boundary
|
|
1886
|
+
if (model.root && model.path === model.root) {
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1732
1889
|
try {
|
|
1733
1890
|
await model.cd('..');
|
|
1734
1891
|
} catch (reason) {
|
|
@@ -1745,9 +1902,8 @@ export class DirListing extends Widget {
|
|
|
1745
1902
|
return;
|
|
1746
1903
|
}
|
|
1747
1904
|
|
|
1748
|
-
switch (event.
|
|
1749
|
-
case
|
|
1750
|
-
// Enter
|
|
1905
|
+
switch (event.key) {
|
|
1906
|
+
case 'Enter': {
|
|
1751
1907
|
// Do nothing if any modifier keys are pressed.
|
|
1752
1908
|
if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
|
|
1753
1909
|
return;
|
|
@@ -1759,16 +1915,14 @@ export class DirListing extends Widget {
|
|
|
1759
1915
|
}
|
|
1760
1916
|
return;
|
|
1761
1917
|
}
|
|
1762
|
-
case
|
|
1763
|
-
// Up arrow
|
|
1918
|
+
case 'ArrowUp':
|
|
1764
1919
|
this._handleArrowY(event, -1);
|
|
1765
1920
|
return;
|
|
1766
|
-
case
|
|
1767
|
-
// Down arrow
|
|
1921
|
+
case 'ArrowDown':
|
|
1768
1922
|
this._handleArrowY(event, 1);
|
|
1769
1923
|
return;
|
|
1770
|
-
case
|
|
1771
|
-
|
|
1924
|
+
case ' ':
|
|
1925
|
+
case 'Spacebar': {
|
|
1772
1926
|
if (event.ctrlKey) {
|
|
1773
1927
|
// Follow the Windows and Ubuntu convention: you must press `ctrl` +
|
|
1774
1928
|
// `space` in order to toggle whether an item is selected.
|
|
@@ -1823,7 +1977,7 @@ export class DirListing extends Widget {
|
|
|
1823
1977
|
// Don't gobble up the space key on the check-all checkbox (which the
|
|
1824
1978
|
// browser treats as a click event).
|
|
1825
1979
|
!(
|
|
1826
|
-
(event.key === ' ' || event.
|
|
1980
|
+
(event.key === ' ' || event.key === 'Spacebar') &&
|
|
1827
1981
|
(event.target as HTMLInputElement).type === 'checkbox'
|
|
1828
1982
|
)
|
|
1829
1983
|
) {
|
|
@@ -2240,11 +2394,14 @@ export class DirListing extends Widget {
|
|
|
2240
2394
|
// Focus the top node if the folder is empty and therefore there are no
|
|
2241
2395
|
// items inside the folder to focus.
|
|
2242
2396
|
this._focusIndex = 0;
|
|
2397
|
+
this._focusPath = '';
|
|
2243
2398
|
this.node.focus();
|
|
2244
2399
|
return;
|
|
2245
2400
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2401
|
+
const clampedIndex = Math.min(Math.max(index, 0), items.length - 1);
|
|
2402
|
+
this._focusIndex = clampedIndex;
|
|
2403
|
+
this._focusPath = this._sortedItems[clampedIndex]?.path ?? '';
|
|
2404
|
+
const node = items[clampedIndex];
|
|
2248
2405
|
const nameNode = this.renderer.getNameNode(node);
|
|
2249
2406
|
if (nameNode) {
|
|
2250
2407
|
// Make the filename text node focusable so that it receives keyboard
|
|
@@ -2662,7 +2819,8 @@ export class DirListing extends Widget {
|
|
|
2662
2819
|
name: null,
|
|
2663
2820
|
file_size: null,
|
|
2664
2821
|
is_selected: null,
|
|
2665
|
-
last_modified: null
|
|
2822
|
+
last_modified: null,
|
|
2823
|
+
date_created: null
|
|
2666
2824
|
};
|
|
2667
2825
|
private _sortNotebooksFirst = false;
|
|
2668
2826
|
private _sortFileNamesNaturally = true;
|
|
@@ -2670,9 +2828,9 @@ export class DirListing extends Widget {
|
|
|
2670
2828
|
private _allowDragDropUpload = true;
|
|
2671
2829
|
// _focusIndex should never be set outside the range [0, this._items.length - 1]
|
|
2672
2830
|
private _focusIndex = 0;
|
|
2673
|
-
|
|
2674
|
-
private
|
|
2675
|
-
private
|
|
2831
|
+
private _focusPath = '';
|
|
2832
|
+
private _modifiedStyle: Time.HumanStyle = 'short';
|
|
2833
|
+
private _createdStyle: Time.HumanStyle = 'short';
|
|
2676
2834
|
private _allUploaded = new Signal<DirListing, void>(this);
|
|
2677
2835
|
private _width: number | null = null;
|
|
2678
2836
|
private _state: IStateDB | null = null;
|
|
@@ -2747,17 +2905,29 @@ export namespace DirListing {
|
|
|
2747
2905
|
/**
|
|
2748
2906
|
* Toggleable columns.
|
|
2749
2907
|
*/
|
|
2750
|
-
export type ToggleableColumn =
|
|
2908
|
+
export type ToggleableColumn =
|
|
2909
|
+
| 'last_modified'
|
|
2910
|
+
| 'is_selected'
|
|
2911
|
+
| 'file_size'
|
|
2912
|
+
| 'date_created';
|
|
2751
2913
|
|
|
2752
2914
|
/**
|
|
2753
2915
|
* Resizable columns.
|
|
2754
2916
|
*/
|
|
2755
|
-
export type ResizableColumn =
|
|
2917
|
+
export type ResizableColumn =
|
|
2918
|
+
| 'name'
|
|
2919
|
+
| 'last_modified'
|
|
2920
|
+
| 'file_size'
|
|
2921
|
+
| 'date_created';
|
|
2756
2922
|
|
|
2757
2923
|
/**
|
|
2758
2924
|
* Sortable columns.
|
|
2759
2925
|
*/
|
|
2760
|
-
export type SortableColumn =
|
|
2926
|
+
export type SortableColumn =
|
|
2927
|
+
| 'name'
|
|
2928
|
+
| 'last_modified'
|
|
2929
|
+
| 'file_size'
|
|
2930
|
+
| 'date_created';
|
|
2761
2931
|
|
|
2762
2932
|
/**
|
|
2763
2933
|
* A file contents model thunk.
|
|
@@ -2796,7 +2966,8 @@ export namespace DirListing {
|
|
|
2796
2966
|
node: HTMLElement,
|
|
2797
2967
|
translator?: ITranslator,
|
|
2798
2968
|
hiddenColumns?: Set<DirListing.ToggleableColumn>,
|
|
2799
|
-
columnsSizes?: Record<IColumn['id'], number | null
|
|
2969
|
+
columnsSizes?: Record<IColumn['id'], number | null>,
|
|
2970
|
+
sortState?: ISortState
|
|
2800
2971
|
): void;
|
|
2801
2972
|
|
|
2802
2973
|
/**
|
|
@@ -2835,6 +3006,21 @@ export namespace DirListing {
|
|
|
2835
3006
|
modifiedStyle: Time.HumanStyle
|
|
2836
3007
|
): void;
|
|
2837
3008
|
|
|
3009
|
+
/**
|
|
3010
|
+
* Update an item's created date.
|
|
3011
|
+
*
|
|
3012
|
+
* @param created - Element containing the file's created date.
|
|
3013
|
+
*
|
|
3014
|
+
* @param createdDate - String representation of the created date.
|
|
3015
|
+
*
|
|
3016
|
+
* @param createdStyle - The date style for the created column: narrow, short, or long
|
|
3017
|
+
*/
|
|
3018
|
+
updateItemCreated?(
|
|
3019
|
+
created: HTMLElement,
|
|
3020
|
+
createdDate: string,
|
|
3021
|
+
createdStyle: Time.HumanStyle
|
|
3022
|
+
): void;
|
|
3023
|
+
|
|
2838
3024
|
/**
|
|
2839
3025
|
* Update an item node to reflect the current state of a model.
|
|
2840
3026
|
*
|
|
@@ -2854,7 +3040,8 @@ export namespace DirListing {
|
|
|
2854
3040
|
hiddenColumns?: Set<DirListing.ToggleableColumn>,
|
|
2855
3041
|
selected?: boolean,
|
|
2856
3042
|
modifiedStyle?: Time.HumanStyle,
|
|
2857
|
-
columnsSizes?: Record<IColumn['id'], number | null
|
|
3043
|
+
columnsSizes?: Record<IColumn['id'], number | null>,
|
|
3044
|
+
createdStyle?: Time.HumanStyle
|
|
2858
3045
|
): void;
|
|
2859
3046
|
|
|
2860
3047
|
/**
|
|
@@ -2864,7 +3051,8 @@ export namespace DirListing {
|
|
|
2864
3051
|
node: HTMLElement,
|
|
2865
3052
|
model: Contents.IModel,
|
|
2866
3053
|
modifiedStyle?: Time.HumanStyle,
|
|
2867
|
-
columnsSizes?: Record<IColumn['id'], number | null
|
|
3054
|
+
columnsSizes?: Record<IColumn['id'], number | null>,
|
|
3055
|
+
createdStyle?: Time.HumanStyle
|
|
2868
3056
|
): void;
|
|
2869
3057
|
|
|
2870
3058
|
/**
|
|
@@ -2992,6 +3180,16 @@ export namespace DirListing {
|
|
|
2992
3180
|
caretSide: 'left',
|
|
2993
3181
|
grow: 1
|
|
2994
3182
|
},
|
|
3183
|
+
{
|
|
3184
|
+
id: 'date_created' as const,
|
|
3185
|
+
className: CREATED_ID_CLASS,
|
|
3186
|
+
itemClassName: ITEM_CREATED_CLASS,
|
|
3187
|
+
minWidth: 60,
|
|
3188
|
+
resizable: true,
|
|
3189
|
+
sortable: true,
|
|
3190
|
+
caretSide: 'left',
|
|
3191
|
+
grow: 1
|
|
3192
|
+
},
|
|
2995
3193
|
{
|
|
2996
3194
|
id: 'file_size' as const,
|
|
2997
3195
|
className: FILE_SIZE_ID_CLASS,
|
|
@@ -3035,7 +3233,8 @@ export namespace DirListing {
|
|
|
3035
3233
|
node: HTMLElement,
|
|
3036
3234
|
translator?: ITranslator,
|
|
3037
3235
|
hiddenColumns?: Set<DirListing.ToggleableColumn>,
|
|
3038
|
-
columnsSizes?: Record<DirListing.IColumn['id'], number | null
|
|
3236
|
+
columnsSizes?: Record<DirListing.IColumn['id'], number | null>,
|
|
3237
|
+
sortState?: DirListing.ISortState
|
|
3039
3238
|
): void {
|
|
3040
3239
|
translator = translator || nullTranslator;
|
|
3041
3240
|
const trans = translator.load('jupyterlab');
|
|
@@ -3052,6 +3251,11 @@ export namespace DirListing {
|
|
|
3052
3251
|
small: trans.__('Size'),
|
|
3053
3252
|
large: trans.__('File Size')
|
|
3054
3253
|
}),
|
|
3254
|
+
date_created: () =>
|
|
3255
|
+
this._createHeaderItemNodeWithSizes({
|
|
3256
|
+
small: trans.__('Created'),
|
|
3257
|
+
large: trans.__('Date Created')
|
|
3258
|
+
}),
|
|
3055
3259
|
is_selected: () =>
|
|
3056
3260
|
this.createCheckboxWrapperNode({
|
|
3057
3261
|
alwaysVisible: true,
|
|
@@ -3086,14 +3290,9 @@ export namespace DirListing {
|
|
|
3086
3290
|
}
|
|
3087
3291
|
}
|
|
3088
3292
|
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
// set the initial caret icon
|
|
3093
|
-
Private.updateCaret(
|
|
3094
|
-
DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS),
|
|
3095
|
-
'right',
|
|
3096
|
-
'up'
|
|
3293
|
+
this._updateSortIndicator(
|
|
3294
|
+
node,
|
|
3295
|
+
sortState ?? { direction: 'ascending', key: 'name' }
|
|
3097
3296
|
);
|
|
3098
3297
|
}
|
|
3099
3298
|
|
|
@@ -3107,58 +3306,59 @@ export namespace DirListing {
|
|
|
3107
3306
|
* @returns The sort state of the header after the click event.
|
|
3108
3307
|
*/
|
|
3109
3308
|
handleHeaderClick(node: HTMLElement, event: MouseEvent): ISortState | null {
|
|
3110
|
-
const state: ISortState = { direction: 'ascending', key: 'name' };
|
|
3111
3309
|
const target = event.target as HTMLElement;
|
|
3112
|
-
|
|
3113
3310
|
const sortableColumns = DirListing.columns.filter(Private.isSortable);
|
|
3114
3311
|
|
|
3115
3312
|
for (const column of sortableColumns) {
|
|
3116
3313
|
const header = node.querySelector(`.${column.className}`);
|
|
3117
3314
|
if (!header) {
|
|
3118
|
-
// skip if the column is hidden
|
|
3119
3315
|
continue;
|
|
3120
3316
|
}
|
|
3121
3317
|
if (header.contains(target)) {
|
|
3122
|
-
|
|
3123
|
-
const
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3318
|
+
const isSelected = header.classList.contains(SELECTED_CLASS);
|
|
3319
|
+
const isDescending = header.classList.contains(DESCENDING_CLASS);
|
|
3320
|
+
let direction: 'ascending' | 'descending' = 'ascending';
|
|
3321
|
+
if (isSelected && !isDescending) {
|
|
3322
|
+
direction = 'descending';
|
|
3323
|
+
}
|
|
3324
|
+
const state: ISortState = { direction, key: column.id };
|
|
3325
|
+
this._updateSortIndicator(node, state);
|
|
3326
|
+
return state;
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
return { direction: 'ascending', key: 'name' };
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
private _updateSortIndicator(
|
|
3333
|
+
node: HTMLElement,
|
|
3334
|
+
sortState: DirListing.ISortState
|
|
3335
|
+
): void {
|
|
3336
|
+
const sortableColumns = DirListing.columns.filter(Private.isSortable);
|
|
3337
|
+
|
|
3338
|
+
for (const column of sortableColumns) {
|
|
3339
|
+
const header = node.querySelector(`.${column.className}`);
|
|
3340
|
+
if (!header) {
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
const headerIcon = DOMUtils.findElement(
|
|
3344
|
+
header as HTMLElement,
|
|
3345
|
+
HEADER_ITEM_ICON_CLASS
|
|
3346
|
+
);
|
|
3347
|
+
if (column.id === sortState.key) {
|
|
3348
|
+
header.classList.add(SELECTED_CLASS);
|
|
3349
|
+
if (sortState.direction === 'descending') {
|
|
3350
|
+
header.classList.add(DESCENDING_CLASS);
|
|
3351
|
+
Private.updateCaret(headerIcon, column.caretSide, 'down');
|
|
3136
3352
|
} else {
|
|
3137
3353
|
header.classList.remove(DESCENDING_CLASS);
|
|
3138
3354
|
Private.updateCaret(headerIcon, column.caretSide, 'up');
|
|
3139
3355
|
}
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
}
|
|
3145
|
-
const otherHeader = node.querySelector(`.${otherColumn.className}`);
|
|
3146
|
-
if (!otherHeader) {
|
|
3147
|
-
// skip if hidden
|
|
3148
|
-
continue;
|
|
3149
|
-
}
|
|
3150
|
-
otherHeader.classList.remove(SELECTED_CLASS);
|
|
3151
|
-
otherHeader.classList.remove(DESCENDING_CLASS);
|
|
3152
|
-
const otherHeaderIcon = DOMUtils.findElement(
|
|
3153
|
-
otherHeader as HTMLElement,
|
|
3154
|
-
HEADER_ITEM_ICON_CLASS
|
|
3155
|
-
);
|
|
3156
|
-
Private.updateCaret(otherHeaderIcon, otherColumn.caretSide);
|
|
3157
|
-
}
|
|
3158
|
-
return state;
|
|
3356
|
+
} else {
|
|
3357
|
+
header.classList.remove(SELECTED_CLASS);
|
|
3358
|
+
header.classList.remove(DESCENDING_CLASS);
|
|
3359
|
+
Private.updateCaret(headerIcon, column.caretSide);
|
|
3159
3360
|
}
|
|
3160
3361
|
}
|
|
3161
|
-
return state;
|
|
3162
3362
|
}
|
|
3163
3363
|
|
|
3164
3364
|
/**
|
|
@@ -3249,29 +3449,71 @@ export namespace DirListing {
|
|
|
3249
3449
|
modifiedDate: string,
|
|
3250
3450
|
modifiedStyle: Time.HumanStyle
|
|
3251
3451
|
): void {
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3452
|
+
this._updateItemDate(
|
|
3453
|
+
modified,
|
|
3454
|
+
modifiedDate,
|
|
3455
|
+
modifiedStyle,
|
|
3456
|
+
this._modifiedColumnLastUpdate
|
|
3457
|
+
);
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
/**
|
|
3461
|
+
* Update an item's created date.
|
|
3462
|
+
*
|
|
3463
|
+
* @param created - Element containing the file's created date.
|
|
3464
|
+
*
|
|
3465
|
+
* @param createdDate - String representation of the created date.
|
|
3466
|
+
*
|
|
3467
|
+
* @param createdStyle - The date style for the created column: narrow, short, or long
|
|
3468
|
+
*/
|
|
3469
|
+
updateItemCreated(
|
|
3470
|
+
created: HTMLElement,
|
|
3471
|
+
createdDate: string,
|
|
3472
|
+
createdStyle: Time.HumanStyle
|
|
3473
|
+
): void {
|
|
3474
|
+
this._updateItemDate(
|
|
3475
|
+
created,
|
|
3476
|
+
createdDate,
|
|
3477
|
+
createdStyle,
|
|
3478
|
+
this._createdColumnLastUpdate
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
/**
|
|
3483
|
+
* Shared implementation for updating a date column element.
|
|
3484
|
+
*
|
|
3485
|
+
* Formatting dates is expensive (0.1-0.2ms per call, so over 150 files
|
|
3486
|
+
* can easily already choke the renderer), so we do the bare minimum check
|
|
3487
|
+
* of comparing if an update is needed using a last update cache.
|
|
3488
|
+
*
|
|
3489
|
+
* @param element - The date column element to update.
|
|
3490
|
+
*
|
|
3491
|
+
* @param dateString - String representation of the date.
|
|
3492
|
+
*
|
|
3493
|
+
* @param style - The date style: narrow, short, or long.
|
|
3494
|
+
*
|
|
3495
|
+
* @param cache - The WeakMap cache tracking the last rendered state for
|
|
3496
|
+
* this column.
|
|
3497
|
+
*/
|
|
3498
|
+
private _updateItemDate(
|
|
3499
|
+
element: HTMLElement,
|
|
3500
|
+
dateString: string,
|
|
3501
|
+
style: Time.HumanStyle,
|
|
3502
|
+
cache: WeakMap<HTMLElement, { date: string; style: Time.HumanStyle }>
|
|
3503
|
+
): void {
|
|
3504
|
+
const previousUpdate = cache.get(element);
|
|
3257
3505
|
if (
|
|
3258
|
-
previousUpdate?.date ===
|
|
3259
|
-
previousUpdate?.style ===
|
|
3506
|
+
previousUpdate?.date === dateString &&
|
|
3507
|
+
previousUpdate?.style === style
|
|
3260
3508
|
) {
|
|
3261
3509
|
return;
|
|
3262
3510
|
}
|
|
3263
3511
|
|
|
3264
|
-
const parsedDate = new Date(
|
|
3512
|
+
const parsedDate = new Date(dateString);
|
|
3265
3513
|
// Render the date in one of multiple formats, depending on the container's size
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
modified.textContent = modText;
|
|
3270
|
-
modified.title = modTitle;
|
|
3271
|
-
this._modifiedColumnLastUpdate.set(modified, {
|
|
3272
|
-
date: modifiedDate,
|
|
3273
|
-
style: modifiedStyle
|
|
3274
|
-
});
|
|
3514
|
+
element.textContent = Time.formatHuman(parsedDate, style);
|
|
3515
|
+
element.title = Time.format(parsedDate);
|
|
3516
|
+
cache.set(element, { date: dateString, style });
|
|
3275
3517
|
}
|
|
3276
3518
|
|
|
3277
3519
|
/**
|
|
@@ -3292,7 +3534,8 @@ export namespace DirListing {
|
|
|
3292
3534
|
hiddenColumns?: Set<DirListing.ToggleableColumn>,
|
|
3293
3535
|
selected?: boolean,
|
|
3294
3536
|
modifiedStyle?: Time.HumanStyle,
|
|
3295
|
-
columnsSizes?: Record<DirListing.IColumn['id'], number | null
|
|
3537
|
+
columnsSizes?: Record<DirListing.IColumn['id'], number | null>,
|
|
3538
|
+
createdStyle?: Time.HumanStyle
|
|
3296
3539
|
): void {
|
|
3297
3540
|
if (selected) {
|
|
3298
3541
|
node.classList.add(SELECTED_CLASS);
|
|
@@ -3308,8 +3551,10 @@ export namespace DirListing {
|
|
|
3308
3551
|
name: model.name,
|
|
3309
3552
|
selected,
|
|
3310
3553
|
lastModified: model.last_modified,
|
|
3554
|
+
created: model.created,
|
|
3311
3555
|
modifiedStyle,
|
|
3312
|
-
|
|
3556
|
+
createdStyle,
|
|
3557
|
+
hiddenColumns: Array.from(hiddenColumns ?? []).sort(),
|
|
3313
3558
|
columnsSizes,
|
|
3314
3559
|
fileSize: model.size
|
|
3315
3560
|
});
|
|
@@ -3336,6 +3581,9 @@ export namespace DirListing {
|
|
|
3336
3581
|
let fileSize = DOMUtils.findElement(node, ITEM_FILE_SIZE_CLASS) as
|
|
3337
3582
|
| HTMLElement
|
|
3338
3583
|
| undefined;
|
|
3584
|
+
let created = DOMUtils.findElement(node, ITEM_CREATED_CLASS) as
|
|
3585
|
+
| HTMLElement
|
|
3586
|
+
| undefined;
|
|
3339
3587
|
|
|
3340
3588
|
const showFileCheckboxes = !hiddenColumns?.has('is_selected');
|
|
3341
3589
|
if (checkboxWrapper && !showFileCheckboxes) {
|
|
@@ -3348,17 +3596,30 @@ export namespace DirListing {
|
|
|
3348
3596
|
const showModified = !hiddenColumns?.has('last_modified');
|
|
3349
3597
|
if (modified && !showModified) {
|
|
3350
3598
|
node.removeChild(modified);
|
|
3599
|
+
modified = undefined;
|
|
3351
3600
|
} else if (showModified && !modified) {
|
|
3352
3601
|
modified = this.itemFactories.last_modified();
|
|
3353
3602
|
nameColumn.insertAdjacentElement('afterend', modified);
|
|
3354
3603
|
}
|
|
3355
3604
|
|
|
3605
|
+
const showCreated = !hiddenColumns?.has('date_created');
|
|
3606
|
+
if (created && !showCreated) {
|
|
3607
|
+
node.removeChild(created);
|
|
3608
|
+
created = undefined;
|
|
3609
|
+
} else if (showCreated && !created) {
|
|
3610
|
+
created = this.itemFactories.date_created();
|
|
3611
|
+
(modified ?? nameColumn).insertAdjacentElement('afterend', created);
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3356
3614
|
const showFileSize = !hiddenColumns?.has('file_size');
|
|
3357
3615
|
if (fileSize && !showFileSize) {
|
|
3358
3616
|
node.removeChild(fileSize);
|
|
3359
3617
|
} else if (showFileSize && !fileSize) {
|
|
3360
3618
|
fileSize = this.itemFactories.file_size();
|
|
3361
|
-
|
|
3619
|
+
const insertAfter = created ?? modified ?? nameColumn;
|
|
3620
|
+
if (insertAfter) {
|
|
3621
|
+
insertAfter.insertAdjacentElement('afterend', fileSize);
|
|
3622
|
+
}
|
|
3362
3623
|
}
|
|
3363
3624
|
|
|
3364
3625
|
// render the file item's icon
|
|
@@ -3441,7 +3702,13 @@ export namespace DirListing {
|
|
|
3441
3702
|
checkbox.checked = selected ?? false;
|
|
3442
3703
|
}
|
|
3443
3704
|
|
|
3444
|
-
this.updateItemSize(
|
|
3705
|
+
this.updateItemSize(
|
|
3706
|
+
node,
|
|
3707
|
+
model,
|
|
3708
|
+
modifiedStyle,
|
|
3709
|
+
columnsSizes,
|
|
3710
|
+
createdStyle
|
|
3711
|
+
);
|
|
3445
3712
|
}
|
|
3446
3713
|
|
|
3447
3714
|
/**
|
|
@@ -3451,7 +3718,8 @@ export namespace DirListing {
|
|
|
3451
3718
|
node: HTMLElement,
|
|
3452
3719
|
model: Contents.IModel,
|
|
3453
3720
|
modifiedStyle?: Time.HumanStyle,
|
|
3454
|
-
columnsSizes?: Record<DirListing.IColumn['id'], number | null
|
|
3721
|
+
columnsSizes?: Record<DirListing.IColumn['id'], number | null>,
|
|
3722
|
+
createdStyle?: Time.HumanStyle
|
|
3455
3723
|
): void {
|
|
3456
3724
|
if (columnsSizes) {
|
|
3457
3725
|
for (const column of columns) {
|
|
@@ -3477,6 +3745,14 @@ export namespace DirListing {
|
|
|
3477
3745
|
modifiedStyle ?? 'short'
|
|
3478
3746
|
);
|
|
3479
3747
|
}
|
|
3748
|
+
|
|
3749
|
+
let created = DOMUtils.findElement(node, ITEM_CREATED_CLASS) as
|
|
3750
|
+
| HTMLElement
|
|
3751
|
+
| undefined;
|
|
3752
|
+
|
|
3753
|
+
if (model.created && created) {
|
|
3754
|
+
this.updateItemCreated(created, model.created, createdStyle ?? 'short');
|
|
3755
|
+
}
|
|
3480
3756
|
}
|
|
3481
3757
|
|
|
3482
3758
|
/**
|
|
@@ -3581,6 +3857,11 @@ export namespace DirListing {
|
|
|
3581
3857
|
fileSize.className = ITEM_FILE_SIZE_CLASS;
|
|
3582
3858
|
return fileSize;
|
|
3583
3859
|
},
|
|
3860
|
+
date_created: () => {
|
|
3861
|
+
const created = document.createElement('span');
|
|
3862
|
+
created.className = ITEM_CREATED_CLASS;
|
|
3863
|
+
return created;
|
|
3864
|
+
},
|
|
3584
3865
|
is_selected: () => this.createCheckboxWrapperNode()
|
|
3585
3866
|
};
|
|
3586
3867
|
|
|
@@ -3631,6 +3912,14 @@ export namespace DirListing {
|
|
|
3631
3912
|
{ date: string; style: Time.HumanStyle }
|
|
3632
3913
|
>();
|
|
3633
3914
|
|
|
3915
|
+
/**
|
|
3916
|
+
* Register of most recent arguments for date created column update.
|
|
3917
|
+
*/
|
|
3918
|
+
private _createdColumnLastUpdate = new WeakMap<
|
|
3919
|
+
HTMLElement,
|
|
3920
|
+
{ date: string; style: Time.HumanStyle }
|
|
3921
|
+
>();
|
|
3922
|
+
|
|
3634
3923
|
private _lastRenderedState = new WeakMap<HTMLElement, string>();
|
|
3635
3924
|
}
|
|
3636
3925
|
|
|
@@ -3670,13 +3959,13 @@ namespace Private {
|
|
|
3670
3959
|
resolve(edit.value);
|
|
3671
3960
|
};
|
|
3672
3961
|
edit.onkeydown = (event: KeyboardEvent) => {
|
|
3673
|
-
switch (event.
|
|
3674
|
-
case
|
|
3962
|
+
switch (event.key) {
|
|
3963
|
+
case 'Enter':
|
|
3675
3964
|
event.stopPropagation();
|
|
3676
3965
|
event.preventDefault();
|
|
3677
3966
|
edit.blur();
|
|
3678
3967
|
break;
|
|
3679
|
-
case
|
|
3968
|
+
case 'Escape':
|
|
3680
3969
|
event.stopPropagation();
|
|
3681
3970
|
event.preventDefault();
|
|
3682
3971
|
edit.value = original;
|
|
@@ -3792,6 +4081,16 @@ namespace Private {
|
|
|
3792
4081
|
return (b.size ?? 0) - (a.size ?? 0);
|
|
3793
4082
|
})
|
|
3794
4083
|
);
|
|
4084
|
+
} else if (state.key === 'date_created') {
|
|
4085
|
+
// Sort by date created
|
|
4086
|
+
copy.sort(
|
|
4087
|
+
compare((a: Contents.IModel, b: Contents.IModel) => {
|
|
4088
|
+
return (
|
|
4089
|
+
new Date(a.created ?? '').getTime() -
|
|
4090
|
+
new Date(b.created ?? '').getTime()
|
|
4091
|
+
);
|
|
4092
|
+
})
|
|
4093
|
+
);
|
|
3795
4094
|
} else {
|
|
3796
4095
|
// Sort by name
|
|
3797
4096
|
copy.sort(
|