@brftech/filex-core 0.40.0 → 0.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -25
- package/dist/ArchiveViewer-DxxjhYZD.js +124 -0
- package/dist/ArchiveViewer-DxxjhYZD.js.map +1 -0
- package/dist/CsvViewer-CyuwENI_.js +141 -0
- package/dist/CsvViewer-CyuwENI_.js.map +1 -0
- package/dist/{DrawioViewer-BNALOB04.js → DrawioViewer-BIYsTX97.js} +48 -42
- package/dist/DrawioViewer-BIYsTX97.js.map +1 -0
- package/dist/EpubViewer-K9PjCMmS.js +163 -0
- package/dist/EpubViewer-K9PjCMmS.js.map +1 -0
- package/dist/IpynbViewer-DYU6Zgz2.js +184 -0
- package/dist/IpynbViewer-DYU6Zgz2.js.map +1 -0
- package/dist/MermaidViewer-Bp73aId8.js +137 -0
- package/dist/MermaidViewer-Bp73aId8.js.map +1 -0
- package/dist/PsdViewer-C2eLivw1.js +122 -0
- package/dist/PsdViewer-C2eLivw1.js.map +1 -0
- package/dist/TiffViewer-DsHg5gcz.js +142 -0
- package/dist/TiffViewer-DsHg5gcz.js.map +1 -0
- package/dist/Viewer3D-C_dc2HN9.js +75 -0
- package/dist/Viewer3D-C_dc2HN9.js.map +1 -0
- package/dist/filex-core.js +218 -20361
- package/dist/filex-core.js.map +1 -1
- package/dist/filex-core.umd.cjs +61 -62
- package/dist/filex-core.umd.cjs.map +1 -1
- package/dist/index-vxMZpYb-.js +26700 -0
- package/dist/index-vxMZpYb-.js.map +1 -0
- package/dist/index.d.ts +4212 -862
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/FileExplorer.vue +2154 -506
- package/src/components/AdvancedSearch.vue +870 -0
- package/src/components/Breadcrumb.vue +191 -19
- package/src/components/CommandPalette.vue +66 -22
- package/src/components/ConnectionsPanel.vue +6 -2
- package/src/components/ContextMenu.vue +39 -2
- package/src/components/EncryptedFolderModal.vue +31 -25
- package/src/components/FilePane.vue +951 -0
- package/src/components/FilterBar.vue +568 -72
- package/src/components/GalleryView.vue +172 -56
- package/src/components/GridView.vue +331 -82
- package/src/components/HomeView.vue +331 -0
- package/src/components/InspectorPanel.vue +296 -53
- package/src/components/ListView.vue +1173 -194
- package/src/components/NFSExportsPanel.vue +7 -4
- package/src/components/OnboardingTour.vue +156 -33
- package/src/components/QuickLook.vue +26 -1
- package/src/components/RecentlyOpened.vue +17 -2
- package/src/components/S3KeysPanel.vue +7 -4
- package/src/components/SSHKeysPanel.vue +7 -4
- package/src/components/ShortcutSettings.vue +6 -2
- package/src/components/ShortcutsHelp.vue +4 -1
- package/src/components/SideNav.vue +206 -140
- package/src/components/StorageFields.vue +19 -1
- package/src/components/TabBar.vue +87 -1
- package/src/components/TagPicker.vue +77 -6
- package/src/components/ThemeGallery.vue +13 -198
- package/src/components/ThemePalette.vue +244 -0
- package/src/components/TimeZoneDialog.vue +135 -0
- package/src/components/TimeZonePicker.vue +696 -0
- package/src/components/TokensPanel.vue +35 -14
- package/src/components/Toolbar.vue +953 -465
- package/src/components/ViewSwitcher.vue +9 -4
- package/src/composables/useConnections.ts +35 -4
- package/src/composables/useExplorerTimeZone.ts +89 -0
- package/src/composables/useFileApi.ts +53 -4
- package/src/composables/useLocale.ts +382 -26
- package/src/composables/useRowTouch.ts +95 -0
- package/src/index.ts +177 -3
- package/src/lib/actionIcons.ts +330 -0
- package/src/lib/advSearch.ts +162 -0
- package/src/lib/dateGroups.ts +197 -0
- package/src/lib/destinationTree.ts +235 -0
- package/src/lib/downloadSelection.ts +154 -0
- package/src/lib/fileFilters.ts +319 -12
- package/src/lib/fileIcons.ts +299 -5
- package/src/lib/filePreview.ts +605 -0
- package/src/lib/listing.ts +72 -0
- package/src/lib/shareTtl.ts +31 -2
- package/src/lib/sortOrder.ts +459 -0
- package/src/lib/themes.ts +151 -116
- package/src/lib/timezone.ts +501 -0
- package/src/lib/uiProfile.ts +82 -0
- package/src/lib/viewPrefs.ts +1036 -0
- package/src/locales/en.ts +416 -26
- package/src/locales/tr.ts +409 -26
- package/src/modals/ConvertModal.vue +23 -3
- package/src/modals/DestinationPickerModal.vue +341 -0
- package/src/modals/Modal.vue +18 -4
- package/src/modals/NewDocumentModal.vue +525 -0
- package/src/modals/NewFolderModal.vue +15 -9
- package/src/modals/PermissionsModal.vue +557 -476
- package/src/modals/PreviewModal.vue +516 -156
- package/src/styles/base.css +5409 -8
- package/src/styles/variables.css +139 -65
- package/src/types/Connections.ts +9 -0
- package/src/types/ExplorerConfig.ts +144 -39
- package/src/types/FileNode.ts +28 -0
- package/src/viewers/ArchiveViewer.vue +46 -8
- package/src/viewers/CsvViewer.vue +19 -2
- package/src/viewers/DrawioViewer.vue +12 -1
- package/src/viewers/EpubViewer.vue +50 -6
- package/src/viewers/IpynbViewer.vue +19 -2
- package/src/viewers/MermaidViewer.vue +20 -3
- package/src/viewers/PdfViewer.vue +24 -4
- package/src/viewers/PsdViewer.vue +28 -4
- package/src/viewers/TiffViewer.vue +19 -2
- package/src/viewers/Viewer3D.vue +36 -2
- package/dist/ArchiveViewer-Cdfv9LFC.js +0 -99
- package/dist/ArchiveViewer-Cdfv9LFC.js.map +0 -1
- package/dist/CsvViewer-CqWeV8VO.js +0 -131
- package/dist/CsvViewer-CqWeV8VO.js.map +0 -1
- package/dist/DrawioViewer-BNALOB04.js.map +0 -1
- package/dist/EpubViewer-BPmlImG7.js +0 -145
- package/dist/EpubViewer-BPmlImG7.js.map +0 -1
- package/dist/IpynbViewer-CDlQmuCV.js +0 -175
- package/dist/IpynbViewer-CDlQmuCV.js.map +0 -1
- package/dist/MermaidViewer-sfZWdR-d.js +0 -127
- package/dist/MermaidViewer-sfZWdR-d.js.map +0 -1
- package/dist/PsdViewer-DwuySZsb.js +0 -112
- package/dist/PsdViewer-DwuySZsb.js.map +0 -1
- package/dist/TiffViewer-BDQ3d3CQ.js +0 -132
- package/dist/TiffViewer-BDQ3d3CQ.js.map +0 -1
- package/dist/Viewer3D-CJJJgkuv.js +0 -60
- package/dist/Viewer3D-CJJJgkuv.js.map +0 -1
- package/src/components/SecondaryPane.vue +0 -447
- package/src/modals/ShareModal.vue +0 -139
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DrawioViewer-BIYsTX97.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 { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\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> | Promise<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: (await 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 ...((await 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', 'diagrams.net is not configured for this filex instance.');\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', 'diagrams.net is not configured for this filex instance.')) {\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\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div class=\"filex-viewer-drawio\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></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","typeTile","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_hoisted_2","_createElementVNode"],"mappings":"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"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { defineComponent as H, ref as r, onMounted as M, onBeforeUnmount as S, watch as F, computed as $, openBlock as u, createElementBlock as c, createElementVNode as d, toDisplayString as k, Fragment as R, createCommentVNode as E } from "vue";
|
|
2
|
+
import { f as V } from "./index-vxMZpYb-.js";
|
|
3
|
+
const z = {
|
|
4
|
+
key: 0,
|
|
5
|
+
class: "filex-viewer-fallback"
|
|
6
|
+
}, A = ["innerHTML"], D = {
|
|
7
|
+
key: 0,
|
|
8
|
+
class: "filex-viewer-epub__nav"
|
|
9
|
+
}, I = ["disabled"], N = ["disabled"], j = {
|
|
10
|
+
key: 1,
|
|
11
|
+
class: "filex-viewer-epub__loading"
|
|
12
|
+
}, T = 15e3, O = /* @__PURE__ */ H({
|
|
13
|
+
__name: "EpubViewer",
|
|
14
|
+
props: {
|
|
15
|
+
url: {},
|
|
16
|
+
mime: {},
|
|
17
|
+
ext: {},
|
|
18
|
+
t: { type: Function },
|
|
19
|
+
authHeaders: { type: Function },
|
|
20
|
+
authCredentials: {}
|
|
21
|
+
},
|
|
22
|
+
setup(P) {
|
|
23
|
+
const l = P, B = r(null), y = r(null), o = r(null), U = r([]);
|
|
24
|
+
r(!1);
|
|
25
|
+
const C = r(100), i = r(!1), f = r(!0);
|
|
26
|
+
let a = null, e = null;
|
|
27
|
+
function p(t, n) {
|
|
28
|
+
let s;
|
|
29
|
+
return Promise.race([
|
|
30
|
+
t,
|
|
31
|
+
new Promise((v, _) => {
|
|
32
|
+
s = setTimeout(
|
|
33
|
+
() => _(new Error(`EPUB ${n} timed out after ${T}ms`)),
|
|
34
|
+
T
|
|
35
|
+
);
|
|
36
|
+
})
|
|
37
|
+
]).finally(() => clearTimeout(s));
|
|
38
|
+
}
|
|
39
|
+
async function m() {
|
|
40
|
+
f.value = !0, i.value = !1, o.value = null;
|
|
41
|
+
let t = null;
|
|
42
|
+
try {
|
|
43
|
+
t = await import(
|
|
44
|
+
/* @vite-ignore */
|
|
45
|
+
"epubjs"
|
|
46
|
+
);
|
|
47
|
+
} catch {
|
|
48
|
+
o.value = l.t ? l.t("viewer.peer_not_installed") : "EPUB viewer requires `epubjs` — install or use download.", f.value = !1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const n = t.default ?? t;
|
|
53
|
+
let s = l.url;
|
|
54
|
+
if (l.authHeaders) {
|
|
55
|
+
const _ = await l.authHeaders(), w = await fetch(l.url, {
|
|
56
|
+
headers: _,
|
|
57
|
+
credentials: l.authCredentials || "same-origin"
|
|
58
|
+
});
|
|
59
|
+
if (!w.ok) throw new Error(`${w.status} ${w.statusText}`);
|
|
60
|
+
s = await w.arrayBuffer();
|
|
61
|
+
}
|
|
62
|
+
if (a = n(s), !y.value)
|
|
63
|
+
throw new Error("EPUB mount target missing");
|
|
64
|
+
e = a.renderTo(y.value, {
|
|
65
|
+
width: "100%",
|
|
66
|
+
height: "100%",
|
|
67
|
+
flow: "paginated",
|
|
68
|
+
manager: "default"
|
|
69
|
+
}), await p(e.display(), "display"), e.themes.fontSize(C.value + "%");
|
|
70
|
+
const v = await p(a.loaded.navigation, "navigation");
|
|
71
|
+
U.value = (v == null ? void 0 : v.toc) ?? [], i.value = !0;
|
|
72
|
+
} catch (n) {
|
|
73
|
+
console.warn("[filex] EPUB load failed:", n), o.value = b("viewer.failed_to_load", "Failed to load file");
|
|
74
|
+
} finally {
|
|
75
|
+
f.value = !1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function h() {
|
|
79
|
+
var t;
|
|
80
|
+
(t = e == null ? void 0 : e.next) == null || t.call(e);
|
|
81
|
+
}
|
|
82
|
+
function g() {
|
|
83
|
+
var t;
|
|
84
|
+
(t = e == null ? void 0 : e.prev) == null || t.call(e);
|
|
85
|
+
}
|
|
86
|
+
function x(t) {
|
|
87
|
+
t.key === "ArrowRight" || t.key === "PageDown" ? h() : (t.key === "ArrowLeft" || t.key === "PageUp") && g();
|
|
88
|
+
}
|
|
89
|
+
M(() => {
|
|
90
|
+
m(), window.addEventListener("keydown", x);
|
|
91
|
+
}), S(() => {
|
|
92
|
+
var t, n;
|
|
93
|
+
window.removeEventListener("keydown", x);
|
|
94
|
+
try {
|
|
95
|
+
(t = e == null ? void 0 : e.destroy) == null || t.call(e);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
(n = a == null ? void 0 : a.destroy) == null || n.call(a);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
e = null, a = null;
|
|
103
|
+
}), F(
|
|
104
|
+
() => l.url,
|
|
105
|
+
() => {
|
|
106
|
+
if (e)
|
|
107
|
+
try {
|
|
108
|
+
e.destroy();
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
if (a)
|
|
112
|
+
try {
|
|
113
|
+
a.destroy();
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
a = null, e = null, m();
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
function b(t, n) {
|
|
120
|
+
return l.t ? l.t(t) : n;
|
|
121
|
+
}
|
|
122
|
+
const L = $(() => V({ type: "file", extension: l.ext }));
|
|
123
|
+
return (t, n) => (u(), c("div", {
|
|
124
|
+
ref_key: "root",
|
|
125
|
+
ref: B,
|
|
126
|
+
class: "filex-viewer-epub"
|
|
127
|
+
}, [
|
|
128
|
+
o.value ? (u(), c("div", z, [
|
|
129
|
+
d("span", {
|
|
130
|
+
class: "filex-viewer-fallback__icon",
|
|
131
|
+
"aria-hidden": "true",
|
|
132
|
+
innerHTML: L.value
|
|
133
|
+
}, null, 8, A),
|
|
134
|
+
d("p", null, k(o.value), 1)
|
|
135
|
+
])) : (u(), c(R, { key: 1 }, [
|
|
136
|
+
d("div", {
|
|
137
|
+
ref_key: "containerRef",
|
|
138
|
+
ref: y,
|
|
139
|
+
class: "filex-viewer-epub__rendition"
|
|
140
|
+
}, null, 512),
|
|
141
|
+
i.value ? (u(), c("div", D, [
|
|
142
|
+
d("button", {
|
|
143
|
+
type: "button",
|
|
144
|
+
class: "filex-viewer-btn",
|
|
145
|
+
onClick: g,
|
|
146
|
+
disabled: !i.value
|
|
147
|
+
}, "‹", 8, I),
|
|
148
|
+
d("button", {
|
|
149
|
+
type: "button",
|
|
150
|
+
class: "filex-viewer-btn",
|
|
151
|
+
onClick: h,
|
|
152
|
+
disabled: !i.value
|
|
153
|
+
}, "›", 8, N)
|
|
154
|
+
])) : E("", !0),
|
|
155
|
+
f.value ? (u(), c("div", j, k(b("viewer.loading", "Loading…")), 1)) : E("", !0)
|
|
156
|
+
], 64))
|
|
157
|
+
], 512));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
export {
|
|
161
|
+
O as default
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=EpubViewer-K9PjCMmS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EpubViewer-K9PjCMmS.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 { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\n\nconst props = defineProps<{\n url: string;\n mime?: string;\n ext: string;\n t?: (key: string) => string;\n authHeaders?: () => Record<string, string> | Promise<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\n/**\n * How long an epub.js step may hang before we call it a failure.\n *\n * Generous on purpose: a large publication on a slow disk legitimately takes\n * seconds, and a false \"cannot open\" on a book that would have rendered is a\n * worse bug than the one this guards against.\n */\nconst EPUB_STEP_TIMEOUT_MS = 15000;\n\n/** Reject if `p` has not settled within `EPUB_STEP_TIMEOUT_MS`. */\nfunction withTimeout<T>(p: Promise<T>, label: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n return Promise.race([\n p,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error(`EPUB ${label} timed out after ${EPUB_STEP_TIMEOUT_MS}ms`)),\n EPUB_STEP_TIMEOUT_MS,\n );\n }),\n ]).finally(() => clearTimeout(timer)) as Promise<T>;\n}\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 = await 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 // ⚠⚠ Both of these have to be raced against a clock. epub.js resolves\n // `display()` and `loaded.navigation` from its own internal promises, and\n // when the archive is not a readable EPUB it simply NEVER settles —\n // neither resolve nor reject — so the `catch` below can't fire and\n // `finally` never runs. Measured 2026-09-13 on an 8-byte `handbook.epub`:\n // the pane sat on \"Yükleniyor…\" forever with no error anywhere.\n await withTimeout(rendition.display(), 'display');\n rendition.themes.fontSize(fontSize.value + '%');\n // `<any>` explicitly: `book` is `any`, so without it `T` infers as `{}`\n // and `nav.toc` stops type-checking.\n const nav = await withTimeout<any>(book.loaded.navigation, 'navigation');\n toc.value = (nav?.toc ?? []) as TocNode[];\n ready.value = true;\n } catch (err) {\n // The reader gets the localized sentence; epub.js's own wording (and the\n // timeout marker) goes to the console for whoever is debugging.\n console.warn('[filex] EPUB load failed:', err);\n error.value = tt('viewer.failed_to_load', 'Failed to load file');\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\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\n</script>\n\n<template>\n <div ref=\"root\" class=\"filex-viewer-epub\">\n <div v-if=\"error\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></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":["EPUB_STEP_TIMEOUT_MS","props","__props","root","ref","containerRef","error","toc","fontSize","ready","loading","book","rendition","withTimeout","p","label","timer","_","reject","load","mod","Epub","source","headers","res","nav","err","tt","next","_a","prev","onKey","ev","onMounted","onBeforeUnmount","_b","watch","key","fallback","typeTile","computed","fileIconTile","_createElementBlock","_openBlock","_hoisted_1","_createElementVNode","_Fragment","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_toDisplayString"],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { defineComponent as U, ref as _, onMounted as D, onBeforeUnmount as q, watch as z, computed as E, openBlock as n, createElementBlock as a, createElementVNode as d, toDisplayString as p, unref as G, Fragment as m, renderList as B, createCommentVNode as N } from "vue";
|
|
2
|
+
import { f as K, a as P, e as Q } from "./index-vxMZpYb-.js";
|
|
3
|
+
import { f as W } from "./useViewerFetch-czqbd2Lj.js";
|
|
4
|
+
const X = { class: "filex-viewer-ipynb" }, Y = { class: "filex-viewer-ipynb__pane" }, Z = {
|
|
5
|
+
key: 0,
|
|
6
|
+
class: "filex-viewer-fallback"
|
|
7
|
+
}, ee = ["innerHTML"], te = {
|
|
8
|
+
key: 1,
|
|
9
|
+
class: "filex-viewer-fallback"
|
|
10
|
+
}, ne = ["innerHTML"], ae = { key: 2 }, re = ["data-type"], le = { class: "filex-viewer-ipynb__exec" }, ie = { class: "filex-viewer-ipynb__src hljs" }, se = ["innerHTML"], ue = { key: 1 }, ce = {
|
|
11
|
+
key: 0,
|
|
12
|
+
class: "filex-viewer-ipynb__outputs"
|
|
13
|
+
}, oe = ["src", "alt"], de = ["innerHTML"], pe = ["data-type"], _e = ["innerHTML"], fe = {
|
|
14
|
+
key: 1,
|
|
15
|
+
class: "filex-viewer-ipynb__md-raw"
|
|
16
|
+
}, ve = {
|
|
17
|
+
key: 2,
|
|
18
|
+
class: "filex-viewer-ipynb__src"
|
|
19
|
+
}, me = /* @__PURE__ */ U({
|
|
20
|
+
__name: "IpynbViewer",
|
|
21
|
+
props: {
|
|
22
|
+
url: {},
|
|
23
|
+
ext: {},
|
|
24
|
+
t: { type: Function },
|
|
25
|
+
authHeaders: { type: Function },
|
|
26
|
+
authCredentials: {}
|
|
27
|
+
},
|
|
28
|
+
setup(V) {
|
|
29
|
+
const s = V, c = _([]), M = _("python"), f = _(null), v = _(!0), w = _(/* @__PURE__ */ new Map()), k = _(/* @__PURE__ */ new Map());
|
|
30
|
+
let y = 0, x = null, h = null;
|
|
31
|
+
async function $() {
|
|
32
|
+
if (h !== null) return h || null;
|
|
33
|
+
try {
|
|
34
|
+
const e = await import(
|
|
35
|
+
/* @vite-ignore */
|
|
36
|
+
"markdown-it"
|
|
37
|
+
), r = e.default ?? e;
|
|
38
|
+
return h = new r({ html: !1, linkify: !0, breaks: !0 }), h;
|
|
39
|
+
} catch {
|
|
40
|
+
return h = !1, null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function u(e) {
|
|
44
|
+
return Array.isArray(e) ? e.join("") : e ?? "";
|
|
45
|
+
}
|
|
46
|
+
async function T() {
|
|
47
|
+
var i, g, C, S;
|
|
48
|
+
v.value = !0, f.value = null, c.value = [], w.value = /* @__PURE__ */ new Map(), k.value = /* @__PURE__ */ new Map();
|
|
49
|
+
const e = ++y;
|
|
50
|
+
let r;
|
|
51
|
+
try {
|
|
52
|
+
r = await W({
|
|
53
|
+
url: s.url,
|
|
54
|
+
headers: await ((i = s.authHeaders) == null ? void 0 : i.call(s)) ?? {},
|
|
55
|
+
credentials: s.authCredentials
|
|
56
|
+
});
|
|
57
|
+
} catch (l) {
|
|
58
|
+
f.value = l instanceof Error ? l.message : "fetch failed", v.value = !1;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (e !== y) return;
|
|
62
|
+
let t;
|
|
63
|
+
try {
|
|
64
|
+
t = JSON.parse(r);
|
|
65
|
+
} catch {
|
|
66
|
+
f.value = "Invalid notebook JSON", v.value = !1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (c.value = Array.isArray(t.cells) ? t.cells : [], M.value = ((C = (g = t.metadata) == null ? void 0 : g.kernelspec) == null ? void 0 : C.language) || "python", x = await Q(), e !== y) return;
|
|
70
|
+
if (x) {
|
|
71
|
+
const l = x.default ?? x, b = (S = l.getLanguage) != null && S.call(l, M.value) ? M.value : null;
|
|
72
|
+
c.value.forEach((j, J) => {
|
|
73
|
+
if (j.cell_type === "code") {
|
|
74
|
+
const A = u(j.source);
|
|
75
|
+
try {
|
|
76
|
+
const R = b ? l.highlight(A, { language: b, ignoreIllegals: !0 }) : l.highlightAuto(A);
|
|
77
|
+
w.value.set(J, R.value);
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const o = await $();
|
|
84
|
+
e === y && (o && c.value.forEach((l, b) => {
|
|
85
|
+
if (l.cell_type === "markdown")
|
|
86
|
+
try {
|
|
87
|
+
k.value.set(b, o.render(u(l.source)));
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}), v.value = !1);
|
|
91
|
+
}
|
|
92
|
+
function H(e) {
|
|
93
|
+
var r;
|
|
94
|
+
return e ? e.output_type === "stream" ? u(e.text) : e.output_type === "error" ? [e.ename, e.evalue, ...e.traceback || []].filter(Boolean).join(`
|
|
95
|
+
`) : (e.output_type === "execute_result" || e.output_type === "display_data") && (r = e.data) != null && r["text/plain"] ? u(e.data["text/plain"]) : null : null;
|
|
96
|
+
}
|
|
97
|
+
function L(e) {
|
|
98
|
+
if (!(e != null && e.data)) return null;
|
|
99
|
+
if (e.data["image/png"]) return `data:image/png;base64,${e.data["image/png"]}`;
|
|
100
|
+
if (e.data["image/jpeg"]) return `data:image/jpeg;base64,${e.data["image/jpeg"]}`;
|
|
101
|
+
if (e.data["image/svg+xml"]) {
|
|
102
|
+
const r = u(e.data["image/svg+xml"]);
|
|
103
|
+
return `data:image/svg+xml;utf8,${encodeURIComponent(r)}`;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function I(e) {
|
|
108
|
+
return e != null && e.data && e.data["text/html"] ? u(e.data["text/html"]).replace(/<script[\s\S]*?<\/script>/gi, "") : null;
|
|
109
|
+
}
|
|
110
|
+
D(T), q(() => {
|
|
111
|
+
y++;
|
|
112
|
+
}), z(() => s.url, T), E(() => {
|
|
113
|
+
const e = c.value.filter((t) => t.cell_type === "code").length, r = c.value.filter((t) => t.cell_type === "markdown").length;
|
|
114
|
+
return { code: e, md: r, total: c.value.length };
|
|
115
|
+
});
|
|
116
|
+
function F(e, r) {
|
|
117
|
+
return s.t ? s.t(e) : r;
|
|
118
|
+
}
|
|
119
|
+
const O = E(() => K({ type: "file", extension: s.ext }));
|
|
120
|
+
return (e, r) => (n(), a("div", X, [
|
|
121
|
+
d("div", Y, [
|
|
122
|
+
f.value ? (n(), a("div", Z, [
|
|
123
|
+
d("span", {
|
|
124
|
+
class: "filex-viewer-fallback__icon",
|
|
125
|
+
"aria-hidden": "true",
|
|
126
|
+
innerHTML: O.value
|
|
127
|
+
}, null, 8, ee),
|
|
128
|
+
d("p", null, p(f.value), 1)
|
|
129
|
+
])) : v.value ? (n(), a("div", te, [
|
|
130
|
+
d("span", {
|
|
131
|
+
class: "filex-viewer-fallback__icon filex-viewer-fallback__icon--spin",
|
|
132
|
+
"aria-hidden": "true",
|
|
133
|
+
innerHTML: G(P)("progress")
|
|
134
|
+
}, null, 8, ne),
|
|
135
|
+
d("p", null, p(F("viewer.loading", "Loading…")), 1)
|
|
136
|
+
])) : (n(), a("div", ae, [
|
|
137
|
+
(n(!0), a(m, null, B(c.value, (t, o) => (n(), a("div", {
|
|
138
|
+
key: o,
|
|
139
|
+
class: "filex-viewer-ipynb__cell",
|
|
140
|
+
"data-type": t.cell_type
|
|
141
|
+
}, [
|
|
142
|
+
t.cell_type === "code" ? (n(), a(m, { key: 0 }, [
|
|
143
|
+
d("div", le, " In [" + p(t.execution_count ?? " ") + "]: ", 1),
|
|
144
|
+
d("pre", ie, [
|
|
145
|
+
w.value.get(o) ? (n(), a("code", {
|
|
146
|
+
key: 0,
|
|
147
|
+
innerHTML: w.value.get(o)
|
|
148
|
+
}, null, 8, se)) : (n(), a("code", ue, p(u(t.source)), 1))
|
|
149
|
+
]),
|
|
150
|
+
t.outputs && t.outputs.length ? (n(), a("div", ce, [
|
|
151
|
+
(n(!0), a(m, null, B(t.outputs, (i, g) => (n(), a(m, { key: g }, [
|
|
152
|
+
L(i) ? (n(), a("img", {
|
|
153
|
+
key: 0,
|
|
154
|
+
src: L(i) || "",
|
|
155
|
+
class: "filex-viewer-ipynb__img",
|
|
156
|
+
alt: `output ${g}`
|
|
157
|
+
}, null, 8, oe)) : I(i) ? (n(), a("div", {
|
|
158
|
+
key: 1,
|
|
159
|
+
class: "filex-viewer-ipynb__html",
|
|
160
|
+
innerHTML: I(i)
|
|
161
|
+
}, null, 8, de)) : H(i) ? (n(), a("pre", {
|
|
162
|
+
key: 2,
|
|
163
|
+
class: "filex-viewer-ipynb__out",
|
|
164
|
+
"data-type": i.output_type
|
|
165
|
+
}, p(H(i)), 9, pe)) : N("", !0)
|
|
166
|
+
], 64))), 128))
|
|
167
|
+
])) : N("", !0)
|
|
168
|
+
], 64)) : t.cell_type === "markdown" ? (n(), a(m, { key: 1 }, [
|
|
169
|
+
k.value.get(o) ? (n(), a("div", {
|
|
170
|
+
key: 0,
|
|
171
|
+
class: "filex-viewer-ipynb__md",
|
|
172
|
+
innerHTML: k.value.get(o)
|
|
173
|
+
}, null, 8, _e)) : (n(), a("pre", fe, p(u(t.source)), 1))
|
|
174
|
+
], 64)) : (n(), a("pre", ve, p(u(t.source)), 1))
|
|
175
|
+
], 8, re))), 128))
|
|
176
|
+
]))
|
|
177
|
+
])
|
|
178
|
+
]));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
export {
|
|
182
|
+
me as default
|
|
183
|
+
};
|
|
184
|
+
//# sourceMappingURL=IpynbViewer-DYU6Zgz2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IpynbViewer-DYU6Zgz2.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 { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\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> | Promise<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: (await 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\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\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 <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div v-else-if=\"loading\" class=\"filex-viewer-fallback\">\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></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>\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-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","typeTile","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_3","_hoisted_5","_unref","actionIconSvg","_hoisted_7","_Fragment","_renderList","_hoisted_9","_hoisted_10","_hoisted_12","_toDisplayString","_hoisted_13","j","_hoisted_16","_hoisted_18","_hoisted_19"],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { defineComponent as E, ref as u, onMounted as V, onBeforeUnmount as $, computed as B, openBlock as y, createElementBlock as w, createElementVNode as c, toDisplayString as k, unref as I, createCommentVNode as Y, withDirectives as q, normalizeStyle as z, vShow as F } from "vue";
|
|
2
|
+
import { f as K, a as N } from "./index-vxMZpYb-.js";
|
|
3
|
+
import { f as U } from "./useViewerFetch-czqbd2Lj.js";
|
|
4
|
+
const W = { class: "filex-viewer-mermaid" }, X = {
|
|
5
|
+
key: 0,
|
|
6
|
+
class: "filex-viewer-fallback"
|
|
7
|
+
}, O = ["innerHTML"], j = {
|
|
8
|
+
key: 1,
|
|
9
|
+
class: "filex-viewer-fallback"
|
|
10
|
+
}, A = ["innerHTML"], R = /* @__PURE__ */ E({
|
|
11
|
+
__name: "MermaidViewer",
|
|
12
|
+
props: {
|
|
13
|
+
url: {},
|
|
14
|
+
mime: {},
|
|
15
|
+
ext: {},
|
|
16
|
+
t: { type: Function },
|
|
17
|
+
authHeaders: { type: Function },
|
|
18
|
+
authCredentials: {}
|
|
19
|
+
},
|
|
20
|
+
setup(M) {
|
|
21
|
+
const n = M, d = u(null), r = u(null), s = u(!0), m = u(1), v = u(0), p = u(0);
|
|
22
|
+
let h = !1, l = null, f = 0, i = null;
|
|
23
|
+
async function T() {
|
|
24
|
+
var e, t;
|
|
25
|
+
if (i) return i;
|
|
26
|
+
try {
|
|
27
|
+
const a = await import(
|
|
28
|
+
/* @vite-ignore */
|
|
29
|
+
"mermaid"
|
|
30
|
+
);
|
|
31
|
+
return i = a.default ?? a, (t = i.initialize) == null || t.call(i, {
|
|
32
|
+
startOnLoad: !1,
|
|
33
|
+
securityLevel: "strict",
|
|
34
|
+
theme: (e = window.matchMedia) != null && e.call(window, "(prefers-color-scheme: dark)").matches ? "dark" : "default"
|
|
35
|
+
}), i;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function L() {
|
|
41
|
+
var g;
|
|
42
|
+
s.value = !0, r.value = null;
|
|
43
|
+
const e = ++f, t = await T();
|
|
44
|
+
if (e !== f) return;
|
|
45
|
+
if (!t) {
|
|
46
|
+
r.value = n.t ? n.t("viewer.peer_not_installed") : "Mermaid viewer requires `mermaid` — install or use download.", s.value = !1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let a;
|
|
50
|
+
try {
|
|
51
|
+
a = await U({
|
|
52
|
+
url: n.url,
|
|
53
|
+
headers: await ((g = n.authHeaders) == null ? void 0 : g.call(n)) ?? {},
|
|
54
|
+
credentials: n.authCredentials
|
|
55
|
+
});
|
|
56
|
+
} catch (o) {
|
|
57
|
+
r.value = o instanceof Error ? o.message : "fetch failed", s.value = !1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (e === f)
|
|
61
|
+
try {
|
|
62
|
+
const o = `filex-mermaid-${Date.now()}`, { svg: D } = await t.render(o, a);
|
|
63
|
+
if (e !== f) return;
|
|
64
|
+
if (d.value) {
|
|
65
|
+
d.value.innerHTML = D;
|
|
66
|
+
const _ = d.value.querySelector("svg");
|
|
67
|
+
_ && (_.style.maxWidth = "100%", _.style.height = "auto");
|
|
68
|
+
}
|
|
69
|
+
} catch (o) {
|
|
70
|
+
r.value = o instanceof Error ? o.message : "render failed";
|
|
71
|
+
} finally {
|
|
72
|
+
s.value = !1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function P(e) {
|
|
76
|
+
if (!e.ctrlKey && !e.metaKey) return;
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
const a = -e.deltaY > 0 ? 1.1 : 1 / 1.1;
|
|
79
|
+
m.value = Math.max(0.2, Math.min(8, m.value * a));
|
|
80
|
+
}
|
|
81
|
+
function H(e) {
|
|
82
|
+
var t, a;
|
|
83
|
+
h = !0, l = { x: e.clientX, y: e.clientY, tx: v.value, ty: p.value }, (a = (t = e.target).setPointerCapture) == null || a.call(t, e.pointerId);
|
|
84
|
+
}
|
|
85
|
+
function S(e) {
|
|
86
|
+
!h || !l || (v.value = l.tx + (e.clientX - l.x), p.value = l.ty + (e.clientY - l.y));
|
|
87
|
+
}
|
|
88
|
+
function x() {
|
|
89
|
+
h = !1, l = null;
|
|
90
|
+
}
|
|
91
|
+
V(L), $(() => {
|
|
92
|
+
f++;
|
|
93
|
+
});
|
|
94
|
+
function b(e, t) {
|
|
95
|
+
return n.t ? n.t(e) : t;
|
|
96
|
+
}
|
|
97
|
+
const C = B(() => K({ type: "file", extension: n.ext }));
|
|
98
|
+
return (e, t) => (y(), w("div", W, [
|
|
99
|
+
c("div", {
|
|
100
|
+
class: "filex-viewer-mermaid__pane",
|
|
101
|
+
onWheel: P,
|
|
102
|
+
onPointerdown: H,
|
|
103
|
+
onPointermove: S,
|
|
104
|
+
onPointerup: x,
|
|
105
|
+
onPointercancel: x
|
|
106
|
+
}, [
|
|
107
|
+
r.value ? (y(), w("div", X, [
|
|
108
|
+
c("span", {
|
|
109
|
+
class: "filex-viewer-fallback__icon",
|
|
110
|
+
"aria-hidden": "true",
|
|
111
|
+
innerHTML: C.value
|
|
112
|
+
}, null, 8, O),
|
|
113
|
+
c("p", null, k(r.value), 1)
|
|
114
|
+
])) : s.value ? (y(), w("div", j, [
|
|
115
|
+
c("span", {
|
|
116
|
+
class: "filex-viewer-fallback__icon filex-viewer-fallback__icon--spin",
|
|
117
|
+
"aria-hidden": "true",
|
|
118
|
+
innerHTML: I(N)("progress")
|
|
119
|
+
}, null, 8, A),
|
|
120
|
+
c("p", null, k(b("viewer.loading", "Loading…")), 1)
|
|
121
|
+
])) : Y("", !0),
|
|
122
|
+
q(c("div", {
|
|
123
|
+
ref_key: "surface",
|
|
124
|
+
ref: d,
|
|
125
|
+
class: "filex-viewer-mermaid__surface",
|
|
126
|
+
style: z({ transform: `translate(${v.value}px, ${p.value}px) scale(${m.value})` })
|
|
127
|
+
}, null, 4), [
|
|
128
|
+
[F, !s.value && !r.value]
|
|
129
|
+
])
|
|
130
|
+
], 32)
|
|
131
|
+
]));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
export {
|
|
135
|
+
R as default
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=MermaidViewer-Bp73aId8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MermaidViewer-Bp73aId8.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 { computed, onBeforeUnmount, onMounted, ref } from 'vue';\nimport { actionIconSvg } from '../lib/actionIcons'; /* ikon:emoji */\nimport { fileIconTile } from '../lib/fileIcons'; /* ikon:emoji */\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> | Promise<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: (await 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\n/* === ikon:emoji — the fallback screen's mark ==========================\n * Every viewer opened its \"cannot show this\" / \"still loading\" screen with a\n * 48px colour emoji, one per format, each from whatever emoji font the OS\n * shipped. The format mark is `lib/fileIcons`'s tile — the SAME tile the row\n * the person just clicked is wearing, so the fallback is recognisably about\n * that file — and \"loading\" is the stroked ring, spun by CSS, because no\n * still picture can say \"still going\". */\nconst typeTile = computed(() => fileIconTile({ type: 'file', extension: props.ext }));\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 <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span class=\"filex-viewer-fallback__icon\" aria-hidden=\"true\" v-html=\"typeTile\"></span>\n <p>{{ error }}</p>\n </div>\n <div\n v-else-if=\"loading\"\n class=\"filex-viewer-fallback\"\n >\n <!-- eslint-disable-next-line vue/no-v-html -- static markup from lib/fileIcons + lib/actionIcons -->\n <span\n class=\"filex-viewer-fallback__icon filex-viewer-fallback__icon--spin\"\n aria-hidden=\"true\"\n v-html=\"actionIconSvg('progress')\"\n ></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>\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-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 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","typeTile","computed","fileIconTile","_openBlock","_createElementBlock","_hoisted_1","_createElementVNode","_hoisted_2","_hoisted_4","_unref","actionIconSvg","_normalizeStyle","_vShow"],"mappings":"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"}
|