Tabbed AtCoder Editorial

display atcoder editorial in tabs

11.02.2023 itibariyledir. En son verisyonu görün.

  1. // ==UserScript==
  2. // @name Tabbed AtCoder Editorial
  3. // @version 0.7
  4. // @description display atcoder editorial in tabs
  5. // @match https://atcoder.jp/contests/*/editorial
  6. // @match https://atcoder.jp/contests/*/editorial?*
  7. // @grant GM_addStyle
  8. // @license MIT
  9. // @namespace https://greatest.deepsurf.us/users/808669
  10. // ==/UserScript==
  11.  
  12. /* jshint esversion:8 */
  13. (async function () {
  14. 'use strict';
  15.  
  16. const katexoption = {
  17. delimiters: [
  18. { left: "$$", right: "$$", display: true },
  19. { left: "$", right: "$", display: false },
  20. { left: "\\(", right: "\\)", display: false },
  21. { left: "\\[", right: "\\]", display: true }
  22. ],
  23. ignoredTags: ["script", "noscript", "style", "textarea", "code", "option"],
  24. ignoredClasses: ["prettyprint", "source-code-for-copy"],
  25. throwOnError: false
  26. };
  27.  
  28. async function addScript(src) {
  29. return new Promise((resolve) => {
  30. const script = document.createElement("script");
  31. script.type = "text/javascript";
  32. script.src = src;
  33. script.onload = resolve;
  34. document.getElementsByTagName("head")[0].appendChild(script);
  35. });
  36. }
  37.  
  38. async function addLink(href) {
  39. return new Promise((resolve) => {
  40. const link = document.createElement("link");
  41. link.rel = "stylesheet";
  42. link.href = href;
  43. link.onload = resolve;
  44. document.getElementsByTagName("head")[0].appendChild(link);
  45. });
  46. }
  47.  
  48. async function getEditorial(link) {
  49. return new Promise((resolve) => {
  50. const xhr = new XMLHttpRequest();
  51. xhr.responseType = "document";
  52. xhr.onload = (response) => {
  53. const editorial = response.target.responseXML.querySelector("#main-container > div.row > div:nth-child(2) > div");
  54. if (editorial) {
  55. renderMathInElement(editorial, katexoption);
  56. link.parentNode.appendChild(editorial);
  57. }
  58. resolve();
  59. };
  60. xhr.open("GET", link.href);
  61. xhr.send();
  62. });
  63. }
  64.  
  65. async function getTextResponse(href) {
  66. return new Promise((resolve) => {
  67. const xhr = new XMLHttpRequest();
  68. xhr.onload = (response) => {
  69. resolve(response.target.responseText);
  70. };
  71. xhr.open("GET", href);
  72. xhr.overrideMimeType("text/plain; charset=Shift_JIS");
  73. xhr.send();
  74. });
  75. }
  76.  
  77. async function typical90(id) {
  78. const editorial = { "005": 3, "011": 2, "017": 3, "023": 4, "029": 2, "035": 3, "041": 3, "047": 2, "053": 4, "059": 3, "065": 3, "071": 3, "077": 3, "083": 4, "084": 2, "085": 2, "086": 2, "087": 2, "088": 2, "089": 4, "090": 6 };
  79. const source = { "005": "005-03", "011": "011-03", "017": "017-03", "023": "023-04b", "029": "029-03", "035": "035-04", "041": "041-03", "047": "047-02", "053": "053-04", "059": "059-02", "061": "061-02", "065": "065-03", "071": "071-03", "077": "077-04b", "083": "083-02a", "084": "084-02", "089": "089-05", "090": "090-07b" };
  80. let content = `<a href="https://github.com/E869120/kyopro_educational_90/blob/main/problem" rel="nofollow">問題文</a>
  81. <img src="https://raw.githubusercontent.com/E869120/kyopro_educational_90/main/problem/${id}.jpg" style="max-width: 100%;">
  82. <hr><a href="https://github.com/E869120/kyopro_educational_90/blob/main/editorial" rel="nofollow">公式解説</a>
  83. `;
  84. if (editorial[id] === undefined) {
  85. content += `<img src="https://raw.githubusercontent.com/E869120/kyopro_educational_90/main/editorial/${id}.jpg" style="max-width: 100%;">`;
  86. } else {
  87. for (let i = 1; i <= editorial[id]; i++) {
  88. content += `<img src="https://raw.githubusercontent.com/E869120/kyopro_educational_90/main/editorial/${id}-${String(i).padStart(2, "0")}.jpg" style="max-width: 100%;">`;
  89. }
  90. }
  91. const code = await getTextResponse(`https://raw.githubusercontent.com/E869120/kyopro_educational_90/main/sol/${source[id] === undefined ? id : source[id]}.cpp`);
  92. content += `<hr><a href="https://github.com/E869120/kyopro_educational_90/tree/main/sol" rel="nofollow">想定ソースコード</a><pre class="prettyprint linenums"><code class="language-cpp">${code}</code></pre>`;
  93. return `<ul><li>${content}</li></ul>`;
  94. }
  95.  
  96. async function createTab() {
  97. const parser = new DOMParser();
  98. const parse = s => parser.parseFromString(s, "text/html").body.firstChild;
  99. const nav = document.querySelector("#main-container > div.row > div:nth-child(2)");
  100. const dummy = document.createElement("div");
  101. const navul = parse(`<ul class="nav nav-tabs" role="tablist"></ul>`);
  102. const navdiv = parse(`<div class="tab-content"></div>`);
  103.  
  104. let previd = "dummy";
  105. let isactive = true;
  106. let prevhead = -1;
  107. let kaisetsu = -1;
  108.  
  109. while (nav.children.length > 0) {
  110. const e = nav.firstChild;
  111. const summary = e.textContent.trimStart().split(/\s+/)[0];
  112. if (e.tagName === "DIV" && summary === "解説") {
  113. kaisetsu = dummy.children.length;
  114. dummy.appendChild(e);
  115. } else if (e.tagName === "DIV" || e.tagName === "H3") {
  116. const cond = e.textContent === "コンテスト全体の解説";
  117. const name = cond ? "全体" : summary;
  118. const id = cond ? "all" : summary;
  119. const li = parse(`<li role="presentation">
  120. <a href="#editorial-${id}" aria-controls="editorial-${id}" role="tab" data-toggle="tab">${name}</a>
  121. </li>`);
  122. if (isactive) li.classList.add("active");
  123. navul.appendChild(li);
  124. previd = id;
  125. prevhead = dummy.children.length;
  126. dummy.appendChild(e);
  127. } else if (e.tagName === "UL" || e.tagName == "P") {
  128. if (e.tagName === "UL") {
  129. for (let i = e.children.length; i-- > 0; i) {
  130. const ch = e.children[i];
  131. if (ch.tagName == "LI") {
  132. const link = dummy.children[prevhead].querySelector("a");
  133. if (link) {
  134. const found = link.href.match(/https:\/\/atcoder\.jp\/contests\/(.+)\/tasks\/(.+)/);
  135. if (found) {
  136. const contest = found[1];
  137. const task = found[2];
  138. const user = ch.querySelector("a.username").textContent;
  139. const a = parse(`<a href="/contests/${contest}/submissions?f.Task=${task}&f.Status=AC&f.User=${user}">
  140. <span aria-hidden="true" data-html="true" data-toggle="tooltip" title="${user}さんの提出を見る" class="glyphicon glyphicon-search black"></span>
  141. </a>`);
  142. ch.appendChild(a);
  143. }
  144. } else {
  145. const found = location.href.match(/https:\/\/atcoder\.jp\/contests\/([^/]+)\//);
  146. if (found) {
  147. const contest = found[1];
  148. const user = ch.querySelector("a.username").textContent;
  149. const a = parse(`<a href="/contests/${contest}/submissions?f.Status=AC&f.User=${user}">
  150. <span aria-hidden="true" data-html="true" data-toggle="tooltip" title="${user}さんの提出を見る" class="glyphicon glyphicon-search black"></span>
  151. </a>`);
  152. ch.appendChild(a);
  153. }
  154. }
  155. }
  156. e.insertBefore(document.createElement("hr"), ch);
  157. }
  158. }
  159. const div = document.createElement("div");
  160. div.role = "tabpanel";
  161. div.classList.add("tab-pane");
  162. if (isactive) div.classList.add("active");
  163. div.id = "editorial-" + previd;
  164. div.appendChild(dummy.children[prevhead]);
  165. if (location.href.match(/https:\/\/atcoder\.jp\/contests\/typical90\/tasks\/.*\/editorial/) && 1 <= Number(previd) && Number(previd) <= 90) {
  166. div.appendChild(parse(await typical90(previd)));
  167. if (e.textContent !== "解説がまだありません。") {
  168. div.appendChild(e);
  169. } else {
  170. dummy.appendChild(e);
  171. }
  172. } else {
  173. div.appendChild(e);
  174. }
  175. navdiv.appendChild(div);
  176. isactive = false;
  177. } else {
  178. dummy.appendChild(e);
  179. }
  180. }
  181.  
  182. if (kaisetsu >= 0) nav.appendChild(dummy.children[kaisetsu]);
  183. nav.appendChild(navul);
  184. nav.appendChild(navdiv);
  185. return Promise.all(
  186. Array.prototype.filter.call(
  187. navdiv.getElementsByTagName('a'),
  188. e => e.href.match(/https:\/\/atcoder\.jp\/contests\/.*\/editorial\//)
  189. ).map(e => getEditorial(e))
  190. );
  191. }
  192.  
  193. GM_addStyle("pre code { tab-size: 4; }");
  194. await addLink("https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css");
  195. await addScript("https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.js");
  196. await addScript("https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/contrib/auto-render.min.js");
  197. await createTab();
  198. await addScript("https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js");
  199. })();