@eternalheart/vue-file-preview 1.3.6 → 1.3.7

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 (61) hide show
  1. package/lib/FilePreviewContent.vue.d.ts.map +1 -1
  2. package/lib/FilePreviewEmbed.vue.d.ts.map +1 -1
  3. package/lib/FilePreviewModal.vue.d.ts.map +1 -1
  4. package/lib/chunks/index-BI0dSTSM.mjs +77 -0
  5. package/lib/chunks/index-BI0dSTSM.mjs.map +1 -0
  6. package/lib/chunks/index-BW61ZRev.mjs +111 -0
  7. package/lib/chunks/index-BW61ZRev.mjs.map +1 -0
  8. package/lib/chunks/index-C8ZF84WX.mjs +113 -0
  9. package/lib/chunks/index-C8ZF84WX.mjs.map +1 -0
  10. package/lib/chunks/index-CB0zCxJb.mjs +353 -0
  11. package/lib/chunks/index-CB0zCxJb.mjs.map +1 -0
  12. package/lib/chunks/index-CBCQs1ZC.mjs +116 -0
  13. package/lib/chunks/index-CBCQs1ZC.mjs.map +1 -0
  14. package/lib/chunks/index-CbKSdAt_.mjs +391 -0
  15. package/lib/chunks/index-CbKSdAt_.mjs.map +1 -0
  16. package/lib/chunks/index-CcJ5NmY1.mjs +152 -0
  17. package/lib/chunks/index-CcJ5NmY1.mjs.map +1 -0
  18. package/lib/chunks/index-ClxBxmPR.mjs +249 -0
  19. package/lib/chunks/index-ClxBxmPR.mjs.map +1 -0
  20. package/lib/chunks/index-D9lByoJw.mjs +111 -0
  21. package/lib/chunks/index-D9lByoJw.mjs.map +1 -0
  22. package/lib/chunks/index-DTLDLOGO.mjs +129 -0
  23. package/lib/chunks/index-DTLDLOGO.mjs.map +1 -0
  24. package/lib/chunks/index-DUUsS_T4.mjs +134 -0
  25. package/lib/chunks/index-DUUsS_T4.mjs.map +1 -0
  26. package/lib/chunks/index-Doy0FvQW.mjs +136 -0
  27. package/lib/chunks/index-Doy0FvQW.mjs.map +1 -0
  28. package/lib/chunks/index-DpAxU2JM.mjs +2017 -0
  29. package/lib/chunks/index-DpAxU2JM.mjs.map +1 -0
  30. package/lib/chunks/index-DsznjwsH.mjs +103 -0
  31. package/lib/chunks/index-DsznjwsH.mjs.map +1 -0
  32. package/lib/chunks/index-SAfVQyNJ.mjs +281 -0
  33. package/lib/chunks/index-SAfVQyNJ.mjs.map +1 -0
  34. package/lib/chunks/index-S_kGRh6H.mjs +203 -0
  35. package/lib/chunks/index-S_kGRh6H.mjs.map +1 -0
  36. package/lib/chunks/index-kBPb_OKm.mjs +139 -0
  37. package/lib/chunks/index-kBPb_OKm.mjs.map +1 -0
  38. package/lib/chunks/index-yMb1JUC4.mjs +84 -0
  39. package/lib/chunks/index-yMb1JUC4.mjs.map +1 -0
  40. package/lib/composables/useResolvedTheme.d.ts +15 -0
  41. package/lib/composables/useResolvedTheme.d.ts.map +1 -0
  42. package/lib/index.cjs +24 -499
  43. package/lib/index.cjs.map +1 -1
  44. package/lib/index.css +1 -1
  45. package/lib/index.mjs +12 -44597
  46. package/lib/index.mjs.map +1 -1
  47. package/lib/renderers/Audio/index.vue.d.ts.map +1 -1
  48. package/lib/renderers/Csv/index.vue.d.ts.map +1 -1
  49. package/lib/renderers/Json/index.vue.d.ts.map +1 -1
  50. package/lib/renderers/Markdown/index.vue.d.ts.map +1 -1
  51. package/lib/renderers/Pptx/index.vue.d.ts.map +1 -1
  52. package/lib/renderers/RendererLoading.vue.d.ts +3 -0
  53. package/lib/renderers/RendererLoading.vue.d.ts.map +1 -0
  54. package/lib/renderers/Text/index.vue.d.ts.map +1 -1
  55. package/lib/renderers/Video/index.vue.d.ts.map +1 -1
  56. package/lib/renderers/Xlsx/index.vue.d.ts.map +1 -1
  57. package/lib/renderers/Xml/index.vue.d.ts.map +1 -1
  58. package/lib/renderers/Zip/index.vue.d.ts.map +1 -1
  59. package/lib/renderers/lazy.d.ts +19 -0
  60. package/lib/renderers/lazy.d.ts.map +1 -0
  61. package/package.json +1 -1
@@ -0,0 +1,281 @@
1
+ import { defineComponent as I, ref as u, computed as S, onMounted as Q, onBeforeUnmount as $, watch as D, openBlock as s, createElementBlock as c, createElementVNode as p, normalizeStyle as R, renderSlot as B, normalizeClass as T, resolveComponent as Y, createVNode as W, unref as C, createBlock as M, resolveDynamicComponent as K, toDisplayString as _, Fragment as F, renderList as O, createCommentVNode as P, defineAsyncComponent as ee, withCtx as N, Teleport as te } from "vue";
2
+ import { _ as V, d as ne, p as le, u as oe, e as H, F as se, v as ae, w as ie, i as re } from "./index-DpAxU2JM.mjs";
3
+ import { FileImage as fe, FileText as ve, FileCode as de, File as pe, ChevronRight as ue, FolderOpen as ce, Folder as me } from "lucide-vue-next";
4
+ const he = { class: "vfp-flex-1 vfp-min-w-0 vfp-min-h-0 vfp-overflow-hidden" }, we = /* @__PURE__ */ I({
5
+ name: "ResizableSplit",
6
+ __name: "ResizableSplit",
7
+ props: {
8
+ initialLeftWidth: { default: 280 },
9
+ minLeftWidth: { default: 160 },
10
+ maxLeftWidth: { default: 640 },
11
+ minRightWidth: { default: 200 },
12
+ storageKey: {},
13
+ desktopMedia: { default: "(min-width: 768px)" }
14
+ },
15
+ setup(a) {
16
+ const r = a, f = u(null), i = u(
17
+ (() => {
18
+ if (r.storageKey && typeof window < "u") {
19
+ const o = Number(window.localStorage.getItem(r.storageKey));
20
+ if (!isNaN(o) && o > 0) return o;
21
+ }
22
+ return r.initialLeftWidth;
23
+ })()
24
+ ), h = u(!1), g = u(!1);
25
+ let m = null;
26
+ const w = () => {
27
+ m && (g.value = m.matches);
28
+ }, k = S(
29
+ () => g.value ? { width: `${i.value}px` } : void 0
30
+ ), x = (o) => {
31
+ if (!f.value) return;
32
+ const v = f.value.getBoundingClientRect(), d = o.clientX - v.left, z = v.width - r.minRightWidth - 6, U = Math.min(r.maxLeftWidth, z);
33
+ i.value = Math.max(
34
+ r.minLeftWidth,
35
+ Math.min(U, d)
36
+ );
37
+ }, l = () => {
38
+ if (h.value && (h.value = !1, window.removeEventListener("mousemove", x), window.removeEventListener("mouseup", l), document.body.style.cursor = "", document.body.style.userSelect = "", r.storageKey))
39
+ try {
40
+ window.localStorage.setItem(r.storageKey, String(i.value));
41
+ } catch {
42
+ }
43
+ }, e = (o) => {
44
+ o.preventDefault(), h.value = !0, window.addEventListener("mousemove", x), window.addEventListener("mouseup", l), document.body.style.cursor = "col-resize", document.body.style.userSelect = "none";
45
+ };
46
+ return Q(() => {
47
+ typeof window < "u" && (m = window.matchMedia(r.desktopMedia), w(), m.addEventListener("change", w));
48
+ }), $(() => {
49
+ m && m.removeEventListener("change", w), window.removeEventListener("mousemove", x), window.removeEventListener("mouseup", l), h.value && (document.body.style.cursor = "", document.body.style.userSelect = "");
50
+ }), D(
51
+ () => r.initialLeftWidth,
52
+ (o) => {
53
+ r.storageKey || (i.value = o);
54
+ }
55
+ ), (o, v) => (s(), c("div", {
56
+ ref_key: "containerRef",
57
+ ref: f,
58
+ class: "vfp-w-full vfp-h-full vfp-flex vfp-flex-col md:vfp-flex-row vfp-min-h-0 vfp-min-w-0"
59
+ }, [
60
+ p("div", {
61
+ class: "vfp-min-h-0 vfp-min-w-0 vfp-flex-shrink-0 vfp-w-full vfp-max-h-60 md:vfp-h-full md:vfp-max-h-none",
62
+ style: R(k.value)
63
+ }, [
64
+ B(o.$slots, "left", {}, void 0, !0)
65
+ ], 4),
66
+ p("div", {
67
+ role: "separator",
68
+ "aria-orientation": "vertical",
69
+ class: T(["split-divider vfp-hidden md:vfp-block vfp-relative vfp-w-1.5 vfp-flex-shrink-0 vfp-cursor-col-resize vfp-transition-colors", h.value ? "dragging" : ""]),
70
+ onMousedown: e
71
+ }, [...v[0] || (v[0] = [
72
+ p("span", { class: "vfp-absolute vfp-inset-y-0 hit-area" }, null, -1)
73
+ ])], 34),
74
+ p("div", he, [
75
+ B(o.$slots, "right", {}, void 0, !0)
76
+ ])
77
+ ], 512));
78
+ }
79
+ }), xe = /* @__PURE__ */ V(we, [["__scopeId", "data-v-790c366f"]]), ye = { key: 0 }, ge = { class: "vfp-truncate vfp-flex-1 vfp-min-w-0" }, be = { class: "vfp-flex-1 vfp-truncate vfp-min-w-0" }, ke = { class: "vfp-text-xs vfp-text-fg-disabled vfp-flex-shrink-0 vfp-ml-2" }, Le = { name: "ZipTreeItem" }, ze = /* @__PURE__ */ I({
80
+ ...Le,
81
+ props: {
82
+ node: {},
83
+ depth: {},
84
+ selectedPath: {},
85
+ expanded: {}
86
+ },
87
+ emits: ["toggle", "select", "hover", "leave"],
88
+ setup(a, { emit: r }) {
89
+ const f = a, i = r, h = S(() => ({ paddingLeft: `${f.depth * 14 + 10}px` })), g = S(() => f.expanded.has(f.node.path)), m = S(() => f.selectedPath === f.node.path), w = S(() => {
90
+ const l = ne({ name: f.node.name, type: "" });
91
+ return l === "image" ? fe : l === "text" || l === "markdown" || l === "json" || l === "csv" || l === "xml" || l === "subtitle" ? f.node.name.endsWith(".md") || f.node.name.endsWith(".markdown") ? ve : de : pe;
92
+ }), k = (l) => {
93
+ const e = l.currentTarget.getBoundingClientRect();
94
+ i("hover", f.node.name || "/", e);
95
+ }, x = () => i("leave");
96
+ return (l, e) => {
97
+ const o = Y("ZipTreeItem");
98
+ return a.node.isDir ? (s(), c("div", ye, [
99
+ p("button", {
100
+ type: "button",
101
+ class: "tree-row dir-row",
102
+ style: R(h.value),
103
+ onClick: e[0] || (e[0] = (v) => i("toggle", a.node.path)),
104
+ onMouseenter: k,
105
+ onMouseleave: x
106
+ }, [
107
+ W(C(ue), {
108
+ class: T(["vfp-w-3.5 vfp-h-3.5 vfp-flex-shrink-0 vfp-transition-transform", { "vfp-rotate-90": g.value }])
109
+ }, null, 8, ["class"]),
110
+ (s(), M(K(g.value ? C(ce) : C(me)), { class: "vfp-w-4 vfp-h-4 vfp-flex-shrink-0 vfp-text-amber-300/80" })),
111
+ p("span", ge, _(a.node.name || "/"), 1)
112
+ ], 36),
113
+ g.value && a.node.children ? (s(!0), c(F, { key: 0 }, O(a.node.children, (v) => (s(), M(o, {
114
+ key: v.path,
115
+ node: v,
116
+ depth: a.depth + 1,
117
+ "selected-path": a.selectedPath,
118
+ expanded: a.expanded,
119
+ onToggle: e[1] || (e[1] = (d) => i("toggle", d)),
120
+ onSelect: e[2] || (e[2] = (d) => i("select", d)),
121
+ onHover: e[3] || (e[3] = (d, z) => i("hover", d, z)),
122
+ onLeave: e[4] || (e[4] = (d) => i("leave"))
123
+ }, null, 8, ["node", "depth", "selected-path", "expanded"]))), 128)) : P("", !0)
124
+ ])) : (s(), c("button", {
125
+ key: 1,
126
+ type: "button",
127
+ class: T(["tree-row file-row", { selected: m.value }]),
128
+ style: R(h.value),
129
+ onClick: e[5] || (e[5] = (v) => i("select", a.node)),
130
+ onMouseenter: k,
131
+ onMouseleave: x
132
+ }, [
133
+ e[6] || (e[6] = p("span", { class: "vfp-w-3.5 vfp-h-3.5 vfp-flex-shrink-0" }, null, -1)),
134
+ (s(), M(K(w.value), { class: "vfp-w-4 vfp-h-4 vfp-flex-shrink-0 vfp-text-fg-tertiary" })),
135
+ p("span", be, _(a.node.name), 1),
136
+ p("span", ke, _(C(le)(a.node.size)), 1)
137
+ ], 38));
138
+ };
139
+ }
140
+ }), Se = /* @__PURE__ */ V(ze, [["__scopeId", "data-v-c8c0f0ee"]]), Ce = {
141
+ key: 0,
142
+ class: "vfp-flex vfp-items-center vfp-justify-center vfp-w-full vfp-h-full"
143
+ }, _e = {
144
+ key: 1,
145
+ class: "vfp-flex vfp-items-center vfp-justify-center vfp-w-full vfp-h-full"
146
+ }, Ee = { class: "vfp-text-fg-secondary vfp-text-center" }, Me = { class: "vfp-text-lg" }, Re = { class: "vfp-w-full vfp-h-full vfp-overflow-auto" }, $e = { class: "vfp-w-full vfp-h-full vfp-flex vfp-flex-col" }, De = {
147
+ key: 0,
148
+ class: "vfp-flex-1 vfp-flex vfp-items-center vfp-justify-center vfp-text-fg-muted vfp-text-sm vfp-p-6"
149
+ }, Te = {
150
+ key: 1,
151
+ class: "vfp-flex-1 vfp-flex vfp-items-center vfp-justify-center"
152
+ }, We = {
153
+ key: 2,
154
+ class: "vfp-flex-1 vfp-flex vfp-items-center vfp-justify-center vfp-text-fg-secondary"
155
+ }, Fe = {
156
+ key: 3,
157
+ class: "vfp-flex-1 vfp-min-h-0 vfp-overflow-hidden vfp-flex vfp-relative vfp-z-0"
158
+ }, Be = /* @__PURE__ */ I({
159
+ __name: "index",
160
+ props: {
161
+ url: {},
162
+ nestingDepth: { default: 0 }
163
+ },
164
+ emits: ["statsChange"],
165
+ setup(a, { emit: r }) {
166
+ const f = ee(
167
+ () => import("./index-DpAxU2JM.mjs").then((t) => t.s)
168
+ ), i = a, h = r, { t: g } = oe(), m = u(null), w = u(null), k = u(!0), x = u(null), l = u(/* @__PURE__ */ new Set([""])), e = u(null), o = u(!1), v = u(null), d = u(null), z = () => {
169
+ var t;
170
+ (t = e.value) != null && t.blobUrl && URL.revokeObjectURL(e.value.blobUrl);
171
+ };
172
+ D(() => i.url, async () => {
173
+ z(), e.value = null, k.value = !0, x.value = null;
174
+ try {
175
+ const t = await fetch(i.url);
176
+ if (!t.ok) throw new Error("加载失败");
177
+ const n = await t.arrayBuffer(), y = await se(n), b = ae(y), L = ie(b);
178
+ m.value = y, w.value = L;
179
+ const E = /* @__PURE__ */ new Set([""]);
180
+ if (L.children)
181
+ for (const j of L.children) j.isDir && E.add(j.path);
182
+ l.value = E;
183
+ } catch (t) {
184
+ console.error(t), x.value = g.value("zip.load_failed");
185
+ } finally {
186
+ k.value = !1;
187
+ }
188
+ }, { immediate: !0 }), $(() => {
189
+ z();
190
+ });
191
+ const Z = S(() => {
192
+ if (!w.value) return null;
193
+ let t = 0, n = 0, y = 0;
194
+ const b = (L) => {
195
+ var E;
196
+ L.isDir ? (L.path && n++, (E = L.children) == null || E.forEach(b)) : (t++, y += L.size);
197
+ };
198
+ return b(w.value), { files: t, dirs: n, size: y };
199
+ });
200
+ D(Z, (t) => h("statsChange", t), { immediate: !0 }), $(() => h("statsChange", null));
201
+ const q = (t) => {
202
+ const n = new Set(l.value);
203
+ n.has(t) ? n.delete(t) : n.add(t), l.value = n;
204
+ }, A = (t, n) => {
205
+ d.value = { text: t, x: n.right + 8, y: n.top + n.height / 2 };
206
+ }, X = () => {
207
+ d.value = null;
208
+ }, G = async (t) => {
209
+ if (!(!m.value || t.isDir)) {
210
+ z(), o.value = !0, v.value = null;
211
+ try {
212
+ const n = H(t.name), y = await re(m.value, t.path, n !== "application/octet-stream" ? n : void 0), b = URL.createObjectURL(y);
213
+ e.value = { path: t.path, name: t.name, size: t.size, blobUrl: b };
214
+ } catch (n) {
215
+ console.error(n), v.value = "条目读取失败";
216
+ } finally {
217
+ o.value = !1;
218
+ }
219
+ }
220
+ }, J = S(() => e.value ? [{ name: e.value.name, url: e.value.blobUrl, type: H(e.value.name) }] : []);
221
+ return (t, n) => k.value ? (s(), c("div", Ce, [...n[0] || (n[0] = [
222
+ p("div", { class: "vfp-w-12 vfp-h-12 vfp-border-4 vfp-border-line-strong vfp-border-t-spinner-head vfp-rounded-full vfp-animate-spin" }, null, -1)
223
+ ])])) : x.value || !w.value ? (s(), c("div", _e, [
224
+ p("div", Ee, [
225
+ p("p", Me, _(x.value || C(g)("zip.parse_failed")), 1)
226
+ ])
227
+ ])) : (s(), c(F, { key: 2 }, [
228
+ W(xe, {
229
+ "initial-left-width": 280,
230
+ "min-left-width": 180,
231
+ "max-left-width": 560,
232
+ "storage-key": "vfp-zip-split-left"
233
+ }, {
234
+ left: N(() => [
235
+ p("div", Re, [
236
+ (s(!0), c(F, null, O(w.value.children || [], (y) => {
237
+ var b;
238
+ return s(), M(Se, {
239
+ key: y.path,
240
+ node: y,
241
+ depth: 0,
242
+ "selected-path": ((b = e.value) == null ? void 0 : b.path) ?? null,
243
+ expanded: l.value,
244
+ onToggle: q,
245
+ onSelect: G,
246
+ onHover: A,
247
+ onLeave: X
248
+ }, null, 8, ["node", "selected-path", "expanded"]);
249
+ }), 128))
250
+ ])
251
+ ]),
252
+ right: N(() => [
253
+ p("div", $e, [
254
+ e.value ? o.value ? (s(), c("div", Te, [...n[1] || (n[1] = [
255
+ p("div", { class: "vfp-w-8 vfp-h-8 vfp-border-4 vfp-border-line-strong vfp-border-t-spinner-head vfp-rounded-full vfp-animate-spin" }, null, -1)
256
+ ])])) : v.value ? (s(), c("div", We, _(v.value), 1)) : (s(), c("div", Fe, [
257
+ W(C(f), {
258
+ mode: "embed",
259
+ files: J.value,
260
+ "current-index": 0,
261
+ "zip-nesting-depth": a.nestingDepth + 1
262
+ }, null, 8, ["files", "zip-nesting-depth"])
263
+ ])) : (s(), c("div", De, " 从左侧选择一个文件以预览 "))
264
+ ])
265
+ ]),
266
+ _: 1
267
+ }),
268
+ (s(), M(te, { to: "body" }, [
269
+ d.value ? (s(), c("div", {
270
+ key: 0,
271
+ class: "vfp-zip-tip",
272
+ style: R({ left: d.value.x + "px", top: d.value.y + "px" })
273
+ }, _(d.value.text), 5)) : P("", !0)
274
+ ]))
275
+ ], 64));
276
+ }
277
+ });
278
+ export {
279
+ Be as default
280
+ };
281
+ //# sourceMappingURL=index-SAfVQyNJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-SAfVQyNJ.mjs","sources":["../../src/components/ResizableSplit.vue","../../src/renderers/Zip/TreeItem.vue","../../src/renderers/Zip/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, onBeforeUnmount, onMounted, watch } from 'vue';\n\ninterface Props {\n /** 左侧初始宽度(px);传入 storageKey 时会从 localStorage 读取 */\n initialLeftWidth?: number;\n /** 左侧最小宽度(px) */\n minLeftWidth?: number;\n /** 左侧最大宽度(px) */\n maxLeftWidth?: number;\n /** 右侧至少保留的宽度(px) */\n minRightWidth?: number;\n /** localStorage 持久化 key */\n storageKey?: string;\n /** 启用横向拖动的媒体查询 */\n desktopMedia?: string;\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n initialLeftWidth: 280,\n minLeftWidth: 160,\n maxLeftWidth: 640,\n minRightWidth: 200,\n desktopMedia: '(min-width: 768px)',\n});\n\ndefineOptions({ name: 'ResizableSplit' });\n\nconst containerRef = ref<HTMLDivElement | null>(null);\nconst leftWidth = ref<number>(\n (() => {\n if (props.storageKey && typeof window !== 'undefined') {\n const saved = Number(window.localStorage.getItem(props.storageKey));\n if (!isNaN(saved) && saved > 0) return saved;\n }\n return props.initialLeftWidth;\n })()\n);\nconst dragging = ref(false);\nconst isDesktop = ref(false);\n\nlet mq: MediaQueryList | null = null;\nconst mqHandler = () => {\n if (mq) isDesktop.value = mq.matches;\n};\n\nconst leftStyle = computed(() =>\n isDesktop.value ? { width: `${leftWidth.value}px` } : undefined\n);\n\nconst onMove = (e: MouseEvent) => {\n if (!containerRef.value) return;\n const rect = containerRef.value.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const cap = rect.width - props.minRightWidth - 6;\n const effectiveMax = Math.min(props.maxLeftWidth, cap);\n leftWidth.value = Math.max(\n props.minLeftWidth,\n Math.min(effectiveMax, x)\n );\n};\n\nconst onUp = () => {\n if (!dragging.value) return;\n dragging.value = false;\n window.removeEventListener('mousemove', onMove);\n window.removeEventListener('mouseup', onUp);\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n if (props.storageKey) {\n try {\n window.localStorage.setItem(props.storageKey, String(leftWidth.value));\n } catch {\n // ignore\n }\n }\n};\n\nconst onDividerDown = (e: MouseEvent) => {\n e.preventDefault();\n dragging.value = true;\n window.addEventListener('mousemove', onMove);\n window.addEventListener('mouseup', onUp);\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n};\n\nonMounted(() => {\n if (typeof window !== 'undefined') {\n mq = window.matchMedia(props.desktopMedia);\n mqHandler();\n mq.addEventListener('change', mqHandler);\n }\n});\n\nonBeforeUnmount(() => {\n if (mq) mq.removeEventListener('change', mqHandler);\n window.removeEventListener('mousemove', onMove);\n window.removeEventListener('mouseup', onUp);\n if (dragging.value) {\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n }\n});\n\nwatch(\n () => props.initialLeftWidth,\n (w) => {\n if (!props.storageKey) leftWidth.value = w;\n }\n);\n</script>\n\n<template>\n <div\n ref=\"containerRef\"\n class=\"vfp-w-full vfp-h-full vfp-flex vfp-flex-col md:vfp-flex-row vfp-min-h-0 vfp-min-w-0\"\n >\n <div\n class=\"vfp-min-h-0 vfp-min-w-0 vfp-flex-shrink-0 vfp-w-full vfp-max-h-60 md:vfp-h-full md:vfp-max-h-none\"\n :style=\"leftStyle\"\n >\n <slot name=\"left\" />\n </div>\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n class=\"split-divider vfp-hidden md:vfp-block vfp-relative vfp-w-1.5 vfp-flex-shrink-0 vfp-cursor-col-resize vfp-transition-colors\"\n :class=\"dragging ? 'dragging' : ''\"\n @mousedown=\"onDividerDown\"\n >\n <span class=\"vfp-absolute vfp-inset-y-0 hit-area\" />\n </div>\n <div class=\"vfp-flex-1 vfp-min-w-0 vfp-min-h-0 vfp-overflow-hidden\">\n <slot name=\"right\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.split-divider {\n background: rgba(255, 255, 255, 0.1);\n}\n.split-divider:hover {\n background: rgba(255, 255, 255, 0.3);\n}\n.split-divider.dragging {\n background: rgba(255, 255, 255, 0.4);\n}\n.hit-area {\n left: -4px;\n right: -4px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport {\n Folder,\n FolderOpen,\n FileText,\n FileImage,\n FileCode,\n File as FileIcon,\n ChevronRight,\n} from 'lucide-vue-next';\nimport { formatFileSize, getFileType, type ZipTreeNode } from '@eternalheart/file-preview-core';\n\nconst props = defineProps<{\n node: ZipTreeNode;\n depth: number;\n selectedPath: string | null;\n expanded: Set<string>;\n}>();\n\nconst emit = defineEmits<{\n (e: 'toggle', path: string): void;\n (e: 'select', node: ZipTreeNode): void;\n (e: 'hover', text: string, rect: DOMRect): void;\n (e: 'leave'): void;\n}>();\n\nconst padStyle = computed(() => ({ paddingLeft: `${props.depth * 14 + 10}px` }));\nconst isOpen = computed(() => props.expanded.has(props.node.path));\nconst isSelected = computed(() => props.selectedPath === props.node.path);\n\nconst fileIcon = computed(() => {\n const ft = getFileType({ id: '', name: props.node.name, url: '', type: '' });\n if (ft === 'image') return FileImage;\n if (ft === 'text' || ft === 'markdown' || ft === 'json' || ft === 'csv' || ft === 'xml' || ft === 'subtitle') {\n return (props.node.name.endsWith('.md') || props.node.name.endsWith('.markdown')) ? FileText : FileCode;\n }\n return FileIcon;\n});\n\nconst handleEnter = (e: MouseEvent) => {\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\n emit('hover', props.node.name || '/', rect);\n};\n\nconst handleLeave = () => emit('leave');\n</script>\n\n<template>\n <div v-if=\"node.isDir\">\n <button\n type=\"button\"\n class=\"tree-row dir-row\"\n :style=\"padStyle\"\n @click=\"emit('toggle', node.path)\"\n @mouseenter=\"handleEnter\"\n @mouseleave=\"handleLeave\"\n >\n <ChevronRight\n class=\"vfp-w-3.5 vfp-h-3.5 vfp-flex-shrink-0 vfp-transition-transform\"\n :class=\"{ 'vfp-rotate-90': isOpen }\"\n />\n <component\n :is=\"isOpen ? FolderOpen : Folder\"\n class=\"vfp-w-4 vfp-h-4 vfp-flex-shrink-0 vfp-text-amber-300/80\"\n />\n <span class=\"vfp-truncate vfp-flex-1 vfp-min-w-0\">{{ node.name || '/' }}</span>\n </button>\n <template v-if=\"isOpen && node.children\">\n <ZipTreeItem\n v-for=\"child in node.children\"\n :key=\"child.path\"\n :node=\"child\"\n :depth=\"depth + 1\"\n :selected-path=\"selectedPath\"\n :expanded=\"expanded\"\n @toggle=\"(p) => emit('toggle', p)\"\n @select=\"(n) => emit('select', n)\"\n @hover=\"(t, r) => emit('hover', t, r)\"\n @leave=\"emit('leave')\"\n />\n </template>\n </div>\n <button\n v-else\n type=\"button\"\n class=\"tree-row file-row\"\n :class=\"{ selected: isSelected }\"\n :style=\"padStyle\"\n @click=\"emit('select', node)\"\n @mouseenter=\"handleEnter\"\n @mouseleave=\"handleLeave\"\n >\n <span class=\"vfp-w-3.5 vfp-h-3.5 vfp-flex-shrink-0\" />\n <component :is=\"fileIcon\" class=\"vfp-w-4 vfp-h-4 vfp-flex-shrink-0 vfp-text-fg-tertiary\" />\n <span class=\"vfp-flex-1 vfp-truncate vfp-min-w-0\">{{ node.name }}</span>\n <span class=\"vfp-text-xs vfp-text-fg-disabled vfp-flex-shrink-0 vfp-ml-2\">{{ formatFileSize(node.size) }}</span>\n </button>\n</template>\n\n<script lang=\"ts\">\nexport default { name: 'ZipTreeItem' };\n</script>\n\n<style scoped>\n.tree-row {\n width: 100%;\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n padding-right: 0.5rem;\n text-align: left;\n font-size: 0.875rem;\n background: transparent;\n border: 0;\n cursor: pointer;\n}\n.dir-row {\n color: var(--fp-fg-secondary);\n}\n.file-row {\n color: var(--fp-fg-secondary);\n}\n.tree-row:hover {\n background: var(--fp-surface-1);\n}\n.file-row.selected {\n background: var(--fp-line);\n color: #fff;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, computed, watch, onBeforeUnmount, defineAsyncComponent } from 'vue';\nimport type JSZip from 'jszip';\nimport {\n loadZip,\n listZipEntries,\n buildZipTree,\n readZipEntryBlob,\n inferMimeType,\n type ZipTreeNode,\n} from '@eternalheart/file-preview-core';\nimport ResizableSplit from '../../components/ResizableSplit.vue';\nimport TreeItem from './TreeItem.vue';\nimport type { ZipToolbarStats } from './toolbar';\nimport { useTranslator } from '../../composables/useTranslator';\n\n// 懒加载 FilePreviewContent 以打破循环依赖\nconst LazyFilePreviewContent = defineAsyncComponent(\n () => import('../../FilePreviewContent.vue')\n);\n\nconst props = withDefaults(defineProps<{\n url: string;\n /** ZIP 嵌套深度(由 FilePreviewContent 传入) */\n nestingDepth?: number;\n}>(), {\n nestingDepth: 0,\n});\n\nconst emit = defineEmits<{\n (e: 'statsChange', stats: ZipToolbarStats | null): void;\n}>();\n\nconst { t } = useTranslator();\n\ninterface SelectedPreview {\n path: string;\n name: string;\n size: number;\n blobUrl: string;\n}\n\ninterface HoverTipState {\n text: string;\n x: number;\n y: number;\n}\n\nconst zip = ref<JSZip | null>(null);\nconst tree = ref<ZipTreeNode | null>(null);\nconst loading = ref(true);\nconst error = ref<string | null>(null);\nconst expanded = ref<Set<string>>(new Set(['']));\nconst selected = ref<SelectedPreview | null>(null);\nconst previewLoading = ref(false);\nconst previewError = ref<string | null>(null);\nconst hoverTip = ref<HoverTipState | null>(null);\n\nconst revokeCurrent = () => {\n if (selected.value?.blobUrl) URL.revokeObjectURL(selected.value.blobUrl);\n};\n\nconst load = async () => {\n revokeCurrent();\n selected.value = null;\n loading.value = true;\n error.value = null;\n try {\n const res = await fetch(props.url);\n if (!res.ok) throw new Error('加载失败');\n const buf = await res.arrayBuffer();\n const z = await loadZip(buf);\n const entries = listZipEntries(z);\n const root = buildZipTree(entries);\n zip.value = z;\n tree.value = root;\n const init = new Set<string>(['']);\n if (root.children) for (const c of root.children) if (c.isDir) init.add(c.path);\n expanded.value = init;\n } catch (err) {\n console.error(err);\n error.value = t.value('zip.load_failed');\n } finally {\n loading.value = false;\n }\n};\n\nwatch(() => props.url, load, { immediate: true });\nonBeforeUnmount(() => { revokeCurrent(); });\n\nconst totalStats = computed<ZipToolbarStats | null>(() => {\n if (!tree.value) return null;\n let files = 0, dirs = 0, size = 0;\n const walk = (n: ZipTreeNode) => {\n if (n.isDir) { if (n.path) dirs++; n.children?.forEach(walk); }\n else { files++; size += n.size; }\n };\n walk(tree.value);\n return { files, dirs, size };\n});\n\nwatch(totalStats, (s) => emit('statsChange', s), { immediate: true });\nonBeforeUnmount(() => emit('statsChange', null));\n\nconst handleToggle = (path: string) => {\n const next = new Set(expanded.value);\n if (next.has(path)) next.delete(path);\n else next.add(path);\n expanded.value = next;\n};\n\nconst handleHover = (text: string, rect: DOMRect) => {\n hoverTip.value = { text, x: rect.right + 8, y: rect.top + rect.height / 2 };\n};\nconst handleLeave = () => { hoverTip.value = null; };\n\nconst handleSelect = async (node: ZipTreeNode) => {\n if (!zip.value || node.isDir) return;\n revokeCurrent();\n previewLoading.value = true;\n previewError.value = null;\n\n try {\n const mime = inferMimeType(node.name);\n const blob = await readZipEntryBlob(zip.value, node.path, mime !== 'application/octet-stream' ? mime : undefined);\n const blobUrl = URL.createObjectURL(blob);\n selected.value = { path: node.path, name: node.name, size: node.size, blobUrl };\n } catch (err) {\n console.error(err);\n previewError.value = '条目读取失败';\n } finally {\n previewLoading.value = false;\n }\n};\n\n/** 为嵌入的 FilePreviewContent 构建 files 数组 */\nconst previewFiles = computed(() => {\n if (!selected.value) return [];\n return [{ name: selected.value.name, url: selected.value.blobUrl, type: inferMimeType(selected.value.name) }];\n});\n</script>\n\n<template>\n <div v-if=\"loading\" class=\"vfp-flex vfp-items-center vfp-justify-center vfp-w-full vfp-h-full\">\n <div class=\"vfp-w-12 vfp-h-12 vfp-border-4 vfp-border-line-strong vfp-border-t-spinner-head vfp-rounded-full vfp-animate-spin\" />\n </div>\n\n <div v-else-if=\"error || !tree\" class=\"vfp-flex vfp-items-center vfp-justify-center vfp-w-full vfp-h-full\">\n <div class=\"vfp-text-fg-secondary vfp-text-center\"><p class=\"vfp-text-lg\">{{ error || t('zip.parse_failed') }}</p></div>\n </div>\n\n <template v-else>\n <ResizableSplit\n :initial-left-width=\"280\"\n :min-left-width=\"180\"\n :max-left-width=\"560\"\n storage-key=\"vfp-zip-split-left\"\n >\n <template #left>\n <div class=\"vfp-w-full vfp-h-full vfp-overflow-auto\">\n <TreeItem\n v-for=\"child in tree.children || []\"\n :key=\"child.path\"\n :node=\"child\"\n :depth=\"0\"\n :selected-path=\"selected?.path ?? null\"\n :expanded=\"expanded\"\n @toggle=\"handleToggle\"\n @select=\"handleSelect\"\n @hover=\"handleHover\"\n @leave=\"handleLeave\"\n />\n </div>\n </template>\n\n <template #right>\n <div class=\"vfp-w-full vfp-h-full vfp-flex vfp-flex-col\">\n <div v-if=\"!selected\" class=\"vfp-flex-1 vfp-flex vfp-items-center vfp-justify-center vfp-text-fg-muted vfp-text-sm vfp-p-6\">\n 从左侧选择一个文件以预览\n </div>\n <div v-else-if=\"previewLoading\" class=\"vfp-flex-1 vfp-flex vfp-items-center vfp-justify-center\">\n <div class=\"vfp-w-8 vfp-h-8 vfp-border-4 vfp-border-line-strong vfp-border-t-spinner-head vfp-rounded-full vfp-animate-spin\" />\n </div>\n <div v-else-if=\"previewError\" class=\"vfp-flex-1 vfp-flex vfp-items-center vfp-justify-center vfp-text-fg-secondary\">{{ previewError }}</div>\n <template v-else>\n <div class=\"vfp-flex-1 vfp-min-h-0 vfp-overflow-hidden vfp-flex vfp-relative vfp-z-0\">\n <LazyFilePreviewContent\n mode=\"embed\"\n :files=\"previewFiles\"\n :current-index=\"0\"\n :zip-nesting-depth=\"nestingDepth + 1\"\n />\n </div>\n </template>\n </div>\n </template>\n </ResizableSplit>\n\n <!-- 文件名 hover tooltip (teleport 到 body 避免被滚动区裁剪) -->\n <Teleport to=\"body\">\n <div\n v-if=\"hoverTip\"\n class=\"vfp-zip-tip\"\n :style=\"{ left: hoverTip.x + 'px', top: hoverTip.y + 'px' }\"\n >\n {{ hoverTip.text }}\n </div>\n </Teleport>\n </template>\n</template>\n\n<style>\n/* 全局 tooltip(不能 scoped,因 Teleport 到 body) */\n.vfp-zip-tip {\n position: fixed;\n z-index: 9999;\n pointer-events: none;\n transform: translateY(-50%);\n padding: 4px 8px;\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 4px;\n white-space: nowrap;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n}\n</style>\n"],"names":["props","__props","containerRef","ref","leftWidth","saved","dragging","isDesktop","mq","mqHandler","leftStyle","computed","onMove","e","rect","x","cap","effectiveMax","onUp","onDividerDown","onMounted","onBeforeUnmount","watch","w","_createElementBlock","_createElementVNode","_renderSlot","_ctx","_normalizeClass","_hoisted_1","__default__","emit","__emit","padStyle","isOpen","isSelected","fileIcon","ft","getFileType","FileImage","FileText","FileCode","FileIcon","handleEnter","handleLeave","_cache","$event","_createVNode","_unref","ChevronRight","_createBlock","_resolveDynamicComponent","FolderOpen","Folder","_hoisted_2","_toDisplayString","_openBlock","_Fragment","_renderList","child","_component_ZipTreeItem","p","n","t","r","_hoisted_3","_hoisted_4","LazyFilePreviewContent","defineAsyncComponent","useTranslator","zip","tree","loading","error","expanded","selected","previewLoading","previewError","hoverTip","revokeCurrent","_a","res","buf","z","loadZip","entries","listZipEntries","root","buildZipTree","init","c","err","totalStats","files","dirs","size","walk","s","handleToggle","path","next","handleHover","text","handleSelect","node","mime","inferMimeType","blob","readZipEntryBlob","blobUrl","previewFiles","ResizableSplit","_hoisted_5","TreeItem","_hoisted_6","_hoisted_8","_hoisted_9","_hoisted_10","_hoisted_7","_Teleport"],"mappings":"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"}
@@ -0,0 +1,203 @@
1
+ import { defineComponent as V, ref as p, watch as te, onBeforeUnmount as oe, openBlock as m, createElementBlock as h, createElementVNode as f, toDisplayString as P, createCommentVNode as C, normalizeStyle as S, unref as z, createVNode as U, h as b } from "vue";
2
+ import { X as re } from "lucide-vue-next";
3
+ import "foliate-js/view.js";
4
+ import { u as ae, _ as le } from "./index-DpAxU2JM.mjs";
5
+ const ne = {
6
+ class: "vfp-relative vfp-w-full vfp-h-full vfp-flex vfp-justify-center vfp-overflow-hidden",
7
+ style: { background: "#f5f5f0" }
8
+ }, se = {
9
+ key: 0,
10
+ class: "vfp-absolute vfp-inset-0 vfp-flex vfp-items-center vfp-justify-center vfp-text-fg-secondary vfp-text-center vfp-p-6"
11
+ }, fe = { class: "vfp-text-lg" }, ie = {
12
+ key: 1,
13
+ class: "vfp-absolute vfp-inset-0 vfp-flex vfp-items-center vfp-justify-center vfp-z-10"
14
+ }, pe = { class: "vfp-flex vfp-items-center vfp-justify-between vfp-px-4 vfp-py-3 vfp-border-b vfp-border-line-weak vfp-flex-shrink-0" }, ve = { class: "vfp-text-fg-primary vfp-font-medium vfp-text-sm" }, ce = { class: "vfp-flex-1 vfp-overflow-y-auto vfp-py-4 vfp-px-1" }, X = V({
15
+ name: "MobiTocList",
16
+ props: {
17
+ items: { type: Array, required: !0 },
18
+ activeHref: { type: String, default: "" },
19
+ depth: { type: Number, default: 0 }
20
+ },
21
+ emits: ["select"],
22
+ render() {
23
+ return b(
24
+ "ul",
25
+ { style: { listStyle: "none", padding: 0, margin: this.depth > 0 ? "0 0 0 16px" : 0 } },
26
+ this.items.map(
27
+ (r, T) => {
28
+ var g, x, A;
29
+ return b("li", { key: `${r.href ?? r.label}-${T}` }, [
30
+ r.href ? b(
31
+ "button",
32
+ {
33
+ onClick: () => this.$emit("select", r.href),
34
+ class: [
35
+ "vfp-w-full vfp-text-left vfp-py-2 vfp-px-3 vfp-text-sm vfp-rounded vfp-transition-all vfp-truncate",
36
+ this.activeHref === r.href ? "vfp-text-fg-primary vfp-bg-surface-3 vfp-font-medium" : "vfp-text-fg-secondary hover:vfp-text-fg-primary hover:vfp-bg-surface-2"
37
+ ],
38
+ title: r.label,
39
+ style: "background: none; border: none; cursor: pointer"
40
+ },
41
+ (g = r.label) == null ? void 0 : g.trim()
42
+ ) : b(
43
+ "div",
44
+ { class: "vfp-w-full vfp-py-2 vfp-px-3 vfp-text-sm vfp-text-fg-tertiary vfp-truncate" },
45
+ (x = r.label) == null ? void 0 : x.trim()
46
+ ),
47
+ (A = r.subitems) != null && A.length ? b(X, {
48
+ items: r.subitems,
49
+ activeHref: this.activeHref,
50
+ depth: this.depth + 1,
51
+ onSelect: (c) => this.$emit("select", c)
52
+ }) : null
53
+ ]);
54
+ }
55
+ )
56
+ );
57
+ }
58
+ }), ue = { name: "MobiRenderer" }, de = /* @__PURE__ */ V({
59
+ ...ue,
60
+ props: {
61
+ url: {}
62
+ },
63
+ emits: ["chapterChange", "fullWidthChange"],
64
+ setup(r, { expose: T, emit: g }) {
65
+ const x = `
66
+ @namespace epub "http://www.idpf.org/2007/ops";
67
+ html { color-scheme: light; }
68
+ body {
69
+ background: #ffffff !important;
70
+ color: #1a1a1a !important;
71
+ font-family: "Noto Serif SC", "Source Han Serif SC", Georgia, "Times New Roman", serif !important;
72
+ font-size: 16px !important;
73
+ line-height: 2 !important;
74
+ max-width: 100% !important;
75
+ box-sizing: border-box !important;
76
+ word-break: break-word !important;
77
+ overflow-wrap: break-word !important;
78
+ }
79
+ p, li, blockquote, dd { line-height: 2; text-align: justify; }
80
+ p { text-indent: 2em; margin: 0.8em 0; }
81
+ h1 { text-align: center; margin: 1.5em 0 1em; }
82
+ h2 { margin: 1.2em 0 0.8em; }
83
+ h3 { margin: 1em 0 0.6em; }
84
+ img { max-width: 100% !important; height: auto !important; }
85
+ a { color: #2563eb; text-decoration: none; }
86
+ pre { white-space: pre-wrap !important; }
87
+ `, c = r, M = g, { t: $ } = ae(), E = p(null);
88
+ let e = null, N = 1;
89
+ const y = p(!0), v = p(null), w = p([]), l = p(!1), k = p(""), u = p(!1), H = (o, t) => {
90
+ t > 0 && (N = t), M("chapterChange", Math.max(1, o + 1), N);
91
+ }, q = () => {
92
+ e == null || e.prev().catch(() => {
93
+ });
94
+ }, G = () => {
95
+ e == null || e.next().catch(() => {
96
+ });
97
+ }, O = () => {
98
+ l.value = !l.value;
99
+ }, Z = () => {
100
+ u.value = !u.value, M("fullWidthChange", u.value), e != null && e.renderer && e.renderer.setAttribute("max-inline-size", u.value ? "9999" : "720");
101
+ }, J = (o) => {
102
+ k.value = o, l.value = !1, e == null || e.goTo(o).catch(() => {
103
+ });
104
+ };
105
+ T({ prevPage: q, nextPage: G, toggleFullWidth: Z, toggleToc: O });
106
+ const K = async () => {
107
+ var t, i, j, L;
108
+ const o = E.value;
109
+ if (o) {
110
+ y.value = !0, v.value = null, w.value = [], l.value = !1, k.value = "", o.replaceChildren(), e = null;
111
+ try {
112
+ const n = document.createElement("foliate-view");
113
+ o.appendChild(n), e = n, n.addEventListener("relocate", (D) => {
114
+ var F;
115
+ const s = D.detail;
116
+ if (!s) return;
117
+ const d = s.location;
118
+ if (d && typeof d.current == "number" && typeof d.total == "number")
119
+ H(d.current, d.total);
120
+ else {
121
+ const Y = ((F = e == null ? void 0 : e.book) == null ? void 0 : F.sections) ?? [], I = s.index ?? 0, ee = s.fraction ?? 0, W = Math.max(Y.length, 1);
122
+ H(Math.round((I + ee) / W * W), W);
123
+ }
124
+ const _ = s.tocItem;
125
+ _ != null && _.href && (k.value = _.href);
126
+ });
127
+ const R = await fetch(c.url);
128
+ if (!R.ok) throw new Error(`请求失败: ${R.status}`);
129
+ const Q = await R.blob();
130
+ let B = "book.mobi";
131
+ try {
132
+ const s = new URL(c.url, window.location.href).pathname.split("/").pop();
133
+ s && (B = decodeURIComponent(s));
134
+ } catch {
135
+ }
136
+ await n.open(new File([Q], B));
137
+ const a = n.renderer;
138
+ a && (a.setAttribute("animated", ""), a.setAttribute("max-inline-size", "720"), a.setAttribute("margin", "48"), a.setAttribute("gap", "5%"), (t = a.setStyles) == null || t.call(a, x), await ((i = a.next) == null ? void 0 : i.call(a))), w.value = ((j = n.book) == null ? void 0 : j.toc) ?? [], y.value = !1, H(0, ((L = n.book) == null ? void 0 : L.sections.length) ?? 1);
139
+ } catch (n) {
140
+ console.error("MOBI/AZW3 加载错误:", n), v.value = $.value("mobi.load_failed"), y.value = !1;
141
+ }
142
+ }
143
+ };
144
+ return te(() => c.url, () => K(), { immediate: !0 }), oe(() => {
145
+ var o, t, i;
146
+ try {
147
+ (t = (o = e == null ? void 0 : e.book) == null ? void 0 : o.destroy) == null || t.call(o);
148
+ } catch {
149
+ }
150
+ e = null, (i = E.value) == null || i.replaceChildren();
151
+ }), (o, t) => (m(), h("div", ne, [
152
+ v.value ? (m(), h("div", se, [
153
+ f("p", fe, P(v.value), 1)
154
+ ])) : C("", !0),
155
+ y.value && !v.value ? (m(), h("div", ie, [...t[2] || (t[2] = [
156
+ f("div", { class: "vfp-w-12 vfp-h-12 vfp-border-4 vfp-border-line-strong vfp-border-t-spinner-head vfp-rounded-full vfp-animate-spin" }, null, -1)
157
+ ])])) : C("", !0),
158
+ w.value.length > 0 ? (m(), h("div", {
159
+ key: 2,
160
+ class: "vfp-absolute vfp-inset-0 vfp-z-20 vfp-flex vfp-transition-opacity vfp-duration-300",
161
+ style: S({ opacity: l.value ? 1 : 0, pointerEvents: l.value ? "auto" : "none" })
162
+ }, [
163
+ f("div", {
164
+ class: "vfp-w-72 vfp-max-w-[80%] vfp-h-full vfp-bg-surface-overlay vfp-backdrop-blur-xl vfp-border-r vfp-border-line-weak vfp-flex vfp-flex-col vfp-shadow-2xl vfp-transition-transform vfp-duration-300",
165
+ style: S({ transform: l.value ? "translateX(0)" : "translateX(-100%)" })
166
+ }, [
167
+ f("div", pe, [
168
+ f("span", ve, P(z($)("toolbar.toc")), 1),
169
+ f("button", {
170
+ class: "toc-close-btn",
171
+ onClick: t[0] || (t[0] = (i) => l.value = !1)
172
+ }, [
173
+ U(z(re), { class: "vfp-w-4 vfp-h-4" })
174
+ ])
175
+ ]),
176
+ f("div", ce, [
177
+ U(z(X), {
178
+ items: w.value,
179
+ "active-href": k.value,
180
+ onSelect: J
181
+ }, null, 8, ["items", "active-href"])
182
+ ])
183
+ ], 4),
184
+ f("div", {
185
+ class: "vfp-flex-1 vfp-transition-opacity vfp-duration-300",
186
+ style: S({ background: l.value ? "rgba(0,0,0,0.3)" : "transparent" }),
187
+ onClick: t[1] || (t[1] = (i) => l.value = !1)
188
+ }, null, 4)
189
+ ], 4)) : C("", !0),
190
+ v.value ? C("", !0) : (m(), h("div", {
191
+ key: 3,
192
+ ref_key: "hostRef",
193
+ ref: E,
194
+ class: "vfp-h-full vfp-bg-surface-toolbar vfp-shadow-lg",
195
+ style: S({ width: u.value ? "100%" : "794px", maxWidth: "100%", transition: "width 0.3s ease" })
196
+ }, null, 4))
197
+ ]));
198
+ }
199
+ }), xe = /* @__PURE__ */ le(de, [["__scopeId", "data-v-c0eebdf8"]]);
200
+ export {
201
+ xe as default
202
+ };
203
+ //# sourceMappingURL=index-S_kGRh6H.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-S_kGRh6H.mjs","sources":["../../src/renderers/Mobi/index.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch, onBeforeUnmount, defineComponent, h, type PropType } from 'vue';\nimport { X } from 'lucide-vue-next';\nimport 'foliate-js/view.js';\nimport { useTranslator } from '../../composables/useTranslator';\n\ninterface TocItem {\n label: string;\n href?: string;\n subitems?: TocItem[];\n}\n\ninterface FoliateView extends HTMLElement {\n book: {\n sections: unknown[];\n toc?: TocItem[];\n destroy?: () => void;\n } | null;\n renderer: HTMLElement & {\n setStyles?: (css: string) => void;\n next?: () => Promise<void>;\n page?: number;\n pages?: number;\n };\n open(target: string | Blob | File | ArrayBuffer): Promise<void>;\n goTo(target: number | string): Promise<void>;\n prev(distance?: number): Promise<void>;\n next(distance?: number): Promise<void>;\n}\n\nconst READER_CSS = `\n @namespace epub \"http://www.idpf.org/2007/ops\";\n html { color-scheme: light; }\n body {\n background: #ffffff !important;\n color: #1a1a1a !important;\n font-family: \"Noto Serif SC\", \"Source Han Serif SC\", Georgia, \"Times New Roman\", serif !important;\n font-size: 16px !important;\n line-height: 2 !important;\n max-width: 100% !important;\n box-sizing: border-box !important;\n word-break: break-word !important;\n overflow-wrap: break-word !important;\n }\n p, li, blockquote, dd { line-height: 2; text-align: justify; }\n p { text-indent: 2em; margin: 0.8em 0; }\n h1 { text-align: center; margin: 1.5em 0 1em; }\n h2 { margin: 1.2em 0 0.8em; }\n h3 { margin: 1em 0 0.6em; }\n img { max-width: 100% !important; height: auto !important; }\n a { color: #2563eb; text-decoration: none; }\n pre { white-space: pre-wrap !important; }\n`;\n\nconst A4_WIDTH = 794;\n\nconst props = defineProps<{ url: string }>();\nconst emit = defineEmits<{\n (e: 'chapterChange', current: number, total: number): void;\n (e: 'fullWidthChange', isFullWidth: boolean): void;\n}>();\n\nconst { t } = useTranslator();\n\nconst hostRef = ref<HTMLDivElement | null>(null);\nlet viewInstance: FoliateView | null = null;\nlet totalLocations = 1;\n\nconst loading = ref(true);\nconst error = ref<string | null>(null);\nconst toc = ref<TocItem[]>([]);\nconst showToc = ref(false);\nconst activeTocHref = ref('');\nconst isFullWidth = ref(false);\n\nconst reportProgress = (current: number, total: number) => {\n if (total > 0) totalLocations = total;\n emit('chapterChange', Math.max(1, current + 1), totalLocations);\n};\n\nconst prevPage = () => { viewInstance?.prev().catch(() => {}); };\nconst nextPage = () => { viewInstance?.next().catch(() => {}); };\nconst toggleToc = () => { showToc.value = !showToc.value; };\nconst toggleFullWidth = () => {\n isFullWidth.value = !isFullWidth.value;\n emit('fullWidthChange', isFullWidth.value);\n if (viewInstance?.renderer) {\n viewInstance.renderer.setAttribute('max-inline-size', isFullWidth.value ? '9999' : '720');\n }\n};\n\nconst handleTocClick = (href: string) => {\n activeTocHref.value = href;\n showToc.value = false;\n viewInstance?.goTo(href).catch(() => {});\n};\n\ndefineExpose({ prevPage, nextPage, toggleFullWidth, toggleToc });\n\nconst load = async () => {\n const host = hostRef.value;\n if (!host) return;\n loading.value = true;\n error.value = null;\n toc.value = [];\n showToc.value = false;\n activeTocHref.value = '';\n host.replaceChildren();\n viewInstance = null;\n\n try {\n const view = document.createElement('foliate-view') as FoliateView;\n host.appendChild(view);\n viewInstance = view;\n\n // 先注册事件\n view.addEventListener('relocate', (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (!detail) return;\n const loc = detail.location as { current?: number; total?: number } | undefined;\n if (loc && typeof loc.current === 'number' && typeof loc.total === 'number') {\n reportProgress(loc.current, loc.total);\n } else {\n const sections = viewInstance?.book?.sections ?? [];\n const idx = detail.index ?? 0;\n const frac = detail.fraction ?? 0;\n const total = Math.max(sections.length, 1);\n reportProgress(Math.round((idx + frac) / total * total), total);\n }\n const tocItem = detail.tocItem as { href?: string } | undefined;\n if (tocItem?.href) activeTocHref.value = tocItem.href;\n });\n\n const res = await fetch(props.url);\n if (!res.ok) throw new Error(`请求失败: ${res.status}`);\n const blob = await res.blob();\n let name = 'book.mobi';\n try {\n const u = new URL(props.url, window.location.href);\n const base = u.pathname.split('/').pop();\n if (base) name = decodeURIComponent(base);\n } catch { /* blob: URL */ }\n\n await view.open(new File([blob], name));\n\n // 配置 paginator:paginated 模式 + 动画\n const renderer = view.renderer;\n if (renderer) {\n renderer.setAttribute('animated', '');\n renderer.setAttribute('max-inline-size', '720');\n renderer.setAttribute('margin', '48');\n renderer.setAttribute('gap', '5%');\n renderer.setStyles?.(READER_CSS);\n await renderer.next?.();\n }\n\n toc.value = (view.book?.toc ?? []) as TocItem[];\n loading.value = false;\n reportProgress(0, view.book?.sections.length ?? 1);\n } catch (err) {\n console.error('MOBI/AZW3 加载错误:', err);\n error.value = t.value('mobi.load_failed');\n loading.value = false;\n }\n};\n\nwatch(() => props.url, () => load(), { immediate: true });\nonBeforeUnmount(() => {\n try { viewInstance?.book?.destroy?.(); } catch { /* ignore */ }\n viewInstance = null;\n hostRef.value?.replaceChildren();\n});\n</script>\n\n<template>\n <div\n class=\"vfp-relative vfp-w-full vfp-h-full vfp-flex vfp-justify-center vfp-overflow-hidden\"\n style=\"background: #f5f5f0\"\n >\n <div\n v-if=\"error\"\n class=\"vfp-absolute vfp-inset-0 vfp-flex vfp-items-center vfp-justify-center vfp-text-fg-secondary vfp-text-center vfp-p-6\"\n >\n <p class=\"vfp-text-lg\">{{ error }}</p>\n </div>\n\n <div\n v-if=\"loading && !error\"\n class=\"vfp-absolute vfp-inset-0 vfp-flex vfp-items-center vfp-justify-center vfp-z-10\"\n >\n <div class=\"vfp-w-12 vfp-h-12 vfp-border-4 vfp-border-line-strong vfp-border-t-spinner-head vfp-rounded-full vfp-animate-spin\" />\n </div>\n\n <!-- 目录侧栏 -->\n <div\n v-if=\"toc.length > 0\"\n class=\"vfp-absolute vfp-inset-0 vfp-z-20 vfp-flex vfp-transition-opacity vfp-duration-300\"\n :style=\"{ opacity: showToc ? 1 : 0, pointerEvents: showToc ? 'auto' : 'none' }\"\n >\n <div\n class=\"vfp-w-72 vfp-max-w-[80%] vfp-h-full vfp-bg-surface-overlay vfp-backdrop-blur-xl vfp-border-r vfp-border-line-weak vfp-flex vfp-flex-col vfp-shadow-2xl vfp-transition-transform vfp-duration-300\"\n :style=\"{ transform: showToc ? 'translateX(0)' : 'translateX(-100%)' }\"\n >\n <div class=\"vfp-flex vfp-items-center vfp-justify-between vfp-px-4 vfp-py-3 vfp-border-b vfp-border-line-weak vfp-flex-shrink-0\">\n <span class=\"vfp-text-fg-primary vfp-font-medium vfp-text-sm\">{{ t('toolbar.toc') }}</span>\n <button class=\"toc-close-btn\" @click=\"showToc = false\">\n <X class=\"vfp-w-4 vfp-h-4\" />\n </button>\n </div>\n <div class=\"vfp-flex-1 vfp-overflow-y-auto vfp-py-4 vfp-px-1\">\n <MobiTocList :items=\"toc\" :active-href=\"activeTocHref\" @select=\"handleTocClick\" />\n </div>\n </div>\n <div\n class=\"vfp-flex-1 vfp-transition-opacity vfp-duration-300\"\n :style=\"{ background: showToc ? 'rgba(0,0,0,0.3)' : 'transparent' }\"\n @click=\"showToc = false\"\n />\n </div>\n\n <div\n v-if=\"!error\"\n ref=\"hostRef\"\n class=\"vfp-h-full vfp-bg-surface-toolbar vfp-shadow-lg\"\n :style=\"{ width: isFullWidth ? '100%' : A4_WIDTH + 'px', maxWidth: '100%', transition: 'width 0.3s ease' }\"\n />\n </div>\n</template>\n\n<script lang=\"ts\">\ninterface TocItemLocal {\n label: string;\n href?: string;\n subitems?: TocItemLocal[];\n}\n\nconst MobiTocList = defineComponent({\n name: 'MobiTocList',\n props: {\n items: { type: Array as PropType<TocItemLocal[]>, required: true },\n activeHref: { type: String, default: '' },\n depth: { type: Number, default: 0 },\n },\n emits: ['select'],\n render(): ReturnType<typeof h> {\n return h(\n 'ul',\n { style: { listStyle: 'none', padding: 0, margin: this.depth > 0 ? '0 0 0 16px' : 0 } },\n this.items.map((item: TocItemLocal, i: number) =>\n h('li', { key: `${item.href ?? item.label}-${i}` }, [\n item.href\n ? h(\n 'button',\n {\n onClick: () => this.$emit('select', item.href),\n class: [\n 'vfp-w-full vfp-text-left vfp-py-2 vfp-px-3 vfp-text-sm vfp-rounded vfp-transition-all vfp-truncate',\n this.activeHref === item.href\n ? 'vfp-text-fg-primary vfp-bg-surface-3 vfp-font-medium'\n : 'vfp-text-fg-secondary hover:vfp-text-fg-primary hover:vfp-bg-surface-2',\n ],\n title: item.label,\n style: 'background: none; border: none; cursor: pointer',\n },\n item.label?.trim()\n )\n : h(\n 'div',\n { class: 'vfp-w-full vfp-py-2 vfp-px-3 vfp-text-sm vfp-text-fg-tertiary vfp-truncate' },\n item.label?.trim()\n ),\n item.subitems?.length\n ? h(MobiTocList, {\n items: item.subitems,\n activeHref: this.activeHref,\n depth: (this.depth as number) + 1,\n onSelect: (href: string) => this.$emit('select', href),\n })\n : null,\n ])\n )\n );\n },\n});\n\nexport default { name: 'MobiRenderer' };\n</script>\n\n<style scoped>\n.toc-close-btn {\n color: var(--fp-fg-tertiary);\n background: none;\n border: none;\n cursor: pointer;\n transition: color 0.15s;\n}\n.toc-close-btn:hover { color: #fff; }\n</style>\n"],"names":["MobiTocList","defineComponent","h","item","i","_a","_b","_c","href","__default__","READER_CSS","props","__props","emit","__emit","t","useTranslator","hostRef","ref","viewInstance","totalLocations","loading","error","toc","showToc","activeTocHref","isFullWidth","reportProgress","current","total","prevPage","nextPage","toggleToc","toggleFullWidth","handleTocClick","__expose","load","host","view","e","detail","loc","sections","idx","frac","tocItem","res","blob","name","base","renderer","_d","err","watch","onBeforeUnmount","_openBlock","_createElementBlock","_hoisted_1","_hoisted_2","_createElementVNode","_hoisted_3","_toDisplayString","_hoisted_4","_cache","_normalizeStyle","_hoisted_5","_hoisted_6","_unref","_createVNode","X","_hoisted_7"],"mappings":"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"}