@file-viewer/core 2.0.10 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/dist/config/options.d.ts +1 -1
- package/dist/config/options.js +1 -1
- package/dist/contracts/types.d.ts +71 -1
- package/dist/headless.d.ts +2 -2
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +9 -6
- package/dist/index.js +105 -48
- package/dist/platform/assets.d.ts +3 -1
- package/dist/platform/assets.js +18 -2
- package/dist/registry/capabilities.d.ts +2 -2
- package/dist/registry/capabilities.js +2 -1
- package/dist/registry/formats.d.ts +20 -7
- package/dist/registry/formats.js +14 -5
- package/dist/registry/registry.d.ts +8 -1
- package/dist/registry/registry.js +29 -0
- package/dist/renderers/image.js +1 -10
- package/dist/renderers/index.d.ts +320 -2
- package/dist/renderers/index.js +27 -157
- package/dist/viewer/createViewer.js +86 -3
- package/package.json +17 -44
- package/dist/renderers/archive.d.ts +0 -2
- package/dist/renderers/archive.js +0 -547
- package/dist/renderers/archiveCache.d.ts +0 -10
- package/dist/renderers/archiveCache.js +0 -96
- package/dist/renderers/archiveFallback.d.ts +0 -7
- package/dist/renderers/archiveFallback.js +0 -166
- package/dist/renderers/archiveShared.d.ts +0 -23
- package/dist/renderers/archiveShared.js +0 -71
- package/dist/renderers/audio.d.ts +0 -8
- package/dist/renderers/audio.js +0 -219
- package/dist/renderers/cad.d.ts +0 -2
- package/dist/renderers/cad.js +0 -446
- package/dist/renderers/code.d.ts +0 -11
- package/dist/renderers/code.js +0 -233
- package/dist/renderers/data.d.ts +0 -7
- package/dist/renderers/data.js +0 -370
- package/dist/renderers/drawing.d.ts +0 -10
- package/dist/renderers/drawing.js +0 -882
- package/dist/renderers/eda.d.ts +0 -2
- package/dist/renderers/eda.js +0 -434
- package/dist/renderers/edaParser.d.ts +0 -77
- package/dist/renderers/edaParser.js +0 -569
- package/dist/renderers/email.d.ts +0 -2
- package/dist/renderers/email.js +0 -463
- package/dist/renderers/epub.d.ts +0 -2
- package/dist/renderers/epub.js +0 -331
- package/dist/renderers/geo.d.ts +0 -2
- package/dist/renderers/geo.js +0 -284
- package/dist/renderers/markdown.d.ts +0 -2
- package/dist/renderers/markdown.js +0 -83
- package/dist/renderers/model.d.ts +0 -2
- package/dist/renderers/model.js +0 -567
- package/dist/renderers/ofd.d.ts +0 -2
- package/dist/renderers/ofd.js +0 -256
- package/dist/renderers/openDocument.d.ts +0 -2
- package/dist/renderers/openDocument.js +0 -122
- package/dist/renderers/pdf.d.ts +0 -3
- package/dist/renderers/pdf.js +0 -1001
- package/dist/renderers/pdfStyles.d.ts +0 -1
- package/dist/renderers/pdfStyles.js +0 -1
- package/dist/renderers/pptx.d.ts +0 -2
- package/dist/renderers/pptx.js +0 -217
- package/dist/renderers/spreadsheet/state.d.ts +0 -80
- package/dist/renderers/spreadsheet/state.js +0 -96
- package/dist/renderers/spreadsheet/view.d.ts +0 -25
- package/dist/renderers/spreadsheet/view.js +0 -833
- package/dist/renderers/spreadsheet/worker/index.d.ts +0 -2
- package/dist/renderers/spreadsheet/worker/index.js +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.d.ts +0 -73
- package/dist/renderers/spreadsheet/worker/sheetjs/SheetJsModel.js +0 -623
- package/dist/renderers/spreadsheet/worker/sheetjs/color.d.ts +0 -2
- package/dist/renderers/spreadsheet/worker/sheetjs/color.js +0 -73
- package/dist/renderers/spreadsheet/worker/sheetjs/index.d.ts +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/index.js +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.d.ts +0 -18
- package/dist/renderers/spreadsheet/worker/sheetjs/parser.js +0 -106
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.d.ts +0 -1
- package/dist/renderers/spreadsheet/worker/sheetjs/sheet.worker.js +0 -11
- package/dist/renderers/spreadsheet/worker/type.d.ts +0 -57
- package/dist/renderers/spreadsheet/worker/type.js +0 -1
- package/dist/renderers/spreadsheet.d.ts +0 -3
- package/dist/renderers/spreadsheet.js +0 -929
- package/dist/renderers/typst.d.ts +0 -8
- package/dist/renderers/typst.js +0 -547
- package/dist/renderers/umd/parser.d.ts +0 -30
- package/dist/renderers/umd/parser.js +0 -408
- package/dist/renderers/umd.d.ts +0 -2
- package/dist/renderers/umd.js +0 -297
- package/dist/renderers/video.d.ts +0 -8
- package/dist/renderers/video.js +0 -108
- package/dist/renderers/wordDoc.d.ts +0 -5
- package/dist/renderers/wordDoc.js +0 -284
- package/dist/renderers/wordDocx.d.ts +0 -5
- package/dist/renderers/wordDocx.js +0 -985
- package/dist/renderers/wordDocx.worker.d.ts +0 -1
- package/dist/renderers/wordDocx.worker.js +0 -96
- package/vendor/ofd/dltech/jbig2/arithmetic_decoder.js +0 -183
- package/vendor/ofd/dltech/jbig2/ccitt.js +0 -1070
- package/vendor/ofd/dltech/jbig2/compatibility.js +0 -12
- package/vendor/ofd/dltech/jbig2/core_utils.js +0 -180
- package/vendor/ofd/dltech/jbig2/is_node.js +0 -27
- package/vendor/ofd/dltech/jbig2/jbig2.js +0 -2589
- package/vendor/ofd/dltech/jbig2/jbig2_stream.js +0 -81
- package/vendor/ofd/dltech/jbig2/primitives.js +0 -387
- package/vendor/ofd/dltech/jbig2/stream.js +0 -1348
- package/vendor/ofd/dltech/jbig2/util.js +0 -972
- package/vendor/ofd/dltech/ofd/ofd.d.ts +0 -11
- package/vendor/ofd/dltech/ofd/ofd.js +0 -100
- package/vendor/ofd/dltech/ofd/ofd_parser.js +0 -395
- package/vendor/ofd/dltech/ofd/ofd_render.js +0 -473
- package/vendor/ofd/dltech/ofd/ofd_util.js +0 -350
- package/vendor/ofd/dltech/ofd/pipeline.js +0 -26
|
@@ -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 {};
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
const DB_NAME = 'flyfish-file-viewer-cache';
|
|
2
|
-
const STORE_NAME = 'archiveEntries';
|
|
3
|
-
const DB_VERSION = 1;
|
|
4
|
-
const MAX_CACHE_ENTRY_BYTES = 24 * 1024 * 1024;
|
|
5
|
-
const MAX_CACHE_TOTAL_BYTES = 96 * 1024 * 1024;
|
|
6
|
-
let dbPromise = null;
|
|
7
|
-
const canUseIndexedDB = () => typeof indexedDB !== 'undefined';
|
|
8
|
-
const openCacheDb = () => {
|
|
9
|
-
if (!canUseIndexedDB()) {
|
|
10
|
-
return Promise.reject(new Error('IndexedDB 不可用'));
|
|
11
|
-
}
|
|
12
|
-
if (dbPromise) {
|
|
13
|
-
return dbPromise;
|
|
14
|
-
}
|
|
15
|
-
dbPromise = new Promise((resolve, reject) => {
|
|
16
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
17
|
-
request.onupgradeneeded = () => {
|
|
18
|
-
const db = request.result;
|
|
19
|
-
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
20
|
-
const store = db.createObjectStore(STORE_NAME, { keyPath: 'key' });
|
|
21
|
-
store.createIndex('updatedAt', 'updatedAt');
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
request.onsuccess = () => resolve(request.result);
|
|
25
|
-
request.onerror = () => reject(request.error);
|
|
26
|
-
});
|
|
27
|
-
return dbPromise;
|
|
28
|
-
};
|
|
29
|
-
const runStore = async (mode, runner) => {
|
|
30
|
-
const db = await openCacheDb();
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
const transaction = db.transaction(STORE_NAME, mode);
|
|
33
|
-
const request = runner(transaction.objectStore(STORE_NAME));
|
|
34
|
-
request.onsuccess = () => resolve(request.result);
|
|
35
|
-
request.onerror = () => reject(request.error);
|
|
36
|
-
transaction.onerror = () => reject(transaction.error);
|
|
37
|
-
});
|
|
38
|
-
};
|
|
39
|
-
export const readArchiveCache = async (key) => {
|
|
40
|
-
try {
|
|
41
|
-
const cached = await runStore('readonly', store => store.get(key));
|
|
42
|
-
return cached || null;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
const pruneArchiveCache = async () => {
|
|
49
|
-
try {
|
|
50
|
-
const db = await openCacheDb();
|
|
51
|
-
await new Promise((resolve, reject) => {
|
|
52
|
-
const transaction = db.transaction(STORE_NAME, 'readwrite');
|
|
53
|
-
const store = transaction.objectStore(STORE_NAME);
|
|
54
|
-
const index = store.index('updatedAt');
|
|
55
|
-
const entries = [];
|
|
56
|
-
let total = 0;
|
|
57
|
-
index.openCursor().onsuccess = event => {
|
|
58
|
-
const cursor = event.target.result;
|
|
59
|
-
if (!cursor) {
|
|
60
|
-
while (total > MAX_CACHE_TOTAL_BYTES && entries.length) {
|
|
61
|
-
const entry = entries.shift();
|
|
62
|
-
if (entry) {
|
|
63
|
-
total -= entry.size;
|
|
64
|
-
store.delete(entry.key);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const value = cursor.value;
|
|
70
|
-
total += value.size || 0;
|
|
71
|
-
entries.push({ key: value.key, size: value.size || 0 });
|
|
72
|
-
cursor.continue();
|
|
73
|
-
};
|
|
74
|
-
transaction.oncomplete = () => resolve();
|
|
75
|
-
transaction.onerror = () => reject(transaction.error);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
// Cache pruning is a best-effort optimization and must not block preview.
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
export const writeArchiveCache = async (entry) => {
|
|
83
|
-
if (entry.size > MAX_CACHE_ENTRY_BYTES) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
await runStore('readwrite', store => store.put({
|
|
88
|
-
...entry,
|
|
89
|
-
updatedAt: Date.now(),
|
|
90
|
-
}));
|
|
91
|
-
await pruneArchiveCache();
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// Quota, private-mode, or browser policy errors simply disable cache writes.
|
|
95
|
-
}
|
|
96
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type ArchiveEntryView } from './archiveShared';
|
|
2
|
-
/**
|
|
3
|
-
* Worker fallback for constrained browsers, temporary local servers, and
|
|
4
|
-
* mobile WebViews. The main libarchive path still covers broader formats;
|
|
5
|
-
* this covers common ZIP/TAR/GZIP archives without an extra static Worker.
|
|
6
|
-
*/
|
|
7
|
-
export declare const loadArchiveEntriesWithoutWorker: (data: ArrayBuffer, filename: string) => Promise<ArchiveEntryView[] | null>;
|