@brftech/filex-core 0.42.1 → 0.42.2
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-CLNevfPE.js → ArchiveViewer-BUQWLhwX.js} +2 -2
- package/dist/{ArchiveViewer-CLNevfPE.js.map → ArchiveViewer-BUQWLhwX.js.map} +1 -1
- package/dist/{CsvViewer-K8XA3z7N.js → CsvViewer-Bg2_aqzc.js} +2 -2
- package/dist/{CsvViewer-K8XA3z7N.js.map → CsvViewer-Bg2_aqzc.js.map} +1 -1
- package/dist/{DrawioViewer-BuhZOAx6.js → DrawioViewer-CC6rJXeP.js} +2 -2
- package/dist/{DrawioViewer-BuhZOAx6.js.map → DrawioViewer-CC6rJXeP.js.map} +1 -1
- package/dist/{EpubViewer-DS3mb3k8.js → EpubViewer-CAoTSXTO.js} +2 -2
- package/dist/{EpubViewer-DS3mb3k8.js.map → EpubViewer-CAoTSXTO.js.map} +1 -1
- package/dist/{IpynbViewer-BTk7p4Dn.js → IpynbViewer-D0GwGpnE.js} +2 -2
- package/dist/{IpynbViewer-BTk7p4Dn.js.map → IpynbViewer-D0GwGpnE.js.map} +1 -1
- package/dist/{MermaidViewer-DqucH_io.js → MermaidViewer-DGwAvRZg.js} +2 -2
- package/dist/{MermaidViewer-DqucH_io.js.map → MermaidViewer-DGwAvRZg.js.map} +1 -1
- package/dist/{PsdViewer-CwiVENmq.js → PsdViewer-DhGweA0m.js} +2 -2
- package/dist/{PsdViewer-CwiVENmq.js.map → PsdViewer-DhGweA0m.js.map} +1 -1
- package/dist/{TiffViewer-CpewSVmQ.js → TiffViewer-D0AurvAh.js} +2 -2
- package/dist/{TiffViewer-CpewSVmQ.js.map → TiffViewer-D0AurvAh.js.map} +1 -1
- package/dist/{Viewer3D-CIrOn63I.js → Viewer3D-CdZeK4j4.js} +2 -2
- package/dist/{Viewer3D-CIrOn63I.js.map → Viewer3D-CdZeK4j4.js.map} +1 -1
- package/dist/filex-core.js +1 -1
- package/dist/filex-core.umd.cjs +47 -47
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/{index-Dx0IbBcI.js → index-BArI-B1M.js} +9261 -9181
- package/dist/index-BArI-B1M.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +152 -90
- package/src/components/RecentlyOpened.vue +16 -2
- package/src/components/S3KeysPanel.vue +33 -27
- package/src/components/SSHKeysPanel.vue +29 -25
- package/src/components/TokensPanel.vue +26 -22
- package/src/composables/useScrolledX.ts +21 -0
- package/src/lib/nodeRow.ts +106 -0
- package/src/locales/en.ts +7 -2
- package/src/locales/tr.ts +3 -3
- package/src/styles/base.css +24 -0
- package/src/types/FileNode.ts +8 -0
- package/dist/index-Dx0IbBcI.js.map +0 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* "Is this box scrolled sideways at all?" — the one fact a pinned column
|
|
5
|
+
* needs in order to draw its left divider only while something is actually
|
|
6
|
+
* sliding underneath it. A permanent divider would claim the column is
|
|
7
|
+
* floating when it is simply the last cell of a table that fits.
|
|
8
|
+
*
|
|
9
|
+
* Bind `onScroll` to the scroll container and `scrolledX` to its
|
|
10
|
+
* `is-scrolled-x` class; the stylesheet does the rest (base.css,
|
|
11
|
+
* `.fe-s3keys__scroll.is-scrolled-x`).
|
|
12
|
+
*/
|
|
13
|
+
export function useScrolledX() {
|
|
14
|
+
const scrolledX = ref(false);
|
|
15
|
+
function onScroll(ev: Event) {
|
|
16
|
+
const el = ev.currentTarget as HTMLElement | null;
|
|
17
|
+
const on = (el?.scrollLeft ?? 0) > 0;
|
|
18
|
+
if (on !== scrolledX.value) scrolledX.value = on;
|
|
19
|
+
}
|
|
20
|
+
return { scrolledX, onScroll };
|
|
21
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nodeRow — the starred / recently-opened / tag endpoints answer with raw node
|
|
3
|
+
* rows (relative `path`, numeric `storage_id`, RFC3339 stamps), not the
|
|
4
|
+
* listing shape. This turns one row into the `FileNode` every view renders.
|
|
5
|
+
*
|
|
6
|
+
* Pure on purpose: the explorer wraps it with its own config, and the unit
|
|
7
|
+
* test pins the two facts the context menu depends on — `perm` and
|
|
8
|
+
* `read_only` — without mounting the component.
|
|
9
|
+
*/
|
|
10
|
+
import type { FileNode } from '../types';
|
|
11
|
+
|
|
12
|
+
/** What the explorer knows about the install, as far as a row needs it. */
|
|
13
|
+
export interface NodeRowContext {
|
|
14
|
+
/** `config.storages` — names, and the host's read-only flag per storage. */
|
|
15
|
+
storages: ReadonlyArray<{ name: string; readOnly?: boolean }>;
|
|
16
|
+
/** Multi-storage root mode: a row that cannot be addressed by storage name
|
|
17
|
+
* is dropped rather than guessed into somebody else's drive. */
|
|
18
|
+
multiStorageRoot: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const PERM_LEVELS = new Set(['none', 'viewer', 'editor', 'owner']);
|
|
22
|
+
|
|
23
|
+
/** tablo:t1 — an RFC3339 stamp from a node row as unix ms, or undefined. A
|
|
24
|
+
* string we cannot parse is left undefined rather than turned into `NaN`,
|
|
25
|
+
* which would print as "Invalid Date" and sort unpredictably. */
|
|
26
|
+
export function rowMillis(v: unknown): number | undefined {
|
|
27
|
+
if (typeof v !== 'string' || !v) return undefined;
|
|
28
|
+
const ms = Date.parse(v);
|
|
29
|
+
return Number.isFinite(ms) ? ms : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The storage NAME is what a qualified path needs, and a node row does not
|
|
34
|
+
* carry the id-to-name mapping. The backend fills `storage` for exactly this
|
|
35
|
+
* (handlers/meta.go, `(*Meta).rows`); against an older server the only safe
|
|
36
|
+
* fallback is the single-storage case — guessing in a multi-storage install
|
|
37
|
+
* sends the user to a path in somebody else's drive.
|
|
38
|
+
*/
|
|
39
|
+
export function nodeRowToFileNode(
|
|
40
|
+
row: Record<string, unknown>,
|
|
41
|
+
ctx: NodeRowContext,
|
|
42
|
+
): FileNode | null {
|
|
43
|
+
const rel = String(row?.path ?? '').replace(/^\/+/, '');
|
|
44
|
+
if (!rel) return null;
|
|
45
|
+
const configured = ctx.storages ?? [];
|
|
46
|
+
const storageName =
|
|
47
|
+
typeof row.storage === 'string' && row.storage
|
|
48
|
+
? row.storage
|
|
49
|
+
: configured.length === 1
|
|
50
|
+
? configured[0].name
|
|
51
|
+
: '';
|
|
52
|
+
if (ctx.multiStorageRoot && !storageName) return null;
|
|
53
|
+
const name = String(row.name ?? rel.split('/').pop() ?? '');
|
|
54
|
+
const isDir = row.type === 'dir';
|
|
55
|
+
const size = typeof row.size === 'number' ? row.size : 0;
|
|
56
|
+
const id = typeof row.id === 'number' ? row.id : undefined;
|
|
57
|
+
/* ⚠⚠ THE CONTEXT MENU MUST BE THE SAME EVERYWHERE (owner, 2026-09-19). The
|
|
58
|
+
menu gates Rename / Delete / Move to / Share on the row's own `perm` and
|
|
59
|
+
on the storage's `read_only` — a folder listing carries both, and until
|
|
60
|
+
these two lines a Recent / Starred / tag / Home row carried neither, so
|
|
61
|
+
the menu fell back to the folder opened LAST: nothing on the landing page
|
|
62
|
+
(every write verb gone), or a stale writable folder (every write verb
|
|
63
|
+
offered on a read-only mount). The row says for itself what may be done
|
|
64
|
+
to it, because in these views there is no folder to ask.
|
|
65
|
+
`read_only` prefers the server's word and falls back to the host's own
|
|
66
|
+
storage list for a server that predates the field. */
|
|
67
|
+
const perm =
|
|
68
|
+
typeof row.perm === 'string' && PERM_LEVELS.has(row.perm)
|
|
69
|
+
? (row.perm as FileNode['perm'])
|
|
70
|
+
: undefined;
|
|
71
|
+
const hostReadOnly = configured.find((s) => s.name === storageName)?.readOnly === true;
|
|
72
|
+
const readOnly = typeof row.read_only === 'boolean' ? row.read_only : hostReadOnly;
|
|
73
|
+
return {
|
|
74
|
+
type: isDir ? 'dir' : 'file',
|
|
75
|
+
id,
|
|
76
|
+
/* tablo:t1 — ⚠⚠ THE DATE. A node row carries `backend_mtime` (what the
|
|
77
|
+
storage says) and `db_mtime` (what our last scan recorded) as RFC3339
|
|
78
|
+
strings; `FileNode.last_modified` is unix MILLISECONDS. Nothing mapped
|
|
79
|
+
between the two, so every row from Recent, Starred and a tag view
|
|
80
|
+
arrived with no date at all — measured on Recent: eleven rows, eleven em
|
|
81
|
+
dashes in the Modified column, and a Modified column header you could
|
|
82
|
+
click that then sorted nothing. It also made the date grouping this view
|
|
83
|
+
is supposed to show impossible, because every row fell in the "No date"
|
|
84
|
+
bucket. Storage first: `backend_mtime` is the file's own truth and
|
|
85
|
+
`db_mtime` only says when we last looked at it. */
|
|
86
|
+
last_modified: rowMillis(row.backend_mtime) ?? rowMillis(row.db_mtime),
|
|
87
|
+
path: storageName ? `${storageName}://${rel}` : rel,
|
|
88
|
+
basename: name,
|
|
89
|
+
extension: isDir
|
|
90
|
+
? ''
|
|
91
|
+
: name.includes('.')
|
|
92
|
+
? (name.split('.').pop() || '').toLowerCase()
|
|
93
|
+
: '',
|
|
94
|
+
storage: storageName,
|
|
95
|
+
visibility: 'private',
|
|
96
|
+
size,
|
|
97
|
+
file_size: size,
|
|
98
|
+
mime_type: typeof row.mime === 'string' ? row.mime : '',
|
|
99
|
+
...(perm ? { perm } : {}),
|
|
100
|
+
read_only: readOnly,
|
|
101
|
+
// Keyed by node id. A file with no rendered thumbnail 404s here and the
|
|
102
|
+
// view falls back to its icon — the contract the ordinary listing has too.
|
|
103
|
+
thumb_url: !isDir && id !== undefined ? `/api/files/thumb/${id}` : undefined,
|
|
104
|
+
extra_metadata: {},
|
|
105
|
+
} as unknown as FileNode;
|
|
106
|
+
}
|
package/src/locales/en.ts
CHANGED
|
@@ -983,7 +983,7 @@ export const en: Record<string, string> = {
|
|
|
983
983
|
'storages.fields.share': 'Share',
|
|
984
984
|
'storages.fields.domain': 'Domain / workgroup',
|
|
985
985
|
'storages.fields.dialTimeout': 'Connect timeout (seconds)',
|
|
986
|
-
'storages.fieldHelp.disablePresign': 'Turn
|
|
986
|
+
'storages.fieldHelp.disablePresign': 'On by default: uploads and the downloads behind public share links stream through filex, so the bucket endpoint never has to be reachable from a browser (a LAN-only MinIO just works). Turn it off only when the endpoint is reachable from your users\' browsers (AWS, a public MinIO) and you want them to talk to the bucket directly — faster for very large files, and the store must accept SDK-signed URLs (Ceph RGW / some Hetzner setups answer SignatureDoesNotMatch).',
|
|
987
987
|
'storages.fieldHelp.endpoint': 'Leave empty for AWS S3. Any S3-compatible store needs its endpoint.',
|
|
988
988
|
'storages.fieldHelp.hostKey': 'A single public key in authorized_keys / known_hosts line form.',
|
|
989
989
|
'storages.fieldHelp.insecureSkipHostKey': 'Accepts any host key. Only for throwaway hosts.',
|
|
@@ -1122,8 +1122,13 @@ export const en: Record<string, string> = {
|
|
|
1122
1122
|
'newdoc.create': 'Create',
|
|
1123
1123
|
'newdoc.creating': 'Creating…',
|
|
1124
1124
|
'newdoc.cancel': 'Cancel',
|
|
1125
|
+
// ⚠ The top search field searches the WHOLE storage (and every storage at
|
|
1126
|
+
// the root) — never only the open folder. The scope named here is therefore
|
|
1127
|
+
// the storage, not the folder; "Filter in this folder…" (FilterBar) is the
|
|
1128
|
+
// folder-scoped one. It used to say "Search in Photos" for a folder called
|
|
1129
|
+
// Photos, which was a lie (2026-09-19).
|
|
1125
1130
|
'drive.search.placeholder': 'Search in {scope}',
|
|
1126
|
-
'drive.search.placeholder_all': 'Search
|
|
1131
|
+
'drive.search.placeholder_all': 'Search all storages',
|
|
1127
1132
|
'drive.search.hint_title': 'Search everywhere and run commands ({combo})',
|
|
1128
1133
|
'drive.storage.used': '{used} of {total} used',
|
|
1129
1134
|
'drive.storage.used_unlimited': '{used} used',
|
package/src/locales/tr.ts
CHANGED
|
@@ -983,7 +983,7 @@ export const tr: Record<string, string> = {
|
|
|
983
983
|
'storages.fields.share': 'Paylaşım',
|
|
984
984
|
'storages.fields.domain': 'Etki alanı / çalışma grubu',
|
|
985
985
|
'storages.fields.dialTimeout': 'Bağlanma zaman aşımı (saniye)',
|
|
986
|
-
'storages.fieldHelp.disablePresign': '
|
|
986
|
+
'storages.fieldHelp.disablePresign': 'Varsayılan olarak açık: yüklemeler ve herkese açık paylaşım bağlantılarındaki indirmeler filex üzerinden akar; kova endpoint\'inin tarayıcıdan erişilebilir olması gerekmez (yalnız yerel ağdaki MinIO doğrudan çalışır). Yalnızca endpoint kullanıcıların tarayıcısından erişilebiliyorsa (AWS, herkese açık MinIO) ve tarayıcının kovayla doğrudan konuşmasını istiyorsanız kapatın — çok büyük dosyalarda daha hızlıdır, ama depo SDK\'nın imzaladığı URL\'leri kabul etmelidir (Ceph RGW / bazı Hetzner kurulumları SignatureDoesNotMatch döner).',
|
|
987
987
|
'storages.fieldHelp.endpoint': 'AWS S3 için boş bırakın. S3 uyumlu diğer tüm depolar kendi endpoint\'ini ister.',
|
|
988
988
|
'storages.fieldHelp.hostKey': 'authorized_keys / known_hosts satır biçiminde tek bir açık anahtar.',
|
|
989
989
|
'storages.fieldHelp.insecureSkipHostKey': 'Her sunucu anahtarını kabul eder. Yalnızca geçici sunucular için.',
|
|
@@ -1120,8 +1120,8 @@ export const tr: Record<string, string> = {
|
|
|
1120
1120
|
'newdoc.create': 'Oluştur',
|
|
1121
1121
|
'newdoc.creating': 'Oluşturuluyor…',
|
|
1122
1122
|
'newdoc.cancel': 'Vazgeç',
|
|
1123
|
-
'drive.search.placeholder': '{scope}
|
|
1124
|
-
'drive.search.placeholder_all': '
|
|
1123
|
+
'drive.search.placeholder': '{scope} deposunda ara',
|
|
1124
|
+
'drive.search.placeholder_all': 'Tüm depolarda ara',
|
|
1125
1125
|
'drive.search.hint_title': 'Her yerde ara ve komut çalıştır ({combo})',
|
|
1126
1126
|
'drive.storage.used': '{total} alanın {used} kadarı dolu',
|
|
1127
1127
|
'drive.storage.used_unlimited': '{used} kullanılıyor',
|
package/src/styles/base.css
CHANGED
|
@@ -10449,6 +10449,30 @@ filex-explorer {
|
|
|
10449
10449
|
box-shadow: -1px 0 0 0 var(--fe-border);
|
|
10450
10450
|
}
|
|
10451
10451
|
|
|
10452
|
+
/* The key/token tables in the connection panels (S3KeysPanel, SSHKeysPanel
|
|
10453
|
+
and TokensPanel share `.fe-s3keys__*`) follow the same model as the list
|
|
10454
|
+
view above: the table is as wide as its content and the panel scrolls
|
|
10455
|
+
sideways, the actions cell rides the right edge over the columns sliding
|
|
10456
|
+
under it, carrying the panel's own ground (`--fe-bg-elev`, what
|
|
10457
|
+
`.fe-s3keys` paints), and its divider appears only while something is
|
|
10458
|
+
scrolled underneath (`useScrolledX` flips the class). One rule here rather
|
|
10459
|
+
than three copies in the panels, so they cannot drift apart. */
|
|
10460
|
+
.fe-s3keys__scroll {
|
|
10461
|
+
overflow-x: auto;
|
|
10462
|
+
}
|
|
10463
|
+
.fe-s3keys__scroll .fe-s3keys__table {
|
|
10464
|
+
min-width: max-content;
|
|
10465
|
+
}
|
|
10466
|
+
.fe-s3keys__table .fe-s3keys__actions {
|
|
10467
|
+
position: sticky;
|
|
10468
|
+
right: 0;
|
|
10469
|
+
z-index: 1;
|
|
10470
|
+
background: var(--fe-bg-elev);
|
|
10471
|
+
}
|
|
10472
|
+
.fe-s3keys__scroll.is-scrolled-x .fe-s3keys__actions {
|
|
10473
|
+
box-shadow: -1px 0 0 0 var(--fe-border);
|
|
10474
|
+
}
|
|
10475
|
+
|
|
10452
10476
|
/* The date headings travel with the horizontal scroll rather than sliding off
|
|
10453
10477
|
the left edge — they are `max-content` wide so sticky has somewhere to move
|
|
10454
10478
|
them to (a full-width block has none). */
|
package/src/types/FileNode.ts
CHANGED
|
@@ -42,6 +42,14 @@ export interface FileNode {
|
|
|
42
42
|
* projectFileNodes emits it when a storage has RBAC on). '' / undefined =
|
|
43
43
|
* ACL not enforced. Used to gate edit/manage affordances client-side. */
|
|
44
44
|
perm?: 'none' | 'viewer' | 'editor' | 'owner';
|
|
45
|
+
/** The row sits on a read-only storage. Carried by the rows that come from
|
|
46
|
+
* OUTSIDE a folder listing — Recent, Starred, a tag view, the Home cards,
|
|
47
|
+
* the Recently-opened tray (`handlers/meta.go`) — because there the
|
|
48
|
+
* listing-level `read_only` has no folder to describe. A folder listing's
|
|
49
|
+
* rows leave it unset; `dirReadOnly` answers for the whole folder. Folded
|
|
50
|
+
* into the write gate with `perm`, so the context menu is the same in
|
|
51
|
+
* every view. */
|
|
52
|
+
read_only?: boolean;
|
|
45
53
|
/* wiring:e2 — dir rows: true when the folder is E2E-encrypted (carries a
|
|
46
54
|
* `.filex-e2e.json` marker). Drives the 🔒 badge in the listings. */
|
|
47
55
|
e2e?: boolean;
|