@brftech/filex-core 0.1.68 → 0.1.70

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 (27) hide show
  1. package/dist/{ArchiveViewer-D8xRrOPd.js → ArchiveViewer-Dt8KlYQy.js} +2 -2
  2. package/dist/{ArchiveViewer-D8xRrOPd.js.map → ArchiveViewer-Dt8KlYQy.js.map} +1 -1
  3. package/dist/{CsvViewer-gPow-K-G.js → CsvViewer-_E_K8vhx.js} +2 -2
  4. package/dist/{CsvViewer-gPow-K-G.js.map → CsvViewer-_E_K8vhx.js.map} +1 -1
  5. package/dist/{DrawioViewer-5Z-Sh7ab.js → DrawioViewer-BEP_mcp4.js} +2 -2
  6. package/dist/{DrawioViewer-5Z-Sh7ab.js.map → DrawioViewer-BEP_mcp4.js.map} +1 -1
  7. package/dist/{EpubViewer-Dpjxu635.js → EpubViewer--u3zyU0t.js} +2 -2
  8. package/dist/{EpubViewer-Dpjxu635.js.map → EpubViewer--u3zyU0t.js.map} +1 -1
  9. package/dist/{IpynbViewer-Ceqkmq9m.js → IpynbViewer-BYFy5ikz.js} +2 -2
  10. package/dist/{IpynbViewer-Ceqkmq9m.js.map → IpynbViewer-BYFy5ikz.js.map} +1 -1
  11. package/dist/{MermaidViewer-qmceupn1.js → MermaidViewer-C5Dc_ZRO.js} +2 -2
  12. package/dist/{MermaidViewer-qmceupn1.js.map → MermaidViewer-C5Dc_ZRO.js.map} +1 -1
  13. package/dist/{PsdViewer-kVwnCUUx.js → PsdViewer-CmiGJBrt.js} +2 -2
  14. package/dist/{PsdViewer-kVwnCUUx.js.map → PsdViewer-CmiGJBrt.js.map} +1 -1
  15. package/dist/{TiffViewer-Cd0YuEuL.js → TiffViewer-CRYsKUiy.js} +2 -2
  16. package/dist/{TiffViewer-Cd0YuEuL.js.map → TiffViewer-CRYsKUiy.js.map} +1 -1
  17. package/dist/{Viewer3D-b5tJm8li.js → Viewer3D-B_AkV4dl.js} +2 -2
  18. package/dist/{Viewer3D-b5tJm8li.js.map → Viewer3D-B_AkV4dl.js.map} +1 -1
  19. package/dist/filex-core.js +1 -1
  20. package/dist/filex-core.umd.cjs +32 -32
  21. package/dist/filex-core.umd.cjs.map +1 -1
  22. package/dist/{index-BkxD82VF.js → index-DzcShGVx.js} +628 -616
  23. package/dist/{index-BkxD82VF.js.map → index-DzcShGVx.js.map} +1 -1
  24. package/dist/index.d.ts +9 -5
  25. package/package.json +1 -1
  26. package/src/FileExplorer.vue +49 -25
  27. package/src/types/ExplorerConfig.ts +9 -5
package/dist/index.d.ts CHANGED
@@ -331,12 +331,16 @@ export declare interface ExplorerConfig {
331
331
  /**
332
332
  * Where to persist the current path so reload lands the user back
333
333
  * in the same folder.
334
- * 'hash' — URL hash. Default — works on plain web pages.
335
- * 'localStorage' — `brf-file-explorer:path` key. Best for SPAs that
336
- * already own the URL (Ionic / Vue Router / Next).
337
- * 'none' don't persist. Embedder controls path externally.
334
+ * 'hash' — URL hash. Default — works on plain web pages.
335
+ * 'localStorage' — `brf-file-explorer:path` key. Best for SPAs that
336
+ * already own the URL (Ionic / Vue Router / Next).
337
+ * 'hash+localStorage' both: the address bar always mirrors the
338
+ * current folder (copy-paste deep links), and
339
+ * localStorage remembers it for hash-less visits.
340
+ * Read priority: hash → `initialPath` → localStorage.
341
+ * 'none' — don't persist. Embedder controls path externally.
338
342
  */
339
- pathPersist?: 'hash' | 'localStorage' | 'none';
343
+ pathPersist?: 'hash' | 'localStorage' | 'hash+localStorage' | 'none';
340
344
  /**
341
345
  * Multi-storage root mode. When true, the explorer's "/" virtual
342
346
  * folder lists every entry in `storages` as a clickable directory.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brftech/filex-core",
3
- "version": "0.1.68",
3
+ "version": "0.1.70",
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",
@@ -600,44 +600,72 @@ watch(
600
600
  // ----------------------------------------------------------------
601
601
  const PATH_LS_KEY = 'brf-file-explorer:path';
602
602
 
603
- function persistMode(): 'hash' | 'localStorage' | 'none' {
603
+ function persistMode(): 'hash' | 'localStorage' | 'hash+localStorage' | 'none' {
604
604
  return props.config.pathPersist ?? 'hash';
605
605
  }
606
606
 
607
- function readPersistedPath(): string {
608
- if (typeof window === 'undefined') return '';
609
- const mode = persistMode();
610
- if (mode === 'none') return '';
611
- if (mode === 'localStorage') {
612
- try {
613
- return localStorage.getItem(PATH_LS_KEY) || '';
614
- } catch {
615
- return '';
616
- }
607
+ function hashPersistEnabled(): boolean {
608
+ const m = persistMode();
609
+ return m === 'hash' || m === 'hash+localStorage';
610
+ }
611
+
612
+ // A pasted/hand-edited hash can carry a stray `%` (a folder literally named
613
+ // "100%") that decodeURIComponent rejects — fall back to the raw text.
614
+ function safeDecode(s: string): string {
615
+ try {
616
+ return decodeURIComponent(s);
617
+ } catch {
618
+ return s;
619
+ }
620
+ }
621
+
622
+ function readLsPath(): string {
623
+ try {
624
+ return localStorage.getItem(PATH_LS_KEY) || '';
625
+ } catch {
626
+ return '';
617
627
  }
628
+ }
629
+
630
+ function readHashPath(): string {
618
631
  const h = window.location.hash || '';
619
632
  if (!h.startsWith('#')) return '';
620
- return decodeURIComponent(h.slice(1)).replace(/^\/+|\/+$/g, '');
633
+ return safeDecode(h.slice(1)).replace(/^\/+|\/+$/g, '');
621
634
  }
622
635
 
623
- let hashSyncSuppressed = false;
636
+ function readPersistedPath(): string {
637
+ if (typeof window === 'undefined') return '';
638
+ const mode = persistMode();
639
+ if (mode === 'none') return '';
640
+ if (mode === 'localStorage') return readLsPath();
641
+ const fromHash = readHashPath();
642
+ if (fromHash || mode === 'hash') return fromHash;
643
+ // hash+localStorage with an empty hash: an explicit start path
644
+ // (?storage= deep link / rootPath floor) outranks the remembered folder.
645
+ if (initialFloorPath) return '';
646
+ return readLsPath();
647
+ }
624
648
 
625
649
  function writePersistedPath(path: string) {
626
650
  if (typeof window === 'undefined') return;
627
651
  const mode = persistMode();
628
652
  if (mode === 'none') return;
629
- if (mode === 'localStorage') {
653
+ if (mode === 'localStorage' || mode === 'hash+localStorage') {
630
654
  try {
631
655
  if (path) localStorage.setItem(PATH_LS_KEY, path);
632
656
  else localStorage.removeItem(PATH_LS_KEY);
633
657
  } catch {
634
658
  /* private mode / quota */
635
659
  }
636
- return;
660
+ if (mode === 'localStorage') return;
637
661
  }
638
- const target = path ? `#${path}` : '';
639
- if (window.location.hash === target) return;
640
- hashSyncSuppressed = true;
662
+ // Encode per segment so folder names with `%`/`#`/`?` survive the URL
663
+ // round-trip while `/` separators stay readable.
664
+ const encoded = path ? path.split('/').map(encodeURIComponent).join('/') : '';
665
+ const target = encoded ? `#${encoded}` : '';
666
+ if ((window.location.hash || '') === target) return;
667
+ // replaceState never fires `hashchange`, so onHashChange only ever sees
668
+ // genuine external edits (paste, back/forward) — no self-echo to suppress.
641
669
  history.replaceState(
642
670
  null,
643
671
  '',
@@ -646,12 +674,8 @@ function writePersistedPath(path: string) {
646
674
  }
647
675
 
648
676
  function onHashChange() {
649
- if (persistMode() !== 'hash') return;
650
- if (hashSyncSuppressed) {
651
- hashSyncSuppressed = false;
652
- return;
653
- }
654
- const p = readPersistedPath();
677
+ if (!hashPersistEnabled()) return;
678
+ const p = readHashPath();
655
679
  if (p && p !== currentPath.value) {
656
680
  void load(p);
657
681
  }
@@ -672,7 +696,7 @@ onMounted(async () => {
672
696
  // expose /api/files/manager/starred. Without this stars never light
673
697
  // up on first render even when the row IS starred server-side.
674
698
  void loadStarred();
675
- if (persistMode() === 'hash') {
699
+ if (hashPersistEnabled()) {
676
700
  window.addEventListener('hashchange', onHashChange);
677
701
  }
678
702
  if (api.endpoints.opsList) {
@@ -236,12 +236,16 @@ export interface ExplorerConfig {
236
236
  /**
237
237
  * Where to persist the current path so reload lands the user back
238
238
  * in the same folder.
239
- * 'hash' — URL hash. Default — works on plain web pages.
240
- * 'localStorage' — `brf-file-explorer:path` key. Best for SPAs that
241
- * already own the URL (Ionic / Vue Router / Next).
242
- * 'none' don't persist. Embedder controls path externally.
239
+ * 'hash' — URL hash. Default — works on plain web pages.
240
+ * 'localStorage' — `brf-file-explorer:path` key. Best for SPAs that
241
+ * already own the URL (Ionic / Vue Router / Next).
242
+ * 'hash+localStorage' both: the address bar always mirrors the
243
+ * current folder (copy-paste deep links), and
244
+ * localStorage remembers it for hash-less visits.
245
+ * Read priority: hash → `initialPath` → localStorage.
246
+ * 'none' — don't persist. Embedder controls path externally.
243
247
  */
244
- pathPersist?: 'hash' | 'localStorage' | 'none';
248
+ pathPersist?: 'hash' | 'localStorage' | 'hash+localStorage' | 'none';
245
249
 
246
250
  /**
247
251
  * Multi-storage root mode. When true, the explorer's "/" virtual