Skip to content

Commit 96f9d8c

Browse files
committed
fix: load sidebar thumbnails via authenticated fetch with IntersectionObserver
1 parent 2af9bac commit 96f9d8c

1 file changed

Lines changed: 45 additions & 4 deletions

File tree

static/js/readers/page-reader.js

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Handles loading and displaying content page by page with spread mode support
55
*/
66

7-
/* global URL, Image */
7+
/* global URL, Image, IntersectionObserver */
88

99
import { APIClient, APIHelper } from '../core/api.js';
1010
import { mediaWorker, Priority } from './media-worker-manager.js';
@@ -71,6 +71,7 @@ export class PageReader {
7171
this.prefetchCache = new Map(); // Cache for prefetched images
7272
this.workerInitialized = false; // Media worker status
7373
this._displayBlobUrls = []; // Blob URLs for currently displayed pages (for cleanup)
74+
this._thumbnailObserver = null; // IntersectionObserver for lazy thumbnail loading
7475

7576
// Initialize managers
7677
this.fullscreenManager = new FullscreenManager({
@@ -207,7 +208,6 @@ export class PageReader {
207208
return;
208209
}
209210

210-
// Use a data attribute to store the reader reference name for onclick
211211
const readerName = this.contentType === 'comic' ? 'comicReader' : 'pdfReader';
212212

213213
pageList.innerHTML = this.metadata.pages
@@ -219,16 +219,52 @@ export class PageReader {
219219
onclick="${readerName}.loadPage(${index})"
220220
>
221221
<img
222-
src="${this.getEndpoint(`page/${index}/thumbnail`)}"
222+
data-src="${this.getEndpoint(`page/${index}/thumbnail`)}"
223223
alt="Page ${index + 1}"
224224
class="page-thumbnail"
225-
loading="lazy"
226225
/>
227226
<span class="page-number">Page ${index + 1}</span>
228227
</div>
229228
`
230229
)
231230
.join('');
231+
232+
this._setupThumbnailObserver(pageList);
233+
}
234+
235+
/**
236+
* Set up IntersectionObserver to lazy-load sidebar thumbnails with auth.
237+
* @param {HTMLElement} pageList - The sidebar page list container
238+
*/
239+
_setupThumbnailObserver(pageList) {
240+
if (this._thumbnailObserver) {
241+
this._thumbnailObserver.disconnect();
242+
}
243+
244+
this._thumbnailObserver = new IntersectionObserver(
245+
(entries) => {
246+
entries.forEach((entry) => {
247+
if (!entry.isIntersecting) return;
248+
const img = entry.target;
249+
const src = img.dataset.src;
250+
if (!src || img.src.startsWith('blob:')) return;
251+
this._thumbnailObserver.unobserve(img);
252+
APIClient.authenticatedFetch(src)
253+
.then((response) => response.blob())
254+
.then((blob) => {
255+
img.src = URL.createObjectURL(blob);
256+
})
257+
.catch(() => {
258+
// leave placeholder on error
259+
});
260+
});
261+
},
262+
{ root: pageList, rootMargin: '200px' }
263+
);
264+
265+
pageList.querySelectorAll('img[data-src]').forEach((img) => {
266+
this._thumbnailObserver.observe(img);
267+
});
232268
}
233269

234270
/**
@@ -768,6 +804,11 @@ export class PageReader {
768804
}
769805
this.prefetchCache.clear();
770806
this._revokePreviousDisplayBlobs();
807+
if (this._thumbnailObserver) {
808+
this._thumbnailObserver.disconnect();
809+
this._thumbnailObserver = null;
810+
}
811+
this._revokePreviousDisplayBlobs();
771812
}
772813

773814
/**

0 commit comments

Comments
 (0)