Объединяет функционал сохранения баллов и комментария в одну кнопку
// ==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('&');
}
})();