AtCoder Dropdown Tasks

AtCoder のコンテストページにおいて、問題タブをホバーするとドロップダウンリストを表示するようにします。

2026/04/05のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         AtCoder Dropdown Tasks
// @namespace    https://atcoder.jp/
// @version      2026-04-05
// @description  AtCoder のコンテストページにおいて、問題タブをホバーするとドロップダウンリストを表示するようにします。
// @author       magurofly
// @match        https://atcoder.jp/contests/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=atcoder.jp
// @grant        unsafeWindow
// @license      CC0-1.0 Universal
// ==/UserScript==

(async function() {
    'use strict';

    if (typeof unsafeWindow.contestScreenName !== "string") return;
    const contestScreenName = unsafeWindow.contestScreenName;

    const navTabsBox = document.querySelector("#contest-nav-tabs").getBoundingClientRect()

    const styleSheet = new CSSStyleSheet();
    styleSheet.replace(`
.atcoder-dropdown-tasks:hover .dropdown-menu {
  display: block;
  overflow: auto;
  max-height: calc(100vh - ${navTabsBox.top}px - ${navTabsBox.height}px - 2em);
}

.atcoder-dropdown-task li {
  font-family: monospace;
}

.atcoder-dropdown-tasks li.current-task {
  background-color: #eeeeee;
  font-weight: bold;
}

.atcoder-dropdown-tasks li.current-task a {
  font-weight: bold;
  padding-left: calc(18px - 1em);
}

.atcoder-dropdown-tasks li.current-task a::before {
  content: "\u25b6";
  padding-right: 2px;
}
    `);
    document.adoptedStyleSheets.push(styleSheet);

    const tasksDropdown = unsafeWindow.document.querySelector("#contest-nav-tabs > ul > li:nth-child(2)");
    tasksDropdown.classList.add("atcoder-dropdown-tasks");

    const tasksDropdownButton = tasksDropdown.querySelector("a");
    tasksDropdownButton.insertAdjacentHTML("beforeend", `<span class="caret"></span>`);

    const tasksDropdownContents = document.createElement("ul");
    tasksDropdownContents.className = "dropdown-menu";
    tasksDropdown.appendChild(tasksDropdownContents);

    let currentTaskURL = null;
    let matchResult;
    if (matchResult = /^\/contests\/[^\/]+\/tasks\/[^\/?]+/.exec(location.pathname)) {
        currentTaskURL = location.origin + matchResult[0];
    } else if (/^\/contests\/[^\/]+\/submissions\/\d+/.test(location.pathname)) {
        const link = document.querySelector("#main-container > div.row > div:nth-child(2) > div:nth-child(8) > table > tbody > tr:nth-child(2) > td > a");
        currentTaskURL = link.href;
    } else if (/^\/contests\/[^\/]+\/editorial\/\d+/.test(location.pathname)) {
        const link = document.querySelector("#main-container > div.row > div:nth-child(2) > h2 > a");
        currentTaskURL = link.href;
    }

    let currentLink = null;
    const tasksHTML = await (await fetch(`/contests/${contestScreenName}/tasks`)).text();
    const tasksDoc = new DOMParser().parseFromString(tasksHTML, "text/html");
    for (const row of tasksDoc.querySelectorAll("#main-container > div.row > div:nth-child(2) > div.panel.panel-default.table-responsive > table > tbody > tr")) {
        const taskNum = row.cells[0].textContent;
        const taskName = row.cells[1].textContent;
        const taskURL = row.cells[0].children[0].href;
        const link = document.createElement("a");
        link.href = taskURL;
        link.textContent = `${taskNum} - ${taskName}`;
        const li = document.createElement("li");
        li.appendChild(link);
        if (taskURL == currentTaskURL) {
            li.classList.add("current-task");
            currentLink = link;
        }
        tasksDropdownContents.appendChild(li);
    }

    if (currentLink) {
        tasksDropdown.addEventListener("mouseenter", currentLink.scrollIntoView.bind(currentLink, { block: "nearest" }), { once: true });
    }
})();