AtCoder ResultsPage Tweaks

AtCoderの提出結果一覧画面に自動検索機能などを追加します。

  1. // ==UserScript==
  2. // @name AtCoder ResultsPage Tweaks
  3. // @namespace https://github.com/yukuse
  4. // @version 1.0.4
  5. // @description AtCoderの提出結果一覧画面に自動検索機能などを追加します。
  6. // @author yukuse
  7. // @include https://atcoder.jp/contests/*/submissions
  8. // @include https://atcoder.jp/contests/*/submissions?*
  9. // @include https://atcoder.jp/contests/*/submissions/me
  10. // @include https://atcoder.jp/contests/*/submissions/me?*
  11. // @grant window.jQuery
  12. // @grant window.fixTime
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. // jQueryカスタムイベントを監視/発火するためwindowのjQueryを使用しています。
  17. jQuery(($) => {
  18. const options = {
  19. // 検索条件変更時に自動検索 on/off
  20. autoSearchOnChange: true,
  21. // 検索結果の動的読み込み(少し検索結果表示が早くなる) on/off
  22. // NOTE: trueのとき、他のスクリプトによる表示変更(AtCoder Difficulty Displayなど)が反映されないため注意!
  23. dynamicResult: false,
  24. // 検索条件変更時のフォーカス維持 on/off
  25. keepSelectFocus: true,
  26. // 画面表示時のフォーカス対象 '#select-task'|'#select-language'|'#select-status'|'#input-user'|null
  27. focusOnVisit: '#select-task',
  28. // 言語固定中の検索時に言語がリセットされないようにする
  29. keepLanguage: true,
  30. };
  31.  
  32. const $container = $('#main-container');
  33. const $panelSubmission = $container.find('.panel-submission');
  34.  
  35. const baseParams = {
  36. 'f.Task': '',
  37. 'f.Language': '',
  38. 'f.LanguageName': '',
  39. 'f.Status': '',
  40. 'f.User': '',
  41. page: 1,
  42. orderBy: '',
  43. desc: '',
  44. };
  45.  
  46. function parseSubmissionsUrl(url) {
  47. const params = { ...baseParams };
  48. if (url) {
  49. Object.keys(params).forEach((key) => {
  50. const regexp = new RegExp(`${key}=([^&]+)`);
  51. const result = url.match(regexp);
  52. if (result) {
  53. [, params[key]] = result;
  54. }
  55. });
  56. }
  57.  
  58. return params;
  59. }
  60.  
  61. /**
  62. * 現在のURLに応じて検索結果表示を更新
  63. * TODO: ジャッジ中表示対応
  64. */
  65. function updateSearchResult() {
  66. const { href } = location;
  67. const params = parseSubmissionsUrl(href);
  68.  
  69. // 検索条件を遷移先の状態にする
  70. Object.keys(params).forEach((key) => {
  71. $panelSubmission.find(`[name="${key}"]`).val(params[key]).trigger('change');
  72. });
  73.  
  74. const $tmp = $('<div>');
  75. $tmp.load(`${href} #main-container`, '', () => {
  76. const $newTable = $tmp.find('.table-responsive, .panel-body');
  77. // テーブル置換
  78. $panelSubmission.find('.table-responsive, .panel-body').replaceWith($newTable);
  79. // ページネーション置換
  80. if ($newTable.hasClass('table-responsive')) {
  81. $container.find('.pagination').replaceWith($tmp.find('.pagination:first'));
  82. } else {
  83. $container.find('.pagination').empty();
  84. }
  85.  
  86. // 日付を表示
  87. fixTime();
  88. });
  89. }
  90.  
  91. /**
  92. * 検索条件を元にURLを更新し、結果を表示する
  93. */
  94. function showSearchResult(params) {
  95. const paramsStr = Object.keys(params).map((key) => `${key}=${params[key]}`).join('&');
  96. const url = `${location.pathname}?${paramsStr}`;
  97.  
  98. if (options.dynamicResult) {
  99. history.pushState({}, '', url);
  100.  
  101. updateSearchResult();
  102. } else {
  103. location.href = url;
  104. }
  105. }
  106.  
  107. /**
  108. * フォームに設定されたパラメータを取得
  109. */
  110. function getFormParams() {
  111. const params = { ...baseParams };
  112. Object.keys(params).forEach((key) => {
  113. params[key] = $panelSubmission.find(`[name="${key}"]`).val();
  114. // 空のキーは外す
  115. if (!params[key]) {
  116. delete params[key];
  117. }
  118. });
  119. params.page = 1;
  120.  
  121. return params;
  122. }
  123.  
  124. /**
  125. * フォームの検索条件で検索
  126. */
  127. function search() {
  128. showSearchResult(getFormParams());
  129. }
  130.  
  131. /**
  132. * 選択欄の調整
  133. * - 選択時に自動検索
  134. * - 画面表示時に選択欄自動フォーカス
  135. * - 選択時にフォーカスが飛ばないようにする
  136. */
  137. function initSelectTweaks() {
  138. // 選択欄自動フォーカス
  139. if (options.focusOnVisit) {
  140. $panelSubmission.find(options.focusOnVisit).focus();
  141. }
  142.  
  143. $panelSubmission.find('#select-task, #select-language, #select-status').on('select2:select select2:unselect', (event) => {
  144. // unselectの場合は選択状態が遅れて反映されるため、実行を遅らせる
  145. setTimeout(() => {
  146. // 選択時に自動検索
  147. if (options.autoSearchOnChange) {
  148. search();
  149. }
  150.  
  151. // 選択時にフォーカスが飛ばないようにする
  152. if (options.keepSelectFocus) {
  153. event.target.focus();
  154. }
  155. }, 0);
  156. });
  157. }
  158.  
  159. const urlRegExp = new RegExp(`${location.pathname}[^/]*$`);
  160. /**
  161. * 検索結果のリンククリック時のページ遷移をなくし、表示を動的に更新する処理に置き換え
  162. */
  163. function initLinks() {
  164. $container.on('click', '.pagination a, .panel-submission a', (event) => {
  165. const { href } = event.target;
  166. if (!urlRegExp.test(href)) {
  167. return;
  168. }
  169. // 言語リンクは除外
  170. if (/f.Language=([^&]+)/.test(href)) {
  171. return;
  172. }
  173.  
  174. event.preventDefault();
  175.  
  176. showSearchResult(parseSubmissionsUrl(href));
  177. });
  178. }
  179.  
  180. function init() {
  181. initSelectTweaks();
  182. if (options.dynamicResult) {
  183. window.addEventListener('popstate', updateSearchResult);
  184. initLinks();
  185.  
  186. // フォームの検索押下時に検索結果を動的読み込み
  187. $panelSubmission.find('form').on('submit', (event) => {
  188. event.preventDefault();
  189. search();
  190. });
  191. }
  192.  
  193. // 言語固定中の検索時に言語がリセットされないようにする
  194. if (options.keepLanguage) {
  195. const result = location.href.match(/f.Language=(\d+)/);
  196. if (result && result[1]) {
  197. const inputHidden = $(`<input type="hidden" name="f.Language" value="${result[1]}">`);
  198. $panelSubmission.find('form').append(inputHidden);
  199. }
  200. }
  201. }
  202.  
  203. init();
  204. });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址