@brftech/filex-core 0.34.2 → 0.36.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/dist/index.d.ts CHANGED
@@ -159,6 +159,9 @@ declare type __VLS_Props_2 = {
159
159
  onlyOfficeBase?: string | null;
160
160
  onlyOfficeConfigEndpoint?: string | null;
161
161
  saveTextEndpoint?: string | null;
162
+ /** Endpoint for the archive (zip/rar/7z/tar) member list. Forwarded to
163
+ * ArchiveViewer, which otherwise assumes a same-origin path. */
164
+ archiveListEndpoint?: string | null;
162
165
  openMode?: 'edit' | 'view';
163
166
  authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
164
167
  authCredentials?: RequestCredentials;
@@ -1046,7 +1049,14 @@ export declare interface ExplorerConfig {
1046
1049
  e2eEscrowChallenge?: string;
1047
1050
  /** E2E escrow use report — `POST { path, id, nonce }`. */
1048
1051
  e2eEscrowUsed?: string;
1049
- /** Public share base URL — `${shareBase}/${uuid}` */
1052
+ /**
1053
+ * Public share base URL.
1054
+ *
1055
+ * @deprecated IGNORED — declared, never read. Share URLs are used exactly as
1056
+ * the server returns them (`share.url`), which is what makes a link work
1057
+ * behind a reverse proxy; a client-side base could only disagree with it.
1058
+ * Set `FILEX_PUBLIC_URL` on the server instead.
1059
+ */
1050
1060
  shareBase?: string;
1051
1061
  /** Auth strategy (see AuthConfig). */
1052
1062
  auth?: AuthConfig;
@@ -1126,7 +1136,10 @@ export declare interface ExplorerConfig {
1126
1136
  * X-Filex-Root header / root-scoped API token; this is the clean-embed UX.
1127
1137
  */
1128
1138
  rootPath?: string;
1129
- /** Whether the info panel toggle is visible. */
1139
+ /**
1140
+ * Whether the info-panel (inspector) toggle is shown in the toolbar.
1141
+ * Default true. The inspector itself stays reachable from the context menu.
1142
+ */
1130
1143
  showInfoPanel?: boolean;
1131
1144
  /**
1132
1145
  * Which set of chrome the explorer presents. A PRESET, not a feature switch:
@@ -1236,9 +1249,26 @@ export declare interface ExplorerConfig {
1236
1249
  callerKind?: 'user' | 'app';
1237
1250
  /** Default view. */
1238
1251
  viewMode?: 'list' | 'grid';
1239
- /** Override max upload size (MB) — falls back to /limits otherwise. */
1252
+ /**
1253
+ * Max upload size in MB.
1254
+ *
1255
+ * @deprecated IGNORED — declared, never read. There is no client-side size
1256
+ * check in the explorer at all (and the `/limits` fallback this comment used
1257
+ * to promise is not called either), so an embedder who set it watched
1258
+ * oversized uploads start anyway and fail at the server, which is the only
1259
+ * place the limit is enforced (FILEX_UPLOAD_* / the storage's own limits).
1260
+ * Left in place rather than removed so existing embeds keep compiling; do
1261
+ * not add a reader without also adding the pre-flight rejection the name
1262
+ * implies.
1263
+ */
1240
1264
  maxFileSizeMb?: number;
1241
- /** Accept patterns (MIME or extension). Empty = unrestricted. */
1265
+ /**
1266
+ * Accept patterns (MIME or extension).
1267
+ *
1268
+ * @deprecated IGNORED — declared, never read. No `accept` attribute is
1269
+ * rendered on any file input in the package and nothing filters a drop, so
1270
+ * setting it restricts nothing. Same note as `maxFileSizeMb`.
1271
+ */
1242
1272
  acceptTypes?: string[];
1243
1273
  /** Storage adapter to default to (avoids the initial flash). */
1244
1274
  defaultAdapter?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brftech/filex-core",
3
- "version": "0.34.2",
3
+ "version": "0.36.0",
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",
@@ -730,6 +730,16 @@ const uiProfile = computed(() => props.config.uiProfile ?? 'standard');
730
730
  * on TOP.
731
731
  */
732
732
  const simpleUi = computed(() => uiProfile.value === 'simple' || uiProfile.value === 'drive');
733
+
734
+ /**
735
+ * ⚠ `showInfoPanel` is documented public API ("whether the info panel toggle
736
+ * is visible") and until now nothing read it: an embedder who set it to false
737
+ * got the toggle anyway. Default stays TRUE, so every existing embed —
738
+ * including the admin SPA, which passes `showInfoPanel: true` — is unchanged.
739
+ * It hides the TOGGLE, exactly as documented; the inspector itself stays
740
+ * reachable from the context menu, which is what the option says.
741
+ */
742
+ const infoPanelToggle = computed(() => props.config.showInfoPanel !== false);
733
743
  /* === surucu:d1 — the Drive shell (GitHub #14, the reporter's mockups) ===== */
734
744
  const driveShell = computed(() => uiProfile.value === 'drive');
735
745
 
@@ -4778,6 +4788,7 @@ function closeRecoveryKey() {
4778
4788
  @update:view-mode="setDisplayedViewMode($event)"
4779
4789
  />
4780
4790
  <button
4791
+ v-if="infoPanelToggle"
4781
4792
  type="button"
4782
4793
  class="fe-btn fe-btn--icon-only fe-toolbar__inspector"
4783
4794
  :class="{ 'is-active': showInspector }"
@@ -5543,6 +5554,7 @@ function closeRecoveryKey() {
5543
5554
  :only-office-config-endpoint="effectiveOnlyOfficeConfigEndpoint"
5544
5555
  :new-tab-enabled="!e2eActive /* wiring:e2 — the standalone route pulls raw bytes */"
5545
5556
  :save-text-endpoint="e2eActive ? null : api.endpoints.saveText || null /* wiring:e2 — a plaintext save would be a leak */"
5557
+ :archive-list-endpoint="api.endpoints.archiveList || null"
5546
5558
  :open-mode="previewMode"
5547
5559
  :auth-headers="() => buildAuthHeaders({ 'Content-Type': 'application/json' })"
5548
5560
  :auth-credentials="api.credentialsMode()"
@@ -37,6 +37,9 @@ const props = defineProps<{
37
37
  onlyOfficeBase?: string | null;
38
38
  onlyOfficeConfigEndpoint?: string | null;
39
39
  saveTextEndpoint?: string | null;
40
+ /** Endpoint for the archive (zip/rar/7z/tar) member list. Forwarded to
41
+ * ArchiveViewer, which otherwise assumes a same-origin path. */
42
+ archiveListEndpoint?: string | null;
40
43
  openMode?: 'edit' | 'view';
41
44
  authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
42
45
  authCredentials?: RequestCredentials;
@@ -217,6 +220,7 @@ const viewerProps = computed(() => {
217
220
  // 500s for every non-default storage (sample.zip on fm s3-test).
218
221
  if (e === 'zip' || e === 'rar' || e === '7z' || e === 'tar' || e === 'gz' || e === 'tgz') {
219
222
  base.filePath = props.file.path;
223
+ if (props.archiveListEndpoint) base.archiveListUrl = props.archiveListEndpoint;
220
224
  }
221
225
  }
222
226
  if (e === 'drawio' || e === 'dio') {
@@ -166,7 +166,14 @@ export interface ExplorerConfig {
166
166
  /** E2E escrow use report — `POST { path, id, nonce }`. */
167
167
  e2eEscrowUsed?: string;
168
168
 
169
- /** Public share base URL — `${shareBase}/${uuid}` */
169
+ /**
170
+ * Public share base URL.
171
+ *
172
+ * @deprecated IGNORED — declared, never read. Share URLs are used exactly as
173
+ * the server returns them (`share.url`), which is what makes a link work
174
+ * behind a reverse proxy; a client-side base could only disagree with it.
175
+ * Set `FILEX_PUBLIC_URL` on the server instead.
176
+ */
170
177
  shareBase?: string;
171
178
 
172
179
  /** Auth strategy (see AuthConfig). */
@@ -263,7 +270,10 @@ export interface ExplorerConfig {
263
270
  */
264
271
  rootPath?: string;
265
272
 
266
- /** Whether the info panel toggle is visible. */
273
+ /**
274
+ * Whether the info-panel (inspector) toggle is shown in the toolbar.
275
+ * Default true. The inspector itself stays reachable from the context menu.
276
+ */
267
277
  showInfoPanel?: boolean;
268
278
 
269
279
  /**
@@ -380,10 +390,27 @@ export interface ExplorerConfig {
380
390
  /** Default view. */
381
391
  viewMode?: 'list' | 'grid';
382
392
 
383
- /** Override max upload size (MB) — falls back to /limits otherwise. */
393
+ /**
394
+ * Max upload size in MB.
395
+ *
396
+ * @deprecated IGNORED — declared, never read. There is no client-side size
397
+ * check in the explorer at all (and the `/limits` fallback this comment used
398
+ * to promise is not called either), so an embedder who set it watched
399
+ * oversized uploads start anyway and fail at the server, which is the only
400
+ * place the limit is enforced (FILEX_UPLOAD_* / the storage's own limits).
401
+ * Left in place rather than removed so existing embeds keep compiling; do
402
+ * not add a reader without also adding the pre-flight rejection the name
403
+ * implies.
404
+ */
384
405
  maxFileSizeMb?: number;
385
406
 
386
- /** Accept patterns (MIME or extension). Empty = unrestricted. */
407
+ /**
408
+ * Accept patterns (MIME or extension).
409
+ *
410
+ * @deprecated IGNORED — declared, never read. No `accept` attribute is
411
+ * rendered on any file input in the package and nothing filters a drop, so
412
+ * setting it restricts nothing. Same note as `maxFileSizeMb`.
413
+ */
387
414
  acceptTypes?: string[];
388
415
 
389
416
  /** Storage adapter to default to (avoids the initial flash). */
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * ArchiveViewer — minimal zip / archive contents preview.
4
4
  *
5
- * Hits `POST /api/files/archive/list` with the adapter-qualified path
5
+ * Hits the configured archive-list endpoint (`archiveListUrl`, default
6
+ * `POST /api/files/archive/list`) with the adapter-qualified path
6
7
  * and renders the member list as a flat table (name, size, mtime).
7
8
  * Read-only — extraction is exposed elsewhere (context menu / actions
8
9
  * panel). The viewer's job is just "what's inside?" so the user can
@@ -24,6 +25,12 @@ const props = defineProps<{
24
25
  t?: (key: string) => string;
25
26
  authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
26
27
  authCredentials?: RequestCredentials;
28
+ /** ⚠ Where to ask. This used to be the literal '/api/files/archive/list',
29
+ * which is the ONE thing an embed cannot assume: with `apiBase` pointing at
30
+ * another origin the request went to the host page instead and 404'd, while
31
+ * every other call in the package honoured the configured base. The default
32
+ * keeps a same-origin install working unchanged. */
33
+ archiveListUrl?: string;
27
34
  }>();
28
35
 
29
36
  const entries = ref<ArchiveEntry[]>([]);
@@ -54,7 +61,7 @@ async function load(): Promise<void> {
54
61
  return;
55
62
  }
56
63
  try {
57
- const res = await fetch('/api/files/archive/list', {
64
+ const res = await fetch(props.archiveListUrl || '/api/files/archive/list', {
58
65
  method: 'POST',
59
66
  credentials: props.authCredentials || 'same-origin',
60
67
  headers: {
@@ -1 +0,0 @@
1
- {"version":3,"file":"ArchiveViewer-B9JSFdX6.js","sources":["../src/viewers/ArchiveViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * ArchiveViewer — minimal zip / archive contents preview.\n *\n * Hits `POST /api/files/archive/list` with the adapter-qualified path\n * and renders the member list as a flat table (name, size, mtime).\n * Read-only — extraction is exposed elsewhere (context menu / actions\n * panel). The viewer's job is just \"what's inside?\" so the user can\n * decide whether to extract or download.\n */\nimport { computed, onMounted, ref, watch } from 'vue';\n\ninterface ArchiveEntry {\n name: string;\n size: number;\n mtime?: string;\n is_dir?: boolean;\n}\n\nconst props = defineProps<{\n url: string;\n filePath?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;\n authCredentials?: RequestCredentials;\n}>();\n\nconst entries = ref<ArchiveEntry[]>([]);\nconst loading = ref(true);\nconst error = ref<string | null>(null);\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n\nfunction fmtSize(n: number): string {\n if (n < 1024) return `${n} B`;\n if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;\n if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;\n return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n entries.value = [];\n // The viewer is mounted with the resolved file path. Hit the API\n // endpoint with the same adapter-qualified path the preview URL\n // points at — the backend resolves storage + relative path itself.\n if (!props.filePath) {\n error.value = tt('viewer.archive.error', 'Could not read archive contents.');\n loading.value = false;\n return;\n }\n try {\n const res = await fetch('/api/files/archive/list', {\n method: 'POST',\n credentials: props.authCredentials || 'same-origin',\n headers: {\n 'Content-Type': 'application/json',\n ...(props.authHeaders ? await props.authHeaders() : {}),\n },\n body: JSON.stringify({ path: props.filePath }),\n });\n if (!res.ok) {\n throw new Error(`${res.status} ${res.statusText}`);\n }\n const body = (await res.json()) as { entries?: ArchiveEntry[] };\n entries.value = body.entries ?? [];\n } catch (err) {\n error.value = err instanceof Error ? err.message : tt('viewer.archive.error', 'Could not read archive contents.');\n } finally {\n loading.value = false;\n }\n}\n\nonMounted(load);\nwatch(() => props.filePath, load);\n\nconst totalSize = computed(() => entries.value.reduce((sum, e) => sum + (e.size || 0), 0));\nconst fileCount = computed(() => entries.value.filter((e) => !e.is_dir).length);\n</script>\n\n<template>\n <div class=\"filex-viewer-archive\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <span class=\"filex-viewer-fallback__icon\">🗜️</span>\n <p>{{ error }}</p>\n </div>\n <div v-else-if=\"loading\" class=\"filex-viewer-fallback\">\n <span class=\"filex-viewer-fallback__icon\">⏳</span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <div v-else-if=\"entries.length === 0\" class=\"filex-viewer-fallback\">\n <span class=\"filex-viewer-fallback__icon\">🗜️</span>\n <p>{{ tt('viewer.archive.empty', 'Archive is empty.') }}</p>\n </div>\n <div v-else class=\"filex-viewer-archive__pane\">\n <div class=\"filex-viewer-archive__summary\">\n {{ fileCount }} {{ tt('viewer.archive.entries', '{n} files').replace('{n}', String(fileCount)) }}\n · {{ fmtSize(totalSize) }}\n </div>\n <table class=\"filex-viewer-archive__table\">\n <thead>\n <tr>\n <th>{{ tt('viewer.name', 'Name') }}</th>\n <th class=\"filex-viewer-archive__size\">{{ tt('viewer.size', 'Size') }}</th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(e, i) in entries\" :key=\"i\" :data-dir=\"e.is_dir ? '1' : '0'\">\n <td>\n <span class=\"filex-viewer-archive__icon\">{{ e.is_dir ? '📁' : '📄' }}</span>\n {{ e.name }}\n </td>\n <td class=\"filex-viewer-archive__size\">{{ e.is_dir ? '' : fmtSize(e.size) }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</template>\n\n<style>\n/* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to\n `.cls[data-v-HASH]`, and in the web-component build the hash baked into\n this CSS does not match the one Vue stamps onto the DOM — so every rule\n here silently stopped applying. Measured in the desktop app: the share\n dialog had `position: static`, no background and no radius, i.e. raw\n unstyled HTML, in EVERY embedded surface.\n Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so\n there is nothing here that can leak into a host page. */\n.filex-viewer-archive {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n}\n.filex-viewer-archive__pane {\n flex: 1;\n overflow: auto;\n padding: 16px 20px;\n}\n.filex-viewer-archive__summary {\n font-size: 12px;\n color: var(--fe-text-muted, #5a6475);\n margin-bottom: 12px;\n font-variant-numeric: tabular-nums;\n}\n.filex-viewer-archive__table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n font-family: var(--fe-font-mono, monospace);\n}\n.filex-viewer-archive__table th,\n.filex-viewer-archive__table td {\n padding: 6px 12px;\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n text-align: left;\n}\n.filex-viewer-archive__table th {\n background: var(--fe-bg-elev, #f7f8fa);\n font-weight: 600;\n position: sticky;\n top: 0;\n}\n.filex-viewer-archive__size {\n text-align: right;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-archive__icon {\n margin-right: 6px;\n}\n.filex-viewer-archive__table tr[data-dir=\"1\"] {\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-fallback__icon {\n font-size: 48px;\n display: block;\n margin-bottom: 12px;\n}\n</style>\n"],"names":["props","__props","entries","ref","loading","error","tt","key","fallback","fmtSize","n","load","res","body","err","onMounted","watch","totalSize","computed","sum","e","fileCount","_openBlock","_createElementBlock","_hoisted_1","_hoisted_2","_cache","_createElementVNode","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_toDisplayString","_hoisted_7","_hoisted_8","_Fragment","_renderList","i","_hoisted_10","_hoisted_11"],"mappings":"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"}