Editorials Dropdown for AtCoder

Add a drop-down list next to the editorial buttons on AtCoder problem pages.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name             Editorials Dropdown for AtCoder
// @name:ja          Editorials Dropdown for AtCoder
// @namespace        https://github.com/roumcha/browser-extensions/tree/main/src/editorials-dropdown-for-atcoder
// @version          1.1.0
// @description      Add a drop-down list next to the editorial buttons on AtCoder problem pages.
// @description:ja   AtCoder の解説ボタンの横にドロップダウンリストを追加します。
// @author           Roumcha
// @license          Creative Commons Zero v1.0 Universal
// @match            https://atcoder.jp/contests/*/tasks/*
// @grant            GM.xmlHttpRequest
// @connect          atcoder.jp
// @run-at           document-end
// ==/UserScript==

"use strict";
(() => {
  // src/editorials-dropdown-for-atcoder/editorials-dropdown.ts
  async function editorialsDropdown({
    fetchXMLDocument
  }) {
    const started = Date.now();
    let lang = getLanguage();
    const link = findEditorialsButton(document);
    if (link) {
      console.log("[EDFA] Found the target button: ", link);
    } else {
      console.log(`[EDFA] Editorials button not found.`);
      return;
    }
    const url2 = new URL(link.href);
    url2.searchParams.set("editorialLang", lang);
    const editorialsPageDoc = await fetchXMLDocument(
      url2
    ).catch((reason) => {
      console.error(`[EDFA] Failed to fetch ${link.href}: ${reason}`);
      return null;
    });
    if (editorialsPageDoc) {
      console.log(`[EDFA] Downloaded ${link.href}.`);
    } else {
      console.error(`[EDFA] ${link.href} is empty or not an XML document.`);
      return;
    }
    const content = createDropdownContent(editorialsPageDoc);
    if (content.length === 0) {
      console.error(`[EDFA] failed to generate the dropdown content.`);
      return;
    }
    const insertedElem = createDropdownAndButton(...content);
    link.after(insertedElem);
    console.log(
      `[EDFA] Successfully generated and inserted a drop-down list: `,
      insertedElem
    );
    console.log(`[EDFA] done in ${Date.now() - started} ms.`);
  }
  var translation = {
    editorial: {
      ja: "\u89E3\u8AAC",
      en: "editorial"
    },
    overallEditorial: {
      ja: "\u30B3\u30F3\u30C6\u30B9\u30C8\u5168\u4F53\u306E\u89E3\u8AAC",
      en: "overall editorial"
    }
  };
  function getLanguage() {
    const param = new URLSearchParams(location.search).get(
      "lang"
    );
    if (param) {
      console.log(`[EDFA] Found language '${param}' in the URL parameter.`);
      return param;
    }
    const cookie = document.cookie.split("; ").find((s) => s.startsWith("language="))?.split("=").at(1);
    if (cookie) {
      console.log(`[EDFA] Found language '${cookie}' in Cookie.`);
      return cookie;
    }
    const browser = navigator.language;
    if (browser == "ja") {
      console.log(`[EDFA] Loaded language '${browser}' from the browser.`);
      return "ja";
    }
    console.log(`[EDFA] Fall back to English.`);
    return "en";
  }
  function findEditorialsButton(root) {
    const res = [...root.querySelectorAll("a.btn")].filter(
      ({ textContent }) => textContent && Object.values(translation["editorial"]).includes(
        textContent.toLowerCase()
      )
    ).at(0);
    return res;
  }
  function createDropdownContent(editorialsPageDoc) {
    const res = [
      ...editorialsPageDoc.querySelectorAll(
        "#main-container > div > div:not(#contest-nav-tabs) > *"
      )
    ].filter(
      ({ tagName }) => ["ul", "h3", "p"].includes(tagName.toLowerCase())
    );
    if (res.length === 0) {
      console.error(`[EDFA] failed to find editorial lists.`);
    }
    return res;
  }
  function createDropdownAndButton(...content) {
    const res = document.createElement("span");
    res.className = "edfa-root";
    res.style.position = "relative";
    res.addEventListener("blur", () => res.classList.remove("open"));
    {
      const button = document.createElement("button");
      button.className = "edfa-button btn btn-default btn-sm";
      button.type = "button";
      button.title = "open editorials list";
      button.onclick = () => res.classList.toggle("open");
      res.append(button);
      {
        const caret = document.createElement("span");
        caret.classList.add("caret");
        button.append(caret);
      }
    }
    {
      const dropdown = document.createElement("div");
      dropdown.className = "edfa-dropdown dropdown-menu";
      dropdown.style.position = "absolute";
      dropdown.style.width = "200px";
      dropdown.style.padding = "8px";
      dropdown.style.zIndex = "998";
      dropdown.append(...content);
      res.append(dropdown);
    }
    return res;
  }

  // src/editorials-dropdown-for-atcoder/info.ts
  var title = "Editorials Dropdown for AtCoder";
  var version = "1.1.0";
  var url = "https://github.com/roumcha/browser-extensions/tree/main/src/editorials-dropdown-for-atcoder";
  var author = "Roumcha";
  var userScriptHeader = `// ==UserScript==
// @name             ${title}
// @name:ja          ${title}
// @namespace        ${url}
// @version          ${version}
// @description      Add a drop-down list next to the editorial buttons on AtCoder problem pages.
// @description:ja   AtCoder \u306E\u89E3\u8AAC\u30DC\u30BF\u30F3\u306E\u6A2A\u306B\u30C9\u30ED\u30C3\u30D7\u30C0\u30A6\u30F3\u30EA\u30B9\u30C8\u3092\u8FFD\u52A0\u3057\u307E\u3059\u3002
// @author           ${author}
// @license          Creative Commons Zero v1.0 Universal
// @match            https://atcoder.jp/contests/*/tasks/*
// @grant            GM.xmlHttpRequest
// @connect          atcoder.jp
// @run-at           document-end
// ==/UserScript==
`;

  // src/editorials-dropdown-for-atcoder/user-script.ts
  (async function() {
    console.log(`[EDFA] ${title} v${version} (UserScript) started.`);
    await editorialsDropdown({
      fetchXMLDocument: async (url2) => await GM.xmlHttpRequest({ url: url2 }).then((res) => res.responseXML)
    });
  })();
})();