@l1yp/file-viewer 0.1.2 → 0.1.4

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 (56) hide show
  1. package/dist/components/ArchiveBrowser.js +1 -1
  2. package/dist/components/ArchiveBrowser.js.map +1 -1
  3. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js +105 -94
  4. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -1
  5. package/dist/components/FileViewerBody.js +1 -1
  6. package/dist/components/FileViewerBody.js.map +1 -1
  7. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
  8. package/dist/components/ImageLightbox.js +1 -1
  9. package/dist/components/ImageLightbox.js.map +1 -1
  10. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js +101 -55
  11. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  12. package/dist/components/JsonlViewer.js +1 -1
  13. package/dist/components/JsonlViewer.js.map +1 -1
  14. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  15. package/dist/components/LightboxGalleryStrip.js +9 -0
  16. package/dist/components/LightboxGalleryStrip.js.map +1 -0
  17. package/dist/components/LightboxGalleryStrip.vue.d.ts +12 -0
  18. package/dist/components/LightboxGalleryStrip.vue_vue_type_script_setup_true_lang.js +60 -0
  19. package/dist/components/LightboxGalleryStrip.vue_vue_type_script_setup_true_lang.js.map +1 -0
  20. package/dist/components/PdfNavigationPane.js +9 -0
  21. package/dist/components/PdfNavigationPane.js.map +1 -0
  22. package/dist/components/PdfNavigationPane.vue.d.ts +24 -0
  23. package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js +149 -0
  24. package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  25. package/dist/components/PdfViewer.js +1 -1
  26. package/dist/components/PdfViewer.js.map +1 -1
  27. package/dist/components/PdfViewer.vue.d.ts +0 -11
  28. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js +131 -69
  29. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  30. package/dist/components/SevenZipBrowser.js +1 -1
  31. package/dist/components/SevenZipBrowser.js.map +1 -1
  32. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js +157 -146
  33. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -1
  34. package/dist/components/ZipBrowser.js +1 -1
  35. package/dist/components/ZipBrowser.js.map +1 -1
  36. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +70 -61
  37. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -1
  38. package/dist/components/ZoomImage.js +1 -1
  39. package/dist/components/ZoomImage.js.map +1 -1
  40. package/dist/components/ZoomImage.vue.d.ts +2 -0
  41. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js +104 -95
  42. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js.map +1 -1
  43. package/dist/composables/useArchiveImageGallery.d.ts +15 -0
  44. package/dist/composables/useArchiveImageGallery.js +62 -0
  45. package/dist/composables/useArchiveImageGallery.js.map +1 -0
  46. package/dist/composables/useLightbox.d.ts +29 -21
  47. package/dist/composables/useLightbox.js +72 -12
  48. package/dist/composables/useLightbox.js.map +1 -1
  49. package/dist/lib/archiveImages.d.ts +14 -0
  50. package/dist/lib/archiveImages.js +15 -0
  51. package/dist/lib/archiveImages.js.map +1 -0
  52. package/dist/lib/pdfNavigation.d.ts +11 -0
  53. package/dist/lib/pdfNavigation.js +32 -0
  54. package/dist/lib/pdfNavigation.js.map +1 -0
  55. package/dist/style.css +1 -1
  56. package/package.json +1 -1
@@ -0,0 +1,149 @@
1
+ import e from "./icons/Icon.js";
2
+ import { Fragment as t, computed as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createVNode as o, defineComponent as s, mergeModels as c, nextTick as l, normalizeClass as u, normalizeStyle as d, openBlock as f, renderList as p, shallowRef as m, toDisplayString as h, useModel as g, useTemplateRef as _, watch as v } from "vue";
3
+ //#region src/components/PdfNavigationPane.vue?vue&type=script&setup=true&lang.ts
4
+ var y = { class: "pn__header" }, b = {
5
+ key: 0,
6
+ class: "pn__tabs",
7
+ role: "tablist",
8
+ "aria-label": "导航类型"
9
+ }, x = ["aria-selected"], S = ["aria-selected"], C = {
10
+ key: 1,
11
+ class: "pn__meta"
12
+ }, w = { class: "pn__count" }, T = [
13
+ "disabled",
14
+ "title",
15
+ "aria-current",
16
+ "onClick"
17
+ ], E = { class: "pn__outline-title" }, D = {
18
+ key: 0,
19
+ class: "pn__outline-page"
20
+ }, O = [
21
+ "data-nav-page-number",
22
+ "aria-current",
23
+ "title",
24
+ "onClick"
25
+ ], k = { class: "pn__preview-wrap" }, A = ["src"], j = { class: "pn__number" }, M = {
26
+ key: 1,
27
+ class: "pn__sheet"
28
+ }, N = { class: "pn__page-label" }, P = /*@__PURE__*/ s({
29
+ __name: "PdfNavigationPane",
30
+ props: /*@__PURE__*/ c({
31
+ outlineItems: {},
32
+ pageCount: {},
33
+ pageLabels: {},
34
+ pageThumbnails: {},
35
+ activePage: {}
36
+ }, {
37
+ collapsed: {
38
+ type: Boolean,
39
+ default: !1
40
+ },
41
+ collapsedModifiers: {},
42
+ view: { default: "outline" },
43
+ viewModifiers: {}
44
+ }),
45
+ emits: /*@__PURE__*/ c(["navigate"], ["update:collapsed", "update:view"]),
46
+ setup(s, { emit: c }) {
47
+ let P = s, F = g(s, "collapsed"), I = g(s, "view"), L = c, R = _("listEl"), z = m(!1), B = n(() => P.outlineItems.length > 0), V = n(() => P.pageThumbnails.some(Boolean)), H = n(() => ({
48
+ "pn--collapsed": F.value,
49
+ "pn--outline": B.value && I.value === "outline",
50
+ "pn--compact": I.value === "pages" && !V.value
51
+ })), U = n(() => {
52
+ let e;
53
+ for (let t of P.outlineItems) {
54
+ if (t.pageNumber === null || t.pageNumber > P.activePage) continue;
55
+ let n = e?.pageNumber ?? 0;
56
+ (!e || t.pageNumber > n || t.pageNumber === n && t.level > e.level) && (e = t);
57
+ }
58
+ return e?.id ?? "";
59
+ });
60
+ function W(e) {
61
+ return P.pageLabels[e - 1] || `第 ${e} 页`;
62
+ }
63
+ function G(e) {
64
+ e !== null && L("navigate", e);
65
+ }
66
+ async function K() {
67
+ F.value || z.value || (z.value = !0, await l(), R.value?.querySelector(".pn__item--active")?.scrollIntoView({ block: "nearest" }), z.value = !1);
68
+ }
69
+ return v(() => P.outlineItems.length, (e) => {
70
+ !e && I.value === "outline" && (I.value = "pages");
71
+ }, { immediate: !0 }), v(() => [
72
+ P.activePage,
73
+ I.value,
74
+ F.value
75
+ ], K), (n, c) => (f(), i("aside", {
76
+ class: u(["pn", H.value]),
77
+ "aria-label": "PDF 导航"
78
+ }, [F.value ? (f(), i("button", {
79
+ key: 0,
80
+ type: "button",
81
+ class: "pn__reopen",
82
+ "aria-label": "展开 PDF 导航",
83
+ title: "展开 PDF 导航",
84
+ onClick: c[0] ||= (e) => F.value = !1
85
+ }, [o(e, {
86
+ name: "chevronRight",
87
+ size: 15
88
+ })])) : (f(), i(t, { key: 1 }, [a("header", y, [B.value ? (f(), i("div", b, [a("button", {
89
+ class: u(["pn__tab", { "pn__tab--active": I.value === "outline" }]),
90
+ type: "button",
91
+ role: "tab",
92
+ "aria-selected": I.value === "outline",
93
+ onClick: c[1] ||= (e) => I.value = "outline"
94
+ }, " 目录 ", 10, x), a("button", {
95
+ class: u(["pn__tab", { "pn__tab--active": I.value === "pages" }]),
96
+ type: "button",
97
+ role: "tab",
98
+ "aria-selected": I.value === "pages",
99
+ onClick: c[2] ||= (e) => I.value = "pages"
100
+ }, " 页面 ", 10, S)])) : (f(), i("div", C, [c[4] ||= a("strong", { class: "pn__title" }, "页面", -1), a("span", w, h(s.pageCount) + " 页", 1)])), a("button", {
101
+ class: "pn__collapse",
102
+ type: "button",
103
+ title: "收起 PDF 导航",
104
+ "aria-label": "收起 PDF 导航",
105
+ onClick: c[3] ||= (e) => F.value = !0
106
+ }, [o(e, {
107
+ name: "chevronLeft",
108
+ size: 15
109
+ })])]), a("nav", {
110
+ ref_key: "listEl",
111
+ ref: R,
112
+ class: "pn__list",
113
+ "aria-label": "PDF 导航列表"
114
+ }, [I.value === "outline" && B.value ? (f(!0), i(t, { key: 0 }, p(s.outlineItems, (e) => (f(), i("button", {
115
+ key: e.id,
116
+ class: u(["pn__item pn__outline", {
117
+ "pn__item--active": e.id === U.value,
118
+ "pn__item--disabled": e.pageNumber === null
119
+ }]),
120
+ style: d({ "--pn-level": e.level }),
121
+ type: "button",
122
+ disabled: e.pageNumber === null,
123
+ title: e.title,
124
+ "aria-current": e.id === U.value ? "location" : void 0,
125
+ onClick: (t) => G(e.pageNumber)
126
+ }, [a("span", E, h(e.title), 1), e.pageNumber === null ? r("", !0) : (f(), i("span", D, h(e.pageNumber), 1))], 14, T))), 128)) : (f(!0), i(t, { key: 1 }, p(s.pageCount, (e) => (f(), i("button", {
127
+ key: e,
128
+ class: u(["pn__item pn__page", {
129
+ "pn__item--active": e === s.activePage,
130
+ "pn__page--fallback": !s.pageThumbnails[e - 1]
131
+ }]),
132
+ type: "button",
133
+ "data-nav-page-number": e,
134
+ "aria-current": e === s.activePage ? "page" : void 0,
135
+ title: `跳转到第 ${e} 页`,
136
+ onClick: (t) => G(e)
137
+ }, [s.pageThumbnails[e - 1] ? (f(), i(t, { key: 0 }, [a("span", k, [a("img", {
138
+ class: "pn__preview",
139
+ src: s.pageThumbnails[e - 1] || "",
140
+ alt: "",
141
+ loading: "lazy",
142
+ draggable: "false"
143
+ }, null, 8, A)]), a("span", j, h(e), 1)], 64)) : (f(), i("span", M, h(e), 1)), a("span", N, h(W(e)), 1)], 10, O))), 128))], 512)], 64))], 2));
144
+ }
145
+ });
146
+ //#endregion
147
+ export { P as default };
148
+
149
+ //# sourceMappingURL=PdfNavigationPane.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PdfNavigationPane.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/PdfNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport Icon from '@/components/icons/Icon.vue'\nimport type { PdfNavigationItem } from '../lib/pdfNavigation'\n\nconst props = defineProps<{\n outlineItems: readonly PdfNavigationItem[]\n pageCount: number\n pageLabels: readonly string[]\n pageThumbnails: readonly (string | null)[]\n activePage: number\n}>()\n\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst view = defineModel<'outline' | 'pages'>('view', { default: 'outline' })\nconst emit = defineEmits<{\n navigate: [pageNumber: number]\n}>()\n\nconst listEl = useTemplateRef<HTMLElement>('listEl')\nconst autoScrollPending = shallowRef(false)\nconst hasOutline = computed(() => props.outlineItems.length > 0)\nconst hasThumbnails = computed(() => props.pageThumbnails.some(Boolean))\nconst panelClasses = computed(() => ({\n 'pn--collapsed': collapsed.value,\n 'pn--outline': hasOutline.value && view.value === 'outline',\n 'pn--compact': view.value === 'pages' && !hasThumbnails.value,\n}))\n\nconst activeOutlineId = computed(() => {\n let active: PdfNavigationItem | undefined\n for (const item of props.outlineItems) {\n if (item.pageNumber === null || item.pageNumber > props.activePage) continue\n const activePageNumber = active?.pageNumber ?? 0\n if (\n !active ||\n item.pageNumber > activePageNumber ||\n (item.pageNumber === activePageNumber && item.level > active.level)\n )\n active = item\n }\n return active?.id ?? ''\n})\n\nfunction pageLabel(pageNumber: number): string {\n return props.pageLabels[pageNumber - 1] || `第 ${pageNumber} 页`\n}\n\nfunction navigate(pageNumber: number | null): void {\n if (pageNumber !== null) emit('navigate', pageNumber)\n}\n\nasync function keepActiveItemVisible(): Promise<void> {\n if (collapsed.value || autoScrollPending.value) return\n autoScrollPending.value = true\n await nextTick()\n listEl.value\n ?.querySelector<HTMLElement>('.pn__item--active')\n ?.scrollIntoView({ block: 'nearest' })\n autoScrollPending.value = false\n}\n\nwatch(\n () => props.outlineItems.length,\n (count) => {\n if (!count && view.value === 'outline') view.value = 'pages'\n },\n { immediate: true },\n)\nwatch(() => [props.activePage, view.value, collapsed.value], keepActiveItemVisible)\n</script>\n\n<template>\n <aside class=\"pn\" :class=\"panelClasses\" aria-label=\"PDF 导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"pn__reopen\"\n aria-label=\"展开 PDF 导航\"\n title=\"展开 PDF 导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"pn__header\">\n <div v-if=\"hasOutline\" class=\"pn__tabs\" role=\"tablist\" aria-label=\"导航类型\">\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'outline' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'outline'\"\n @click=\"view = 'outline'\"\n >\n 目录\n </button>\n <button\n class=\"pn__tab\"\n :class=\"{ 'pn__tab--active': view === 'pages' }\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"view === 'pages'\"\n @click=\"view = 'pages'\"\n >\n 页面\n </button>\n </div>\n <div v-else class=\"pn__meta\">\n <strong class=\"pn__title\">页面</strong>\n <span class=\"pn__count\">{{ pageCount }} 页</span>\n </div>\n <button\n class=\"pn__collapse\"\n type=\"button\"\n title=\"收起 PDF 导航\"\n aria-label=\"收起 PDF 导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <nav ref=\"listEl\" class=\"pn__list\" aria-label=\"PDF 导航列表\">\n <template v-if=\"view === 'outline' && hasOutline\">\n <button\n v-for=\"item in outlineItems\"\n :key=\"item.id\"\n class=\"pn__item pn__outline\"\n :class=\"{\n 'pn__item--active': item.id === activeOutlineId,\n 'pn__item--disabled': item.pageNumber === null,\n }\"\n :style=\"{ '--pn-level': item.level }\"\n type=\"button\"\n :disabled=\"item.pageNumber === null\"\n :title=\"item.title\"\n :aria-current=\"item.id === activeOutlineId ? 'location' : undefined\"\n @click=\"navigate(item.pageNumber)\"\n >\n <span class=\"pn__outline-title\">{{ item.title }}</span>\n <span v-if=\"item.pageNumber !== null\" class=\"pn__outline-page\">{{\n item.pageNumber\n }}</span>\n </button>\n </template>\n\n <template v-else>\n <button\n v-for=\"pageNumber in pageCount\"\n :key=\"pageNumber\"\n class=\"pn__item pn__page\"\n :class=\"{\n 'pn__item--active': pageNumber === activePage,\n 'pn__page--fallback': !pageThumbnails[pageNumber - 1],\n }\"\n type=\"button\"\n :data-nav-page-number=\"pageNumber\"\n :aria-current=\"pageNumber === activePage ? 'page' : undefined\"\n :title=\"`跳转到第 ${pageNumber} 页`\"\n @click=\"navigate(pageNumber)\"\n >\n <template v-if=\"pageThumbnails[pageNumber - 1]\">\n <span class=\"pn__preview-wrap\">\n <img\n class=\"pn__preview\"\n :src=\"pageThumbnails[pageNumber - 1] || ''\"\n alt=\"\"\n loading=\"lazy\"\n draggable=\"false\"\n />\n </span>\n <span class=\"pn__number\">{{ pageNumber }}</span>\n </template>\n <span v-else class=\"pn__sheet\">{{ pageNumber }}</span>\n <span class=\"pn__page-label\">{{ pageLabel(pageNumber) }}</span>\n </button>\n </template>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.pn {\n position: relative;\n width: 142px;\n min-width: 0;\n display: flex;\n flex: 0 0 142px;\n flex-direction: column;\n color: var(--muted);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n border-right: 1px solid var(--border);\n transition:\n flex-basis 160ms ease,\n width 160ms ease;\n}\n\n.pn--outline {\n width: 190px;\n flex-basis: 190px;\n}\n\n.pn--compact {\n width: 96px;\n flex-basis: 96px;\n}\n\n.pn--collapsed {\n width: 0;\n flex-basis: 0;\n border-right: 0;\n}\n\n.pn__header {\n min-height: 45px;\n padding: 7px 7px 7px 10px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n\n.pn__tabs {\n min-width: 0;\n display: flex;\n flex: 1;\n gap: 3px;\n}\n\n.pn__tab,\n.pn__collapse,\n.pn__reopen,\n.pn__item {\n color: inherit;\n font: inherit;\n cursor: pointer;\n background: transparent;\n border: 0;\n}\n\n.pn__tab {\n position: relative;\n min-width: 0;\n padding: 7px 5px;\n flex: 1;\n color: var(--faint);\n font-size: 11.5px;\n border-radius: 6px;\n}\n\n.pn__tab:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__tab--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n\n.pn__tab--active::after {\n position: absolute;\n right: 10px;\n bottom: 2px;\n left: 10px;\n height: 2px;\n content: '';\n background: var(--accent);\n border-radius: 2px;\n}\n\n.pn__meta {\n min-width: 0;\n}\n\n.pn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n\n.pn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n\n.pn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n flex: none;\n place-items: center;\n border-radius: 7px;\n}\n\n.pn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n\n.pn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__list {\n min-height: 0;\n padding: 8px 7px 22px;\n flex: 1 1 auto;\n overflow: auto;\n scrollbar-width: thin;\n}\n\n.pn__item {\n width: 100%;\n min-width: 0;\n color: var(--muted);\n text-align: left;\n border: 1px solid transparent;\n border-radius: 8px;\n}\n\n.pn__item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n\n.pn__item--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n border-color: color-mix(in srgb, var(--accent) 70%, transparent);\n}\n\n.pn__item--disabled {\n cursor: default;\n opacity: 0.52;\n}\n\n.pn__item--disabled:hover {\n color: var(--muted);\n background: transparent;\n}\n\n.pn__outline {\n gap: 8px;\n padding: 7px 8px 7px calc(9px + min(var(--pn-level), 5) * 14px);\n display: flex;\n align-items: center;\n margin: 1px 0;\n font-size: 12px;\n line-height: 1.35;\n}\n\n.pn__outline-title {\n min-width: 0;\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.pn__outline-page {\n color: var(--faint);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n}\n\n.pn__page {\n position: relative;\n padding: 5px;\n display: block;\n margin: 0 0 8px;\n}\n\n.pn__preview-wrap {\n min-width: 0;\n min-height: 62px;\n overflow: hidden;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #fff;\n border: 1px solid var(--border-2);\n border-radius: 4px;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__preview-wrap {\n border-color: var(--accent);\n box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent);\n}\n\n.pn__preview {\n width: 104px;\n max-width: 100%;\n max-height: 142px;\n display: block;\n object-fit: contain;\n pointer-events: none;\n}\n\n.pn__number {\n position: absolute;\n z-index: 2;\n top: 9px;\n left: 9px;\n min-width: 20px;\n height: 20px;\n padding: 0 5px;\n display: grid;\n place-items: center;\n color: #fff;\n font-size: 9.5px;\n font-weight: 650;\n font-variant-numeric: tabular-nums;\n background: rgba(20, 20, 20, 0.68);\n border-radius: 5px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(4px);\n}\n\n.pn__item--active .pn__number {\n background: var(--accent);\n}\n\n.pn__page-label {\n min-width: 0;\n margin-top: 5px;\n overflow: hidden;\n display: -webkit-box;\n color: inherit;\n font-size: 10.5px;\n line-height: 1.35;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n}\n\n.pn__page--fallback {\n padding: 5px;\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 5px;\n}\n\n.pn__sheet {\n width: 28px;\n height: 38px;\n display: grid;\n flex: none;\n place-items: center;\n color: #526072;\n font-size: 9.5px;\n font-variant-numeric: tabular-nums;\n background: #fff;\n border: 1px solid var(--border-2);\n border-radius: 3px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n}\n\n.pn__item--active .pn__sheet {\n color: var(--accent);\n border-color: var(--accent);\n}\n\n.pn__page--fallback .pn__page-label {\n margin-top: 0;\n flex: 1;\n}\n\n@container (max-width: 500px) {\n .pn {\n width: 124px;\n flex-basis: 124px;\n }\n\n .pn--outline {\n width: 158px;\n flex-basis: 158px;\n }\n\n .pn--compact {\n width: 84px;\n flex-basis: 84px;\n }\n\n .pn--collapsed {\n width: 0;\n flex-basis: 0;\n }\n\n .pn__preview {\n max-height: 126px;\n }\n}\n</style>\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
2
2
  import t from "./PdfViewer.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/components/PdfViewer.vue
5
- var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-2a30ef87"]]);
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-7de14862"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
8
 
@@ -1 +1 @@
1
- {"version":3,"file":"PdfViewer.js","names":[],"sources":["../../src/components/PdfViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport * as pdfjsLib from 'pdfjs-dist'\nimport type { PDFDocumentProxy, RenderTask } from 'pdfjs-dist'\n// pdfjs runs its parser/rasterizer in a web worker. Vite's `?url` emits the real worker asset and returns a\n// servable URL (root-relative in the same-origin prod build, a dev URL under `pnpm dev`); we point pdfjs at\n// it. The `.mjs` is served as text/javascript by Ktor (see AGENTS.md) so the module worker loads in prod.\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\npdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl\n\n/**\n * Side-viewer PDF (`.pdf`) renderer. Fetches the document bytes from the download URL and rasterizes each\n * page to a <canvas> entirely in-browser via pdfjs-dist (https://github.com/mozilla/pdf.js) — a vertically\n * scrollable list of pages (white sheets on a neutral \"desk\", like the docx/pptx viewers). No CDN / network\n * beyond the file fetch: pdfjs bundles its own worker, fetched lazily as a same-origin asset (see above).\n *\n * Two entry shapes mirror the other viewers (docx/xlsx/pptx/zip): a real download URL for a held attachment,\n * or a blob URL minted by the archive browsers from already-extracted bytes. Pages re-rasterize when the pane\n * is resized (debounced) so text stays crisp at the new width.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .pdf bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst pageCount = ref(0)\n// The scroll container the page canvases render into. Always mounted (the loading/error states overlay it).\nconst docEl = ref<HTMLElement | null>(null)\n\n// Live document proxy + in-flight render tasks — torn down on re-render / unmount (the worker and page\n// rasterizers don't GC on their own).\nlet pdfDoc: PDFDocumentProxy | null = null\nlet renderTasks: RenderTask[] = []\n// Bumped on every (re)render so a stale async pass (older file, or a superseded resize) can bail out.\nlet renderToken = 0\n// Cap the backing-store resolution: device pixels × this, clamped, keeps big/high-DPI pages from minting\n// multi-hundred-MB canvases while staying crisp.\nconst MAX_SCALE = 2\n\nfunction cancelRenders(): void {\n for (const t of renderTasks) {\n try {\n t.cancel()\n } catch {\n /* already settled — ignore */\n }\n }\n renderTasks = []\n}\n\nfunction disposeDoc(): void {\n cancelRenders()\n if (pdfDoc) {\n try {\n pdfDoc.destroy()\n } catch {\n /* already torn down — ignore */\n }\n pdfDoc = null\n }\n}\n\n// Rasterize every page of the live document into the container at its current width. Called on load and on\n// resize; guarded by `renderToken` so an outdated pass (file switched, or a newer resize started) stops early.\nasync function renderPages(): Promise<void> {\n const el = docEl.value\n const doc = pdfDoc\n if (!el || !doc) return\n const token = ++renderToken\n cancelRenders()\n el.innerHTML = ''\n // Available CSS width for a page (minus the container's horizontal padding). Floor at a sane minimum so a\n // momentarily-zero clientWidth (not yet laid out) doesn't render a 0-px canvas.\n const style = getComputedStyle(el)\n const padX = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)\n const cssWidth = Math.max(120, el.clientWidth - padX)\n const dpr = Math.min(typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, MAX_SCALE)\n for (let n = 1; n <= doc.numPages; n++) {\n let page\n try {\n page = await doc.getPage(n)\n } catch {\n if (token !== renderToken) return\n continue // skip an unreadable page rather than failing the whole doc\n }\n if (token !== renderToken) return\n const base = page.getViewport({ scale: 1 })\n const cssScale = cssWidth / base.width\n const viewport = page.getViewport({ scale: cssScale * dpr })\n const canvas = document.createElement('canvas')\n canvas.className = 'pf__page'\n canvas.width = Math.ceil(viewport.width)\n canvas.height = Math.ceil(viewport.height)\n // Display at the CSS width; the extra backing-store pixels (× dpr) make text sharp on HiDPI screens.\n canvas.style.width = `${Math.round(cssWidth)}px`\n canvas.style.height = `${Math.round(cssWidth * (base.height / base.width))}px`\n if (!canvas.getContext('2d')) continue // no 2D context available — skip rather than crash the render\n el.appendChild(canvas)\n // Pass `canvas` (the preferred param in pdfjs v5); it derives the 2D context itself.\n const task = page.render({ canvas, viewport })\n renderTasks.push(task)\n try {\n await task.promise\n } catch {\n // RenderingCancelledException on supersede/unmount is expected — only a live token cares about failures.\n if (token !== renderToken) return\n }\n if (token !== renderToken) return\n }\n}\n\nasync function load(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n disposeDoc()\n el.innerHTML = ''\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer load may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el) return\n const doc = await pdfjsLib.getDocument({ data: new Uint8Array(buf) }).promise\n if (props.url !== url || docEl.value !== el) {\n try {\n doc.destroy()\n } catch {\n /* ignore */\n }\n return\n }\n pdfDoc = doc\n pageCount.value = doc.numPages\n await renderPages()\n if (props.url !== url || docEl.value !== el) return\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\n// Re-rasterize on width changes (debounced) so pages stay crisp as the pane is dragged wider/narrower. Only\n// the width matters; height changes don't affect page scale.\nlet resizeTimer: ReturnType<typeof setTimeout> | undefined\nlet lastWidth = 0\nlet ro: ResizeObserver | null = null\nfunction onResize(): void {\n const el = docEl.value\n if (!el || !pdfDoc) return\n const w = el.clientWidth\n if (Math.abs(w - lastWidth) < 8) return // ignore sub-pixel / negligible jitter\n lastWidth = w\n clearTimeout(resizeTimer)\n resizeTimer = setTimeout(() => {\n if (pdfDoc) void renderPages()\n }, 180)\n}\n\nonMounted(() => {\n load()\n if (typeof ResizeObserver !== 'undefined' && docEl.value) {\n lastWidth = docEl.value.clientWidth\n ro = new ResizeObserver(onResize)\n ro.observe(docEl.value)\n }\n})\nwatch(() => props.url, load)\nonBeforeUnmount(() => {\n clearTimeout(resizeTimer)\n ro?.disconnect()\n renderToken++ // invalidate any in-flight render pass\n disposeDoc()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"pf\">\n <!-- Always mounted so the renderer has a sized, scrollable container; states overlay it. -->\n <div ref=\"docEl\" class=\"pf__doc\" />\n <div v-if=\"status === 'loading'\" class=\"pf__overlay\">\n <p class=\"pf__msg\">正在渲染 PDF…</p>\n </div>\n <div v-else-if=\"status === 'error'\" class=\"pf__overlay\">\n <div class=\"pf__msg pf__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"pf__retry\" @click=\"load\">重试</button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.pf {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n/* Scroll region holding the rendered pages — white sheets on a neutral \"desk\", like the docx/pptx viewers. */\n.pf__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n padding: 18px 16px 40px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n}\n/* Each page canvas: a white sheet with a soft shadow. Width is set inline (the rasterized CSS width); the\n * canvas paints its own white, so no background needed here. */\n.pf__doc :deep(.pf__page) {\n display: block;\n max-width: 100%;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n border-radius: 2px;\n background: #fff;\n}\n/* Overlay covers the (empty) container while loading or on error, without unmounting it. */\n.pf__overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n background: var(--surface);\n}\n.pf__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.pf__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.pf__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.pf__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"PdfViewer.js","names":[],"sources":["../../src/components/PdfViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue'\nimport * as pdfjsLib from 'pdfjs-dist'\nimport type { PDFDocumentProxy, RenderTask } from 'pdfjs-dist'\n// pdfjs runs its parser/rasterizer in a web worker. Vite's `?url` emits the real worker asset and returns a\n// servable URL (root-relative in the same-origin prod build, a dev URL under `pnpm dev`).\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\nimport PdfNavigationPane from './PdfNavigationPane.vue'\nimport { loadPdfNavigation, type PdfNavigationItem } from '../lib/pdfNavigation'\n\npdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl\n\nconst props = defineProps<{\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\nconst pageCount = shallowRef(0)\nconst activePage = shallowRef(1)\nconst outlineItems = shallowRef<PdfNavigationItem[]>([])\nconst pageLabels = shallowRef<string[]>([])\nconst pageThumbnails = shallowRef<Array<string | null>>([])\nconst navigationCollapsed = shallowRef(false)\nconst navigationView = shallowRef<'outline' | 'pages'>('outline')\nconst docEl = useTemplateRef<HTMLElement>('docEl')\n\nlet pdfDoc: PDFDocumentProxy | null = null\nlet renderTasks: RenderTask[] = []\nlet renderToken = 0\nlet resizeTimer: ReturnType<typeof setTimeout> | undefined\nlet lastSize = ''\nlet resizeObserver: ResizeObserver | null = null\nlet scrollFrame = 0\nconst MAX_SCALE = 2\nconst PAGE_GUTTER = 24\nconst THUMBNAIL_WIDTH = 104\n\nfunction cancelRenders(): void {\n for (const task of renderTasks) {\n try {\n task.cancel()\n } catch {\n // The task may already have settled.\n }\n }\n renderTasks = []\n}\n\nfunction disposeDoc(): void {\n cancelRenders()\n if (!pdfDoc) return\n try {\n pdfDoc.destroy()\n } catch {\n // The worker may already have been torn down.\n }\n pdfDoc = null\n}\n\nfunction createPageThumbnail(canvas: HTMLCanvasElement): string | null {\n if (!canvas.width || !canvas.height) return null\n try {\n const thumbnail = document.createElement('canvas')\n thumbnail.width = THUMBNAIL_WIDTH\n thumbnail.height = Math.max(1, Math.round(THUMBNAIL_WIDTH * (canvas.height / canvas.width)))\n const context = thumbnail.getContext('2d')\n if (!context) return null\n context.fillStyle = '#fff'\n context.fillRect(0, 0, thumbnail.width, thumbnail.height)\n context.drawImage(canvas, 0, 0, thumbnail.width, thumbnail.height)\n return thumbnail.toDataURL('image/jpeg', 0.78)\n } catch {\n return null\n }\n}\n\nfunction scrollToPage(pageNumber: number, behavior: ScrollBehavior = 'smooth'): void {\n const el = docEl.value\n const target = el?.querySelector<HTMLElement>(`[data-page-number=\"${pageNumber}\"]`)\n if (!el || !target) return\n activePage.value = pageNumber\n const rootRect = el.getBoundingClientRect()\n const targetRect = target.getBoundingClientRect()\n const paddingTop = Number.parseFloat(getComputedStyle(el).paddingTop)\n el.scrollTo({\n top: el.scrollTop + targetRect.top - rootRect.top - paddingTop,\n behavior,\n })\n}\n\nfunction syncActivePage(): void {\n if (scrollFrame) return\n scrollFrame = window.requestAnimationFrame(() => {\n scrollFrame = 0\n const el = docEl.value\n if (!el) return\n const rootRect = el.getBoundingClientRect()\n const targetY = rootRect.top + Math.min(el.clientHeight * 0.36, 220)\n let nearestPage = activePage.value\n let nearestDistance = Number.POSITIVE_INFINITY\n for (const page of el.querySelectorAll<HTMLElement>('[data-page-number]')) {\n const rect = page.getBoundingClientRect()\n const pageTarget = Math.min(Math.max(targetY, rect.top), rect.bottom)\n const distance = Math.abs(targetY - pageTarget)\n if (distance >= nearestDistance) continue\n nearestDistance = distance\n nearestPage = Number(page.dataset.pageNumber) || nearestPage\n }\n activePage.value = nearestPage\n })\n}\n\nasync function renderPages(): Promise<void> {\n const el = docEl.value\n const doc = pdfDoc\n if (!el || !doc) return\n const token = ++renderToken\n const preservedPage = activePage.value\n const preserveScroll = el.scrollTop > 0\n cancelRenders()\n el.innerHTML = ''\n\n const style = getComputedStyle(el)\n const padX = Number.parseFloat(style.paddingLeft) + Number.parseFloat(style.paddingRight)\n const padY = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)\n const availableWidth = Math.max(120, el.clientWidth - padX - PAGE_GUTTER)\n const availableHeight = Math.max(160, el.clientHeight - padY - PAGE_GUTTER)\n const outputScale = Math.min(window.devicePixelRatio || 1, MAX_SCALE)\n const thumbnails = Array<string | null>(doc.numPages).fill(null)\n\n for (let pageNumber = 1; pageNumber <= doc.numPages; pageNumber += 1) {\n let page\n try {\n page = await doc.getPage(pageNumber)\n } catch {\n if (token !== renderToken) return\n continue\n }\n if (token !== renderToken) return\n\n const base = page.getViewport({ scale: 1 })\n const cssScale = Math.min(availableWidth / base.width, availableHeight / base.height)\n const cssWidth = Math.round(base.width * cssScale)\n const cssHeight = Math.round(base.height * cssScale)\n const viewport = page.getViewport({ scale: cssScale * outputScale })\n\n const pageWrap = document.createElement('div')\n pageWrap.className = 'pf__page-wrap'\n pageWrap.dataset.pageNumber = `${pageNumber}`\n const canvas = document.createElement('canvas')\n canvas.className = 'pf__page'\n canvas.width = Math.ceil(viewport.width)\n canvas.height = Math.ceil(viewport.height)\n canvas.style.width = `${cssWidth}px`\n canvas.style.height = `${cssHeight}px`\n canvas.setAttribute('aria-label', `PDF 第 ${pageNumber} 页`)\n if (!canvas.getContext('2d')) continue\n pageWrap.appendChild(canvas)\n el.appendChild(pageWrap)\n\n const task = page.render({ canvas, viewport })\n renderTasks.push(task)\n let rendered = true\n try {\n await task.promise\n } catch {\n if (token !== renderToken) return\n rendered = false\n }\n if (token !== renderToken) return\n if (rendered) thumbnails[pageNumber - 1] = createPageThumbnail(canvas)\n }\n\n pageThumbnails.value = thumbnails\n if (preserveScroll) scrollToPage(preservedPage, 'auto')\n syncActivePage()\n}\n\nasync function load(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n outlineItems.value = []\n pageLabels.value = []\n pageThumbnails.value = []\n disposeDoc()\n el.innerHTML = ''\n const url = props.url\n\n try {\n const response = await fetch(url)\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n const bytes = new Uint8Array(await response.arrayBuffer())\n if (props.url !== url || docEl.value !== el) return\n\n const document = await pdfjsLib.getDocument({ data: bytes }).promise\n if (props.url !== url || docEl.value !== el) {\n document.destroy()\n return\n }\n pdfDoc = document\n pageCount.value = document.numPages\n activePage.value = 1\n const navigation = await loadPdfNavigation(document)\n if (props.url !== url || docEl.value !== el) return\n outlineItems.value = navigation.outline\n pageLabels.value = navigation.pageLabels\n navigationView.value = navigation.outline.length ? 'outline' : 'pages'\n await renderPages()\n if (props.url !== url || docEl.value !== el) return\n status.value = 'ready'\n } catch (cause) {\n errorMsg.value = cause instanceof Error ? cause.message : String(cause)\n status.value = 'error'\n }\n}\n\nfunction onResize(): void {\n const el = docEl.value\n if (!el || !pdfDoc) return\n const size = `${el.clientWidth}x${el.clientHeight}`\n if (!el.clientWidth || !el.clientHeight || size === lastSize) return\n lastSize = size\n clearTimeout(resizeTimer)\n resizeTimer = setTimeout(() => {\n if (pdfDoc) void renderPages()\n }, 180)\n}\n\nonMounted(() => {\n void load()\n if (typeof ResizeObserver !== 'undefined' && docEl.value) {\n lastSize = `${docEl.value.clientWidth}x${docEl.value.clientHeight}`\n resizeObserver = new ResizeObserver(onResize)\n resizeObserver.observe(docEl.value)\n }\n})\nwatch(() => props.url, load)\nonBeforeUnmount(() => {\n clearTimeout(resizeTimer)\n resizeObserver?.disconnect()\n if (scrollFrame) window.cancelAnimationFrame(scrollFrame)\n renderToken += 1\n disposeDoc()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"pf\">\n <PdfNavigationPane\n v-if=\"status === 'ready'\"\n v-model:collapsed=\"navigationCollapsed\"\n v-model:view=\"navigationView\"\n :outline-items=\"outlineItems\"\n :page-count=\"pageCount\"\n :page-labels=\"pageLabels\"\n :page-thumbnails=\"pageThumbnails\"\n :active-page=\"activePage\"\n @navigate=\"scrollToPage\"\n />\n <div class=\"pf__stage\">\n <div ref=\"docEl\" class=\"pf__doc\" @scroll.passive=\"syncActivePage\" />\n <div v-if=\"status === 'loading'\" class=\"pf__overlay\">\n <p class=\"pf__msg\">正在渲染 PDF…</p>\n </div>\n <div v-else-if=\"status === 'error'\" class=\"pf__overlay\">\n <div class=\"pf__msg pf__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"pf__retry\" @click=\"load\">重试</button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.pf {\n position: relative;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex: 1 1 auto;\n overflow: hidden;\n container-type: inline-size;\n background: var(--surface);\n}\n\n.pf__stage {\n position: relative;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex: 1 1 0;\n overflow: hidden;\n}\n\n.pf__doc {\n width: 100%;\n min-width: 0;\n min-height: 0;\n flex: 1 1 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 24px 20px 42px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 20px;\n box-sizing: border-box;\n background: var(--surface-2);\n scrollbar-color: #555961 transparent;\n scrollbar-width: thin;\n}\n\n.pf__doc :deep(.pf__page-wrap) {\n max-width: 100%;\n display: flex;\n flex: none;\n justify-content: center;\n}\n\n.pf__doc :deep(.pf__page) {\n max-width: 100%;\n display: block;\n background: #fff;\n border-radius: 2px;\n box-shadow: 0 4px 18px rgba(0, 0, 0, 0.28);\n}\n\n.pf__overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n background: var(--surface);\n}\n\n.pf__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n\n.pf__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n\n.pf__retry {\n padding: 5px 12px;\n color: var(--text);\n font-family: inherit;\n font-size: 12.5px;\n cursor: pointer;\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n}\n\n.pf__retry:hover {\n color: var(--accent);\n border-color: var(--accent);\n}\n</style>\n"],"mappings":""}
@@ -1,15 +1,4 @@
1
- /**
2
- * Side-viewer PDF (`.pdf`) renderer. Fetches the document bytes from the download URL and rasterizes each
3
- * page to a <canvas> entirely in-browser via pdfjs-dist (https://github.com/mozilla/pdf.js) — a vertically
4
- * scrollable list of pages (white sheets on a neutral "desk", like the docx/pptx viewers). No CDN / network
5
- * beyond the file fetch: pdfjs bundles its own worker, fetched lazily as a same-origin asset (see above).
6
- *
7
- * Two entry shapes mirror the other viewers (docx/xlsx/pptx/zip): a real download URL for a held attachment,
8
- * or a blob URL minted by the archive browsers from already-extracted bytes. Pages re-rasterize when the pane
9
- * is resized (debounced) so text stays crisp at the new width.
10
- */
11
1
  type __VLS_Props = {
12
- /** Download/blob URL of the .pdf bytes. */
13
2
  url?: string;
14
3
  name: string;
15
4
  size?: number;
@@ -1,116 +1,178 @@
1
- import { createCommentVNode as e, createElementBlock as t, createElementVNode as n, defineComponent as r, onBeforeUnmount as i, onMounted as a, openBlock as o, ref as s, toDisplayString as c, watch as l } from "vue";
2
- import * as u from "pdfjs-dist";
3
- import d from "pdfjs-dist/build/pdf.worker.min.mjs?url";
1
+ import e from "./PdfNavigationPane.js";
2
+ import { loadPdfNavigation as t } from "../lib/pdfNavigation.js";
3
+ import { createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, defineComponent as o, onBeforeUnmount as s, onMounted as c, openBlock as l, shallowRef as u, toDisplayString as d, useTemplateRef as f, watch as p } from "vue";
4
+ import * as m from "pdfjs-dist";
5
+ import h from "pdfjs-dist/build/pdf.worker.min.mjs?url";
4
6
  //#region src/components/PdfViewer.vue?vue&type=script&setup=true&lang.ts
5
- var f = { class: "pf" }, p = {
7
+ var g = { class: "pf" }, _ = { class: "pf__stage" }, v = {
6
8
  key: 0,
7
9
  class: "pf__overlay"
8
- }, m = {
10
+ }, y = {
9
11
  key: 1,
10
12
  class: "pf__overlay"
11
- }, h = { class: "pf__msg pf__msg--error" }, g = 2, _ = /*@__PURE__*/ r({
13
+ }, b = { class: "pf__msg pf__msg--error" }, x = 2, S = 24, C = 104, w = /*@__PURE__*/ o({
12
14
  __name: "PdfViewer",
13
15
  props: {
14
16
  url: {},
15
17
  name: {},
16
18
  size: {}
17
19
  },
18
- setup(r) {
19
- u.GlobalWorkerOptions.workerSrc = d;
20
- let _ = r, v = s("loading"), y = s(""), b = s(0), x = s(null), S = null, C = [], w = 0;
21
- function T() {
22
- for (let e of C) try {
20
+ setup(o) {
21
+ m.GlobalWorkerOptions.workerSrc = h;
22
+ let w = o, T = u("loading"), E = u(""), D = u(0), O = u(1), k = u([]), A = u([]), j = u([]), M = u(!1), N = u("outline"), P = f("docEl"), F = null, I = [], L = 0, R, z = "", B = null, V = 0;
23
+ function H() {
24
+ for (let e of I) try {
23
25
  e.cancel();
24
26
  } catch {}
25
- C = [];
27
+ I = [];
26
28
  }
27
- function E() {
28
- if (T(), S) {
29
+ function U() {
30
+ if (H(), F) {
29
31
  try {
30
- S.destroy();
32
+ F.destroy();
31
33
  } catch {}
32
- S = null;
34
+ F = null;
33
35
  }
34
36
  }
35
- async function D() {
36
- let e = x.value, t = S;
37
+ function W(e) {
38
+ if (!e.width || !e.height) return null;
39
+ try {
40
+ let t = document.createElement("canvas");
41
+ t.width = C, t.height = Math.max(1, Math.round(C * (e.height / e.width)));
42
+ let n = t.getContext("2d");
43
+ return n ? (n.fillStyle = "#fff", n.fillRect(0, 0, t.width, t.height), n.drawImage(e, 0, 0, t.width, t.height), t.toDataURL("image/jpeg", .78)) : null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+ function G(e, t = "smooth") {
49
+ let n = P.value, r = n?.querySelector(`[data-page-number="${e}"]`);
50
+ if (!n || !r) return;
51
+ O.value = e;
52
+ let i = n.getBoundingClientRect(), a = r.getBoundingClientRect(), o = Number.parseFloat(getComputedStyle(n).paddingTop);
53
+ n.scrollTo({
54
+ top: n.scrollTop + a.top - i.top - o,
55
+ behavior: t
56
+ });
57
+ }
58
+ function K() {
59
+ V ||= window.requestAnimationFrame(() => {
60
+ V = 0;
61
+ let e = P.value;
62
+ if (!e) return;
63
+ let t = e.getBoundingClientRect().top + Math.min(e.clientHeight * .36, 220), n = O.value, r = Infinity;
64
+ for (let i of e.querySelectorAll("[data-page-number]")) {
65
+ let e = i.getBoundingClientRect(), a = Math.min(Math.max(t, e.top), e.bottom), o = Math.abs(t - a);
66
+ o >= r || (r = o, n = Number(i.dataset.pageNumber) || n);
67
+ }
68
+ O.value = n;
69
+ });
70
+ }
71
+ async function q() {
72
+ let e = P.value, t = F;
37
73
  if (!e || !t) return;
38
- let n = ++w;
39
- T(), e.innerHTML = "";
40
- let r = getComputedStyle(e), i = parseFloat(r.paddingLeft) + parseFloat(r.paddingRight), a = Math.max(120, e.clientWidth - i), o = Math.min(typeof window < "u" && window.devicePixelRatio || 1, g);
41
- for (let r = 1; r <= t.numPages; r++) {
74
+ let n = ++L, r = O.value, i = e.scrollTop > 0;
75
+ H(), e.innerHTML = "";
76
+ let a = getComputedStyle(e), o = Number.parseFloat(a.paddingLeft) + Number.parseFloat(a.paddingRight), s = Number.parseFloat(a.paddingTop) + Number.parseFloat(a.paddingBottom), c = Math.max(120, e.clientWidth - o - S), l = Math.max(160, e.clientHeight - s - S), u = Math.min(window.devicePixelRatio || 1, x), d = Array(t.numPages).fill(null);
77
+ for (let r = 1; r <= t.numPages; r += 1) {
42
78
  let i;
43
79
  try {
44
80
  i = await t.getPage(r);
45
81
  } catch {
46
- if (n !== w) return;
82
+ if (n !== L) return;
47
83
  continue;
48
84
  }
49
- if (n !== w) return;
50
- let s = i.getViewport({ scale: 1 }), c = a / s.width, l = i.getViewport({ scale: c * o }), u = document.createElement("canvas");
51
- if (u.className = "pf__page", u.width = Math.ceil(l.width), u.height = Math.ceil(l.height), u.style.width = `${Math.round(a)}px`, u.style.height = `${Math.round(a * (s.height / s.width))}px`, !u.getContext("2d")) continue;
52
- e.appendChild(u);
53
- let d = i.render({
54
- canvas: u,
55
- viewport: l
85
+ if (n !== L) return;
86
+ let a = i.getViewport({ scale: 1 }), o = Math.min(c / a.width, l / a.height), s = Math.round(a.width * o), f = Math.round(a.height * o), p = i.getViewport({ scale: o * u }), m = document.createElement("div");
87
+ m.className = "pf__page-wrap", m.dataset.pageNumber = `${r}`;
88
+ let h = document.createElement("canvas");
89
+ if (h.className = "pf__page", h.width = Math.ceil(p.width), h.height = Math.ceil(p.height), h.style.width = `${s}px`, h.style.height = `${f}px`, h.setAttribute("aria-label", `PDF 第 ${r} 页`), !h.getContext("2d")) continue;
90
+ m.appendChild(h), e.appendChild(m);
91
+ let g = i.render({
92
+ canvas: h,
93
+ viewport: p
56
94
  });
57
- C.push(d);
95
+ I.push(g);
96
+ let _ = !0;
58
97
  try {
59
- await d.promise;
98
+ await g.promise;
60
99
  } catch {
61
- if (n !== w) return;
100
+ if (n !== L) return;
101
+ _ = !1;
62
102
  }
63
- if (n !== w) return;
103
+ if (n !== L) return;
104
+ _ && (d[r - 1] = W(h));
64
105
  }
106
+ j.value = d, i && G(r, "auto"), K();
65
107
  }
66
- async function O() {
67
- let e = x.value;
68
- if (!_.url || !e) return;
69
- v.value = "loading", y.value = "", E(), e.innerHTML = "";
70
- let t = _.url;
108
+ async function J() {
109
+ let e = P.value;
110
+ if (!w.url || !e) return;
111
+ T.value = "loading", E.value = "", k.value = [], A.value = [], j.value = [], U(), e.innerHTML = "";
112
+ let n = w.url;
71
113
  try {
72
- let n = await fetch(t);
73
- if (!n.ok) throw Error(`HTTP ${n.status}`);
74
- let r = await n.arrayBuffer();
75
- if (_.url !== t || x.value !== e) return;
76
- let i = await u.getDocument({ data: new Uint8Array(r) }).promise;
77
- if (_.url !== t || x.value !== e) {
78
- try {
79
- i.destroy();
80
- } catch {}
114
+ let r = await fetch(n);
115
+ if (!r.ok) throw Error(`HTTP ${r.status}`);
116
+ let i = new Uint8Array(await r.arrayBuffer());
117
+ if (w.url !== n || P.value !== e) return;
118
+ let a = await m.getDocument({ data: i }).promise;
119
+ if (w.url !== n || P.value !== e) {
120
+ a.destroy();
81
121
  return;
82
122
  }
83
- if (S = i, b.value = i.numPages, await D(), _.url !== t || x.value !== e) return;
84
- v.value = "ready";
123
+ F = a, D.value = a.numPages, O.value = 1;
124
+ let o = await t(a);
125
+ if (w.url !== n || P.value !== e || (k.value = o.outline, A.value = o.pageLabels, N.value = o.outline.length ? "outline" : "pages", await q(), w.url !== n || P.value !== e)) return;
126
+ T.value = "ready";
85
127
  } catch (e) {
86
- y.value = e instanceof Error ? e.message : String(e), v.value = "error";
128
+ E.value = e instanceof Error ? e.message : String(e), T.value = "error";
87
129
  }
88
130
  }
89
- let k, A = 0, j = null;
90
- function M() {
91
- let e = x.value;
92
- if (!e || !S) return;
93
- let t = e.clientWidth;
94
- Math.abs(t - A) < 8 || (A = t, clearTimeout(k), k = setTimeout(() => {
95
- S && D();
131
+ function Y() {
132
+ let e = P.value;
133
+ if (!e || !F) return;
134
+ let t = `${e.clientWidth}x${e.clientHeight}`;
135
+ !e.clientWidth || !e.clientHeight || t === z || (z = t, clearTimeout(R), R = setTimeout(() => {
136
+ F && q();
96
137
  }, 180));
97
138
  }
98
- return a(() => {
99
- O(), typeof ResizeObserver < "u" && x.value && (A = x.value.clientWidth, j = new ResizeObserver(M), j.observe(x.value));
100
- }), l(() => _.url, O), i(() => {
101
- clearTimeout(k), j?.disconnect(), w++, E(), x.value && (x.value.innerHTML = "");
102
- }), (r, i) => (o(), t("div", f, [n("div", {
139
+ return c(() => {
140
+ J(), typeof ResizeObserver < "u" && P.value && (z = `${P.value.clientWidth}x${P.value.clientHeight}`, B = new ResizeObserver(Y), B.observe(P.value));
141
+ }), p(() => w.url, J), s(() => {
142
+ clearTimeout(R), B?.disconnect(), V && window.cancelAnimationFrame(V), L += 1, U(), P.value && (P.value.innerHTML = "");
143
+ }), (t, o) => (l(), i("div", g, [T.value === "ready" ? (l(), n(e, {
144
+ key: 0,
145
+ collapsed: M.value,
146
+ "onUpdate:collapsed": o[0] ||= (e) => M.value = e,
147
+ view: N.value,
148
+ "onUpdate:view": o[1] ||= (e) => N.value = e,
149
+ "outline-items": k.value,
150
+ "page-count": D.value,
151
+ "page-labels": A.value,
152
+ "page-thumbnails": j.value,
153
+ "active-page": O.value,
154
+ onNavigate: G
155
+ }, null, 8, [
156
+ "collapsed",
157
+ "view",
158
+ "outline-items",
159
+ "page-count",
160
+ "page-labels",
161
+ "page-thumbnails",
162
+ "active-page"
163
+ ])) : r("", !0), a("div", _, [a("div", {
103
164
  ref_key: "docEl",
104
- ref: x,
105
- class: "pf__doc"
106
- }, null, 512), v.value === "loading" ? (o(), t("div", p, [...i[0] ||= [n("p", { class: "pf__msg" }, "正在渲染 PDF…", -1)]])) : v.value === "error" ? (o(), t("div", m, [n("div", h, [n("p", null, "渲染失败:" + c(y.value), 1), n("button", {
165
+ ref: P,
166
+ class: "pf__doc",
167
+ onScrollPassive: K
168
+ }, null, 544), T.value === "loading" ? (l(), i("div", v, [...o[2] ||= [a("p", { class: "pf__msg" }, "正在渲染 PDF…", -1)]])) : T.value === "error" ? (l(), i("div", y, [a("div", b, [a("p", null, "渲染失败:" + d(E.value), 1), a("button", {
107
169
  type: "button",
108
170
  class: "pf__retry",
109
- onClick: O
110
- }, "重试")])])) : e("", !0)]));
171
+ onClick: J
172
+ }, "重试")])])) : r("", !0)])]));
111
173
  }
112
174
  });
113
175
  //#endregion
114
- export { _ as default };
176
+ export { w as default };
115
177
 
116
178
  //# sourceMappingURL=PdfViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PdfViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/PdfViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport * as pdfjsLib from 'pdfjs-dist'\nimport type { PDFDocumentProxy, RenderTask } from 'pdfjs-dist'\n// pdfjs runs its parser/rasterizer in a web worker. Vite's `?url` emits the real worker asset and returns a\n// servable URL (root-relative in the same-origin prod build, a dev URL under `pnpm dev`); we point pdfjs at\n// it. The `.mjs` is served as text/javascript by Ktor (see AGENTS.md) so the module worker loads in prod.\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\npdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl\n\n/**\n * Side-viewer PDF (`.pdf`) renderer. Fetches the document bytes from the download URL and rasterizes each\n * page to a <canvas> entirely in-browser via pdfjs-dist (https://github.com/mozilla/pdf.js) — a vertically\n * scrollable list of pages (white sheets on a neutral \"desk\", like the docx/pptx viewers). No CDN / network\n * beyond the file fetch: pdfjs bundles its own worker, fetched lazily as a same-origin asset (see above).\n *\n * Two entry shapes mirror the other viewers (docx/xlsx/pptx/zip): a real download URL for a held attachment,\n * or a blob URL minted by the archive browsers from already-extracted bytes. Pages re-rasterize when the pane\n * is resized (debounced) so text stays crisp at the new width.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .pdf bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst pageCount = ref(0)\n// The scroll container the page canvases render into. Always mounted (the loading/error states overlay it).\nconst docEl = ref<HTMLElement | null>(null)\n\n// Live document proxy + in-flight render tasks — torn down on re-render / unmount (the worker and page\n// rasterizers don't GC on their own).\nlet pdfDoc: PDFDocumentProxy | null = null\nlet renderTasks: RenderTask[] = []\n// Bumped on every (re)render so a stale async pass (older file, or a superseded resize) can bail out.\nlet renderToken = 0\n// Cap the backing-store resolution: device pixels × this, clamped, keeps big/high-DPI pages from minting\n// multi-hundred-MB canvases while staying crisp.\nconst MAX_SCALE = 2\n\nfunction cancelRenders(): void {\n for (const t of renderTasks) {\n try {\n t.cancel()\n } catch {\n /* already settled — ignore */\n }\n }\n renderTasks = []\n}\n\nfunction disposeDoc(): void {\n cancelRenders()\n if (pdfDoc) {\n try {\n pdfDoc.destroy()\n } catch {\n /* already torn down — ignore */\n }\n pdfDoc = null\n }\n}\n\n// Rasterize every page of the live document into the container at its current width. Called on load and on\n// resize; guarded by `renderToken` so an outdated pass (file switched, or a newer resize started) stops early.\nasync function renderPages(): Promise<void> {\n const el = docEl.value\n const doc = pdfDoc\n if (!el || !doc) return\n const token = ++renderToken\n cancelRenders()\n el.innerHTML = ''\n // Available CSS width for a page (minus the container's horizontal padding). Floor at a sane minimum so a\n // momentarily-zero clientWidth (not yet laid out) doesn't render a 0-px canvas.\n const style = getComputedStyle(el)\n const padX = parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)\n const cssWidth = Math.max(120, el.clientWidth - padX)\n const dpr = Math.min(typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1, MAX_SCALE)\n for (let n = 1; n <= doc.numPages; n++) {\n let page\n try {\n page = await doc.getPage(n)\n } catch {\n if (token !== renderToken) return\n continue // skip an unreadable page rather than failing the whole doc\n }\n if (token !== renderToken) return\n const base = page.getViewport({ scale: 1 })\n const cssScale = cssWidth / base.width\n const viewport = page.getViewport({ scale: cssScale * dpr })\n const canvas = document.createElement('canvas')\n canvas.className = 'pf__page'\n canvas.width = Math.ceil(viewport.width)\n canvas.height = Math.ceil(viewport.height)\n // Display at the CSS width; the extra backing-store pixels (× dpr) make text sharp on HiDPI screens.\n canvas.style.width = `${Math.round(cssWidth)}px`\n canvas.style.height = `${Math.round(cssWidth * (base.height / base.width))}px`\n if (!canvas.getContext('2d')) continue // no 2D context available — skip rather than crash the render\n el.appendChild(canvas)\n // Pass `canvas` (the preferred param in pdfjs v5); it derives the 2D context itself.\n const task = page.render({ canvas, viewport })\n renderTasks.push(task)\n try {\n await task.promise\n } catch {\n // RenderingCancelledException on supersede/unmount is expected — only a live token cares about failures.\n if (token !== renderToken) return\n }\n if (token !== renderToken) return\n }\n}\n\nasync function load(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n disposeDoc()\n el.innerHTML = ''\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer load may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el) return\n const doc = await pdfjsLib.getDocument({ data: new Uint8Array(buf) }).promise\n if (props.url !== url || docEl.value !== el) {\n try {\n doc.destroy()\n } catch {\n /* ignore */\n }\n return\n }\n pdfDoc = doc\n pageCount.value = doc.numPages\n await renderPages()\n if (props.url !== url || docEl.value !== el) return\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\n// Re-rasterize on width changes (debounced) so pages stay crisp as the pane is dragged wider/narrower. Only\n// the width matters; height changes don't affect page scale.\nlet resizeTimer: ReturnType<typeof setTimeout> | undefined\nlet lastWidth = 0\nlet ro: ResizeObserver | null = null\nfunction onResize(): void {\n const el = docEl.value\n if (!el || !pdfDoc) return\n const w = el.clientWidth\n if (Math.abs(w - lastWidth) < 8) return // ignore sub-pixel / negligible jitter\n lastWidth = w\n clearTimeout(resizeTimer)\n resizeTimer = setTimeout(() => {\n if (pdfDoc) void renderPages()\n }, 180)\n}\n\nonMounted(() => {\n load()\n if (typeof ResizeObserver !== 'undefined' && docEl.value) {\n lastWidth = docEl.value.clientWidth\n ro = new ResizeObserver(onResize)\n ro.observe(docEl.value)\n }\n})\nwatch(() => props.url, load)\nonBeforeUnmount(() => {\n clearTimeout(resizeTimer)\n ro?.disconnect()\n renderToken++ // invalidate any in-flight render pass\n disposeDoc()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"pf\">\n <!-- Always mounted so the renderer has a sized, scrollable container; states overlay it. -->\n <div ref=\"docEl\" class=\"pf__doc\" />\n <div v-if=\"status === 'loading'\" class=\"pf__overlay\">\n <p class=\"pf__msg\">正在渲染 PDF…</p>\n </div>\n <div v-else-if=\"status === 'error'\" class=\"pf__overlay\">\n <div class=\"pf__msg pf__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"pf__retry\" @click=\"load\">重试</button>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.pf {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n/* Scroll region holding the rendered pages — white sheets on a neutral \"desk\", like the docx/pptx viewers. */\n.pf__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n padding: 18px 16px 40px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n}\n/* Each page canvas: a white sheet with a soft shadow. Width is set inline (the rasterized CSS width); the\n * canvas paints its own white, so no background needed here. */\n.pf__doc :deep(.pf__page) {\n display: block;\n max-width: 100%;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n border-radius: 2px;\n background: #fff;\n}\n/* Overlay covers the (empty) container while loading or on error, without unmounting it. */\n.pf__overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n background: var(--surface);\n}\n.pf__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.pf__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.pf__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.pf__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"PdfViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/PdfViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, useTemplateRef, watch } from 'vue'\nimport * as pdfjsLib from 'pdfjs-dist'\nimport type { PDFDocumentProxy, RenderTask } from 'pdfjs-dist'\n// pdfjs runs its parser/rasterizer in a web worker. Vite's `?url` emits the real worker asset and returns a\n// servable URL (root-relative in the same-origin prod build, a dev URL under `pnpm dev`).\nimport pdfWorkerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'\n\nimport PdfNavigationPane from './PdfNavigationPane.vue'\nimport { loadPdfNavigation, type PdfNavigationItem } from '../lib/pdfNavigation'\n\npdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorkerUrl\n\nconst props = defineProps<{\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\nconst pageCount = shallowRef(0)\nconst activePage = shallowRef(1)\nconst outlineItems = shallowRef<PdfNavigationItem[]>([])\nconst pageLabels = shallowRef<string[]>([])\nconst pageThumbnails = shallowRef<Array<string | null>>([])\nconst navigationCollapsed = shallowRef(false)\nconst navigationView = shallowRef<'outline' | 'pages'>('outline')\nconst docEl = useTemplateRef<HTMLElement>('docEl')\n\nlet pdfDoc: PDFDocumentProxy | null = null\nlet renderTasks: RenderTask[] = []\nlet renderToken = 0\nlet resizeTimer: ReturnType<typeof setTimeout> | undefined\nlet lastSize = ''\nlet resizeObserver: ResizeObserver | null = null\nlet scrollFrame = 0\nconst MAX_SCALE = 2\nconst PAGE_GUTTER = 24\nconst THUMBNAIL_WIDTH = 104\n\nfunction cancelRenders(): void {\n for (const task of renderTasks) {\n try {\n task.cancel()\n } catch {\n // The task may already have settled.\n }\n }\n renderTasks = []\n}\n\nfunction disposeDoc(): void {\n cancelRenders()\n if (!pdfDoc) return\n try {\n pdfDoc.destroy()\n } catch {\n // The worker may already have been torn down.\n }\n pdfDoc = null\n}\n\nfunction createPageThumbnail(canvas: HTMLCanvasElement): string | null {\n if (!canvas.width || !canvas.height) return null\n try {\n const thumbnail = document.createElement('canvas')\n thumbnail.width = THUMBNAIL_WIDTH\n thumbnail.height = Math.max(1, Math.round(THUMBNAIL_WIDTH * (canvas.height / canvas.width)))\n const context = thumbnail.getContext('2d')\n if (!context) return null\n context.fillStyle = '#fff'\n context.fillRect(0, 0, thumbnail.width, thumbnail.height)\n context.drawImage(canvas, 0, 0, thumbnail.width, thumbnail.height)\n return thumbnail.toDataURL('image/jpeg', 0.78)\n } catch {\n return null\n }\n}\n\nfunction scrollToPage(pageNumber: number, behavior: ScrollBehavior = 'smooth'): void {\n const el = docEl.value\n const target = el?.querySelector<HTMLElement>(`[data-page-number=\"${pageNumber}\"]`)\n if (!el || !target) return\n activePage.value = pageNumber\n const rootRect = el.getBoundingClientRect()\n const targetRect = target.getBoundingClientRect()\n const paddingTop = Number.parseFloat(getComputedStyle(el).paddingTop)\n el.scrollTo({\n top: el.scrollTop + targetRect.top - rootRect.top - paddingTop,\n behavior,\n })\n}\n\nfunction syncActivePage(): void {\n if (scrollFrame) return\n scrollFrame = window.requestAnimationFrame(() => {\n scrollFrame = 0\n const el = docEl.value\n if (!el) return\n const rootRect = el.getBoundingClientRect()\n const targetY = rootRect.top + Math.min(el.clientHeight * 0.36, 220)\n let nearestPage = activePage.value\n let nearestDistance = Number.POSITIVE_INFINITY\n for (const page of el.querySelectorAll<HTMLElement>('[data-page-number]')) {\n const rect = page.getBoundingClientRect()\n const pageTarget = Math.min(Math.max(targetY, rect.top), rect.bottom)\n const distance = Math.abs(targetY - pageTarget)\n if (distance >= nearestDistance) continue\n nearestDistance = distance\n nearestPage = Number(page.dataset.pageNumber) || nearestPage\n }\n activePage.value = nearestPage\n })\n}\n\nasync function renderPages(): Promise<void> {\n const el = docEl.value\n const doc = pdfDoc\n if (!el || !doc) return\n const token = ++renderToken\n const preservedPage = activePage.value\n const preserveScroll = el.scrollTop > 0\n cancelRenders()\n el.innerHTML = ''\n\n const style = getComputedStyle(el)\n const padX = Number.parseFloat(style.paddingLeft) + Number.parseFloat(style.paddingRight)\n const padY = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)\n const availableWidth = Math.max(120, el.clientWidth - padX - PAGE_GUTTER)\n const availableHeight = Math.max(160, el.clientHeight - padY - PAGE_GUTTER)\n const outputScale = Math.min(window.devicePixelRatio || 1, MAX_SCALE)\n const thumbnails = Array<string | null>(doc.numPages).fill(null)\n\n for (let pageNumber = 1; pageNumber <= doc.numPages; pageNumber += 1) {\n let page\n try {\n page = await doc.getPage(pageNumber)\n } catch {\n if (token !== renderToken) return\n continue\n }\n if (token !== renderToken) return\n\n const base = page.getViewport({ scale: 1 })\n const cssScale = Math.min(availableWidth / base.width, availableHeight / base.height)\n const cssWidth = Math.round(base.width * cssScale)\n const cssHeight = Math.round(base.height * cssScale)\n const viewport = page.getViewport({ scale: cssScale * outputScale })\n\n const pageWrap = document.createElement('div')\n pageWrap.className = 'pf__page-wrap'\n pageWrap.dataset.pageNumber = `${pageNumber}`\n const canvas = document.createElement('canvas')\n canvas.className = 'pf__page'\n canvas.width = Math.ceil(viewport.width)\n canvas.height = Math.ceil(viewport.height)\n canvas.style.width = `${cssWidth}px`\n canvas.style.height = `${cssHeight}px`\n canvas.setAttribute('aria-label', `PDF 第 ${pageNumber} 页`)\n if (!canvas.getContext('2d')) continue\n pageWrap.appendChild(canvas)\n el.appendChild(pageWrap)\n\n const task = page.render({ canvas, viewport })\n renderTasks.push(task)\n let rendered = true\n try {\n await task.promise\n } catch {\n if (token !== renderToken) return\n rendered = false\n }\n if (token !== renderToken) return\n if (rendered) thumbnails[pageNumber - 1] = createPageThumbnail(canvas)\n }\n\n pageThumbnails.value = thumbnails\n if (preserveScroll) scrollToPage(preservedPage, 'auto')\n syncActivePage()\n}\n\nasync function load(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n outlineItems.value = []\n pageLabels.value = []\n pageThumbnails.value = []\n disposeDoc()\n el.innerHTML = ''\n const url = props.url\n\n try {\n const response = await fetch(url)\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n const bytes = new Uint8Array(await response.arrayBuffer())\n if (props.url !== url || docEl.value !== el) return\n\n const document = await pdfjsLib.getDocument({ data: bytes }).promise\n if (props.url !== url || docEl.value !== el) {\n document.destroy()\n return\n }\n pdfDoc = document\n pageCount.value = document.numPages\n activePage.value = 1\n const navigation = await loadPdfNavigation(document)\n if (props.url !== url || docEl.value !== el) return\n outlineItems.value = navigation.outline\n pageLabels.value = navigation.pageLabels\n navigationView.value = navigation.outline.length ? 'outline' : 'pages'\n await renderPages()\n if (props.url !== url || docEl.value !== el) return\n status.value = 'ready'\n } catch (cause) {\n errorMsg.value = cause instanceof Error ? cause.message : String(cause)\n status.value = 'error'\n }\n}\n\nfunction onResize(): void {\n const el = docEl.value\n if (!el || !pdfDoc) return\n const size = `${el.clientWidth}x${el.clientHeight}`\n if (!el.clientWidth || !el.clientHeight || size === lastSize) return\n lastSize = size\n clearTimeout(resizeTimer)\n resizeTimer = setTimeout(() => {\n if (pdfDoc) void renderPages()\n }, 180)\n}\n\nonMounted(() => {\n void load()\n if (typeof ResizeObserver !== 'undefined' && docEl.value) {\n lastSize = `${docEl.value.clientWidth}x${docEl.value.clientHeight}`\n resizeObserver = new ResizeObserver(onResize)\n resizeObserver.observe(docEl.value)\n }\n})\nwatch(() => props.url, load)\nonBeforeUnmount(() => {\n clearTimeout(resizeTimer)\n resizeObserver?.disconnect()\n if (scrollFrame) window.cancelAnimationFrame(scrollFrame)\n renderToken += 1\n disposeDoc()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"pf\">\n <PdfNavigationPane\n v-if=\"status === 'ready'\"\n v-model:collapsed=\"navigationCollapsed\"\n v-model:view=\"navigationView\"\n :outline-items=\"outlineItems\"\n :page-count=\"pageCount\"\n :page-labels=\"pageLabels\"\n :page-thumbnails=\"pageThumbnails\"\n :active-page=\"activePage\"\n @navigate=\"scrollToPage\"\n />\n <div class=\"pf__stage\">\n <div ref=\"docEl\" class=\"pf__doc\" @scroll.passive=\"syncActivePage\" />\n <div v-if=\"status === 'loading'\" class=\"pf__overlay\">\n <p class=\"pf__msg\">正在渲染 PDF…</p>\n </div>\n <div v-else-if=\"status === 'error'\" class=\"pf__overlay\">\n <div class=\"pf__msg pf__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"pf__retry\" @click=\"load\">重试</button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.pf {\n position: relative;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex: 1 1 auto;\n overflow: hidden;\n container-type: inline-size;\n background: var(--surface);\n}\n\n.pf__stage {\n position: relative;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex: 1 1 0;\n overflow: hidden;\n}\n\n.pf__doc {\n width: 100%;\n min-width: 0;\n min-height: 0;\n flex: 1 1 0;\n overflow: auto;\n overscroll-behavior: contain;\n padding: 24px 20px 42px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 20px;\n box-sizing: border-box;\n background: var(--surface-2);\n scrollbar-color: #555961 transparent;\n scrollbar-width: thin;\n}\n\n.pf__doc :deep(.pf__page-wrap) {\n max-width: 100%;\n display: flex;\n flex: none;\n justify-content: center;\n}\n\n.pf__doc :deep(.pf__page) {\n max-width: 100%;\n display: block;\n background: #fff;\n border-radius: 2px;\n box-shadow: 0 4px 18px rgba(0, 0, 0, 0.28);\n}\n\n.pf__overlay {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: flex-start;\n justify-content: flex-start;\n background: var(--surface);\n}\n\n.pf__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n\n.pf__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n\n.pf__retry {\n padding: 5px 12px;\n color: var(--text);\n font-family: inherit;\n font-size: 12.5px;\n cursor: pointer;\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n}\n\n.pf__retry:hover {\n color: var(--accent);\n border-color: var(--accent);\n}\n</style>\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
2
2
  import t from "./SevenZipBrowser.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/components/SevenZipBrowser.vue
5
- var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-c8a21efa"]]);
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-6e24bc60"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
8