@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/styles/variables.css
CHANGED
|
@@ -4,29 +4,78 @@
|
|
|
4
4
|
* Consumer overrides by setting `--fe-*` on a higher scope.
|
|
5
5
|
*/
|
|
6
6
|
:root {
|
|
7
|
+
/* gorunum:v1 — the product's own palette and metrics. Everything below is a
|
|
8
|
+
* measured value, not a taste: the type scale, the control heights and the
|
|
9
|
+
* radii were read off the shell this look follows, in a real browser at
|
|
10
|
+
* 1440, and the palette clears the contrast bar in
|
|
11
|
+
* web/tests/api/themeContrast.test.ts in both variants. */
|
|
7
12
|
--fe-bg: #ffffff;
|
|
8
|
-
--fe-bg-elev: #
|
|
9
|
-
--fe-bg-hover: #
|
|
10
|
-
--fe-bg-selected: #
|
|
11
|
-
--fe-border: #
|
|
12
|
-
--fe-border-
|
|
13
|
-
--fe-
|
|
14
|
-
--fe-text
|
|
13
|
+
--fe-bg-elev: #f7f8fb;
|
|
14
|
+
--fe-bg-hover: #f3f4f6;
|
|
15
|
+
--fe-bg-selected: #eef3ff;
|
|
16
|
+
--fe-border: #e5e7eb;
|
|
17
|
+
--fe-border-soft: #eef0f4;
|
|
18
|
+
--fe-border-strong: #d1d5db;
|
|
19
|
+
--fe-text: #1f2937;
|
|
20
|
+
--fe-text-muted: #6b7280;
|
|
15
21
|
--fe-text-on-primary: #ffffff;
|
|
16
|
-
--fe-primary: #
|
|
17
|
-
--fe-primary-hover: #
|
|
22
|
+
--fe-primary: #2f6ceb;
|
|
23
|
+
--fe-primary-hover: #2559c9;
|
|
24
|
+
--fe-primary-soft: #e6ecfa;
|
|
25
|
+
/* Ink for text and icons ON the soft-primary ground — the active sidebar
|
|
26
|
+
row, the active scope tab, the active segment of a segmented control.
|
|
27
|
+
⚠ It is a token of its own because `--fe-primary` cannot carry that job:
|
|
28
|
+
measured, the stock primary on the stock tint is 3.97:1, under the 4.5 the
|
|
29
|
+
palette file claims, and no tint rescues it (the primary is only 4.70:1 on
|
|
30
|
+
white to begin with). It is not `--fe-primary-hover` either, though that is
|
|
31
|
+
the value HERE: three of the shipped palettes darken their hover in dark
|
|
32
|
+
mode, toward the tint rather than away from it, and Night dark measures
|
|
33
|
+
3.78:1 that way. Each palette names the ink that clears the bar on its own
|
|
34
|
+
tint; web/tests/api/themeContrast.test.ts pins the pairing. */
|
|
35
|
+
--fe-primary-ink: #2559c9;
|
|
18
36
|
--fe-danger: #dc2626;
|
|
19
37
|
--fe-danger-hover: #b91c1c;
|
|
20
38
|
--fe-keep-ok: #16a34a; /* selective-sync "on this computer" check */
|
|
21
|
-
--fe-shadow: 0
|
|
22
|
-
--fe-shadow-sm: 0
|
|
39
|
+
--fe-shadow: 0 20px 60px rgba(17, 24, 39, 0.18);
|
|
40
|
+
--fe-shadow-sm: 0 8px 24px rgba(17, 24, 39, 0.12);
|
|
23
41
|
--fe-radius: 8px;
|
|
24
|
-
--fe-radius-sm:
|
|
42
|
+
--fe-radius-sm: 6px;
|
|
25
43
|
--fe-radius-md: 10px;
|
|
26
44
|
--fe-radius-lg: 12px;
|
|
27
|
-
|
|
45
|
+
/* Control heights. A listing is a stack of rows and chips; when each one
|
|
46
|
+
* picks its own height the column edges wander and the page reads as
|
|
47
|
+
* hand-assembled. Three sizes, and every control picks one. */
|
|
48
|
+
--fe-h-sm: 28px;
|
|
49
|
+
--fe-h-md: 34px;
|
|
50
|
+
--fe-h-lg: 40px;
|
|
51
|
+
/* gorunum:v3-shell — the navigation panel's expanded width. A token because
|
|
52
|
+
* TWO places have to agree on it: the panel itself, and the gutter the top
|
|
53
|
+
* bar reserves at its left so the search field starts where the content
|
|
54
|
+
* area does. Written once in each place, they were one redesign away from
|
|
55
|
+
* disagreeing — and the failure is silent, a header that no longer lines up
|
|
56
|
+
* with the column under it.
|
|
57
|
+
* ⚠ The rail and the drawer keep their own widths; only the expanded column
|
|
58
|
+
* follows this.
|
|
59
|
+
* ⚠ 192px, not 255px (owner, 2026-09-13: "şu an default'ta çok açık geliyor
|
|
60
|
+
* yan menü, biraz kısaltabiliriz bence"). It is the reference build's own
|
|
61
|
+
* number, measured off it at 1280, and it is the WHOLE change: the top bar's
|
|
62
|
+
* gutter is `calc(this - 12px)`, so the search field moves left with the
|
|
63
|
+
* column instead of the header drifting out of line with it. Checked at the
|
|
64
|
+
* new width in Turkish, the longer of the two catalogues — the widest row
|
|
65
|
+
* label ("Paylaşılanlar") clears it, and so does the quota line. */
|
|
66
|
+
--fe-sidenav-w: 192px;
|
|
67
|
+
/* Type scale. Small and tight on purpose: the shell packs a filename, a
|
|
68
|
+
* size, a date and a menu into one row, and 14px with normal leading pushes
|
|
69
|
+
* that to two lines at any realistic column width. */
|
|
70
|
+
--fe-text-xs: 12px;
|
|
71
|
+
--fe-text-sm: 12.5px;
|
|
72
|
+
--fe-text-md: 13px;
|
|
73
|
+
--fe-font: Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
28
74
|
--fe-font-mono: ui-monospace, "SF Mono", Consolas, Menlo, monospace;
|
|
75
|
+
--fe-gap-xs: 4px;
|
|
76
|
+
--fe-gap-sm: 8px;
|
|
29
77
|
--fe-gap: 12px;
|
|
78
|
+
--fe-gap-lg: 16px;
|
|
30
79
|
}
|
|
31
80
|
|
|
32
81
|
.fe--theme-dark,
|
|
@@ -54,16 +103,22 @@
|
|
|
54
103
|
* as white-on-white inside dark hosts. */
|
|
55
104
|
.fe-ctx-backdrop--theme-dark,
|
|
56
105
|
.fe-ctx-backdrop--theme-auto[data-prefers-dark='1'] {
|
|
57
|
-
--fe-bg: #
|
|
58
|
-
--fe-bg-elev: #
|
|
59
|
-
--fe-bg-hover: #
|
|
60
|
-
--fe-bg-selected: #
|
|
61
|
-
--fe-border: #
|
|
62
|
-
--fe-border-
|
|
63
|
-
--fe-
|
|
64
|
-
--fe-text
|
|
65
|
-
--fe-
|
|
66
|
-
|
|
106
|
+
--fe-bg: #15171c;
|
|
107
|
+
--fe-bg-elev: #1a1d23;
|
|
108
|
+
--fe-bg-hover: #1f232a;
|
|
109
|
+
--fe-bg-selected: #1c2740;
|
|
110
|
+
--fe-border: #2e333c;
|
|
111
|
+
--fe-border-soft: #262a32;
|
|
112
|
+
--fe-border-strong: #3d444f;
|
|
113
|
+
--fe-text: #e6e8ec;
|
|
114
|
+
--fe-text-muted: #888f9b;
|
|
115
|
+
/* Dark ink on the light-blue button. White measures 3.16:1 against it —
|
|
116
|
+
* the bar for text on a primary surface is 4.5, and #15171c measures 5.67. */
|
|
117
|
+
--fe-text-on-primary: #15171c;
|
|
118
|
+
--fe-primary: #5b8cff;
|
|
119
|
+
--fe-primary-hover: #7ba3ff;
|
|
120
|
+
--fe-primary-soft: #1c2740;
|
|
121
|
+
--fe-primary-ink: #7ba3ff;
|
|
67
122
|
--fe-danger: #f87171;
|
|
68
123
|
--fe-danger-hover: #ef4444;
|
|
69
124
|
--fe-keep-ok: #4ade80;
|
|
@@ -75,16 +130,21 @@
|
|
|
75
130
|
.fe:not(.fe--theme-light),
|
|
76
131
|
/* Same trick for the teleported backdrop in auto mode. */
|
|
77
132
|
.fe-ctx-backdrop--theme-auto:not(.fe-ctx-backdrop--theme-light) {
|
|
78
|
-
--fe-bg: #
|
|
79
|
-
--fe-bg-elev: #
|
|
80
|
-
--fe-bg-hover: #
|
|
81
|
-
--fe-bg-selected: #
|
|
82
|
-
--fe-border: #
|
|
83
|
-
--fe-border-
|
|
84
|
-
--fe-
|
|
85
|
-
--fe-text
|
|
86
|
-
--fe-
|
|
87
|
-
|
|
133
|
+
--fe-bg: #15171c;
|
|
134
|
+
--fe-bg-elev: #1a1d23;
|
|
135
|
+
--fe-bg-hover: #1f232a;
|
|
136
|
+
--fe-bg-selected: #1c2740;
|
|
137
|
+
--fe-border: #2e333c;
|
|
138
|
+
--fe-border-soft: #262a32;
|
|
139
|
+
--fe-border-strong: #3d444f;
|
|
140
|
+
--fe-text: #e6e8ec;
|
|
141
|
+
--fe-text-muted: #888f9b;
|
|
142
|
+
/* See the note on the class-based dark block above. */
|
|
143
|
+
--fe-text-on-primary: #15171c;
|
|
144
|
+
--fe-primary: #5b8cff;
|
|
145
|
+
--fe-primary-hover: #7ba3ff;
|
|
146
|
+
--fe-primary-soft: #1c2740;
|
|
147
|
+
--fe-primary-ink: #7ba3ff;
|
|
88
148
|
--fe-danger: #f87171;
|
|
89
149
|
--fe-danger-hover: #ef4444;
|
|
90
150
|
--fe-keep-ok: #4ade80;
|
|
@@ -95,18 +155,34 @@
|
|
|
95
155
|
|
|
96
156
|
/* === cila:a — file-type icon accent tokens (lib/fileIcons families) === */
|
|
97
157
|
:root {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
158
|
+
/* gorunum:v1 — the folder yellow, same value in both variants. A folder is
|
|
159
|
+
* the only glyph that is filled, and the one people find by colour before
|
|
160
|
+
* they read the name; darkening it for the light theme made it read as an
|
|
161
|
+
* archive box. */
|
|
162
|
+
--fe-icon-folder: #f4b400;
|
|
163
|
+
/* gorunum:v1 — these are now the fill of a chip, not the stroke of a glyph,
|
|
164
|
+
* so they are read as a block of colour and a family has to be one glance
|
|
165
|
+
* apart from its neighbours. Documents and code share the blue on purpose:
|
|
166
|
+
* both are "something you open and read", and splitting them made every
|
|
167
|
+
* listing look like a paint chart. */
|
|
168
|
+
--fe-icon-image: #2f6ceb;
|
|
169
|
+
--fe-icon-video: #7c3aed;
|
|
101
170
|
--fe-icon-audio: #0d9488;
|
|
102
171
|
--fe-icon-pdf: #dc2626;
|
|
103
|
-
--fe-icon-doc: #
|
|
172
|
+
--fe-icon-doc: #2f6ceb;
|
|
104
173
|
--fe-icon-sheet: #16a34a;
|
|
105
174
|
--fe-icon-slides: #ea580c;
|
|
106
|
-
--fe-icon-archive: #
|
|
107
|
-
--fe-icon-code: #
|
|
108
|
-
--fe-icon-text: #
|
|
109
|
-
--fe-icon-unknown: #
|
|
175
|
+
--fe-icon-archive: #6b7280;
|
|
176
|
+
--fe-icon-code: #2f6ceb;
|
|
177
|
+
--fe-icon-text: #374151;
|
|
178
|
+
--fe-icon-unknown: #6b7280;
|
|
179
|
+
/* Not file types: the two rows that are PLACES. A drive is infrastructure
|
|
180
|
+
* rather than content, so it takes a slate that sits beside the type
|
|
181
|
+
* colours without competing with any of them — it is the first thing on the
|
|
182
|
+
* multi-storage root and should read as a shelf, not as another document.
|
|
183
|
+
* The bin is deliberately the quietest chip in the set. */
|
|
184
|
+
--fe-icon-storage: #475569;
|
|
185
|
+
--fe-icon-trash: #6b7280;
|
|
110
186
|
}
|
|
111
187
|
|
|
112
188
|
/* Dark values — same selector strategy as the palette blocks above. */
|
|
@@ -120,34 +196,38 @@
|
|
|
120
196
|
.fe-ctx-backdrop--theme-dark,
|
|
121
197
|
.fe-ctx-backdrop--theme-auto[data-prefers-dark='1'] {
|
|
122
198
|
--fe-icon-folder: #fbbf24;
|
|
123
|
-
--fe-icon-image: #
|
|
124
|
-
--fe-icon-video: #
|
|
199
|
+
--fe-icon-image: #4d7ff0;
|
|
200
|
+
--fe-icon-video: #9061f0;
|
|
125
201
|
--fe-icon-audio: #2dd4bf;
|
|
126
|
-
--fe-icon-pdf: #
|
|
127
|
-
--fe-icon-doc: #
|
|
128
|
-
--fe-icon-sheet: #
|
|
202
|
+
--fe-icon-pdf: #e05252;
|
|
203
|
+
--fe-icon-doc: #4d7ff0;
|
|
204
|
+
--fe-icon-sheet: #2eab63;
|
|
129
205
|
--fe-icon-slides: #fb923c;
|
|
130
|
-
--fe-icon-archive: #
|
|
131
|
-
--fe-icon-code: #
|
|
132
|
-
--fe-icon-text: #
|
|
133
|
-
--fe-icon-unknown: #
|
|
206
|
+
--fe-icon-archive: #79808d;
|
|
207
|
+
--fe-icon-code: #4d7ff0;
|
|
208
|
+
--fe-icon-text: #5b6472;
|
|
209
|
+
--fe-icon-unknown: #79808d;
|
|
210
|
+
--fe-icon-storage: #8b95a6;
|
|
211
|
+
--fe-icon-trash: #79808d;
|
|
134
212
|
}
|
|
135
213
|
|
|
136
214
|
@media (prefers-color-scheme: dark) {
|
|
137
215
|
.fe:not(.fe--theme-light),
|
|
138
216
|
.fe-ctx-backdrop--theme-auto:not(.fe-ctx-backdrop--theme-light) {
|
|
139
|
-
--fe-icon-folder: #
|
|
140
|
-
--fe-icon-image: #
|
|
141
|
-
--fe-icon-video: #
|
|
217
|
+
--fe-icon-folder: #f4b400;
|
|
218
|
+
--fe-icon-image: #4d7ff0;
|
|
219
|
+
--fe-icon-video: #9061f0;
|
|
142
220
|
--fe-icon-audio: #2dd4bf;
|
|
143
|
-
--fe-icon-pdf: #
|
|
144
|
-
--fe-icon-doc: #
|
|
145
|
-
--fe-icon-sheet: #
|
|
221
|
+
--fe-icon-pdf: #e05252;
|
|
222
|
+
--fe-icon-doc: #4d7ff0;
|
|
223
|
+
--fe-icon-sheet: #2eab63;
|
|
146
224
|
--fe-icon-slides: #fb923c;
|
|
147
|
-
--fe-icon-archive: #
|
|
148
|
-
--fe-icon-code: #
|
|
149
|
-
--fe-icon-text: #
|
|
150
|
-
--fe-icon-unknown: #
|
|
225
|
+
--fe-icon-archive: #79808d;
|
|
226
|
+
--fe-icon-code: #4d7ff0;
|
|
227
|
+
--fe-icon-text: #5b6472;
|
|
228
|
+
--fe-icon-unknown: #79808d;
|
|
229
|
+
--fe-icon-storage: #8b95a6;
|
|
230
|
+
--fe-icon-trash: #79808d;
|
|
151
231
|
}
|
|
152
232
|
}
|
|
153
233
|
|
package/src/types/Connections.ts
CHANGED
|
@@ -54,8 +54,17 @@ export interface StorageField {
|
|
|
54
54
|
aliases?: string[];
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* A driver's runtime feature set, from `backend/internal/storage.Capabilities`.
|
|
59
|
+
*
|
|
60
|
+
* ⚠ `range` was missing from BOTH hand-written copies of this (here and in
|
|
61
|
+
* web/src/api/types.ts) while the server has always sent it — the exact way a
|
|
62
|
+
* type re-declared per package drifts from the struct it describes.
|
|
63
|
+
*/
|
|
57
64
|
export interface StorageDriverCapabilities {
|
|
58
65
|
read?: boolean;
|
|
66
|
+
/** Ranged reads: the difference between 206/Content-Range and a whole-object copy. */
|
|
67
|
+
range?: boolean;
|
|
59
68
|
write?: boolean;
|
|
60
69
|
move?: boolean;
|
|
61
70
|
copy?: boolean;
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
* keeping the auto-derived rest).
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import type { UiProfile } from '../lib/uiProfile';
|
|
21
|
+
|
|
22
|
+
export type { UiProfile };
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
25
|
* Auth strategy. Discriminated union so request building is type-safe.
|
|
22
26
|
*
|
|
@@ -185,6 +189,21 @@ export interface ExplorerConfig {
|
|
|
185
189
|
/** UI dil kodu */
|
|
186
190
|
locale?: LocaleCode;
|
|
187
191
|
|
|
192
|
+
/**
|
|
193
|
+
* The clock this embed's visitors read dates on by default — an IANA id,
|
|
194
|
+
* `'Asia/Tokyo'`, `'Europe/Istanbul'`, `'UTC'`.
|
|
195
|
+
*
|
|
196
|
+
* Only which clock an instant is READ on changes; the instant itself does
|
|
197
|
+
* not. Unset, a visitor sees the zone of the account behind the credential
|
|
198
|
+
* when that credential is a person's, and otherwise their browser's own.
|
|
199
|
+
*
|
|
200
|
+
* ⚠ A default, not a lock. A visitor who picks a zone in the explorer's own
|
|
201
|
+
* "⋯" → Time zone keeps their pick, in their browser. The whole order, and
|
|
202
|
+
* why it is that order, is `TIME_ZONE_TIERS` in `lib/timezone`. An id the
|
|
203
|
+
* browser does not accept is ignored rather than guessed at.
|
|
204
|
+
*/
|
|
205
|
+
timeZone?: string;
|
|
206
|
+
|
|
188
207
|
/** OnlyOffice iframe base (e.g. `https://docs.example.com`). */
|
|
189
208
|
onlyOfficeBase?: string;
|
|
190
209
|
|
|
@@ -259,7 +278,21 @@ export interface ExplorerConfig {
|
|
|
259
278
|
*/
|
|
260
279
|
tabStrip?: 'auto' | 'always';
|
|
261
280
|
|
|
262
|
-
/**
|
|
281
|
+
/**
|
|
282
|
+
* Initial path (storage-prefix included: `local://`). Default: root.
|
|
283
|
+
*
|
|
284
|
+
* gorunum:v3-shell — a VIEW is addressable here too, by its sentinel:
|
|
285
|
+
* `'.home'` opens the overview (storages · recent · starred), and
|
|
286
|
+
* `'.recent'` / `'.starred'` / `'.shared'` / `'.trash'` / `'.tag~invoices'`
|
|
287
|
+
* open theirs. That is how our own `/home` route opens Home without a second
|
|
288
|
+
* mechanism — the sentinels are the same strings the address-bar hash, the
|
|
289
|
+
* tab strip and a restored session already speak (lib/listing
|
|
290
|
+
* VIRTUAL_SEGMENTS), so there is one answer to "where does this explorer
|
|
291
|
+
* start" rather than a path answer and a view answer that can disagree.
|
|
292
|
+
*
|
|
293
|
+
* ⚠ A sentinel this build does not know keeps going to the backend as a
|
|
294
|
+
* folder name, so a host cannot invent views by passing strings.
|
|
295
|
+
*/
|
|
263
296
|
initialPath?: string;
|
|
264
297
|
|
|
265
298
|
/**
|
|
@@ -270,6 +303,42 @@ export interface ExplorerConfig {
|
|
|
270
303
|
*/
|
|
271
304
|
rootPath?: string;
|
|
272
305
|
|
|
306
|
+
/**
|
|
307
|
+
* gorunum:v3-shell — the product mark at the far left of the top bar: the
|
|
308
|
+
* wordmark beside it, and an image for the mark itself.
|
|
309
|
+
*
|
|
310
|
+
* ⚠⚠ This exists because a `<slot>` IS NOT REACHABLE from a host that mounts
|
|
311
|
+
* `<filex-explorer>`, and that is not a bug anybody can fix in a line.
|
|
312
|
+
* Measured, 2026-09-13, in a real browser with Vue's own
|
|
313
|
+
* `defineCustomElement`: a `<span slot="brand">` placed inside the element
|
|
314
|
+
* leaves `Object.keys(slots)` EMPTY in the element's `setup` — with the
|
|
315
|
+
* wrapper forwarding slots, without it, when the node is added after mount,
|
|
316
|
+
* and with `shadowRoot: true` as well. Vue projects light DOM into a custom
|
|
317
|
+
* element only through a NATIVE `<slot>` element inside a shadow root, which
|
|
318
|
+
* this package cannot have: its entire look is one global stylesheet
|
|
319
|
+
* (`styles/base.css`), and a shadow root would leave every embed unstyled.
|
|
320
|
+
*
|
|
321
|
+
* So the slot is for hosts that mount the Vue SFC (our admin app does, and
|
|
322
|
+
* its `#brand` still wins when it is filled); this is for everybody else —
|
|
323
|
+
* and "everybody else" includes OUR OWN DESKTOP APP, which mounts the web
|
|
324
|
+
* component. A logo the web app has and the desktop app silently lacks is
|
|
325
|
+
* exactly the one-surface split this package exists to prevent.
|
|
326
|
+
*
|
|
327
|
+
* ⚠ An `<img src>`, not markup. The mark is drawn from a URL — a file, or a
|
|
328
|
+
* `data:image/svg+xml;base64,…` URI for an inline logo — so a host never
|
|
329
|
+
* hands this package a string to inject. There is no `v-html` on the path.
|
|
330
|
+
*
|
|
331
|
+
* ⚠ Both halves are optional and independent: a name with no mark renders
|
|
332
|
+
* the wordmark alone, a mark with no name renders the glyph alone, and
|
|
333
|
+
* neither renders nothing at all (the collapse control still sits there).
|
|
334
|
+
*/
|
|
335
|
+
brand?: {
|
|
336
|
+
/** Wordmark text, e.g. `'filex'`. Printed verbatim, never translated. */
|
|
337
|
+
name?: string;
|
|
338
|
+
/** URL of the mark: a path, an absolute URL, or a `data:` URI. */
|
|
339
|
+
markUrl?: string;
|
|
340
|
+
};
|
|
341
|
+
|
|
273
342
|
/**
|
|
274
343
|
* Whether the info-panel (inspector) toggle is shown in the toolbar.
|
|
275
344
|
* Default true. The inspector itself stays reachable from the context menu.
|
|
@@ -277,44 +346,42 @@ export interface ExplorerConfig {
|
|
|
277
346
|
showInfoPanel?: boolean;
|
|
278
347
|
|
|
279
348
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* '
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
*/
|
|
317
|
-
uiProfile?: 'standard' | 'simple' | 'drive';
|
|
349
|
+
* How much of the explorer to put on screen. A REDUCTION, and only that.
|
|
350
|
+
*
|
|
351
|
+
* 'standard' (default) — everything: the tab strip, the split pane and all
|
|
352
|
+
* three view modes, on top of the shell below.
|
|
353
|
+
* 'simple' — one pane, one folder, list and grid only. The tab
|
|
354
|
+
* strip and the split pane are off, the gallery view
|
|
355
|
+
* mode is hidden, and the "How to connect" /
|
|
356
|
+
* "API keys" entries default to off.
|
|
357
|
+
*
|
|
358
|
+
* There is no third value and no alias. ⚠ Anything else that arrives here —
|
|
359
|
+
* a typo, or the `'drive'` profile that was REMOVED after v0.40.0 —
|
|
360
|
+
* resolves to `'standard'` and logs one console line naming it
|
|
361
|
+
* (`lib/uiProfile.resolveUiProfile`, which explains why that direction and
|
|
362
|
+
* not the other). **If you passed `'drive'`, pass `'simple'`.**
|
|
363
|
+
*
|
|
364
|
+
* ⚠⚠ IT DOES NOT DECIDE THE LOOK, and that is the whole of this option.
|
|
365
|
+
* The header with its one wide search field, the "+ New" menu, the filter
|
|
366
|
+
* row, the Folders/Files sections, the Details/Activity tabs and the storage
|
|
367
|
+
* line USED to be gated behind a profile. They are what filex is now — the
|
|
368
|
+
* admin panel, the desktop app and every `<filex-explorer>` embed draw them,
|
|
369
|
+
* with no string passed. Owner's decision, 2026-09-12, verbatim (translated
|
|
370
|
+
* from Turkish): "their app and our app will be one to one. The admin gets
|
|
371
|
+
* one extra button, nothing else. The things we have over them — tabs, split
|
|
372
|
+
* pane, theme choice, icon choice — stay."
|
|
373
|
+
*
|
|
374
|
+
* Why the reduction exists at all (GitHub #14): the reporter's users are not
|
|
375
|
+
* in IT and read split panes, tabs and mount instructions as a file manager
|
|
376
|
+
* they would have to relearn. The answer was NOT a second UI — one explorer,
|
|
377
|
+
* configured, so a fix lands in one place for every surface that mounts this
|
|
378
|
+
* package.
|
|
379
|
+
*
|
|
380
|
+
* ⚠ It does not gate the navigation panel. The panel ships in every profile,
|
|
381
|
+
* for administrators too; only a viewer's own collapse choice moves it, and
|
|
382
|
+
* that is a per-viewer preference, not a policy.
|
|
383
|
+
*/
|
|
384
|
+
uiProfile?: UiProfile;
|
|
318
385
|
|
|
319
386
|
/**
|
|
320
387
|
* Render the navigation panel (Upload · Recent / Starred / Shared with me /
|
|
@@ -449,11 +516,49 @@ export interface ExplorerConfig {
|
|
|
449
516
|
* + (optional) display label / read-only flag. The SFC mirrors
|
|
450
517
|
* each entry as a virtual `dir` row at "/".
|
|
451
518
|
*/
|
|
519
|
+
/**
|
|
520
|
+
* tablo:t1 — remember how each folder was last viewed (view mode + sort),
|
|
521
|
+
* Windows Explorer style. Default ON.
|
|
522
|
+
*
|
|
523
|
+
* ⚠ The opt-out exists for embeds. A product mounting filex in a two-inch
|
|
524
|
+
* panel has one shape it wants and no room to argue with a gallery view
|
|
525
|
+
* arriving from the person's main window; setting this false makes every
|
|
526
|
+
* folder open in the host's chosen default and writes nothing. The columns
|
|
527
|
+
* are NOT covered by this flag — their widths are a global preference about
|
|
528
|
+
* the reader's screen, and a panel that narrow sheds them for want of room
|
|
529
|
+
* before any preference is consulted.
|
|
530
|
+
*/
|
|
531
|
+
rememberFolderView?: boolean;
|
|
452
532
|
storages?: Array<{
|
|
453
533
|
name: string;
|
|
534
|
+
/**
|
|
535
|
+
* tablo:t1 — the storage's immutable uid, when the host knows it.
|
|
536
|
+
*
|
|
537
|
+
* ⚠ A storage's NAME is editable — that is the point of a name — so it is
|
|
538
|
+
* not a stable address, which is why every protocol also accepts the uid
|
|
539
|
+
* as a path's first segment (`backend/internal/storageref`, filex issue
|
|
540
|
+
* #21). Anything keyed on a storage for the long term should prefer this:
|
|
541
|
+
* per-folder view memory does (`lib/viewPrefs.folderKey`), and folders
|
|
542
|
+
* under a renamed storage keep their remembered view the moment a host
|
|
543
|
+
* starts filling it in. Absent = the name is used and a rename loses the
|
|
544
|
+
* memory, which is a mild, self-healing loss.
|
|
545
|
+
*/
|
|
546
|
+
uid?: string;
|
|
454
547
|
label?: string;
|
|
455
548
|
driver?: string;
|
|
456
549
|
readOnly?: boolean;
|
|
550
|
+
/**
|
|
551
|
+
* gorunum:v3-shell — bytes this storage holds, when the host knows. Drawn
|
|
552
|
+
* as the caption on the Home view's storage card; absent means the card
|
|
553
|
+
* names the kind of thing instead.
|
|
554
|
+
*
|
|
555
|
+
* ⚠ It must be the SAME quantity for every caller who gets it (in our own
|
|
556
|
+
* app: `/api/admin/storages` for an operator, the RBAC-filtered
|
|
557
|
+
* `/api/files/quota/storages` for everybody else). The per-USER quota is a
|
|
558
|
+
* sum across every storage, so passing that here would print a number
|
|
559
|
+
* about the person under a label about the drive.
|
|
560
|
+
*/
|
|
561
|
+
usedBytes?: number;
|
|
457
562
|
}>;
|
|
458
563
|
|
|
459
564
|
/**
|
package/src/types/FileNode.ts
CHANGED
|
@@ -76,7 +76,31 @@ export interface ExternalServiceStatus {
|
|
|
76
76
|
detail?: string;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* One document type the SERVER can create, from `capabilities.newdoc_types`.
|
|
81
|
+
*
|
|
82
|
+
* ⚠ `requires` is the whole point of shipping this list instead of hardcoding
|
|
83
|
+
* one in the client. The server knows it holds template bytes for `.docx`; it
|
|
84
|
+
* does NOT know whether this deployment has a document server that can open
|
|
85
|
+
* one. So it names the dependency and the client — which already resolves
|
|
86
|
+
* OnlyOffice/drawio, config override included — answers it. A client that
|
|
87
|
+
* re-derived "docx needs OnlyOffice" from a list of its own would be a second
|
|
88
|
+
* source of truth, and the one that rots first.
|
|
89
|
+
*/
|
|
90
|
+
export interface NewDocType {
|
|
91
|
+
/** Extension without the dot, lowercase. Also the key the create call sends. */
|
|
92
|
+
ext: string;
|
|
93
|
+
/** Coarse family, used for the picker's section headings. */
|
|
94
|
+
group: 'document' | 'text' | 'diagram';
|
|
95
|
+
mime: string;
|
|
96
|
+
/** External service the editor for this type needs; absent = built-in. */
|
|
97
|
+
requires?: 'onlyoffice' | 'drawio';
|
|
98
|
+
}
|
|
99
|
+
|
|
79
100
|
export interface Capabilities {
|
|
101
|
+
/** Document types this build can create. Absent on a server older than the
|
|
102
|
+
* "New document" feature — hosts must treat that as "offer nothing". */
|
|
103
|
+
newdoc_types?: NewDocType[];
|
|
80
104
|
ffmpeg?: boolean;
|
|
81
105
|
ghostscript?: boolean;
|
|
82
106
|
libreoffice?: boolean;
|
|
@@ -94,6 +118,10 @@ export interface Capabilities {
|
|
|
94
118
|
* ExplorerConfig.callerKind. Absent on a server older than the app/user
|
|
95
119
|
* token split, which is why every reader treats "missing" as a person. */
|
|
96
120
|
caller_kind?: 'user' | 'app';
|
|
121
|
+
/** The address this deployment is reached at — only when it is real (the
|
|
122
|
+
* operator configured it, or the request came in on a tenant's host).
|
|
123
|
+
* Read by the connection guides; see `connectionsOrigin`. */
|
|
124
|
+
public_url?: string;
|
|
97
125
|
external?: {
|
|
98
126
|
onlyoffice?: ExternalServiceStatus;
|
|
99
127
|
drawio?: ExternalServiceStatus;
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* decide whether to extract or download.
|
|
11
11
|
*/
|
|
12
12
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
13
|
+
import { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */
|
|
14
|
+
import { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */
|
|
13
15
|
|
|
14
16
|
interface ArchiveEntry {
|
|
15
17
|
name: string;
|
|
@@ -22,7 +24,7 @@ const props = defineProps<{
|
|
|
22
24
|
url: string;
|
|
23
25
|
filePath?: string;
|
|
24
26
|
ext: string;
|
|
25
|
-
t?: (key: string) => string;
|
|
27
|
+
t?: (key: string, vars?: Record<string, string | number>) => string;
|
|
26
28
|
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
27
29
|
authCredentials?: RequestCredentials;
|
|
28
30
|
/** ⚠ Where to ask. This used to be the literal '/api/files/archive/list',
|
|
@@ -37,8 +39,11 @@ const entries = ref<ArchiveEntry[]>([]);
|
|
|
37
39
|
const loading = ref(true);
|
|
38
40
|
const error = ref<string | null>(null);
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
/** ⚠ `vars` go THROUGH `t()`, not into a `.replace()` afterwards: `t()` is what
|
|
43
|
+
* picks the singular (`viewer.archive.entries_one`) from the count, and it can
|
|
44
|
+
* only do that if it is told the count. Same shape CsvViewer's helper has. */
|
|
45
|
+
function tt(key: string, fallback: string, vars?: Record<string, string | number>): string {
|
|
46
|
+
return props.t ? props.t(key, vars) : fallback;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
function fmtSize(n: number): string {
|
|
@@ -87,25 +92,54 @@ watch(() => props.filePath, load);
|
|
|
87
92
|
|
|
88
93
|
const totalSize = computed(() => entries.value.reduce((sum, e) => sum + (e.size || 0), 0));
|
|
89
94
|
const fileCount = computed(() => entries.value.filter((e) => !e.is_dir).length);
|
|
95
|
+
|
|
96
|
+
/* === ikon:emoji — the fallback screen's mark ==========================
|
|
97
|
+
* Every viewer opened its "cannot show this" / "still loading" screen with a
|
|
98
|
+
* 48px colour emoji, one per format, each from whatever emoji font the OS
|
|
99
|
+
* shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row
|
|
100
|
+
* the person just clicked is wearing, so the fallback is recognisably about
|
|
101
|
+
* that file — and "loading" is the stroked ring, spun by CSS, because no
|
|
102
|
+
* still picture can say "still going". */
|
|
103
|
+
const typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));
|
|
104
|
+
|
|
105
|
+
/** A row inside the archive, drawn the way the listing draws the same kind. */
|
|
106
|
+
function entryTile(e: ArchiveEntry): string {
|
|
107
|
+
const name = e.name || '';
|
|
108
|
+
const dot = name.lastIndexOf('.');
|
|
109
|
+
return fileIconTile({
|
|
110
|
+
type: e.is_dir ? 'dir' : 'file',
|
|
111
|
+
extension: dot > 0 ? name.slice(dot + 1) : '',
|
|
112
|
+
basename: name,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
90
115
|
</script>
|
|
91
116
|
|
|
92
117
|
<template>
|
|
93
118
|
<div class="filex-viewer-archive">
|
|
94
119
|
<div v-if="error" class="filex-viewer-fallback">
|
|
95
|
-
|
|
120
|
+
<!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->
|
|
121
|
+
<span class="filex-viewer-fallback__icon" aria-hidden="true" v-html="typeTile"></span>
|
|
96
122
|
<p>{{ error }}</p>
|
|
97
123
|
</div>
|
|
98
124
|
<div v-else-if="loading" class="filex-viewer-fallback">
|
|
99
|
-
|
|
125
|
+
<!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->
|
|
126
|
+
<span
|
|
127
|
+
class="filex-viewer-fallback__icon filex-viewer-fallback__icon--spin"
|
|
128
|
+
aria-hidden="true"
|
|
129
|
+
v-html="actionIconSvg('progress')"
|
|
130
|
+
></span>
|
|
100
131
|
<p>{{ tt('viewer.loading', 'Loading…') }}</p>
|
|
101
132
|
</div>
|
|
102
133
|
<div v-else-if="entries.length === 0" class="filex-viewer-fallback">
|
|
103
|
-
|
|
134
|
+
<!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->
|
|
135
|
+
<span class="filex-viewer-fallback__icon" aria-hidden="true" v-html="typeTile"></span>
|
|
104
136
|
<p>{{ tt('viewer.archive.empty', 'Archive is empty.') }}</p>
|
|
105
137
|
</div>
|
|
106
138
|
<div v-else class="filex-viewer-archive__pane">
|
|
107
139
|
<div class="filex-viewer-archive__summary">
|
|
108
|
-
|
|
140
|
+
<!-- ⚠ The count is IN the message ("{n} files"). A `{{ fileCount }}`
|
|
141
|
+
printed in front of it read "3 3 files". -->
|
|
142
|
+
{{ tt('viewer.archive.entries', `${fileCount} files`, { n: fileCount }) }}
|
|
109
143
|
· {{ fmtSize(totalSize) }}
|
|
110
144
|
</div>
|
|
111
145
|
<table class="filex-viewer-archive__table">
|
|
@@ -118,7 +152,11 @@ const fileCount = computed(() => entries.value.filter((e) => !e.is_dir).length);
|
|
|
118
152
|
<tbody>
|
|
119
153
|
<tr v-for="(e, i) in entries" :key="i" :data-dir="e.is_dir ? '1' : '0'">
|
|
120
154
|
<td>
|
|
121
|
-
|
|
155
|
+
<!-- ikon:emoji — the same tile the listing draws for the same
|
|
156
|
+
kind of file, so an archive's contents and the folder it
|
|
157
|
+
came from read as one product. -->
|
|
158
|
+
<!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons -->
|
|
159
|
+
<span class="filex-viewer-archive__icon" aria-hidden="true" v-html="entryTile(e)"></span>
|
|
122
160
|
{{ e.name }}
|
|
123
161
|
</td>
|
|
124
162
|
<td class="filex-viewer-archive__size">{{ e.is_dir ? '' : fmtSize(e.size) }}</td>
|