@brftech/filex-core 0.2.0 → 0.4.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/dist/{ArchiveViewer-DCZ1FZYV.js → ArchiveViewer-Dgvl43gn.js} +2 -2
- package/dist/{ArchiveViewer-DCZ1FZYV.js.map → ArchiveViewer-Dgvl43gn.js.map} +1 -1
- package/dist/{CsvViewer-DMvnp82x.js → CsvViewer-gWO5pc8F.js} +2 -2
- package/dist/{CsvViewer-DMvnp82x.js.map → CsvViewer-gWO5pc8F.js.map} +1 -1
- package/dist/{DrawioViewer-B5Io0yTO.js → DrawioViewer-DUAlRIAb.js} +2 -2
- package/dist/{DrawioViewer-B5Io0yTO.js.map → DrawioViewer-DUAlRIAb.js.map} +1 -1
- package/dist/{EpubViewer-z6RZd3K8.js → EpubViewer-DIXzuQWK.js} +2 -2
- package/dist/{EpubViewer-z6RZd3K8.js.map → EpubViewer-DIXzuQWK.js.map} +1 -1
- package/dist/{IpynbViewer-DfL4EauH.js → IpynbViewer-CJ-6pU_Z.js} +2 -2
- package/dist/{IpynbViewer-DfL4EauH.js.map → IpynbViewer-CJ-6pU_Z.js.map} +1 -1
- package/dist/{MermaidViewer-QNl_o7V-.js → MermaidViewer-CyQQDyRb.js} +2 -2
- package/dist/{MermaidViewer-QNl_o7V-.js.map → MermaidViewer-CyQQDyRb.js.map} +1 -1
- package/dist/{PsdViewer-Btmpr0Fz.js → PsdViewer-ChfaXVgN.js} +2 -2
- package/dist/{PsdViewer-Btmpr0Fz.js.map → PsdViewer-ChfaXVgN.js.map} +1 -1
- package/dist/{TiffViewer-CMLpwds5.js → TiffViewer-CsfD-NG6.js} +2 -2
- package/dist/{TiffViewer-CMLpwds5.js.map → TiffViewer-CsfD-NG6.js.map} +1 -1
- package/dist/{Viewer3D-gf3cb2gv.js → Viewer3D-BQ2XYmkN.js} +2 -2
- package/dist/{Viewer3D-gf3cb2gv.js.map → Viewer3D-BQ2XYmkN.js.map} +1 -1
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +42 -42
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-PnhGunSA.js +7972 -0
- package/dist/index-PnhGunSA.js.map +1 -0
- package/dist/index.d.ts +69 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +146 -0
- package/src/components/ContextMenu.vue +101 -3
- package/src/components/InspectorPanel.vue +455 -0
- package/src/components/Toolbar.vue +250 -5
- package/src/composables/useFileApi.ts +59 -0
- package/src/composables/useKeyboardShortcuts.ts +11 -0
- package/src/locales/en.ts +45 -0
- package/src/locales/tr.ts +45 -0
- package/src/styles/base.css +458 -0
- package/dist/index-D49iw00E.js +0 -7119
- package/dist/index-D49iw00E.js.map +0 -1
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* Presentational only — all logic (rename, share, …) lives in
|
|
12
12
|
* FileExplorer.vue, which listens for `action` emits.
|
|
13
13
|
*/
|
|
14
|
-
import { computed, onMounted, ref, watch } from 'vue';
|
|
14
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
15
15
|
import type { ViewMode } from '../types/FileNode';
|
|
16
|
-
import type { LocaleCode } from '../types/ExplorerConfig';
|
|
17
|
-
import
|
|
16
|
+
import type { LocaleCode, ThemeMode } from '../types/ExplorerConfig';
|
|
17
|
+
import ContextMenu, { type ContextAction } from './ContextMenu.vue';
|
|
18
18
|
import { useLocale } from '../composables/useLocale';
|
|
19
19
|
|
|
20
20
|
export type SelectionMode = 'none' | 'single-file' | 'single-dir' | 'multi';
|
|
@@ -58,6 +58,21 @@ const props = defineProps<{
|
|
|
58
58
|
*/
|
|
59
59
|
canWrite?: boolean;
|
|
60
60
|
locale: LocaleCode;
|
|
61
|
+
/**
|
|
62
|
+
* bag:b4 — narrow/embed mini mode. When true the toolbar collapses to
|
|
63
|
+
* [↑?] [🔍] [⬆] [⋯]: secondary actions (New Folder, Refresh, density,
|
|
64
|
+
* view switcher, selection actions, Paste) move into the "⋯" overflow
|
|
65
|
+
* menu and search expands from an icon to a full-width input. When
|
|
66
|
+
* absent/false the classic wide layout renders unchanged.
|
|
67
|
+
*/
|
|
68
|
+
narrow?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* bag:b4 — resolved theme, forwarded to the teleported overflow menu
|
|
71
|
+
* (which loses the `.fe` variable scope under <body>).
|
|
72
|
+
*/
|
|
73
|
+
theme?: ThemeMode;
|
|
74
|
+
/* koru:k1 — inspector (details panel) toggle state, for the pressed style. */
|
|
75
|
+
inspectorOpen?: boolean;
|
|
61
76
|
}>();
|
|
62
77
|
|
|
63
78
|
const emit = defineEmits<{
|
|
@@ -70,6 +85,7 @@ const emit = defineEmits<{
|
|
|
70
85
|
(e: 'action', key: string): void;
|
|
71
86
|
(e: 'open-recents'): void;
|
|
72
87
|
(e: 'update:density', v: Density): void;
|
|
88
|
+
(e: 'toggle-inspector'): void /* koru:k1 */;
|
|
73
89
|
}>();
|
|
74
90
|
|
|
75
91
|
// Density toggle — the toolbar owns the persisted preference; the parent
|
|
@@ -114,7 +130,12 @@ function onSearchInput(ev: Event) {
|
|
|
114
130
|
debounce = setTimeout(() => emit('update:searchQuery', v), 200);
|
|
115
131
|
}
|
|
116
132
|
|
|
117
|
-
function focusSearch() {
|
|
133
|
+
async function focusSearch() {
|
|
134
|
+
/* bag:b4 — in narrow mode the input is collapsed behind the icon. */
|
|
135
|
+
if (props.narrow && !searchOpen.value) {
|
|
136
|
+
searchOpen.value = true;
|
|
137
|
+
await nextTick();
|
|
138
|
+
}
|
|
118
139
|
searchEl.value?.focus();
|
|
119
140
|
searchEl.value?.select();
|
|
120
141
|
}
|
|
@@ -132,10 +153,125 @@ const toolbarItems = computed(() => props.actions.filter((a) => !a.divider && !a
|
|
|
132
153
|
function fire(key: string) {
|
|
133
154
|
emit('action', key);
|
|
134
155
|
}
|
|
156
|
+
|
|
157
|
+
/* === bag:b4 — narrow-mode state: expandable search + "⋯" overflow menu === */
|
|
158
|
+
|
|
159
|
+
const searchOpen = ref(false);
|
|
160
|
+
// Leaving narrow mode discards the transient expanded-search state so the
|
|
161
|
+
// wide layout always comes back exactly as it was.
|
|
162
|
+
watch(
|
|
163
|
+
() => props.narrow,
|
|
164
|
+
(n) => {
|
|
165
|
+
if (!n) searchOpen.value = false;
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
async function openSearch() {
|
|
170
|
+
searchOpen.value = true;
|
|
171
|
+
await nextTick();
|
|
172
|
+
searchEl.value?.focus();
|
|
173
|
+
}
|
|
174
|
+
function closeSearch() {
|
|
175
|
+
// Keep the query (it still filters the listing); the icon shows an
|
|
176
|
+
// active state while a query is set.
|
|
177
|
+
searchOpen.value = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Coarse-pointer detection — the overflow menu renders as a bottom sheet on
|
|
181
|
+
// touch devices, matching the file context menu.
|
|
182
|
+
const coarse = ref(false);
|
|
183
|
+
let coarseMq: MediaQueryList | undefined;
|
|
184
|
+
function syncCoarse(e?: MediaQueryListEvent | MediaQueryList) {
|
|
185
|
+
coarse.value = !!(e && 'matches' in e && e.matches);
|
|
186
|
+
}
|
|
187
|
+
onMounted(() => {
|
|
188
|
+
if (typeof window === 'undefined' || !window.matchMedia) return;
|
|
189
|
+
coarseMq = window.matchMedia('(pointer: coarse)');
|
|
190
|
+
syncCoarse(coarseMq);
|
|
191
|
+
coarseMq.addEventListener?.('change', syncCoarse);
|
|
192
|
+
});
|
|
193
|
+
onBeforeUnmount(() => {
|
|
194
|
+
coarseMq?.removeEventListener?.('change', syncCoarse);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const moreBtnEl = ref<HTMLElement | null>(null);
|
|
198
|
+
const moreRef = ref<InstanceType<typeof ContextMenu> | null>(null);
|
|
199
|
+
|
|
200
|
+
// Everything the wide toolbar renders as standalone buttons, folded into one
|
|
201
|
+
// action list: folder-level writes (New Folder / Paste), the shared
|
|
202
|
+
// selection actions, then the view utilities (Refresh / density / view mode).
|
|
203
|
+
const moreActions = computed<ContextAction[]>(() => {
|
|
204
|
+
const list: ContextAction[] = [];
|
|
205
|
+
const writable =
|
|
206
|
+
!props.trashActive && !props.atVirtualRoot && props.canWrite !== false;
|
|
207
|
+
if (mode.value === 'none' && writable) {
|
|
208
|
+
list.push({ key: 'new-folder', label: t('toolbar.new_folder'), icon: '📁' });
|
|
209
|
+
if (props.pasteEnabled) list.push({ key: 'paste', label: t('ctx.paste'), icon: '📋' });
|
|
210
|
+
}
|
|
211
|
+
list.push(...toolbarItems.value);
|
|
212
|
+
if (list.length) list.push({ divider: true, key: 'bag-sep', label: '' });
|
|
213
|
+
list.push({ key: 'refresh', label: t('toolbar.refresh'), icon: '↻' });
|
|
214
|
+
list.push({
|
|
215
|
+
key: 'density',
|
|
216
|
+
label:
|
|
217
|
+
density.value === 'compact'
|
|
218
|
+
? t('toolbar.density.comfortable')
|
|
219
|
+
: t('toolbar.density.compact'),
|
|
220
|
+
icon: '⇅',
|
|
221
|
+
});
|
|
222
|
+
list.push(
|
|
223
|
+
props.viewMode === 'list'
|
|
224
|
+
? { key: 'view-grid', label: t('toolbar.view.grid'), icon: '▦' }
|
|
225
|
+
: { key: 'view-list', label: t('toolbar.view.list'), icon: '☰' },
|
|
226
|
+
);
|
|
227
|
+
/* koru:k1 — inspector toggle also reachable from the narrow overflow menu */
|
|
228
|
+
list.push({ key: 'inspector', label: t('toolbar.inspector'), icon: 'ℹ' });
|
|
229
|
+
return list;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
function openMore() {
|
|
233
|
+
const r = moreBtnEl.value?.getBoundingClientRect();
|
|
234
|
+
moreRef.value?.show({ clientX: r ? r.right : 0, clientY: r ? r.bottom + 4 : 0 }, []);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function onMoreSelect(a: ContextAction) {
|
|
238
|
+
switch (a.key) {
|
|
239
|
+
case 'new-folder':
|
|
240
|
+
emit('new-folder');
|
|
241
|
+
break;
|
|
242
|
+
case 'refresh':
|
|
243
|
+
emit('refresh');
|
|
244
|
+
break;
|
|
245
|
+
case 'density':
|
|
246
|
+
toggleDensity();
|
|
247
|
+
break;
|
|
248
|
+
case 'view-list':
|
|
249
|
+
emit('update:viewMode', 'list');
|
|
250
|
+
break;
|
|
251
|
+
case 'view-grid':
|
|
252
|
+
emit('update:viewMode', 'grid');
|
|
253
|
+
break;
|
|
254
|
+
case 'inspector' /* koru:k1 */:
|
|
255
|
+
emit('toggle-inspector');
|
|
256
|
+
break;
|
|
257
|
+
default:
|
|
258
|
+
fire(a.key);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* === /bag:b4 === */
|
|
135
263
|
</script>
|
|
136
264
|
|
|
137
265
|
<template>
|
|
138
|
-
<div
|
|
266
|
+
<div
|
|
267
|
+
class="fe-toolbar"
|
|
268
|
+
:class="{
|
|
269
|
+
'fe-toolbar--narrow': narrow /* bag:b4 */,
|
|
270
|
+
'fe-toolbar--searching': narrow && searchOpen /* bag:b4 */,
|
|
271
|
+
}"
|
|
272
|
+
>
|
|
273
|
+
<!-- bag:b4 — wide layout, untouched; renders exactly as before when not narrow -->
|
|
274
|
+
<template v-if="!narrow">
|
|
139
275
|
<div class="fe-toolbar__primary">
|
|
140
276
|
<button
|
|
141
277
|
v-if="canGoUp"
|
|
@@ -256,6 +392,32 @@ function fire(key: string) {
|
|
|
256
392
|
</svg>
|
|
257
393
|
</button>
|
|
258
394
|
|
|
395
|
+
<!-- koru:k1 — inspector (details panel) toggle -->
|
|
396
|
+
<button
|
|
397
|
+
type="button"
|
|
398
|
+
class="fe-btn fe-btn--icon-only fe-toolbar__inspector"
|
|
399
|
+
:class="{ 'is-active': inspectorOpen }"
|
|
400
|
+
:aria-pressed="!!inspectorOpen"
|
|
401
|
+
:title="t('toolbar.inspector')"
|
|
402
|
+
:aria-label="t('toolbar.inspector')"
|
|
403
|
+
@click="emit('toggle-inspector')"
|
|
404
|
+
>
|
|
405
|
+
<svg
|
|
406
|
+
class="fe-ficon"
|
|
407
|
+
viewBox="0 0 24 24"
|
|
408
|
+
fill="none"
|
|
409
|
+
stroke="currentColor"
|
|
410
|
+
stroke-width="1.8"
|
|
411
|
+
stroke-linecap="round"
|
|
412
|
+
aria-hidden="true"
|
|
413
|
+
focusable="false"
|
|
414
|
+
>
|
|
415
|
+
<circle cx="12" cy="12" r="9" />
|
|
416
|
+
<path d="M12 11v5" />
|
|
417
|
+
<circle cx="12" cy="7.6" r="1" fill="currentColor" stroke="none" />
|
|
418
|
+
</svg>
|
|
419
|
+
</button>
|
|
420
|
+
|
|
259
421
|
<div class="fe-toolbar__view" role="tablist" aria-label="View">
|
|
260
422
|
<button
|
|
261
423
|
type="button"
|
|
@@ -280,5 +442,88 @@ function fire(key: string) {
|
|
|
280
442
|
<span class="fe-icon">▦</span>
|
|
281
443
|
</button>
|
|
282
444
|
</div>
|
|
445
|
+
</template>
|
|
446
|
+
|
|
447
|
+
<!-- bag:b4 — narrow layout: [↑?] ... [🔍] [⬆] [⋯], search expands full-width -->
|
|
448
|
+
<template v-else>
|
|
449
|
+
<template v-if="searchOpen">
|
|
450
|
+
<div class="fe-search fe-search--full">
|
|
451
|
+
<input
|
|
452
|
+
ref="searchEl"
|
|
453
|
+
type="search"
|
|
454
|
+
class="fe-search__input"
|
|
455
|
+
:placeholder="t('toolbar.search.placeholder')"
|
|
456
|
+
:value="localSearch"
|
|
457
|
+
:aria-label="t('toolbar.search')"
|
|
458
|
+
@input="onSearchInput"
|
|
459
|
+
@keydown.esc.stop.prevent="closeSearch"
|
|
460
|
+
/>
|
|
461
|
+
</div>
|
|
462
|
+
<button
|
|
463
|
+
type="button"
|
|
464
|
+
class="fe-btn fe-btn--icon-only"
|
|
465
|
+
:title="t('toolbar.search.close')"
|
|
466
|
+
:aria-label="t('toolbar.search.close')"
|
|
467
|
+
@click="closeSearch"
|
|
468
|
+
>
|
|
469
|
+
<span class="fe-icon">✕</span>
|
|
470
|
+
</button>
|
|
471
|
+
</template>
|
|
472
|
+
<template v-else>
|
|
473
|
+
<button
|
|
474
|
+
v-if="canGoUp"
|
|
475
|
+
type="button"
|
|
476
|
+
class="fe-btn fe-btn--icon-only"
|
|
477
|
+
:title="t('toolbar.go_up')"
|
|
478
|
+
:aria-label="t('toolbar.go_up')"
|
|
479
|
+
@click="emit('go-up')"
|
|
480
|
+
>
|
|
481
|
+
<span class="fe-icon">↑</span>
|
|
482
|
+
</button>
|
|
483
|
+
|
|
484
|
+
<div class="fe-toolbar__spacer" />
|
|
485
|
+
|
|
486
|
+
<button
|
|
487
|
+
type="button"
|
|
488
|
+
class="fe-btn fe-btn--icon-only fe-toolbar__search-toggle"
|
|
489
|
+
:class="{ 'is-active': !!localSearch }"
|
|
490
|
+
:title="t('toolbar.search')"
|
|
491
|
+
:aria-label="t('toolbar.search')"
|
|
492
|
+
@click="openSearch"
|
|
493
|
+
>
|
|
494
|
+
<span class="fe-icon">🔍</span>
|
|
495
|
+
</button>
|
|
496
|
+
<button
|
|
497
|
+
v-if="!atVirtualRoot && canWrite !== false"
|
|
498
|
+
type="button"
|
|
499
|
+
class="fe-btn fe-btn--icon-only"
|
|
500
|
+
:title="t('toolbar.upload')"
|
|
501
|
+
:aria-label="t('toolbar.upload')"
|
|
502
|
+
@click="emit('upload')"
|
|
503
|
+
>
|
|
504
|
+
<span class="fe-icon">⬆</span>
|
|
505
|
+
</button>
|
|
506
|
+
<button
|
|
507
|
+
ref="moreBtnEl"
|
|
508
|
+
type="button"
|
|
509
|
+
class="fe-btn fe-btn--icon-only fe-toolbar__more"
|
|
510
|
+
:title="t('toolbar.more')"
|
|
511
|
+
:aria-label="t('toolbar.more')"
|
|
512
|
+
aria-haspopup="menu"
|
|
513
|
+
@click="openMore"
|
|
514
|
+
>
|
|
515
|
+
<span class="fe-icon">⋯</span>
|
|
516
|
+
</button>
|
|
517
|
+
</template>
|
|
518
|
+
|
|
519
|
+
<ContextMenu
|
|
520
|
+
ref="moreRef"
|
|
521
|
+
:locale="locale"
|
|
522
|
+
:theme="theme || 'auto'"
|
|
523
|
+
:sheet="coarse"
|
|
524
|
+
:actions="moreActions"
|
|
525
|
+
@select="onMoreSelect"
|
|
526
|
+
/>
|
|
527
|
+
</template>
|
|
283
528
|
</div>
|
|
284
529
|
</template>
|
|
@@ -89,6 +89,19 @@ export interface UserSuggestion {
|
|
|
89
89
|
display_name: string;
|
|
90
90
|
role: string;
|
|
91
91
|
}
|
|
92
|
+
|
|
93
|
+
/* === koru:k1 — version history (inspector panel) === */
|
|
94
|
+
|
|
95
|
+
/** Mirrors backend `model.NodeVersion` (GET /api/files/versions?node_id=…). */
|
|
96
|
+
export interface NodeVersion {
|
|
97
|
+
id: number;
|
|
98
|
+
node_id: number;
|
|
99
|
+
version_n: number;
|
|
100
|
+
storage_key?: string;
|
|
101
|
+
size: number;
|
|
102
|
+
etag?: string;
|
|
103
|
+
created_at: string;
|
|
104
|
+
}
|
|
92
105
|
export interface UserSearchResponse {
|
|
93
106
|
users: UserSuggestion[];
|
|
94
107
|
}
|
|
@@ -686,6 +699,48 @@ export function useFileApi(config: ExplorerConfig) {
|
|
|
686
699
|
});
|
|
687
700
|
}
|
|
688
701
|
|
|
702
|
+
/* === koru:k1 — version history (inspector panel) ===
|
|
703
|
+
* Derived from the manager endpoint by swapping `/manager` for `/versions`
|
|
704
|
+
* (same trick permissionsUrl/globalSearch use) so embedded proxies that
|
|
705
|
+
* forward the whole /api/files/* subtree keep working.
|
|
706
|
+
* GET /api/files/versions?node_id=N → {versions, node_id}
|
|
707
|
+
* POST /api/files/versions/restore → {node_id, version_id, snapshot_current}
|
|
708
|
+
* POST /api/files/versions/snapshot → {node_id} (may not exist on older backends)
|
|
709
|
+
*/
|
|
710
|
+
function versionsUrl(sub = ''): string {
|
|
711
|
+
const base = endpoints.manager.replace(/\/manager(\?.*)?$/, '/versions');
|
|
712
|
+
return base + sub;
|
|
713
|
+
}
|
|
714
|
+
async function listVersions(nodeId: number): Promise<NodeVersion[]> {
|
|
715
|
+
const data = await jsonFetch<{ versions?: NodeVersion[] | null }>(
|
|
716
|
+
versionsUrl() + '?node_id=' + encodeURIComponent(String(nodeId)),
|
|
717
|
+
);
|
|
718
|
+
return Array.isArray(data?.versions) ? data.versions : [];
|
|
719
|
+
}
|
|
720
|
+
async function restoreVersion(
|
|
721
|
+
nodeId: number,
|
|
722
|
+
versionId: number,
|
|
723
|
+
snapshotCurrent = true,
|
|
724
|
+
): Promise<void> {
|
|
725
|
+
await jsonFetch(versionsUrl('/restore'), {
|
|
726
|
+
method: 'POST',
|
|
727
|
+
headers: { 'Content-Type': 'application/json' },
|
|
728
|
+
body: JSON.stringify({
|
|
729
|
+
node_id: nodeId,
|
|
730
|
+
version_id: versionId,
|
|
731
|
+
snapshot_current: snapshotCurrent,
|
|
732
|
+
}),
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
async function snapshotVersion(nodeId: number): Promise<void> {
|
|
736
|
+
await jsonFetch(versionsUrl('/snapshot'), {
|
|
737
|
+
method: 'POST',
|
|
738
|
+
headers: { 'Content-Type': 'application/json' },
|
|
739
|
+
body: JSON.stringify({ node_id: nodeId }),
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
/* === /koru:k1 === */
|
|
743
|
+
|
|
689
744
|
// Mint a short-lived WebSocket auth ticket for the realtime layer. Derived
|
|
690
745
|
// from the manager URL (so it flows through the same host proxy) and uses the
|
|
691
746
|
// same auth/creds as every other call. Returns null on any failure (a backend
|
|
@@ -731,6 +786,10 @@ export function useFileApi(config: ExplorerConfig) {
|
|
|
731
786
|
archiveList,
|
|
732
787
|
archiveExtract,
|
|
733
788
|
archiveAdd,
|
|
789
|
+
// Version history (koru:k1 inspector)
|
|
790
|
+
listVersions,
|
|
791
|
+
restoreVersion,
|
|
792
|
+
snapshotVersion,
|
|
734
793
|
// Permissions (RBAC panel)
|
|
735
794
|
listPermissions,
|
|
736
795
|
resolveEmail,
|
|
@@ -22,6 +22,7 @@ export interface ShortcutHandlers {
|
|
|
22
22
|
onPathJump?: () => void; // Cmd+K / Ctrl+K
|
|
23
23
|
onGoUp?: () => void; // Alt+Up / Backspace (when nothing selected)
|
|
24
24
|
onShowHelp?: () => void; // ? (Shift+/ on most layouts)
|
|
25
|
+
onToggleInspector?: () => void; // i (koru:k1 details panel)
|
|
25
26
|
hasSelection?: () => boolean; // disambiguates Backspace
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -45,6 +46,7 @@ export const SHORTCUTS: ShortcutDef[] = [
|
|
|
45
46
|
{ keys: ['Alt+↑', 'Backspace'], labelKey: 'shortcuts.go_up', groupKey: 'shortcuts.group.nav' },
|
|
46
47
|
{ keys: ['Enter'], labelKey: 'shortcuts.open', groupKey: 'shortcuts.group.nav' },
|
|
47
48
|
{ keys: ['Esc'], labelKey: 'shortcuts.close', groupKey: 'shortcuts.group.nav' },
|
|
49
|
+
{ keys: ['I'], labelKey: 'shortcuts.inspector', groupKey: 'shortcuts.group.nav' } /* koru:k1 */,
|
|
48
50
|
{ keys: ['?'], labelKey: 'shortcuts.help', groupKey: 'shortcuts.group.nav' },
|
|
49
51
|
// Selection
|
|
50
52
|
{ keys: ['Ctrl+A'], labelKey: 'shortcuts.select_all', groupKey: 'shortcuts.group.selection' },
|
|
@@ -160,6 +162,15 @@ export function useKeyboardShortcuts(rootEl: Ref<HTMLElement | null>, handlers:
|
|
|
160
162
|
handlers.onDuplicate();
|
|
161
163
|
}
|
|
162
164
|
break;
|
|
165
|
+
/* koru:k1 — plain `i` toggles the inspector (details) panel. Ctrl+I /
|
|
166
|
+
Alt+I stay untouched so browser/OS combos keep working. */
|
|
167
|
+
case 'i':
|
|
168
|
+
case 'I':
|
|
169
|
+
if (!ctrl && !e.altKey && handlers.onToggleInspector) {
|
|
170
|
+
e.preventDefault();
|
|
171
|
+
handlers.onToggleInspector();
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
163
174
|
case 'k':
|
|
164
175
|
case 'K':
|
|
165
176
|
if (ctrl && handlers.onPathJump) {
|
package/src/locales/en.ts
CHANGED
|
@@ -195,4 +195,49 @@ export const en: Record<string, string> = {
|
|
|
195
195
|
'palette.save': 'Save search',
|
|
196
196
|
'palette.saved.delete': 'Delete saved search',
|
|
197
197
|
'search.in_content': 'In content',
|
|
198
|
+
|
|
199
|
+
/* === bag:b4 === */
|
|
200
|
+
'toolbar.more': 'More actions',
|
|
201
|
+
'toolbar.search.close': 'Close search',
|
|
202
|
+
'sheet.menu': 'Actions',
|
|
203
|
+
'sheet.close': 'Close',
|
|
204
|
+
|
|
205
|
+
/* === koru:k1 === */
|
|
206
|
+
'toolbar.inspector': 'Details',
|
|
207
|
+
'ctx.details': 'Details',
|
|
208
|
+
'shortcuts.inspector': 'Toggle details panel',
|
|
209
|
+
'inspector.title': 'Details',
|
|
210
|
+
'inspector.close': 'Close',
|
|
211
|
+
'inspector.section.general': 'General',
|
|
212
|
+
'inspector.section.versions': 'Versions',
|
|
213
|
+
'inspector.section.permissions': 'Permissions',
|
|
214
|
+
'inspector.section.shares': 'Shares',
|
|
215
|
+
'inspector.path': 'Path',
|
|
216
|
+
'inspector.size': 'Size',
|
|
217
|
+
'inspector.modified': 'Modified',
|
|
218
|
+
'inspector.mime': 'MIME type',
|
|
219
|
+
'inspector.etag': 'ETag',
|
|
220
|
+
'inspector.copy': 'Copy',
|
|
221
|
+
'inspector.copied': 'Copied',
|
|
222
|
+
'inspector.error': 'Could not load',
|
|
223
|
+
'inspector.items_summary': '{n} items, {size} total',
|
|
224
|
+
'inspector.folder_items': '{n} items',
|
|
225
|
+
'inspector.versions.empty': 'No version history',
|
|
226
|
+
'inspector.versions.v': 'Version {n}',
|
|
227
|
+
'inspector.versions.restore': 'Restore',
|
|
228
|
+
'inspector.versions.restore_confirm': 'Restore this version?',
|
|
229
|
+
'inspector.versions.snapshot_current': 'Snapshot the current state first',
|
|
230
|
+
'inspector.versions.confirm': 'Confirm',
|
|
231
|
+
'inspector.versions.cancel': 'Cancel',
|
|
232
|
+
'inspector.versions.take_snapshot': 'Snapshot current state',
|
|
233
|
+
'inspector.versions.restored': 'Version restored',
|
|
234
|
+
'inspector.versions.snapshotted': 'Snapshot taken',
|
|
235
|
+
'inspector.versions.unsupported': 'The server does not support this operation',
|
|
236
|
+
'inspector.perm.none': 'No access',
|
|
237
|
+
'inspector.perm.viewer': 'Viewer',
|
|
238
|
+
'inspector.perm.editor': 'Editor',
|
|
239
|
+
'inspector.perm.owner': 'Owner',
|
|
240
|
+
'inspector.perm.manage': 'Manage permissions',
|
|
241
|
+
'inspector.shares.empty': 'No share links',
|
|
242
|
+
'inspector.shares.copy': 'Copy link',
|
|
198
243
|
};
|
package/src/locales/tr.ts
CHANGED
|
@@ -195,4 +195,49 @@ export const tr: Record<string, string> = {
|
|
|
195
195
|
'palette.save': 'Aramayı kaydet',
|
|
196
196
|
'palette.saved.delete': 'Kayıtlı aramayı sil',
|
|
197
197
|
'search.in_content': 'İçerikte',
|
|
198
|
+
|
|
199
|
+
/* === bag:b4 === */
|
|
200
|
+
'toolbar.more': 'Diğer işlemler',
|
|
201
|
+
'toolbar.search.close': 'Aramayı kapat',
|
|
202
|
+
'sheet.menu': 'İşlemler',
|
|
203
|
+
'sheet.close': 'Kapat',
|
|
204
|
+
|
|
205
|
+
/* === koru:k1 === */
|
|
206
|
+
'toolbar.inspector': 'Ayrıntılar',
|
|
207
|
+
'ctx.details': 'Ayrıntılar',
|
|
208
|
+
'shortcuts.inspector': 'Ayrıntılar panelini aç/kapat',
|
|
209
|
+
'inspector.title': 'Ayrıntılar',
|
|
210
|
+
'inspector.close': 'Kapat',
|
|
211
|
+
'inspector.section.general': 'Genel',
|
|
212
|
+
'inspector.section.versions': 'Sürümler',
|
|
213
|
+
'inspector.section.permissions': 'İzinler',
|
|
214
|
+
'inspector.section.shares': 'Paylaşımlar',
|
|
215
|
+
'inspector.path': 'Yol',
|
|
216
|
+
'inspector.size': 'Boyut',
|
|
217
|
+
'inspector.modified': 'Değiştirilme',
|
|
218
|
+
'inspector.mime': 'MIME türü',
|
|
219
|
+
'inspector.etag': 'ETag',
|
|
220
|
+
'inspector.copy': 'Kopyala',
|
|
221
|
+
'inspector.copied': 'Kopyalandı',
|
|
222
|
+
'inspector.error': 'Yüklenemedi',
|
|
223
|
+
'inspector.items_summary': '{n} öğe, toplam {size}',
|
|
224
|
+
'inspector.folder_items': '{n} öğe',
|
|
225
|
+
'inspector.versions.empty': 'Sürüm geçmişi yok',
|
|
226
|
+
'inspector.versions.v': 'Sürüm {n}',
|
|
227
|
+
'inspector.versions.restore': 'Geri yükle',
|
|
228
|
+
'inspector.versions.restore_confirm': 'Bu sürüm geri yüklensin mi?',
|
|
229
|
+
'inspector.versions.snapshot_current': 'Önce şimdiki halin sürümünü al',
|
|
230
|
+
'inspector.versions.confirm': 'Onayla',
|
|
231
|
+
'inspector.versions.cancel': 'Vazgeç',
|
|
232
|
+
'inspector.versions.take_snapshot': 'Şimdiki halin sürümünü al',
|
|
233
|
+
'inspector.versions.restored': 'Sürüm geri yüklendi',
|
|
234
|
+
'inspector.versions.snapshotted': 'Sürüm alındı',
|
|
235
|
+
'inspector.versions.unsupported': 'Sunucu bu işlemi desteklemiyor',
|
|
236
|
+
'inspector.perm.none': 'Erişim yok',
|
|
237
|
+
'inspector.perm.viewer': 'Görüntüleyen',
|
|
238
|
+
'inspector.perm.editor': 'Düzenleyen',
|
|
239
|
+
'inspector.perm.owner': 'Sahip',
|
|
240
|
+
'inspector.perm.manage': 'İzinleri yönet',
|
|
241
|
+
'inspector.shares.empty': 'Paylaşım linki yok',
|
|
242
|
+
'inspector.shares.copy': 'Linki kopyala',
|
|
198
243
|
};
|