Beautiful OpenJudge

使用 BootStrap 库美化 OpenJudge

当前为 2023-03-20 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Beautiful OpenJudge
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1.13
  5. // @description 使用 BootStrap 库美化 OpenJudge
  6. // @author Guyutongxue
  7. // @match http://*.openjudge.cn/*
  8. // @match http://*.test.openjudge.org/*
  9. // @exclude http://*.openjudge.cn/admin/*
  10. // @grant none
  11. // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js
  12. // @require https://cdn.bootcdn.net/ajax/libs/jquery.form/4.3.0/jquery.form.min.js
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // Load BootStrap 4
  19. document.head.innerHTML += `<meta name="viewport" content="width=device-width, initial-scale=1">
  20. <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css">
  21. <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js"></script>
  22. <style>
  23. /* Fix styles*/
  24. /* Titles*/
  25. h4{font-size:inherit;}
  26. h2,h3{font-size:18px;}
  27. h1,h2,h3{font-weight:bold;}
  28. h1{font-size:2em}
  29. /*Headers*/
  30. #headerTop{background:initial;}
  31. #headerTop a{color:initial;}
  32. #userToolbar, #headerTop .logo {font-size: inherit;}
  33. #headerTop #userToolbar .current a.link{background:initial;}
  34. .practice-search button{background:#545b62;font-size:12px;width:25px;padding:0px;}
  35. #groupBigLogo,#groupBigLogo img{max-width: 100%;height: auto;}
  36. #pageTitle{padding-bottom:0px;border-bottom:0px;margin-bottom:0px;}
  37. .wrapper{width:initial;margin:0 10%;}
  38. .appli-group{height:auto;}
  39. /*Contest title*/
  40. .recently-update, .over-time{position:relative;}
  41. .label h3, .current-contest h3{border-bottom:0;}
  42. .group-setting{font-size:smaller;}
  43. /*Tables*/
  44. table{line-height:initial;font-size:smaller;}
  45. .my-solutions td.result{line-height:2.5em;}
  46. #problemsList table tr{font-size:inherit;line-height:inherit}
  47. .my-solutions{font-size:smaller;}
  48. .my-solutions .time{width:auto;}
  49. .practice-info table tr td{padding:.5em;}
  50. table th{font-weight:bold;}
  51. table thead tr{background:initial;}
  52. /*Personal Page*/
  53. .recently-submit table td{padding:.3rem;}
  54. .recently-submit .contest{width:auto;}
  55. .all-group li{float:left; overflow: initial;}
  56. .my-group-logo{margin-right:15px;}
  57. /*Problem*/
  58. .problem-content pre {margin-bottom:0;}
  59. .problem-content dd {margin-bottom:0;}
  60. </style>`;
  61. // Dealing with main header
  62. $("#headerTop").addClass('bg-light');
  63. $("#userToolbar").css('margin-bottom','0');
  64. $("#userToolbar").addClass('btn-group btn-group-sm');
  65. $("#userToolbar li").addClass('btn btn-sm btn-light');
  66. $(".account-list li").attr('class', 'btn btn-sm btn-link');
  67. $(".search-form").addClass('inline-form');
  68. $(".search-form input").addClass('form-control').attr('value','').attr('placeholder','题目ID, 标题, 描述');
  69. $("button").addClass('btn btn-secondary');
  70. $(".search-form button").html("&#10140;"); // right arrow
  71.  
  72. // Dealing with main container
  73. $("#pagebody,#sitePagebody,#footer .wrapper").attr('class','container');
  74. $("#pagebody .wrapper,#sitePagebody .wrapper").attr('class','row mt-3');
  75. $(".col-2").removeClass('col-2').addClass('col-md-2');
  76. $(".col-3").removeClass('col-3').addClass('col-md-3');
  77. $(".col-4").removeClass('col-4').addClass('col-md-4');
  78. $(".col-8").removeClass('col-8').addClass('col-md-8');
  79. $(".col-9").removeClass('col-9').addClass('col-md-9');
  80. $(".col-10").removeClass('col-10').addClass('col-md-10');
  81. $(".problem-page").removeClass('problem-page');
  82. $(".problem-statistics").removeClass('problem-statistics');
  83. $(".problem-status").removeClass('problem-status');
  84. $(".problem-my-statistics").removeClass('problem-my-statistics');
  85.  
  86. // Dealing with problem header
  87. $("#header").addClass("mb-4");
  88.  
  89. // Group index page
  90. $(".contest-info").removeClass('contest-info').addClass('d-flex justify-content-lg-between flex-lg-row flex-column').css('border-bottom','1px dotted #666666');
  91. $(".recently-update").remove();
  92. $(".practice-info h3,.coming-contest h3,.past-contest h3").css('border-bottom','1px dotted #666666');
  93. // console.log($(".group-setting").children().text().replace(/\s+/g, ""));
  94. // If I'm in this group, then change it's style
  95. if($(".group-setting").children().text().replace(/\s+/g, "")!=""){
  96. $(".group-setting").html(`
  97. <ul class="btn-group btn-group-sm">
  98. ${$(".group-setting").text().includes("管理后台") ? '<a href="/admin/" class="btn btn-sm btn-outline-secondary">管理后台</a>' : ''}
  99. <a href="/mine/" class="btn btn-sm btn-outline-secondary">修改设定</a>
  100. <a href="javascript:void(0);" onclick="if (confirm('你确定要退出小组吗?')) api.leaveGroup(9,null,local.redirect);" class="btn btn-sm btn-danger">退出小组</a>
  101. </ul>`).addClass('mt-3 mb-0');
  102. }
  103.  
  104. // 夹带私货
  105. $("img[alt='软件设计实践']").attr("src", "https://s1.ax1x.com/2023/03/02/ppk9fGn.png");
  106.  
  107. // Site index page fixing
  108. $(".row").children('p').addClass('col-md-12 alert alert-info');
  109. $(".row").children('p').each(function(){if($(this).text().replace(/\s+/g, "")=="")$(this).hide();}); // remove extra spaces
  110. $(".user-group,.my-group-contest").addClass("row");
  111. $(".recently-submit,.my-contest-list").addClass('col-md-10');
  112.  
  113. // Alerts
  114. $(".notification").attr('class','alert alert-warning');
  115. $(".contest-description").attr('class','alert alert-info');
  116. $(".notice").attr('class','alert alert-danger');
  117.  
  118. // Change top menu
  119. var tabs = $("#topMenu ul")
  120. tabs.addClass('nav nav-tabs');
  121. tabs.children('li').addClass('nav-item');
  122. $('.nav a').addClass('nav-link');
  123. $(".current-show").children().addClass('nav-link active');
  124. tabs.children('li').removeClass('current-show');
  125. $("#topMenu").addClass('col-md-12 mb-3 mt-2').removeAttr('id');
  126.  
  127. // Change bottom menu
  128. tabs = $(".bottomMenu");
  129. tabs.addClass('pagination');
  130. tabs.children('li').addClass('page-item');
  131. $('.pagination a').addClass('page-link');
  132. $(".current-show").addClass('active');
  133. tabs.children('li').removeClass('current-show');
  134.  
  135. // Change tables' style
  136. $("table").addClass('table table-sm table-hover table-responsive');
  137. $("#main,#contestStatistics,#problemStatus").children("table").wrapAll("<div class='row justify-content-center'><div class='col-auto'></div></div>");
  138. $(".practice-info table,#problemsList table,.recently-submit table").removeClass('table-responsive');
  139. $("thead tr td").replaceWith(function () {
  140. return $("<th />").append($(this).contents());
  141. });
  142. $("table thead").addClass('thead-light text-center');
  143. $(".practice-info table thead").removeClass('text-center');
  144. $("table td.accepted,table td.submissions,table td.code-length").css('min-width','5em');
  145. $("table td.title").css('min-width','15em');
  146.  
  147.  
  148. // Remove too long text
  149. $("td.class-name,td.className").each(function() {
  150. if ($(this).text().length > 10 && $(this).width() < 150) {
  151. $(this).attr('title',$(this).text());
  152. $(this).html($(this).text().replace(/\s+/g, "").substr(0, 10) + "...")
  153. }
  154. })
  155.  
  156. // Change searching form
  157. $(".status-search form").addClass('form-inline justify-content-center');
  158. $(".status-query-params").addClass('row');
  159. $(".status-query-params input,.status-query-params select").addClass('form-control form-control-sm');
  160. $(".status-query-params button").addClass('btn-sm');
  161.  
  162. // Change page bar
  163. var pageBar = $(".page-bar");
  164. if(pageBar.length > 0){
  165. pageBar.removeClass('page-bar');
  166. pageBar = pageBar.children('.pages').attr('class','pagination pagination-sm justify-content-center');
  167. pageBar.children().each(function(){
  168. if($(this).hasClass('current')){
  169. $(this).attr('class','page-link');
  170. $(this).wrapAll('<span class="page-item active"></span>');
  171. } else if ($(this).is('a')){
  172. $(this).attr('class','page-link');
  173. $(this).wrapAll('<span class="page-item"></span>');
  174. } else {
  175. $(this).attr('class','page-link');
  176. $(this).wrapAll('<span class="page-item disabled"></span>');
  177. }
  178. });
  179. // If it's a contest page, add pagigation in the top
  180. if($(".timeBar").length > 0) {
  181. $(".timeBar").after(pageBar.clone());
  182. }
  183. }
  184.  
  185. var abbr = {
  186. "Accepted": "AC",
  187. "Wrong Answer": "WA",
  188. "Time Limit Exceeded": "TLE",
  189. "Memory Limit Exceeded": "MLE",
  190. "Output Limit Exceeded": "OLE",
  191. "Runtime Error": "RE",
  192. "Compile Error": "CE",
  193. "Presentation Error": "PE",
  194. "Waiting": "W.",
  195. "Problem Disabled": "PD",
  196. "Running And Judging": "R&J.",
  197. "System Error": "SE",
  198. "Validator Error": "VE"
  199. };
  200.  
  201. var color = {
  202. "Accepted": "#52C41A",
  203. "Wrong Answer": "#E74C3C",
  204. "Presentation Error": "#00A497",
  205. "Time Limit Exceeded": "#052242",
  206. "Memory Limit Exceeded": "#052242",
  207. "Output Limit Exceeded": "#E74C3C",
  208. "Runtime Error": "#9D3DCF",
  209. "Compile Error": "#FADB14",
  210. "Waiting": "#14558F",
  211. "Problem Disabled": "#AAAAAA",
  212. "Running And Judging": "#14558F",
  213. "System Error":"#CC317C",
  214. "Validator Error": "#CC317C"
  215. }
  216.  
  217. // Change solution's style
  218. if(/^\/[^\/]+\/solution\/\d+\/?$/.test(window.location.pathname)) {
  219.  
  220. var result = $('.compile-status a').text();
  221. $('.compile-status a').remove();
  222.  
  223. var memory = $('.compile-info dl dd:eq(3)').text();
  224. var time = $('.compile-info dl dd:eq(4)').text();
  225.  
  226. var detail = result == "Compile Error" || result == "Waiting" ? "" : time +"/" + memory;
  227.  
  228. var newStatus = "\
  229. <div class='beautiful-status' title='" + result + "'>\
  230. " + abbr[result] + "\
  231. <div style='font-size:11px;'>" + detail + "</div>\
  232. </div>";
  233.  
  234. $('.compile-status').append(newStatus);
  235.  
  236. $('.beautiful-status').css({
  237. "background-color": color[result],
  238. "height": "100px",
  239. "width": "100px",
  240. "margin-top": "20px",
  241. "display": "flex",
  242. "align-items": "center",
  243. "justify-content": "center",
  244. "flex-direction": "column",
  245. "color": "white",
  246. "font-size": "24px",
  247. "font-family": "-apple-system, BlinkMacSystemFont, 'San Francisco', 'Helvetica Neue', 'Noto Sans CJK SC', 'Noto Sans CJK', 'Source Han Sans', 'PingFang SC', 'Microsoft YaHei', sans-serif"
  248. });
  249. }
  250.  
  251. // Change code's font
  252. if(/^\/[^\/]+\/[^\/]+\/submit\/?$/.test(window.location.pathname)) {
  253. $("textarea#source").css({
  254. "font-family": "Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace",
  255. "width": "100%"
  256. })
  257. // $("#submit").removeClass('col-md-9');
  258. $("#submit dt:eq(1)").text("语言");
  259. $("#submit form textarea").addClass('form-control');
  260. $("#main").width("100%");
  261. $(".submit-button").removeClass('btn-secondary').addClass('btn-primary');
  262. }
  263. $("pre,span.sh_string").css({
  264. "font-family": "Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace"
  265. })
  266.  
  267. // Change question('clarify')'s style
  268. if(/^\/[^\/]+\/clarify(\/[^\/]*\/?)?$/.test(window.location.pathname) || /^\/mine\/?$/.test(window.location.pathname)) {
  269. $("#main form").addClass('form');
  270. $("#main form textarea,#main form :text").addClass("form-control").width("90%");
  271. $("#main form :text").height("auto");
  272. }
  273.  
  274. // Change ranking style
  275. if(/^\/[^\/]+\/ranking\/?$/.test(window.location.pathname)) {
  276. let allData = $("td.alpha");
  277. for(let i = 0; i < allData.length; i++) {
  278. let text = allData.eq(i).html();
  279. if(text.indexOf("<a") != -1) continue;
  280. if(text.indexOf(":") != -1 ) { // If passed
  281. let res = '&#8730;';
  282. if(text.indexOf('<br>') != -1) {
  283. res = "&#8730;" + text.split('<br>')[1];
  284. }
  285. allData.eq(i).html(res);
  286. allData.eq(i).css({
  287. "background-color": "#dff0d890",
  288. "color": "#3c763d"
  289. });
  290. } else if(text.indexOf('(-') != -1) { // Or not passed, but tried
  291. let res = text.split('<br>')[1];
  292. allData.eq(i).html(res);
  293. allData.eq(i).css({
  294. "background-color": "#f2dede90",
  295. "color": "#a94442"
  296. });
  297. }
  298. }
  299.  
  300. $(document).ready(function(){
  301. // If too wide, add scrolling event
  302. if($('table')[0].scrollWidth > $('table').width()) {
  303. $('table').attr('id','scroll-horizontally');
  304. {
  305. function scrollHorizontally(e) {
  306. e = window.event || e;
  307. var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
  308. document.getElementById('scroll-horizontally').scrollLeft -= (delta * 60);
  309. e.preventDefault();
  310. }
  311. $('#scroll-horizontally').on('mousewheel',scrollHorizontally);
  312. }
  313. }
  314. });
  315.  
  316. }
  317.  
  318. // Change other status's style
  319. var otherResult = $('.result-wrong,.result-ce,.result-right,.result-pending');
  320. otherResult.css({
  321. "padding": "3px 6px",
  322. "color": "white",
  323. "border-radius": "2px",
  324. "font-style": "normal"
  325. });
  326. for(let i = 0; i < otherResult.length; i++) {
  327. let res = otherResult.eq(i).text();
  328. otherResult.eq(i).text(abbr[res]);
  329. otherResult.eq(i).attr('title',res);
  330. otherResult.eq(i).css({
  331. "background-color": color[res],
  332. });
  333. }
  334. $('.result').css('width','auto');
  335.  
  336.  
  337. // Change finish ratio's style
  338. function getColorByRatio(ratio){
  339. var one = (255+255) / 100;
  340. var r = 0;
  341. var g = 0;
  342. var b = 0;
  343. if (ratio < 50) {
  344. r = one * ratio;
  345. g = 255;
  346. }
  347. if (ratio >= 50) {
  348. g = 255 - ((ratio - 50 ) * one) ;
  349. r = 255;
  350. }
  351. r = parseInt(r);
  352. g = parseInt(g);
  353. b = parseInt(b);
  354. return "rgba(" + r + "," + g + "," + b + ",0.5)";
  355. }
  356. var ratios = $('.ratio');
  357. for(let i = 0; i < ratios.length; i++) {
  358. let value = parseInt(ratios.eq(i).text().replace('%',''));
  359. if (!isNaN(value)) {
  360. ratios.eq(i).css({'background-color':getColorByRatio(100 - value),'min-width':'4em'});
  361. }
  362. }
  363.  
  364. // Change time bar
  365. if($('.timeBar').length > 0) {
  366. let startTime = new Date($(".start-time-dd").text());
  367. let endTime = new Date($(".end-time-dd").text());
  368. let currentState = $(".current-time").text();
  369. function formatDateTime(date) {
  370. var y = date.getFullYear();
  371. var m = date.getMonth() + 1;
  372. m = m < 10 ? ('0' + m) : m;
  373. var d = date.getDate();
  374. d = d < 10 ? ('0' + d) : d;
  375. var h = date.getHours();
  376. h=h < 10 ? ('0' + h) : h;
  377. var minute = date.getMinutes();
  378. minute = minute < 10 ? ('0' + minute) : minute;
  379. var second=date.getSeconds();
  380. second=second < 10 ? ('0' + second) : second;
  381. return y + '-' + m + '-' + d+' '+h+':'+minute+':'+second;
  382. };
  383. let newTime = `
  384. <div class="row">
  385. <div class="col-md-3 text-left"><small>开始时间</small><br><b>${formatDateTime(startTime)}</b></div>
  386. <div class="col-md-6 text-center"><b>${currentState}</b><p><small id="currentTime"></small></p></div>
  387. <div class="col-md-3 text-right"><small>结束时间</small><br><b>${formatDateTime(endTime)}</b></div>
  388. </div>`;
  389. $('.timeBar').append(newTime).addClass('mt-4 mb-4');
  390. $('.current-contest-info,.past-contest-info').remove();
  391. $('.timeBar').append('<p style="display:none;" id="timeclock"></p>');
  392. if(new Date() < endTime && new Date() > startTime){
  393. let total = endTime - startTime;
  394. let progress = `
  395. <div class="progress">
  396. <div class="progress-bar progress-bar-striped progress-bar-animated" id="timeProgressBar"
  397. role="progressbar" style="width: 0%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">
  398. </div>
  399. </div>`;
  400. $('.timeBar').append(progress);
  401. function updateValue(){
  402. var currentTime = new Date();
  403. var percent = (currentTime - startTime) * 100 / total;
  404. $("#timeProgressBar").attr({'style':'width:' + percent + '%','aria-valuenow':percent + ''});
  405. $("#currentTime").text(formatDateTime(currentTime));
  406. }
  407. updateValue();
  408. setInterval(updateValue,1000);
  409. }
  410. }
  411.  
  412. // Change those old icon to unicode character
  413. var solvedIcon = $('.solved');
  414. for(let i = 0; i < solvedIcon.length; i++) {
  415. let html = solvedIcon.eq(i).html();
  416. if(html.indexOf('accepted') != -1) {
  417. solvedIcon.eq(i).html("<span style='color:green;font-weight:bold;'>&#8730;</span>");
  418. }
  419. else if(html.indexOf('wrong') != -1) {
  420. solvedIcon.eq(i).html("<span style='color:red;;font-weight:bold;'>&#215;</span>");
  421. }
  422. }
  423.  
  424. // Change login page
  425. var loginForm = $("#main form[action='/api/auth/login/']");
  426. if(loginForm.length>0) {
  427. loginForm.parent().removeClass('col-md-8').addClass('col-md-12');
  428. loginForm.html(`
  429. <style>
  430. .form-signin {
  431. width: 100%;
  432. max-width: 330px;
  433. padding: 15px;
  434. margin: auto;
  435. }
  436. .form-signin .checkbox {
  437. font-weight: 400;
  438. }
  439. .form-signin .form-control {
  440. position: relative;
  441. box-sizing: border-box;
  442. height: auto;
  443. padding: 10px;
  444. font-size: 16px;
  445. }
  446. .form-signin .form-control:focus {
  447. z-ind ex: 2;
  448. }
  449. .form-signin input[type="text"] {
  450. margin-bottom: -1px;
  451. border-bottom-right-radius: 0;
  452. border-bottom-left-radius: 0;
  453. }
  454. .form-signin input[type="password"] {
  455. margin-bottom: 10px;
  456. border-top-left-radius: 0;
  457. border-top-right-radius: 0;
  458. }
  459. </style>
  460. <form action="/api/auth/login/" method="post" onsubmit="$(this).ajaxSubmit({dataType:'json',success:local.redirect,'target':'#result'}).find(':submit').each(function(){this.disabled=true;});return false;" class="form-signin">\
  461. <input type="hidden" name="redirectUrl" value="">
  462. <div class="login-message"></div>
  463. <label for="email" class="sr-only">邮箱地址</label>
  464. <input id="email" type="text" name="email" size="20" onfocus="this.select();" class="form-control" placeholder="邮箱地址">
  465. <label for="password" class="sr-only">密码</label>
  466. <input id="password" type="password" name="password" size="20" class="form-control" placeholder="密码">
  467. <button type="submit" class="btn btn-block btn-primary mt-3">登入</button>
  468. <div class="d-flex justify-content-between mt-3">
  469. <p><a href="/register/">点此注册(不可用)</a></p>
  470. <p><a href="http://openjudge.cn/auth/forget/">忘记密码?</a></p>
  471. </div>
  472. </form>`);
  473. }
  474.  
  475. // Change register style
  476. if(/^\/register\/?$/.test(window.location.pathname)) {
  477. var originalStyle = $('#pagebody .row,#sitePagebody .row').children('style').text();
  478. $('#pagebody .row,#sitePagebody .row').children('style').text(originalStyle.replace(/\.btn\s*\{[^}]*\}/,''));
  479. $('#pagebody .row,#sitePagebody .row').addClass('justify-content-center');
  480. $('#main').removeClass('col-md-10').addClass('col-md-4');
  481. $('#main form dd,form dt').remove();
  482. $('#main form').prepend(`
  483. <div class="mb-3">
  484. <label for="regEmail">Email地址</label>
  485. <input id="regEmail" type="text" name="user_email" class="form-control">
  486. </div>
  487. <div class="mb-3">
  488. <label for="regName">用户名</label>
  489. <input id="regName" type="text" name="user_name" class="form-control">
  490. </div>
  491. <div class="mb-3">
  492. <label for="regPasswd">密码</label>
  493. <input id="regPasswd" type="password" name="user_passwd" class="form-control">
  494. </div>
  495. <div class="mb-3">
  496. <label for="regPasswd2">确认密码</label>
  497. <input id="regPasswd2" type="password" name="user_passwd2" class="form-control">
  498. </div>`);
  499. $('#main form').append(`
  500. <p id="wait" class="hide">正在加载验证码......</p>
  501. <p id="notice" class="hide">请先拖动验证码到相应位置</p>
  502. <button type="submit" class="mt-3 btn btn-block btn-primary btn-lg">注册(不可用)</button>`);
  503. };
  504. })();

QingJ © 2025

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