@brftech/filex-core 0.39.0 → 0.40.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 +9 -0
- package/dist/filex-core.js +7339 -7014
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +49 -49
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index.d.ts +84 -11
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +30 -0
- package/src/components/ContextMenu.vue +18 -0
- package/src/components/OnboardingTour.vue +11 -1
- package/src/components/QuickLook.vue +85 -33
- package/src/components/ShortcutSettings.vue +37 -0
- package/src/components/Toolbar.vue +28 -15
- package/src/composables/useKeyboardShortcuts.ts +175 -0
- package/src/index.ts +12 -0
- package/src/lib/themes.ts +96 -2
- package/src/locales/en.ts +21 -2
- package/src/locales/tr.ts +21 -2
- package/src/styles/base.css +42 -2
- package/src/styles/variables.css +6 -0
|
@@ -47,6 +47,22 @@ export interface ShortcutHandlers {
|
|
|
47
47
|
onTabNext?: () => void; // Ctrl+Tab
|
|
48
48
|
onTabPrev?: () => void; // Ctrl+Shift+Tab
|
|
49
49
|
/* /wiring:d1 */
|
|
50
|
+
/* tus:t1 — the verbs that were only ever reachable from the menus. Every
|
|
51
|
+
* one of them is remappable like the rest; the ones that ship unbound are
|
|
52
|
+
* there so the user can give them a key, not because they do less. */
|
|
53
|
+
onNewFolder?: () => void; // Shift+N
|
|
54
|
+
onUpload?: () => void; // U
|
|
55
|
+
onRefresh?: () => void; // R
|
|
56
|
+
onDownload?: () => void; // D
|
|
57
|
+
onPreview?: () => void; // P
|
|
58
|
+
onShare?: () => void; // Shift+S
|
|
59
|
+
onTags?: () => void; // T
|
|
60
|
+
onConvert?: () => void; // (unbound)
|
|
61
|
+
onOpenTab?: () => void; // (unbound)
|
|
62
|
+
onCopyPath?: () => void; // (unbound)
|
|
63
|
+
onCopyId?: () => void; // (unbound)
|
|
64
|
+
onRestore?: () => void; // (unbound)
|
|
65
|
+
/* /tus:t1 */
|
|
50
66
|
hasSelection?: () => boolean; // disambiguates Backspace
|
|
51
67
|
}
|
|
52
68
|
|
|
@@ -77,6 +93,33 @@ export interface ShortcutActionDef {
|
|
|
77
93
|
inForms?: boolean;
|
|
78
94
|
}
|
|
79
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Combos the BROWSER takes before the page sees them. `preventDefault()`
|
|
98
|
+
* cannot stop these in an ordinary tab — Chrome and Firefox handle them at
|
|
99
|
+
* the window level — so an action bound to one of them only ever fires in
|
|
100
|
+
* the desktop app, an installed PWA or a kiosk window.
|
|
101
|
+
*
|
|
102
|
+
* They are not forbidden as DEFAULTS (the tab actions have shipped on
|
|
103
|
+
* Ctrl+T/W/Tab since wiring:d1 and work in the desktop app), but the
|
|
104
|
+
* settings modal refuses to let a user assign one: choosing a key that
|
|
105
|
+
* silently does nothing where you are standing is not a choice, it is a
|
|
106
|
+
* trap. `isReservedCombo` is the one place that list lives.
|
|
107
|
+
*/
|
|
108
|
+
const RESERVED_COMBOS = new Set([
|
|
109
|
+
'Ctrl+N', 'Ctrl+Shift+N',
|
|
110
|
+
'Ctrl+T', 'Ctrl+Shift+T',
|
|
111
|
+
'Ctrl+W', 'Ctrl+Shift+W',
|
|
112
|
+
'Ctrl+Tab', 'Ctrl+Shift+Tab',
|
|
113
|
+
'Ctrl+Q',
|
|
114
|
+
'Ctrl+Shift+I', 'Ctrl+Shift+J', 'Ctrl+Shift+C',
|
|
115
|
+
'F11', 'F12',
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
/** Does the browser eat this combo before the page can see it? */
|
|
119
|
+
export function isReservedCombo(combo: string): boolean {
|
|
120
|
+
return RESERVED_COMBOS.has(combo);
|
|
121
|
+
}
|
|
122
|
+
|
|
80
123
|
/** Registry order = help/settings display order. */
|
|
81
124
|
export const SHORTCUT_ACTIONS: ShortcutActionDef[] = [
|
|
82
125
|
// Navigation
|
|
@@ -109,6 +152,25 @@ export const SHORTCUT_ACTIONS: ShortcutActionDef[] = [
|
|
|
109
152
|
{ id: 'tab-next', defaultCombo: 'Ctrl+Tab', labelKey: 'shortcuts.tab_next', groupKey: 'shortcuts.group.tabs' },
|
|
110
153
|
{ id: 'tab-prev', defaultCombo: 'Ctrl+Shift+Tab', labelKey: 'shortcuts.tab_prev', groupKey: 'shortcuts.group.tabs' },
|
|
111
154
|
/* /wiring:d1 */
|
|
155
|
+
/* tus:t1 — the rest of the menu verbs. Until now these were reachable only
|
|
156
|
+
* by right-clicking, and the right-click menu named no keys at all, so 16 of
|
|
157
|
+
* the registry's actions were invisible unless somebody opened the `?` sheet
|
|
158
|
+
* on their own. The ones with no default are deliberate: they are rare
|
|
159
|
+
* enough that taking a key from the user by default is the wrong trade, and
|
|
160
|
+
* they are in the registry so the user can take one. */
|
|
161
|
+
{ id: 'new-folder', defaultCombo: 'Shift+N', labelKey: 'shortcuts.new_folder', groupKey: 'shortcuts.group.file' },
|
|
162
|
+
{ id: 'upload', defaultCombo: 'U', labelKey: 'shortcuts.upload', groupKey: 'shortcuts.group.file' },
|
|
163
|
+
{ id: 'refresh', defaultCombo: 'R', labelKey: 'shortcuts.refresh', groupKey: 'shortcuts.group.nav' },
|
|
164
|
+
{ id: 'download', defaultCombo: 'D', labelKey: 'shortcuts.download', groupKey: 'shortcuts.group.file' },
|
|
165
|
+
{ id: 'preview', defaultCombo: 'P', labelKey: 'shortcuts.preview', groupKey: 'shortcuts.group.nav' },
|
|
166
|
+
{ id: 'share', defaultCombo: 'Shift+S', labelKey: 'shortcuts.share', groupKey: 'shortcuts.group.file' },
|
|
167
|
+
{ id: 'tags', defaultCombo: 'T', labelKey: 'shortcuts.tags', groupKey: 'shortcuts.group.file' },
|
|
168
|
+
{ id: 'convert', defaultCombo: '', labelKey: 'shortcuts.convert', groupKey: 'shortcuts.group.file' },
|
|
169
|
+
{ id: 'open-tab', defaultCombo: '', labelKey: 'shortcuts.open_tab', groupKey: 'shortcuts.group.tabs' },
|
|
170
|
+
{ id: 'copy-path', defaultCombo: '', labelKey: 'shortcuts.copy_path', groupKey: 'shortcuts.group.file' },
|
|
171
|
+
{ id: 'copy-id', defaultCombo: '', labelKey: 'shortcuts.copy_id', groupKey: 'shortcuts.group.file' },
|
|
172
|
+
{ id: 'restore', defaultCombo: '', labelKey: 'shortcuts.restore', groupKey: 'shortcuts.group.file' },
|
|
173
|
+
/* /tus:t1 */
|
|
112
174
|
];
|
|
113
175
|
|
|
114
176
|
/** action id → ShortcutHandlers callback name. */
|
|
@@ -134,6 +196,19 @@ const HANDLER_KEY: Record<string, keyof ShortcutHandlers> = {
|
|
|
134
196
|
'tab-close': 'onTabClose',
|
|
135
197
|
'tab-next': 'onTabNext',
|
|
136
198
|
'tab-prev': 'onTabPrev',
|
|
199
|
+
/* tus:t1 */
|
|
200
|
+
'new-folder': 'onNewFolder',
|
|
201
|
+
upload: 'onUpload',
|
|
202
|
+
refresh: 'onRefresh',
|
|
203
|
+
download: 'onDownload',
|
|
204
|
+
preview: 'onPreview',
|
|
205
|
+
share: 'onShare',
|
|
206
|
+
tags: 'onTags',
|
|
207
|
+
convert: 'onConvert',
|
|
208
|
+
'open-tab': 'onOpenTab',
|
|
209
|
+
'copy-path': 'onCopyPath',
|
|
210
|
+
'copy-id': 'onCopyId',
|
|
211
|
+
restore: 'onRestore',
|
|
137
212
|
};
|
|
138
213
|
|
|
139
214
|
// --------------------------------------------------------------------
|
|
@@ -314,6 +389,106 @@ export function useShortcutList(): ComputedRef<ShortcutView[]> {
|
|
|
314
389
|
);
|
|
315
390
|
}
|
|
316
391
|
|
|
392
|
+
// --------------------------------------------------------------------
|
|
393
|
+
// Hint surfaces
|
|
394
|
+
// --------------------------------------------------------------------
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Is this a Mac-style keyboard? Only affects how a combo is PRINTED —
|
|
398
|
+
* the canonical form folds Meta into Ctrl, so one saved combo works on
|
|
399
|
+
* every platform and only the label differs.
|
|
400
|
+
*/
|
|
401
|
+
export function isMacLike(): boolean {
|
|
402
|
+
return (
|
|
403
|
+
typeof navigator !== 'undefined' &&
|
|
404
|
+
/mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent || '')
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Canonical combo → what a human should see. '' stays ''. */
|
|
409
|
+
export function comboLabel(combo: string): string {
|
|
410
|
+
if (!combo) return '';
|
|
411
|
+
return isMacLike() ? combo.replace(/\bCtrl\b/g, '⌘') : combo;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Menu action key → registry action id.
|
|
416
|
+
*
|
|
417
|
+
* The right-click menu, the toolbar and the keyboard all name the same verbs,
|
|
418
|
+
* but the menu's `key` and the registry's `id` are separate vocabularies and
|
|
419
|
+
* two of them genuinely differ (`access` is the share dialog, `details` is the
|
|
420
|
+
* inspector). One map, in one place: a second copy is a second chance to
|
|
421
|
+
* forget, and the symptom would be a menu row that silently stops naming its
|
|
422
|
+
* key while every other row keeps working.
|
|
423
|
+
*/
|
|
424
|
+
export const MENU_ACTION_SHORTCUTS: Record<string, string> = {
|
|
425
|
+
open: 'open',
|
|
426
|
+
'open-tab': 'open-tab',
|
|
427
|
+
preview: 'preview',
|
|
428
|
+
download: 'download',
|
|
429
|
+
convert: 'convert',
|
|
430
|
+
access: 'share',
|
|
431
|
+
details: 'inspector',
|
|
432
|
+
'copy-id': 'copy-id',
|
|
433
|
+
'copy-path': 'copy-path',
|
|
434
|
+
rename: 'rename',
|
|
435
|
+
cut: 'cut',
|
|
436
|
+
copy: 'copy',
|
|
437
|
+
paste: 'paste',
|
|
438
|
+
star: 'star',
|
|
439
|
+
tags: 'tags',
|
|
440
|
+
delete: 'delete',
|
|
441
|
+
restore: 'restore',
|
|
442
|
+
'new-folder': 'new-folder',
|
|
443
|
+
'toggle-hidden': 'toggle-hidden',
|
|
444
|
+
upload: 'upload',
|
|
445
|
+
refresh: 'refresh',
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* What a menu row or a toolbar button should print beside its label: the
|
|
450
|
+
* current combo for the verb it triggers, or '' when it has none (unbound, or
|
|
451
|
+
* not a registry action at all — `keep-local`, the E2E entries and so on).
|
|
452
|
+
*/
|
|
453
|
+
export function menuShortcutHint(menuKey: string): string {
|
|
454
|
+
/* Fall back to the key itself: the toolbar's own buttons (go-up, upload,
|
|
455
|
+
* refresh…) pass a registry id directly, and an id nothing knows returns ''
|
|
456
|
+
* rather than throwing. */
|
|
457
|
+
const id = MENU_ACTION_SHORTCUTS[menuKey] ?? menuKey;
|
|
458
|
+
return SHORTCUT_ACTIONS.some((a) => a.id === id) ? shortcutHint(id) : '';
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* The one way a hint may name a key.
|
|
463
|
+
*
|
|
464
|
+
* ⚠ Never write a key into a hint by hand — not in a template, not in a
|
|
465
|
+
* locale string. Every combo in this registry is remappable, so a
|
|
466
|
+
* hardcoded "Ctrl+K" is true only until someone opens the shortcut
|
|
467
|
+
* settings, and then it is a label that tells the user to press a key
|
|
468
|
+
* that does nothing. Call this instead and interpolate the result;
|
|
469
|
+
* `web/tests/ui/shortcutHints.test.ts` fails the build on a literal.
|
|
470
|
+
*
|
|
471
|
+
* Returns '' when the action is unbound, so a caller can drop the whole
|
|
472
|
+
* segment rather than print an empty key cap.
|
|
473
|
+
*/
|
|
474
|
+
export function shortcutHint(id: string): string {
|
|
475
|
+
return comboLabel(effectiveCombo(id));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Does this event fire the given registry action? For overlays that
|
|
480
|
+
* handle their own keys (the quick-look peek) rather than going through
|
|
481
|
+
* the global binder — without this they keep answering to the DEFAULT
|
|
482
|
+
* key after the user has remapped the action.
|
|
483
|
+
*/
|
|
484
|
+
export function eventMatchesShortcut(e: KeyboardEvent, id: string): boolean {
|
|
485
|
+
const combo = effectiveCombo(id);
|
|
486
|
+
if (!combo) return false;
|
|
487
|
+
const fired = comboFromEvent(e);
|
|
488
|
+
if (fired === combo) return true;
|
|
489
|
+
return defOf(id)?.fixedCombos?.includes(fired ?? '') ?? false;
|
|
490
|
+
}
|
|
491
|
+
|
|
317
492
|
/**
|
|
318
493
|
* @deprecated Legacy static cheat-sheet shape (pre-registry). Kept for
|
|
319
494
|
* API compatibility; use `useShortcutList()` for the live, remap-aware
|
package/src/index.ts
CHANGED
|
@@ -139,6 +139,18 @@ export {
|
|
|
139
139
|
resetAllShortcuts,
|
|
140
140
|
findShortcutConflict,
|
|
141
141
|
comboFromEvent,
|
|
142
|
+
/* Hint surfaces: the only sanctioned way to NAME a key on screen. A hint
|
|
143
|
+
* written by hand is true until the user remaps that action, and then the
|
|
144
|
+
* product is telling them to press something that does nothing. */
|
|
145
|
+
shortcutHint,
|
|
146
|
+
comboLabel,
|
|
147
|
+
eventMatchesShortcut,
|
|
148
|
+
isMacLike,
|
|
149
|
+
/* tus:t1 — menu/toolbar rows name their key from the same registry, and the
|
|
150
|
+
* combos a browser tab never receives are declared in one place. */
|
|
151
|
+
menuShortcutHint,
|
|
152
|
+
MENU_ACTION_SHORTCUTS,
|
|
153
|
+
isReservedCombo,
|
|
142
154
|
} from './composables/useKeyboardShortcuts';
|
|
143
155
|
export type {
|
|
144
156
|
ShortcutActionDef,
|
package/src/lib/themes.ts
CHANGED
|
@@ -83,7 +83,11 @@ export const THEMES: ThemeDef[] = [
|
|
|
83
83
|
'--fe-text': '#1a1e27',
|
|
84
84
|
'--fe-text-muted': '#5a6475',
|
|
85
85
|
'--fe-text-on-primary': '#ffffff',
|
|
86
|
-
|
|
86
|
+
/* ⚠ Mirrors styles/variables.css. It drifted once: the stock primary was
|
|
87
|
+
* darkened to #2f6fe0 for WCAG AA against white text and this preview
|
|
88
|
+
* map kept the old #3b82f6, so the gallery card advertised a blue the
|
|
89
|
+
* product no longer used. */
|
|
90
|
+
'--fe-primary': '#2f6fe0',
|
|
87
91
|
'--fe-primary-hover': '#2563eb',
|
|
88
92
|
'--fe-danger': '#dc2626',
|
|
89
93
|
'--fe-danger-hover': '#b91c1c',
|
|
@@ -97,7 +101,9 @@ export const THEMES: ThemeDef[] = [
|
|
|
97
101
|
'--fe-border-strong': '#3a4453',
|
|
98
102
|
'--fe-text': '#e5e9f0',
|
|
99
103
|
'--fe-text-muted': '#8b95a7',
|
|
100
|
-
|
|
104
|
+
/* Dark ink, not white: white on #60a5fa measures 2.54 against the 4.5
|
|
105
|
+
* this file claims. Mirrors styles/variables.css. */
|
|
106
|
+
'--fe-text-on-primary': '#0f1419',
|
|
101
107
|
'--fe-primary': '#60a5fa',
|
|
102
108
|
'--fe-primary-hover': '#3b82f6',
|
|
103
109
|
'--fe-danger': '#f87171',
|
|
@@ -355,6 +361,94 @@ export const THEMES: ThemeDef[] = [
|
|
|
355
361
|
'--fe-font': 'ui-monospace, "SF Mono", Consolas, Menlo, monospace',
|
|
356
362
|
},
|
|
357
363
|
},
|
|
364
|
+
{
|
|
365
|
+
/**
|
|
366
|
+
* Drive — the palette of the end-user shell alfatm built on top of filex
|
|
367
|
+
* and put on a demo stand for review (GitHub #14). The drive profile's
|
|
368
|
+
* layout came out of their mockups; this is the colour and shape half of
|
|
369
|
+
* the same look, mapped onto our own tokens so every surface the explorer
|
|
370
|
+
* has — not just the drive shell — can wear it.
|
|
371
|
+
*
|
|
372
|
+
* Measured from their demo rather than eyeballed: 43 custom properties,
|
|
373
|
+
* of which the ones with a home here are the palette, the radii (6/8/10)
|
|
374
|
+
* and the per-file-type accents. Inter leads the font stack because that
|
|
375
|
+
* is what the demo uses; nothing is downloaded for it, so a machine
|
|
376
|
+
* without Inter installed falls back to the same system face as every
|
|
377
|
+
* other theme.
|
|
378
|
+
*/
|
|
379
|
+
id: 'drive',
|
|
380
|
+
nameKey: 'theme.name.drive',
|
|
381
|
+
light: {
|
|
382
|
+
'--fe-bg': '#ffffff',
|
|
383
|
+
'--fe-bg-elev': '#f7f8fb',
|
|
384
|
+
'--fe-bg-hover': '#f3f4f6',
|
|
385
|
+
'--fe-bg-selected': '#eef3ff',
|
|
386
|
+
'--fe-border': '#e5e7eb',
|
|
387
|
+
'--fe-border-strong': '#d1d5db',
|
|
388
|
+
'--fe-text': '#1f2937',
|
|
389
|
+
'--fe-text-muted': '#6b7280',
|
|
390
|
+
'--fe-text-on-primary': '#ffffff',
|
|
391
|
+
'--fe-primary': '#2f6ceb',
|
|
392
|
+
'--fe-primary-hover': '#2559c9',
|
|
393
|
+
'--fe-danger': '#dc2626',
|
|
394
|
+
'--fe-danger-hover': '#b91c1c',
|
|
395
|
+
'--fe-font': 'Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
|
396
|
+
'--fe-radius-sm': '6px',
|
|
397
|
+
'--fe-radius': '8px',
|
|
398
|
+
'--fe-radius-md': '10px',
|
|
399
|
+
'--fe-radius-lg': '12px',
|
|
400
|
+
'--fe-shadow': '0 20px 60px rgba(17, 24, 39, 0.18)',
|
|
401
|
+
'--fe-shadow-sm': '0 8px 24px rgba(17, 24, 39, 0.12)',
|
|
402
|
+
'--fe-icon-folder': '#f4b400',
|
|
403
|
+
'--fe-icon-image': '#2f6ceb',
|
|
404
|
+
'--fe-icon-video': '#7c3aed',
|
|
405
|
+
'--fe-icon-audio': '#0d9488',
|
|
406
|
+
'--fe-icon-pdf': '#dc2626',
|
|
407
|
+
'--fe-icon-doc': '#2f6ceb',
|
|
408
|
+
'--fe-icon-sheet': '#16a34a',
|
|
409
|
+
'--fe-icon-slides': '#ea580c',
|
|
410
|
+
'--fe-icon-archive': '#6b7280',
|
|
411
|
+
'--fe-icon-code': '#2f6ceb',
|
|
412
|
+
'--fe-icon-text': '#374151',
|
|
413
|
+
'--fe-icon-unknown': '#6b7280',
|
|
414
|
+
},
|
|
415
|
+
dark: {
|
|
416
|
+
'--fe-bg': '#15171c',
|
|
417
|
+
'--fe-bg-elev': '#1a1d23',
|
|
418
|
+
'--fe-bg-hover': '#1f232a',
|
|
419
|
+
'--fe-bg-selected': '#1c2740',
|
|
420
|
+
'--fe-border': '#2e333c',
|
|
421
|
+
'--fe-border-strong': '#3d444f',
|
|
422
|
+
'--fe-text': '#e6e8ec',
|
|
423
|
+
'--fe-text-muted': '#888f9b',
|
|
424
|
+
/* Dark ink on a light-blue button: white on #5b8cff measures 3.16, and
|
|
425
|
+
* the bar this file documents is 4.5. */
|
|
426
|
+
'--fe-text-on-primary': '#15171c',
|
|
427
|
+
'--fe-primary': '#5b8cff',
|
|
428
|
+
'--fe-primary-hover': '#7ba3ff',
|
|
429
|
+
'--fe-danger': '#f87171',
|
|
430
|
+
'--fe-danger-hover': '#ef4444',
|
|
431
|
+
'--fe-font': 'Inter, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
|
432
|
+
'--fe-radius-sm': '6px',
|
|
433
|
+
'--fe-radius': '8px',
|
|
434
|
+
'--fe-radius-md': '10px',
|
|
435
|
+
'--fe-radius-lg': '12px',
|
|
436
|
+
'--fe-shadow': '0 20px 60px rgba(0, 0, 0, 0.6)',
|
|
437
|
+
'--fe-shadow-sm': '0 8px 24px rgba(0, 0, 0, 0.5)',
|
|
438
|
+
'--fe-icon-folder': '#f4b400',
|
|
439
|
+
'--fe-icon-image': '#4d7ff0',
|
|
440
|
+
'--fe-icon-video': '#9061f0',
|
|
441
|
+
'--fe-icon-audio': '#2dd4bf',
|
|
442
|
+
'--fe-icon-pdf': '#e05252',
|
|
443
|
+
'--fe-icon-doc': '#4d7ff0',
|
|
444
|
+
'--fe-icon-sheet': '#2eab63',
|
|
445
|
+
'--fe-icon-slides': '#fb923c',
|
|
446
|
+
'--fe-icon-archive': '#79808d',
|
|
447
|
+
'--fe-icon-code': '#4d7ff0',
|
|
448
|
+
'--fe-icon-text': '#5b6472',
|
|
449
|
+
'--fe-icon-unknown': '#79808d',
|
|
450
|
+
},
|
|
451
|
+
},
|
|
358
452
|
];
|
|
359
453
|
|
|
360
454
|
export function themeById(id: string | null | undefined): ThemeDef | undefined {
|
package/src/locales/en.ts
CHANGED
|
@@ -306,6 +306,7 @@ export const en: Record<string, string> = {
|
|
|
306
306
|
'theme.name.contrast': 'High Contrast',
|
|
307
307
|
'theme.name.gray': 'Soft Gray',
|
|
308
308
|
'theme.name.terminal': 'Terminal Green',
|
|
309
|
+
'theme.name.drive': 'Drive',
|
|
309
310
|
/* === wiring:c2 — customizable shortcuts + quick look === */
|
|
310
311
|
'shortcuts.quicklook': 'Quick look',
|
|
311
312
|
'shortcuts.customize': 'Customize',
|
|
@@ -366,7 +367,7 @@ export const en: Record<string, string> = {
|
|
|
366
367
|
'Pick files with this button — or simply drag & drop them anywhere in the window.',
|
|
367
368
|
'tour.step.search.title': 'Search',
|
|
368
369
|
'tour.step.search.desc':
|
|
369
|
-
'This box searches the whole storage by name — separators and one typo are forgiven, so "invoice 2026" finds invoice_2026.pdf. Add tag:invoice to filter by tag. The
|
|
370
|
+
'This box searches the whole storage by name — separators and one typo are forgiven, so "invoice 2026" finds invoice_2026.pdf. Add tag:invoice to filter by tag. The {palette} command palette also runs commands.',
|
|
370
371
|
'tour.step.view.title': 'Switch views',
|
|
371
372
|
'tour.step.view.desc':
|
|
372
373
|
'Toggle between the list and grid layout; your choice is remembered.',
|
|
@@ -375,7 +376,7 @@ export const en: Record<string, string> = {
|
|
|
375
376
|
'Right-click any file and use "Share / Permissions" to create a link with an optional PIN and expiry.',
|
|
376
377
|
'tour.step.help.title': 'Shortcuts',
|
|
377
378
|
'tour.step.help.desc':
|
|
378
|
-
'Press
|
|
379
|
+
'Press {help} for the shortcut cheat-sheet and {palette} for the command palette. Replay this tour any time via "Restart the tour" in the menu.',
|
|
379
380
|
'error.hint': 'This may be a temporary network or server problem. Please try again.',
|
|
380
381
|
'error.details': 'Technical details',
|
|
381
382
|
'col.star': 'Star',
|
|
@@ -395,6 +396,24 @@ export const en: Record<string, string> = {
|
|
|
395
396
|
'shortcuts.tab_close': 'Close tab',
|
|
396
397
|
'shortcuts.tab_next': 'Next tab',
|
|
397
398
|
'shortcuts.tab_prev': 'Previous tab',
|
|
399
|
+
/* tus:t1 — the menu verbs */
|
|
400
|
+
'shortcuts.new_folder': 'New folder',
|
|
401
|
+
'shortcuts.upload': 'Upload files',
|
|
402
|
+
'shortcuts.refresh': 'Refresh the listing',
|
|
403
|
+
'shortcuts.download': 'Download',
|
|
404
|
+
'shortcuts.preview': 'Preview',
|
|
405
|
+
'shortcuts.share': 'Share / permissions',
|
|
406
|
+
'shortcuts.tags': 'Tags',
|
|
407
|
+
'shortcuts.convert': 'Convert',
|
|
408
|
+
'shortcuts.open_tab': 'Open in a new tab',
|
|
409
|
+
'shortcuts.copy_path': 'Copy path',
|
|
410
|
+
'shortcuts.copy_id': 'Copy id',
|
|
411
|
+
'shortcuts.restore': 'Restore from trash',
|
|
412
|
+
'shortcuts.settings.reserved':
|
|
413
|
+
'is taken by the browser before the page sees it, so it would do nothing here. Pick another key.',
|
|
414
|
+
'shortcuts.settings.reserved_badge': 'Desktop app only',
|
|
415
|
+
'shortcuts.settings.reserved_title':
|
|
416
|
+
'The browser handles this combination itself. It fires in the desktop app and in an installed PWA, not in a browser tab.',
|
|
398
417
|
'cmd.tab_new': 'Open a new tab',
|
|
399
418
|
'cmd.split_toggle': 'Toggle split view',
|
|
400
419
|
'split.pane': 'Secondary pane',
|
package/src/locales/tr.ts
CHANGED
|
@@ -306,6 +306,7 @@ export const tr: Record<string, string> = {
|
|
|
306
306
|
'theme.name.contrast': 'Yüksek Kontrast',
|
|
307
307
|
'theme.name.gray': 'Yumuşak Gri',
|
|
308
308
|
'theme.name.terminal': 'Terminal Yeşili',
|
|
309
|
+
'theme.name.drive': 'Drive',
|
|
309
310
|
/* === wiring:c2 — özelleştirilebilir kısayollar + hızlı bakış === */
|
|
310
311
|
'shortcuts.quicklook': 'Hızlı bakış',
|
|
311
312
|
'shortcuts.customize': 'Özelleştir',
|
|
@@ -366,7 +367,7 @@ export const tr: Record<string, string> = {
|
|
|
366
367
|
'Bu düğmeyle dosya seçebilirsin; dosyaları doğrudan pencereye sürükleyip bırakmak da çalışır.',
|
|
367
368
|
'tour.step.search.title': 'Arama',
|
|
368
369
|
'tour.step.search.desc':
|
|
369
|
-
'Bu kutu deponun tamamında ada göre arar; ayraçları ve bir harflik yazım hatasını affeder, yani "fatura 2026" yazdığında fatura_2026.pdf gelir. tag:fatura yazarak etikete göre süzebilirsin.
|
|
370
|
+
'Bu kutu deponun tamamında ada göre arar; ayraçları ve bir harflik yazım hatasını affeder, yani "fatura 2026" yazdığında fatura_2026.pdf gelir. tag:fatura yazarak etikete göre süzebilirsin. {palette} komut paleti ayrıca komut da çalıştırır.',
|
|
370
371
|
'tour.step.view.title': 'Görünümü değiştir',
|
|
371
372
|
'tour.step.view.desc':
|
|
372
373
|
'Liste ve ızgara görünümü arasında geçiş yapabilirsin; seçimin hatırlanır.',
|
|
@@ -375,7 +376,7 @@ export const tr: Record<string, string> = {
|
|
|
375
376
|
'Bir dosyaya sağ tıklayıp "Paylaş / İzinler" ile bağlantı oluşturabilir, PIN ve son kullanma süresi ekleyebilirsin.',
|
|
376
377
|
'tour.step.help.title': 'Kısayollar',
|
|
377
378
|
'tour.step.help.desc':
|
|
378
|
-
'
|
|
379
|
+
'{help} tuşu kısayol kartını, {palette} komut paletini açar. Bu turu menüdeki "Turu tekrar başlat" ile dilediğinde yeniden izleyebilirsin.',
|
|
379
380
|
'error.hint': 'Bağlantı ya da sunucu kaynaklı geçici bir sorun olabilir. Yeniden deneyin.',
|
|
380
381
|
'error.details': 'Teknik ayrıntılar',
|
|
381
382
|
'col.star': 'Yıldız',
|
|
@@ -395,6 +396,24 @@ export const tr: Record<string, string> = {
|
|
|
395
396
|
'shortcuts.tab_close': 'Sekmeyi kapat',
|
|
396
397
|
'shortcuts.tab_next': 'Sonraki sekme',
|
|
397
398
|
'shortcuts.tab_prev': 'Önceki sekme',
|
|
399
|
+
/* tus:t1 — menü fiilleri */
|
|
400
|
+
'shortcuts.new_folder': 'Yeni klasör',
|
|
401
|
+
'shortcuts.upload': 'Dosya yükle',
|
|
402
|
+
'shortcuts.refresh': 'Listeyi yenile',
|
|
403
|
+
'shortcuts.download': 'İndir',
|
|
404
|
+
'shortcuts.preview': 'Önizle',
|
|
405
|
+
'shortcuts.share': 'Paylaş / izinler',
|
|
406
|
+
'shortcuts.tags': 'Etiketler',
|
|
407
|
+
'shortcuts.convert': 'Dönüştür',
|
|
408
|
+
'shortcuts.open_tab': 'Yeni sekmede aç',
|
|
409
|
+
'shortcuts.copy_path': 'Yolu kopyala',
|
|
410
|
+
'shortcuts.copy_id': 'Kimliği kopyala',
|
|
411
|
+
'shortcuts.restore': 'Çöp kutusundan geri al',
|
|
412
|
+
'shortcuts.settings.reserved':
|
|
413
|
+
'tarayıcı tarafından sayfaya ulaşmadan yakalanıyor, burada hiçbir şey yapmaz. Başka bir tuş seçin.',
|
|
414
|
+
'shortcuts.settings.reserved_badge': 'Yalnız masaüstü uygulamasında',
|
|
415
|
+
'shortcuts.settings.reserved_title':
|
|
416
|
+
'Bu kombinasyonu tarayıcının kendisi işliyor. Masaüstü uygulamasında ve kurulu PWA’da çalışır, tarayıcı sekmesinde çalışmaz.',
|
|
398
417
|
'cmd.tab_new': 'Yeni sekme aç',
|
|
399
418
|
'cmd.split_toggle': 'Görünümü böl / birleştir',
|
|
400
419
|
'split.pane': 'İkincil panel',
|
package/src/styles/base.css
CHANGED
|
@@ -866,6 +866,22 @@ filex-explorer {
|
|
|
866
866
|
width: 1.2em;
|
|
867
867
|
text-align: center;
|
|
868
868
|
}
|
|
869
|
+
/* tus:t1 — the key that runs this row, pushed to the trailing edge. Muted and
|
|
870
|
+
* smaller: it is a reminder, not a second label, and it must never win the
|
|
871
|
+
* row's width from the label (hence auto margin + no shrink on the label). */
|
|
872
|
+
.fe-ctx__label {
|
|
873
|
+
flex: 1 1 auto;
|
|
874
|
+
min-width: 0;
|
|
875
|
+
}
|
|
876
|
+
.fe-ctx__key {
|
|
877
|
+
flex: 0 0 auto;
|
|
878
|
+
margin-left: auto;
|
|
879
|
+
padding-left: 16px;
|
|
880
|
+
color: var(--fe-text-muted);
|
|
881
|
+
font-size: 11px;
|
|
882
|
+
font-family: var(--fe-font-mono);
|
|
883
|
+
white-space: nowrap;
|
|
884
|
+
}
|
|
869
885
|
.fe-ctx__sep {
|
|
870
886
|
height: 1px;
|
|
871
887
|
background: var(--fe-border);
|
|
@@ -2588,6 +2604,16 @@ filex-explorer {
|
|
|
2588
2604
|
padding: 3px 9px;
|
|
2589
2605
|
font-size: 12px;
|
|
2590
2606
|
}
|
|
2607
|
+
/* tus:t1 — "Desktop app only" on a combo the browser keeps for itself. */
|
|
2608
|
+
.fe-shortset__reserved {
|
|
2609
|
+
margin-left: 6px;
|
|
2610
|
+
font-size: 10.5px;
|
|
2611
|
+
color: var(--fe-text-muted);
|
|
2612
|
+
border: 1px dashed var(--fe-border);
|
|
2613
|
+
border-radius: var(--fe-radius-sm);
|
|
2614
|
+
padding: 1px 6px;
|
|
2615
|
+
white-space: nowrap;
|
|
2616
|
+
}
|
|
2591
2617
|
.fe-shortset__fixed {
|
|
2592
2618
|
font-size: 11px;
|
|
2593
2619
|
text-transform: uppercase;
|
|
@@ -2622,8 +2648,15 @@ filex-explorer {
|
|
|
2622
2648
|
/* Quick-look hint bar — floats above the PreviewModal backdrop (z 70).
|
|
2623
2649
|
* Carries the `.fe` class for the theme-variable cascade, so the `.fe`
|
|
2624
2650
|
* root layout defaults (column flex, min-height 420px, 100% height)
|
|
2625
|
-
* must be explicitly neutralised here.
|
|
2626
|
-
|
|
2651
|
+
* must be explicitly neutralised here.
|
|
2652
|
+
*
|
|
2653
|
+
* Written as `.fe.fe-ql-hint` on purpose: a single class ties with the
|
|
2654
|
+
* `.fe` root rule and would be decided by source order alone, which is
|
|
2655
|
+
* not something a host embed can be trusted to preserve. The hint is
|
|
2656
|
+
* also Teleported under <body> (QuickLook.vue) so a host container's
|
|
2657
|
+
* own `.fe` override cannot reach it by descendant — issue #22, where
|
|
2658
|
+
* the pill grew to the full viewport height inside the admin UI. */
|
|
2659
|
+
.fe.fe-ql-hint {
|
|
2627
2660
|
position: fixed;
|
|
2628
2661
|
left: 50%;
|
|
2629
2662
|
bottom: 14px;
|
|
@@ -2653,6 +2686,13 @@ filex-explorer {
|
|
|
2653
2686
|
.fe-ql-hint__sep {
|
|
2654
2687
|
opacity: 0.5;
|
|
2655
2688
|
}
|
|
2689
|
+
/* One key-cap group plus its label, kept together so the pill can only
|
|
2690
|
+
* break between segments. */
|
|
2691
|
+
.fe-ql-hint__seg {
|
|
2692
|
+
display: inline-flex;
|
|
2693
|
+
align-items: center;
|
|
2694
|
+
gap: 4px;
|
|
2695
|
+
}
|
|
2656
2696
|
/* /wiring:c2 */
|
|
2657
2697
|
/* =========================================================
|
|
2658
2698
|
* wiring:c3 — Operations center (unified upload + ops-queue surface)
|
package/src/styles/variables.css
CHANGED
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
--fe-border-strong: #3a4453;
|
|
63
63
|
--fe-text: #e5e9f0;
|
|
64
64
|
--fe-text-muted: #8b95a7;
|
|
65
|
+
/* Dark ink on the light-blue button. White measured 2.54:1 against it —
|
|
66
|
+
* the palette's own bar for text on a primary surface is 4.5, and every
|
|
67
|
+
* other themed palette clears it. #0f1419 measures 7.28. */
|
|
68
|
+
--fe-text-on-primary: #0f1419;
|
|
65
69
|
--fe-primary: #60a5fa;
|
|
66
70
|
--fe-primary-hover: #3b82f6;
|
|
67
71
|
--fe-danger: #f87171;
|
|
@@ -83,6 +87,8 @@
|
|
|
83
87
|
--fe-border-strong: #3a4453;
|
|
84
88
|
--fe-text: #e5e9f0;
|
|
85
89
|
--fe-text-muted: #8b95a7;
|
|
90
|
+
/* See the note on the class-based dark block above. */
|
|
91
|
+
--fe-text-on-primary: #0f1419;
|
|
86
92
|
--fe-primary: #60a5fa;
|
|
87
93
|
--fe-primary-hover: #3b82f6;
|
|
88
94
|
--fe-danger: #f87171;
|