AtCoder ResultsPage Tweaks

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

当前为 2021-03-28 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AtCoder ResultsPage Tweaks
// @namespace    https://github.com/yukuse
// @version      1.0.2
// @description  AtCoderの提出結果一覧画面に自動検索機能などを追加します。
// @author       yukuse
// @include      https://atcoder.jp/contests/*/submissions*
// @grant        window.jQuery
// @grant        window.fixTime
// @license      MIT
// ==/UserScript==

// jQueryカスタムイベントを監視・発火するためwindowのjQueryを使用
jQuery(($) => {
  const options = {
    // 検索条件変更時に自動検索 on/off
    autoSearchOnChange: true,
    // 検索結果の動的読み込み on/off
    dynamicResult: true,
    // 検索条件変更時のフォーカス維持 on/off
    keepSelectFocus: true,
  };

  const $container = $('#main-container');
  const $panelSubmission = $container.find('.panel-submission');

  const baseParams = {
    'f.Task': '',
    'f.LanguageName': '',
    'f.Status': '',
    'f.User': '',
    page: 1,
  };

  function parseSubmissionsUrl(url) {
    const params = { ...baseParams };
    if (url) {
      Object.keys(params).forEach((key) => {
        const regexp = new RegExp(`${key}=([^&]+)`);
        const result = url.match(regexp);
        if (result) {
          [, params[key]] = result;
        }
      });
    }

    return params;
  }

  /**
   * 現在のURLに応じて検索結果表示を更新
   * TODO: ジャッジ中表示対応
   */
  function updateSearchResult() {
    const { href } = location;
    const params = parseSubmissionsUrl(href);

    // 検索条件を遷移先の状態にする
    Object.keys(params).forEach((key) => {
      $panelSubmission.find(`[name="${key}"]`).val(params[key]).trigger('change');
    });

    const $tmp = $('<div>');
    $tmp.load(`${href} #main-container`, '', () => {
      const $newTable = $tmp.find('.table-responsive, .panel-body');
      // テーブル置換
      $panelSubmission.find('.table-responsive, .panel-body').replaceWith($newTable);
      // ページネーション置換
      if ($newTable.hasClass('table-responsive')) {
        $container.find('.pagination').replaceWith($tmp.find('.pagination:first'));
      } else {
        $container.find('.pagination').empty();
      }

      // 日付を表示
      fixTime();
    });
  }

  /**
   * 検索条件を元にURLを更新し、結果を表示する
   */
  function showSearchResult(params) {
    const paramsStr = Object.keys(params).map((key) => `${key}=${params[key]}`).join('&');
    const url = `${location.pathname}?${paramsStr}`;

    if (options.dynamicResult) {
      history.pushState({}, '', url);

      updateSearchResult();
    } else {
      location.href = url;
    }
  }

  /**
   * 選択欄の調整
   * - 選択時に自動検索
   * - 選択時にフォーカスが飛ばないようにする
   */
  function initSelectTweaks() {
    $panelSubmission.find('#select-task, #select-language, #select-status').on('select2:select select2:unselect', (event) => {
      // unselectの場合は選択状態が遅れて反映されるため、実行を遅らせる
      setTimeout(() => {
        // 選択時に自動検索
        if (options.autoSearchOnChange) {
          const params = { ...baseParams };
          Object.keys(params).forEach((key) => {
            params[key] = $panelSubmission.find(`[name="${key}"]`).val();
          });
          params.page = 1;

          showSearchResult(params);
        }

        // 選択時にフォーカスが飛ばないようにする
        if (options.keepSelectFocus) {
          event.target.focus();
        }
      }, 0);
    });
  }

  const urlRegExp = new RegExp(location.pathname);
  /**
   * 検索結果のリンククリック時のページ遷移をなくし、表示を動的に更新する処理に置き換え
   */
  function initLinks() {
    $container.on('click', '.pagination a, .panel-submission a', (event) => {
      const { href } = event.target;
      if (!urlRegExp.test(href)) {
        return;
      }

      event.preventDefault();

      showSearchResult(parseSubmissionsUrl(href));
    });
  }

  function init() {
    initSelectTweaks();
    if (options.dynamicResult) {
      window.addEventListener('popstate', updateSearchResult);
      initLinks();
    }
  }

  init();
});