From 8a2b3260841fc5c2e0d24758bf94628ac52300d3 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 12 Feb 2018 19:54:51 +0100 Subject: [PATCH] Harmonize with web version, better style, fix for Firefox 45.9.0 ESR --- public/javascripts/assessment.js | 2 +- public/javascripts/components/statements.js | 1 + public/javascripts/course.js | 620 ++++++++++---------- public/javascripts/courseList.js | 108 ++-- public/javascripts/login.js | 148 +++-- public/stylesheets/assessment.css | 7 +- public/stylesheets/course.css | 12 +- routes/assessments.js | 3 +- views/assessment.pug | 2 +- views/course.pug | 8 +- 10 files changed, 458 insertions(+), 453 deletions(-) diff --git a/public/javascripts/assessment.js b/public/javascripts/assessment.js index ad309d7..acce548 100644 --- a/public/javascripts/assessment.js +++ b/public/javascripts/assessment.js @@ -12,7 +12,7 @@ function checkWindowSize() return window.innerWidth >= screen.width-3 && window.innerHeight >= screen.height-3; }; -let V = new Vue({ +new Vue({ el: "#assessment", data: { assessment: assessment, diff --git a/public/javascripts/components/statements.js b/public/javascripts/components/statements.js index 67aaf12..819d723 100644 --- a/public/javascripts/components/statements.js +++ b/public/javascripts/components/statements.js @@ -48,6 +48,7 @@ Vue.component("statements", { change: e => { this.answers.inputs[i][idx] = e.target.checked; }, }, }, + [ '' ] //to work in Firefox 45.9 ESR @ ENSTA... ) ); option.push( diff --git a/public/javascripts/course.js b/public/javascripts/course.js index e0f2e37..8064eaf 100644 --- a/public/javascripts/course.js +++ b/public/javascripts/course.js @@ -3,353 +3,349 @@ // Use open mode for question banks: add setting "nbQuestions" to show nbQuestions // at random among active questions -window.onload = function() { - - V = new Vue({ - el: '#course', - data: { - display: "assessments", //or "students", or "grades" (admin mode) - course: course, - mode: "view", //or "edit" (some assessment) - // assessment data: - monitorPwd: "", - newAssessment: { name: "" }, - assessmentArray: assessmentArray, - assessmentIndex: 0, //current edited assessment index - assessment: { }, //copy of assessment at editing index in array - assessmentText: "", //questions in an assessment, in text format - // grades data: - settings: { - totalPoints: 20, - halfPoints: false, - zeroSum: false, - }, - group: 1, //for detailed grades tables - grades: { }, //computed +new Vue({ + el: '#course', + data: { + display: "assessments", //or "students", or "grades" (admin mode) + course: course, + mode: "view", //or "edit" (some assessment) + // assessment data: + monitorPwd: "", + newAssessment: { name: "" }, + assessmentArray: assessmentArray, + assessmentIndex: 0, //current edited assessment index + assessment: { }, //copy of assessment at editing index in array + assessmentText: "", //questions in an assessment, in text format + // grades data: + settings: { + totalPoints: 20, + halfPoints: false, + zeroSum: false, }, - mounted: function() { - $('.modal').each( (i,elem) => { - if (elem.id != "assessmentEdit") - $(elem).modal(); - }); - $('ul.tabs').tabs(); - $('#assessmentEdit').modal({ - complete: () => { - this.parseAssessment(); - Vue.nextTick( () => { - $("#questionList").find("code[class^=language-]").each( (i,elem) => { - Prism.highlightElement(elem); - }); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,"questionList"]); + group: 1, //for detailed grades tables + grades: { }, //computed + }, + mounted: function() { + $('.modal').each( (i,elem) => { + if (elem.id != "assessmentEdit") + $(elem).modal(); + }); + $('ul.tabs').tabs(); + $('#assessmentEdit').modal({ + complete: () => { + this.parseAssessment(); + Vue.nextTick( () => { + $("#questionList").find("code[class^=language-]").each( (i,elem) => { + Prism.highlightElement(elem); }); - }, - }); - }, - methods: { - // GENERAL: - toggleDisplay: function(area) { - if (this.display == area) - this.display = ""; - else - this.display = area; - }, - studentList: function(group) { - return this.course.students - .filter( s => { return group==0 || s.group == group; }) - .map( s => { return Object.assign({}, s); }) //not altering initial array - .sort( (a,b) => { return a.name.localeCompare(b.name); }) - }, - // STUDENTS: - uploadTrigger: function() { - $("#upload").click(); - }, - upload: function(e) { - let file = (e.target.files || e.dataTransfer.files)[0]; - Papa.parse(file, { - header: true, - skipEmptyLines: true, - complete: (results,file) => { - let students = [ ]; - // Post-process: add group/number if missing - let number = 1; - results.data.forEach( d => { - if (!d.group) - d.group = 1; - if (!d.number) - d.number = number++; - if (typeof d.number !== "string") - d.number = d.number.toString(); - students.push(d); - }); - $.ajax("/import/students", { - method: "POST", - data: { - cid: this.course._id, - students: JSON.stringify(students), - }, - dataType: "json", - success: res => { - if (!res.errmsg) - this.course.students = students; - else - alert(res.errmsg); - }, - }); - }, + MathJax.Hub.Queue(["Typeset",MathJax.Hub,"questionList"]); }); }, - // ASSESSMENT: - addAssessment: function() { - if (!admin) - return; - // modal, fill code and description - let error = Validator.checkObject(this.newAssessment, "Assessment"); - if (!!error) - return alert(error); - else - $('#newAssessment').modal('close'); - $.ajax("/add/assessment", - { - method: "GET", + }); + }, + methods: { + // GENERAL: + toggleDisplay: function(area) { + if (this.display == area) + this.display = ""; + else + this.display = area; + }, + studentList: function(group) { + return this.course.students + .filter( s => { return group==0 || s.group == group; }) + .map( s => { return Object.assign({}, s); }) //not altering initial array + .sort( (a,b) => { return a.name.localeCompare(b.name); }) + }, + // STUDENTS: + uploadTrigger: function() { + $("#upload").click(); + }, + upload: function(e) { + let file = (e.target.files || e.dataTransfer.files)[0]; + Papa.parse(file, { + header: true, + skipEmptyLines: true, + complete: (results,file) => { + let students = [ ]; + // Post-process: add group/number if missing + let number = 1; + results.data.forEach( d => { + if (!d.group) + d.group = 1; + if (!d.number) + d.number = number++; + if (typeof d.number !== "string") + d.number = d.number.toString(); + students.push(d); + }); + $.ajax("/import/students", { + method: "POST", data: { - name: this.newAssessment.name, - cid: course._id, + cid: this.course._id, + students: JSON.stringify(students), }, dataType: "json", success: res => { if (!res.errmsg) - { - this.newAssessment["name"] = ""; - this.assessmentArray.push(res); - } + this.course.students = students; else alert(res.errmsg); }, - } - ); - }, - materialOpenModal: function(id) { - $("#" + id).modal("open"); - Materialize.updateTextFields(); //textareas, time field... - }, - updateAssessment: function() { - $.ajax("/update/assessment", { - method: "POST", - data: {assessment: JSON.stringify(this.assessment)}, + }); + }, + }); + }, + // ASSESSMENT: + addAssessment: function() { + if (!admin) + return; + // modal, fill code and description + let error = Validator.checkObject(this.newAssessment, "Assessment"); + if (!!error) + return alert(error); + else + $('#newAssessment').modal('close'); + $.ajax("/add/assessment", + { + method: "GET", + data: { + name: this.newAssessment.name, + cid: course._id, + }, dataType: "json", success: res => { if (!res.errmsg) { - this.assessmentArray[this.assessmentIndex] = this.assessment; - this.mode = "view"; + this.newAssessment["name"] = ""; + this.assessmentArray.push(res); } else alert(res.errmsg); }, - }); - }, - deleteAssessment: function(assessment) { - if (!admin) - return; - if (confirm("Delete assessment '" + assessment.name + "' ?")) - { - $.ajax("/remove/assessment", - { - method: "GET", - data: { qid: this.assessment._id }, - dataType: "json", - success: res => { - if (!res.errmsg) - this.assessmentArray.splice( this.assessmentArray.findIndex( item => { - return item._id == assessment._id; - }), 1 ); - else - alert(res.errmsg); - }, - } - ); } - }, - toggleState: function(questionIndex) { - // add or remove from activeSet of current assessment - let activeIndex = this.assessment.activeSet.findIndex( item => { return item == questionIndex; }); - if (activeIndex >= 0) - this.assessment.activeSet.splice(activeIndex, 1); - else - this.assessment.activeSet.push(questionIndex); - }, - setAssessmentText: function() { - let txt = ""; - this.assessment.questions.forEach( q => { - txt += q.wording + "\n"; - q.options.forEach( (o,i) => { - let symbol = q.answer.includes(i) ? "+" : "-"; - txt += symbol + " " + o + "\n"; - }); - txt += "\n"; //separate questions by new line - }); - this.assessmentText = txt; - }, - parseAssessment: function() { - let questions = [ ]; - let lines = this.assessmentText.split("\n").map( L => { return L.trim(); }) - lines.push(""); //easier parsing - let emptyQuestion = () => { - return { - wording: "", - options: [ ], - answer: [ ], - active: true, //default - }; - }; - let q = emptyQuestion(); - lines.forEach( L => { - if (L.length > 0) + ); + }, + materialOpenModal: function(id) { + $("#" + id).modal("open"); + Materialize.updateTextFields(); //textareas, time field... + }, + updateAssessment: function() { + $.ajax("/update/assessment", { + method: "POST", + data: {assessment: JSON.stringify(this.assessment)}, + dataType: "json", + success: res => { + if (!res.errmsg) { - if (['+','-'].includes(L.charAt(0))) - { - if (L.charAt(0) == '+') - q.answer.push(q.options.length); - q.options.push(L.slice(1).trim()); - } - else if (L.charAt(0) == '*') - { - // TODO: read current + next lines into q.answer (HTML, 1-elem array) - } - else - q.wording += L + " "; //space required at line breaks, generally + this.assessmentArray[this.assessmentIndex] = this.assessment; + this.mode = "view"; } else - { - // Flush current question (if any) - if (q.wording.length > 0) - { - questions.push(q); - q = emptyQuestion(); - } - } - }); - this.assessment.questions = questions; - }, - actionAssessment: function(index) { - if (admin) - { - // Edit screen - this.assessmentIndex = index; - this.assessment = $.extend(true, {}, this.assessmentArray[index]); - this.setAssessmentText(); - this.mode = "edit"; - Vue.nextTick( () => { - $("#questionList").find("code[class^=language-]").each( (i,elem) => { - Prism.highlightElement(elem); - }); - MathJax.Hub.Queue(["Typeset",MathJax.Hub,"questionList"]); - }); - } - else //external user: show assessment - this.redirect(this.assessmentArray[index].name); - }, - redirect: function(assessmentName) { - document.location.href = "/" + initials + "/" + course.code + "/" + assessmentName; - }, - setPassword: function() { - let hashPwd = Sha1.Compute(this.monitorPwd); - let error = Validator.checkObject({password:hashPwd}, "Course"); - if (error.length > 0) - return alert(error); - $.ajax("/set/password", + alert(res.errmsg); + }, + }); + }, + deleteAssessment: function(assessment) { + if (!admin) + return; + if (confirm("Delete assessment '" + assessment.name + "' ?")) + { + $.ajax("/remove/assessment", { method: "GET", - data: { - cid: this.course._id, - pwd: hashPwd, - }, + data: { qid: this.assessment._id }, dataType: "json", success: res => { if (!res.errmsg) - alert("Password saved!"); + this.assessmentArray.splice( this.assessmentArray.findIndex( item => { + return item._id == assessment._id; + }), 1 ); else alert(res.errmsg); }, } ); - }, - // NOTE: artifact required for Vue v-model to behave well - checkBoxFixedId: function(i) { - return "questionFixed" + i; - }, - checkBoxActiveId: function(i) { - return "questionActive" + i; - }, - // GRADES: - gradeSettings: function() { - $("#gradeSettings").modal("open"); - Materialize.updateTextFields(); //total points field in grade settings overlap - }, - download: function() { - // Download (all) grades as a CSV file - let data = [ ]; - this.studentList(0).forEach( s => { - let finalGrade = 0.; - let gradesCount = 0; - if (!!this.grades[s.number]) + } + }, + toggleState: function(questionIndex) { + // add or remove from activeSet of current assessment + let activeIndex = this.assessment.activeSet.findIndex( item => { return item == questionIndex; }); + if (activeIndex >= 0) + this.assessment.activeSet.splice(activeIndex, 1); + else + this.assessment.activeSet.push(questionIndex); + }, + setAssessmentText: function() { + let txt = ""; + this.assessment.questions.forEach( q => { + txt += q.wording; //already ended by \n + q.options.forEach( (o,i) => { + let symbol = q.answer.includes(i) ? "+" : "-"; + txt += symbol + " " + o + "\n"; + }); + txt += "\n"; //separate questions by new line + }); + this.assessmentText = txt; + }, + parseAssessment: function() { + let questions = [ ]; + let lines = this.assessmentText.split("\n").map( L => { return L.trim(); }) + lines.push(""); //easier parsing + let emptyQuestion = () => { + return { + wording: "", + options: [ ], + answer: [ ], + active: true, //default + }; + }; + let q = emptyQuestion(); + lines.forEach( L => { + if (L.length > 0) + { + if (['+','-'].includes(L.charAt(0))) { - Object.keys(this.grades[s.number]).forEach( assessmentName => { - s[assessmentName] = this.grades[s.number][assessmentName]; - if (_.isNumeric(s[assessmentName]) && !isNaN(s[assessmentName])) - { - finalGrade += s[assessmentName]; - gradesCount++; - } - if (gradesCount >= 1) - finalGrade /= gradesCount; - s["final"] = finalGrade; //TODO: forbid "final" as assessment name - }); + if (L.charAt(0) == '+') + q.answer.push(q.options.length); + q.options.push(L.slice(1).trim()); } - data.push(s); //number,name,group,assessName1...assessNameN,final - }); - let csv = Papa.unparse(data, { - quotes: true, - header: true, - }); - let downloadAnchor = $("#download"); - downloadAnchor.attr("download", this.course.code + "_results.csv"); - downloadAnchor.attr("href", "data:text/plain;charset=utf-8," + encodeURIComponent(csv)); - this.$refs.download.click() - //downloadAnchor.click(); //fails - }, - showDetails: function(group) { - this.group = group; - $("#detailedGrades").modal("open"); - }, - groupList: function() { - let maxGrp = 1; - this.course.students.forEach( s => { - if (s.group > maxGrp) - maxGrp = s.group; + else if (L.charAt(0) == '*') + { + // TODO: read current + next lines into q.answer (HTML, 1-elem array) + } + else + q.wording += L + "\n"; + } + else + { + // Flush current question (if any) + if (q.wording.length > 0) + { + questions.push(q); + q = emptyQuestion(); + } + } + }); + this.assessment.questions = questions; + }, + actionAssessment: function(index) { + if (admin) + { + // Edit screen + this.assessmentIndex = index; + this.assessment = $.extend(true, {}, this.assessmentArray[index]); + this.setAssessmentText(); + this.mode = "edit"; + Vue.nextTick( () => { + $("#questionList").find("code[class^=language-]").each( (i,elem) => { + Prism.highlightElement(elem); + }); + MathJax.Hub.Queue(["Typeset",MathJax.Hub,"questionList"]); }); - return _.range(1,maxGrp+1); - }, - grade: function(assessmentIndex, studentNumber) { - if (!this.grades[assessmentIndex] || !this.grades[assessmentIndex][studentNumber]) - return ""; //no grade yet - return this.grades[assessmentIndex][studentNumber]; - }, - groupId: function(group, prefix) { - return (prefix || "") + "group" + group; - }, - togglePresence: function(number, index) { - // UNIMPLEMENTED - // TODO: if no grade (thus automatic 0), toggle "exempt" state on student for current exam - // --> automatic update of grades view (just a few number to change) - }, - computeGrades: function() { - // UNIMPLEMENTED - // TODO: compute all grades using settings (points, coefficients, bonus/malus...). - // If some questions with free answers (open), display answers and ask teacher action. - // TODO: need a setting for that too (by student, by exercice, by question) - }, + } + else //external user: show assessment + this.redirect(this.assessmentArray[index].name); }, - }); - -}; + redirect: function(assessmentName) { + document.location.href = "/" + initials + "/" + course.code + "/" + assessmentName; + }, + setPassword: function() { + let hashPwd = Sha1.Compute(this.monitorPwd); + let error = Validator.checkObject({password:hashPwd}, "Course"); + if (error.length > 0) + return alert(error); + $.ajax("/set/password", + { + method: "GET", + data: { + cid: this.course._id, + pwd: hashPwd, + }, + dataType: "json", + success: res => { + if (!res.errmsg) + alert("Password saved!"); + else + alert(res.errmsg); + }, + } + ); + }, + // NOTE: artifact required for Vue v-model to behave well + checkBoxFixedId: function(i) { + return "questionFixed" + i; + }, + checkBoxActiveId: function(i) { + return "questionActive" + i; + }, + // GRADES: + gradeSettings: function() { + $("#gradeSettings").modal("open"); + Materialize.updateTextFields(); //total points field in grade settings overlap + }, + download: function() { + // Download (all) grades as a CSV file + let data = [ ]; + this.studentList(0).forEach( s => { + let finalGrade = 0.; + let gradesCount = 0; + if (!!this.grades[s.number]) + { + Object.keys(this.grades[s.number]).forEach( assessmentName => { + s[assessmentName] = this.grades[s.number][assessmentName]; + if (_.isNumeric(s[assessmentName]) && !isNaN(s[assessmentName])) + { + finalGrade += s[assessmentName]; + gradesCount++; + } + if (gradesCount >= 1) + finalGrade /= gradesCount; + s["final"] = finalGrade; //TODO: forbid "final" as assessment name + }); + } + data.push(s); //number,name,group,assessName1...assessNameN,final + }); + let csv = Papa.unparse(data, { + quotes: true, + header: true, + }); + let downloadAnchor = $("#download"); + downloadAnchor.attr("download", this.course.code + "_results.csv"); + downloadAnchor.attr("href", "data:text/plain;charset=utf-8," + encodeURIComponent(csv)); + this.$refs.download.click() + //downloadAnchor.click(); //fails + }, + showDetails: function(group) { + this.group = group; + $("#detailedGrades").modal("open"); + }, + groupList: function() { + let maxGrp = 1; + this.course.students.forEach( s => { + if (s.group > maxGrp) + maxGrp = s.group; + }); + return _.range(1,maxGrp+1); + }, + grade: function(assessmentIndex, studentNumber) { + if (!this.grades[assessmentIndex] || !this.grades[assessmentIndex][studentNumber]) + return ""; //no grade yet + return this.grades[assessmentIndex][studentNumber]; + }, + groupId: function(group, prefix) { + return (prefix || "") + "group" + group; + }, + togglePresence: function(number, index) { + // UNIMPLEMENTED + // TODO: if no grade (thus automatic 0), toggle "exempt" state on student for current exam + // --> automatic update of grades view (just a few number to change) + }, + computeGrades: function() { + // UNIMPLEMENTED + // TODO: compute all grades using settings (points, coefficients, bonus/malus...). + // If some questions with free answers (open), display answers and ask teacher action. + // TODO: need a setting for that too (by student, by exercice, by question) + }, + }, +}); diff --git a/public/javascripts/courseList.js b/public/javascripts/courseList.js index 238c1fc..020af55 100644 --- a/public/javascripts/courseList.js +++ b/public/javascripts/courseList.js @@ -1,69 +1,65 @@ -window.onload = function() { - - new Vue({ - el: '#courseList', - data: { - courseArray: courseArray, - newCourse: { - code: "", - description: "", - }, +new Vue({ + el: '#courseList', + data: { + courseArray: courseArray, + newCourse: { + code: "", + description: "", }, - mounted: function() { - $('.modal').modal(); + }, + mounted: function() { + $('.modal').modal(); + }, + methods: { + redirect: function(code) { + document.location.href = "/" + initials + "/" + code; }, - methods: { - redirect: function(code) { - document.location.href = "/" + initials + "/" + code; - }, - addCourse: function() { - if (!admin) - return; - // modal, fill code and description - let error = Validator.checkObject({code:this.newCourse.code}, "Course"); - if (!!error) - return alert(error); - else - $('#newCourse').modal('close'); - $.ajax("/add/course", + addCourse: function() { + if (!admin) + return; + // modal, fill code and description + let error = Validator.checkObject({code:this.newCourse.code}, "Course"); + if (!!error) + return alert(error); + else + $('#newCourse').modal('close'); + $.ajax("/add/course", + { + method: "GET", + data: this.newCourse, + dataType: "json", + success: res => { + if (!res.errmsg) + { + this.newCourse["code"] = ""; + this.newCourse["description"] = ""; + this.courseArray.push(res); + } + else + alert(res.errmsg); + }, + } + ); + }, + deleteCourse: function(course) { + if (!admin) + return; + if (confirm("Delete course '" + course.code + "' ?")) + $.ajax("/remove/course", { method: "GET", - data: this.newCourse, + data: { cid: course._id }, dataType: "json", success: res => { if (!res.errmsg) - { - this.newCourse["code"] = ""; - this.newCourse["description"] = ""; - this.courseArray.push(res); - } + this.courseArray.splice( this.courseArray.findIndex( item => { + return item._id == course._id; + }), 1 ); else alert(res.errmsg); }, } ); }, - deleteCourse: function(course) { - if (!admin) - return; - if (confirm("Delete course '" + course.code + "' ?")) - $.ajax("/remove/course", - { - method: "GET", - data: { cid: course._id }, - dataType: "json", - success: res => { - if (!res.errmsg) - this.courseArray.splice( this.courseArray.findIndex( item => { - return item._id == course._id; - }), 1 ); - else - alert(res.errmsg); - }, - } - ); - }, - } - }); - -}; + } +}); diff --git a/public/javascripts/login.js b/public/javascripts/login.js index 4f70e19..8752ba8 100644 --- a/public/javascripts/login.js +++ b/public/javascripts/login.js @@ -1,86 +1,82 @@ -window.onload = function() { - - const messages = { - "login": "Go", - "register": "Send", - }; +const messages = { + "login": "Go", + "register": "Send", +}; - const ajaxUrl = { - "login": "/sendtoken", - "register": "/register", - }; +const ajaxUrl = { + "login": "/sendtoken", + "register": "/register", +}; - const infos = { - "login": "Connection token sent. Check your emails!", - "register": "Registration complete! Please check your emails.", - }; +const infos = { + "login": "Connection token sent. Check your emails!", + "register": "Registration complete! Please check your emails.", +}; - const animationDuration = 300; //in milliseconds +const animationDuration = 300; //in milliseconds - // Basic anti-bot measure: force at least N seconds between arrival on page, and register form validation: - const enterTime = Date.now(); +// Basic anti-bot measure: force at least N seconds between arrival on page, and register form validation: +const enterTime = Date.now(); - new Vue({ - el: '#login', - data: { - messages: messages, - user: { - name: "", - email: "", - }, - stage: "login", //or "register" +new Vue({ + el: '#login', + data: { + messages: messages, + user: { + name: "", + email: "", }, - mounted: function() { - // https://laracasts.com/discuss/channels/vue/vuejs-set-focus-on-textfield - this.$refs.userEmail.focus(); + stage: "login", //or "register" + }, + mounted: function() { + // https://laracasts.com/discuss/channels/vue/vuejs-set-focus-on-textfield + this.$refs.userEmail.focus(); + }, + methods: { + toggleStage: function(stage) { + let $form = $("#form"); + $form.fadeOut(animationDuration); + setTimeout( () => { + this.stage = stage; + $form.show(0); + }, animationDuration); }, - methods: { - toggleStage: function(stage) { - let $form = $("#form"); - $form.fadeOut(animationDuration); - setTimeout( () => { - this.stage = stage; - $form.show(0); - }, animationDuration); - }, - submit: function() { - if (this.stage=="register") + submit: function() { + if (this.stage=="register") + { + if (Date.now() - enterTime < 5000) + return; + } + let error = Validator.checkObject({email: this.user.email}, "User"); + if (!error && this.stage == "register") + error = Validator.checkObject({name: this.user.name}, "User"); + let $dialog = $("#dialog"); + show($dialog); + setTimeout(() => {hide($dialog);}, 3000); + if (error.length > 0) + return showMsg($dialog, "error", error); + showMsg($dialog, "process", "Processing... Please wait"); + $.ajax(ajaxUrl[this.stage], { - if (Date.now() - enterTime < 5000) - return; - } - let error = Validator.checkObject({email: this.user.email}, "User"); - if (!error && this.stage == "register") - error = Validator.checkObject({name: this.user.name}, "User"); - let $dialog = $("#dialog"); - show($dialog); - setTimeout(() => {hide($dialog);}, 3000); - if (error.length > 0) - return showMsg($dialog, "error", error); - showMsg($dialog, "process", "Processing... Please wait"); - $.ajax(ajaxUrl[this.stage], + method: "GET", + data: { - method: "GET", - data: + email: encodeURIComponent(this.user.email), + name: encodeURIComponent(this.user.name), //may be unused + }, + dataType: "json", + success: res => { + if (!res.errmsg) { - email: encodeURIComponent(this.user.email), - name: encodeURIComponent(this.user.name), //may be unused - }, - dataType: "json", - success: res => { - if (!res.errmsg) - { - this.user["name"] = ""; - this.user["email"] = ""; - showMsg($dialog, "info", infos[this.stage]); - } - else - showMsg($dialog, "error", res.errmsg); - }, - } - ); - }, - } - }); - -}; + this.user["name"] = ""; + this.user["email"] = ""; + showMsg($dialog, "info", infos[this.stage]); + } + else + showMsg($dialog, "error", res.errmsg); + }, + } + ); + }, + } +}); diff --git a/public/stylesheets/assessment.css b/public/stylesheets/assessment.css index 26d9d75..7677063 100644 --- a/public/stylesheets/assessment.css +++ b/public/stylesheets/assessment.css @@ -28,6 +28,7 @@ button.sendAnswer { .question .wording { margin-bottom: 10px; + overflow: auto; } .question .option { @@ -61,6 +62,10 @@ table.in-question { } table.in-question th, table.in-question td { - padding: 3px; + padding: 7px; border-bottom: 1px solid grey; } + +/*table { border: none; border-collapse: collapse; }*/ +table.in-question td { border-left: 1px solid grey; } +table.in-question td:first-child { border-left: none; } diff --git a/public/stylesheets/course.css b/public/stylesheets/course.css index c1c4a9b..178e824 100644 --- a/public/stylesheets/course.css +++ b/public/stylesheets/course.css @@ -3,6 +3,11 @@ h4.title { background-color: lightgrey; } +.idle { + background-color: #EFEFEF; + opacity: 0.5; +} + tr.assessment { cursor: pointer; } @@ -33,6 +38,7 @@ tr.stats { .question .wording { margin-bottom: 10px; + overflow: auto; } .question .option { @@ -62,6 +68,10 @@ table.in-question { } table.in-question th, table.in-question td { - padding: 3px; + padding: 7px; border-bottom: 1px solid grey; } + +/*table { border: none; border-collapse: collapse; }*/ +table.in-question td { border-left: 1px solid grey; } +table.in-question td:first-child { border-left: none; } diff --git a/routes/assessments.js b/routes/assessments.js index a107d7e..b7dcfd9 100644 --- a/routes/assessments.js +++ b/routes/assessments.js @@ -11,9 +11,10 @@ const sanitizeHtml = require('sanitize-html'); const sanitizeOpts = { allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'img', 'u' ]), allowedAttributes: { - img: [ 'src' ], + img: [ 'src','style' ], code: [ 'class' ], table: [ 'class' ], + div: [ 'style' ], }, }; diff --git a/views/assessment.pug b/views/assessment.pug index d1f696f..9e8550f 100644 --- a/views/assessment.pug +++ b/views/assessment.pug @@ -18,7 +18,7 @@ block content .center-align a.modal-action.modal-close.waves-effect.waves-light.btn-flat(href="#!") Ok .row - .col.s12.m10.offset-m1.l8.offset-l2.xl6.offset-xl3 + .col.s12.m10.offset-m1 h4= assessment.name #stage0(v-show="stage==0") .card diff --git a/views/course.pug b/views/course.pug index 7aa6619..9de3570 100644 --- a/views/course.pug +++ b/views/course.pug @@ -90,12 +90,12 @@ block content tr.grade(v-for="student in studentList(group)") td {{ student.number }} td(v-for="(assessment,i) in assessmentArray" @click="togglePresence(student.number,i)") - {{ grade(i,student.number) }} + | {{ grade(i,student.number) }} .modal-footer .center-align a.modal-action.modal-close.waves-effect.waves-light.btn-flat(href="#!") Close .row(v-show="mode=='view'") - .col.s12.m10.offset-m1.l8.offset-l2.xl6.offset-xl3 + .col.s12.m10.offset-m1 if teacher h4.title(@click="toggleDisplay('students')") Students .card(v-show="display=='students'") @@ -128,7 +128,7 @@ block content th #Questions th Time tbody - tr.assessment(v-for="(assessment,i) in assessmentArray" v-if="assessment.active" + tr.assessment(v-for="(assessment,i) in assessmentArray" :class="{idle:!assessment.active}" @click.left="actionAssessment(i)" @contextmenu.prevent="deleteAssessment(assessment)") td {{ assessment.name }} td {{ assessment.mode }} @@ -162,7 +162,7 @@ block content td(colspan="4") Stats: range= stdev= mean= if teacher .row(v-show="mode=='edit'") - .col.s12.m10.offset-m1.l8.offset-l2.xl6.offset-xl3 + .col.s12.m10.offset-m1 h4 {{ assessment.name }} .card .center-align -- 2.44.0