您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Дополнительные инструменты для NetSchool / NetCity (Сетевой Город. Образование)
当前为
// ==UserScript== // @name NetSchool Tweaks // @namespace https://greasyfork.org/users/843419 // @description Дополнительные инструменты для NetSchool / NetCity (Сетевой Город. Образование) // @version 1.0.4 // @author Zgoly // @match *://*/* // @icon https://www.google.com/s2/favicons?sz=64&domain=ir-tech.ru // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @license MIT // ==/UserScript== try { console.log(language.Generic.Calendar.kTitle1, 'найден, NetSchool Tweaks активен') } catch { return } let autoLogin = GM_getValue('autoLogin', false) let loginName = GM_getValue('loginName', 'Пользователь') let password = GM_getValue('password', '12345678') let schoolId = GM_getValue('schoolId', '') let autoSkip = GM_getValue('autoSkip', true) function waitForElement(selector) { return new Promise(resolve => { if (document.querySelector(selector)) return resolve(document.querySelector(selector)) const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect() resolve(document.querySelector(selector)) } }) observer.observe(document.body, { childList: true, subtree: true }) }) } if (autoLogin && window.location.pathname.startsWith('/authorize')) { function runAngularAction() { try { angular.element(document.body).scope().$$childTail.$ctrl.loginStrategiesService.loginWithLoginPassCheck(loginName, password, schoolId, null, { 'idpBindUser': 1 }) } catch { requestAnimationFrame(runAngularAction) } } runAngularAction() } waitForElement('ns-modal').then((element) => { if (autoSkip && element.getAttribute('header') == language.Generic.Login.kTitleSecurityWarning) { element.querySelector('button').click() console.log('Security warning modal skipped') } }) function nstSwitch(parentElement) { let label = document.createElement('label') label.classList.add('nst-switch') let input = document.createElement('input') input.type = 'checkbox' input.classList.add('nst-hide') let div = document.createElement('div') label.append(input) label.append(div) parentElement.append(label) return input } function nstModal(headlineText, contentHTML, showSaveButton = true) { return new Promise((resolve, reject) => { let dialog = document.createElement('dialog') dialog.classList.add('nst-dialog') let dialogWrapper = document.createElement('div') dialogWrapper.classList.add('nst-dialog-wrapper') dialog.append(dialogWrapper) let headline = document.createElement('p') headline.classList.add('nst-headline') headline.textContent = headlineText dialogWrapper.append(headline) let dialogAutofocus = document.createElement('input') dialogAutofocus.autofocus = 'autofocus' dialogAutofocus.style.display = 'none' dialogWrapper.append(dialogAutofocus) let content = document.createElement('div') content.classList.add('nst-content') content.append(contentHTML) dialogWrapper.append(content) let actions = document.createElement('div') actions.classList.add('nst-actions') let closeButton = document.createElement('button') closeButton.classList.add('nst-close') closeButton.textContent = 'Закрыть' closeButton.addEventListener('click', () => closeDialog(false)) actions.append(closeButton) if (showSaveButton) { let saveButton = document.createElement('button') saveButton.classList.add('nst-save') saveButton.textContent = 'Сохранить' saveButton.addEventListener('click', () => closeDialog(true)) actions.append(saveButton) } dialogWrapper.append(actions) document.body.append(dialog) dialog.showModal() // Убираем фокус с поля ввода document.activeElement.blur() document.body.classList.add('nst-no-scroll') function closeDialog(result = false) { dialog.classList.add('nst-hide-dialog') setTimeout(() => { dialog.remove() if (document.getElementsByTagName('dialog').length < 1) document.body.classList.remove('nst-no-scroll') }, 500) resolve(result) } contentHTML.closeDialog = closeDialog dialog.addEventListener('click', (event) => { if (event.target === dialog) { closeDialog(false) } }) dialog.addEventListener('close', () => closeDialog()) dialog.addEventListener('error', reject) }) } let settings = document.createElement('li') let settingsLink = document.createElement('a') settings.append(settingsLink) let settingsBody = document.createElement('span') settingsBody.classList.add('cb-settings') settingsLink.append(settingsBody) let settingsIcon = document.createElement('i') settingsIcon.classList.add('icon-gear', 'nst-settings-icon') settingsBody.append(settingsIcon) settingsBody.addEventListener('click', () => { let div = document.createElement('div') let table = document.createElement('table') // Переключатель авто входа let autoLoginRow = document.createElement('tr') let autoLoginLabelCell = document.createElement('td') let autoLoginLabelTitle = document.createElement('div') autoLoginLabelTitle.classList.add('nst-label-title') autoLoginLabelTitle.textContent = 'Авто вход' autoLoginLabelCell.append(autoLoginLabelTitle) let autoLoginLabelDescription = document.createElement('div') autoLoginLabelDescription.classList.add('nst-label-description') autoLoginLabelDescription.textContent = 'Авто вход по логину и паролю.' autoLoginLabelCell.append(autoLoginLabelDescription) let autoLoginInputCell = document.createElement('td') let autoLoginInput = nstSwitch(autoLoginInputCell) autoLoginInput.checked = autoLogin autoLoginRow.append(autoLoginLabelCell) autoLoginRow.append(autoLoginInputCell) table.append(autoLoginRow) // Поле логина let loginNameRow = document.createElement('tr') let loginNameLabelCell = document.createElement('td') let loginNameLabelTitle = document.createElement('div') loginNameLabelTitle.classList.add('nst-label-title') loginNameLabelTitle.textContent = 'Логин' loginNameLabelCell.append(loginNameLabelTitle) let loginNameLabelDescription = document.createElement('div') loginNameLabelDescription.classList.add('nst-label-description') loginNameLabelDescription.textContent = 'Логин для входа.' loginNameLabelCell.append(loginNameLabelDescription) let loginNameInputCell = document.createElement('td') loginNameInputCell.classList.add('nst-flex') let loginNameInput = document.createElement('input') loginNameInput.type = 'text' loginNameInput.value = loginName loginNameInputCell.append(loginNameInput) loginNameRow.append(loginNameLabelCell) loginNameRow.append(loginNameInputCell) table.append(loginNameRow) // Поле пароля let passwordRow = document.createElement('tr') let passwordLabelCell = document.createElement('td') let passwordLabelTitle = document.createElement('div') passwordLabelTitle.classList.add('nst-label-title') passwordLabelTitle.textContent = 'Пароль' passwordLabelCell.append(passwordLabelTitle) let passwordLabelDescription = document.createElement('div') passwordLabelDescription.classList.add('nst-label-description') passwordLabelDescription.textContent = 'Пароль для входа.' passwordLabelCell.append(passwordLabelDescription) let passwordInputCell = document.createElement('td') passwordInputCell.classList.add('nst-password-input-cell', 'nst-flex') let passwordInput = document.createElement('input') passwordInput.type = 'password' passwordInput.classList.add('nst-password') passwordInput.value = password let passwordEye = document.createElement('div') passwordEye.classList.add('nst-password-eye') passwordEye.addEventListener('click', () => { passwordInput.type = passwordInput.type === 'password' ? 'text' : 'password' }) let passwordEyeInner = document.createElement('div') passwordEyeInner.classList.add('nst-password-eye-inner') let passwordEyeOuter = document.createElement('div') passwordEyeOuter.classList.add('nst-password-eye-outer') passwordEye.append(passwordEyeInner) passwordEye.append(passwordEyeOuter) passwordInputCell.append(passwordInput) passwordInputCell.append(passwordEye) passwordRow.append(passwordLabelCell) passwordRow.append(passwordInputCell) table.append(passwordRow) // Поле ID школы let schoolIdRow = document.createElement('tr') let schoolIdLabelCell = document.createElement('td') let schoolIdLabelTitle = document.createElement('div') schoolIdLabelTitle.classList.add('nst-label-title') schoolIdLabelTitle.textContent = 'ID школы' schoolIdLabelCell.append(schoolIdLabelTitle) let schoolIdLabelDescription = document.createElement('div') schoolIdLabelDescription.classList.add('nst-label-description') schoolIdLabelDescription.textContent = 'ID школы для входа. Оставьте пустым, если не знаете.' schoolIdLabelCell.append(schoolIdLabelDescription) let schoolIdInputCell = document.createElement('td') schoolIdInputCell.classList.add('nst-flex') let schoolIdInput = document.createElement('input') schoolIdInput.type = 'text' schoolIdInput.value = schoolId schoolIdInput.placeholder = schoolId schoolIdInputCell.append(schoolIdInput) schoolIdRow.append(schoolIdLabelCell) schoolIdRow.append(schoolIdInputCell) table.append(schoolIdRow) // Переключатель авто пропуска let autoSkipRow = document.createElement('tr') let autoSkipLabelCell = document.createElement('td') let autoSkipLabelTitle = document.createElement('div') autoSkipLabelTitle.classList.add('nst-label-title') autoSkipLabelTitle.textContent = 'Авто пропуск' autoSkipLabelCell.append(autoSkipLabelTitle) let autoSkipLabelDescription = document.createElement('div') autoSkipLabelDescription.classList.add('nst-label-description') autoSkipLabelDescription.textContent = 'Авто пропуск навязчивых уведомлений.' autoSkipLabelCell.append(autoSkipLabelDescription) let autoSkipInputCell = document.createElement('td') let autoSkipInput = nstSwitch(autoSkipInputCell) autoSkipInput.checked = autoSkip autoSkipRow.append(autoSkipLabelCell) autoSkipRow.append(autoSkipInputCell) table.append(autoSkipRow) function toggleFields() { let fields = [loginNameInput, passwordInput, schoolIdInput] fields.forEach(field => { field.disabled = !autoLoginInput.checked }) } toggleFields() autoLoginInput.addEventListener('change', toggleFields) div.append(table) // Сохранение настроек nstModal('Настройки', div).then(save => { if (save) { GM_setValue('autoLogin', autoLoginInput.checked) autoLogin = autoLoginInput.checked GM_setValue('loginName', loginNameInput.value) loginName = loginNameInput.value GM_setValue('password', passwordInput.value) password = passwordInput.value GM_setValue('schoolId', schoolIdInput.value == '' ? appContext.schoolId : schoolIdInput.value) schoolId = schoolIdInput.value == '' ? appContext.schoolId : schoolIdInput.value GM_setValue('autoSkip', autoSkipInput.checked) autoSkip = autoSkipInput.checked } }) let previewMarksWrapper = document.createElement('div') previewMarksWrapper.classList.add('preview-marks-wrapper') div.append(previewMarksWrapper) let at = appContext.at let weekStart = appContext.weekStart let weekEnd = appContext.weekEnd // Начало учебного года let startDateInput = document.createElement('input') startDateInput.type = 'date' startDateInput.value = weekStart previewMarksWrapper.append(startDateInput) // Конец учебного года let endDateInput = document.createElement('input') endDateInput.type = 'date' endDateInput.value = weekEnd previewMarksWrapper.append(endDateInput) if (weekStart == undefined || weekEnd == undefined) { fetch('/webapi/v2/reports/studenttotal', { 'headers': { 'at': at } }).then((response) => { return response.json() }).then((data) => { weekStart = data.filterSources[3].defaultRange.start.substring(0, 10) startDateInput.value = weekStart weekEnd = data.filterSources[3].defaultRange.end.substring(0, 10) endDateInput.value = weekEnd }) } // Кнопка предпросмотра оценок let previewMarksButton = document.createElement('button') previewMarksButton.innerText = 'Предпросмотр оценок' previewMarksButton.addEventListener('click', () => { let marksTableWrapper = document.createElement('div') marksTableWrapper.classList.add('nst-marks-table-wrapper') let contentDiv = document.createElement('div') contentDiv.classList.add('nst-content') contentDiv.append(marksTableWrapper) fetch('/webapi/student/diary/init', { 'headers': { 'at': at } }).then((response) => { return response.json() }).then((data) => { let studentId = data.students[0].studentId let yearId = appContext.yearId let startDate = startDateInput.value let endDate = endDateInput.value // Запрос дневика fetch(`/webapi/student/diary?studentId=${studentId}&weekStart=${startDate}&weekEnd=${endDate}&withLaAssigns=true&yearId=${yearId}`, { 'headers': { 'at': at } }).then((response) => { return response.json() }).then((data) => { // Повторный запрос дневника (с текущей датой для отображения правильной недели, игнорируется) let currentDate = date2strf(new Date(), 'yyyy\x01mm\x01dd\x01.') fetch(`/webapi/student/diary?studentId=${studentId}&weekStart=${currentDate}&weekEnd=${currentDate}&withLaAssigns=true&yearId=${yearId}`, { 'headers': { 'at': at } }) let marksTable = document.createElement('table') marksTableWrapper.append(marksTable) let tableControlsDiv = document.createElement('div') tableControlsDiv.classList.add('nst-controls') contentDiv.append(tableControlsDiv) let selectAllButton = document.createElement('button') selectAllButton.innerText = 'Выбрать все' selectAllButton.addEventListener('click', () => { for (let row of marksTable.rows) { row.classList.add('nst-row-selected') } updateButtons() }) tableControlsDiv.append(selectAllButton) let deselectAllButton = document.createElement('button') deselectAllButton.innerText = 'Отменить выбор' deselectAllButton.addEventListener('click', () => { for (let row of marksTable.rows) { row.classList.remove('nst-row-selected') } updateButtons() }) tableControlsDiv.append(deselectAllButton) let addRowButton = document.createElement('button') addRowButton.innerText = 'Cоздать' addRowButton.addEventListener('click', () => { let selectedRows = marksTable.querySelectorAll('.nst-row-selected') let newRow = createRow() if (selectedRows.length > 0) { selectedRows[selectedRows.length - 1].after(newRow) selectedRows.forEach(row => row.classList.remove('nst-row-selected')) } else { marksTable.prepend(newRow) } cookRow(newRow) newRow.classList.add('nst-row-selected') newRow.scrollIntoView({behavior: "smooth"}) updateButtons() }) tableControlsDiv.append(addRowButton) let cloneRowButton = document.createElement('button') cloneRowButton.innerText = 'Клонировать' cloneRowButton.addEventListener('click', () => { let selectedRows = marksTable.querySelectorAll('.nst-row-selected') selectedRows.forEach(row => { let clonedRow = row.cloneNode(true) row.after(clonedRow) cookRow(clonedRow) let marksCell = clonedRow.querySelector('.nst-marks-cell') Array.from(marksCell.children).forEach(markDiv => { cookMark(markDiv) }) row.classList.remove('nst-row-selected') }) updateButtons() }) tableControlsDiv.append(cloneRowButton) let removeRowButton = document.createElement('button') removeRowButton.innerText = 'Удалить' removeRowButton.addEventListener('click', () => { let selectedRows = marksTable.querySelectorAll('.nst-row-selected') selectedRows.forEach(row => row.remove()) updateButtons() }) tableControlsDiv.append(removeRowButton) let addMarkButton = document.createElement('button') addMarkButton.innerText = 'Добавить оценку' addMarkButton.addEventListener('click', () => { let selectedRows = marksTable.querySelectorAll('.nst-row-selected') if (selectedRows.length == 0) return let [templateTable, markInput, weightInput] = markModalTemplate(5, 20) nstModal('Добавление оценки', templateTable).then(save => { if (save) { selectedRows.forEach(row => { let mark = createMark(markInput.value, weightInput.value) row.querySelector('.nst-marks-cell').append(mark) cookMark(mark) highlightMark(mark) row.classList.remove('nst-row-selected') }) updateButtons() } }) }) tableControlsDiv.append(addMarkButton) function createMark(mark, weight, fullAssignment = null) { let markDiv = document.createElement('div') if (fullAssignment) markDiv.dataset.assignment = JSON.stringify(fullAssignment) markDiv.classList.add('nst-mark') let markValue = document.createElement('p') markValue.innerText = mark markValue.classList.add('nst-mark-value') markDiv.append(markValue) let weightValue = document.createElement('p') weightValue.innerText = weight weightValue.classList.add('nst-weight-value') markDiv.append(weightValue) return markDiv } function createRow(name = '') { let row = document.createElement('tr') let nameCell = document.createElement('td') let nameInput = document.createElement('input') nameInput.value = name nameInput.classList.add('nst-name-input') nameInput.placeholder = "Имя предмета" nameCell.append(nameInput) row.append(nameCell) let marksCell = document.createElement('td') marksCell.classList.add('nst-marks-cell') row.append(marksCell) let totalCell = document.createElement('td') totalCell.classList.add('nst-total-cell') row.append(totalCell) return row } function markModalTemplate(mark, weight) { let templateTable = document.createElement('table') let markRow = document.createElement('tr') templateTable.append(markRow) let markLabelCell = document.createElement('td') markRow.append(markLabelCell) let markLabelTitle = document.createElement('div') markLabelTitle.classList.add('nst-label-title') markLabelTitle.textContent = 'Оценка' markLabelCell.append(markLabelTitle) let markLabelDescription = document.createElement('div') markLabelDescription.classList.add('nst-label-description') markLabelDescription.textContent = 'Оценка.' markLabelCell.append(markLabelDescription) let markInputCell = document.createElement('td') markInputCell.classList.add('nst-flex') markRow.append(markInputCell) let markInput = document.createElement('input') markInput.readOnly = true markInput.value = mark markInputCell.append(markInput) let markSelectorsDiv = document.createElement('div') markSelectorsDiv.classList.add('nst-mark-selectors') markInputCell.append(markSelectorsDiv) let marks = ['5', '4', '3', '2', '•'] marks.forEach(mark => { let markButton = document.createElement('button') markButton.innerText = mark markButton.addEventListener('click', () => markInput.value = mark) markSelectorsDiv.append(markButton) }) let weightRow = document.createElement('tr') templateTable.append(weightRow) let weightLabelCell = document.createElement('td') weightRow.append(weightLabelCell) let weightLabelTitle = document.createElement('div') weightLabelTitle.classList.add('nst-label-title') weightLabelTitle.textContent = 'Вес' weightLabelCell.append(weightLabelTitle) let weightLabelDescription = document.createElement('div') weightLabelDescription.classList.add('nst-label-description') weightLabelDescription.textContent = 'Вес оценки.' weightLabelCell.append(weightLabelDescription) let weightInputCell = document.createElement('td') weightInputCell.classList.add('nst-flex') weightRow.append(weightInputCell) let weightInput = document.createElement('input') weightInput.type = 'number' weightInput.value = weight weightInputCell.append(weightInput) return [templateTable, markInput, weightInput] } function highlightMark(mark) { mark.classList.remove('nst-mark-highlight'); mark.offsetWidth; mark.classList.add('nst-mark-highlight'); } function cookMark(mark) { let markValue = mark.querySelector('.nst-mark-value') let weightValue = mark.querySelector('.nst-weight-value') mark.addEventListener('click', () => { let modalDiv = document.createElement('div') let [templateTable, markInput, weightInput] = markModalTemplate(markValue.innerText, weightValue.innerText) modalDiv.append(templateTable) let controlsDiv = document.createElement('div') controlsDiv.classList.add('nst-controls') modalDiv.append(controlsDiv) let cloneMarkButton = document.createElement('button') cloneMarkButton.innerText = 'Клонировать' controlsDiv.append(cloneMarkButton) cloneMarkButton.addEventListener('click', () => { modalDiv.closeDialog(true) let newMark = mark.cloneNode(true) mark.after(newMark) cookMark(newMark) highlightMark(newMark) }) let deleteMarkButton = document.createElement('button') deleteMarkButton.innerText = 'Удалить' controlsDiv.append(deleteMarkButton) deleteMarkButton.addEventListener('click', () => { mark.remove() modalDiv.closeDialog(false) }) if (mark.dataset.assignment) { let assignment = JSON.parse(mark.dataset.assignment) let restoreMarkButton = document.createElement('button') restoreMarkButton.innerText = 'Восстановить' controlsDiv.append(restoreMarkButton) restoreMarkButton.addEventListener('click', () => { markInput.value = assignment.mark weightInput.value = assignment.weight }) let assignmentMarkButton = document.createElement('button') assignmentMarkButton.innerText = 'Подробности' controlsDiv.append(assignmentMarkButton) assignmentMarkButton.addEventListener('click', () => { // TODO Доделать let assignmentTable = document.createElement('table') let translations = { 'id': 'ID задания', 'assignmentName': 'Тема задания', 'activityName': 'Имя деятельности', 'problemName': 'Название задачи', 'studentId': 'ID ученика', 'subjectGroup.id': 'ID предмета', 'subjectGroup.name': 'Название предмета', 'teachers.0.id': 'ID учителя', 'teachers.0.name': 'Имя учителя', 'productId': 'ID продукта', 'isDeleted': 'Удалено', 'weight': 'Вес', 'date': 'Дата', 'description': 'Описание', 'mark': 'Оценка', 'typeId': 'ID типа задания', 'type': 'Тип задания' } for (let key in assignment) { let translation = translations[key] || key let value = assignment[key] value = value === true ? "Да" : value === false ? "Нет" : value let assignmentRow = document.createElement('tr') assignmentTable.append(assignmentRow) let assignmentLabelCell = document.createElement('td') assignmentLabelCell.innerText = translation assignmentRow.append(assignmentLabelCell) let assignmentInputCell = document.createElement('td') assignmentInputCell.classList.add('nst-flex') assignmentRow.append(assignmentInputCell) let assignmentInput console.log(key, value, typeof value) if (key === 'date') { assignmentInput = document.createElement('input') assignmentInput.readOnly = true assignmentInput.type = 'date' assignmentInput.value = value } else if (typeof value === 'number') { assignmentInput = document.createElement('input') assignmentInput.readOnly = true assignmentInput.type = 'number' assignmentInput.value = value } else { assignmentInput = document.createElement('div') assignmentInput.innerText = value assignmentInput.classList.add('nst-area') } assignmentInputCell.append(assignmentInput) } nstModal('Подробности задания', assignmentTable, false) }) } nstModal('Редактирование оценки', modalDiv).then(save => { if (save) { markValue.innerText = markInput.value weightValue.innerText = weightInput.value highlightMark(mark) } }) }) } function updateButtons() { if (marksTable.querySelectorAll('.nst-row-selected').length > 0) { addMarkButton.disabled = false cloneRowButton.disabled = false removeRowButton.disabled = false } else { addMarkButton.disabled = true cloneRowButton.disabled = true removeRowButton.disabled = true } } updateButtons() function cookRow(row) { let marksCell = row.querySelector('.nst-marks-cell') let totalCell = row.querySelector('.nst-total-cell') // Изменение цвета оценки / балла function calculateTotalScore() { let markSum = 0 let weightSum = 0 Array.from(marksCell.children).forEach(markDiv => { let markValue = markDiv.querySelector('.nst-mark-value') let weightValue = markDiv.querySelector('.nst-weight-value') let mark = markValue.innerText.replaceAll('•', '2') let weight = Number(weightValue.innerText) markValue.classList.remove(...['nst-mark-excellent', 'nst-mark-good', 'nst-mark-average', 'nst-mark-bad']) markValue.classList.add(getMarkClass(mark)) markSum += mark * weight weightSum += weight }) totalCell.innerText = weightSum ? Number((markSum / weightSum).toFixed(2)) : 0 totalCell.classList.remove(...['nst-mark-excellent', 'nst-mark-good', 'nst-mark-average', 'nst-mark-bad']) totalCell.classList.add(getMarkClass(totalCell.innerText)) } function getMarkClass(mark) { return Number(mark) >= 4.60 ? 'nst-mark-excellent' : Number(mark) >= 3.60 ? 'nst-mark-good' : Number(mark) >= 2.60 ? 'nst-mark-average' : 'nst-mark-bad' } let observer = new MutationObserver(() => { calculateTotalScore() }) observer.observe(marksCell, { childList: true, subtree: true }) calculateTotalScore() // Выделение строки при нажатии на балл totalCell.addEventListener('click', () => { row.classList.toggle('nst-row-selected') updateButtons() }) } function flattenJson(json) { let result = {} function flatten(obj, prefix = '') { for (let key in obj) { if (typeof obj[key] === 'object' && obj[key] !== null) { flatten(obj[key], prefix + key + '.') } else { result[prefix + key] = obj[key] } } } flatten(json) return result } fetch('/webapi/grade/assignment/types').then((response) => { return response.json() }).then((types) => { for (let day of data.weekDays) { for (let lesson of day.lessons) { if (Array.isArray(lesson.assignments)) { for (let assignment of lesson.assignments) { if (assignment.mark) { fetch(`/webapi/student/diary/assigns/${assignment.id}`, { 'headers': { 'at': at } }).then((response) => { return response.json() }).then((fullAssignment) => { // Модификация данных в удобный формат fullAssignment.mark = assignment.mark.mark fullAssignment.studentId = assignment.mark.studentId fullAssignment.typeId = assignment.typeId fullAssignment.date = fullAssignment.date.substring(0, 10) let item = types.find(data => data.id == fullAssignment.typeId) fullAssignment.type = item.name fullAssignment = flattenJson(fullAssignment) for (let key in fullAssignment) { if (fullAssignment[key] == null) delete fullAssignment[key] } // Объявление / создание ряда let row = Array.from(marksTable.rows).find(r => r.querySelector('.nst-name-input').value == lesson.subjectName) if (!row) { row = createRow(lesson.subjectName) marksTable.append(row) cookRow(row) } // Добавление оценки let createdMark = createMark(fullAssignment.mark, fullAssignment.weight, fullAssignment) row.querySelector('.nst-marks-cell').append(createdMark) cookMark(createdMark) }) } } } } } }).catch(err => { console.error('Произошла ошибка: ', err) }) }) }) nstModal('Предпросмотр оценок', contentDiv, false) }) previewMarksWrapper.append(previewMarksButton) }) waitForElement('.top-right-menu').then((elm) => { elm.prepend(settings) }) GM_addStyle(` @import url('https://fonts.googleapis.com/css2?family=Nunito&display=swap'); :root { --nst-primary: #64a0c8; --nst-secondary: #415f78; --nst-tertiary: #374b5f; --nst-quaternary: #232a32; --nst-quinary: #1a1c1e; --nst-senary: #141618; --nst-text-primary: #c8e6ff; --nst-text-secondary: #aadcff; --nst-text-tertiary: #87afc8; } .nst-no-scroll { touch-action: none; overflow: hidden; } .nst-flex { display: flex; flex-direction: column; } .nst-flex input { flex: 1; } .nst-settings-icon { display: flex !important; justify-content: center; color: white; scale: 0.75; } .nst-dialog { border: none; outline: none; background: var(--nst-quinary); border-radius: 32px; box-shadow: rgba(0, 0, 0, 0.25) 0 0 25px; padding: 0; } .nst-dialog-wrapper { display: flex; flex-direction: column; padding: 24px; max-height: calc(100vh - 48px); max-width: calc(100vw - 48px); } .nst-content { flex: 1; display: flex; flex-direction: column; overflow: auto; } .nst-dialog .preview-marks-wrapper { display: flex; flex-wrap: wrap; justify-content: center; padding-top: 16px; gap: 8px; } .nst-dialog * { font-family: 'Nunito'; color: var(--nst-text-secondary); } .nst-dialog .nst-headline:first-child { margin-top: 0px; } .nst-dialog .nst-headline { color: var(--nst-text-primary); font-size: 1.5em; margin: 0px; margin-bottom: 16px; } .nst-dialog .nst-actions { margin-top: 16px; display: flex; gap: 8px; justify-content: flex-end; } .nst-dialog button { cursor: pointer; color: var(--button-color); border-radius: 28px; padding: 12px; margin: 0; border: none; outline: none; transition: 0.2s; background: var(--nst-quaternary); } .nst-dialog button:not([disabled]):hover { background: var(--nst-tertiary); } .nst-dialog button:not([disabled]):active { background: var(--nst-secondary); } .nst-dialog button[disabled] { opacity: 0.5; cursor: default; } .nst-dialog input { text-shadow: none; box-shadow: none; line-height: normal; border: 2px solid var(--nst-tertiary); color: var(--nst-text-secondary); border-radius: 16px; padding: 12px; background: var(--nst-quaternary); transition: 0.2s; } .nst-dialog input::-webkit-outer-spin-button, .nst-dialog input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } .nst-dialog input[disabled] { border: 2px solid var(--nst-tertiary); opacity: 0.5; } .nst-dialog input[disabled]:hover, .nst-dialog input[disabled]:focus, .nst-dialog input[disabled]:active { border: 2px solid transparent; border-radius: 16px; color: var(--nst-text-secondary); box-shadow: none; padding: 12px; } .nst-dialog input:hover { border: 2px solid transparent; color: var(--nst-text-secondary); box-shadow: none; } .nst-dialog input:focus, .nst-dialog input:active { border: 2px solid transparent; color: var(--nst-text-secondary); background: var(--nst-tertiary); box-shadow: none; } .nst-password { padding-right: 48px; } .nst-password ~ .nst-password-eye { transition: 0.2s; position: absolute; display: flex; justify-content: center; align-items: center; right: 8px; top: 8px; width: 48px; height: 48px; } .nst-password ~ .nst-password-eye:hover { opacity: 0.75; } .nst-password ~ .nst-password-eye:active { opacity: 0.5; } .nst-password ~ .nst-password-eye::after { transition: 0.2s; content: ""; position: absolute; width: 60%; height: 2px; background-color: currentColor; transform: rotate(45deg); border-radius: 2px; } .nst-password[type="password"] ~ .nst-password-eye::after { width: 0%; } .nst-password ~ .nst-password-eye .nst-password-eye-inner { width: 24px; height: 16px; border: 2px solid currentColor; border-radius: 50%; position: absolute; } .nst-password ~ .nst-password-eye .nst-password-eye-outer { width: 8px; height: 8px; border: 2px solid currentColor; border-radius: 50%; position: absolute; } .nst-dialog .nst-area { border-radius: 16px; background: var(--nst-quaternary); padding: 16px; width: 100%; box-sizing: border-box; } .nst-dialog .nst-switch { position: relative; display: inline-block; width: 3.5em; height: 2em; margin: 0; } .nst-dialog .nst-switch .nst-hide { opacity: 0; width: 0; height: 0; } .nst-dialog .nst-switch div { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: var(--nst-quaternary); border: 2px solid var(--nst-tertiary); border-radius: 24px; transition: .4s; } .nst-dialog .nst-switch div:before { position: absolute; content: ""; height: 1.2em; width: 1.2em; left: calc(0.4em - 2px); top: calc(0.4em - 2px); background: var(--nst-tertiary); border-radius: 50%; transition: .4s; } .nst-dialog .nst-switch input:checked + div { background: var(--nst-primary); border: 2px solid transparent; } .nst-dialog .nst-switch input:checked + div:before { transform: translateX(1.4em); background: rgba(0, 0, 0, 0.5); } .nst-dialog table { margin-left: auto; margin-right: auto; } .nst-dialog td { padding: 8px; position: relative; } .nst-total-cell { right: 0; position: sticky !important; cursor: pointer; background: var(--nst-quinary); transition: 0.1s; } tr.nst-row-selected .nst-total-cell:last-child { background: var(--nst-senary); } .nst-dialog .nst-marks-table-wrapper tr { transition: 0.2s; } .nst-dialog .nst-marks-table-wrapper tr.nst-row-selected { background: var(--nst-senary); } .nst-dialog .nst-marks-table-wrapper { overflow-y: auto; border-radius: 24px; } .nst-marks-cell { display: flex; } .nst-controls { display: flex; flex-wrap: wrap; justify-content: space-evenly; gap: 8px; padding-top: 8px; } .nst-controls button { flex-grow: 1; } .nst-dialog[open] { animation: nst-show-dialog 0.5s forwards; } .nst-dialog.nst-hide-dialog { animation: nst-hide-dialog 0.5s forwards; } @keyframes nst-show-dialog { from { opacity: 0; transform: scale(0.5); } to { opacity: 1; transform: scale(1); } } @keyframes nst-hide-dialog { to { opacity: 0; transform: scale(0.5); } } .nst-dialog::backdrop { background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(5px); animation: none; } .nst-dialog[open]::backdrop { animation: nst-show-opacity 0.5s forwards; } .nst-dialog.nst-hide-dialog::backdrop { animation: nst-hide-opacity 0.5s forwards; } @keyframes nst-show-opacity { from { opacity: 0; } to { opacity: 1; } } @keyframes nst-hide-opacity { to { opacity: 0; } } .nst-label-title { font-size: 18px; } .nst-label-description { font-size: 12px; color: var(--nst-text-tertiary); } .nst-mark { min-width: 24px; display: flex; flex-flow: column wrap; align-items: stretch; cursor: pointer; animation: nst-show-opacity 0.5s forwards; } .nst-mark-selectors { display: flex; gap: 8px; padding-top: 8px; justify-content: space-between; } .nst-mark p { text-align: center; margin: 0px; } .nst-mark p:first-child { font-size: large; } .nst-mark p:nth-child(2) { color: gray; font-size: x-small; } .nst-mark-highlight { animation: nst-mark-highlight 3s forwards; } @keyframes nst-mark-highlight { 0% { opacity: 1; } 16% { opacity: 0; } 32% { opacity: 1; } 48% { opacity: 0; } 64% { opacity: 1; } 80% { opacity: 0; } 96% { opacity: 1; } } .nst-mark-excellent { color: #96e400; } .nst-mark-good { color: #00c8ff; } .nst-mark-average { color: #f09600; } .nst-mark-bad { color: #ff3232; } `)