Объединение сохранения баллов и комментария

Объединяет функционал сохранения баллов и комментария в одну кнопку

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

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Объединение сохранения баллов и комментария
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Объединяет функционал сохранения баллов и комментария в одну кнопку
// @author       wryyshee
// @match        https://edu.mipt.ru/adm/testing/*/result/*
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Ждем загрузки страницы
    setTimeout(() => {
        cleanupPage();
    }, 1000);

    function cleanupPage() {
        removeOldButtons();
        removeFinalScoreField();
        moveDeleteButton();
        addCombinedButton();
    }

    function addCombinedButton() {
        // Находим контейнер формы
        const form = document.querySelector('form.form-horizontal');
        if (!form) return;

        // Находим последний .form-group.row перед комментарием
        const formGroups = document.querySelectorAll('.form-group.row');
        if (formGroups.length < 3) return;

        // Берем предпоследний .form-group.row (где были кнопки)
        const buttonGroup = formGroups[formGroups.length - 2];

        // Создаем новую кнопку
        const combinedButton = document.createElement('button');
        combinedButton.type = 'button';
        combinedButton.className = 'btn btn-success';
        combinedButton.style.marginRight = '10px';
        combinedButton.innerHTML = '<i class="fa fa-save"></i> Записать результат';
        combinedButton.onclick = handleCombinedSave;

        // Добавляем кнопку в новый контейнер
        const colDiv = document.createElement('div');
        colDiv.className = 'col-sm-6 text-left';
        colDiv.appendChild(combinedButton);

        // Добавляем кнопку в группу
        buttonGroup.appendChild(colDiv);
    }

    function removeOldButtons() {
        // Удаляем старые кнопки Сохранить баллы и Добавить комментарий
        document.querySelectorAll('button[type="submit"]').forEach(button => {
            if (button.name === 'updateBalli' || button.name === 'addComment') {
                const parentCol = button.closest('div[class*="col-sm"]');
                if (parentCol) {
                    parentCol.remove();
                }
            }
        });
    }

    function removeFinalScoreField() {
        // Находим все строки формы
        const formRows = document.querySelectorAll('.form-group.row');

        // Ищем строку с итоговой оценкой (там где есть label с текстом "ИТОГОВАЯ ОЦЕНКА")
        formRows.forEach(row => {
            const label = row.querySelector('label.control-label');
            if (label && label.textContent.includes('ИТОГОВАЯ')) {
                // Удаляем всю строку
                row.remove();
                return;
            }
        });
    }

    function moveDeleteButton() {
        // Находим кнопку удаления тестирования
        const deleteButton = document.querySelector('button[name="deleteTestirovanie"]');
        if (!deleteButton) return;

        // Находим её текущий контейнер
        const deleteButtonCol = deleteButton.closest('div[class*="col-sm"]');
        if (!deleteButtonCol) return;

        // Удаляем из текущего места
        deleteButtonCol.remove();

        // Находим наш новый контейнер с кнопкой "Записать результат"
        setTimeout(() => {
            const buttonGroup = document.querySelectorAll('.form-group.row')[document.querySelectorAll('.form-group.row').length - 2];
            if (!buttonGroup) return;

            // Создаем контейнер для кнопки удаления
            const deleteCol = document.createElement('div');
            deleteCol.className = 'col-sm-6 text-right';

            // Меняем стиль кнопки удаления
            deleteButton.className = 'btn btn-danger';
            deleteButton.style.marginTop = '0';

            // Добавляем кнопку удаления в контейнер
            deleteCol.appendChild(deleteButton);

            // Добавляем контейнер в ту же строку
            buttonGroup.appendChild(deleteCol);
        }, 100);
    }

    function handleCombinedSave() {
        const button = this;
        const originalText = button.innerHTML;
        button.disabled = true;
        button.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Сохранение...';

        // Получаем CSRF токен
        const csrfToken = getCsrfToken();

        // Выполняем последовательно все сохранения
        saveScores(csrfToken)
            .then(() => addComment(csrfToken))
            .then(() => {
            // Обновляем страницу после успешного сохранения
            setTimeout(() => location.reload(), 500);
        })
            .catch(() => {
            // В случае ошибки восстанавливаем кнопку
            button.disabled = false;
            button.innerHTML = originalText;
            alert('Ошибка сохранения. Попробуйте еще раз.');
        });
    }

    function saveScores(csrfToken) {
        return new Promise((resolve, reject) => {
            const scoresData = new FormData();

            const ballInputs = document.querySelectorAll('input.countBallPrepod');
            ballInputs.forEach(input => {
                scoresData.append(input.name, input.value || '0');
            });

            scoresData.append('updateBalli', '1');

            if (csrfToken) {
                scoresData.append('csrf_token', csrfToken);
            }

            GM_xmlhttpRequest({
                method: 'POST',
                url: window.location.href,
                data: serializeFormData(scoresData),
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                onload: function(response) {
                    if (response.status === 200 || response.status === 302) {
                        resolve();
                    } else {
                        reject();
                    }
                },
                onerror: reject
            });
        });
    }

    function addComment(csrfToken) {
        return new Promise((resolve, reject) => {
            const commentData = new FormData();

            const commentTextarea = document.querySelector('textarea[name="commentDliaStudenta"]');
            if (commentTextarea) {
                commentData.append('commentDliaStudenta', commentTextarea.value || '');
            }

            commentData.append('addComment', '1');

            if (csrfToken) {
                commentData.append('csrf_token', csrfToken);
            }

            GM_xmlhttpRequest({
                method: 'POST',
                url: window.location.href,
                data: serializeFormData(commentData),
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                onload: function(response) {
                    if (response.status === 200 || response.status === 302) {
                        resolve();
                    } else {
                        reject();
                    }
                },
                onerror: reject
            });
        });
    }

    function getCsrfToken() {
        const tokenInput = document.querySelector('input[name="csrf_token"], input[name="_token"]');
        return tokenInput ? tokenInput.value : null;
    }

    function serializeFormData(formData) {
        const pairs = [];
        for (const [key, value] of formData.entries()) {
            pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
        }
        return pairs.join('&');
    }
})();