@file-viewer/renderer-archive 2.1.26 → 2.1.27

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 CHANGED
@@ -30,6 +30,8 @@ const options = {
30
30
  ## Capabilities
31
31
 
32
32
  - Previews ZIP, TAR, GZIP, RAR, 7z, and common archive directories.
33
+ - `.cbz` and `.cbr` automatically enter comic-reading mode: image-only pages, natural path ordering, automatic cover opening, page controls, keyboard shortcuts, and touch swipes. Pages retain the shared image renderer's fit, zoom, and lightbox behavior.
34
+ - Comic mode fully reuses the existing Worker, RAR4/RAR5 support, ZIP fallback, passwords, lazy extraction, IndexedDB cache, size limits, and asset configuration instead of maintaining a second extraction stack.
33
35
  - Decodes legacy ZIP entry names that omit the UTF-8 flag but are actually encoded as GBK/GB18030.
34
36
  - Supports encrypted archives: encrypted content is handled through the unified `libarchive.js` path, the built-in dialog asks for a password, and a correct password unlocks directory reading or nested entry previews.
35
37
  - Uses `libarchive.js` Worker + WASM first to keep large archive parsing off the main thread.
package/README.md CHANGED
@@ -30,6 +30,8 @@ const options = {
30
30
  ## 能力边界
31
31
 
32
32
  - 支持 ZIP、TAR、GZIP、RAR、7z 等常见压缩包目录预览。
33
+ - `.cbz` / `.cbr` 自动进入漫画阅读模式:只展示图片页,按路径自然排序并自动打开封面,支持翻页按钮、页码、方向键、PageUp/PageDown、Home/End、空格键和触摸滑动。页面继续复用统一图片 renderer 的适屏、缩放和灯箱能力。
34
+ - 漫画模式完全复用现有 Worker、RAR4/RAR5、ZIP fallback、密码、按需解压、IndexedDB 缓存、体积限制和资源配置,不维护第二套解包逻辑。
33
35
  - 自动兼容未声明 UTF-8、实际使用 GBK/GB18030 编码的旧 ZIP 中文文件名。
34
36
  - 支持加密压缩包:检测到加密内容后使用 `libarchive.js` 统一解密,默认弹框要求用户输入密码,密码正确后继续读取目录或预览内部文件。
35
37
  - 优先使用 `libarchive.js` Worker + WASM,避免大压缩包阻塞主线程。
package/dist/archive.js CHANGED
@@ -3,6 +3,7 @@ import { collectFileViewerRendererPlugins, createFileRenderHandlerLoader, create
3
3
  import { buildArchiveNestedRenderContext, createArchiveCacheKey, flattenArchiveObject, formatArchiveBytes, getArchiveEntryExtension, isArchiveEntryDownloadAllowed, } from './archiveShared.js';
4
4
  import { readArchiveCache, writeArchiveCache } from './archiveCache.js';
5
5
  import { hasLikelyGbkZipFilenames, isLikelyEncryptedArchive, loadArchiveEntriesWithoutWorker, } from './archiveFallback.js';
6
+ import { comicBookStyle, createComicBookController, } from './comicBook.js';
6
7
  const DEFAULT_MAX_ARCHIVE_SIZE = 320 * 1024 * 1024;
7
8
  const DEFAULT_MAX_ENTRY_PREVIEW_SIZE = 64 * 1024 * 1024;
8
9
  const DEFAULT_WORKER_TIMEOUT_MS = 30000;
@@ -14,6 +15,7 @@ class ArchivePasswordCancelledError extends Error {
14
15
  }
15
16
  }
16
17
  const archiveStyle = `
18
+ ${comicBookStyle}
17
19
  .archive-shell,.archive-viewer{position:relative;box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;display:grid;grid-template-columns:minmax(280px,34%) minmax(0,1fr);grid-template-rows:minmax(0,1fr);--archive-sidebar-gap:12px;--archive-sidebar-padding:16px;--archive-head-gap:10px;--archive-title-margin:4px;--archive-title-size:18px;--archive-stats-margin:8px;--archive-toggle-size:34px;--archive-toggle-radius:10px;--archive-notice-padding:10px 12px;--archive-notice-radius:12px;--archive-search-height:42px;--archive-search-padding:0 12px;--archive-search-radius:12px;--archive-list-gap:7px;--archive-list-padding:4px;--archive-entry-min-height:58px;--archive-entry-icon-column:42px;--archive-entry-gap:10px;--archive-entry-padding-y:8px;--archive-entry-padding-x:10px;--archive-entry-depth-step:10px;--archive-entry-radius:12px;--archive-entry-ext-height:34px;--archive-entry-ext-radius:10px;--archive-entry-size-width:74px;--archive-preview-toolbar-min-height:64px;--archive-preview-toolbar-gap:12px;--archive-preview-toolbar-padding:12px 16px;--archive-preview-button-height:34px;--archive-preview-button-padding:0 12px;--archive-preview-button-radius:10px;--archive-empty-padding:28px;--archive-state-gap:14px;--archive-state-padding:18px;--archive-state-radius:16px;background:#edf2f7;color:#172033;font-family:Aptos,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif}
18
20
  .archive-shell *,.archive-viewer *{box-sizing:border-box}
19
21
  .archive-shell[data-viewer-density='compact'],.archive-viewer[data-viewer-density='compact'],.file-viewer[data-viewer-density='compact'] .archive-shell,.file-viewer[data-viewer-density='compact'] .archive-viewer,.file-viewer-web-shell[data-viewer-density='compact'] .archive-shell,.file-viewer-web-shell[data-viewer-density='compact'] .archive-viewer{--archive-sidebar-gap:5px;--archive-sidebar-padding:5px;--archive-head-gap:5px;--archive-title-margin:2px;--archive-title-size:16px;--archive-stats-margin:3px;--archive-toggle-size:28px;--archive-toggle-radius:7px;--archive-notice-padding:4px 5px;--archive-notice-radius:7px;--archive-search-height:30px;--archive-search-padding:0 5px;--archive-search-radius:7px;--archive-list-gap:3px;--archive-list-padding:2px;--archive-entry-min-height:42px;--archive-entry-icon-column:30px;--archive-entry-gap:5px;--archive-entry-padding-y:3px;--archive-entry-padding-x:5px;--archive-entry-depth-step:7px;--archive-entry-radius:7px;--archive-entry-ext-height:26px;--archive-entry-ext-radius:6px;--archive-entry-size-width:58px;--archive-preview-toolbar-min-height:38px;--archive-preview-toolbar-gap:5px;--archive-preview-toolbar-padding:4px 5px;--archive-preview-button-height:28px;--archive-preview-button-padding:0 5px;--archive-preview-button-radius:7px;--archive-empty-padding:18px;--archive-state-gap:5px;--archive-state-padding:5px;--archive-state-radius:10px}
@@ -318,6 +320,7 @@ export default async function renderArchive(buffer, target, _type, context) {
318
320
  const targetWindow = documentRef.defaultView || null;
319
321
  const archiveOptions = (_a = context === null || context === void 0 ? void 0 : context.options) === null || _a === void 0 ? void 0 : _a.archive;
320
322
  const filename = (context === null || context === void 0 ? void 0 : context.filename) || 'archive.bin';
323
+ const sourceExtension = (_type || getArchiveEntryExtension(filename)).toLowerCase();
321
324
  const maxArchiveSize = (archiveOptions === null || archiveOptions === void 0 ? void 0 : archiveOptions.maxArchiveSize) || DEFAULT_MAX_ARCHIVE_SIZE;
322
325
  const maxEntryPreviewSize = (archiveOptions === null || archiveOptions === void 0 ? void 0 : archiveOptions.maxEntryPreviewSize) || DEFAULT_MAX_ENTRY_PREVIEW_SIZE;
323
326
  const cacheEnabled = (archiveOptions === null || archiveOptions === void 0 ? void 0 : archiveOptions.cache) !== false;
@@ -340,6 +343,7 @@ export default async function renderArchive(buffer, target, _type, context) {
340
343
  let passwordAttempt = 0;
341
344
  let passwordResolver = null;
342
345
  let sidebarCollapsed = false;
346
+ let previewSequence = 0;
343
347
  const style = createStyle(documentRef);
344
348
  const root = createElement(documentRef, 'section', 'archive-shell archive-viewer');
345
349
  root.dataset.viewerDensity = normalizeFileViewerUiDensity((_c = (_b = context === null || context === void 0 ? void 0 : context.options) === null || _b === void 0 ? void 0 : _b.ui) === null || _c === void 0 ? void 0 : _c.density);
@@ -370,8 +374,19 @@ export default async function renderArchive(buffer, target, _type, context) {
370
374
  toolbarTitle.append(createElement(documentRef, 'span', undefined, t('archive.preview.title')), createElement(documentRef, 'strong', undefined, t('archive.preview.chooseFile')));
371
375
  const downloadButton = createElement(documentRef, 'button', 'archive-download-button', t('archive.preview.downloadFile'));
372
376
  downloadButton.type = 'button';
373
- toolbar.append(sidebarShowButton, toolbarTitle, downloadButton);
374
377
  const nestedTarget = createElement(documentRef, 'div', 'archive-nested-target');
378
+ const comicBook = createComicBookController({
379
+ document: documentRef,
380
+ extension: sourceExtension,
381
+ root,
382
+ stage: nestedTarget,
383
+ previousLabel: t('epub.previousPage'),
384
+ nextLabel: t('epub.nextPage'),
385
+ getEntries: () => entries,
386
+ openEntry: entry => void previewEntry(entry),
387
+ });
388
+ badge.textContent = comicBook.badge;
389
+ toolbar.append(sidebarShowButton, toolbarTitle, ...comicBook.toolbarElements, downloadButton);
375
390
  preview.append(toolbar, nestedTarget);
376
391
  root.append(sidebar, preview);
377
392
  const state = createElement(documentRef, 'div', 'archive-state');
@@ -512,9 +527,10 @@ export default async function renderArchive(buffer, target, _type, context) {
512
527
  };
513
528
  const getFilteredEntries = () => {
514
529
  const keyword = filterText.trim().toLowerCase();
530
+ const visibleEntries = comicBook.getVisibleEntries(entries);
515
531
  const source = keyword
516
- ? entries.filter(entry => entry.path.toLowerCase().includes(keyword))
517
- : entries;
532
+ ? visibleEntries.filter(entry => entry.path.toLowerCase().includes(keyword))
533
+ : visibleEntries;
518
534
  return source.slice(0, MAX_LISTED_ENTRIES);
519
535
  };
520
536
  const clearNestedPreview = async () => {
@@ -567,6 +583,7 @@ export default async function renderArchive(buffer, target, _type, context) {
567
583
  activeTitle.textContent = (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.name) || t('archive.preview.chooseFile');
568
584
  activeTitle.setAttribute('title', (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.path) || (selectedEntry === null || selectedEntry === void 0 ? void 0 : selectedEntry.name) || '');
569
585
  }
586
+ comicBook.sync();
570
587
  syncSidebarToggleState();
571
588
  };
572
589
  const renderEmptyState = () => {
@@ -659,11 +676,11 @@ export default async function renderArchive(buffer, target, _type, context) {
659
676
  }
660
677
  setLoading(true, t('archive.loading.readingDirectory'), t('archive.loading.directoryReadyHint'));
661
678
  const fileTree = await readArchiveDirectoryWithPassword(archive, candidate);
662
- entries = flattenArchiveObject(fileTree)
663
- .sort((left, right) => left.path.localeCompare(right.path));
679
+ entries = comicBook.sortEntries(flattenArchiveObject(fileTree));
664
680
  syncState();
665
681
  renderEntryList();
666
682
  renderEmptyState();
683
+ comicBook.onEntriesReady();
667
684
  return true;
668
685
  }
669
686
  catch (reason) {
@@ -684,12 +701,13 @@ export default async function renderArchive(buffer, target, _type, context) {
684
701
  if (!fallbackEntries) {
685
702
  return false;
686
703
  }
687
- entries = fallbackEntries.sort((left, right) => left.path.localeCompare(right.path));
704
+ entries = comicBook.sortEntries(fallbackEntries);
688
705
  encrypted = null;
689
706
  archiveNotice = showWorkerFallbackNotice ? t('archive.notice.workerFallback') : '';
690
707
  syncState();
691
708
  renderEntryList();
692
709
  renderEmptyState();
710
+ comicBook.onEntriesReady();
693
711
  return true;
694
712
  };
695
713
  const openArchive = async () => {
@@ -746,12 +764,21 @@ export default async function renderArchive(buffer, target, _type, context) {
746
764
  setLoading(false);
747
765
  }
748
766
  };
749
- const renderEntryBuffer = async (entry, entryBuffer) => {
767
+ const renderEntryBuffer = async (entry, entryBuffer, requestId) => {
750
768
  await clearNestedPreview();
769
+ if (requestId !== previewSequence) {
770
+ return;
771
+ }
751
772
  const child = createElement(documentRef, 'div', 'archive-nested-content');
752
773
  nestedTarget.append(child);
753
774
  const nestedContext = buildArchiveNestedRenderContext(context, entry, archiveOptions);
754
- nestedRendered = await renderNestedEntry(entryBuffer, entry.extension, child, nestedContext);
775
+ const rendered = await renderNestedEntry(entryBuffer, entry.extension, child, nestedContext);
776
+ if (requestId !== previewSequence) {
777
+ await disposeFileViewerRendered(rendered);
778
+ child.remove();
779
+ return;
780
+ }
781
+ nestedRendered = rendered;
755
782
  };
756
783
  const extractEntryBuffer = async (entry) => {
757
784
  const cacheKey = createArchiveCacheKey(filename, buffer.byteLength, entry);
@@ -789,7 +816,9 @@ export default async function renderArchive(buffer, target, _type, context) {
789
816
  return entryBuffer;
790
817
  };
791
818
  async function previewEntry(entry) {
819
+ const requestId = ++previewSequence;
792
820
  selectedEntry = entry;
821
+ comicBook.onEntrySelected(entry);
793
822
  renderEntryList();
794
823
  syncState();
795
824
  if (entry.size > maxEntryPreviewSize) {
@@ -804,15 +833,22 @@ export default async function renderArchive(buffer, target, _type, context) {
804
833
  setError('');
805
834
  try {
806
835
  const entryBuffer = await extractEntryBuffer(entry);
836
+ if (requestId !== previewSequence) {
837
+ return;
838
+ }
807
839
  setLoading(true, t('archive.loading.rendering', { name: entry.name }));
808
- await renderEntryBuffer(entry, entryBuffer);
840
+ await renderEntryBuffer(entry, entryBuffer, requestId);
809
841
  }
810
842
  catch (nextError) {
811
- console.error(nextError);
812
- setError(nextError instanceof Error ? nextError.message : String(nextError));
843
+ if (requestId === previewSequence) {
844
+ console.error(nextError);
845
+ setError(nextError instanceof Error ? nextError.message : String(nextError));
846
+ }
813
847
  }
814
848
  finally {
815
- setLoading(false);
849
+ if (requestId === previewSequence) {
850
+ setLoading(false);
851
+ }
816
852
  }
817
853
  }
818
854
  const downloadEntry = async (entry) => {
@@ -848,6 +884,7 @@ export default async function renderArchive(buffer, target, _type, context) {
848
884
  $el: root,
849
885
  async unmount() {
850
886
  closePasswordDialog(null);
887
+ comicBook.dispose();
851
888
  cleanups.splice(0).forEach(cleanup => cleanup());
852
889
  await clearNestedPreview();
853
890
  await closeArchive();
@@ -0,0 +1,30 @@
1
+ import type { ArchiveEntryView } from './archiveShared.js';
2
+ export declare const comicBookStyle = "\n.archive-comic-page{min-width:64px;color:#64748b!important;text-align:center;font-variant-numeric:tabular-nums}\n.archive-comic-nav:disabled{cursor:not-allowed;opacity:.42}\n";
3
+ export declare const isComicBookExtension: (extension: string) => boolean;
4
+ export declare const isComicBookPage: (entry: Pick<ArchiveEntryView, "extension">) => boolean;
5
+ export declare const naturalComicPageCompare: (left: Pick<ArchiveEntryView, "path">, right: Pick<ArchiveEntryView, "path">) => number;
6
+ export declare const getComicBookPages: (entries: readonly ArchiveEntryView[]) => ArchiveEntryView[];
7
+ export declare const clampComicPageIndex: (index: number, pageCount: number) => number;
8
+ interface ComicBookControllerOptions {
9
+ document: Document;
10
+ extension: string;
11
+ root: HTMLElement;
12
+ stage: HTMLElement;
13
+ previousLabel: string;
14
+ nextLabel: string;
15
+ getEntries(): readonly ArchiveEntryView[];
16
+ openEntry(entry: ArchiveEntryView): void;
17
+ }
18
+ export interface ComicBookController {
19
+ enabled: boolean;
20
+ badge: string;
21
+ toolbarElements: readonly HTMLElement[];
22
+ getVisibleEntries(entries: readonly ArchiveEntryView[]): ArchiveEntryView[];
23
+ sortEntries(entries: ArchiveEntryView[]): ArchiveEntryView[];
24
+ onEntriesReady(): void;
25
+ onEntrySelected(entry: ArchiveEntryView): void;
26
+ sync(): void;
27
+ dispose(): void;
28
+ }
29
+ export declare const createComicBookController: (options: ComicBookControllerOptions) => ComicBookController;
30
+ export {};
@@ -0,0 +1,147 @@
1
+ const COMIC_IMAGE_EXTENSIONS = new Set([
2
+ 'avif', 'bmp', 'gif', 'heic', 'heif', 'jpe', 'jpeg', 'jpg', 'jxl', 'png', 'tif', 'tiff', 'webp',
3
+ ]);
4
+ export const comicBookStyle = `
5
+ .archive-comic-page{min-width:64px;color:#64748b!important;text-align:center;font-variant-numeric:tabular-nums}
6
+ .archive-comic-nav:disabled{cursor:not-allowed;opacity:.42}
7
+ `;
8
+ export const isComicBookExtension = (extension) => {
9
+ const normalized = extension.trim().toLowerCase();
10
+ return normalized === 'cbz' || normalized === 'cbr';
11
+ };
12
+ export const isComicBookPage = (entry) => (COMIC_IMAGE_EXTENSIONS.has(entry.extension.toLowerCase()));
13
+ export const naturalComicPageCompare = (left, right) => left.path.localeCompare(right.path, undefined, { numeric: true, sensitivity: 'base' });
14
+ export const getComicBookPages = (entries) => entries
15
+ .filter(isComicBookPage)
16
+ .sort(naturalComicPageCompare);
17
+ export const clampComicPageIndex = (index, pageCount) => (Math.max(0, Math.min(index, Math.max(0, pageCount - 1))));
18
+ const createButton = (documentRef, text, title) => {
19
+ const button = documentRef.createElement('button');
20
+ button.type = 'button';
21
+ button.className = 'archive-comic-nav';
22
+ button.textContent = text;
23
+ button.title = title;
24
+ button.setAttribute('aria-label', title);
25
+ return button;
26
+ };
27
+ export const createComicBookController = (options) => {
28
+ const enabled = isComicBookExtension(options.extension);
29
+ const previousButton = createButton(options.document, '‹', options.previousLabel);
30
+ const pageText = options.document.createElement('span');
31
+ pageText.className = 'archive-comic-page';
32
+ pageText.setAttribute('role', 'status');
33
+ pageText.setAttribute('aria-live', 'polite');
34
+ pageText.setAttribute('aria-atomic', 'true');
35
+ const nextButton = createButton(options.document, '›', options.nextLabel);
36
+ const toolbarElements = [previousButton, pageText, nextButton];
37
+ const cleanups = [];
38
+ const timers = new Set();
39
+ let pageIndex = -1;
40
+ let pointerStartX = null;
41
+ toolbarElements.forEach(element => {
42
+ element.hidden = !enabled;
43
+ });
44
+ if (enabled) {
45
+ options.root.tabIndex = 0;
46
+ }
47
+ const pages = () => getComicBookPages(options.getEntries());
48
+ const openPage = (index) => {
49
+ const currentPages = pages();
50
+ if (!currentPages.length) {
51
+ return;
52
+ }
53
+ pageIndex = clampComicPageIndex(index, currentPages.length);
54
+ options.openEntry(currentPages[pageIndex]);
55
+ };
56
+ const listen = (element, event, listener) => {
57
+ element.addEventListener(event, listener);
58
+ cleanups.push(() => element.removeEventListener(event, listener));
59
+ };
60
+ if (enabled) {
61
+ listen(previousButton, 'click', () => openPage(pageIndex - 1));
62
+ listen(nextButton, 'click', () => openPage(pageIndex + 1));
63
+ listen(options.root, 'keydown', event => {
64
+ const eventTarget = event.target;
65
+ if (eventTarget === null || eventTarget === void 0 ? void 0 : eventTarget.closest('input, button, select, textarea, [contenteditable="true"]')) {
66
+ return;
67
+ }
68
+ if (event.key === 'ArrowLeft' || event.key === 'PageUp') {
69
+ event.preventDefault();
70
+ openPage(pageIndex - 1);
71
+ }
72
+ else if (event.key === 'ArrowRight' || event.key === 'PageDown' || event.key === ' ') {
73
+ event.preventDefault();
74
+ openPage(pageIndex + 1);
75
+ }
76
+ else if (event.key === 'Home') {
77
+ event.preventDefault();
78
+ openPage(0);
79
+ }
80
+ else if (event.key === 'End') {
81
+ event.preventDefault();
82
+ openPage(pages().length - 1);
83
+ }
84
+ });
85
+ listen(options.stage, 'pointerdown', event => {
86
+ if (event.isPrimary) {
87
+ options.root.focus({ preventScroll: true });
88
+ pointerStartX = event.clientX;
89
+ }
90
+ });
91
+ listen(options.stage, 'pointerup', event => {
92
+ if (pointerStartX === null || !event.isPrimary) {
93
+ pointerStartX = null;
94
+ return;
95
+ }
96
+ const distance = event.clientX - pointerStartX;
97
+ pointerStartX = null;
98
+ if (Math.abs(distance) >= 56) {
99
+ openPage(pageIndex + (distance < 0 ? 1 : -1));
100
+ }
101
+ });
102
+ listen(options.stage, 'pointercancel', () => {
103
+ pointerStartX = null;
104
+ });
105
+ }
106
+ return {
107
+ enabled,
108
+ badge: enabled ? options.extension.toUpperCase() : 'ARCHIVE',
109
+ toolbarElements,
110
+ getVisibleEntries: entries => enabled ? getComicBookPages(entries) : [...entries],
111
+ sortEntries: entries => entries.sort(enabled
112
+ ? naturalComicPageCompare
113
+ : (left, right) => left.path.localeCompare(right.path)),
114
+ onEntriesReady() {
115
+ var _a;
116
+ if (!enabled || !pages().length) {
117
+ return;
118
+ }
119
+ const timer = (_a = options.document.defaultView) === null || _a === void 0 ? void 0 : _a.setTimeout(() => {
120
+ timers.delete(timer || 0);
121
+ openPage(0);
122
+ }, 0);
123
+ if (timer !== undefined) {
124
+ timers.add(timer);
125
+ }
126
+ },
127
+ onEntrySelected(entry) {
128
+ if (enabled) {
129
+ pageIndex = pages().findIndex(page => page.id === entry.id);
130
+ }
131
+ },
132
+ sync() {
133
+ if (!enabled) {
134
+ return;
135
+ }
136
+ const pageCount = pages().length;
137
+ pageText.textContent = pageCount ? `${pageIndex + 1} / ${pageCount}` : '0 / 0';
138
+ previousButton.disabled = pageIndex <= 0;
139
+ nextButton.disabled = pageIndex < 0 || pageIndex >= pageCount - 1;
140
+ },
141
+ dispose() {
142
+ cleanups.splice(0).forEach(cleanup => cleanup());
143
+ timers.forEach(timer => { var _a; return (_a = options.document.defaultView) === null || _a === void 0 ? void 0 : _a.clearTimeout(timer); });
144
+ timers.clear();
145
+ },
146
+ };
147
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/renderer-archive",
3
- "version": "2.1.26",
3
+ "version": "2.1.27",
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.26",
59
+ "@file-viewer/core": "2.1.27",
60
60
  "jszip": "^3.10.1",
61
61
  "libarchive.js": "^2.0.2"
62
62
  },
@@ -66,6 +66,7 @@
66
66
  "license": "Apache-2.0",
67
67
  "scripts": {
68
68
  "build": "tsc -b tsconfig.json",
69
- "type-check": "tsc -b tsconfig.json"
69
+ "type-check": "tsc -b tsconfig.json",
70
+ "verify:github-101": "pnpm build && node scripts/verify-github-101.mjs"
70
71
  }
71
72
  }