let gallery; let sentinel; let counter; let select; let observer; let offset = 0; let loading = false; let hasMore = true; const urlParams = new URLSearchParams(window.location.search); let height = urlParams.get("height") || (window.innerWidth > 1600 ? 320 : 220); let sort = urlParams.get("sort") || "time"; let tags = urlParams.get("tags") || ""; const limit = window.innerWidth > 1600 ? 50 : 20; function resetGallery() { imgviewer.resetList(); gallery.replaceChildren(); height = urlParams.get("height") || (window.innerWidth > 1600 ? 320 : 220); offset = 0; hasMore = true; loadMore(); } async function loadMore() { if (loading || !hasMore) return; let url = `/api/artworks?offset=${offset}&limit=${limit}&sort=${sort}`; observer.unobserve(sentinel); loading = true; if (tags) url += `&tags=${tags}` try { const res = await fetch(url); const data = await res.json(); switch (res.status) { case 200: break; case 401: alert("Session expired"); return; case 500: if (data.error) { hasMore = false; alert(`${data.msg}: ${data.error}`); return } } appendArtworks(data.artworks); hasMore = data.offset + data.limit < data.total; offset += data.limit; counter.textContent = data.total; if (!sort) sort = data.sort; } catch (err) { console.error(err); hasMore = false; } finally { loading = false; requestAnimationFrame(() => { observer.observe(sentinel); }); } } function appendArtworks(artworks) { imgviewer.appendArtworks(artworks); const fragment = document.createDocumentFragment(); for (const artwork of artworks) { let image = artwork.edges.files[0]; const jWidth = image.width * height / image.height; const jPadding = image.height / image.width * 100; const wrapper = document.createElement("div"); wrapper.style.width = jWidth + "px"; wrapper.style.flexGrow = jWidth; const i = document.createElement("i"); i.style.paddingBottom = jPadding + "%"; if (artwork.edges.files.length > 1) { const span = document.createElement("span"); const p = document.createElement("p"); p.textContent = `${artwork.edges.files.length} pic`; span.appendChild(p); i.appendChild(span); } wrapper.appendChild(i); const imgs = document.createElement("div"); imgs.id = "imgs"; const img = document.createElement("img"); img.loading = "lazy"; img.decoding = "async"; img.tabIndex = "0"; if (image.thumbed) { img.src = `/thumbnails/${image.id_str}.jpg`; img.dataset.highres = `/files/${image.id_str}.${image.ext}`; } else { img.src = `/files/${image.id_str}.${image.ext}`; } img.alt = `trpic_artwork_${artwork.id}_${image.id_str}`; img.dataset.id_str = image.id_str; img.dataset.artwork_id = artwork.id; imgs.appendChild(img); if (image.thumbhash) { const thumb = document.createElement("img"); thumb.id = thumb.alt = "thumbhash"; thumb.src = thumbHashToDataURL(image.thumbhash); imgs.appendChild(thumb); img.onload = () => { img.onload = null; thumb.style.opacity = 0; setInterval(() => { thumb.remove(); }, 1000) }; } wrapper.appendChild(imgs); imgviewer.bindClickEvents(img); fragment.appendChild(wrapper); } gallery.appendChild(fragment); } document.addEventListener("DOMContentLoaded", () => { gallery = document.getElementById("gallery"); sentinel = document.getElementById("sentinel"); counter = document.getElementById("artwork-count"); select = document.getElementById('sort-select') select.value = sort select.addEventListener('change', () => { urlParams.set("sort", sort = select.value); history.replaceState(null, null, `/artworks?${urlParams.toString()}`); resetGallery() }) observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) { loadMore(); } }, { root: null, rootMargin: "800px", // 提前 800px 加载 threshold: 0, } ); observer.observe(sentinel); }); document.addEventListener("error", (e) => { let target = e.target; const tagName = target.tagName || ""; const curTimes = Number(target.dataset.retryTimes) || 0; if (tagName.toLowerCase() === "img") { if (curTimes >= 5) { target.alt = "loading failed"; } else { setTimeout(() => { target.dataset.retryTimes = curTimes + 1; target.src = target.src; }, 10000); } } else { target = null; } }, true);