vjudge++

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

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

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

QingJ © 2025

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