class MetaParser { static parse() { const result = { artwork: null, socials: [], files: [], posts: [], tags: [] }; const metas = document.querySelectorAll("meta[name]"); metas.forEach(meta => { const name = meta.getAttribute("name"); const data = this.metaToObject(meta); if (name === "artwork") { result.artwork = data; } else if (name.startsWith("social-")) { result.socials.push(data); } else if (name.startsWith("file-")) { result.files.push(data); } else if (name.startsWith("post-")) { result.posts.push(data); } else if (name.startsWith("tag-")) { result.tags.push(data); } }); return result; } static metaToObject(meta) { const obj = {}; for (const attr of meta.attributes) { let value = attr.value; if (value === "true") { value = true; } else if (value === "false") { value = false; } else if (Number.isSafeInteger(value) && !isNaN(value) && value.trim() !== "") { value = Number(value); } obj[attr.name] = value; } return obj; } } document.addEventListener('DOMContentLoaded', () => { const pathIndex = parseInt(window.location.pathname.split("/photo/").pop()); let data = MetaParser.parse(), currentScale = 1.0, maxScale = 8, minScale = 0.5, isDragging = false, startX = 0, startY = 0, currentX = 0, currentY = 0, index = pathIndex ? pathIndex : 0; let preview = document.getElementById("preview"); preview.style.display = "flex"; let previewImg = document.getElementById("preview-img"); let prevBtn = document.getElementById("prev-btn"); prevBtn.addEventListener("click", showPrevImage.bind(this)); let nextBtn = document.getElementById("next-btn"); nextBtn.addEventListener("click", showNextImage.bind(this)); document.addEventListener("keydown", handleKeyDown.bind(this)); let imageWrapper = document.getElementById("image-wrapper"); imageWrapper.addEventListener("wheel", handleWheel.bind(this)); imageWrapper.addEventListener("mousedown", startDrag.bind(this)); let navControls = document.getElementById("nav-controls"); let downloadBtn = document.getElementById("download-btn"); downloadBtn.addEventListener("click", downloadImage.bind(this)); let deleteBtn = document.getElementById("delete-btn"); deleteBtn.addEventListener("click", deleteImage.bind(this)); let sidebar = document.getElementById("sidebar"); let sidebarBtn = document.getElementById("sidebar-btn"); sidebarBtn.addEventListener("click", toggleSidebar.bind(this)); let sidebarImageAuthor = document.getElementById("img-author"); let sidebarImageSource = document.getElementById("img-source"); let sidebarImageTag = document.getElementById("img-tag"); let sidebarImageInfo = document.getElementById("img-info"); let sidebarImageSetting = document.getElementById("img-setting"); let sidebarImageSettingSselect = document.getElementById("sensitive-select"); let sidebarImageSettingPselect = document.getElementById("private-select"); let sidebarImageSettingButton = document.getElementById("setting-button"); sidebarImageSettingButton.addEventListener("click", patchArtwork.bind(this)); let sidebarImageSettingPostInput = document.getElementById("post-input"); let sidebarImageSettingPostButton = document.getElementById("post-button"); sidebarImageSettingPostButton.addEventListener("click", postSource.bind(this)); let sidebarImageSettingUploadFileButton = document.getElementById("upload-file-button"); let sidebarImageSettingUploadURLInput = document.getElementById("upload-url-input"); let sidebarImageSettingUploadButton = document.getElementById("upload-button"); sidebarImageSettingUploadButton.addEventListener("click", postArtworkFile.bind(this)); let SettingSubmitting = false; let on = false; let status = localStorage.getItem("sidebar_on") if (status) { if (status === "true" && window.innerWidth > 640) { on = true; } } else if (window.innerWidth > 1600) { on = true; } if (on) openSidebar(); if (document.cookie.includes("_trpic_user")) { deleteBtn.style.display = "flex"; sidebarImageSetting.style.display = "block"; } previewImg.style.opacity = 0; prevBtn.style.opacity = 0; nextBtn.style.opacity = 0; showImage(); function postArtworkFile() { const link = sidebarImageSettingUploadURLInput.value; const files = sidebarImageSettingUploadFileButton.files; if (link == "" && files.length == 0) { alert("请填入链接或文件"); return; } SettingSubmitting = true; sidebarImageSettingUploadButton.style.backgroundColor = "rgb(50, 50, 50)"; if (link) { const formData = new FormData(); formData.append("link", link); fetch(`/api/artworks/${data.artwork.id}/files`, { method: "POST", body: formData, }) .then(res => { res.json().then(resjson => { switch (res.status) { case 201: data.files = resjson.edges.files; showImage(); break; case 401: alert("提交失败:需要验证"); break; default: alert(`提交失败:${resjson.msg}: ${resjson.error}`); break; } }) }) .finally(() => { sidebarImageSettingUploadButton.style.backgroundColor = "rgb(50, 50, 50)"; sidebarImageSettingUploadURLInput.value = ""; sidebarImageSettingUploadFileButton.value = ""; SettingSubmitting = false; }) .catch(error => { alert(`请求失败: ${error}`); }); } else { for (const file of files) { const formData = new FormData(); formData.append("file", file); fetch(`/api/artworks/${data.artwork.id}/files`, { method: "POST", body: formData, }) .then(res => { res.json().then(resjson => { switch (res.status) { case 201: data.files = resjson.edges.files; showImage(); break; case 401: alert("提交失败:需要验证"); break; default: alert(`提交失败:${resjson.msg}: ${resjson.error}`); break; } }) }) .finally(() => { sidebarImageSettingUploadButton.style.backgroundColor = "rgb(50, 50, 50)"; sidebarImageSettingUploadURLInput.value = ""; sidebarImageSettingUploadFileButton.value = ""; SettingSubmitting = false; }) .catch(error => { alert(`请求失败: ${error}`); }); } } } function postSource() { const link = sidebarImageSettingPostInput.value; if (link == "" || SettingSubmitting == true) return; SettingSubmitting = true; sidebarImageSettingPostButton.style.backgroundColor = "rgb(50, 80, 80)"; const formData = new FormData(); formData.append("link", link); fetch(`/api/artworks/${data.artwork.id}/posts`, { method: "POST", body: formData, }) .then(res => { res.json().then(resjson => { switch (res.status) { case 200: data.posts = resjson.edges.posts; data.socials = resjson.edges.socials; data.tags = resjson.edges.tags; updateInfoBar(); break; case 401: alert("提交失败:需要验证"); break; case 409: if (confirm("此来源已链接到另一个作品,前往查看?")) { window.location.href = `/artworks/${resjson.artwork_id}`; } break; default: alert(`提交失败:${resjson.msg}: ${resjson.error}`); break; } }) }) .finally(() => { sidebarImageSettingPostButton.style.backgroundColor = "rgb(50, 50, 50)"; sidebarImageSettingPostInput.value = ""; SettingSubmitting = false; }) .catch(error => { alert(`请求失败: ${error}`); }); } function patchArtwork() { if (SettingSubmitting) return; SettingSubmitting = true; let Svalue = sidebarImageSettingSselect.value; if (Svalue < 0 || Svalue > 5) { SettingSubmitting = false; return; } let Pvalue = sidebarImageSettingPselect.value; const formData = new FormData(); formData.append("private", Pvalue); formData.append("sensitive", Svalue); sidebarImageSettingButton.style.backgroundColor = "rgb(50, 80, 80)"; fetch(`/api/artworks/${data.artwork.id}`, { method: "PATCH", body: formData, }) .then(res => { res.json().then(resjson => { switch (res.status) { case 200: data.artwork.sensitive = resjson.sensitive ? resjson.sensitive : 0; data.artwork.private = resjson.private ? true : false; updateInfoBar(); break; case 401: alert("提交失败:需要验证"); break; default: alert(`提交失败:${resjson.msg}: ${resjson.error}`); break; } }) }) .finally(() => { SettingSubmitting = false; setInterval(() => { sidebarImageSettingButton.style.backgroundColor = "rgb(50, 50, 50)"; }, 1000) }) .catch(error => { alert(`请求失败: ${error}`); }); } function showImage() { previewImg.style.opacity = 1; resetTransform(); let image = data.files[index]; previewImg.src = `/files/${image.id_str}.${image.ext}`; updateNavButtons(); updateInfoBar(); previewImg.onerror = () => { alert("Failed to load image"); }; } function showPrevImage() { if (index > 0) { index = index - 1; showImage(); } } function showNextImage() { if (index < data.files.length - 1) { index = index + 1; showImage(); } } function handleKeyDown(e) { if (document.activeElement === sidebarImageSettingPostInput || document.activeElement === sidebarImageSettingUploadURLInput) return; switch (e.key) { case "Escape": if (sidebar.classList.contains("open")) { closeSidebar(); } else { window.history.back(); } break; case "ArrowRight": showNextImage(); break; case "ArrowLeft": showPrevImage(); break; case "b": if (e.ctrlKey) { toggleSidebar(); } break; } } function handleWheel(e) { e.preventDefault(); const delta = e.deltaY > 0 ? currentScale < 2 ? -0.1 : -0.2 : currentScale < 2 ? 0.1 : 0.2; const newScale = parseFloat((currentScale + delta).toFixed(10)); if (newScale >= minScale && newScale <= maxScale) { const ratio = newScale / currentScale; const rect = imageWrapper.getBoundingClientRect(); const offsetX = e.clientX - (rect.left + rect.width / 2); const offsetY = e.clientY - (rect.top + rect.height / 2); currentX = offsetX * (1 - ratio) + currentX * ratio; currentY = offsetY * (1 - ratio) + currentY * ratio; currentScale = newScale; updateImageTransform(); } } function startDrag(e) { if (e.button !== 0) return; e.preventDefault(); e.stopPropagation(); isDragging = true; startX = e.clientX - currentX; startY = e.clientY - currentY; imageWrapper.classList.add("dragging"); bindDragEvents(); } function doDrag(e) { if (!isDragging) return; e.preventDefault(); e.stopPropagation(); currentX = e.clientX - startX; currentY = e.clientY - startY; updateImageTransform(); } function endDrag(e) { if (!isDragging) return; if (e) e.stopPropagation(); isDragging = false; imageWrapper.classList.remove("dragging"); unbindDragEvents(); } function bindDragEvents() { imageWrapper.addEventListener("mousemove", doDrag.bind(this)); imageWrapper.addEventListener("mouseup", endDrag.bind(this)); imageWrapper.addEventListener("mouseleave", endDrag.bind(this)); } function unbindDragEvents() { imageWrapper.removeEventListener("mousemove", doDrag.bind(this)); imageWrapper.removeEventListener("mouseup", endDrag.bind(this)); imageWrapper.removeEventListener("mouseleave", endDrag.bind(this)); } function resetTransform() { currentScale = 1.0; currentX = 0; currentY = 0; updateImageTransform(); } function updateImageTransform() { previewImg.style.transform = `translate(${currentX}px, ${currentY}px) scale(${currentScale})`; } function updateNavButtons() { prevBtn.style.opacity = index <= 0 ? "0" : "1"; prevBtn.style.pointerEvents = index <= 0 ? "none" : "auto"; nextBtn.style.opacity = index >= data.files.length - 1 ? "0" : "1"; nextBtn.style.pointerEvents = index >= data.files.length - 1 ? "none" : "auto"; history.replaceState(null, null, `/artworks/${data.artwork.id}${index > 0 ? `/photo/${index}` : ""}`); } function updateInfoBar() { sidebarImageAuthor.replaceChildren(); data.socials.forEach((social, index) => { const author = document.createElement("p"); if (index > 0) { author.style.borderTop = "1px solid #777"; } const authorLink = document.createElement("a"); authorLink.textContent = `${social.platform}/${social.display_name}`; authorLink.href = social.url; authorLink.target = "_blank"; author.appendChild(authorLink); sidebarImageAuthor.appendChild(author); }); sidebarImageSource.replaceChildren(); data.posts.forEach((post, index) => { const source = document.createElement("div"); if (index > 0) { source.style.borderTop = "1px solid #777"; } const link = document.createElement("a"); link.textContent = `${post.platform}/${post.post_id}`; link.href = post.url; link.target = "_blank"; source.appendChild(link); if (post.time) { const time = document.createElement("p"); time.classList.add("time"); time.textContent = post.time.substring(0, 19); source.appendChild(time); } if (post.title) { const title = document.createElement("p"); title.textContent = post.title; source.appendChild(title); } if (post.text) { const text = document.createElement("p"); text.classList.add("desc"); text.textContent = post.text; // text.innerHTML = post.text; source.appendChild(text); } sidebarImageSource.appendChild(source); }) sidebarImageTag.replaceChildren(); data.tags.forEach(tag => { const tagLink = document.createElement("a"); tagLink.textContent = `#${tag.text}`; tagLink.href = `/artworks/?tags=${tag.id}`; tagLink.target = "_blank"; sidebarImageTag.appendChild(tagLink); }) const file = data.files[index]; const fileRes = document.createElement("p"); fileRes.textContent = `${file.width}*${file.height}px`; const fileInfo = document.createElement("p"); fileInfo.textContent = `${file.size_str} · ${file.ext}`; const id = document.createElement("p"); id.id = "img-id"; id.textContent = `${file.id_str} L${data.artwork.sensitive} ${data.artwork.private ? " (private)" : ""}`; sidebarImageInfo.replaceChildren(fileRes, fileInfo, id); if (document.cookie.includes("_trpic_user")) { const a = document.createElement("a"); a.href = `/convert/${file.id_str}`; a.target = "_blank"; a.textContent = `${file.quality ? `${file.quality}% ` : ""}Convert`; sidebarImageInfo.appendChild(a); } sidebarImageSettingSselect.value = data.artwork.sensitive ? data.artwork.sensitive : 0; sidebarImageSettingPselect.value = Boolean(data.artwork.private); sidebarImageSettingPselect.style.backgroundColor = data.artwork.private ? "rgb(80, 70, 50)" : "rgb(50, 80, 50)"; } function openSidebar() { localStorage.setItem("sidebar_on", true); sidebar.style.width = "30vw"; sidebar.classList.add("open"); navControls.classList.add("min-hide"); sidebarBtn.style.backgroundColor = "rgba(105, 105, 105, 0.3)"; } function closeSidebar() { localStorage.setItem("sidebar_on", false); sidebar.style.width = "0px"; sidebar.classList.remove("open"); navControls.classList.remove("min-hide"); sidebarBtn.style.backgroundColor = ""; } function toggleSidebar() { if (sidebar.classList.contains("open")) { closeSidebar(); } else { openSidebar(); } } function downloadImage() { const file = data.files[index]; const post = data.posts[0]; const link = document.createElement("a"); link.download = `${post.platform}_${post.post_id}_trpic_${file.id_str}.${file.ext}`; link.href = `/files/${file.id_str}.${file.ext}`; link.click(); link.remove(); } function deleteImage() { const file = data.files[index]; if (!confirm(`Are you sure you want to delete this ${file.id_str} image?`)) return; fetch(`/api/files/${file.id_str}`, { method: "DELETE", }) .then((response) => { if (response.ok) { data.files.splice(index, 1); index = 0; showImage(); } else { response.json().then((resjson) => { if (resjson.code == 403) { if (confirm(`This is last image of this artwork, are you want to delete this artwork?`)) deleteArtwork(); return } alert(`Failed to delete image: ${resjson.msg}`); }); } }) .catch((err) => { console.error(err); alert(`Failed to delete image: ${err}`); }); } function deleteArtwork() { fetch(`/api/artworks/${data.artwork.id}`, { method: "DELETE", }) .then((response) => { if (response.ok) { window.location.href = "/"; } else { response.json().then((resjson) => { alert(`Failed to delete artwork: ${resjson.msg}`); }); } }) .catch((err) => { console.error(err); alert(`Failed to delete artwork: ${err}`); }); } });