@brftech/filex-core 0.7.3 → 0.7.4
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/dist/{ArchiveViewer-xGV6PJ2d.js → ArchiveViewer-BRFvhWwL.js} +2 -2
- package/dist/{ArchiveViewer-xGV6PJ2d.js.map → ArchiveViewer-BRFvhWwL.js.map} +1 -1
- package/dist/{CsvViewer-C_VfZCbP.js → CsvViewer-1cGGKeGi.js} +2 -2
- package/dist/{CsvViewer-C_VfZCbP.js.map → CsvViewer-1cGGKeGi.js.map} +1 -1
- package/dist/{DrawioViewer--Wx7CUSa.js → DrawioViewer-C0oThT59.js} +2 -2
- package/dist/{DrawioViewer--Wx7CUSa.js.map → DrawioViewer-C0oThT59.js.map} +1 -1
- package/dist/{EpubViewer-CIlKN5rU.js → EpubViewer-DCPOD2S-.js} +2 -2
- package/dist/{EpubViewer-CIlKN5rU.js.map → EpubViewer-DCPOD2S-.js.map} +1 -1
- package/dist/{IpynbViewer-D9nID_-7.js → IpynbViewer-mv4xiv3H.js} +2 -2
- package/dist/{IpynbViewer-D9nID_-7.js.map → IpynbViewer-mv4xiv3H.js.map} +1 -1
- package/dist/{MermaidViewer-BnW75WoJ.js → MermaidViewer-BBNVghTz.js} +2 -2
- package/dist/{MermaidViewer-BnW75WoJ.js.map → MermaidViewer-BBNVghTz.js.map} +1 -1
- package/dist/{PsdViewer-yaU0b1GT.js → PsdViewer-B8ygWw-s.js} +2 -2
- package/dist/{PsdViewer-yaU0b1GT.js.map → PsdViewer-B8ygWw-s.js.map} +1 -1
- package/dist/{TiffViewer-BsLGsy10.js → TiffViewer-BhHjf3s5.js} +2 -2
- package/dist/{TiffViewer-BsLGsy10.js.map → TiffViewer-BhHjf3s5.js.map} +1 -1
- package/dist/{Viewer3D-DrVf_Uyz.js → Viewer3D-BhOJpzyG.js} +2 -2
- package/dist/{Viewer3D-DrVf_Uyz.js.map → Viewer3D-BhOJpzyG.js.map} +1 -1
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +31 -31
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/{index-BrUiOc9C.js → index-BjiUhk9K.js} +3090 -3060
- package/dist/index-BjiUhk9K.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/package.json +1 -1
- package/src/FileExplorer.vue +23 -49
- package/src/components/SecondaryPane.vue +21 -9
- package/src/lib/listing.ts +105 -0
- package/dist/index-BrUiOc9C.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,9 @@ declare type __VLS_Props_11 = {
|
|
|
48
48
|
viewMode?: ViewMode;
|
|
49
49
|
/** ui-fix — authenticated thumb resolver, forwarded to grid/gallery. */
|
|
50
50
|
thumbSrc?: (n: FileNode) => string | null;
|
|
51
|
+
/** ui-fix — mirror the main panel's virtual `.trash` row at storage root
|
|
52
|
+
* so both split panes list identical rows (no row-offset). Defaults on. */
|
|
53
|
+
trashVisible?: boolean;
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
declare type __VLS_Props_12 = {
|
|
@@ -856,9 +859,11 @@ virtualRows?: () => FileNode[];
|
|
|
856
859
|
active?: boolean;
|
|
857
860
|
viewMode?: ViewMode;
|
|
858
861
|
thumbSrc?: (n: FileNode) => string | null;
|
|
862
|
+
trashVisible?: boolean;
|
|
859
863
|
}> & Readonly<{
|
|
860
864
|
onClose?: (() => any) | undefined;
|
|
861
865
|
onNavigate?: ((path: string) => any) | undefined;
|
|
866
|
+
"onOpen-trash"?: (() => any) | undefined;
|
|
862
867
|
onActivate?: (() => any) | undefined;
|
|
863
868
|
"onOpen-tab"?: ((path: string) => any) | undefined;
|
|
864
869
|
onTransfer?: ((p: {
|
|
@@ -877,6 +882,7 @@ getPath: () => string;
|
|
|
877
882
|
}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
878
883
|
close: () => any;
|
|
879
884
|
navigate: (path: string) => any;
|
|
885
|
+
"open-trash": () => any;
|
|
880
886
|
activate: () => any;
|
|
881
887
|
"open-tab": (path: string) => any;
|
|
882
888
|
transfer: (p: {
|
|
@@ -906,9 +912,11 @@ virtualRows?: () => FileNode[];
|
|
|
906
912
|
active?: boolean;
|
|
907
913
|
viewMode?: ViewMode;
|
|
908
914
|
thumbSrc?: (n: FileNode) => string | null;
|
|
915
|
+
trashVisible?: boolean;
|
|
909
916
|
}> & Readonly<{
|
|
910
917
|
onClose?: (() => any) | undefined;
|
|
911
918
|
onNavigate?: ((path: string) => any) | undefined;
|
|
919
|
+
"onOpen-trash"?: (() => any) | undefined;
|
|
912
920
|
onActivate?: (() => any) | undefined;
|
|
913
921
|
"onOpen-tab"?: ((path: string) => any) | undefined;
|
|
914
922
|
onTransfer?: ((p: {
|
|
@@ -1373,6 +1381,7 @@ getPath: typeof getPath;
|
|
|
1373
1381
|
}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
|
|
1374
1382
|
close: () => any;
|
|
1375
1383
|
navigate: (path: string) => any;
|
|
1384
|
+
"open-trash": () => any;
|
|
1376
1385
|
activate: () => any;
|
|
1377
1386
|
"open-tab": (path: string) => any;
|
|
1378
1387
|
transfer: (p: {
|
|
@@ -1384,6 +1393,7 @@ context: (node: FileNode | null, ev: MouseEvent) => any;
|
|
|
1384
1393
|
}, string, PublicProps, Readonly<__VLS_Props_11> & Readonly<{
|
|
1385
1394
|
onClose?: (() => any) | undefined;
|
|
1386
1395
|
onNavigate?: ((path: string) => any) | undefined;
|
|
1396
|
+
"onOpen-trash"?: (() => any) | undefined;
|
|
1387
1397
|
onActivate?: (() => any) | undefined;
|
|
1388
1398
|
"onOpen-tab"?: ((path: string) => any) | undefined;
|
|
1389
1399
|
onTransfer?: ((p: {
|
package/package.json
CHANGED
package/src/FileExplorer.vue
CHANGED
|
@@ -86,6 +86,15 @@ import {
|
|
|
86
86
|
} from './lib/e2ecrypto';
|
|
87
87
|
/* /wiring:e2 */
|
|
88
88
|
|
|
89
|
+
/* ui-fix — listing helpers shared with SecondaryPane (single source: the
|
|
90
|
+
internal-entry filter + virtual `.trash` row must be identical in both
|
|
91
|
+
panes or split view shows mismatched rows). */
|
|
92
|
+
import {
|
|
93
|
+
filterInternalEntries,
|
|
94
|
+
injectTrashRow,
|
|
95
|
+
hydrateTrashRow as hydrateTrashRowShared,
|
|
96
|
+
} from './lib/listing';
|
|
97
|
+
|
|
89
98
|
import NewFolderModal from './modals/NewFolderModal.vue';
|
|
90
99
|
import RenameModal from './modals/RenameModal.vue';
|
|
91
100
|
import DeleteConfirmModal from './modals/DeleteConfirmModal.vue';
|
|
@@ -707,33 +716,11 @@ async function load(path?: string) {
|
|
|
707
716
|
subtree; '' resets on every plain folder. Drives the lock screen. */
|
|
708
717
|
e2eRoot.value = typeof resp.e2e_root === 'string' ? resp.e2e_root : '';
|
|
709
718
|
/* /wiring:e2 */
|
|
710
|
-
files.value = (resp.files
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (f.basename === E2E_MARKER_NAME) return false; /* wiring:e2 — marker gizli */
|
|
716
|
-
return true;
|
|
717
|
-
});
|
|
718
|
-
// Inject virtual `.trash` entry at root only.
|
|
719
|
-
const dirRel = stripAdapter(resp.dirname);
|
|
720
|
-
const inRoot = dirRel === 'fileman' || dirRel === '';
|
|
721
|
-
const isTrashListing = dirRel.startsWith('fileman/.trash');
|
|
722
|
-
const trashEntryEnabled = props.config.trashVisible !== false;
|
|
723
|
-
if (!isTrashListing && inRoot && trashEntryEnabled) {
|
|
724
|
-
files.value.unshift({
|
|
725
|
-
type: 'dir',
|
|
726
|
-
path: `${resp.adapter}://fileman/.trash`,
|
|
727
|
-
basename: '.trash',
|
|
728
|
-
extension: '',
|
|
729
|
-
storage: resp.adapter,
|
|
730
|
-
visibility: 'private',
|
|
731
|
-
size: 0,
|
|
732
|
-
file_size: 0,
|
|
733
|
-
mime_type: 'inode/directory',
|
|
734
|
-
extra_metadata: {},
|
|
735
|
-
} as unknown as FileNode);
|
|
736
|
-
void hydrateTrashRow(resp.adapter);
|
|
719
|
+
files.value = filterInternalEntries(resp.files);
|
|
720
|
+
// Inject virtual `.trash` entry at root only — shared helper so the
|
|
721
|
+
// split-view secondary pane shows the exact same row (no row-offset).
|
|
722
|
+
if (injectTrashRow(files.value, resp.adapter, resp.dirname, props.config.trashVisible !== false)) {
|
|
723
|
+
void hydrateTrashRowShared(files.value, resp.adapter, api);
|
|
737
724
|
}
|
|
738
725
|
// currentPath is the user-facing form: `s3-test/example` in
|
|
739
726
|
// multi-storage mode, the bare relative path otherwise.
|
|
@@ -784,28 +771,7 @@ function leaveNotFound() {
|
|
|
784
771
|
void load('');
|
|
785
772
|
}
|
|
786
773
|
|
|
787
|
-
// hydrateTrashRow
|
|
788
|
-
// fill both from the backend trash listing (total bytes + newest deletion) so
|
|
789
|
-
// the row reads like a real folder instead of "— / —". Best-effort and
|
|
790
|
-
// non-blocking: the row appears immediately and updates when the listing lands.
|
|
791
|
-
async function hydrateTrashRow(storage: string) {
|
|
792
|
-
try {
|
|
793
|
-
const { entries } = await api.listTrash(storage);
|
|
794
|
-
const row = files.value.find((f) => f.basename === '.trash');
|
|
795
|
-
if (!row) return;
|
|
796
|
-
let total = 0;
|
|
797
|
-
let newest = 0;
|
|
798
|
-
for (const e of entries) {
|
|
799
|
-
total += e.size || 0;
|
|
800
|
-
const ts = Date.parse(e.deleted_at);
|
|
801
|
-
if (!Number.isNaN(ts) && ts > newest) newest = ts;
|
|
802
|
-
}
|
|
803
|
-
row.size = total;
|
|
804
|
-
if (newest > 0) row.last_modified = newest;
|
|
805
|
-
} catch {
|
|
806
|
-
/* keep the bare row */
|
|
807
|
-
}
|
|
808
|
-
}
|
|
774
|
+
// (hydrateTrashRow moved to lib/listing.ts — shared with SecondaryPane.)
|
|
809
775
|
|
|
810
776
|
// loadTrash — show the backend trash (soft-deleted nodes) as a flat listing.
|
|
811
777
|
// Entered by opening the virtual `.trash` row. Each row keeps its node `id`
|
|
@@ -2616,6 +2582,12 @@ function closeSplit() {
|
|
|
2616
2582
|
function onPaneNavigate(p: string) {
|
|
2617
2583
|
tabsApi.setSplit({ ...(activeSplit.value ?? {}), path: p });
|
|
2618
2584
|
}
|
|
2585
|
+
/* ui-fix — çöp kutusu satırı yan panelde açılınca: trash görünümü (geri
|
|
2586
|
+
* yükleme aksiyonlarıyla) ana panele aittir → ana paneli aktif edip aç. */
|
|
2587
|
+
function onPaneOpenTrash() {
|
|
2588
|
+
activePane.value = 'main';
|
|
2589
|
+
void loadTrash();
|
|
2590
|
+
}
|
|
2619
2591
|
/* ui-fix — pane'in KENDİ görünüm modu: split açılırken ana panelinkini
|
|
2620
2592
|
* devralır, sonrasında bağımsız. Toolbar'ın görünüm değiştiricisi ve palet
|
|
2621
2593
|
* toggle'ı AKTİF panele yazar (Burak: "B tıklıyken ikon değiştir dersem
|
|
@@ -3349,12 +3321,14 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3349
3321
|
:active="paneIsActive"
|
|
3350
3322
|
:view-mode="paneViewMode /* ui-fix */"
|
|
3351
3323
|
:thumb-src="thumbs.src /* ui-fix */"
|
|
3324
|
+
:trash-visible="config.trashVisible !== false /* ui-fix — çöp satırı simetrisi */"
|
|
3352
3325
|
@navigate="onPaneNavigate"
|
|
3353
3326
|
@activate="activePane = 'split'"
|
|
3354
3327
|
@close="closeSplit"
|
|
3355
3328
|
@open-tab="(p: string) => tabsApi.openTab(p, { viewMode: viewMode, background: true })"
|
|
3356
3329
|
@transfer="onPaneTransfer"
|
|
3357
3330
|
@context="onPaneContext /* ui-fix — yan panel sağ-tık menüsü */"
|
|
3331
|
+
@open-trash="onPaneOpenTrash /* ui-fix — çöp kutusu ana panelde açılır */"
|
|
3358
3332
|
/>
|
|
3359
3333
|
<!-- /wiring:d1 -->
|
|
3360
3334
|
|
|
@@ -21,6 +21,7 @@ import type { FileApi } from '../composables/useFileApi';
|
|
|
21
21
|
import type { FileNode, ViewMode } from '../types/FileNode';
|
|
22
22
|
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
23
23
|
import { useLocale } from '../composables/useLocale';
|
|
24
|
+
import { filterInternalEntries, injectTrashRow, hydrateTrashRow } from '../lib/listing';
|
|
24
25
|
import ListView from './ListView.vue';
|
|
25
26
|
import GridView from './GridView.vue';
|
|
26
27
|
import GalleryView from './GalleryView.vue';
|
|
@@ -57,6 +58,9 @@ const props = defineProps<{
|
|
|
57
58
|
viewMode?: ViewMode;
|
|
58
59
|
/** ui-fix — authenticated thumb resolver, forwarded to grid/gallery. */
|
|
59
60
|
thumbSrc?: (n: FileNode) => string | null;
|
|
61
|
+
/** ui-fix — mirror the main panel's virtual `.trash` row at storage root
|
|
62
|
+
* so both split panes list identical rows (no row-offset). Defaults on. */
|
|
63
|
+
trashVisible?: boolean;
|
|
60
64
|
}>();
|
|
61
65
|
|
|
62
66
|
const emit = defineEmits<{
|
|
@@ -68,6 +72,9 @@ const emit = defineEmits<{
|
|
|
68
72
|
/* ui-fix — yan panelde sağ-tık: menü ana bileşende (FileExplorer) açılır.
|
|
69
73
|
* node=null → boş alana sağ-tık (seçimsiz, yalnız Yapıştır). */
|
|
70
74
|
(e: 'context', node: FileNode | null, ev: MouseEvent): void;
|
|
75
|
+
/* ui-fix — çöp kutusu satırı açıldığında: trash görünümü ana panele aittir
|
|
76
|
+
* (geri yükleme aksiyonlarıyla), host loadTrash() ile açar. */
|
|
77
|
+
(e: 'open-trash'): void;
|
|
71
78
|
}>();
|
|
72
79
|
|
|
73
80
|
const { t } = useLocale(() => props.locale);
|
|
@@ -101,14 +108,12 @@ async function loadPane(target?: string): Promise<void> {
|
|
|
101
108
|
return;
|
|
102
109
|
}
|
|
103
110
|
const resp = await props.api.index(props.qualify(requested));
|
|
104
|
-
// Same internal-entry filter as the main
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return true;
|
|
111
|
-
});
|
|
111
|
+
// Same internal-entry filter + virtual `.trash` row as the main panel
|
|
112
|
+
// (shared helpers → both split panes list identical rows, no offset).
|
|
113
|
+
files.value = filterInternalEntries(resp.files);
|
|
114
|
+
if (injectTrashRow(files.value, resp.adapter, resp.dirname, props.trashVisible !== false)) {
|
|
115
|
+
void hydrateTrashRow(files.value, resp.adapter, props.api);
|
|
116
|
+
}
|
|
112
117
|
path.value = props.toUser(resp.dirname);
|
|
113
118
|
selected.value = new Set();
|
|
114
119
|
emit('navigate', path.value);
|
|
@@ -204,6 +209,13 @@ function onViewDropInto(target: FileNode, ev: DragEvent) {
|
|
|
204
209
|
|
|
205
210
|
function onRowDbl(n: FileNode) {
|
|
206
211
|
if (n.type !== 'dir') return;
|
|
212
|
+
// The virtual `.trash` row opens the backend trash listing (restore
|
|
213
|
+
// actions) — that view lives in the main panel; let the host open it.
|
|
214
|
+
if (n.basename === '.trash') {
|
|
215
|
+
emit('activate');
|
|
216
|
+
emit('open-trash');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
207
219
|
void loadPane(isStorageRow(n) ? n.path : props.toUser(n.path));
|
|
208
220
|
}
|
|
209
221
|
|
|
@@ -217,7 +229,7 @@ function clearSelection() {
|
|
|
217
229
|
|
|
218
230
|
function onRowDragStart(n: FileNode, ev: DragEvent) {
|
|
219
231
|
if (!ev.dataTransfer) return;
|
|
220
|
-
if (isStorageRow(n) || atVirtualRoot.value) {
|
|
232
|
+
if (isStorageRow(n) || atVirtualRoot.value || n.basename === '.trash') {
|
|
221
233
|
ev.preventDefault();
|
|
222
234
|
return;
|
|
223
235
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* listing.ts — shared listing helpers used by BOTH the main panel
|
|
3
|
+
* (FileExplorer.load) and the split-view secondary panel
|
|
4
|
+
* (SecondaryPane.loadPane). SINGLE SOURCE so the two panes can never
|
|
5
|
+
* drift apart: the internal-entry filter and the virtual `.trash` row
|
|
6
|
+
* must be identical in both, otherwise split view shows mismatched rows
|
|
7
|
+
* (the trash row missing on one side → visible row-offset).
|
|
8
|
+
*/
|
|
9
|
+
import type { FileNode } from '../types/FileNode';
|
|
10
|
+
import { E2E_MARKER_NAME } from './e2ecrypto';
|
|
11
|
+
|
|
12
|
+
/** Adapter-strip: `s3-test://fileman/x` → `fileman/x`. */
|
|
13
|
+
export function stripAdapter(p: string): string {
|
|
14
|
+
const idx = p.indexOf('://');
|
|
15
|
+
return idx === -1 ? p : p.slice(idx + 3);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hide system/internal entries the user must never see as files:
|
|
20
|
+
* thumbnails, version history, the soft-delete store, keepdir markers
|
|
21
|
+
* and the E2E marker. Shared by both panes' listing filters.
|
|
22
|
+
*/
|
|
23
|
+
export function filterInternalEntries(files: FileNode[]): FileNode[] {
|
|
24
|
+
return (files || []).filter((f) => {
|
|
25
|
+
if (f.path.includes('.thumbs')) return false;
|
|
26
|
+
if (f.path.includes('.versions') || f.basename === '.versions') return false;
|
|
27
|
+
if (f.basename === '.trash') return false;
|
|
28
|
+
if (f.basename === '.keepdir') return false;
|
|
29
|
+
if (f.basename === E2E_MARKER_NAME) return false;
|
|
30
|
+
return true;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** True when `dirname` (adapter-qualified) is the storage root, where the
|
|
35
|
+
* virtual trash row belongs. */
|
|
36
|
+
export function isStorageRootDir(dirname: string): boolean {
|
|
37
|
+
const rel = stripAdapter(dirname);
|
|
38
|
+
return rel === 'fileman' || rel === '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** True when the listing IS the trash view (don't inject the row into itself). */
|
|
42
|
+
export function isTrashListing(dirname: string): boolean {
|
|
43
|
+
return stripAdapter(dirname).startsWith('fileman/.trash');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The synthetic `.trash` row shown at storage root — rendered as
|
|
47
|
+
* "Çöp Kutusu" / "Trash" via the `.trash` basename → locale mapping. */
|
|
48
|
+
export function makeTrashRow(adapter: string): FileNode {
|
|
49
|
+
return {
|
|
50
|
+
type: 'dir',
|
|
51
|
+
path: `${adapter}://fileman/.trash`,
|
|
52
|
+
basename: '.trash',
|
|
53
|
+
extension: '',
|
|
54
|
+
storage: adapter,
|
|
55
|
+
visibility: 'private',
|
|
56
|
+
size: 0,
|
|
57
|
+
file_size: 0,
|
|
58
|
+
mime_type: 'inode/directory',
|
|
59
|
+
extra_metadata: {},
|
|
60
|
+
} as unknown as FileNode;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Inject the virtual `.trash` row at the front of a root listing when
|
|
65
|
+
* enabled. Returns true if a row was added (so the caller can hydrate
|
|
66
|
+
* it). Mutates `files` in place. Single source for both panes.
|
|
67
|
+
*/
|
|
68
|
+
export function injectTrashRow(
|
|
69
|
+
files: FileNode[],
|
|
70
|
+
adapter: string,
|
|
71
|
+
dirname: string,
|
|
72
|
+
trashVisible: boolean,
|
|
73
|
+
): boolean {
|
|
74
|
+
if (!trashVisible) return false;
|
|
75
|
+
if (isTrashListing(dirname)) return false;
|
|
76
|
+
if (!isStorageRootDir(dirname)) return false;
|
|
77
|
+
files.unshift(makeTrashRow(adapter));
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Best-effort fill of the trash row's size (total bytes) + date (newest
|
|
82
|
+
* deletion) from the backend trash listing, so it reads like a real
|
|
83
|
+
* folder instead of "— / —". Non-blocking; mutates the row in place. */
|
|
84
|
+
export async function hydrateTrashRow(
|
|
85
|
+
files: FileNode[],
|
|
86
|
+
storage: string,
|
|
87
|
+
api: { listTrash: (s?: string) => Promise<{ entries: Array<{ size?: number; deleted_at: string }> }> },
|
|
88
|
+
): Promise<void> {
|
|
89
|
+
try {
|
|
90
|
+
const { entries } = await api.listTrash(storage);
|
|
91
|
+
const row = files.find((f) => f.basename === '.trash');
|
|
92
|
+
if (!row) return;
|
|
93
|
+
let total = 0;
|
|
94
|
+
let newest = 0;
|
|
95
|
+
for (const e of entries) {
|
|
96
|
+
total += e.size || 0;
|
|
97
|
+
const ts = Date.parse(e.deleted_at);
|
|
98
|
+
if (!Number.isNaN(ts) && ts > newest) newest = ts;
|
|
99
|
+
}
|
|
100
|
+
row.size = total;
|
|
101
|
+
if (newest > 0) row.last_modified = newest;
|
|
102
|
+
} catch {
|
|
103
|
+
/* keep the bare row */
|
|
104
|
+
}
|
|
105
|
+
}
|