AtCoder Editorial for Typical90

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

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

  1. // ==UserScript==
  2. // @name AtCoder Editorial for Typical90
  3. // @namespace https://github.com/KATO-Hiro
  4. // @version 0.4.4
  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. "089": ["-01", "-02", "-03", "-04"],
  581. };
  582.  
  583. if (taskId in multipleEditorialUrls) {
  584. return multipleEditorialUrls[taskId];
  585. } else {
  586. return [""]; // dummy
  587. }
  588. }
  589.  
  590. // TODO: 複数の想定コードがアップロードされた日があれば更新する
  591. function getMultipleCodeUrls(taskId) {
  592. // タスク名: ソースコードの番号と拡張子
  593. // 0xx-yyy.langの0xxをキーに、-yyy.langを値としている
  594. const multipleCodeUrls = {
  595. "005": ["-01.cpp", "-02.cpp", "-03.cpp"],
  596. "011": ["-01.cpp", "-02.cpp", "-03.cpp"],
  597. "017": ["-01.cpp", "-02.cpp", "-03.cpp"],
  598. "023": ["-01.cpp", "-02.cpp", "-03.cpp", "-04a.cpp", "-04b.cpp"],
  599. "029": ["-01.cpp", "-02.cpp", "-03.cpp"],
  600. "035": ["-01.cpp", "-02.cpp", "-03.cpp", "-04.cpp"],
  601. "041": ["-01a.cpp", "-01b.cpp", "-02.cpp", "-03.cpp"],
  602. "047": ["-01.cpp", "-02.cpp"],
  603. "053": ["-01.cpp", "-02.cpp", "-03.cpp", "-04.cpp"],
  604. "055": [".cpp", "-02.py", "-03.py"],
  605. "059": ["-01.cpp", "-02.cpp"],
  606. "061": ["-01.cpp", "-02.cpp"],
  607. "065": ["-01.cpp", "-02.cpp", "-03.cpp"],
  608. "066": ["a.cpp", "b.cpp"],
  609. "068": ["a.cpp", "b.cpp"],
  610. "071": ["-02.cpp", "-03.cpp"],
  611. "077": ["-01.cpp", "-02.cpp", "-03.cpp", "-04a.cpp", "-04b.cpp"],
  612. "080": ["a.cpp", "b.cpp"],
  613. "082": ["a.cpp", "b.cpp"],
  614. "083": ["-01.cpp", "-02a.cpp", "-02b.cpp"],
  615. "084": ["-01.cpp", "-02.cpp"],
  616. "089": ["-01.cpp", "-02.cpp", "-03.cpp", "-04.cpp", "-05.cpp"],
  617. };
  618.  
  619. if (taskId in multipleCodeUrls) {
  620. return multipleCodeUrls[taskId];
  621. } else {
  622. return [".cpp"];
  623. }
  624. }
  625.  
  626. function addNote(className, message, parent_tag) {
  627. $("<p>", {
  628. class: className,
  629. text: message,
  630. }).appendTo(parent_tag);
  631. }
  632.  
  633. function showEditorial(taskId, url, additionalUrl, tag) {
  634. const ulClass = `editorial-${taskId}-ul`;
  635. const liClass = `editorial-${taskId}-li`;
  636.  
  637. $("<ul>", {
  638. class: ulClass,
  639. text: ""
  640. }).appendTo(tag);
  641.  
  642. $("<li>", {
  643. class: liClass,
  644. text: ""
  645. }).appendTo(`.${ulClass}`);
  646.  
  647. $("<a>", {
  648. class: `editorial-${taskId}-url`,
  649. href: url,
  650. text: `公式解説${additionalUrl}`,
  651. target: "_blank",
  652. rel: "noopener",
  653. }).appendTo(`.${liClass}`);
  654. }
  655.  
  656. function showCode(taskId, url, additionalUrl, tag) {
  657. const ulClass = `editorial-${taskId}-code-ul`;
  658. const liClass = `editorial-${taskId}-code-li`;
  659.  
  660. $("<ul>", {
  661. class: ulClass,
  662. text: ""
  663. }).appendTo(tag);
  664.  
  665. $("<li>", {
  666. class: liClass,
  667. text: ""
  668. }).appendTo(`.${ulClass}`);
  669.  
  670. $("<a>", {
  671. class: `editorial-${taskId}-code-url`,
  672. href: url,
  673. text: `想定ソースコード${additionalUrl}`,
  674. target: "_blank",
  675. rel: "noopener",
  676. }).appendTo(`.${liClass}`);
  677. }
  678.  
  679. function addEditorialButtonToTaskPage() {
  680. // See:
  681. // https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
  682. // https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
  683. const editorialButton = document.createElement("a");
  684. editorialButton.classList.add("btn", "btn-default", "btn-sm");
  685. editorialButton.textContent = "解説";
  686.  
  687. const taskTitle = document.querySelector(".row > div > .h2");
  688.  
  689. if (taskTitle) {
  690. taskTitle.appendChild(editorialButton);
  691. return editorialButton;
  692. } else {
  693. return;
  694. }
  695. }
  696.  
  697. function changeTab(this_object) {
  698. // See:
  699. // https://api.jquery.com/parent/
  700. // https://api.jquery.com/addClass/#addClass-className
  701. // https://api.jquery.com/siblings/#siblings-selector
  702. // https://api.jquery.com/removeClass/#removeClass-className
  703. // https://www.design-memo.com/coding/jquery-tab-change
  704. this_object.parent().addClass("active").siblings(".active").removeClass("active");
  705. const tabContentsUrl = this_object.attr("href");
  706. $(tabContentsUrl).addClass("active").siblings(".active").removeClass("active");
  707. }
  708.  
  709. function hideContentsOfPreviousPage() {
  710. // See:
  711. // https://api.jquery.com/length/
  712. // https://api.jquery.com/hide/
  713. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
  714. // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String
  715. const tagCount = $(".col-sm-12").length;
  716.  
  717. for (let index = 0; index < tagCount; index++) {
  718. if (index != 0) {
  719. $("#main-container > div.row > div:nth-child(" + String(index + 1) + ")").hide();
  720. }
  721. }
  722. }