@brftech/filex-core 0.41.4 → 0.42.1
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-oqaEyqcB.js → ArchiveViewer-CLNevfPE.js} +2 -2
- package/dist/{ArchiveViewer-oqaEyqcB.js.map → ArchiveViewer-CLNevfPE.js.map} +1 -1
- package/dist/{CsvViewer-BfT-4WpB.js → CsvViewer-K8XA3z7N.js} +2 -2
- package/dist/{CsvViewer-BfT-4WpB.js.map → CsvViewer-K8XA3z7N.js.map} +1 -1
- package/dist/{DrawioViewer-Cu2ZquNO.js → DrawioViewer-BuhZOAx6.js} +2 -2
- package/dist/{DrawioViewer-Cu2ZquNO.js.map → DrawioViewer-BuhZOAx6.js.map} +1 -1
- package/dist/{EpubViewer-yfAfnrYF.js → EpubViewer-DS3mb3k8.js} +2 -2
- package/dist/{EpubViewer-yfAfnrYF.js.map → EpubViewer-DS3mb3k8.js.map} +1 -1
- package/dist/{IpynbViewer-DLSOs3yU.js → IpynbViewer-BTk7p4Dn.js} +2 -2
- package/dist/{IpynbViewer-DLSOs3yU.js.map → IpynbViewer-BTk7p4Dn.js.map} +1 -1
- package/dist/{MermaidViewer-D2AsT14x.js → MermaidViewer-DqucH_io.js} +2 -2
- package/dist/{MermaidViewer-D2AsT14x.js.map → MermaidViewer-DqucH_io.js.map} +1 -1
- package/dist/{PsdViewer-DOza4QJt.js → PsdViewer-CwiVENmq.js} +2 -2
- package/dist/{PsdViewer-DOza4QJt.js.map → PsdViewer-CwiVENmq.js.map} +1 -1
- package/dist/{TiffViewer-24MCOn1t.js → TiffViewer-CpewSVmQ.js} +2 -2
- package/dist/{TiffViewer-24MCOn1t.js.map → TiffViewer-CpewSVmQ.js.map} +1 -1
- package/dist/{Viewer3D-BVjtbhhc.js → Viewer3D-CIrOn63I.js} +2 -2
- package/dist/{Viewer3D-BVjtbhhc.js.map → Viewer3D-CIrOn63I.js.map} +1 -1
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +46 -46
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/{index-B8PLGR-X.js → index-Dx0IbBcI.js} +8607 -8579
- package/dist/index-Dx0IbBcI.js.map +1 -0
- package/dist/index.d.ts +59 -4
- package/package.json +1 -1
- package/src/FileExplorer.vue +32 -0
- package/src/components/FilePane.vue +22 -7
- package/src/components/HomeView.vue +6 -3
- package/src/components/SideNav.vue +22 -0
- package/src/locales/en.ts +1 -0
- package/src/locales/tr.ts +1 -0
- package/src/types/ExplorerConfig.ts +32 -0
- package/dist/index-B8PLGR-X.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -484,6 +484,7 @@ readonly authHeaders?: (() => Record<string, string> | Promise<Record<string, st
|
|
|
484
484
|
readonly authCredentials?: RequestCredentials | undefined;
|
|
485
485
|
readonly e2eActive?: boolean | undefined;
|
|
486
486
|
readonly bodyOverride?: boolean | undefined;
|
|
487
|
+
readonly openTrigger?: "single" | "double" | undefined;
|
|
487
488
|
} & VNodeProps & AllowedComponentProps & ComponentCustomProps;
|
|
488
489
|
$attrs: Attrs;
|
|
489
490
|
$refs: {
|
|
@@ -548,6 +549,7 @@ authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
|
548
549
|
authCredentials?: RequestCredentials;
|
|
549
550
|
e2eActive?: boolean;
|
|
550
551
|
bodyOverride?: boolean;
|
|
552
|
+
openTrigger?: "single" | "double";
|
|
551
553
|
}> & Readonly<{
|
|
552
554
|
[x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
|
|
553
555
|
}>, {
|
|
@@ -566,6 +568,7 @@ trashVisible: boolean;
|
|
|
566
568
|
showCrumbs: boolean;
|
|
567
569
|
showFilterBar: boolean;
|
|
568
570
|
showViewSwitcher: boolean;
|
|
571
|
+
openTrigger: "single" | "double";
|
|
569
572
|
}, {}, string, {}, GlobalComponents, GlobalDirectives, string, ComponentProvideOptions> & {
|
|
570
573
|
beforeCreate?: (() => void) | (() => void)[];
|
|
571
574
|
created?: (() => void) | (() => void)[];
|
|
@@ -655,6 +658,7 @@ trashVisible: boolean;
|
|
|
655
658
|
showCrumbs: boolean;
|
|
656
659
|
showFilterBar: boolean;
|
|
657
660
|
showViewSwitcher: boolean;
|
|
661
|
+
openTrigger: "single" | "double";
|
|
658
662
|
}> & Omit<Readonly<{
|
|
659
663
|
paneId: PaneId;
|
|
660
664
|
api: FileApi;
|
|
@@ -706,9 +710,10 @@ authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
|
706
710
|
authCredentials?: RequestCredentials;
|
|
707
711
|
e2eActive?: boolean;
|
|
708
712
|
bodyOverride?: boolean;
|
|
713
|
+
openTrigger?: "single" | "double";
|
|
709
714
|
}> & Readonly<{
|
|
710
715
|
[x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
|
|
711
|
-
}>, "reload" | "dirPerm" | "goUp" | "getPath" | "visibleNodes" | "loadFolder" | "rowCount" | "rowPaths" | ("trashVisible" | "showCrumbs" | "showFilterBar" | "showViewSwitcher")> & {
|
|
716
|
+
}>, "reload" | "dirPerm" | "goUp" | "getPath" | "visibleNodes" | "loadFolder" | "rowCount" | "rowPaths" | ("trashVisible" | "showCrumbs" | "showFilterBar" | "showViewSwitcher" | "openTrigger")> & {
|
|
712
717
|
reload: () => Promise<void>;
|
|
713
718
|
goUp: () => void;
|
|
714
719
|
getPath: () => string;
|
|
@@ -780,6 +785,7 @@ readonly authHeaders?: (() => Record<string, string> | Promise<Record<string, st
|
|
|
780
785
|
readonly authCredentials?: RequestCredentials | undefined;
|
|
781
786
|
readonly e2eActive?: boolean | undefined;
|
|
782
787
|
readonly bodyOverride?: boolean | undefined;
|
|
788
|
+
readonly openTrigger?: "single" | "double" | undefined;
|
|
783
789
|
} & VNodeProps & AllowedComponentProps & ComponentCustomProps;
|
|
784
790
|
$attrs: Attrs;
|
|
785
791
|
$refs: {
|
|
@@ -844,6 +850,7 @@ authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
|
844
850
|
authCredentials?: RequestCredentials;
|
|
845
851
|
e2eActive?: boolean;
|
|
846
852
|
bodyOverride?: boolean;
|
|
853
|
+
openTrigger?: "single" | "double";
|
|
847
854
|
}> & Readonly<{
|
|
848
855
|
[x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
|
|
849
856
|
}>, {
|
|
@@ -862,6 +869,7 @@ trashVisible: boolean;
|
|
|
862
869
|
showCrumbs: boolean;
|
|
863
870
|
showFilterBar: boolean;
|
|
864
871
|
showViewSwitcher: boolean;
|
|
872
|
+
openTrigger: "single" | "double";
|
|
865
873
|
}, {}, string, {}, GlobalComponents, GlobalDirectives, string, ComponentProvideOptions> & {
|
|
866
874
|
beforeCreate?: (() => void) | (() => void)[];
|
|
867
875
|
created?: (() => void) | (() => void)[];
|
|
@@ -951,6 +959,7 @@ trashVisible: boolean;
|
|
|
951
959
|
showCrumbs: boolean;
|
|
952
960
|
showFilterBar: boolean;
|
|
953
961
|
showViewSwitcher: boolean;
|
|
962
|
+
openTrigger: "single" | "double";
|
|
954
963
|
}> & Omit<Readonly<{
|
|
955
964
|
paneId: PaneId;
|
|
956
965
|
api: FileApi;
|
|
@@ -1002,9 +1011,10 @@ authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
|
1002
1011
|
authCredentials?: RequestCredentials;
|
|
1003
1012
|
e2eActive?: boolean;
|
|
1004
1013
|
bodyOverride?: boolean;
|
|
1014
|
+
openTrigger?: "single" | "double";
|
|
1005
1015
|
}> & Readonly<{
|
|
1006
1016
|
[x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
|
|
1007
|
-
}>, "reload" | "dirPerm" | "goUp" | "getPath" | "visibleNodes" | "loadFolder" | "rowCount" | "rowPaths" | ("trashVisible" | "showCrumbs" | "showFilterBar" | "showViewSwitcher")> & {
|
|
1017
|
+
}>, "reload" | "dirPerm" | "goUp" | "getPath" | "visibleNodes" | "loadFolder" | "rowCount" | "rowPaths" | ("trashVisible" | "showCrumbs" | "showFilterBar" | "showViewSwitcher" | "openTrigger")> & {
|
|
1008
1018
|
reload: () => Promise<void>;
|
|
1009
1019
|
goUp: () => void;
|
|
1010
1020
|
getPath: () => string;
|
|
@@ -1081,6 +1091,7 @@ trashVisible: boolean;
|
|
|
1081
1091
|
showCrumbs: boolean;
|
|
1082
1092
|
showFilterBar: boolean;
|
|
1083
1093
|
showViewSwitcher: boolean;
|
|
1094
|
+
openTrigger: "single" | "double";
|
|
1084
1095
|
}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLElement>;
|
|
1085
1096
|
|
|
1086
1097
|
declare type __VLS_Props = {
|
|
@@ -1284,6 +1295,10 @@ declare type __VLS_Props_15 = {
|
|
|
1284
1295
|
* encrypted lock screen) — those are WINDOW states with no listing
|
|
1285
1296
|
* behind them, so the pane's own state chain must not also run. */
|
|
1286
1297
|
bodyOverride?: boolean;
|
|
1298
|
+
/** Mouse open gesture — 'double' (single selects, double opens) or
|
|
1299
|
+
* 'single' (first click opens). Touch always opens on tap; the checkbox
|
|
1300
|
+
* always selects. See onViewClick. */
|
|
1301
|
+
openTrigger?: 'single' | 'double';
|
|
1287
1302
|
};
|
|
1288
1303
|
|
|
1289
1304
|
declare type __VLS_Props_16 = {
|
|
@@ -1979,6 +1994,7 @@ declare function __VLS_template(): {
|
|
|
1979
1994
|
readonly authCredentials?: RequestCredentials | undefined;
|
|
1980
1995
|
readonly e2eActive?: boolean | undefined;
|
|
1981
1996
|
readonly bodyOverride?: boolean | undefined;
|
|
1997
|
+
readonly openTrigger?: "single" | "double" | undefined;
|
|
1982
1998
|
} & VNodeProps & AllowedComponentProps & ComponentCustomProps;
|
|
1983
1999
|
$attrs: Attrs;
|
|
1984
2000
|
$refs: {
|
|
@@ -2043,6 +2059,7 @@ declare function __VLS_template(): {
|
|
|
2043
2059
|
authCredentials?: RequestCredentials;
|
|
2044
2060
|
e2eActive?: boolean;
|
|
2045
2061
|
bodyOverride?: boolean;
|
|
2062
|
+
openTrigger?: "single" | "double";
|
|
2046
2063
|
}> & Readonly<{
|
|
2047
2064
|
[x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
|
|
2048
2065
|
}>, {
|
|
@@ -2061,6 +2078,7 @@ declare function __VLS_template(): {
|
|
|
2061
2078
|
showCrumbs: boolean;
|
|
2062
2079
|
showFilterBar: boolean;
|
|
2063
2080
|
showViewSwitcher: boolean;
|
|
2081
|
+
openTrigger: "single" | "double";
|
|
2064
2082
|
}, {}, string, {}, GlobalComponents, GlobalDirectives, string, ComponentProvideOptions> & {
|
|
2065
2083
|
beforeCreate?: (() => void) | (() => void)[];
|
|
2066
2084
|
created?: (() => void) | (() => void)[];
|
|
@@ -2150,6 +2168,7 @@ declare function __VLS_template(): {
|
|
|
2150
2168
|
showCrumbs: boolean;
|
|
2151
2169
|
showFilterBar: boolean;
|
|
2152
2170
|
showViewSwitcher: boolean;
|
|
2171
|
+
openTrigger: "single" | "double";
|
|
2153
2172
|
}> & Omit<Readonly<{
|
|
2154
2173
|
paneId: PaneId;
|
|
2155
2174
|
api: FileApi;
|
|
@@ -2201,9 +2220,10 @@ declare function __VLS_template(): {
|
|
|
2201
2220
|
authCredentials?: RequestCredentials;
|
|
2202
2221
|
e2eActive?: boolean;
|
|
2203
2222
|
bodyOverride?: boolean;
|
|
2223
|
+
openTrigger?: "single" | "double";
|
|
2204
2224
|
}> & Readonly<{
|
|
2205
2225
|
[x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
|
|
2206
|
-
}>, "reload" | "dirPerm" | "goUp" | "getPath" | "visibleNodes" | "loadFolder" | "rowCount" | "rowPaths" | ("trashVisible" | "showCrumbs" | "showFilterBar" | "showViewSwitcher")> & {
|
|
2226
|
+
}>, "reload" | "dirPerm" | "goUp" | "getPath" | "visibleNodes" | "loadFolder" | "rowCount" | "rowPaths" | ("trashVisible" | "showCrumbs" | "showFilterBar" | "showViewSwitcher" | "openTrigger")> & {
|
|
2207
2227
|
reload: () => Promise<void>;
|
|
2208
2228
|
goUp: () => void;
|
|
2209
2229
|
getPath: () => string;
|
|
@@ -2275,6 +2295,7 @@ declare function __VLS_template(): {
|
|
|
2275
2295
|
readonly authCredentials?: RequestCredentials | undefined;
|
|
2276
2296
|
readonly e2eActive?: boolean | undefined;
|
|
2277
2297
|
readonly bodyOverride?: boolean | undefined;
|
|
2298
|
+
readonly openTrigger?: "single" | "double" | undefined;
|
|
2278
2299
|
} & VNodeProps & AllowedComponentProps & ComponentCustomProps;
|
|
2279
2300
|
$attrs: Attrs;
|
|
2280
2301
|
$refs: {
|
|
@@ -2339,6 +2360,7 @@ declare function __VLS_template(): {
|
|
|
2339
2360
|
authCredentials?: RequestCredentials;
|
|
2340
2361
|
e2eActive?: boolean;
|
|
2341
2362
|
bodyOverride?: boolean;
|
|
2363
|
+
openTrigger?: "single" | "double";
|
|
2342
2364
|
}> & Readonly<{
|
|
2343
2365
|
[x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
|
|
2344
2366
|
}>, {
|
|
@@ -2357,6 +2379,7 @@ declare function __VLS_template(): {
|
|
|
2357
2379
|
showCrumbs: boolean;
|
|
2358
2380
|
showFilterBar: boolean;
|
|
2359
2381
|
showViewSwitcher: boolean;
|
|
2382
|
+
openTrigger: "single" | "double";
|
|
2360
2383
|
}, {}, string, {}, GlobalComponents, GlobalDirectives, string, ComponentProvideOptions> & {
|
|
2361
2384
|
beforeCreate?: (() => void) | (() => void)[];
|
|
2362
2385
|
created?: (() => void) | (() => void)[];
|
|
@@ -2446,6 +2469,7 @@ declare function __VLS_template(): {
|
|
|
2446
2469
|
showCrumbs: boolean;
|
|
2447
2470
|
showFilterBar: boolean;
|
|
2448
2471
|
showViewSwitcher: boolean;
|
|
2472
|
+
openTrigger: "single" | "double";
|
|
2449
2473
|
}> & Omit<Readonly<{
|
|
2450
2474
|
paneId: PaneId;
|
|
2451
2475
|
api: FileApi;
|
|
@@ -2497,9 +2521,10 @@ declare function __VLS_template(): {
|
|
|
2497
2521
|
authCredentials?: RequestCredentials;
|
|
2498
2522
|
e2eActive?: boolean;
|
|
2499
2523
|
bodyOverride?: boolean;
|
|
2524
|
+
openTrigger?: "single" | "double";
|
|
2500
2525
|
}> & Readonly<{
|
|
2501
2526
|
[x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
|
|
2502
|
-
}>, "reload" | "dirPerm" | "goUp" | "getPath" | "visibleNodes" | "loadFolder" | "rowCount" | "rowPaths" | ("trashVisible" | "showCrumbs" | "showFilterBar" | "showViewSwitcher")> & {
|
|
2527
|
+
}>, "reload" | "dirPerm" | "goUp" | "getPath" | "visibleNodes" | "loadFolder" | "rowCount" | "rowPaths" | ("trashVisible" | "showCrumbs" | "showFilterBar" | "showViewSwitcher" | "openTrigger")> & {
|
|
2503
2528
|
reload: () => Promise<void>;
|
|
2504
2529
|
goUp: () => void;
|
|
2505
2530
|
getPath: () => string;
|
|
@@ -4188,6 +4213,36 @@ export declare interface ExplorerConfig {
|
|
|
4188
4213
|
* Starred row that then disappears. A value here wins over the server's.
|
|
4189
4214
|
*/
|
|
4190
4215
|
callerKind?: 'user' | 'app';
|
|
4216
|
+
/**
|
|
4217
|
+
* How a mouse opens an item.
|
|
4218
|
+
*
|
|
4219
|
+
* 'double' (default) — a single click SELECTS, a double click OPENS
|
|
4220
|
+
* (classic desktop file-manager). This is a
|
|
4221
|
+
* per-viewer preference the host may expose as a
|
|
4222
|
+
* setting.
|
|
4223
|
+
* 'single' — a single click OPENS (the item is opened on the
|
|
4224
|
+
* first click; the checkbox is still the one click
|
|
4225
|
+
* that selects).
|
|
4226
|
+
*
|
|
4227
|
+
* ⚠ TOUCH is not governed by this: a finger tap always opens (the mobile
|
|
4228
|
+
* convention, and there is no hover-to-select on a touchscreen). The
|
|
4229
|
+
* checkbox always selects, on every device and in either mode. See
|
|
4230
|
+
* FilePane.onViewClick + composables/useRowTouch.
|
|
4231
|
+
*/
|
|
4232
|
+
openTrigger?: 'single' | 'double';
|
|
4233
|
+
/**
|
|
4234
|
+
* Hand the OPEN of a file to the host instead of opening the in-page
|
|
4235
|
+
* preview/editor overlay. When true, opening a file emits `file-opened`
|
|
4236
|
+
* (path + basename) and the explorer does NOT mount its own PreviewModal —
|
|
4237
|
+
* the host decides what to do (the desktop app opens the file in its own
|
|
4238
|
+
* window per document). Directories still navigate inline, and Space
|
|
4239
|
+
* quick-look still peeks in-page. Default false (web/embeds preview inline).
|
|
4240
|
+
*
|
|
4241
|
+
* ⚠ Ignored inside an unlocked E2E-encrypted folder: the host window would
|
|
4242
|
+
* fetch raw server bytes (ciphertext), so those keep the in-page decrypted
|
|
4243
|
+
* preview.
|
|
4244
|
+
*/
|
|
4245
|
+
openInHost?: boolean;
|
|
4191
4246
|
/** Default view. */
|
|
4192
4247
|
viewMode?: 'list' | 'grid';
|
|
4193
4248
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brftech/filex-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.1",
|
|
4
4
|
"description": "filex core — Vue 3 source of truth for the filex file manager (FileExplorer + ConnectionsPanel SFCs, composables, types)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/filex-core.umd.cjs",
|
package/src/FileExplorer.vue
CHANGED
|
@@ -332,6 +332,10 @@ const files = ref<FileNode[]>([]);
|
|
|
332
332
|
// RBAC effective level for the current directory ('' = ACL not enforced on
|
|
333
333
|
// this storage → no gating). Drives which write/manage actions are offered.
|
|
334
334
|
const dirPerm = ref<string>('');
|
|
335
|
+
/** The listed folder sits on a read-only storage (`read_only` on the index
|
|
336
|
+
* response). Folded into permCanEdit so every write affordance — toolbar,
|
|
337
|
+
* sidebar menu, context menu, drop zone, editor mode — reads the same fact. */
|
|
338
|
+
const dirReadOnly = ref(false);
|
|
335
339
|
// The dead deep-link state: set to the requested path when a listing came
|
|
336
340
|
// back 404 (folder doesn't exist) or 403 (RBAC-hidden — rendered identically
|
|
337
341
|
// on purpose so a denied folder doesn't reveal that it exists). '' = none.
|
|
@@ -2034,6 +2038,14 @@ function onInspectorManage(n: FileNode) {
|
|
|
2034
2038
|
// (the pre-RBAC default). Otherwise 'editor'/'owner' may write; only 'owner'
|
|
2035
2039
|
// manages permissions. Enforcement is server-side; this just shapes the menu.
|
|
2036
2040
|
function permCanEdit(p: string | undefined): boolean {
|
|
2041
|
+
// A read-only storage refuses every write on the server (403 "storage is
|
|
2042
|
+
// read-only") whatever level the ACL grants — an owner of a read-only mount
|
|
2043
|
+
// is an owner who cannot write. Say so here rather than after the click:
|
|
2044
|
+
// until this line the toolbar offered New folder / Upload and the sidebar's
|
|
2045
|
+
// "+ New" menu on a read-only mount, and the user found out from the error
|
|
2046
|
+
// (issue #30). The level itself is left alone: 'owner' still opens the
|
|
2047
|
+
// permissions panel, sharing a read-only file is still allowed.
|
|
2048
|
+
if (dirReadOnly.value) return false;
|
|
2037
2049
|
// undefined = ACL not enforced (dev / unwired) → full access. In production
|
|
2038
2050
|
// the backend always sends a level; 'none'/'viewer' cannot write, only
|
|
2039
2051
|
// 'editor'/'owner' can.
|
|
@@ -2350,6 +2362,7 @@ async function load(path?: string) {
|
|
|
2350
2362
|
adapter.value = resp.adapter;
|
|
2351
2363
|
dirname.value = resp.dirname;
|
|
2352
2364
|
dirPerm.value = (resp.perm as string) || '';
|
|
2365
|
+
dirReadOnly.value = resp.read_only === true;
|
|
2353
2366
|
/* wiring:e2 — backend tells us when this dir sits inside an encrypted
|
|
2354
2367
|
subtree; '' resets on every plain folder. Drives the lock screen. */
|
|
2355
2368
|
e2eRoot.value = typeof resp.e2e_root === 'string' ? resp.e2e_root : '';
|
|
@@ -3090,6 +3103,16 @@ function openNode(n: FileNode) {
|
|
|
3090
3103
|
}
|
|
3091
3104
|
if (e2eLocked.value && n.type === 'file') return;
|
|
3092
3105
|
/* /wiring:e2 */
|
|
3106
|
+
// Host-owned open (desktop app): a file opens in the host's OWN window per
|
|
3107
|
+
// document, not in the in-page overlay. Emit and stop — the host listens on
|
|
3108
|
+
// `file-opened` and spawns the window. Directories still navigate inline
|
|
3109
|
+
// (handled above); Space quick-look still peeks in-page. E2E files fell into
|
|
3110
|
+
// the decrypted in-page branch above, so a host window never gets ciphertext.
|
|
3111
|
+
if (props.config.openInHost && n.type === 'file') {
|
|
3112
|
+
emit('file-opened', { path: n.path, basename: n.basename });
|
|
3113
|
+
void markRecent(n);
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3093
3116
|
// "Aç" / double-click contract: open in a new tab against the
|
|
3094
3117
|
// standalone editor route, regardless of file type. The editor page
|
|
3095
3118
|
// picks the right viewer (OnlyOffice for office, Monaco for code/
|
|
@@ -3204,6 +3227,13 @@ function openNodeInNewTab(n: FileNode) {
|
|
|
3204
3227
|
return;
|
|
3205
3228
|
}
|
|
3206
3229
|
/* /wiring:e2 */
|
|
3230
|
+
// Host-owned open (desktop): the context-menu "Aç" opens the host window
|
|
3231
|
+
// too, so Enter/double-click/"Aç" are ONE open path — not a window here and
|
|
3232
|
+
// a system-browser tab there. openNode carries the `file-opened` emit.
|
|
3233
|
+
if (props.config.openInHost) {
|
|
3234
|
+
openNode(n);
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3207
3237
|
// RBAC: a viewer (no edit on this item) can't use the editable "Aç"
|
|
3208
3238
|
// surface — drop to the read-only in-page preview instead.
|
|
3209
3239
|
if (!permCanEdit((n.perm as string) ?? dirPerm.value)) {
|
|
@@ -6365,6 +6395,7 @@ function closeRecoveryKey() {
|
|
|
6365
6395
|
:order="listingOrder"
|
|
6366
6396
|
:view-mode="viewMode"
|
|
6367
6397
|
:view-modes="allowedViewModes"
|
|
6398
|
+
:open-trigger="props.config.openTrigger"
|
|
6368
6399
|
:show-crumbs="navView !== 'home' /* Home has no address: its cards come
|
|
6369
6400
|
from every folder in every storage, so a trail would have to name
|
|
6370
6401
|
one */"
|
|
@@ -6843,6 +6874,7 @@ function closeRecoveryKey() {
|
|
|
6843
6874
|
:nav-offers-trash="navOffersTrash"
|
|
6844
6875
|
:view-mode="paneViewMode"
|
|
6845
6876
|
:view-modes="allowedViewModes"
|
|
6877
|
+
:open-trigger="props.config.openTrigger"
|
|
6846
6878
|
:clipped="clippedPaths"
|
|
6847
6879
|
:can-write="canWriteHere"
|
|
6848
6880
|
:can-paste="!!clipboard.mode"
|
|
@@ -217,12 +217,18 @@ const props = withDefaults(
|
|
|
217
217
|
* encrypted lock screen) — those are WINDOW states with no listing
|
|
218
218
|
* behind them, so the pane's own state chain must not also run. */
|
|
219
219
|
bodyOverride?: boolean;
|
|
220
|
+
|
|
221
|
+
/** Mouse open gesture — 'double' (single selects, double opens) or
|
|
222
|
+
* 'single' (first click opens). Touch always opens on tap; the checkbox
|
|
223
|
+
* always selects. See onViewClick. */
|
|
224
|
+
openTrigger?: 'single' | 'double';
|
|
220
225
|
}>(),
|
|
221
226
|
{
|
|
222
227
|
showCrumbs: true,
|
|
223
228
|
showFilterBar: true,
|
|
224
229
|
showViewSwitcher: true,
|
|
225
230
|
trashVisible: true,
|
|
231
|
+
openTrigger: 'double',
|
|
226
232
|
},
|
|
227
233
|
);
|
|
228
234
|
|
|
@@ -458,22 +464,31 @@ function withinOpenGuard(): boolean {
|
|
|
458
464
|
|
|
459
465
|
function onViewClick(n: FileNode, mod: ClickMod) {
|
|
460
466
|
if (withinOpenGuard()) return;
|
|
461
|
-
/*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
* on the item opens it, Ctrl/Shift held or not, selection or not. See
|
|
465
|
-
* composables/useRowTouch. */
|
|
467
|
+
/* The checkbox is the one click that always reaches the selection — issue
|
|
468
|
+
* #26's rule, kept on every device and in either open mode. A shift-tick
|
|
469
|
+
* still extends the range; a plain tick toggles (ctrl: true). */
|
|
466
470
|
if (mod.check) {
|
|
467
471
|
emit('activate');
|
|
468
472
|
emit('click-row', n, { ctrl: true, shift: mod.shift });
|
|
469
473
|
return;
|
|
470
474
|
}
|
|
471
|
-
|
|
472
|
-
|
|
475
|
+
/* A finger tap always OPENS (the mobile convention — there is no
|
|
476
|
+
* hover-to-select on a touchscreen). For a mouse the host's `openTrigger`
|
|
477
|
+
* decides: 'single' opens on this click; 'double' (default) makes a single
|
|
478
|
+
* click SELECT and leaves opening to onViewDbl. issue #26's "any click
|
|
479
|
+
* opens" is `openTrigger: 'single'`. */
|
|
480
|
+
if (mod.touch || props.openTrigger === 'single') {
|
|
481
|
+
openedAt = Date.now();
|
|
482
|
+
onRowOpen(n);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
emit('activate');
|
|
486
|
+
emit('click-row', n, { ctrl: mod.ctrl, shift: mod.shift });
|
|
473
487
|
}
|
|
474
488
|
|
|
475
489
|
function onViewDbl(n: FileNode) {
|
|
476
490
|
if (withinOpenGuard()) return;
|
|
491
|
+
openedAt = Date.now();
|
|
477
492
|
onRowOpen(n);
|
|
478
493
|
}
|
|
479
494
|
|
|
@@ -144,9 +144,12 @@ const storageTile = fileIconTile({ type: 'dir', mime_type: 'inode/storage' });
|
|
|
144
144
|
* with the wrong size line says something false.
|
|
145
145
|
*/
|
|
146
146
|
function storageCaption(s: HomeStorage): string {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
const base =
|
|
148
|
+
typeof s.usedBytes === 'number' && s.usedBytes >= 0
|
|
149
|
+
? t('drive.storage.used_unlimited', { used: formatSize(s.usedBytes) })
|
|
150
|
+
: t('conn.guide.storage');
|
|
151
|
+
// A read-only mount says so on its card too (issue #30).
|
|
152
|
+
return s.readOnly ? `${base} · ${t('sidenav.storage.readOnly')}` : base;
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
/**
|
|
@@ -242,6 +242,16 @@ const views = computed(() => {
|
|
|
242
242
|
|
|
243
243
|
const writable = computed(() => props.canWrite !== false);
|
|
244
244
|
|
|
245
|
+
/**
|
|
246
|
+
* The storage the listing is in is mounted read-only. Its "+ New" menu is not
|
|
247
|
+
* disabled but GONE: on a read-only mount there is no folder where any of
|
|
248
|
+
* its rows could ever work, and a disabled menu invites the question a menu
|
|
249
|
+
* cannot answer. The row itself says why — see the tag on it (issue #30).
|
|
250
|
+
*/
|
|
251
|
+
const activeReadOnly = computed(
|
|
252
|
+
() => !!props.activeStorage && props.storages.some((s) => s.name === props.activeStorage && s.readOnly === true),
|
|
253
|
+
);
|
|
254
|
+
|
|
245
255
|
/**
|
|
246
256
|
* Which row reads as the one you are standing on.
|
|
247
257
|
*
|
|
@@ -426,6 +436,7 @@ const toggleLabel = computed(() => t('sidenav.close'));
|
|
|
426
436
|
stand here behind `v-if="!newMenu"` (Upload + New folder) is gone:
|
|
427
437
|
both verbs are the first two rows of this menu. -->
|
|
428
438
|
<button
|
|
439
|
+
v-if="!activeReadOnly"
|
|
429
440
|
ref="newBtnEl"
|
|
430
441
|
type="button"
|
|
431
442
|
class="fe-sidenav__new"
|
|
@@ -693,6 +704,17 @@ const toggleLabel = computed(() => t('sidenav.close'));
|
|
|
693
704
|
:aria-label="t('sidenav.storage.shared')"
|
|
694
705
|
>{{ t('sidenav.storage.shared') }}</span
|
|
695
706
|
>
|
|
707
|
+
<!-- A read-only mount says so on the row, where the user looks
|
|
708
|
+
for it. The admin list had an "RO" badge; the people it
|
|
709
|
+
applied to never saw one (issue #30). -->
|
|
710
|
+
<span
|
|
711
|
+
v-if="showLabels && s.readOnly"
|
|
712
|
+
class="fe-sidenav__tag"
|
|
713
|
+
role="img"
|
|
714
|
+
data-testid="sidenav-storage-readonly"
|
|
715
|
+
:aria-label="t('sidenav.storage.readOnly')"
|
|
716
|
+
>{{ t('sidenav.storage.readOnly') }}</span
|
|
717
|
+
>
|
|
696
718
|
</button>
|
|
697
719
|
</li>
|
|
698
720
|
</ul>
|
package/src/locales/en.ts
CHANGED
|
@@ -1047,6 +1047,7 @@ export const en: Record<string, string> = {
|
|
|
1047
1047
|
// "expand" would promise a width change to something that has no width.
|
|
1048
1048
|
'sidenav.open': 'Open navigation',
|
|
1049
1049
|
'sidenav.storage.shared': 'Shared',
|
|
1050
|
+
'sidenav.storage.readOnly': 'Read-only',
|
|
1050
1051
|
// ⚠ `toolbar.nav` is GONE, and nothing replaced it. It was the one label on
|
|
1051
1052
|
// the panel's collapse control, first as the noun "Navigation" — which names
|
|
1052
1053
|
// the panel and never says the button does anything to it, so the owner
|
package/src/locales/tr.ts
CHANGED
|
@@ -1047,6 +1047,7 @@ export const tr: Record<string, string> = {
|
|
|
1047
1047
|
// genişliği büyütmeyi vaat ederdi.
|
|
1048
1048
|
'sidenav.open': 'Gezintiyi aç',
|
|
1049
1049
|
'sidenav.storage.shared': 'Paylaşılan',
|
|
1050
|
+
'sidenav.storage.readOnly': 'Salt okunur',
|
|
1050
1051
|
// ⚠ `toolbar.nav` KALDIRILDI, yerine bir şey konmadı — bkz. en.ts'teki not.
|
|
1051
1052
|
// Paneli daraltan tek denetimin etiketiydi; önce "Gezinti" (panelin adı,
|
|
1052
1053
|
// düğmenin ne yaptığını söylemiyor), sonra "Gezintiyi göster/gizle" (sabit
|
|
@@ -454,6 +454,38 @@ export interface ExplorerConfig {
|
|
|
454
454
|
callerKind?: 'user' | 'app';
|
|
455
455
|
|
|
456
456
|
|
|
457
|
+
/**
|
|
458
|
+
* How a mouse opens an item.
|
|
459
|
+
*
|
|
460
|
+
* 'double' (default) — a single click SELECTS, a double click OPENS
|
|
461
|
+
* (classic desktop file-manager). This is a
|
|
462
|
+
* per-viewer preference the host may expose as a
|
|
463
|
+
* setting.
|
|
464
|
+
* 'single' — a single click OPENS (the item is opened on the
|
|
465
|
+
* first click; the checkbox is still the one click
|
|
466
|
+
* that selects).
|
|
467
|
+
*
|
|
468
|
+
* ⚠ TOUCH is not governed by this: a finger tap always opens (the mobile
|
|
469
|
+
* convention, and there is no hover-to-select on a touchscreen). The
|
|
470
|
+
* checkbox always selects, on every device and in either mode. See
|
|
471
|
+
* FilePane.onViewClick + composables/useRowTouch.
|
|
472
|
+
*/
|
|
473
|
+
openTrigger?: 'single' | 'double';
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Hand the OPEN of a file to the host instead of opening the in-page
|
|
477
|
+
* preview/editor overlay. When true, opening a file emits `file-opened`
|
|
478
|
+
* (path + basename) and the explorer does NOT mount its own PreviewModal —
|
|
479
|
+
* the host decides what to do (the desktop app opens the file in its own
|
|
480
|
+
* window per document). Directories still navigate inline, and Space
|
|
481
|
+
* quick-look still peeks in-page. Default false (web/embeds preview inline).
|
|
482
|
+
*
|
|
483
|
+
* ⚠ Ignored inside an unlocked E2E-encrypted folder: the host window would
|
|
484
|
+
* fetch raw server bytes (ciphertext), so those keep the in-page decrypted
|
|
485
|
+
* preview.
|
|
486
|
+
*/
|
|
487
|
+
openInHost?: boolean;
|
|
488
|
+
|
|
457
489
|
/** Default view. */
|
|
458
490
|
viewMode?: 'list' | 'grid';
|
|
459
491
|
|