AtCoder Listing Tasks

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

Versione datata 28/05/2023. Vedi la nuova versione l'ultima versione.

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