@file-viewer/renderer-archive 2.1.1 → 2.1.3

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/dist/archive.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { resolveFileViewerArchiveWasmUrl, resolveFileViewerArchiveWorkerUrl, } from '@file-viewer/core/assets';
2
- import { disposeFileViewerRendered, } from '@file-viewer/core';
3
- import { createArchiveCacheKey, flattenArchiveObject, formatArchiveBytes, getArchiveEntryExtension, } from './archiveShared.js';
2
+ import { createFileViewerTranslator, disposeFileViewerRendered, } from '@file-viewer/core';
3
+ import { buildArchiveNestedRenderContext, createArchiveCacheKey, flattenArchiveObject, formatArchiveBytes, getArchiveEntryExtension, } from './archiveShared.js';
4
4
  import { readArchiveCache, writeArchiveCache } from './archiveCache.js';
5
5
  import { loadArchiveEntriesWithoutWorker } from './archiveFallback.js';
6
6
  const DEFAULT_MAX_ARCHIVE_SIZE = 320 * 1024 * 1024;
@@ -98,7 +98,7 @@ const getWorkerConstructor = (documentRef) => {
98
98
  const WorkerCtor = ((_a = documentRef.defaultView) === null || _a === void 0 ? void 0 : _a.Worker) ||
99
99
  (typeof Worker !== 'undefined' ? Worker : undefined);
100
100
  if (!WorkerCtor) {
101
- throw new Error('当前浏览器不支持 Web Worker');
101
+ throw new Error('Web Worker is not supported by this browser.');
102
102
  }
103
103
  return WorkerCtor;
104
104
  };
@@ -157,7 +157,7 @@ const prepareWorkerUrl = async (candidate, objectUrls) => {
157
157
  }
158
158
  const response = await fetch(candidate.workerUrl, { cache: 'no-cache' });
159
159
  if (!response.ok) {
160
- throw new Error(`无法读取 libarchive Worker: ${response.status}`);
160
+ throw new Error(`Unable to read libarchive Worker: ${response.status}`);
161
161
  }
162
162
  const source = await response.text();
163
163
  const workerUrl = URL.createObjectURL(new Blob([
@@ -174,7 +174,7 @@ const resolveWorkerCandidates = async (documentRef, options) => {
174
174
  : undefined;
175
175
  if (options === null || options === void 0 ? void 0 : options.workerUrl) {
176
176
  candidates.push({
177
- label: '自定义 libarchive Worker',
177
+ label: 'custom libarchive Worker',
178
178
  workerUrl: resolveFileViewerArchiveWorkerUrl(options, baseUrl),
179
179
  wasmUrl,
180
180
  });
@@ -183,22 +183,19 @@ const resolveWorkerCandidates = async (documentRef, options) => {
183
183
  const publicWorkerUrl = resolveFileViewerArchiveWorkerUrl(undefined, baseUrl);
184
184
  if (await probeWorkerUrl(publicWorkerUrl)) {
185
185
  candidates.push({
186
- label: '静态 libarchive Worker',
186
+ label: 'static libarchive Worker',
187
187
  workerUrl: publicWorkerUrl,
188
188
  wasmUrl,
189
189
  });
190
190
  }
191
191
  return candidates;
192
192
  };
193
- const buildNestedOptions = (context, archiveOptions) => ({
194
- ...((context === null || context === void 0 ? void 0 : context.options) || {}),
195
- archive: archiveOptions,
196
- });
197
193
  const renderNestedWithCoreFallback = async (buffer, type, target, context) => {
194
+ const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
198
195
  const { fileViewerCoreRendererDispatcher } = await import('@file-viewer/core');
199
196
  const handler = fileViewerCoreRendererDispatcher.resolve(type);
200
197
  if (!handler) {
201
- target.textContent = `不支持.${type}格式的在线预览,请下载后预览或转换为支持的格式`;
198
+ target.textContent = t('archive.error.nestedUnsupported', { type });
202
199
  return undefined;
203
200
  }
204
201
  return handler(buffer, target, type, {
@@ -223,8 +220,9 @@ export default async function renderArchive(buffer, target, _type, context) {
223
220
  let selectedEntry = null;
224
221
  let nestedRendered;
225
222
  let loading = false;
226
- let loadingText = '正在读取压缩包目录...';
227
- let loadingHint = '大文件会在 Worker 中解析,避免阻塞主线程。';
223
+ const t = createFileViewerTranslator(context === null || context === void 0 ? void 0 : context.options);
224
+ let loadingText = t('archive.loading.readingDirectory');
225
+ let loadingHint = t('archive.loading.readingDirectoryHint');
228
226
  let errorText = '';
229
227
  let archiveNotice = '';
230
228
  let encrypted = null;
@@ -241,15 +239,15 @@ export default async function renderArchive(buffer, target, _type, context) {
241
239
  const info = createElement(documentRef, 'div', 'archive-info');
242
240
  const search = createElement(documentRef, 'input', 'archive-search');
243
241
  search.type = 'search';
244
- search.placeholder = '筛选压缩包内文件';
242
+ search.placeholder = t('archive.search.placeholder');
245
243
  const list = createElement(documentRef, 'div', 'archive-list');
246
244
  list.setAttribute('role', 'list');
247
245
  sidebar.append(head, warning, info, search, list);
248
246
  const preview = createElement(documentRef, 'main', 'archive-preview');
249
247
  const toolbar = createElement(documentRef, 'div', 'archive-preview-toolbar');
250
248
  const toolbarTitle = createElement(documentRef, 'div');
251
- toolbarTitle.append(createElement(documentRef, 'span', undefined, '压缩包内预览'), createElement(documentRef, 'strong', undefined, '请选择一个文件'));
252
- const downloadButton = createElement(documentRef, 'button', undefined, '下载文件');
249
+ toolbarTitle.append(createElement(documentRef, 'span', undefined, t('archive.preview.title')), createElement(documentRef, 'strong', undefined, t('archive.preview.chooseFile')));
250
+ const downloadButton = createElement(documentRef, 'button', undefined, t('archive.preview.downloadFile'));
253
251
  downloadButton.type = 'button';
254
252
  toolbar.append(toolbarTitle, downloadButton);
255
253
  const nestedTarget = createElement(documentRef, 'div', 'archive-nested-target');
@@ -266,7 +264,7 @@ export default async function renderArchive(buffer, target, _type, context) {
266
264
  state.append(stateContent);
267
265
  root.append(state);
268
266
  const error = createElement(documentRef, 'div', 'archive-error');
269
- const errorTitle = createElement(documentRef, 'strong', undefined, '压缩包预览提示');
267
+ const errorTitle = createElement(documentRef, 'strong', undefined, t('archive.error.title'));
270
268
  const errorMessage = createElement(documentRef, 'p');
271
269
  error.append(errorTitle, errorMessage);
272
270
  root.append(error);
@@ -303,8 +301,12 @@ export default async function renderArchive(buffer, target, _type, context) {
303
301
  };
304
302
  const syncState = () => {
305
303
  const archiveStats = getArchiveStats();
306
- stats.textContent = `${archiveStats.count} 个文件 · ${formatArchiveBytes(archiveStats.totalSize)} · ${archiveStats.previewableCount} 个可直接预览`;
307
- warning.textContent = '检测到加密内容,当前在线预览不接收密码,建议下载后本地解压。';
304
+ stats.textContent = t('archive.stats.summary', {
305
+ count: archiveStats.count,
306
+ size: formatArchiveBytes(archiveStats.totalSize),
307
+ previewable: archiveStats.previewableCount,
308
+ });
309
+ warning.textContent = t('archive.warning.encrypted');
308
310
  warning.classList.toggle('archive-hidden', !encrypted);
309
311
  info.textContent = archiveNotice;
310
312
  info.classList.toggle('archive-hidden', !archiveNotice);
@@ -316,7 +318,7 @@ export default async function renderArchive(buffer, target, _type, context) {
316
318
  downloadButton.hidden = !selectedEntry;
317
319
  const activeTitle = toolbarTitle.querySelector('strong');
318
320
  if (activeTitle) {
319
- activeTitle.textContent = (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.name) || '请选择一个文件';
321
+ activeTitle.textContent = (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.name) || t('archive.preview.chooseFile');
320
322
  }
321
323
  };
322
324
  const renderEmptyState = () => {
@@ -324,7 +326,7 @@ export default async function renderArchive(buffer, target, _type, context) {
324
326
  return;
325
327
  }
326
328
  const empty = createElement(documentRef, 'div', 'archive-empty');
327
- empty.append(createElement(documentRef, 'strong', undefined, '选择左侧文件即可预览'), createElement(documentRef, 'p', undefined, '压缩包只读取目录;文件内容会在点击后按需解压,并在体积允许时缓存到 IndexedDB。'));
329
+ empty.append(createElement(documentRef, 'strong', undefined, t('archive.empty.title')), createElement(documentRef, 'p', undefined, t('archive.empty.message')));
328
330
  nestedTarget.replaceChildren(empty);
329
331
  };
330
332
  const renderEntryList = () => {
@@ -375,13 +377,13 @@ export default async function renderArchive(buffer, target, _type, context) {
375
377
  return worker;
376
378
  },
377
379
  });
378
- setLoading(true, `正在初始化${candidate.label}...`, '如果当前服务器没有正确发布 Worker/WASM,会自动切换兼容模式。');
380
+ setLoading(true, t('archive.loading.initializingCandidate', { label: candidate.label }), t('archive.loading.initializingCandidateHint'));
379
381
  const archiveFile = createArchiveFile(documentRef, buffer, filename);
380
- const archive = await withTimeout(Archive.open(archiveFile), workerTimeoutMs, `${candidate.label} 初始化超时`, targetWindow);
382
+ const archive = await withTimeout(Archive.open(archiveFile), workerTimeoutMs, t('archive.error.candidateInitTimeout', { label: candidate.label }), targetWindow);
381
383
  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);
384
+ encrypted = await withTimeout(archive.hasEncryptedData(), workerTimeoutMs, t('archive.error.encryptedCheckTimeout', { label: candidate.label }), targetWindow).catch(() => null);
385
+ setLoading(true, t('archive.loading.readingDirectory'), t('archive.loading.directoryReadyHint'));
386
+ const fileTree = await withTimeout(archive.getFilesObject(), workerTimeoutMs, t('archive.error.candidateReadTimeout', { label: candidate.label }), targetWindow);
385
387
  entries = flattenArchiveObject(fileTree)
386
388
  .sort((left, right) => left.path.localeCompare(right.path));
387
389
  syncState();
@@ -397,14 +399,14 @@ export default async function renderArchive(buffer, target, _type, context) {
397
399
  }
398
400
  };
399
401
  const tryOpenArchiveWithFallback = async () => {
400
- setLoading(true, 'Worker 不可用,正在切换 ZIP/TAR 兼容模式...', '兼容模式无需额外静态 Worker,适合手机 WebView 或本地临时服务器。');
402
+ setLoading(true, t('archive.loading.workerFallback'), t('archive.loading.workerFallbackHint'));
401
403
  const fallbackEntries = await loadArchiveEntriesWithoutWorker(buffer, filename);
402
404
  if (!fallbackEntries) {
403
405
  return false;
404
406
  }
405
407
  entries = fallbackEntries.sort((left, right) => left.path.localeCompare(right.path));
406
408
  encrypted = null;
407
- archiveNotice = '当前环境的 libarchive Worker 未能启动,已自动切换到 ZIP/TAR/GZIP 兼容模式。RAR、7z 等格式仍建议发布 vendor/libarchive/worker-bundle.js 与 libarchive.wasm。';
409
+ archiveNotice = t('archive.notice.workerFallback');
408
410
  syncState();
409
411
  renderEntryList();
410
412
  renderEmptyState();
@@ -412,10 +414,13 @@ export default async function renderArchive(buffer, target, _type, context) {
412
414
  };
413
415
  const openArchive = async () => {
414
416
  if (buffer.byteLength > maxArchiveSize) {
415
- setError(`压缩包体积 ${formatArchiveBytes(buffer.byteLength)} 超过安全上限 ${formatArchiveBytes(maxArchiveSize)},请下载后在本地解压。`);
417
+ setError(t('archive.error.tooLarge', {
418
+ size: formatArchiveBytes(buffer.byteLength),
419
+ limit: formatArchiveBytes(maxArchiveSize),
420
+ }));
416
421
  return;
417
422
  }
418
- setLoading(true, '正在初始化压缩包解析 Worker...', '大文件会在 Worker 中解析,避免阻塞主线程。');
423
+ setLoading(true, t('archive.loading.initializingWorker'), t('archive.loading.initializingWorkerHint'));
419
424
  setError('');
420
425
  archiveNotice = '';
421
426
  try {
@@ -438,7 +443,7 @@ export default async function renderArchive(buffer, target, _type, context) {
438
443
  if (await tryOpenArchiveWithFallback()) {
439
444
  return;
440
445
  }
441
- throw new Error(errors.join('') || '压缩包 Worker 初始化失败');
446
+ throw new Error(errors.join('; ') || t('archive.error.workerInitFailed'));
442
447
  }
443
448
  catch (nextError) {
444
449
  console.error(nextError);
@@ -452,11 +457,7 @@ export default async function renderArchive(buffer, target, _type, context) {
452
457
  await clearNestedPreview();
453
458
  const child = createElement(documentRef, 'div', 'archive-nested-content');
454
459
  nestedTarget.append(child);
455
- const nestedContext = {
456
- ...context,
457
- filename: entry.name,
458
- options: buildNestedOptions(context, archiveOptions),
459
- };
460
+ const nestedContext = buildArchiveNestedRenderContext(context, entry, archiveOptions);
460
461
  nestedRendered = (context === null || context === void 0 ? void 0 : context.renderNestedBuffer)
461
462
  ? await context.renderNestedBuffer(entryBuffer, entry.extension, child, nestedContext)
462
463
  : await renderNestedWithCoreFallback(entryBuffer, entry.extension, child, nestedContext);
@@ -487,14 +488,18 @@ export default async function renderArchive(buffer, target, _type, context) {
487
488
  renderEntryList();
488
489
  syncState();
489
490
  if (entry.size > maxEntryPreviewSize) {
490
- setError(`压缩包内文件 ${entry.name} 体积 ${formatArchiveBytes(entry.size)} 超过预览上限 ${formatArchiveBytes(maxEntryPreviewSize)}。`);
491
+ setError(t('archive.error.entryTooLarge', {
492
+ name: entry.name,
493
+ size: formatArchiveBytes(entry.size),
494
+ limit: formatArchiveBytes(maxEntryPreviewSize),
495
+ }));
491
496
  return;
492
497
  }
493
- setLoading(true, `正在按需解压 ${entry.name}...`);
498
+ setLoading(true, t('archive.loading.extracting', { name: entry.name }));
494
499
  setError('');
495
500
  try {
496
501
  const entryBuffer = await extractEntryBuffer(entry);
497
- setLoading(true, `正在渲染 ${entry.name}...`);
502
+ setLoading(true, t('archive.loading.rendering', { name: entry.name }));
498
503
  await renderEntryBuffer(entry, entryBuffer);
499
504
  }
500
505
  catch (nextError) {
@@ -506,7 +511,7 @@ export default async function renderArchive(buffer, target, _type, context) {
506
511
  }
507
512
  }
508
513
  const downloadEntry = async (entry) => {
509
- setLoading(true, `正在导出 ${entry.name}...`);
514
+ setLoading(true, t('archive.loading.exporting', { name: entry.name }));
510
515
  try {
511
516
  const entryBuffer = await extractEntryBuffer(entry);
512
517
  const url = URL.createObjectURL(new Blob([entryBuffer]));
@@ -1,3 +1,4 @@
1
+ import type { FileRenderContext, FileViewerArchiveOptions } from '@file-viewer/core';
1
2
  export declare const ARCHIVE_PREVIEWABLE_EXTENSIONS: readonly string[];
2
3
  export interface ArchiveEntryView {
3
4
  id: string;
@@ -22,3 +23,4 @@ export declare const isPreviewableArchiveEntry: (name: string) => boolean;
22
23
  export declare const formatArchiveBytes: (value: number) => string;
23
24
  export declare const flattenArchiveObject: (input: Record<string, unknown>, prefix?: string) => ArchiveEntryView[];
24
25
  export declare const createArchiveCacheKey: (archiveName: string, archiveSize: number, entry: ArchiveEntryView) => string;
26
+ export declare const buildArchiveNestedRenderContext: (context: FileRenderContext | undefined, entry: Pick<ArchiveEntryView, "name">, archiveOptions: FileViewerArchiveOptions | undefined) => FileRenderContext;
@@ -85,3 +85,17 @@ export const createArchiveCacheKey = (archiveName, archiveSize, entry) => {
85
85
  entry.lastModified || 0,
86
86
  ].join(':');
87
87
  };
88
+ const buildNestedOptions = (context, archiveOptions) => ({
89
+ ...((context === null || context === void 0 ? void 0 : context.options) || {}),
90
+ archive: archiveOptions,
91
+ });
92
+ export const buildArchiveNestedRenderContext = (context, entry, archiveOptions) => ({
93
+ ...context,
94
+ filename: entry.name,
95
+ // Archive children are rendered from the extracted bytes. Never inherit
96
+ // the parent archive URL, otherwise streaming renderers such as PDF.js
97
+ // would try to parse the .zip/.rar source as the nested file.
98
+ url: undefined,
99
+ streamUrl: undefined,
100
+ options: buildNestedOptions(context, archiveOptions),
101
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-archive",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone archive renderer plugin for Flyfish File Viewer with libarchive worker, ZIP/TAR fallback, IndexedDB cache, and nested previews.",
@@ -56,7 +56,7 @@
56
56
  "LICENSE"
57
57
  ],
58
58
  "dependencies": {
59
- "@file-viewer/core": "^2.1.1",
59
+ "@file-viewer/core": "^2.1.3",
60
60
  "jszip": "^3.10.1",
61
61
  "libarchive.js": "^2.0.2"
62
62
  },