@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.
- package/README.md +1 -0
- package/dist/components/DocxNavigationPane.js +7 -0
- package/dist/components/DocxNavigationPane.vue.d.ts +23 -0
- package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js +96 -0
- package/dist/components/DocxViewer.js +1 -1
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +70 -37
- package/dist/components/FileViewerBody.js +1 -1
- package/dist/components/FileViewerBody.vue.d.ts +5 -0
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +248 -247
- package/dist/components/PptxNavigationPane.js +7 -0
- package/dist/components/PptxNavigationPane.vue.d.ts +20 -0
- package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js +118 -0
- package/dist/components/PptxViewer.js +1 -1
- package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js +64 -38
- package/dist/components/XlsxViewer.js +1 -1
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +299 -304
- package/dist/components/ZipBrowser.js +1 -1
- package/dist/components/ZipBrowser.vue.d.ts +3 -1
- package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +152 -122
- package/dist/composables/useElementNavigation.d.ts +21 -0
- package/dist/composables/useElementNavigation.js +61 -0
- package/dist/lib/archiveHtml.d.ts +49 -0
- package/dist/lib/archiveHtml.js +375 -0
- package/dist/lib/docxNavigation.d.ts +31 -0
- package/dist/lib/docxNavigation.js +63 -0
- package/dist/lib/pptxNavigation.d.ts +7 -0
- package/dist/lib/pptxNavigation.js +27 -0
- package/dist/style.css +1 -1
- 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-
|
|
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
|
-
*
|
|
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 {
|
|
6
|
-
import {
|
|
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
|
-
},
|
|
36
|
+
}, T = /*@__PURE__*/ y({
|
|
35
37
|
__name: "ZipBrowser",
|
|
36
38
|
props: {
|
|
37
39
|
url: {},
|
|
38
40
|
name: {}
|
|
39
41
|
},
|
|
40
|
-
setup(
|
|
41
|
-
let
|
|
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
|
-
}),
|
|
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
|
|
88
|
+
return I(t), t;
|
|
87
89
|
}
|
|
88
|
-
function
|
|
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 &&
|
|
92
|
+
for (let t of e.children) t.isDir && I(t);
|
|
91
93
|
}
|
|
92
|
-
let
|
|
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 &&
|
|
99
|
+
}), i.isDir && F.has(i.path) && t(i.children, r + 1);
|
|
98
100
|
};
|
|
99
|
-
return t(
|
|
101
|
+
return t(P.value.children, 0), e;
|
|
100
102
|
});
|
|
101
|
-
async function
|
|
102
|
-
if (
|
|
103
|
-
|
|
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(
|
|
109
|
+
let e = await fetch(E.url);
|
|
108
110
|
if (!e.ok) throw Error(`HTTP ${e.status}`);
|
|
109
|
-
|
|
110
|
-
let t =
|
|
111
|
-
|
|
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
|
-
|
|
115
|
+
O.value = e instanceof Error ? e.message : String(e), D.value = "error";
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
|
-
let
|
|
117
|
-
function
|
|
118
|
-
|
|
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
|
|
122
|
+
function Z(e) {
|
|
121
123
|
let t = e.lastIndexOf("/");
|
|
122
124
|
return t < 0 ? "" : e.slice(0, t);
|
|
123
125
|
}
|
|
124
|
-
function
|
|
125
|
-
let n =
|
|
126
|
-
return
|
|
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
|
|
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
|
|
139
|
-
|
|
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 =
|
|
144
|
+
let t = He(e);
|
|
142
145
|
if (t.length > 1) {
|
|
143
|
-
|
|
146
|
+
Y.value = t.map((e) => ({
|
|
144
147
|
name: e.name,
|
|
145
148
|
size: e.size,
|
|
146
149
|
load: async () => {
|
|
147
|
-
let t = await
|
|
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
|
-
})),
|
|
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
|
|
155
|
-
if (
|
|
156
|
-
|
|
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:
|
|
163
|
+
url: s,
|
|
159
164
|
size: e.size,
|
|
160
|
-
textHint:
|
|
161
|
-
}),
|
|
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
|
|
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
|
|
167
|
-
|
|
168
|
-
let e =
|
|
169
|
-
if (!e || !
|
|
170
|
-
|
|
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
|
|
199
|
+
await l(k, e, j.value), N.value = !0, z.value && We(z.value);
|
|
175
200
|
} catch (e) {
|
|
176
|
-
|
|
201
|
+
N.value = !1, M.value = e instanceof c ? "密码错误" : e instanceof Error ? e.message : String(e);
|
|
177
202
|
}
|
|
178
203
|
}
|
|
179
|
-
function
|
|
180
|
-
e.isDir ?
|
|
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
|
|
183
|
-
|
|
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
|
|
186
|
-
if (
|
|
187
|
-
let t = URL.createObjectURL(new Blob([await
|
|
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 =
|
|
192
|
-
async function
|
|
216
|
+
let Q = S(!1), $;
|
|
217
|
+
async function Ye() {
|
|
193
218
|
try {
|
|
194
|
-
await navigator.clipboard?.writeText(
|
|
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(
|
|
199
|
-
J(), clearTimeout($);
|
|
200
|
-
}), (e, t) => (
|
|
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:
|
|
204
|
-
}, "重试")])) :
|
|
205
|
-
|
|
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:
|
|
210
|
-
}, [
|
|
234
|
+
onClick: qe
|
|
235
|
+
}, [_(o, {
|
|
211
236
|
name: "arrowLeft",
|
|
212
237
|
size: 16
|
|
213
238
|
})]),
|
|
214
|
-
|
|
239
|
+
g("span", {
|
|
215
240
|
class: "zip__path",
|
|
216
|
-
title:
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
|
|
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:
|
|
246
|
+
class: b(["zip__vbtn", { "zip__vbtn--on": K.value === "render" }]),
|
|
222
247
|
title: "渲染",
|
|
223
|
-
onClick: t[0] ||= (e) =>
|
|
224
|
-
}, [
|
|
248
|
+
onClick: t[0] ||= (e) => K.value = "render"
|
|
249
|
+
}, [_(o, {
|
|
225
250
|
name: "eye",
|
|
226
251
|
size: 14
|
|
227
|
-
})], 2),
|
|
252
|
+
})], 2), g("button", {
|
|
228
253
|
type: "button",
|
|
229
|
-
class:
|
|
254
|
+
class: b(["zip__vbtn", { "zip__vbtn--on": K.value === "source" }]),
|
|
230
255
|
title: "源码",
|
|
231
|
-
onClick: t[1] ||= (e) =>
|
|
232
|
-
}, [
|
|
256
|
+
onClick: t[1] ||= (e) => K.value = "source"
|
|
257
|
+
}, [_(o, {
|
|
233
258
|
name: "code",
|
|
234
259
|
size: 14
|
|
235
|
-
})], 2)])) :
|
|
236
|
-
|
|
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:
|
|
242
|
-
}, [
|
|
266
|
+
onClick: Ye
|
|
267
|
+
}, [_(o, {
|
|
243
268
|
name: "copy",
|
|
244
269
|
size: 14
|
|
245
|
-
})], 8, be)) :
|
|
246
|
-
|
|
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) =>
|
|
251
|
-
}, [
|
|
275
|
+
onClick: t[2] ||= (e) => Je(z.value)
|
|
276
|
+
}, [_(o, {
|
|
252
277
|
name: "download",
|
|
253
278
|
size: 15
|
|
254
279
|
})])
|
|
255
|
-
]),
|
|
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:
|
|
258
|
-
start:
|
|
259
|
-
name:
|
|
260
|
-
size:
|
|
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
|
-
])) :
|
|
291
|
+
])) : U.value ? (x(), p(w(Ne), {
|
|
267
292
|
key: 3,
|
|
268
|
-
file:
|
|
269
|
-
mode:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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:
|
|
305
|
+
class: b(["zip__pw", { "zip__pw--ok": N.value }])
|
|
276
306
|
}, [
|
|
277
|
-
|
|
307
|
+
_(o, {
|
|
278
308
|
name: "key",
|
|
279
309
|
size: 14
|
|
280
310
|
}),
|
|
281
|
-
de(
|
|
282
|
-
"onUpdate:modelValue": t[4] ||= (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:
|
|
286
|
-
onKeyup: fe(
|
|
287
|
-
}, null, 40, we), [[le,
|
|
288
|
-
|
|
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:
|
|
292
|
-
},
|
|
293
|
-
|
|
294
|
-
], 2)) :
|
|
295
|
-
|
|
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:
|
|
328
|
+
class: b(["zip__row", { "zip__row--dir": e.node.isDir }]),
|
|
299
329
|
style: ie({ paddingLeft: `${12 + e.depth * 14}px` }),
|
|
300
|
-
onClick: (t) =>
|
|
330
|
+
onClick: (t) => Ke(e.node)
|
|
301
331
|
}, [
|
|
302
|
-
|
|
332
|
+
g("span", ke, [e.node.isDir ? (x(), p(o, {
|
|
303
333
|
key: 0,
|
|
304
334
|
name: "chevronRight",
|
|
305
335
|
size: 13,
|
|
306
|
-
class:
|
|
307
|
-
}, null, 8, ["class"])) :
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
e.node.entry?.encrypted ? (
|
|
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
|
-
})) :
|
|
320
|
-
e.node.isDir ?
|
|
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 {
|
|
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>;
|