vjudge++

为vJudge设置背景,并汉化部分界面

当前为 2023-08-17 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name vjudge++
  3. // @namespace vjudge-plus-v2
  4. // @version 1.8.4b8
  5. // @description 为vJudge设置背景,并汉化部分界面
  6. // @author axototl (original by Suntra)
  7. // @match https://vjudge.net/*
  8. // @noframes
  9. // @icon https://vjudge.net/favicon.ico
  10. // @license AGPLv3 or later
  11. // @grant GM_addStyle
  12. // @grant GM_registerMenuCommand
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @run-at document-end
  16. // ==/UserScript==
  17.  
  18. // license text: https://www.gnu.org/licenses/agpl-3.0.txt
  19.  
  20. let config = {experimental: false, debug: false};
  21.  
  22. function dbgopt(...txt) {
  23. if(config.debug) console.debug(txt.join(' '));
  24. }
  25.  
  26. function getVal(key, def) {
  27. let gg = GM_getValue(key);
  28. if (gg === '' || gg === undefined) {
  29. GM_setValue(key, def);
  30. gg = def;
  31. }
  32. return gg;
  33. }
  34.  
  35. function reloader() {
  36. alert("设置成功,刷新生效");
  37. if (!navigator.onLine) {
  38. alert("离线状态,无法重加载。\n修改无法即刻生效");
  39. return;
  40. }
  41. location.reload();
  42. }
  43.  
  44. // 获取环境配置(不得异步处理) -Begin-
  45. (() => {
  46. config.experimental = getVal("experimental", false);
  47. config.debug = getVal("debug", true);
  48. config.ads = getVal("ads", true);
  49. function reg_command(name, prompts, need_reload = true) {
  50. let flag = true;
  51. GM_registerMenuCommand(prompts[config[name] | 0], () => {
  52. if(flag) {
  53. config[name] = !config[name];
  54. GM_setValue(name, config[name]);
  55. flag = false;
  56. }
  57. if (need_reload) reloader();
  58. })
  59. }
  60. // 设置实验性功能
  61. reg_command("experimental", ["× 点击启用实验性功能(界面汉化等)", "✔ 点击关闭实验性功能"]);
  62. // 设置debug
  63. reg_command("debug", ["已禁用 debug 输出", "已启用debug输出"], false);
  64. reg_command("ads", ["× 屏蔽广告(点击启用)", "✔ 屏蔽广告(点击禁用)"]);
  65. // 在基于 Blink 浏览器上检测是否为正常返回
  66. if (navigator.userAgent.includes("Chrome") && performance.getEntries()[0].responseStatus != 200) return;
  67.  
  68. // 设置背景
  69. config.back = getVal("background", "https://cdn.luogu.com.cn/images/bg/fe/Day_And_Night_1.jpg");
  70. GM_registerMenuCommand("设置背景URL", () => {
  71. config.back = window.prompt("请输入背景URL", config.back);
  72. GM_setValue("background", config.back);
  73. reloader();
  74. });
  75.  
  76. // 设置文字颜色
  77. config.col = getVal("col", "#b93e3e");
  78. const tester = /^#([0-9a-f]{3,4}|[0-9a-f]{6})$/i;
  79. function getColor(t) {
  80. let tmp;
  81. do {
  82. tmp = window.prompt("请输入颜色的Hexcode\n(比如#b93e3e)\n建议选择背景主色调的反差色", t);
  83. } while (!tester.test(tmp));
  84. dbgopt();
  85. return tmp;
  86. }
  87. GM_registerMenuCommand("设置文字颜色", () => {
  88. GM_setValue("col", getColor(config.col));
  89. reloader();
  90. });
  91. config.collink = getVal("collink", "#ff4c8c");
  92. GM_registerMenuCommand("设置链接背景颜色", () => {
  93. GM_setValue("collink", getColor(config.collink));
  94. reloader();
  95. });
  96. })();
  97. // 获取环境配置 -End-
  98.  
  99. // 界面美化程序 -Begin-
  100. (async () => {
  101. document.body.innerHTML = "<div style='height: 60px'></div>" + document.body.innerHTML; // 防止顶栏和页面内容重叠
  102. // User defined style
  103. GM_addStyle("body {background: url("+config.back+") no-repeat center top fixed;background-size: 100% 100%;-moz-background-size: 100% 100%;color: "+config.col+";}"+
  104. "a:focus, a:hover, .active {&:not(.nav-link){color: "+config.collink+" !important;text-decoration: underline;}}");
  105. // Global Style
  106. GM_addStyle(
  107. ".navbar {border-radius:0rem;background-color: rgba(0,0,0,65%) !important;position: fixed;top: 0;left: 0;z-index: 1000;width: 100%;}"+
  108. "scrollbar-width: none"+
  109. ".card-block, .card, .list-group-item, .btn-secondary, .page-link, .page-item.disabled .page-link, .dropdown-menu {background-color: rgba(255,255,255,65%)!important;}"+
  110. ".modal-content {background-color: rgba(255,255,255,90%);}"+
  111. ".form-control {background-color: rgba(255,255,255,50%);}"+
  112. ".tab-content {background-color: rgba(255,255,255,50%);border: 2px solid #eceeef;border-radius: 0.25rem;padding: 20px;}"+
  113. "table {background-color: rgba(255,255,255,70%);border-radius: 0.25rem;}"
  114. );
  115. if(location.pathname == "/") GM_addStyle("#ojs {opacity:0.75;}");
  116. document.querySelector("body > div.body-footer").innerHTML += '<p style="color: #3fb98b">Theme powered by vjudge++ (original <a href="https://gf.qytechs.cn/scripts/448801">vjudge+</a>)</p>';
  117. })();
  118. // 界面美化程序 -End-
  119.  
  120. (async () => {
  121. if (!config.ads) return;
  122. let arr = document.querySelectorAll(".social, #prob-ads, #img-support");
  123. for (let x of arr) x.remove();
  124. })(); // 广告移除
  125.  
  126. // 界面汉化程序 -Begin-
  127. (() => {
  128. if (!config.experimental) return;
  129.  
  130. const basicTranslateTable = {
  131. "#nav-problem > a": "问题列表",
  132. "#nav-status > a": "提交记录",
  133. "#nav-contest > a": "比赛",
  134. "#nav-workbook > a": "题单",
  135. "#nav-user > a": "用户",
  136. "#nav-group > a": "小组",
  137. "#nav-comment > a": "留言板",
  138. ".login": "登录(不可用)",
  139. ".register": "注册(不可用)",
  140. ".logout": "登出",
  141. ".user-dropdown > a:nth-child(1)": "个人主页",
  142. ".update-profile": "更新个人信息",
  143. ".message": "消息"
  144. };
  145.  
  146. const basicDynTransTable = {
  147. ".previous > a": "上一页",
  148. ".next > a": "下一页",
  149. "#filter": "应用过滤器", // 无法工作
  150. "#reset": "重置过滤器", // 无法工作
  151. };
  152. const loginBoxTranslate = {
  153. "#loginModalLabel": "登录(不可用)",
  154. "#btn-forget-password": "忘记密码",
  155. "#btn-login": "登录(不可用)",
  156. ".btn[data-dismiss]": "取消"
  157. };
  158.  
  159. const registerBoxTrans = {
  160. "#registerModalLabel": "注册(不可用)",
  161. "[for=register-username]": "用户名\n(必填)",
  162. "[for=register-password]": "密码\n(必填)",
  163. "[for=register-repeat-password]": "重复密码\n(必填)",
  164. "[for=register-nickname]": "昵称\n(可修改)",
  165. "[for=register-school]": "学校",
  166. "[for=register-email]": "邮箱\n(必填)",
  167. "[for=register-introduction]": "自我介绍",
  168. "[for=register-captcha]": "验证码\n(必填)",
  169. "#btn-register": "注册(不可用)",
  170. ".btn[data-dismiss]": "取消"
  171. };
  172.  
  173. const updateProfileTrans = {
  174. "#updateModalLabel": "更新个人信息",
  175. "[for=update-username]": "用户名",
  176. "[for=update-orig-password]": "原密码(必填)",
  177. "[for=update-password]": "新密码\n(可选)",
  178. "[for=update-repeat-password]": "重复新密码",
  179. "[for=update-nickname]": "昵称",
  180. "[for=update-school]": "学校",
  181. "[for=update-captcha]": "验证码",
  182. "[for=update-email]": "邮箱",
  183. "[for=update-introduction]": "个人简介",
  184. "#btn-update-profile": "更新",
  185. ".btn[data-dismiss]": "取消"
  186. };
  187.  
  188. function upd_trans(tr, flag = false) {
  189. for (let prop in tr) {
  190. let k = document.querySelector(prop);
  191. if (null != k)
  192. if (flag && k.childNodes.length >= 1) k.childNodes[0].data = tr[prop];
  193. else {
  194. k.innerText = tr[prop];
  195. }
  196. else dbgopt(prop, "is null");
  197. }
  198. }
  199.  
  200. function dynamic_trans(table, triggerDOM = null) {
  201. let ev = "click";
  202. if (null == triggerDOM)
  203. ev = "load", triggerDOM = window;
  204. triggerDOM.addEventListener(ev, () => setTimeout(() => upd_trans(table), 200));
  205.  
  206. }
  207.  
  208. function reg_box_trans(triggerElem_selector, table) {
  209. let s = document.querySelector(triggerElem_selector);
  210. if (null != s) dynamic_trans(table, s);
  211. else dbgopt(triggerElem_selector, "is null");
  212. }
  213.  
  214. (async () => {
  215. document.querySelector(".navbar-brand").childNodes[2].data = " 首页";
  216. upd_trans(basicTranslateTable);
  217. reg_box_trans(".login", loginBoxTranslate);
  218. reg_box_trans(".register", registerBoxTrans);
  219. reg_box_trans(".update-profile", updateProfileTrans);
  220. dynamic_trans(basicDynTransTable);
  221. })(); //基本汉化
  222.  
  223. // 静态内容汉化
  224. /* -Begin- */
  225. const staticTransTable = {
  226. "/": [{
  227. "#index-intro > div > div > p": "Vritual Judge(以下简称vj)并不是一个真实的在线评测网站(以下简称OJ),\
  228. 而是整合了各大OJ平台的题目形成的虚拟OJ平台。你提交的所有代码都会被发回原平台进行评测。\n\
  229. vj可以让你轻松开展比赛,不再为测试数据发愁\n\n\
  230. 目前我们支持以下平台的题库:"
  231. }, 0],
  232. "/problem": [{
  233. "[data-category=all]": "全部问题",
  234. "[data-category=solved]": "已解决问题",
  235. '[data-category=favorites]': "收藏的问题",
  236. "[data-category=attempted]": "未通过/正在评测的问题"
  237. }, 1],
  238. "/status": [{
  239. "[data-owner=all]": "所有提交",
  240. "[data-owner=mine]": "我的提交",
  241. ".username": "用户名",
  242. ".oj": "测评平台",
  243. ".prob_num": "问题编号",
  244. ".status": "状态",
  245. ".runtime": "运行时长",
  246. ".memory": "运行内存",
  247. ".length": "代码长度",
  248. ".language": "语言",
  249. ".date": "提交时间"
  250. }, 1],
  251. };
  252. (async () => {
  253. for (const path in staticTransTable) {
  254. if (path == location.pathname) {
  255. const tr = staticTransTable[path]
  256. upd_trans(tr[0], tr[1]);
  257. break;
  258. }
  259. }
  260. })();
  261. /* -End- */
  262.  
  263.  
  264.  
  265. })();
  266. // 界面汉化程序 -End-

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址