@brftech/filex-core 0.13.3 → 0.14.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/{ArchiveViewer-CxdOyBhs.js → ArchiveViewer-B9JSFdX6.js} +2 -2
- package/dist/ArchiveViewer-B9JSFdX6.js.map +1 -0
- package/dist/{CsvViewer-JG3nzTgf.js → CsvViewer-DxujFEM3.js} +2 -2
- package/dist/CsvViewer-DxujFEM3.js.map +1 -0
- package/dist/{DrawioViewer-BEYLdtPG.js → DrawioViewer-DCvWnX2t.js} +3 -3
- package/dist/DrawioViewer-DCvWnX2t.js.map +1 -0
- package/dist/{EpubViewer-BwUDmJm4.js → EpubViewer-BPmlImG7.js} +2 -2
- package/dist/EpubViewer-BPmlImG7.js.map +1 -0
- package/dist/{IpynbViewer-CuHQoAHU.js → IpynbViewer-CDlQmuCV.js} +2 -2
- package/dist/IpynbViewer-CDlQmuCV.js.map +1 -0
- package/dist/{MermaidViewer-DLmijDXo.js → MermaidViewer-sfZWdR-d.js} +19 -19
- package/dist/MermaidViewer-sfZWdR-d.js.map +1 -0
- package/dist/{PsdViewer-D-FM4eNi.js → PsdViewer-DwuySZsb.js} +2 -2
- package/dist/PsdViewer-DwuySZsb.js.map +1 -0
- package/dist/{TiffViewer-DOw4z_dO.js → TiffViewer-BDQ3d3CQ.js} +8 -8
- package/dist/TiffViewer-BDQ3d3CQ.js.map +1 -0
- package/dist/common-xdTjul80.js +12982 -0
- package/dist/common-xdTjul80.js.map +1 -0
- package/dist/filex-core.js +4423 -4400
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +45 -41
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/FileExplorer.vue +46 -9
- package/src/components/QuickLook.vue +1 -1
- package/src/composables/useFileApi.ts +29 -9
- package/src/composables/useMonacoLoader.ts +1 -1
- package/src/modals/PreviewModal.vue +33 -6
- package/src/viewers/ArchiveViewer.vue +2 -2
- package/src/viewers/CsvViewer.vue +2 -2
- package/src/viewers/DrawioViewer.vue +3 -3
- package/src/viewers/EpubViewer.vue +2 -2
- package/src/viewers/IpynbViewer.vue +2 -2
- package/src/viewers/MermaidViewer.vue +2 -2
- package/src/viewers/PdfViewer.vue +3 -3
- package/src/viewers/PsdViewer.vue +2 -2
- package/src/viewers/TiffViewer.vue +2 -2
- package/dist/ArchiveViewer-CxdOyBhs.js.map +0 -1
- package/dist/CsvViewer-JG3nzTgf.js.map +0 -1
- package/dist/DrawioViewer-BEYLdtPG.js.map +0 -1
- package/dist/EpubViewer-BwUDmJm4.js.map +0 -1
- package/dist/IpynbViewer-CuHQoAHU.js.map +0 -1
- package/dist/MermaidViewer-DLmijDXo.js.map +0 -1
- package/dist/PsdViewer-D-FM4eNi.js.map +0 -1
- package/dist/TiffViewer-DOw4z_dO.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -70,7 +70,7 @@ declare type __VLS_Props_2 = {
|
|
|
70
70
|
onlyOfficeConfigEndpoint?: string | null;
|
|
71
71
|
saveTextEndpoint?: string | null;
|
|
72
72
|
openMode?: 'edit' | 'view';
|
|
73
|
-
authHeaders?: () => Record<string, string
|
|
73
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
74
74
|
authCredentials?: RequestCredentials;
|
|
75
75
|
/** New rich-viewer config (forwarded by FileExplorer from ExplorerConfig). */
|
|
76
76
|
drawioUrl?: string | null;
|
|
@@ -160,7 +160,7 @@ declare type __VLS_Props_8 = {
|
|
|
160
160
|
downloadUrl: (path: string) => string;
|
|
161
161
|
onlyOfficeBase?: string | null;
|
|
162
162
|
onlyOfficeConfigEndpoint?: string | null;
|
|
163
|
-
authHeaders?: () => Record<string, string
|
|
163
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
164
164
|
authCredentials?: RequestCredentials;
|
|
165
165
|
drawioUrl?: string | null;
|
|
166
166
|
pdfWorkerUrl?: string | null;
|
package/package.json
CHANGED
package/src/FileExplorer.vue
CHANGED
|
@@ -1372,9 +1372,10 @@ function openNodeInNewTab(n: FileNode) {
|
|
|
1372
1372
|
// mode so OnlyOffice / Monaco mount with write permissions.
|
|
1373
1373
|
// Read-only inspection lives on the "Önizle" entry + the dbl-click
|
|
1374
1374
|
// in-page modal.
|
|
1375
|
-
const
|
|
1375
|
+
const abs = absolutePageUrl(base);
|
|
1376
|
+
const sep = abs.includes('?') ? '&' : '?';
|
|
1376
1377
|
const url =
|
|
1377
|
-
`${
|
|
1378
|
+
`${abs}${sep}path=${encodeURIComponent(n.path)}` +
|
|
1378
1379
|
`&type=${encodeURIComponent(ext)}` +
|
|
1379
1380
|
`&mode=edit`;
|
|
1380
1381
|
window.open(url, '_blank', 'noopener');
|
|
@@ -2287,14 +2288,50 @@ async function onCopyPath(adapterPath: string) {
|
|
|
2287
2288
|
}
|
|
2288
2289
|
}
|
|
2289
2290
|
|
|
2290
|
-
//
|
|
2291
|
-
//
|
|
2292
|
-
//
|
|
2293
|
-
//
|
|
2291
|
+
// Auth-headers builder handed to PreviewModal, QuickLook and every viewer.
|
|
2292
|
+
//
|
|
2293
|
+
// ⚠⚠ ASYNC on purpose. This used to call `authHeadersSync`, which drops the
|
|
2294
|
+
// bearer entirely when the embedder supplies a token FUNCTION rather than a
|
|
2295
|
+
// string — the shape the desktop app uses so the credential is fetched per
|
|
2296
|
+
// call instead of sitting in the page. The result was an Authorization-less
|
|
2297
|
+
// request and a 401 on the OnlyOffice config endpoint, the starred list, the
|
|
2298
|
+
// recently-opened POST and every viewer that fetches its own bytes. Consumers
|
|
2299
|
+
// must `await` this; the guard test in web/tests/api/authHeaders.test.ts fails
|
|
2300
|
+
// the build if one forgets.
|
|
2294
2301
|
function buildAuthHeaders(extra: Record<string, string> = {}) {
|
|
2295
|
-
return api.
|
|
2302
|
+
return api.authHeaders({ ...extra });
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
/** The standalone viewer/editor route the backend serves next to the API. */
|
|
2306
|
+
const STANDALONE_ROUTE = '/files/edit';
|
|
2307
|
+
|
|
2308
|
+
/**
|
|
2309
|
+
* Makes a page route absolute against the API host.
|
|
2310
|
+
*
|
|
2311
|
+
* ⚠ A root-relative route like `/files/edit` is resolved by the browser
|
|
2312
|
+
* against the PAGE, not the API — which is right when the explorer is embedded
|
|
2313
|
+
* in the same app that serves the route, and wrong for every embed served from
|
|
2314
|
+
* a different origin. In the desktop app the page origin is `app://filex`, so
|
|
2315
|
+
* "Open in new tab" asked the OS to open `app://filex/files/edit?…`: no handler
|
|
2316
|
+
* for that scheme exists, so the click did nothing at all, silently. Measured
|
|
2317
|
+
* 2026-08-10.
|
|
2318
|
+
*/
|
|
2319
|
+
function absolutePageUrl(base: string): string {
|
|
2320
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(base)) return base; // already absolute
|
|
2321
|
+
const api = props.config.apiBase;
|
|
2322
|
+
if (!api) return base; // same-origin embed — the browser resolves it correctly
|
|
2323
|
+
try {
|
|
2324
|
+
return new URL(base, api.endsWith('/') ? api : `${api}/`).toString();
|
|
2325
|
+
} catch {
|
|
2326
|
+
return base;
|
|
2327
|
+
}
|
|
2296
2328
|
}
|
|
2297
2329
|
|
|
2330
|
+
/** Where the modal's "Open in new tab" / "Edit" buttons should land. */
|
|
2331
|
+
const effectiveViewerBaseUrl = computed(() =>
|
|
2332
|
+
absolutePageUrl(props.config.viewerBaseUrl || STANDALONE_ROUTE),
|
|
2333
|
+
);
|
|
2334
|
+
|
|
2298
2335
|
/* === wiring:c1 — tema galerisi ===
|
|
2299
2336
|
* Selected theme (shared module state, localStorage `filex.theme`) is applied
|
|
2300
2337
|
* as inline `--fe-*` variables on the explorer root, resolved to the active
|
|
@@ -3536,7 +3573,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3536
3573
|
:drawio-url="effectiveDrawioUrl"
|
|
3537
3574
|
:pdf-worker-url="props.config.pdfWorkerUrl || null"
|
|
3538
3575
|
:pdf-save-url="props.config.pdfSaveUrl || null"
|
|
3539
|
-
:viewer-base-url="
|
|
3576
|
+
:viewer-base-url="effectiveViewerBaseUrl"
|
|
3540
3577
|
@close="showPreview = false"
|
|
3541
3578
|
/>
|
|
3542
3579
|
<ConvertModal
|
|
@@ -3710,7 +3747,7 @@ async function submitEncryptedFolder(payload: { name: string; password: string }
|
|
|
3710
3747
|
:auth-credentials="api.credentialsMode()"
|
|
3711
3748
|
:drawio-url="effectiveDrawioUrl"
|
|
3712
3749
|
:pdf-worker-url="props.config.pdfWorkerUrl || null"
|
|
3713
|
-
:viewer-base-url="
|
|
3750
|
+
:viewer-base-url="effectiveViewerBaseUrl"
|
|
3714
3751
|
@close="quickLookOpen = false"
|
|
3715
3752
|
@nav="quickLookNav"
|
|
3716
3753
|
@open-full="quickLookOpenFull"
|
|
@@ -32,7 +32,7 @@ const props = defineProps<{
|
|
|
32
32
|
downloadUrl: (path: string) => string;
|
|
33
33
|
onlyOfficeBase?: string | null;
|
|
34
34
|
onlyOfficeConfigEndpoint?: string | null;
|
|
35
|
-
authHeaders?: () => Record<string, string
|
|
35
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
36
36
|
authCredentials?: RequestCredentials;
|
|
37
37
|
drawioUrl?: string | null;
|
|
38
38
|
pdfWorkerUrl?: string | null;
|
|
@@ -259,11 +259,27 @@ export function useFileApi(config: ExplorerConfig) {
|
|
|
259
259
|
const endpoints = resolveEndpoints(config);
|
|
260
260
|
const authConf = normalizeAuth(config.auth);
|
|
261
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Last bearer value a function-token actually produced.
|
|
264
|
+
*
|
|
265
|
+
* ⚠ Exists for `authHeadersSync` only. A function token is resolved
|
|
266
|
+
* asynchronously (the desktop app fetches it from the main process per
|
|
267
|
+
* call), and a synchronous caller cannot wait for that — before this cache
|
|
268
|
+
* it simply emitted NO Authorization header, so the request went out
|
|
269
|
+
* anonymous and came back 401. Remembering the last value turns "no
|
|
270
|
+
* credential at all" into "the credential we last held", which is the
|
|
271
|
+
* difference between a dead feature and a stale-token retry.
|
|
272
|
+
*/
|
|
273
|
+
let lastBearer: string | null = null;
|
|
274
|
+
|
|
262
275
|
async function authHeaders(extra: Record<string, string> = {}): Promise<Record<string, string>> {
|
|
263
276
|
const h: Record<string, string> = { Accept: 'application/json', ...extra };
|
|
264
277
|
if (authConf.kind === 'bearer') {
|
|
265
278
|
const token = await resolveToken(authConf.token);
|
|
266
|
-
if (token)
|
|
279
|
+
if (token) {
|
|
280
|
+
h.Authorization = `Bearer ${token}`;
|
|
281
|
+
lastBearer = token;
|
|
282
|
+
}
|
|
267
283
|
} else if (authConf.kind === 'csrf') {
|
|
268
284
|
h['X-CSRF-TOKEN'] = authConf.csrf;
|
|
269
285
|
h['X-Requested-With'] = 'XMLHttpRequest';
|
|
@@ -275,17 +291,21 @@ export function useFileApi(config: ExplorerConfig) {
|
|
|
275
291
|
}
|
|
276
292
|
|
|
277
293
|
/**
|
|
278
|
-
* Sync auth-header builder for callers that
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
294
|
+
* Sync auth-header builder for the few callers that genuinely cannot await
|
|
295
|
+
* (XMLHttpRequest's `setRequestHeader` loop).
|
|
296
|
+
*
|
|
297
|
+
* ⚠ Prefer `authHeaders()`. A function token can only be *resolved*
|
|
298
|
+
* asynchronously, so this returns the last value one produced — which is
|
|
299
|
+
* nothing at all until the first async call has run. Measured on 2026-08-10
|
|
300
|
+
* in the desktop app: the OnlyOffice config POST, the starred list and the
|
|
301
|
+
* recently-opened POST all went out with no Authorization header and came
|
|
302
|
+
* back 401, because every one of them reached the API through this function.
|
|
284
303
|
*/
|
|
285
304
|
function authHeadersSync(extra: Record<string, string> = {}): Record<string, string> {
|
|
286
305
|
const h: Record<string, string> = { Accept: 'application/json', ...extra };
|
|
287
|
-
if (authConf.kind === 'bearer'
|
|
288
|
-
|
|
306
|
+
if (authConf.kind === 'bearer') {
|
|
307
|
+
const token = typeof authConf.token === 'string' ? authConf.token : lastBearer;
|
|
308
|
+
if (token) h.Authorization = `Bearer ${token}`;
|
|
289
309
|
} else if (authConf.kind === 'csrf') {
|
|
290
310
|
h['X-CSRF-TOKEN'] = authConf.csrf;
|
|
291
311
|
h['X-Requested-With'] = 'XMLHttpRequest';
|
|
@@ -45,7 +45,7 @@ export function preloadEditor(): void {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
if (!highlightPromise) {
|
|
48
|
-
highlightPromise = import(/* @vite-ignore */ 'highlight.js')
|
|
48
|
+
highlightPromise = import(/* @vite-ignore */ 'highlight.js/lib/common')
|
|
49
49
|
.then((mod) => {
|
|
50
50
|
highlightCached = (mod as { default?: unknown }).default ?? mod;
|
|
51
51
|
return highlightCached;
|
|
@@ -38,7 +38,7 @@ const props = defineProps<{
|
|
|
38
38
|
onlyOfficeConfigEndpoint?: string | null;
|
|
39
39
|
saveTextEndpoint?: string | null;
|
|
40
40
|
openMode?: 'edit' | 'view';
|
|
41
|
-
authHeaders?: () => Record<string, string
|
|
41
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
42
42
|
authCredentials?: RequestCredentials;
|
|
43
43
|
/** New rich-viewer config (forwarded by FileExplorer from ExplorerConfig). */
|
|
44
44
|
drawioUrl?: string | null;
|
|
@@ -351,7 +351,7 @@ async function fetchText(url: string): Promise<void> {
|
|
|
351
351
|
tooLarge.value = false;
|
|
352
352
|
try {
|
|
353
353
|
const headers: Record<string, string> = {};
|
|
354
|
-
if (props.authHeaders) Object.assign(headers, props.authHeaders());
|
|
354
|
+
if (props.authHeaders) Object.assign(headers, await props.authHeaders());
|
|
355
355
|
const res = await fetch(url, {
|
|
356
356
|
credentials: props.authCredentials || 'same-origin',
|
|
357
357
|
headers,
|
|
@@ -390,14 +390,25 @@ async function fetchText(url: string): Promise<void> {
|
|
|
390
390
|
|
|
391
391
|
const markdownHtml = ref<string>('');
|
|
392
392
|
const markdownEl = ref<HTMLDivElement | null>(null);
|
|
393
|
+
/**
|
|
394
|
+
* The renderer could not be loaded at all.
|
|
395
|
+
*
|
|
396
|
+
* ⚠ Tracked separately from "nothing to render". In view mode a missing
|
|
397
|
+
* renderer falls through to the raw source, which is honest; in the split
|
|
398
|
+
* editor the preview pane simply stayed BLANK — a white half-window with no
|
|
399
|
+
* explanation, measured in the desktop app on 2026-08-10.
|
|
400
|
+
*/
|
|
401
|
+
const markdownUnavailable = ref(false);
|
|
393
402
|
|
|
394
403
|
async function renderMarkdown(text: string): Promise<void> {
|
|
395
404
|
try {
|
|
396
405
|
const mod = (await import(/* @vite-ignore */ 'markdown-it').catch(() => null)) as any;
|
|
397
406
|
if (!mod) {
|
|
398
407
|
markdownHtml.value = '';
|
|
408
|
+
markdownUnavailable.value = true;
|
|
399
409
|
return;
|
|
400
410
|
}
|
|
411
|
+
markdownUnavailable.value = false;
|
|
401
412
|
const Md = (mod as { default: any }).default ?? (mod as any);
|
|
402
413
|
// html: true so inline HTML in README.md / docs renders (the
|
|
403
414
|
// GitHub / GitLab contract — operators expect `<img>`, tables,
|
|
@@ -552,6 +563,8 @@ const monacoEl = ref<HTMLDivElement | null>(null);
|
|
|
552
563
|
let monacoEditor: any = null;
|
|
553
564
|
let codeAutosaveTimer: ReturnType<typeof setTimeout> | undefined;
|
|
554
565
|
const monacoReady = ref(false);
|
|
566
|
+
/** Monaco resolved to nothing — the peer is not in this build at all. */
|
|
567
|
+
const monacoUnavailable = ref(false);
|
|
555
568
|
const saving = ref(false);
|
|
556
569
|
const saveOk = ref(false);
|
|
557
570
|
const saveError = ref<string | null>(null);
|
|
@@ -596,6 +609,12 @@ function monacoLanguageFor(extension: string): string {
|
|
|
596
609
|
|
|
597
610
|
async function tryMountMonaco(text: string, extension: string): Promise<boolean> {
|
|
598
611
|
const monaco = (await ensureMonaco()) as any;
|
|
612
|
+
// ⚠ "Not installed" is not "still loading". Monaco is an optional peer that
|
|
613
|
+
// most embeds do not ship, and the toolbar label was gated only on "not ready
|
|
614
|
+
// yet" — so a bundle without it showed "Editor loading…" forever, over a
|
|
615
|
+
// perfectly good read-only view. Measured in the desktop app once syntax
|
|
616
|
+
// highlighting started working and that label finally had something to sit on.
|
|
617
|
+
if (!monaco) monacoUnavailable.value = true;
|
|
599
618
|
if (!monaco || !monacoEl.value) return false;
|
|
600
619
|
try {
|
|
601
620
|
disposeMonaco();
|
|
@@ -653,7 +672,7 @@ async function saveCode(): Promise<void> {
|
|
|
653
672
|
try {
|
|
654
673
|
const text = monacoEditor ? monacoEditor.getValue() : '';
|
|
655
674
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
656
|
-
if (props.authHeaders) Object.assign(headers, props.authHeaders());
|
|
675
|
+
if (props.authHeaders) Object.assign(headers, await props.authHeaders());
|
|
657
676
|
const res = await fetch(props.saveTextEndpoint, {
|
|
658
677
|
method: 'POST',
|
|
659
678
|
headers,
|
|
@@ -718,7 +737,10 @@ async function runOrchestration(open: boolean, url: string, k: string): Promise<
|
|
|
718
737
|
// kick off the Monaco load and swap it in once ready.
|
|
719
738
|
if (k === 'code') await highlightCode(rawText.value, lang);
|
|
720
739
|
ensureMonaco().then(async (m) => {
|
|
721
|
-
|
|
740
|
+
// ⚠ Record the "never coming" case here too. This branch returns early
|
|
741
|
+
// without ever reaching tryMountMonaco, so a build with no Monaco left
|
|
742
|
+
// the toolbar saying "Editor loading…" indefinitely.
|
|
743
|
+
if (!m) { monacoUnavailable.value = true; return; }
|
|
722
744
|
if (!props.open) return;
|
|
723
745
|
// Wait one tick so the placeholder DOM exists before swapping.
|
|
724
746
|
await new Promise<void>((r) => setTimeout(r, 0));
|
|
@@ -770,7 +792,7 @@ async function mountOnlyOfficeEditor(): Promise<void> {
|
|
|
770
792
|
|
|
771
793
|
try {
|
|
772
794
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
773
|
-
if (props.authHeaders) Object.assign(headers, props.authHeaders());
|
|
795
|
+
if (props.authHeaders) Object.assign(headers, await props.authHeaders());
|
|
774
796
|
const res = await fetch(props.onlyOfficeConfigEndpoint, {
|
|
775
797
|
method: 'POST',
|
|
776
798
|
headers,
|
|
@@ -932,6 +954,11 @@ function loadOnlyOfficeScript(base: string): Promise<void> {
|
|
|
932
954
|
placeholder="# Markdown buraya…"
|
|
933
955
|
/>
|
|
934
956
|
<div
|
|
957
|
+
v-if="markdownUnavailable"
|
|
958
|
+
class="fe-preview__md-split-output fe-preview__md"
|
|
959
|
+
>{{ t('viewer.peer_not_installed') }}</div>
|
|
960
|
+
<div
|
|
961
|
+
v-else
|
|
935
962
|
ref="markdownEl"
|
|
936
963
|
class="fe-preview__md-split-output fe-preview__md"
|
|
937
964
|
v-html="markdownHtml"
|
|
@@ -978,7 +1005,7 @@ function loadOnlyOfficeScript(base: string): Promise<void> {
|
|
|
978
1005
|
<div class="fe-preview__code-toolbar">
|
|
979
1006
|
<span class="fe-preview__code-lang">{{ codeLanguage }}</span>
|
|
980
1007
|
<span v-if="!monacoReady && codeHtml" class="fe-preview__code-status">
|
|
981
|
-
{{ saveTextEndpoint ? t('viewer.editor_loading') : t('viewer.read_only') }}
|
|
1008
|
+
{{ saveTextEndpoint && !monacoUnavailable ? t('viewer.editor_loading') : t('viewer.read_only') }}
|
|
982
1009
|
</span>
|
|
983
1010
|
<span v-if="saveOk" class="fe-preview__code-status fe-preview__code-status--ok">✓ {{ t('viewer.saved') }}</span>
|
|
984
1011
|
<span v-if="saveError" class="fe-preview__code-status fe-preview__code-status--err" :title="saveError">✗ {{ t('viewer.save_error') }}</span>
|
|
@@ -22,7 +22,7 @@ const props = defineProps<{
|
|
|
22
22
|
filePath?: string;
|
|
23
23
|
ext: string;
|
|
24
24
|
t?: (key: string) => string;
|
|
25
|
-
authHeaders?: () => Record<string, string
|
|
25
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
26
26
|
authCredentials?: RequestCredentials;
|
|
27
27
|
}>();
|
|
28
28
|
|
|
@@ -59,7 +59,7 @@ async function load(): Promise<void> {
|
|
|
59
59
|
credentials: props.authCredentials || 'same-origin',
|
|
60
60
|
headers: {
|
|
61
61
|
'Content-Type': 'application/json',
|
|
62
|
-
...(props.authHeaders ? props.authHeaders() : {}),
|
|
62
|
+
...(props.authHeaders ? await props.authHeaders() : {}),
|
|
63
63
|
},
|
|
64
64
|
body: JSON.stringify({ path: props.filePath }),
|
|
65
65
|
});
|
|
@@ -20,7 +20,7 @@ const props = defineProps<{
|
|
|
20
20
|
url: string;
|
|
21
21
|
ext: string;
|
|
22
22
|
t?: (key: string) => string;
|
|
23
|
-
authHeaders?: () => Record<string, string
|
|
23
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
24
24
|
authCredentials?: RequestCredentials;
|
|
25
25
|
}>();
|
|
26
26
|
|
|
@@ -80,7 +80,7 @@ async function load(): Promise<void> {
|
|
|
80
80
|
try {
|
|
81
81
|
text = await fetchViewerText({
|
|
82
82
|
url: props.url,
|
|
83
|
-
headers: props.authHeaders?.() ?? {},
|
|
83
|
+
headers: (await props.authHeaders?.()) ?? {},
|
|
84
84
|
credentials: props.authCredentials,
|
|
85
85
|
});
|
|
86
86
|
} catch (err) {
|
|
@@ -26,7 +26,7 @@ const props = defineProps<{
|
|
|
26
26
|
saveUrl?: string;
|
|
27
27
|
readOnly?: boolean;
|
|
28
28
|
t?: (key: string) => string;
|
|
29
|
-
authHeaders?: () => Record<string, string
|
|
29
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
30
30
|
authCredentials?: RequestCredentials;
|
|
31
31
|
}>();
|
|
32
32
|
|
|
@@ -71,7 +71,7 @@ async function loadXml(): Promise<void> {
|
|
|
71
71
|
try {
|
|
72
72
|
pendingXml = await fetchViewerText({
|
|
73
73
|
url: props.url,
|
|
74
|
-
headers: props.authHeaders?.() ?? {},
|
|
74
|
+
headers: (await props.authHeaders?.()) ?? {},
|
|
75
75
|
credentials: props.authCredentials,
|
|
76
76
|
});
|
|
77
77
|
} catch (err) {
|
|
@@ -90,7 +90,7 @@ async function persist(xml: string): Promise<void> {
|
|
|
90
90
|
try {
|
|
91
91
|
const headers: Record<string, string> = {
|
|
92
92
|
'Content-Type': 'application/json',
|
|
93
|
-
...(props.authHeaders?.() ?? {}),
|
|
93
|
+
...((await props.authHeaders?.()) ?? {}),
|
|
94
94
|
};
|
|
95
95
|
const res = await fetch(props.saveUrl, {
|
|
96
96
|
method: 'POST',
|
|
@@ -20,7 +20,7 @@ const props = defineProps<{
|
|
|
20
20
|
mime?: string;
|
|
21
21
|
ext: string;
|
|
22
22
|
t?: (key: string) => string;
|
|
23
|
-
authHeaders?: () => Record<string, string
|
|
23
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
24
24
|
authCredentials?: RequestCredentials;
|
|
25
25
|
}>();
|
|
26
26
|
|
|
@@ -63,7 +63,7 @@ async function load(): Promise<void> {
|
|
|
63
63
|
// and pass an ArrayBuffer instead.
|
|
64
64
|
let source: string | ArrayBuffer = props.url;
|
|
65
65
|
if (props.authHeaders) {
|
|
66
|
-
const headers = props.authHeaders();
|
|
66
|
+
const headers = await props.authHeaders();
|
|
67
67
|
const res = await fetch(props.url, {
|
|
68
68
|
headers,
|
|
69
69
|
credentials: props.authCredentials || 'same-origin',
|
|
@@ -21,7 +21,7 @@ const props = defineProps<{
|
|
|
21
21
|
url: string;
|
|
22
22
|
ext: string;
|
|
23
23
|
t?: (key: string) => string;
|
|
24
|
-
authHeaders?: () => Record<string, string
|
|
24
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
25
25
|
authCredentials?: RequestCredentials;
|
|
26
26
|
}>();
|
|
27
27
|
|
|
@@ -78,7 +78,7 @@ async function load(): Promise<void> {
|
|
|
78
78
|
try {
|
|
79
79
|
raw = await fetchViewerText({
|
|
80
80
|
url: props.url,
|
|
81
|
-
headers: props.authHeaders?.() ?? {},
|
|
81
|
+
headers: (await props.authHeaders?.()) ?? {},
|
|
82
82
|
credentials: props.authCredentials,
|
|
83
83
|
});
|
|
84
84
|
} catch (err) {
|
|
@@ -16,7 +16,7 @@ const props = defineProps<{
|
|
|
16
16
|
mime?: string;
|
|
17
17
|
ext: string;
|
|
18
18
|
t?: (key: string) => string;
|
|
19
|
-
authHeaders?: () => Record<string, string
|
|
19
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
20
20
|
authCredentials?: RequestCredentials;
|
|
21
21
|
}>();
|
|
22
22
|
|
|
@@ -69,7 +69,7 @@ async function load(): Promise<void> {
|
|
|
69
69
|
try {
|
|
70
70
|
src = await fetchViewerText({
|
|
71
71
|
url: props.url,
|
|
72
|
-
headers: props.authHeaders?.() ?? {},
|
|
72
|
+
headers: (await props.authHeaders?.()) ?? {},
|
|
73
73
|
credentials: props.authCredentials,
|
|
74
74
|
});
|
|
75
75
|
} catch (err) {
|
|
@@ -30,7 +30,7 @@ const props = defineProps<{
|
|
|
30
30
|
pdfWorkerUrl?: string;
|
|
31
31
|
pdfSaveUrl?: string;
|
|
32
32
|
t?: (key: string) => string;
|
|
33
|
-
authHeaders?: () => Record<string, string
|
|
33
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
34
34
|
authCredentials?: RequestCredentials;
|
|
35
35
|
}>();
|
|
36
36
|
|
|
@@ -116,7 +116,7 @@ async function load(): Promise<void> {
|
|
|
116
116
|
try {
|
|
117
117
|
const buf = await fetchViewerArrayBuffer({
|
|
118
118
|
url: props.url,
|
|
119
|
-
headers: props.authHeaders?.() ?? {},
|
|
119
|
+
headers: (await props.authHeaders?.()) ?? {},
|
|
120
120
|
credentials: props.authCredentials,
|
|
121
121
|
});
|
|
122
122
|
if (myToken !== renderToken) return;
|
|
@@ -301,7 +301,7 @@ async function saveAnnotations(): Promise<void> {
|
|
|
301
301
|
const base64 = btoa(String.fromCharCode(...data));
|
|
302
302
|
const headers: Record<string, string> = {
|
|
303
303
|
'Content-Type': 'application/json',
|
|
304
|
-
...(props.authHeaders?.() ?? {}),
|
|
304
|
+
...((await props.authHeaders?.()) ?? {}),
|
|
305
305
|
};
|
|
306
306
|
const res = await fetch(props.pdfSaveUrl, {
|
|
307
307
|
method: 'POST',
|
|
@@ -17,7 +17,7 @@ const props = defineProps<{
|
|
|
17
17
|
mime?: string;
|
|
18
18
|
ext: string;
|
|
19
19
|
t?: (key: string) => string;
|
|
20
|
-
authHeaders?: () => Record<string, string
|
|
20
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
21
21
|
authCredentials?: RequestCredentials;
|
|
22
22
|
}>();
|
|
23
23
|
|
|
@@ -82,7 +82,7 @@ async function load(): Promise<void> {
|
|
|
82
82
|
try {
|
|
83
83
|
const buf = await fetchViewerArrayBuffer({
|
|
84
84
|
url: props.url,
|
|
85
|
-
headers: props.authHeaders?.() ?? {},
|
|
85
|
+
headers: (await props.authHeaders?.()) ?? {},
|
|
86
86
|
credentials: props.authCredentials,
|
|
87
87
|
});
|
|
88
88
|
if (myToken !== renderToken) return;
|
|
@@ -16,7 +16,7 @@ const props = defineProps<{
|
|
|
16
16
|
mime?: string;
|
|
17
17
|
ext: string;
|
|
18
18
|
t?: (key: string) => string;
|
|
19
|
-
authHeaders?: () => Record<string, string
|
|
19
|
+
authHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
20
20
|
authCredentials?: RequestCredentials;
|
|
21
21
|
}>();
|
|
22
22
|
|
|
@@ -64,7 +64,7 @@ async function load(): Promise<void> {
|
|
|
64
64
|
try {
|
|
65
65
|
const buf = await fetchViewerArrayBuffer({
|
|
66
66
|
url: props.url,
|
|
67
|
-
headers: props.authHeaders?.() ?? {},
|
|
67
|
+
headers: (await props.authHeaders?.()) ?? {},
|
|
68
68
|
credentials: props.authCredentials,
|
|
69
69
|
});
|
|
70
70
|
if (myToken !== renderToken) return;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ArchiveViewer-CxdOyBhs.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>;\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 ? 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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CsvViewer-JG3nzTgf.js","sources":["../src/viewers/CsvViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * CsvViewer — lightweight CSV / TSV table preview.\n *\n * Lazy-imports `papaparse` when available (best-quality parser — handles\n * embedded quotes, multi-line cells, etc). Falls back to a simple\n * `split` parser when the peer is missing — workable for the trivial\n * \"first 1000 rows\" preview case but loses fidelity on complex files.\n *\n * UX:\n * - first-row-as-header toggle\n * - filter input that case-insensitively matches any cell\n * - 100-rows-per-page pagination\n * - tab vs comma auto-detection (manual override via toolbar)\n */\nimport { computed, onMounted, ref, watch } from 'vue';\nimport { fetchViewerText } from '../composables/useViewerFetch';\n\nconst props = defineProps<{\n url: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string>;\n authCredentials?: RequestCredentials;\n}>();\n\nconst PAGE_SIZE = 100;\nconst ROW_LIMIT = 1000;\n\nconst rows = ref<string[][]>([]);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst firstRowHeader = ref(true);\nconst filter = ref('');\nconst page = ref(1);\nconst detectedDelim = ref<',' | '\\t' | ';'>(',');\nconst userDelim = ref<'auto' | ',' | '\\t' | ';'>('auto');\n\nlet renderToken = 0;\n\nfunction detectDelimiter(sample: string): ',' | '\\t' | ';' {\n const sampleLines = sample.split(/\\r?\\n/).slice(0, 5).join('\\n');\n const tab = (sampleLines.match(/\\t/g) || []).length;\n const semi = (sampleLines.match(/;/g) || []).length;\n const comma = (sampleLines.match(/,/g) || []).length;\n if (tab > comma && tab > semi) return '\\t';\n if (semi > comma && semi > tab) return ';';\n return ',';\n}\n\nasync function parseWith(text: string, delim: string): Promise<string[][]> {\n try {\n const mod = await import(/* @vite-ignore */ 'papaparse');\n const Papa = (mod as any).default ?? mod;\n const result = Papa.parse(text, {\n delimiter: delim,\n skipEmptyLines: true,\n header: false,\n });\n return (result.data as string[][]).slice(0, ROW_LIMIT);\n } catch {\n // Fallback: naive line/column split. Loses quoted-comma support\n // but renders something reasonable.\n return text\n .split(/\\r?\\n/)\n .filter((l) => l.length > 0)\n .slice(0, ROW_LIMIT)\n .map((line) => line.split(delim));\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n rows.value = [];\n page.value = 1;\n const myToken = ++renderToken;\n\n let text: string;\n try {\n text = await fetchViewerText({\n url: props.url,\n headers: props.authHeaders?.() ?? {},\n credentials: props.authCredentials,\n });\n } catch (err) {\n error.value = err instanceof Error ? err.message : 'fetch failed';\n loading.value = false;\n return;\n }\n\n if (myToken !== renderToken) return;\n\n if (props.ext === 'tsv') {\n detectedDelim.value = '\\t';\n } else {\n detectedDelim.value = detectDelimiter(text.slice(0, 4096));\n }\n\n const delim = userDelim.value === 'auto' ? detectedDelim.value : userDelim.value;\n rows.value = await parseWith(text, delim);\n loading.value = false;\n}\n\nonMounted(load);\nwatch(() => props.url, load);\nwatch(() => userDelim.value, () => {\n if (rows.value.length === 0) return;\n // Re-parse with the new delimiter using the cached source — but we\n // didn't keep it. Cheapest path is a refetch.\n load();\n});\n\nconst headers = computed<string[]>(() => {\n if (!firstRowHeader.value || rows.value.length === 0) {\n if (rows.value.length === 0) return [];\n return rows.value[0].map((_, i) => `Col ${i + 1}`);\n }\n return rows.value[0];\n});\n\nconst dataRows = computed<string[][]>(() => {\n return firstRowHeader.value ? rows.value.slice(1) : rows.value;\n});\n\nconst filtered = computed<string[][]>(() => {\n const q = filter.value.trim().toLowerCase();\n if (!q) return dataRows.value;\n return dataRows.value.filter((r) =>\n r.some((c) => (c || '').toLowerCase().includes(q)),\n );\n});\n\nconst totalPages = computed(() =>\n Math.max(1, Math.ceil(filtered.value.length / PAGE_SIZE)),\n);\n\nconst visibleRows = computed<string[][]>(() => {\n const start = (page.value - 1) * PAGE_SIZE;\n return filtered.value.slice(start, start + PAGE_SIZE);\n});\n\nwatch(filter, () => {\n page.value = 1;\n});\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n</script>\n\n<template>\n <div class=\"filex-viewer-csv\">\n <div class=\"filex-viewer-csv__pane\">\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 <table v-else class=\"filex-viewer-csv__table\">\n <thead>\n <tr>\n <th class=\"filex-viewer-csv__rownum\">#</th>\n <th v-for=\"(h, i) in headers\" :key=\"i\">{{ h }}</th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, ri) in visibleRows\" :key=\"ri\">\n <td class=\"filex-viewer-csv__rownum\">{{ (page - 1) * 100 + ri + 1 }}</td>\n <td v-for=\"(c, ci) in row\" :key=\"ci\">{{ c }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n <div v-if=\"!loading && !error && totalPages > 1\" class=\"filex-viewer-csv__pager\">\n <button\n type=\"button\"\n class=\"filex-viewer-btn\"\n :disabled=\"page <= 1\"\n @click=\"page--\"\n >‹</button>\n <span class=\"filex-viewer-csv__pageno\">{{ page }} / {{ totalPages }} ({{ filtered.length }} satır)</span>\n <button\n type=\"button\"\n class=\"filex-viewer-btn\"\n :disabled=\"page >= totalPages\"\n @click=\"page++\"\n >›</button>\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-csv {\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-csv__bar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 12px;\n}\n.filex-viewer-csv__check {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n}\n.filex-viewer-csv__select,\n.filex-viewer-csv__filter {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: inherit;\n padding: 4px 8px;\n border-radius: 4px;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-csv__filter {\n flex: 0 1 240px;\n}\n.filex-viewer-spacer { flex: 1; }\n.filex-viewer-csv__count {\n font-size: 11px;\n color: var(--fe-text-muted, #5a6475);\n font-variant-numeric: tabular-nums;\n}\n.filex-viewer-csv__pane {\n flex: 1;\n overflow: auto;\n}\n.filex-viewer-csv__table {\n border-collapse: collapse;\n width: max-content;\n min-width: 100%;\n font-size: 12px;\n font-family: var(--fe-font-mono, monospace);\n}\n.filex-viewer-csv__table th,\n.filex-viewer-csv__table td {\n padding: 4px 10px;\n border: 1px solid var(--fe-border, #e2e6ed);\n white-space: nowrap;\n max-width: 320px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.filex-viewer-csv__table th {\n background: var(--fe-bg-elev, #f7f8fa);\n position: sticky;\n top: 0;\n font-weight: 600;\n text-align: left;\n}\n.filex-viewer-csv__table tbody tr:nth-child(even) {\n background: var(--fe-bg-hover, rgba(0, 0, 0, 0.02));\n}\n.filex-viewer-csv__rownum {\n color: var(--fe-text-muted, #5a6475);\n text-align: right;\n background: var(--fe-bg-elev, #f7f8fa);\n font-variant-numeric: tabular-nums;\n position: sticky;\n left: 0;\n}\n.filex-viewer-csv__pager {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 8px;\n padding: 6px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-top: 1px solid var(--fe-border, #e2e6ed);\n font-size: 12px;\n}\n.filex-viewer-csv__pageno {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\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.filex-viewer-btn {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-btn:hover:not(:disabled) {\n background: var(--fe-bg-hover, #edf0f5);\n}\n.filex-viewer-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n</style>\n"],"names":["PAGE_SIZE","ROW_LIMIT","props","__props","rows","ref","error","loading","firstRowHeader","filter","page","detectedDelim","userDelim","renderToken","detectDelimiter","sample","sampleLines","tab","semi","comma","parseWith","text","delim","mod","n","l","line","load","myToken","fetchViewerText","_a","err","onMounted","watch","headers","computed","_","i","dataRows","filtered","q","r","c","totalPages","visibleRows","start","tt","key","fallback","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_cache","_hoisted_4","_hoisted_5","_Fragment","_renderList","h","row","ri","_hoisted_6","_toDisplayString","ci","_hoisted_7","_hoisted_8","_hoisted_9","_hoisted_10"],"mappings":";;;;;;;;;;;;;;kFA0BMA,IAAY,KACZC,IAAY;;;;;;;;;;AATlB,UAAMC,IAAQC,GAWRC,IAAOC,EAAgB,EAAE,GACzBC,IAAQD,EAAmB,IAAI,GAC/BE,IAAUF,EAAI,EAAI,GAClBG,IAAiBH,EAAI,EAAI,GACzBI,IAASJ,EAAI,EAAE,GACfK,IAAOL,EAAI,CAAC,GACZM,IAAgBN,EAAsB,GAAG,GACzCO,IAAYP,EAA+B,MAAM;AAEvD,QAAIQ,IAAc;AAElB,aAASC,EAAgBC,GAAkC;AACzD,YAAMC,IAAcD,EAAO,MAAM,OAAO,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK;AAAA,CAAI,GACzDE,KAAOD,EAAY,MAAM,KAAK,KAAK,CAAA,GAAI,QACvCE,KAAQF,EAAY,MAAM,IAAI,KAAK,CAAA,GAAI,QACvCG,KAASH,EAAY,MAAM,IAAI,KAAK,CAAA,GAAI;AAC9C,aAAIC,IAAME,KAASF,IAAMC,IAAa,MAClCA,IAAOC,KAASD,IAAOD,IAAY,MAChC;AAAA,IACT;AAEA,mBAAeG,EAAUC,GAAcC,GAAoC;AACzE,UAAI;AACF,cAAMC,IAAM,MAAM;AAAA;AAAA,UAA0B;AAAA,QAAA,EAAA,KAAA,CAAAC,MAAAA,EAAA,CAAA;AAO5C,gBANcD,EAAY,WAAWA,GACjB,MAAMF,GAAM;AAAA,UAC9B,WAAWC;AAAA,UACX,gBAAgB;AAAA,UAChB,QAAQ;AAAA,QAAA,CACT,EACc,KAAoB,MAAM,GAAGrB,CAAS;AAAA,MACvD,QAAQ;AAGN,eAAOoB,EACJ,MAAM,OAAO,EACb,OAAO,CAACI,MAAMA,EAAE,SAAS,CAAC,EAC1B,MAAM,GAAGxB,CAAS,EAClB,IAAI,CAACyB,MAASA,EAAK,MAAMJ,CAAK,CAAC;AAAA,MACpC;AAAA,IACF;AAEA,mBAAeK,IAAsB;;AACnC,MAAApB,EAAQ,QAAQ,IAChBD,EAAM,QAAQ,MACdF,EAAK,QAAQ,CAAA,GACbM,EAAK,QAAQ;AACb,YAAMkB,IAAU,EAAEf;AAElB,UAAIQ;AACJ,UAAI;AACF,QAAAA,IAAO,MAAMQ,EAAgB;AAAA,UAC3B,KAAK3B,EAAM;AAAA,UACX,WAAS4B,IAAA5B,EAAM,gBAAN,gBAAA4B,EAAA,KAAA5B,OAAyB,CAAA;AAAA,UAClC,aAAaA,EAAM;AAAA,QAAA,CACpB;AAAA,MACH,SAAS6B,GAAK;AACZ,QAAAzB,EAAM,QAAQyB,aAAe,QAAQA,EAAI,UAAU,gBACnDxB,EAAQ,QAAQ;AAChB;AAAA,MACF;AAEA,UAAIqB,MAAYf,EAAa;AAE7B,MAAIX,EAAM,QAAQ,QAChBS,EAAc,QAAQ,MAEtBA,EAAc,QAAQG,EAAgBO,EAAK,MAAM,GAAG,IAAI,CAAC;AAG3D,YAAMC,IAAQV,EAAU,UAAU,SAASD,EAAc,QAAQC,EAAU;AAC3E,MAAAR,EAAK,QAAQ,MAAMgB,EAAUC,GAAMC,CAAK,GACxCf,EAAQ,QAAQ;AAAA,IAClB;AAEA,IAAAyB,EAAUL,CAAI,GACdM,EAAM,MAAM/B,EAAM,KAAKyB,CAAI,GAC3BM,EAAM,MAAMrB,EAAU,OAAO,MAAM;AACjC,MAAIR,EAAK,MAAM,WAAW,KAG1BuB,EAAA;AAAA,IACF,CAAC;AAED,UAAMO,IAAUC,EAAmB,MAC7B,CAAC3B,EAAe,SAASJ,EAAK,MAAM,WAAW,IAC7CA,EAAK,MAAM,WAAW,IAAU,CAAA,IAC7BA,EAAK,MAAM,CAAC,EAAE,IAAI,CAACgC,GAAGC,MAAM,OAAOA,IAAI,CAAC,EAAE,IAE5CjC,EAAK,MAAM,CAAC,CACpB,GAEKkC,IAAWH,EAAqB,MAC7B3B,EAAe,QAAQJ,EAAK,MAAM,MAAM,CAAC,IAAIA,EAAK,KAC1D,GAEKmC,IAAWJ,EAAqB,MAAM;AAC1C,YAAMK,IAAI/B,EAAO,MAAM,KAAA,EAAO,YAAA;AAC9B,aAAK+B,IACEF,EAAS,MAAM;AAAA,QAAO,CAACG,MAC5BA,EAAE,KAAK,CAACC,OAAOA,KAAK,IAAI,cAAc,SAASF,CAAC,CAAC;AAAA,MAAA,IAFpCF,EAAS;AAAA,IAI1B,CAAC,GAEKK,IAAaR;AAAA,MAAS,MAC1B,KAAK,IAAI,GAAG,KAAK,KAAKI,EAAS,MAAM,SAASvC,CAAS,CAAC;AAAA,IAAA,GAGpD4C,IAAcT,EAAqB,MAAM;AAC7C,YAAMU,KAASnC,EAAK,QAAQ,KAAKV;AACjC,aAAOuC,EAAS,MAAM,MAAMM,GAAOA,IAAQ7C,CAAS;AAAA,IACtD,CAAC;AAED,IAAAiC,EAAMxB,GAAQ,MAAM;AAClB,MAAAC,EAAK,QAAQ;AAAA,IACf,CAAC;AAED,aAASoC,EAAGC,GAAaC,GAA0B;AACjD,aAAO9C,EAAM,IAAIA,EAAM,EAAE6C,CAAG,IAAIC;AAAA,IAClC;sBAIEC,EAAA,GAAAC,EAwCM,OAxCNC,GAwCM;AAAA,MAvCJC,EAuBM,OAvBNC,GAuBM;AAAA,QAtBO/C,EAAA,SAAX2C,EAAA,GAAAC,EAGM,OAHNI,GAGM;AAAA,UAFJC,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAAH,EAAmD,QAAA,EAA7C,OAAM,8BAAA,GAA8B,MAAE,EAAA;AAAA,UAC5CA,EAAkB,aAAZ9C,EAAA,KAAK,GAAA,CAAA;AAAA,QAAA,MAEGC,EAAA,SAAhB0C,KAAAC,EAGM,OAHNM,GAGM;AAAA,UAFJD,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAAH,EAAkD,QAAA,EAA5C,OAAM,8BAAA,GAA8B,KAAC,EAAA;AAAA,UAC3CA,EAA6C,aAAvCN,EAAE,kBAAA,UAAA,CAAA,GAAA,CAAA;AAAA,QAAA,OAEVG,EAAA,GAAAC,EAaQ,SAbRO,GAaQ;AAAA,UAZNL,EAKQ,SAAA,MAAA;AAAA,YAJNA,EAGK,MAAA,MAAA;AAAA,cAFHG,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAAH,EAA2C,MAAA,EAAvC,OAAM,2BAAA,GAA2B,KAAC,EAAA;AAAA,eACtCH,EAAA,EAAA,GAAAC,EAAmDQ,GAAA,MAAAC,EAA9BzB,EAAA,OAAO,CAAhB0B,GAAGvB,OAAfY,EAAA,GAAAC,EAAmD,MAAA,EAApB,KAAKb,EAAA,KAAMuB,CAAC,GAAA,CAAA;;;UAG/CR,EAKQ,SAAA,MAAA;AAAA,aAJNH,EAAA,EAAA,GAAAC,EAGKQ,GAAA,MAAAC,EAHmBf,EAAA,OAAW,CAAvBiB,GAAKC,YAAjBZ,EAGK,MAAA,EAHiC,KAAKY,KAAE;AAAA,cAC3CV,EAAyE,MAAzEW,GAAyEC,GAAhCtD,EAAA,mBAAkBoD,IAAE,CAAA,GAAA,CAAA;AAAA,eAC7Db,EAAA,EAAA,GAAAC,EAAiDQ,GAAA,MAAAC,EAA3BE,GAAG,CAAb,GAAGI,OAAfhB,EAAA,GAAAC,EAAiD,MAAA,EAArB,KAAKe,EAAA,KAAO,CAAC,GAAA,CAAA;;;;;OAKrC1D,EAAA,SAAO,CAAKD,EAAA,SAASqC,EAAA,QAAU,KAA3CM,EAAA,GAAAC,EAcM,OAdNgB,GAcM;AAAA,QAbJd,EAKW,UAAA;AAAA,UAJT,MAAK;AAAA,UACL,OAAM;AAAA,UACL,UAAU1C,EAAA,SAAI;AAAA,UACd,gCAAOA,EAAA;AAAA,QAAA,GACT,KAAC,GAAAyD,CAAA;AAAA,QACFf,EAAyG,QAAzGgB,GAAyGJ,EAA/DtD,EAAA,KAAI,IAAG,QAAGsD,EAAGrB,EAAA,KAAU,IAAG,OAAEqB,EAAGzB,EAAA,MAAS,MAAM,IAAG,WAAO,CAAA;AAAA,QAClGa,EAKW,UAAA;AAAA,UAJT,MAAK;AAAA,UACL,OAAM;AAAA,UACL,UAAU1C,EAAA,SAAQiC,EAAA;AAAA,UAClB,gCAAOjC,EAAA;AAAA,QAAA,GACT,KAAC,GAAA2D,CAAA;AAAA,MAAA;;;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DrawioViewer-BEYLdtPG.js","sources":["../src/viewers/DrawioViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * DrawioViewer — embed diagrams.net via iframe + postMessage handshake.\n *\n * The diagrams.net embed mode (`?embed=1&proto=json`) handshakes via\n * window.postMessage:\n *\n * 1. iframe sends {event:'init'} ← bootstrap\n * 2. parent sends {action:'load', xml} ← our payload\n * 3. iframe sends {event:'save', xml} on save ← we POST back\n *\n * No external library required — diagrams.net hosts the entire editor\n * inside the iframe. We just fetch the source XML on mount and route\n * `save` events back to the configured `pdfSaveUrl`-style endpoint\n * (drawio uses the same shape: POST `{path, content}` body).\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { fetchViewerText } from '../composables/useViewerFetch';\n\nconst props = defineProps<{\n url: string;\n filePath: string;\n ext: string;\n drawioUrl?: string;\n /** Optional save endpoint. When unset the iframe stays read-only. */\n saveUrl?: string;\n readOnly?: boolean;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string>;\n authCredentials?: RequestCredentials;\n}>();\n\nconst iframeRef = ref<HTMLIFrameElement | null>(null);\nconst error = ref<string | null>(null);\nconst status = ref<'loading' | 'ready' | 'saving' | 'saved' | 'error'>('loading');\nconst readOnly = ref(!!props.readOnly || !props.saveUrl);\n\n// drawioUrl=null is the operator's \"off\" signal — FileExplorer wipes it\n// when the capabilities probe reports drawio offline. Don't silently fall\n// back to the public embed.diagrams.net iframe, that defeats the point of\n// gating: render a \"not configured\" pane instead so the user understands\n// why the editor isn't loading.\n//\n// `drawioBase` MUST be a computed (not a const), otherwise a stale null\n// at mount time (capabilities probe still inflight) gets cached for the\n// component's lifetime — leading to the spurious\n// \"viewer.drawio.disabled\" fallback after the capability flips to \"ok\".\nconst drawioBase = computed<string | null>(() =>\n props.drawioUrl ? props.drawioUrl.replace(/\\/$/, '') : null,\n);\nconst iframeSrc = computed<string>(() => {\n if (!drawioBase.value) return '';\n const params = new URLSearchParams({\n embed: '1',\n proto: 'json',\n spin: '1',\n saveAndExit: '0',\n noSaveBtn: readOnly.value ? '1' : '0',\n noExitBtn: '1',\n ui: 'kennedy',\n modified: 'unsavedChanges',\n });\n return `${drawioBase.value}/?${params.toString()}`;\n});\n\nlet pendingXml: string = '';\n\nasync function loadXml(): Promise<void> {\n status.value = 'loading';\n error.value = null;\n try {\n pendingXml = await fetchViewerText({\n url: props.url,\n headers: props.authHeaders?.() ?? {},\n credentials: props.authCredentials,\n });\n } catch (err) {\n error.value = err instanceof Error ? err.message : 'fetch failed';\n status.value = 'error';\n }\n}\n\nfunction send(msg: unknown): void {\n iframeRef.value?.contentWindow?.postMessage(JSON.stringify(msg), '*');\n}\n\nasync function persist(xml: string): Promise<void> {\n if (!props.saveUrl) return;\n status.value = 'saving';\n try {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(props.authHeaders?.() ?? {}),\n };\n const res = await fetch(props.saveUrl, {\n method: 'POST',\n headers,\n credentials: props.authCredentials || 'same-origin',\n body: JSON.stringify({ path: props.filePath, content: xml }),\n });\n if (!res.ok) {\n throw new Error(`${res.status} ${res.statusText}`);\n }\n status.value = 'saved';\n setTimeout(() => {\n if (status.value === 'saved') status.value = 'ready';\n }, 2500);\n } catch (err) {\n error.value = err instanceof Error ? err.message : 'save failed';\n status.value = 'error';\n }\n}\n\nfunction onMessage(ev: MessageEvent): void {\n if (typeof ev.data !== 'string' || ev.data.length === 0) return;\n // Drawio messages always start with '{' or '<'. Ignore anything else\n // (devtools / unrelated postMessage senders inside the same window).\n const first = ev.data[0];\n if (first !== '{' && first !== '<') return;\n let payload: any;\n try {\n payload = JSON.parse(ev.data);\n } catch {\n return;\n }\n if (!payload || typeof payload !== 'object') return;\n switch (payload.event) {\n case 'init':\n send({\n action: 'load',\n xml: pendingXml,\n autosave: 0,\n });\n status.value = 'ready';\n break;\n case 'save':\n if (typeof payload.xml === 'string') {\n persist(payload.xml);\n }\n break;\n case 'export':\n // Could persist a PNG/SVG render; out of V1 scope.\n break;\n case 'autosave':\n if (typeof payload.xml === 'string' && !readOnly.value) {\n persist(payload.xml);\n }\n break;\n default:\n break;\n }\n}\n\nfunction bootIfReady(): void {\n if (!drawioBase.value) {\n error.value = tt('viewer.drawio.disabled', 'Drawio (diagrams.net) yapılandırılmamış.');\n status.value = 'error';\n return;\n }\n // Clear a previous \"disabled\" error in case the prop just became\n // available (capability probe finished after mount).\n if (error.value === tt('viewer.drawio.disabled', 'Drawio (diagrams.net) yapılandırılmamış.')) {\n error.value = null;\n status.value = 'loading';\n }\n loadXml();\n}\n\nonMounted(() => {\n window.addEventListener('message', onMessage);\n bootIfReady();\n});\n\nwatch(() => props.drawioUrl, bootIfReady);\n\nonBeforeUnmount(() => {\n window.removeEventListener('message', onMessage);\n});\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n</script>\n\n<template>\n <div class=\"filex-viewer-drawio\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <span class=\"filex-viewer-fallback__icon\">📐</span>\n <p>{{ error }}</p>\n </div>\n <iframe\n v-else\n ref=\"iframeRef\"\n :src=\"iframeSrc\"\n class=\"filex-viewer-drawio__frame\"\n title=\"diagrams.net editor\"\n />\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-drawio {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 70vh;\n background: var(--fe-bg, #fff);\n}\n.filex-viewer-drawio__bar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 6px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 12px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-drawio__status[data-state=\"error\"] { color: var(--fe-danger, #dc2626); }\n.filex-viewer-drawio__status[data-state=\"saved\"] { color: #059669; }\n.filex-viewer-drawio__readonly {\n font-style: italic;\n margin-left: auto;\n}\n.filex-viewer-drawio__frame {\n flex: 1;\n width: 100%;\n border: 0;\n background: #fafafa;\n min-height: 0;\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","iframeRef","ref","error","status","readOnly","drawioBase","computed","iframeSrc","params","pendingXml","loadXml","fetchViewerText","_a","err","send","msg","_b","persist","xml","headers","res","onMessage","ev","first","payload","bootIfReady","tt","onMounted","watch","onBeforeUnmount","key","fallback","_openBlock","_createElementBlock","_hoisted_1","_hoisted_2","_cache","_createElementVNode"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"EpubViewer-BwUDmJm4.js","sources":["../src/viewers/EpubViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * EpubViewer — flowable EPUB reader via `epubjs`.\n *\n * Lazy-imports `epubjs` (~80 KB gzipped). epub.js renders the publication\n * inside an iframe (default `flow: 'paginated'`), but we use it in\n * scrolled-doc mode only when the user toggles the \"fit-width\" flag — by\n * default we keep paginated since that matches reader-app expectations.\n *\n * UX:\n * - prev/next page buttons (also keyboard arrow keys via composable)\n * - collapsible TOC sidebar (built from `book.loaded.navigation`)\n * - font size +/- (Themes API)\n * - graceful fallback if peer not installed\n */\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue';\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string>;\n authCredentials?: RequestCredentials;\n}>();\n\ninterface TocNode {\n href: string;\n label: string;\n subitems?: TocNode[];\n}\n\nconst root = ref<HTMLDivElement | null>(null);\nconst containerRef = ref<HTMLDivElement | null>(null);\nconst error = ref<string | null>(null);\nconst toc = ref<TocNode[]>([]);\nconst tocOpen = ref(false);\nconst fontSize = ref(100);\nconst ready = ref(false);\nconst loading = ref(true);\n\nlet book: any = null;\nlet rendition: any = null;\n\nasync function load(): Promise<void> {\n loading.value = true;\n ready.value = false;\n error.value = null;\n let mod: any = null;\n try {\n mod = await import(/* @vite-ignore */ 'epubjs');\n } catch {\n error.value = props.t\n ? props.t('viewer.peer_not_installed')\n : 'EPUB viewer requires `epubjs` — install or use download.';\n loading.value = false;\n return;\n }\n try {\n const Epub = mod.default ?? mod;\n // epub.js wants a URL (it then fetches with XHR). When the host\n // site requires auth headers we have to load the file ourselves\n // and pass an ArrayBuffer instead.\n let source: string | ArrayBuffer = props.url;\n if (props.authHeaders) {\n const headers = props.authHeaders();\n const res = await fetch(props.url, {\n headers,\n credentials: props.authCredentials || 'same-origin',\n });\n if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);\n source = await res.arrayBuffer();\n }\n book = Epub(source);\n if (!containerRef.value) {\n throw new Error('EPUB mount target missing');\n }\n rendition = book.renderTo(containerRef.value, {\n width: '100%',\n height: '100%',\n flow: 'paginated',\n manager: 'default',\n });\n await rendition.display();\n rendition.themes.fontSize(fontSize.value + '%');\n const nav = await book.loaded.navigation;\n toc.value = (nav?.toc ?? []) as TocNode[];\n ready.value = true;\n } catch (err) {\n error.value =\n err instanceof Error ? err.message : 'EPUB load failed';\n } finally {\n loading.value = false;\n }\n}\n\nfunction next(): void {\n rendition?.next?.();\n}\nfunction prev(): void {\n rendition?.prev?.();\n}\nfunction gotoHref(href: string): void {\n rendition?.display?.(href);\n tocOpen.value = false;\n}\nfunction bigger(): void {\n fontSize.value = Math.min(200, fontSize.value + 10);\n rendition?.themes?.fontSize(fontSize.value + '%');\n}\nfunction smaller(): void {\n fontSize.value = Math.max(60, fontSize.value - 10);\n rendition?.themes?.fontSize(fontSize.value + '%');\n}\n\nfunction onKey(ev: KeyboardEvent): void {\n if (ev.key === 'ArrowRight' || ev.key === 'PageDown') next();\n else if (ev.key === 'ArrowLeft' || ev.key === 'PageUp') prev();\n}\n\nonMounted(() => {\n load();\n window.addEventListener('keydown', onKey);\n});\n\nonBeforeUnmount(() => {\n window.removeEventListener('keydown', onKey);\n try {\n rendition?.destroy?.();\n } catch {\n /* ignore */\n }\n try {\n book?.destroy?.();\n } catch {\n /* ignore */\n }\n rendition = null;\n book = null;\n});\n\nwatch(\n () => props.url,\n () => {\n if (rendition) {\n try {\n rendition.destroy();\n } catch {\n /* ignore */\n }\n }\n if (book) {\n try {\n book.destroy();\n } catch {\n /* ignore */\n }\n }\n book = null;\n rendition = null;\n load();\n },\n);\n\nfunction tt(key: string, fallback: string): string {\n return props.t ? props.t(key) : fallback;\n}\n</script>\n\n<template>\n <div ref=\"root\" class=\"filex-viewer-epub\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <span class=\"filex-viewer-fallback__icon\">📖</span>\n <p>{{ error }}</p>\n </div>\n <template v-else>\n <div ref=\"containerRef\" class=\"filex-viewer-epub__rendition\" />\n <div v-if=\"ready\" class=\"filex-viewer-epub__nav\">\n <button type=\"button\" class=\"filex-viewer-btn\" @click=\"prev\" :disabled=\"!ready\">‹</button>\n <button type=\"button\" class=\"filex-viewer-btn\" @click=\"next\" :disabled=\"!ready\">›</button>\n </div>\n <div v-if=\"loading\" class=\"filex-viewer-epub__loading\">\n {{ tt('viewer.loading', 'Loading…') }}\n </div>\n </template>\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-epub__nav {\n position: absolute;\n bottom: 12px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n gap: 8px;\n padding: 4px 8px;\n background: var(--fe-bg-elev, rgba(255, 255, 255, 0.9));\n border: 1px solid var(--fe-border, #e2e6ed);\n border-radius: 6px;\n backdrop-filter: blur(4px);\n}\n.filex-viewer-epub {\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 position: relative;\n}\n.filex-viewer-epub__bar {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 12px;\n background: var(--fe-bg-elev, #f7f8fa);\n border-bottom: 1px solid var(--fe-border, #e2e6ed);\n font-size: 13px;\n}\n.filex-viewer-spacer { flex: 1; }\n.filex-viewer-epub__fs {\n display: inline-block;\n min-width: 40px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-epub__main {\n flex: 1;\n min-height: 0;\n display: flex;\n}\n.filex-viewer-epub__rendition {\n flex: 1;\n min-height: 0;\n}\n.filex-viewer-epub__toc {\n width: 260px;\n border-right: 1px solid var(--fe-border, #e2e6ed);\n overflow-y: auto;\n padding: 12px 0;\n background: var(--fe-bg-elev, #f7f8fa);\n}\n.filex-viewer-epub__toc ul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.filex-viewer-epub__toc-link {\n display: block;\n width: 100%;\n text-align: left;\n background: transparent;\n border: 0;\n padding: 6px 14px;\n font: inherit;\n color: inherit;\n cursor: pointer;\n}\n.filex-viewer-epub__toc-link:hover {\n background: var(--fe-bg-hover, #edf0f5);\n}\n.filex-viewer-epub__toc-link.is-child {\n padding-left: 28px;\n color: var(--fe-text-muted, #5a6475);\n font-size: 12px;\n}\n.filex-viewer-epub__loading {\n position: absolute;\n inset: auto 0 0 0;\n text-align: center;\n padding: 6px;\n background: rgba(0, 0, 0, 0.05);\n font-size: 12px;\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.filex-viewer-btn {\n border: 1px solid var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n color: var(--fe-text, #1a1e27);\n padding: 4px 10px;\n border-radius: 4px;\n cursor: pointer;\n font: inherit;\n font-size: 12px;\n}\n.filex-viewer-btn:hover:not(:disabled) {\n background: var(--fe-bg-hover, #edf0f5);\n}\n.filex-viewer-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n</style>\n"],"names":["props","__props","root","ref","containerRef","error","toc","fontSize","ready","loading","book","rendition","load","mod","Epub","source","headers","res","nav","err","next","_a","prev","onKey","ev","onMounted","onBeforeUnmount","_b","watch","tt","key","fallback","_createElementBlock","_openBlock","_hoisted_1","_cache","_createElementVNode","_Fragment","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_toDisplayString"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiBA,UAAMA,IAAQC,GAeRC,IAAOC,EAA2B,IAAI,GACtCC,IAAeD,EAA2B,IAAI,GAC9CE,IAAQF,EAAmB,IAAI,GAC/BG,IAAMH,EAAe,EAAE;AACb,IAAAA,EAAI,EAAK;AACzB,UAAMI,IAAWJ,EAAI,GAAG,GAClBK,IAAQL,EAAI,EAAK,GACjBM,IAAUN,EAAI,EAAI;AAExB,QAAIO,IAAY,MACZC,IAAiB;AAErB,mBAAeC,IAAsB;AACnC,MAAAH,EAAQ,QAAQ,IAChBD,EAAM,QAAQ,IACdH,EAAM,QAAQ;AACd,UAAIQ,IAAW;AACf,UAAI;AACF,QAAAA,IAAM,MAAM;AAAA;AAAA,UAA0B;AAAA,QAAA;AAAA,MACxC,QAAQ;AACN,QAAAR,EAAM,QAAQL,EAAM,IAChBA,EAAM,EAAE,2BAA2B,IACnC,4DACJS,EAAQ,QAAQ;AAChB;AAAA,MACF;AACA,UAAI;AACF,cAAMK,IAAOD,EAAI,WAAWA;AAI5B,YAAIE,IAA+Bf,EAAM;AACzC,YAAIA,EAAM,aAAa;AACrB,gBAAMgB,IAAUhB,EAAM,YAAA,GAChBiB,IAAM,MAAM,MAAMjB,EAAM,KAAK;AAAA,YACjC,SAAAgB;AAAA,YACA,aAAahB,EAAM,mBAAmB;AAAA,UAAA,CACvC;AACD,cAAI,CAACiB,EAAI,GAAI,OAAM,IAAI,MAAM,GAAGA,EAAI,MAAM,IAAIA,EAAI,UAAU,EAAE;AAC9D,UAAAF,IAAS,MAAME,EAAI,YAAA;AAAA,QACrB;AAEA,YADAP,IAAOI,EAAKC,CAAM,GACd,CAACX,EAAa;AAChB,gBAAM,IAAI,MAAM,2BAA2B;AAE7C,QAAAO,IAAYD,EAAK,SAASN,EAAa,OAAO;AAAA,UAC5C,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,QAAA,CACV,GACD,MAAMO,EAAU,QAAA,GAChBA,EAAU,OAAO,SAASJ,EAAS,QAAQ,GAAG;AAC9C,cAAMW,IAAM,MAAMR,EAAK,OAAO;AAC9B,QAAAJ,EAAI,SAASY,KAAA,gBAAAA,EAAK,QAAO,CAAA,GACzBV,EAAM,QAAQ;AAAA,MAChB,SAASW,GAAK;AACZ,QAAAd,EAAM,QACJc,aAAe,QAAQA,EAAI,UAAU;AAAA,MACzC,UAAA;AACE,QAAAV,EAAQ,QAAQ;AAAA,MAClB;AAAA,IACF;AAEA,aAASW,IAAa;;AACpB,OAAAC,IAAAV,KAAA,gBAAAA,EAAW,SAAX,QAAAU,EAAA,KAAAV;AAAA,IACF;AACA,aAASW,IAAa;;AACpB,OAAAD,IAAAV,KAAA,gBAAAA,EAAW,SAAX,QAAAU,EAAA,KAAAV;AAAA,IACF;AAcA,aAASY,EAAMC,GAAyB;AACtC,MAAIA,EAAG,QAAQ,gBAAgBA,EAAG,QAAQ,aAAYJ,EAAA,KAC7CI,EAAG,QAAQ,eAAeA,EAAG,QAAQ,aAAUF,EAAA;AAAA,IAC1D;AAEA,IAAAG,EAAU,MAAM;AACd,MAAAb,EAAA,GACA,OAAO,iBAAiB,WAAWW,CAAK;AAAA,IAC1C,CAAC,GAEDG,EAAgB,MAAM;;AACpB,aAAO,oBAAoB,WAAWH,CAAK;AAC3C,UAAI;AACF,SAAAF,IAAAV,KAAA,gBAAAA,EAAW,YAAX,QAAAU,EAAA,KAAAV;AAAA,MACF,QAAQ;AAAA,MAER;AACA,UAAI;AACF,SAAAgB,IAAAjB,KAAA,gBAAAA,EAAM,YAAN,QAAAiB,EAAA,KAAAjB;AAAA,MACF,QAAQ;AAAA,MAER;AACA,MAAAC,IAAY,MACZD,IAAO;AAAA,IACT,CAAC,GAEDkB;AAAA,MACE,MAAM5B,EAAM;AAAA,MACZ,MAAM;AACJ,YAAIW;AACF,cAAI;AACF,YAAAA,EAAU,QAAA;AAAA,UACZ,QAAQ;AAAA,UAER;AAEF,YAAID;AACF,cAAI;AACF,YAAAA,EAAK,QAAA;AAAA,UACP,QAAQ;AAAA,UAER;AAEF,QAAAA,IAAO,MACPC,IAAY,MACZC,EAAA;AAAA,MACF;AAAA,IAAA;AAGF,aAASiB,EAAGC,GAAaC,GAA0B;AACjD,aAAO/B,EAAM,IAAIA,EAAM,EAAE8B,CAAG,IAAIC;AAAA,IAClC;2BAIEC,EAeM,OAAA;AAAA,eAfG;AAAA,MAAJ,KAAI9B;AAAA,MAAO,OAAM;AAAA,IAAA;MACTG,EAAA,SAAX4B,EAAA,GAAAD,EAGM,OAHNE,GAGM;AAAA,QAFJC,EAAA,CAAA,MAAAA,EAAA,CAAA,IAAAC,EAAmD,QAAA,EAA7C,OAAM,8BAAA,GAA8B,MAAE,EAAA;AAAA,QAC5CA,EAAkB,aAAZ/B,EAAA,KAAK,GAAA,CAAA;AAAA,MAAA,YAEb2B,EASWK,GAAA,EAAA,KAAA,KAAA;AAAA,QARTD,EAA+D,OAAA;AAAA,mBAAtD;AAAA,UAAJ,KAAIhC;AAAA,UAAe,OAAM;AAAA,QAAA;QACnBI,EAAA,SAAXyB,EAAA,GAAAD,EAGM,OAHNM,GAGM;AAAA,UAFJF,EAA0F,UAAA;AAAA,YAAlF,MAAK;AAAA,YAAS,OAAM;AAAA,YAAoB,SAAOd;AAAA,YAAO,WAAWd,EAAA;AAAA,UAAA,GAAO,KAAC,GAAA+B,CAAA;AAAA,UACjFH,EAA0F,UAAA;AAAA,YAAlF,MAAK;AAAA,YAAS,OAAM;AAAA,YAAoB,SAAOhB;AAAA,YAAO,WAAWZ,EAAA;AAAA,UAAA,GAAO,KAAC,GAAAgC,CAAA;AAAA,QAAA;QAExE/B,EAAA,cAAXuB,EAEM,OAFNS,GAEMC,EADDb,EAAE,kBAAA,UAAA,CAAA,GAAA,CAAA;;;;;"}
|