Greasy Fork 支持简体中文。

CFCodereviewer

Codereview codeforces

// ==UserScript==
// @name         CFCodereviewer
// @namespace    http://tampermonkey.net/
// @version      2.2.0
// @description  Codereview codeforces
// @author       kdzestelov
// @license MIT
// @match        *://*codeforces.com/*
// @grant        GM_xmlhttpRequest
// @updateUrl    https://gist.githubusercontent.com/KhetagAb/000670a28c8a37b917ece858b142b747/raw/script.js
// ==/UserScript==

const SHEET_URL = 'https://script.google.com/macros/s/AKfycbxGoJrsWBtwNFcYcXBNrk-PUTIOWrsRvPqDxBzBqT_TFpfaurt8IB5Dc9dEQU97-Eoyug/exec';

const SUB_RJ_BUTTON_CLASS = "submission-rj-button";
const SUB_AC_BUTTON_CLASS = "submission-ac-button";
const SUB_RG_BUTTON_CLASS = "submission-rejudge-form";
const SUB_COMMENT_SEND_BUTTON_CLASS = "submission-comment-send-form";

function getLast(href) {
    return href.split("/").pop()
}

function getGroupUrl() {
    const url = window.location.href
    return url.substring(0, url.indexOf("contest"))
}

const getContestUrl = () => {
    const url = window.location.href
    return url.substring(0, url.indexOf("status"))
}

function getContestId() {
    const url = window.location.href
    return url.substr(url.indexOf("contest") + 8, 6);
}

function wrap(text) {
    return '[' + text + ']';
}

function getSubRjButton(subId) {
    return $("[submissionid=" + subId + "] ." + SUB_RJ_BUTTON_CLASS);
}

function getSubAcButton(subId) {
    return $("[submissionid=" + subId + "] ." + SUB_AC_BUTTON_CLASS);
}

function getCommentSubAcButton(subId) {
    return $("." + SUB_COMMENT_SEND_BUTTON_CLASS)
}

function getProblemIndex(subId) {
    return getLast($("tr[data-submission-id=" + subId + "] .status-small a").attr('href'));
}

function getSubRow(subId) {
    return $('tr[data-submission-id="' + subId + '"]')
}

function getHandle(subId) {
    return $("tr[data-submission-id=" + subId + "] .status-party-cell").text().trim();
}

function getAllSubmissionsRow() {
    return $(".status-frame-datatable tbody tr")
}

function getSubmissionRow(subId) {
    return $(".status-frame-datatable tbody tr[data-submission-id=" + subId + "]")
}

function getSubsId() {
    return $(".information-box-link")
}

function getCorrectSubs() {
    return $(".information-box-link .verdict-accepted").parent();
}

function getSubButtons() {
    return $(".submission-action-form");
}

function getSideBar() {
    return $("div[id=sidebar]")
}

function getFilterBox() {
    return $(".status-filter-box");
}

const getSheetSubmissions = () => {
    const fullUrl = SHEET_URL + "?type=get"
    GM_xmlhttpRequest({
        method: 'GET',
        url: fullUrl,
        onload: (response) => {
            if (response.status === 200) {
                try {
                    console.log("Going to parse response: " + response.responseText);
                    var submissions = JSON.parse(response.responseText)
                    submissions = JSON.parse(response.responseText)
                    localStorage.setItem("c_status", JSON.stringify(submissions));
                    console.log("Submissions loaded from sheet: " + submissions);
                } catch (e) {
                    console.error("Unable to parse submissions: " + e);
                }
            } else {
                console.error("Unable to load submissions from sheet: " + response.responseText);
            }
        },
        onerror: (error) => {
            console.error(error);
        }
    });
}

const acceptSheetSubmission = (subId, button, type) => {
    const full_url = SHEET_URL + "?type=" + type + "&value=" + subId;
    GM_xmlhttpRequest({
        method: 'GET',
        url: full_url,
        timeout: 5000,
        onload: (response) => {
            if (response.status === 200) {
                const submissions = getSubmissions();
                submissions.push(subId);
                localStorage.setItem("c_status", JSON.stringify(submissions));
                button.style.backgroundColor = "#81D718";
                button.style.borderColor = "#81D718";
                if (showed_codes[subId] != null && showed_codes[subId].showed) {
                    showed_codes[subId]["showButton"].click();
                }
                button.innerText = (type === "star" ? "🔥" : "Похвалить");
            } else {
                button.innerText = "Ошибка!"
                console.error(response)
            }
        },
        onerror: (error) => {
            console.error(error);
        }
    });
}
const getSubmissions = () => {
    return JSON.parse(localStorage.getItem("c_status") ?? "[]");
};
const isSubAccepted = (subId) => {
    return getSubmissions().includes(subId)
}

const acceptSubmission = (subId, button) => {
    const type = isSubAccepted(subId) ? "star" : "accept";
    acceptSheetSubmission(subId, button, type);
}

const showed_codes = {};

function createSubShowButton(subId, lang) {
    const button = document.createElement("button");
    button.className = "submission-show";
    button.style.marginTop = "4px";
    button.style.marginBottom = "4px";
    button.style.backgroundColor = "#176F95";
    button.style.border = "1px solid #176F95";
    button.style.borderRadius = "8px"
    button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
    button.style.color = "#fff"
    button.style.cursor = "pointer"
    button.style.padding = "10px"
    button.style.width = "100%";
    button.innerText = "Показать " + lang;
    button.onclick = (_) => showButtonClick(subId, button);
    return button;
}

function patchCodeSection(subId) {
    const patchLine = (i, line) => {
        line.addEventListener('click', () => {
            if(window.getSelection().toString() != "")
                return;
            const text = $("[data-submission-id=" + subId + "] textarea")
            text.val((text.val().length === 0 ? "" : text.val() + "\n") + "Строка " + (i + 1) + ": ")
            var x = window.scrollX, y = window.scrollY;
            text.focus();
            window.scrollTo(x, y);
        });
        return line;
    };

    let pretty_code = $('[data-submission-id=' + subId + '] .program-source li');
    const code_lines_count = pretty_code.length
    pretty_code.each((i, line) => patchLine(i, line))
    pretty_code = pretty_code.parent().parent();
    pretty_code.before((_) => {
        const lines = document.createElement("pre");
        lines.style.width = '4%';
        lines.style.padding = "0.5em";
        lines.style.display = 'inline-block';
        const lineNs = [...Array(code_lines_count).keys()].map((i) => {
            const line = document.createElement("span");
            line.style.color = 'rgb(153, 153, 153)';
            line.innerText = "[" + (i + 1) + "]";
            line.style.display = "block";
            line.style.textAlign = "right";
            line.style.userSelect = "none";
            line.style.cursor = "pointer";
            return patchLine(i, line);
        })
        lines.append(...lineNs)
        return lines
    })
    pretty_code.css({'display': 'inline-block', 'width': '90%'})
}

function showButtonClick(subId, button) {
    if (showed_codes[subId] != null) {
        if (showed_codes[subId].showed == true) {
            $(showed_codes[subId].commentSection).hide();
            $(showed_codes[subId].codeSection).hide();
            button.innerText = "Показать код";
            showed_codes[subId].showed = false;
        } else if (showed_codes[subId].showed == false) {
            $(showed_codes[subId].commentSection).show();
            $(showed_codes[subId].codeSection).show();
            button.innerText = "Скрыть код";
            showed_codes[subId].showed = true;
        }
    } else {
        button.innerText = showed_codes[subId] = "Загружаю код..."
        const requestUrl = getContestUrl() + 'submission/' + subId;
        console.log(requestUrl);
        $.get(requestUrl, function (html) {
            const codeHtml = $(html).find(".SubmissionDetailsFrameRoundBox-" + subId).html()

            if (codeHtml == undefined) {
                button.innerText = "Ошибка!";
                //location.reload();
                return;
            }

            const commentSection = createCommentSection(subId)
            const subCodeSection = createSubCodeSection(subId, codeHtml);

            const subRow = getSubRow(subId);
            subRow.after(commentSection, subCodeSection)

            prettyPrint(subId);

            patchCodeSection(subId);

            showed_codes[subId] = {
                "showed": true,
                "showButton": button,
                "commentSection": commentSection,
                "codeSection": subCodeSection
            }
            button.innerText = "Скрыть код"
        });
    }
}

function createSubCodeSection(subId, codeHtml) {
    const trSubCode = document.createElement("tr");
    trSubCode.setAttribute('data-submission-id', subId);
    const tdSubCode = document.createElement("td");
    tdSubCode.setAttribute('colspan', '8');
    tdSubCode.innerHTML = codeHtml;
    tdSubCode.style.textAlign = "start"
    trSubCode.append(tdSubCode);
    return trSubCode;
}

const createCommentSection = (subId) => {
    const subAcButton = getSubAcButton(subId)[0];
    const isAccepted = isSubAccepted(subId);
    const commentTextfield = createCommentTextfield()
    const commentAcButton = commentSendButtonTemplate(subId, (isAccepted ? "Похвалить" : "Принять") + " с комментарием", (isAccepted ? "#81D718" : "#13aa52"), (subId, button) => {
        const text = $(commentTextfield).val();
        if (text.length === 0) {
            button.innerText = "Принимаю...";
            acceptSubmission(subId, subAcButton);
        } else {
            button.innerText = "Отправляю...";
            $.post(getGroupUrl() + 'data/newAnnouncement', {
                contestId: getContestId(),
                englishText: "",
                russianText: text,
                submittedProblemIndex: getProblemIndex(subId),
                targetUserHandle: getHandle(subId),
                announceInPairContest: true,
            }, () => {
                acceptSubmission(subId, subAcButton);
            })
        }
    })
    const commentRjButton = commentSendButtonTemplate(subId, "Отклонить с комментарием", "#EC431A", (subId, button) => {
        const text = $(commentTextfield).val();
        if (text.length > 0) {
            button.innerText = "Отклоняю...";
            $.post(getGroupUrl() + 'data/newAnnouncement', {
                contestId: getContestId(),
                englishText: "",
                russianText: text,
                submittedProblemIndex: getProblemIndex(subId),
                targetUserHandle: getHandle(subId),
                announceInPairContest: true,
            }, () => {
                rejectSub(subId);
                if (showed_codes[subId] != null) {
                    $(showed_codes[subId]["codeSection"]).hide();
                }
                button.innerText = "Отклонено";
            })
        }
    });

    commentTextfield.addEventListener("keyup", (event) => {
        event.preventDefault();
        if (event.keyCode === 13) {
            commentRjButton.click();
        }
    });

    const trSection = document.createElement("tr");
    trSection.setAttribute('data-submission-id', subId);
    const tdSection = document.createElement("td");
    tdSection.setAttribute('colspan', '8');
    const tdSectionTitle = document.createElement("div");
    tdSectionTitle.style.textAlign = "left";
    tdSectionTitle.className = "caption titled"
    tdSectionTitle.innerText = "→ Комментарий"
    tdSection.append(tdSectionTitle, commentTextfield, commentAcButton, commentRjButton);
    trSection.append(tdSection)
    return trSection;
}

function createCommentTextfield() {
    const textField = document.createElement("textarea");
    textField.name = "russianText"
    textField.className = "bottom-space-small monospaced"
    textField.style.width = "80rem";
    textField.style.height = "5rem";
    textField.style.margin = "4px";
    return textField;
}

const commentSendButtonTemplate = (subId, text, color, action) => {
    const button = document.createElement("button");
    button.className = SUB_COMMENT_SEND_BUTTON_CLASS;
    button.style.margin = "4px";
    button.style.width = "40%";
    button.style.backgroundColor = color;
    button.style.border = "1px solid " + color;
    button.style.borderRadius = "8px"
    button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
    button.style.color = "#fff"
    button.style.cursor = "pointer"
    button.style.padding = "8px 0.5em"
    button.innerText = text;
    button.onclick = () => action(subId, button);
    return button
}

const acButtonTemplate = (subId, action, text) => {
    const acButton = document.createElement("button");
    acButton.className = SUB_AC_BUTTON_CLASS;
    const color = (isSubAccepted(subId) ? "#81D718" : "#13aa52");
    acButton.style.backgroundColor = color;
    acButton.style.border = "1px solid " + color;
    acButton.style.borderRadius = "8px"
    acButton.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
    acButton.style.color = "#fff"
    acButton.style.cursor = "pointer"
    acButton.style.padding = "8px 0.5em"
    acButton.style.margin = "5px 5px 0 0";
    acButton.style.width = "59%";
    acButton.innerText = text !== undefined ? text : (isSubAccepted(subId) ? "Похвалить" : "AC");
    acButton.onclick = (_) => action(subId, acButton);
    return acButton;
}

const createAcButton = (template, subId, ...args) => {
    return template(subId, (subId, button) => {
        button.innerText = "Подтверждаю...";
        button.style.borderColor = "gray";
        button.style.backgroundColor = "gray";
        acceptSubmission(subId, button);
    }, ...args);
}

const createRjButton = (subId, text, action) => {
    const rjButton = document.createElement("button");
    rjButton.className = SUB_RJ_BUTTON_CLASS;
    rjButton.style.width = "38%";
    rjButton.style.backgroundColor = "#EC431A";
    rjButton.style.border = "1px solid #EC431A"
    rjButton.style.borderRadius = "8px"
    rjButton.style.padding = "8px 0.5em"
    rjButton.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
    rjButton.style.color = "#fff"
    rjButton.style.cursor = "pointer"
    rjButton.innerText = text;
    rjButton.onclick = (_) => action(subId, rjButton);
    return rjButton
}

const createRgButton = (subId) => {
    const button = document.createElement("button");
    button.className = SUB_RG_BUTTON_CLASS;
    button.style.margin = "5px 0";
    button.style.padding = "4px"
    button.style.width = "100%";
    button.style.backgroundColor = "#176F95";
    button.style.border = "1px solid #176F95"
    button.style.borderRadius = "15px"
    button.style.boxShadow = "rgba(0, 0, 0, .1) 0 2px 4px 0"
    button.style.color = "#fff"
    button.style.cursor = "pointer"
    button.innerText = "Перетестировать";
    button.onclick = (_) => {
        const requestUrl = getContestUrl() + 'submission/' + subId
        const data = {action: "rejudge", submissionId: subId}
        $.post(requestUrl, data, (_) => location.reload());
        button.innerText = "Тестирую...";
    };
    return button;
}

const rejectSub = (subId) => {
    const subRjButton = getSubRjButton(subId);
    subRjButton.innerText = "Отклоняю...";
    const subAcButton = getSubAcButton(subId);
    const commentSubAcButton = getCommentSubAcButton(subId);
    const requestUrl = getContestUrl() + 'submission/' + subId
    const data = {action: "reject", submissionId: subId}
    $.post(requestUrl, data, function (_) {
        $("[submissionid=" + subId + "] .verdict-accepted").remove()
        subAcButton.remove()
        subRjButton.remove()
        commentSubAcButton.remove();
    })
}

const patchSubmissions = () => {
    const subsId = getSubsId();
    const languages = subsId.parent().prev()

    languages.append((i, e) => {
        const subId = Number($(subsId[i])[0].getAttribute('submissionid'))
        const language = e.split('(')[0]
        $(languages[i]).empty()
        return createSubShowButton(subId, language);
    })
}

const patchCorrectSubmissions = () => {
    const correctSubs = getCorrectSubs();
    correctSubs.parent().append((i) => {
        const subId = Number($(correctSubs[i]).attr('submissionid'))

        const acButton = createAcButton(acButtonTemplate, subId)
        const rgButton = createRgButton(subId);
        const rjButton = createRjButton(subId, "RJ", (subId, _) => {
            rejectSub(subId);
        });

        return [acButton, rjButton, rgButton]
    })
}

const patchContestSidebar = () => {
    const contestsSidebar = $(".GroupContestsSidebarFrame ul a")
    contestsSidebar.before((i) => {
        const contestHref = $(contestsSidebar[i]).attr('href');
        return document.createTextNode(wrap(getLast(contestHref)));
    });
}

const patchSubmission = () => {
    const buttons = getSubButtons()
    if (buttons.length > 0) {
        const subId = Number(getLast(location.pathname));
        const acButton = createAcButton(acButtonTemplate, subId);
        buttons[0].before(acButton);
    }
}

const patchFilterBox = () => {
    const filterBox = getFilterBox();
    const sidebar = getSideBar();
    filterBox.detach().prependTo(sidebar);
    const filterBoxPart = filterBox.find(".status-filter-form-part")[0];

    const correctSubsId = getCorrectSubs().map((i, e) => Number($(e).attr("submissionid"))).toArray();
    const filter = (checkbox) => {
        localStorage.setItem("filterPendingSubs", checkbox.checked);
        const filtered = correctSubsId.filter(subId => {
            console.log(subId + " " + !isSubAccepted(subId))
            return !isSubAccepted(subId)
        });
        console.log(filtered)
        getAllSubmissionsRow().each((i, e) => {
            if (checkbox.checked) {
                if (!filtered.includes(Number($(e).attr('data-submission-id')))) {
                    $(e).hide();
                }
            } else {
                $(e).show()
            }
        });
    };

    const template = createFilterPendingCheckboxTemplate(filter);
    const label = template[0]
    const checkbox = template[1]
    checkbox.checked = ('true' === localStorage.getItem("filterPendingSubs") ?? false);
    filter(checkbox);
    filterBoxPart.before(label);
}

function createFilterPendingCheckboxTemplate(action) {
    const label = document.createElement("label");
    const checkbox = document.createElement("input");
    checkbox.type = "checkbox";
    checkbox.onclick = (_) => action(checkbox);
    const title = document.createElement("span");
    title.style.padding = "5px";
    title.className = "smaller";
    title.innerText = "Только непроверенные посылки";
    label.append(checkbox, title);
    return [label, checkbox];
}


(function () {
    getSheetSubmissions();

    try {
        patchContestSidebar();
    } catch (e) {
        console.error(e);
    }

    try {
        patchFilterBox();
    } catch (e) {
        console.error(e);
    }

    try {
        patchCorrectSubmissions();
    } catch (e) {
        console.error(e);
    }

    try {
        patchSubmissions();
    } catch (e) {
        console.error(e);
    }

    try {
        patchSubmission();
    } catch (e) {
        console.error(e);
    }
})();