AtCoder Listing Tasks

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

נכון ליום 28-05-2023. ראה הגרסה האחרונה.

  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. let hash = contest_name.search(/[#\?]/);
  65. if(hash !== -1){
  66. //ハッシュ(#,?)を除く
  67. contest_name = contest_name.slice(0, hash);
  68. }
  69.  
  70. let tasks_tab_li = document.getElementById(unique_tag).parentNode;
  71. let dropdown_menu = tasks_tab_li.querySelector('.dropdown-menu');
  72.  
  73. //[問題一覧]の追加
  74. let all_tasks = document.createElement('li');
  75. let lang = document.querySelector('meta[http-equiv="Content-Language"]');
  76. if(lang !== null && lang.getAttribute('content') === 'en'){
  77. all_tasks.innerHTML = '<a href="' + contest_url + contest_name + '/tasks"><span class="glyphicon glyphicon-list" aria-hidden="true"></span> All Tasks</a>';
  78. }
  79. else{
  80. all_tasks.innerHTML = '<a href="' + contest_url + contest_name + '/tasks"><span class="glyphicon glyphicon-list" aria-hidden="true"></span> 問題一覧</a>';
  81. }
  82. dropdown_menu.appendChild(all_tasks);
  83.  
  84. //分割線の追加
  85. let divider = document.createElement('li');
  86. divider.className = 'divider';
  87. dropdown_menu.appendChild(divider);
  88.  
  89. //https://atcoder.jp/contests/***/tasks から問題のリストを抽出
  90. let xhr = new XMLHttpRequest();
  91. xhr.responseType = 'document';
  92. xhr.onreadystatechange = function(){
  93. let tasks_tab_li = document.getElementById(unique_tag).parentNode;
  94. let dropdown_menu = tasks_tab_li.querySelector('.dropdown-menu');
  95.  
  96. if(xhr.readyState === 4){
  97. if(xhr.status === 200){
  98. let result = xhr.responseXML;
  99. let problem_node = result.querySelector('#contest-nav-tabs + .col-sm-12');
  100. let problem_list = problem_node.querySelectorAll('table.table tbody tr');
  101.  
  102. //問題情報を抽出
  103. let list = [];
  104. for(let li of problem_list){
  105. let td = li.querySelectorAll('td');
  106. let problem_url = td[0].firstChild.getAttribute('href');
  107. let problem_diff = td[0].firstChild.textContent;
  108. let problem_name = td[1].firstChild.textContent;
  109. list.push({
  110. url: problem_url,
  111. diff: problem_diff,
  112. name: problem_name,
  113. });
  114. }
  115.  
  116. //リストを追加
  117. for(let data of list){
  118. let li = document.createElement('li');
  119. let a = document.createElement('a');
  120. a.setAttribute('href', data.url);
  121. a.textContent = data.diff + ' - ' + data.name;
  122. li.appendChild(a);
  123. dropdown_menu.appendChild(li);
  124. }
  125. console.log('[AtCoder Listing Tasks] Succeeded!');
  126. }
  127. else{
  128. let li = document.createElement('li');
  129. let a = document.createElement('a');
  130. a.setAttribute('href', 'javascript:void(0)');
  131. a.textContent = '(読み込み失敗)';
  132. li.appendChild(a);
  133. dropdown_menu.appendChild(li);
  134. console.log('[AtCoder Listing Tasks] Failed...');
  135. }
  136. }
  137. };
  138. xhr.open('GET', 'https://atcoder.jp/contests/' + contest_name + '/tasks', true);
  139. xhr.send(null);
  140. }
  141.  
  142. let tab_exist = AttachId();
  143. if(!tab_exist){
  144. //タブがない場合は終了
  145. console.log('[AtCoder Listing Tasks] [Tasks] Tab isn\'t exist.');
  146. return;
  147. }
  148. Togglize();
  149. AddList();
  150.  
  151. })();