// ==UserScript==
// @name 洛谷提交记录显示优化
// @namespace https://github.com/chenyuxuan2009/luogu_submission_better
// @version 2.2
// @description 修改提交记录背景
// @author 沉石鱼惊旋
// @match *://www.luogu.com.cn/record/*
// @match *://www.luogu.com.cn
// @match *://www.luogu.com.cn/*
// @run-at document-end
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
let opacity = localStorage.getItem("opacity") || 0.3;
const jsdelivrOptions = [
'https://cdn.jsdelivr.net',
'https://jsdelivrcn.netlify.app',
'https://cdn.mengze.vip'
];
const themeOptions = [
'milkdragon'
];
let jsdelivr = localStorage.getItem("jsdelivr") || 'https://cdn.jsdelivr.net';
let theme = localStorage.getItem("theme") || 'milkdragon';
function getImage(theme, x) {
return Object.hasOwn(themeOptions, theme) ?
`${jsdelivr}/gh/chenyuxuan2009/luogu_submission_better/theme/${theme}/${x}.gif` :
localStorage.getItem(`${x}`);
}
let AC = getImage(theme, 'AC');
let WA = getImage(theme, 'WA');
let TLE = getImage(theme, 'TLE');
let MLE = getImage(theme, 'MLE');
let RE = getImage(theme, 'RE');
let OLE = getImage(theme, 'OLE');
let UKE = getImage(theme, 'UKE');
let Judging = getImage(theme, 'Judging');
let CE = getImage(theme, 'CE');
let Waiting = getImage(theme, 'Waiting');
let Unshown = getImage(theme, 'Unshown');
let ACcol = `rgba(82, 196, 26, ${opacity})`;
let WAcol = `rgba(231, 76, 60, ${opacity})`;
let TLEcol = `rgba(5, 34, 66, ${opacity})`;
let MLEcol = `rgba(5, 34, 66, ${opacity})`;
let REcol = `rgba(157, 61, 207, ${opacity})`;
let OLEcol = `rgba(5, 34, 66, ${opacity})`;
let UKEcol = `rgba(14, 29, 105, ${opacity})`;
let Judgingcol = `rgba(20, 85, 143, ${opacity})`;
let CEcol = `rgba(250, 219, 20, ${opacity})`;
let Waitingcol = `rgba(20, 85, 143, ${opacity})`;
let Unshowncol = `rgba(38, 38, 38, ${opacity})`;
let sta = [AC, WA, TLE, MLE, RE, OLE, UKE, Judging, CE, Waiting, Unshown];
let col = [ACcol, WAcol, TLEcol, MLEcol, REcol, OLEcol, UKEcol, Judgingcol, CEcol, Waitingcol, Unshowncol];
let txt = ["AC", "WA", "TLE", "MLE", "RE", "OLE", "UKE", "Judging", "CE", "WJ", "US"];
function getCol(x) {
return `background: linear-gradient(${col[x]}, ${col[x]}), url('${sta[x]}'); background-size: cover;`;
}
function subBetter() {
let tc = document.getElementsByClassName('test-case');
let len = tc.length;
let firstSTA = -1;
let ac = 0;
let judging = 0;
for (let i = 0; i < len; i += 1) {
if (tc[i].id === 'luogu_submission_better_right_row') continue;
if (tc[i].style.background === 'rgb(20, 85, 143)') {
judging = 1;
tc[i].style = getCol(7);
continue;
}
let status = tc[i].getElementsByClassName('status')[0].innerHTML;
if (status.length > 2) status = status.substring(0, 2);
let tmpSTA = -1;
if (status === "AC") {
tmpSTA = 0;
} else if (status === "WA") {
tmpSTA = 1;
} else if (status === "TL") {
tmpSTA = 2;
} else if (status === "ML") {
tmpSTA = 3;
} else if (status === "RE") {
tmpSTA = 4;
} else if (status === "OL") {
tmpSTA = 5;
} else if (status === "UK") {
tmpSTA = 6;
}
tc[i].style = getCol(tmpSTA);
if (tmpSTA === 0) {
ac = 1;
}
if (tmpSTA != 0 && firstSTA === -1) {
firstSTA = tmpSTA;
}
}
if (judging) firstSTA = 7;
if (firstSTA === -1 && ac) firstSTA = 0;
let doc = document.querySelector('div.info-rows');
let id = -1;
if (!doc) return;
for (let i = 0; i < doc.children.length; i += 1) {
if (doc.children[i].children[0].children[0].innerHTML.includes('评测状态')) {
id = i;
break;
}
}
let info = document.getElementsByClassName('info-rows')[0].children[id].children[1];;
if (info.innerHTML.includes('Judging')) firstSTA = 7;
if (info.innerHTML.includes('Compile Error') || info.innerHTML.includes('CE')) firstSTA = 8;
if (info.innerHTML.includes('Unknown Error') || info.innerHTML.includes('UKE')) firstSTA = 6;
if (info.innerHTML.includes('Waiting') || info.innerHTML.includes('WJ')) firstSTA = 9;
if (info.innerHTML.includes('Unshown') || info.innerHTML.includes('US')) firstSTA = 10;
if (firstSTA === -1) return;
// info.innerHTML = `${firstSTA}`;
// info.innerHTML = `${txt[firstSTA]}`;
// return;
if (firstSTA == 7) {
if (!info.innerHTML.includes('spinner')) {
info.innerHTML = `<div data-v-21e0a7cc="" class="test-case" style="${getCol(firstSTA)}" id="luogu_submission_better_right_row"><div data-v-21e0a7cc="" class="content"><div data-v-bbdab89a="" data-v-21e0a7cc="" class="spinner" style="width: 32px; height: 32px;"><div data-v-bbdab89a="" style="width: 32px; height: 32px; border-width: 2px;"></div></div></div></div>`
}
}
else {
if (!info.innerHTML.includes(txt[firstSTA])) {
info.innerHTML = `<div data-v-21e0a7cc="" class="test-case" style="${getCol(firstSTA)}" id="luogu_submission_better_right_row"><div data-v-21e0a7cc="" class="content"><div data-v-21e0a7cc="" class="status">${txt[firstSTA]}</div></div> </div>`;
}
}
}
function addButton() {
function createSettingsPopup() {
// 如果已经存在弹窗,避免重复创建
if (document.getElementById('settingsPopup')) return;
// 创建悬浮窗口
let popup = document.createElement("div");
popup.id = "settingsPopup";
popup.innerHTML = `
<div class="popup-header">
<span>插件设置</span>
<button id="closePopup">✖</button>
</div>
<p>调整纯色背景的透明度:</p>
<input type="number" id="opacityInput" min="0" max="1" value="${localStorage.getItem("opacity") || 0.3}">
<button id="saveOpacity">保存透明度设置</button>
<p>选择 jsdelivr 源服务器:<br>(只适用官方主题)</p>
<select id="jsdelivrSelect">
${jsdelivrOptions.map(option => `<option value="${option}" ${option === jsdelivr ? 'selected' : ''}>${option}</option>`).join('')}
${`<option value="custom" ${!jsdelivrOptions.includes(jsdelivr) ? 'selected' : ''}>自定义</option>`}
</select >
<input type="text" id="customJsdelivr" placeholder="输入自定义地址" ${!jsdelivrOptions.includes(jsdelivr) ? `value='${jsdelivr}' style="display:block;" ` : `style="display:none;"`}>
<button id="saveJsdelivr">保存 jsdelivr 设置</button>
`;
// 添加样式
let style = document.createElement("style");
style.innerHTML = `
#settingsPopup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
background: white;
border: 1px solid #ccc;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
padding: 20px;
z-index: 9999;
border-radius: 8px;
text-align: center;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid #ccc;
padding-bottom: 8px;
margin-bottom: 10px;
width: 100%;
}
#closePopup {
background: none;
border: none;
cursor: pointer;
font-size: 18px;
}
#opacityInput {
width: 80%;
margin: 10px 0;
padding: 5px;
text-align: center;
border-radius: 5px;
border: 1px solid #ccc; /* 和 jsdelivr 选择框一致的边框 */
}
#saveOpacity {
background: #007bff;
color: white;
border: none;
padding: 8px 12px;
cursor: pointer;
border-radius: 5px;
}
#saveOpacity:hover {
background: #0056b3;
}
.jsdelivr-settings {
margin: 10px 0;
width: 80%;
text-align: left;
}
#jsdelivrSelect {
width: 100%;
padding: 5px;
margin: 5px 0;
border-radius: 5px;
border: 1px solid #ccc;
}
#customJsdelivr {
width: 100%;
padding: 5px;
margin: 5px 0;
border-radius: 5px;
border: 1px solid #ccc;
box-sizing: border-box;
text-align: center;
}
#saveJsdelivr {
background: #007bff; /* 改成蓝色 */
color: white;
border: none;
padding: 8px 12px;
cursor: pointer;
border-radius: 5px;
}
#saveJsdelivr:hover {
background: #0056b3;
}
`;
document.body.appendChild(style);
document.body.appendChild(popup);
// 事件监听
document.getElementById("saveOpacity").addEventListener("click", function () {
let opacity = document.getElementById("opacityInput").value;
if (opacity < 0 || opacity > 1 || opacity === "") {
alert("请输入 0~1 之间的数");
return;
}
localStorage.setItem("opacity", opacity);
alert(`设置已保存:透明度 = ${opacity}`);
});
document.getElementById("jsdelivrSelect").addEventListener("change", function () {
let customInput = document.getElementById("customJsdelivr");
customInput.style.display = this.value === "custom" ? "block" : "none";
});
document.getElementById("saveJsdelivr").addEventListener("click", function () {
let selected = document.getElementById("jsdelivrSelect").value;
let newJsdelivr = selected === "custom" ? document.getElementById("customJsdelivr").value : selected;
if (!newJsdelivr) {
alert("请输入有效的 jsdelivr 地址");
return;
}
localStorage.setItem("jsdelivr", newJsdelivr);
alert(`设置已保存:jsdelivr 源 = ${newJsdelivr}`);
});
document.getElementById("closePopup").addEventListener("click", function () {
document.body.removeChild(popup);
});
}
// 绑定点击事件,打开悬浮框
let sidebar = document.querySelector(".nav-group.on-expand ul");
if (!sidebar) return;
sidebar.insertAdjacentHTML("beforeend", `
<li data-v-40281d0d="" data-v-6c9e83f4="" title="插件设置">
<a data-v-12b24cc3="" data-v-40281d0d="" href="#" class="" disabled="false" id="pluginSettingsBtn">
<span data-v-40281d0d="" class="title minor">插件设置</span>
</a>
</li>
`);
document.getElementById("pluginSettingsBtn").addEventListener("click", function (event) {
event.preventDefault();
createSettingsPopup();
});
}
(function () {
'use strict';
if (/^https:\/\/www\.luogu\.com\.cn\/record\/\d+$/.test(window.location.href)) {
setInterval(function () {
subBetter();
}, 10);
}
setTimeout(function () {
addButton();
}, 1000);
})();