mirror of
https://gitlab.com/MrFry/moodle-test-userscript
synced 2025-04-01 20:22:48 +02:00
2311 lines
78 KiB
JavaScript
2311 lines
78 KiB
JavaScript
/* ----------------------------------------------------------------------------
|
|
|
|
Online Moodle/Elearning/KMOOC test help
|
|
Greasyfork: <https://greasyfork.org/en/scripts/38999-moodle-elearning-kmooc-test-help>
|
|
GitLab: <https://gitlab.com/MrFry/moodle-test-userscript>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
------------------------------------------------------------------------- */
|
|
|
|
// ==UserScript==
|
|
// @name Moodle/Elearning/KMOOC test help
|
|
// @version 1.6.4.9
|
|
// @description Online Moodle/Elearning/KMOOC test help
|
|
// @author MrFry
|
|
// @match https://elearning.uni-obuda.hu/main/*
|
|
// @match https://elearning.uni-obuda.hu/kmooc/*
|
|
// @match https://mooc.unideb.hu/*
|
|
// @grant GM_getResourceText
|
|
// @grant GM_info
|
|
// @grant GM_getValue
|
|
// @grant GM_setValue
|
|
// @grant GM_deleteValue
|
|
// @grant GM_xmlhttpRequest
|
|
// @grant GM_openInTab
|
|
// @license GNU General Public License v3.0 or later
|
|
// @supportURL qmining.frylabs.net
|
|
// @contributionURL qmining.frylabs.net
|
|
// @namespace https://qmining.frylabs.net
|
|
// @updateURL https://qmining.frylabs.net/moodle-test-userscript/stable.user.js?up
|
|
// ==/UserScript==
|
|
//
|
|
// TODO:
|
|
// grabboxes test on quiz page
|
|
|
|
(function() { // eslint-disable-line
|
|
// GM functions, only to disable ESLINT errors
|
|
/* eslint-disable */
|
|
const a = Main
|
|
function getVal (name) { return GM_getValue(name) }
|
|
function setVal (name, val) { return GM_setValue(name, val) }
|
|
function delVal (name) { return GM_deleteValue(name) }
|
|
function openInTab (address, options) { GM_openInTab(address, options) }
|
|
function xmlhttpRequest (opts) { GM_xmlhttpRequest(opts) }
|
|
function info () { return GM_info }
|
|
/* eslint-enable */
|
|
|
|
var data // all data, which is in the resource txt
|
|
var addEventListener // add event listener function
|
|
const lastChangeLog = '' // TODO
|
|
const serverAdress = 'https://qmining.frylabs.net/'
|
|
// const serverAdress = 'http://localhost:8080/'
|
|
|
|
// forcing pages for testing. unless you test, do not set these to true!
|
|
// only one of these should be true for testing
|
|
const forceTestPage = false
|
|
const forceResultPage = false
|
|
const forceDefaultPage = false
|
|
const logElementGetting = false
|
|
const log = true
|
|
|
|
const motdShowCount = 3 /* Ammount of times to show motd */
|
|
var motd = ''
|
|
var lastestVersion = ''
|
|
|
|
const minResultMatchPercent = 99 /* Minimum ammount to consider that two questions match during saving */
|
|
|
|
// : question-classes {{{
|
|
const commonUselessAnswerParts = [
|
|
'A helyes válasz az ',
|
|
'A helyes válasz a ',
|
|
'A helyes válaszok: ',
|
|
'A helyes válaszok:',
|
|
'A helyes válasz: ',
|
|
'A helyes válasz:',
|
|
'The correct answer is:',
|
|
'\''
|
|
]
|
|
const commonUselessStringParts = [',', '\\.', ':', '!', '\\+']
|
|
const specialChars = [ '&', '\\+' ]
|
|
const lengthDiffMultiplier = 10 /* Percent minus for length difference */
|
|
const minMatchAmmount = 60 /* Minimum ammount to consider that two questions match during answering */
|
|
const notSameDataTypePenalty = 30 // substracted from match percent if 2 questions are not same type
|
|
|
|
const assert = (val) => {
|
|
if (!val) { throw new Error('Assertion failed') }
|
|
}
|
|
|
|
class StringUtils {
|
|
RemoveStuff (value, removableStrings, toReplace) {
|
|
removableStrings.forEach((x) => {
|
|
var regex = new RegExp(x, 'g')
|
|
value = value.replace(regex, toReplace || '')
|
|
})
|
|
return value
|
|
}
|
|
|
|
SimplifyQuery (q) {
|
|
assert(q)
|
|
|
|
var result = q.replace(/\n/g, ' ').replace(/\s/g, ' ')
|
|
return this.RemoveUnnecesarySpaces(result)
|
|
}
|
|
|
|
ShortenString (toShorten, ammount) {
|
|
assert(toShorten)
|
|
|
|
var result = ''
|
|
var i = 0
|
|
while (i < toShorten.length && i < ammount) {
|
|
result += toShorten[i]
|
|
i++
|
|
}
|
|
return result
|
|
}
|
|
|
|
ReplaceCharsWithSpace (val, char) {
|
|
assert(val)
|
|
assert(char)
|
|
|
|
var toremove = this.NormalizeSpaces(val)
|
|
|
|
var regex = new RegExp(char, 'g')
|
|
toremove = toremove.replace(regex, ' ')
|
|
|
|
return this.RemoveUnnecesarySpaces(toremove)
|
|
}
|
|
|
|
// removes whitespace from begining and and, and replaces multiple spaces with one space
|
|
RemoveUnnecesarySpaces (toremove) {
|
|
assert(toremove)
|
|
|
|
toremove = this.NormalizeSpaces(toremove)
|
|
while (toremove.includes(' ')) {
|
|
toremove = toremove.replace(/ {2}/g, ' ')
|
|
}
|
|
return toremove.trim()
|
|
}
|
|
|
|
// simplifies a string for easier comparison
|
|
SimplifyStringForComparison (value) {
|
|
assert(value)
|
|
|
|
value = this.RemoveUnnecesarySpaces(value).toLowerCase()
|
|
return this.RemoveStuff(value, commonUselessStringParts)
|
|
}
|
|
|
|
RemoveSpecialChars (value) {
|
|
assert(value)
|
|
|
|
return this.RemoveStuff(value, specialChars, ' ')
|
|
}
|
|
|
|
// if the value is empty, or whitespace
|
|
EmptyOrWhiteSpace (value) {
|
|
// replaces /n-s with "". then replaces spaces with "". if it equals "", then its empty, or only consists of white space
|
|
if (value === undefined) { return true }
|
|
return (value.replace(/\n/g, '').replace(/ /g, '').replace(/\s/g, ' ') === '')
|
|
}
|
|
|
|
// damn nonbreaking space
|
|
NormalizeSpaces (input) {
|
|
assert(input)
|
|
|
|
return input.replace(/\s/g, ' ')
|
|
}
|
|
|
|
CompareString (s1, s2) {
|
|
if (!s1 || !s2) {
|
|
return 0
|
|
}
|
|
|
|
s1 = this.SimplifyStringForComparison(s1).split(' ')
|
|
s2 = this.SimplifyStringForComparison(s2).split(' ')
|
|
var match = 0
|
|
for (var i = 0; i < s1.length; i++) {
|
|
if (s2.includes(s1[i])) { match++ }
|
|
}
|
|
var percent = Math.round(((match / s1.length) * 100).toFixed(2)) // matched words percent
|
|
var lengthDifference = Math.abs(s2.length - s1.length)
|
|
percent -= lengthDifference * lengthDiffMultiplier
|
|
if (percent < 0) { percent = 0 }
|
|
return percent
|
|
}
|
|
|
|
AnswerPreProcessor (value) {
|
|
assert(value)
|
|
|
|
return this.RemoveStuff(
|
|
value, commonUselessAnswerParts)
|
|
}
|
|
|
|
// 'a. pécsi sör' -> 'pécsi sör'
|
|
RemoveAnswerLetters (value) {
|
|
assert(value)
|
|
|
|
let s = value.split('. ')
|
|
if (s[0].length < 2 && s.length > 1) {
|
|
s.shift()
|
|
return s.join(' ')
|
|
} else {
|
|
return value
|
|
}
|
|
}
|
|
|
|
SimplifyQA (value, mods) {
|
|
if (!value) { return }
|
|
|
|
const reducer = (res, fn) => {
|
|
return fn(res)
|
|
}
|
|
|
|
return mods.reduce(reducer, value)
|
|
}
|
|
|
|
SimplifyAnswer (value) {
|
|
return this.SimplifyQA(
|
|
value,
|
|
[
|
|
this.RemoveSpecialChars.bind(this),
|
|
this.RemoveUnnecesarySpaces.bind(this),
|
|
this.AnswerPreProcessor.bind(this),
|
|
this.RemoveAnswerLetters.bind(this)
|
|
])
|
|
}
|
|
|
|
SimplifyQuestion (value) {
|
|
return this.SimplifyQA(
|
|
value,
|
|
[
|
|
this.RemoveSpecialChars.bind(this),
|
|
this.RemoveUnnecesarySpaces.bind(this)
|
|
])
|
|
}
|
|
|
|
SimplifyStack (stack) {
|
|
return this.SimplifyQuery(stack)
|
|
}
|
|
}
|
|
|
|
const SUtils = new StringUtils()
|
|
|
|
class Question {
|
|
constructor (q, a, data) {
|
|
this.Q = SUtils.SimplifyQuestion(q)
|
|
this.A = SUtils.SimplifyAnswer(a)
|
|
this.data = { ...data }
|
|
}
|
|
|
|
toString () {
|
|
if (this.data.type !== 'simple') {
|
|
return '?' + this.Q + '\n!' + this.A + '\n>' + JSON.stringify(this.data)
|
|
} else {
|
|
return '?' + this.Q + '\n!' + this.A
|
|
}
|
|
}
|
|
|
|
HasQuestion () {
|
|
return this.Q !== undefined
|
|
}
|
|
|
|
HasAnswer () {
|
|
return this.A !== undefined
|
|
}
|
|
|
|
HasImage () {
|
|
return this.data.type === 'image'
|
|
}
|
|
|
|
IsComplete () {
|
|
return this.HasQuestion() && this.HasAnswer()
|
|
}
|
|
|
|
CompareToQuestionObj (q2) {
|
|
const qmatchpercent = SUtils.CompareString(this.Q, q2.Q)
|
|
const amatchpercent = SUtils.CompareString(this.A, q2.A)
|
|
if (this.data.images !== undefined) {
|
|
const imatchpercent = this.data.images === undefined ? SUtils.CompareString(this.data.images.join(' '), q2.data.images.join(
|
|
' ')) : 0
|
|
return (qmatchpercent + amatchpercent + imatchpercent) / 3
|
|
} else {
|
|
return (qmatchpercent + amatchpercent) / 2
|
|
}
|
|
}
|
|
|
|
CompareImage (data2) {
|
|
return SUtils.CompareString(this.data.images.join(' '), data2.images.join(' '))
|
|
}
|
|
|
|
CompareToQuestionString (q2, data) {
|
|
assert(data)
|
|
|
|
let qmatchpercent = SUtils.CompareString(this.Q, q2)
|
|
let dataMatchPercent = 0
|
|
|
|
if (data.type === this.data.type) { // both questins are same type
|
|
if (data.type === 'simple') {
|
|
return qmatchpercent
|
|
} else if (data.type === 'image') {
|
|
dataMatchPercent = this.CompareImage(data)
|
|
}
|
|
|
|
return (qmatchpercent + dataMatchPercent) / 2
|
|
} else { // question types are not the same
|
|
dataMatchPercent = 0
|
|
qmatchpercent -= notSameDataTypePenalty
|
|
if (qmatchpercent < 0) { return 0 } else { return qmatchpercent }
|
|
}
|
|
}
|
|
|
|
Compare (q2, data) {
|
|
assert(q2)
|
|
|
|
if (typeof q2 === 'string') {
|
|
return this.CompareToQuestionString(q2, data)
|
|
} else {
|
|
return this.CompareToQuestionObj(q2)
|
|
}
|
|
}
|
|
}
|
|
|
|
class Subject {
|
|
constructor (n) {
|
|
assert(n)
|
|
|
|
this.Name = n
|
|
this.Questions = []
|
|
this.active = false
|
|
}
|
|
|
|
setIndex (i) {
|
|
this.index = i
|
|
}
|
|
|
|
getIndex () {
|
|
return this.index
|
|
}
|
|
|
|
get length () {
|
|
return this.Questions.length
|
|
}
|
|
|
|
setActive (val) {
|
|
this.active = !!val
|
|
}
|
|
|
|
getIfActive () {
|
|
return this.active
|
|
}
|
|
|
|
AddQuestion (q) {
|
|
assert(q)
|
|
|
|
this.Questions.push(q)
|
|
}
|
|
|
|
getSubjNameWithoutYear () {
|
|
let t = this.Name.split(' - ')
|
|
if (t[0].match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{1}$/i)) {
|
|
return t[1] || this.Name
|
|
} else {
|
|
return this.Name
|
|
}
|
|
}
|
|
|
|
getYear () {
|
|
let t = this.Name.split(' - ')[0]
|
|
if (t.match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{1}$/i)) {
|
|
return t
|
|
} else {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
Search (q, data) {
|
|
assert(q)
|
|
|
|
var r = []
|
|
for (let i = 0; i < this.length; i++) {
|
|
let percent = this.Questions[i].Compare(q, data)
|
|
if (percent > minMatchAmmount) {
|
|
r.push({
|
|
q: this.Questions[i],
|
|
match: percent
|
|
})
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < r.length; i++) {
|
|
for (var j = i; j < r.length; j++) {
|
|
if (r[i].match < r[j].match) {
|
|
var tmp = r[i]
|
|
r[i] = r[j]
|
|
r[j] = tmp
|
|
}
|
|
}
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
toString () {
|
|
var r = []
|
|
for (var i = 0; i < this.Questions.length; i++) { r.push(this.Questions[i].toString()) }
|
|
return '+' + this.Name + '\n' + r.join('\n')
|
|
}
|
|
}
|
|
|
|
class QuestionDB {
|
|
constructor (getVal, setVal, delVal) {
|
|
this.Subjects = []
|
|
this.getVal = getVal
|
|
this.setVal = setVal
|
|
this.delVal = delVal
|
|
}
|
|
|
|
get length () {
|
|
return this.Subjects.length
|
|
}
|
|
|
|
get activeIndexes () {
|
|
return this.Subjects.reduce((acc, item, i) => {
|
|
if (item.getIfActive()) {
|
|
acc.push(i)
|
|
}
|
|
return acc
|
|
}, [])
|
|
}
|
|
|
|
GetIfActive (ind) {
|
|
return this.Subjects[ind].getIfActive()
|
|
}
|
|
|
|
ChangeActive (subjName, value) {
|
|
this.Subjects.find((x) => {
|
|
return x.Name === subjName
|
|
}).setActive(value)
|
|
|
|
let actives = JSON.parse(getVal('actives'))
|
|
if (value) {
|
|
actives.push(subjName)
|
|
} else {
|
|
actives = actives.reduce((acc, item) => {
|
|
if (item !== subjName) {
|
|
acc.push(item)
|
|
}
|
|
return acc
|
|
}, [])
|
|
}
|
|
setVal('actives', JSON.stringify(actives))
|
|
}
|
|
|
|
AddQuestion (subj, q) {
|
|
assert(subj)
|
|
|
|
var i = 0
|
|
while (i < this.Subjects.length && this.Subjects[i].Name !== subj) { i++ }
|
|
if (i < this.Subjects.length) { this.Subjects[i].AddQuestion(q) } else {
|
|
const n = new Subject(subj)
|
|
n.AddQuestion(q)
|
|
this.Subjects.push(n)
|
|
}
|
|
}
|
|
|
|
Search (q, data) {
|
|
assert(q)
|
|
|
|
var r = []
|
|
for (let i = 0; i < this.length; i++) {
|
|
if (this.GetIfActive(i)) { r = r.concat(this.Subjects[i].Search(q, data)) }
|
|
}
|
|
|
|
for (let i = 0; i < r.length; i++) {
|
|
for (var j = i; j < r.length; j++) {
|
|
if (r[i].match < r[j].match) {
|
|
var tmp = r[i]
|
|
r[i] = r[j]
|
|
r[j] = tmp
|
|
}
|
|
}
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
AddSubject (subj) {
|
|
assert(subj)
|
|
|
|
var i = 0
|
|
while (i < this.length && subj.Name !== this.Subjects[i].Name) { i++ }
|
|
|
|
if (i < this.length) {
|
|
this.Subjects.concat(subj.Questions)
|
|
} else {
|
|
this.Subjects.push(subj)
|
|
}
|
|
}
|
|
|
|
toString () {
|
|
var r = []
|
|
for (var i = 0; i < this.Subjects.length; i++) { r.push(this.Subjects[i].toString()) }
|
|
return r.join('\n\n')
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : DOM getting stuff {{{
|
|
// all dom getting stuff are in this sections, so on
|
|
// moodle dom change, stuff breaks here
|
|
|
|
class QuestionsPageModell {
|
|
GetAllQuestionsDropdown () {
|
|
if (logElementGetting) { Log('getting dropdown question') }
|
|
let items = document.getElementById('responseform').getElementsByTagName('p')[0].childNodes
|
|
let r = ''
|
|
items.forEach((item) => {
|
|
if (item.tagName === undefined) { r += item.nodeValue }
|
|
})
|
|
return r
|
|
}
|
|
|
|
GetAllQuestionsQtext () {
|
|
if (logElementGetting) { Log('getting all questions qtext') }
|
|
return document.getElementById('responseform').getElementsByClassName('qtext') // getting questions
|
|
}
|
|
|
|
GetAllQuestionsP () {
|
|
if (logElementGetting) { Log('getting all questions by tag p') }
|
|
return document.getElementById('responseform').getElementsByTagName('p')
|
|
}
|
|
|
|
GetFormulationClearfix () {
|
|
if (logElementGetting) { Log('getting formulation clearfix lol') }
|
|
return document.getElementsByClassName('formulation clearfix')
|
|
}
|
|
|
|
GetAnswerOptions () {
|
|
if (logElementGetting) { Log('getting all answer options') }
|
|
return this.GetFormulationClearfix()[0].childNodes[3].innerText
|
|
}
|
|
|
|
GetQuestionImages () {
|
|
if (logElementGetting) { Log('getting question images') }
|
|
return this.GetFormulationClearfix()[0].getElementsByTagName('img')
|
|
}
|
|
|
|
// this function should return the question, posible answers, and image names
|
|
GetQuestionFromTest () {
|
|
var questions // the important questions
|
|
var allQuestions // all questions
|
|
try {
|
|
allQuestions = this.GetAllQuestionsQtext() // getting questions
|
|
if (allQuestions.length === 0) {
|
|
var ddq = this.GetAllQuestionsDropdown()
|
|
if (SUtils.EmptyOrWhiteSpace(ddq)) {
|
|
var questionData = ''
|
|
for (var j = 0; j < allQuestions.length; j++) {
|
|
// TODO: test dis
|
|
let subAllQuestions = allQuestions[j].childNodes
|
|
for (let i = 0; i < subAllQuestions.length; i++) {
|
|
if (subAllQuestions[i].data !== undefined && !SUtils.EmptyOrWhiteSpace(subAllQuestions[i].data)) {
|
|
questionData += subAllQuestions[i].data + ' ' // adding text to question data
|
|
}
|
|
}
|
|
}
|
|
questions = [questionData]
|
|
} else { questions = [ddq] }
|
|
} else {
|
|
questions = []
|
|
for (let i = 0; i < allQuestions.length; i++) {
|
|
questions.push(allQuestions[i].innerText)
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Exception(e, 'script error at getting question:')
|
|
}
|
|
var imgNodes = '' // the image nodes for questions
|
|
try {
|
|
imgNodes = this.GetQuestionImages() // getting question images, if there is any
|
|
AddImageNamesToImages(imgNodes) // adding image names to images, so its easier to search for, or even guessing
|
|
} catch (e) {
|
|
Log(e)
|
|
Log('Some error with images')
|
|
}
|
|
|
|
questions = questions.map((item) => {
|
|
if (item) {
|
|
return SUtils.ReplaceCharsWithSpace(item, '\n')
|
|
}
|
|
})
|
|
|
|
return {
|
|
imgnodes: imgNodes,
|
|
allQ: allQuestions,
|
|
q: questions
|
|
}
|
|
}
|
|
}
|
|
|
|
class ResultsPageModell {
|
|
DetermineQuestionType (nodes) {
|
|
let qtype = ''
|
|
let i = 0
|
|
|
|
while (i < nodes.length && qtype === '') {
|
|
let inps = nodes[i].getElementsByTagName('input')
|
|
|
|
if (inps.length > 0) {
|
|
qtype = inps[0].type
|
|
}
|
|
|
|
i++
|
|
}
|
|
|
|
return qtype
|
|
}
|
|
|
|
GetSelectAnswer (i) {
|
|
if (logElementGetting) { Log('getting selected answer') }
|
|
var t = document.getElementsByTagName('select')
|
|
if (t.length > 0) {
|
|
return t[i].options[t[i].selectedIndex].innerText
|
|
}
|
|
}
|
|
|
|
GetCurrQuestion (i) {
|
|
if (logElementGetting) { Log('getting curr questions by index: ' + i) }
|
|
return document.getElementsByTagName('form')[0].childNodes[0].childNodes[i].childNodes[1].childNodes[0].innerText
|
|
}
|
|
|
|
GetFormResult () {
|
|
if (logElementGetting) { Log('getting form result') }
|
|
var t = document.getElementsByTagName('form')[0].childNodes[0].childNodes
|
|
if (t.length > 0 && t[0].tagName === undefined) { // debreceni moodle
|
|
return document.getElementsByTagName('form')[1].childNodes[0].childNodes
|
|
} else {
|
|
return t
|
|
}
|
|
}
|
|
|
|
GetAnswerNode (i) {
|
|
if (logElementGetting) { Log('getting answer node') }
|
|
|
|
var results = this.GetFormResult() // getting results element
|
|
|
|
var r = results[i].getElementsByClassName('answer')[0].childNodes
|
|
var ret = []
|
|
for (var j = 0; j < r.length; j++) {
|
|
if (r[j].tagName !== undefined && r[j].tagName.toLowerCase() === 'div') { ret.push(r[j]) }
|
|
}
|
|
|
|
let qtype = this.DetermineQuestionType(ret)
|
|
|
|
return {
|
|
nodes: ret,
|
|
type: qtype
|
|
}
|
|
}
|
|
|
|
GetCurrentAnswer (i) {
|
|
if (logElementGetting) { Log('getting curr answer by index: ' + i) }
|
|
var results = this.GetFormResult() // getting results element
|
|
var t = results[i].getElementsByClassName('formulation clearfix')[0].getElementsByTagName('span')
|
|
if (t.length > 2) { return t[1].innerHTML.split('<br>')[1] }
|
|
}
|
|
|
|
GetQText (i) {
|
|
if (logElementGetting) { Log('getting qtext by index: ' + i) }
|
|
var results = this.GetFormResult() // getting results element
|
|
return results[i].getElementsByClassName('qtext')
|
|
}
|
|
|
|
GetDropboxes (i) {
|
|
if (logElementGetting) { Log('getting dropboxes by index: ' + i) }
|
|
var results = this.GetFormResult() // getting results element
|
|
return results[i].getElementsByTagName('select')
|
|
}
|
|
|
|
GetAllAnswer (index) {
|
|
if (logElementGetting) { Log('getting all answers, ind: ' + index) }
|
|
return document.getElementsByClassName('answer')[index].childNodes
|
|
}
|
|
|
|
GetPossibleAnswers (i) {
|
|
if (logElementGetting) { Log('getting possible answers') }
|
|
var results = this.GetFormResult() // getting results element
|
|
var items = results[i].getElementsByTagName('label')
|
|
var r = []
|
|
for (var j = 0; j < items.length; j++) {
|
|
const TryGetCorrect = (j) => {
|
|
var cn = items[j].parentNode.className
|
|
if (cn.includes('correct')) { return cn.includes('correct') && !cn.includes('incorrect') }
|
|
}
|
|
r.push({
|
|
value: items[j].innerText,
|
|
iscorrect: TryGetCorrect(j)
|
|
})
|
|
}
|
|
return r
|
|
}
|
|
|
|
GetAnswersFromGrabBox (i) {
|
|
try {
|
|
if (logElementGetting) { Log('testing if question is grab-box') }
|
|
let results = this.GetFormResult() // getting results element
|
|
let t = results[i].getElementsByClassName('dragitems')[0].childNodes
|
|
if (t.length !== 1) { Log('grab box drag items group length is not 1!'); Log(results[i].getElementsByClassName('dragitems')[0]) }
|
|
let placedItems = t[0].getElementsByClassName('placed')
|
|
let res = []
|
|
for (let i = 0; i < placedItems.length; i++) {
|
|
let item = placedItems[i]
|
|
res.push({
|
|
text: item.innerText,
|
|
left: item.style.left,
|
|
top: item.style.top
|
|
})
|
|
}
|
|
return res
|
|
} catch (e) {
|
|
|
|
}
|
|
}
|
|
|
|
GetRightAnswerIfCorrectShown (i) {
|
|
if (logElementGetting) { Log('getting right answer if correct shown') }
|
|
var results = this.GetFormResult() // getting results element
|
|
return results[i].getElementsByClassName('rightanswer')
|
|
}
|
|
|
|
GetWrongAnswerIfCorrectNotShown (i) {
|
|
if (logElementGetting) { Log('getting wrong answer if correct not shown') }
|
|
var results = this.GetFormResult() // getting results element
|
|
var n = results[i].getElementsByTagName('i')[0].parentNode
|
|
if (n.className.includes('incorrect')) { return results[i].getElementsByTagName('i')[0].parentNode.innerText } else { return '' }
|
|
}
|
|
|
|
GetRightAnswerIfCorrectNotShown (i) {
|
|
if (logElementGetting) { Log('Getting right answer if correct not shown') }
|
|
var results = this.GetFormResult() // getting results element
|
|
var n = results[i].getElementsByTagName('i')[0].parentNode
|
|
if (n.className.includes('correct') && !n.className.includes('incorrect')) {
|
|
return results[i].getElementsByTagName('i')[0].parentNode.innerText
|
|
}
|
|
}
|
|
|
|
GetFormCFOfResult (result) {
|
|
if (logElementGetting) { Log('getting formulation clearfix') }
|
|
return result.getElementsByClassName('formulation clearfix')[0]
|
|
}
|
|
|
|
GetResultText (i) {
|
|
if (logElementGetting) { Log('getting result text') }
|
|
var results = this.GetFormResult() // getting results element
|
|
return this.GetFormCFOfResult(results[i]).getElementsByTagName('p')
|
|
}
|
|
|
|
GetResultImage (i) {
|
|
if (logElementGetting) { Log('getting result image') }
|
|
var results = this.GetFormResult() // getting results element
|
|
return this.GetFormCFOfResult(results[i]).getElementsByTagName('img')
|
|
}
|
|
|
|
// gets the question from the result page
|
|
// i is the index of the question
|
|
GetQuestionFromResult (i) {
|
|
var temp = this.GetQText(i)
|
|
var currQuestion = ''
|
|
if (temp.length > 0) {
|
|
currQuestion = temp[0].innerText // adding the question to curr question as .q
|
|
} else {
|
|
// this is black magic fuckery a bit
|
|
if (this.GetDropboxes(i).length > 0) {
|
|
var allNodes = this.GetResultText(i)
|
|
currQuestion = ''
|
|
for (var k = 0; k < allNodes.length; k++) {
|
|
var allQuestions = this.GetResultText(i)[k].childNodes
|
|
for (var j = 0; j < allQuestions.length; j++) {
|
|
if (allQuestions[j].data !== undefined && !SUtils.EmptyOrWhiteSpace(allQuestions[j].data)) {
|
|
currQuestion += allQuestions[j].data + ' '
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
currQuestion = this.GetCurrQuestion(i)
|
|
} catch (e) {
|
|
currQuestion = 'REEEEEEEEEEEEEEEEEEEEE' // this shouldnt really happen sry guys
|
|
Log('Unable to get question in GetQuestionFromResult')
|
|
}
|
|
}
|
|
}
|
|
return currQuestion
|
|
}
|
|
|
|
// tries to get right answer from result page
|
|
// i is the index of the question
|
|
GetRightAnswerFromResult (i) {
|
|
var fun = []
|
|
|
|
// "húzza oda ..." skip
|
|
fun.push((i) => {
|
|
let temp = RPM.GetAnswersFromGrabBox(i)
|
|
return temp.map((x) => {
|
|
return x.text
|
|
}).join(', ')
|
|
})
|
|
|
|
// the basic type of getting answers
|
|
fun.push((i) => {
|
|
var temp = RPM.GetRightAnswerIfCorrectShown(i) // getting risht answer
|
|
if (temp.length > 0) { return temp[0].innerText } // adding the answer to curr question as .a
|
|
})
|
|
|
|
// if there is dropdown list in the current question
|
|
fun.push((i) => {
|
|
if (RPM.GetDropboxes(i).length > 0) { return RPM.GetCurrentAnswer(i) }
|
|
})
|
|
|
|
// if the correct answers are not shown, and the selected answer
|
|
// is correct
|
|
fun.push((i) => {
|
|
return RPM.GetRightAnswerIfCorrectNotShown(i)
|
|
})
|
|
|
|
// if there is dropbox in the question
|
|
fun.push((i) => {
|
|
return RPM.GetSelectAnswer(i)
|
|
})
|
|
|
|
// if the correct answers are not shown, and the selected answer
|
|
// is incorrect, and there are only 2 options
|
|
fun.push((i) => {
|
|
var possibleAnswers = RPM.GetPossibleAnswers(i)
|
|
if (possibleAnswers.length === 2) {
|
|
for (var k = 0; k < possibleAnswers.length; k++) {
|
|
if (possibleAnswers[k].iscorrect === undefined) { return possibleAnswers[k].value }
|
|
}
|
|
}
|
|
})
|
|
|
|
// if everything fails
|
|
fun.push((i) => {
|
|
return undefined
|
|
})
|
|
|
|
var j = 0
|
|
var currAnswer
|
|
while (j < fun.length && SUtils.EmptyOrWhiteSpace(currAnswer)) {
|
|
try {
|
|
currAnswer = fun[j](i)
|
|
} catch (e) {
|
|
}
|
|
j++
|
|
}
|
|
|
|
return currAnswer
|
|
}
|
|
|
|
// version 2 of getting right answer from result page
|
|
// i is the index of the question
|
|
GetRightAnswerFromResultv2 (i) {
|
|
try {
|
|
var answerNodes = this.GetAnswerNode(i)
|
|
let items = answerNodes.nodes
|
|
|
|
if (answerNodes.type === 'checkbox') { return RPM.GetRightAnswerFromResult(i) }
|
|
|
|
for (let j = 0; j < items.length; j++) {
|
|
let cn = items[j].className
|
|
if (cn.includes('correct') && !cn.includes('incorrect')) { return items[j].innerText }
|
|
}
|
|
if (items.length === 2) {
|
|
for (let j = 0; j < items.length; j++) {
|
|
let cn = items[j].className
|
|
if (!cn.includes('correct')) { return items[j].innerText }
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Log('error at new nodegetting, trying the oldschool way')
|
|
}
|
|
}
|
|
}
|
|
|
|
class MiscPageModell {
|
|
GetCurrentSubjectName () {
|
|
if (logElementGetting) { Log('getting current subjects name') }
|
|
return document.getElementById('page-header').innerText.split('\n')[0]
|
|
}
|
|
|
|
GetVideo () {
|
|
if (logElementGetting) { Log('getting video stuff') }
|
|
return document.getElementsByTagName('video')[0]
|
|
}
|
|
|
|
GetVideoElement () {
|
|
if (logElementGetting) { Log('getting video element') }
|
|
return document.getElementById('videoElement').parentNode
|
|
}
|
|
|
|
GetInputType (answers, i) {
|
|
if (logElementGetting) { Log('getting input type') }
|
|
return answers[i].getElementsByTagName('input')[0].type
|
|
}
|
|
}
|
|
|
|
var QPM = new QuestionsPageModell()
|
|
var RPM = new ResultsPageModell()
|
|
var MPM = new MiscPageModell()
|
|
|
|
// : }}}
|
|
|
|
// : Main function {{{
|
|
Main()
|
|
function Main () {
|
|
'use strict'
|
|
console.time('main')
|
|
|
|
Init(function (count, subjCount) {
|
|
var url = location.href // eslint-disable-line
|
|
|
|
let skipLoad = getVal('skipLoad')
|
|
if (count === -2 && subjCount === -2 && skipLoad) {
|
|
if (url.includes('/quiz/') && url.includes('attempt.php')) {
|
|
ShowMessage({
|
|
m: 'Passzív mód bekapcsolva, válaszok megjelenítéséhez menü gomb alatt kapcsold ki, és frissíts!',
|
|
isSimple: true
|
|
})
|
|
}
|
|
} else {
|
|
try {
|
|
if ((url.includes('/quiz/') && url.includes('attempt.php')) || forceTestPage) { // if the current page is a test
|
|
HandleQuiz()
|
|
} else if ((url.includes('/quiz/') && url.includes('review.php')) || forceResultPage) { // if the current window is a test-s result
|
|
HandleResults(url)
|
|
} else if ((!url.includes('/quiz/') && !url.includes('review.php') && !url.includes('.pdf')) ||
|
|
(forceDefaultPage)) { // if the current window is any other window than a quiz or pdf.
|
|
HandleUI(url, count, subjCount)
|
|
}
|
|
} catch (e) {
|
|
ShowMessage({
|
|
m: 'Fatál error. Check console (f12). Kattints az üzenetre az összes kérdés/válaszért manuális kereséshez!',
|
|
isSimple: true
|
|
}, undefined, () => {
|
|
OpenErrorPage(e)
|
|
})
|
|
|
|
Exception(e, 'script error at main:')
|
|
}
|
|
if (url.includes('eduplayer')) { AddVideoHotkeys(url) } // adding video hotkeys
|
|
Log(
|
|
'Itteni hibák 100% a moodle hiba. Kivéve, ha oda van írva hogy script error ;) Ha ilyesmi szerepel itt, akkor olvasd el a segítség szekciót!'
|
|
)
|
|
}
|
|
console.log('Moodle Test Script run time:')
|
|
console.timeEnd('main')
|
|
SetActivesAsJSON()
|
|
})
|
|
|
|
if (forceTestPage || forceResultPage || forceDefaultPage) {
|
|
if (document.getElementById('scriptMessage')) { document.getElementById('scriptMessage').style.background = 'green' }
|
|
}
|
|
}
|
|
// : }}}
|
|
|
|
// : Main logic stuff {{{
|
|
|
|
// : Loading {{{
|
|
|
|
function Init (cwith) {
|
|
if (false) { // eslint-disable-line
|
|
setVal('version16', undefined)
|
|
setVal('version15', undefined)
|
|
setVal('firstRun', undefined)
|
|
setVal('showQuestions', undefined)
|
|
setVal('showSplash', undefined)
|
|
}
|
|
var url = location.href // eslint-disable-line
|
|
var count = -1 // loaded question count. stays -1 if the load failed.
|
|
// --------------------------------------------------------------------------------------
|
|
// event listener fuckery
|
|
// --------------------------------------------------------------------------------------
|
|
try {
|
|
// adding addeventlistener stuff, for the ability to add more event listeners for the same event
|
|
addEventListener = (function () {
|
|
if (document.addEventListener) {
|
|
return function (element, event, handler) {
|
|
element.addEventListener(event, handler, false)
|
|
}
|
|
} else {
|
|
return function (element, event, handler) {
|
|
element.attachEvent('on' + event, handler)
|
|
}
|
|
}
|
|
}())
|
|
} catch (e) {
|
|
Exception(e, 'script error at addEventListener:')
|
|
}
|
|
VersionActions()
|
|
count = Load(cwith) // loads resources
|
|
if (!url.includes('.pdf')) { ShowMenu() }
|
|
return count
|
|
}
|
|
|
|
function VersionActions () {
|
|
// FOR TESTING ONLY
|
|
// setVal("version15", true);
|
|
// setVal("firstRun", true);
|
|
// setVal("version16", true);
|
|
// throw "asd";
|
|
|
|
FreshStart()
|
|
|
|
Version15()
|
|
}
|
|
|
|
// : Version action functions {{{
|
|
|
|
function SetActivesAsJSON () {
|
|
if (!getVal('actives')) {
|
|
let res = []
|
|
for (let i = 0; i < 100; i++) {
|
|
let a = getVal('Is' + i + 'Active')
|
|
if (a && data.Subjects[i]) {
|
|
res.push(data.Subjects[i].Name)
|
|
}
|
|
delVal('Is' + i + 'Active')
|
|
}
|
|
setVal('actives', JSON.stringify(res))
|
|
}
|
|
}
|
|
|
|
function FreshStart () {
|
|
var firstRun = getVal('firstRun') // if the current run is the frst
|
|
if (firstRun === undefined || firstRun === true) {
|
|
setVal('firstRun', false)
|
|
ShowHelp() // showing help
|
|
return true
|
|
}
|
|
}
|
|
|
|
function Version15 () {
|
|
var version15 = getVal('version15') // if the current run is the frst
|
|
if (version15 === undefined || version15 === true) {
|
|
setVal('version15', false)
|
|
document.write(
|
|
'<h1>Moodle teszt userscript:<h1><h3>1.5.0 verzió: a script mostantól XMLHTTP kéréseket küld szerver fele! Erre a userscript futtató kiegészitőd is figyelmeztetni fog! Ha ez történik, a script rendes működése érdekében engedélyezd (Always allow domain)! Ha nem akarod, hogy ez történjen, akkor ne engedélyezd, vagy a menüben válaszd ki a "helyi fájl használata" opciót!</h3> <h3>Elküldött adatok: minden teszt után a kérdés-válasz páros. Fogadott adatok: Az összes eddig ismert kérdés. Érdemes help-et elolvasni!!!</h3><h5>Ez az ablak frissités után eltűnik. Ha nem, akkor a visza gombbal próbálkozz.</h5>'
|
|
)
|
|
document.close()
|
|
throw new Error('something, so this stuff stops')
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
function ReadNetDB (cwith) {
|
|
function NewXMLHttpRequest () {
|
|
const url = serverAdress + 'data.json'
|
|
xmlhttpRequest({
|
|
method: 'GET',
|
|
synchronous: true,
|
|
url: url,
|
|
onload: function (response) {
|
|
NLoad(response.responseText, cwith)
|
|
},
|
|
onerror: function () {
|
|
NLoad(undefined, cwith) // server down
|
|
}
|
|
})
|
|
}
|
|
try {
|
|
Log('Sending XMLHTTP Request...')
|
|
return NewXMLHttpRequest()
|
|
} catch (e) {
|
|
Exception(e, 'script error at reading online database:')
|
|
}
|
|
}
|
|
|
|
function Load (cwith) {
|
|
let skipLoad = getVal('skipLoad')
|
|
|
|
if (skipLoad) {
|
|
cwith(-2, -2)
|
|
return -1
|
|
}
|
|
|
|
ReadNetDB(cwith)
|
|
}
|
|
|
|
function LoadMOTD (resource) {
|
|
try {
|
|
motd = resource.motd
|
|
} catch (e) {
|
|
Log('Error loading motd :c')
|
|
Log(e)
|
|
}
|
|
}
|
|
|
|
function LoadVersion (resource) {
|
|
try {
|
|
lastestVersion = resource.version
|
|
} catch (e) {
|
|
Log('Error loading version :c')
|
|
Log(e)
|
|
}
|
|
}
|
|
|
|
// loading stuff
|
|
function NLoad (resource, cwith) {
|
|
assert(resource)
|
|
|
|
var count = -1
|
|
var subjCount = 0
|
|
try {
|
|
var d = {}
|
|
try {
|
|
d = JSON.parse(resource)
|
|
} catch (e) {
|
|
Log('Old data, trying with old methods....')
|
|
Log('Couldt parse data!')
|
|
Log(e)
|
|
ShowMessage({
|
|
m: 'Nem sikerült betölteni az adatokat! Kattints a manualért',
|
|
isSimple: true
|
|
}, undefined, ShowHelp)
|
|
}
|
|
var r = new QuestionDB(getVal, setVal, delVal)
|
|
var rt = []
|
|
var allCount = -1
|
|
LoadMOTD(d)
|
|
LoadVersion(d)
|
|
|
|
let actives = []
|
|
try {
|
|
actives = JSON.parse(getVal('actives'))
|
|
if (!Array.isArray(actives)) {
|
|
throw new Error('not an array')
|
|
}
|
|
} catch (e) {
|
|
Log('Unable to parse active subjects!')
|
|
setVal('actives', '[]')
|
|
actives = []
|
|
}
|
|
|
|
for (let i = 0; i < d.Subjects.length; i++) {
|
|
let s = new Subject(d.Subjects[i].Name)
|
|
s.setIndex(i)
|
|
let isActive = actives.includes(d.Subjects[i].Name)
|
|
if (isActive) {
|
|
s.setActive(true)
|
|
var j = 0
|
|
for (j = 0; j < d.Subjects[i].Questions.length; j++) {
|
|
var currQ = d.Subjects[i].Questions[j]
|
|
s.AddQuestion(new Question(currQ.Q, currQ.A, currQ.data))
|
|
}
|
|
rt.push({
|
|
name: d.Subjects[i].Name,
|
|
count: j
|
|
})
|
|
allCount += j
|
|
subjCount++
|
|
}
|
|
r.AddSubject(s)
|
|
}
|
|
|
|
data = r
|
|
count = allCount + 1 // couse starting with -1 to show errors
|
|
} catch (e) {
|
|
Exception(e, 'script error at loading:')
|
|
count = -1 // returns -1 if error
|
|
}
|
|
cwith(count, subjCount)
|
|
}
|
|
|
|
function AlertOnNoQuestion () {
|
|
try {
|
|
document.getElementById('HelperMenuButton').style.background = 'yellow'
|
|
} catch (e) {
|
|
Log('Unable to get helper menu button')
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : UI handling {{{
|
|
function HandleUI (url, count, subjCount) {
|
|
var newVersion = false // if the script is newer than last start
|
|
var loaded = count !== -1 // if script could load stuff
|
|
|
|
try {
|
|
newVersion = info().script.version !== getVal('lastVerson')
|
|
} catch (e) {
|
|
Log('Some weird error trying to set new verison')
|
|
}
|
|
var greetMsg = '' // message to show at the end
|
|
var timeout = null // the timeout. if null, it wont be hidden
|
|
// no new version, nothing loaded
|
|
if (!newVersion && !loaded) { // --------------------------------------------------------------------------------------------------------------
|
|
greetMsg = 'Hiba a @resource tagnál, vagy a fileval van gond! (Lehet át lett helyezve, vagy üres, vagy nincs tárgy kiválasztva) Vagy válaszd a netes adatok használatát menüben. Ellenőrizd az elérési utat, vagy hogy a Tampermonkey bővítmény eléri-e a fájlokat. Ha netes forrást használsz, akkor nem elérhető a szerver! Segítségért kattints!'
|
|
}
|
|
var showSplash = (getVal('showSplash') === undefined) || getVal('showSplash') // getting value, if splash screen should be shown. Its true, if its undefined, or true
|
|
// no new version, everything loaded, and show splash is enabled. otherwise something happened, so showing it
|
|
if (!newVersion && loaded && showSplash) { // ------------------------------------------------------------------------------------------------
|
|
timeout = 5
|
|
greetMsg = 'Moodle/Elearning/KMOOC segéd v. ' + info().script.version + '. '
|
|
|
|
if (lastestVersion !== undefined && info().script.version !== lastestVersion) {
|
|
greetMsg += 'Új verzió elérhető: ' + lastestVersion + '\n'
|
|
timeout = undefined
|
|
}
|
|
greetMsg += count + ' kérdés és ' + subjCount + ' tárgy betöltve. (click for help).'
|
|
if (data.length > 0) {
|
|
var toAdd = []
|
|
for (var i = 0; i < data.length; i++) {
|
|
if (data.GetIfActive(i)) {
|
|
toAdd.push(data.Subjects[i].Name + ' (' + data.Subjects[i].length + ')')
|
|
}
|
|
}
|
|
if (toAdd.length !== 0) {
|
|
greetMsg += '\nAktív tárgyak: ' + toAdd.join(', ') + '.'
|
|
} else {
|
|
AlertOnNoQuestion()
|
|
greetMsg += '\nNincs aktív tárgyad. Menüből válassz ki eggyet!'
|
|
timeout = undefined
|
|
}
|
|
} else {
|
|
greetMsg += ' Az adatfájlban nem adtál meg nevet. Vagy nem elérhető a szerver. Katt a helpért!'
|
|
}
|
|
}
|
|
// new version, nothing loaded
|
|
if (newVersion && !loaded) { // --------------------------------------------------------------------------------------------------------------
|
|
greetMsg = 'Moodle/Elearning/KMOOC segéd v. ' + info().script.version + '. Új verzió!\n Írd át a @resouce tagnál az elírési utat! Kivéve ha üres a file, akkor töltsd fel :) Nincs kérdés betöltve! Segítséghez kattints. Changelog:\n' + lastChangeLog // showing changelog too
|
|
}
|
|
// new version, everything loaded -> set lastVerson to current
|
|
if (newVersion && loaded) { // --------------------------------------------------------------------------------------------------------------
|
|
greetMsg = 'Moodle/Elearning/KMOOC segéd v. ' + info().script.version + '. ' + count + ' kérdés és ' + subjCount + ' tárgy betöltve. Verzió frissítve ' + info().script.version + '-re. Changelog:\n' + lastChangeLog
|
|
setVal('lastVerson', info().script.version) // setting lastVersion
|
|
}
|
|
if (!SUtils.EmptyOrWhiteSpace(motd)) {
|
|
var prevmotd = getVal('motd')
|
|
if (prevmotd !== motd) {
|
|
greetMsg += '\nMOTD:\n' + motd
|
|
timeout = null
|
|
setVal('motdcount', motdShowCount)
|
|
setVal('motd', motd)
|
|
} else {
|
|
var motdcount = getVal('motdcount')
|
|
if (motdcount === undefined) {
|
|
setVal('motdcount', motdShowCount)
|
|
motdcount = motdShowCount
|
|
}
|
|
|
|
motdcount--
|
|
if (motdcount > 0) {
|
|
greetMsg += '\nMOTD:\n' + motd
|
|
timeout = null
|
|
setVal('motdcount', motdcount)
|
|
}
|
|
}
|
|
}
|
|
ShowMessage({
|
|
m: greetMsg,
|
|
isSimple: true
|
|
}, timeout, ShowHelp) // showing message. If "m" is empty it wont show it, thats how showSplash works.
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Answering stuffs {{{
|
|
|
|
function HandleQuiz () {
|
|
var q = QPM.GetQuestionFromTest()
|
|
var questions = q.q
|
|
var imgNodes = q.imgnodes
|
|
// ------------------------------------------------------------------------------------------------------
|
|
var answers = []
|
|
questions.forEach((x, j) => {
|
|
let question = SUtils.EmptyOrWhiteSpace(x) ? '' : SUtils.RemoveUnnecesarySpaces(x) // simplifying question
|
|
var result = data.Search(question, GetImageDataFromImgNodes(imgNodes))
|
|
var r = PrepareAnswers(result, j)
|
|
if (r !== undefined) { answers.push(r) }
|
|
HighLightAnswer(result, j) // highlights the answer for the current result
|
|
})
|
|
ShowAnswers(answers, q.q)
|
|
}
|
|
|
|
function PrepareAnswers (result, j) {
|
|
assert(result)
|
|
|
|
if (result.length > 0) {
|
|
var allMessages = [] // preparing all messages
|
|
for (var k = 0; k < result.length; k++) {
|
|
var msg = '' // the current message
|
|
if ((getVal('showQuestions') === undefined) || getVal('showQuestions')) {
|
|
msg += result[k].q.Q + '\n' // adding the question if yes
|
|
}
|
|
msg += result[k].q.A.replace(/, /g, '\n') // adding answer
|
|
if (result[k].q.HasImage()) {
|
|
msg += '\n\nKépek fenti válaszok sorrendjében: ' + result[k].q.data.images.join(', ') // if it has image part, adding that too
|
|
}
|
|
allMessages.push({
|
|
m: msg,
|
|
p: result[k].match
|
|
})
|
|
}
|
|
return allMessages
|
|
}
|
|
}
|
|
|
|
function ShowAnswers (answers, question) {
|
|
assert(answers)
|
|
|
|
if (answers.length > 0) { // if there are more than 0 answer
|
|
ShowMessage(answers)
|
|
} else {
|
|
ShowMessage({
|
|
m: 'Nincs találat :( Kattints az üzenetre az összes kérdés/válaszért manuális kereséshez! Előfordulhat, hogy a tárgyat nem válsztottad ki a menüben.',
|
|
isSimple: true
|
|
}, undefined, function () {
|
|
OpenErrorPage({
|
|
message: 'No result found',
|
|
stack: JSON.stringify(question)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Quiz saving {{{
|
|
|
|
function HandleResults (url) {
|
|
var d = SaveQuiz(GetQuiz(), data) // saves the quiz questions and answers
|
|
|
|
if (d) { ShowSaveQuizDialog(d.addedQ, d.allQ, d.allOutput, d.output, d.sendSuccess, d.sentData) }
|
|
}
|
|
|
|
function ShowSaveQuizDialog (addedQ, allQ, allOutput, output, sendSuccess, sentData) {
|
|
var msg = ''
|
|
if (addedQ > 0) {
|
|
msg = 'Klikk ide a nyers adatokhoz. ' + addedQ + ' új kérdés!'
|
|
|
|
if (!sendSuccess) { msg += ' Nem sikerült kérdéseket elküldeni szervernek. Ha gondolod utánanézhetsz.' } else { msg += 'Az új kérdések elküldve.' }
|
|
} else {
|
|
msg = 'A kérdőívben nincsen új kérdés. Ha mégis le akarod menteni klikk ide.'
|
|
if (!data) { msg += ' Lehet azért, mert nincs kérdés betöltve.' }
|
|
}
|
|
// showing a message wit the click event, and the generated page
|
|
ShowMessage({
|
|
m: msg,
|
|
isSimple: true
|
|
}, null, function () {
|
|
var towrite = '<h3>' + sentData.subj + '<br>TXT-ben nem szereplő kérdések: ' + addedQ + '/' + allQ + '</h3><br>' + output.replace(/\n/g, '<br>') + '<br><h3>Összes kérdés/válasz:</h3>' + allOutput.replace(
|
|
/\n/g, '<br>')
|
|
|
|
try {
|
|
towrite += '</p>Elküldött adatok:</p> ' + JSON.stringify(sentData)
|
|
} catch (e) {
|
|
towrite += '</p>Elküldött adatok:</p> ' + sentData
|
|
}
|
|
document.write(towrite)
|
|
document.close()
|
|
})
|
|
}
|
|
|
|
function SearchSameQuestion (questionData, quiz, i) {
|
|
var r = questionData.Search(quiz[i])
|
|
|
|
let count = 0
|
|
r.forEach((item) => {
|
|
if (item.match > minResultMatchPercent) {
|
|
count++
|
|
}
|
|
})
|
|
|
|
return count === 0 ? -1 : count
|
|
}
|
|
|
|
// this should get the image url from a result page
|
|
// i is the index of the question
|
|
// FIXME: move this to RPM class ??? and refactor this
|
|
function GetImageFormResult (i) {
|
|
try {
|
|
var imgElements = RPM.GetResultImage(i) // trying to get image
|
|
var imgURL = [] // image urls
|
|
for (var j = 0; j < imgElements.length; j++) {
|
|
if (!imgElements[j].src.includes('brokenfile')) {
|
|
var filePart = imgElements[j].src.split('/') // splits the link by "/"
|
|
filePart = filePart[filePart.length - 1] // the last one is the image name
|
|
imgURL.push(decodeURI(SUtils.ShortenString(filePart, 30)))
|
|
}
|
|
}
|
|
if (imgURL.length > 0) {
|
|
return imgURL
|
|
}
|
|
} catch (e) {
|
|
Log("Couldn't get images from result")
|
|
}
|
|
}
|
|
|
|
function GetDataFormResult (i) {
|
|
let data = { type: 'simple' }
|
|
|
|
let img = GetImageFormResult(i)
|
|
let grabbox = RPM.GetAnswersFromGrabBox(i)
|
|
if (img) {
|
|
data = {
|
|
type: 'image',
|
|
images: img
|
|
}
|
|
}
|
|
if (grabbox) {
|
|
data = {
|
|
type: 'grabbox',
|
|
images: img,
|
|
grabbox: grabbox
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// saves the current quiz. questionData contains the active subjects questions
|
|
function SaveQuiz (quiz, questionData) {
|
|
try {
|
|
if (quiz.length === 0) {
|
|
throw new Error('quiz length is zero!')
|
|
}
|
|
var output = '' // thefinal output
|
|
var allOutput = '' // thefinal output with all questions
|
|
var allQ = 0
|
|
var addedQ = 0
|
|
var newQuestions = []
|
|
for (var i = 0; i < quiz.length; i++) {
|
|
// searching for same questions in questionData
|
|
var toAdd = '' // this will be added to some variable depending on if its already in the database
|
|
toAdd += '?' + SUtils.RemoveUnnecesarySpaces(quiz[i].Q) + '\n' // adding quiz question
|
|
toAdd += '!' + SUtils.RemoveUnnecesarySpaces(quiz[i].A) + '\n' // adding quiz answer
|
|
if (quiz[i].HasImage()) {
|
|
let imgString = quiz[i].data.images.join(', ')
|
|
toAdd += '>' + imgString + '\n' // adding quiz image if there is any
|
|
}
|
|
if (SearchSameQuestion(questionData, quiz, i) === -1) {
|
|
output += toAdd // adding to output
|
|
newQuestions.push(quiz[i])
|
|
addedQ++
|
|
}
|
|
allOutput += toAdd // adding to all
|
|
allQ++
|
|
}
|
|
var sendSuccess = false
|
|
var sentData = {}
|
|
try {
|
|
try {
|
|
sentData.subj = MPM.GetCurrentSubjectName()
|
|
} catch (e) {
|
|
sentData.subj = 'NOSUBJ'
|
|
Log('unable to get subject name :c')
|
|
}
|
|
sentData.allData = quiz
|
|
sentData.data = newQuestions
|
|
sentData.version = info().script.version
|
|
sentData.id = GetId()
|
|
Log('SENT DATA', sentData)
|
|
SendXHRMessage('datatoadd=' + JSON.stringify(sentData))
|
|
sendSuccess = true
|
|
} catch (e) {
|
|
Exception(e, 'error at sending data to server.')
|
|
}
|
|
return {
|
|
addedQ: addedQ,
|
|
allQ: allQ,
|
|
allOutput: allOutput,
|
|
output: output,
|
|
sendSuccess: sendSuccess,
|
|
sentData: sentData
|
|
}
|
|
} catch (e) {
|
|
Exception(e, 'script error at saving quiz')
|
|
}
|
|
}
|
|
|
|
// getting quiz from finish page
|
|
function GetQuiz () {
|
|
try {
|
|
var quiz = [] // final quiz stuff
|
|
var results = RPM.GetFormResult() // getting results element
|
|
for (var i = 0; i < results.length - 2; i++) {
|
|
var question = {} // the current question
|
|
// QUESTION --------------------------------------------------------------------------------------------------------------------
|
|
var q = RPM.GetQuestionFromResult(i)
|
|
if (q !== undefined) { question.q = SUtils.SimplifyQuestion(q) }
|
|
|
|
// RIGHTANSWER ---------------------------------------------------------------------------------------------------------------------
|
|
var a = RPM.GetRightAnswerFromResultv2(i)
|
|
if (a === undefined) { a = RPM.GetRightAnswerFromResult(i) }
|
|
if (a !== undefined) { question.a = SUtils.SimplifyAnswer(a) }
|
|
// DATA ---------------------------------------------------------------------------------------------------------------------
|
|
question.data = GetDataFormResult(i)
|
|
|
|
if (question.a !== undefined) {
|
|
quiz.push(new Question(question.q, question.a, question.data)) // adding current question to quiz
|
|
} else {
|
|
Log('error getting queston, no correct answer given, or its incorrect')
|
|
Log(question)
|
|
}
|
|
}
|
|
return quiz
|
|
} catch (e) {
|
|
Exception(e, 'script error at quiz parsing:')
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Helpers {{{
|
|
|
|
function GetImageDataFromImgNodes (imgs) {
|
|
var questionImages = [] // the array for the image names in question
|
|
for (var i = 0; i < imgs.length; i++) {
|
|
if (!imgs[i].src.includes('brokenfile')) {
|
|
var filePart = imgs[i].src.split('/') // splits the link by "/"
|
|
filePart = filePart[filePart.length - 1] // the last one is the image name
|
|
questionImages.push(decodeURI(SUtils.RemoveUnnecesarySpaces(SUtils.ShortenString(filePart, 30)))) // decodes uri codes, and removes exess spaces, and shortening it
|
|
}
|
|
}
|
|
if (questionImages.length > 0) {
|
|
return {
|
|
type: 'image',
|
|
images: questionImages
|
|
}
|
|
} else {
|
|
return {
|
|
type: 'simple'
|
|
}
|
|
}
|
|
}
|
|
|
|
// adds image names to image nodes
|
|
function AddImageNamesToImages (imgs) {
|
|
for (var i = 0; i < imgs.length; i++) {
|
|
if (!imgs[i].src.includes('brokenfile')) {
|
|
var filePart = imgs[i].src.split('/') // splits the link by "/"
|
|
filePart = filePart[filePart.length - 1] // the last one is the image name
|
|
var appedtTo = imgs[i].parentNode // it will be appended here
|
|
var mainDiv = document.createElement('div')
|
|
var fileName = SUtils.ShortenString(decodeURI(filePart), 15) // shortening name, couse it can be long as fuck
|
|
var textNode = document.createTextNode('(' + fileName + ')')
|
|
mainDiv.appendChild(textNode)
|
|
appedtTo.appendChild(mainDiv)
|
|
}
|
|
}
|
|
}
|
|
|
|
// this function adds basic hotkeys for video controll.
|
|
function AddVideoHotkeys (url) {
|
|
var seekTime = 20
|
|
document.addEventListener('keydown', function (e) {
|
|
try {
|
|
var video = MPM.GetVideo()
|
|
var keyCode = e.keyCode // getting keycode
|
|
if (keyCode === 32) { // if the keycode is 32 (space)
|
|
e.preventDefault() // preventing default action (space scrolles down)
|
|
if (video.paused && video.buffered.length > 0) {
|
|
video.play()
|
|
} else {
|
|
video.pause()
|
|
}
|
|
}
|
|
if (keyCode === 39) { // rigth : 39
|
|
video.currentTime += seekTime
|
|
}
|
|
if (keyCode === 37) { // left : 37
|
|
video.currentTime -= seekTime
|
|
}
|
|
} catch (err) {
|
|
Log('Hotkey error.')
|
|
Log(err.message)
|
|
}
|
|
})
|
|
var toadd = MPM.GetVideoElement()
|
|
var node = CreateNodeWithText(toadd,
|
|
'Miután elindítottad: Play/pause: space. Seek: Bal/jobb nyíl.')
|
|
node.style.margin = '5px 5px 5px 5px' // fancy margin
|
|
}
|
|
|
|
// removes stuff like " a. q1" -> "q1"
|
|
function RemoveLetterMarking (inp) {
|
|
let dotIndex = inp.indexOf('.')
|
|
let doubledotIndex = inp.indexOf(':')
|
|
let maxInd = 4 // inp.length * 0.2;
|
|
|
|
if (dotIndex < maxInd) { return SUtils.RemoveUnnecesarySpaces(inp.substr(inp.indexOf('.') + 1, inp.length)) } else if (doubledotIndex < maxInd) { return SUtils.RemoveUnnecesarySpaces(inp.substr(inp.indexOf(':') + 1, inp.length)) } else { return inp }
|
|
}
|
|
|
|
// highlights the possible solutions to the current question
|
|
function HighLightAnswer (results, currQuestionNumber) {
|
|
try {
|
|
if (results.length > 0) {
|
|
var answers = RPM.GetAllAnswer(currQuestionNumber) // getting all answers
|
|
var toColor = [] // the numberth in the array will be colored, and .length items will be colored
|
|
var type = '' // type of the question. radio or ticbox or whatitscalled
|
|
for (let i = 0; i < answers.length; i++) { // going thtough answers
|
|
if (answers[i].tagName && answers[i].tagName.toLowerCase() === 'div') { // if its not null and is "div"
|
|
var correct = results[0].q.A.toLowerCase() // getting current correct answer from data
|
|
var answer = answers[i].innerText.replace(/\n/g, '').toLowerCase() // getting current answer
|
|
|
|
// removing stuff like "a."
|
|
answer = RemoveLetterMarking(answer)
|
|
|
|
if (SUtils.EmptyOrWhiteSpace(correct) || SUtils.EmptyOrWhiteSpace(answer)) { continue }
|
|
|
|
if (SUtils.NormalizeSpaces(SUtils.RemoveUnnecesarySpaces(correct)).includes(answer)) { // if the correct answer includes the current answer
|
|
toColor.push(i) // adding the index
|
|
type = MPM.GetInputType(answers, i) // setting the type
|
|
}
|
|
}
|
|
}
|
|
if (results[0].match === 100) { // if the result is 100% correct
|
|
if (type !== 'radio' || toColor.length === 1) { // TODO why not radio
|
|
for (let i = 0; i < toColor.length; i++) { // going through "toColor"
|
|
answers[toColor[i]].style.backgroundColor = '#8cff66'
|
|
}
|
|
}
|
|
} // and coloring the correct index
|
|
}
|
|
} catch (e) { // catching errors. Sometimes there are random errors, wich i did not test, but they are rare, and does not break the main script.
|
|
Log('script error at highlightin answer: ' + e.message)
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
function Log (value) {
|
|
if (log) { console.log(value) }
|
|
}
|
|
|
|
function Exception (e, msg) {
|
|
Log('------------------------------------------')
|
|
Log(msg)
|
|
Log(e.message)
|
|
Log('------------------------------------------')
|
|
Log(e.stack)
|
|
Log('------------------------------------------')
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Minor UI stuff {{{
|
|
|
|
// shows a message with "msg" text, "matchPercent" tip and transp, and "timeout" time
|
|
function ShowMessage (msgItem, timeout, funct) {
|
|
// msgItem help:
|
|
// [ [ {}{}{}{} ] [ {}{}{} ] ]
|
|
// msgItem[] <- a questions stuff
|
|
// msgItem[][] <- a questions relevant answers array
|
|
// msgItem[][].p <- a questions precent
|
|
// msgItem[][].m <- a questions message
|
|
try {
|
|
var defMargin = '0px 5px 0px 5px'
|
|
var isSimpleMessage = false
|
|
var simpleMessageText = ''
|
|
if (msgItem.isSimple) { // parsing msgItem for easier use
|
|
simpleMessageText = msgItem.m
|
|
if (simpleMessageText === '') {
|
|
return
|
|
}
|
|
msgItem = [
|
|
[{
|
|
m: simpleMessageText
|
|
}]
|
|
]
|
|
isSimpleMessage = true
|
|
}
|
|
|
|
var appedtTo = document.body // will be appended here
|
|
var width = window.innerWidth - window.innerWidth / 6 // with of the box
|
|
var startFromTop = 25 // top distance
|
|
|
|
var mainDiv = document.createElement('div') // the main divider, wich items will be attached to
|
|
mainDiv.setAttribute('id', 'messageMainDiv')
|
|
if (funct) { // if there is a function as parameter
|
|
addEventListener(mainDiv, 'click', funct) // adding it as click
|
|
}
|
|
// lotsa crap style
|
|
mainDiv.style.position = 'fixed'
|
|
mainDiv.style.zIndex = 999999
|
|
mainDiv.style.textAlign = 'center'
|
|
mainDiv.style.width = width + 'px'
|
|
// mainDiv.style.height = height + 'px';
|
|
mainDiv.style.padding = '0px'
|
|
mainDiv.style.background = '#222d32' // background color
|
|
mainDiv.style.color = '#ffffff' // text color
|
|
mainDiv.style.borderColor = '#035a8f' // border color
|
|
mainDiv.style.border = 'none'
|
|
mainDiv.style.top = (startFromTop) + 'px'
|
|
mainDiv.style.left = (window.innerWidth - width) / 2 + 'px'
|
|
mainDiv.style.opacity = '0.9' // setting starting opacity
|
|
mainDiv.setAttribute('id', 'scriptMessage')
|
|
var matchPercent = msgItem[0][0].p
|
|
if (isSimpleMessage) {
|
|
var simpleMessageParagrapg = document.createElement('p') // new paragraph
|
|
simpleMessageParagrapg.style.margin = defMargin // fancy margin
|
|
var splitText = simpleMessageText.split('\n')
|
|
for (var i = 0; i < splitText.length; i++) {
|
|
var mesageNode = CreateNodeWithText(simpleMessageParagrapg, splitText[i])
|
|
mesageNode.style.margin = defMargin // fancy margin
|
|
}
|
|
mainDiv.appendChild(simpleMessageParagrapg) // adding text box to main div
|
|
} else { // if its a fucking complicated message
|
|
// TABLE SETUP ------------------------------------------------------------------------------------------------------------
|
|
var table = document.createElement('table')
|
|
table.style.width = '100%'
|
|
// ROWS -----------------------------------------------------------------------------------------------------
|
|
var rowOne = table.insertRow() // previous suggestion, question text, and prev question
|
|
var rowTwo = table.insertRow() // next question button
|
|
var rowThree = table.insertRow() // next suggetsion button
|
|
// CELLS -----------------------------------------------------------------------------------------------------
|
|
// row one
|
|
var numberTextCell = rowOne.insertCell()
|
|
var questionCell = rowOne.insertCell() // QUESTION CELL
|
|
questionCell.setAttribute('id', 'questionCell')
|
|
questionCell.rowSpan = 3
|
|
questionCell.style.width = '90%'
|
|
var prevQuestionCell = rowOne.insertCell()
|
|
// row two
|
|
var percentTextCell = rowTwo.insertCell()
|
|
var nextQuestionCell = rowTwo.insertCell()
|
|
// row three
|
|
var prevSuggestionCell = rowThree.insertCell()
|
|
var nextSuggestionCell = rowThree.insertCell()
|
|
// adding finally
|
|
mainDiv.appendChild(table)
|
|
// PERCENT TEXT SETUP -----------------------------------------------------------------------------------------------------
|
|
var percentTextBox = CreateNodeWithText(percentTextCell, '')
|
|
percentTextBox.setAttribute('id', 'percentTextBox')
|
|
|
|
if (matchPercent) { // if match percent param is not null
|
|
percentTextBox.innerText = matchPercent + '%'
|
|
}
|
|
// NUMBER SETUP -----------------------------------------------------------------------------------------------------
|
|
var numberTextBox = CreateNodeWithText(numberTextCell, '1.')
|
|
numberTextBox.setAttribute('id', 'numberTextBox')
|
|
|
|
// ANSWER NODE SETUP -------------------------------------------------------------------------------------------------------------
|
|
var questionTextElement = CreateNodeWithText(questionCell, 'ur question goes here, mister OwO')
|
|
questionTextElement.setAttribute('id', 'questionTextElement')
|
|
|
|
// BUTTON SETUP -----------------------------------------------------------------------------------------------------------
|
|
var currItem = 0
|
|
var currRelevantQuestion = 0
|
|
|
|
const GetRelevantQuestion = () => { // returns the currItemth questions currRelevantQuestionth relevant question
|
|
return msgItem[currItem][currRelevantQuestion]
|
|
}
|
|
|
|
const ChangeCurrItemIndex = (to) => {
|
|
currItem += to
|
|
if (currItem < 0) {
|
|
currItem = 0
|
|
}
|
|
if (currItem > msgItem.length - 1) {
|
|
currItem = msgItem.length - 1
|
|
}
|
|
currRelevantQuestion = 0
|
|
}
|
|
|
|
const ChangeCurrRelevantQuestionIndex = (to) => {
|
|
currRelevantQuestion += to
|
|
if (currRelevantQuestion < 0) {
|
|
currRelevantQuestion = 0
|
|
}
|
|
if (currRelevantQuestion > msgItem[currItem].length - 1) {
|
|
currRelevantQuestion = msgItem[currItem].length - 1
|
|
}
|
|
}
|
|
|
|
const SetQuestionText = () => {
|
|
var relevantQuestion = GetRelevantQuestion()
|
|
questionTextElement.innerText = relevantQuestion.m
|
|
if (currItem === 0 && currRelevantQuestion === 0) {
|
|
numberTextBox.innerText = (currRelevantQuestion + 1) + '.'
|
|
} else {
|
|
numberTextBox.innerText = (currItem + 1) + './' + (currRelevantQuestion + 1) + '.'
|
|
}
|
|
percentTextBox.innerText = relevantQuestion.p + '%'
|
|
}
|
|
|
|
var buttonMargin = '2px 2px 2px 2px' // uniform button margin
|
|
if (msgItem[currItem].length > 1) {
|
|
// PREV SUGG BUTTON ------------------------------------------------------------------------------------------------------------
|
|
var prevSuggButton = CreateNodeWithText(prevSuggestionCell, '<', 'button')
|
|
prevSuggButton.style.margin = buttonMargin // fancy margin
|
|
|
|
prevSuggButton.addEventListener('click', function () {
|
|
ChangeCurrRelevantQuestionIndex(-1)
|
|
SetQuestionText()
|
|
})
|
|
// NEXT SUGG BUTTON ------------------------------------------------------------------------------------------------------------
|
|
var nextSuggButton = CreateNodeWithText(nextSuggestionCell, '>', 'button')
|
|
nextSuggButton.style.margin = buttonMargin // fancy margin
|
|
|
|
nextSuggButton.addEventListener('click', function () {
|
|
ChangeCurrRelevantQuestionIndex(1)
|
|
SetQuestionText()
|
|
})
|
|
}
|
|
// deciding if has multiple questions ------------------------------------------------------------------------------------------------
|
|
if (msgItem.length === 1) {
|
|
SetQuestionText()
|
|
} else { // if there are multiple items to display
|
|
// PREV QUESTION BUTTON ------------------------------------------------------------------------------------------------------------
|
|
var prevButton = CreateNodeWithText(prevQuestionCell, '^', 'button')
|
|
prevButton.style.margin = buttonMargin // fancy margin
|
|
|
|
// event listener
|
|
prevButton.addEventListener('click', function () {
|
|
ChangeCurrItemIndex(-1)
|
|
SetQuestionText()
|
|
})
|
|
// NEXT QUESTION BUTTON ------------------------------------------------------------------------------------------------------------
|
|
var nextButton = CreateNodeWithText(nextQuestionCell, 'ˇ', 'button')
|
|
nextButton.style.margin = buttonMargin // fancy margin
|
|
|
|
// event listener
|
|
nextButton.addEventListener('click', function () {
|
|
ChangeCurrItemIndex(1)
|
|
SetQuestionText()
|
|
})
|
|
SetQuestionText()
|
|
}
|
|
}
|
|
appedtTo.appendChild(mainDiv) // THE FINAL APPEND
|
|
|
|
// setting some events
|
|
// addEventListener(window, 'scroll', function () {
|
|
// mainDiv.style.top = (pageYOffset + startFromTop) + 'px';
|
|
// })
|
|
addEventListener(window, 'resize', function () {
|
|
mainDiv.style.left = (window.innerWidth - width) / 2 + 'px'
|
|
})
|
|
var timeOut
|
|
if (timeout && timeout > 0) { // setting timeout if not zero or null
|
|
timeOut = setTimeout(function () {
|
|
mainDiv.parentNode.removeChild(mainDiv)
|
|
}, timeout * 1000)
|
|
}
|
|
// middle click close event listener
|
|
addEventListener(mainDiv, 'mousedown', function (e) {
|
|
if (e.which === 2) {
|
|
mainDiv.parentNode.removeChild(mainDiv)
|
|
if (timeOut) {
|
|
clearTimeout(timeOut)
|
|
}
|
|
}
|
|
})
|
|
} catch (e) {
|
|
Exception(e, 'script error at showing message:')
|
|
}
|
|
}
|
|
|
|
// shows a fancy menu
|
|
function ShowMenu () {
|
|
try {
|
|
var buttonWidth = 100 // button size ;)
|
|
var buttonHeight = 85
|
|
var appedtTo = document.body // will be appended here
|
|
|
|
// mainDiv.style.left = (window.innerWidth - width) / 2 + 'px';
|
|
|
|
var menuButtonDiv = document.createElement('div')
|
|
menuButtonDiv.style.width = buttonWidth + 'px'
|
|
menuButtonDiv.style.height = buttonHeight + 'px'
|
|
menuButtonDiv.style.top = (window.innerHeight - buttonHeight * 1.5) + 'px'
|
|
menuButtonDiv.style.left = window.innerWidth - buttonWidth * 1.5 + 'px'
|
|
menuButtonDiv.style.zIndex = 999999 // TO THE MAX
|
|
menuButtonDiv.style.position = 'fixed'
|
|
// menuButtonDiv.style.borderStyle = "solid";
|
|
// menuButtonDiv.style.borderWidth = "1px";
|
|
|
|
// design
|
|
menuButtonDiv.style.textAlign = 'center'
|
|
menuButtonDiv.style.padding = '0px'
|
|
menuButtonDiv.style.margin = '0px'
|
|
menuButtonDiv.style.background = 'transparent' // background color
|
|
|
|
// menu text
|
|
// var menuTextBox = CreateNodeWithText(menuButtonDiv, "Kérdések\nMenü");
|
|
|
|
var menuButton = CreateNodeWithText(menuButtonDiv, 'Kérdések Menu', 'button')
|
|
menuButton.style.width = buttonWidth + 'px'
|
|
menuButton.style.border = 'none'
|
|
menuButton.style.height = buttonHeight - 20 + 'px'
|
|
menuButton.style.background = '#222d32' // background color
|
|
menuButton.style.color = '#ffffff' // background color
|
|
menuButton.setAttribute('id', 'HelperMenuButton')
|
|
|
|
menuButton.addEventListener('click', function () {
|
|
if (document.getElementById('HelperMenu') == null) {
|
|
ShowMenuList()
|
|
} else {
|
|
CloseMenu()
|
|
}
|
|
}) // adding click
|
|
|
|
// passive mode stuff
|
|
var questionsTickBox = document.createElement('input')
|
|
questionsTickBox.type = 'checkbox'
|
|
questionsTickBox.checked = getVal('skipLoad')
|
|
questionsTickBox.style.position = ''
|
|
questionsTickBox.style.left = 10 + 'px'
|
|
questionsTickBox.style.margin = '5px 5px 5px 5px' // fancy margin
|
|
questionsTickBox.style.top = 0 + 'px'
|
|
|
|
menuButtonDiv.appendChild(questionsTickBox) // adding to main div
|
|
|
|
questionsTickBox.addEventListener('click', function () {
|
|
setVal('skipLoad', questionsTickBox.checked)
|
|
var msg = ''
|
|
if (getVal('skipLoad')) { msg = 'Passzív mód bekapcsolva, mostantól kérdések nem lesznek betöltve/lekérve.' } else { msg = 'Passzív mód kikapcsolva, frissíts az érvénybe lépéshez!' }
|
|
|
|
ShowMessage({
|
|
m: msg,
|
|
isSimple: true
|
|
}, 6)
|
|
})
|
|
var loadDataCheckBoxText = CreateNodeWithText(questionsTickBox,
|
|
'Passzív mód', 'span')
|
|
loadDataCheckBoxText.style.fontSize = '12px'
|
|
|
|
menuButtonDiv.appendChild(loadDataCheckBoxText)
|
|
|
|
addEventListener(window, 'resize', function () {
|
|
menuButtonDiv.style.left = window.innerWidth - buttonWidth * 2 + 'px'
|
|
})
|
|
|
|
appedtTo.appendChild(menuButtonDiv)
|
|
} catch (e) {
|
|
Exception(e, 'script error at showing menu:')
|
|
}
|
|
}
|
|
|
|
// shows a fancy menu list with the subjects
|
|
function ShowMenuList () {
|
|
try {
|
|
var appedtTo = document.body // will be appended here
|
|
|
|
var menuDiv = document.createElement('div')
|
|
menuDiv.setAttribute('id', 'HelperMenu')
|
|
menuDiv.style.width = (window.innerWidth / 2) + 'px'
|
|
menuDiv.style.top = (window.innerHeight / 10) + 'px'
|
|
menuDiv.style.left = window.innerWidth / 2 - (window.innerWidth / 2) / 2 + 'px'
|
|
menuDiv.style.zIndex = 999999
|
|
menuDiv.style.position = 'fixed'
|
|
|
|
// design
|
|
menuDiv.style.textAlign = 'center'
|
|
menuDiv.style.padding = '0px'
|
|
menuDiv.style.background = '#222d32' // background color
|
|
menuDiv.style.color = '#ffffff' // text color
|
|
menuDiv.style.borderColor = '#035a8f' // border color
|
|
menuDiv.style.border = 'none'
|
|
menuDiv.style.opacity = '1' // setting starting opacity
|
|
|
|
var fiveMargin = '5px 5px 5px 5px'
|
|
var tbl = document.createElement('table')
|
|
tbl.style.margin = fiveMargin
|
|
tbl.style.textAlign = 'left'
|
|
tbl.style.width = '98%'
|
|
|
|
// adding headers ---------------------------------------------------------------------------------------------------------------
|
|
var subjTable = document.createElement('div')
|
|
subjTable.style.margin = fiveMargin
|
|
subjTable.style.textAlign = 'left'
|
|
subjTable.style.width = '98%'
|
|
|
|
if (data && data.length > 0) {
|
|
let grouped = data.Subjects.reduce((res, s) => {
|
|
let sName = s.getSubjNameWithoutYear()
|
|
if (sName) {
|
|
if (!res[sName]) {
|
|
res[sName] = []
|
|
}
|
|
res[sName].push(s)
|
|
} else {
|
|
res.others.push(s)
|
|
}
|
|
return res
|
|
}, {
|
|
others: []
|
|
})
|
|
|
|
const ordered = {} // FIXME: this shouldt be ordered here.
|
|
// subjects should be sorted in 'data', but the active indexes would be incorrect then
|
|
Object.keys(grouped).sort().forEach((key) => {
|
|
ordered[key] = grouped[key]
|
|
})
|
|
|
|
grouped = ordered
|
|
|
|
let collapsibles = []
|
|
|
|
Object.entries(grouped).forEach(([subjName, subjGroup]) => {
|
|
let b = CreateNodeWithText(subjTable, subjName, 'button')
|
|
b.style.backgroundColor = '#222d32'
|
|
b.style.color = '#ffffff'
|
|
b.style.cursor = 'pointer'
|
|
b.style.padding = '5px'
|
|
b.style.width = '100%'
|
|
b.style.border = 'none'
|
|
b.style.textAlign = 'left'
|
|
b.style.outline = 'none'
|
|
collapsibles.push(b)
|
|
|
|
let content = document.createElement('div')
|
|
content.style.padding = '0 18px'
|
|
content.style.overflow = 'hidden'
|
|
content.style.backgroundColor = '#222d32'
|
|
content.style.borderColor = '#212127'
|
|
content.style.borderStyle = 'solid'
|
|
content.style.borderWidth = '5px'
|
|
let ifGroupActive = subjGroup.some((x) => {
|
|
return x.getIfActive()
|
|
})
|
|
content.style.display = ifGroupActive ? 'block' : 'none'
|
|
|
|
subjTable.appendChild(content)
|
|
|
|
subjGroup.forEach((subj) => {
|
|
let tbl = document.createElement('table')
|
|
content.appendChild(tbl)
|
|
|
|
var row = tbl.insertRow()
|
|
let td = row.insertCell()
|
|
let text = subj.getYear() || subj.Name
|
|
if (subj.length !== 0) { text += ' [ ' + subj.length + 'db ]' }
|
|
CreateNodeWithText(td, text)
|
|
|
|
td = row.insertCell()
|
|
let checkbox = document.createElement('input') // new paragraph
|
|
checkbox.type = 'checkbox'
|
|
checkbox.style.background = 'white'
|
|
checkbox.style.margin = '5px 5px 5px 5px' // fancy margin
|
|
td.appendChild(checkbox) // adding text box to main td
|
|
|
|
checkbox.checked = subj.active
|
|
let i = subj.getIndex()
|
|
checkbox.setAttribute('id', 'HelperTextNode' + i)
|
|
checkbox.addEventListener('click', function () {
|
|
var checked = document.getElementById('HelperTextNode' + i).checked
|
|
data.ChangeActive(subj.Name, checked)
|
|
}) // adding click
|
|
})
|
|
})
|
|
|
|
collapsibles.forEach((x) => {
|
|
x.addEventListener('click', function () {
|
|
this.classList.toggle('active')
|
|
var content = this.nextElementSibling
|
|
if (content.style.display === 'block') {
|
|
content.style.display = 'none'
|
|
} else {
|
|
content.style.display = 'block'
|
|
}
|
|
})
|
|
})
|
|
|
|
var scrollDiv = document.createElement('div')
|
|
scrollDiv.style.width = '100%'
|
|
scrollDiv.style.height = window.innerHeight - (window.innerHeight * 0.4) + 'px'
|
|
scrollDiv.style.overflow = 'auto'
|
|
|
|
scrollDiv.appendChild(subjTable)
|
|
|
|
var subjtblrow = tbl.insertRow()
|
|
var subjtbltd = subjtblrow.insertCell()
|
|
subjtbltd.appendChild(scrollDiv)
|
|
} else { // if no data
|
|
var noDataRow = tbl.insertRow()
|
|
var noDataRowCell = noDataRow.insertCell()
|
|
let textBox
|
|
|
|
if (getVal('skipLoad')) {
|
|
textBox = CreateNodeWithText(noDataRowCell,
|
|
'Passszív mód bekapcsolva. Kapcsold ki a kérdések betöltéséhez!'
|
|
)
|
|
} else {
|
|
textBox = CreateNodeWithText(noDataRowCell,
|
|
'A kérdéseket nem lehetett beolvasni. Vagy nem elérhető a szerver, vagy ha offline módot használsz, akkor hibás a fájl elérési útja, vagy a fájl maga. Olvasd el a manualt!'
|
|
)
|
|
}
|
|
textBox.style.margin = fiveMargin // fancy margin
|
|
}
|
|
|
|
// show splash tickbox -----------------------------------------------------------------------------------------------------------------------------
|
|
var splasTickboxRow = tbl.insertRow()
|
|
var splashTickboxCell = splasTickboxRow.insertCell()
|
|
|
|
var splashTickBox = document.createElement('input')
|
|
splashTickBox.type = 'checkbox'
|
|
splashTickBox.checked = getVal('showSplash') || false
|
|
splashTickBox.style.position = ''
|
|
// splashTickBox.style.background = "white";
|
|
splashTickBox.style.left = 10 + 'px'
|
|
splashTickBox.style.margin = '5px 5px 5px 5px' // fancy margin
|
|
splashTickBox.style.top = menuDiv.offsetHeight + 'px'
|
|
splashTickboxCell.appendChild(splashTickBox) // adding to main div
|
|
|
|
splashTickBox.addEventListener('click', function () {
|
|
setVal('showSplash', splashTickBox.checked)
|
|
}) // adding clicktextNode
|
|
|
|
CreateNodeWithText(splashTickboxCell, 'Üdvözlő üzenet mutatása minden oldalon', 'span')
|
|
|
|
// show questons tickbox -----------------------------------------------------------------------------------------------------------------------------
|
|
var questionTickboxRow = tbl.insertRow()
|
|
var questionTickboxCell = questionTickboxRow.insertCell()
|
|
|
|
var questionsTickBox = document.createElement('input')
|
|
questionsTickBox.type = 'checkbox'
|
|
questionsTickBox.checked = getVal('showQuestions')
|
|
questionsTickBox.style.position = ''
|
|
// questionsTickBox.style.background = "white";
|
|
questionsTickBox.style.left = 10 + 'px'
|
|
questionsTickBox.style.margin = '5px 5px 5px 5px' // fancy margin
|
|
questionsTickBox.style.top = menuDiv.offsetHeight + 'px'
|
|
questionTickboxCell.appendChild(questionsTickBox) // adding to main div
|
|
|
|
questionsTickBox.addEventListener('click', function () {
|
|
setVal('showQuestions', questionsTickBox.checked)
|
|
if (!questionsTickBox.checked) {
|
|
ShowMessage({
|
|
m: 'Szinte mindég jó az talált válasz a kérdésre, de attól még könnyen előfordulhat, hogy rosz kérdésre írja ki a választ! Ez a opció nélkül ezt az ellenőrzési lehetőséget nem tudod kihasználni',
|
|
isSimple: true
|
|
}, 7)
|
|
}
|
|
}) // adding clicktextNode
|
|
|
|
CreateNodeWithText(questionTickboxCell, 'Kérdések mutatása válaszhoz', 'span')
|
|
|
|
// setting up buttons
|
|
var buttonRow = tbl.insertRow()
|
|
var buttonCell = buttonRow.insertCell()
|
|
buttonCell.style.textAlign = 'center'
|
|
// x button ------------------------------------------------------------------------------------------------------------------------------
|
|
var xButton = CreateNodeWithText(buttonCell, 'Bezárás', 'button')
|
|
|
|
xButton.style.position = ''
|
|
xButton.style.left = 10 + 'px'
|
|
xButton.style.margin = '5px 5px 5px 5px' // fancy margin
|
|
xButton.style.top = menuDiv.offsetHeight + 'px'
|
|
|
|
xButton.addEventListener('click', function () {
|
|
CloseMenu()
|
|
}) // adding clicktextNode
|
|
// help button ----------------------------------------------------------------------------------------------------------------
|
|
var helpButton = CreateNodeWithText(buttonCell, 'Help', 'button')
|
|
|
|
helpButton.style.position = ''
|
|
helpButton.style.left = 10 + 'px'
|
|
helpButton.style.margin = '5px 5px 5px 5px' // fancy margin
|
|
helpButton.style.top = menuDiv.offsetHeight + 'px'
|
|
|
|
helpButton.addEventListener('click', function () {
|
|
ShowHelp()
|
|
}) // adding clicktextNode
|
|
|
|
// site link ----------------------------------------------------------------------------------------------------------------
|
|
|
|
var siteLink = CreateNodeWithText(buttonCell, 'Help', 'button')
|
|
siteLink.innerText = 'Weboldal'
|
|
|
|
siteLink.addEventListener('click', function () {
|
|
location.href = serverAdress + 'menuClick' // eslint-disable-line
|
|
})
|
|
|
|
// addEventListener(window, 'scroll', function () {
|
|
// menuDiv.style.top = (pageYOffset + window.innerHeight / 3) + 'px';
|
|
// })
|
|
addEventListener(window, 'resize', function () {
|
|
menuDiv.style.left = window.innerWidth / 2 + 'px'
|
|
})
|
|
|
|
menuDiv.appendChild(tbl)
|
|
appedtTo.appendChild(menuDiv)
|
|
} catch (e) {
|
|
Exception(e, 'script error at showing menu list:')
|
|
}
|
|
|
|
document.addEventListener('keydown', EscClose)
|
|
}
|
|
|
|
function EscClose (e) {
|
|
if (e.keyCode === 27) { CloseMenu() }
|
|
}
|
|
|
|
function CloseMenu () {
|
|
document.getElementById('HelperMenu').parentNode.removeChild(document.getElementById(
|
|
'HelperMenu'))
|
|
|
|
document.removeEventListener('keydown', EscClose)
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Generic utils {{{
|
|
function GetId () {
|
|
let currId = getVal('clientId')
|
|
if (currId) {
|
|
return currId
|
|
} else {
|
|
currId = new Date()
|
|
currId = currId.getTime() + Math.floor(Math.random() * 10000000)
|
|
setVal('clientId', currId.toString())
|
|
return currId
|
|
}
|
|
}
|
|
|
|
function CreateNodeWithText (to, text, type) {
|
|
var paragraphElement = document.createElement(type || 'p') // new paragraph
|
|
var textNode = document.createTextNode(text)
|
|
paragraphElement.appendChild(textNode)
|
|
to.appendChild(paragraphElement)
|
|
return paragraphElement
|
|
}
|
|
|
|
function SendXHRMessage (message) {
|
|
var url = serverAdress + 'isAdding'
|
|
xmlhttpRequest({
|
|
method: 'POST',
|
|
url: url,
|
|
data: message,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
},
|
|
onerror: function (response) {
|
|
Log('XMLHTTP request POST error')
|
|
}
|
|
})
|
|
}
|
|
|
|
function OpenErrorPage (e) {
|
|
let path = 'lred'
|
|
try {
|
|
if (e.message || e.stack) {
|
|
path += '?'
|
|
}
|
|
if (e.message) {
|
|
path += 'msg:' + SUtils.SimplifyQuery(e.message)
|
|
}
|
|
if (e.stack) {
|
|
path += '___stack:' + SUtils.SimplifyStack(e.stack)
|
|
}
|
|
path += '___version:' + info().script.version
|
|
path = SUtils.RemoveSpecialChars(path)
|
|
} catch (e) {
|
|
Exception(e, 'error at setting error stack/msg link')
|
|
}
|
|
path = path.replace(/ /g, '_')
|
|
openInTab(serverAdress + path, {
|
|
active: true
|
|
})
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Help {{{
|
|
|
|
// shows some neat help
|
|
function ShowHelp () {
|
|
openInTab(serverAdress + 'manual', {
|
|
active: true
|
|
})
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// I am not too proud to cry that He and he
|
|
// Will never never go out of my mind.
|
|
// All his bones crying, and poor in all but pain,
|
|
|
|
// Being innocent, he dreaded that he died
|
|
// Hating his God, but what he was was plain:
|
|
// An old kind man brave in his burning pride.
|
|
|
|
// The sticks of the house were his; his books he owned.
|
|
// Even as a baby he had never cried;
|
|
// Nor did he now, save to his secret wound.
|
|
|
|
// Out of his eyes I saw the last light glide.
|
|
// Here among the liught of the lording sky
|
|
// An old man is with me where I go
|
|
|
|
// Walking in the meadows of his son's eye
|
|
// Too proud to cry, too frail to check the tears,
|
|
// And caught between two nights, blindness and death.
|
|
|
|
// O deepest wound of all that he should die
|
|
// On that darkest day.
|
|
})(); // eslint-disable-line
|