@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
|
@@ -33,8 +33,26 @@ import { useLocale } from '../composables/useLocale';
|
|
|
33
33
|
import type { LocaleCode, ThemeMode } from '../types/ExplorerConfig';
|
|
34
34
|
import ContextMenu, { type ContextAction } from './ContextMenu.vue';
|
|
35
35
|
|
|
36
|
-
/** The virtual listings the panel can open. '' = an ordinary folder.
|
|
37
|
-
|
|
36
|
+
/** The virtual listings the panel can open. '' = an ordinary folder.
|
|
37
|
+
* ⚠ `home` is not a listing: it is the overview (storages · recent · starred)
|
|
38
|
+
* the reference shell lands on, and the explorer renders it instead of a file
|
|
39
|
+
* list. It is in this union because the panel's selected-row logic, the
|
|
40
|
+
* address bar and the tab strip all key off ONE type — a second mechanism for
|
|
41
|
+
* "which destination am I on" is how two of them end up disagreeing. */
|
|
42
|
+
export type NavView = '' | 'home' | 'recent' | 'starred' | 'shared' | 'trash' | 'tag';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A row in the panel's first group — what `open-view` carries.
|
|
46
|
+
*
|
|
47
|
+
* ⚠ It is NOT `NavView`. "My files" is a destination without being a view: it
|
|
48
|
+
* opens the root LISTING, so the explorer answers it with an ordinary
|
|
49
|
+
* navigation and `activeView` goes back to `''`. Giving it a NavView value
|
|
50
|
+
* would mean a mode that every `if (navView)` in the explorer — "is this a
|
|
51
|
+
* cross-folder view?", "is there a folder to upload into?", "what does Up do?"
|
|
52
|
+
* — would answer wrongly, for a row whose whole job is to be an ordinary
|
|
53
|
+
* folder again.
|
|
54
|
+
*/
|
|
55
|
+
export type NavDest = Exclude<NavView, '' | 'tag'> | 'myfiles';
|
|
38
56
|
|
|
39
57
|
export interface NavStorage {
|
|
40
58
|
name: string;
|
|
@@ -99,24 +117,28 @@ const props = defineProps<{
|
|
|
99
117
|
/** RBAC/root state — false hides the write affordances. */
|
|
100
118
|
canWrite?: boolean;
|
|
101
119
|
locale: LocaleCode;
|
|
102
|
-
/* === surucu:d1 — the
|
|
120
|
+
/* === surucu:d1 — the shell ========================================== */
|
|
103
121
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
122
|
+
* ⚠ There is no `newMenu` prop any more. The two-button block (Upload as the
|
|
123
|
+
* primary, New folder one step quieter) is gone and the "+ New" menu is what
|
|
124
|
+
* this panel draws, everywhere — it is the shell, not a profile. Nothing was
|
|
125
|
+
* removed: the menu holds what the explorer could already do — upload files,
|
|
126
|
+
* make a folder (the modal offers the encrypted variant from inside itself),
|
|
127
|
+
* and ask somebody else for files.
|
|
109
128
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* directories itself, and an entry that quietly flattens someone's folder into
|
|
115
|
-
* one heap is worse than an entry that is not there.
|
|
129
|
+
* There is deliberately no "Upload folder": the upload path takes a flat
|
|
130
|
+
* `File[]` and would have to create the intermediate directories itself, and
|
|
131
|
+
* an entry that quietly flattens someone's folder into one heap is worse
|
|
132
|
+
* than an entry that is not there.
|
|
116
133
|
*/
|
|
117
|
-
newMenu?: boolean;
|
|
118
134
|
/** Offer "Request files" in that menu — a folder we may write to and share. */
|
|
119
135
|
canRequestFiles?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* belge:n1 — offer "New document" in that menu. False on a server that
|
|
138
|
+
* publishes no `newdoc_types`, and on one where every type it publishes
|
|
139
|
+
* needs an editor service this deployment has not got.
|
|
140
|
+
*/
|
|
141
|
+
canNewDocument?: boolean;
|
|
120
142
|
/**
|
|
121
143
|
* The signed-in person's storage line, from `GET /api/files/quota/me`. Null
|
|
122
144
|
* (the default) renders nothing at all.
|
|
@@ -134,12 +156,13 @@ const props = defineProps<{
|
|
|
134
156
|
|
|
135
157
|
const emit = defineEmits<{
|
|
136
158
|
(e: 'toggle'): void;
|
|
137
|
-
(e: 'open-view', view:
|
|
159
|
+
(e: 'open-view', view: NavDest): void;
|
|
138
160
|
(e: 'open-tag', tag: string): void;
|
|
139
161
|
(e: 'open-storage', name: string): void;
|
|
140
162
|
(e: 'open-root'): void;
|
|
141
163
|
(e: 'upload'): void;
|
|
142
164
|
(e: 'new-folder'): void;
|
|
165
|
+
(e: 'new-document'): void;
|
|
143
166
|
(e: 'open-connections'): void;
|
|
144
167
|
(e: 'open-tokens'): void;
|
|
145
168
|
/* surucu:d1 — "Request files": the access modal on THIS folder, drop tab. */
|
|
@@ -148,7 +171,7 @@ const emit = defineEmits<{
|
|
|
148
171
|
(e: 'close'): void;
|
|
149
172
|
}>();
|
|
150
173
|
|
|
151
|
-
const { t } = useLocale(() => props.locale);
|
|
174
|
+
const { t, formatSize } = useLocale(() => props.locale);
|
|
152
175
|
|
|
153
176
|
// In drawer mode "expanded" is the only meaningful state: a rail inside an
|
|
154
177
|
// overlay would be an overlay that shows nothing but icons while covering the
|
|
@@ -160,11 +183,54 @@ const sharedSet = computed(() => new Set(props.sharedStorages ?? []));
|
|
|
160
183
|
/** The views that answer "what did *I* do" — dropped for an app token. */
|
|
161
184
|
const IDENTITY_VIEWS = new Set<string>(['recent', 'starred', 'shared']);
|
|
162
185
|
|
|
186
|
+
/**
|
|
187
|
+
* gorunum:v3-shell — the destinations, and their ORDER.
|
|
188
|
+
*
|
|
189
|
+
* Home · My files · Shared with me · Recent · Starred · Trash. The order is
|
|
190
|
+
* not a taste: it runs from the widest answer to the narrowest — everything
|
|
191
|
+
* you have, then your own tree, then somebody else's, then two slices of your
|
|
192
|
+
* own, then what you threw away. The panel used to open with Recent, which
|
|
193
|
+
* put a slice of the tree above the tree.
|
|
194
|
+
*
|
|
195
|
+
* ⚠⚠ "My files" is drawn ONLY where it can honestly mean "my files" — a
|
|
196
|
+
* deployment whose caller can see at most ONE storage. There it opens that
|
|
197
|
+
* storage's root and the label is the truth (measured: `soleStorageName` in
|
|
198
|
+
* FileExplorer sends `load('')` straight to the storage, so the row lands on
|
|
199
|
+
* the file list, exactly as the reference build's own "My files" does).
|
|
200
|
+
*
|
|
201
|
+
* With SEVERAL storages it cannot mean that, and what it actually did was open
|
|
202
|
+
* the multi-storage root — a listing OF THE DRIVES. Owner, 2026-09-13: "my
|
|
203
|
+
* files kısmında yüklediğimiz dosyalar gelmesi lazım ama onun yerine storages
|
|
204
|
+
* gösteren ana bölge geliyor… direk silebiliriz." Three reasons it goes rather
|
|
205
|
+
* than gets repointed:
|
|
206
|
+
*
|
|
207
|
+
* 1. it was the THIRD copy of that list. Home's "Storages" section draws the
|
|
208
|
+
* same drives as cards with a size caption, and this panel's own STORAGES
|
|
209
|
+
* group draws them one click away — and all three are on screen together.
|
|
210
|
+
* "Aynı işlevi yapan iki buton olmaması lazım."
|
|
211
|
+
* 2. it was the worst of the three. The drives are synthesized rows, so the
|
|
212
|
+
* listing shows them under Type / Modified / Size columns that are all
|
|
213
|
+
* "—", owner "System", and with no filter row at all.
|
|
214
|
+
* 3. nothing is stranded. Measured in the browser: the breadcrumb's house
|
|
215
|
+
* crumb (`crumbs[0]`, `adapterPath: ''`) and Alt+↑ from a storage root
|
|
216
|
+
* both land on exactly that listing, and both leave the panel in the same
|
|
217
|
+
* state this row used to.
|
|
218
|
+
*
|
|
219
|
+
* ⚠ `<= 1`, not `=== 1`: a caller who can see NO storage keeps the row, so an
|
|
220
|
+
* account with nothing mounted still has a door back to the root listing and
|
|
221
|
+
* its "no storages" message, rather than a panel of views with no files in it.
|
|
222
|
+
* ⚠ It is still not an identity view — where it IS drawn it stays drawn for an
|
|
223
|
+
* app token, which has no Recent and no Starred to escape through.
|
|
224
|
+
*/
|
|
163
225
|
const views = computed(() => {
|
|
164
|
-
const list: Array<{ key:
|
|
226
|
+
const list: Array<{ key: NavDest; label: string }> = [
|
|
227
|
+
{ key: 'home', label: t('sidenav.home') },
|
|
228
|
+
...(props.storages.length <= 1
|
|
229
|
+
? [{ key: 'myfiles' as NavDest, label: t('sidenav.myfiles') }]
|
|
230
|
+
: []),
|
|
231
|
+
{ key: 'shared', label: t('sidenav.shared') },
|
|
165
232
|
{ key: 'recent', label: t('sidenav.recent') },
|
|
166
233
|
{ key: 'starred', label: t('sidenav.starred') },
|
|
167
|
-
{ key: 'shared', label: t('sidenav.shared') },
|
|
168
234
|
];
|
|
169
235
|
if (props.trashVisible !== false) list.push({ key: 'trash', label: t('sidenav.trash') });
|
|
170
236
|
// Filtered at the end rather than built conditionally: Trash is shared by
|
|
@@ -176,6 +242,21 @@ const views = computed(() => {
|
|
|
176
242
|
|
|
177
243
|
const writable = computed(() => props.canWrite !== false);
|
|
178
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Which row reads as the one you are standing on.
|
|
247
|
+
*
|
|
248
|
+
* Every view answers for itself. "My files" is the exception, because it has
|
|
249
|
+
* no view value to compare against: it is lit when the explorer is showing the
|
|
250
|
+
* ROOT of the tree and nothing else — no virtual view, and no storage opened,
|
|
251
|
+
* which is the state where the storage rows below all read as inactive too.
|
|
252
|
+
* Inside a storage the storage's own row is the active one, so lighting both
|
|
253
|
+
* would claim the selection is in two places.
|
|
254
|
+
*/
|
|
255
|
+
function isActiveDest(key: NavDest): boolean {
|
|
256
|
+
if (key === 'myfiles') return !props.activeView && !props.activeStorage;
|
|
257
|
+
return props.activeView === key;
|
|
258
|
+
}
|
|
259
|
+
|
|
179
260
|
/* === etiket:t1 — an unbounded list in a fixed panel ====================
|
|
180
261
|
* A user with sixty tags must not push Storages and Connections off the
|
|
181
262
|
* bottom of the panel, and must not be handed sixty identical glyphs on a
|
|
@@ -212,9 +293,29 @@ const newMenuRef = ref<InstanceType<typeof ContextMenu> | null>(null);
|
|
|
212
293
|
|
|
213
294
|
const newActions = computed<ContextAction[]>(() => {
|
|
214
295
|
const list: ContextAction[] = [
|
|
215
|
-
|
|
216
|
-
|
|
296
|
+
/* ⚠ NO `icon:` on any of these three, and that is the fix rather than an
|
|
297
|
+
omission. `ContextMenu.iconFor` resolves `actionIconSvg(a.icon || a.key)`,
|
|
298
|
+
so an `icon` string is an OVERRIDE — and `'⬆'` / `'📁'` / `'🔗'` are not
|
|
299
|
+
keys, so the lookup missed, the component fell through to its literal
|
|
300
|
+
branch and printed the emoji. Meanwhile `new-document`, which carries no
|
|
301
|
+
override, resolved by key and drew a proper glyph: one menu, two icon
|
|
302
|
+
systems, three rows apart. All three keys have had stroked glyphs in
|
|
303
|
+
`lib/actionIcons.ts` the whole time (`upload`, `new-folder`,
|
|
304
|
+
`request-files`); dropping the override is what reaches them.
|
|
305
|
+
⚠ Emoji in markup is forbidden here for a reason that is visible in this
|
|
306
|
+
very menu: they are rendered by whatever font the OS ships, so the row
|
|
307
|
+
came out flat-grey on one machine and full-colour on another, at a weight
|
|
308
|
+
that matched nothing beside it. */
|
|
309
|
+
{ key: 'upload', label: t('drive.new.upload'), disabled: !writable.value },
|
|
310
|
+
{ key: 'new-folder', label: t('drive.new.folder'), disabled: !writable.value },
|
|
217
311
|
];
|
|
312
|
+
// belge:n1 — DROPPED rather than disabled when unavailable, unlike
|
|
313
|
+
// "Request files": a disabled row invites the question "why?" and cannot
|
|
314
|
+
// answer it. The dialog behind it explains what is missing; a greyed row
|
|
315
|
+
// in a menu has nowhere to say so.
|
|
316
|
+
if (props.canNewDocument) {
|
|
317
|
+
list.push({ key: 'new-document', label: t('drive.new.document'), disabled: !writable.value });
|
|
318
|
+
}
|
|
218
319
|
// Only when there is a real folder to hang a drop link on. Rendered as a
|
|
219
320
|
// disabled row rather than dropped, so the menu does not change height
|
|
220
321
|
// between folders — a menu whose items move is a menu people misclick.
|
|
@@ -222,7 +323,6 @@ const newActions = computed<ContextAction[]>(() => {
|
|
|
222
323
|
list.push({
|
|
223
324
|
key: 'request-files',
|
|
224
325
|
label: t('drive.new.request'),
|
|
225
|
-
icon: '🔗',
|
|
226
326
|
disabled: !props.canRequestFiles,
|
|
227
327
|
});
|
|
228
328
|
return list;
|
|
@@ -238,23 +338,17 @@ function openNewMenu() {
|
|
|
238
338
|
|
|
239
339
|
function onNewSelect(a: ContextAction) {
|
|
240
340
|
if (a.key === 'upload') emit('upload');
|
|
341
|
+
else if (a.key === 'new-document') emit('new-document');
|
|
241
342
|
else if (a.key === 'new-folder') emit('new-folder');
|
|
242
343
|
else if (a.key === 'request-files') emit('request-files');
|
|
243
344
|
}
|
|
244
345
|
|
|
245
|
-
/* === surucu:d1 — the storage line ===================================
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
while (v >= 1024 && i < units.length - 1) {
|
|
252
|
-
v /= 1024;
|
|
253
|
-
i += 1;
|
|
254
|
-
}
|
|
255
|
-
const digits = v < 10 && i > 0 ? 1 : 0;
|
|
256
|
-
return `${v.toFixed(digits)} ${units[i]}`;
|
|
257
|
-
}
|
|
346
|
+
/* === surucu:d1 — the storage line ===================================
|
|
347
|
+
* The size comes from `useLocale.formatSize`, the same call `HomeView` makes
|
|
348
|
+
* for the same `drive.storage.*` string. There was a private byte formatter
|
|
349
|
+
* here with hardcoded English units and its own rounding, so one quota
|
|
350
|
+
* rendered two ways — "9.3 GB" in this rail, "9.31 GB" on the home screen,
|
|
351
|
+
* and "10 GB" in the admin panel that set it. */
|
|
258
352
|
|
|
259
353
|
const quotaPercent = computed(() => {
|
|
260
354
|
const q = props.quota;
|
|
@@ -266,17 +360,12 @@ const quotaText = computed(() => {
|
|
|
266
360
|
const q = props.quota;
|
|
267
361
|
if (!q) return '';
|
|
268
362
|
return q.unlimited || q.total <= 0
|
|
269
|
-
? t('drive.storage.used_unlimited', { used:
|
|
270
|
-
: t('drive.storage.used', { used:
|
|
363
|
+
? t('drive.storage.used_unlimited', { used: formatSize(q.used) })
|
|
364
|
+
: t('drive.storage.used', { used: formatSize(q.used), total: formatSize(q.total) });
|
|
271
365
|
});
|
|
272
366
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
? t('sidenav.close')
|
|
276
|
-
: props.expanded
|
|
277
|
-
? t('sidenav.collapse')
|
|
278
|
-
: t('sidenav.expand'),
|
|
279
|
-
);
|
|
367
|
+
/** Only the drawer draws this control now (see the template). */
|
|
368
|
+
const toggleLabel = computed(() => t('sidenav.close'));
|
|
280
369
|
</script>
|
|
281
370
|
|
|
282
371
|
<template>
|
|
@@ -290,7 +379,16 @@ const toggleLabel = computed(() =>
|
|
|
290
379
|
:aria-label="t('sidenav.title')"
|
|
291
380
|
data-testid="sidenav"
|
|
292
381
|
>
|
|
293
|
-
|
|
382
|
+
<!-- gorunum:v3-shell — the panel's own edge control is now the DRAWER's
|
|
383
|
+
close button and nothing else.
|
|
384
|
+
⚠ At 560px and up it is gone, deliberately: the collapse button lives
|
|
385
|
+
at the far left of the top bar (`.fe-toolbar__brand`), above the panel
|
|
386
|
+
rather than inside it, which is where the reference puts it and where
|
|
387
|
+
it still exists when the panel is a 56px rail. Two buttons for one
|
|
388
|
+
verb, one of them inside the thing it collapses, was the duplicate
|
|
389
|
+
this removed. At 390px the panel is an overlay ON TOP of the files, so
|
|
390
|
+
it keeps a dismiss of its own — the same reason a dialog has one. -->
|
|
391
|
+
<div v-if="narrow" class="fe-sidenav__head">
|
|
294
392
|
<button
|
|
295
393
|
type="button"
|
|
296
394
|
class="fe-sidenav__toggle"
|
|
@@ -298,13 +396,8 @@ const toggleLabel = computed(() =>
|
|
|
298
396
|
:title="toggleLabel"
|
|
299
397
|
:aria-label="toggleLabel"
|
|
300
398
|
data-testid="sidenav-toggle"
|
|
301
|
-
@click="
|
|
399
|
+
@click="emit('close')"
|
|
302
400
|
>
|
|
303
|
-
<!-- The glyph says what the control does NEXT, and each state gets
|
|
304
|
-
its own: an expanded panel closes (arrow into the sidebar), a rail
|
|
305
|
-
opens (arrow out of it), a drawer dismisses (cross). One hamburger
|
|
306
|
-
for all three reads as decoration — and this toolbar already
|
|
307
|
-
carries two other three-line glyphs. -->
|
|
308
401
|
<svg
|
|
309
402
|
class="fe-ficon"
|
|
310
403
|
viewBox="0 0 24 24"
|
|
@@ -316,34 +409,23 @@ const toggleLabel = computed(() =>
|
|
|
316
409
|
aria-hidden="true"
|
|
317
410
|
focusable="false"
|
|
318
411
|
>
|
|
319
|
-
<
|
|
320
|
-
<path d="M6 6l12 12M18 6L6 18" />
|
|
321
|
-
</template>
|
|
322
|
-
<template v-else-if="expanded">
|
|
323
|
-
<rect x="3.5" y="4.5" width="17" height="15" rx="2" />
|
|
324
|
-
<path d="M9.5 4.5v15" />
|
|
325
|
-
<path d="M17 9.5L14.5 12l2.5 2.5" />
|
|
326
|
-
</template>
|
|
327
|
-
<template v-else>
|
|
328
|
-
<rect x="3.5" y="4.5" width="17" height="15" rx="2" />
|
|
329
|
-
<path d="M9.5 4.5v15" />
|
|
330
|
-
<path d="M14.5 9.5L17 12l-2.5 2.5" />
|
|
331
|
-
</template>
|
|
412
|
+
<path d="M6 6l12 12M18 6L6 18" />
|
|
332
413
|
</svg>
|
|
333
414
|
</button>
|
|
334
415
|
</div>
|
|
335
416
|
|
|
336
|
-
<!-- Primary action.
|
|
337
|
-
|
|
338
|
-
fourteen.
|
|
417
|
+
<!-- Primary action. Making something is what people come to this panel to
|
|
418
|
+
do, so it reads as the main button rather than one toolbar icon among
|
|
419
|
+
fourteen. -->
|
|
339
420
|
<!-- Rendered even with nowhere to write, and disabled instead. A block
|
|
340
421
|
that appears and disappears makes every row below it jump by 90px each
|
|
341
422
|
time the user opens a view, which reads as the panel reloading. -->
|
|
342
423
|
<div class="fe-sidenav__primary">
|
|
343
|
-
<!-- surucu:d1 —
|
|
344
|
-
|
|
424
|
+
<!-- surucu:d1 — ONE primary action, the shape #14's mockups draw, in
|
|
425
|
+
every profile and every embed. The two-button block that used to
|
|
426
|
+
stand here behind `v-if="!newMenu"` (Upload + New folder) is gone:
|
|
427
|
+
both verbs are the first two rows of this menu. -->
|
|
345
428
|
<button
|
|
346
|
-
v-if="newMenu"
|
|
347
429
|
ref="newBtnEl"
|
|
348
430
|
type="button"
|
|
349
431
|
class="fe-sidenav__new"
|
|
@@ -353,72 +435,49 @@ const toggleLabel = computed(() =>
|
|
|
353
435
|
data-testid="sidenav-new"
|
|
354
436
|
@click="openNewMenu"
|
|
355
437
|
>
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
<span
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
class="fe-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
type="button"
|
|
400
|
-
class="fe-sidenav__secondary"
|
|
401
|
-
:disabled="!writable"
|
|
402
|
-
:title="t('toolbar.new_folder')"
|
|
403
|
-
:aria-label="t('toolbar.new_folder')"
|
|
404
|
-
data-testid="sidenav-new-folder"
|
|
405
|
-
@click="emit('new-folder')"
|
|
406
|
-
>
|
|
407
|
-
<svg
|
|
408
|
-
class="fe-ficon"
|
|
409
|
-
viewBox="0 0 24 24"
|
|
410
|
-
fill="none"
|
|
411
|
-
stroke="currentColor"
|
|
412
|
-
stroke-width="1.8"
|
|
413
|
-
stroke-linecap="round"
|
|
414
|
-
stroke-linejoin="round"
|
|
415
|
-
aria-hidden="true"
|
|
416
|
-
focusable="false"
|
|
417
|
-
>
|
|
418
|
-
<path d="M3 7.5A1.5 1.5 0 0 1 4.5 6h4l2 2.5h7A1.5 1.5 0 0 1 19 10v7.5a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 3 17.5z" />
|
|
419
|
-
<path d="M12 11.5v5M9.5 14h5" />
|
|
420
|
-
</svg>
|
|
421
|
-
<span v-if="showLabels" class="fe-sidenav__text">{{ t('toolbar.new_folder') }}</span>
|
|
438
|
+
<!-- ⚠⚠ TWO SPANS AND A DIVIDER, NOT A SPLIT BUTTON. The reference draws
|
|
439
|
+
a label half, a hairline and a chevron half; the owner asked twice
|
|
440
|
+
for the chevron ("new tuşunun yanına caret koymamışsın, onu koymanı
|
|
441
|
+
istiyorum") because a button that opens a menu has to look like
|
|
442
|
+
one. But it stays ONE `<button aria-haspopup="menu">`: a real split
|
|
443
|
+
button needs two focusable regions with two different actions, and
|
|
444
|
+
here both halves do the same thing. Copying the shape without
|
|
445
|
+
copying the mechanism is deliberate — two tab stops that lead to
|
|
446
|
+
one menu is a keyboard user counting controls that do not exist.
|
|
447
|
+
⚠ The divider is `aria-hidden` decoration; it is 16px inset inside
|
|
448
|
+
a 34px pill, not full height, or it reads as a seam between two
|
|
449
|
+
buttons — which is exactly the thing this is not. -->
|
|
450
|
+
<span class="fe-sidenav__new-main">
|
|
451
|
+
<svg
|
|
452
|
+
class="fe-ficon"
|
|
453
|
+
viewBox="0 0 24 24"
|
|
454
|
+
fill="none"
|
|
455
|
+
stroke="currentColor"
|
|
456
|
+
stroke-width="2"
|
|
457
|
+
stroke-linecap="round"
|
|
458
|
+
aria-hidden="true"
|
|
459
|
+
focusable="false"
|
|
460
|
+
>
|
|
461
|
+
<path d="M12 5v14M5 12h14" />
|
|
462
|
+
</svg>
|
|
463
|
+
<span v-if="showLabels" class="fe-sidenav__text">{{ t('drive.new') }}</span>
|
|
464
|
+
</span>
|
|
465
|
+
<span v-if="showLabels" class="fe-sidenav__new-rule" aria-hidden="true"></span>
|
|
466
|
+
<span v-if="showLabels" class="fe-sidenav__new-caret" aria-hidden="true">
|
|
467
|
+
<svg
|
|
468
|
+
class="fe-ficon"
|
|
469
|
+
viewBox="0 0 24 24"
|
|
470
|
+
fill="none"
|
|
471
|
+
stroke="currentColor"
|
|
472
|
+
stroke-width="2"
|
|
473
|
+
stroke-linecap="round"
|
|
474
|
+
stroke-linejoin="round"
|
|
475
|
+
aria-hidden="true"
|
|
476
|
+
focusable="false"
|
|
477
|
+
>
|
|
478
|
+
<path d="M7 10l5 5 5-5" />
|
|
479
|
+
</svg>
|
|
480
|
+
</span>
|
|
422
481
|
</button>
|
|
423
482
|
</div>
|
|
424
483
|
|
|
@@ -428,8 +487,8 @@ const toggleLabel = computed(() =>
|
|
|
428
487
|
<button
|
|
429
488
|
type="button"
|
|
430
489
|
class="fe-sidenav__item"
|
|
431
|
-
:class="{ 'is-active':
|
|
432
|
-
:aria-current="
|
|
490
|
+
:class="{ 'is-active': isActiveDest(v.key) }"
|
|
491
|
+
:aria-current="isActiveDest(v.key) ? 'page' : undefined"
|
|
433
492
|
:title="v.label"
|
|
434
493
|
:aria-label="v.label"
|
|
435
494
|
:data-testid="`sidenav-view-${v.key}`"
|
|
@@ -446,7 +505,15 @@ const toggleLabel = computed(() =>
|
|
|
446
505
|
aria-hidden="true"
|
|
447
506
|
focusable="false"
|
|
448
507
|
>
|
|
449
|
-
<template v-if="v.key === '
|
|
508
|
+
<template v-if="v.key === 'home'">
|
|
509
|
+
<path d="M4 10.5L12 4l8 6.5" />
|
|
510
|
+
<path d="M6 9.8V19a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V9.8" />
|
|
511
|
+
<path d="M10 20v-5.5h4V20" />
|
|
512
|
+
</template>
|
|
513
|
+
<template v-else-if="v.key === 'myfiles'">
|
|
514
|
+
<path d="M3.5 7.5A1.5 1.5 0 0 1 5 6h4l2 2.5h8A1.5 1.5 0 0 1 20.5 10v7.5A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5z" />
|
|
515
|
+
</template>
|
|
516
|
+
<template v-else-if="v.key === 'recent'">
|
|
450
517
|
<circle cx="12" cy="12" r="8.5" />
|
|
451
518
|
<path d="M12 7.5V12l3 2" />
|
|
452
519
|
</template>
|
|
@@ -721,7 +788,6 @@ const toggleLabel = computed(() =>
|
|
|
721
788
|
</div>
|
|
722
789
|
|
|
723
790
|
<ContextMenu
|
|
724
|
-
v-if="newMenu"
|
|
725
791
|
ref="newMenuRef"
|
|
726
792
|
:locale="locale"
|
|
727
793
|
:theme="theme"
|
|
@@ -19,6 +19,7 @@ import { computed, ref } from 'vue';
|
|
|
19
19
|
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
20
20
|
import type { StorageField, StorageFieldOption } from '../types/Connections';
|
|
21
21
|
import { useLocale } from '../composables/useLocale';
|
|
22
|
+
import { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */
|
|
22
23
|
|
|
23
24
|
const props = defineProps<{
|
|
24
25
|
fields: StorageField[];
|
|
@@ -175,7 +176,16 @@ function toggleReveal(key: string) {
|
|
|
175
176
|
:aria-label="revealed[f.key] ? t('conn.form.hide') : t('conn.form.reveal')"
|
|
176
177
|
@click="toggleReveal(f.key)"
|
|
177
178
|
>
|
|
178
|
-
|
|
179
|
+
<!-- ikon:emoji — the two states of a secret field. 👁/🙈 were a
|
|
180
|
+
hairline dingbat beside a full-colour monkey, i.e. two
|
|
181
|
+
different fonts for one toggle; these are the same eye the
|
|
182
|
+
context menu's Preview row uses, with and without its slash.
|
|
183
|
+
The state is named on the button (title + aria-label). -->
|
|
184
|
+
<!-- eslint-disable-next-line vue/no-v-html — static markup from lib/actionIcons -->
|
|
185
|
+
<span
|
|
186
|
+
aria-hidden="true"
|
|
187
|
+
v-html="actionIconSvg(revealed[f.key] ? 'toggle-hidden' : 'preview')"
|
|
188
|
+
></span>
|
|
179
189
|
</button>
|
|
180
190
|
</div>
|
|
181
191
|
|
|
@@ -313,6 +323,11 @@ function toggleReveal(key: string) {
|
|
|
313
323
|
}
|
|
314
324
|
.fe-cfield__eye {
|
|
315
325
|
flex: 0 0 auto;
|
|
326
|
+
/* ikon:emoji — it holds an SVG now, not a 👁, so it centres the box
|
|
327
|
+
instead of a text baseline. */
|
|
328
|
+
display: inline-flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
justify-content: center;
|
|
316
331
|
border: 1px solid var(--fe-border-strong);
|
|
317
332
|
background: var(--fe-bg-elev);
|
|
318
333
|
color: var(--fe-text);
|
|
@@ -321,6 +336,9 @@ function toggleReveal(key: string) {
|
|
|
321
336
|
cursor: pointer;
|
|
322
337
|
font-size: 14px;
|
|
323
338
|
}
|
|
339
|
+
.fe-cfield__eye .fe-aicon {
|
|
340
|
+
color: currentColor;
|
|
341
|
+
}
|
|
324
342
|
.fe-cfield__check {
|
|
325
343
|
display: flex;
|
|
326
344
|
align-items: center;
|