@brftech/filex-core 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{ArchiveViewer-Brzg_0Xj.js → ArchiveViewer-CxdOyBhs.js} +35 -36
  2. package/dist/ArchiveViewer-CxdOyBhs.js.map +1 -0
  3. package/dist/{CsvViewer-T7W9mqZB.js → CsvViewer-JG3nzTgf.js} +52 -53
  4. package/dist/CsvViewer-JG3nzTgf.js.map +1 -0
  5. package/dist/{DrawioViewer-DReOOHRc.js → DrawioViewer-BEYLdtPG.js} +22 -23
  6. package/dist/{DrawioViewer-DReOOHRc.js.map → DrawioViewer-BEYLdtPG.js.map} +1 -1
  7. package/dist/{EpubViewer-yf9zJXXg.js → EpubViewer-BwUDmJm4.js} +31 -32
  8. package/dist/EpubViewer-BwUDmJm4.js.map +1 -0
  9. package/dist/{IpynbViewer-Dt_l00CD.js → IpynbViewer-CuHQoAHU.js} +35 -35
  10. package/dist/IpynbViewer-CuHQoAHU.js.map +1 -0
  11. package/dist/{MermaidViewer-Nbv27ana.js → MermaidViewer-DLmijDXo.js} +47 -48
  12. package/dist/MermaidViewer-DLmijDXo.js.map +1 -0
  13. package/dist/{PsdViewer-1nRpsuvz.js → PsdViewer-D-FM4eNi.js} +38 -39
  14. package/dist/PsdViewer-D-FM4eNi.js.map +1 -0
  15. package/dist/{TiffViewer-Djawe0Rq.js → TiffViewer-DOw4z_dO.js} +36 -37
  16. package/dist/TiffViewer-DOw4z_dO.js.map +1 -0
  17. package/dist/{Viewer3D-D7EtANiH.js → Viewer3D-CJJJgkuv.js} +15 -16
  18. package/dist/{Viewer3D-D7EtANiH.js.map → Viewer3D-CJJJgkuv.js.map} +1 -1
  19. package/dist/filex-core.js +11942 -66
  20. package/dist/filex-core.js.map +1 -1
  21. package/dist/filex-core.umd.cjs +29 -29
  22. package/dist/filex-core.umd.cjs.map +1 -1
  23. package/dist/style.css +1 -1
  24. package/package.json +1 -1
  25. package/src/components/PresenceBar.vue +13 -5
  26. package/src/components/RecentlyOpened.vue +9 -1
  27. package/src/components/StarButton.vue +9 -1
  28. package/src/components/TagPicker.vue +9 -1
  29. package/src/modals/ConvertModal.vue +10 -2
  30. package/src/modals/PermissionsModal.vue +9 -1
  31. package/src/viewers/ArchiveViewer.vue +9 -1
  32. package/src/viewers/CsvViewer.vue +9 -1
  33. package/src/viewers/DrawioViewer.vue +9 -1
  34. package/src/viewers/EpubViewer.vue +9 -1
  35. package/src/viewers/IpynbViewer.vue +9 -1
  36. package/src/viewers/MermaidViewer.vue +10 -2
  37. package/src/viewers/PdfViewer.vue +9 -1
  38. package/src/viewers/PsdViewer.vue +9 -1
  39. package/src/viewers/TiffViewer.vue +9 -1
  40. package/src/viewers/Viewer3D.vue +10 -2
  41. package/dist/ArchiveViewer-Brzg_0Xj.js.map +0 -1
  42. package/dist/CsvViewer-T7W9mqZB.js.map +0 -1
  43. package/dist/EpubViewer-yf9zJXXg.js.map +0 -1
  44. package/dist/IpynbViewer-Dt_l00CD.js.map +0 -1
  45. package/dist/MermaidViewer-Nbv27ana.js.map +0 -1
  46. package/dist/PsdViewer-1nRpsuvz.js.map +0 -1
  47. package/dist/TiffViewer-Djawe0Rq.js.map +0 -1
  48. package/dist/index-DeVgQCwW.js +0 -11951
  49. package/dist/index-DeVgQCwW.js.map +0 -1
@@ -193,7 +193,15 @@ function tt(key: string, fallback: string): string {
193
193
  </div>
194
194
  </template>
195
195
 
196
- <style scoped>
196
+ <style>
197
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
198
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
199
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
200
+ here silently stopped applying. Measured in the desktop app: the share
201
+ dialog had `position: static`, no background and no radius, i.e. raw
202
+ unstyled HTML, in EVERY embedded surface.
203
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
204
+ there is nothing here that can leak into a host page. */
197
205
  .filex-viewer-csv {
198
206
  display: flex;
199
207
  flex-direction: column;
@@ -198,7 +198,15 @@ function tt(key: string, fallback: string): string {
198
198
  </div>
199
199
  </template>
200
200
 
201
- <style scoped>
201
+ <style>
202
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
203
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
204
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
205
+ here silently stopped applying. Measured in the desktop app: the share
206
+ dialog had `position: static`, no background and no radius, i.e. raw
207
+ unstyled HTML, in EVERY embedded surface.
208
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
209
+ there is nothing here that can leak into a host page. */
202
210
  .filex-viewer-drawio {
203
211
  display: flex;
204
212
  flex-direction: column;
@@ -186,7 +186,15 @@ function tt(key: string, fallback: string): string {
186
186
  </div>
187
187
  </template>
188
188
 
189
- <style scoped>
189
+ <style>
190
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
191
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
192
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
193
+ here silently stopped applying. Measured in the desktop app: the share
194
+ dialog had `position: static`, no background and no radius, i.e. raw
195
+ unstyled HTML, in EVERY embedded surface.
196
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
197
+ there is nothing here that can leak into a host page. */
190
198
  .filex-viewer-epub__nav {
191
199
  position: absolute;
192
200
  bottom: 12px;
@@ -260,7 +260,15 @@ function tt(key: string, fallback: string): string {
260
260
  </div>
261
261
  </template>
262
262
 
263
- <style scoped>
263
+ <style>
264
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
265
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
266
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
267
+ here silently stopped applying. Measured in the desktop app: the share
268
+ dialog had `position: static`, no background and no radius, i.e. raw
269
+ unstyled HTML, in EVERY embedded surface.
270
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
271
+ there is nothing here that can leak into a host page. */
264
272
  .filex-viewer-ipynb {
265
273
  display: flex;
266
274
  flex-direction: column;
@@ -178,7 +178,15 @@ function tt(key: string, fallback: string): string {
178
178
  </div>
179
179
  </template>
180
180
 
181
- <style scoped>
181
+ <style>
182
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
183
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
184
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
185
+ here silently stopped applying. Measured in the desktop app: the share
186
+ dialog had `position: static`, no background and no radius, i.e. raw
187
+ unstyled HTML, in EVERY embedded surface.
188
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
189
+ there is nothing here that can leak into a host page. */
182
190
  .filex-viewer-mermaid {
183
191
  display: flex;
184
192
  flex-direction: column;
@@ -224,7 +232,7 @@ function tt(key: string, fallback: string): string {
224
232
  transition: transform 0.05s linear;
225
233
  user-select: none;
226
234
  }
227
- .filex-viewer-mermaid__surface :deep(svg) {
235
+ .filex-viewer-mermaid__surface svg {
228
236
  max-width: 90%;
229
237
  max-height: 90%;
230
238
  }
@@ -444,7 +444,15 @@ function tt(key: string, fallback: string): string {
444
444
  </div>
445
445
  </template>
446
446
 
447
- <style scoped>
447
+ <style>
448
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
449
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
450
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
451
+ here silently stopped applying. Measured in the desktop app: the share
452
+ dialog had `position: static`, no background and no radius, i.e. raw
453
+ unstyled HTML, in EVERY embedded surface.
454
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
455
+ there is nothing here that can leak into a host page. */
448
456
  .filex-viewer-pdf {
449
457
  display: flex;
450
458
  flex-direction: column;
@@ -154,7 +154,15 @@ function tt(key: string, fallback: string): string {
154
154
  </div>
155
155
  </template>
156
156
 
157
- <style scoped>
157
+ <style>
158
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
159
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
160
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
161
+ here silently stopped applying. Measured in the desktop app: the share
162
+ dialog had `position: static`, no background and no radius, i.e. raw
163
+ unstyled HTML, in EVERY embedded surface.
164
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
165
+ there is nothing here that can leak into a host page. */
158
166
  .filex-viewer-psd {
159
167
  display: flex;
160
168
  flex-direction: column;
@@ -180,7 +180,15 @@ function tt(key: string, fallback: string): string {
180
180
  </div>
181
181
  </template>
182
182
 
183
- <style scoped>
183
+ <style>
184
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
185
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
186
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
187
+ here silently stopped applying. Measured in the desktop app: the share
188
+ dialog had `position: static`, no background and no radius, i.e. raw
189
+ unstyled HTML, in EVERY embedded surface.
190
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
191
+ there is nothing here that can leak into a host page. */
184
192
  .filex-viewer-tiff__pager {
185
193
  position: absolute;
186
194
  bottom: 12px;
@@ -89,7 +89,15 @@ watch(() => props.url, () => {
89
89
  </div>
90
90
  </template>
91
91
 
92
- <style scoped>
92
+ <style>
93
+ /* ⚠ NOT `scoped`, deliberately. Vue's scoped styles compile to
94
+ `.cls[data-v-HASH]`, and in the web-component build the hash baked into
95
+ this CSS does not match the one Vue stamps onto the DOM — so every rule
96
+ here silently stopped applying. Measured in the desktop app: the share
97
+ dialog had `position: static`, no background and no radius, i.e. raw
98
+ unstyled HTML, in EVERY embedded surface.
99
+ Safe to drop: every selector below is prefixed (fx-/fe-/filex-), so
100
+ there is nothing here that can leak into a host page. */
93
101
  .filex-viewer-3d {
94
102
  width: 100%;
95
103
  height: 100%;
@@ -99,7 +107,7 @@ watch(() => props.url, () => {
99
107
  align-items: center;
100
108
  justify-content: center;
101
109
  }
102
- .filex-viewer-3d :deep(model-viewer) {
110
+ .filex-viewer-3d model-viewer {
103
111
  width: 100%;
104
112
  height: 100%;
105
113
  min-height: 480px;
@@ -1 +0,0 @@
1
- {"version":3,"file":"ArchiveViewer-Brzg_0Xj.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 scoped>\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-T7W9mqZB.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 scoped>\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":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"EpubViewer-yf9zJXXg.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 scoped>\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":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"IpynbViewer-Dt_l00CD.js","sources":["../src/viewers/IpynbViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * IpynbViewer — Jupyter notebook (.ipynb JSON) renderer.\n *\n * Pure-JS render — no peer dependency required:\n * - code cells → `<pre><code>` (highlight.js when available)\n * - markdown cells → markdown-it when available, plain `<pre>` otherwise\n * - output cells → text (stream / display_data 'text/plain'),\n * images (display_data 'image/png' base64),\n * HTML (display_data 'text/html', sanitized via\n * element-only insertion — no script tags)\n *\n * Source list / output count counters at the top so a 200-cell\n * notebook doesn't surprise the user.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { fetchViewerText } from '../composables/useViewerFetch';\nimport { ensureHighlight } from '../composables/useMonacoLoader';\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\ninterface Cell {\n cell_type: 'code' | 'markdown' | 'raw' | string;\n source: string | string[];\n outputs?: any[];\n execution_count?: number | null;\n metadata?: Record<string, unknown>;\n}\n\ninterface Notebook {\n cells: Cell[];\n metadata?: { kernelspec?: { language?: string; name?: string } };\n}\n\nconst cells = ref<Cell[]>([]);\nconst language = ref<string>('python');\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst renderedSources = ref<Map<number, string>>(new Map());\nconst renderedMarkdown = ref<Map<number, string>>(new Map());\n\nlet renderToken = 0;\nlet highlight: any = null;\nlet mdRenderer: any = null;\n\nasync function ensureMd(): Promise<any | null> {\n if (mdRenderer !== null) return mdRenderer || null;\n try {\n const mod = await import(/* @vite-ignore */ 'markdown-it');\n const Md = (mod as any).default ?? mod;\n mdRenderer = new Md({ html: false, linkify: true, breaks: true });\n return mdRenderer;\n } catch {\n mdRenderer = false;\n return null;\n }\n}\n\nfunction joinSource(source: string | string[]): string {\n return Array.isArray(source) ? source.join('') : (source ?? '');\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n cells.value = [];\n renderedSources.value = new Map();\n renderedMarkdown.value = new Map();\n const myToken = ++renderToken;\n\n let raw: string;\n try {\n raw = 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 let nb: Notebook;\n try {\n nb = JSON.parse(raw);\n } catch (err) {\n error.value = 'Invalid notebook JSON';\n loading.value = false;\n return;\n }\n cells.value = Array.isArray(nb.cells) ? nb.cells : [];\n language.value = nb.metadata?.kernelspec?.language || 'python';\n\n // Best-effort hljs highlight per code cell.\n highlight = (await ensureHighlight()) as any;\n if (myToken !== renderToken) return;\n if (highlight) {\n const hljs = highlight.default ?? highlight;\n const langOk = hljs.getLanguage?.(language.value) ? language.value : null;\n cells.value.forEach((cell, idx) => {\n if (cell.cell_type === 'code') {\n const text = joinSource(cell.source);\n try {\n const result = langOk\n ? hljs.highlight(text, { language: langOk, ignoreIllegals: true })\n : hljs.highlightAuto(text);\n renderedSources.value.set(idx, result.value);\n } catch {\n /* leave unrendered */\n }\n }\n });\n }\n\n const md = await ensureMd();\n if (myToken !== renderToken) return;\n if (md) {\n cells.value.forEach((cell, idx) => {\n if (cell.cell_type === 'markdown') {\n try {\n renderedMarkdown.value.set(idx, md.render(joinSource(cell.source)));\n } catch {\n /* leave plain */\n }\n }\n });\n }\n\n loading.value = false;\n}\n\nfunction outputText(out: any): string | null {\n if (!out) return null;\n if (out.output_type === 'stream') {\n return joinSource(out.text);\n }\n if (out.output_type === 'error') {\n return [out.ename, out.evalue, ...(out.traceback || [])]\n .filter(Boolean)\n .join('\\n');\n }\n if (out.output_type === 'execute_result' || out.output_type === 'display_data') {\n if (out.data?.['text/plain']) {\n return joinSource(out.data['text/plain']);\n }\n }\n return null;\n}\n\nfunction outputImage(out: any): string | null {\n if (!out?.data) return null;\n if (out.data['image/png']) return `data:image/png;base64,${out.data['image/png']}`;\n if (out.data['image/jpeg']) return `data:image/jpeg;base64,${out.data['image/jpeg']}`;\n if (out.data['image/svg+xml']) {\n const svg = joinSource(out.data['image/svg+xml']);\n return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;\n }\n return null;\n}\n\nfunction outputHtml(out: any): string | null {\n if (!out?.data) return null;\n if (out.data['text/html']) {\n const html = joinSource(out.data['text/html']);\n // Strip <script> for safety — outputs from arbitrary notebooks.\n return html.replace(/<script[\\s\\S]*?<\\/script>/gi, '');\n }\n return null;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n});\nwatch(() => props.url, load);\n\nconst stats = computed(() => {\n const codeCount = cells.value.filter((c) => c.cell_type === 'code').length;\n const mdCount = cells.value.filter((c) => c.cell_type === 'markdown').length;\n return { code: codeCount, md: mdCount, total: cells.value.length };\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-ipynb\">\n <div class=\"filex-viewer-ipynb__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 <div v-else>\n <div\n v-for=\"(cell, idx) in cells\"\n :key=\"idx\"\n class=\"filex-viewer-ipynb__cell\"\n :data-type=\"cell.cell_type\"\n >\n <template v-if=\"cell.cell_type === 'code'\">\n <div class=\"filex-viewer-ipynb__exec\">\n In [{{ cell.execution_count ?? ' ' }}]:\n </div>\n <pre class=\"filex-viewer-ipynb__src hljs\"><code\n v-if=\"renderedSources.get(idx)\"\n v-html=\"renderedSources.get(idx)\"\n /><code v-else>{{ joinSource(cell.source) }}</code></pre>\n <div v-if=\"cell.outputs && cell.outputs.length\" class=\"filex-viewer-ipynb__outputs\">\n <template v-for=\"(out, j) in cell.outputs\" :key=\"j\">\n <img\n v-if=\"outputImage(out)\"\n :src=\"outputImage(out) || ''\"\n class=\"filex-viewer-ipynb__img\"\n :alt=\"`output ${j}`\"\n />\n <div\n v-else-if=\"outputHtml(out)\"\n class=\"filex-viewer-ipynb__html\"\n v-html=\"outputHtml(out)\"\n />\n <pre\n v-else-if=\"outputText(out)\"\n class=\"filex-viewer-ipynb__out\"\n :data-type=\"out.output_type\"\n >{{ outputText(out) }}</pre>\n </template>\n </div>\n </template>\n\n <template v-else-if=\"cell.cell_type === 'markdown'\">\n <div\n v-if=\"renderedMarkdown.get(idx)\"\n class=\"filex-viewer-ipynb__md\"\n v-html=\"renderedMarkdown.get(idx)\"\n />\n <pre v-else class=\"filex-viewer-ipynb__md-raw\">{{ joinSource(cell.source) }}</pre>\n </template>\n\n <template v-else>\n <pre class=\"filex-viewer-ipynb__src\">{{ joinSource(cell.source) }}</pre>\n </template>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.filex-viewer-ipynb {\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-ipynb__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 color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-ipynb__lang {\n font-family: var(--fe-font-mono, monospace);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n.filex-viewer-ipynb__pane {\n flex: 1;\n overflow: auto;\n padding: 16px 24px;\n}\n.filex-viewer-ipynb__cell {\n border: 1px solid var(--fe-border, #e2e6ed);\n border-radius: 6px;\n margin-bottom: 16px;\n overflow: hidden;\n}\n.filex-viewer-ipynb__cell[data-type=\"code\"] {\n background: var(--fe-bg-elev, #f7f8fa);\n}\n.filex-viewer-ipynb__exec {\n padding: 4px 12px;\n font-family: var(--fe-font-mono, monospace);\n font-size: 11px;\n color: var(--fe-text-muted, #5a6475);\n background: rgba(0, 0, 0, 0.04);\n}\n.filex-viewer-ipynb__src {\n margin: 0;\n padding: 12px;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n overflow-x: auto;\n}\n.filex-viewer-ipynb__outputs {\n border-top: 1px dashed var(--fe-border, #e2e6ed);\n background: var(--fe-bg, #fff);\n padding: 8px 12px;\n}\n.filex-viewer-ipynb__out {\n margin: 0;\n padding: 4px 0;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n white-space: pre-wrap;\n word-wrap: break-word;\n}\n.filex-viewer-ipynb__out[data-type=\"error\"] {\n color: var(--fe-danger, #dc2626);\n}\n.filex-viewer-ipynb__img {\n display: block;\n max-width: 100%;\n height: auto;\n margin: 8px 0;\n}\n.filex-viewer-ipynb__html {\n font-size: 13px;\n}\n.filex-viewer-ipynb__md {\n padding: 12px 16px;\n font-size: 14px;\n line-height: 1.6;\n}\n.filex-viewer-ipynb__md-raw {\n padding: 12px;\n margin: 0;\n font-family: var(--fe-font-mono, monospace);\n font-size: 12px;\n white-space: pre-wrap;\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","cells","ref","language","error","loading","renderedSources","renderedMarkdown","renderToken","highlight","mdRenderer","ensureMd","mod","Md","joinSource","source","load","myToken","raw","fetchViewerText","_a","err","nb","_c","_b","ensureHighlight","hljs","langOk","_d","cell","idx","text","result","md","outputText","out","outputImage","svg","outputHtml","onMounted","onBeforeUnmount","watch","computed","codeCount","c","mdCount","tt","key","fallback","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_cache","_hoisted_4","_hoisted_5","_Fragment","_renderList","_hoisted_7","_hoisted_8","_hoisted_10","_toDisplayString","_hoisted_11","j","_hoisted_14","_hoisted_16","_hoisted_17"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MermaidViewer-Nbv27ana.js","sources":["../src/viewers/MermaidViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * MermaidViewer — render a `.mmd` / `.mermaid` source file.\n *\n * Lazy-imports the `mermaid` library (~150 KB gzipped). Source text is\n * fetched from `props.url` (with the auth headers from PreviewModal),\n * then rendered to SVG via `mermaid.render('id', src)`. The resulting\n * SVG node is inserted into a wrapper that supports pan + scale via\n * pure CSS transforms (no extra panzoom dep).\n */\nimport { onBeforeUnmount, onMounted, ref } from 'vue';\nimport { fetchViewerText } from '../composables/useViewerFetch';\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\nconst surface = ref<HTMLDivElement | null>(null);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst scale = ref(1);\nconst tx = ref(0);\nconst ty = ref(0);\n\nlet dragging = false;\nlet dragStart: { x: number; y: number; tx: number; ty: number } | null = null;\nlet renderToken = 0;\n\nlet cachedMermaid: any = null;\nasync function ensureMermaid(): Promise<any | null> {\n if (cachedMermaid) return cachedMermaid;\n try {\n const mod = await import(/* @vite-ignore */ 'mermaid');\n cachedMermaid = mod.default ?? mod;\n cachedMermaid.initialize?.({\n startOnLoad: false,\n securityLevel: 'strict',\n theme: window.matchMedia?.('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'default',\n });\n return cachedMermaid;\n } catch {\n return null;\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n const myToken = ++renderToken;\n\n const mermaid = await ensureMermaid();\n if (myToken !== renderToken) return;\n if (!mermaid) {\n error.value = props.t\n ? props.t('viewer.peer_not_installed')\n : 'Mermaid viewer requires `mermaid` — install or use download.';\n loading.value = false;\n return;\n }\n\n let src: string;\n try {\n src = 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 try {\n const id = `filex-mermaid-${Date.now()}`;\n const { svg } = await mermaid.render(id, src);\n if (myToken !== renderToken) return;\n if (surface.value) {\n surface.value.innerHTML = svg;\n const svgEl = surface.value.querySelector('svg');\n if (svgEl) {\n svgEl.style.maxWidth = '100%';\n svgEl.style.height = 'auto';\n }\n }\n } catch (err) {\n error.value = err instanceof Error ? err.message : 'render failed';\n } finally {\n loading.value = false;\n }\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.2, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n tx.value = 0;\n ty.value = 0;\n}\n\nfunction onWheel(ev: WheelEvent): void {\n if (!ev.ctrlKey && !ev.metaKey) return;\n ev.preventDefault();\n const delta = -ev.deltaY;\n const factor = delta > 0 ? 1.1 : 1 / 1.1;\n scale.value = Math.max(0.2, Math.min(8, scale.value * factor));\n}\n\nfunction onPointerDown(ev: PointerEvent): void {\n dragging = true;\n dragStart = { x: ev.clientX, y: ev.clientY, tx: tx.value, ty: ty.value };\n (ev.target as HTMLElement).setPointerCapture?.(ev.pointerId);\n}\nfunction onPointerMove(ev: PointerEvent): void {\n if (!dragging || !dragStart) return;\n tx.value = dragStart.tx + (ev.clientX - dragStart.x);\n ty.value = dragStart.ty + (ev.clientY - dragStart.y);\n}\nfunction onPointerUp(): void {\n dragging = false;\n dragStart = null;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\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-mermaid\">\n <div\n class=\"filex-viewer-mermaid__pane\"\n @wheel=\"onWheel\"\n @pointerdown=\"onPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerUp\"\n >\n <div\n v-if=\"error\"\n class=\"filex-viewer-fallback\"\n >\n <span class=\"filex-viewer-fallback__icon\">📊</span>\n <p>{{ error }}</p>\n </div>\n <div\n v-else-if=\"loading\"\n class=\"filex-viewer-fallback\"\n >\n <span class=\"filex-viewer-fallback__icon\">⏳</span>\n <p>{{ tt('viewer.loading', 'Loading…') }}</p>\n </div>\n <div\n v-show=\"!loading && !error\"\n ref=\"surface\"\n class=\"filex-viewer-mermaid__surface\"\n :style=\"{ transform: `translate(${tx}px, ${ty}px) scale(${scale})` }\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.filex-viewer-mermaid {\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-mermaid__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-mermaid__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-mermaid__pane {\n flex: 1;\n overflow: hidden;\n position: relative;\n cursor: grab;\n background: var(--fe-bg-elev, #f7f8fa);\n touch-action: none;\n}\n.filex-viewer-mermaid__pane:active {\n cursor: grabbing;\n}\n.filex-viewer-mermaid__surface {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n transform-origin: center center;\n transition: transform 0.05s linear;\n user-select: none;\n}\n.filex-viewer-mermaid__surface :deep(svg) {\n max-width: 90%;\n max-height: 90%;\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: var(--fe-text-muted, #5a6475);\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\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</style>\n"],"names":["props","__props","surface","ref","error","loading","scale","tx","ty","dragging","dragStart","renderToken","cachedMermaid","ensureMermaid","mod","_b","_a","load","myToken","mermaid","src","fetchViewerText","err","id","svg","svgEl","onWheel","ev","factor","onPointerDown","onPointerMove","onPointerUp","onMounted","onBeforeUnmount","tt","key","fallback","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_cache","_hoisted_3","_normalizeStyle","_vShow"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PsdViewer-1nRpsuvz.js","sources":["../src/viewers/PsdViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * PsdViewer — Photoshop document preview via `ag-psd`.\n *\n * Lazy-imports `ag-psd` (~150 KB). Renders the flattened composite to\n * a canvas via `psd.canvas`. A simple layer panel toggles the rendered\n * preview (V1: just lists names + visibility; full composite swap on\n * toggle requires re-rendering with `applyOpacity`/`composite` logic\n * — left as TODO for V2 since `ag-psd` doesn't ship a composite\n * pipeline of its own).\n */\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { fetchViewerArrayBuffer } from '../composables/useViewerFetch';\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 FlatLayer {\n index: number;\n name: string;\n hidden: boolean;\n}\n\nconst canvasEl = ref<HTMLCanvasElement | null>(null);\nconst layers = ref<FlatLayer[]>([]);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst showLayerPanel = ref(false);\nconst dims = ref<{ width: number; height: number }>({ width: 0, height: 0 });\nconst scale = ref(1);\n\nlet renderToken = 0;\n\nlet agPsd: any = null;\nasync function ensureAgPsd(): Promise<any | null> {\n if (agPsd) return agPsd;\n try {\n const mod = await import(/* @vite-ignore */ 'ag-psd');\n agPsd = mod;\n return agPsd;\n } catch {\n return null;\n }\n}\n\nfunction flattenLayers(node: any, list: FlatLayer[]): void {\n if (!node) return;\n if (Array.isArray(node.children)) {\n for (const child of node.children) {\n list.push({\n index: list.length,\n name: child.name ?? '(unnamed)',\n hidden: !!child.hidden,\n });\n if (child.children) flattenLayers(child, list);\n }\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n layers.value = [];\n const myToken = ++renderToken;\n\n const lib = await ensureAgPsd();\n if (myToken !== renderToken) return;\n if (!lib) {\n error.value = props.t\n ? props.t('viewer.peer_not_installed')\n : 'PSD viewer requires `ag-psd` — install or use download.';\n loading.value = false;\n return;\n }\n\n try {\n const buf = await fetchViewerArrayBuffer({\n url: props.url,\n headers: props.authHeaders?.() ?? {},\n credentials: props.authCredentials,\n });\n if (myToken !== renderToken) return;\n const psd = lib.readPsd(buf, {\n skipLayerImageData: false,\n skipCompositeImageData: false,\n skipThumbnail: true,\n });\n dims.value = { width: psd.width, height: psd.height };\n\n const composite = psd.canvas;\n if (composite && canvasEl.value) {\n const target = canvasEl.value;\n target.width = psd.width;\n target.height = psd.height;\n const ctx = target.getContext('2d');\n ctx?.drawImage(composite, 0, 0);\n } else {\n throw new Error('PSD has no composite image (skipCompositeImageData?)');\n }\n\n const flat: FlatLayer[] = [];\n flattenLayers(psd, flat);\n layers.value = flat;\n } catch (err) {\n error.value = err instanceof Error ? err.message : 'PSD decode failed';\n } finally {\n loading.value = false;\n }\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.1, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n});\nwatch(() => props.url, load);\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-psd\">\n <div class=\"filex-viewer-psd__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 <canvas\n v-show=\"!loading && !error\"\n ref=\"canvasEl\"\n />\n </div>\n </div>\n</template>\n\n<style scoped>\n.filex-viewer-psd {\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-psd__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-psd__dim {\n font-size: 12px;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-psd__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-psd__main {\n flex: 1;\n display: flex;\n min-height: 0;\n}\n.filex-viewer-psd__layers {\n width: 240px;\n border-right: 1px solid var(--fe-border, #e2e6ed);\n overflow-y: auto;\n padding: 8px 0;\n background: var(--fe-bg-elev, #f7f8fa);\n}\n.filex-viewer-psd__layers ul {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.filex-viewer-psd__layers li {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n font-size: 12px;\n}\n.filex-viewer-psd__layer-vis {\n width: 14px;\n text-align: center;\n color: var(--fe-text-muted, #5a6475);\n}\n.filex-viewer-psd__layer-vis[data-hidden=\"1\"] { color: var(--fe-border-strong, #c7ced9); }\n.filex-viewer-psd__layer-name {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.filex-viewer-psd__pane {\n flex: 1;\n overflow: auto;\n padding: 16px;\n background: #2a2d33;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n}\n.filex-viewer-psd__pane canvas {\n background: #fff\n repeating-conic-gradient(#e0e0e0 0% 25%, transparent 0% 50%) 50% / 16px 16px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: #c8cdd6;\n margin: auto;\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.is-active {\n background: var(--fe-bg-selected, #dfe8ff);\n border-color: var(--fe-primary, #3b82f6);\n}\n</style>\n"],"names":["props","__props","canvasEl","ref","layers","error","loading","dims","renderToken","agPsd","ensureAgPsd","flattenLayers","node","list","child","load","myToken","lib","buf","fetchViewerArrayBuffer","_a","psd","composite","target","ctx","flat","err","onMounted","onBeforeUnmount","watch","tt","key","fallback","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_cache","_hoisted_4","_vShow"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"TiffViewer-Djawe0Rq.js","sources":["../src/viewers/TiffViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * TiffViewer — multi-page TIFF preview via `utif`.\n *\n * Lazy-imports `utif` (~50 KB). Decodes IFDs once, paints the active\n * page to a `<canvas>` via `UTIF.toRGBA8`. Page navigation (1/N) +\n * zoom in/out controls. Browser <img> can't render TIFF natively, so\n * the canvas pipeline is the only option short of server-side\n * conversion.\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { fetchViewerArrayBuffer } from '../composables/useViewerFetch';\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\nconst canvasEl = ref<HTMLCanvasElement | null>(null);\nconst error = ref<string | null>(null);\nconst loading = ref(true);\nconst scale = ref(1);\nconst pageIndex = ref(0);\nconst pageCount = ref(0);\nconst pageDims = ref<{ width: number; height: number }>({ width: 0, height: 0 });\n\nlet UTIF: any = null;\nlet ifds: any[] | null = null;\nlet renderToken = 0;\n\nasync function ensureUtif(): Promise<any | null> {\n if (UTIF) return UTIF;\n try {\n const mod = await import(/* @vite-ignore */ 'utif');\n UTIF = mod.default ?? mod;\n return UTIF;\n } catch {\n return null;\n }\n}\n\nasync function load(): Promise<void> {\n loading.value = true;\n error.value = null;\n ifds = null;\n pageIndex.value = 0;\n pageCount.value = 0;\n const myToken = ++renderToken;\n\n const lib = await ensureUtif();\n if (myToken !== renderToken) return;\n if (!lib) {\n error.value = props.t\n ? props.t('viewer.peer_not_installed')\n : 'TIFF viewer requires `utif` — install or use download.';\n loading.value = false;\n return;\n }\n\n try {\n const buf = await fetchViewerArrayBuffer({\n url: props.url,\n headers: props.authHeaders?.() ?? {},\n credentials: props.authCredentials,\n });\n if (myToken !== renderToken) return;\n ifds = lib.decode(buf);\n pageCount.value = ifds?.length ?? 0;\n if (pageCount.value === 0) {\n throw new Error('No pages decoded');\n }\n paint();\n } catch (err) {\n error.value = err instanceof Error ? err.message : 'TIFF decode failed';\n } finally {\n loading.value = false;\n }\n}\n\nfunction paint(): void {\n if (!UTIF || !ifds || !canvasEl.value) return;\n const idx = Math.max(0, Math.min(pageIndex.value, ifds.length - 1));\n const ifd = ifds[idx];\n try {\n UTIF.decodeImage(undefined, ifd);\n } catch {\n // some images already decoded — UTIF.decodeImage is idempotent on\n // older versions.\n }\n const rgba = UTIF.toRGBA8(ifd);\n const w = ifd.width;\n const h = ifd.height;\n pageDims.value = { width: w, height: h };\n const canvas = canvasEl.value;\n canvas.width = w;\n canvas.height = h;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const imageData = ctx.createImageData(w, h);\n imageData.data.set(rgba);\n ctx.putImageData(imageData, 0, 0);\n}\n\nfunction next(): void {\n if (pageIndex.value < pageCount.value - 1) {\n pageIndex.value++;\n paint();\n }\n}\nfunction prev(): void {\n if (pageIndex.value > 0) {\n pageIndex.value--;\n paint();\n }\n}\nfunction zoomIn(): void {\n scale.value = Math.min(8, scale.value * 1.25);\n}\nfunction zoomOut(): void {\n scale.value = Math.max(0.1, scale.value / 1.25);\n}\nfunction reset(): void {\n scale.value = 1;\n}\n\nonMounted(load);\nonBeforeUnmount(() => {\n renderToken++;\n ifds = null;\n});\n\nwatch(() => props.url, load);\n\nconst pageLabel = computed(() => {\n const m = (props.t && props.t('viewer.page_n_of_m')) || 'Page {n} of {m}';\n return m.replace('{n}', String(pageIndex.value + 1)).replace('{m}', String(pageCount.value));\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-tiff\">\n <div class=\"filex-viewer-tiff__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 <canvas\n v-show=\"!loading && !error\"\n ref=\"canvasEl\"\n :style=\"{ imageRendering: 'pixelated' }\"\n />\n <div v-if=\"!loading && !error && pageCount > 1\" class=\"filex-viewer-tiff__pager\">\n <button\n type=\"button\"\n class=\"filex-viewer-btn\"\n :disabled=\"pageIndex === 0\"\n @click=\"prev\"\n >‹</button>\n <span class=\"filex-viewer-tiff__pages\">{{ pageLabel }}</span>\n <button\n type=\"button\"\n class=\"filex-viewer-btn\"\n :disabled=\"pageIndex >= pageCount - 1\"\n @click=\"next\"\n >›</button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.filex-viewer-tiff__pager {\n position: absolute;\n bottom: 12px;\n left: 50%;\n transform: translateX(-50%);\n display: flex;\n align-items: center;\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-tiff__pages {\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n}\n.filex-viewer-tiff {\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-tiff__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-tiff__pages {\n min-width: 110px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-tiff__zoom {\n min-width: 50px;\n text-align: center;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n}\n.filex-viewer-spacer { flex: 1; }\n.filex-viewer-tiff__pane {\n flex: 1;\n overflow: auto;\n padding: 16px;\n background: #2a2d33;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n}\n.filex-viewer-tiff__pane canvas {\n background: #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);\n}\n.filex-viewer-fallback {\n text-align: center;\n padding: 32px;\n color: #c8cdd6;\n margin: auto;\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":["props","__props","canvasEl","ref","error","loading","pageIndex","pageCount","pageDims","UTIF","ifds","renderToken","ensureUtif","mod","n","load","myToken","lib","buf","fetchViewerArrayBuffer","_a","paint","err","idx","ifd","rgba","w","h","canvas","ctx","imageData","next","prev","onMounted","onBeforeUnmount","watch","pageLabel","computed","tt","key","fallback","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_cache","_hoisted_4","_vShow","_hoisted_5","_hoisted_6","_hoisted_7","_toDisplayString","_hoisted_8"],"mappings":"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"}