洛谷 - 梦回考场

洛谷题目页面改为NOI/NOIP/CSP经典PDF风格,享受沉浸(致郁)式刷题。

当前为 2025-08-03 提交的版本,查看 最新版本

// ==UserScript==
// @name         洛谷 - 梦回考场
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  洛谷题目页面改为NOI/NOIP/CSP经典PDF风格,享受沉浸(致郁)式刷题。
// @author       Jerrycyx (Luogu UID 545986)
// @match        https://www.luogu.com.cn/problem/*
// @license      MPL-2.0
// @grant        GM_addStyle
// @require      https://scriptcat.org/lib/513/2.1.0/ElementGetter.js#sha256=aQF7JFfhQ7Hi+weLrBlOsY24Z2ORjaxgZNoni7pAz5U=
// ==/UserScript==

(function () {
    "use strict";

    if (CheckUrl()) {
        console.log("[Luogu-RER] URL passed");
        MainScript();
    }

    function CheckUrl() {
        const path = window.location.pathname;
        const validPrefixes = ["P", "B", "CF", "SP", "AT", "UVA", "T", "U"];
        const regex = /^\/problem\/([^/]+)$/;
        const match = path.match(regex);
        if (!match) return false;
        if (!validPrefixes.some(prefix => match[1].startsWith(prefix))) return false;
        return true;
    }

    async function MainScript() {
        console.log("[Luogu-RER] MainScript Running.");
        const mainElement = await elmGetter.get("#app > .main-container");
        mainElement.classList.add("main-element");
        mainElement.id = "Main-element";
        const [oringinalProblemHeader, oringinalSidebar, problemElement] = await elmGetter.get([
            ".theme-bg:nth-child(1) > .theme-fg > .columba-content-wrap.header-layout",
            "main > .columba-content-wrap > .sidebar-container > .side",
            "main > .columba-content-wrap > .sidebar-container > .main > .problem"
        ], mainElement);
        problemElement.classList.add("problem-element");
        problemElement.id = "Problem-element";

        (async function () {
            await Promise.all([
                GlobalStyles(),
                ProcessSampleBlocks(),
                ProcessCodeBlocks(),
                ProcessTitles().then(ProcessImages),
                ProcessStrong(),
                ProcessA(),
                ProcessTable(),
                BuildToolbar(),
                BuildStat().then(BuildHeader)
            ]);
            RemoveElement();
        })();


        async function RemoveElement() {
            console.log("[Luogu-RER] RemoveElement Running.");

            removeSome(1, "#app > nav");
            removeSome(1, "#app > .top-bar");
            removeSome(1, "#app > .page-loading");
            removeSome(1, "#app > .nav-scrollbar");
            removeSome(2, ".theme-bg", mainElement);
            removeSome(1, "main .columba-content-wrap > .sidebar-container > .side", mainElement);
            removeSome(1, ".problem-block-actions", problemElement);
            removeAll(".io-sample > .io-sample-block button.lform-size-small", problemElement);
        }

        async function GlobalStyles() {
            console.log("[Luogu-RER] GlobalStyles Running.");

            elmGetter.each("#Problem-element p, #Problem-element ul, #Problem-element ol, #Problem-element .attachments", problemElement, e => {
                e.classList.add("custom-common-text");
            });
            GM_addStyle(`
                #Problem-element {
                    --common-font-family: KaTeX_Main, SimSun, serif;
                    --strong-font-family: KaTeX_Main, SimHei, sans-serif;
                }
                .custom-common-text {
                    font-family: var(--common-font-family);
                    font-weight: normal;
                }
                .custom-strong-text {
                    font-family: var(--strong-font-family);
                    font-weight: normal;
                }
            `);

            GM_addStyle(`
                #Main-element {
                    margin: 12px 0;
                    width: auto;
                }
                #Main-element > main {
                    background-color: unset;
                }
                @media (prefers-color-scheme: light) {
                    body {
                        background-color: #e6e6e6;
                    }
                }
                @media (prefers-color-scheme: dark) {
                    body {
                        background-color: #333333;
                    }
                }
                #Main-element > main > .columba-content-wrap {
                    padding: 0px;
                }
                #Main-element > main > .columba-content-wrap > .sidebar-container {
                    margin: unset;
                }
                #Problem-element {
                    color: black;
                    width: 100%
                    max-width: 1191px;
                    margin: 0 auto;
                    padding: 77px 141px 156px 141px;
                    border-radius: 0px;
                    box-shadow: 0 1px 10px 0px #1a1a1a1a;
                }

                #Problem-element p:not(li > p) {
                    text-indent: 2em;
                }
                #Problem-element p, #Problem-element ul, #Problem-element ol, #Problem-element .attachments {
                    font-size: 24px;
                    line-height: 1.6;
                    margin: unset;
                }
                #Problem-element ul, #Problem-element ol {
                    margin-left: 1em;
                }
                #Problem-element :is(ul, ol) :is(ul, ol) {
                    margin-left: unset;
                }
                #Problem-element :is(ul, ol) :is(ul, ol) li::marker {
                    content: "–  ";
                }

                .katex:not(.katex-display .katex) {
                    font-size: 1em;
                }

                #Problem-element ::selection{
                    background-color: #99c1da; //TODO
                    mix-blend-mode: multiply;
                    color: inherit;
                }

                #Problem-element :focus-visible {
                    outline: 3px dashed black;
                }
            `);
        }

        async function ProcessSampleBlocks() {
            console.log("[Luogu-RER] ProcessSampleBlocks Running.");

            elmGetter.each("h2.lfe-h2", problemElement, h2 => {
                if (h2.textContent.trim() === "输入输出样例") { h2.remove(); }
            });

            elmGetter.each(".io-sample > .io-sample-block b", problemElement, b => {
                const content = b.textContent.trim();
                const regex = /^(输入|输出)\s*#(\d+)$/;
                const match = content.match(regex);
                if (match) {
                    const type = match[1];
                    const num = match[2];
                    const h2 = document.createElement("h2");
                    h2.classList.add("lfe-h2");
                    h2.textContent = `样例 ${num} ${type}`;
                    b.parentNode.parentNode.replaceChild(h2, b.parentNode);

                }
            });
        }

        async function ProcessCodeBlocks() {
            console.log("[Luogu-RER] ProcessCodeBlocks Running.");

            /*
            elmGetter.each("pre", problemElement, pre => {
                const code = pre.querySelector("code") || pre;
                const text = code.textContent;
                const lines = text.split("\n");

                let lastNonEmptyIndex = -1;
                for (let i = lines.length - 1; i >= 0; i--) {
                    if (lines[i].trim() !== "") {
                        lastNonEmptyIndex = i;
                        break;
                    }
                }

                const nonEmptyLines = lastNonEmptyIndex >= 0
                    ? lines.slice(0, lastNonEmptyIndex + 1)
                    : [];
                const lineCount = nonEmptyLines.length;

                const lineNumbersContainer = document.createElement("div");
                lineNumbersContainer.classList.add("custom-pre-line-number");

                for (let i = 1; i <= lineCount; i++) {
                    const lineNumber = document.createElement("div");
                    lineNumber.textContent = i;
                    lineNumbersContainer.appendChild(lineNumber);
                }

                const digits = lineCount.toString().length;
                lineNumbersContainer.style.width = `${digits * 0.5 + 0.5}em`;

                const wrapper = document.createElement("div");
                wrapper.classList.add("custom-pre-wrapper");

                pre.parentNode.insertBefore(wrapper, pre);
                wrapper.appendChild(lineNumbersContainer);
                wrapper.appendChild(pre);
            });
            */

            async function ProcessSingleCodeBlocks(e) {
                e.classList.add("code-block");
                var lines = e.textContent.split("\n");
                const fragment = document.createDocumentFragment();
                lines.forEach((lineText, idx) => {
                    const lineContainer = document.createElement("div");
                    lineContainer.classList.add("custom-code-line");
                    const lineNumber = document.createElement("span");
                    lineNumber.classList.add("custom-line-number");
                    lineNumber.textContent = idx + 1;
                    const contentSpan = document.createElement("span");
                    contentSpan.classList.add("custom-code-content");
                    contentSpan.textContent = lineText;
                    lineContainer.appendChild(lineNumber);
                    lineContainer.appendChild(contentSpan);
                    fragment.appendChild(lineContainer);
                });
                e.innerHTML = "";
                e.appendChild(fragment);
            }

            elmGetter.each(".io-sample-block pre", problemElement, ProcessSingleCodeBlocks);

            removeAll(".code-container > button.copy-button", problemElement);
            removeAll(".line-numbers-rows", problemElement);

            GM_addStyle(`
                #Problem-element > .lfe-marked-wrap {
                    overflow: visible;
                }

                #Problem-element > .io-sample {
                    margin: 0px;
                    display: block;
                }
                #Problem-element > .io-sample > .io-sample-block {
                    margin: 0px;
                }

                #Problem-element pre, #Problem-element code, .custom-pre-line-number {
                    font-family: Consolas !important; /* SB Luogu don't use that f**king "!important" */
                    background-color: unset;
                }
                #Problem-element pre, #Problem-element code {
                    font-size: 24px;
                    line-height: 1.5;
                }
                #Problem-element pre {
                    margin: 27px -5px 62px 17px;
                }
                .code-block {
                    position: relative;
                    overflow: visible;
                }

                #Problem-element .custom-line-number {
                    position: absolute;
                    text-align: right;
                    right: 100%;
                    font-size: 20px;
                    color: #949494;
                    padding-right: 14px;
                    padding-top: 4px;
                }

                #Problem-element code {
                    border: none;
                }
                #Problem-element pre {
                    border: solid 1px blue;
                    border-radius: 0;
                    padding: 5px;
                }
            `);
        }

        async function ProcessTitles() {
            console.log("[Luogu-RER] ProcessTitles Running.");

            elmGetter.each("h1, h2, h3, h4, h5, h6", problemElement, h => {
                h.classList.add("H123456", "custom-strong-text");
                if (!h.classList.contains("custom-title")) {
                    const text = h.textContent;
                    if (text.length > 0) {
                        if (text[0] !== "【") {
                            h.insertBefore(document.createTextNode("【"), h.firstChild);
                        }
                        if (text[text.length - 1] !== "】") {
                            h.appendChild(document.createTextNode("】"));
                        }
                    }
                }
            });

            GM_addStyle(`
                .custom-title {
                    text-align: center;
                }
                .H123456 {
                    font-weight: normal;
                }
                #Problem-element .H123456:not(.custom-title) {
                    text-indent: 1em;
                }
                #Problem-element h1 {
                    font-size: 36px;
                    margin: 37px 0px;
                    border-bottom: unset;
                    padding-bottom: unset;
                }
                #Problem-element h2 {
                    margin-top: 43px;
                    margin-bottom: 18px;
                    font-size: 26px;
                    border-bottom: unset;
                    padding-bottom: unset;
                }
                #Problem-element > p.problem-block-actions:not(#problem-element :first-child) {
                    margin-top: 43px;
                    margin-bottom: 18px;
                }
                #Problem-element h3 {
                    margin-top: 24px;
                    margin-bottom: 7px;
                    font-size: 25px;
                }
                #Problem-element h4, #Problem-element h5, #Problem-element h6 {
                    margin-top: 10px;
                    margin-bottom: 7px;
                    font-size: 24px;
                }
            `);
        }

        async function ProcessImages() {
            console.log("[Luogu-RER] ProcessImages Running.");

            elmGetter.each("p, .H123456", problemElement, e => {
                function isValidChainImage(e) {
                    if (e.tagName === 'IMG') return true;
                    let current = e;
                    while (current) {
                        const childNodes = current.childNodes;
                        if (childNodes.length !== 1 || childNodes[0].nodeType !== 1) {
                            return false;
                        }
                        current = current.firstElementChild;
                        if (current && current.tagName === 'IMG') {
                            return true;
                        }
                    }
                }
                if (isValidChainImage(e)) e.classList.add("single-line-image");
            });

            GM_addStyle(`
                .single-line-image {
                    text-indent: 0 !important;
                    text-align: center;
                }
            `)
        }

        async function ProcessStrong() {
            console.log("[Luogu-RER] ProcessStrong Running.");

            elmGetter.each("strong", problemElement, strong => {
                strong.classList.add("custom-strong-text");
                const fragment = document.createDocumentFragment();
                const processNode = (node) => {
                    if (node.nodeType === Node.TEXT_NODE) {
                        const text = node.textContent;
                        const segments = text.split(/([\u4e00-\u9fa5])/);
                        segments.forEach(segment => {
                            if (!segment) return;

                            if (/^[\u4e00-\u9fa5]$/.test(segment)) {
                                const span = document.createElement("span");
                                span.classList.add("custom-hanzi-dot");
                                span.textContent = segment;

                                const svgNS = "http://www.w3.org/2000/svg";
                                const svg = document.createElementNS(svgNS, "svg");
                                svg.setAttribute("class", "custom-dot");
                                svg.setAttribute("viewBox", "0 0 10 10");

                                const circle = document.createElementNS(svgNS, "circle");
                                circle.setAttribute("cx", "5");
                                circle.setAttribute("cy", "5");
                                circle.setAttribute("r", "3");
                                circle.setAttribute("fill", "black");

                                svg.appendChild(circle);
                                span.appendChild(svg);
                                fragment.appendChild(span);
                            } else {
                                fragment.appendChild(document.createTextNode(segment));
                            }
                        });
                    } else if (node.nodeType === Node.ELEMENT_NODE) {
                        fragment.appendChild(node.cloneNode(true));
                    }
                };
                strong.childNodes.forEach(node => processNode(node));
                strong.innerHTML = "";
                strong.appendChild(fragment);
            });

            GM_addStyle(`
                .custom-hanzi-dot {
                    position: relative;
                }
                .custom-hanzi-dot svg.custom-dot {
                    height: 0.16em;
                    position: absolute;
                    left: 0.5em;
                    bottom: -0.05em;
                    transform: translateX(-50%);
                    pointer-events: none;
                }
            `);
        }

        async function ProcessA() {
            console.log("[Luogu-RER] ProcessA Running.");

            elmGetter.each("a", problemElement, e => {
                e.classList.add("custom-strong-text");
            });

            GM_addStyle(`
                #Problem-element a {
                    color: black;
                    font-style: italic;
                    transition: unset;
                }
                #Problem-element a:hover {
                    filter: none;
                }
            `)
        }

        async function ProcessTable() {
            console.log("[Luogu-RER] ProcessTable Running.");

            elmGetter.each("table > thead > tr > th, table > tbody > tr > td", problemElement, e => {
                e.classList.add("custom-common-text");
            });

            GM_addStyle(`
                #Problem-element table {
                    display: table;
                    margin: 25px auto;
                    width: fit-content;
                }

                #Problem-element table > thead > tr {
                    border-bottom: 3px solid black;
                }
                #Problem-element table > thead > tr > th, #Problem-element table > tbody > tr > td {
                    font-size: 24px;
                    border: 1px solid black;
                }
                #Problem-element table {
                    border-top: 4px solid black;
                    border-bottom: 4px solid black;
                    border-left: 2px solid white;
                    border-right: 2px solid white;
                }
            `)
        }

        async function BuildToolbar() {
            GM_addStyle(`
                @media (prefers-color-scheme: light) {
                    #Custom-toolbar {
                        --toolbar-bg: #f7f7f7;
                        --toolbar-border: #bebebe;
                        --toolbar-text: #262626;
                        --toolbar-button-on: #e5e5e5;
                        --toolbar-button-on-bottom: #0072c9;
                        --toolbar-button-hover: #eaeaea;
                        --toolbar-button-active: #efefef;
                        --toolbar-separator-color: #b6b6b6;
                        --toolbar-shadow: 0px 4.8px 10.8px rgba(0,0,0,0.13), 0px 0px 4.7px rgba(0,0,0,0.11);
                    }
                    #Custom-toolbar-setting {
                        --setting-menu-bg: #ffffff;
                        --setting-item-hover: #ebebeb;
                        --setting-menu-hr: #e0e0e0
                    }
                }
                @media (prefers-color-scheme: dark) {
                    #Custom-toolbar {
                        --toolbar-bg: #3b3b3b;
                        --toolbar-border: #4f4f4f;
                        --toolbar-text: #ffffff;
                        --toolbar-button-on: #4d4d4d;
                        --toolbar-button-on-bottom: #63ade5;
                        --toolbar-button-hover: #484848;
                        --toolbar-button-active: #424242;
                        --toolbar-separator-color: #737373;
                        --toolbar-shadow: rgba(0, 0, 0, 0.26) 0px 4.8px 10.8px, rgba(0, 0, 0, 0.22) 0px 0px 4.7px;
                    }
                    #Custom-toolbar-setting {
                        --setting-menu-bg: #292929;
                        --setting-item-hover: #313131;
                        --setting-menu-hr: #545454;
                    }
                }
            `)

            console.log("[Luogu-RER] BuildToolbar Running.");

            const toolbar = document.createElement("div");
            toolbar.id = "Custom-toolbar";
            toolbar.classList.add("custom-toolbar");

            const toolbarLeft = document.createElement("div");
            toolbarLeft.id = "Custom-toolbar-left";
            toolbarLeft.classList.add("custom-toolbar-left");

            const toolbarCenter = document.createElement("div");
            toolbarCenter.id = "Custom-toolbar-center";
            toolbarCenter.classList.add("custom-toolbar-center");

            const toolbarRight = document.createElement("div");
            toolbarRight.id = "Custom-toolbar-right";
            toolbarRight.classList.add("custom-toolbar-right");

            toolbar.append(toolbarLeft, toolbarCenter, toolbarRight);
            const documentApp = document.querySelector("#app");
            documentApp.insertBefore(toolbar, documentApp.firstChild);


            function RemoveAllVueDataAttributes(e) {
                Array.from(e.attributes).forEach(attr => {
                    if (attr.name.startsWith('data-v-')) {
                        e.removeAttribute(attr.name);
                    }
                });
            }
            function removeAllAttributes(e) {
                [...e.attributes].forEach(attr => e.removeAttribute(attr.name));
            }

            async function InitToolbarLeftLargeButton(selector, parent, id, stateUpdater) {
                const button = parent.querySelector(selector);
                removeAllAttributes(button);
                button.classList.add("custom-toolbar-button", "custom-toolbar-button-large");
                button.id = id;
                toolbarLeft.appendChild(button);
                if (stateUpdater) stateUpdater(button);
                return button;
            }

            const submitButton = await InitToolbarLeftLargeButton(
                "div > div > button[type='button']",
                oringinalProblemHeader,
                "Custom-toolbar-submit",
                btn => btn.classList.toggle(
                    "custom-toolbar-button-on",
                    window.location.hash === "#submit"
                )
            );
            new MutationObserver(() => submitButton.classList.toggle(
                "custom-toolbar-button-on",
                window.location.hash === "#submit"
            )).observe(document, { subtree: true, childList: true });

            const leftSeparator1 = document.createElement("div");
            leftSeparator1.classList.add("custom-toolbar-separator");
            toolbarLeft.appendChild(leftSeparator1);

            async function BuildModalButton(id, modalIndex) {
                const button = await InitToolbarLeftLargeButton(
                    "div > div > button[type='button']",
                    oringinalProblemHeader,
                    id
                );
                const modal = document.querySelectorAll("#app > div.modal")[modalIndex];
                const updateState = () => button.classList.toggle(
                    "custom-toolbar-button-on",
                    !modal.classList.contains("hide")
                );
                updateState();
                new MutationObserver(updateState).observe(modal, {
                    attributes: true,
                    attributeFilter: ["class"]
                });
                return button;
            }
            await BuildModalButton("Custom-toolbar-add-list", 0);
            await BuildModalButton("Custom-toolbar-copy-problem", 1);

            const leftSeparator2 = document.createElement("div");
            leftSeparator2.classList.add("custom-toolbar-separator");
            toolbarLeft.appendChild(leftSeparator2);

            problemElement.querySelectorAll(".problem-block-actions > *").forEach(actionButton => {
                const text = actionButton.textContent.trim();
                if (text != "展开" && text != "进入 IDE 模式" && text != "显示翻译" && text != "隐藏翻译") {
                    removeAllAttributes(actionButton);
                    actionButton.className = "custom-toolbar-a";
                    toolbarLeft.appendChild(actionButton);
                }
                if (text == "显示翻译" || text == "隐藏翻译") {

                }
            });


            const ids = ["record", "solution", "feedback"];
            for (const id of ids) {
                const element = oringinalSidebar.querySelector("div:first-child > a:first-of-type");
                RemoveAllVueDataAttributes(element);
                element.classList.add("custom-toolbar-a");
                element.id = `Custom-toolbar-a-${id}`;
                toolbarRight.appendChild(element);
            }


            const rightSeparator1 = document.createElement("div");
            rightSeparator1.classList.add("custom-toolbar-separator");
            toolbarRight.appendChild(rightSeparator1);

            const setting = document.createElement("div");
            setting.id = "Custom-toolbar-setting";
            setting.classList.add("custom-toolbar-setting");
            toolbarRight.appendChild(setting);

            const settingButton = document.createElement("button");
            settingButton.id = "Custom-toolbar-setting-button";
            settingButton.classList.add("custom-toolbar-setting-button", "custom-toolbar-button");
            function CreateSettingSVG() {
                const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
                svg.setAttribute("width", "20");
                svg.setAttribute("height", "20");
                svg.setAttribute("viewBox", "0 0 20 20");
                const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
                path.setAttribute("d", "M 1.91 7.38 A 8.5 8.5 0 0 1 3.7 4.3 a 0.5 0.5 0 0 1 0.54 -0.13 l 1.92 0.68 a 1 1 0 0 0 1.32 -0.76 l 0.36 -2 a 0.5 0.5 0 0 1 0.4 -0.4 a 8.53 8.53 0 0 1 3.55 0 c 0.2 0.04 0.35 0.2 0.38 0.4 l 0.37 2 a 1 1 0 0 0 1.32 0.76 l 1.92 -0.68 a 0.5 0.5 0 0 1 0.54 0.13 a 8.5 8.5 0 0 1 1.78 3.08 c 0.06 0.2 0 0.4 -0.15 0.54 l -1.56 1.32 a 1 1 0 0 0 0 1.52 l 1.56 1.32 a 0.5 0.5 0 0 1 0.15 0.54 a 8.5 8.5 0 0 1 -1.78 3.08 a 0.5 0.5 0 0 1 -0.54 0.13 l -1.92 -0.68 a 1 1 0 0 0 -1.32 0.76 l -0.37 2 a 0.5 0.5 0 0 1 -0.38 0.4 a 8.53 8.53 0 0 1 -3.56 0 a 0.5 0.5 0 0 1 -0.39 -0.4 l -0.36 -2 a 1 1 0 0 0 -1.32 -0.76 l -1.92 0.68 a 0.5 0.5 0 0 1 -0.54 -0.13 a 8.5 8.5 0 0 1 -1.78 -3.08 a 0.5 0.5 0 0 1 0.15 -0.54 l 1.56 -1.32 a 1 1 0 0 0 0 -1.52 L 2.06 7.92 a 0.5 0.5 0 0 1 -0.15 -0.54 Z m 1.06 0 l 1.3 1.1 a 2 2 0 0 1 0 3.04 l -1.3 1.1 c 0.3 0.79 0.71 1.51 1.25 2.16 l 1.6 -0.58 a 2 2 0 0 1 2.63 1.53 l 0.3 1.67 a 7.55 7.55 0 0 0 2.5 0 l 0.3 -1.67 a 2 2 0 0 1 2.64 -1.53 l 1.6 0.58 a 7.5 7.5 0 0 0 1.24 -2.16 l -1.3 -1.1 a 2 2 0 0 1 0 -3.04 l 1.3 -1.1 a 7.5 7.5 0 0 0 -1.25 -2.16 l -1.6 0.58 a 2 2 0 0 1 -2.63 -1.53 l -0.3 -1.67 a 7.55 7.55 0 0 0 -2.5 0 l -0.3 1.67 A 2 2 0 0 1 5.81 5.8 l -1.6 -0.58 a 7.5 7.5 0 0 0 -1.24 2.16 Z M 7.5 10 a 2.5 2.5 0 1 1 5 0 a 2.5 2.5 0 0 1 -5 0 Z m 1 0 a 1.5 1.5 0 1 0 3 0 a 1.5 1.5 0 0 0 -3 0 Z");
                path.setAttribute("fill", getComputedStyle(toolbar).getPropertyValue("--toolbar-text"));
                svg.appendChild(path);
                return svg;
            }
            settingButton.appendChild(CreateSettingSVG());
            setting.appendChild(settingButton);

            const settingMenu = document.createElement("div");
            settingMenu.id = "Custom-toolbar-setting-menu";
            settingMenu.classList.add("custom-toolbar-setting-menu", "custom-toolbar-setting-menu-hidden");
            setting.appendChild(settingMenu);

            let globalClickListener = null;
            settingButton.addEventListener("click", function (e) {
                e.stopPropagation();
                settingMenu.classList.remove("custom-toolbar-setting-menu-hidden");
                if (globalClickListener) {
                    document.removeEventListener("click", globalClickListener);
                }
                globalClickListener = function (event) {
                    const isClickInsideMenu = settingMenu.contains(event.target);
                    const isClickOnButton = settingButton.contains(event.target);

                    if (!isClickInsideMenu || isClickOnButton) {
                        settingMenu.classList.add("custom-toolbar-setting-menu-hidden");
                        document.removeEventListener("click", globalClickListener);
                        globalClickListener = null;
                    }
                };
                setTimeout(() => {
                    document.addEventListener("click", globalClickListener);
                }, 0);
            });

            function BuildSettingItemSwitch(text, onToggle, initialState = false) {
                const settingItem = document.createElement("button");
                settingItem.classList.add("custom-toolbar-setting-item");

                const checkbox = document.createElement("span");
                checkbox.classList.add("custom-toolbar-setting-checkbox");
                checkbox.innerHTML = '<svg style="width: 1.25em; height: 1.25em; vertical-align: middle; fill: currentColor; overflow: hidden;" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M365.624235 692.484607 165.695786 492.556159 99.05297 559.198975 365.624235 825.77024 936.850128 254.54537 870.207311 187.902554Z" fill="#b3b3b3"></path></svg>';

                const textSpan = document.createElement("span");
                textSpan.classList.add("custom-toolbar-setting-text");
                textSpan.textContent = text;

                const content = document.createElement("div");
                content.classList.add("custom-toolbar-setting-content");
                content.appendChild(checkbox);
                content.appendChild(textSpan);
                settingItem.appendChild(content);

                let enabled = initialState;
                checkbox.style.visibility = enabled ? "visible" : "hidden";

                onToggle(enabled, text);
                settingItem.addEventListener("click", () => {
                    enabled = !enabled;
                    checkbox.style.visibility = enabled ? "visible" : "hidden";
                    onToggle(enabled, text);
                });

                settingMenu.appendChild(settingItem);

                return {
                    getState: () => enabled,
                    setState: (newState) => {
                        enabled = newState;
                        checkbox.style.visibility = enabled ? "visible" : "hidden";
                        onToggle(enabled, text);
                    },
                    toggle: () => settingItem.click(),
                    element: settingItem
                };
            }

            function SwitchStyle(enabled, styleId, enabledStyle, disabledStyle) {
                let styleElement = document.getElementById(styleId);
                if (!styleElement) {
                    styleElement = document.createElement("style");
                    styleElement.id = styleId;
                    document.head.appendChild(styleElement);
                }
                const cssRule = enabled ? enabledStyle : disabledStyle;
                styleElement.textContent = cssRule;
            }

            BuildSettingItemSwitch("锁定工具栏", (enabled) => {
                SwitchStyle(enabled, "fix-toolbar-style",
                    "#Custom-toolbar {\
                        position: sticky;\
                        top: 0;\
                    }",
                    "#Custom-toolbar {\
                        position: fixed;\
                        top: 0;\
                        box-shadow: var(--toolbar-shadow)\
                    }");

                let isNearTop = true, hideTimer = null;

                function showToolbar() {
                    toolbar.style.top = "0";
                    if (hideTimer) {
                        clearTimeout(hideTimer);
                        hideTimer = null;
                    }
                }
                function hideToolbar() {
                    toolbar.style.top = "-41px";
                }

                function optimizedThrottle(func, limit) {
                    let lastRan = 0;
                    let ticking = false;

                    return function () {
                        const context = this;
                        const args = arguments;
                        const now = Date.now();

                        if (now - lastRan >= limit) {
                            if (!ticking) {
                                requestAnimationFrame(function () {
                                    func.apply(context, args);
                                    lastRan = now;
                                    ticking = false;
                                });
                                ticking = true;
                            }
                        }
                    };
                }

                const handleMouseMove = optimizedThrottle(function (e) {
                    if (enabled) {
                        showToolbar();
                        if (hideTimer) {
                            clearTimeout(hideTimer);
                            hideTimer = null;
                        }
                        return;
                    }
                    const mouseY = e.clientY;
                    const newState = mouseY <= 41;

                    if (newState !== isNearTop) {
                        isNearTop = newState;
                        if (isNearTop) {
                            showToolbar();
                        } else {
                            if (hideTimer) {
                                clearTimeout(hideTimer);
                                hideTimer = null;
                            }
                            hideTimer = setTimeout(hideToolbar, 2000);
                        }
                    }
                }, 100);

                window.addEventListener('mousemove', handleMouseMove);
            }, true);

            BuildSettingItemSwitch("样例复制时带行号", (enabled) => {
                SwitchStyle(enabled, "line-number-selection-style",
                    "#Problem-element .custom-line-number { user-select: all; }",
                    "#Problem-element .custom-line-number { user-select: none; }"
                );
            }, false);

            BuildSettingItemSwitch("显示时空限制", (enabled) => {
                SwitchStyle(enabled, "limit-show-style",
                    "",
                    ".custom-stat { display: none; }"
                );
            }, true);

            const settingHr1 = document.createElement("hr");
            settingHr1.classList.add("custom-setting-hr");
            settingMenu.appendChild(settingHr1);

            const settingItemInfo = document.createElement("button");
            settingItemInfo.classList.add("custom-toolbar-setting-item");
            settingItemInfo.id = "Custom-setting-item-info";
            settingItemInfo.textContent = "查看题目详细信息(暂不可用)";
            settingMenu.appendChild(settingItemInfo);

            GM_addStyle(`
                #Custom-toolbar {
                    background-color: var(--toolbar-bg);
                    border-bottom: 1px solid var(--toolbar-border);

                    z-index: 10;
                    width: 100%;
                    height: 41px;
                    padding: 0px 4px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    box-sizing: border-box;

                    transition: top 0.5s;

                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI';
                }
                #Custom-toolbar-left, #Custom-toolbar-center, #Custom-toolbar-right {
                    height: 100%;
                    display: flex;
                    align-items: center;
                }

                #Custom-toolbar-left {
                    justify-content: flex-start;
                    margin-right: auto;
                }
                #Custom-toolbar-center {
                    justify-content: center;
                    margin-left: auto;
                    margin-right: auto;
                }
                #Custom-toolbar-right {
                    justify-content: flex-end;
                    margin-left: auto;
                }
                #Custom-toolbar-setting-menu {
                    position: absolute;
                    right: 2px;
                    width: max-content;
                }

                .custom-toolbar-button:not(.custom-toolbar-button-large), .custom-toolbar-a {
                    font-size: 15px;
                }
                .custom-toolbar-button-large {
                    font-size: 16px;
                }

                .custom-toolbar-button, .custom-toolbar-a {
                    cursor: default;
                    border: 2px solid transparent;
                    border-radius: 2px;

                    padding: 0 8px;
                    margin: 2px 0px;
                    max-height: 100%;

                    display: flex;
                    white-space: pre-wrap;
                    justify-content: center;
                    align-items: center;

                    color: var(--toolbar-text);
                    background-color: var(--toolbar-bg);
                    outline: none;
                    transition: background-color 0.1s ease-in-out;
                }
                .custom-toolbar-button {
                    height: 32px;
                }
                .custom-toolbar-a {
                    height: 28px;
                }
                .custom-toolbar-a a {
                    color: var(--toolbar-text);
                    outline: none;
                }

                .custom-toolbar-button-on {
                    background-color: var(--toolbar-button-on);
                    border-bottom-color: var(--toolbar-button-on-bottom);
                }

                .custom-toolbar-button:hover, .custom-toolbar-a:hover {
                    background-color: var(--toolbar-button-hover);
                    filter: unset;
                }
                .custom-toolbar-button:active, .custom-toolbar-a:active {
                    background-color: var(--toolbar-button-active);
                }

                .custom-toolbar-separator {
                    margin: 0 4px;
                    width: 1px;
                    height: 16px;
                    background-color: var(--toolbar-separator-color);
                }

                #app > .dropdown.shown {
                    z-index: 12;
                }

                #Custom-toolbar-setting-button {
                    cursor: pointer;
                }
                #Custom-toolbar-setting-menu {
                    display: flex;
                    flex-direction: column;

                    background-color: var(--setting-menu-bg);
                    padding: 5px 2px;
                    border-radius: 10px;
                    box-shadow: var(--toolbar-shadow);
                    font-size: 15px;

                    transition: opacity 0.1s ease, visibility 0.1s ease;
                }
                .custom-toolbar-setting-menu-hidden {
                    opacity: 0;
                    visibility: hidden;
                }
                .custom-toolbar-setting-checkbox {
                    margin-right: 0.5em;
                }
                .custom-toolbar-setting-item {
                    width: auto;
                    text-align: left;

                    padding: 5px 10px;
                    margin: 0 4px;
                    background-color: transparent;
                    color: var(--toolbar-text);
                    border: unset;
                    border-radius: 4px;
                }
                .custom-toolbar-setting-item:hover {
                    background-color: var(--setting-item-hover);
                }
                .custom-setting-hr {
                    border: 1px var(--setting-menu-hr) solid;
                    width: 100%;
                    margin: 5px 0;
                }
            `);
        }

        async function BuildStat() {
            const [timeLimit, memoryLimit] = (await elmGetter.get([
                ".stat > .field:nth-child(3) > .stat-text.value",
                ".stat > .field:nth-child(4) > .stat-text.value"
            ], oringinalProblemHeader)).map(e => e.textContent.trim());

            console.log("[Luogu-RER] BuildStat Running.");

            const stat = document.createElement("div");
            stat.classList.add("custom-stat", "custom-common-text");

            const timeStat = document.createElement("div");
            timeStat.classList.add("custom-time-stat");
            timeStat.textContent = "时间限制:" + timeLimit;

            const memoryStat = document.createElement("div");
            memoryStat.classList.add("custom-memory-stat");
            memoryStat.textContent = "空间限制:" + memoryLimit;

            stat.append(timeStat, memoryStat);
            problemElement.insertBefore(stat, problemElement.firstChild);

            GM_addStyle(`
                .custom-stat {
                    font-size: 24px;
                    display: flex;
                    flex-wrap: wrap;
                    justify-content: center;
                }
                .custom-time-stat, .custom-memory-stat {
                    margin-left: 1em;
                    margin-right: 1em;
                }
            `);

            return stat;
        }

        async function BuildHeader(stat) {
            console.log("[Luogu-RER] BuildHeader Running.");

            const titleContent = (await elmGetter.get("h1.lfe-h1", oringinalProblemHeader)).textContent;

            const title = document.createElement("h1");
            title.classList.add("lfe-h1", "custom-title");
            title.textContent = titleContent.substring(titleContent.indexOf(" ") + 1);
            problemElement.insertBefore(title, stat);

            const customHeaderElement = document.createElement("header");
            customHeaderElement.classList.add("custom-header", "custom-common-text");
            problemElement.insertBefore(customHeaderElement, title);

            const leftText = document.createElement("div");
            leftText.classList.add("custom-header-left-text");
            leftText.textContent = "洛谷 Luogu";
            customHeaderElement.appendChild(leftText);

            const rightText = document.createElement("div");
            rightText.classList.add("custom-header-right-text");
            rightText.textContent = titleContent;
            customHeaderElement.appendChild(rightText);

            const hrElement = document.createElement("hr");
            hrElement.classList.add("custom-header-hr");
            customHeaderElement.appendChild(hrElement);

            GM_addStyle(`
                .custom-header {
                    font-size: 20px;
                    display: flex;
                    flex-wrap: wrap;
                    margin: 0px 0px 48px 0px;
                }
                .custom-header-left-text, .custom-header-right-text {
                    margin: 0px;
                }
                .custom-header-left-text {
                    margin-right: auto;
                }
                .custom-header-hr {
                    flex-basis: 100%;
                    border: 1px solid #bfbfbf;
                    margin: 0px;
                }
            `);
        }
    }

    async function removeAll(selector, container = document) {
        elmGetter.each(selector, container, e => { e.remove(); });
    }
    async function removeSome(number, selector, container = document) {
        for (let i = 1; i <= number; i++) {
            const e = await elmGetter.get(selector, container);
            e.remove();
        }
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址