@brftech/filex-core 0.1.56 → 0.1.61
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/LICENSE +21 -21
- package/README.md +108 -108
- package/dist/{ArchiveViewer-CJdaw5x3.js → ArchiveViewer-CbUUEREI.js} +20 -20
- package/dist/ArchiveViewer-CbUUEREI.js.map +1 -0
- package/dist/{CsvViewer-QYx_tePQ.js → CsvViewer-72rDsQJX.js} +3 -3
- package/dist/CsvViewer-72rDsQJX.js.map +1 -0
- package/dist/{DrawioViewer-GWt0TyB7.js → DrawioViewer-DSyMwGvg.js} +7 -7
- package/dist/DrawioViewer-DSyMwGvg.js.map +1 -0
- package/dist/{EpubViewer-Je0ywklQ.js → EpubViewer-DvDa17d7.js} +3 -3
- package/dist/EpubViewer-DvDa17d7.js.map +1 -0
- package/dist/{IpynbViewer-TsB4gqP-.js → IpynbViewer-DibIkdsz.js} +3 -3
- package/dist/IpynbViewer-DibIkdsz.js.map +1 -0
- package/dist/{MermaidViewer-CXfeDS-7.js → MermaidViewer-BoFLtGvl.js} +3 -3
- package/dist/MermaidViewer-BoFLtGvl.js.map +1 -0
- package/dist/{PsdViewer-VtPpxKVe.js → PsdViewer-K2iZkwax.js} +3 -3
- package/dist/PsdViewer-K2iZkwax.js.map +1 -0
- package/dist/{TiffViewer-OINn9bbb.js → TiffViewer-JzDzbqpO.js} +3 -3
- package/dist/TiffViewer-JzDzbqpO.js.map +1 -0
- package/dist/{Viewer3D-xvvyiWHd.js → Viewer3D-WEMYCP8l.js} +7 -7
- package/dist/Viewer3D-WEMYCP8l.js.map +1 -0
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +4 -4
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/{index-CmSYzt6L.js → index-B5YT-m5Q.js} +209 -209
- package/dist/index-B5YT-m5Q.js.map +1 -0
- package/dist/style.css +1 -1
- package/dist/useViewerFetch-czqbd2Lj.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Breadcrumb.vue +282 -282
- package/src/components/ContextMenu.vue +152 -152
- package/src/components/GridView.vue +166 -166
- package/src/components/ListView.vue +212 -212
- package/src/components/PendingOpsTray.vue +91 -91
- package/src/components/RecentlyOpened.vue +155 -155
- package/src/components/StarButton.vue +101 -101
- package/src/components/TagPicker.vue +184 -184
- package/src/components/Toolbar.vue +220 -220
- package/src/components/UploadProgress.vue +78 -78
- package/src/composables/useFileApi.ts +692 -692
- package/src/composables/useKeyboardShortcuts.ts +144 -144
- package/src/composables/useLocale.ts +60 -60
- package/src/composables/useMonacoLoader.ts +98 -98
- package/src/composables/usePendingOps.ts +200 -200
- package/src/composables/useSelection.ts +81 -81
- package/src/composables/useUploadChunked.ts +250 -250
- package/src/composables/useViewerFetch.ts +58 -58
- package/src/index.ts +80 -80
- package/src/locales/en.ts +122 -122
- package/src/locales/index.ts +12 -12
- package/src/locales/tr.ts +122 -122
- package/src/modals/DeleteConfirmModal.vue +32 -32
- package/src/modals/Modal.vue +104 -104
- package/src/modals/NewFolderModal.vue +64 -64
- package/src/modals/PermissionsModal.vue +815 -815
- package/src/modals/PreviewModal.vue +1070 -1070
- package/src/modals/RenameModal.vue +58 -58
- package/src/modals/ShareModal.vue +135 -135
- package/src/styles/base.css +1224 -1224
- package/src/styles/variables.css +90 -90
- package/src/types/FileNode.ts +136 -136
- package/src/types/index.ts +27 -27
- package/src/types/peers.d.ts +45 -45
- package/src/viewers/ArchiveViewer.vue +186 -186
- package/src/viewers/CsvViewer.vue +319 -319
- package/src/viewers/DrawioViewer.vue +243 -243
- package/src/viewers/EpubViewer.vue +307 -307
- package/src/viewers/IpynbViewer.vue +365 -365
- package/src/viewers/MermaidViewer.vue +259 -259
- package/src/viewers/PdfViewer.vue +562 -562
- package/src/viewers/PsdViewer.vue +264 -264
- package/src/viewers/TiffViewer.vue +273 -273
- package/src/viewers/Viewer3D.vue +120 -120
- package/dist/ArchiveViewer-CJdaw5x3.js.map +0 -1
- package/dist/CsvViewer-QYx_tePQ.js.map +0 -1
- package/dist/DrawioViewer-GWt0TyB7.js.map +0 -1
- package/dist/EpubViewer-Je0ywklQ.js.map +0 -1
- package/dist/IpynbViewer-TsB4gqP-.js.map +0 -1
- package/dist/MermaidViewer-CXfeDS-7.js.map +0 -1
- package/dist/PsdViewer-VtPpxKVe.js.map +0 -1
- package/dist/TiffViewer-OINn9bbb.js.map +0 -1
- package/dist/Viewer3D-xvvyiWHd.js.map +0 -1
- package/dist/index-CmSYzt6L.js.map +0 -1
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useKeyboardShortcuts — binds FileExplorer's keyboard affordances.
|
|
3
|
-
*
|
|
4
|
-
* Activated whenever the explorer is mounted (only one instance per
|
|
5
|
-
* page is expected). Skips events that originate inside form controls
|
|
6
|
-
* so the user can type filenames in the search box, modals, etc.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { onMounted, onBeforeUnmount, type Ref } from 'vue';
|
|
10
|
-
|
|
11
|
-
export interface ShortcutHandlers {
|
|
12
|
-
onDelete?: () => void;
|
|
13
|
-
onRename?: () => void; // F2
|
|
14
|
-
onSelectAll?: () => void; // Ctrl+A
|
|
15
|
-
onCut?: () => void; // Ctrl+X
|
|
16
|
-
onCopy?: () => void; // Ctrl+C
|
|
17
|
-
onPaste?: () => void; // Ctrl+V
|
|
18
|
-
onOpen?: () => void; // Enter
|
|
19
|
-
onClose?: () => void; // Escape
|
|
20
|
-
onFocusSearch?: () => void; // /
|
|
21
|
-
onDuplicate?: () => void; // Ctrl+D
|
|
22
|
-
onPathJump?: () => void; // Cmd+K / Ctrl+K
|
|
23
|
-
onGoUp?: () => void; // Alt+Up / Backspace (when nothing selected)
|
|
24
|
-
hasSelection?: () => boolean; // disambiguates Backspace
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function useKeyboardShortcuts(rootEl: Ref<HTMLElement | null>, handlers: ShortcutHandlers) {
|
|
28
|
-
function onKey(e: KeyboardEvent) {
|
|
29
|
-
const root = rootEl.value;
|
|
30
|
-
if (!root) return;
|
|
31
|
-
|
|
32
|
-
// Skip when the event originates inside a form control — don't
|
|
33
|
-
// want `Delete` while editing a filename, `/` while typing in the
|
|
34
|
-
// search box, etc. Escape always goes through so modals can close.
|
|
35
|
-
const target = e.target as HTMLElement | null;
|
|
36
|
-
if (
|
|
37
|
-
target &&
|
|
38
|
-
(target.tagName === 'INPUT' ||
|
|
39
|
-
target.tagName === 'TEXTAREA' ||
|
|
40
|
-
target.isContentEditable)
|
|
41
|
-
) {
|
|
42
|
-
if (e.key !== 'Escape') return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const ctrl = e.ctrlKey || e.metaKey;
|
|
46
|
-
|
|
47
|
-
switch (e.key) {
|
|
48
|
-
case 'Delete':
|
|
49
|
-
if (handlers.onDelete) {
|
|
50
|
-
e.preventDefault();
|
|
51
|
-
handlers.onDelete();
|
|
52
|
-
}
|
|
53
|
-
break;
|
|
54
|
-
case 'Backspace':
|
|
55
|
-
// Backspace = delete when something is selected (file-manager
|
|
56
|
-
// convention), parent-dir navigation otherwise. Without this
|
|
57
|
-
// disambiguation Backspace was firing onDelete with an empty
|
|
58
|
-
// selection and nothing happened, leaving users wondering why
|
|
59
|
-
// the obvious 'go back' key did nothing.
|
|
60
|
-
if (handlers.hasSelection?.() && handlers.onDelete) {
|
|
61
|
-
e.preventDefault();
|
|
62
|
-
handlers.onDelete();
|
|
63
|
-
} else if (handlers.onGoUp) {
|
|
64
|
-
e.preventDefault();
|
|
65
|
-
handlers.onGoUp();
|
|
66
|
-
}
|
|
67
|
-
break;
|
|
68
|
-
case 'F2':
|
|
69
|
-
if (handlers.onRename) {
|
|
70
|
-
e.preventDefault();
|
|
71
|
-
handlers.onRename();
|
|
72
|
-
}
|
|
73
|
-
break;
|
|
74
|
-
case 'Enter':
|
|
75
|
-
if (handlers.onOpen) {
|
|
76
|
-
handlers.onOpen();
|
|
77
|
-
}
|
|
78
|
-
break;
|
|
79
|
-
case 'Escape':
|
|
80
|
-
if (handlers.onClose) {
|
|
81
|
-
handlers.onClose();
|
|
82
|
-
}
|
|
83
|
-
break;
|
|
84
|
-
case '/':
|
|
85
|
-
if (handlers.onFocusSearch && !ctrl) {
|
|
86
|
-
e.preventDefault();
|
|
87
|
-
handlers.onFocusSearch();
|
|
88
|
-
}
|
|
89
|
-
break;
|
|
90
|
-
case 'a':
|
|
91
|
-
case 'A':
|
|
92
|
-
if (ctrl && handlers.onSelectAll) {
|
|
93
|
-
e.preventDefault();
|
|
94
|
-
handlers.onSelectAll();
|
|
95
|
-
}
|
|
96
|
-
break;
|
|
97
|
-
case 'x':
|
|
98
|
-
case 'X':
|
|
99
|
-
if (ctrl && handlers.onCut) {
|
|
100
|
-
e.preventDefault();
|
|
101
|
-
handlers.onCut();
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
case 'c':
|
|
105
|
-
case 'C':
|
|
106
|
-
if (ctrl && handlers.onCopy) {
|
|
107
|
-
e.preventDefault();
|
|
108
|
-
handlers.onCopy();
|
|
109
|
-
}
|
|
110
|
-
break;
|
|
111
|
-
case 'v':
|
|
112
|
-
case 'V':
|
|
113
|
-
if (ctrl && handlers.onPaste) {
|
|
114
|
-
e.preventDefault();
|
|
115
|
-
handlers.onPaste();
|
|
116
|
-
}
|
|
117
|
-
break;
|
|
118
|
-
case 'd':
|
|
119
|
-
case 'D':
|
|
120
|
-
if (ctrl && handlers.onDuplicate) {
|
|
121
|
-
e.preventDefault();
|
|
122
|
-
handlers.onDuplicate();
|
|
123
|
-
}
|
|
124
|
-
break;
|
|
125
|
-
case 'k':
|
|
126
|
-
case 'K':
|
|
127
|
-
if (ctrl && handlers.onPathJump) {
|
|
128
|
-
e.preventDefault();
|
|
129
|
-
handlers.onPathJump();
|
|
130
|
-
}
|
|
131
|
-
break;
|
|
132
|
-
case 'ArrowUp':
|
|
133
|
-
// Alt+Up = parent dir (matches Finder / Files / Explorer).
|
|
134
|
-
if (e.altKey && handlers.onGoUp) {
|
|
135
|
-
e.preventDefault();
|
|
136
|
-
handlers.onGoUp();
|
|
137
|
-
}
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
onMounted(() => window.addEventListener('keydown', onKey));
|
|
143
|
-
onBeforeUnmount(() => window.removeEventListener('keydown', onKey));
|
|
144
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* useKeyboardShortcuts — binds FileExplorer's keyboard affordances.
|
|
3
|
+
*
|
|
4
|
+
* Activated whenever the explorer is mounted (only one instance per
|
|
5
|
+
* page is expected). Skips events that originate inside form controls
|
|
6
|
+
* so the user can type filenames in the search box, modals, etc.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { onMounted, onBeforeUnmount, type Ref } from 'vue';
|
|
10
|
+
|
|
11
|
+
export interface ShortcutHandlers {
|
|
12
|
+
onDelete?: () => void;
|
|
13
|
+
onRename?: () => void; // F2
|
|
14
|
+
onSelectAll?: () => void; // Ctrl+A
|
|
15
|
+
onCut?: () => void; // Ctrl+X
|
|
16
|
+
onCopy?: () => void; // Ctrl+C
|
|
17
|
+
onPaste?: () => void; // Ctrl+V
|
|
18
|
+
onOpen?: () => void; // Enter
|
|
19
|
+
onClose?: () => void; // Escape
|
|
20
|
+
onFocusSearch?: () => void; // /
|
|
21
|
+
onDuplicate?: () => void; // Ctrl+D
|
|
22
|
+
onPathJump?: () => void; // Cmd+K / Ctrl+K
|
|
23
|
+
onGoUp?: () => void; // Alt+Up / Backspace (when nothing selected)
|
|
24
|
+
hasSelection?: () => boolean; // disambiguates Backspace
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useKeyboardShortcuts(rootEl: Ref<HTMLElement | null>, handlers: ShortcutHandlers) {
|
|
28
|
+
function onKey(e: KeyboardEvent) {
|
|
29
|
+
const root = rootEl.value;
|
|
30
|
+
if (!root) return;
|
|
31
|
+
|
|
32
|
+
// Skip when the event originates inside a form control — don't
|
|
33
|
+
// want `Delete` while editing a filename, `/` while typing in the
|
|
34
|
+
// search box, etc. Escape always goes through so modals can close.
|
|
35
|
+
const target = e.target as HTMLElement | null;
|
|
36
|
+
if (
|
|
37
|
+
target &&
|
|
38
|
+
(target.tagName === 'INPUT' ||
|
|
39
|
+
target.tagName === 'TEXTAREA' ||
|
|
40
|
+
target.isContentEditable)
|
|
41
|
+
) {
|
|
42
|
+
if (e.key !== 'Escape') return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ctrl = e.ctrlKey || e.metaKey;
|
|
46
|
+
|
|
47
|
+
switch (e.key) {
|
|
48
|
+
case 'Delete':
|
|
49
|
+
if (handlers.onDelete) {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
handlers.onDelete();
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
case 'Backspace':
|
|
55
|
+
// Backspace = delete when something is selected (file-manager
|
|
56
|
+
// convention), parent-dir navigation otherwise. Without this
|
|
57
|
+
// disambiguation Backspace was firing onDelete with an empty
|
|
58
|
+
// selection and nothing happened, leaving users wondering why
|
|
59
|
+
// the obvious 'go back' key did nothing.
|
|
60
|
+
if (handlers.hasSelection?.() && handlers.onDelete) {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
handlers.onDelete();
|
|
63
|
+
} else if (handlers.onGoUp) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
handlers.onGoUp();
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case 'F2':
|
|
69
|
+
if (handlers.onRename) {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
handlers.onRename();
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case 'Enter':
|
|
75
|
+
if (handlers.onOpen) {
|
|
76
|
+
handlers.onOpen();
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
case 'Escape':
|
|
80
|
+
if (handlers.onClose) {
|
|
81
|
+
handlers.onClose();
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case '/':
|
|
85
|
+
if (handlers.onFocusSearch && !ctrl) {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
handlers.onFocusSearch();
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
case 'a':
|
|
91
|
+
case 'A':
|
|
92
|
+
if (ctrl && handlers.onSelectAll) {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
handlers.onSelectAll();
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case 'x':
|
|
98
|
+
case 'X':
|
|
99
|
+
if (ctrl && handlers.onCut) {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
handlers.onCut();
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case 'c':
|
|
105
|
+
case 'C':
|
|
106
|
+
if (ctrl && handlers.onCopy) {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
handlers.onCopy();
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case 'v':
|
|
112
|
+
case 'V':
|
|
113
|
+
if (ctrl && handlers.onPaste) {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
handlers.onPaste();
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case 'd':
|
|
119
|
+
case 'D':
|
|
120
|
+
if (ctrl && handlers.onDuplicate) {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
handlers.onDuplicate();
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
case 'k':
|
|
126
|
+
case 'K':
|
|
127
|
+
if (ctrl && handlers.onPathJump) {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
handlers.onPathJump();
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
case 'ArrowUp':
|
|
133
|
+
// Alt+Up = parent dir (matches Finder / Files / Explorer).
|
|
134
|
+
if (e.altKey && handlers.onGoUp) {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
handlers.onGoUp();
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
onMounted(() => window.addEventListener('keydown', onKey));
|
|
143
|
+
onBeforeUnmount(() => window.removeEventListener('keydown', onKey));
|
|
144
|
+
}
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useLocale — tr/en string table with a tiny `t()` helper.
|
|
3
|
-
*
|
|
4
|
-
* No i18n library — the catalogue is small enough to ship inline (see
|
|
5
|
-
* src/locales).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { computed, type Ref } from 'vue';
|
|
9
|
-
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
10
|
-
import { messages } from '../locales';
|
|
11
|
-
|
|
12
|
-
export function useLocale(localeRef: Ref<LocaleCode> | (() => LocaleCode)) {
|
|
13
|
-
const lookup = computed(() => {
|
|
14
|
-
const code = typeof localeRef === 'function' ? localeRef() : localeRef.value;
|
|
15
|
-
return messages[code] ?? messages.tr;
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
function t(key: string, vars: Record<string, string | number> = {}): string {
|
|
19
|
-
const raw = lookup.value[key] ?? key;
|
|
20
|
-
return Object.entries(vars).reduce(
|
|
21
|
-
(acc, [k, v]) => acc.replaceAll(`{${k}}`, String(v)),
|
|
22
|
-
raw,
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function formatSize(bytes: number | undefined | null): string {
|
|
27
|
-
if (bytes == null || bytes <= 0) return '—';
|
|
28
|
-
const units: Array<[number, string]> = [
|
|
29
|
-
[1024 ** 4, 'unit.tb'],
|
|
30
|
-
[1024 ** 3, 'unit.gb'],
|
|
31
|
-
[1024 ** 2, 'unit.mb'],
|
|
32
|
-
[1024, 'unit.kb'],
|
|
33
|
-
[1, 'unit.bytes'],
|
|
34
|
-
];
|
|
35
|
-
for (const [div, key] of units) {
|
|
36
|
-
if (bytes >= div) {
|
|
37
|
-
const val = bytes / div;
|
|
38
|
-
const rounded = val >= 100 ? Math.round(val) : val.toFixed(val >= 10 ? 1 : 2);
|
|
39
|
-
return `${rounded} ${t(key)}`;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return `${bytes} ${t('unit.bytes')}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Render a FileNode's basename with locale-aware overrides:
|
|
47
|
-
* - `.trash` directory → "Çöp Kutusu" / "Trash"
|
|
48
|
-
* - Trash entries are stored as `<Ymd-His>-<rand>__<original>` so
|
|
49
|
-
* listings inside .trash/ would otherwise show timestamps. Strip
|
|
50
|
-
* that prefix so the user sees the original basename.
|
|
51
|
-
*/
|
|
52
|
-
function nodeDisplayName(node: { basename: string }): string {
|
|
53
|
-
if (node.basename === '.trash') return t('node.trash');
|
|
54
|
-
const m = node.basename.match(/^\d{8}-\d{6}-[A-Za-z0-9]+__(.+)$/);
|
|
55
|
-
if (m) return m[1];
|
|
56
|
-
return node.basename;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return { t, formatSize, nodeDisplayName };
|
|
60
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* useLocale — tr/en string table with a tiny `t()` helper.
|
|
3
|
+
*
|
|
4
|
+
* No i18n library — the catalogue is small enough to ship inline (see
|
|
5
|
+
* src/locales).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { computed, type Ref } from 'vue';
|
|
9
|
+
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
10
|
+
import { messages } from '../locales';
|
|
11
|
+
|
|
12
|
+
export function useLocale(localeRef: Ref<LocaleCode> | (() => LocaleCode)) {
|
|
13
|
+
const lookup = computed(() => {
|
|
14
|
+
const code = typeof localeRef === 'function' ? localeRef() : localeRef.value;
|
|
15
|
+
return messages[code] ?? messages.tr;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function t(key: string, vars: Record<string, string | number> = {}): string {
|
|
19
|
+
const raw = lookup.value[key] ?? key;
|
|
20
|
+
return Object.entries(vars).reduce(
|
|
21
|
+
(acc, [k, v]) => acc.replaceAll(`{${k}}`, String(v)),
|
|
22
|
+
raw,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatSize(bytes: number | undefined | null): string {
|
|
27
|
+
if (bytes == null || bytes <= 0) return '—';
|
|
28
|
+
const units: Array<[number, string]> = [
|
|
29
|
+
[1024 ** 4, 'unit.tb'],
|
|
30
|
+
[1024 ** 3, 'unit.gb'],
|
|
31
|
+
[1024 ** 2, 'unit.mb'],
|
|
32
|
+
[1024, 'unit.kb'],
|
|
33
|
+
[1, 'unit.bytes'],
|
|
34
|
+
];
|
|
35
|
+
for (const [div, key] of units) {
|
|
36
|
+
if (bytes >= div) {
|
|
37
|
+
const val = bytes / div;
|
|
38
|
+
const rounded = val >= 100 ? Math.round(val) : val.toFixed(val >= 10 ? 1 : 2);
|
|
39
|
+
return `${rounded} ${t(key)}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return `${bytes} ${t('unit.bytes')}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Render a FileNode's basename with locale-aware overrides:
|
|
47
|
+
* - `.trash` directory → "Çöp Kutusu" / "Trash"
|
|
48
|
+
* - Trash entries are stored as `<Ymd-His>-<rand>__<original>` so
|
|
49
|
+
* listings inside .trash/ would otherwise show timestamps. Strip
|
|
50
|
+
* that prefix so the user sees the original basename.
|
|
51
|
+
*/
|
|
52
|
+
function nodeDisplayName(node: { basename: string }): string {
|
|
53
|
+
if (node.basename === '.trash') return t('node.trash');
|
|
54
|
+
const m = node.basename.match(/^\d{8}-\d{6}-[A-Za-z0-9]+__(.+)$/);
|
|
55
|
+
if (m) return m[1];
|
|
56
|
+
return node.basename;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { t, formatSize, nodeDisplayName };
|
|
60
|
+
}
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useMonacoLoader — fire-and-forget Monaco preloader.
|
|
3
|
-
*
|
|
4
|
-
* The preview/edit flow wants Monaco when available (best-in-class
|
|
5
|
-
* editor) and highlight.js for read-only syntax colour as a graceful
|
|
6
|
-
* fallback. Monaco is heavy (~5 MB), so we kick off the dynamic import
|
|
7
|
-
* at module-init time — by the time the user clicks "edit" on a code
|
|
8
|
-
* file (typically several seconds in), the chunk is already cached.
|
|
9
|
-
*
|
|
10
|
-
* - `monacoPromise` resolves to the Monaco namespace, or `null`
|
|
11
|
-
* if the package is not installed (peer
|
|
12
|
-
* missing) / load fails.
|
|
13
|
-
* - `highlightPromise` resolves to the highlight.js export, or
|
|
14
|
-
* `null` similarly.
|
|
15
|
-
* - `getMonaco()` returns the cached module synchronously
|
|
16
|
-
* (after `await monacoPromise`) so a click
|
|
17
|
-
* handler doesn't re-trigger the import.
|
|
18
|
-
*
|
|
19
|
-
* Both modules are externalized in the bundler config — the consumer
|
|
20
|
-
* either declares them as direct deps (Monaco UX) or doesn't (the
|
|
21
|
-
* highlight.js read-only path takes over). No build-time hard dep here.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
let monacoPromise: Promise<unknown | null> | null = null;
|
|
25
|
-
let highlightPromise: Promise<unknown | null> | null = null;
|
|
26
|
-
let monacoCached: unknown | null = null;
|
|
27
|
-
let highlightCached: unknown | null = null;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Kick off the Monaco import the first time someone calls
|
|
31
|
-
* `preloadEditor()` (typically the FileExplorer onMounted hook). Cheap
|
|
32
|
-
* to call repeatedly — only the first call actually triggers the
|
|
33
|
-
* dynamic import.
|
|
34
|
-
*/
|
|
35
|
-
export function preloadEditor(): void {
|
|
36
|
-
if (!monacoPromise) {
|
|
37
|
-
monacoPromise = import(/* @vite-ignore */ 'monaco-editor')
|
|
38
|
-
.then((mod) => {
|
|
39
|
-
monacoCached = (mod as { default?: unknown }).default ?? mod;
|
|
40
|
-
return monacoCached;
|
|
41
|
-
})
|
|
42
|
-
.catch(() => {
|
|
43
|
-
monacoCached = null;
|
|
44
|
-
return null;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
if (!highlightPromise) {
|
|
48
|
-
highlightPromise = import(/* @vite-ignore */ 'highlight.js')
|
|
49
|
-
.then((mod) => {
|
|
50
|
-
highlightCached = (mod as { default?: unknown }).default ?? mod;
|
|
51
|
-
return highlightCached;
|
|
52
|
-
})
|
|
53
|
-
.catch(() => {
|
|
54
|
-
highlightCached = null;
|
|
55
|
-
return null;
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Resolve Monaco. If preload hasn't been kicked off yet, this triggers
|
|
62
|
-
* it (and pays the load cost on the spot). Returns `null` when the
|
|
63
|
-
* package isn't installed.
|
|
64
|
-
*/
|
|
65
|
-
export async function ensureMonaco(): Promise<unknown | null> {
|
|
66
|
-
if (!monacoPromise) preloadEditor();
|
|
67
|
-
return monacoPromise!;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function ensureHighlight(): Promise<unknown | null> {
|
|
71
|
-
if (!highlightPromise) preloadEditor();
|
|
72
|
-
return highlightPromise!;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Synchronous cached lookup — null if Monaco not yet ready or absent. */
|
|
76
|
-
export function getMonaco(): unknown | null {
|
|
77
|
-
return monacoCached;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function getHighlight(): unknown | null {
|
|
81
|
-
return highlightCached;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Composable wrapper — components that want reactive "is Monaco ready
|
|
86
|
-
* yet?" semantics can read the resolved promise. The implementation is
|
|
87
|
-
* intentionally tiny: components don't need anything fancier than
|
|
88
|
-
* `await ensureMonaco()` inside the click handler.
|
|
89
|
-
*/
|
|
90
|
-
export function useMonacoLoader() {
|
|
91
|
-
preloadEditor();
|
|
92
|
-
return {
|
|
93
|
-
ensureMonaco,
|
|
94
|
-
ensureHighlight,
|
|
95
|
-
getMonaco,
|
|
96
|
-
getHighlight,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* useMonacoLoader — fire-and-forget Monaco preloader.
|
|
3
|
+
*
|
|
4
|
+
* The preview/edit flow wants Monaco when available (best-in-class
|
|
5
|
+
* editor) and highlight.js for read-only syntax colour as a graceful
|
|
6
|
+
* fallback. Monaco is heavy (~5 MB), so we kick off the dynamic import
|
|
7
|
+
* at module-init time — by the time the user clicks "edit" on a code
|
|
8
|
+
* file (typically several seconds in), the chunk is already cached.
|
|
9
|
+
*
|
|
10
|
+
* - `monacoPromise` resolves to the Monaco namespace, or `null`
|
|
11
|
+
* if the package is not installed (peer
|
|
12
|
+
* missing) / load fails.
|
|
13
|
+
* - `highlightPromise` resolves to the highlight.js export, or
|
|
14
|
+
* `null` similarly.
|
|
15
|
+
* - `getMonaco()` returns the cached module synchronously
|
|
16
|
+
* (after `await monacoPromise`) so a click
|
|
17
|
+
* handler doesn't re-trigger the import.
|
|
18
|
+
*
|
|
19
|
+
* Both modules are externalized in the bundler config — the consumer
|
|
20
|
+
* either declares them as direct deps (Monaco UX) or doesn't (the
|
|
21
|
+
* highlight.js read-only path takes over). No build-time hard dep here.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
let monacoPromise: Promise<unknown | null> | null = null;
|
|
25
|
+
let highlightPromise: Promise<unknown | null> | null = null;
|
|
26
|
+
let monacoCached: unknown | null = null;
|
|
27
|
+
let highlightCached: unknown | null = null;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Kick off the Monaco import the first time someone calls
|
|
31
|
+
* `preloadEditor()` (typically the FileExplorer onMounted hook). Cheap
|
|
32
|
+
* to call repeatedly — only the first call actually triggers the
|
|
33
|
+
* dynamic import.
|
|
34
|
+
*/
|
|
35
|
+
export function preloadEditor(): void {
|
|
36
|
+
if (!monacoPromise) {
|
|
37
|
+
monacoPromise = import(/* @vite-ignore */ 'monaco-editor')
|
|
38
|
+
.then((mod) => {
|
|
39
|
+
monacoCached = (mod as { default?: unknown }).default ?? mod;
|
|
40
|
+
return monacoCached;
|
|
41
|
+
})
|
|
42
|
+
.catch(() => {
|
|
43
|
+
monacoCached = null;
|
|
44
|
+
return null;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (!highlightPromise) {
|
|
48
|
+
highlightPromise = import(/* @vite-ignore */ 'highlight.js')
|
|
49
|
+
.then((mod) => {
|
|
50
|
+
highlightCached = (mod as { default?: unknown }).default ?? mod;
|
|
51
|
+
return highlightCached;
|
|
52
|
+
})
|
|
53
|
+
.catch(() => {
|
|
54
|
+
highlightCached = null;
|
|
55
|
+
return null;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve Monaco. If preload hasn't been kicked off yet, this triggers
|
|
62
|
+
* it (and pays the load cost on the spot). Returns `null` when the
|
|
63
|
+
* package isn't installed.
|
|
64
|
+
*/
|
|
65
|
+
export async function ensureMonaco(): Promise<unknown | null> {
|
|
66
|
+
if (!monacoPromise) preloadEditor();
|
|
67
|
+
return monacoPromise!;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function ensureHighlight(): Promise<unknown | null> {
|
|
71
|
+
if (!highlightPromise) preloadEditor();
|
|
72
|
+
return highlightPromise!;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Synchronous cached lookup — null if Monaco not yet ready or absent. */
|
|
76
|
+
export function getMonaco(): unknown | null {
|
|
77
|
+
return monacoCached;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function getHighlight(): unknown | null {
|
|
81
|
+
return highlightCached;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Composable wrapper — components that want reactive "is Monaco ready
|
|
86
|
+
* yet?" semantics can read the resolved promise. The implementation is
|
|
87
|
+
* intentionally tiny: components don't need anything fancier than
|
|
88
|
+
* `await ensureMonaco()` inside the click handler.
|
|
89
|
+
*/
|
|
90
|
+
export function useMonacoLoader() {
|
|
91
|
+
preloadEditor();
|
|
92
|
+
return {
|
|
93
|
+
ensureMonaco,
|
|
94
|
+
ensureHighlight,
|
|
95
|
+
getMonaco,
|
|
96
|
+
getHighlight,
|
|
97
|
+
};
|
|
98
|
+
}
|