AtCoder Listing Tasks

Click [Tasks] tab to open a drop-down list linked to each task. / [問題]タブをクリックすると、各問題へのリンクをドロップダウンリストで表示します。

As of 28.05.2023. See ბოლო ვერსია.

  1. // ==UserScript==
  2. // @name AtCoder Listing Tasks
  3. // @namespace https://github.com/luuguas/AtCoderListingTasks
  4. // @version 1.0
  5. // @description Click [Tasks] tab to open a drop-down list linked to each task. / [問題]タブをクリックすると、各問題へのリンクをドロップダウンリストで表示します。
  6. // @author luuguas
  7. // @license Apache
  8. // @match https://atcoder.jp/contests/*
  9. // @exclude https://atcoder.jp/contests/
  10. // @exclude https://atcoder.jp/contests/archive
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16. const contest_url = 'https://atcoder.jp/contests/';
  17. const unique_tag = 'user-script-listing-tasks';
  18.  
  19. //[問題]タブに本UserScript用のidを追加(タブがなければfalseを返す)
  20. function AttachId(){
  21. let tabs = document.getElementById('contest-nav-tabs');
  22. if(tabs === null){
  23. return false;
  24. }
  25. let tasks_tab = tabs.querySelector('a[href$="tasks"]');
  26. if(tasks_tab === null){
  27. return false;
  28. }
  29. else{
  30. tasks_tab.setAttribute('id', unique_tag);
  31. return true;
  32. }
  33. }
  34.  
  35. //[提出結果]タブと同様のドロップダウンリストに変える(中身は空)
  36. function Togglize(){
  37. let tasks_tab = document.getElementById(unique_tag);
  38.  
  39. let attr = {
  40. 'class': 'dropdown-toggle',
  41. 'data-toggle': 'dropdown',
  42. 'href': '#',
  43. 'role:': 'button',
  44. 'aria-haspopup': 'true',
  45. 'aria-expanded': 'false',
  46. };
  47. for(let [key, value] of Object.entries(attr)){
  48. tasks_tab.setAttribute(key, value);
  49. }
  50.  
  51. let caret = document.createElement('span');
  52. caret.className = 'caret';
  53. tasks_tab.appendChild(caret);
  54.  
  55. let dropdown_menu = document.createElement('ul');
  56. dropdown_menu.className = 'dropdown-menu';
  57. tasks_tab.parentNode.appendChild(dropdown_menu);
  58. }
  59.  
  60. //リストを追加する
  61. function AddList(){
  62. let url = location.href;
  63. let contest_name = url.split('/')[4];
  64.  
  65. let tasks_tab_li = document.getElementById(unique_tag).parentNode;
  66. let dropdown_menu = tasks_tab_li.querySelector('.dropdown-menu');
  67.  
  68. //[問題一覧]の追加
  69. let all_tasks = document.createElement('li');
  70. let lang = document.querySelector('meta[http-equiv="Content-Language"]');
  71. if(lang !== null && lang.getAttribute('content') === 'en'){
  72. all_tasks.innerHTML = '<a href="' + contest_url + contest_name + '/tasks"><span class="glyphicon glyphicon-list" aria-hidden="true"></span> All Tasks</a>';
  73. }
  74. else{
  75. all_tasks.innerHTML = '<a href="' + contest_url + contest_name + '/tasks"><span class="glyphicon glyphicon-list" aria-hidden="true"></span> 問題一覧</a>';
  76. }
  77. dropdown_menu.appendChild(all_tasks);
  78.  
  79. //分割線の追加
  80. let divider = document.createElement('li');
  81. divider.className = 'divider';
  82. dropdown_menu.appendChild(divider);
  83.  
  84. //https://atcoder.jp/contests/***/tasks から問題のリストを抽出
  85. let xhr = new XMLHttpRequest();
  86. xhr.responseType = 'document';
  87. xhr.onreadystatechange = function(){
  88. let tasks_tab_li = document.getElementById(unique_tag).parentNode;
  89. let dropdown_menu = tasks_tab_li.querySelector('.dropdown-menu');
  90.  
  91. if(xhr.readyState === 4){
  92. if(xhr.status === 200){
  93. let result = xhr.responseXML;
  94. let problem_node = result.querySelector('#contest-nav-tabs + .col-sm-12');
  95. let problem_list = problem_node.querySelectorAll('table.table tbody tr');
  96.  
  97. //問題情報を抽出
  98. let list = [];
  99. for(let li of problem_list){
  100. let td = li.querySelectorAll('td');
  101. let problem_url = td[0].firstChild.getAttribute('href');
  102. let problem_diff = td[0].firstChild.textContent;
  103. let problem_name = td[1].firstChild.textContent;
  104. list.push({
  105. url: problem_url,
  106. diff: problem_diff,
  107. name: problem_name,
  108. });
  109. }
  110.  
  111. //リストを追加
  112. for(let data of list){
  113. let li = document.createElement('li');
  114. let a = document.createElement('a');
  115. a.setAttribute('href', data.url);
  116. a.textContent = data.diff + ' - ' + data.name;
  117. li.appendChild(a);
  118. dropdown_menu.appendChild(li);
  119. }
  120. console.log('[AtCoder Listing Tasks] Succeeded!');
  121. }
  122. else{
  123. let li = document.createElement('li');
  124. let a = document.createElement('a');
  125. a.setAttribute('href', 'javascript:void(0)');
  126. a.textContent = '(読み込み失敗)';
  127. li.appendChild(a);
  128. dropdown_menu.appendChild(li);
  129. console.log('[AtCoder Listing Tasks] Failed...');
  130. }
  131. }
  132. };
  133. xhr.open('GET', 'https://atcoder.jp/contests/' + contest_name + '/tasks', true);
  134. xhr.send(null);
  135. }
  136.  
  137. let tab_exist = AttachId();
  138. if(!tab_exist){
  139. //タブがない場合は終了
  140. console.log('[AtCoder Listing Tasks] [Tasks] Tab isn\'t exist.');
  141. return;
  142. }
  143. Togglize();
  144. AddList();
  145.  
  146. })();