Greasy Fork镜像 还支持 简体中文。

CFCodereviewer

Codereview codeforces

目前為 2024-11-13 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name CFCodereviewer
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1.2
  5. // @description Codereview codeforces
  6. // @author kdzestelov
  7. // @license MIT
  8. // @match *://*codeforces.com/*
  9. // @grant GM_xmlhttpRequest
  10. // @updateUrl https://gist.githubusercontent.com/KhetagAb/000670a28c8a37b917ece858b142b747/raw/script.js
  11. // ==/UserScript==
  12.  
  13. const SHEET_URL = 'https://script.google.com/macros/s/AKfycbxGoJrsWBtwNFcYcXBNrk-PUTIOWrsRvPqDxBzBqT_TFpfaurt8IB5Dc9dEQU97-Eoyug/exec';
  14.  
  15. const SUB_RJ_BUTTON_CLASS = "submission-rj-button";
  16. const SUB_AC_BUTTON_CLASS = "submission-ac-button";
  17. const SUB_RG_BUTTON_CLASS = "submission-rejudge-form";
  18. const SUB_COMMENT_SEND_BUTTON_CLASS = "submission-comment-send-form";
  19.  
  20. function getLast(href) {
  21. return href.split("/").pop()
  22. }
  23.  
  24. function getGroupUrl() {
  25. const url = window.location.href
  26. return url.substring(0, url.indexOf("contest"))
  27. }
  28.  
  29. const getContestUrl = () => {
  30. const url = window.location.href
  31. return url.substring(0, url.indexOf("status"))
  32. }
  33.  
  34. function getContestId() {
  35. const url = window.location.href
  36. return url.substr(url.indexOf("contest") + 8, 6);
  37. }
  38.  
  39. function wrap(text) {
  40. return '[' + text + ']';
  41. }
  42.  
  43. function getSubRjButton(subId) {
  44. return $("[submissionid=" + subId + "] ." + SUB_RJ_BUTTON_CLASS);
  45. }
  46.  
  47. function getSubAcButton(subId) {
  48. return $("[submissionid=" + subId + "] ." + SUB_AC_BUTTON_CLASS);
  49. }
  50.  
  51. function getCommentSubAcButton(subId) {
  52. return $("." + SUB_COMMENT_SEND_BUTTON_CLASS)
  53. }
  54.  
  55. function getProblemIndex(subId) {
  56. return getLast($("tr[data-submission-id=" + subId + "] .status-small a").attr('href'));
  57. }
  58.  
  59. function getSubRow(subId) {
  60. return $('tr[data-submission-id="' + subId + '"]')
  61. }
  62.  
  63. function getHandle(subId) {
  64. return $("tr[data-submission-id=" + subId + "] .status-party-cell").text().trim();
  65. }
  66.  
  67. function getAllSubmissionsRow() {
  68. return $(".status-frame-datatable tbody tr")
  69. }
  70.  
  71. function getSubmissionRow(subId) {
  72. return $(".status-frame-datatable tbody tr[data-submission-id=" + subId + "]")
  73. }
  74.  
  75. function getSubsId() {
  76. return $(".information-box-link")
  77. }
  78.  
  79. function getCorrectSubs() {
  80. return $(".information-box-link .verdict-accepted").parent();
  81. }
  82.  
  83. function getSubButtons() {
  84. return $(".submission-action-form");
  85. }
  86.  
  87. function getSideBar() {
  88. return $("div[id=sidebar]")
  89. }
  90.  
  91. function getFilterBox() {
  92. return $(".status-filter-box");
  93. }
  94.  
  95. const getSheetSubmissions = () => {
  96. const fullUrl = SHEET_URL + "?type=get"
  97. GM_xmlhttpRequest({
  98. method: 'GET',
  99. url: fullUrl,
  100. onload: (response) => {
  101. if (response.status === 200) {
  102. localStorage.setItem("c_status", response.responseText);
  103. console.log("Submissions loaded from sheet: " + response.responseText);
  104. } else {
  105. console.error("Unable to load submissions from sheet: " + response.responseText);
  106. }
  107. },
  108. onerror: (error) => {
  109. console.error(error);
  110. }
  111. });
  112. }
  113.  
  114. const acceptSheetSubmission = (subId, button, type) => {
  115. const full_url = SHEET_URL + "?type=" + type + "&value=" + subId;
  116. GM_xmlhttpRequest({
  117. method: 'GET',
  118. url: full_url,
  119. timeout: 5000,
  120. onload: (response) => {
  121. if (response.status === 200) {
  122. const submissions = getSubmissions();
  123. submissions.push(subId);
  124. localStorage.setItem("c_status", JSON.stringify(submissions));
  125. button.style.backgroundColor = "#81D718";
  126. button.style.borderColor = "#81D718";
  127. if (showed_codes[subId] != null && showed_codes[subId].showed) {
  128. showed_codes[subId]["showButton"].click();
  129. }
  130. button.innerText = (type === "star" ? "🔥 Восхвалено" : "Похвалить");
  131. } else {
  132. button.innerText = "Ошибка!"
  133. console.error(response)
  134. }
  135. },
  136. onerror: (error) => {
  137. console.error(error);
  138. }
  139. });
  140. }
  141. const getSubmissions = () => {
  142. return JSON.parse(localStorage.getItem("c_status") ?? "[]");
  143. };
  144. const isSubAccepted = (subId) => {
  145. return getSubmissions().includes(subId)
  146. }
  147.  
  148. const acceptSubmission = (subId, button) => {
  149. const type = isSubAccepted(subId) ? "star" : "accept";
  150. acceptSheetSubmission(subId, button, type);
  151. }
  152.  
  153. const showed_codes = {};
  154.  
  155. function createSubShowButton(subId) {
  156. const button = document.createElement("button");
  157. button.className = "submission-show";
  158. button.style.marginTop = "10px";
  159. button.style.backgroundColor = "#176F95";
  160. button.style.border = "1px solid #176F95";
  161. button.style.borderRadius = "8px"
  162. button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
  163. button.style.color = "#fff"
  164. button.style.cursor = "pointer"
  165. button.style.padding = "8px 0.5em"
  166. button.style.width = "100%";
  167. button.innerText = "Показать код";
  168. button.onclick = (_) => showButtonClick(subId, button);
  169. return button;
  170. }
  171.  
  172. function patchCodeSection(subId) {
  173. const patchLine = (i, line) => {
  174. line.addEventListener('click', () => {
  175. const text = $("[data-submission-id=" + subId + "] textarea")
  176. text.val((text.val().length === 0 ? "" : text.val() + "\n") + "Строка " + i + ": ")
  177. text.focus();
  178. });
  179. return line;
  180. };
  181.  
  182. let pretty_code = $('[data-submission-id=' + subId + '] .program-source li');
  183. const code_lines_count = pretty_code.length
  184. pretty_code.each((i, line) => patchLine(i, line))
  185. pretty_code = pretty_code.parent().parent();
  186. pretty_code.before((_) => {
  187. const lines = document.createElement("pre");
  188. lines.style.width = '4%';
  189. lines.style.padding = "0.5em";
  190. lines.style.display = 'inline-block';
  191. const lineNs = [...Array(code_lines_count).keys()].map((i) => {
  192. const line = document.createElement("span");
  193. line.style.color = 'rgb(153, 153, 153)';
  194. line.innerText = "[" + i + "]";
  195. line.style.display = "block";
  196. line.style.textAlign = "right";
  197. line.style.userSelect = "none";
  198. line.style.cursor = "pointer";
  199. return patchLine(i, line);
  200. })
  201. lines.append(...lineNs)
  202. return lines
  203. })
  204. pretty_code.css({'display': 'inline-block', 'width': '90%'})
  205. }
  206.  
  207. function showButtonClick(subId, button) {
  208. if (showed_codes[subId] != null) {
  209. if (showed_codes[subId].showed == true) {
  210. $(showed_codes[subId]["commentSection"]).hide();
  211. $(showed_codes[subId]["codeSection"]).hide();
  212. button.innerText = "Показать код";
  213. showed_codes[subId].showed = false;
  214. } else if (showed_codes[subId].showed == false) {
  215. $(showed_codes[subId]["commentSection"]).show();
  216. $(showed_codes[subId]["codeSection"]).show();
  217. button.innerText = "Скрыть код";
  218. showed_codes[subId].showed = true;
  219. }
  220. } else {
  221. button.innerText = showed_codes[subId] = "Загружаю код..."
  222. const requestUrl = getContestUrl() + 'submission/' + subId;
  223. console.log(requestUrl);
  224. $.get(requestUrl, function (html) {
  225. const codeHtml = $(html).find(".SubmissionDetailsFrameRoundBox-" + subId).html()
  226.  
  227. if (codeHtml == undefined) {
  228. button.innerText = "Ошибка!";
  229. //location.reload();
  230. return;
  231. }
  232.  
  233. const commentSection = createCommentSection(subId)
  234. const subCodeSection = createSubCodeSection(subId, codeHtml);
  235.  
  236. const subRow = getSubRow(subId);
  237. subRow.after(commentSection, subCodeSection)
  238.  
  239. prettyPrint(subId);
  240.  
  241. patchCodeSection(subId);
  242.  
  243. showed_codes[subId] = {
  244. "showed": true,
  245. "showButton": button,
  246. "commentSection": commentSection,
  247. "codeSection": subCodeSection
  248. }
  249. button.innerText = "Скрыть код"
  250. });
  251. }
  252. }
  253.  
  254. function createSubCodeSection(subId, codeHtml) {
  255. const trSubCode = document.createElement("tr");
  256. trSubCode.setAttribute('data-submission-id', subId);
  257. const tdSubCode = document.createElement("td");
  258. tdSubCode.setAttribute('colspan', '8');
  259. tdSubCode.innerHTML = codeHtml;
  260. tdSubCode.style.textAlign = "start"
  261. trSubCode.append(tdSubCode);
  262. return trSubCode;
  263. }
  264.  
  265. const createCommentSection = (subId) => {
  266. const subAcButton = getSubAcButton(subId)[0];
  267. const isAccepted = isSubAccepted(subId);
  268. const commentTextfield = createCommentTextfield()
  269. const commentAcButton = commentSendButtonTemplate(subId, (isAccepted ? "Похвалить" : "Принять") + " с комментарием", (isAccepted ? "#81D718" : "#13aa52"), (subId, button) => {
  270. const text = $(commentTextfield).val();
  271. if (text.length === 0) {
  272. button.innerText = "Принимаю...";
  273. acceptSubmission(subId, subAcButton);
  274. } else {
  275. button.innerText = "Отправляю...";
  276. $.post(getGroupUrl() + 'data/newAnnouncement', {
  277. contestId: getContestId(),
  278. englishText: "",
  279. russianText: text,
  280. submittedProblemIndex: getProblemIndex(subId),
  281. targetUserHandle: getHandle(subId),
  282. announceInPairContest: true,
  283. }, () => {
  284. acceptSubmission(subId, subAcButton);
  285. })
  286. }
  287. })
  288. const commentRjButton = commentSendButtonTemplate(subId, "Отклонить с комментарием", "#EC431A", (subId, button) => {
  289. const text = $(commentTextfield).val();
  290. if (text.length > 0) {
  291. button.innerText = "Отклоняю...";
  292. $.post(getGroupUrl() + 'data/newAnnouncement', {
  293. contestId: getContestId(),
  294. englishText: "",
  295. russianText: text,
  296. submittedProblemIndex: getProblemIndex(subId),
  297. targetUserHandle: getHandle(subId),
  298. announceInPairContest: true,
  299. }, () => {
  300. rejectSub(subId);
  301. if (showed_codes[subId] != null) {
  302. showed_codes[subId]['codeSection'].hide()
  303. }
  304. button.innerText = "Отклонено";
  305. })
  306. }
  307. });
  308.  
  309. commentTextfield.addEventListener("keyup", (event) => {
  310. event.preventDefault();
  311. if (event.keyCode === 13) {
  312. commentRjButton.click();
  313. }
  314. });
  315.  
  316. const trSection = document.createElement("tr");
  317. trSection.setAttribute('data-submission-id', subId);
  318. const tdSection = document.createElement("td");
  319. tdSection.setAttribute('colspan', '8');
  320. const tdSectionTitle = document.createElement("div");
  321. tdSectionTitle.style.textAlign = "left";
  322. tdSectionTitle.className = "caption titled"
  323. tdSectionTitle.innerText = "→ Комментарий"
  324. tdSection.append(tdSectionTitle, commentTextfield, commentAcButton, commentRjButton);
  325. trSection.append(tdSection)
  326. return trSection;
  327. }
  328.  
  329. function createCommentTextfield() {
  330. const textField = document.createElement("textarea");
  331. textField.name = "russianText"
  332. textField.className = "bottom-space-small monospaced"
  333. textField.style.width = "80rem";
  334. textField.style.height = "5rem";
  335. textField.style.margin = "4px";
  336. return textField;
  337. }
  338.  
  339. const commentSendButtonTemplate = (subId, text, color, action) => {
  340. const button = document.createElement("button");
  341. button.className = SUB_COMMENT_SEND_BUTTON_CLASS;
  342. button.style.margin = "4px";
  343. button.style.width = "40%";
  344. button.style.backgroundColor = color;
  345. button.style.border = "1px solid " + color;
  346. button.style.borderRadius = "8px"
  347. button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
  348. button.style.color = "#fff"
  349. button.style.cursor = "pointer"
  350. button.style.padding = "8px 0.5em"
  351. button.innerText = text;
  352. button.onclick = () => action(subId, button);
  353. return button
  354. }
  355.  
  356. const acButtonTemplate = (subId, action, text) => {
  357. const acButton = document.createElement("button");
  358. acButton.className = SUB_AC_BUTTON_CLASS;
  359. const color = (isSubAccepted(subId) ? "#81D718" : "#13aa52");
  360. acButton.style.backgroundColor = color;
  361. acButton.style.border = "1px solid " + color;
  362. acButton.style.borderRadius = "8px"
  363. acButton.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
  364. acButton.style.color = "#fff"
  365. acButton.style.cursor = "pointer"
  366. acButton.style.padding = "8px 0.5em"
  367. acButton.style.margin = "5px 5px 0 0";
  368. acButton.style.width = "59%";
  369. acButton.innerText = text !== undefined ? text : (isSubAccepted(subId) ? "Похвалить" : "Принять");
  370. acButton.onclick = (_) => action(subId, acButton);
  371. return acButton;
  372. }
  373.  
  374. const createAcButton = (template, subId, ...args) => {
  375. return template(subId, (subId, button) => {
  376. button.innerText = "Подтверждаю...";
  377. button.style.borderColor = "gray";
  378. button.style.backgroundColor = "gray";
  379. acceptSubmission(subId, button);
  380. }, ...args);
  381. }
  382.  
  383. const createRjButton = (subId, text, action) => {
  384. const rjButton = document.createElement("button");
  385. rjButton.className = SUB_RJ_BUTTON_CLASS;
  386. rjButton.style.width = "38%";
  387. rjButton.style.backgroundColor = "#EC431A";
  388. rjButton.style.border = "1px solid #EC431A"
  389. rjButton.style.borderRadius = "8px"
  390. rjButton.style.padding = "8px 0.5em"
  391. rjButton.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
  392. rjButton.style.color = "#fff"
  393. rjButton.style.cursor = "pointer"
  394. rjButton.innerText = text;
  395. rjButton.onclick = (_) => action(subId, rjButton);
  396. return rjButton
  397. }
  398.  
  399. function createRgButtonFromTemplate(subId, text, action) {
  400. const button = document.createElement("button");
  401. button.className = SUB_RG_BUTTON_CLASS;
  402. button.style.marginTop = "7px";
  403. button.style.width = "200px";
  404. button.innerText = text;
  405. button.onclick = (_) => action(subId, button);
  406. return button;
  407. }
  408.  
  409. const createRgButton = (subId) => {
  410. return createRgButtonFromTemplate(subId, "Перетестировать", (subId, button) => {
  411. const requestUrl = getContestUrl() + 'submission/' + subId
  412. const data = {action: "rejudge", submissionId: subId}
  413. $.post(requestUrl, data, (_) => location.reload());
  414. button.innerText = "Тестирую...";
  415. });
  416. }
  417.  
  418. const rejectSub = (subId) => {
  419. const subRjButton = getSubRjButton(subId);
  420. subRjButton.innerText = "Отклоняю...";
  421. const subAcButton = getSubAcButton(subId);
  422. const commentSubAcButton = getCommentSubAcButton(subId);
  423. const requestUrl = getContestUrl() + 'submission/' + subId
  424. const data = {action: "reject", submissionId: subId}
  425. $.post(requestUrl, data, function (_) {
  426. $("[submissionid=" + subId + "] .verdict-accepted").remove()
  427. subAcButton.remove()
  428. subRjButton.remove()
  429. commentSubAcButton.remove();
  430. })
  431. }
  432.  
  433. const patchSubmissions = () => {
  434. const subsId = getSubsId();
  435. subsId.after((i) => {
  436. const subId = Number($(subsId[i])[0].getAttribute('submissionid'))
  437. return createSubShowButton(subId);
  438. })
  439. }
  440.  
  441. const patchCorrectSubmissions = () => {
  442. const correctSubs = getCorrectSubs();
  443. correctSubs.after((i) => {
  444. const subId = Number($(correctSubs[i]).attr('submissionid'))
  445.  
  446. const acButton = createAcButton(acButtonTemplate, subId)
  447. const rgButton = createRgButton(subId);
  448. const rjButton = createRjButton(subId, "Отклонить", (subId, _) => {
  449. rejectSub(subId);
  450. });
  451.  
  452. return [acButton, rjButton, rgButton]
  453. })
  454. }
  455.  
  456. const patchContestSidebar = () => {
  457. const contestsSidebar = $(".GroupContestsSidebarFrame ul a")
  458. contestsSidebar.before((i) => {
  459. const contestHref = $(contestsSidebar[i]).attr('href');
  460. return document.createTextNode(wrap(getLast(contestHref)));
  461. });
  462. }
  463.  
  464. const patchSubmission = () => {
  465. const buttons = getSubButtons()
  466. if (buttons.length > 0) {
  467. const subId = Number(getLast(location.pathname));
  468. const acButton = createAcButton(acButtonTemplate, subId);
  469. buttons[0].before(acButton);
  470. }
  471. }
  472.  
  473. const patchFilterBox = () => {
  474. const filterBox = getFilterBox();
  475. const sidebar = getSideBar();
  476. filterBox.detach().prependTo(sidebar);
  477. const filterBoxPart = filterBox.find(".status-filter-form-part")[0];
  478.  
  479. const correctSubsId = getCorrectSubs().map((i, e) => Number($(e).attr("submissionid"))).toArray();
  480. const filter = (checkbox) => {
  481. localStorage.setItem("filterPendingSubs", checkbox.checked);
  482. const filtered = correctSubsId.filter(subId => {
  483. console.log(subId + " " + !isSubAccepted(subId))
  484. return !isSubAccepted(subId)
  485. });
  486. console.log(filtered)
  487. getAllSubmissionsRow().each((i, e) => {
  488. if (checkbox.checked) {
  489. if (!filtered.includes(Number($(e).attr('data-submission-id')))) {
  490. $(e).hide();
  491. }
  492. } else {
  493. $(e).show()
  494. }
  495. });
  496. };
  497.  
  498. const template = createFilterPendingCheckboxTemplate(filter);
  499. const label = template[0]
  500. const checkbox = template[1]
  501. checkbox.checked = ('true' === localStorage.getItem("filterPendingSubs") ?? false);
  502. filter(checkbox);
  503. filterBoxPart.before(label);
  504. }
  505.  
  506. function createFilterPendingCheckboxTemplate(action) {
  507. const label = document.createElement("label");
  508. const checkbox = document.createElement("input");
  509. checkbox.type = "checkbox";
  510. checkbox.onclick = (_) => action(checkbox);
  511. const title = document.createElement("span");
  512. title.style.padding = "5px";
  513. title.className = "smaller";
  514. title.innerText = "Только непроверенные посылки";
  515. label.append(checkbox, title);
  516. return [label, checkbox];
  517. }
  518.  
  519.  
  520. (function () {
  521. getSheetSubmissions();
  522.  
  523. try {
  524. patchContestSidebar();
  525. } catch (e) {
  526. console.error(e);
  527. }
  528.  
  529. try {
  530. patchFilterBox();
  531. } catch (e) {
  532. console.error(e);
  533. }
  534.  
  535. try {
  536. patchCorrectSubmissions();
  537. } catch (e) {
  538. console.error(e);
  539. }
  540.  
  541. try {
  542. patchSubmissions();
  543. } catch (e) {
  544. console.error(e);
  545. }
  546.  
  547. try {
  548. patchSubmission();
  549. } catch (e) {
  550. console.error(e);
  551. }
  552. })();

QingJ © 2025

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