Ace to Monaco for AtCoder

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

当前为 2023-12-15 提交的版本,查看 最新版本

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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();
});