From f81babd3302609379ac551fd11e46c544268af6b Mon Sep 17 00:00:00 2001 From: pythonSmall-Q Date: Sat, 7 Mar 2026 09:07:48 +0800 Subject: [PATCH 01/29] Add ImageEnlarger feature with modal viewer Introduce an ImageEnlarger utility: add a toggle option to the feature list and implement a modal image viewer with CSS, toolbar (zoom in/out, reset, save), keyboard shortcuts, and click-to-open behavior. Images are annotated with a preview class and the feature ignores gravatar/cravatar sources; it applies to existing and dynamically added images via a MutationObserver. Errors are logged and surface a DebugMode alert when enabled. --- XMOJ.user.js | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) diff --git a/XMOJ.user.js b/XMOJ.user.js index 4c9d072c..d9eb2e9e 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -2121,6 +2121,8 @@ async function main() { }, {"ID": "CompareSource", "Type": "A", "Name": "比较代码"}, { "ID": "BBSPopup", "Type": "A", "Name": "讨论提醒" }, {"ID": "MessagePopup", "Type": "A", "Name": "短消息提醒"}, { + "ID": "ImageEnlarger", "Type": "A", "Name": "图片放大功能" + }, { "ID": "DebugMode", "Type": "A", "Name": "调试模式(仅供开发者使用)" }, { "ID": "SuperDebug", "Type": "A", "Name": "本地调试模式(仅供开发者使用) (未经授权的擅自开启将导致大部分功能不可用!)" @@ -5572,6 +5574,275 @@ int main() } } } + + // Image Enlargement Feature + if (UtilityEnabled("ImageEnlarger")) { + try { + // Add CSS styles for the enlarger + let EnlargerStyle = document.createElement("style"); + EnlargerStyle.textContent = ` + .xmoj-image-preview { + cursor: pointer; + } + + .xmoj-image-preview:hover { + opacity: 0.8; + transition: opacity 0.2s ease; + } + + .xmoj-image-preview::after { + content: "点击放大"; + position: absolute; + background-color: rgba(0, 0, 0, 0.7); + color: white; + padding: 5px 10px; + border-radius: 3px; + font-size: 12px; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s ease; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + .xmoj-image-preview:hover::after { + opacity: 1; + } + + .xmoj-image-modal { + display: none; + position: fixed; + z-index: 2000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.9); + } + + .xmoj-image-modal.show { + display: flex; + flex-direction: column; + } + + .xmoj-image-modal-content { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: auto; + } + + .xmoj-image-modal-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; + } + + .xmoj-image-modal-toolbar { + display: flex; + justify-content: center; + gap: 10px; + padding: 15px; + background-color: rgba(0, 0, 0, 0.5); + flex-wrap: wrap; + } + + .xmoj-image-modal-toolbar button { + padding: 8px 16px; + background-color: #0d6efd; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.2s ease; + } + + .xmoj-image-modal-toolbar button:hover { + background-color: #0b5ed7; + } + + .xmoj-image-modal-toolbar button:active { + background-color: #0a58ca; + } + + .xmoj-image-modal-close { + position: absolute; + top: 20px; + right: 30px; + color: white; + font-size: 40px; + font-weight: bold; + cursor: pointer; + transition: color 0.2s ease; + } + + .xmoj-image-modal-close:hover { + color: #ccc; + } + `; + document.head.appendChild(EnlargerStyle); + + // Create modal element + let ImageModal = document.createElement("div"); + ImageModal.className = "xmoj-image-modal"; + ImageModal.id = "xmoj-image-modal"; + + let CloseButton = document.createElement("span"); + CloseButton.className = "xmoj-image-modal-close"; + CloseButton.innerHTML = "×"; + ImageModal.appendChild(CloseButton); + + let ModalContent = document.createElement("div"); + ModalContent.className = "xmoj-image-modal-content"; + let ModalImage = document.createElement("img"); + ModalImage.className = "xmoj-image-modal-image"; + ModalContent.appendChild(ModalImage); + ImageModal.appendChild(ModalContent); + + let Toolbar = document.createElement("div"); + Toolbar.className = "xmoj-image-modal-toolbar"; + + let ZoomInBtn = document.createElement("button"); + ZoomInBtn.innerHTML = "放大 (+)"; + ZoomInBtn.type = "button"; + Toolbar.appendChild(ZoomInBtn); + + let ZoomOutBtn = document.createElement("button"); + ZoomOutBtn.innerHTML = "缩小 (-)"; + ZoomOutBtn.type = "button"; + Toolbar.appendChild(ZoomOutBtn); + + let ResetZoomBtn = document.createElement("button"); + ResetZoomBtn.innerHTML = "重置大小"; + ResetZoomBtn.type = "button"; + Toolbar.appendChild(ResetZoomBtn); + + let SaveBtn = document.createElement("button"); + SaveBtn.innerHTML = "保存图片"; + SaveBtn.type = "button"; + Toolbar.appendChild(SaveBtn); + + ImageModal.appendChild(Toolbar); + document.body.appendChild(ImageModal); + + // Zoom level state + let CurrentZoom = 1; + const ZoomStep = 0.1; + const MinZoom = 0.1; + const MaxZoom = 5; + + // Function to update image size + let UpdateImageSize = () => { + ModalImage.style.transform = `scale(${CurrentZoom})`; + ModalImage.style.transition = "transform 0.2s ease"; + }; + + // Function to open modal + let OpenImageModal = (imgSrc) => { + CurrentZoom = 1; + ModalImage.src = imgSrc; + ImageModal.classList.add("show"); + UpdateImageSize(); + }; + + // Function to close modal + let CloseImageModal = () => { + ImageModal.classList.remove("show"); + }; + + // Close button click + CloseButton.addEventListener("click", CloseImageModal); + + // Close when clicking outside the image + ImageModal.addEventListener("click", (e) => { + if (e.target === ImageModal || e.target === ModalContent) { + CloseImageModal(); + } + }); + + // Keyboard shortcuts + document.addEventListener("keydown", (e) => { + if (ImageModal.classList.contains("show")) { + if (e.key === "Escape") { + CloseImageModal(); + } else if (e.key === "+") { + ZoomInBtn.click(); + } else if (e.key === "-") { + ZoomOutBtn.click(); + } + } + }); + + // Zoom controls + ZoomInBtn.addEventListener("click", () => { + CurrentZoom = Math.min(CurrentZoom + ZoomStep, MaxZoom); + UpdateImageSize(); + }); + + ZoomOutBtn.addEventListener("click", () => { + CurrentZoom = Math.max(CurrentZoom - ZoomStep, MinZoom); + UpdateImageSize(); + }); + + ResetZoomBtn.addEventListener("click", () => { + CurrentZoom = 1; + UpdateImageSize(); + }); + + // Save/Download image + SaveBtn.addEventListener("click", () => { + let Link = document.createElement("a"); + Link.href = ModalImage.src; + Link.download = ModalImage.src.split("/").pop() || "image.png"; + document.body.appendChild(Link); + Link.click(); + document.body.removeChild(Link); + }); + + // Apply to all images on the page + let ApplyEnlargerToImages = () => { + let Images = document.querySelectorAll("img"); + Images.forEach((img) => { + if (!img.classList.contains("xmoj-image-preview") && + !img.parentElement.classList.contains("xmoj-image-modal") && + img.src && + !img.src.includes("gravatar") && + !img.src.includes("cravatar")) { + + img.classList.add("xmoj-image-preview"); + img.style.position = "relative"; + img.addEventListener("click", (e) => { + e.stopPropagation(); + OpenImageModal(img.src); + }); + } + }); + }; + + // Apply to existing images + ApplyEnlargerToImages(); + + // Apply to dynamically added images + let Observer = new MutationObserver((mutations) => { + ApplyEnlargerToImages(); + }); + + Observer.observe(document.body, { + childList: true, + subtree: true + }); + + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } } catch (e) { console.error(e); if (UtilityEnabled("DebugMode")) { From a13e96430c18d9075b23e221fe6c1cb0531f3b53 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Mar 2026 01:09:17 +0000 Subject: [PATCH 02/29] 3.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33385212..9ffb1aa4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.3.0", + "version": "3.3.1", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": { From 35a234617139641c63b81dd533e119e90e8a7db6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Mar 2026 01:09:23 +0000 Subject: [PATCH 03/29] Update version info to 3.3.1 --- Update.json | 11 +++++++++++ XMOJ.user.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 38e5a669..6964284d 100644 --- a/Update.json +++ b/Update.json @@ -3422,6 +3422,17 @@ } ], "Notes": "Bug 修复
\n- 修复了暗色模式下比赛排名表(contestrank-oi.php 和 contestrank-correct.php)颜色显示异常的问题(#916)
\n- 修复了 WebSocket 弹窗通知未遵循各功能独立弹窗开关(BBSPopup/MessagePopup)的问题(#919)" + }, + "3.3.1": { + "UpdateDate": 1772845758104, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 924, + "Description": "Add ImageEnlarger feature with modal viewer" + } + ], + "Notes": "No release notes were provided for this release." } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index d9eb2e9e..b2f990ed 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.3.0 +// @version 3.3.1 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen From e6653cc6d5ec49a64341460c72b3db35ac80f415 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 01:19:08 +0000 Subject: [PATCH 04/29] Initial plan From c1e52467a9728ae044e41145b86ae07d71ff1646 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 01:22:23 +0000 Subject: [PATCH 05/29] Address review feedback: fix accessibility, modal exclusion, currentSrc, and MutationObserver performance Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com> --- XMOJ.user.js | 69 +++++++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index b2f990ed..b4dfc714 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -5590,26 +5590,6 @@ int main() transition: opacity 0.2s ease; } - .xmoj-image-preview::after { - content: "点击放大"; - position: absolute; - background-color: rgba(0, 0, 0, 0.7); - color: white; - padding: 5px 10px; - border-radius: 3px; - font-size: 12px; - white-space: nowrap; - pointer-events: none; - opacity: 0; - transition: opacity 0.2s ease; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - - .xmoj-image-preview:hover::after { - opacity: 1; - } .xmoj-image-modal { display: none; @@ -5691,8 +5671,11 @@ int main() ImageModal.className = "xmoj-image-modal"; ImageModal.id = "xmoj-image-modal"; - let CloseButton = document.createElement("span"); + let CloseButton = document.createElement("button"); CloseButton.className = "xmoj-image-modal-close"; + CloseButton.type = "button"; + CloseButton.setAttribute("aria-label", "关闭图片"); + CloseButton.title = "关闭图片"; CloseButton.innerHTML = "×"; ImageModal.appendChild(CloseButton); @@ -5804,23 +5787,24 @@ int main() }); // Apply to all images on the page + let ApplyEnlargerToImage = (img) => { + if (!img.classList.contains("xmoj-image-preview") && + !img.closest(".xmoj-image-modal") && + img.src && + !img.src.includes("gravatar") && + !img.src.includes("cravatar")) { + + img.classList.add("xmoj-image-preview"); + img.title = "点击放大"; + img.addEventListener("click", (e) => { + e.stopPropagation(); + OpenImageModal(img.currentSrc || img.src); + }); + } + }; + let ApplyEnlargerToImages = () => { - let Images = document.querySelectorAll("img"); - Images.forEach((img) => { - if (!img.classList.contains("xmoj-image-preview") && - !img.parentElement.classList.contains("xmoj-image-modal") && - img.src && - !img.src.includes("gravatar") && - !img.src.includes("cravatar")) { - - img.classList.add("xmoj-image-preview"); - img.style.position = "relative"; - img.addEventListener("click", (e) => { - e.stopPropagation(); - OpenImageModal(img.src); - }); - } - }); + document.querySelectorAll("img").forEach(ApplyEnlargerToImage); }; // Apply to existing images @@ -5828,7 +5812,16 @@ int main() // Apply to dynamically added images let Observer = new MutationObserver((mutations) => { - ApplyEnlargerToImages(); + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType !== Node.ELEMENT_NODE) return; + if (node.tagName === "IMG") { + ApplyEnlargerToImage(node); + } else { + node.querySelectorAll("img").forEach(ApplyEnlargerToImage); + } + }); + }); }); Observer.observe(document.body, { From 9b68c78f59c4dc775badfe5894ebad2383ba6398 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 01:26:34 +0000 Subject: [PATCH 06/29] Fix close button background: reset browser default button styles Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com> --- XMOJ.user.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/XMOJ.user.js b/XMOJ.user.js index b4dfc714..b5f9d6a5 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -5654,6 +5654,10 @@ int main() top: 20px; right: 30px; color: white; + background: none; + border: none; + padding: 0; + line-height: 1; font-size: 40px; font-weight: bold; cursor: pointer; From 2b8330db5800892ec2ffb23818020c2914a5238c Mon Sep 17 00:00:00 2001 From: Shan Wenxiao Date: Sat, 7 Mar 2026 09:35:05 +0800 Subject: [PATCH 07/29] Update XMOJ.user.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Shan Wenxiao --- XMOJ.user.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index b5f9d6a5..203b627e 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -5792,17 +5792,18 @@ int main() // Apply to all images on the page let ApplyEnlargerToImage = (img) => { - if (!img.classList.contains("xmoj-image-preview") && + const effectiveSrc = img.currentSrc || img.src; + if (!img.classList.contains("xmoj-image-preview") && !img.closest(".xmoj-image-modal") && - img.src && - !img.src.includes("gravatar") && - !img.src.includes("cravatar")) { - + effectiveSrc && + !effectiveSrc.includes("gravatar") && + !effectiveSrc.includes("cravatar")) { + img.classList.add("xmoj-image-preview"); img.title = "点击放大"; img.addEventListener("click", (e) => { e.stopPropagation(); - OpenImageModal(img.currentSrc || img.src); + OpenImageModal(effectiveSrc); }); } }; From 259cc7ae833c8c3f31f5a0fd9ae22785ce23c623 Mon Sep 17 00:00:00 2001 From: Shan Wenxiao Date: Sat, 7 Mar 2026 09:35:20 +0800 Subject: [PATCH 08/29] Update XMOJ.user.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Shan Wenxiao --- XMOJ.user.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 203b627e..f4459add 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -5800,7 +5800,9 @@ int main() !effectiveSrc.includes("cravatar")) { img.classList.add("xmoj-image-preview"); - img.title = "点击放大"; + if (!img.title) { + img.title = "点击放大"; + } img.addEventListener("click", (e) => { e.stopPropagation(); OpenImageModal(effectiveSrc); From 7f74188ab051766fa0fbaa883ad6050a9e3923f6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Mar 2026 01:35:56 +0000 Subject: [PATCH 09/29] Update time and description of 3.3.1 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 6964284d..171a1fb4 100644 --- a/Update.json +++ b/Update.json @@ -3424,7 +3424,7 @@ "Notes": "Bug 修复
\n- 修复了暗色模式下比赛排名表(contestrank-oi.php 和 contestrank-correct.php)颜色显示异常的问题(#916)
\n- 修复了 WebSocket 弹窗通知未遵循各功能独立弹窗开关(BBSPopup/MessagePopup)的问题(#919)" }, "3.3.1": { - "UpdateDate": 1772845758104, + "UpdateDate": 1772847350873, "Prerelease": true, "UpdateContents": [ { From 5c6daab8ad7ba732cfb5eab1abb3c05e5ec3458f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:20:52 +0000 Subject: [PATCH 10/29] Initial plan From b5945cfd2ffab7ed43818570f050ce8dd046b11a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:27:11 +0000 Subject: [PATCH 11/29] Plan: add pan support to ImageEnlarger Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com> --- XMOJ.user.js | 160 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 148 insertions(+), 12 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index f4459add..f0b4a62b 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -5613,6 +5613,7 @@ int main() align-items: center; justify-content: center; overflow: auto; + position: relative; } .xmoj-image-modal-image { @@ -5662,11 +5663,46 @@ int main() font-weight: bold; cursor: pointer; transition: color 0.2s ease; + z-index: 1; } .xmoj-image-modal-close:hover { color: #ccc; } + + .xmoj-image-modal-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: rgba(0, 0, 0, 0.5); + color: white; + border: none; + padding: 20px 12px; + cursor: pointer; + font-size: 28px; + transition: background-color 0.2s ease; + user-select: none; + -webkit-user-select: none; + } + + .xmoj-image-modal-nav:hover { + background: rgba(0, 0, 0, 0.8); + } + + .xmoj-image-modal-nav:disabled { + opacity: 0.3; + cursor: default; + } + + .xmoj-image-modal-nav-prev { + left: 0; + border-radius: 0 4px 4px 0; + } + + .xmoj-image-modal-nav-next { + right: 0; + border-radius: 4px 0 0 4px; + } `; document.head.appendChild(EnlargerStyle); @@ -5685,6 +5721,21 @@ int main() let ModalContent = document.createElement("div"); ModalContent.className = "xmoj-image-modal-content"; + + let PrevBtn = document.createElement("button"); + PrevBtn.className = "xmoj-image-modal-nav xmoj-image-modal-nav-prev"; + PrevBtn.type = "button"; + PrevBtn.setAttribute("aria-label", "上一张"); + PrevBtn.innerHTML = "❮"; + ModalContent.appendChild(PrevBtn); + + let NextBtn = document.createElement("button"); + NextBtn.className = "xmoj-image-modal-nav xmoj-image-modal-nav-next"; + NextBtn.type = "button"; + NextBtn.setAttribute("aria-label", "下一张"); + NextBtn.innerHTML = "❯"; + ModalContent.appendChild(NextBtn); + let ModalImage = document.createElement("img"); ModalImage.className = "xmoj-image-modal-image"; ModalContent.appendChild(ModalImage); @@ -5716,11 +5767,15 @@ int main() ImageModal.appendChild(Toolbar); document.body.appendChild(ImageModal); - // Zoom level state + // Zoom level and navigation state let CurrentZoom = 1; const ZoomStep = 0.1; const MinZoom = 0.1; const MaxZoom = 5; + let ImageList = []; + let CurrentImageIndex = -1; + let TouchStartX = 0; + let TouchStartY = 0; // Function to update image size let UpdateImageSize = () => { @@ -5728,11 +5783,38 @@ int main() ModalImage.style.transition = "transform 0.2s ease"; }; + // Function to update prev/next button state + let UpdateNavButtons = () => { + let HasMultiple = ImageList.length > 1; + PrevBtn.style.display = HasMultiple ? "" : "none"; + NextBtn.style.display = HasMultiple ? "" : "none"; + PrevBtn.disabled = CurrentImageIndex <= 0; + NextBtn.disabled = CurrentImageIndex >= ImageList.length - 1; + }; + + // Function to navigate to a specific image by index + let NavigateTo = (index) => { + if (index < 0 || index >= ImageList.length) return; + CurrentImageIndex = index; + CurrentZoom = 1; + ModalImage.src = ImageList[CurrentImageIndex]; + UpdateNavButtons(); + UpdateImageSize(); + }; + // Function to open modal - let OpenImageModal = (imgSrc) => { + let OpenImageModal = (imgElement) => { + let PreviewImages = [...document.querySelectorAll("img.xmoj-image-preview")]; + ImageList = PreviewImages.map(img => img.currentSrc || img.src).filter(src => src); + CurrentImageIndex = PreviewImages.indexOf(imgElement); + if (CurrentImageIndex === -1) { + ImageList = [(imgElement.currentSrc || imgElement.src)]; + CurrentImageIndex = 0; + } CurrentZoom = 1; - ModalImage.src = imgSrc; + ModalImage.src = ImageList[CurrentImageIndex]; ImageModal.classList.add("show"); + UpdateNavButtons(); UpdateImageSize(); }; @@ -5760,8 +5842,44 @@ int main() ZoomInBtn.click(); } else if (e.key === "-") { ZoomOutBtn.click(); + } else if (e.key === "ArrowLeft") { + NavigateTo(CurrentImageIndex - 1); + } else if (e.key === "ArrowRight") { + NavigateTo(CurrentImageIndex + 1); + } + } + }); + + // Touch swipe for image navigation + ModalContent.addEventListener("touchstart", (e) => { + TouchStartX = e.changedTouches[0].screenX; + TouchStartY = e.changedTouches[0].screenY; + }, { passive: true }); + + ModalContent.addEventListener("touchend", (e) => { + let TouchEndX = e.changedTouches[0].screenX; + let TouchEndY = e.changedTouches[0].screenY; + let DeltaX = TouchEndX - TouchStartX; + let DeltaY = TouchEndY - TouchStartY; + const SwipeThreshold = 50; + if (Math.abs(DeltaX) > SwipeThreshold && Math.abs(DeltaX) > Math.abs(DeltaY)) { + if (DeltaX < 0) { + NavigateTo(CurrentImageIndex + 1); + } else { + NavigateTo(CurrentImageIndex - 1); } } + }, { passive: true }); + + // Navigation button clicks + PrevBtn.addEventListener("click", (e) => { + e.stopPropagation(); + NavigateTo(CurrentImageIndex - 1); + }); + + NextBtn.addEventListener("click", (e) => { + e.stopPropagation(); + NavigateTo(CurrentImageIndex + 1); }); // Zoom controls @@ -5780,14 +5898,32 @@ int main() UpdateImageSize(); }); - // Save/Download image - SaveBtn.addEventListener("click", () => { - let Link = document.createElement("a"); - Link.href = ModalImage.src; - Link.download = ModalImage.src.split("/").pop() || "image.png"; - document.body.appendChild(Link); - Link.click(); - document.body.removeChild(Link); + // Save/Download image using fetch to trigger actual download instead of redirecting + SaveBtn.addEventListener("click", async () => { + let src = ModalImage.src; + let filename = src.split("/").pop().split("?")[0] || "image.png"; + try { + let response = await fetch(src); + let blob = await response.blob(); + saveAs(blob, filename); + } catch (e) { + GM_xmlhttpRequest({ + method: "GET", + url: src, + responseType: "blob", + onload: (resp) => { + saveAs(resp.response, filename); + }, + onerror: () => { + let Link = document.createElement("a"); + Link.href = src; + Link.download = filename; + document.body.appendChild(Link); + Link.click(); + document.body.removeChild(Link); + } + }); + } }); // Apply to all images on the page @@ -5805,7 +5941,7 @@ int main() } img.addEventListener("click", (e) => { e.stopPropagation(); - OpenImageModal(effectiveSrc); + OpenImageModal(img); }); } }; From 279dada58511ef0d1e44a1a994f177c34087a1cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:29:40 +0000 Subject: [PATCH 12/29] Add drag-to-pan for zoomed images in ImageEnlarger modal Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com> --- XMOJ.user.js | 85 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index f0b4a62b..599daeef 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -5612,7 +5612,7 @@ int main() display: flex; align-items: center; justify-content: center; - overflow: auto; + overflow: hidden; position: relative; } @@ -5774,13 +5774,24 @@ int main() const MaxZoom = 5; let ImageList = []; let CurrentImageIndex = -1; + let PanX = 0; + let PanY = 0; + let IsDragging = false; + let DragStartX = 0; + let DragStartY = 0; + let DragStartPanX = 0; + let DragStartPanY = 0; + let IsTouchPanning = false; let TouchStartX = 0; let TouchStartY = 0; + let TouchPanStartPanX = 0; + let TouchPanStartPanY = 0; - // Function to update image size + // Function to update image transform (zoom + pan) let UpdateImageSize = () => { - ModalImage.style.transform = `scale(${CurrentZoom})`; - ModalImage.style.transition = "transform 0.2s ease"; + ModalImage.style.transform = `translate(${PanX}px, ${PanY}px) scale(${CurrentZoom})`; + ModalImage.style.transition = IsDragging ? "none" : "transform 0.2s ease"; + ModalImage.style.cursor = CurrentZoom > 1 ? "grab" : ""; }; // Function to update prev/next button state @@ -5797,6 +5808,8 @@ int main() if (index < 0 || index >= ImageList.length) return; CurrentImageIndex = index; CurrentZoom = 1; + PanX = 0; + PanY = 0; ModalImage.src = ImageList[CurrentImageIndex]; UpdateNavButtons(); UpdateImageSize(); @@ -5812,6 +5825,8 @@ int main() CurrentImageIndex = 0; } CurrentZoom = 1; + PanX = 0; + PanY = 0; ModalImage.src = ImageList[CurrentImageIndex]; ImageModal.classList.add("show"); UpdateNavButtons(); @@ -5850,15 +5865,35 @@ int main() } }); - // Touch swipe for image navigation + // Touch events: pan when zoomed, swipe to navigate when at zoom level 1 ModalContent.addEventListener("touchstart", (e) => { - TouchStartX = e.changedTouches[0].screenX; - TouchStartY = e.changedTouches[0].screenY; + if (e.touches.length !== 1) return; + TouchStartX = e.touches[0].clientX; + TouchStartY = e.touches[0].clientY; + if (CurrentZoom > 1) { + IsTouchPanning = true; + TouchPanStartPanX = PanX; + TouchPanStartPanY = PanY; + } else { + IsTouchPanning = false; + } }, { passive: true }); + ModalContent.addEventListener("touchmove", (e) => { + if (!IsTouchPanning || e.touches.length !== 1) return; + PanX = TouchPanStartPanX + (e.touches[0].clientX - TouchStartX); + PanY = TouchPanStartPanY + (e.touches[0].clientY - TouchStartY); + UpdateImageSize(); + e.preventDefault(); + }, { passive: false }); + ModalContent.addEventListener("touchend", (e) => { - let TouchEndX = e.changedTouches[0].screenX; - let TouchEndY = e.changedTouches[0].screenY; + if (IsTouchPanning) { + IsTouchPanning = false; + return; + } + let TouchEndX = e.changedTouches[0].clientX; + let TouchEndY = e.changedTouches[0].clientY; let DeltaX = TouchEndX - TouchStartX; let DeltaY = TouchEndY - TouchStartY; const SwipeThreshold = 50; @@ -5871,6 +5906,32 @@ int main() } }, { passive: true }); + // Mouse drag to pan when zoomed + ModalImage.addEventListener("mousedown", (e) => { + if (CurrentZoom <= 1) return; + IsDragging = true; + DragStartX = e.clientX; + DragStartY = e.clientY; + DragStartPanX = PanX; + DragStartPanY = PanY; + ModalImage.style.cursor = "grabbing"; + e.preventDefault(); + }); + + document.addEventListener("mousemove", (e) => { + if (!IsDragging) return; + PanX = DragStartPanX + (e.clientX - DragStartX); + PanY = DragStartPanY + (e.clientY - DragStartY); + UpdateImageSize(); + }); + + document.addEventListener("mouseup", () => { + if (IsDragging) { + IsDragging = false; + ModalImage.style.cursor = CurrentZoom > 1 ? "grab" : ""; + } + }); + // Navigation button clicks PrevBtn.addEventListener("click", (e) => { e.stopPropagation(); @@ -5895,15 +5956,19 @@ int main() ResetZoomBtn.addEventListener("click", () => { CurrentZoom = 1; + PanX = 0; + PanY = 0; UpdateImageSize(); }); // Save/Download image using fetch to trigger actual download instead of redirecting SaveBtn.addEventListener("click", async () => { let src = ModalImage.src; - let filename = src.split("/").pop().split("?")[0] || "image.png"; + let urlPath = src.split("?")[0]; + let filename = urlPath.split("/").pop() || "image.png"; try { let response = await fetch(src); + if (!response.ok) throw new Error("Failed to fetch image"); let blob = await response.blob(); saveAs(blob, filename); } catch (e) { From 6f310f392574e215b87df20312c0fc038e2f0242 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 8 Mar 2026 14:40:43 +0000 Subject: [PATCH 13/29] Fix image viewer: drag/pan, mouse wheel zoom, and download Co-authored-by: PythonSmall-Q <106425289+PythonSmall-Q@users.noreply.github.com> --- XMOJ.user.js | 71 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 599daeef..49538eed 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -5791,7 +5791,9 @@ int main() let UpdateImageSize = () => { ModalImage.style.transform = `translate(${PanX}px, ${PanY}px) scale(${CurrentZoom})`; ModalImage.style.transition = IsDragging ? "none" : "transform 0.2s ease"; - ModalImage.style.cursor = CurrentZoom > 1 ? "grab" : ""; + let CursorStyle = CurrentZoom > 1 ? "grab" : ""; + ModalImage.style.cursor = CursorStyle; + ModalContent.style.cursor = CursorStyle; }; // Function to update prev/next button state @@ -5907,14 +5909,16 @@ int main() }, { passive: true }); // Mouse drag to pan when zoomed - ModalImage.addEventListener("mousedown", (e) => { + ModalContent.addEventListener("mousedown", (e) => { if (CurrentZoom <= 1) return; + if (e.target.tagName.toUpperCase() === "BUTTON") return; IsDragging = true; DragStartX = e.clientX; DragStartY = e.clientY; DragStartPanX = PanX; DragStartPanY = PanY; ModalImage.style.cursor = "grabbing"; + ModalContent.style.cursor = "grabbing"; e.preventDefault(); }); @@ -5928,10 +5932,20 @@ int main() document.addEventListener("mouseup", () => { if (IsDragging) { IsDragging = false; - ModalImage.style.cursor = CurrentZoom > 1 ? "grab" : ""; + let CursorStyle = CurrentZoom > 1 ? "grab" : ""; + ModalImage.style.cursor = CursorStyle; + ModalContent.style.cursor = CursorStyle; } }); + // Mouse wheel to zoom in/out + ModalContent.addEventListener("wheel", (e) => { + e.preventDefault(); + let ZoomDelta = e.deltaY > 0 ? -ZoomStep : ZoomStep; + CurrentZoom = Math.max(MinZoom, Math.min(MaxZoom, CurrentZoom + ZoomDelta)); + UpdateImageSize(); + }, { passive: false }); + // Navigation button clicks PrevBtn.addEventListener("click", (e) => { e.stopPropagation(); @@ -5961,34 +5975,35 @@ int main() UpdateImageSize(); }); - // Save/Download image using fetch to trigger actual download instead of redirecting - SaveBtn.addEventListener("click", async () => { + // Save/Download image: fetch via GM_xmlhttpRequest to bypass CORS, then use blob URL for reliable download + SaveBtn.addEventListener("click", () => { let src = ModalImage.src; let urlPath = src.split("?")[0]; let filename = urlPath.split("/").pop() || "image.png"; - try { - let response = await fetch(src); - if (!response.ok) throw new Error("Failed to fetch image"); - let blob = await response.blob(); - saveAs(blob, filename); - } catch (e) { - GM_xmlhttpRequest({ - method: "GET", - url: src, - responseType: "blob", - onload: (resp) => { - saveAs(resp.response, filename); - }, - onerror: () => { - let Link = document.createElement("a"); - Link.href = src; - Link.download = filename; - document.body.appendChild(Link); - Link.click(); - document.body.removeChild(Link); - } - }); - } + GM_xmlhttpRequest({ + method: "GET", + url: src, + responseType: "blob", + onload: (resp) => { + let BlobUrl = URL.createObjectURL(resp.response); + let Link = document.createElement("a"); + Link.href = BlobUrl; + Link.download = filename; + document.body.appendChild(Link); + Link.click(); + document.body.removeChild(Link); + setTimeout(() => URL.revokeObjectURL(BlobUrl), 100); + }, + onerror: () => { + let Link = document.createElement("a"); + Link.href = src; + Link.download = filename; + Link.target = "_blank"; + document.body.appendChild(Link); + Link.click(); + document.body.removeChild(Link); + } + }); }); // Apply to all images on the page From 9dba4034390b91bf526ee08e7ac0413f05eef0fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 8 Mar 2026 14:50:19 +0000 Subject: [PATCH 14/29] Update time and description of 3.3.1 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 171a1fb4..e12d6229 100644 --- a/Update.json +++ b/Update.json @@ -3424,7 +3424,7 @@ "Notes": "Bug 修复
\n- 修复了暗色模式下比赛排名表(contestrank-oi.php 和 contestrank-correct.php)颜色显示异常的问题(#916)
\n- 修复了 WebSocket 弹窗通知未遵循各功能独立弹窗开关(BBSPopup/MessagePopup)的问题(#919)" }, "3.3.1": { - "UpdateDate": 1772847350873, + "UpdateDate": 1772981411290, "Prerelease": true, "UpdateContents": [ { From 7119f81e047f6c127c4c681bbefb808546743563 Mon Sep 17 00:00:00 2001 From: zsTree Date: Sat, 14 Mar 2026 22:37:49 +0800 Subject: [PATCH 15/29] Fix problem switcher not update (#933) * Update Update.json Signed-off-by: Zhu Chenrui * Parse release notes from comment block (cherry picked from commit c7137ff7122b2307b9ea78a24de240b3bd00c6ab) * Update bug.yml Signed-off-by: Zhu Chenrui (cherry picked from commit 07d7590955a2913e422e500613f140f7619ff364) Update feature.yml Signed-off-by: Zhu Chenrui (cherry picked from commit 1a99430f1edf90e782b984b956dcb78efd6e88db) Update docs.yml Signed-off-by: Zhu Chenrui (cherry picked from commit 6017bcf99acdb37c877633c4f13f9851393fa1af) * Update GitHub Actions workflow to skip bot triggers Signed-off-by: Shan Wenxiao * Prevent UpdateVersion from running if last commit was by github-actions[bot] This prevents infinite loops where the bot commits version updates, which triggers the workflow again, causing another commit. Co-Authored-By: Claude Sonnet 4.5 * Allow metadata updates on edited PRs after bot version commit The last-commit-author guard now only exits for non-edited events, so PR title/body changes still update Update.json metadata even when the branch tip is a github-actions[bot] commit. Co-Authored-By: Claude Opus 4.6 * Allow metadata updates on edited PRs after bot version commit Exclude all bot actors (not just github-actions[bot]) from triggering the UpdateVersion workflow, preventing loops from AI code review bots. Allow edited events through the script-level guard so PR title/body changes still update Update.json metadata. Co-Authored-By: Claude Opus 4.6 * Fix Problem Switcher Not Update * Fix Problem Switcher Not Update * 3.3.2 * Update version info to 3.3.2 * Fix Version List (1.999999.0 -> 1.10.0) Signed-off-by: zsTree * Update time and description of 3.3.2 * Fix Problem Switcher Null Problem Signed-off-by: zsTree * Update time and description of 3.3.2 * Fix XSS bug Signed-off-by: zsTree * Update time and description of 3.3.2 --------- Signed-off-by: Zhu Chenrui Signed-off-by: Shan Wenxiao Signed-off-by: zsTree Co-authored-by: Zhu Chenrui Co-authored-by: Shan Wenxiao Co-authored-by: boomzero Co-authored-by: Claude Sonnet 4.5 Co-authored-by: github-actions[bot] --- Update.json | 11 +++++++++++ XMOJ.user.js | 54 ++++++++++++++++++++++++++++++++++------------------ package.json | 2 +- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/Update.json b/Update.json index e12d6229..65325b1f 100644 --- a/Update.json +++ b/Update.json @@ -3433,6 +3433,17 @@ } ], "Notes": "No release notes were provided for this release." + }, + "3.3.2": { + "UpdateDate": 1773495619873, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 933, + "Description": "Fix problem switcher not update" + } + ], + "Notes": "Fix ProblemSwitcher Not Update" } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 49538eed..897238fe 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.3.1 +// @version 3.3.2 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen @@ -533,6 +533,29 @@ let RequestAPI = (Action, Data, CallBack) => { } }; +unsafeWindow.GetContestProblemList = async function(RefreshList) { + try { + const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid")); + const res = await contestReq.text(); + if (contestReq.status === 200 && res.indexOf("比赛尚未开始或私有,不能查看题目。") === -1) { + const parser = new DOMParser(); + const dom = parser.parseFromString(res, "text/html"); + const rows = (dom.querySelector("#problemset > tbody")).rows; + let problemList = []; + for (let i = 0; i < rows.length; i++) { + problemList.push({ + "title": rows[i].children[2].innerText, + "url": rows[i].children[2].children[0].href + }); + } + localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList)); + if (RefreshList) location.reload(); + } + } catch (e) { + console.error(e); + } +} + // WebSocket Notification System let NotificationSocket = null; let NotificationSocketReconnectAttempts = 0; @@ -1551,6 +1574,9 @@ async function main() { display: none !important; } } + .refreshList { + cursor: pointer; + } /* Contain images */ img { @@ -1616,6 +1642,11 @@ async function main() { border: 1px solid var(--bs-secondary-bg); border-top: none; border-radius: 0 0 0.3rem 0.3rem; + } + .refreshList { + cursor: pointer; + color: #6c757d; + text-decoration: none; }`; } if (UtilityEnabled("AddAnimation")) { @@ -2272,22 +2303,8 @@ async function main() { } let ContestProblemList = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList"); if (ContestProblemList == null) { - const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid")); - const res = await contestReq.text(); - if (contestReq.status === 200 && res.indexOf("比赛尚未开始或私有,不能查看题目。") === -1) { - const parser = new DOMParser(); - const dom = parser.parseFromString(res, "text/html"); - const rows = (dom.querySelector("#problemset > tbody")).rows; - let problemList = []; - for (let i = 0; i < rows.length; i++) { - problemList.push({ - "title": rows[i].children[2].innerText, - "url": rows[i].children[2].children[0].href - }); - } - localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList)); - ContestProblemList = JSON.stringify(problemList); - } + await unsafeWindow.GetContestProblemList(false); + ContestProblemList = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList"); } let problemSwitcher = document.createElement("div"); @@ -2310,6 +2327,7 @@ async function main() { problemSwitcher.style.flexDirection = "column"; let problemList = JSON.parse(ContestProblemList); + problemSwitcher.innerHTML += `刷新`; for (let i = 0; i < problemList.length; i++) { let buttonText = ""; if (i < 26) { @@ -6069,4 +6087,4 @@ int main() main().then(r => { console.log("XMOJ-Script loaded successfully!"); -}); \ No newline at end of file +}); diff --git a/package.json b/package.json index 9ffb1aa4..7a191097 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.3.1", + "version": "3.3.2", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": { From 657eb756790440ded1712f1952d862f4a65925f6 Mon Sep 17 00:00:00 2001 From: def-WA2025 Date: Sun, 15 Mar 2026 09:19:15 +0800 Subject: [PATCH 16/29] Display status.php Query Content --- XMOJ.user.js | 141 +++++++++++++++++++++++++++++---------------------- 1 file changed, 79 insertions(+), 62 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 897238fe..0bac56b2 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -2526,16 +2526,28 @@ async function main() { document.title = "提交状态"; document.querySelector("body > script:nth-child(5)").remove(); if (UtilityEnabled("NewBootstrap")) { + const url = window.location.href; + const paramsRegex = /[?&]([^=#]+)=([^&#]*)/g; + let match; + let CurrentProblemId, CurrentLanguage, CurrentJresult; + while ((match = paramsRegex.exec(url)) !== null) { + const [_, key, value] = match; + if (key == 'problem_id') CurrentProblemId = value; + if (key == 'language') CurrentLanguage = value; + if (key == 'jresult') CurrentJresult = value; + } + console.log(CurrentProblemId + '\n' + CurrentLanguage + '\n' + CurrentJresult); + document.querySelector("#simform").outerHTML = `
- +
- + @@ -2561,6 +2573,11 @@ async function main() {
`; + + var selectElement = document.getElementById('language'); + selectElement.value = CurrentLanguage; + selectElement = document.getElementById('jresult'); + selectElement.value = CurrentJresult; } if (UtilityEnabled("ImproveACRate")) { @@ -3298,8 +3315,8 @@ async function main() { } ErrorElement.style.display = "block"; ErrorMessage.style.color = "red"; - ErrorMessage.innerText = "比赛已结束, 正在尝试像题目 " + rPID + " 提交"; - console.log("比赛已结束, 正在尝试像题目 " + rPID + " 提交"); + ErrorMessage.innerText = "比赛已结束, 正在尝试向题目 " + rPID + " 提交"; + console.log("比赛已结束, 正在尝试向题目 " + rPID + " 提交"); let o2Switch = "&enable_O2=on"; if (!document.querySelector("#enable_O2").checked) o2Switch = ""; await fetch("https://www.xmoj.tech/submit.php", { @@ -5602,13 +5619,13 @@ int main() .xmoj-image-preview { cursor: pointer; } - + .xmoj-image-preview:hover { opacity: 0.8; transition: opacity 0.2s ease; } - - + + .xmoj-image-modal { display: none; position: fixed; @@ -5619,12 +5636,12 @@ int main() height: 100%; background-color: rgba(0, 0, 0, 0.9); } - + .xmoj-image-modal.show { display: flex; flex-direction: column; } - + .xmoj-image-modal-content { flex: 1; display: flex; @@ -5633,13 +5650,13 @@ int main() overflow: hidden; position: relative; } - + .xmoj-image-modal-image { max-width: 100%; max-height: 100%; object-fit: contain; } - + .xmoj-image-modal-toolbar { display: flex; justify-content: center; @@ -5648,7 +5665,7 @@ int main() background-color: rgba(0, 0, 0, 0.5); flex-wrap: wrap; } - + .xmoj-image-modal-toolbar button { padding: 8px 16px; background-color: #0d6efd; @@ -5659,15 +5676,15 @@ int main() font-size: 14px; transition: background-color 0.2s ease; } - + .xmoj-image-modal-toolbar button:hover { background-color: #0b5ed7; } - + .xmoj-image-modal-toolbar button:active { background-color: #0a58ca; } - + .xmoj-image-modal-close { position: absolute; top: 20px; @@ -5683,11 +5700,11 @@ int main() transition: color 0.2s ease; z-index: 1; } - + .xmoj-image-modal-close:hover { color: #ccc; } - + .xmoj-image-modal-nav { position: absolute; top: 50%; @@ -5702,33 +5719,33 @@ int main() user-select: none; -webkit-user-select: none; } - + .xmoj-image-modal-nav:hover { background: rgba(0, 0, 0, 0.8); } - + .xmoj-image-modal-nav:disabled { opacity: 0.3; cursor: default; } - + .xmoj-image-modal-nav-prev { left: 0; border-radius: 0 4px 4px 0; } - + .xmoj-image-modal-nav-next { right: 0; border-radius: 4px 0 0 4px; } `; document.head.appendChild(EnlargerStyle); - + // Create modal element let ImageModal = document.createElement("div"); ImageModal.className = "xmoj-image-modal"; ImageModal.id = "xmoj-image-modal"; - + let CloseButton = document.createElement("button"); CloseButton.className = "xmoj-image-modal-close"; CloseButton.type = "button"; @@ -5736,55 +5753,55 @@ int main() CloseButton.title = "关闭图片"; CloseButton.innerHTML = "×"; ImageModal.appendChild(CloseButton); - + let ModalContent = document.createElement("div"); ModalContent.className = "xmoj-image-modal-content"; - + let PrevBtn = document.createElement("button"); PrevBtn.className = "xmoj-image-modal-nav xmoj-image-modal-nav-prev"; PrevBtn.type = "button"; PrevBtn.setAttribute("aria-label", "上一张"); PrevBtn.innerHTML = "❮"; ModalContent.appendChild(PrevBtn); - + let NextBtn = document.createElement("button"); NextBtn.className = "xmoj-image-modal-nav xmoj-image-modal-nav-next"; NextBtn.type = "button"; NextBtn.setAttribute("aria-label", "下一张"); NextBtn.innerHTML = "❯"; ModalContent.appendChild(NextBtn); - + let ModalImage = document.createElement("img"); ModalImage.className = "xmoj-image-modal-image"; ModalContent.appendChild(ModalImage); ImageModal.appendChild(ModalContent); - + let Toolbar = document.createElement("div"); Toolbar.className = "xmoj-image-modal-toolbar"; - + let ZoomInBtn = document.createElement("button"); ZoomInBtn.innerHTML = "放大 (+)"; ZoomInBtn.type = "button"; Toolbar.appendChild(ZoomInBtn); - + let ZoomOutBtn = document.createElement("button"); ZoomOutBtn.innerHTML = "缩小 (-)"; ZoomOutBtn.type = "button"; Toolbar.appendChild(ZoomOutBtn); - + let ResetZoomBtn = document.createElement("button"); ResetZoomBtn.innerHTML = "重置大小"; ResetZoomBtn.type = "button"; Toolbar.appendChild(ResetZoomBtn); - + let SaveBtn = document.createElement("button"); SaveBtn.innerHTML = "保存图片"; SaveBtn.type = "button"; Toolbar.appendChild(SaveBtn); - + ImageModal.appendChild(Toolbar); document.body.appendChild(ImageModal); - + // Zoom level and navigation state let CurrentZoom = 1; const ZoomStep = 0.1; @@ -5804,7 +5821,7 @@ int main() let TouchStartY = 0; let TouchPanStartPanX = 0; let TouchPanStartPanY = 0; - + // Function to update image transform (zoom + pan) let UpdateImageSize = () => { ModalImage.style.transform = `translate(${PanX}px, ${PanY}px) scale(${CurrentZoom})`; @@ -5813,7 +5830,7 @@ int main() ModalImage.style.cursor = CursorStyle; ModalContent.style.cursor = CursorStyle; }; - + // Function to update prev/next button state let UpdateNavButtons = () => { let HasMultiple = ImageList.length > 1; @@ -5822,7 +5839,7 @@ int main() PrevBtn.disabled = CurrentImageIndex <= 0; NextBtn.disabled = CurrentImageIndex >= ImageList.length - 1; }; - + // Function to navigate to a specific image by index let NavigateTo = (index) => { if (index < 0 || index >= ImageList.length) return; @@ -5834,7 +5851,7 @@ int main() UpdateNavButtons(); UpdateImageSize(); }; - + // Function to open modal let OpenImageModal = (imgElement) => { let PreviewImages = [...document.querySelectorAll("img.xmoj-image-preview")]; @@ -5852,22 +5869,22 @@ int main() UpdateNavButtons(); UpdateImageSize(); }; - + // Function to close modal let CloseImageModal = () => { ImageModal.classList.remove("show"); }; - + // Close button click CloseButton.addEventListener("click", CloseImageModal); - + // Close when clicking outside the image ImageModal.addEventListener("click", (e) => { if (e.target === ImageModal || e.target === ModalContent) { CloseImageModal(); } }); - + // Keyboard shortcuts document.addEventListener("keydown", (e) => { if (ImageModal.classList.contains("show")) { @@ -5884,7 +5901,7 @@ int main() } } }); - + // Touch events: pan when zoomed, swipe to navigate when at zoom level 1 ModalContent.addEventListener("touchstart", (e) => { if (e.touches.length !== 1) return; @@ -5898,7 +5915,7 @@ int main() IsTouchPanning = false; } }, { passive: true }); - + ModalContent.addEventListener("touchmove", (e) => { if (!IsTouchPanning || e.touches.length !== 1) return; PanX = TouchPanStartPanX + (e.touches[0].clientX - TouchStartX); @@ -5906,7 +5923,7 @@ int main() UpdateImageSize(); e.preventDefault(); }, { passive: false }); - + ModalContent.addEventListener("touchend", (e) => { if (IsTouchPanning) { IsTouchPanning = false; @@ -5925,7 +5942,7 @@ int main() } } }, { passive: true }); - + // Mouse drag to pan when zoomed ModalContent.addEventListener("mousedown", (e) => { if (CurrentZoom <= 1) return; @@ -5939,14 +5956,14 @@ int main() ModalContent.style.cursor = "grabbing"; e.preventDefault(); }); - + document.addEventListener("mousemove", (e) => { if (!IsDragging) return; PanX = DragStartPanX + (e.clientX - DragStartX); PanY = DragStartPanY + (e.clientY - DragStartY); UpdateImageSize(); }); - + document.addEventListener("mouseup", () => { if (IsDragging) { IsDragging = false; @@ -5955,7 +5972,7 @@ int main() ModalContent.style.cursor = CursorStyle; } }); - + // Mouse wheel to zoom in/out ModalContent.addEventListener("wheel", (e) => { e.preventDefault(); @@ -5963,36 +5980,36 @@ int main() CurrentZoom = Math.max(MinZoom, Math.min(MaxZoom, CurrentZoom + ZoomDelta)); UpdateImageSize(); }, { passive: false }); - + // Navigation button clicks PrevBtn.addEventListener("click", (e) => { e.stopPropagation(); NavigateTo(CurrentImageIndex - 1); }); - + NextBtn.addEventListener("click", (e) => { e.stopPropagation(); NavigateTo(CurrentImageIndex + 1); }); - + // Zoom controls ZoomInBtn.addEventListener("click", () => { CurrentZoom = Math.min(CurrentZoom + ZoomStep, MaxZoom); UpdateImageSize(); }); - + ZoomOutBtn.addEventListener("click", () => { CurrentZoom = Math.max(CurrentZoom - ZoomStep, MinZoom); UpdateImageSize(); }); - + ResetZoomBtn.addEventListener("click", () => { CurrentZoom = 1; PanX = 0; PanY = 0; UpdateImageSize(); }); - + // Save/Download image: fetch via GM_xmlhttpRequest to bypass CORS, then use blob URL for reliable download SaveBtn.addEventListener("click", () => { let src = ModalImage.src; @@ -6023,7 +6040,7 @@ int main() } }); }); - + // Apply to all images on the page let ApplyEnlargerToImage = (img) => { const effectiveSrc = img.currentSrc || img.src; @@ -6047,10 +6064,10 @@ int main() let ApplyEnlargerToImages = () => { document.querySelectorAll("img").forEach(ApplyEnlargerToImage); }; - + // Apply to existing images ApplyEnlargerToImages(); - + // Apply to dynamically added images let Observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { @@ -6064,12 +6081,12 @@ int main() }); }); }); - + Observer.observe(document.body, { childList: true, subtree: true }); - + } catch (e) { console.error(e); if (UtilityEnabled("DebugMode")) { @@ -6087,4 +6104,4 @@ int main() main().then(r => { console.log("XMOJ-Script loaded successfully!"); -}); +}); \ No newline at end of file From b41dccaf22b184922a1c7ef1474b8a19b2268cda Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 02:39:14 +0000 Subject: [PATCH 17/29] 3.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a191097..a2826d87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.3.2", + "version": "3.3.3", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": { From b9fe815555370999acefba7c2c8d42cfa5ae5e2b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 02:39:20 +0000 Subject: [PATCH 18/29] Update version info to 3.3.3 --- Update.json | 11 +++++++++++ XMOJ.user.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 65325b1f..b27ae8b7 100644 --- a/Update.json +++ b/Update.json @@ -3444,6 +3444,17 @@ } ], "Notes": "Fix ProblemSwitcher Not Update" + }, + "3.3.3": { + "UpdateDate": 1773542354999, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 937, + "Description": "Display status.php Query Content" + } + ], + "Notes": "Display status.php query content." } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 0bac56b2..7e6952c6 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.3.2 +// @version 3.3.3 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen From 129f43427c1015b9156954bcd2a4cfc83e928503 Mon Sep 17 00:00:00 2001 From: zsTree Date: Sun, 15 Mar 2026 10:52:47 +0800 Subject: [PATCH 19/29] Fix XSS bug & Remove irrelevant console logs Signed-off-by: zsTree --- XMOJ.user.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 7e6952c6..b7ad9a10 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.3.3 +// @version 3.3.2 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen @@ -2526,23 +2526,16 @@ async function main() { document.title = "提交状态"; document.querySelector("body > script:nth-child(5)").remove(); if (UtilityEnabled("NewBootstrap")) { - const url = window.location.href; - const paramsRegex = /[?&]([^=#]+)=([^&#]*)/g; - let match; - let CurrentProblemId, CurrentLanguage, CurrentJresult; - while ((match = paramsRegex.exec(url)) !== null) { - const [_, key, value] = match; - if (key == 'problem_id') CurrentProblemId = value; - if (key == 'language') CurrentLanguage = value; - if (key == 'jresult') CurrentJresult = value; - } - console.log(CurrentProblemId + '\n' + CurrentLanguage + '\n' + CurrentJresult); + const params = new URLSearchParams(window.location.search); + const CurrentProblemId = params.get('problem_id'); + const CurrentLanguage = params.get('language'); + const CurrentJresult = params.get('jresult'); document.querySelector("#simform").outerHTML = `
- +
@@ -2574,7 +2567,9 @@ async function main() {
`; - var selectElement = document.getElementById('language'); + var selectElement = document.getElementById('problem_id'); + selectElement.value = CurrentProblemId; + selectElement = document.getElementById('language'); selectElement.value = CurrentLanguage; selectElement = document.getElementById('jresult'); selectElement.value = CurrentJresult; @@ -6104,4 +6099,4 @@ int main() main().then(r => { console.log("XMOJ-Script loaded successfully!"); -}); \ No newline at end of file +}); From 055cb968459fd78e8dfed95d62c0da094e4ce02e Mon Sep 17 00:00:00 2001 From: zsTree Date: Sun, 15 Mar 2026 11:34:25 +0800 Subject: [PATCH 20/29] Determine whether status.php query parameters are valid Signed-off-by: zsTree --- XMOJ.user.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index b7ad9a10..3f99e1d0 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -2526,10 +2526,11 @@ async function main() { document.title = "提交状态"; document.querySelector("body > script:nth-child(5)").remove(); if (UtilityEnabled("NewBootstrap")) { - const params = new URLSearchParams(window.location.search); - const CurrentProblemId = params.get('problem_id'); - const CurrentLanguage = params.get('language'); - const CurrentJresult = params.get('jresult'); + const params = new URL(location.href).searchParams; + let nonDigitPattern = /D/; + let CurrentProblemId = isNaN(params.get("problem_id")) ? "" : params.get("problem_id"); + let CurrentLanguage = isNaN(params.get("language")) || params.get("language") < -1 || params.get("language") > 2 ? "-1" : params.get("language"); + let CurrentJresult = isNaN(params.get("jresult")) || params.get("jresult") < -1 || params.get("jresult") > 11 ? "-1" : params.get("jresult"); document.querySelector("#simform").outerHTML = `
From e221d2d012ee98dd6fd6ba041e1839421f7ed219 Mon Sep 17 00:00:00 2001 From: zsTree Date: Sun, 15 Mar 2026 11:38:53 +0800 Subject: [PATCH 21/29] Bump version to 3.3.3 Signed-off-by: zsTree --- XMOJ.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 3f99e1d0..1ba91077 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.3.2 +// @version 3.3.3 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen From dfe5d27fd1ddcf4d1e7a8e532d4a11a7a9c27d39 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 03:39:15 +0000 Subject: [PATCH 22/29] Update time and description of 3.3.3 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index b27ae8b7..6e1e8af6 100644 --- a/Update.json +++ b/Update.json @@ -3446,7 +3446,7 @@ "Notes": "Fix ProblemSwitcher Not Update" }, "3.3.3": { - "UpdateDate": 1773542354999, + "UpdateDate": 1773545950014, "Prerelease": true, "UpdateContents": [ { From fbff864d31cff46d32ad28d27971d05cceabd183 Mon Sep 17 00:00:00 2001 From: zsTree Date: Sun, 15 Mar 2026 12:57:48 +0800 Subject: [PATCH 23/29] Fixed the issue where certain special content could not be filled Signed-off-by: zsTree --- XMOJ.user.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 1ba91077..3cddb42c 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -2526,11 +2526,19 @@ async function main() { document.title = "提交状态"; document.querySelector("body > script:nth-child(5)").remove(); if (UtilityEnabled("NewBootstrap")) { + var checkNum = function(str) { + var patrn = /^[0-9]{1,20}$/; + var ans = true; + if (!patrn.exec(str)) ans = false; + return ans; + } + const params = new URL(location.href).searchParams; - let nonDigitPattern = /D/; - let CurrentProblemId = isNaN(params.get("problem_id")) ? "" : params.get("problem_id"); - let CurrentLanguage = isNaN(params.get("language")) || params.get("language") < -1 || params.get("language") > 2 ? "-1" : params.get("language"); - let CurrentJresult = isNaN(params.get("jresult")) || params.get("jresult") < -1 || params.get("jresult") > 11 ? "-1" : params.get("jresult"); + let CurrentProblemId = checkNum(params.get("problem_id")) ? Number(params.get("problem_id")) : ""; + let CurrentLanguageParam = params.get("language"); + let CurrentLanguage = checkNum(CurrentLanguageParam) && -1 <= CurrentLanguageParam && CurrentLanguageParam <= 2 ? Number(CurrentLanguageParam) : "-1"; + let CurrentJresultParam = params.get("jresult"); + let CurrentJresult = checkNum(CurrentJresultParam) && -1 <= CurrentJresultParam && CurrentJresultParam <= 11 ? Number(CurrentJresultParam) : "-1"; document.querySelector("#simform").outerHTML = ` From ac131134b5614df88bd223beefad6a4a6034fa82 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 04:58:09 +0000 Subject: [PATCH 24/29] Update time and description of 3.3.3 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 6e1e8af6..63669aa3 100644 --- a/Update.json +++ b/Update.json @@ -3446,7 +3446,7 @@ "Notes": "Fix ProblemSwitcher Not Update" }, "3.3.3": { - "UpdateDate": 1773545950014, + "UpdateDate": 1773550684270, "Prerelease": true, "UpdateContents": [ { From bd16dbbe251e65a5cdf28ac36c133ee6cfce1653 Mon Sep 17 00:00:00 2001 From: boomzero Date: Sun, 15 Mar 2026 15:29:44 +0800 Subject: [PATCH 25/29] fix: gate MonochromeUI-specific styling in contestrank pages behind flag Fixes #932. Header black/white forcing and inline style clearing on contestrank-oi.php and contestrank-correct.php now only apply when MonochromeUI is enabled. Badge ranks, color-coded cells, and auto-refresh remain unconditional as part of the base UI. Co-Authored-By: Claude Sonnet 4.6 --- XMOJ.user.js | 56 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 3cddb42c..20ebce26 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -3043,13 +3043,15 @@ async function main() { HeaderCells[2].innerText = "昵称"; HeaderCells[3].innerText = "AC数"; HeaderCells[4].innerText = "得分"; - for (let j = 0; j < HeaderCells.length; j++) { - HeaderCells[j].removeAttribute("bgcolor"); - HeaderCells[j].style.setProperty("background-color", "black", "important"); - HeaderCells[j].style.setProperty("color", "white", "important"); - let Links = HeaderCells[j].querySelectorAll("a"); - for (let k = 0; k < Links.length; k++) { - Links[k].style.setProperty("color", "white", "important"); + if (UtilityEnabled("MonochromeUI")) { + for (let j = 0; j < HeaderCells.length; j++) { + HeaderCells[j].removeAttribute("bgcolor"); + HeaderCells[j].style.setProperty("background-color", "black", "important"); + HeaderCells[j].style.setProperty("color", "white", "important"); + let Links = HeaderCells[j].querySelectorAll("a"); + for (let k = 0; k < Links.length; k++) { + Links[k].style.setProperty("color", "white", "important"); + } } } let RefreshOIRank = async () => { @@ -3062,7 +3064,9 @@ async function main() { TidyTable(ParsedDocument.getElementById("rank")); let Temp = ParsedDocument.getElementById("rank").rows; for (var i = 1; i < Temp.length; i++) { - Temp[i].style.backgroundColor = ""; + if (UtilityEnabled("MonochromeUI")) { + Temp[i].style.backgroundColor = ""; + } let MetalCell = Temp[i].cells[0]; let Metal = document.createElement("span"); Metal.innerText = MetalCell.innerText; @@ -3072,9 +3076,11 @@ async function main() { GetUsernameHTML(Temp[i].cells[1], Temp[i].cells[1].innerText); Temp[i].cells[2].innerHTML = Temp[i].cells[2].innerText; Temp[i].cells[3].innerHTML = Temp[i].cells[3].innerText; - for (let j = 0; j < 5 && j < Temp[i].cells.length; j++) { - Temp[i].cells[j].style.backgroundColor = ""; - Temp[i].cells[j].style.color = ""; + if (UtilityEnabled("MonochromeUI")) { + for (let j = 0; j < 5 && j < Temp[i].cells.length; j++) { + Temp[i].cells[j].style.backgroundColor = ""; + Temp[i].cells[j].style.color = ""; + } } for (let j = 5; j < Temp[i].cells.length; j++) { let InnerText = Temp[i].cells[j].innerText; @@ -3144,13 +3150,15 @@ async function main() { HeaderCells[2].innerText = "昵称"; HeaderCells[3].innerText = "AC数"; HeaderCells[4].innerText = "得分"; - for (let j = 0; j < HeaderCells.length; j++) { - HeaderCells[j].removeAttribute("bgcolor"); - HeaderCells[j].style.setProperty("background-color", "black", "important"); - HeaderCells[j].style.setProperty("color", "white", "important"); - let Links = HeaderCells[j].querySelectorAll("a"); - for (let k = 0; k < Links.length; k++) { - Links[k].style.setProperty("color", "white", "important"); + if (UtilityEnabled("MonochromeUI")) { + for (let j = 0; j < HeaderCells.length; j++) { + HeaderCells[j].removeAttribute("bgcolor"); + HeaderCells[j].style.setProperty("background-color", "black", "important"); + HeaderCells[j].style.setProperty("color", "white", "important"); + let Links = HeaderCells[j].querySelectorAll("a"); + for (let k = 0; k < Links.length; k++) { + Links[k].style.setProperty("color", "white", "important"); + } } } let RefreshCorrectRank = async () => { @@ -3163,7 +3171,9 @@ async function main() { TidyTable(ParsedDocument.getElementById("rank")); let Temp = ParsedDocument.getElementById("rank").rows; for (var i = 1; i < Temp.length; i++) { - Temp[i].style.backgroundColor = ""; + if (UtilityEnabled("MonochromeUI")) { + Temp[i].style.backgroundColor = ""; + } let MetalCell = Temp[i].cells[0]; let Metal = document.createElement("span"); Metal.innerText = MetalCell.innerText; @@ -3173,9 +3183,11 @@ async function main() { GetUsernameHTML(Temp[i].cells[1], Temp[i].cells[1].innerText); Temp[i].cells[2].innerHTML = Temp[i].cells[2].innerText; Temp[i].cells[3].innerHTML = Temp[i].cells[3].innerText; - for (let j = 0; j < 5 && j < Temp[i].cells.length; j++) { - Temp[i].cells[j].style.backgroundColor = ""; - Temp[i].cells[j].style.color = ""; + if (UtilityEnabled("MonochromeUI")) { + for (let j = 0; j < 5 && j < Temp[i].cells.length; j++) { + Temp[i].cells[j].style.backgroundColor = ""; + Temp[i].cells[j].style.color = ""; + } } for (let j = 5; j < Temp[i].cells.length; j++) { let InnerText = Temp[i].cells[j].innerText; From 8384f850fd117a6163284d05f9a5cde8d568294f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 07:30:46 +0000 Subject: [PATCH 26/29] 3.3.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a2826d87..47509807 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.3.3", + "version": "3.3.4", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": { From 0ca625329e54ff5e90cb70c4ab95ba7ae152fa4b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 07:30:52 +0000 Subject: [PATCH 27/29] Update version info to 3.3.4 --- Update.json | 11 +++++++++++ XMOJ.user.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 63669aa3..dc6815cb 100644 --- a/Update.json +++ b/Update.json @@ -3455,6 +3455,17 @@ } ], "Notes": "Display status.php query content." + }, + "3.3.4": { + "UpdateDate": 1773559847015, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 939, + "Description": "fix: gate MonochromeUI-specific styling in contestrank pages behind flag" + } + ], + "Notes": "修复了未开启「极简黑白界面风格」时,比赛排行榜表头仍显示为黑白样式的问题 (#932)" } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 20ebce26..d8c8c077 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.3.3 +// @version 3.3.4 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen From d07172e30934db7dc90c131041527251b3263f69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Mar 2026 07:31:02 +0000 Subject: [PATCH 28/29] Update time and description of 3.3.4 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index dc6815cb..1b7716ab 100644 --- a/Update.json +++ b/Update.json @@ -3457,7 +3457,7 @@ "Notes": "Display status.php query content." }, "3.3.4": { - "UpdateDate": 1773559847015, + "UpdateDate": 1773559861504, "Prerelease": true, "UpdateContents": [ { From e509bb55f1b850a00c8776162803cfc33465166c Mon Sep 17 00:00:00 2001 From: def-WA2025 Date: Sun, 15 Mar 2026 17:27:48 +0800 Subject: [PATCH 29/29] Remove Problem Translate Button --- XMOJ.user.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index d8c8c077..f4cc6e6e 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.3.4 +// @version 3.3.5 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen @@ -2277,6 +2277,11 @@ async function main() { localStorage.setItem("UserScript-Problem-" + Temp[i].children[1].innerText + "-Name", Temp[i].children[2].innerText); } } else if (location.pathname == "/problem.php") { + let transZhEn = document.getElementById("lang_cn_to_en"); + let transEnZh = document.getElementById("lang_en_to_cn"); + if (transZhEn !== null) await transZhEn.remove(); + if (transEnZh !== null) await transEnZh.remove(); + await RenderMathJax(); if (SearchParams.get("cid") != null && UtilityEnabled("ProblemSwitcher")) { let pid = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-Problem-" + SearchParams.get("pid") + "-PID");