Files
trpic/web/scripts/load_mixed/load_mixed.js
Hubert Chen 85cf0aedf8 refactor
migrate sql:
```
ALTER TABLE posts ADD COLUMN `time` datetime NULL;
ALTER TABLE posts ADD COLUMN `title` text NULL;
ALTER TABLE posts ADD COLUMN `text` text NULL;

UPDATE posts SET
  time = (SELECT a.time FROM artworks a WHERE a.id = posts.artwork_posts),
  title = (SELECT a.title FROM artworks a WHERE a.id = posts.artwork_posts),
  text = (SELECT a.text FROM artworks a WHERE a.id = posts.artwork_posts)
WHERE EXISTS (SELECT 1 FROM artworks a WHERE a.id = posts.artwork_posts);

ALTER TABLE artworks DROP COLUMN time;
ALTER TABLE artworks DROP COLUMN title;
ALTER TABLE artworks DROP COLUMN text;
```

configs:
    add `CheckArtworkIntervalInSeconds` flag to set  check artwork task interval
database:
    add `migrate.sql` file
    artwork:
        move `time`, `title` and `text` fields to `post` schema
    file:
        set `id_str` and `filename` fields immutable
        add `source_url` optional field
    post:
        set `platform` field immutable
        add `time`, `title` and `text` optional from `artwork` schema
    social:
        set `platform` field immutable
    tag:
        set `text` field immutable
action(new):
    add action package to create and update `file`, `post`, `social` and `tag` info
api:
    artworks:
        adjust GET method order option to match the database
    files:
        adjust GET method with `artwork/tags` edge
        rename GET method respond `images` field to `files`
    collect:
        refactor to work with new action package
task:
    artwork(new):
        create `CheckArtworks` function to check and update artwork info
    social:
        refactor `CheckSocials` to work with new action package
router:
    add `/api/task/artworks/check` endpoint for `CheckArtworks` task
src/web:
    artwork noscript:
        adjust order option to match the database
    file noscript:
        rename execute template struct `Images` field to `Files`
web:
    scripts:
        refactor to work with new database schema
    styles:
        adjust some style
        use `<button>` to replace `<span>` to allow keyboard control
    templates:
        adjust to work with new database schema
        use `<button>` to replace `<span>` to allow keyboard control
2026-03-27 13:37:59 +08:00

218 lines
5.5 KiB
JavaScript

let gallery;
let sentinel;
let counter;
let countbox;
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";
const limit = window.innerWidth > 1600 ? 50 : 20;
function resetGallery() {
imgviewer.resetList();
gallery.replaceChildren(gallery.firstElementChild);
height = urlParams.get("height") || (window.innerWidth > 1600 ? 320 : 220);
offset = 0;
hasMore = true;
loadMore();
}
async function loadMore() {
if (loading || !hasMore) return;
observer.unobserve(sentinel);
loading = true;
let url;
let fromArtworks = false;
switch (sort) {
case "time":
case "time-down":
case "publish":
case "publish-down":
url = `/api/artworks?offset=${offset}&limit=${limit}&sort=${sort}`
fromArtworks = true;
break;
default:
url = `/api/files?offset=${offset}&limit=${limit}&sort=${sort}`
break;
}
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
}
}
counter.textContent = data.total;
if (fromArtworks) {
appendArtworks(data.artworks);
countbox.childNodes[2].nodeValue = " 个作品"
} else {
appendFiles(data.files);
countbox.childNodes[2].nodeValue = " 张图片"
}
hasMore = data.offset + data.limit < data.total;
offset += data.limit;
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);
for (const artwork of artworks) {
let index = 0;
for (const file of artwork.edges.files) {
const fragment = document.createDocumentFragment();
const jWidth = file.width * height / file.height;
const jPadding = file.height / file.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 + "%";
wrapper.appendChild(i);
const img = document.createElement("img");
img.loading = "lazy";
img.decoding = "async";
if (file.thumbed) {
img.src = `/thumbnails/${file.id_str}.jpg`;
img.dataset.highres = `/files/${file.id_str}.${file.ext}`;
} else {
img.src = `/files/${file.id_str}.${file.ext}`;
}
img.dataset.id_str = file.id_str;
img.dataset.artwork_id = artwork.id;
wrapper.appendChild(img);
imgviewer.bindClickEvents(img);
fragment.appendChild(wrapper);
gallery.appendChild(fragment);
index++;
}
}
}
function appendFiles(files) {
imgviewer.appendFiles(files);
const fragment = document.createDocumentFragment();
for (const image of files) {
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 (sort == "size" || sort == "size-down") {
const span = document.createElement("span");
const p = document.createElement("p");
p.textContent = `${image.ext} | ${image.thumbed ? "☇ " : ""}${image.size_str}`;
span.appendChild(p);
i.appendChild(span);
}
wrapper.appendChild(i);
const img = document.createElement("img");
img.loading = "lazy";
img.decoding = "async";
img.src = image.thumbed ? `/thumbnails/${image.id_str}.jpg` : `/files/${image.id_str}.${image.ext}`;
let artwork = image.edges.artwork;
img.dataset.id_str = image.id_str;
img.dataset.artwork_id = artwork.id;
wrapper.appendChild(img);
imgviewer.bindClickEvents(img);
fragment.appendChild(wrapper);
}
gallery.appendChild(fragment);
}
document.addEventListener("DOMContentLoaded", () => {
gallery = document.getElementById("gallery");
sentinel = document.getElementById("sentinel");
counter = document.getElementById("count");
countbox = document.getElementById("count-box");
select = document.getElementById('sort-select')
select.value = sort
select.addEventListener('change', () => {
urlParams.set("sort", sort = select.value);
history.replaceState(null, null, `/?${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);