AtCoder Dropdown Tasks

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

As of 2025-03-07. See the latest version.

// ==UserScript==
// @name         AtCoder Dropdown Tasks
// @namespace    https://atcoder.jp/
// @version      2025-03-06
// @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';

    const styleSheet = new CSSStyleSheet();
    styleSheet.insertRule(`
.atcoder-dropdown-tasks:hover .dropdown-menu {
  display: block;
}
    `);
    document.adoptedStyleSheets.push(styleSheet);

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

    const tasksHTML = await (await fetch(`/contests/${contestScreenName}/tasks`)).text();

    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);

    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;
        tasksDropdownContents.insertAdjacentHTML("beforeend", `<li><a href="${taskURL}">${taskNum} - ${taskName}</a></li>`);
    }
})();