您需要先安装一个扩展,例如 篡改猴、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;
- }
- `)