@brftech/filex-core 0.9.0 → 0.10.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.
Files changed (32) hide show
  1. package/dist/{ArchiveViewer-DkspPMSy.js → ArchiveViewer-ChemVSr_.js} +2 -2
  2. package/dist/{ArchiveViewer-DkspPMSy.js.map → ArchiveViewer-ChemVSr_.js.map} +1 -1
  3. package/dist/{CsvViewer-D0UCc1Kh.js → CsvViewer-BPLL4a6K.js} +2 -2
  4. package/dist/{CsvViewer-D0UCc1Kh.js.map → CsvViewer-BPLL4a6K.js.map} +1 -1
  5. package/dist/{DrawioViewer-CHjj_lou.js → DrawioViewer-HUDLaBMb.js} +2 -2
  6. package/dist/{DrawioViewer-CHjj_lou.js.map → DrawioViewer-HUDLaBMb.js.map} +1 -1
  7. package/dist/{EpubViewer-BOAa22iy.js → EpubViewer-Dlb9JwZx.js} +2 -2
  8. package/dist/{EpubViewer-BOAa22iy.js.map → EpubViewer-Dlb9JwZx.js.map} +1 -1
  9. package/dist/{IpynbViewer-BlBa-Q-t.js → IpynbViewer-omprV92r.js} +2 -2
  10. package/dist/{IpynbViewer-BlBa-Q-t.js.map → IpynbViewer-omprV92r.js.map} +1 -1
  11. package/dist/{MermaidViewer-CvpM7z1N.js → MermaidViewer-DwgszRcQ.js} +2 -2
  12. package/dist/{MermaidViewer-CvpM7z1N.js.map → MermaidViewer-DwgszRcQ.js.map} +1 -1
  13. package/dist/{PsdViewer-CMzqfXHr.js → PsdViewer-DIHfkYmM.js} +2 -2
  14. package/dist/{PsdViewer-CMzqfXHr.js.map → PsdViewer-DIHfkYmM.js.map} +1 -1
  15. package/dist/{TiffViewer-Dgg2FGW6.js → TiffViewer-D5YH_ISS.js} +2 -2
  16. package/dist/{TiffViewer-Dgg2FGW6.js.map → TiffViewer-D5YH_ISS.js.map} +1 -1
  17. package/dist/{Viewer3D-kixldPCf.js → Viewer3D-Qq7TBKJ_.js} +2 -2
  18. package/dist/{Viewer3D-kixldPCf.js.map → Viewer3D-Qq7TBKJ_.js.map} +1 -1
  19. package/dist/filex-core.js +1 -1
  20. package/dist/filex-core.umd.cjs +38 -38
  21. package/dist/filex-core.umd.cjs.map +1 -1
  22. package/dist/{index-CR9pfSDq.js → index-BekR3mux.js} +4825 -4769
  23. package/dist/index-BekR3mux.js.map +1 -0
  24. package/dist/index.d.ts +1 -0
  25. package/package.json +1 -1
  26. package/src/FileExplorer.vue +49 -3
  27. package/src/components/SecondaryPane.vue +2 -2
  28. package/src/composables/useKeyboardShortcuts.ts +3 -0
  29. package/src/lib/listing.ts +48 -0
  30. package/src/locales/en.ts +3 -0
  31. package/src/locales/tr.ts +3 -0
  32. package/dist/index-CR9pfSDq.js.map +0 -1
package/dist/index.d.ts CHANGED
@@ -1472,6 +1472,7 @@ export declare interface ShortcutHandlers {
1472
1472
  onGoUp?: () => void;
1473
1473
  onShowHelp?: () => void;
1474
1474
  onToggleInspector?: () => void;
1475
+ onToggleHidden?: () => void;
1475
1476
  onQuickLook?: () => void;
1476
1477
  onTabNew?: () => void;
1477
1478
  onTabClose?: () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brftech/filex-core",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "filex core — Vue 3 source of truth for the filex file manager (FileExplorer SFC + composables + types)",
5
5
  "type": "module",
6
6
  "main": "./dist/filex-core.umd.cjs",
@@ -90,7 +90,9 @@ import {
90
90
  internal-entry filter + virtual `.trash` row must be identical in both
91
91
  panes or split view shows mismatched rows). */
92
92
  import {
93
- filterInternalEntries,
93
+ filterListing,
94
+ showHiddenFiles,
95
+ setShowHiddenFiles,
94
96
  injectTrashRow,
95
97
  hydrateTrashRow as hydrateTrashRowShared,
96
98
  } from './lib/listing';
@@ -274,12 +276,26 @@ const trashMode = ref(false);
274
276
  const trashOrigin = ref<string>('');
275
277
  const trashActive = computed(() => trashMode.value);
276
278
 
279
+ // When the caller can see exactly ONE storage, the multi-storage root is a
280
+ // one-row list that carries no information — the user clicks through it every
281
+ // single time. Treat that storage as the floor instead: open it directly and
282
+ // stop offering an "up" that only leads back to the one row.
283
+ //
284
+ // Empty string means "not in that situation" (single-storage mode, or more
285
+ // than one storage visible), which leaves every existing path untouched.
286
+ const soleStorageName = computed(() => {
287
+ if (!multiStorageRoot.value) return '';
288
+ const list = props.config.storages ?? [];
289
+ return list.length === 1 ? list[0].name : '';
290
+ });
291
+
277
292
  // canGoUp/goUp — toolbar's "↑ Up one level" button. In single-storage
278
293
  // mode "" means the storage root; in multi-storage mode "" means
279
294
  // the global root (storage list). Both → no parent → button hidden.
280
295
  const canGoUp = computed(() => {
281
296
  const p = (currentPath.value ?? '').replace(/^\/+|\/+$/g, '');
282
297
  if (rootFloor && p === rootFloor) return false; // at the confined floor — nowhere above
298
+ if (soleStorageName.value && p === soleStorageName.value) return false;
283
299
  return p.length > 0;
284
300
  });
285
301
 
@@ -300,6 +316,8 @@ function goUp() {
300
316
  }
301
317
  const cur = (currentPath.value ?? '').replace(/^\/+|\/+$/g, '');
302
318
  if (!cur || cur === rootFloor) return;
319
+ // The button is hidden here, but Alt+↑ / Backspace still route through.
320
+ if (soleStorageName.value && cur === soleStorageName.value) return;
303
321
  const idx = cur.lastIndexOf('/');
304
322
  let parent = idx === -1 ? '' : cur.slice(0, idx);
305
323
  // Never step above the confined floor.
@@ -674,6 +692,16 @@ function virtualStorageRows(): FileNode[] {
674
692
  } as unknown as FileNode));
675
693
  }
676
694
 
695
+ // Flip dot-file visibility. Both panes filter at load time rather than in a
696
+ // computed, so the listings are re-fetched instead of re-filtered — that keeps
697
+ // selection and the operations that read `files` working on exactly what is
698
+ // on screen.
699
+ function toggleHiddenFiles() {
700
+ setShowHiddenFiles(!showHiddenFiles.value);
701
+ void load();
702
+ void splitPaneRef.value?.reload();
703
+ }
704
+
677
705
  async function load(path?: string) {
678
706
  loading.value = true;
679
707
  // Any normal navigation exits trash mode (the trash view is entered only
@@ -694,6 +722,10 @@ async function load(path?: string) {
694
722
  // Multi-storage virtual root — synthesize a list of storages
695
723
  // instead of calling the backend.
696
724
  if (multiStorageRoot.value && !virtualToWire(requested)) {
725
+ // One visible storage → skip the one-row list and open it. Recursion is
726
+ // bounded: the recursive call carries a non-empty path, so
727
+ // virtualToWire() resolves and this branch is not re-entered.
728
+ if (soleStorageName.value) return await load(soleStorageName.value);
697
729
  currentPath.value = '';
698
730
  adapter.value = '';
699
731
  dirname.value = '';
@@ -716,7 +748,7 @@ async function load(path?: string) {
716
748
  subtree; '' resets on every plain folder. Drives the lock screen. */
717
749
  e2eRoot.value = typeof resp.e2e_root === 'string' ? resp.e2e_root : '';
718
750
  /* /wiring:e2 */
719
- files.value = filterInternalEntries(resp.files);
751
+ files.value = filterListing(resp.files);
720
752
  // Inject virtual `.trash` entry at root only — shared helper so the
721
753
  // split-view secondary pane shows the exact same row (no row-offset).
722
754
  if (injectTrashRow(files.value, resp.adapter, resp.dirname, props.config.trashVisible !== false)) {
@@ -1147,6 +1179,7 @@ useKeyboardShortcuts(rootEl, {
1147
1179
  },
1148
1180
  /* /cila:c wiring */
1149
1181
  onQuickLook: () => quickLookToggle() /* wiring:c2 */,
1182
+ onToggleHidden: () => toggleHiddenFiles(),
1150
1183
  onToggleInspector: () => toggleInspector() /* koru:k1 */,
1151
1184
  /* wiring:d1 — sekme aksiyonları (registry: tab-new/close/next/prev) */
1152
1185
  onTabNew: () => newTabHere(),
@@ -1463,10 +1496,20 @@ const contextActions = computed<ContextAction[]>(() => {
1463
1496
  // Empty background right-click: folder-level actions only. Viewers (no edit
1464
1497
  // on this dir) get nothing here.
1465
1498
  if (!any) {
1466
- if (!permCanEdit(dirPerm.value)) return [];
1499
+ // Showing hidden files is a VIEW preference, so it stays available to
1500
+ // viewers too — the edit gate below only covers the mutating actions.
1501
+ const view: ContextAction[] = [
1502
+ {
1503
+ key: 'toggle-hidden',
1504
+ label: showHiddenFiles.value ? t('ctx.hide_hidden') : t('ctx.show_hidden'),
1505
+ icon: showHiddenFiles.value ? '🙈' : '👁',
1506
+ },
1507
+ ];
1508
+ if (!permCanEdit(dirPerm.value)) return view;
1467
1509
  return [
1468
1510
  { key: 'new-folder', label: t('toolbar.new_folder'), icon: '📁' },
1469
1511
  { key: 'paste', label: t('ctx.paste'), icon: '📋', disabled: !clipboard.value.mode },
1512
+ ...view,
1470
1513
  ];
1471
1514
  }
1472
1515
 
@@ -1633,6 +1676,9 @@ async function dispatchItemAction(key: string, targets: FileNode[]) {
1633
1676
  case 'restore':
1634
1677
  if (targets.length > 0) await restoreSelection(targets);
1635
1678
  break;
1679
+ case 'toggle-hidden':
1680
+ toggleHiddenFiles();
1681
+ break;
1636
1682
  case 'new-folder':
1637
1683
  mutationInPane.value = ctxMode.value === 'pane'; /* ui-fix */
1638
1684
  showNewFolder.value = true;
@@ -21,7 +21,7 @@ import type { FileApi } from '../composables/useFileApi';
21
21
  import type { FileNode, ViewMode } from '../types/FileNode';
22
22
  import type { LocaleCode } from '../types/ExplorerConfig';
23
23
  import { useLocale } from '../composables/useLocale';
24
- import { filterInternalEntries, injectTrashRow, hydrateTrashRow } from '../lib/listing';
24
+ import { filterListing, injectTrashRow, hydrateTrashRow } from '../lib/listing';
25
25
  import ListView from './ListView.vue';
26
26
  import GridView from './GridView.vue';
27
27
  import GalleryView from './GalleryView.vue';
@@ -110,7 +110,7 @@ async function loadPane(target?: string): Promise<void> {
110
110
  const resp = await props.api.index(props.qualify(requested));
111
111
  // Same internal-entry filter + virtual `.trash` row as the main panel
112
112
  // (shared helpers → both split panes list identical rows, no offset).
113
- files.value = filterInternalEntries(resp.files);
113
+ files.value = filterListing(resp.files);
114
114
  if (injectTrashRow(files.value, resp.adapter, resp.dirname, props.trashVisible !== false)) {
115
115
  void hydrateTrashRow(files.value, resp.adapter, props.api);
116
116
  }
@@ -37,6 +37,7 @@ export interface ShortcutHandlers {
37
37
  onGoUp?: () => void; // Alt+Up / Backspace (when nothing selected)
38
38
  onShowHelp?: () => void; // ? (Shift+/ on most layouts)
39
39
  onToggleInspector?: () => void; // i (koru:k1 details panel)
40
+ onToggleHidden?: () => void; // Ctrl+Shift+. — dot-file visibility
40
41
  onQuickLook?: () => void; // Space (wiring:c2 quick-look overlay)
41
42
  /* wiring:d1 — tab strip actions */
42
43
  onTabNew?: () => void; // Ctrl+T
@@ -85,6 +86,7 @@ export const SHORTCUT_ACTIONS: ShortcutActionDef[] = [
85
86
  { id: 'close', defaultCombo: 'Esc', customizable: false, prevent: false, inForms: true, labelKey: 'shortcuts.close', groupKey: 'shortcuts.group.nav' },
86
87
  { id: 'inspector', defaultCombo: 'I', labelKey: 'shortcuts.inspector', groupKey: 'shortcuts.group.nav' } /* koru:k1 */,
87
88
  { id: 'help', defaultCombo: '?', labelKey: 'shortcuts.help', groupKey: 'shortcuts.group.nav' },
89
+ { id: 'toggle-hidden', defaultCombo: 'Ctrl+Shift+.', labelKey: 'shortcuts.toggle_hidden', groupKey: 'shortcuts.group.nav' },
88
90
  // Selection
89
91
  { id: 'select-all', defaultCombo: 'Ctrl+A', labelKey: 'shortcuts.select_all', groupKey: 'shortcuts.group.selection' },
90
92
  // File operations
@@ -112,6 +114,7 @@ const HANDLER_KEY: Record<string, keyof ShortcutHandlers> = {
112
114
  quicklook: 'onQuickLook',
113
115
  close: 'onClose',
114
116
  inspector: 'onToggleInspector',
117
+ 'toggle-hidden': 'onToggleHidden',
115
118
  help: 'onShowHelp',
116
119
  'select-all': 'onSelectAll',
117
120
  rename: 'onRename',
@@ -6,6 +6,8 @@
6
6
  * must be identical in both, otherwise split view shows mismatched rows
7
7
  * (the trash row missing on one side → visible row-offset).
8
8
  */
9
+ import { ref } from 'vue';
10
+
9
11
  import type { FileNode } from '../types/FileNode';
10
12
  import { E2E_MARKER_NAME } from './e2ecrypto';
11
13
 
@@ -31,6 +33,52 @@ export function filterInternalEntries(files: FileNode[]): FileNode[] {
31
33
  });
32
34
  }
33
35
 
36
+ const SHOW_HIDDEN_LS_KEY = 'filex.showHidden';
37
+
38
+ function readShowHidden(): boolean {
39
+ try {
40
+ return localStorage.getItem(SHOW_HIDDEN_LS_KEY) === '1';
41
+ } catch {
42
+ return false; // private mode / no storage → hidden, the safe default
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Whether dot-prefixed entries are listed. Off by default, like every other
48
+ * file manager. Shared by both panes so split view can't disagree with itself.
49
+ */
50
+ export const showHiddenFiles = ref(readShowHidden());
51
+
52
+ export function setShowHiddenFiles(v: boolean): void {
53
+ showHiddenFiles.value = v;
54
+ try {
55
+ localStorage.setItem(SHOW_HIDDEN_LS_KEY, v ? '1' : '0');
56
+ } catch {
57
+ /* preference just won't persist */
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Hide dot-prefixed entries unless the user asked to see them.
63
+ *
64
+ * These are mostly not the user's files at all: a Mac writing over WebDAV
65
+ * leaves a `.DS_Store` in every folder it opens and an AppleDouble `._name`
66
+ * beside every file carrying extended attributes. Finder hides its own litter
67
+ * locally, so seeing it reappear in the web UI reads as corruption.
68
+ *
69
+ * A toggle rather than a blocklist, because they ARE real files: hiding them
70
+ * outright would leave the ones already uploaded unreachable and undeletable.
71
+ */
72
+ export function filterHiddenEntries(files: FileNode[], showHidden: boolean): FileNode[] {
73
+ if (showHidden) return files || [];
74
+ return (files || []).filter((f) => !(f.basename || '').startsWith('.'));
75
+ }
76
+
77
+ /** filterInternalEntries + filterHiddenEntries — what both panes render. */
78
+ export function filterListing(files: FileNode[]): FileNode[] {
79
+ return filterHiddenEntries(filterInternalEntries(files), showHiddenFiles.value);
80
+ }
81
+
34
82
  /** True when `dirname` (adapter-qualified) is the storage root, where the
35
83
  * virtual trash row belongs. */
36
84
  export function isStorageRootDir(dirname: string): boolean {
package/src/locales/en.ts CHANGED
@@ -21,6 +21,8 @@ export const en: Record<string, string> = {
21
21
  'ctx.move': 'Move',
22
22
  'ctx.copy': 'Copy',
23
23
  'ctx.cut': 'Cut',
24
+ 'ctx.show_hidden': 'Show hidden files',
25
+ 'ctx.hide_hidden': 'Hide hidden files',
24
26
  'ctx.paste': 'Paste',
25
27
  'ctx.info': 'Info',
26
28
  'ctx.duplicate': 'Duplicate',
@@ -163,6 +165,7 @@ export const en: Record<string, string> = {
163
165
  'shortcuts.go_up': 'Go up one level',
164
166
  'shortcuts.open': 'Open selected item',
165
167
  'shortcuts.close': 'Close / cancel',
168
+ 'shortcuts.toggle_hidden': 'Show/hide hidden files',
166
169
  'shortcuts.help': 'This help dialog',
167
170
  'shortcuts.select_all': 'Select all',
168
171
  'shortcuts.rename': 'Rename',
package/src/locales/tr.ts CHANGED
@@ -21,6 +21,8 @@ export const tr: Record<string, string> = {
21
21
  'ctx.move': 'Taşı',
22
22
  'ctx.copy': 'Kopyala',
23
23
  'ctx.cut': 'Kes',
24
+ 'ctx.show_hidden': 'Gizli dosyaları göster',
25
+ 'ctx.hide_hidden': 'Gizli dosyaları gizle',
24
26
  'ctx.paste': 'Yapıştır',
25
27
  'ctx.info': 'Bilgi',
26
28
  'ctx.duplicate': 'Kopyasını Oluştur',
@@ -163,6 +165,7 @@ export const tr: Record<string, string> = {
163
165
  'shortcuts.go_up': 'Üst klasöre çık',
164
166
  'shortcuts.open': 'Seçili öğeyi aç',
165
167
  'shortcuts.close': 'Kapat / vazgeç',
168
+ 'shortcuts.toggle_hidden': 'Gizli dosyaları göster/gizle',
166
169
  'shortcuts.help': 'Bu yardım penceresi',
167
170
  'shortcuts.select_all': 'Tümünü seç',
168
171
  'shortcuts.rename': 'Yeniden adlandır',