@brftech/filex-core 0.39.1 → 0.41.0
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/README.md +32 -25
- package/dist/ArchiveViewer-BNqukFg8.js +124 -0
- package/dist/ArchiveViewer-BNqukFg8.js.map +1 -0
- package/dist/CsvViewer-ii_-MgmG.js +141 -0
- package/dist/CsvViewer-ii_-MgmG.js.map +1 -0
- package/dist/{DrawioViewer-BNALOB04.js → DrawioViewer-B2tuu4rX.js} +48 -42
- package/dist/DrawioViewer-B2tuu4rX.js.map +1 -0
- package/dist/EpubViewer-p4B8iiWb.js +163 -0
- package/dist/EpubViewer-p4B8iiWb.js.map +1 -0
- package/dist/IpynbViewer-D_qYRJhu.js +184 -0
- package/dist/IpynbViewer-D_qYRJhu.js.map +1 -0
- package/dist/MermaidViewer-3ZkfNM8v.js +137 -0
- package/dist/MermaidViewer-3ZkfNM8v.js.map +1 -0
- package/dist/PsdViewer-CuV-OPSd.js +122 -0
- package/dist/PsdViewer-CuV-OPSd.js.map +1 -0
- package/dist/TiffViewer-BLeoF4Be.js +142 -0
- package/dist/TiffViewer-BLeoF4Be.js.map +1 -0
- package/dist/Viewer3D-DHhhU76E.js +75 -0
- package/dist/Viewer3D-DHhhU76E.js.map +1 -0
- package/dist/filex-core.js +217 -20077
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +61 -62
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-BraG7Cz4.js +26664 -0
- package/dist/index-BraG7Cz4.js.map +1 -0
- package/dist/index.d.ts +4276 -851
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +2170 -492
- package/src/components/AdvancedSearch.vue +870 -0
- package/src/components/Breadcrumb.vue +191 -19
- package/src/components/CommandPalette.vue +66 -22
- package/src/components/ConnectionsPanel.vue +6 -2
- package/src/components/ContextMenu.vue +57 -2
- package/src/components/EncryptedFolderModal.vue +31 -25
- package/src/components/FilePane.vue +939 -0
- package/src/components/FilterBar.vue +568 -72
- package/src/components/GalleryView.vue +162 -30
- package/src/components/GridView.vue +322 -58
- package/src/components/HomeView.vue +331 -0
- package/src/components/InspectorPanel.vue +296 -53
- package/src/components/ListView.vue +1158 -167
- package/src/components/NFSExportsPanel.vue +7 -4
- package/src/components/OnboardingTour.vue +156 -33
- package/src/components/QuickLook.vue +34 -5
- package/src/components/RecentlyOpened.vue +17 -2
- package/src/components/S3KeysPanel.vue +7 -4
- package/src/components/SSHKeysPanel.vue +7 -4
- package/src/components/ShortcutSettings.vue +43 -2
- package/src/components/ShortcutsHelp.vue +4 -1
- package/src/components/SideNav.vue +206 -140
- package/src/components/StorageFields.vue +19 -1
- package/src/components/TabBar.vue +87 -1
- package/src/components/TagPicker.vue +77 -6
- package/src/components/ThemeGallery.vue +13 -198
- package/src/components/ThemePalette.vue +244 -0
- package/src/components/TimeZoneDialog.vue +127 -0
- package/src/components/TimeZonePicker.vue +696 -0
- package/src/components/TokensPanel.vue +16 -4
- package/src/components/Toolbar.vue +965 -469
- package/src/components/ViewSwitcher.vue +9 -4
- package/src/composables/useConnections.ts +35 -4
- package/src/composables/useExplorerTimeZone.ts +85 -0
- package/src/composables/useFileApi.ts +53 -4
- package/src/composables/useKeyboardShortcuts.ts +122 -0
- package/src/composables/useLocale.ts +365 -25
- package/src/index.ts +182 -3
- package/src/lib/actionIcons.ts +330 -0
- package/src/lib/advSearch.ts +162 -0
- package/src/lib/dateGroups.ts +197 -0
- package/src/lib/destinationTree.ts +235 -0
- package/src/lib/downloadSelection.ts +154 -0
- package/src/lib/fileFilters.ts +319 -12
- package/src/lib/fileIcons.ts +299 -5
- package/src/lib/filePreview.ts +605 -0
- package/src/lib/listing.ts +72 -0
- package/src/lib/shareTtl.ts +11 -1
- package/src/lib/sortOrder.ts +459 -0
- package/src/lib/themes.ts +152 -23
- package/src/lib/timezone.ts +456 -0
- package/src/lib/uiProfile.ts +82 -0
- package/src/lib/viewPrefs.ts +1036 -0
- package/src/locales/en.ts +432 -25
- package/src/locales/tr.ts +425 -25
- package/src/modals/ConvertModal.vue +23 -3
- package/src/modals/DestinationPickerModal.vue +341 -0
- package/src/modals/Modal.vue +18 -4
- package/src/modals/NewDocumentModal.vue +525 -0
- package/src/modals/NewFolderModal.vue +15 -9
- package/src/modals/PermissionsModal.vue +543 -473
- package/src/modals/PreviewModal.vue +516 -156
- package/src/styles/base.css +5435 -8
- package/src/styles/variables.css +140 -60
- package/src/types/Connections.ts +9 -0
- package/src/types/ExplorerConfig.ts +144 -39
- package/src/types/FileNode.ts +28 -0
- package/src/viewers/ArchiveViewer.vue +46 -8
- package/src/viewers/CsvViewer.vue +19 -2
- package/src/viewers/DrawioViewer.vue +12 -1
- package/src/viewers/EpubViewer.vue +50 -6
- package/src/viewers/IpynbViewer.vue +19 -2
- package/src/viewers/MermaidViewer.vue +20 -3
- package/src/viewers/PdfViewer.vue +24 -4
- package/src/viewers/PsdViewer.vue +28 -4
- package/src/viewers/TiffViewer.vue +19 -2
- package/src/viewers/Viewer3D.vue +36 -2
- package/dist/ArchiveViewer-Cdfv9LFC.js +0 -99
- package/dist/ArchiveViewer-Cdfv9LFC.js.map +0 -1
- package/dist/CsvViewer-CqWeV8VO.js +0 -131
- package/dist/CsvViewer-CqWeV8VO.js.map +0 -1
- package/dist/DrawioViewer-BNALOB04.js.map +0 -1
- package/dist/EpubViewer-BPmlImG7.js +0 -145
- package/dist/EpubViewer-BPmlImG7.js.map +0 -1
- package/dist/IpynbViewer-CDlQmuCV.js +0 -175
- package/dist/IpynbViewer-CDlQmuCV.js.map +0 -1
- package/dist/MermaidViewer-sfZWdR-d.js +0 -127
- package/dist/MermaidViewer-sfZWdR-d.js.map +0 -1
- package/dist/PsdViewer-DwuySZsb.js +0 -112
- package/dist/PsdViewer-DwuySZsb.js.map +0 -1
- package/dist/TiffViewer-BDQ3d3CQ.js +0 -132
- package/dist/TiffViewer-BDQ3d3CQ.js.map +0 -1
- package/dist/Viewer3D-CJJJgkuv.js +0 -60
- package/dist/Viewer3D-CJJJgkuv.js.map +0 -1
- package/src/components/SecondaryPane.vue +0 -447
- package/src/modals/ShareModal.vue +0 -139
package/src/lib/fileIcons.ts
CHANGED
|
@@ -6,8 +6,18 @@
|
|
|
6
6
|
* each family gets an accent via `--fe-icon-<family>` (variables.css),
|
|
7
7
|
* applied through the `fe-ficon--<family>` class in base.css.
|
|
8
8
|
*
|
|
9
|
-
* Scope:
|
|
10
|
-
*
|
|
9
|
+
* Scope: what a listing row IS — its file type, plus the one STATE that
|
|
10
|
+
* changes the picture rather than the caption (an encrypted folder, bottom of
|
|
11
|
+
* this file). What a row DOES is `lib/actionIcons.ts`.
|
|
12
|
+
*
|
|
13
|
+
* ⚠ `.trash` and STORAGE rows used to be the documented exception here, and
|
|
14
|
+
* they were drawn as 💾 and 🗑 by three separate copies of the same line in
|
|
15
|
+
* GridView, GalleryView and ListView. On the multi-storage "My files" root —
|
|
16
|
+
* the screen the sidebar now lands everybody on — that made a pixelated
|
|
17
|
+
* floppy disk the first thing on the page, in a shape that changes between
|
|
18
|
+
* Windows, macOS and Linux because it is a system font's idea of the glyph
|
|
19
|
+
* rather than ours. They are families like any other now, so the card, the
|
|
20
|
+
* tile and the row all get one definition (filex lesson #67).
|
|
11
21
|
*/
|
|
12
22
|
|
|
13
23
|
export type IconFamily =
|
|
@@ -22,6 +32,11 @@ export type IconFamily =
|
|
|
22
32
|
| 'archive'
|
|
23
33
|
| 'code'
|
|
24
34
|
| 'text'
|
|
35
|
+
// Not file types: the two rows a listing can hold that are PLACES rather
|
|
36
|
+
// than things. They carry no extension, so `iconFamilyFor` recognises them
|
|
37
|
+
// from the node instead.
|
|
38
|
+
| 'storage'
|
|
39
|
+
| 'trash'
|
|
25
40
|
| 'unknown';
|
|
26
41
|
|
|
27
42
|
const EXT_FAMILIES: Record<string, IconFamily> = {};
|
|
@@ -35,7 +50,11 @@ reg('video', ['mp4', 'webm', 'mov', 'mkv', 'avi', 'ogv', 'm4v']);
|
|
|
35
50
|
reg('audio', ['mp3', 'wav', 'flac', 'ogg', 'm4a', 'aac', 'opus']);
|
|
36
51
|
reg('pdf', ['pdf']);
|
|
37
52
|
reg('doc', ['doc', 'docx', 'odt', 'rtf']);
|
|
38
|
-
|
|
53
|
+
/* gorunum:v1-preview — `tsv` joins its twin. It was the one tabular extension
|
|
54
|
+
* with no family at all (so: the "unknown" glyph, and invisible to the
|
|
55
|
+
* Spreadsheet filter), while the text viewer has read it as a table since
|
|
56
|
+
* v0.5 (`viewers/CsvViewer.vue` branches on `ext === 'tsv'`). */
|
|
57
|
+
reg('sheet', ['xls', 'xlsx', 'ods', 'csv', 'tsv']);
|
|
39
58
|
reg('slides', ['ppt', 'pptx', 'odp']);
|
|
40
59
|
reg('archive', ['zip', 'tar', 'gz', 'bz2', '7z', 'rar', 'xz', 'zst']);
|
|
41
60
|
reg('code', [
|
|
@@ -45,11 +64,31 @@ reg('code', [
|
|
|
45
64
|
]);
|
|
46
65
|
reg('text', ['txt', 'md', 'markdown', 'log', 'ini', 'conf', 'cfg', 'env']);
|
|
47
66
|
|
|
67
|
+
/**
|
|
68
|
+
* The synthetic "a storage" row a listing can carry.
|
|
69
|
+
*
|
|
70
|
+
* The backend never sends `inode/storage`; FileExplorer mints these rows for
|
|
71
|
+
* the multi-storage overview, and they are `type: 'dir'` with no folder behind
|
|
72
|
+
* them — every mutation would 4xx. The test was written out longhand in three
|
|
73
|
+
* view components, which is how a fourth surface came to be written without
|
|
74
|
+
* it, so it is a function now and this file is its one home.
|
|
75
|
+
*/
|
|
76
|
+
export function isStorageRow(node: { mime_type?: string | null }): boolean {
|
|
77
|
+
return node.mime_type === 'inode/storage';
|
|
78
|
+
}
|
|
79
|
+
|
|
48
80
|
/** Pick the icon family for a listing node. */
|
|
49
81
|
export function iconFamilyFor(node: {
|
|
50
82
|
type?: string;
|
|
51
83
|
extension?: string | null;
|
|
84
|
+
mime_type?: string | null;
|
|
85
|
+
basename?: string | null;
|
|
52
86
|
}): IconFamily {
|
|
87
|
+
// ⚠ Both of these are also `type: 'dir'`, so they have to be answered
|
|
88
|
+
// BEFORE the folder branch or they fall through to a plain folder — which
|
|
89
|
+
// is what `HomeView` still does by asking for `{ type: 'dir' }` by hand.
|
|
90
|
+
if (isStorageRow(node)) return 'storage';
|
|
91
|
+
if (node.basename === '.trash') return 'trash';
|
|
53
92
|
if (node.type === 'dir') return 'folder';
|
|
54
93
|
const ext = (node.extension || '').toLowerCase();
|
|
55
94
|
return EXT_FAMILIES[ext] ?? 'unknown';
|
|
@@ -64,8 +103,15 @@ const FILE_BASE =
|
|
|
64
103
|
const GLYPHS: Record<IconFamily, string> = {
|
|
65
104
|
// Folder is the one "filled" member — a soft currentColor wash keeps it
|
|
66
105
|
// visually anchored without breaking the line style.
|
|
106
|
+
/**
|
|
107
|
+
* gorunum:v1 — the folder is SOLID, and it is the one glyph that never
|
|
108
|
+
* gets a tile. A folder is the most common thing in a listing; drawn as an
|
|
109
|
+
* outline in the same weight as the file glyphs it disappears into them,
|
|
110
|
+
* and the eye has to read the label to tell a folder from a document. Filled
|
|
111
|
+
* and in its own colour it is recognisable before the name is read.
|
|
112
|
+
*/
|
|
67
113
|
folder:
|
|
68
|
-
'<path d="
|
|
114
|
+
'<path d="M2.5 6.5A2.5 2.5 0 0 1 5 4h4.6a2.5 2.5 0 0 1 1.77.73L13 6.4h6a2.5 2.5 0 0 1 2.5 2.5v8.6A2.5 2.5 0 0 1 19 20H5a2.5 2.5 0 0 1-2.5-2.5z" fill="currentColor" stroke="none"/>',
|
|
69
115
|
image:
|
|
70
116
|
'<rect x="3" y="5" width="18" height="14" rx="2"/>' +
|
|
71
117
|
'<circle cx="8.6" cy="10" r="1.6"/>' +
|
|
@@ -99,6 +145,17 @@ const GLYPHS: Record<IconFamily, string> = {
|
|
|
99
145
|
text:
|
|
100
146
|
FILE_BASE +
|
|
101
147
|
'<path d="M8.5 12h7M8.5 15h7M8.5 18h4.5"/>',
|
|
148
|
+
// The two-bay drive the sidebar already draws for the same storages, so a
|
|
149
|
+
// drive is one shape across the panel rather than one per surface.
|
|
150
|
+
storage:
|
|
151
|
+
'<rect x="3" y="5" width="18" height="6" rx="1.6"/>' +
|
|
152
|
+
'<rect x="3" y="13" width="18" height="6" rx="1.6"/>' +
|
|
153
|
+
'<path d="M6.5 8h.01M6.5 16h.01"/>',
|
|
154
|
+
trash:
|
|
155
|
+
'<path d="M4 7h16"/>' +
|
|
156
|
+
'<path d="M9.5 7V5.6A1.6 1.6 0 0 1 11.1 4h1.8a1.6 1.6 0 0 1 1.6 1.6V7"/>' +
|
|
157
|
+
'<path d="M6.2 7l.85 11.6A1.6 1.6 0 0 0 8.65 20h6.7a1.6 1.6 0 0 0 1.6-1.4L17.8 7"/>' +
|
|
158
|
+
'<path d="M10.2 10.8v5.6M13.8 10.8v5.6"/>',
|
|
102
159
|
unknown:
|
|
103
160
|
FILE_BASE +
|
|
104
161
|
'<path d="M10.3 13.1a1.8 1.8 0 1 1 2.5 1.9c-.6.3-.8.7-.8 1.4"/>' +
|
|
@@ -122,6 +179,243 @@ export function iconSvg(family: IconFamily): string {
|
|
|
122
179
|
}
|
|
123
180
|
|
|
124
181
|
/** Convenience: node → SVG markup in one call. */
|
|
125
|
-
export function fileIconSvg(node:
|
|
182
|
+
export function fileIconSvg(node: Parameters<typeof iconFamilyFor>[0]): string {
|
|
126
183
|
return iconSvg(iconFamilyFor(node));
|
|
127
184
|
}
|
|
185
|
+
|
|
186
|
+
const TILE_CACHE = new Map<IconFamily, string>();
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* gorunum:v1 — the listing badge: a filled, rounded square in the family's
|
|
190
|
+
* colour with the glyph knocked out in white.
|
|
191
|
+
*
|
|
192
|
+
* Why not the bare coloured glyph it replaces: at 16px a line drawing in a
|
|
193
|
+
* pale accent is a smudge, and twelve of them down a column read as noise
|
|
194
|
+
* rather than as twelve kinds of file. A solid chip of colour is legible at a
|
|
195
|
+
* glance, holds its shape at any density, and gives the row a consistent left
|
|
196
|
+
* edge whether the file has a thumbnail or not.
|
|
197
|
+
*
|
|
198
|
+
* The folder is the exception and returns its solid glyph untiled — a folder
|
|
199
|
+
* is not a file type, and boxing it makes a listing look like a colour chart.
|
|
200
|
+
*/
|
|
201
|
+
export function iconTile(family: IconFamily): string {
|
|
202
|
+
if (family === 'folder') {
|
|
203
|
+
return `<span class="fe-ftile fe-ftile--folder">${iconSvg(family)}</span>`;
|
|
204
|
+
}
|
|
205
|
+
let tile = TILE_CACHE.get(family);
|
|
206
|
+
if (!tile) {
|
|
207
|
+
tile = `<span class="fe-ftile fe-ftile--${family}">${iconSvg(family)}</span>`;
|
|
208
|
+
TILE_CACHE.set(family, tile);
|
|
209
|
+
}
|
|
210
|
+
return tile;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Convenience: node → tile markup in one call. */
|
|
214
|
+
export function fileIconTile(node: Parameters<typeof iconFamilyFor>[0]): string {
|
|
215
|
+
return iconTile(iconFamilyFor(node));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ======================================================================
|
|
219
|
+
* ikon:emoji — the one listing marker that is a STATE, not a type.
|
|
220
|
+
*
|
|
221
|
+
* An end-to-end encrypted folder (wiring:e2) is a folder like any other; what
|
|
222
|
+
* is special about it is a fact, not a kind, so it cannot be an `IconFamily`
|
|
223
|
+
* — `iconFamilyFor` is the taxonomy `lib/fileFilters` filters by, and moving
|
|
224
|
+
* these rows out of `folder` would quietly drop them from the Folders filter.
|
|
225
|
+
* It lives beside the families instead, as one definition.
|
|
226
|
+
*
|
|
227
|
+
* ⚠ It was three. `specialEmojiFor(n) { if (n.type === 'dir' && n.e2e) return
|
|
228
|
+
* '🔒' }` was copy-pasted verbatim into ListView, GridView and GalleryView —
|
|
229
|
+
* the exact shape of the thing the duplicate-code gate exists to stop, and
|
|
230
|
+
* the second time this file has had to absorb it (the first was `.trash` and
|
|
231
|
+
* the storage row, drawn as 🗑 and 💾 by the same three copies).
|
|
232
|
+
* ====================================================================== */
|
|
233
|
+
|
|
234
|
+
/** True for a folder that is end-to-end encrypted. */
|
|
235
|
+
export function isEncryptedFolder(node: { type?: string; e2e?: boolean }): boolean {
|
|
236
|
+
return node.type === 'dir' && node.e2e === true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The solid folder with a padlock cut OUT of it.
|
|
241
|
+
*
|
|
242
|
+
* One path, `fill-rule="evenodd"`: the shackle and the body are subpaths
|
|
243
|
+
* inside the folder outline, so they become holes and show whatever the tile
|
|
244
|
+
* sits on. That is deliberate — the alternative (drawing the lock in a
|
|
245
|
+
* background colour) picks one background and is wrong on the other two
|
|
246
|
+
* surfaces this tile appears on.
|
|
247
|
+
*
|
|
248
|
+
* ⚠ It replaces a 🔒 that replaced the folder entirely, so the row lost the
|
|
249
|
+
* one thing it had always said: that it is a folder. Both facts are here now,
|
|
250
|
+
* and the callers give the span an `aria-label` for the half a shape cannot
|
|
251
|
+
* carry.
|
|
252
|
+
*/
|
|
253
|
+
const LOCKED_FOLDER =
|
|
254
|
+
'<path fill="currentColor" stroke="none" fill-rule="evenodd" d="' +
|
|
255
|
+
/* the folder — the same outline as GLYPHS.folder */
|
|
256
|
+
'M2.5 6.5A2.5 2.5 0 0 1 5 4h4.6a2.5 2.5 0 0 1 1.77.73L13 6.4h6a2.5 2.5 0 0 1 2.5 2.5v8.6A2.5 2.5 0 0 1 19 20H5a2.5 2.5 0 0 1-2.5-2.5z' +
|
|
257
|
+
/* the shackle: outer arc over, step in, inner arc back, close down the leg */
|
|
258
|
+
'M9.7 12a2.3 2.3 0 0 1 4.6 0h-1.05a1.25 1.25 0 0 0-2.5 0z' +
|
|
259
|
+
/* the body */
|
|
260
|
+
'M9.2 12h5.6a1 1 0 0 1 1 1v3.6a1 1 0 0 1-1 1H9.2a1 1 0 0 1-1-1V13a1 1 0 0 1 1-1z' +
|
|
261
|
+
'"/>';
|
|
262
|
+
|
|
263
|
+
let LOCKED_FOLDER_TILE = '';
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Tile markup for an encrypted folder — `fe-ftile--folder`'s own box, so it
|
|
267
|
+
* is the same size, colour and (lack of) chip as every other folder in the
|
|
268
|
+
* column; only the shape differs.
|
|
269
|
+
*/
|
|
270
|
+
export function encryptedFolderTile(): string {
|
|
271
|
+
if (!LOCKED_FOLDER_TILE) {
|
|
272
|
+
LOCKED_FOLDER_TILE =
|
|
273
|
+
'<span class="fe-ftile fe-ftile--folder fe-ftile--folder-locked">' +
|
|
274
|
+
'<svg class="fe-ficon fe-ficon--folder" viewBox="0 0 24 24" fill="none" ' +
|
|
275
|
+
'stroke="currentColor" stroke-width="1.6" stroke-linecap="round" ' +
|
|
276
|
+
'stroke-linejoin="round" aria-hidden="true" focusable="false">' +
|
|
277
|
+
`${LOCKED_FOLDER}</svg></span>`;
|
|
278
|
+
}
|
|
279
|
+
return LOCKED_FOLDER_TILE;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* ======================================================================
|
|
283
|
+
* gorunum:v1-preview — what the thing IS, said in words.
|
|
284
|
+
*
|
|
285
|
+
* The Type column used to print the raw extension in caps: `TS`, `CSV`,
|
|
286
|
+
* `JPG`. That is not a kind, it is a suffix — it answers "what did the file
|
|
287
|
+
* get called" when the question the column asks is "what is this". The
|
|
288
|
+
* reference shell prints `TypeScript`, `Spreadsheet`, `Image`.
|
|
289
|
+
*
|
|
290
|
+
* ⚠ ONE table, here, beside the extension → family map it is built on top of
|
|
291
|
+
* (filex lesson #67). Every surface that names a kind — the Type column
|
|
292
|
+
* today, an info panel tomorrow — calls `typeLabelFor` and gets the same
|
|
293
|
+
* answer; a second table beside it would disagree the first week somebody
|
|
294
|
+
* added an extension to only one of them.
|
|
295
|
+
*
|
|
296
|
+
* Three tiers, in order:
|
|
297
|
+
* 1. the extension has a name of its own → `ftype.typescript`
|
|
298
|
+
* 2. otherwise its FAMILY has one → `ftype.code`, `ftype.image`
|
|
299
|
+
* 3. neither (family `unknown`) → the uppercased extension
|
|
300
|
+
*
|
|
301
|
+
* Tier 3 is the deliberate fallback the brief asks for: an unmapped `.zig`
|
|
302
|
+
* reads "ZIG", which is at least the truth, rather than an empty cell or a
|
|
303
|
+
* confident lie like "File".
|
|
304
|
+
* ====================================================================== */
|
|
305
|
+
|
|
306
|
+
/** Extension → its own i18n key. Only extensions whose NAME differs from
|
|
307
|
+
* what the family would say need an entry here. */
|
|
308
|
+
const EXT_TYPE_KEYS: Record<string, string> = {
|
|
309
|
+
// Languages the person reading the column thinks of by name.
|
|
310
|
+
ts: 'ftype.typescript',
|
|
311
|
+
tsx: 'ftype.typescript',
|
|
312
|
+
mts: 'ftype.typescript',
|
|
313
|
+
cts: 'ftype.typescript',
|
|
314
|
+
js: 'ftype.javascript',
|
|
315
|
+
jsx: 'ftype.javascript',
|
|
316
|
+
mjs: 'ftype.javascript',
|
|
317
|
+
cjs: 'ftype.javascript',
|
|
318
|
+
vue: 'ftype.vue',
|
|
319
|
+
py: 'ftype.python',
|
|
320
|
+
go: 'ftype.go',
|
|
321
|
+
rs: 'ftype.rust',
|
|
322
|
+
php: 'ftype.php',
|
|
323
|
+
rb: 'ftype.ruby',
|
|
324
|
+
java: 'ftype.java',
|
|
325
|
+
kt: 'ftype.kotlin',
|
|
326
|
+
swift: 'ftype.swift',
|
|
327
|
+
c: 'ftype.c',
|
|
328
|
+
h: 'ftype.c',
|
|
329
|
+
cpp: 'ftype.cpp',
|
|
330
|
+
hpp: 'ftype.cpp',
|
|
331
|
+
cs: 'ftype.csharp',
|
|
332
|
+
// Structured text that is code by family but not a language.
|
|
333
|
+
css: 'ftype.stylesheet',
|
|
334
|
+
scss: 'ftype.stylesheet',
|
|
335
|
+
less: 'ftype.stylesheet',
|
|
336
|
+
html: 'ftype.html',
|
|
337
|
+
htm: 'ftype.html',
|
|
338
|
+
json: 'ftype.json',
|
|
339
|
+
yml: 'ftype.yaml',
|
|
340
|
+
yaml: 'ftype.yaml',
|
|
341
|
+
xml: 'ftype.xml',
|
|
342
|
+
toml: 'ftype.toml',
|
|
343
|
+
sql: 'ftype.sql',
|
|
344
|
+
sh: 'ftype.shell',
|
|
345
|
+
bash: 'ftype.shell',
|
|
346
|
+
zsh: 'ftype.shell',
|
|
347
|
+
ps1: 'ftype.powershell',
|
|
348
|
+
// Plain-text family members that are each their own thing.
|
|
349
|
+
md: 'ftype.markdown',
|
|
350
|
+
markdown: 'ftype.markdown',
|
|
351
|
+
txt: 'ftype.plaintext',
|
|
352
|
+
log: 'ftype.log',
|
|
353
|
+
ini: 'ftype.config',
|
|
354
|
+
conf: 'ftype.config',
|
|
355
|
+
cfg: 'ftype.config',
|
|
356
|
+
env: 'ftype.config',
|
|
357
|
+
// Tabular: a `.csv` IS a spreadsheet to the person looking at the column,
|
|
358
|
+
// whatever the program that wrote it was.
|
|
359
|
+
csv: 'ftype.sheet',
|
|
360
|
+
tsv: 'ftype.sheet',
|
|
361
|
+
// Design files. No icon family of their own (they fall to `unknown`, which
|
|
362
|
+
// is a decision for the GLYPH, not for the word) — naming them still beats
|
|
363
|
+
// printing "FIG".
|
|
364
|
+
fig: 'ftype.figma',
|
|
365
|
+
sketch: 'ftype.design',
|
|
366
|
+
xd: 'ftype.design',
|
|
367
|
+
psd: 'ftype.design',
|
|
368
|
+
ai: 'ftype.design',
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/** Family → i18n key, the second tier. `unknown` is absent on purpose: it is
|
|
372
|
+
* what sends a node to the uppercased-extension fallback. */
|
|
373
|
+
const FAMILY_TYPE_KEYS: Partial<Record<IconFamily, string>> = {
|
|
374
|
+
folder: 'node.folder',
|
|
375
|
+
image: 'ftype.image',
|
|
376
|
+
video: 'ftype.video',
|
|
377
|
+
audio: 'ftype.audio',
|
|
378
|
+
pdf: 'ftype.pdf',
|
|
379
|
+
doc: 'ftype.document',
|
|
380
|
+
sheet: 'ftype.sheet',
|
|
381
|
+
slides: 'ftype.slides',
|
|
382
|
+
archive: 'ftype.archive',
|
|
383
|
+
code: 'ftype.code',
|
|
384
|
+
text: 'ftype.plaintext',
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* The i18n key naming this node's kind, or null when nothing maps it and the
|
|
389
|
+
* caller should fall back to the extension. Directories answer
|
|
390
|
+
* `node.folder` — the key the grid card's caption already prints, not a
|
|
391
|
+
* second string saying the same word.
|
|
392
|
+
*/
|
|
393
|
+
export function typeLabelKey(node: {
|
|
394
|
+
type?: string;
|
|
395
|
+
extension?: string | null;
|
|
396
|
+
}): string | null {
|
|
397
|
+
if (node.type === 'dir') return 'node.folder';
|
|
398
|
+
const ext = (node.extension || '').trim().toLowerCase();
|
|
399
|
+
const own = EXT_TYPE_KEYS[ext];
|
|
400
|
+
if (own) return own;
|
|
401
|
+
return FAMILY_TYPE_KEYS[iconFamilyFor(node)] ?? null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* The finished words for the Type column and anything else that names a
|
|
406
|
+
* kind: the mapped name, else the uppercased extension, else an em dash.
|
|
407
|
+
*
|
|
408
|
+
* ⚠ The fallback lives HERE rather than at each call site. Two callers each
|
|
409
|
+
* writing `key ? t(key) : ext.toUpperCase()` is two chances to render an
|
|
410
|
+
* empty cell, and the first one to forget the `|| '—'` prints nothing at all
|
|
411
|
+
* for a file with no extension.
|
|
412
|
+
*/
|
|
413
|
+
export function typeLabelFor(
|
|
414
|
+
node: { type?: string; extension?: string | null },
|
|
415
|
+
t: (key: string) => string,
|
|
416
|
+
): string {
|
|
417
|
+
const key = typeLabelKey(node);
|
|
418
|
+
if (key) return t(key);
|
|
419
|
+
const ext = (node.extension || '').trim();
|
|
420
|
+
return ext ? ext.toUpperCase() : '—';
|
|
421
|
+
}
|