fix: resolve i18n issues - URL duplication, translation loading, and caching
- Fix URL path duplication when clicking logo (added missing leading slash) - Use network-first caching for translation files in service worker - Add missing translation keys (common.close, upload.clearFiles) to all languages - Add Dutch (nl) language support to URL regex patterns - Bump service worker cache version to v8
This commit is contained in:
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDFs oder Bilder",
|
||||
"filesNeverLeave": "Ihre Dateien verlassen nie Ihr Gerät.",
|
||||
"addMore": "Weitere Dateien hinzufügen",
|
||||
"clearAll": "Alle löschen"
|
||||
"clearAll": "Alle löschen",
|
||||
"clearFiles": "Dateien löschen"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Verarbeitung..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Fehler",
|
||||
"success": "Erfolg",
|
||||
"file": "Datei",
|
||||
"files": "Dateien"
|
||||
"files": "Dateien",
|
||||
"close": "Schließen"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDFs or Images",
|
||||
"filesNeverLeave": "Your files never leave your device.",
|
||||
"addMore": "Add More Files",
|
||||
"clearAll": "Clear All"
|
||||
"clearAll": "Clear All",
|
||||
"clearFiles": "Clear Files"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Processing..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Error",
|
||||
"success": "Success",
|
||||
"file": "File",
|
||||
"files": "Files"
|
||||
"files": "Files",
|
||||
"close": "Close"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDFs o Imágenes",
|
||||
"filesNeverLeave": "Tus archivos nunca salen de tu dispositivo.",
|
||||
"addMore": "Agregar Más Archivos",
|
||||
"clearAll": "Limpiar Todo"
|
||||
"clearAll": "Limpiar Todo",
|
||||
"clearFiles": "Borrar archivos"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Procesando..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Error",
|
||||
"success": "Éxito",
|
||||
"file": "Archivo",
|
||||
"files": "Archivos"
|
||||
"files": "Archivos",
|
||||
"close": "Cerrar"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDF ou images",
|
||||
"filesNeverLeave": "Vos fichiers restent sur votre appareil.",
|
||||
"addMore": "Ajouter d’autres fichiers",
|
||||
"clearAll": "Tout effacer"
|
||||
"clearAll": "Tout effacer",
|
||||
"clearFiles": "Effacer les fichiers"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Traitement en cours..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Erreur",
|
||||
"success": "Succès",
|
||||
"file": "Fichier",
|
||||
"files": "Fichiers"
|
||||
"files": "Fichiers",
|
||||
"close": "Fermer"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDF atau Gambar",
|
||||
"filesNeverLeave": "File Anda tidak pernah meninggalkan perangkat Anda.",
|
||||
"addMore": "Tambah Lebih Banyak File",
|
||||
"clearAll": "Hapus Semua"
|
||||
"clearAll": "Hapus Semua",
|
||||
"clearFiles": "Hapus file"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Memproses..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Kesalahan",
|
||||
"success": "Berhasil",
|
||||
"file": "File",
|
||||
"files": "File"
|
||||
"files": "File",
|
||||
"close": "Tutup"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDF o immagini",
|
||||
"filesNeverLeave": "I tuoi file non lasciano mai il tuo dispositivo.",
|
||||
"addMore": "Aggiungi altri file",
|
||||
"clearAll": "Svuota tutto"
|
||||
"clearAll": "Svuota tutto",
|
||||
"clearFiles": "Cancella file"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Elaborazione..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Errore",
|
||||
"success": "Successo",
|
||||
"file": "File",
|
||||
"files": "File"
|
||||
"files": "File",
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"toolsLabel": "Tools",
|
||||
"subtitle": "Klik een tool om de bestandslader te openen",
|
||||
"searchPlaceholder": "Zoek een tool (bijv., 'splitsen', 'organiseren'...)",
|
||||
"backToTools": "Terug naar Tools"
|
||||
"backToTools": "Terug naar Tools",
|
||||
"firstLoadNotice": "De eerste keer duurt het even om onze conversiemachine te laden. Daarna gaat alles meteen."
|
||||
},
|
||||
"upload": {
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDF's of Afbeeldingen",
|
||||
"filesNeverLeave": "Je bestanden blijven op jouw apparaat.",
|
||||
"addMore": "Meer bestanden toevoegen",
|
||||
"clearAll": "Alles wissen"
|
||||
"clearAll": "Alles wissen",
|
||||
"clearFiles": "Bestanden wissen"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Verwerken..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Fout",
|
||||
"success": "Success",
|
||||
"file": "Bestand",
|
||||
"files": "Bestanden"
|
||||
"files": "Bestanden",
|
||||
"close": "Sluiten"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
@@ -321,4 +323,3 @@
|
||||
"failedToLoad": "Laden is mislukt"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDFs ou Imagens",
|
||||
"filesNeverLeave": "Seus arquivos nunca saem do seu dispositivo.",
|
||||
"addMore": "Adicionar Mais Arquivos",
|
||||
"clearAll": "Limpar Tudo"
|
||||
"clearAll": "Limpar Tudo",
|
||||
"clearFiles": "Limpar arquivos"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Processando..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Erro",
|
||||
"success": "Sucesso",
|
||||
"file": "Arquivo",
|
||||
"files": "Arquivos"
|
||||
"files": "Arquivos",
|
||||
"close": "Fechar"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDF veya Görseller",
|
||||
"filesNeverLeave": "Dosyalarınız cihazınızı asla terk etmez.",
|
||||
"addMore": "Daha Fazla Dosya Ekle",
|
||||
"clearAll": "Tümünü Temizle"
|
||||
"clearAll": "Tümünü Temizle",
|
||||
"clearFiles": "Dosyaları temizle"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "İşleniyor..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Hata",
|
||||
"success": "Başarılı",
|
||||
"file": "Dosya",
|
||||
"files": "Dosya"
|
||||
"files": "Dosya",
|
||||
"close": "Kapat"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDF hoặc Hình ảnh",
|
||||
"filesNeverLeave": "Tệp của bạn không bao giờ rời khỏi thiết bị.",
|
||||
"addMore": "Thêm tệp",
|
||||
"clearAll": "Xóa tất cả"
|
||||
"clearAll": "Xóa tất cả",
|
||||
"clearFiles": "Xóa tệp"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "Đang xử lý..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "Lỗi",
|
||||
"success": "Thành công",
|
||||
"file": "Tệp",
|
||||
"files": "Tệp"
|
||||
"files": "Tệp",
|
||||
"close": "Đóng"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDF 或圖片",
|
||||
"filesNeverLeave": "你的檔案永遠不會離開你的裝置。",
|
||||
"addMore": "添加更多檔案",
|
||||
"clearAll": "清除全部"
|
||||
"clearAll": "清除全部",
|
||||
"clearFiles": "清除檔案"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "正在處理..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "錯誤",
|
||||
"success": "成功",
|
||||
"file": "檔案",
|
||||
"files": "檔案"
|
||||
"files": "檔案",
|
||||
"close": "關閉"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
@@ -66,7 +66,8 @@
|
||||
"pdfOrImages": "PDF 或图片",
|
||||
"filesNeverLeave": "您的文件从未离开您的设备。",
|
||||
"addMore": "添加更多文件",
|
||||
"clearAll": "清空所有"
|
||||
"clearAll": "清空所有",
|
||||
"clearFiles": "清除文件"
|
||||
},
|
||||
"loader": {
|
||||
"processing": "处理中..."
|
||||
@@ -226,7 +227,8 @@
|
||||
"error": "错误",
|
||||
"success": "成功",
|
||||
"file": "文件",
|
||||
"files": "文件"
|
||||
"files": "文件",
|
||||
"close": "关闭"
|
||||
},
|
||||
"about": {
|
||||
"hero": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
46
public/sw.js
46
public/sw.js
@@ -5,10 +5,9 @@
|
||||
* Version: 1.1.0
|
||||
*/
|
||||
|
||||
const CACHE_VERSION = 'bentopdf-v7';
|
||||
const CACHE_VERSION = 'bentopdf-v8';
|
||||
const CACHE_NAME = `${CACHE_VERSION}-static`;
|
||||
|
||||
|
||||
const getBasePath = () => {
|
||||
const scope = self.registration?.scope || self.location.href;
|
||||
const url = new URL(scope);
|
||||
@@ -44,7 +43,8 @@ self.addEventListener('install', (event) => {
|
||||
// console.log('📦 [ServiceWorker] Will cache', CRITICAL_ASSETS.length, 'critical assets');
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME)
|
||||
caches
|
||||
.open(CACHE_NAME)
|
||||
.then((cache) => {
|
||||
// console.log('[ServiceWorker] Caching critical assets...');
|
||||
return cacheInBatches(cache, CRITICAL_ASSETS, 5);
|
||||
@@ -64,7 +64,8 @@ self.addEventListener('activate', (event) => {
|
||||
// console.log('🔄 [ServiceWorker] Activating version:', CACHE_VERSION);
|
||||
|
||||
event.waitUntil(
|
||||
caches.keys()
|
||||
caches
|
||||
.keys()
|
||||
.then((cacheNames) => {
|
||||
return Promise.all(
|
||||
cacheNames.map((cacheName) => {
|
||||
@@ -92,18 +93,33 @@ self.addEventListener('fetch', (event) => {
|
||||
if (!isLocal && !isCDN) {
|
||||
return;
|
||||
}
|
||||
if (isLocal && (url.searchParams.has('t') || url.searchParams.has('import') || url.searchParams.has('direct'))) {
|
||||
if (
|
||||
isLocal &&
|
||||
(url.searchParams.has('t') ||
|
||||
url.searchParams.has('import') ||
|
||||
url.searchParams.has('direct'))
|
||||
) {
|
||||
// console.log('🔧 [Dev Mode] Skipping Vite HMR request:', url.pathname);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLocal && (url.pathname.includes('/@vite') || url.pathname.includes('/@id') || url.pathname.includes('/@fs'))) {
|
||||
if (
|
||||
isLocal &&
|
||||
(url.pathname.includes('/@vite') ||
|
||||
url.pathname.includes('/@id') ||
|
||||
url.pathname.includes('/@fs'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldCache(url.pathname, isCDN)) {
|
||||
if (isLocal && url.pathname.includes('/locales/')) {
|
||||
event.respondWith(networkFirstStrategy(event.request));
|
||||
} else if (shouldCache(url.pathname, isCDN)) {
|
||||
event.respondWith(cacheFirstStrategyWithDedup(event.request, isCDN));
|
||||
} else if (isLocal && (url.pathname.endsWith('.html') || url.pathname === '/')) {
|
||||
} else if (
|
||||
isLocal &&
|
||||
(url.pathname.endsWith('.html') || url.pathname === '/')
|
||||
) {
|
||||
event.respondWith(networkFirstStrategy(event.request));
|
||||
}
|
||||
});
|
||||
@@ -154,7 +170,10 @@ async function cacheFirstStrategyWithDedup(request, isCDN) {
|
||||
return fallbackResponse;
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error('[ServiceWorker] Both CDN and local failed for:', fileName);
|
||||
console.error(
|
||||
'[ServiceWorker] Both CDN and local failed for:',
|
||||
fileName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,7 +271,9 @@ function shouldCache(pathname, isCDN = false) {
|
||||
pathname.includes('/ghostscript-wasm/') ||
|
||||
pathname.includes('/embedpdf/') ||
|
||||
pathname.includes('/assets/') ||
|
||||
pathname.match(/\.(js|mjs|css|wasm|whl|zip|json|png|jpg|jpeg|gif|svg|woff|woff2|ttf|gz|br)$/)
|
||||
pathname.match(
|
||||
/\.(js|mjs|css|wasm|whl|zip|json|png|jpg|jpeg|gif|svg|woff|woff2|ttf|gz|br)$/
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -269,7 +290,10 @@ async function cacheInBatches(cache, urls, batchSize = 5) {
|
||||
try {
|
||||
await cache.add(url);
|
||||
const fileName = url.split('/').pop();
|
||||
const fileSize = fileName.includes('.wasm') || fileName.includes('.whl') ? '(large file)' : '';
|
||||
const fileSize =
|
||||
fileName.includes('.wasm') || fileName.includes('.whl')
|
||||
? '(large file)'
|
||||
: '';
|
||||
// console.log(` ✓ Cached: ${fileName} ${fileSize}`);
|
||||
} catch (error) {
|
||||
console.warn('[ServiceWorker] Failed to cache:', url, error.message);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import i18next from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import HttpBackend from 'i18next-http-backend';
|
||||
|
||||
// Supported languages
|
||||
@@ -48,7 +47,6 @@ export const getLanguageFromUrl = (): SupportedLanguage => {
|
||||
|
||||
const langMatch = path.match(
|
||||
/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl)(?:\/|$)/
|
||||
/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)(?:\/|$)/
|
||||
);
|
||||
if (
|
||||
langMatch &&
|
||||
@@ -75,28 +73,25 @@ export const initI18n = async (): Promise<typeof i18next> => {
|
||||
|
||||
const currentLang = getLanguageFromUrl();
|
||||
|
||||
await i18next
|
||||
.use(HttpBackend)
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
localStorage.setItem('i18nextLng', currentLang);
|
||||
|
||||
await i18next.use(HttpBackend).init({
|
||||
lng: currentLang,
|
||||
fallbackLng: 'en',
|
||||
supportedLngs: supportedLanguages as unknown as string[],
|
||||
ns: ['common', 'tools'],
|
||||
defaultNS: 'common',
|
||||
preload: [currentLang],
|
||||
backend: {
|
||||
loadPath: `${import.meta.env.BASE_URL.replace(/\/?$/, '/')}locales/{{lng}}/{{ns}}.json`,
|
||||
},
|
||||
detection: {
|
||||
order: ['path', 'localStorage', 'navigator'],
|
||||
lookupFromPathIndex: 0,
|
||||
caches: ['localStorage'],
|
||||
},
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
await i18next.loadNamespaces('tools');
|
||||
|
||||
initialized = true;
|
||||
return i18next;
|
||||
};
|
||||
@@ -122,7 +117,7 @@ export const changeLanguage = (lang: SupportedLanguage): void => {
|
||||
|
||||
let pagePathWithoutLang = relativePath;
|
||||
const langPrefixMatch = relativePath.match(
|
||||
/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt)(\/.*)?$/
|
||||
/^\/(en|fr|es|de|zh|zh-TW|vi|tr|id|it|pt|nl)(\/.*)?$/
|
||||
);
|
||||
if (langPrefixMatch) {
|
||||
pagePathWithoutLang = langPrefixMatch[2] || '/';
|
||||
@@ -237,7 +232,7 @@ export const rewriteLinks = (): void => {
|
||||
newHref = `/${currentLang}/`;
|
||||
}
|
||||
} else {
|
||||
newHref = `${currentLang}/${href}`;
|
||||
newHref = `/${currentLang}/${href}`;
|
||||
}
|
||||
|
||||
newHref = newHref.replace(/([^:])\/+/g, '$1/');
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
/**
|
||||
* Configuration for progressive rendering
|
||||
@@ -25,7 +28,12 @@ interface PageTask {
|
||||
fileName?: string;
|
||||
container: HTMLElement;
|
||||
scale?: number;
|
||||
createWrapper: (canvas: HTMLCanvasElement, pageNumber: number, fileName?: string) => HTMLElement;
|
||||
createWrapper: (
|
||||
canvas: HTMLCanvasElement,
|
||||
pageNumber: number,
|
||||
fileName?: string
|
||||
) => HTMLElement;
|
||||
placeholderElement?: HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,6 +42,7 @@ interface PageTask {
|
||||
interface LazyLoadState {
|
||||
observer: IntersectionObserver | null;
|
||||
pendingTasks: Map<HTMLElement, PageTask>;
|
||||
pendingTasksByPageNumber: Map<number, PageTask>;
|
||||
isRendering: boolean;
|
||||
eagerLoadQueue: PageTask[];
|
||||
nextEagerIndex: number;
|
||||
@@ -42,6 +51,7 @@ interface LazyLoadState {
|
||||
const lazyLoadState: LazyLoadState = {
|
||||
observer: null,
|
||||
pendingTasks: new Map(),
|
||||
pendingTasksByPageNumber: new Map(),
|
||||
isRendering: false,
|
||||
eagerLoadQueue: [],
|
||||
nextEagerIndex: 0,
|
||||
@@ -50,7 +60,10 @@ const lazyLoadState: LazyLoadState = {
|
||||
/**
|
||||
* Creates a placeholder element for a page that will be lazy-loaded
|
||||
*/
|
||||
export function createPlaceholder(pageNumber: number, fileName?: string): HTMLElement {
|
||||
export function createPlaceholder(
|
||||
pageNumber: number,
|
||||
fileName?: string
|
||||
): HTMLElement {
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.className =
|
||||
'page-thumbnail relative cursor-move flex flex-col items-center gap-1 p-2 border-2 border-gray-600 rounded-lg bg-gray-800 transition-colors';
|
||||
@@ -62,7 +75,8 @@ export function createPlaceholder(pageNumber: number, fileName?: string): HTMLEl
|
||||
|
||||
// Create skeleton loader
|
||||
const skeletonContainer = document.createElement('div');
|
||||
skeletonContainer.className = 'relative w-full h-36 bg-gray-700 rounded-md animate-pulse flex items-center justify-center';
|
||||
skeletonContainer.className =
|
||||
'relative w-full h-36 bg-gray-700 rounded-md animate-pulse flex items-center justify-center';
|
||||
|
||||
const loadingText = document.createElement('span');
|
||||
loadingText.className = 'text-gray-500 text-xs';
|
||||
@@ -107,7 +121,7 @@ async function renderPageBatch(
|
||||
tasks: PageTask[],
|
||||
onProgress?: (current: number, total: number) => void
|
||||
): Promise<void> {
|
||||
const renderPromises = tasks.map(async (task) => {
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
const canvas = await renderPageToCanvas(
|
||||
task.pdfjsDoc,
|
||||
@@ -115,30 +129,50 @@ async function renderPageBatch(
|
||||
task.scale || 0.5
|
||||
);
|
||||
|
||||
const wrapper = task.createWrapper(canvas, task.pageNumber, task.fileName);
|
||||
|
||||
// Find and replace the placeholder for this specific page number
|
||||
const placeholder = task.container.querySelector(
|
||||
`[data-page-number="${task.pageNumber}"][data-lazy-load="true"]`
|
||||
const wrapper = task.createWrapper(
|
||||
canvas,
|
||||
task.pageNumber,
|
||||
task.fileName
|
||||
);
|
||||
|
||||
if (placeholder) {
|
||||
// Replace placeholder with rendered page
|
||||
task.container.replaceChild(wrapper, placeholder);
|
||||
let placeholder: Element | null = task.placeholderElement || null;
|
||||
if (!placeholder) {
|
||||
placeholder = task.container.querySelector(
|
||||
`[data-page-number="${task.pageNumber}"][data-lazy-load="true"]`
|
||||
);
|
||||
}
|
||||
|
||||
if (placeholder && placeholder.parentNode) {
|
||||
const parent = placeholder.parentNode;
|
||||
parent.insertBefore(wrapper, placeholder);
|
||||
parent.removeChild(placeholder);
|
||||
} else {
|
||||
const allChildren = Array.from(
|
||||
task.container.children
|
||||
) as HTMLElement[];
|
||||
let insertBefore: Element | null = null;
|
||||
|
||||
for (const child of allChildren) {
|
||||
const childPageNum = parseInt(child.dataset.pageNumber || '0', 10);
|
||||
if (childPageNum > task.pageNumber) {
|
||||
insertBefore = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertBefore) {
|
||||
task.container.insertBefore(wrapper, insertBefore);
|
||||
} else {
|
||||
// Fallback: shouldn't happen with new approach, but just in case
|
||||
console.warn(`No placeholder found for page ${task.pageNumber}, appending instead`);
|
||||
task.container.appendChild(wrapper);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
console.warn(
|
||||
`Placeholder not found for page ${task.pageNumber}, inserted at calculated position`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error rendering page ${task.pageNumber}:`, error);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(renderPromises);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,12 +192,19 @@ function setupLazyRendering(
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const placeholder = entry.target as HTMLElement;
|
||||
const task = lazyLoadState.pendingTasks.get(placeholder);
|
||||
const pageNumberStr = placeholder.dataset.pageNumber;
|
||||
if (!pageNumberStr) return;
|
||||
|
||||
const pageNumber = parseInt(pageNumberStr, 10);
|
||||
const task = lazyLoadState.pendingTasksByPageNumber.get(pageNumber);
|
||||
|
||||
if (task) {
|
||||
// Immediately unobserve to prevent multiple triggers
|
||||
observer.unobserve(placeholder);
|
||||
lazyLoadState.pendingTasks.delete(placeholder);
|
||||
lazyLoadState.pendingTasksByPageNumber.delete(pageNumber);
|
||||
|
||||
task.placeholderElement = placeholder;
|
||||
|
||||
// Render this page immediately (not waiting for isRendering flag)
|
||||
renderPageBatch([task], config.onProgress)
|
||||
@@ -174,13 +215,19 @@ function setupLazyRendering(
|
||||
}
|
||||
|
||||
// Check if all pages are rendered
|
||||
if (lazyLoadState.pendingTasks.size === 0 && lazyLoadState.observer) {
|
||||
if (
|
||||
lazyLoadState.pendingTasks.size === 0 &&
|
||||
lazyLoadState.observer
|
||||
) {
|
||||
lazyLoadState.observer.disconnect();
|
||||
lazyLoadState.observer = null;
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(`Error lazy loading page ${task.pageNumber}:`, error);
|
||||
console.error(
|
||||
`Error lazy loading page ${task.pageNumber}:`,
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -208,7 +255,11 @@ function requestIdleCallbackPolyfill(callback: () => void): void {
|
||||
export async function renderPagesProgressively(
|
||||
pdfjsDoc: any,
|
||||
container: HTMLElement,
|
||||
createWrapper: (canvas: HTMLCanvasElement, pageNumber: number, fileName?: string) => HTMLElement,
|
||||
createWrapper: (
|
||||
canvas: HTMLCanvasElement,
|
||||
pageNumber: number,
|
||||
fileName?: string
|
||||
) => HTMLElement,
|
||||
config: RenderConfig = {}
|
||||
): Promise<void> {
|
||||
const {
|
||||
@@ -236,7 +287,7 @@ export async function renderPagesProgressively(
|
||||
|
||||
const tasks: PageTask[] = [];
|
||||
|
||||
// Create tasks for all pages
|
||||
// Create tasks for all pages with direct placeholder references
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
tasks.push({
|
||||
pageNumber: i,
|
||||
@@ -244,6 +295,7 @@ export async function renderPagesProgressively(
|
||||
container,
|
||||
scale: config.useLazyLoading ? 0.3 : 0.5,
|
||||
createWrapper,
|
||||
placeholderElement: placeholders[i - 1],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -253,15 +305,17 @@ export async function renderPagesProgressively(
|
||||
|
||||
for (let i = initialRenderCount + 1; i <= totalPages; i++) {
|
||||
const placeholder = placeholders[i - 1];
|
||||
const task = tasks[i - 1];
|
||||
// Store the task for lazy rendering
|
||||
lazyLoadState.pendingTasks.set(placeholder, tasks[i - 1]);
|
||||
lazyLoadState.pendingTasks.set(placeholder, task);
|
||||
lazyLoadState.pendingTasksByPageNumber.set(task.pageNumber, task);
|
||||
observer.observe(placeholder);
|
||||
}
|
||||
|
||||
// Prepare eager load queue
|
||||
const eagerStartIndex = initialRenderCount;
|
||||
const eagerEndIndex = Math.min(
|
||||
eagerStartIndex + (eagerLoadBatches * batchSize),
|
||||
eagerStartIndex + eagerLoadBatches * batchSize,
|
||||
totalPages
|
||||
);
|
||||
lazyLoadState.eagerLoadQueue = tasks.slice(eagerStartIndex, eagerEndIndex);
|
||||
@@ -294,7 +348,11 @@ export async function renderPagesProgressively(
|
||||
}
|
||||
|
||||
// Start eager loading AFTER initial batch is complete
|
||||
if (useLazyLoading && eagerLoadBatches > 0 && totalPages > initialRenderCount) {
|
||||
if (
|
||||
useLazyLoading &&
|
||||
eagerLoadBatches > 0 &&
|
||||
totalPages > initialRenderCount
|
||||
) {
|
||||
renderEagerBatch(config);
|
||||
}
|
||||
}
|
||||
@@ -311,6 +369,7 @@ export function observePlaceholder(
|
||||
return;
|
||||
}
|
||||
lazyLoadState.pendingTasks.set(placeholder, task);
|
||||
lazyLoadState.pendingTasksByPageNumber.set(task.pageNumber, task);
|
||||
lazyLoadState.observer.observe(placeholder);
|
||||
}
|
||||
|
||||
@@ -339,12 +398,12 @@ function renderEagerBatch(config: RenderConfig): void {
|
||||
if (config.shouldCancel?.()) return;
|
||||
|
||||
// Remove these tasks from pending since we're rendering them eagerly
|
||||
batch.forEach(task => {
|
||||
const placeholder = Array.from(lazyLoadState.pendingTasks.entries())
|
||||
.find(([_, t]) => t.pageNumber === task.pageNumber)?.[0];
|
||||
batch.forEach((task) => {
|
||||
const placeholder = task.placeholderElement;
|
||||
if (placeholder && lazyLoadState.observer) {
|
||||
lazyLoadState.observer.unobserve(placeholder);
|
||||
lazyLoadState.pendingTasks.delete(placeholder);
|
||||
lazyLoadState.pendingTasksByPageNumber.delete(task.pageNumber);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -358,7 +417,9 @@ function renderEagerBatch(config: RenderConfig): void {
|
||||
lazyLoadState.nextEagerIndex = batchEnd;
|
||||
|
||||
// Queue next eager batch
|
||||
const remainingBatches = Math.ceil((eagerLoadQueue.length - batchEnd) / batchSize);
|
||||
const remainingBatches = Math.ceil(
|
||||
(eagerLoadQueue.length - batchEnd) / batchSize
|
||||
);
|
||||
if (remainingBatches > 0 && remainingBatches < eagerLoadBatches) {
|
||||
// Continue eager loading if we have more batches within the eager threshold
|
||||
renderEagerBatch(config);
|
||||
@@ -375,6 +436,7 @@ export function cleanupLazyRendering(): void {
|
||||
lazyLoadState.observer = null;
|
||||
}
|
||||
lazyLoadState.pendingTasks.clear();
|
||||
lazyLoadState.pendingTasksByPageNumber.clear();
|
||||
lazyLoadState.isRendering = false;
|
||||
lazyLoadState.eagerLoadQueue = [];
|
||||
lazyLoadState.nextEagerIndex = 0;
|
||||
|
||||
Reference in New Issue
Block a user