@brftech/filex-core 0.30.1 → 0.31.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/README.md +11 -0
- package/dist/filex-core.js +9718 -8611
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +95 -87
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index.d.ts +368 -64
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +599 -19
- package/src/components/Breadcrumb.vue +4 -2
- package/src/components/E2eRecoveryUnlockModal.vue +168 -0
- package/src/components/EncryptedFolderModal.vue +10 -0
- package/src/components/GalleryView.vue +37 -0
- package/src/components/GridView.vue +39 -0
- package/src/components/ListView.vue +1 -0
- package/src/components/RecoveryKeyModal.vue +133 -0
- package/src/components/SideNav.vue +168 -5
- package/src/components/StarButton.vue +27 -15
- package/src/composables/useFileApi.ts +38 -0
- package/src/composables/useKeyboardShortcuts.ts +7 -0
- package/src/index.ts +27 -1
- package/src/lib/e2ecrypto.ts +528 -70
- package/src/lib/listing.ts +56 -1
- package/src/lib/star.ts +42 -0
- package/src/lib/tags.ts +105 -0
- package/src/locales/en.ts +71 -2
- package/src/locales/tr.ts +71 -2
- package/src/styles/base.css +252 -0
- package/src/types/ExplorerConfig.ts +36 -0
- package/src/types/FileNode.ts +23 -0
package/src/styles/base.css
CHANGED
|
@@ -542,6 +542,11 @@ filex-explorer {
|
|
|
542
542
|
opacity: 0.7;
|
|
543
543
|
}
|
|
544
544
|
.fe-grid__thumb {
|
|
545
|
+
/* yildiz:s1 — positioning context for the star chip in the corner. The
|
|
546
|
+
gallery's thumb already had one; this one did not, so an absolutely
|
|
547
|
+
placed chip would have resolved against whatever ancestor happened to be
|
|
548
|
+
positioned (the grid, or the explorer root) and landed in the wrong tile. */
|
|
549
|
+
position: relative;
|
|
545
550
|
display: flex;
|
|
546
551
|
align-items: center;
|
|
547
552
|
justify-content: center;
|
|
@@ -3836,6 +3841,153 @@ filex-explorer {
|
|
|
3836
3841
|
.fe-e2e-optlink:hover {
|
|
3837
3842
|
text-decoration: underline;
|
|
3838
3843
|
}
|
|
3844
|
+
|
|
3845
|
+
/* --- wiring:e2 recovery ---------------------------------------------
|
|
3846
|
+
Unscoped and fe-prefixed on purpose: a scoped <style> in an SFC does not
|
|
3847
|
+
survive the web-component build, so every class a core component uses has
|
|
3848
|
+
to live here. */
|
|
3849
|
+
|
|
3850
|
+
/* The shown-once key. Monospace, big, wrapping — it exists to be read off
|
|
3851
|
+
the screen and typed somewhere else, so legibility beats compactness. */
|
|
3852
|
+
.fe-e2e-rk {
|
|
3853
|
+
display: flex;
|
|
3854
|
+
flex-direction: column;
|
|
3855
|
+
gap: 12px;
|
|
3856
|
+
}
|
|
3857
|
+
.fe-e2e-rk__lead {
|
|
3858
|
+
margin: 0;
|
|
3859
|
+
font-size: 13px;
|
|
3860
|
+
color: var(--fe-text-muted);
|
|
3861
|
+
}
|
|
3862
|
+
.fe-e2e-rk__key {
|
|
3863
|
+
display: block;
|
|
3864
|
+
padding: 12px 14px;
|
|
3865
|
+
border: 1px solid var(--fe-border);
|
|
3866
|
+
border-radius: var(--fe-radius-sm);
|
|
3867
|
+
background: var(--fe-bg-elev);
|
|
3868
|
+
color: var(--fe-text);
|
|
3869
|
+
font-family: var(--fe-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
3870
|
+
font-size: 15px;
|
|
3871
|
+
letter-spacing: 0.04em;
|
|
3872
|
+
line-height: 1.7;
|
|
3873
|
+
word-break: break-all;
|
|
3874
|
+
user-select: all;
|
|
3875
|
+
text-align: center;
|
|
3876
|
+
}
|
|
3877
|
+
.fe-e2e-rk__actions {
|
|
3878
|
+
display: flex;
|
|
3879
|
+
gap: 8px;
|
|
3880
|
+
}
|
|
3881
|
+
.fe-e2e-rk__escrow {
|
|
3882
|
+
border: 1px solid var(--fe-border);
|
|
3883
|
+
border-left: 3px solid var(--fe-warning, var(--fe-primary));
|
|
3884
|
+
border-radius: var(--fe-radius-sm);
|
|
3885
|
+
padding: 8px 10px;
|
|
3886
|
+
font-size: 12.5px;
|
|
3887
|
+
}
|
|
3888
|
+
.fe-e2e-rk__escrow strong {
|
|
3889
|
+
display: block;
|
|
3890
|
+
margin-bottom: 2px;
|
|
3891
|
+
color: var(--fe-text);
|
|
3892
|
+
}
|
|
3893
|
+
.fe-e2e-rk__escrow p {
|
|
3894
|
+
margin: 0;
|
|
3895
|
+
color: var(--fe-text-muted);
|
|
3896
|
+
}
|
|
3897
|
+
.fe-e2e-rk__kid code {
|
|
3898
|
+
font-size: 11.5px;
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
/* Unlock-without-the-password dialog. */
|
|
3902
|
+
.fe-e2e-recover {
|
|
3903
|
+
display: flex;
|
|
3904
|
+
flex-direction: column;
|
|
3905
|
+
gap: 12px;
|
|
3906
|
+
}
|
|
3907
|
+
.fe-e2e-recover__none {
|
|
3908
|
+
margin: 0;
|
|
3909
|
+
font-size: 13px;
|
|
3910
|
+
color: var(--fe-text-muted);
|
|
3911
|
+
}
|
|
3912
|
+
.fe-e2e-recover__hint {
|
|
3913
|
+
margin: 0;
|
|
3914
|
+
font-size: 12.5px;
|
|
3915
|
+
color: var(--fe-text-muted);
|
|
3916
|
+
}
|
|
3917
|
+
.fe-e2e-recover__tabs {
|
|
3918
|
+
display: flex;
|
|
3919
|
+
gap: 4px;
|
|
3920
|
+
padding: 3px;
|
|
3921
|
+
border: 1px solid var(--fe-border);
|
|
3922
|
+
border-radius: var(--fe-radius-lg);
|
|
3923
|
+
background: var(--fe-bg-elev);
|
|
3924
|
+
}
|
|
3925
|
+
.fe-e2e-recover__tab {
|
|
3926
|
+
flex: 1;
|
|
3927
|
+
padding: 6px 10px;
|
|
3928
|
+
border: 0;
|
|
3929
|
+
border-radius: calc(var(--fe-radius-lg) - 4px);
|
|
3930
|
+
background: transparent;
|
|
3931
|
+
color: var(--fe-text-muted);
|
|
3932
|
+
font: inherit;
|
|
3933
|
+
font-size: 12.5px;
|
|
3934
|
+
cursor: pointer;
|
|
3935
|
+
}
|
|
3936
|
+
.fe-e2e-recover__tab--on {
|
|
3937
|
+
background: var(--fe-bg);
|
|
3938
|
+
color: var(--fe-text);
|
|
3939
|
+
font-weight: 600;
|
|
3940
|
+
}
|
|
3941
|
+
.fe-e2e-recover__key {
|
|
3942
|
+
font-family: var(--fe-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
3943
|
+
letter-spacing: 0.04em;
|
|
3944
|
+
text-align: center;
|
|
3945
|
+
}
|
|
3946
|
+
.fe-e2e-recover__escrow {
|
|
3947
|
+
font-family: var(--fe-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
3948
|
+
font-size: 11.5px;
|
|
3949
|
+
resize: vertical;
|
|
3950
|
+
min-height: 90px;
|
|
3951
|
+
}
|
|
3952
|
+
|
|
3953
|
+
/* The offer to give a pre-recovery folder a recovery key. A strip, not a
|
|
3954
|
+
modal: it must be visible and dismissible, never something that blocks
|
|
3955
|
+
the folder the user came here to open. */
|
|
3956
|
+
.fe-e2e-upgrade {
|
|
3957
|
+
display: flex;
|
|
3958
|
+
align-items: flex-start;
|
|
3959
|
+
justify-content: space-between;
|
|
3960
|
+
gap: 14px;
|
|
3961
|
+
flex-wrap: wrap;
|
|
3962
|
+
margin: 0 0 10px;
|
|
3963
|
+
padding: 10px 12px;
|
|
3964
|
+
border: 1px solid var(--fe-border);
|
|
3965
|
+
border-left: 3px solid var(--fe-primary);
|
|
3966
|
+
border-radius: var(--fe-radius-sm);
|
|
3967
|
+
background: color-mix(in srgb, var(--fe-primary) 6%, transparent);
|
|
3968
|
+
}
|
|
3969
|
+
.fe-e2e-upgrade__text {
|
|
3970
|
+
flex: 1 1 320px;
|
|
3971
|
+
min-width: 0;
|
|
3972
|
+
}
|
|
3973
|
+
.fe-e2e-upgrade__text strong {
|
|
3974
|
+
display: block;
|
|
3975
|
+
font-size: 13px;
|
|
3976
|
+
color: var(--fe-text);
|
|
3977
|
+
}
|
|
3978
|
+
.fe-e2e-upgrade__text p {
|
|
3979
|
+
margin: 4px 0 0;
|
|
3980
|
+
font-size: 12.5px;
|
|
3981
|
+
color: var(--fe-text-muted);
|
|
3982
|
+
}
|
|
3983
|
+
.fe-e2e-upgrade__escrow {
|
|
3984
|
+
color: var(--fe-danger) !important;
|
|
3985
|
+
}
|
|
3986
|
+
.fe-e2e-upgrade__actions {
|
|
3987
|
+
display: flex;
|
|
3988
|
+
gap: 8px;
|
|
3989
|
+
flex: none;
|
|
3990
|
+
}
|
|
3839
3991
|
/* === /wiring:e2 === */
|
|
3840
3992
|
|
|
3841
3993
|
/* === ui-fix — toolbar tek satır + katlama + ölçüm şeridi ============= */
|
|
@@ -4220,6 +4372,106 @@ filex-explorer {
|
|
|
4220
4372
|
letter-spacing: 0.02em;
|
|
4221
4373
|
}
|
|
4222
4374
|
|
|
4375
|
+
/* === etiket:t1 — the Tags section ==================================== */
|
|
4376
|
+
/* A tag row is an ordinary panel item; only its label wraps differently,
|
|
4377
|
+
because a tag is user text and can be long. */
|
|
4378
|
+
.fe-sidenav__item--tag .fe-sidenav__text {
|
|
4379
|
+
text-transform: none;
|
|
4380
|
+
}
|
|
4381
|
+
/* "Show all (12)" / "Show fewer" — a link, not a button: it changes what this
|
|
4382
|
+
section shows, it is not a destination. */
|
|
4383
|
+
.fe-sidenav__more {
|
|
4384
|
+
display: block;
|
|
4385
|
+
width: 100%;
|
|
4386
|
+
margin: 2px 0 0;
|
|
4387
|
+
padding: 5px 10px;
|
|
4388
|
+
border: 0;
|
|
4389
|
+
border-radius: 999px;
|
|
4390
|
+
background: transparent;
|
|
4391
|
+
color: var(--fe-primary);
|
|
4392
|
+
font: inherit;
|
|
4393
|
+
font-size: 12.5px;
|
|
4394
|
+
text-align: left;
|
|
4395
|
+
cursor: pointer;
|
|
4396
|
+
}
|
|
4397
|
+
.fe-sidenav__more:hover {
|
|
4398
|
+
background: var(--fe-bg-hover);
|
|
4399
|
+
}
|
|
4400
|
+
.fe-sidenav__more:focus-visible {
|
|
4401
|
+
outline: 2px solid var(--fe-primary);
|
|
4402
|
+
outline-offset: 1px;
|
|
4403
|
+
}
|
|
4404
|
+
/* "No tags yet — add them from a file's menu." */
|
|
4405
|
+
.fe-sidenav__hint {
|
|
4406
|
+
margin: 2px 0 4px;
|
|
4407
|
+
padding: 0 10px;
|
|
4408
|
+
font-size: 11.5px;
|
|
4409
|
+
line-height: 1.45;
|
|
4410
|
+
color: var(--fe-text-muted);
|
|
4411
|
+
}
|
|
4412
|
+
|
|
4413
|
+
/* === yildiz:s1 — the star chip on a grid / gallery tile =============== */
|
|
4414
|
+
/* Positioned by the card, painted by StarButton (.filex-star-btn.is-card).
|
|
4415
|
+
⚠ The rules live HERE and not in the SFC: the panel's styles do too, and
|
|
4416
|
+
these are about the CARD's geometry, which is this file's business. */
|
|
4417
|
+
.fe-grid__star,
|
|
4418
|
+
.fe-gal__star {
|
|
4419
|
+
position: absolute;
|
|
4420
|
+
top: 6px;
|
|
4421
|
+
right: 6px;
|
|
4422
|
+
z-index: 2;
|
|
4423
|
+
/* Hidden until the tile is hovered or something inside it is focused —
|
|
4424
|
+
EXCEPT when the file is starred, which is the whole point: the mark has
|
|
4425
|
+
to be visible without hovering every tile to find it (see .is-starred
|
|
4426
|
+
below). */
|
|
4427
|
+
opacity: 0;
|
|
4428
|
+
transition: opacity 120ms ease;
|
|
4429
|
+
}
|
|
4430
|
+
.fe-grid__card:hover .fe-grid__star,
|
|
4431
|
+
.fe-grid__card:focus-within .fe-grid__star,
|
|
4432
|
+
.fe-gal__card:hover .fe-gal__star,
|
|
4433
|
+
.fe-gal__card:focus-within .fe-gal__star,
|
|
4434
|
+
.fe-grid__star:focus-within,
|
|
4435
|
+
.fe-gal__star:focus-within,
|
|
4436
|
+
.fe-grid__star:has(.is-starred),
|
|
4437
|
+
.fe-gal__star:has(.is-starred) {
|
|
4438
|
+
opacity: 1;
|
|
4439
|
+
}
|
|
4440
|
+
/* `:has()` is the modern half of the rule above; this is the same thing for
|
|
4441
|
+
engines without it — a starred chip paints itself. Both are needed: the
|
|
4442
|
+
parent wrapper carries the opacity, the child cannot raise it on its own. */
|
|
4443
|
+
.filex-star-btn.is-card.is-starred {
|
|
4444
|
+
opacity: 1;
|
|
4445
|
+
}
|
|
4446
|
+
/* The chip itself: a round translucent puck, legible over a photo thumbnail
|
|
4447
|
+
as well as over a flat icon tile. */
|
|
4448
|
+
.filex-star-btn.is-card {
|
|
4449
|
+
width: 26px;
|
|
4450
|
+
height: 26px;
|
|
4451
|
+
padding: 0;
|
|
4452
|
+
justify-content: center;
|
|
4453
|
+
border-radius: 999px;
|
|
4454
|
+
background: rgba(17, 24, 39, 0.55);
|
|
4455
|
+
color: #fff;
|
|
4456
|
+
backdrop-filter: blur(2px);
|
|
4457
|
+
}
|
|
4458
|
+
.filex-star-btn.is-card:hover {
|
|
4459
|
+
background: rgba(17, 24, 39, 0.78);
|
|
4460
|
+
color: var(--filex-accent-amber, #f59e0b);
|
|
4461
|
+
}
|
|
4462
|
+
.filex-star-btn.is-card.is-starred {
|
|
4463
|
+
color: var(--filex-accent-amber, #f59e0b);
|
|
4464
|
+
}
|
|
4465
|
+
.filex-star-btn.is-card:focus-visible {
|
|
4466
|
+
outline: 2px solid var(--fe-primary);
|
|
4467
|
+
outline-offset: 1px;
|
|
4468
|
+
}
|
|
4469
|
+
/* The gallery's meta strip covers the tile's foot on hover and is
|
|
4470
|
+
aria-hidden; the star sits above it and stays clickable. */
|
|
4471
|
+
.fe-gal__star {
|
|
4472
|
+
z-index: 3;
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4223
4475
|
/* Narrow (<560px): a drawer over the listing, never a column. At 390px a
|
|
4224
4476
|
232px column leaves 158px for the files, and a 56px rail still takes an
|
|
4225
4477
|
eighth of the screen from a listing that is already tight. */
|
|
@@ -77,6 +77,9 @@ export interface EndpointMap {
|
|
|
77
77
|
restore: string | null;
|
|
78
78
|
trashList: string | null;
|
|
79
79
|
trashRestore: string | null;
|
|
80
|
+
/* wiring:e2 */
|
|
81
|
+
e2eEscrowChallenge: string | null;
|
|
82
|
+
e2eEscrowUsed: string | null;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
export interface ExplorerConfig {
|
|
@@ -157,6 +160,12 @@ export interface ExplorerConfig {
|
|
|
157
160
|
/** filex trash restore endpoint — `POST { node_id }`. */
|
|
158
161
|
trashRestore?: string;
|
|
159
162
|
|
|
163
|
+
/* wiring:e2 */
|
|
164
|
+
/** E2E escrow proof-of-possession — `POST { path } → { id, challenge }`. */
|
|
165
|
+
e2eEscrowChallenge?: string;
|
|
166
|
+
/** E2E escrow use report — `POST { path, id, nonce }`. */
|
|
167
|
+
e2eEscrowUsed?: string;
|
|
168
|
+
|
|
160
169
|
/** Public share base URL — `${shareBase}/${uuid}` */
|
|
161
170
|
shareBase?: string;
|
|
162
171
|
|
|
@@ -318,12 +327,39 @@ export interface ExplorerConfig {
|
|
|
318
327
|
* the bug this became: for a year the sole place to mint the token the FTPS
|
|
319
328
|
* guide told you to use was the admin panel.
|
|
320
329
|
*
|
|
330
|
+
* ⚠ Role is not the same question as `callerKind`. "API keys" IS hidden for
|
|
331
|
+
* an app token — not because of what that caller may do, but because there is
|
|
332
|
+
* no single person behind it whose keys they would be. See `callerKind`.
|
|
333
|
+
*
|
|
321
334
|
* ⚠ The entries live in the panel, so `sideNav: false` takes them with it.
|
|
322
335
|
* Hosts that want the surface without the panel mount `<filex-connections>`
|
|
323
336
|
* (or `ConnectionsPanel`) on a page of their own.
|
|
324
337
|
*/
|
|
325
338
|
connections?: boolean;
|
|
326
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Who is behind this explorer — a person, or an integration?
|
|
342
|
+
*
|
|
343
|
+
* `'user'` (a signed-in human, or their own API token) draws everything.
|
|
344
|
+
* `'app'` suppresses the surfaces that only mean something for ONE person:
|
|
345
|
+
* **API keys**, **Recent**, **Starred** and **Shared with me**. Upload, the
|
|
346
|
+
* storage list, Trash and "How to connect" stay — an embed's users still
|
|
347
|
+
* upload files and still need mount instructions.
|
|
348
|
+
*
|
|
349
|
+
* Why it exists: a filex API token authenticates AS its owner, and the embeds
|
|
350
|
+
* we run authenticate every visitor with ONE shared token injected by the
|
|
351
|
+
* host's proxy. v0.30.0 put "API keys" in the panel, so under that token an
|
|
352
|
+
* embed visitor could list and revoke the credential the embed itself runs
|
|
353
|
+
* on — and "your Recent" meant the token owner's history shown to a stranger.
|
|
354
|
+
*
|
|
355
|
+
* Default: read from `GET /api/files/capabilities` (`caller_kind`), which is
|
|
356
|
+
* authoritative because only the server knows the token's kind (migration
|
|
357
|
+
* 00030). Set this only to answer BEFORE that request lands — a host that
|
|
358
|
+
* already knows it proxies with an app token spares its users the flash of a
|
|
359
|
+
* Starred row that then disappears. A value here wins over the server's.
|
|
360
|
+
*/
|
|
361
|
+
callerKind?: 'user' | 'app';
|
|
362
|
+
|
|
327
363
|
|
|
328
364
|
/** Default view. */
|
|
329
365
|
viewMode?: 'list' | 'grid';
|
package/src/types/FileNode.ts
CHANGED
|
@@ -88,11 +88,34 @@ export interface Capabilities {
|
|
|
88
88
|
/** Longest life a new share link may be given, in days (0 = no ceiling).
|
|
89
89
|
* Read by the share dialogs so they offer only expiries the server keeps. */
|
|
90
90
|
share_max_ttl_days?: number;
|
|
91
|
+
/** Is the caller a person (`'user'` — a session OR their own API token) or an
|
|
92
|
+
* integration (`'app'` — a host proxy, a bot, an MCP client)? The explorer
|
|
93
|
+
* reads it to decide whether to draw the identity-bearing surfaces; see
|
|
94
|
+
* ExplorerConfig.callerKind. Absent on a server older than the app/user
|
|
95
|
+
* token split, which is why every reader treats "missing" as a person. */
|
|
96
|
+
caller_kind?: 'user' | 'app';
|
|
91
97
|
external?: {
|
|
92
98
|
onlyoffice?: ExternalServiceStatus;
|
|
93
99
|
drawio?: ExternalServiceStatus;
|
|
94
100
|
mermaid?: ExternalServiceStatus;
|
|
95
101
|
};
|
|
102
|
+
/* wiring:e2 */
|
|
103
|
+
/** Whether this installation holds an escrow key for E2E-encrypted folders,
|
|
104
|
+
* and the public half the browser wraps new folders' master keys to.
|
|
105
|
+
*
|
|
106
|
+
* Published on purpose. Escrow means the operator can open the folders you
|
|
107
|
+
* create here without your password, and someone about to create one is
|
|
108
|
+
* entitled to know that BEFORE they create it. Fixed at install time
|
|
109
|
+
* (FILEX_INSTALLATION_E2E_ESCROW_KEY), so this answer never changes for a
|
|
110
|
+
* running installation. */
|
|
111
|
+
e2e_escrow?: {
|
|
112
|
+
enabled: boolean;
|
|
113
|
+
/** Short id of the escrow key (SHA-256(SPKI)[:8], hex). */
|
|
114
|
+
kid?: string;
|
|
115
|
+
alg?: string;
|
|
116
|
+
/** Base64 SPKI. Public material — it can only seal, never open. */
|
|
117
|
+
public_key?: string;
|
|
118
|
+
};
|
|
96
119
|
}
|
|
97
120
|
|
|
98
121
|
/** Single source of truth for "is the IdP/editor/diagram service ready?".
|