AtCoder Editorial for Typical90

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

Από την 13/06/2021. Δείτε την τελευταία έκδοση.

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