Ace to Monaco for AtCoder

AtCoderのエディタをMonacoに差し替えます

目前為 2023-12-15 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Ace to Monaco for AtCoder
// @namespace    https://yahoo.co.jp
// @version      1.0.0
// @description  AtCoderのエディタをMonacoに差し替えます
// @author       茶色コーダー
// @license      MIT
// @match        https://atcoder.jp/contests/*/custom_test
// @exclude      
// @grant        none
// ==/UserScript==

// 要素が出現するまで待つ関数(出てこなかったら永遠ループ)
const waitQuerySelector = async function (selector, node = document) {
    let obj = null;

    while (!obj) {
        obj = await new Promise((resolve) =>
            setTimeout(() => resolve(node.querySelector(selector), 100))
        );
    }

    return obj;
};

// 指定秒数待つ関数
const wait = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const func1 = async function () {
    // headを取得する
    const head = document.head;
    // headの末尾にMonaco Editor用CSS(+α)を追加する
    head.insertAdjacentHTML(
        "beforeEnd",
        `<link rel="stylesheet" data-name="vs/editor/editor.main" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/editor/editor.main.min.css" />
        <style>#editor {max-height: 600px;}</style>`
    );

    const script = document.createElement("script");
    script.src = "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.min.js";
    document.head.prepend(script);

    // ACEエディタの中身はいらないので消す
    const editor = document.getElementById("editor");
    editor.innerHTML = "";

    // loaderを読み込み終わったら処理開始
    script.onload = async function () {
        // 画面読み込み時の初期コードの取得
        const initCode = document.querySelector("#plain-textarea").innerText;

        // プログラミング言語を選択する要素の取得(読み込み対策で0.1秒遅延)
        await wait(100);
        const langSelector = document.querySelector('select[name="data.LanguageId"]');
        const langOptions = Array.from(document.querySelectorAll("option"));

        // 選択した要素のIDがどの言語なのか判別
        const langText = langOptions.find((item) => {
            return item.getAttribute("value") === langSelector.value;
        });
        const langMode = langText ? langText.getAttribute("data-ace-mode") : "text";
        // console.log("最初のテキストは", langMode);

        // Monaco EditorのCDNのパスを設定
        require.config({
            paths: { vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs" },
        });

        // Monaco Editor周りの処理
        require(["vs/editor/editor.main"], async function () {
            // Monaco Editorを降臨させる
            const localEditor = monaco.editor.create(editor, {
                value: initCode,
                language: langMode,
                theme: "vs-dark",
                lineHeight: 21,
            });

            // 言語変更時の監視対象のエレメントの取得
            const langElement = await waitQuerySelector(".select2-selection__rendered");

            //MutationObserver(インスタンス)の作成
            var mo = new MutationObserver(function (record, observer) {
                const lang = langOptions.find((item) => {
                    return item.getAttribute("value") === langSelector.value;
                });

                // console.info(
                //     "innerText: ",
                //     langElement.innerText,
                //     "\n ace-mode: ",
                //     lang.getAttribute("data-ace-mode")
                // );

                // Editorに新たな言語を設定
                monaco.editor.setModelLanguage(
                    localEditor.getModel(),
                    lang.getAttribute("data-ace-mode")
                );
            });

            // 監視する「もの」の指定(必ず1つ以上trueにする)
            var config = {
                childList: true, //「子ノード(テキストノードも含む)」の変化
                attributes: false, //「属性」の変化
                characterData: false, //「テキストノード」の変化
            };

            // 監視の開始
            mo.observe(langElement, config);

            // コードテストのform実行時にAce Editorから値を持ってきてしまうので、
            // その上からMonaco Editorの値で書き換える
            const formRef = $(".form-code-submit");
            formRef.on("submit", function () {
                $("#plain-textarea").val(localEditor.getValue());
            });
        });
    };
};

async function execWorkflow() {
    await func1();
}

// メイン処理の実行タイミングが、windowのロード時となるように登録する
window.addEventListener("load", async function () {
    await execWorkflow();
});