ES6 Plato on Github
Report Home
Summary Display
quizMaker.mjs
Maintainability
69.43
Lines of code
284
Difficulty
41.57
Estimated Errors
1.98
Function weight
By Complexity
By SLOC
//const quizParser = require('./generated.quizParser'); import {parse} from '../generated/tmp/quizParser.mjs' import latinize from '../node_modules/latinize-to-ascii/latinize.mjs' import resultViewers from '../generated/tmp/resultViewers.mjs' import * as utility from './utilityFuncLib.mjs' import yaml from '../node_modules/js-yaml/dist/js-yaml.mjs' function normalizedString(str) { return latinize(str).replace(/[^A-Za-z0-9]/g, '-').replace(/-{2,}/g, '-'); } function quizMaker(multiPartTextQuiz,quizName=''){ const defaultKey = { "yaml":"options", "yml":"options", "css":"style", //"stylus":"style", "qqa":"quiz", } const parseFunc = { "yaml":yaml.load, "yml":yaml.load, "css":(css)=>css, //"stylus":"style", "qqa":parse, } let quizData = {}; //multiPartTextQuiz.replace(/\r\n/mg,'\n').replace(/\n\r/mg,'\n').replace(/\r/mg,'\n'); const partFinder = /^(?:``` *([^`\n]+)\n((?:[^`]|[^`]`|[^`]``)+)```|---+ *([^- \n]+) *---+\n((?:[^`-]|[^-]-|[^-]--|[^`]`|[^`]``)+)(?=^---+|$|^```$))/mg multiPartTextQuiz = multiPartTextQuiz.replace(partFinder,(all,head1,content1,head2,content2)=>{ const head = head1?head1:head2; const content = content1?content1:content2; const tmp = head.split(':'); const type = tmp.pop().trim(); let key = tmp.pop(); if(key) key = key.trim(); if(!key) key = defaultKey[type]; //console.log(`key : |${key}|`); //console.log(`type : |${type}|`); if(typeof quizData[key] !== "undefined") quizData[key] = utility.merge2(quizData[key], parseFunc[type](content)); else quizData[key] = parseFunc[type](content); return ''; }); //console.log('\n\nName:',quizName,'QuizData:',quizData, '\n\n\n\nmultiPartTextQuiz:',multiPartTextQuiz) quizData = utility.merge2(quizData, parse(multiPartTextQuiz)); if(!quizData.options) quizData.options = {}; if(!quizData.options.quizName) quizData.options.quizName = quizName || "Quiz"; if(quizData.quiz) quizData.questions = utility.merge2(quizData.quiz.questions, quizData.questions ||[]); insertId(quizData.questions); let html = templateIndex(quizData); if(quizData.options.quotation==='public') return {[`${normalizedString(quizData.options.quizName)}.html`]: html}; else return { [`${normalizedString(quizData.options.quizName)}.php`]: html, // formulaire avec css et js intégré pour la randomisation des ordres et l'interface paginée //"quotation.php": quizMarkdownLikeString, // analyse des réponses avec quotation (php), puis génération du bilan graphique et textuel (html svg css, js ?). }; } export default quizMaker //if(typeof module !== 'undefined' && module.exports) module.exports = quizMaker; function insertId(questions) { questions.forEach((q,i)=>{ q.id = `q${i}`; q.qs = questions; q.i = i; q.answers.forEach((a,i)=>{ a.id = `${q.id}a${i}`; a.q = q; }); }) } function templateAnswer(answer){ return `<label><input type="checkbox" id="${answer.id}" name="${answer.id}" value="1"/> <span>${answer.label}</span></label>`; } function templateQuestion(question){ return `<section class="question" id="${question.id}"><div class="page"> <h2 class="legend">${question.label}</h2> ${question.answers.map(templateAnswer).join('')} <footer> ${question.qs.obj.options.goBackButton?templateButton((question.qs[question.i-1] || {id:'top'}).id,'←','back'):'<div></div>'} ${templateButton((question.qs[question.i+1] || {id:'end'}).id)} </footer> </div></section>`; } function templateQuestions(questions){ return questions.map(templateQuestion).join('\n'); } function templateIndex(obj) { obj.questions.obj = obj; return `<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>${obj.options.quizName}</title> ${templateCss(obj.style)} </head> <body> <section id="top"><div class="page"><h1>${obj.options.quizName}</h1>${obj.options.headerText?`<p>${obj.options.headerText}</p>`:''}<footer><div></div>${templateButton(obj.questions[0].id)}</footer></div></section> <form action="quotation.php" method="get"> ${templateQuestions(obj.questions)} <section id="end"><div class="page"> <footer> ${obj.options.goBackButton?templateButton(obj.questions[obj.questions.length-1].id,'←','back'):'<div></div>'} <input type="submit" class="button"/> </footer> </div></section> </form> ${templateJs(obj)} </body> </html> `; } function templateButton(targetAnchor,label='→',htmlClass='next'){ // » → return `<a class="button ${htmlClass}" href="#${targetAnchor}">${label}</a>` } function templateCss(customCss= ''){ return ` <style> * {margin: 0; padding: 0; border: 0; box-sizing: border-box; font-family: sans-serif; color: #ccc; text-align: justify} body{background-color: #aaa; overflow: hidden;} section {padding: 1px 0;} .page { position: relative; display: flex; width: 90vw; height: 90vh; margin: 5vh 5vw; padding: 5vh 5vw; padding-bottom: calc(10vh + 2em ); flex-direction: column; background-color: #222; box-shadow: 1vh 1vw calc(1vh + 1vw) #666; justify-content: space-between; flex-wrap: wrap; } footer{position:absolute; bottom:5vh;left:5vw;right:5vw;display: flex;justify-content: space-between;} label, .legend, h1, p{display: flex; flex:1; align-items: center; padding: 1vh 5vw} .legend{flex: 1.5;} label:hover{ background-color: #333; box-shadow: .2vh .2vw calc(.2vh + .2vw) #111; } label > span {display: inline-block; padding: 0 1vw;} input[type=checkbox] {transform: scale(2);} h1{text-align: center; margin: auto;} input[type=submit], .button { background-color: #333; padding: 2vh 2vw; box-shadow: .2vh .2vw calc(.2vh + .2vw) #181818; text-decoration: none; font-size: 2em; } .errors { color:#f88; padding: 2vh 2vw; border: 1px #f88 dashed; line-height: 2em; } input[type=submit]:hover, .button:hover { background-color: #444; box-shadow: .2vh .2vw calc(.2vh + .2vw) #111; } .button.disable{opacity: .5;} ${customCss?customCss:''} </style> ` } function templateJs(obj){ let js =''; js +=` <script> ${utility.shuffle.toString()} ${utility.getUrlJson.toString()} </script>`; if(obj.options.answerOrder === 'random' || obj.options.random) js +=` <script> document.querySelectorAll('.question .page').forEach(q=>{ const items = shuffle([].slice.call(q.querySelectorAll('label'))); items.forEach(i=>q.appendChild(i)); q.appendChild(q.querySelector('footer')); }); </script>`; if(obj.options.questionOrder === 'random' || obj.options.random) js +=` <script> const parent = document.querySelector('form'); const items = shuffle([].slice.call(parent.querySelectorAll('.question'))); items.forEach(i=>parent.appendChild(i)); parent.appendChild(parent.querySelector('#end')); document.querySelectorAll('section').forEach((page,i,list)=>{ const back = page.querySelector('.back'); const next = page.querySelector('.next'); if(back) back.href = '#'+list[i-1].id; if(next) next.href = '#'+list[i+1].id; }); </script>`; // validations and error display js +=` <script> document.querySelectorAll('.question').forEach(question=>{ question.errorList = {}; question.isValid = ()=> !Object.keys(question.errorList).length; question.updateButton = ()=> { if(question.isValid()) question.querySelector('footer .button:last-child').classList.remove('disable'); else question.querySelector('footer .button:last-child').classList.add('disable'); }; question.updateErrorsDisplay = ()=>{ if(question.isValid()) question.querySelector('footer .errors').remove(); else{ const htmlErr = question.querySelector('footer .errors'); const errors = Object.values(question.errorList).join(' | '); if(htmlErr) htmlErr.innerHTML = errors; else question.querySelector('footer .button:last-child').insertAdjacentHTML('beforebegin','<div class="errors">'+errors+'</div>'); } }; question.querySelector('footer .button:last-child').addEventListener('click',e=>{ if(e.target.classList.contains('disable')) { e.preventDefault(); question.updateErrorsDisplay(); } }); }); </script>`; if(obj.options.min && obj.options.min>0) js +=` <script> document.querySelectorAll('.question').forEach(q=>{ q.validateMin = ()=>{ const answerCount = [].slice.call(q.querySelectorAll('input[type="checkbox"]')).reduce( (acc,cur)=>cur.checked?acc+1:acc, 0); if(answerCount>=${obj.options.min}) delete q.errorList.min; else q.errorList.min = 'Check at least ${obj.options.min} answer'; q.updateButton(); }; q.validateMin(); }); document.querySelectorAll('.question input[type="checkbox"]').forEach(a=>a.addEventListener("change",e => e.target.closest('.question').validateMin())); </script>`; if(obj.options.max && obj.options.max>0) js +=` <script> document.querySelectorAll('.question').forEach(q=>{ q.validateMax = ()=>{ const answerCount = [].slice.call(q.querySelectorAll('input[type="checkbox"]')).reduce( (acc,cur)=>cur.checked?acc+1:acc, 0); if(answerCount<=${obj.options.max}) delete q.errorList.max; else q.errorList.max = 'Check at most ${obj.options.max} answer'; q.updateButton(); }; q.validateMax(); }); document.querySelectorAll('.question input[type="checkbox"]').forEach(a=>a.addEventListener("change",e => e.target.closest('.question').validateMax())); </script>`; if(obj.options.quotation==='public') js +=` ${resultViewers[obj.options.resultView || 'default'](obj)} <script> const options = ${JSON.stringify(obj.options)}; ${utility.scoreType2nameAndPosition.toString()} ${utility.scoreQuotation.toString()} document.querySelector('form').addEventListener("submit",e => { e.preventDefault(); //basicQuotation const basicQuotation = {total:0}; ${obj.questions.map(q=>q.answers.map(a=>`if(document.getElementById("${a.id}").checked) {if(basicQuotation["${a.quotation}"]) basicQuotation["${a.quotation}"]++; else basicQuotation["${a.quotation}"]=1; basicQuotation["total"]++;}\n`).join('')).join('\n')} const score = scoreQuotation(basicQuotation,options); const quoted = {answered:basicQuotation.total,score}; history.pushState(quoted, 'Results','#'+JSON.stringify(quoted)); renderResultIfPresent(); }); </script>`; js+= ` <script> document.querySelectorAll('.question').forEach(q=>{ q.updateButton(); q.querySelectorAll('input[type="checkbox"]').forEach(a=>a.addEventListener("change",()=> q.querySelector('footer .errors')?q.updateErrorsDisplay():'' )); }); function renderResultIfPresent(){ if(decodeURIComponent(location.hash)[1]==='{') renderResult(getUrlJson()); } window.addEventListener("hashchange", renderResultIfPresent); window.addEventListener("popstate", renderResultIfPresent); renderResultIfPresent(); </script>`; return js//.replace(/<\/script>[ \r\n\t]*<script>/g,''); }