AtCoder Editorial for Typical90

AtCoder「競プロ典型 90 問」に解説タブを追加し、E869120さんがGitHubで公開されている問題の解説・想定ソースコードなどのリンクを表示します。

اعتبارا من 09-07-2021. شاهد أحدث إصدار.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

  1. // ==UserScript==
  2. // @name AtCoder Editorial for Typical90
  3. // @namespace https://github.com/KATO-Hiro
  4. // @version 0.4.3
  5. // @description AtCoder「競プロ典型 90 問」に解説タブを追加し、E869120さんがGitHubで公開されている問題の解説・想定ソースコードなどのリンクを表示します。
  6. // @match https://atcoder.jp/contests/typical90*
  7. // @require https://code.jquery.com/jquery-3.6.0.min.js
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.10.5/dayjs.min.js
  9. // @author hiro_hiro
  10. // @license CC0
  11. // @downloadURL
  12. // @updateURL
  13. // @homepage https://github.com/KATO-Hiro/AtCoder-Editorial-for-Typical90
  14. // @supportURL https://github.com/KATO-Hiro/AtCoder-Editorial-for-Typical90/issues
  15. // @grant GM_addStyle
  16. // ==/UserScript==
  17.  
  18. (async function () {
  19. "use strict";
  20.  
  21. addTabs();
  22.  
  23. addLatestTaskPage();
  24.  
  25. const tasks = await fetchTasks(); // TODO: Use cache to reduce access to AtCoder.
  26. addEditorialPage(tasks);
  27.  
  28. $(".nav-tabs a").click(function () {
  29. changeTab($(this));
  30. hideContentsOfPreviousPage();
  31.  
  32. return false;
  33. });
  34.  
  35. // TODO: 「解説」ボタンをクリックしたら、該当する問題のリンクを表示できるようにする
  36. })();
  37.  
  38. function addTabs() {
  39. addTabContentStyles();
  40. addTabContents();
  41. addLatestTaskTab();
  42. addEditorialTab();
  43. }
  44.  
  45. function addTabContentStyles() {
  46. const tabContentStyles = `
  47. .tab-content {
  48. display: none;
  49. }
  50. .tab-content.active {
  51. display: block;
  52. }
  53. `;
  54.  
  55. GM_addStyle(tabContentStyles);
  56. }
  57.  
  58. function addTabContents() {
  59. const contestNavTabsId = document.getElementById("contest-nav-tabs");
  60.  
  61. // See:
  62. // https://stackoverflow.com/questions/268490/jquery-document-createelement-equivalent
  63. // https://blog.toshimaru.net/jqueryhidden-inputjquery/
  64. const idNames = [
  65. "latest-task-created-by-userscript",
  66. "editorial-created-by-userscript"
  67. ];
  68.  
  69. for (const idName of idNames) {
  70. $("<div>", {
  71. class: "tab-content",
  72. id: idName,
  73. }).appendTo(contestNavTabsId);
  74. }
  75. }
  76.  
  77. // FIXME: Hard code is not good.
  78. function addLatestTaskTab() {
  79. const parentTag = document.getElementsByClassName("nav nav-tabs")[0];
  80. const liNode = document.createElement("li");
  81. parentTag.insertBefore(liNode, parentTag.children[1]);
  82.  
  83. const idName = "#latest-task-created-by-userscript";
  84. const aClass = "latest-task-a";
  85.  
  86. $("<a>", {
  87. class: aClass,
  88. href: idName,
  89. }).appendTo(liNode);
  90.  
  91. $("<span>", {
  92. class: "glyphicon glyphicon-star",
  93. text: "新着",
  94. style: "margin-right:4px;",
  95. "aria-hidden": true,
  96. }).appendTo(`.${aClass}`);
  97. }
  98.  
  99. // FIXME: Hard coding is not good.
  100. function addEditorialTab() {
  101. // See:
  102. // https://api.jquery.com/before/
  103. $("li.pull-right").before("<li><a href='#editorial-created-by-userscript'><span class='glyphicon glyphicon-book' style='margin-right:4px;' aria-hidden='true'></span>解説</a></li>");
  104. }
  105.  
  106. function addLatestTaskPage() {
  107. const latestTaskId = "#latest-task-created-by-userscript";
  108.  
  109. showHeader("latest-task-header", "新着", latestTaskId);
  110. addHorizontalRule(latestTaskId);
  111.  
  112. const message = `注: コンテスト開催期間の平日と土曜日の08:00(日本標準時)に更新されます。`;
  113. addNote("latest-task-message", message, latestTaskId);
  114.  
  115. const taskId = getTodayTaskId();
  116. const taskIdPaddingZero = padZero(taskId);
  117. addLatestTaskHeader(taskIdPaddingZero, latestTaskId);
  118.  
  119. const ulName = "latest-task-ul";
  120.  
  121. $("<ul>", {
  122. class: ulName,
  123. text: ""
  124. }).appendTo(latestTaskId);
  125.  
  126. const githubRepoUrl = getGitHubRepoUrl();
  127. const latestTaskWithImageUrl = githubRepoUrl + "problem/" + taskIdPaddingZero + ".jpg";
  128. const latestTaskUrl = githubRepoUrl + "problem-txt/" + taskIdPaddingZero + ".txt";
  129. const latestTaskSampleUrl = githubRepoUrl + "sample/" + taskIdPaddingZero + ".txt";
  130. addLatestTask(latestTaskWithImageUrl, latestTaskUrl, ulName);
  131. addSample(latestTaskSampleUrl, ulName);
  132. }
  133.  
  134. function addLatestTaskHeader(taskId, parentTag) {
  135. addHeader(
  136. "<h3>", // heading_tag
  137. "latest-task", // className
  138. `問題 ${taskId}`, // text
  139. parentTag
  140. );
  141. }
  142.  
  143. function padZero(taskId) {
  144. // See:
  145. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
  146. return String(taskId).padStart(3, '0');
  147. }
  148.  
  149. function addLatestTask(latestTaskWithImageUrl, latestTaskUrl, parentTag) {
  150. const taskWithImage = "latest-task-with-image-li";
  151.  
  152. $("<li>", {
  153. class: taskWithImage,
  154. text: ""
  155. }).appendTo(`.${parentTag}`);
  156.  
  157. $("<a>", {
  158. class: "latest-task-image-url",
  159. href: latestTaskWithImageUrl,
  160. text: "問題文 + 画像",
  161. target: "_blank",
  162. rel: "noopener",
  163. }).appendTo(`.${taskWithImage}`);
  164.  
  165. const task = "latest-task-li";
  166.  
  167. $("<li>", {
  168. class: task,
  169. text: ""
  170. }).appendTo(`.${parentTag}`);
  171. $("<a>", {
  172. class: "latest-task-text-url",
  173. href: latestTaskUrl,
  174. text: "問題文のみ",
  175. target: "_blank",
  176. rel: "noopener",
  177. }).appendTo(`.${task}`);
  178. }
  179.  
  180. function addSample(url, parentTag) {
  181. const liName = "latest-task-sample-li";
  182.  
  183. $("<li>", {
  184. class: liName,
  185. text: ""
  186. }).appendTo(`.${parentTag}`);
  187.  
  188. $("<a>", {
  189. class: "latest-task-sample-url",
  190. href: url,
  191. text: "サンプル(入力形式、入出力例)",
  192. target: "_blank",
  193. rel: "noopener",
  194. }).appendTo(`.${liName}`);
  195. }
  196.  
  197. function getTodayTaskId() {
  198. // See:
  199. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
  200. const today = getToday();
  201. const startDay = getStartDay();
  202. let taskCount = today.diff(startDay, "day") + 1;
  203. taskCount -= countSunday(today);
  204.  
  205. if (!isSunday(today) && (isBeforeAmEight(today))) {
  206. taskCount -= 1
  207. }
  208.  
  209. const maxTaskId = 90;
  210. taskCount = Math.min(taskCount, maxTaskId);
  211.  
  212. return taskCount;
  213. }
  214.  
  215. // See:
  216. // https://day.js.org/en/
  217. function getToday() {
  218. const today = dayjs();
  219.  
  220. return today;
  221. }
  222.  
  223. function getStartDay() {
  224. const startDay = dayjs("2021-03-30");
  225.  
  226. return startDay;
  227. }
  228.  
  229. function getEndDay() {
  230. const endDay = dayjs("2021-07-12");
  231.  
  232. return endDay;
  233. }
  234.  
  235. function countSunday(today) {
  236. const sundays = getSundays();
  237. let count = 0;
  238.  
  239. for (const sunday of sundays) {
  240. if (today.isAfter(sunday)) {
  241. count += 1;
  242. }
  243. }
  244.  
  245. return count;
  246. }
  247.  
  248. function getSundays() {
  249. const sundays = [
  250. dayjs("2021-04-04"),
  251. dayjs("2021-04-11"),
  252. dayjs("2021-04-18"),
  253. dayjs("2021-04-25"),
  254. dayjs("2021-05-02"),
  255. dayjs("2021-05-09"),
  256. dayjs("2021-05-16"),
  257. dayjs("2021-05-23"),
  258. dayjs("2021-05-30"),
  259. dayjs("2021-06-06"),
  260. dayjs("2021-06-13"),
  261. dayjs("2021-06-20"),
  262. dayjs("2021-06-27"),
  263. dayjs("2021-07-04"),
  264. dayjs("2021-07-11"),
  265. ];
  266.  
  267. return sundays;
  268. }
  269.  
  270. function isSunday(today) {
  271. const sunday = 0;
  272.  
  273. if (today.day() == sunday) {
  274. return true;
  275. } else {
  276. return false
  277. }
  278. }
  279.  
  280. function isBeforeAmEight(today) {
  281. const todayHour = today.hour();
  282. const todayMinute = today.minute();
  283.  
  284. const year = today.year();
  285. const month = today.month() + 1; // 0-indexed.
  286. const date = today.date();
  287. const amEight = dayjs(`${year}-${month}-${date}T08:00`);
  288.  
  289. if (today.isBefore(amEight)) {
  290. return true
  291. } else {
  292. false
  293. }
  294. }
  295.  
  296. // TODO: キャッシュを利用して、本家へのアクセスを少なくなるようにする
  297. async function fetchTasks() {
  298. const tbodies = await fetchTaskPage();
  299. const tasks = new Object();
  300. let taskCount = 1;
  301.  
  302. for (const [index, aTag] of Object.entries($(tbodies).find("a"))) {
  303. // Ignore a-tags including task-id and "Submit".
  304. // See:
  305. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
  306. if (index % 3 == 1 && aTag.text.includes("★")) {
  307. const taskId = String(taskCount).padStart(3, "0");
  308. tasks[taskId] = [aTag.text, aTag.href];
  309. taskCount += 1;
  310. }
  311. }
  312.  
  313. return tasks;
  314. }
  315.  
  316. async function fetchTaskPage() {
  317. // See:
  318. // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
  319. // https://developer.mozilla.org/en-US/docs/Web/API/Body/text
  320. // https://developer.mozilla.org/ja/docs/Web/API/DOMParser
  321. // https://api.jquery.com/each/
  322. // http://dyn-web.com/tutorials/object-literal/properties.php#:~:text=Add%20a%20Property%20to%20an%20Existing%20Object%20Literal&text=myObject.,if%20it%20is%20a%20string).
  323. const tbodies = await fetch("https://atcoder.jp/contests/typical90/tasks", {
  324. method: "GET"
  325. })
  326. .then(response => {
  327. return response.text()
  328. })
  329. .then(html => {
  330. const parser = new DOMParser();
  331. const doc = parser.parseFromString(html, "text/html");
  332. const messages = doc.querySelector("#main-container > div.row > div:nth-child(2) > div > table > tbody");
  333.  
  334. return messages;
  335. })
  336. .catch(error => {
  337. console.warn('Something went wrong.', error);
  338. });
  339.  
  340. return tbodies;
  341. }
  342.  
  343. function addEditorialPage(tasks) {
  344. const editorialId = "#editorial-created-by-userscript";
  345.  
  346. showHeader("editorial-header", "解説", editorialId);
  347. addHorizontalRule(editorialId);
  348. showDifficultyVotingAndUserCodes(editorialId);
  349.  
  350. let taskEditorialsDiv = addDiv("task-editorials", editorialId);
  351. taskEditorialsDiv = "." + taskEditorialsDiv;
  352. addEditorials(tasks, taskEditorialsDiv);
  353. }
  354.  
  355. function showHeader(className, text, tag) {
  356. addHeader(
  357. "<h2>", // heading_tag
  358. className, // className
  359. text, // text
  360. tag // parent_tag
  361. );
  362. }
  363.  
  364. function addHeader(heading_tag, className, text, parent_tag) {
  365. $(heading_tag, {
  366. class: className,
  367. text: text,
  368. }).appendTo(parent_tag);
  369. }
  370.  
  371. function addHorizontalRule(tag) {
  372. // See:
  373. // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr
  374. $("<hr>", {
  375. class: "",
  376. }).appendTo(tag);
  377. }
  378.  
  379. function showDifficultyVotingAndUserCodes(tag) {
  380. addHeader(
  381. "<h3>", // heading_tag
  382. "difficulty-voting-and-user-codes", // className
  383. "問題の難易度を投票する・ソースコードを共有する", // text
  384. tag // parent_tag
  385. );
  386.  
  387. $("<ul>", {
  388. class: "spread-sheets-ul",
  389. text: ""
  390. }).appendTo(tag);
  391.  
  392. const spreadSheetUrl = "https://docs.google.com/spreadsheets/d/1GG4Higis4n4GJBViVltjcbuNfyr31PzUY_ZY1zh2GuI/edit#gid=";
  393.  
  394. const homeID = "0";
  395. addSpreadSheetHomeURL(spreadSheetUrl + homeID);
  396.  
  397. const difficultyVotingID = "1593175261";
  398. addDifficultyVotingURL(spreadSheetUrl + difficultyVotingID);
  399.  
  400. const taskGroups = [
  401. ["001", "023", spreadSheetUrl + "105162261"], // task start, task end, spread sheet id.
  402. ["024", "047", spreadSheetUrl + "1671161250"],
  403. ["048", "071", spreadSheetUrl + "671876031"],
  404. ["072", "090", spreadSheetUrl + "428850451"]
  405. ];
  406.  
  407. // See:
  408. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
  409. taskGroups.forEach(
  410. taskGroup => {
  411. const taskStart = taskGroup[0];
  412. const taskEnd = taskGroup[1];
  413. const url = taskGroup[2];
  414.  
  415. addUserCodesURL(
  416. taskStart,
  417. taskEnd,
  418. url
  419. );
  420. }
  421. );
  422. }
  423.  
  424. function addSpreadSheetHomeURL(url) {
  425. $("<li>", {
  426. class: "spread-sheet-home-li",
  427. text: ""
  428. }).appendTo(".spread-sheets-ul");
  429.  
  430. $("<a>", {
  431. class: "spread-sheet-home-url",
  432. href: url,
  433. text: "目的",
  434. target: "_blank",
  435. rel: "noopener",
  436. }).appendTo(".spread-sheet-home-li");
  437. }
  438.  
  439. function addDifficultyVotingURL(url) {
  440. $("<li>", {
  441. class: "difficulty-voting-li",
  442. text: ""
  443. }).appendTo(".spread-sheets-ul");
  444.  
  445. $("<a>", {
  446. class: "difficulty-voting-url",
  447. href: url,
  448. text: "問題の難易度を投票する",
  449. target: "_blank",
  450. rel: "noopener",
  451. }).appendTo(".difficulty-voting-li");
  452. }
  453.  
  454. function addUserCodesURL(taskStart, taskEnd, url) {
  455. // See:
  456. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals
  457. $("<li>", {
  458. class: `user-codes-${taskStart}-${taskEnd}-li`,
  459. text: ""
  460. }).appendTo(".spread-sheets-ul");
  461.  
  462. $("<a>", {
  463. class: `user-codes-${taskStart}-${taskEnd}-url`,
  464. href: url,
  465. text: `ソースコード(${taskStart}〜${taskEnd})を見る・共有する`,
  466. target: "_blank",
  467. rel: "noopener",
  468. }).appendTo(`.user-codes-${taskStart}-${taskEnd}-li`);
  469. }
  470.  
  471. function addDiv(tagName, parentTag) {
  472. $("<div>", {
  473. class: tagName,
  474. }).appendTo(parentTag);
  475.  
  476. return tagName;
  477. }
  478.  
  479. function addEditorials(tasks, parentTag) {
  480. const githubRepoUrl = getGitHubRepoUrl();
  481. const editorialsUrl = githubRepoUrl + "editorial/";
  482. const codesUrl = githubRepoUrl + "sol/";
  483.  
  484. // See:
  485. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
  486. const latestTaskId = Object.keys(tasks).slice(-1)[0];
  487.  
  488. // HACK: 公開当日分の問題についてはリンク切れを回避するため、解説・ソースコードの一覧を示すことで応急的に対処
  489. // HACK: 問題によっては、複数の解説とソースコードが公開される日もある
  490. // getMultipleEditorialUrlsIfNeeds()とgetMultipleCodeUrls()で、アドホック的に対処している
  491. for (const [taskId, [taskName, taskUrl]] of Object.entries(tasks)) {
  492. let taskEditorialDiv = addDiv(`task-${taskId}-editorial`, parentTag);
  493. taskEditorialDiv = "." + taskEditorialDiv;
  494.  
  495. // See:
  496. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
  497. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
  498. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/split
  499. showTaskName(taskId, `${taskId} - ${taskName}`, taskUrl, taskEditorialDiv);
  500.  
  501. if (taskId == latestTaskId) {
  502. const message = "注: 閲覧する時間帯によっては、公式解説・想定ソースコードが公開されているかもしれません。しばらくお待ちください。";
  503. const additionalUrl = "(一覧)";
  504. addNote("no-editorial", message, taskEditorialDiv);
  505. showEditorial(taskId, editorialsUrl, additionalUrl, taskEditorialDiv);
  506. showCode(taskId, codesUrl, additionalUrl, taskEditorialDiv);
  507. } else {
  508. const additionalUrls = getMultipleEditorialUrlsIfNeeds(taskId);
  509.  
  510. // TODO: AtCoderの解説ページで図を表示できるようにする
  511. for (const [index, additionalUrl] of Object.entries(additionalUrls)) {
  512. const editorialUrl = editorialsUrl + taskId + additionalUrl + ".jpg";
  513. showEditorial(taskId + additionalUrl, editorialUrl, additionalUrl, taskEditorialDiv);
  514. }
  515.  
  516. const codeUrls = getMultipleCodeUrls(taskId);
  517.  
  518. // TODO: ソースコードをフォーマットされた状態で表示する
  519. for (const [index, codeUrl] of Object.entries(codeUrls)) {
  520. const editorialCodelUrl = codesUrl + taskId + codeUrl;
  521. const [additionalUrl, language] = codeUrl.split(".");
  522. showCode(taskId + additionalUrl, editorialCodelUrl, codeUrl, taskEditorialDiv);
  523. }
  524. }
  525. }
  526. }
  527.  
  528. function getGitHubRepoUrl() {
  529. const url = "https://github.com/E869120/kyopro_educational_90/blob/main/";
  530.  
  531. return url;
  532. }
  533.  
  534. function showTaskName(taskId, taskName, taskUrl, tag) {
  535. const taskIdClass = `task-${taskId}`;
  536.  
  537. addHeader(
  538. "<h3>", // heading_tag
  539. taskIdClass, // className
  540. taskName, // text
  541. tag // parent_tag
  542. );
  543.  
  544. $("<a>", {
  545. class: `${`task-${taskId}-url`} small glyphicon glyphicon-new-window`,
  546. href: taskUrl,
  547. target: "_blank",
  548. }).appendTo(`.${taskIdClass}`);
  549. }
  550.  
  551. // TODO: 複数の解説資料がアップロードされた日があれば更新する
  552. function getMultipleEditorialUrlsIfNeeds(taskId) {
  553. // See:
  554. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Working_with_Objects
  555. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Property_Accessors
  556. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/in
  557.  
  558. // タスク名: 解説ファイルの番号
  559. // 0xx-yyy.jpgの0xxをキーに、-yyyを値としている
  560. const multipleEditorialUrls = {
  561. "005": ["-01", "-02", "-03"],
  562. "011": ["-01", "-02"],
  563. "017": ["-01", "-02", "-03"],
  564. "023": ["-01", "-02", "-03", "-04"],
  565. "029": ["-01", "-02"],
  566. "035": ["-01", "-02", "-03"],
  567. "041": ["-01", "-02", "-03"],
  568. "047": ["-01", "-02"],
  569. "053": ["-01", "-02", "-03", "-04"],
  570. "059": ["-01", "-02", "-03"],
  571. "065": ["-01", "-02", "-03"],
  572. "071": ["-01", "-02", "-03"],
  573. "077": ["-01", "-02", "-03"],
  574. "083": ["-01", "-02", "-03", "-04"],
  575. "084": ["-01", "-02"],
  576. "085": ["-01", "-02"],
  577. "086": ["-01", "-02"],
  578. "087": ["-01", "-02"],
  579. "088": ["-01", "-02"],
  580. };
  581.  
  582. if (taskId in multipleEditorialUrls) {
  583. return multipleEditorialUrls[taskId];
  584. } else {
  585. return [""]; // dummy
  586. }
  587. }
  588.  
  589. // TODO: 複数の想定コードがアップロードされた日があれば更新する
  590. function getMultipleCodeUrls(taskId) {
  591. // タスク名: ソースコードの番号と拡張子
  592. // 0xx-yyy.langの0xxをキーに、-yyy.langを値としている
  593. const multipleCodeUrls = {
  594. "005": ["-01.cpp", "-02.cpp", "-03.cpp"],
  595. "011": ["-01.cpp", "-02.cpp", "-03.cpp"],
  596. "017": ["-01.cpp", "-02.cpp", "-03.cpp"],
  597. "023": ["-01.cpp", "-02.cpp", "-03.cpp", "-04a.cpp", "-04b.cpp"],
  598. "029": ["-01.cpp", "-02.cpp", "-03.cpp"],
  599. "035": ["-01.cpp", "-02.cpp", "-03.cpp", "-04.cpp"],
  600. "041": ["-01a.cpp", "-01b.cpp", "-02.cpp", "-03.cpp"],
  601. "047": ["-01.cpp", "-02.cpp"],
  602. "053": ["-01.cpp", "-02.cpp", "-03.cpp", "-04.cpp"],
  603. "055": [".cpp", "-02.py", "-03.py"],
  604. "059": ["-01.cpp", "-02.cpp"],
  605. "061": ["-01.cpp", "-02.cpp"],
  606. "065": ["-01.cpp", "-02.cpp", "-03.cpp"],
  607. "066": ["a.cpp", "b.cpp"],
  608. "068": ["a.cpp", "b.cpp"],
  609. "071": ["-02.cpp", "-03.cpp"],
  610. "077": ["-01.cpp", "-02.cpp", "-03.cpp", "-04a.cpp", "-04b.cpp"],
  611. "080": ["a.cpp", "b.cpp"],
  612. "082": ["a.cpp", "b.cpp"],
  613. "083": ["-01.cpp", "-02a.cpp", "-02b.cpp"],
  614. "084": ["-01.cpp", "-02.cpp"],
  615. };
  616.  
  617. if (taskId in multipleCodeUrls) {
  618. return multipleCodeUrls[taskId];
  619. } else {
  620. return [".cpp"];
  621. }
  622. }
  623.  
  624. function addNote(className, message, parent_tag) {
  625. $("<p>", {
  626. class: className,
  627. text: message,
  628. }).appendTo(parent_tag);
  629. }
  630.  
  631. function showEditorial(taskId, url, additionalUrl, tag) {
  632. const ulClass = `editorial-${taskId}-ul`;
  633. const liClass = `editorial-${taskId}-li`;
  634.  
  635. $("<ul>", {
  636. class: ulClass,
  637. text: ""
  638. }).appendTo(tag);
  639.  
  640. $("<li>", {
  641. class: liClass,
  642. text: ""
  643. }).appendTo(`.${ulClass}`);
  644.  
  645. $("<a>", {
  646. class: `editorial-${taskId}-url`,
  647. href: url,
  648. text: `公式解説${additionalUrl}`,
  649. target: "_blank",
  650. rel: "noopener",
  651. }).appendTo(`.${liClass}`);
  652. }
  653.  
  654. function showCode(taskId, url, additionalUrl, tag) {
  655. const ulClass = `editorial-${taskId}-code-ul`;
  656. const liClass = `editorial-${taskId}-code-li`;
  657.  
  658. $("<ul>", {
  659. class: ulClass,
  660. text: ""
  661. }).appendTo(tag);
  662.  
  663. $("<li>", {
  664. class: liClass,
  665. text: ""
  666. }).appendTo(`.${ulClass}`);
  667.  
  668. $("<a>", {
  669. class: `editorial-${taskId}-code-url`,
  670. href: url,
  671. text: `想定ソースコード${additionalUrl}`,
  672. target: "_blank",
  673. rel: "noopener",
  674. }).appendTo(`.${liClass}`);
  675. }
  676.  
  677. function addEditorialButtonToTaskPage() {
  678. // See:
  679. // https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
  680. // https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
  681. const editorialButton = document.createElement("a");
  682. editorialButton.classList.add("btn", "btn-default", "btn-sm");
  683. editorialButton.textContent = "解説";
  684.  
  685. const taskTitle = document.querySelector(".row > div > .h2");
  686.  
  687. if (taskTitle) {
  688. taskTitle.appendChild(editorialButton);
  689. return editorialButton;
  690. } else {
  691. return;
  692. }
  693. }
  694.  
  695. function changeTab(this_object) {
  696. // See:
  697. // https://api.jquery.com/parent/
  698. // https://api.jquery.com/addClass/#addClass-className
  699. // https://api.jquery.com/siblings/#siblings-selector
  700. // https://api.jquery.com/removeClass/#removeClass-className
  701. // https://www.design-memo.com/coding/jquery-tab-change
  702. this_object.parent().addClass("active").siblings(".active").removeClass("active");
  703. const tabContentsUrl = this_object.attr("href");
  704. $(tabContentsUrl).addClass("active").siblings(".active").removeClass("active");
  705. }
  706.  
  707. function hideContentsOfPreviousPage() {
  708. // See:
  709. // https://api.jquery.com/length/
  710. // https://api.jquery.com/hide/
  711. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
  712. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String
  713. const tagCount = $(".col-sm-12").length;
  714.  
  715. for (let index = 0; index < tagCount; index++) {
  716. if (index != 0) {
  717. $("#main-container > div.row > div:nth-child(" + String(index + 1) + ")").hide();
  718. }
  719. }
  720. }