@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 +2 -0
- package/README.md +2 -0
- package/dist/archive.js +49 -12
- package/dist/comicBook.d.ts +30 -0
- package/dist/comicBook.js +147 -0
- package/package.json +4 -3
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
|
-
?
|
|
517
|
-
:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
812
|
-
|
|
843
|
+
if (requestId === previewSequence) {
|
|
844
|
+
console.error(nextError);
|
|
845
|
+
setError(nextError instanceof Error ? nextError.message : String(nextError));
|
|
846
|
+
}
|
|
813
847
|
}
|
|
814
848
|
finally {
|
|
815
|
-
|
|
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.
|
|
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.
|
|
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
|
}
|