@file-viewer/core 2.0.11 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/config/options.d.ts +1 -1
  4. package/dist/config/options.js +1 -1
  5. package/dist/contracts/types.d.ts +76 -1
  6. package/dist/headless.d.ts +3 -3
  7. package/dist/headless.js +2 -2
  8. package/dist/index.d.ts +10 -7
  9. package/dist/index.js +106 -49
  10. package/dist/lifecycle/operations.d.ts +1 -0
  11. package/dist/lifecycle/operations.js +65 -6
  12. package/dist/platform/assets.d.ts +3 -1
  13. package/dist/platform/assets.js +43 -6
  14. package/dist/registry/capabilities.d.ts +2 -2
  15. package/dist/registry/capabilities.js +2 -1
  16. package/dist/registry/formats.d.ts +20 -7
  17. package/dist/registry/formats.js +14 -5
  18. package/dist/registry/registry.d.ts +8 -1
  19. package/dist/registry/registry.js +29 -0
  20. package/dist/renderers/image.js +1 -10
  21. package/dist/renderers/index.d.ts +320 -2
  22. package/dist/renderers/index.js +27 -157
  23. package/dist/viewer/createViewer.js +86 -3
  24. package/package.json +17 -44
  25. package/dist/renderers/archive.d.ts +0 -2
  26. package/dist/renderers/archive.js +0 -547
  27. package/dist/renderers/archiveCache.d.ts +0 -10
  28. package/dist/renderers/archiveCache.js +0 -96
  29. package/dist/renderers/archiveFallback.d.ts +0 -7
  30. package/dist/renderers/archiveFallback.js +0 -166
  31. package/dist/renderers/archiveShared.d.ts +0 -23
  32. package/dist/renderers/archiveShared.js +0 -71
  33. package/dist/renderers/audio.d.ts +0 -8
  34. package/dist/renderers/audio.js +0 -219
  35. package/dist/renderers/cad.d.ts +0 -2
  36. package/dist/renderers/cad.js +0 -446
  37. package/dist/renderers/code.d.ts +0 -11
  38. package/dist/renderers/code.js +0 -233
  39. package/dist/renderers/data.d.ts +0 -7
  40. package/dist/renderers/data.js +0 -370
  41. package/dist/renderers/drawing.d.ts +0 -10
  42. package/dist/renderers/drawing.js +0 -882
  43. package/dist/renderers/eda.d.ts +0 -2
  44. package/dist/renderers/eda.js +0 -434
  45. package/dist/renderers/edaParser.d.ts +0 -77
  46. package/dist/renderers/edaParser.js +0 -569
  47. package/dist/renderers/email.d.ts +0 -2
  48. package/dist/renderers/email.js +0 -463
  49. package/dist/renderers/epub.d.ts +0 -2
  50. package/dist/renderers/epub.js +0 -331
  51. package/dist/renderers/geo.d.ts +0 -2
  52. package/dist/renderers/geo.js +0 -284
  53. package/dist/renderers/markdown.d.ts +0 -2
  54. package/dist/renderers/markdown.js +0 -83
  55. package/dist/renderers/model.d.ts +0 -2
  56. package/dist/renderers/model.js +0 -567
  57. package/dist/renderers/ofd.d.ts +0 -2
  58. package/dist/renderers/ofd.js +0 -256
  59. package/dist/renderers/openDocument.d.ts +0 -2
  60. package/dist/renderers/openDocument.js +0 -122
  61. package/dist/renderers/pdf.d.ts +0 -3
  62. package/dist/renderers/pdf.js +0 -1001
  63. package/dist/renderers/pdfStyles.d.ts +0 -1
  64. package/dist/renderers/pdfStyles.js +0 -1
  65. package/dist/renderers/pptx.d.ts +0 -2
  66. package/dist/renderers/pptx.js +0 -217
  67. package/dist/renderers/spreadsheet/state.d.ts +0 -80
  68. package/dist/renderers/spreadsheet/state.js +0 -96
  69. package/dist/renderers/spreadsheet/view.d.ts +0 -25
  70. package/dist/renderers/spreadsheet/view.js +0 -833
  71. package/dist/renderers/spreadsheet/worker/index.d.ts +0 -2
  72. package/dist/renderers/spreadsheet/worker/index.js +0 -1
  73. package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +0 -73
  74. package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +0 -623
  75. package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +0 -2
  76. package/dist/renderers/spreadsheet/worker/sheetjs/color.js +0 -73
  77. package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +0 -1
  78. package/dist/renderers/spreadsheet/worker/sheetjs/index.js +0 -1
  79. package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +0 -18
  80. package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +0 -106
  81. package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +0 -1
  82. package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +0 -11
  83. package/dist/renderers/spreadsheet/worker/type.d.ts +0 -57
  84. package/dist/renderers/spreadsheet/worker/type.js +0 -1
  85. package/dist/renderers/spreadsheet.d.ts +0 -3
  86. package/dist/renderers/spreadsheet.js +0 -929
  87. package/dist/renderers/typst.d.ts +0 -8
  88. package/dist/renderers/typst.js +0 -547
  89. package/dist/renderers/umd/parser.d.ts +0 -30
  90. package/dist/renderers/umd/parser.js +0 -408
  91. package/dist/renderers/umd.d.ts +0 -2
  92. package/dist/renderers/umd.js +0 -297
  93. package/dist/renderers/video.d.ts +0 -8
  94. package/dist/renderers/video.js +0 -108
  95. package/dist/renderers/wordDoc.d.ts +0 -5
  96. package/dist/renderers/wordDoc.js +0 -284
  97. package/dist/renderers/wordDocx.d.ts +0 -5
  98. package/dist/renderers/wordDocx.js +0 -985
  99. package/dist/renderers/wordDocx.worker.d.ts +0 -1
  100. package/dist/renderers/wordDocx.worker.js +0 -96
  101. package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +0 -183
  102. package/vendor/ofd/dltech/jbig2/ccitt.js +0 -1070
  103. package/vendor/ofd/dltech/jbig2/compatibility.js +0 -12
  104. package/vendor/ofd/dltech/jbig2/core_utils.js +0 -180
  105. package/vendor/ofd/dltech/jbig2/is_node.js +0 -27
  106. package/vendor/ofd/dltech/jbig2/jbig2.js +0 -2589
  107. package/vendor/ofd/dltech/jbig2/jbig2_stream.js +0 -81
  108. package/vendor/ofd/dltech/jbig2/primitives.js +0 -387
  109. package/vendor/ofd/dltech/jbig2/stream.js +0 -1348
  110. package/vendor/ofd/dltech/jbig2/util.js +0 -972
  111. package/vendor/ofd/dltech/ofd/ofd.d.ts +0 -11
  112. package/vendor/ofd/dltech/ofd/ofd.js +0 -100
  113. package/vendor/ofd/dltech/ofd/ofd_parser.js +0 -395
  114. package/vendor/ofd/dltech/ofd/ofd_render.js +0 -473
  115. package/vendor/ofd/dltech/ofd/ofd_util.js +0 -350
  116. package/vendor/ofd/dltech/ofd/pipeline.js +0 -26
package/package.json CHANGED
@@ -1,14 +1,21 @@
1
1
  {
2
2
  "name": "@file-viewer/core",
3
- "version": "2.0.11",
3
+ "version": "2.1.1",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Framework-neutral TypeScript core for Flyfish File Viewer",
6
+ "description": "Framework-neutral modular TypeScript core for Flyfish File Viewer renderer orchestration, lifecycle APIs, search, zoom, print, and export",
7
7
  "keywords": [
8
8
  "file-viewer",
9
9
  "document-preview",
10
10
  "typescript",
11
- "core"
11
+ "core",
12
+ "document-viewer",
13
+ "file-preview",
14
+ "self-hosted",
15
+ "browser",
16
+ "wasm",
17
+ "office-viewer",
18
+ "pdf-viewer"
12
19
  ],
13
20
  "publishConfig": {
14
21
  "access": "public",
@@ -23,6 +30,13 @@
23
30
  "url": "git+https://github.com/flyfish-dev/file-viewer-core.git",
24
31
  "directory": "packages/core"
25
32
  },
33
+ "bugs": {
34
+ "url": "https://github.com/flyfish-dev/file-viewer-core/issues"
35
+ },
36
+ "funding": {
37
+ "type": "individual",
38
+ "url": "https://dev.flyfish.group/shop"
39
+ },
26
40
  "homepage": "https://doc.file-viewer.app/",
27
41
  "main": "./dist/index.js",
28
42
  "module": "./dist/index.js",
@@ -57,48 +71,7 @@
57
71
  "README.en.md",
58
72
  "LICENSE"
59
73
  ],
60
- "dependencies": {
61
- "@excalidraw/excalidraw": "^0.18.1",
62
- "@file-viewer/docx": "^0.3.12",
63
- "@file-viewer/pptx": "^2.0.11",
64
- "@flyfish-dev/cad-viewer": "^0.6.4",
65
- "@kenjiuno/msgreader": "^1.28.0",
66
- "@myriaddreamin/typst-ts-renderer": "0.7.0",
67
- "@myriaddreamin/typst-ts-web-compiler": "0.7.0",
68
- "@myriaddreamin/typst.ts": "0.7.0",
69
- "@tmcw/togeojson": "^7.1.2",
70
- "@tonejs/midi": "^2.0.28",
71
- "@xmldom/xmldom": "^0.9.10",
72
- "ag-psd": "^30.1.1",
73
- "avsc": "^5.7.9",
74
- "cfb": "^1.2.2",
75
- "e-virt-table": "^1.3.26",
76
- "epubjs": "^0.3.93",
77
- "heic2any": "^0.0.4",
78
- "highlight.js": "^11.11.1",
79
- "hls.js": "^1.6.16",
80
- "hyparquet": "^1.26.0",
81
- "jszip": "^3.10.1",
82
- "libarchive.js": "^2.0.2",
83
- "linkedom": "^0.18.12",
84
- "marked": "^18.0.5",
85
- "msdoc-viewer": "^0.2.0",
86
- "ofd-xml-parser": "^0.0.6",
87
- "pako": "^2.1.0",
88
- "pdfjs-dist": "^6.0.227",
89
- "postal-mime": "^2.7.4",
90
- "roughjs": "^4.6.6",
91
- "rtf.js": "^3.0.9",
92
- "shpjs": "^6.2.0",
93
- "sql.js": "^1.14.1",
94
- "styled-exceljs": "0.21.1",
95
- "three": "^0.184.0",
96
- "tinycolor2": "^1.6.0"
97
- },
98
74
  "devDependencies": {
99
- "@types/pako": "^2.0.4",
100
- "@types/three": "^0.184.1",
101
- "@types/tinycolor2": "^1.4.6",
102
75
  "typescript": "^6.0.3"
103
76
  },
104
77
  "license": "Apache-2.0",
@@ -1,2 +0,0 @@
1
- import type { FileRenderContext, FileViewerRenderedInstance } from '../contracts/types';
2
- export default function renderArchive(buffer: ArrayBuffer, target: HTMLDivElement, _type?: string, context?: FileRenderContext): Promise<FileViewerRenderedInstance>;
@@ -1,547 +0,0 @@
1
- import { resolveFileViewerArchiveWasmUrl, resolveFileViewerArchiveWorkerUrl, } from '../platform/assets.js';
2
- import { disposeFileViewerRendered } from '../rendering/handler.js';
3
- import { createArchiveCacheKey, flattenArchiveObject, formatArchiveBytes, getArchiveEntryExtension, } from './archiveShared.js';
4
- import { readArchiveCache, writeArchiveCache } from './archiveCache.js';
5
- import { loadArchiveEntriesWithoutWorker } from './archiveFallback.js';
6
- const DEFAULT_MAX_ARCHIVE_SIZE = 320 * 1024 * 1024;
7
- const DEFAULT_MAX_ENTRY_PREVIEW_SIZE = 64 * 1024 * 1024;
8
- const DEFAULT_WORKER_TIMEOUT_MS = 30000;
9
- const MAX_LISTED_ENTRIES = 5000;
10
- const archiveStyle = `
11
- .archive-shell,.archive-viewer{position:relative;box-sizing:border-box;height:100%;min-height:0;display:grid;grid-template-columns:minmax(280px,34%) minmax(0,1fr);background:#edf2f7;color:#172033;font-family:Aptos,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif}
12
- .archive-shell *,.archive-viewer *{box-sizing:border-box}
13
- .archive-sidebar{min-width:0;min-height:0;display:flex;flex-direction:column;gap:12px;padding:16px;border-right:1px solid rgba(23,32,51,.08);background:rgba(255,255,255,.72)}
14
- .archive-head span,.archive-preview-toolbar span{color:#6c7c90;font-size:12px;font-weight:800;letter-spacing:0}
15
- .archive-head strong,.archive-preview-toolbar strong{display:block;margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:18px;line-height:1.25}
16
- .archive-head p{margin:8px 0 0;color:#69798b;font-size:13px}
17
- .archive-warning,.archive-info,.archive-error{border-radius:12px;padding:10px 12px;background:#fff7e8;color:#8a4b00;font-size:13px;line-height:1.5}
18
- .archive-info{background:#ecfdf5;color:#166534}
19
- .archive-search{width:100%;height:42px;padding:0 12px;border-radius:12px;border:1px solid rgba(23,32,51,.1);outline:none;background:#fff;color:#172033;font:inherit}
20
- .archive-list{flex:1;min-height:0;overflow:auto;display:flex;flex-direction:column;gap:7px;padding-right:4px}
21
- .archive-entry{width:100%;min-height:58px;display:grid;grid-template-columns:42px minmax(0,1fr) auto;gap:10px;align-items:center;padding:8px 10px 8px calc(10px + var(--entry-depth,0) * 10px);border:1px solid rgba(23,32,51,.07);border-radius:12px;background:rgba(255,255,255,.86);color:inherit;text-align:left;cursor:pointer;font:inherit}
22
- .archive-entry:hover,.archive-entry.active{border-color:rgba(33,129,95,.28);box-shadow:0 10px 22px rgba(23,32,51,.08)}
23
- .entry-ext{height:34px;display:inline-flex;align-items:center;justify-content:center;border-radius:10px;background:rgba(33,129,95,.12);color:#1d7a56;font-size:11px;font-weight:900;text-transform:uppercase}
24
- .entry-copy{min-width:0}
25
- .entry-copy strong,.entry-copy em{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
26
- .entry-copy em,.archive-entry small{color:#718096;font-size:12px;font-style:normal}
27
- .archive-preview{min-width:0;min-height:0;display:flex;flex-direction:column}
28
- .archive-preview-toolbar{min-height:64px;display:flex;align-items:center;justify-content:space-between;gap:14px;padding:12px 16px;border-bottom:1px solid rgba(23,32,51,.08);background:rgba(255,255,255,.76)}
29
- .archive-preview-toolbar button{height:34px;border:0;border-radius:10px;padding:0 12px;background:#1f7a58;color:#fff;font:inherit;font-size:13px;font-weight:800;cursor:pointer}
30
- .archive-nested-target{position:relative;flex:1;min-height:0;overflow:auto}
31
- .archive-nested-content{width:100%;height:100%;min-height:420px}
32
- .archive-empty{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:28px;text-align:center;color:#64748b}
33
- .archive-empty strong{color:#172033;font-size:18px}
34
- .archive-state{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(241,245,249,.82);backdrop-filter:blur(8px);z-index:4}
35
- .archive-state>div{display:flex;align-items:center;gap:14px;width:min(92%,430px);padding:18px;border-radius:16px;background:#fff;box-shadow:0 18px 42px rgba(15,23,42,.14)}
36
- .archive-state p{margin:4px 0 0;color:#64748b}
37
- .archive-spinner{width:34px;height:34px;flex-shrink:0;border-radius:999px;border:3px solid rgba(31,122,88,.16);border-top-color:#1f7a58;animation:archive-spin .9s linear infinite}
38
- .archive-error{position:absolute;right:18px;bottom:18px;width:min(460px,calc(100% - 36px));box-shadow:0 16px 36px rgba(23,32,51,.14);z-index:5}
39
- .archive-hidden{display:none!important}
40
- .file-viewer[data-viewer-theme='dark'] .archive-shell,.file-viewer[data-viewer-theme='dark'] .archive-viewer{background:#101820;color:#e6edf3}
41
- .file-viewer[data-viewer-theme='dark'] .archive-sidebar,.file-viewer[data-viewer-theme='dark'] .archive-preview-toolbar{border-color:rgba(139,148,158,.2);background:rgba(21,27,35,.82)}
42
- .file-viewer[data-viewer-theme='dark'] .archive-entry,.file-viewer[data-viewer-theme='dark'] .archive-search,.file-viewer[data-viewer-theme='dark'] .archive-state>div{background:#151b23;color:#e6edf3;border-color:rgba(139,148,158,.2)}
43
- .file-viewer[data-viewer-theme='dark'] .archive-empty strong{color:#f8fafc}
44
- @media (prefers-color-scheme:dark){.file-viewer[data-viewer-theme='system'] .archive-shell,.file-viewer[data-viewer-theme='system'] .archive-viewer{background:#101820;color:#e6edf3}.file-viewer[data-viewer-theme='system'] .archive-sidebar,.file-viewer[data-viewer-theme='system'] .archive-preview-toolbar{border-color:rgba(139,148,158,.2);background:rgba(21,27,35,.82)}.file-viewer[data-viewer-theme='system'] .archive-entry,.file-viewer[data-viewer-theme='system'] .archive-search,.file-viewer[data-viewer-theme='system'] .archive-state>div{background:#151b23;color:#e6edf3;border-color:rgba(139,148,158,.2)}.file-viewer[data-viewer-theme='system'] .archive-empty strong{color:#f8fafc}}
45
- @keyframes archive-spin{to{transform:rotate(360deg)}}
46
- @media (max-width:860px){.archive-shell,.archive-viewer{grid-template-columns:1fr;grid-template-rows:minmax(220px,38%) minmax(0,1fr)}.archive-sidebar{border-right:0;border-bottom:1px solid rgba(23,32,51,.08)}}
47
- `;
48
- const createStyle = (documentRef) => {
49
- const style = documentRef.createElement('style');
50
- style.textContent = archiveStyle;
51
- return style;
52
- };
53
- const createElement = (documentRef, tagName, className, text) => {
54
- const element = documentRef.createElement(tagName);
55
- if (className) {
56
- element.className = className;
57
- }
58
- if (text !== undefined) {
59
- element.textContent = text;
60
- }
61
- return element;
62
- };
63
- const normalizeWorkerError = (reason) => {
64
- if (reason instanceof Error) {
65
- return reason.message;
66
- }
67
- return typeof reason === 'string' ? reason : JSON.stringify(reason);
68
- };
69
- const withTimeout = async (promise, timeout, message, targetWindow) => {
70
- let timer = 0;
71
- const timerWindow = targetWindow || (typeof window !== 'undefined' ? window : undefined);
72
- try {
73
- return await Promise.race([
74
- promise,
75
- new Promise((_, reject) => {
76
- timer = (timerWindow === null || timerWindow === void 0 ? void 0 : timerWindow.setTimeout)
77
- ? timerWindow.setTimeout(() => reject(new Error(message)), timeout)
78
- : setTimeout(() => reject(new Error(message)), timeout);
79
- }),
80
- ]);
81
- }
82
- finally {
83
- if (timerWindow === null || timerWindow === void 0 ? void 0 : timerWindow.clearTimeout) {
84
- timerWindow.clearTimeout(timer);
85
- }
86
- else {
87
- clearTimeout(timer);
88
- }
89
- }
90
- };
91
- const getDocumentBaseUrl = (documentRef) => {
92
- return documentRef.baseURI ||
93
- documentRef.URL ||
94
- 'http://localhost/';
95
- };
96
- const getWorkerConstructor = (documentRef) => {
97
- var _a;
98
- const WorkerCtor = ((_a = documentRef.defaultView) === null || _a === void 0 ? void 0 : _a.Worker) ||
99
- (typeof Worker !== 'undefined' ? Worker : undefined);
100
- if (!WorkerCtor) {
101
- throw new Error('当前浏览器不支持 Web Worker');
102
- }
103
- return WorkerCtor;
104
- };
105
- const getFileConstructor = (documentRef) => {
106
- var _a;
107
- return (((_a = documentRef.defaultView) === null || _a === void 0 ? void 0 : _a.File) ||
108
- (typeof File !== 'undefined' ? File : undefined));
109
- };
110
- const createArchiveFile = (documentRef, buffer, filename) => {
111
- const FileCtor = getFileConstructor(documentRef);
112
- if (FileCtor) {
113
- return new FileCtor([buffer], filename || 'archive.bin', {
114
- type: 'application/octet-stream',
115
- });
116
- }
117
- return Object.assign(new Blob([buffer], { type: 'application/octet-stream' }), {
118
- name: filename || 'archive.bin',
119
- });
120
- };
121
- const probeWorkerUrl = async (url) => {
122
- try {
123
- const response = await fetch(url, { method: 'HEAD', cache: 'no-cache' });
124
- const contentType = response.headers.get('content-type') || '';
125
- if (response.ok && /javascript|ecmascript|octet-stream/i.test(contentType)) {
126
- return true;
127
- }
128
- if (response.status && response.status !== 405) {
129
- return false;
130
- }
131
- }
132
- catch {
133
- // Some local servers do not support HEAD; continue with a tiny GET probe.
134
- }
135
- try {
136
- const response = await fetch(url, {
137
- method: 'GET',
138
- cache: 'no-cache',
139
- headers: {
140
- Range: 'bytes=0-0',
141
- },
142
- });
143
- const contentType = response.headers.get('content-type') || '';
144
- return response.ok && /javascript|ecmascript|octet-stream/i.test(contentType);
145
- }
146
- catch {
147
- return false;
148
- }
149
- };
150
- const patchLibarchiveWorkerSource = (source, wasmUrl) => {
151
- const wasmLiteral = JSON.stringify(wasmUrl);
152
- return source.replace(/new URL\((['"])libarchive\.wasm\1\s*,\s*import\.meta\.url\)\.href/g, wasmLiteral);
153
- };
154
- const prepareWorkerUrl = async (candidate, objectUrls) => {
155
- if (!candidate.wasmUrl) {
156
- return candidate.workerUrl;
157
- }
158
- const response = await fetch(candidate.workerUrl, { cache: 'no-cache' });
159
- if (!response.ok) {
160
- throw new Error(`无法读取 libarchive Worker: ${response.status}`);
161
- }
162
- const source = await response.text();
163
- const workerUrl = URL.createObjectURL(new Blob([
164
- patchLibarchiveWorkerSource(source, candidate.wasmUrl),
165
- ], { type: 'application/javascript' }));
166
- objectUrls.push(workerUrl);
167
- return workerUrl;
168
- };
169
- const resolveWorkerCandidates = async (documentRef, options) => {
170
- const candidates = [];
171
- const baseUrl = getDocumentBaseUrl(documentRef);
172
- const wasmUrl = (options === null || options === void 0 ? void 0 : options.wasmUrl)
173
- ? resolveFileViewerArchiveWasmUrl(options, '')
174
- : undefined;
175
- if (options === null || options === void 0 ? void 0 : options.workerUrl) {
176
- candidates.push({
177
- label: '自定义 libarchive Worker',
178
- workerUrl: resolveFileViewerArchiveWorkerUrl(options, baseUrl),
179
- wasmUrl,
180
- });
181
- return candidates;
182
- }
183
- const publicWorkerUrl = resolveFileViewerArchiveWorkerUrl(undefined, baseUrl);
184
- if (await probeWorkerUrl(publicWorkerUrl)) {
185
- candidates.push({
186
- label: '静态 libarchive Worker',
187
- workerUrl: publicWorkerUrl,
188
- wasmUrl,
189
- });
190
- }
191
- return candidates;
192
- };
193
- const buildNestedOptions = (context, archiveOptions) => ({
194
- ...((context === null || context === void 0 ? void 0 : context.options) || {}),
195
- archive: archiveOptions,
196
- });
197
- const renderNestedWithCoreFallback = async (buffer, type, target, context) => {
198
- const { fileViewerCoreRendererDispatcher } = await import('./index.js');
199
- const handler = fileViewerCoreRendererDispatcher.resolve(type);
200
- if (!handler) {
201
- target.textContent = `不支持.${type}格式的在线预览,请下载后预览或转换为支持的格式`;
202
- return undefined;
203
- }
204
- return handler(buffer, target, type, {
205
- ...context,
206
- renderNestedBuffer: (context === null || context === void 0 ? void 0 : context.renderNestedBuffer) || renderNestedWithCoreFallback,
207
- });
208
- };
209
- export default async function renderArchive(buffer, target, _type, context) {
210
- var _a;
211
- const documentRef = target.ownerDocument;
212
- const targetWindow = documentRef.defaultView || null;
213
- const archiveOptions = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.archive;
214
- const filename = (context === null || context === void 0 ? void 0 : context.filename) || 'archive.bin';
215
- const maxArchiveSize = (archiveOptions === null || archiveOptions === void 0 ? void 0 : archiveOptions.maxArchiveSize) || DEFAULT_MAX_ARCHIVE_SIZE;
216
- const maxEntryPreviewSize = (archiveOptions === null || archiveOptions === void 0 ? void 0 : archiveOptions.maxEntryPreviewSize) || DEFAULT_MAX_ENTRY_PREVIEW_SIZE;
217
- const cacheEnabled = (archiveOptions === null || archiveOptions === void 0 ? void 0 : archiveOptions.cache) !== false;
218
- const workerTimeoutMs = (archiveOptions === null || archiveOptions === void 0 ? void 0 : archiveOptions.workerTimeoutMs) || DEFAULT_WORKER_TIMEOUT_MS;
219
- const objectUrls = [];
220
- const cleanups = [];
221
- let archiveReader = null;
222
- let entries = [];
223
- let selectedEntry = null;
224
- let nestedRendered;
225
- let loading = false;
226
- let loadingText = '正在读取压缩包目录...';
227
- let loadingHint = '大文件会在 Worker 中解析,避免阻塞主线程。';
228
- let errorText = '';
229
- let archiveNotice = '';
230
- let encrypted = null;
231
- let filterText = '';
232
- const style = createStyle(documentRef);
233
- const root = createElement(documentRef, 'section', 'archive-shell archive-viewer');
234
- const sidebar = createElement(documentRef, 'aside', 'archive-sidebar');
235
- const head = createElement(documentRef, 'div', 'archive-head');
236
- const badge = createElement(documentRef, 'span', undefined, 'ARCHIVE');
237
- const title = createElement(documentRef, 'strong', undefined, filename);
238
- const stats = createElement(documentRef, 'p');
239
- head.append(badge, title, stats);
240
- const warning = createElement(documentRef, 'div', 'archive-warning');
241
- const info = createElement(documentRef, 'div', 'archive-info');
242
- const search = createElement(documentRef, 'input', 'archive-search');
243
- search.type = 'search';
244
- search.placeholder = '筛选压缩包内文件';
245
- const list = createElement(documentRef, 'div', 'archive-list');
246
- list.setAttribute('role', 'list');
247
- sidebar.append(head, warning, info, search, list);
248
- const preview = createElement(documentRef, 'main', 'archive-preview');
249
- const toolbar = createElement(documentRef, 'div', 'archive-preview-toolbar');
250
- const toolbarTitle = createElement(documentRef, 'div');
251
- toolbarTitle.append(createElement(documentRef, 'span', undefined, '压缩包内预览'), createElement(documentRef, 'strong', undefined, '请选择一个文件'));
252
- const downloadButton = createElement(documentRef, 'button', undefined, '下载文件');
253
- downloadButton.type = 'button';
254
- toolbar.append(toolbarTitle, downloadButton);
255
- const nestedTarget = createElement(documentRef, 'div', 'archive-nested-target');
256
- preview.append(toolbar, nestedTarget);
257
- root.append(sidebar, preview);
258
- const state = createElement(documentRef, 'div', 'archive-state');
259
- const stateContent = createElement(documentRef, 'div');
260
- const spinner = createElement(documentRef, 'span', 'archive-spinner');
261
- const stateCopy = createElement(documentRef, 'div');
262
- const stateTitle = createElement(documentRef, 'strong', undefined, loadingText);
263
- const stateHint = createElement(documentRef, 'p', undefined, loadingHint);
264
- stateCopy.append(stateTitle, stateHint);
265
- stateContent.append(spinner, stateCopy);
266
- state.append(stateContent);
267
- root.append(state);
268
- const error = createElement(documentRef, 'div', 'archive-error');
269
- const errorTitle = createElement(documentRef, 'strong', undefined, '压缩包预览提示');
270
- const errorMessage = createElement(documentRef, 'p');
271
- error.append(errorTitle, errorMessage);
272
- root.append(error);
273
- target.replaceChildren(style, root);
274
- const listen = (element, event, listener) => {
275
- element.addEventListener(event, listener);
276
- cleanups.push(() => element.removeEventListener(event, listener));
277
- };
278
- const getArchiveStats = () => {
279
- const totalSize = entries.reduce((sum, entry) => sum + entry.size, 0);
280
- const previewableCount = entries.filter(entry => entry.previewable).length;
281
- return {
282
- count: entries.length,
283
- totalSize,
284
- previewableCount,
285
- };
286
- };
287
- const getFilteredEntries = () => {
288
- const keyword = filterText.trim().toLowerCase();
289
- const source = keyword
290
- ? entries.filter(entry => entry.path.toLowerCase().includes(keyword))
291
- : entries;
292
- return source.slice(0, MAX_LISTED_ENTRIES);
293
- };
294
- const clearNestedPreview = async () => {
295
- await disposeFileViewerRendered(nestedRendered);
296
- nestedRendered = undefined;
297
- nestedTarget.replaceChildren();
298
- };
299
- const closeArchive = async () => {
300
- var _a;
301
- await ((_a = archiveReader === null || archiveReader === void 0 ? void 0 : archiveReader.close) === null || _a === void 0 ? void 0 : _a.call(archiveReader));
302
- archiveReader = null;
303
- };
304
- const syncState = () => {
305
- const archiveStats = getArchiveStats();
306
- stats.textContent = `${archiveStats.count} 个文件 · ${formatArchiveBytes(archiveStats.totalSize)} · ${archiveStats.previewableCount} 个可直接预览`;
307
- warning.textContent = '检测到加密内容,当前在线预览不接收密码,建议下载后本地解压。';
308
- warning.classList.toggle('archive-hidden', !encrypted);
309
- info.textContent = archiveNotice;
310
- info.classList.toggle('archive-hidden', !archiveNotice);
311
- state.classList.toggle('archive-hidden', !loading);
312
- stateTitle.textContent = loadingText;
313
- stateHint.textContent = loadingHint;
314
- error.classList.toggle('archive-hidden', !errorText);
315
- errorMessage.textContent = errorText;
316
- downloadButton.hidden = !selectedEntry;
317
- const activeTitle = toolbarTitle.querySelector('strong');
318
- if (activeTitle) {
319
- activeTitle.textContent = (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.name) || '请选择一个文件';
320
- }
321
- };
322
- const renderEmptyState = () => {
323
- if (selectedEntry || loading || nestedTarget.childElementCount) {
324
- return;
325
- }
326
- const empty = createElement(documentRef, 'div', 'archive-empty');
327
- empty.append(createElement(documentRef, 'strong', undefined, '选择左侧文件即可预览'), createElement(documentRef, 'p', undefined, '压缩包只读取目录;文件内容会在点击后按需解压,并在体积允许时缓存到 IndexedDB。'));
328
- nestedTarget.replaceChildren(empty);
329
- };
330
- const renderEntryList = () => {
331
- list.replaceChildren();
332
- getFilteredEntries().forEach(entry => {
333
- const button = createElement(documentRef, 'button', 'archive-entry');
334
- button.type = 'button';
335
- button.style.setProperty('--entry-depth', String(entry.depth));
336
- button.classList.toggle('active', (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.id) === entry.id);
337
- const icon = createElement(documentRef, 'span', 'entry-ext', entry.extension || 'file');
338
- const copy = createElement(documentRef, 'span', 'entry-copy');
339
- copy.append(createElement(documentRef, 'strong', undefined, entry.name), createElement(documentRef, 'em', undefined, entry.path));
340
- button.append(icon, copy, createElement(documentRef, 'small', undefined, formatArchiveBytes(entry.size)));
341
- button.addEventListener('click', () => {
342
- void previewEntry(entry);
343
- });
344
- list.append(button);
345
- });
346
- };
347
- const setLoading = (next, text, hint) => {
348
- loading = next;
349
- if (text) {
350
- loadingText = text;
351
- }
352
- if (hint) {
353
- loadingHint = hint;
354
- }
355
- syncState();
356
- renderEmptyState();
357
- };
358
- const setError = (message) => {
359
- errorText = message;
360
- syncState();
361
- };
362
- const terminateWorkers = (workers) => {
363
- workers.forEach(worker => worker.terminate());
364
- workers.length = 0;
365
- };
366
- const tryOpenArchiveWithWorker = async (Archive, candidate) => {
367
- const createdWorkers = [];
368
- const workerUrl = await prepareWorkerUrl(candidate, objectUrls);
369
- const WorkerCtor = getWorkerConstructor(documentRef);
370
- try {
371
- Archive.init({
372
- getWorker: () => {
373
- const worker = new WorkerCtor(workerUrl, { type: 'module' });
374
- createdWorkers.push(worker);
375
- return worker;
376
- },
377
- });
378
- setLoading(true, `正在初始化${candidate.label}...`, '如果当前服务器没有正确发布 Worker/WASM,会自动切换兼容模式。');
379
- const archiveFile = createArchiveFile(documentRef, buffer, filename);
380
- const archive = await withTimeout(Archive.open(archiveFile), workerTimeoutMs, `${candidate.label} 初始化超时`, targetWindow);
381
- archiveReader = archive;
382
- encrypted = await withTimeout(archive.hasEncryptedData(), workerTimeoutMs, `${candidate.label} 加密检测超时`, targetWindow).catch(() => null);
383
- setLoading(true, '正在读取压缩包目录...', '目录读取完成后,点击内部文件才会按需解压。');
384
- const fileTree = await withTimeout(archive.getFilesObject(), workerTimeoutMs, `${candidate.label} 读取目录超时`, targetWindow);
385
- entries = flattenArchiveObject(fileTree)
386
- .sort((left, right) => left.path.localeCompare(right.path));
387
- syncState();
388
- renderEntryList();
389
- renderEmptyState();
390
- return true;
391
- }
392
- catch (reason) {
393
- if (!archiveReader) {
394
- terminateWorkers(createdWorkers);
395
- }
396
- throw reason;
397
- }
398
- };
399
- const tryOpenArchiveWithFallback = async () => {
400
- setLoading(true, 'Worker 不可用,正在切换 ZIP/TAR 兼容模式...', '兼容模式无需额外静态 Worker,适合手机 WebView 或本地临时服务器。');
401
- const fallbackEntries = await loadArchiveEntriesWithoutWorker(buffer, filename);
402
- if (!fallbackEntries) {
403
- return false;
404
- }
405
- entries = fallbackEntries.sort((left, right) => left.path.localeCompare(right.path));
406
- encrypted = null;
407
- archiveNotice = '当前环境的 libarchive Worker 未能启动,已自动切换到 ZIP/TAR/GZIP 兼容模式。RAR、7z 等格式仍建议发布 vendor/libarchive/worker-bundle.js 与 libarchive.wasm。';
408
- syncState();
409
- renderEntryList();
410
- renderEmptyState();
411
- return true;
412
- };
413
- const openArchive = async () => {
414
- if (buffer.byteLength > maxArchiveSize) {
415
- setError(`压缩包体积 ${formatArchiveBytes(buffer.byteLength)} 超过安全上限 ${formatArchiveBytes(maxArchiveSize)},请下载后在本地解压。`);
416
- return;
417
- }
418
- setLoading(true, '正在初始化压缩包解析 Worker...', '大文件会在 Worker 中解析,避免阻塞主线程。');
419
- setError('');
420
- archiveNotice = '';
421
- try {
422
- const [{ Archive }, candidates] = await Promise.all([
423
- import('libarchive.js'),
424
- resolveWorkerCandidates(documentRef, archiveOptions),
425
- ]);
426
- const errors = [];
427
- for (const candidate of candidates) {
428
- try {
429
- await closeArchive();
430
- await tryOpenArchiveWithWorker(Archive, candidate);
431
- return;
432
- }
433
- catch (reason) {
434
- errors.push(`${candidate.label}: ${normalizeWorkerError(reason)}`);
435
- }
436
- }
437
- await closeArchive();
438
- if (await tryOpenArchiveWithFallback()) {
439
- return;
440
- }
441
- throw new Error(errors.join(';') || '压缩包 Worker 初始化失败');
442
- }
443
- catch (nextError) {
444
- console.error(nextError);
445
- setError(nextError instanceof Error ? nextError.message : String(nextError));
446
- }
447
- finally {
448
- setLoading(false);
449
- }
450
- };
451
- const renderEntryBuffer = async (entry, entryBuffer) => {
452
- await clearNestedPreview();
453
- const child = createElement(documentRef, 'div', 'archive-nested-content');
454
- nestedTarget.append(child);
455
- const nestedContext = {
456
- ...context,
457
- filename: entry.name,
458
- options: buildNestedOptions(context, archiveOptions),
459
- };
460
- nestedRendered = (context === null || context === void 0 ? void 0 : context.renderNestedBuffer)
461
- ? await context.renderNestedBuffer(entryBuffer, entry.extension, child, nestedContext)
462
- : await renderNestedWithCoreFallback(entryBuffer, entry.extension, child, nestedContext);
463
- };
464
- const extractEntryBuffer = async (entry) => {
465
- const cacheKey = createArchiveCacheKey(filename, buffer.byteLength, entry);
466
- if (cacheEnabled) {
467
- const cached = await readArchiveCache(cacheKey);
468
- if (cached) {
469
- return cached.buffer;
470
- }
471
- }
472
- const file = await entry.compressedFile.extract();
473
- const entryBuffer = await file.arrayBuffer();
474
- if (cacheEnabled) {
475
- await writeArchiveCache({
476
- key: cacheKey,
477
- filename: entry.name,
478
- size: entryBuffer.byteLength,
479
- updatedAt: Date.now(),
480
- buffer: entryBuffer,
481
- });
482
- }
483
- return entryBuffer;
484
- };
485
- async function previewEntry(entry) {
486
- selectedEntry = entry;
487
- renderEntryList();
488
- syncState();
489
- if (entry.size > maxEntryPreviewSize) {
490
- setError(`压缩包内文件 ${entry.name} 体积 ${formatArchiveBytes(entry.size)} 超过预览上限 ${formatArchiveBytes(maxEntryPreviewSize)}。`);
491
- return;
492
- }
493
- setLoading(true, `正在按需解压 ${entry.name}...`);
494
- setError('');
495
- try {
496
- const entryBuffer = await extractEntryBuffer(entry);
497
- setLoading(true, `正在渲染 ${entry.name}...`);
498
- await renderEntryBuffer(entry, entryBuffer);
499
- }
500
- catch (nextError) {
501
- console.error(nextError);
502
- setError(nextError instanceof Error ? nextError.message : String(nextError));
503
- }
504
- finally {
505
- setLoading(false);
506
- }
507
- }
508
- const downloadEntry = async (entry) => {
509
- setLoading(true, `正在导出 ${entry.name}...`);
510
- try {
511
- const entryBuffer = await extractEntryBuffer(entry);
512
- const url = URL.createObjectURL(new Blob([entryBuffer]));
513
- objectUrls.push(url);
514
- const link = documentRef.createElement('a');
515
- link.href = url;
516
- link.download = entry.name;
517
- documentRef.body.append(link);
518
- link.click();
519
- link.remove();
520
- }
521
- finally {
522
- setLoading(false);
523
- }
524
- };
525
- listen(search, 'input', () => {
526
- filterText = search.value;
527
- renderEntryList();
528
- });
529
- listen(downloadButton, 'click', () => {
530
- if (selectedEntry) {
531
- void downloadEntry(selectedEntry);
532
- }
533
- });
534
- syncState();
535
- renderEmptyState();
536
- void openArchive();
537
- return {
538
- $el: root,
539
- async unmount() {
540
- cleanups.splice(0).forEach(cleanup => cleanup());
541
- await clearNestedPreview();
542
- await closeArchive();
543
- objectUrls.forEach(url => URL.revokeObjectURL(url));
544
- target.replaceChildren();
545
- },
546
- };
547
- }
@@ -1,10 +0,0 @@
1
- interface CachedArchiveEntry {
2
- key: string;
3
- filename: string;
4
- size: number;
5
- updatedAt: number;
6
- buffer: ArrayBuffer;
7
- }
8
- export declare const readArchiveCache: (key: string) => Promise<CachedArchiveEntry | null>;
9
- export declare const writeArchiveCache: (entry: CachedArchiveEntry) => Promise<void>;
10
- export {};