MENDO.MK Enhancement

Adds dark mode, search in tasks and other stuff to MENDO.MK

安装此脚本
作者推荐脚本

您可能也喜欢Send to VJudge

安装此脚本
  1. // ==UserScript==
  2. // @name MENDO.MK Enhancement
  3. // @version 50.2
  4. // @namespace mendo-mk-enhancement
  5. // @description Adds dark mode, search in tasks and other stuff to MENDO.MK
  6. // @author EntityPlantt
  7. // @match *://mendo.mk/*
  8. // @exclude *://mendo.mk/jforum/*
  9. // @require https://cdn.jsdelivr.net/npm/chart.js@4.4.8
  10. // @noframes
  11. // @icon https://mendo.mk/img/favicon.ico
  12. // @grant none
  13. // @license CC-BY-ND
  14. // ==/UserScript==
  15.  
  16. const VERSION = 50.2, AprilFools = new Date().getMonth() == 3 && new Date().getDate() < 3, EventDeadline = new Date("apr 15 25").getTime();
  17. console.log("%cMENDO.MK Enhancement", "color:magenta;text-decoration:underline;font-size:20px");
  18. function localize(english, macedonian) {
  19. return document.cookie.includes("mkjudge_language=en") ? english : macedonian;
  20. }
  21. const achlink = {
  22. task: "/Training.do?cid=1",
  23. readlec: "/Training.do?cid=6"
  24. };
  25. const achname = {
  26. task0: localize("Apprentice (10 tasks)", "Чирак (10 задачи)"),
  27. task1: localize("Guru (25 tasks)", "Гуру (25 задачи)"),
  28. task2: localize("Pupil (50 tasks)", "Ученик (50 задачи)"),
  29. task3: localize("Specialist (100 tasks)", "Специјалист (100 задачи)"),
  30. task4: localize("Sage (150 tasks)", "Мудрец (150 задачи)"),
  31. task5: localize("Expert (200 tasks)", "Експерта (200 задачи)"),
  32. task6: localize("Master (250 tasks)", "Мајстор (250 задачи)"),
  33. task7: localize("Champion (300 tasks)", "Шампион (300 задачи)"),
  34. task8: localize("Titan (350 tasks)", "Титан (350 задачи)"),
  35. task9: localize("The Architect (400 tasks)", "Архитектот (400 задачи)"),
  36. task10: localize("Baba (450 tasks)", "Баба (450 задачи)"),
  37. task11: localize("Zenith (500 tasks)", "Зенит (500 задачи)"),
  38. readlec0: localize("Student I (all Learn C++ tasks solved)", "Студент I (сите Научи C++ лекции прочитани)"),
  39. readlec1: localize("Student II (all Algorithms & Learn C++ tasks solved)", "Студент II (сите Алгоритми и Научи C++ лекции прочитани)"),
  40. colorful0: localize("Colorful! (Get 1 testcase with every verdict on a single submission)", "Шарено! (Добиј барем 1 тест случај од секој verdict на едно исто решение)")
  41. };
  42. async function MendoMkEnhancement() {
  43. try {
  44. function logFinish(taskName) {
  45. (console.debug ?? console.log)("%cFinished setting up:%c " + taskName, "color:#0f0", "");
  46. }
  47. function collapseNavigation() {
  48. if (!document.querySelector(".main-navigation")) return;
  49. localStorage.setItem("nav collapsed", document.querySelector(".main-navigation").classList.toggle("collapsed"));
  50. }
  51. if (localStorage.getItem("nav collapsed") == "true") collapseNavigation();
  52. logFinish("collapse navigation if collapsed");
  53. var style = document.createElement("style");
  54. if (!(parseFloat(localStorage.getItem("enhancement last version")) >= VERSION)) {
  55. Changelog();
  56. localStorage.setItem("enhancement last version", VERSION);
  57. }
  58. if (!localStorage.getItem("mendo-mk-enhancement-theme")) {
  59. localStorage.setItem("mendo-mk-enhancement-theme", window.matchMedia && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
  60. logFinish("detect color scheme");
  61. }
  62. style.innerHTML = `
  63. @import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css");
  64. ${ // Dark mode
  65. localStorage.getItem("mendo-mk-enhancement-theme") == "dark" ? `
  66. .page-container, .page-container img, .page-container svg, #cboxWrapper, .copy-io-btn span, .precinematicscreen, .sitelogo, #container, #container img {
  67. filter: invert(1) hue-rotate(180deg);
  68. }
  69. body, img, svg {
  70. background-color: black;
  71. }
  72. ::-webkit-scrollbar {
  73. width: initial;
  74. }
  75. ::-webkit-scrollbar-track {
  76. background-color: #eee;
  77. }
  78. body::-webkit-scrollbar-track {
  79. background-color: #111;
  80. }
  81. ::-webkit-scrollbar-thumb {
  82. background-color: #ddd;
  83. }
  84. #infoinfo {
  85. color: white;
  86. }
  87. ` : ""}
  88. body::-webkit-scrollbar-thumb {
  89. background-color: #222;
  90. }
  91. ${AprilFools ? `
  92. td.solved, td.wrong {
  93. background-color: #bfb !important;
  94. }
  95. td.correct {
  96. background-color: #fbb !important;
  97. }` : `
  98. td.solved, td.correct {
  99. background-color: #bfb !important;
  100. }
  101. td.wrong {
  102. background-color: #fbb !important;
  103. }
  104. `}
  105. .copy-io-btn span:before {
  106. content: "📃";
  107. }
  108. #search {
  109. font-family: consolas;
  110. }
  111. #search, #search-submit {
  112. border: solid 2px black;
  113. transition: box-shadow .5s;
  114. margin-bottom: 20px;
  115. }
  116. #search-submit:hover {
  117. cursor: pointer;
  118. }
  119. #search:focus {
  120. background-color: #eee;
  121. }
  122. #search-submit:hover, #search:hover {
  123. background-color: #ddd;
  124. }
  125. .copy-io-btn {
  126. float: right;
  127. background-color: #ddd;
  128. padding: 5px;
  129. cursor: pointer;
  130. border-radius: 5px;
  131. user-select: none;
  132. }
  133. .copy-io-btn:hover {
  134. background-color: #e8e8e8;
  135. }
  136. #search:active, #search:focus, #search-submit:active, #search-submit:focus {
  137. box-shadow: 0 0 2.5px 2.5px black;
  138. }
  139. @keyframes gta-cinematic-image {
  140. 0%, 50% {filter: blur(500px); opacity: 0;}
  141. 10%, 40% {filter: blur(0px); opacity: 1;}
  142. }
  143. td.share-solved {
  144. background-color: #ac0 !important;
  145. }
  146. .update-available {
  147. animation: update-available .5s infinite linear;
  148. }
  149. @keyframes update-available {
  150. from, to {color: red}
  151. 50% {color: white}
  152. }
  153. .ojtxt {
  154. animation: ojtxt 3s 1 linear;
  155. color: white;
  156. display: block;
  157. }
  158. @keyframes ojtxt {
  159. from {color: #4f4}
  160. to {color: white}
  161. }
  162. .progbar {
  163. background-color: #0004;
  164. margin-top: .5em;
  165. border-radius: 5px;
  166. padding: 2.5px;
  167. width: 100%;
  168. }
  169. .progbar > div {
  170. background-color: #bfb;
  171. border: solid 1px gray;
  172. border-radius: 2.5px;
  173. height: 100%;
  174. position: relative;
  175. padding: 5px;
  176. box-sizing: border-box;
  177. }
  178. .hidden {
  179. display: none !important;
  180. }
  181. .sorttask {
  182. font: inherit;
  183. background-color: transparent;
  184. border: none;
  185. display: inline-block;
  186. float: right;
  187. width: 1rem;
  188. }
  189. td.wrong.verdict-re {
  190. background-color: #fda !important;
  191. }
  192. td.wrong.verdict-tle {
  193. background-color: #bbf !important;
  194. }
  195. .precinematicscreen {
  196. top: 0px;
  197. left: 0px;
  198. position: fixed;
  199. width: 100vw;
  200. height: 100vh;
  201. background-color: white;
  202. font-size: 20px;
  203. cursor: pointer;
  204. z-index: 99999;
  205. }
  206. .page-container.deathscreen {
  207. rotate: 4deg;
  208. scale: .95;
  209. animation: deathscreen 10s linear 1;
  210. }
  211. #achievement-toast {
  212. position: fixed;
  213. bottom: 0;
  214. right: 0;
  215. font-family: Times New Roman, serif;
  216. text-align: right;
  217. color: black;
  218. }
  219. #achievement-toast > div {
  220. background-color: gold;
  221. border-top-left-radius: 20px;
  222. border-bottom-left-radius: 20px;
  223. padding: 10px 20px;
  224. flex-direction: column;
  225. margin-bottom: 10px;
  226. animation: achievement-toast 6s ease-in 1;
  227. opacity: 0;
  228. translate: 100%;
  229. scale: 0;
  230. }
  231. #achievement-toast > div > :first-child {
  232. font-size: 20px;
  233. }
  234. #achievement-toast > div > :last-child {
  235. font-size: 30px;
  236. }
  237. @keyframes achievement-toast {
  238. 0%, 100% { opacity: 0; translate: 100%; scale: 0; }
  239. 20%, 80% { opacity: 1; translate: 0%; scale: 1; }
  240. }
  241. @keyframes deathscreen {
  242. 0% { rotate: 0deg; scale: 1 }
  243. to { rotate: 4deg; scale: .95 }
  244. }
  245. .main {
  246. display: flex;
  247. flex-direction: row;
  248. justify-content: center;
  249. }
  250. .main-navigation {
  251. transition: width .5s;
  252. overflow: hidden !important;
  253. }
  254. .main-navigation.collapsed {
  255. width: 35px;
  256. }
  257. .main-navigation>* {
  258. transition: opacity .5s;
  259. opacity: 1;
  260. }
  261. .main-navigation.collapsed>* {
  262. opacity: 0;
  263. }
  264. .collapse-navigation-container {
  265. opacity: 1 !important;
  266. float: right;
  267. }
  268. .collapse-navigation {
  269. cursor: pointer;
  270. border: none;
  271. padding: 0;
  272. font-size: 25px;
  273. overflow: visible;
  274. height: 0;
  275. margin-right: 10px;
  276. color: white;
  277. transition: color .5s;
  278. z-index: 99;
  279. position: relative;
  280. top: -5px;
  281. }
  282. .main-navigation.collapsed .collapse-navigation {
  283. color: black;
  284. }
  285. .main-content {
  286. padding-left: 30px;
  287. margin: 0;
  288. }
  289. .event {
  290. position: fixed;
  291. left: 0;
  292. bottom: 0;
  293. display: flex;
  294. flex-direction: column;
  295. width: 200px;
  296. height: 100vh;
  297. background-color: #606;
  298. font-family: system-ui, Verdana, sans-serif;
  299. font-size: 20px;
  300. font-weight: bold;
  301. text-align: center;
  302. color: white;
  303. opacity: .75;
  304. }
  305. .event-text {
  306. flex-shrink: 0;
  307. padding: 10px 0;
  308. }
  309. .event-pbar {
  310. height: 100%;
  311. width: 100%;
  312. display: flex;
  313. flex-direction: column-reverse;
  314. overflow: hidden;
  315. }
  316. .event-prog {
  317. background-color: #808;
  318. width: 100%;
  319. padding-top: 10px;
  320. overflow: hidden;
  321. }
  322. .event select {
  323. color: inherit;
  324. font: inherit;
  325. background-color: transparent;
  326. border: none;
  327. border-top: solid 3px #808;
  328. }
  329. .event-hot, td.event-hot {
  330. background-color: #fbf !important;
  331. }
  332. .event-hot a:hover {
  333. background-color: #fdf !important;
  334. }
  335. #olympsearch {
  336. color: #808;
  337. text-decoration: underline;
  338. }
  339. .nameplate {
  340. display: flex;
  341. align-items: center;
  342. flex-direction: row;
  343. gap: .5em;
  344. }
  345. /* April Fools'! */
  346. html.mirrored {
  347. transform: rotateY(1620deg) rotateX(-10deg);
  348. }
  349. html {
  350. transition: transform 3s cubic-bezier(0.45, 0, 0.55, 1);
  351. }
  352. `;
  353. document.head.appendChild(style);
  354. logFinish("inject style sheet");
  355. if (document.querySelector(".sitename h1 a")) {
  356. document.querySelector(".sitename h1").innerHTML += " <a href='https://greasyfork.org/en/scripts/450985-mendo-mk-enhancement' id=enhancement-logo><em><b>Enhanced</b></em></a>";
  357. }
  358. logFinish("complete site logo");
  359. fetch("https://raw.githubusercontent.com/EntityPlantt/EntityPlantt.github.io/refs/heads/main/mendo-enhancement/version.txt").then(async x => {
  360. x = await x.text();
  361. let offv = parseFloat(x);
  362. if (offv > VERSION) {
  363. if (document.getElementById("enhancement-logo")) {
  364. document.getElementById("enhancement-logo").classList.add("update-available");
  365. document.querySelector("#enhancement-logo b").innerText = "Update";
  366. }
  367. }
  368. else if (offv < VERSION) {
  369. if (document.getElementById("enhancement-logo")) {
  370. document.querySelector("#enhancement-logo b").innerText = "Development";
  371. }
  372. }
  373. logFinish("check for updates");
  374. });
  375. /* if (document.querySelector(".main-navigation > ul") && !document.URL.includes("Help.do")) {
  376. let elm = document.createElement("li");
  377. elm.innerHTML = `<a href="/simple_jsp/report_bug.jsp" class="cbrbm cboxElement">${true ? "Пријави Грешка" : "Report Bug"}</a>`;
  378. document.querySelector(".main-navigation > ul").appendChild(elm);
  379. logFinish("add report bug form");
  380. } */
  381. if (document.URL.includes("/Training.do") || document.URL.includes("/User_Competition.do")) {
  382. var search = document.createElement("form");
  383. search.className = "content-search";
  384. search.action = "#";
  385. search.innerHTML = `
  386. <input type=text id=search autocomplete=off>
  387. <input type=submit id=search-submit value=Search>
  388. `;
  389. search.onsubmit = e => {
  390. e.preventDefault();
  391. location.hash = "#" + encodeURIComponent(search.querySelector("#search").value);
  392. search.querySelector("#search").blur();
  393. hashChange();
  394. }
  395. function hashChange() {
  396. let kw = decodeURIComponent(location.hash.substring(1));
  397. search.querySelector("#search").value = kw;
  398. kw = kw.toLowerCase();
  399. if (kw.includes("mirror")) document.body.parentElement.classList.add("mirrored");
  400. else document.body.parentElement.classList.remove("mirrored");
  401. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div > div > table > tbody > tr").forEach(elm => {
  402. if (!elm.querySelector("td:nth-child(2) > a")) {
  403. return;
  404. }
  405. if ((kw == "event.olymp2025" && /(олимпијада|мои|ibuoi)/i.test(elm.querySelector("td:nth-child(3)").innerText)) || elm.innerText.toLowerCase().includes(kw) || elm.querySelector("td:nth-child(2) > a").href.toLowerCase().includes(kw)) {
  406. elm.style.display = "";
  407. }
  408. else {
  409. elm.style.display = "none";
  410. }
  411. });
  412. if (AprilFools) {
  413. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div > div > table > tbody").forEach(elm => {
  414. let afa = Array.from(elm.querySelectorAll("tr")).slice(1);
  415. function shuffle(array) {
  416. let currentIndex = array.length;
  417. while (currentIndex != 0) {
  418. let randomIndex = Math.floor(Math.random() * currentIndex);
  419. currentIndex--;
  420. [array[currentIndex], array[randomIndex]] = [
  421. array[randomIndex], array[currentIndex]];
  422. }
  423. }
  424. shuffle(afa);
  425. afa.forEach(x => elm.appendChild(x));
  426. });
  427. }
  428. }
  429. window.onhashchange = hashChange;
  430. hashChange();
  431. document.querySelector(".main-content").prepend(search);
  432. logFinish("add task search bar");
  433. document.querySelector("body > div.page-container > div.main > div.main-content > div:nth-child(3)").innerHTML += `<a href="./Training.do?cid=5">[ ${localize("Other tasks", "Други задачи")} ]</a>&nbsp;&nbsp;`;
  434. document.querySelector("body > div.page-container > div.main > div.main-content > div:last-child").innerHTML =
  435. document.querySelector("body > div.page-container > div.main > div.main-content > div:nth-child(3)").innerHTML;
  436. logFinish("add secret tasks");
  437. if (/(Статистика|Success rate)/.test(document.querySelector(".main-content > .column1-unit > .training-content table tr th:last-child")?.textContent)) {
  438. let elm = document.querySelector(".main-content > .column1-unit > .training-content table tr th:last-child");
  439. let select = document.createElement("select");
  440. select.innerHTML = [["normal", "indexmin"], ["latest", "indexmax"], ["min %", "percmin"], ["max %", "percmax"], ["least tried", "submin"],
  441. ["most tried", "submax"], ["least solved", "solvmin"], ["most solved", "solvmax"]].map(x => `<option value="${x[1]}">${x[0]}</option>`).join("");
  442. select.className = "sorttask";
  443. let sortcriteria = {
  444. index: tr => parseInt(tr.querySelector("td").textContent),
  445. perc: tr => parseInt(/\((\d+)%\)/.exec(tr.querySelector("td:last-child").textContent)[1]),
  446. sub: tr => parseInt(/\/(\d+)\b/.exec(tr.querySelector("td:last-child").textContent)[1]),
  447. solv: tr => parseInt(/^(\d+)\//.exec(tr.querySelector("td:last-child").textContent)[1])
  448. };
  449. select.oninput = event => {
  450. let [, criteria, rev] = /^([a-z]+)(min|max)$/.exec(select.value);
  451. rev = rev == "max" ? -1 : 1;
  452. let f = sortcriteria[criteria];
  453. let rows = Array.from(document.querySelectorAll(".main-content > .column1-unit > .training-content table tr")).slice(1).sort((a, b) => (f(a) - f(b)) * rev);
  454. rows.forEach(x => document.querySelector(".main-content > .column1-unit > .training-content tbody").appendChild(x));
  455. };
  456. elm.appendChild(select);
  457. logFinish("add statistics sorting");
  458. }
  459. }
  460. if (document.querySelector("body > div.page-container > div.header > div.header-bottom > div")) {
  461. document.querySelector("body > div.page-container > div.header > div.header-bottom > div").innerHTML += `<ul><li><a style="
  462. background-image: url(./img/lightbulb.png);
  463. " href='/algoritmi'>${localize("II Algorithms", "ИИ Алгоритми")}</a></li></ul>`;
  464. logFinish("add ii algorithms button");
  465. document.querySelector("body > div.page-container > div.header > div.header-bottom > div > ul:nth-child(1) > li > a").href = "/";
  466. document.querySelector("body > div.page-container > div.header > div.header-bottom > div > ul:nth-child(2) > li > a").href = "/Training.do";
  467. document.querySelector("body > div.page-container > div.header > div.header-bottom > div > ul:nth-child(2) > li > a").className = "";
  468. document.querySelectorAll("div.main-content > div > div > table > tbody > tr > td:nth-child(2) > a").forEach(e => void (e.target = "_blank"));
  469. logFinish("make task links open in another window");
  470. if (document.URL.includes("/Training.do")) {
  471. var solved = 0, total = 0, progbar = document.createElement("div");
  472. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div > div > table > tbody > tr").forEach(elm => {
  473. if (elm.children.length < 2 || elm.children[0].nodeName.toUpperCase() == "TH") return;
  474. if (elm.querySelector("td.solved")) solved++;
  475. total++;
  476. });
  477. progbar.className = "progbar";
  478. progbar.innerHTML = `<div style="width:${solved / total * 100}%">${solved} / ${total} (${Math.floor(solved / total * 100)}%)</div>`;
  479. document.querySelector("body > div.page-container > div.main > div.main-content > div > div > table > caption").appendChild(progbar);
  480. logFinish("task solved percentage");
  481. var taskShare = document.createElement("div");
  482. taskShare.id = "task-list-buttons";
  483. taskShare.style.marginBottom = "10px";
  484. taskShare.innerHTML = `
  485. <button id=solved-tasks-save>${localize("Share solved tasks", "Сподели решени задачи")}</button>
  486. <button id=solved-tasks-load>${localize("Load shared solved tasks", "Лоадирај споделени решени задачи")}</button>
  487. <button id=hide-solved-tasks>${localize("Hide/Show solved tasks", "Скриј/Откриј решени задачи")}</button>`;
  488. let taskshcode = "mendo-reseni-zadaci" + /^https?(.*)$/.exec(document.URL)[1];
  489. if (taskshcode.includes("#")) taskshcode = taskshcode.slice(0, taskshcode.indexOf("#"));
  490. taskShare.querySelector("#hide-solved-tasks").onclick = () => {
  491. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div:nth-child(5) > div > table > tbody > tr").forEach(td => {
  492. if (td.childNodes[0].classList.contains("solved")) td.classList.toggle("hidden");
  493. });
  494. };
  495. taskShare.querySelector("#solved-tasks-save").onclick = () => {
  496. var array = [];
  497. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div:nth-child(5) > div > table > tbody > tr > td:first-child").forEach(td => {
  498. if (td.classList.contains("solved")) array.push(td.innerText.substring(0, td.innerText.length - 1));
  499. });
  500. array.unshift(taskshcode);
  501. navigator.clipboard.writeText(array.join(","));
  502. alert(localize("Solved tasks copied, share them by pasting", "Решените задачи се копирани, сподели ги со пејстање"));
  503. };
  504. taskShare.querySelector("#solved-tasks-load").onclick = async () => {
  505. var array = prompt(localize("Enter code...", "Внеси код...")).split(",");
  506. if (array[0] != taskshcode) {
  507. alert(localize("Invalid task solve share schema! / Invalid page!", "Невалидна шема на споделени решени задачи! / Невалидна страна!"));
  508. return;
  509. }
  510. array.shift();
  511. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div:nth-child(5) > div > table > tbody > tr > td:first-child").forEach(td => {
  512. if (array.includes(td.innerText.substring(0, td.innerText.length - 1))) td.classList.add("share-solved");
  513. else td.classList.remove("share-solved");
  514. });
  515. };
  516. document.querySelector(".main-content").prepend(taskShare);
  517. logFinish("add task share");
  518. }
  519. }
  520. if (document.querySelector("body > div.page-container > div.header > div.header-breadcrumbs > ul > li:last-child > a")) {
  521. window.name = document.querySelector("body > div.page-container > div.header > div.header-breadcrumbs > ul > li:last-child > a").innerText;
  522. }
  523. else if (document.querySelector(".pagetitle")) {
  524. window.name = document.querySelector(".pagetitle").innerText;
  525. }
  526. document.title = (document.querySelector(".pagetitle")
  527. ?? document.querySelector("body > div.page-container > div.header > div.header-breadcrumbs > ul > li:last-child > a")
  528. ?? document.querySelector(".pagename")
  529. ?? document.getElementById("infoinfo")
  530. ?? { innerText: document.URL.substring(document.URL.indexOf("/", 8) + 1) }
  531. ).innerText + " – МЕНДО";
  532. logFinish("document title set");
  533. if (document.URL.includes("/Task.do") || document.URL.includes("User_ListSubmissions.do")) {
  534. document.querySelectorAll("body > div.page-container > div.main > div.main-content > div.column1-unit.taskContentView > table pre").forEach(pre => {
  535. var text = pre.innerText.substring(pre.innerText.indexOf("\n") + 1);
  536. var copyIoBtn = document.createElement("span");
  537. copyIoBtn.innerHTML = "<span></span>";
  538. copyIoBtn.setAttribute("onclick", `navigator.clipboard.writeText(${JSON.stringify(text)})`);
  539. copyIoBtn.className = "copy-io-btn";
  540. pre.parentElement.appendChild(copyIoBtn);
  541. });
  542. logFinish("copy io buttons");
  543. let nav = document.createElement("div");
  544. if (document.URL.includes("/Task.do")) {
  545. nav.innerHTML = `<a href="#">${localize("Task", "Задача")}</a> |
  546. <a href="#submit">${localize("Submit", "Поднеси")}</a> |
  547. <a href="${document.URL.replace(/Task\.do\?(?:competition=\d+&)?id/, "User_ListSubmissions.do?task").replace(/#.*$/, "")}">${localize("Previous submissions", "Претходни субмисии")}</a>`;
  548. }
  549. else {
  550. nav.innerHTML = `<a href="${document.URL.replace("User_ListSubmissions.do?task", "Task.do?id")}">${localize("Task", "Задача")}</a> |
  551. <a href="${document.URL.replace("User_ListSubmissions.do?task", "Task.do?id")}#submit">${localize("Submit", "Поднеси")}</a> | <b style=color:black>${localize("Previous submissions", "Претходни субмисии")}</b>`;
  552. }
  553. nav.style = `font-size:15px;margin-bottom:20px;color:gray`;
  554. document.querySelector(".main-content").prepend(nav);
  555. logFinish("add nav buttons");
  556. if (document.URL.includes("/Task.do")) {
  557. function hchange() {
  558. let Vtask = document.querySelector(".taskContentView"), Vsubmit = document.querySelector("#submitinfocontainer");
  559. if (!Vsubmit) return;
  560. if (location.hash.length < 2) {
  561. Vtask.classList.remove("hidden");
  562. Vsubmit.classList.add("hidden");
  563. }
  564. else if (location.hash == "#submit") {
  565. Vtask.classList.add("hidden");
  566. Vsubmit.classList.remove("hidden");
  567. }
  568. document.body.scrollTo(0, 0);
  569. }
  570. hchange();
  571. addEventListener("hashchange", hchange);
  572. logFinish("listen to hash change");
  573. }
  574. if (document.URL.includes("/Task.do") && document.querySelector("#solutionCode")) {
  575. setInterval(() => {
  576. let scode = document.getElementById("solutionCode");
  577. if (!scode.value.includes("// online judge") && !scode.value.includes("#define ONLINE_JUDGE") && scode.value.length) {
  578. scode.value = "#define ONLINE_JUDGE // online judge\n" + scode.value;
  579. document.querySelector("label[for=solutionCode]").innerHTML += `<a href="https://greasyfork.org/en/scripts/450985-mendo-mk-enhancement" class=ojtxt>${localize("This macro was automatically added", "Ова макро беше автоматски додадено")}: <code>ONLINE_JUDGE</code></a>`;
  580. setTimeout(() => document.querySelector(".ojtxt:last-child").remove(), 3000);
  581. }
  582. }, 500);
  583. logFinish("#define ONLINE_JUDGE");
  584. }
  585. }
  586. (document.querySelector(".footer") ?? {}).innerHTML += `<p class="credits"><a href="https://greasyfork.org/en/scripts/450985-mendo-mk-enhancement">MENDO.MK Enhancement</a> <a href="javascript:toggleTheme()">🎨</a> <a href="javascript:Changelog()">Changelog</a></p>`;
  587. window.toggleTheme = () => {
  588. localStorage.setItem("mendo-mk-enhancement-theme", localStorage.getItem("mendo-mk-enhancement-theme") == "dark" ? "light" : "dark");
  589. location.reload();
  590. };
  591. let dmodebtn = document.createElement("div");
  592. dmodebtn.className = `bi bi-${localStorage.getItem("mendo-mk-enhancement-theme") == "dark" ? "moon" : "sun"}-fill`;
  593. document.body.appendChild(dmodebtn);
  594. dmodebtn.style = "width:30px;height:30px;font-size:30px;position:fixed;bottom:0;right:0;margin:10px;cursor:pointer;color:#89AAD6";
  595. dmodebtn.setAttribute("onclick", "toggleTheme()");
  596. logFinish("dark mode button");
  597. if (/^https?:\/\/mendo\.mk\/.*?User_Submission.do\?/.test(document.URL) && !document.URL.includes("ctest=true")) {
  598. let ok = false;
  599. for (let elm of document.querySelectorAll("img")) {
  600. if (elm.src.includes("loadingAnimation")) {
  601. ok = true;
  602. break;
  603. }
  604. }
  605. function checkForCinematic() {
  606. let usubTBody = document.querySelector("div.main-content > div > div > table:nth-child(6) > tbody");
  607. if (!ok) {
  608. taskSolveCinematic(0, true);
  609. return;
  610. }
  611. if (!usubTBody) {
  612. requestAnimationFrame(checkForCinematic);
  613. }
  614. else if (usubTBody.querySelectorAll("tr td.correct:first-child").length + 1 >= usubTBody.querySelectorAll("tr").length) taskSolveCinematic(1, true);
  615. else taskSolveCinematic(2, true);
  616. }
  617. checkForCinematic();
  618. logFinish("task solve cinematic setup");
  619. }
  620. if (!isNaN(parseInt(document.querySelector("#LoginForm>fieldset>p:last-child>a")?.innerText))) {
  621. let elm = document.createElement("p");
  622. let tasks = parseInt(document.querySelector("#LoginForm>fieldset>p:last-child>a").innerText);
  623. let ach = getAchievements();
  624. let allread = true, lecturepage = false;
  625. for (let e of document.querySelectorAll(".training-content td:last-child")) {
  626. if (e.innerText.includes(localize("lecture", "предавање"))) {
  627. lecturepage = true;
  628. if (!e.classList.contains("solved")) {
  629. allread = false;
  630. break;
  631. }
  632. }
  633. }
  634. if (tasks >= 10) addAchievement("task", 0);
  635. if (tasks >= 25) addAchievement("task", 1);
  636. if (tasks >= 50) addAchievement("task", 2);
  637. if (tasks >= 100) addAchievement("task", 3);
  638. if (tasks >= 150) addAchievement("task", 4);
  639. if (tasks >= 200) addAchievement("task", 5);
  640. if (tasks >= 250) addAchievement("task", 6);
  641. if (tasks >= 300) addAchievement("task", 7);
  642. if (tasks >= 350) addAchievement("task", 8);
  643. if (tasks >= 400) addAchievement("task", 9);
  644. if (tasks >= 450) addAchievement("task", 10);
  645. if (tasks >= 500) addAchievement("task", 11);
  646. if (allread && lecturepage && (document.URL.endsWith("/Training.do") || document.URL.endsWith("/Training.do?cid=0") || document.URL.endsWith("/Training.do?cid=4"))) addAchievement("readlec", 0);
  647. if (allread && lecturepage && document.URL.endsWith("/Training.do?cid=6") && ach.readlec === 0) addAchievement("readlec", 1);
  648. ach = getAchievements();
  649. elm.innerHTML = `${localize("Achievements", "Постигнувања")}:<br>${Object.entries(ach).map(x => `&nbsp; &nbsp; <a title="${achname[x[0] + x[1]]}" href="${achlink[x[0]]}">${/(.+) \(/i.exec(achname[x[0] + x[1]])[1]}</a>`).join("<br>") || localize("None", "Нема")}`;
  650. document.querySelector("#LoginForm>fieldset").appendChild(document.createElement("br"));
  651. document.querySelector("#LoginForm>fieldset").appendChild(elm);
  652. logFinish("achievements");
  653. }
  654. if (document.querySelector(".main-navigation>ul")) {
  655. let nav = document.querySelector(".main-navigation>ul");
  656. let collapseparent = document.querySelector(".main-navigation>.round-border-topright");
  657. if (!collapseparent) {
  658. collapseparent = document.createElement("div");
  659. document.querySelector(".main-navigation").prepend(collapseparent);
  660. }
  661. collapseparent.className = "collapse-navigation-container";
  662. let collapse = document.createElement("button");
  663. collapseparent.appendChild(collapse);
  664. collapse.innerText = "≡";
  665. collapse.className = "collapse-navigation";
  666. collapse.onclick = collapseNavigation;
  667. let links = [
  668. ["National", "Национални", "/Training.do?cid=1"],
  669. ["International", "Интернационални", "/Training.do?cid=2"]
  670. ];
  671. nav.innerHTML = links.map(l => `<li><a href="${l[2]}">${localize(l[0], l[1])}</a></li>`).join("") + nav.innerHTML;
  672. if (Date.now() < EventDeadline) {
  673. nav.childNodes[0].classList.add("event-hot");
  674. nav.querySelector("a").innerHTML += " <i class='bi bi-hourglass-split'></i>";
  675. }
  676. logFinish("navigation bar");
  677. }
  678. if (document.URL.includes("/Training.do?cid=1") && Date.now() < EventDeadline && localStorage.getItem("event.total") !== "0") {
  679. let evbar = document.createElement("div");
  680. let totalTasks = parseInt(localStorage.getItem("event.total") || "45"), solvedTasks = Array.from(document.querySelectorAll(".training-content td:nth-child(3)")).filter(x => /(Олимпијада|ibuoi|мои)/i.test(x.innerText)), untilend = EventDeadline - Date.now();
  681. evbar.className = "event";
  682. solvedTasks.forEach(x => {
  683. x.classList.add("event-hot");
  684. x.innerHTML = `<a href="#event.olymp2025">${x.innerHTML}</a>`;
  685. });
  686. solvedTasks = solvedTasks.filter(x => x.classList.contains("solved"));
  687. evbar.innerHTML = `
  688. <div class=event-pbar>
  689. <div class=event-prog style="height:${solvedTasks.length / totalTasks * 100}%">[${(Math.min(solvedTasks.length / totalTasks, 1) * 100).toFixed(0)}%] ${solvedTasks.length} / ${totalTasks}<br>${localize("TASKS SOLVED", "ЗАДАЧИ РЕШЕНИ")}</div>
  690. </div>
  691. <div class=event-text>${Math.floor(untilend / 864e5)} ${localize("DAYS LEFT", "ДЕНОВИ ОСТАНАТИ")}<br>
  692. <a href="https://cs.org.mk/konechna-lista-na-uchesnici-pokaneti-na-moi-2024-copy/" style="color:white" target=_blank>${localize("Announcement", "Соопштение")}</a></div>
  693. <select autocomplete=off onchange="localStorage.setItem('event.total', this.value);if(this.value=='0')alert(localize('Turn it on again with:', 'Упали го пак со:')+'\\ndelete localStorage[\\'event.total\\']');location.reload()">
  694. <option value=45>${localize("MOI", "МОИ")}</option>
  695. <option value=20>${localize("MJOI", "МЈОИ")}</option>
  696. <option value=25>${localize("EGOI", "ЕГОИ")}</option>
  697. <option value=0>${localize("Turn off", "Исклучи")}</option>
  698. </select>
  699. `;
  700. evbar.querySelector("select").value = totalTasks + "";
  701. document.body.appendChild(evbar);
  702. let olympsearch = document.createElement("a");
  703. olympsearch.id = "olympsearch";
  704. olympsearch.innerText = localize("Olympiad valid tasks", "Задачи валидни за олимпијада");
  705. olympsearch.href = "#event.olymp2025";
  706. let olympsend = document.createElement("button");
  707. olympsend.innerText = "Направи листа од решени валидни задачи";
  708. olympsend.style = "color:#f0f;background-color:#eae;border-radius:5px";
  709. olympsend.onclick = () => {
  710. let list = solvedTasks.map(x => x.previousElementSibling).map(x => `${x.previousElementSibling.innerText} ${x.innerText} (${x.nextElementSibling.innerText}) - ${x.querySelector("a").href}`).join("\n");
  711. navigator.clipboard.writeText(list);
  712. alert(list);
  713. };
  714. document.querySelector(".content-search").appendChild(olympsearch);
  715. document.getElementById("task-list-buttons").appendChild(olympsend);
  716. logFinish("event bar");
  717. }
  718. if (document.URL.includes("/User_CompetitionResults.do?id=")) {
  719. let res = document.getElementById("resultstable");
  720. res.querySelectorAll("td[data-user-id] table").forEach(tb => {
  721. let wrapper = document.createElement("div");
  722. tb.querySelectorAll("td").forEach(x => Array.from(x.childNodes).forEach(y => wrapper.appendChild(y)));
  723. wrapper.className = "nameplate";
  724. tb.parentElement.appendChild(wrapper);
  725. tb.remove();
  726. });
  727. let table = Array(res.querySelector("tr").childNodes.length);
  728. for (let tr of res.querySelectorAll("tr")) {
  729. for (let i = 0; i < tr.childNodes.length; i++) {
  730. if (isNaN(tr.childNodes[i].innerText)) continue;
  731. if (!table[i]) table[i] = [];
  732. table[i].push(tr.childNodes[i]);
  733. }
  734. }
  735. for (let i of table) {
  736. if (!i) continue;
  737. let mx = Math.max(...i.map(x => parseInt(x.innerText)));
  738. if (mx < 15) continue;
  739. i.forEach(j => {
  740. let perc = parseInt(j.innerText) / mx;
  741. if (perc > .9999) {
  742. j.style.fontWeight = "bold";
  743. j.style.border = "solid 2px #0008";
  744. }
  745. j.style.backgroundColor = `hsl(${perc * 120}, 100%, 70%)`;
  746. });
  747. }
  748. logFinish("olympiad results coloring");
  749. }
  750. }
  751. catch (_) {
  752. console.error(_);
  753. }
  754. }
  755. function taskSolveCinematic(showType, reformatTcs = false) {
  756. if (AprilFools) {
  757. let congrattd = document.querySelector(".submission-content tr[align=right] td");
  758. if (congrattd && congrattd.innerText.includes("Congratulations!")) congrattd.innerHTML = "April Fools!";
  759. if (congrattd && congrattd.innerText.includes("Честитки!")) congrattd.innerHTML = "Априлилили!";
  760. }
  761. if (reformatTcs) {
  762. let tcs = document.querySelector("div.main-content > div > div > table:nth-child(6) > tbody");
  763. let tr, nAC = 0, nWA = 0, nTLE = 0, nRE = 0, actimes = {};
  764. for (let tc of Array.from(tcs.children)) {
  765. if (!tr || tr.children.length == 5) {
  766. tr = document.createElement("tr");
  767. tcs.appendChild(tr);
  768. }
  769. let td = tc.querySelector("td");
  770. if (td) {
  771. if (tc.innerText.includes("Runtime Error")) {
  772. td.innerText += " RE";
  773. td.classList.add("verdict-re");
  774. nRE++;
  775. }
  776. if (tc.innerText.includes("Wrong") || tc.innerText.includes("Погрешен")) {
  777. td.innerText += " WA";
  778. td.classList.add("verdict-wa");
  779. nWA++;
  780. }
  781. if (tc.innerText.includes("Time") || tc.innerText.includes("време")) {
  782. td.innerText += " TLE";
  783. td.classList.add("verdict-tle");
  784. nTLE++;
  785. }
  786. if (tc.innerText.includes("Точен") || tc.innerText.includes("Correct")) {
  787. let time = parseFloat(/[\d.]+/.exec(tc.children[1].innerText)[0]) * 1e3;
  788. td.innerText += `AC (${time})`;
  789. actimes[time <= 5 ? -1 : Math.floor(time / 100)] ??= 0;
  790. actimes[time <= 5 ? -1 : Math.floor(time / 100)]++;
  791. td.classList.add("verdict-ac");
  792. nAC++;
  793. }
  794. if (tc.querySelector("a")) {
  795. td.innerHTML = `<a href="${tc.querySelector("a").href}">${td.innerText}</a>`;
  796. }
  797. tr.appendChild(td);
  798. }
  799. tc.remove();
  800. }
  801. // Colorful achievement
  802. if (nAC > 0 && nTLE > 0 && nWA > 0 && nRE > 0) {
  803. tcs.previousElementSibling.innerHTML = localize("Colorful!!!", "Шарено!!!").split("").map((x, i) => `<span style="color:${["#fda", "#bbf", "#fbb", "#bfb"][i % 4]}">${x}</span>`).join("");
  804. addAchievement("colorful", 0);
  805. }
  806. let chartscont = document.createElement("div");
  807. let pie = document.createElement("canvas"), bar = document.createElement("canvas");
  808. chartscont.appendChild(pie);
  809. chartscont.appendChild(bar);
  810. chartscont.style = "height:250px;display:flex;justify-content:center;align-items:center;flex-directon:column";
  811. document.querySelector(".submission-content").insertBefore(chartscont, document.querySelectorAll(".submission-content table")[2]);
  812. new window.Chart(pie, {
  813. type: "pie",
  814. data: {
  815. labels: ["AC", "WA", "TLE", "RE"],
  816. datasets: [{
  817. label: localize("Results", "Резултати"),
  818. data: [nAC, nWA, nTLE, nRE],
  819. backgroundColor: ["#bfb", "#fbb", "#bbf", "#fda"]
  820. }]
  821. }
  822. });
  823. new window.Chart(bar, {
  824. type: "bar",
  825. data: {
  826. labels: (x => nTLE ? x.concat("TLE") : x)(Object.keys(actimes).sort((a, b) => parseInt(a) - parseInt(b)).map(x => parseInt(x)).map(x => x < 0 ? localize("Instant", "Инстантно") : `${x * 100}-${x * 100 + 99}`)),
  827. datasets: [{
  828. label: localize("Runtime", "Време на извршување"),
  829. data: (x => nTLE ? x.concat(nTLE) : x)(Object.entries(actimes).sort((a, b) => parseInt(a[0]) - parseInt(b[0])).map(x => x[1])),
  830. backgroundColor: "#bbb",
  831. inflateAmount: 0
  832. }]
  833. }
  834. });
  835. }
  836. if (!showType) return;
  837. var preCinematicScreen = document.createElement("div");
  838. preCinematicScreen.className = "precinematicscreen";
  839. preCinematicScreen.innerHTML = `
  840. <div style="color: black; position: fixed; top: 50vh; left: 50vw; transform: translate(-50%, -50%);">[ ${localize("Reveal", "Откриј")} ]</div>
  841. <div style="color: black; position: fixed; top: 10px; right: 10px;" id=skip-cinematic>${localize("Skip", "Скокни")} &gt;&gt;</div>
  842. `;
  843. preCinematicScreen.onclick = ev => {
  844. preCinematicScreen.remove();
  845. if (window.event.target.id == "skip-cinematic") return;
  846. const cinematics = [() => {
  847. let img = document.createElement("img");
  848. img.src = "https://i.ibb.co/b7WW8Q3/mission-passed.png";
  849. img.style = "animation: gta-cinematic-image 17s 1 linear; position: fixed; top: 0; left: 0; width: 100vw; background-color: transparent !important";
  850. let audio = document.createElement("audio");
  851. audio.oncanplay = () => audio.play();
  852. audio.crossorigin = "anonymous";
  853. audio.src = "https://www.myinstants.com/media/sounds/gta-san-andreas-mission-passed-sound_TpUVE5G.mp3";
  854. document.body.appendChild(img);
  855. setTimeout(() => img.remove(), 10000);
  856. }, () => {
  857. let xporb = new Image;
  858. xporb.src = "https://minecraft.wiki/images/Experience_Orb_Value_3-6.png?6de8c&format=original";
  859. xporb.style = `position:fixed;translate:-50% -50%;width:25px;background-color:transparent`;
  860. let orbsfx = new Audio("https://minecraft.wiki/images/Successful_hit.ogg?b99e0");
  861. let orbs = Array(document.querySelectorAll(".verdict-ac").length * 2).fill(0).map(() => {
  862. let orb = xporb.cloneNode();
  863. orb.style.top = Math.random() * innerHeight + "px";
  864. orb.style.left = Math.random() * innerWidth + "px";
  865. document.body.appendChild(orb);
  866. return orb;
  867. });
  868. let mousex = ev.clientX, mousey = ev.clientY;
  869. addEventListener("mousemove", e => {
  870. mousex = e.clientX;
  871. mousey = e.clientY;
  872. });
  873. function fr() {
  874. orbs = orbs.map(o => {
  875. if (!o) return;
  876. let x = parseFloat(o.style.left), y = parseFloat(o.style.top);
  877. let dist = ((mousex - x) ** 2 + (mousey - y) ** 2) ** .5, speed = 150 * dist ** -.7;
  878. let vx = speed / dist * (mousex - x), vy = speed / dist * (mousey - y);
  879. if (Math.abs(x - mousex) < o.offsetWidth / 2 && Math.abs(y - mousey) < o.offsetHeight / 2) {
  880. o.remove();
  881. let sfx = orbsfx.cloneNode();
  882. sfx.playbackRate = Math.random() * .7 + .55;
  883. sfx.mozPreservesPitch = false;
  884. sfx.webkitPreservesPitch = false;
  885. sfx.preservesPitch = false;
  886. sfx.play();
  887. return null;
  888. }
  889. o.style.left = x + vx + "px";
  890. o.style.top = y + vy + "px";
  891. return o;
  892. });
  893. if (orbs.some(x => x)) requestAnimationFrame(fr);
  894. }
  895. fr();
  896. }], failCinematics = [() => {
  897. let audio = document.createElement("audio");
  898. audio.oncanplay = () => audio.play();
  899. audio.crossorigin = "anonymous";
  900. audio.src = "https://www.myinstants.com/media/sounds/erro.mp3";
  901. let img = document.createElement("img");
  902. img.src = "https://i.kym-cdn.com/photos/images/original/000/918/810/a22.jpg";
  903. img.style = "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(.5); background-color: transparent !important; box-shadow: black 10px 10px 5px";
  904. document.body.appendChild(img);
  905. img.onclick = () => img.remove();
  906. }, () => {
  907. let audio = new Audio("https://minecraft.wiki/images/Player_hurt3.ogg");
  908. audio.play();
  909. audio.oncanplay = () => audio.play();
  910. let dm = document.createElement("map");
  911. dm.name = "deathmap";
  912. dm.innerHTML = `<area shape=rect coords="322,371,977,436" href="javascript:document.getElementById('deathmap').remove();document.querySelector('.page-container').classList.remove('deathscreen')">
  913. <area shape=rect coords="322,450,977,509" href="/">
  914. <img src="https://i.ibb.co/DHYzFtyL/deathscreen.png" height=720 style="background-color:transparent" usemap="#deathmap">`;
  915. dm.style = "position:fixed;top:50%;left:50%;translate:-50% -50%";
  916. let bg = document.createElement("div");
  917. bg.style = "top:0;left:0;width:100vw;height:100vh;position:fixed;background-color:#ff000540";
  918. bg.id = "deathmap";
  919. bg.appendChild(dm);
  920. document.body.appendChild(bg);
  921. document.querySelector(".page-container").classList.add("deathscreen");
  922. }];
  923. if (showType == 1) cinematics[Math.floor(Math.random() * cinematics.length)]();
  924. else failCinematics[Math.floor(Math.random() * failCinematics.length)]();
  925. };
  926. document.body.appendChild(preCinematicScreen);
  927. }
  928. async function Changelog() {
  929. alert(await fetch("https://raw.githubusercontent.com/EntityPlantt/EntityPlantt.github.io/refs/heads/main/mendo-enhancement/changelog.txt").then(x => x.text()).catch(x => "Changelog not found"));
  930. }
  931. function getAchievements() {
  932. let ach = (localStorage.getItem("achievements " + document.querySelector("#LoginForm>fieldset>p>a").innerText) || "{}");
  933. if (ach[0] != "{") {
  934. alert(localize("As of 44.2, The way achievements are stored was updated, please get them again. Sorry", "Од 44.2, Начинот на зачувување на постигнувањата е сменет, ве молам добиете ги пак. Жал ми е"));
  935. ach = {};
  936. }
  937. else ach = JSON.parse(ach);
  938. return ach;
  939. }
  940. function addAchievement(name, lvl) {
  941. let ach = getAchievements();
  942. if (ach[name] >= lvl) return false;
  943. ach[name] = lvl;
  944. localStorage.setItem("achievements " + document.querySelector("#LoginForm>fieldset>p>a").innerText, JSON.stringify(ach));
  945. achievementToast(achname[name + lvl]);
  946. return true;
  947. }
  948. function achievementToast(text) {
  949. if (!document.getElementById("achievement-toast")) {
  950. let d = document.createElement("div");
  951. d.id = "achievement-toast";
  952. document.body.appendChild(d);
  953. }
  954. let div = document.createElement("div");
  955. div.innerHTML = `<div>${localize("Achievement got!", "Постигнување добиено!")}</div><div>${text}</div>`;
  956. document.getElementById("achievement-toast").appendChild(div);
  957. }
  958. Object.assign(window, { MendoMkEnhancement, taskSolveCinematic, Changelog, addAchievement, getAchievements, achievementToast, localize });
  959. MendoMkEnhancement();