@l1yp/file-viewer 0.1.0 → 0.1.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.
Files changed (29) hide show
  1. package/README.md +1 -0
  2. package/dist/components/DocxNavigationPane.js +7 -0
  3. package/dist/components/DocxNavigationPane.vue.d.ts +23 -0
  4. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js +96 -0
  5. package/dist/components/DocxViewer.js +1 -1
  6. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +70 -37
  7. package/dist/components/FileViewerBody.js +1 -1
  8. package/dist/components/FileViewerBody.vue.d.ts +5 -0
  9. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +248 -247
  10. package/dist/components/PptxNavigationPane.js +7 -0
  11. package/dist/components/PptxNavigationPane.vue.d.ts +20 -0
  12. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js +118 -0
  13. package/dist/components/PptxViewer.js +1 -1
  14. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js +64 -38
  15. package/dist/components/XlsxViewer.js +1 -1
  16. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +299 -304
  17. package/dist/components/ZipBrowser.js +1 -1
  18. package/dist/components/ZipBrowser.vue.d.ts +3 -1
  19. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +152 -122
  20. package/dist/composables/useElementNavigation.d.ts +21 -0
  21. package/dist/composables/useElementNavigation.js +61 -0
  22. package/dist/lib/archiveHtml.d.ts +49 -0
  23. package/dist/lib/archiveHtml.js +375 -0
  24. package/dist/lib/docxNavigation.d.ts +31 -0
  25. package/dist/lib/docxNavigation.js +63 -0
  26. package/dist/lib/pptxNavigation.d.ts +7 -0
  27. package/dist/lib/pptxNavigation.js +27 -0
  28. package/dist/style.css +1 -1
  29. package/package.json +1 -1
@@ -2,6 +2,6 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
2
2
  import t from "./ZipBrowser.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/components/ZipBrowser.vue
5
- var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-04e7e09d"]]);
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-3b2fe067"]]);
6
6
  //#endregion
7
7
  export { n as default };
@@ -3,7 +3,9 @@
3
3
  * and browse the contents. A master→detail within the narrow pane — a collapsible file tree, and selecting an
4
4
  * entry extracts just that one, wraps it in a blob URL, and hands it to {@link FileViewerBody} (so a file
5
5
  * inside the zip previews with every viewer a top-level file gets — code/image/pdf/cert/audio/nested-zip/…).
6
- * No entry bytes are kept around: selecting decodes on demand, and the download button re-extracts.
6
+ * HTML is the one multi-entry case: its relative JS/CSS/image/font dependencies are resolved from the same ZIP
7
+ * and rewritten before entering the existing sandboxed iframe. Other entry bytes are not retained; selecting
8
+ * decodes on demand, and the download button re-extracts.
7
9
  */
8
10
  type __VLS_Props = {
9
11
  url: string;
@@ -2,8 +2,10 @@ import { fileExt as e, isDicomFile as t, isTextFile as n } from "../lib/fileType
2
2
  import { canToggleView as r, classifyViewerFile as i, mediaMimeForName as ee } from "../lib/viewerKind.js";
3
3
  import { formatBytes as a } from "../lib/format.js";
4
4
  import o from "./icons/Icon.js";
5
- import { ZipEncryptedError as te, ZipPasswordError as s, extractZipEntry as c, readZip as ne, zipSupported as re } from "../lib/zip.js";
6
- import { Fragment as l, computed as u, createBlock as d, createCommentVNode as f, createElementBlock as p, createElementVNode as m, createVNode as h, defineAsyncComponent as g, defineComponent as _, normalizeClass as v, normalizeStyle as ie, onBeforeUnmount as ae, onMounted as oe, openBlock as y, reactive as se, ref as b, renderList as ce, toDisplayString as x, unref as S, vModelText as le, watch as ue, withDirectives as de, withKeys as fe } from "vue";
5
+ import { decodeTextSniff as te } from "../lib/encoding.js";
6
+ import { ZipEncryptedError as s, ZipPasswordError as c, extractZipEntry as l, readZip as u, zipSupported as ne } from "../lib/zip.js";
7
+ import { buildArchiveHtmlPreview as re } from "../lib/archiveHtml.js";
8
+ import { Fragment as d, computed as f, createBlock as p, createCommentVNode as m, createElementBlock as h, createElementVNode as g, createVNode as _, defineAsyncComponent as v, defineComponent as y, normalizeClass as b, normalizeStyle as ie, onBeforeUnmount as ae, onMounted as oe, openBlock as x, reactive as se, ref as S, renderList as ce, toDisplayString as C, unref as w, vModelText as le, watch as ue, withDirectives as de, withKeys as fe } from "vue";
7
9
  //#region src/components/ZipBrowser.vue?vue&type=script&setup=true&lang.ts
8
10
  var pe = { class: "zip" }, me = {
9
11
  key: 0,
@@ -31,20 +33,20 @@ var pe = { class: "zip" }, me = {
31
33
  }, Oe = ["onClick"], ke = { class: "zip__twisty" }, Ae = { class: "zip__rowicon" }, je = { class: "zip__rowname" }, Me = {
32
34
  key: 1,
33
35
  class: "zip__rowsize"
34
- }, C = /*@__PURE__*/ _({
36
+ }, T = /*@__PURE__*/ y({
35
37
  __name: "ZipBrowser",
36
38
  props: {
37
39
  url: {},
38
40
  name: {}
39
41
  },
40
- setup(_) {
41
- let C = g(() => import("./DicomViewer.js")), Ne = g(() => import("./FileViewerBody.js")), w = _, T = b("loading"), E = b(""), D = null, O = b([]), k = b(""), A = b(""), j = b(!1), Pe = u(() => O.value.some((e) => e.encrypted)), M = b({
42
+ setup(y) {
43
+ let T = v(() => import("./DicomViewer.js")), Ne = v(() => import("./FileViewerBody.js")), E = y, D = S("loading"), O = S(""), k = null, A = S([]), j = S(""), M = S(""), N = S(!1), Pe = f(() => A.value.some((e) => e.encrypted)), P = S({
42
44
  name: "",
43
45
  path: "",
44
46
  isDir: !0,
45
47
  size: 0,
46
48
  children: []
47
- }), N = se(/* @__PURE__ */ new Set()), Fe = u(() => O.value.reduce((e, t) => e + +!t.isDir, 0)), Ie = u(() => O.value.reduce((e, t) => e + (t.isDir ? 0 : t.size), 0));
49
+ }), F = se(/* @__PURE__ */ new Set()), Fe = f(() => A.value.reduce((e, t) => e + +!t.isDir, 0)), Ie = f(() => A.value.reduce((e, t) => e + (t.isDir ? 0 : t.size), 0));
48
50
  function Le(e) {
49
51
  let t = {
50
52
  name: "",
@@ -83,244 +85,272 @@ var pe = { class: "zip" }, me = {
83
85
  children: []
84
86
  });
85
87
  }
86
- return P(t), t;
88
+ return I(t), t;
87
89
  }
88
- function P(e) {
90
+ function I(e) {
89
91
  e.children.sort((e, t) => e.isDir === t.isDir ? e.name.localeCompare(t.name) : e.isDir ? -1 : 1);
90
- for (let t of e.children) t.isDir && P(t);
92
+ for (let t of e.children) t.isDir && I(t);
91
93
  }
92
- let F = u(() => {
94
+ let L = f(() => {
93
95
  let e = [], t = (n, r) => {
94
96
  for (let i of n) e.push({
95
97
  node: i,
96
98
  depth: r
97
- }), i.isDir && N.has(i.path) && t(i.children, r + 1);
99
+ }), i.isDir && F.has(i.path) && t(i.children, r + 1);
98
100
  };
99
- return t(M.value.children, 0), e;
101
+ return t(P.value.children, 0), e;
100
102
  });
101
- async function I() {
102
- if (T.value = "loading", E.value = "", L.value = null, k.value = "", A.value = "", j.value = !1, J(), G.value = [], !re()) {
103
- E.value = "当前浏览器不支持在前端解压 ZIP(缺少 DecompressionStream)", T.value = "error";
103
+ async function R() {
104
+ if (J++, D.value = "loading", O.value = "", z.value = null, U.value = null, W.value = "", j.value = "", M.value = "", N.value = !1, X(), Y.value = [], !ne()) {
105
+ O.value = "当前浏览器不支持在前端解压 ZIP(缺少 DecompressionStream)", D.value = "error";
104
106
  return;
105
107
  }
106
108
  try {
107
- let e = await fetch(w.url);
109
+ let e = await fetch(E.url);
108
110
  if (!e.ok) throw Error(`HTTP ${e.status}`);
109
- D = await e.arrayBuffer();
110
- let t = ne(D);
111
- O.value = t, M.value = Le(t), N.clear(), T.value = "ready";
111
+ k = await e.arrayBuffer();
112
+ let t = u(k);
113
+ A.value = t, P.value = Le(t), F.clear(), D.value = "ready";
112
114
  } catch (e) {
113
- E.value = e instanceof Error ? e.message : String(e), T.value = "error";
115
+ O.value = e instanceof Error ? e.message : String(e), D.value = "error";
114
116
  }
115
117
  }
116
- let L = b(null), R = b("ready"), z = b(""), B = b(""), V = b(null), H = b(!1), U = b("render"), W = b(""), G = b([]), K = b(0), q = b(""), Re = u(() => G.value.reduce((e, t) => e + (t.size ?? 0), 0)), ze = u(() => r(V.value, B.value));
117
- function J() {
118
- W.value &&= (URL.revokeObjectURL(W.value), "");
118
+ let z = S(null), B = S("ready"), V = S(""), H = S(""), U = S(null), W = S(""), G = S(!1), K = S("render"), q = S(""), J = 0, Y = S([]), Re = S(0), ze = S(""), Be = f(() => Y.value.reduce((e, t) => e + (t.size ?? 0), 0)), Ve = f(() => r(U.value, H.value));
119
+ function X() {
120
+ q.value &&= (URL.revokeObjectURL(q.value), "");
119
121
  }
120
- function Y(e) {
122
+ function Z(e) {
121
123
  let t = e.lastIndexOf("/");
122
124
  return t < 0 ? "" : e.slice(0, t);
123
125
  }
124
- function Be(e) {
125
- let n = Y(e.path);
126
- return O.value.filter((e) => !e.isDir && t(e.name) && Y(e.path) === n).sort((e, t) => e.path.localeCompare(t.path, void 0, {
126
+ function He(e) {
127
+ let n = Z(e.path);
128
+ return A.value.filter((e) => !e.isDir && t(e.name) && Z(e.path) === n).sort((e, t) => e.path.localeCompare(t.path, void 0, {
127
129
  numeric: !0,
128
130
  sensitivity: "base"
129
131
  }));
130
132
  }
131
- function Ve(t, r) {
133
+ function Ue(t, r) {
132
134
  if (n(r)) return !0;
133
135
  if (e(r)) return !1;
134
136
  let i = Math.min(t.length, 4096);
135
137
  for (let e = 0; e < i; e++) if (t[e] === 0) return !1;
136
138
  return !0;
137
139
  }
138
- async function X(e) {
139
- if (L.value = e, R.value = "loading", z.value = "", B.value = "", V.value = null, H.value = !1, U.value = "render", J(), G.value = [], D) try {
140
+ async function We(e) {
141
+ let n = ++J, r = "";
142
+ if (z.value = e, B.value = "loading", V.value = "", H.value = "", U.value = null, W.value = "", G.value = !1, K.value = "render", X(), Y.value = [], k) try {
140
143
  if (t(e.name)) {
141
- let t = Be(e);
144
+ let t = He(e);
142
145
  if (t.length > 1) {
143
- G.value = t.map((e) => ({
146
+ Y.value = t.map((e) => ({
144
147
  name: e.name,
145
148
  size: e.size,
146
149
  load: async () => {
147
- let t = await c(D, e, k.value), n = new ArrayBuffer(t.byteLength);
150
+ let t = await l(k, e, j.value), n = new ArrayBuffer(t.byteLength);
148
151
  return new Uint8Array(n).set(t), n;
149
152
  }
150
- })), K.value = Math.max(0, t.findIndex((t) => t.path === e.path)), q.value = Y(e.path).split("/").pop() || e.name, H.value = !0, R.value = "ready";
153
+ })), Re.value = Math.max(0, t.findIndex((t) => t.path === e.path)), ze.value = Z(e.path).split("/").pop() || e.name, G.value = !0, B.value = "ready";
151
154
  return;
152
155
  }
153
156
  }
154
- let n = await c(D, e, k.value);
155
- if (L.value !== e) return;
156
- W.value = URL.createObjectURL(new Blob([n], { type: ee(e.name) })), V.value = i({
157
+ let a = k, o = await l(a, e, j.value);
158
+ if (J !== n || z.value !== e) return;
159
+ let s = URL.createObjectURL(new Blob([o], { type: ee(e.name) }));
160
+ r = s;
161
+ let c = i({
157
162
  name: e.name,
158
- url: W.value,
163
+ url: s,
159
164
  size: e.size,
160
- textHint: Ve(n, e.name)
161
- }), R.value = "ready";
165
+ textHint: Ue(o, e.name)
166
+ }), u = "";
167
+ if (c.html) {
168
+ let t = te(o);
169
+ c.content = t;
170
+ let i = await re({
171
+ html: t,
172
+ documentPath: e.path,
173
+ entries: A.value,
174
+ loadEntry: (e) => l(a, e, j.value)
175
+ });
176
+ if (J !== n || z.value !== e) {
177
+ URL.revokeObjectURL(s), r = "";
178
+ return;
179
+ }
180
+ u = i.html;
181
+ }
182
+ if (J !== n || z.value !== e) {
183
+ URL.revokeObjectURL(s), r = "";
184
+ return;
185
+ }
186
+ q.value = s, r = "", W.value = u, U.value = c, B.value = "ready";
162
187
  } catch (e) {
163
- e instanceof te ? z.value = "该文件已加密:请返回列表并在顶部输入密码" : e instanceof s ? (z.value = "密码错误", j.value = !1) : z.value = e instanceof Error ? e.message : String(e), R.value = "error";
188
+ r && URL.revokeObjectURL(r), e instanceof s ? V.value = "该文件已加密:请返回列表并在顶部输入密码" : e instanceof c ? (V.value = "密码错误", N.value = !1) : V.value = e instanceof Error ? e.message : String(e), B.value = "error";
164
189
  }
165
190
  }
166
- async function Z() {
167
- A.value = "";
168
- let e = O.value.find((e) => !e.isDir && e.encrypted && e.size > 0);
169
- if (!e || !D) {
170
- j.value = !0;
191
+ async function Ge() {
192
+ M.value = "";
193
+ let e = A.value.find((e) => !e.isDir && e.encrypted && e.size > 0);
194
+ if (!e || !k) {
195
+ N.value = !0;
171
196
  return;
172
197
  }
173
198
  try {
174
- await c(D, e, k.value), j.value = !0, L.value && X(L.value);
199
+ await l(k, e, j.value), N.value = !0, z.value && We(z.value);
175
200
  } catch (e) {
176
- j.value = !1, A.value = e instanceof s ? "密码错误" : e instanceof Error ? e.message : String(e);
201
+ N.value = !1, M.value = e instanceof c ? "密码错误" : e instanceof Error ? e.message : String(e);
177
202
  }
178
203
  }
179
- function He(e) {
180
- e.isDir ? N.has(e.path) ? N.delete(e.path) : N.add(e.path) : e.entry && X(e.entry);
204
+ function Ke(e) {
205
+ e.isDir ? F.has(e.path) ? F.delete(e.path) : F.add(e.path) : e.entry && We(e.entry);
181
206
  }
182
- function Ue() {
183
- L.value = null, B.value = "", V.value = null, H.value = !1, J(), G.value = [];
207
+ function qe() {
208
+ J++, z.value = null, H.value = "", U.value = null, W.value = "", G.value = !1, X(), Y.value = [];
184
209
  }
185
- async function We(e) {
186
- if (D) try {
187
- let t = URL.createObjectURL(new Blob([await c(D, e, k.value)])), n = document.createElement("a");
210
+ async function Je(e) {
211
+ if (k) try {
212
+ let t = URL.createObjectURL(new Blob([await l(k, e, j.value)])), n = document.createElement("a");
188
213
  n.href = t, n.download = e.name, document.body.appendChild(n), n.click(), n.remove(), setTimeout(() => URL.revokeObjectURL(t), 1e3);
189
214
  } catch {}
190
215
  }
191
- let Q = b(!1), $;
192
- async function Ge() {
216
+ let Q = S(!1), $;
217
+ async function Ye() {
193
218
  try {
194
- await navigator.clipboard?.writeText(B.value);
219
+ await navigator.clipboard?.writeText(H.value);
195
220
  } catch {}
196
221
  Q.value = !0, clearTimeout($), $ = setTimeout(() => Q.value = !1, 1400);
197
222
  }
198
- return oe(I), ue(() => w.url, I), ae(() => {
199
- J(), clearTimeout($);
200
- }), (e, t) => (y(), p("div", pe, [T.value === "loading" ? (y(), p("p", me, "正在解压…")) : T.value === "error" ? (y(), p("div", he, [m("p", null, x(E.value), 1), m("button", {
223
+ return oe(R), ue(() => E.url, R), ae(() => {
224
+ J++, X(), clearTimeout($);
225
+ }), (e, t) => (x(), h("div", pe, [D.value === "loading" ? (x(), h("p", me, "正在解压…")) : D.value === "error" ? (x(), h("div", he, [g("p", null, C(O.value), 1), g("button", {
201
226
  type: "button",
202
227
  class: "zip__retry",
203
- onClick: I
204
- }, "重试")])) : L.value ? (y(), p(l, { key: 2 }, [m("div", ge, [
205
- m("button", {
228
+ onClick: R
229
+ }, "重试")])) : z.value ? (x(), h(d, { key: 2 }, [g("div", ge, [
230
+ g("button", {
206
231
  type: "button",
207
232
  class: "zip__back",
208
233
  title: "返回列表",
209
- onClick: Ue
210
- }, [h(o, {
234
+ onClick: qe
235
+ }, [_(o, {
211
236
  name: "arrowLeft",
212
237
  size: 16
213
238
  })]),
214
- m("span", {
239
+ g("span", {
215
240
  class: "zip__path",
216
- title: L.value.path
217
- }, x(L.value.path), 9, _e),
218
- m("span", ve, x(S(a)(L.value.size)), 1),
219
- ze.value ? (y(), p("div", ye, [m("button", {
241
+ title: z.value.path
242
+ }, C(z.value.path), 9, _e),
243
+ g("span", ve, C(w(a)(z.value.size)), 1),
244
+ Ve.value ? (x(), h("div", ye, [g("button", {
220
245
  type: "button",
221
- class: v(["zip__vbtn", { "zip__vbtn--on": U.value === "render" }]),
246
+ class: b(["zip__vbtn", { "zip__vbtn--on": K.value === "render" }]),
222
247
  title: "渲染",
223
- onClick: t[0] ||= (e) => U.value = "render"
224
- }, [h(o, {
248
+ onClick: t[0] ||= (e) => K.value = "render"
249
+ }, [_(o, {
225
250
  name: "eye",
226
251
  size: 14
227
- })], 2), m("button", {
252
+ })], 2), g("button", {
228
253
  type: "button",
229
- class: v(["zip__vbtn", { "zip__vbtn--on": U.value === "source" }]),
254
+ class: b(["zip__vbtn", { "zip__vbtn--on": K.value === "source" }]),
230
255
  title: "源码",
231
- onClick: t[1] ||= (e) => U.value = "source"
232
- }, [h(o, {
256
+ onClick: t[1] ||= (e) => K.value = "source"
257
+ }, [_(o, {
233
258
  name: "code",
234
259
  size: 14
235
- })], 2)])) : f("", !0),
236
- B.value && R.value === "ready" ? (y(), p("button", {
260
+ })], 2)])) : m("", !0),
261
+ H.value && B.value === "ready" ? (x(), h("button", {
237
262
  key: 1,
238
263
  type: "button",
239
264
  class: "zip__act",
240
265
  title: Q.value ? "已复制" : "复制",
241
- onClick: Ge
242
- }, [h(o, {
266
+ onClick: Ye
267
+ }, [_(o, {
243
268
  name: "copy",
244
269
  size: 14
245
- })], 8, be)) : f("", !0),
246
- m("button", {
270
+ })], 8, be)) : m("", !0),
271
+ g("button", {
247
272
  type: "button",
248
273
  class: "zip__act",
249
274
  title: "下载",
250
- onClick: t[2] ||= (e) => We(L.value)
251
- }, [h(o, {
275
+ onClick: t[2] ||= (e) => Je(z.value)
276
+ }, [_(o, {
252
277
  name: "download",
253
278
  size: 15
254
279
  })])
255
- ]), R.value === "loading" ? (y(), p("p", xe, "读取中…")) : R.value === "error" ? (y(), p("div", Se, [m("p", null, x(z.value), 1)])) : H.value ? (y(), d(S(C), {
280
+ ]), B.value === "loading" ? (x(), h("p", xe, "读取中…")) : B.value === "error" ? (x(), h("div", Se, [g("p", null, C(V.value), 1)])) : G.value ? (x(), p(w(T), {
256
281
  key: 2,
257
- slices: G.value,
258
- start: K.value,
259
- name: q.value,
260
- size: Re.value
282
+ slices: Y.value,
283
+ start: Re.value,
284
+ name: ze.value,
285
+ size: Be.value
261
286
  }, null, 8, [
262
287
  "slices",
263
288
  "start",
264
289
  "name",
265
290
  "size"
266
- ])) : V.value ? (y(), d(S(Ne), {
291
+ ])) : U.value ? (x(), p(w(Ne), {
267
292
  key: 3,
268
- file: V.value,
269
- mode: U.value,
270
- onLoaded: t[3] ||= (e) => B.value = e
271
- }, null, 8, ["file", "mode"])) : f("", !0)], 64)) : (y(), p(l, { key: 3 }, [
272
- m("div", Ce, x(Fe.value) + " 个文件 · " + x(S(a)(Ie.value)), 1),
273
- Pe.value ? (y(), p("div", {
293
+ file: U.value,
294
+ mode: K.value,
295
+ "html-preview": W.value || void 0,
296
+ onLoaded: t[3] ||= (e) => H.value = e
297
+ }, null, 8, [
298
+ "file",
299
+ "mode",
300
+ "html-preview"
301
+ ])) : m("", !0)], 64)) : (x(), h(d, { key: 3 }, [
302
+ g("div", Ce, C(Fe.value) + " 个文件 · " + C(w(a)(Ie.value)), 1),
303
+ Pe.value ? (x(), h("div", {
274
304
  key: 0,
275
- class: v(["zip__pw", { "zip__pw--ok": j.value }])
305
+ class: b(["zip__pw", { "zip__pw--ok": N.value }])
276
306
  }, [
277
- h(o, {
307
+ _(o, {
278
308
  name: "key",
279
309
  size: 14
280
310
  }),
281
- de(m("input", {
282
- "onUpdate:modelValue": t[4] ||= (e) => k.value = e,
311
+ de(g("input", {
312
+ "onUpdate:modelValue": t[4] ||= (e) => j.value = e,
283
313
  type: "password",
284
314
  class: "zip__pwinput",
285
- placeholder: j.value ? "已解锁(如需可重新输入)" : "此压缩包已加密,输入密码解锁",
286
- onKeyup: fe(Z, ["enter"])
287
- }, null, 40, we), [[le, k.value]]),
288
- m("button", {
315
+ placeholder: N.value ? "已解锁(如需可重新输入)" : "此压缩包已加密,输入密码解锁",
316
+ onKeyup: fe(Ge, ["enter"])
317
+ }, null, 40, we), [[le, j.value]]),
318
+ g("button", {
289
319
  type: "button",
290
320
  class: "zip__pwbtn",
291
- onClick: Z
292
- }, x(j.value ? "已解锁" : "解锁"), 1),
293
- A.value ? (y(), p("span", Te, x(A.value), 1)) : f("", !0)
294
- ], 2)) : f("", !0),
295
- m("div", Ee, [F.value.length === 0 ? (y(), p("p", De, "空压缩包")) : f("", !0), (y(!0), p(l, null, ce(F.value, (e) => (y(), p("button", {
321
+ onClick: Ge
322
+ }, C(N.value ? "已解锁" : "解锁"), 1),
323
+ M.value ? (x(), h("span", Te, C(M.value), 1)) : m("", !0)
324
+ ], 2)) : m("", !0),
325
+ g("div", Ee, [L.value.length === 0 ? (x(), h("p", De, "空压缩包")) : m("", !0), (x(!0), h(d, null, ce(L.value, (e) => (x(), h("button", {
296
326
  key: e.node.path,
297
327
  type: "button",
298
- class: v(["zip__row", { "zip__row--dir": e.node.isDir }]),
328
+ class: b(["zip__row", { "zip__row--dir": e.node.isDir }]),
299
329
  style: ie({ paddingLeft: `${12 + e.depth * 14}px` }),
300
- onClick: (t) => He(e.node)
330
+ onClick: (t) => Ke(e.node)
301
331
  }, [
302
- m("span", ke, [e.node.isDir ? (y(), d(o, {
332
+ g("span", ke, [e.node.isDir ? (x(), p(o, {
303
333
  key: 0,
304
334
  name: "chevronRight",
305
335
  size: 13,
306
- class: v({ "zip__twisty--open": N.has(e.node.path) })
307
- }, null, 8, ["class"])) : f("", !0)]),
308
- m("span", Ae, [h(o, {
336
+ class: b({ "zip__twisty--open": F.has(e.node.path) })
337
+ }, null, 8, ["class"])) : m("", !0)]),
338
+ g("span", Ae, [_(o, {
309
339
  name: e.node.isDir ? "folder" : "fileOutline",
310
340
  size: 15
311
341
  }, null, 8, ["name"])]),
312
- m("span", je, x(e.node.name), 1),
313
- e.node.entry?.encrypted ? (y(), d(o, {
342
+ g("span", je, C(e.node.name), 1),
343
+ e.node.entry?.encrypted ? (x(), p(o, {
314
344
  key: 0,
315
345
  name: "key",
316
346
  size: 12,
317
347
  class: "zip__rowlock",
318
348
  title: "已加密"
319
- })) : f("", !0),
320
- e.node.isDir ? f("", !0) : (y(), p("span", Me, x(S(a)(e.node.size)), 1))
349
+ })) : m("", !0),
350
+ e.node.isDir ? m("", !0) : (x(), h("span", Me, C(w(a)(e.node.size)), 1))
321
351
  ], 14, Oe))), 128))])
322
352
  ], 64))]));
323
353
  }
324
354
  });
325
355
  //#endregion
326
- export { C as default };
356
+ export { T as default };
@@ -0,0 +1,21 @@
1
+ import { type ShallowRef } from 'vue';
2
+ export interface ElementNavigationItem {
3
+ id: string;
4
+ title: string;
5
+ level: number;
6
+ target: HTMLElement;
7
+ }
8
+ /**
9
+ * Scroll-spy + in-container navigation for DOM-backed document viewers.
10
+ *
11
+ * The rendered document remains the only source of scroll state. Navigation entries retain their target
12
+ * elements, while this composable exposes readonly-by-convention refs and explicit mutation actions.
13
+ */
14
+ export declare function useElementNavigation(scrollRoot: Readonly<ShallowRef<HTMLElement | null>>): {
15
+ outlineItems: ShallowRef<ElementNavigationItem[], ElementNavigationItem[]>;
16
+ pageItems: ShallowRef<ElementNavigationItem[], ElementNavigationItem[]>;
17
+ activeOutlineId: ShallowRef<string, string>;
18
+ activePageId: ShallowRef<string, string>;
19
+ setItems: (outline: readonly ElementNavigationItem[], pages: readonly ElementNavigationItem[]) => void;
20
+ navigate: (item: ElementNavigationItem) => void;
21
+ };
@@ -0,0 +1,61 @@
1
+ import { onBeforeUnmount as e, shallowRef as t, watch as n } from "vue";
2
+ //#region src/composables/useElementNavigation.ts
3
+ function r(r) {
4
+ let i = t([]), a = t([]), o = t(""), s = t(""), c = 0, l = null;
5
+ function u(e, t) {
6
+ if (!e.length) return;
7
+ let n = e[0];
8
+ for (let r of e) {
9
+ if (r.target.getBoundingClientRect().top > t) break;
10
+ n = r;
11
+ }
12
+ return n;
13
+ }
14
+ function d() {
15
+ c = 0;
16
+ let e = r.value;
17
+ if (!e) return;
18
+ let t = e.getBoundingClientRect().top + 32;
19
+ o.value = u(i.value, t)?.id ?? "", s.value = u(a.value, t)?.id ?? "";
20
+ }
21
+ function f() {
22
+ if (!c) {
23
+ if (typeof requestAnimationFrame > "u") {
24
+ d();
25
+ return;
26
+ }
27
+ c = requestAnimationFrame(d);
28
+ }
29
+ }
30
+ function p(e) {
31
+ l?.removeEventListener("scroll", f), l = e, l?.addEventListener("scroll", f, { passive: !0 }), f();
32
+ }
33
+ let m = n(r, p, {
34
+ immediate: !0,
35
+ flush: "post"
36
+ });
37
+ function h(e, t) {
38
+ i.value = [...e], a.value = [...t], o.value = e[0]?.id ?? "", s.value = t[0]?.id ?? "", f();
39
+ }
40
+ function g(e) {
41
+ let t = r.value;
42
+ if (!t) return;
43
+ let n = t.getBoundingClientRect().top, i = e.target.getBoundingClientRect().top;
44
+ t.scrollTo({
45
+ top: t.scrollTop + i - n - 18,
46
+ behavior: "smooth"
47
+ });
48
+ }
49
+ return e(() => {
50
+ m(), l?.removeEventListener("scroll", f), c && typeof cancelAnimationFrame < "u" && cancelAnimationFrame(c);
51
+ }), {
52
+ outlineItems: i,
53
+ pageItems: a,
54
+ activeOutlineId: o,
55
+ activePageId: s,
56
+ setItems: h,
57
+ navigate: g
58
+ };
59
+ }
60
+ //#endregion
61
+ export { r as useElementNavigation };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Build a renderable HTML document from one entry inside an archive.
3
+ *
4
+ * A selected ZIP entry normally gets a single `blob:` URL. That is enough for the HTML bytes themselves, but
5
+ * relative references such as `./assets/app.js` and `./styles/site.css` have nowhere to resolve: blob URLs have
6
+ * no archive directory behind them. This module treats the archive's flat entry list as a tiny read-only web
7
+ * root, extracts only resources referenced by the selected document, and rewrites those references to `data:`
8
+ * URLs before the document reaches FileViewerBody's sandboxed iframe.
9
+ *
10
+ * `data:` is deliberate rather than `blob:`. FileViewerBody's opt-in interactive iframe uses `allow-scripts`
11
+ * WITHOUT `allow-same-origin`, giving it an opaque origin. Chromium correctly refuses subresource blob URLs
12
+ * minted by the parent from that opaque frame, while data URLs remain loadable. Scripts therefore still run
13
+ * only after the existing explicit opt-in, but both inert CSS/image preview and interactive JS work reliably.
14
+ */
15
+ export interface ArchiveHtmlEntry {
16
+ path: string;
17
+ name: string;
18
+ size: number;
19
+ isDir: boolean;
20
+ }
21
+ export interface ArchiveHtmlPreviewOptions<T extends ArchiveHtmlEntry> {
22
+ html: string;
23
+ documentPath: string;
24
+ entries: readonly T[];
25
+ loadEntry: (entry: T) => Uint8Array | Promise<Uint8Array>;
26
+ /** Maximum number of distinct dependency entries automatically extracted. */
27
+ maxResources?: number;
28
+ /** Maximum declared/decoded bytes across automatically extracted dependencies. */
29
+ maxTotalBytes?: number;
30
+ /** Maximum declared/decoded bytes for one dependency. */
31
+ maxEntryBytes?: number;
32
+ }
33
+ export interface ArchiveHtmlPreview {
34
+ html: string;
35
+ loadedResources: number;
36
+ skippedResources: number;
37
+ }
38
+ export declare function createArchiveEntryResolver<T extends ArchiveHtmlEntry>(entries: readonly T[]): (ref: string, basePath: string) => T | undefined;
39
+ /** Rewrite CSS `@import` and `url(...)` references through an async archive resource resolver. */
40
+ export declare function rewriteArchiveCssReferences(css: string, resolve: (ref: string, kind: 'css' | 'raw') => string | null | Promise<string | null>): Promise<string>;
41
+ /** Best-effort rewrite for static/dynamic ESM imports and `new URL(x, import.meta.url)` assets. */
42
+ export declare function rewriteArchiveModuleReferences(source: string, resolve: (ref: string, kind: 'module' | 'raw') => string | null | Promise<string | null>): Promise<string>;
43
+ /** Resolve bare ESM specifiers against inline import maps while paths still refer to archive entries. */
44
+ export declare function createArchiveImportMapResolver(sources: readonly string[], documentPath: string): (specifier: string, referrerPath: string) => string | null | undefined;
45
+ /**
46
+ * Resolve one archive HTML entry plus its reachable dependencies into a complete iframe document.
47
+ * Browser-only: DOMParser parses inertly; it neither executes scripts nor fetches the original references.
48
+ */
49
+ export declare function buildArchiveHtmlPreview<T extends ArchiveHtmlEntry>(options: ArchiveHtmlPreviewOptions<T>): Promise<ArchiveHtmlPreview>;