diff --git a/amd/build/ui.min.js b/amd/build/ui.min.js index 84093bf..31e6721 100644 --- a/amd/build/ui.min.js +++ b/amd/build/ui.min.js @@ -5,6 +5,6 @@ define("tiny_cloze/ui",["exports","core/modal_events","core/modal","core/modal_f * @module tiny_cloze/ui * @copyright 2023 MoodleDACH * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.resolveSubquestion=_exports.onSubmit=_exports.onInit=_exports.onBeforeGetContent=_exports.displayDialogueForEdit=_exports.displayDialogue=void 0,_modal_events=_interopRequireDefault(_modal_events),_modal2=_interopRequireDefault(_modal2),_modal_factory=_interopRequireDefault(_modal_factory),_mustache=_interopRequireDefault(_mustache);const isNull=a=>null==a,strdecode=t=>String(t).replace(/\\(#|\}|~)/g,"$1"),strencode=t=>String(t).replace(/(#|\}|~)/g,"\\$1"),indexOfNode=(list,node)=>{for(let i=0;i{const attrSel=' selected="selected"';let isSel="="===s?attrSel:"",html='");return FRACTIONS.forEach((item=>{isSel=item.value.toString()===s?attrSel:"",html+='")})),isSel=""!==s&&-1===html.indexOf(attrSel)?attrSel:"",html+='"),html},isCustomGrade=s=>{if("="===s||""===s)return!1;let found=!1;return FRACTIONS.forEach((item=>{item.value.toString()===s&&(found=!0)})),!found},markerClass="cloze-question-marker",markerSpan='',CSS={ANSWER:"tiny_cloze_answer",ANSWERS:"tiny_cloze_answers",ADD:"tiny_cloze_add",CANCEL:"tiny_cloze_cancel",DELETE:"tiny_cloze_delete",FEEDBACK:"tiny_cloze_feedback",FRACTION:"tiny_cloze_fraction",FRAC_CUSTOM:"tiny_cloze_frac_custom",LEFT:"tiny_cloze_col0",LOWER:"tiny_cloze_down",RIGHT:"tiny_cloze_col1",MARKS:"tiny_cloze_marks",DUPLICATE:"tiny_cloze_duplicate",RAISE:"tiny_cloze_up",SUBMIT:"tiny_cloze_submit",SUMMARY:"tiny_cloze_summary",TOLERANCE:"tiny_cloze_tolerance",TYPE:"tiny_cloze_qtype"},TEMPLATE={FORM:'

{{name}} ({{qtype}})

{{STR.addmoreanswerblanks}}
    {{#answerdata}}
  1. {{STR.addmoreanswerblanks}}{{STR.delete}}{{STR.up}}{{STR.down}}
    {{#numerical}}
    {{/numerical}}
    %
  2. {{/answerdata}}
',TYPE:'

{{STR.chooseqtypetoadd}}

{{#types}}
{{/types}}
',FOOTER:''},FRACTIONS=[{value:100},{value:50},{value:0}],STR={};let _editor=null,_form=null,_answerdata=[],_qtype=null,_selectedOffset=-1,_marks=1,_modal=null,_firstAnswer=null;_exports.onInit=function(ed){_editor=ed,_addMarkers(),_getStr(ed)};const _getRegexQtype=editor=>{const extQtypes=(0,_options.hasQtypeMultianswerrgx)(editor)?"|REGEXP(_C)?|RXC?":"";return new RegExp("\\{([0-9]*):(MULTICHOICE(_H|_V|_S|_HS|_VS)?|MULTIRESPONSE(_H|_S|_HS)?|NUMERICAL|SHORTANSWER(_C)?|SAC?|NM|MWC?|M[CR](V|H|VS|HS)?"+extQtypes+"):(.*?)(?{let strToFetch=[{key:"answer",component:"question"},{key:"chooseqtypetoadd",component:"question"},{key:"defaultmark",component:"question"},{key:"feedback",component:"question"},{key:"correct",component:"question"},{key:"incorrect",component:"question"},{key:"addmoreanswerblanks",component:"qtype_calculated"},{key:"delete",component:"core"},{key:"up",component:"core"},{key:"down",component:"core"},{key:"tolerance",component:"qtype_calculated"},{key:"grade",component:"grades"},{key:"caseno",component:"mod_quiz"},{key:"caseyes",component:"mod_quiz"},{key:"answersingleno",component:"qtype_multichoice"},{key:"answersingleyes",component:"qtype_multichoice"},{key:"layoutselectinline",component:"qtype_multianswer"},{key:"layouthorizontal",component:"qtype_multianswer"},{key:"layoutvertical",component:"qtype_multianswer"},{key:"shufflewithin",component:"mod_quiz"},{key:"layoutmultiple_horizontal",component:"qtype_multianswer"},{key:"layoutmultiple_vertical",component:"qtype_multianswer"},{key:"pluginnamesummary",component:"qtype_multichoice"},{key:"pluginnamesummary",component:"qtype_shortanswer"},{key:"pluginnamesummary",component:"qtype_numerical"},{key:"multichoice",component:_common.component},{key:"multiresponse",component:_common.component},{key:"numerical",component:"mod_quiz"},{key:"shortanswer",component:"mod_quiz"},{key:"cancel",component:"core"},{key:"select",component:_common.component},{key:"insert",component:_common.component},{key:"pluginname",component:_common.component},{key:"customgrade",component:_common.component},{key:"err_custom_rate",component:_common.component},{key:"err_empty_answer",component:_common.component},{key:"err_none_correct",component:_common.component},{key:"err_not_numeric",component:_common.component}],langKeys=["answer","chooseqtypetoadd","defaultmark","feedback","correct","incorrect","addmoreanswerblanks","delete","up","down","tolerance","grade","caseno","caseyes","singleno","singleyes","selectinline","horizontal","vertical","shuffle","multi_horizontal","multi_vertical","summary_multichoice","summary_shortanswer","summary_numerical","multichoice","multiresponse","numerical","shortanswer","btn_cancel","btn_select","btn_insert","title","custom_grade","err_custom_rate","err_empty_answer","err_none_correct","err_not_numeric"];(0,_options.hasQtypeMultianswerrgx)(editor)&&(strToFetch.push({key:"regexp",component:"qtype_regexp"}),strToFetch.push({key:"pluginnamesummary",component:"qtype_regexp"}),langKeys.push("regexp"),langKeys.push("summary_regexp")),(0,_str.get_strings)(strToFetch).then((function(){const args=Array.from(arguments);return langKeys.map(((l,i)=>(STR[l]=args[0][i],""))),""})).catch((()=>""))},_getQuestionTypes=function(){let qtypes=[{type:"MULTICHOICE",abbr:["MC"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.selectinline,STR.singleyes]},{type:"MULTICHOICE_H",abbr:["MCH"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.horizontal,STR.singleyes]},{type:"MULTICHOICE_V",abbr:["MCV"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.vertical,STR.singleyes]},{type:"MULTICHOICE_S",abbr:["MCS"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.selectinline,STR.shuffle,STR.singleyes]},{type:"MULTICHOICE_HS",abbr:["MCHS"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.horizontal,STR.shuffle,STR.singleyes]},{type:"MULTICHOICE_VS",abbr:["MCVS"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.vertical,STR.shuffle,STR.singleyes]},{type:"MULTIRESPONSE",abbr:["MR"],name:STR.multiresponse,summary:STR.summary_multichoice,options:[STR.multi_vertical,STR.singleno]},{type:"MULTIRESPONSE_H",abbr:["MRH"],name:STR.multiresponse,summary:STR.summary_multichoice,options:[STR.multi_horizontal,STR.singleno]},{type:"MULTIRESPONSE_S",abbr:["MRS"],name:STR.multiresponse,summary:STR.summary_multichoice,options:[STR.multi_vertical,STR.shuffle,STR.singleno]},{type:"MULTIRESPONSE_HS",abbr:["MRHS"],name:STR.multiresponse,summary:STR.summary_multichoice,options:[STR.multi_horizontal,STR.shuffle,STR.singleno]},{type:"NUMERICAL",abbr:["NM"],name:STR.numerical,summary:STR.summary_numerical},{type:"SHORTANSWER",abbr:["SA","MW"],name:STR.shortanswer,summary:STR.summary_shortanswer,options:[STR.caseno]},{type:"SHORTANSWER_C",abbr:["SAC","MWC"],name:STR.shortanswer,summary:STR.summary_shortanswer,options:[STR.caseyes]}];return(0,_options.hasQtypeMultianswerrgx)(_editor)&&qtypes.splice(11,0,{type:"REGEXP",abbr:["RX"],name:STR.regexp,summary:STR.summary_regexp,options:[STR.caseno]},{type:"REGEXP_C",abbr:["RXC"],name:STR.regexp,summary:STR.summary_regexp,options:[STR.caseyes]}),qtypes},_createModal=async function(){const cfg={title:STR.title,templateContext:{elementid:_editor.id},removeOnClose:!0,large:!0};_modal="function"==typeof _modal2.default.create?await _modal2.default.create(cfg):await _modal_factory.default.create(cfg)};_exports.displayDialogue=async function(){await _createModal();const subquestion=resolveSubquestion();subquestion?(_firstAnswer=null,_selectedOffset=indexOfNode(_editor.dom.select("."+markerClass),subquestion),_parseSubquestion(subquestion.innerHTML),_setDialogueContent(_qtype)):(_firstAnswer=_editor.selection.getContent(),_selectedOffset=-1,_setDialogueContent())};_exports.displayDialogueForEdit=async function(target){const subquestion=resolveSubquestion(target);subquestion&&(await _createModal(),_selectedOffset=indexOfNode(_editor.dom.select("."+markerClass),subquestion),_parseSubquestion(subquestion.innerHTML),_setDialogueContent(_qtype))};const _addMarkers=function(){let m,content=_editor.getContent(),newContent="";if(-1===content.indexOf(markerClass)){do{if(m=content.match(_getRegexQtype(_editor)),!m){newContent+=content;break}const pos=content.indexOf(m[0]);newContent+=content.substring(0,pos)+markerSpan+content.substring(pos,pos+m[0].length),content=content.substring(pos+m[0].length);let level=(m[0].match(/\{/g)||[]).length;if(1!==level){for(;level>1;){const a=content.indexOf("{"),b=content.indexOf("}");a>-1&&b>-1&&a-1?(newContent=content.substring(0,b),content=content.substring(b+1),level--):level=1}newContent+="
"}else newContent+=""}while(m);_editor.setContent(newContent)}},_removeMarkers=function(){for(const span of _editor.dom.select("span."+markerClass))_editor.dom.setOuterHTML(span,span.classList.contains("new")?"":span.innerHTML)};_exports.onBeforeGetContent=function(content){if(!isNull(content.source_view)&&!0===content.source_view){var onClose=function(){_editor.off("close",onClose),_addMarkers()};_editor.on("CloseWindow",(()=>{onClose()})),_modal||_removeMarkers()}};_exports.onSubmit=function(){_removeMarkers()};const _setDialogueContent=function(qtype,nomodalevents){const footer=_mustache.default.render(TEMPLATE.FOOTER,{cancel:STR.btn_cancel,submit:qtype?STR.btn_insert:STR.btn_select});let contentText;contentText=qtype?_mustache.default.render(TEMPLATE.FORM,{CSS:CSS,STR:STR,answerdata:_answerdata,elementid:getUuid(),qtype:_qtype,name:_getQuestionTypes().filter((q=>_qtype===q.type))[0].name,marks:_marks,numerical:"NUMERICAL"===_qtype||"NM"===_qtype}):_mustache.default.render(TEMPLATE.TYPE,{CSS:CSS,STR:STR,qtype:_qtype,types:_getQuestionTypes()}),_modal.setBody(contentText),_modal.setFooter(footer),_modal.show();const $root=_modal.getRoot();if(_form=$root.get(0).querySelector("form"),_toggleDeleteIcon(),!nomodalevents){if(_modal.registerEventListeners(),_modal.registerCloseOnSave(),_modal.registerCloseOnCancel(),$root.on(_modal_events.default.cancel,_cancel),!qtype)return void $root.on(_modal_events.default.save,_choiceHandler);$root.on(_modal_events.default.save,_setSubquestion)}const getTarget=e=>{let p=e.target;for(;!isNull(p)&&1===p.nodeType&&"A"!==p.tagName;)p=p.parentNode;return isNull(p.classList)?null:p};_form.addEventListener("click",(e=>{const p=getTarget(e);if(!isNull(p))return p.classList.contains(CSS.DELETE)?(e.preventDefault(),void _deleteAnswer(p)):p.classList.contains(CSS.ADD)?(e.preventDefault(),void _addAnswer(p)):p.classList.contains(CSS.LOWER)?(e.preventDefault(),void _lowerAnswer(p)):void(p.classList.contains(CSS.RAISE)&&(e.preventDefault(),_raiseAnswer(p)))})),_form.addEventListener("keyup",(e=>{const p=getTarget(e);isNull(p)||(p.classList.contains(CSS.ANSWER)||p.classList.contains(CSS.FEEDBACK))&&(e.preventDefault(),_addAnswer(p))})),_form.querySelectorAll("."+CSS.FRACTION).forEach((sel=>{sel.addEventListener("change",(e=>{const id=e.target.getAttribute("id");"__custom__"===e.target.value?document.getElementById(id+"_custom").parentNode.classList.remove("hidden"):document.getElementById(id+"_custom").parentNode.classList.add("hidden")}))}))},_toggleDeleteIcon=function(){const deleteIcons=_form.querySelectorAll("."+CSS.DELETE);if(1!==deleteIcons.length)for(let i=0;i(_setDialogueContent(_qtype),_form.querySelector("."+CSS.ANSWER).focus(),""))).catch((()=>""))},_parseSubquestion=function(question){_answerdata=[];const regexQtype=_getRegexQtype(_editor),parts=regexQtype.exec(question);if(regexQtype.lastIndex=0,!parts)return;_marks=parts[1],_qtype=parts[2],_qtype.length<5&&_getQuestionTypes().forEach((l=>{for(const a of l.abbr)if(a===_qtype)return void(_qtype=l.type)}));const answers=parts[(0,_options.hasQtypeMultianswerrgx)(_editor)?8:7].match(/(\\.|[^~])*/g);answers&&answers.forEach((function(answer){const options=/^(%(-?[.0-9]+)%|(=?))((\\.|[^#])*)#?(.*)/.exec(answer);if(options&&options[4]){let frac="";if(options[3]?frac="="===options[3]?"=":100:options[2]&&(frac=options[2]),"NUMERICAL"===_qtype||"NM"===_qtype){const tolerance=/^([^:]*):?(.*)/.exec(options[4])[2]||0;return void _answerdata.push({id:getUuid(),answer:strdecode(options[4].replace(/:.*/,"")),feedback:strdecode(options[6]),tolerance:tolerance,fraction:frac,fractionOptions:getFractionOptions(frac),isCustomGrade:isCustomGrade(frac)})}_answerdata.push({answer:strdecode(options[4]),id:getUuid(),feedback:strdecode(options[6]),fraction:frac,fractionOptions:getFractionOptions(frac),isCustomGrade:isCustomGrade(frac)})}}))},_addAnswer=function(a){let index=indexOfNode(_form.querySelectorAll("."+CSS.ADD),a);-1===index&&(index=0);let fraction="",answer="",feedback="",tolerance=0;a.closest("li")&&(fraction=a.closest("li").querySelector("."+CSS.FRACTION).value,"__custom__"===fraction&&(fraction=a.closest("li").querySelector("."+CSS.FRAC_CUSTOM).value),answer=a.closest("li").querySelector("."+CSS.ANSWER).value,feedback=a.closest("li").querySelector("."+CSS.FEEDBACK).value,a.closest("li").querySelector("."+CSS.TOLERANCE)&&(tolerance=a.closest("li").querySelector("."+CSS.TOLERANCE).value)),_processFormData(),_answerdata.splice(index,0,{id:getUuid(),answer:answer,feedback:feedback,fraction:fraction,fractionOptions:getFractionOptions(fraction),tolerance:tolerance,isCustomGrade:isCustomGrade(fraction)}),_setDialogueContent(_qtype,!0),_toggleDeleteIcon(),_form.querySelectorAll("."+CSS.ANSWER).item(index).focus()},_deleteAnswer=function(a){let index=indexOfNode(_form.querySelectorAll("."+CSS.DELETE),a);-1===index&&(index=indexOfNode(_form.querySelectorAll("li"),a.closest("li"))),_processFormData(),_answerdata.splice(index,1),_setDialogueContent(_qtype,!0);const answers=_form.querySelectorAll("."+CSS.ANSWER);index=Math.min(index,answers.length-1),answers.item(index).focus(),_toggleDeleteIcon()},_lowerAnswer=function(a){const li=a.closest("li");li.before(li.nextSibling),li.querySelector("."+CSS.ANSWER).focus()},_raiseAnswer=function(a){const li=a.closest("li");li.after(li.previousSibling),li.querySelector("."+CSS.ANSWER).focus()},_cancel=function(e){e.preventDefault();for(const span of _editor.dom.select("."+markerClass+".new"))span.remove();_modal.destroy(),_editor.focus(),_modal=null},_setSubquestion=function(e){e.preventDefault();const errMsg=_form.querySelector(".msg-error"),formErrors=_processFormData(!0);if(formErrors.length>0)return errMsg.innerHTML="",void errMsg.classList.remove("hidden");errMsg.classList.add("hidden");let question="{"+_marks+":"+_qtype+":";for(let i=0;i<_answerdata.length;i++)""!==_answerdata[i].raw&&(question+=_answerdata[i].fraction&&!isNaN(_answerdata[i].fraction)?"%"+_answerdata[i].fraction+"%":_answerdata[i].fraction,question+=strencode(_answerdata[i].answer),"NM"!==_qtype&&"NUMERICAL"!==_qtype||(question+=":"+_answerdata[i].tolerance),_answerdata[i].feedback&&(question+="#"+strencode(_answerdata[i].feedback)),i<_answerdata.length-1&&(question+="~"));"~"===question.slice(-1)&&(question=question.substring(0,question.length-1)),question+="}",_modal.destroy(),_modal=null,_editor.focus(),_selectedOffset>-1?_editor.dom.select("."+markerClass)[_selectedOffset].innerHTML=question:_editor.insertContent(markerSpan+question+"")},_processFormData=function(validate){_answerdata=[];let globalErrors=[];const answers=_form.querySelectorAll("."+CSS.ANSWER),feedbacks=_form.querySelectorAll("."+CSS.FEEDBACK),fractions=_form.querySelectorAll("."+CSS.FRACTION),customGrades=_form.querySelectorAll("."+CSS.FRAC_CUSTOM),tolerances=_form.querySelectorAll("."+CSS.TOLERANCE);for(let i=0;i0?tolerances.item(i).value:0,isCustomGrade:"__custom__"===fractions.item(i).value};"NM"!==_qtype&&"NUMERICAL"!==_qtype||(tolerances.item(i).classList.remove("error"),currentAnswer.answer=Number(currentAnswer.answer),currentAnswer.tolerance=Number(currentAnswer.tolerance)),_answerdata.push(currentAnswer)}if(_marks=_form.querySelector("."+CSS.MARKS).value,validate){const{hasCorrectAnswer:hasCorrectAnswer,errors:errors}=_validateAnswers();for(let i=0;i<_answerdata.length;i++)for(const err of _answerdata[i].hasErrors){if(hasCorrectAnswer&&("empty_answer"===err||"correct_but_empty"===err))break;"answer_not_numeric"===err||"empty_answer"===err||"correct_but_empty"===err?answers.item(i).classList.add("error"):"tolerance_not_numeric"===err?tolerances.item(i).classList.add("error"):"error_custom_rate"===err&&customGrades.item(i).classList.add("error")}globalErrors=_translateGlobalErrors(hasCorrectAnswer,errors),globalErrors.length>0&&_form.querySelector("input.error").focus()}return globalErrors},_validateAnswers=function(){let errors=[],hasCorrect=!1;for(let i=0;i<_answerdata.length;i++)_answerdata[i].hasErrors=[],""===_answerdata[i].raw&&_answerdata[i].hasErrors.push("empty_answer"),"NM"!==_qtype&&"NUMERICAL"!==_qtype||(isNaN(_answerdata[i].answer)&&""!==_answerdata[i].raw&&_answerdata[i].hasErrors.push("answer_not_numeric"),isNaN(_answerdata[i].tolerance)&&_answerdata[i].hasErrors.push("tolerance_not_numeric")),_answerdata[i].isCustomGrade&&(isNaN(_answerdata[i].fraction)||_answerdata[i].fraction<-100||_answerdata[i].fraction>100||""===_answerdata[i].fraction.trim())&&_answerdata[i].hasErrors.push("error_custom_rate"),"100"!==_answerdata[i].fraction&&"="!==_answerdata[i].fraction||(""!==_answerdata[i].raw?(_answerdata[i].isCorrect=!0,hasCorrect=!0):_answerdata[i].hasErrors.push("correct_but_empty")),errors=errors.concat(_answerdata[i].hasErrors);return{hasCorrectAnswer:hasCorrect,errors:_combineGlobalErrors(hasCorrect,errors)}},_translateGlobalErrors=function(hasCorrectAnswer,errors){const errTranslated=[],trMsg={emptyanswer:STR.err_empty_answer,answernotnumeric:STR.err_not_numeric,tolerancenotnumeric:STR.err_not_numeric,errorcustomrate:STR.err_custom_rate,nonecorrect:STR.err_none_correct};for(const err of errors){if(hasCorrectAnswer&&"empty_answer"===err||"correct_but_empty"===err)continue;const key=err.replace(/_/g,"");errTranslated.push(trMsg[key])}return errTranslated},_combineGlobalErrors=function(hasCorrectAnswer,errors){const errUnique=errors.filter(((value,index,array)=>array.indexOf(value)===index));if(hasCorrectAnswer){const i=errUnique.indexOf("empty_answer");i>-1&&errUnique.splice(i,1)}else errUnique.includes("correct_but_empty")||errUnique.push("none_correct");return errUnique},resolveSubquestion=function(element){let span=element||_editor.selection.getStart();return!isNull(span.classList)&&span.classList.contains(markerClass)?span:(_editor.dom.getParents(span,(elm=>!(isNull(elm.classList)||!elm.classList.contains(markerClass))&&elm)),!1)};_exports.resolveSubquestion=resolveSubquestion})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.resolveSubquestion=_exports.onSubmit=_exports.onInit=_exports.onBeforeGetContent=_exports.displayDialogueForEdit=_exports.displayDialogue=void 0,_modal_events=_interopRequireDefault(_modal_events),_modal2=_interopRequireDefault(_modal2),_modal_factory=_interopRequireDefault(_modal_factory),_mustache=_interopRequireDefault(_mustache);const isNull=a=>null==a,strdecode=t=>String(t).replace(/\\(#|\}|~)/g,"$1"),strencode=t=>String(t).replace(/(#|\}|~)/g,"\\$1"),indexOfNode=(list,node)=>{for(let i=0;i{const attrSel=' selected="selected"';let isSel="="===s?attrSel:"",html='");return FRACTIONS.forEach((item=>{isSel=item.value.toString()===s?attrSel:"",html+='")})),isSel=""!==s&&-1===html.indexOf(attrSel)?attrSel:"",html+='"),html},isCustomGrade=s=>{if("="===s||""===s)return!1;let found=!1;return FRACTIONS.forEach((item=>{item.value.toString()===s&&(found=!0)})),!found},markerClass="cloze-question-marker",markerSpan='',CSS={ANSWER:"tiny_cloze_answer",ANSWERS:"tiny_cloze_answers",ADD:"tiny_cloze_add",CANCEL:"tiny_cloze_cancel",DELETE:"tiny_cloze_delete",FEEDBACK:"tiny_cloze_feedback",FRACTION:"tiny_cloze_fraction",FRAC_CUSTOM:"tiny_cloze_frac_custom",LEFT:"tiny_cloze_col0",LOWER:"tiny_cloze_down",RIGHT:"tiny_cloze_col1",MARKS:"tiny_cloze_marks",DUPLICATE:"tiny_cloze_duplicate",RAISE:"tiny_cloze_up",SUBMIT:"tiny_cloze_submit",SUMMARY:"tiny_cloze_summary",TOLERANCE:"tiny_cloze_tolerance",TYPE:"tiny_cloze_qtype"},TEMPLATE={FORM:'

{{name}} ({{qtype}})

{{STR.addmoreanswerblanks}}
    {{#answerdata}}
  1. {{STR.addmoreanswerblanks}}{{STR.delete}}{{STR.up}}{{STR.down}}
    {{#numerical}}
    {{/numerical}}
    %
  2. {{/answerdata}}
',TYPE:'

{{STR.chooseqtypetoadd}}

{{#types}}
{{/types}}
',FOOTER:''},FRACTIONS=[{value:100},{value:50},{value:0}],STR={};let _editor=null,_form=null,_answerdata=[],_qtype=null,_selectedOffset=-1,_marks=1,_modal=null,_firstAnswer=null;_exports.onInit=function(ed){_editor=ed,_addMarkers(),_getStr()};const _getRegexQtype=editor=>{const extQtypes=(0,_options.hasQtypeMultianswerrgx)(editor)?"|REGEXP(_C)?|RXC?":"";return new RegExp("\\{([0-9]*):(MULTICHOICE(_H|_V|_S|_HS|_VS)?|MULTIRESPONSE(_H|_S|_HS)?|NUMERICAL|SHORTANSWER(_C)?|SAC?|NM|MWC?|M[CR](V|H|VS|HS)?"+extQtypes+"):(.*?)(?{let strToFetch=[{key:"answer",component:"question"},{key:"chooseqtypetoadd",component:"question"},{key:"defaultmark",component:"question"},{key:"feedback",component:"question"},{key:"correct",component:"question"},{key:"incorrect",component:"question"},{key:"addmoreanswerblanks",component:"qtype_calculated"},{key:"delete",component:"core"},{key:"up",component:"core"},{key:"down",component:"core"},{key:"tolerance",component:"qtype_calculated"},{key:"grade",component:"grades"},{key:"caseno",component:"mod_quiz"},{key:"caseyes",component:"mod_quiz"},{key:"answersingleno",component:"qtype_multichoice"},{key:"answersingleyes",component:"qtype_multichoice"},{key:"layoutselectinline",component:"qtype_multianswer"},{key:"layouthorizontal",component:"qtype_multianswer"},{key:"layoutvertical",component:"qtype_multianswer"},{key:"shufflewithin",component:"mod_quiz"},{key:"layoutmultiple_horizontal",component:"qtype_multianswer"},{key:"layoutmultiple_vertical",component:"qtype_multianswer"},{key:"pluginnamesummary",component:"qtype_multichoice"},{key:"pluginnamesummary",component:"qtype_shortanswer"},{key:"pluginnamesummary",component:"qtype_numerical"},{key:"multichoice",component:_common.component},{key:"multiresponse",component:_common.component},{key:"numerical",component:"mod_quiz"},{key:"shortanswer",component:"mod_quiz"},{key:"cancel",component:"core"},{key:"select",component:_common.component},{key:"insert",component:_common.component},{key:"pluginname",component:_common.component},{key:"customgrade",component:_common.component},{key:"err_custom_rate",component:_common.component},{key:"err_empty_answer",component:_common.component},{key:"err_none_correct",component:_common.component},{key:"err_not_numeric",component:_common.component}],langKeys=["answer","chooseqtypetoadd","defaultmark","feedback","correct","incorrect","addmoreanswerblanks","delete","up","down","tolerance","grade","caseno","caseyes","singleno","singleyes","selectinline","horizontal","vertical","shuffle","multi_horizontal","multi_vertical","summary_multichoice","summary_shortanswer","summary_numerical","multichoice","multiresponse","numerical","shortanswer","btn_cancel","btn_select","btn_insert","title","custom_grade","err_custom_rate","err_empty_answer","err_none_correct","err_not_numeric"];(0,_options.hasQtypeMultianswerrgx)(_editor)&&(strToFetch.push({key:"regexp",component:"qtype_regexp"}),strToFetch.push({key:"pluginnamesummary",component:"qtype_regexp"}),langKeys.push("regexp"),langKeys.push("summary_regexp")),(0,_str.get_strings)(strToFetch).then((function(){const args=Array.from(arguments);return langKeys.map(((l,i)=>(STR[l]=args[0][i],""))),""})).catch((()=>""))},_getQuestionTypes=function(){let qtypes=[{type:"MULTICHOICE",abbr:["MC"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.selectinline,STR.singleyes]},{type:"MULTICHOICE_H",abbr:["MCH"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.horizontal,STR.singleyes]},{type:"MULTICHOICE_V",abbr:["MCV"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.vertical,STR.singleyes]},{type:"MULTICHOICE_S",abbr:["MCS"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.selectinline,STR.shuffle,STR.singleyes]},{type:"MULTICHOICE_HS",abbr:["MCHS"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.horizontal,STR.shuffle,STR.singleyes]},{type:"MULTICHOICE_VS",abbr:["MCVS"],name:STR.multichoice,summary:STR.summary_multichoice,options:[STR.vertical,STR.shuffle,STR.singleyes]},{type:"MULTIRESPONSE",abbr:["MR"],name:STR.multiresponse,summary:STR.summary_multichoice,options:[STR.multi_vertical,STR.singleno]},{type:"MULTIRESPONSE_H",abbr:["MRH"],name:STR.multiresponse,summary:STR.summary_multichoice,options:[STR.multi_horizontal,STR.singleno]},{type:"MULTIRESPONSE_S",abbr:["MRS"],name:STR.multiresponse,summary:STR.summary_multichoice,options:[STR.multi_vertical,STR.shuffle,STR.singleno]},{type:"MULTIRESPONSE_HS",abbr:["MRHS"],name:STR.multiresponse,summary:STR.summary_multichoice,options:[STR.multi_horizontal,STR.shuffle,STR.singleno]},{type:"NUMERICAL",abbr:["NM"],name:STR.numerical,summary:STR.summary_numerical},{type:"SHORTANSWER",abbr:["SA","MW"],name:STR.shortanswer,summary:STR.summary_shortanswer,options:[STR.caseno]},{type:"SHORTANSWER_C",abbr:["SAC","MWC"],name:STR.shortanswer,summary:STR.summary_shortanswer,options:[STR.caseyes]}];return(0,_options.hasQtypeMultianswerrgx)(_editor)&&qtypes.splice(11,0,{type:"REGEXP",abbr:["RX"],name:STR.regexp,summary:STR.summary_regexp,options:[STR.caseno]},{type:"REGEXP_C",abbr:["RXC"],name:STR.regexp,summary:STR.summary_regexp,options:[STR.caseyes]}),qtypes},_createModal=async function(){const cfg={title:STR.title,templateContext:{elementid:_editor.id},removeOnClose:!0,large:!0};_modal="function"==typeof _modal2.default.create?await _modal2.default.create(cfg):await _modal_factory.default.create(cfg)};_exports.displayDialogue=async function(){await _createModal();const subquestion=resolveSubquestion();subquestion?(_firstAnswer=null,_selectedOffset=indexOfNode(_editor.dom.select("."+markerClass),subquestion),_parseSubquestion(subquestion.innerHTML),_setDialogueContent(_qtype)):(_firstAnswer=_editor.selection.getContent(),_selectedOffset=-1,_setDialogueContent())};_exports.displayDialogueForEdit=async function(target){const subquestion=resolveSubquestion(target);subquestion&&(await _createModal(),_selectedOffset=indexOfNode(_editor.dom.select("."+markerClass),subquestion),_parseSubquestion(subquestion.innerHTML),_setDialogueContent(_qtype))};const _addMarkers=function(){let m,content=_editor.getContent(),newContent="";if(-1===content.indexOf(markerClass)){do{if(m=content.match(_getRegexQtype(_editor)),!m){newContent+=content;break}const pos=content.indexOf(m[0]);newContent+=content.substring(0,pos)+markerSpan+content.substring(pos,pos+m[0].length),content=content.substring(pos+m[0].length);let level=(m[0].match(/\{/g)||[]).length;if(1!==level){for(;level>1;){const a=content.indexOf("{"),b=content.indexOf("}");a>-1&&b>-1&&a-1?(newContent=content.substring(0,b),content=content.substring(b+1),level--):level=1}newContent+="
"}else newContent+=""}while(m);_editor.setContent(newContent)}},_removeMarkers=function(){for(const span of _editor.dom.select("span."+markerClass))_editor.dom.setOuterHTML(span,span.classList.contains("new")?"":span.innerHTML)};_exports.onBeforeGetContent=function(content){if(!isNull(content.source_view)&&!0===content.source_view){var onClose=function(){_editor.off("close",onClose),_addMarkers()};_editor.on("CloseWindow",(()=>{onClose()})),_modal||_removeMarkers()}};_exports.onSubmit=function(){_removeMarkers()};const _setDialogueContent=function(qtype,nomodalevents){const footer=_mustache.default.render(TEMPLATE.FOOTER,{cancel:STR.btn_cancel,submit:qtype?STR.btn_insert:STR.btn_select});let contentText;contentText=qtype?_mustache.default.render(TEMPLATE.FORM,{CSS:CSS,STR:STR,answerdata:_answerdata,elementid:getUuid(),qtype:_qtype,name:_getQuestionTypes().filter((q=>_qtype===q.type))[0].name,marks:_marks,numerical:"NUMERICAL"===_qtype||"NM"===_qtype}):_mustache.default.render(TEMPLATE.TYPE,{CSS:CSS,STR:STR,qtype:_qtype,types:_getQuestionTypes()}),_modal.setBody(contentText),_modal.setFooter(footer),_modal.show();const $root=_modal.getRoot();if(_form=$root.get(0).querySelector("form"),_toggleDeleteIcon(),!nomodalevents){if(_modal.registerEventListeners(),_modal.registerCloseOnSave(),_modal.registerCloseOnCancel(),$root.on(_modal_events.default.cancel,_cancel),!qtype)return void $root.on(_modal_events.default.save,_choiceHandler);$root.on(_modal_events.default.save,_setSubquestion)}const getTarget=e=>{let p=e.target;for(;!isNull(p)&&1===p.nodeType&&"A"!==p.tagName;)p=p.parentNode;return isNull(p.classList)?null:p};_form.addEventListener("click",(e=>{const p=getTarget(e);if(!isNull(p))return p.classList.contains(CSS.DELETE)?(e.preventDefault(),void _deleteAnswer(p)):p.classList.contains(CSS.ADD)?(e.preventDefault(),void _addAnswer(p)):p.classList.contains(CSS.LOWER)?(e.preventDefault(),void _lowerAnswer(p)):void(p.classList.contains(CSS.RAISE)&&(e.preventDefault(),_raiseAnswer(p)))})),_form.addEventListener("keyup",(e=>{const p=getTarget(e);isNull(p)||(p.classList.contains(CSS.ANSWER)||p.classList.contains(CSS.FEEDBACK))&&(e.preventDefault(),_addAnswer(p))})),_form.querySelectorAll("."+CSS.FRACTION).forEach((sel=>{sel.addEventListener("change",(e=>{const id=e.target.getAttribute("id");"__custom__"===e.target.value?document.getElementById(id+"_custom").parentNode.classList.remove("hidden"):document.getElementById(id+"_custom").parentNode.classList.add("hidden")}))}))},_toggleDeleteIcon=function(){const deleteIcons=_form.querySelectorAll("."+CSS.DELETE);if(1!==deleteIcons.length)for(let i=0;i(_setDialogueContent(_qtype),_form.querySelector("."+CSS.ANSWER).focus(),""))).catch((()=>""))},_parseSubquestion=function(question){_answerdata=[];const regexQtype=_getRegexQtype(_editor),parts=regexQtype.exec(question);if(regexQtype.lastIndex=0,!parts)return;_marks=parts[1],_qtype=parts[2],_qtype.length<5&&_getQuestionTypes().forEach((l=>{for(const a of l.abbr)if(a===_qtype)return void(_qtype=l.type)}));const answers=parts[(0,_options.hasQtypeMultianswerrgx)(_editor)?8:7].match(/(\\.|[^~])*/g);answers&&answers.forEach((function(answer){const options=/^(%(-?[.0-9]+)%|(=?))((\\.|[^#])*)#?(.*)/.exec(answer);if(options&&options[4]){let frac="";if(options[3]?frac="="===options[3]?"=":100:options[2]&&(frac=options[2]),"NUMERICAL"===_qtype||"NM"===_qtype){const tolerance=/^([^:]*):?(.*)/.exec(options[4])[2]||0;return void _answerdata.push({id:getUuid(),answer:strdecode(options[4].replace(/:.*/,"")),feedback:strdecode(options[6]),tolerance:tolerance,fraction:frac,fractionOptions:getFractionOptions(frac),isCustomGrade:isCustomGrade(frac)})}_answerdata.push({answer:strdecode(options[4]),id:getUuid(),feedback:strdecode(options[6]),fraction:frac,fractionOptions:getFractionOptions(frac),isCustomGrade:isCustomGrade(frac)})}}))},_addAnswer=function(a){let index=indexOfNode(_form.querySelectorAll("."+CSS.ADD),a);-1===index&&(index=0);let fraction="",answer="",feedback="",tolerance=0;a.closest("li")&&(fraction=a.closest("li").querySelector("."+CSS.FRACTION).value,"__custom__"===fraction&&(fraction=a.closest("li").querySelector("."+CSS.FRAC_CUSTOM).value),answer=a.closest("li").querySelector("."+CSS.ANSWER).value,feedback=a.closest("li").querySelector("."+CSS.FEEDBACK).value,a.closest("li").querySelector("."+CSS.TOLERANCE)&&(tolerance=a.closest("li").querySelector("."+CSS.TOLERANCE).value)),_processFormData(),_answerdata.splice(index,0,{id:getUuid(),answer:answer,feedback:feedback,fraction:fraction,fractionOptions:getFractionOptions(fraction),tolerance:tolerance,isCustomGrade:isCustomGrade(fraction)}),_setDialogueContent(_qtype,!0),_toggleDeleteIcon(),_form.querySelectorAll("."+CSS.ANSWER).item(index).focus()},_deleteAnswer=function(a){let index=indexOfNode(_form.querySelectorAll("."+CSS.DELETE),a);-1===index&&(index=indexOfNode(_form.querySelectorAll("li"),a.closest("li"))),_processFormData(),_answerdata.splice(index,1),_setDialogueContent(_qtype,!0);const answers=_form.querySelectorAll("."+CSS.ANSWER);index=Math.min(index,answers.length-1),answers.item(index).focus(),_toggleDeleteIcon()},_lowerAnswer=function(a){const li=a.closest("li");li.before(li.nextSibling),li.querySelector("."+CSS.ANSWER).focus()},_raiseAnswer=function(a){const li=a.closest("li");li.after(li.previousSibling),li.querySelector("."+CSS.ANSWER).focus()},_cancel=function(e){e.preventDefault();for(const span of _editor.dom.select("."+markerClass+".new"))span.remove();_modal.destroy(),_editor.focus(),_modal=null},_setSubquestion=function(e){e.preventDefault();const errMsg=_form.querySelector(".msg-error"),formErrors=_processFormData(!0);if(formErrors.length>0)return errMsg.innerHTML="
  • "+formErrors.join("
  • ")+"
",void errMsg.classList.remove("hidden");errMsg.classList.add("hidden");let question="{"+_marks+":"+_qtype+":";for(let i=0;i<_answerdata.length;i++)""!==_answerdata[i].raw&&(question+=_answerdata[i].fraction&&!isNaN(_answerdata[i].fraction)?"%"+_answerdata[i].fraction+"%":_answerdata[i].fraction,question+=strencode(_answerdata[i].answer),"NM"!==_qtype&&"NUMERICAL"!==_qtype||(question+=":"+_answerdata[i].tolerance),_answerdata[i].feedback&&(question+="#"+strencode(_answerdata[i].feedback)),i<_answerdata.length-1&&(question+="~"));"~"===question.slice(-1)&&(question=question.substring(0,question.length-1)),question+="}",_modal.destroy(),_modal=null,_editor.focus(),_selectedOffset>-1?_editor.dom.select("."+markerClass)[_selectedOffset].innerHTML=question:_editor.insertContent(markerSpan+question+"")},_processFormData=function(validate){_answerdata=[];let globalErrors=[];const answers=_form.querySelectorAll("."+CSS.ANSWER),feedbacks=_form.querySelectorAll("."+CSS.FEEDBACK),fractions=_form.querySelectorAll("."+CSS.FRACTION),customGrades=_form.querySelectorAll("."+CSS.FRAC_CUSTOM),tolerances=_form.querySelectorAll("."+CSS.TOLERANCE);for(let i=0;i0?tolerances.item(i).value:0,isCustomGrade:"__custom__"===fractions.item(i).value};"NM"!==_qtype&&"NUMERICAL"!==_qtype||(tolerances.item(i).classList.remove("error"),currentAnswer.answer=Number(currentAnswer.answer),currentAnswer.tolerance=Number(currentAnswer.tolerance)),_answerdata.push(currentAnswer)}if(_marks=_form.querySelector("."+CSS.MARKS).value,validate){const{hasCorrectAnswer:hasCorrectAnswer,errors:errors}=_validateAnswers();for(let i=0;i<_answerdata.length;i++)for(const err of _answerdata[i].hasErrors){if(hasCorrectAnswer&&("empty_answer"===err||"correct_but_empty"===err))break;"answer_not_numeric"===err||"empty_answer"===err||"correct_but_empty"===err?answers.item(i).classList.add("error"):"tolerance_not_numeric"===err?tolerances.item(i).classList.add("error"):"error_custom_rate"===err&&customGrades.item(i).classList.add("error")}globalErrors=_translateGlobalErrors(hasCorrectAnswer,errors),globalErrors.length>0&&_form.querySelector("input.error").focus()}return globalErrors},_validateAnswers=function(){let errors=[],hasCorrect=!1;for(let i=0;i<_answerdata.length;i++)_answerdata[i].hasErrors=[],""===_answerdata[i].raw&&_answerdata[i].hasErrors.push("empty_answer"),"NM"!==_qtype&&"NUMERICAL"!==_qtype||(isNaN(_answerdata[i].answer)&&""!==_answerdata[i].raw&&_answerdata[i].hasErrors.push("answer_not_numeric"),isNaN(_answerdata[i].tolerance)&&_answerdata[i].hasErrors.push("tolerance_not_numeric")),_answerdata[i].isCustomGrade&&(isNaN(_answerdata[i].fraction)||_answerdata[i].fraction<-100||_answerdata[i].fraction>100||""===_answerdata[i].fraction.trim())&&_answerdata[i].hasErrors.push("error_custom_rate"),"100"!==_answerdata[i].fraction&&"="!==_answerdata[i].fraction||(""!==_answerdata[i].raw?(_answerdata[i].isCorrect=!0,hasCorrect=!0):_answerdata[i].hasErrors.push("correct_but_empty")),errors=errors.concat(_answerdata[i].hasErrors);return{hasCorrectAnswer:hasCorrect,errors:_combineGlobalErrors(hasCorrect,errors)}},_translateGlobalErrors=function(hasCorrectAnswer,errors){const errTranslated=[],trMsg={emptyanswer:STR.err_empty_answer,answernotnumeric:STR.err_not_numeric,tolerancenotnumeric:STR.err_not_numeric,errorcustomrate:STR.err_custom_rate,nonecorrect:STR.err_none_correct};for(const err of errors){if(hasCorrectAnswer&&"empty_answer"===err||"correct_but_empty"===err)continue;const key=err.replace(/_/g,"");errTranslated.push(trMsg[key])}return errTranslated},_combineGlobalErrors=function(hasCorrectAnswer,errors){const errUnique=errors.filter(((value,index,array)=>array.indexOf(value)===index));if(hasCorrectAnswer){const i=errUnique.indexOf("empty_answer");i>-1&&errUnique.splice(i,1)}else errUnique.includes("correct_but_empty")||errUnique.push("none_correct");return errUnique},resolveSubquestion=function(element){let span=element||_editor.selection.getStart();return!isNull(span.classList)&&span.classList.contains(markerClass)?span:(_editor.dom.getParents(span,(elm=>!(isNull(elm.classList)||!elm.classList.contains(markerClass))&&elm)),!1)};_exports.resolveSubquestion=resolveSubquestion})); //# sourceMappingURL=ui.min.js.map \ No newline at end of file diff --git a/amd/build/ui.min.js.map b/amd/build/ui.min.js.map index 34ceb28..5271562 100644 --- a/amd/build/ui.min.js.map +++ b/amd/build/ui.min.js.map @@ -1 +1 @@ -{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Plugin tiny_cloze for TinyMCE v6 in Moodle.\n *\n * @module tiny_cloze/ui\n * @copyright 2023 MoodleDACH\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalEvents from 'core/modal_events';\nimport Modal from 'core/modal';\nimport ModalFactory from 'core/modal_factory';\nimport Mustache from 'core/mustache';\nimport {get_strings as getStrings} from 'core/str';\nimport {component} from './common';\nimport {hasQtypeMultianswerrgx} from './options';\n\n// Helper functions.\nconst isNull = a => a === null || a === undefined;\nconst strdecode = t => String(t).replace(/\\\\(#|\\}|~)/g, '$1');\nconst strencode = t => String(t).replace(/(#|\\}|~)/g, '\\\\$1');\nconst indexOfNode = (list, node) => {\n for (let i = 0; i < list.length; i++) {\n if (list[i] === node) {\n return i;\n }\n }\n return -1;\n};\nconst getUuid = function() {\n if (!isNull(crypto.randomUUID)) {\n return crypto.randomUUID();\n }\n return 'ed-cloze-' + Math.floor(Math.random() * 100000).toString();\n};\n// Grade Selector value when custom percentage is selected.\nconst selectCustomPercent = '__custom__';\n// This is a specific helper function to return the options html for the fraction select element.\nconst getFractionOptions = s => {\n const attrSel = ' selected=\"selected\"';\n let isSel = s === '=' ? attrSel : '';\n let html = ``;\n FRACTIONS.forEach(item => {\n isSel = item.value.toString() === s ? attrSel : '';\n html += ``;\n });\n isSel = s !== '' && html.indexOf(attrSel) === -1 ? attrSel : '';\n html += ``;\n return html;\n};\n// Check if the value is a custom grade value (in order to show the input field).\nconst isCustomGrade = s => {\n if (s === '=' || s === '') {\n return false;\n }\n let found = false;\n FRACTIONS.forEach(item => {\n if (item.value.toString() === s) {\n found = true;\n }\n });\n return !found;\n};\n// Marker class and the whole span element that is used to encapsulate the cloze question text.\nconst markerClass = 'cloze-question-marker';\nconst markerSpan = '';\n\n// CSS classes that are used in the modal dialogue.\nconst CSS = {\n ANSWER: 'tiny_cloze_answer',\n ANSWERS: 'tiny_cloze_answers',\n ADD: 'tiny_cloze_add',\n CANCEL: 'tiny_cloze_cancel',\n DELETE: 'tiny_cloze_delete',\n FEEDBACK: 'tiny_cloze_feedback',\n FRACTION: 'tiny_cloze_fraction',\n FRAC_CUSTOM: 'tiny_cloze_frac_custom',\n LEFT: 'tiny_cloze_col0',\n LOWER: 'tiny_cloze_down',\n RIGHT: 'tiny_cloze_col1',\n MARKS: 'tiny_cloze_marks',\n DUPLICATE: 'tiny_cloze_duplicate',\n RAISE: 'tiny_cloze_up',\n SUBMIT: 'tiny_cloze_submit',\n SUMMARY: 'tiny_cloze_summary',\n TOLERANCE: 'tiny_cloze_tolerance',\n TYPE: 'tiny_cloze_qtype'\n};\nconst TEMPLATE = {\n FORM: '
' +\n '

{{name}} ({{qtype}})

' +\n '
' +\n '
' +\n '
' +\n '' +\n '' +\n '' +\n '\"{{STR.addmoreanswerblanks}}\"' +\n '
' +\n '
' +\n '
' +\n '
' +\n '
    {{#answerdata}}' +\n '
  1. ' +\n '
    ' +\n '' +\n '' +\n '
    ' +\n '
    ' +\n '' +\n '\"{{STR.addmoreanswerblanks}}\"' +\n '' +\n '\"{{STR.delete}}\"' +\n '' +\n '\"{{STR.up}}\"' +\n '' +\n '\"{{STR.down}}\"' +\n '
    ' +\n '
    ' +\n '{{#numerical}}' +\n '
    ' +\n '
    ' +\n '' +\n '' +\n '
    ' +\n '
    ' +\n '{{/numerical}}' +\n '
    ' +\n '
    ' +\n '' +\n '' +\n '
    ' +\n '
    ' +\n '' +\n '' +\n '
    ' +\n '
    ' +\n '%' +\n '
    ' +\n '
  2. ' +\n '{{/answerdata}}
' +\n '
' +\n '
',\n TYPE: '
' +\n '

{{STR.chooseqtypetoadd}}

' +\n '
' +\n '
' +\n '{{#types}}' +\n '
' +\n '' +\n '
' +\n '{{/types}}
' +\n '
',\n FOOTER: '' +\n '',\n};\nconst FRACTIONS = [\n {value: 100},\n {value: 50},\n {value: 0},\n];\n\n// Language strings used in the modal dialogue.\nconst STR = {};\n\n/**\n * The editor instance that is injected via the onInit() function.\n *\n * @type {tinymce.Editor}\n * @private\n */\nlet _editor = null;\n\n/**\n * A reference to the currently open form.\n *\n * @param _form\n * @type {Node}\n * @private\n */\nlet _form = null;\n\n/**\n * An array containing the current answers options\n *\n * @param _answerdata\n * @type {Array}\n * @private\n */\nlet _answerdata = [];\n\n/**\n * The sub question type to be edited\n *\n * @param _qtype\n * @type {string|null}\n * @private\n */\nlet _qtype = null;\n\n/**\n * Remember the pos of the selected node.\n * @type {number}\n * @private\n */\nlet _selectedOffset = -1;\n\n/**\n * The maximum marks for the sub question\n *\n * @param _marks\n * @type {Integer}\n * @private\n */\nlet _marks = 1;\n\n/**\n * The modal dialogue to be displayed when designing the cloze question types.\n * @type {Modal|null}\n */\nlet _modal = null;\n\n/**\n * If its a normal selection of text, use it for the first answer field.\n * @type {string|null}\n */\nlet _firstAnswer = null;\n\n/**\n * Inject the editor instance and add markers to the cloze question texts.\n * @param {tinymce.Editor} ed\n */\nconst onInit = function(ed) {\n _editor = ed; // The current editor instance.\n // Add the marker spans.\n _addMarkers();\n // And get the language strings.\n _getStr(ed);\n};\n\n/**\n * Regex to recognize the question string in the text e.g. {1:NUMERICAL:...} or {:MULTICHOICE:...}\n * @param {tinymce.Editor} editor\n * @return {RegExp}\n * @private\n */\nconst _getRegexQtype = (editor) => {\n // eslint-disable-next-line max-len\n const baseQtypes = 'MULTICHOICE(_H|_V|_S|_HS|_VS)?|MULTIRESPONSE(_H|_S|_HS)?|NUMERICAL|SHORTANSWER(_C)?|SAC?|NM|MWC?|M[CR](V|H|VS|HS)?';\n const extQtypes = hasQtypeMultianswerrgx(editor) ? '|REGEXP(_C)?|RXC?' : '';\n return new RegExp('\\\\{([0-9]*):(' + baseQtypes + extQtypes + '):(.*?)(? {\n let strToFetch = [\n {key: 'answer', component: 'question'},\n {key: 'chooseqtypetoadd', component: 'question'},\n {key: 'defaultmark', component: 'question'},\n {key: 'feedback', component: 'question'},\n {key: 'correct', component: 'question'},\n {key: 'incorrect', component: 'question'},\n {key: 'addmoreanswerblanks', component: 'qtype_calculated'},\n {key: 'delete', component: 'core'},\n {key: 'up', component: 'core'},\n {key: 'down', component: 'core'},\n {key: 'tolerance', component: 'qtype_calculated'},\n {key: 'grade', component: 'grades'},\n {key: 'caseno', component: 'mod_quiz'},\n {key: 'caseyes', component: 'mod_quiz'},\n {key: 'answersingleno', component: 'qtype_multichoice'},\n {key: 'answersingleyes', component: 'qtype_multichoice'},\n {key: 'layoutselectinline', component: 'qtype_multianswer'},\n {key: 'layouthorizontal', component: 'qtype_multianswer'},\n {key: 'layoutvertical', component: 'qtype_multianswer'},\n {key: 'shufflewithin', component: 'mod_quiz'},\n {key: 'layoutmultiple_horizontal', component: 'qtype_multianswer'},\n {key: 'layoutmultiple_vertical', component: 'qtype_multianswer'},\n {key: 'pluginnamesummary', component: 'qtype_multichoice'},\n {key: 'pluginnamesummary', component: 'qtype_shortanswer'},\n {key: 'pluginnamesummary', component: 'qtype_numerical'},\n {key: 'multichoice', component},\n {key: 'multiresponse', component},\n {key: 'numerical', component: 'mod_quiz'},\n {key: 'shortanswer', component: 'mod_quiz'},\n {key: 'cancel', component: 'core'},\n {key: 'select', component},\n {key: 'insert', component},\n {key: 'pluginname', component},\n {key: 'customgrade', component},\n {key: 'err_custom_rate', component},\n {key: 'err_empty_answer', component},\n {key: 'err_none_correct', component},\n {key: 'err_not_numeric', component},\n ];\n let langKeys = [\n 'answer',\n 'chooseqtypetoadd',\n 'defaultmark',\n 'feedback',\n 'correct',\n 'incorrect',\n 'addmoreanswerblanks',\n 'delete',\n 'up',\n 'down',\n 'tolerance',\n 'grade',\n 'caseno',\n 'caseyes',\n 'singleno',\n 'singleyes',\n 'selectinline',\n 'horizontal',\n 'vertical',\n 'shuffle',\n 'multi_horizontal',\n 'multi_vertical',\n 'summary_multichoice',\n 'summary_shortanswer',\n 'summary_numerical',\n 'multichoice',\n 'multiresponse',\n 'numerical',\n 'shortanswer',\n 'btn_cancel',\n 'btn_select',\n 'btn_insert',\n 'title',\n 'custom_grade',\n 'err_custom_rate',\n 'err_empty_answer',\n 'err_none_correct',\n 'err_not_numeric',\n ];\n if (hasQtypeMultianswerrgx(editor)) {\n strToFetch.push({key: 'regexp', component: 'qtype_regexp'});\n strToFetch.push({key: 'pluginnamesummary', component: 'qtype_regexp'});\n langKeys.push('regexp');\n langKeys.push('summary_regexp');\n }\n getStrings(strToFetch).then(function() {\n const args = Array.from(arguments);\n langKeys.map((l, i) => {\n STR[l] = args[0][i];\n return ''; // Make the linter happy.\n });\n return ''; // Make the linter happy.\n }).catch(() => {\n return '';\n });\n};\n\n/**\n * Return the question types that are available for the cloze question.\n * @returns {Array}\n * @private\n */\nconst _getQuestionTypes = function() {\n let qtypes = [\n {\n 'type': 'MULTICHOICE',\n 'abbr': ['MC'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.selectinline, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_H',\n 'abbr': ['MCH'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.horizontal, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_V',\n 'abbr': ['MCV'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.vertical, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_S',\n 'abbr': ['MCS'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.selectinline, STR.shuffle, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_HS',\n 'abbr': ['MCHS'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.horizontal, STR.shuffle, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_VS',\n 'abbr': ['MCVS'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.vertical, STR.shuffle, STR.singleyes],\n },\n {\n 'type': 'MULTIRESPONSE',\n 'abbr': ['MR'],\n 'name': STR.multiresponse,\n 'summary': STR.summary_multichoice,\n 'options': [STR.multi_vertical, STR.singleno],\n },\n {\n 'type': 'MULTIRESPONSE_H',\n 'abbr': ['MRH'],\n 'name': STR.multiresponse,\n 'summary': STR.summary_multichoice,\n 'options': [STR.multi_horizontal, STR.singleno],\n },\n {\n 'type': 'MULTIRESPONSE_S',\n 'abbr': ['MRS'],\n 'name': STR.multiresponse,\n 'summary': STR.summary_multichoice,\n 'options': [STR.multi_vertical, STR.shuffle, STR.singleno],\n },\n {\n 'type': 'MULTIRESPONSE_HS',\n 'abbr': ['MRHS'],\n 'name': STR.multiresponse,\n 'summary': STR.summary_multichoice,\n 'options': [STR.multi_horizontal, STR.shuffle, STR.singleno],\n },\n {\n 'type': 'NUMERICAL',\n 'abbr': ['NM'],\n 'name': STR.numerical,\n 'summary': STR.summary_numerical,\n },\n {\n 'type': 'SHORTANSWER',\n 'abbr': ['SA', 'MW'],\n 'name': STR.shortanswer,\n 'summary': STR.summary_shortanswer,\n 'options': [STR.caseno],\n },\n {\n 'type': 'SHORTANSWER_C',\n 'abbr': ['SAC', 'MWC'],\n 'name': STR.shortanswer,\n 'summary': STR.summary_shortanswer,\n 'options': [STR.caseyes],\n },\n ];\n if (hasQtypeMultianswerrgx(_editor)) {\n qtypes.splice(11, 0, {\n 'type': 'REGEXP',\n 'abbr': ['RX'],\n 'name': STR.regexp,\n 'summary': STR.summary_regexp,\n 'options': [STR.caseno],\n }, {\n 'type': 'REGEXP_C',\n 'abbr': ['RXC'],\n 'name': STR.regexp,\n 'summary': STR.summary_regexp,\n 'options': [STR.caseyes],\n });\n }\n return qtypes;\n};\n\n/**\n * Create the modal.\n * @return {Promise}\n * @private\n */\nconst _createModal = async function() {\n // Create the modal dialogue. Depending on whether we have a selected node or not, the content is different.\n const cfg = {\n title: STR.title,\n templateContext: {\n elementid: _editor.id\n },\n removeOnClose: true,\n large: true,\n };\n if (typeof Modal.create === 'function') {\n _modal = await Modal.create(cfg);\n } else {\n _modal = await ModalFactory.create(cfg);\n }\n};\n\n/**\n * Display modal dialogue to edit a cloze question. Either a form is displayed to edit subquestion or a list\n * of possible questions is show.\n *\n * @method displayDialogue\n * @public\n */\nconst displayDialogue = async function() {\n await _createModal();\n\n // Resolve whether cursor is in a subquestion.\n const subquestion = resolveSubquestion();\n if (subquestion) {\n _firstAnswer = null;\n // Subquestion found, remember which node of the marker nodes is selected.\n _selectedOffset = indexOfNode(_editor.dom.select('.' + markerClass), subquestion);\n _parseSubquestion(subquestion.innerHTML);\n _setDialogueContent(_qtype);\n } else {\n // No subquestion found, no offset to remember.\n _firstAnswer = _editor.selection.getContent();\n _selectedOffset = -1;\n _setDialogueContent();\n }\n};\n\n/**\n * On double click, check that we are on a question and display the dialogue with the question to edit.\n * @method displayDialogueForEdit\n * @param {Node} target\n * @public\n */\nconst displayDialogueForEdit = async function(target) {\n\n const subquestion = resolveSubquestion(target);\n if (!subquestion) {\n return;\n }\n await _createModal();\n _selectedOffset = indexOfNode(_editor.dom.select('.' + markerClass), subquestion);\n _parseSubquestion(subquestion.innerHTML);\n _setDialogueContent(_qtype);\n};\n\n/**\n * Search for cloze questions based on a regular expression. All the matching snippets at least contain the cloze\n * question definition. Although Moodle does not support encapsulated other functions within curly brackets, we\n * still try to find the correct closing bracket. The so extracted cloze question is surrounded by a marker span\n * element, that contains attributes so that the content inside the span cannot be modified by the editor (in the\n * textarea). Also, this makes it a lot easier to select the question, edit it in the dialogue and replace the result\n * in the existing text area.\n *\n * @method _addMarkers\n * @private\n */\nconst _addMarkers = function() {\n\n let content = _editor.getContent();\n let newContent = '';\n\n // Check if there is already a marker span. In this case we do not have to do anything.\n if (content.indexOf(markerClass) !== -1) {\n return;\n }\n\n let m;\n do {\n m = content.match((_getRegexQtype(_editor)));\n if (!m) { // No match of a cloze question, then we are done.\n newContent += content;\n break;\n }\n // Copy the current match to the new string preceded with the .\n const pos = content.indexOf(m[0]);\n newContent += content.substring(0, pos) + markerSpan + content.substring(pos, pos + m[0].length);\n content = content.substring(pos + m[0].length);\n\n // Count the { in the string, should be just one (the very first one at position 0).\n let level = (m[0].match(/\\{/g) || []).length;\n if (level === 1) {\n // If that's the case, we close the span and the cloze question text is the innerHTML of that marker span.\n newContent += '';\n continue; // Look for the next matching cloze question.\n }\n // If there are more { than } in the string, then we did not find the corresponding } that belongs to the cloze string.\n while (level > 1) {\n const a = content.indexOf('{');\n const b = content.indexOf('}');\n if (a > -1 && b > -1 && a < b) { // The { is before another } so remember to find as many } until we back at level 1.\n level++;\n newContent = content.substring(0, a);\n content = content.substring(a + 1);\n } else if (b > -1) { // We found a closing } to a previously {.\n newContent = content.substring(0, b);\n content = content.substring(b + 1);\n level--;\n } else {\n level = 1; // Should not happen, just to stop the endless loop.\n }\n }\n newContent += '
';\n } while (m);\n _editor.setContent(newContent);\n};\n\n/**\n * Look for the marker span elements around a cloze question and remove that span. Also, the marker for a new\n * node to be inserted would be removed here as well.\n */\nconst _removeMarkers = function() {\n for (const span of _editor.dom.select('span.' + markerClass)) {\n _editor.dom.setOuterHTML(span, span.classList.contains('new') ? '' : span.innerHTML);\n }\n};\n\n/**\n * When the source code view dialogue is show, we must remove the spans around the cloze question strings\n * from the editor content and add them again when the dialogue is closed.\n * Since this event is also triggered when the editor data is saved, we use this function to remove the\n * highlighting content at that time.\n *\n * @method onBeforeGetContent\n * @param {object} content\n * @public\n */\nconst onBeforeGetContent = function(content) {\n if (!isNull(content.source_view) && content.source_view === true) {\n // If the user clicks on 'Cancel' or the close button on the html\n // source code dialog view, make sure we re-add the visual styling.\n var onClose = function() {\n _editor.off('close', onClose);\n _addMarkers();\n };\n _editor.on('CloseWindow', () => {\n onClose();\n });\n // Remove markers only if modal is not called, otherwise we will lose our new question marker.\n if (!_modal) {\n _removeMarkers();\n }\n }\n};\n\n/**\n * Fires when the form containing the editor is submitted.\n *\n * @method onSubmit\n * @public\n */\nconst onSubmit = function() {\n _removeMarkers();\n};\n\n/**\n * Set the dialogue content for the tool, attaching any required events. Either the modal dialogue displays\n * a list of the question types for the form for a particular question to edit. The set content is also\n * called when the form has changed (up or down move, deletion and adding a response). We must be aware of that\n * an event to the dialogue buttons must be attached once only. Therefore, when the form content is modified, only\n * the form events for the answers are set again, the general events are nor (nomodalevents is true then).\n *\n * @method _setDialogueContent\n * @param {String} qtype The question type to be used\n * @param {boolean} nomodalevents Optional do not attach events.\n * @private\n */\nconst _setDialogueContent = function(qtype, nomodalevents) {\n const footer = Mustache.render(TEMPLATE.FOOTER, {\n cancel: STR.btn_cancel,\n submit: !qtype ? STR.btn_select : STR.btn_insert,\n });\n let contentText;\n if (!qtype) {\n contentText = Mustache.render(TEMPLATE.TYPE, {\n CSS: CSS,\n STR: STR,\n qtype: _qtype,\n types: _getQuestionTypes()\n });\n } else {\n contentText = Mustache.render(TEMPLATE.FORM, {\n CSS: CSS,\n STR: STR,\n answerdata: _answerdata,\n elementid: getUuid(),\n qtype: _qtype,\n name: _getQuestionTypes().filter(q => _qtype === q.type)[0].name,\n marks: _marks,\n numerical: (_qtype === 'NUMERICAL' || _qtype === 'NM')\n });\n }\n _modal.setBody(contentText);\n _modal.setFooter(footer);\n _modal.show();\n const $root = _modal.getRoot();\n _form = $root.get(0).querySelector('form');\n _toggleDeleteIcon();\n\n if (!nomodalevents) {\n _modal.registerEventListeners();\n _modal.registerCloseOnSave();\n _modal.registerCloseOnCancel();\n $root.on(ModalEvents.cancel, _cancel);\n\n if (!qtype) { // For the question list we need the choice handler only, and we are done.\n $root.on(ModalEvents.save, _choiceHandler);\n return;\n } // Handler to add the question string to the editor content.\n $root.on(ModalEvents.save, _setSubquestion);\n }\n // The form needs events for the icons to move up/down, add or delete a response.\n const getTarget = e => {\n let p = e.target;\n while (!isNull(p) && p.nodeType === 1 && p.tagName !== 'A') {\n p = p.parentNode;\n }\n if (isNull(p.classList)) {\n return null;\n }\n return p;\n };\n\n _form.addEventListener('click', e => {\n const p = getTarget(e);\n if (isNull(p)) {\n return;\n }\n if (p.classList.contains(CSS.DELETE)) {\n e.preventDefault();\n _deleteAnswer(p);\n return;\n }\n if (p.classList.contains(CSS.ADD)) {\n e.preventDefault();\n _addAnswer(p);\n return;\n }\n if (p.classList.contains(CSS.LOWER)) {\n e.preventDefault();\n _lowerAnswer(p);\n return;\n }\n if (p.classList.contains(CSS.RAISE)) {\n e.preventDefault();\n _raiseAnswer(p);\n }\n });\n _form.addEventListener('keyup', e => {\n const p = getTarget(e);\n if (isNull(p)) {\n return;\n }\n if (p.classList.contains(CSS.ANSWER) || p.classList.contains(CSS.FEEDBACK)) {\n e.preventDefault();\n _addAnswer(p);\n }\n });\n _form.querySelectorAll('.' + CSS.FRACTION).forEach((sel) => {\n sel.addEventListener('change', e => {\n const id = e.target.getAttribute('id');\n if (e.target.value === selectCustomPercent) {\n document.getElementById(id + '_custom').parentNode.classList.remove('hidden');\n } else {\n document.getElementById(id + '_custom').parentNode.classList.add('hidden');\n }\n });\n });\n};\n\n/**\n * If there is one answer field, hide the delete icon. Otherwise show them\n * all to allow deletion of any answer.\n *\n * @private\n */\nconst _toggleDeleteIcon = function() {\n const deleteIcons = _form.querySelectorAll('.' + CSS.DELETE);\n if (deleteIcons.length === 1) {\n deleteIcons[0].classList.add('hidden');\n return;\n }\n for (let i = 0; i < deleteIcons.length; i++) {\n deleteIcons[i].classList.remove('hidden');\n }\n};\n\n/**\n * Handle question choice.\n *\n * @method _choiceHandler\n * @private\n * @param {Event} e Event from button click in chooser\n */\nconst _choiceHandler = function(e) {\n e.preventDefault();\n let qtype = _form.querySelector('input[name=qtype]:checked');\n if (qtype) {\n _qtype = qtype.value;\n }\n // For numerical and short answer questions (and when installed regexp) we offer one response field only.\n // All other question types have three empty response fields.\n const max = (_qtype.indexOf('SHORTANSWER') !== -1 || _qtype === 'NUMERICAL' || _qtype.indexOf('REGEXP') !== -1) ? 1 : 3;\n const blankAnswer = {\n id: getUuid(),\n answer: '',\n feedback: '',\n fraction: 100,\n fractionOptions: getFractionOptions(''),\n tolerance: 0,\n isCustomGrade: false,\n };\n _answerdata = [];\n for (let x = 0; x < max; x++) {\n _answerdata.push({...blankAnswer, id: getUuid()});\n }\n // The first response field gets the default grade correct.\n _answerdata[0].fractionOptions = getFractionOptions('=');\n // In case the user seleced some text, this is used as the first answer.\n if (_firstAnswer) {\n _answerdata[0].answer = _firstAnswer;\n }\n _modal.destroy();\n // Our choice is stored in _qtype. We need to create the modal dialogue with the form now.\n _createModal().then(() => {\n _setDialogueContent(_qtype);\n _form.querySelector('.' + CSS.ANSWER).focus();\n return ''; // Make the linter happy.\n }).catch(() => {\n return '';\n });\n};\n\n/**\n * Parse question and set properties found.\n *\n * @method _parseSubquestion\n * @private\n * @param {String} question The question string\n */\nconst _parseSubquestion = function(question) {\n _answerdata = []; // Flush answers to have an empty dialogue if something goes wrong parsing the question string.\n const regexQtype = _getRegexQtype(_editor);\n const parts = regexQtype.exec(question);\n regexQtype.lastIndex = 0; // Reset lastIndex so that the next match starts from the beginning of the question string.\n if (!parts) {\n return;\n }\n _marks = parts[1];\n _qtype = parts[2];\n // Convert the short notation to the long form e.g. SA to SHORTANSWER.\n if (_qtype.length < 5) {\n _getQuestionTypes().forEach(l => {\n for (const a of l.abbr) {\n if (a === _qtype) {\n _qtype = l.type;\n return;\n }\n }\n });\n }\n // Depending on the regex the position of the answers is different.\n const answers = parts[hasQtypeMultianswerrgx(_editor) ? 8 : 7].match(/(\\\\.|[^~])*/g);\n if (!answers) {\n return;\n }\n answers.forEach(function(answer) {\n const options = /^(%(-?[.0-9]+)%|(=?))((\\\\.|[^#])*)#?(.*)/.exec(answer);\n if (options && options[4]) {\n let frac = '';\n if (options[3]) {\n frac = options[3] === '=' ? '=' : 100;\n } else if (options[2]) {\n frac = options[2];\n }\n if (_qtype === 'NUMERICAL' || _qtype === 'NM') {\n const tolerance = /^([^:]*):?(.*)/.exec(options[4])[2] || 0;\n _answerdata.push({\n id: getUuid(),\n answer: strdecode(options[4].replace(/:.*/, '')),\n feedback: strdecode(options[6]),\n tolerance: tolerance,\n fraction: frac,\n fractionOptions: getFractionOptions(frac),\n isCustomGrade: isCustomGrade(frac),\n });\n return;\n }\n _answerdata.push({\n answer: strdecode(options[4]),\n id: getUuid(),\n feedback: strdecode(options[6]),\n fraction: frac,\n fractionOptions: getFractionOptions(frac),\n isCustomGrade: isCustomGrade(frac),\n });\n }\n });\n};\n\n/**\n * Insert a new set of answer blanks below the button.\n *\n * @method _addAnswer\n * @param {Node} a Node that is the referred element\n * @private\n */\nconst _addAnswer = function(a) {\n let index = indexOfNode(_form.querySelectorAll('.' + CSS.ADD), a);\n if (index === -1) {\n index = 0;\n }\n let fraction = '';\n let answer = '';\n let feedback = '';\n let tolerance = 0;\n if (a.closest('li')) {\n fraction = a.closest('li').querySelector('.' + CSS.FRACTION).value;\n if (fraction === selectCustomPercent) {\n fraction = a.closest('li').querySelector('.' + CSS.FRAC_CUSTOM).value;\n }\n answer = a.closest('li').querySelector('.' + CSS.ANSWER).value;\n feedback = a.closest('li').querySelector('.' + CSS.FEEDBACK).value;\n if (a.closest('li').querySelector('.' + CSS.TOLERANCE)) {\n tolerance = a.closest('li').querySelector('.' + CSS.TOLERANCE).value;\n }\n }\n _processFormData();\n _answerdata.splice(index, 0, {\n id: getUuid(),\n answer: answer,\n feedback: feedback,\n fraction: fraction,\n fractionOptions: getFractionOptions(fraction),\n tolerance: tolerance,\n isCustomGrade: isCustomGrade(fraction)\n });\n _setDialogueContent(_qtype, true);\n _toggleDeleteIcon();\n _form.querySelectorAll('.' + CSS.ANSWER).item(index).focus();\n};\n\n/**\n * Delete set of answer next to the button.\n *\n * @method _deleteAnswer\n * @param {Node} a Node that is the referred element\n * @private\n */\nconst _deleteAnswer = function(a) {\n let index = indexOfNode(_form.querySelectorAll('.' + CSS.DELETE), a);\n if (index === -1) {\n index = indexOfNode(_form.querySelectorAll('li'), a.closest('li'));\n }\n _processFormData();\n _answerdata.splice(index, 1);\n _setDialogueContent(_qtype, true);\n const answers = _form.querySelectorAll('.' + CSS.ANSWER);\n index = Math.min(index, answers.length - 1);\n answers.item(index).focus();\n _toggleDeleteIcon();\n};\n\n/**\n * Lower answer option\n *\n * @method _lowerAnswer\n * @param {Node} a Node that is the referred element\n * @private\n */\nconst _lowerAnswer = function(a) {\n const li = a.closest('li');\n li.before(li.nextSibling);\n li.querySelector('.' + CSS.ANSWER).focus();\n};\n\n/**\n * Raise answer option\n *\n * @method _raiseAnswer\n * @param {Node} a Node that is the referred element\n * @private\n */\nconst _raiseAnswer = function(a) {\n const li = a.closest('li');\n li.after(li.previousSibling);\n li.querySelector('.' + CSS.ANSWER).focus();\n};\n\n/**\n * Reset and hide form.\n *\n * @method _cancel\n * @param {Event} e Event from button click\n * @private\n */\nconst _cancel = function(e) {\n e.preventDefault();\n // In case there is a marker where the new question should be inserted in the text it needs to be removed.\n for (const span of _editor.dom.select('.' + markerClass + '.new')) {\n span.remove();\n }\n _modal.destroy();\n _editor.focus();\n _modal = null;\n};\n\n/**\n * Insert question string into editor content and reset and hide form. If the form contains an error\n * nothing happens.\n *\n * @method _setSubquestion\n * @param {Event} e Event from button click\n * @private\n */\nconst _setSubquestion = function(e) {\n e.preventDefault();\n // Check if there are any errors and if so, fill the error container with the\n // messages and return without going any further and closing the dialogue.\n const errMsg = _form.querySelector('.msg-error');\n const formErrors = _processFormData(true);\n if (formErrors.length > 0) {\n errMsg.innerHTML = '
  • ' + formErrors.join('
  • ') + '
';\n errMsg.classList.remove('hidden');\n return;\n } else {\n errMsg.classList.add('hidden');\n }\n // Build the parser function from the data, that is going to be placed into the editor content.\n let question = '{' + _marks + ':' + _qtype + ':';\n\n // Filter all empty responses\n for (let i = 0; i < _answerdata.length; i++) {\n if (_answerdata[i].raw === '') {\n continue;\n }\n question += _answerdata[i].fraction && !isNaN(_answerdata[i].fraction)\n ? '%' + _answerdata[i].fraction + '%' : _answerdata[i].fraction;\n question += strencode(_answerdata[i].answer);\n if (_qtype === 'NM' || _qtype === 'NUMERICAL') {\n question += ':' + _answerdata[i].tolerance;\n }\n if (_answerdata[i].feedback) {\n question += '#' + strencode(_answerdata[i].feedback);\n }\n if (i < _answerdata.length - 1) {\n question += '~';\n }\n }\n if (question.slice(-1) === '~') {\n question = question.substring(0, question.length - 1);\n }\n question += '}';\n\n _modal.destroy();\n _modal = null;\n _editor.focus();\n if (_selectedOffset > -1) { // We have to replace one of the marker spans (the innerHTML contains the question string).\n _editor.dom.select('.' + markerClass)[_selectedOffset].innerHTML = question;\n } else {\n // Just add the question text with markup.\n _editor.insertContent(markerSpan + question + '');\n }\n};\n\n/**\n * Read the form data, process it and store the result in the internal _answerdata array.\n * Also, if validation is enabled, the fields are checked for invalid values e.g.\n * - answer field is empty (if a correct answer is contained, empty fields are eliminated).\n * - custom_grade field whenin use and does not contain a number.\n * - no field is marked as a correct answer.\n * - tolerance field must be in percentage of min -100 and max 100.\n * Any field with an error is maked and the first field containing an error gets the focus.\n *\n * @method _processFormData\n * @param {boolean} validate\n * @return {Array}\n * @private\n */\nconst _processFormData = function(validate) {\n _answerdata = [];\n let globalErrors = [];\n const answers = _form.querySelectorAll('.' + CSS.ANSWER);\n const feedbacks = _form.querySelectorAll('.' + CSS.FEEDBACK);\n const fractions = _form.querySelectorAll('.' + CSS.FRACTION);\n const customGrades = _form.querySelectorAll('.' + CSS.FRAC_CUSTOM);\n const tolerances = _form.querySelectorAll('.' + CSS.TOLERANCE);\n // Remove any error classes.\n for (let i = 0; i < answers.length; i++) {\n answers.item(i).classList.remove('error');\n customGrades.item(i).classList.remove('error');\n const currentAnswer = {\n raw: answers.item(i).value.trim(),\n answer: answers.item(i).value.trim(),\n id: getUuid(),\n feedback: feedbacks.item(i).value,\n fraction: fractions.item(i).value === selectCustomPercent ? customGrades.item(i).value : fractions.item(i).value,\n fractionOptions: getFractionOptions(fractions.item(i).value),\n tolerance: tolerances.length > 0 ? tolerances.item(i).value : 0,\n isCustomGrade: fractions.item(i).value === selectCustomPercent\n };\n if (_qtype === 'NM' || _qtype === 'NUMERICAL') {\n tolerances.item(i).classList.remove('error');\n // In numeric questions convert answer and tolerance to numeric values (this filters non numeric values).\n currentAnswer.answer = Number(currentAnswer.answer);\n currentAnswer.tolerance = Number(currentAnswer.tolerance);\n }\n _answerdata.push(currentAnswer);\n }\n _marks = _form.querySelector('.' + CSS.MARKS).value;\n\n if (validate) {\n const {hasCorrectAnswer, errors} = _validateAnswers();\n for (let i = 0; i < _answerdata.length; i++) {\n for (const err of _answerdata[i].hasErrors) {\n if (hasCorrectAnswer && (err === 'empty_answer' || err === 'correct_but_empty')) {\n break;\n }\n if (err === 'answer_not_numeric' || err === 'empty_answer' || err === 'correct_but_empty') {\n answers.item(i).classList.add('error');\n } else if (err === 'tolerance_not_numeric') {\n tolerances.item(i).classList.add('error');\n } else if (err === 'error_custom_rate') {\n customGrades.item(i).classList.add('error');\n }\n }\n }\n globalErrors = _translateGlobalErrors(hasCorrectAnswer, errors);\n // If we have errors, we focus the first field that contains an error.\n if (globalErrors.length > 0) {\n _form.querySelector('input.error').focus();\n }\n }\n return globalErrors;\n};\n\n/**\n * Validates the answer array. Checks for each question if the data from the form is\n * incomplete or has other errors. These are flagged accordingly in the array element.\n * The retruned object contains the properties:\n * - hasCorrectAnswer {boolean} is true if there is at least one correct answer.\n * - errors {Array} list of strings that contain an error code that is globaly used for error messages.\n *\n * @return {Array}\n * @private\n */\nconst _validateAnswers = function() {\n let errors = [];\n let hasCorrect = false;\n for (let i = 0; i < _answerdata.length; i++) {\n _answerdata[i].hasErrors = [];\n // Check if we have an empty answer string.\n if (_answerdata[i].raw === '') {\n _answerdata[i].hasErrors.push('empty_answer');\n }\n // When there are numeric questions, check that the answer and tolerance is a valid number.\n if (_qtype === 'NM' || _qtype === 'NUMERICAL') {\n if (isNaN(_answerdata[i].answer) && _answerdata[i].raw !== '') {\n _answerdata[i].hasErrors.push('answer_not_numeric');\n }\n if (isNaN(_answerdata[i].tolerance)) {\n _answerdata[i].hasErrors.push('tolerance_not_numeric');\n }\n }\n // Check the custom grade, that must be a percentage number between -100 and 100.\n if (_answerdata[i].isCustomGrade &&\n (isNaN(_answerdata[i].fraction) || _answerdata[i].fraction < -100 || _answerdata[i].fraction > 100\n || _answerdata[i].fraction.trim() === '')\n ) {\n _answerdata[i].hasErrors.push('error_custom_rate');\n }\n // We found a correct answer, when grade is marked as 100 or \"=\" and the answer is not empty.\n if (_answerdata[i].fraction === '100' || _answerdata[i].fraction === '=') {\n if (_answerdata[i].raw !== '') {\n _answerdata[i].isCorrect = true;\n hasCorrect = true;\n } else {\n _answerdata[i].hasErrors.push('correct_but_empty');\n }\n }\n errors = errors.concat(_answerdata[i].hasErrors);\n }\n\n return {\n hasCorrectAnswer: hasCorrect,\n errors: _combineGlobalErrors(hasCorrect, errors),\n };\n};\n\n/**\n * Translate the errors into a readable string for a list that is used on top of the\n * input fields, to indicate what part of the data is incorrect.\n *\n * @param {Boolean} hasCorrectAnswer\n * @param {Array} errors\n * @return {Array}\n * @private\n */\nconst _translateGlobalErrors = function(hasCorrectAnswer, errors) {\n const errTranslated = [];\n // Translate the error strings into a string that can be displayed in the form.\n const trMsg = {\n emptyanswer: STR.err_empty_answer,\n answernotnumeric: STR.err_not_numeric,\n tolerancenotnumeric: STR.err_not_numeric,\n errorcustomrate: STR.err_custom_rate,\n nonecorrect: STR.err_none_correct,\n };\n for (const err of errors) {\n // If there's at least one correct answer, we filter out all empty answers and therefore do not\n // show the error message.\n if (hasCorrectAnswer && err === 'empty_answer' || err === 'correct_but_empty') {\n continue;\n }\n // Remove underscore (we do this only because of the js linter).\n const key = err.replace(/_/g, '');\n errTranslated.push(trMsg[key]);\n }\n return errTranslated;\n};\n\n/**\n * Combine the error list from the answers to a global list.\n *\n * @param {Boolean} hasCorrectAnswer\n * @param {Array} errors\n * @return {Array}\n * @private\n */\nconst _combineGlobalErrors = function(hasCorrectAnswer, errors) {\n // Unique errors for the global error list.\n const errUnique = errors.filter((value, index, array) => array.indexOf(value) === index);\n // If we have a correct answer, do not show the empty answer error, because empty responses are filtered.\n if (hasCorrectAnswer) {\n const i = errUnique.indexOf('empty_answer');\n if (i > -1) {\n errUnique.splice(i, 1);\n }\n } else if (!errUnique.includes('correct_but_empty')) {\n errUnique.push('none_correct');\n }\n return errUnique;\n};\n\n/**\n * Check whether cursor is in a subquestion and return subquestion text if\n * true.\n *\n * @method resolveSubquestion\n * @param {Node|null} element The element to check if it is a subquestion.\n * @return {Mixed} The selected node of with the subquestion if found, false otherwise.\n */\nconst resolveSubquestion = function(element) {\n let span = element || _editor.selection.getStart();\n if (!isNull(span.classList) && span.classList.contains(markerClass)) {\n return span;\n }\n _editor.dom.getParents(span, elm => {\n // Are we in a span that encapsulates the cloze question?\n if (!isNull(elm.classList) && elm.classList.contains(markerClass)) {\n return elm;\n }\n return false;\n });\n return false;\n};\n\nexport {\n displayDialogue,\n displayDialogueForEdit,\n resolveSubquestion,\n onInit,\n onBeforeGetContent,\n onSubmit,\n};\n"],"names":["isNull","a","strdecode","t","String","replace","strencode","indexOfNode","list","node","i","length","getUuid","crypto","randomUUID","Math","floor","random","toString","getFractionOptions","s","attrSel","isSel","html","STR","incorrect","correct","FRACTIONS","forEach","item","value","indexOf","custom_grade","isCustomGrade","found","markerClass","markerSpan","CSS","ANSWER","ANSWERS","ADD","CANCEL","DELETE","FEEDBACK","FRACTION","FRAC_CUSTOM","LEFT","LOWER","RIGHT","MARKS","DUPLICATE","RAISE","SUBMIT","SUMMARY","TOLERANCE","TYPE","TEMPLATE","FORM","M","util","image_url","FOOTER","_editor","_form","_answerdata","_qtype","_selectedOffset","_marks","_modal","_firstAnswer","ed","_addMarkers","_getStr","_getRegexQtype","editor","extQtypes","RegExp","async","strToFetch","key","component","langKeys","push","then","args","Array","from","arguments","map","l","catch","_getQuestionTypes","qtypes","multichoice","summary_multichoice","selectinline","singleyes","horizontal","vertical","shuffle","multiresponse","multi_vertical","singleno","multi_horizontal","numerical","summary_numerical","shortanswer","summary_shortanswer","caseno","caseyes","splice","regexp","summary_regexp","_createModal","cfg","title","templateContext","elementid","id","removeOnClose","large","Modal","create","ModalFactory","subquestion","resolveSubquestion","dom","select","_parseSubquestion","innerHTML","_setDialogueContent","selection","getContent","target","m","content","newContent","match","pos","substring","level","b","setContent","_removeMarkers","span","setOuterHTML","classList","contains","source_view","onClose","off","on","qtype","nomodalevents","footer","Mustache","render","cancel","btn_cancel","submit","btn_insert","btn_select","contentText","answerdata","name","filter","q","type","marks","types","setBody","setFooter","show","$root","getRoot","get","querySelector","_toggleDeleteIcon","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","ModalEvents","_cancel","save","_choiceHandler","_setSubquestion","getTarget","e","p","nodeType","tagName","parentNode","addEventListener","preventDefault","_deleteAnswer","_addAnswer","_lowerAnswer","_raiseAnswer","querySelectorAll","sel","getAttribute","document","getElementById","remove","add","deleteIcons","max","blankAnswer","answer","feedback","fraction","fractionOptions","tolerance","x","destroy","focus","question","regexQtype","parts","exec","lastIndex","abbr","answers","options","frac","index","closest","_processFormData","min","li","before","nextSibling","after","previousSibling","errMsg","formErrors","join","raw","isNaN","slice","insertContent","validate","globalErrors","feedbacks","fractions","customGrades","tolerances","currentAnswer","trim","Number","hasCorrectAnswer","errors","_validateAnswers","err","hasErrors","_translateGlobalErrors","hasCorrect","isCorrect","concat","_combineGlobalErrors","errTranslated","trMsg","emptyanswer","err_empty_answer","answernotnumeric","err_not_numeric","tolerancenotnumeric","errorcustomrate","err_custom_rate","nonecorrect","err_none_correct","errUnique","array","includes","element","getStart","getParents","elm"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"ui.min.js","sources":["../src/ui.js"],"sourcesContent":["// This file is part of Moodle - https://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Plugin tiny_cloze for TinyMCE v6 in Moodle.\n *\n * @module tiny_cloze/ui\n * @copyright 2023 MoodleDACH\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport ModalEvents from 'core/modal_events';\nimport Modal from 'core/modal';\nimport ModalFactory from 'core/modal_factory';\nimport Mustache from 'core/mustache';\nimport {get_strings as getStrings} from 'core/str';\nimport {component} from './common';\nimport {hasQtypeMultianswerrgx} from './options';\n\n// Helper functions.\nconst isNull = a => a === null || a === undefined;\nconst strdecode = t => String(t).replace(/\\\\(#|\\}|~)/g, '$1');\nconst strencode = t => String(t).replace(/(#|\\}|~)/g, '\\\\$1');\nconst indexOfNode = (list, node) => {\n for (let i = 0; i < list.length; i++) {\n if (list[i] === node) {\n return i;\n }\n }\n return -1;\n};\nconst getUuid = function() {\n if (!isNull(crypto.randomUUID)) {\n return crypto.randomUUID();\n }\n return 'ed-cloze-' + Math.floor(Math.random() * 100000).toString();\n};\n// Grade Selector value when custom percentage is selected.\nconst selectCustomPercent = '__custom__';\n// This is a specific helper function to return the options html for the fraction select element.\nconst getFractionOptions = s => {\n const attrSel = ' selected=\"selected\"';\n let isSel = s === '=' ? attrSel : '';\n let html = ``;\n FRACTIONS.forEach(item => {\n isSel = item.value.toString() === s ? attrSel : '';\n html += ``;\n });\n isSel = s !== '' && html.indexOf(attrSel) === -1 ? attrSel : '';\n html += ``;\n return html;\n};\n// Check if the value is a custom grade value (in order to show the input field).\nconst isCustomGrade = s => {\n if (s === '=' || s === '') {\n return false;\n }\n let found = false;\n FRACTIONS.forEach(item => {\n if (item.value.toString() === s) {\n found = true;\n }\n });\n return !found;\n};\n// Marker class and the whole span element that is used to encapsulate the cloze question text.\nconst markerClass = 'cloze-question-marker';\nconst markerSpan = '';\n\n// CSS classes that are used in the modal dialogue.\nconst CSS = {\n ANSWER: 'tiny_cloze_answer',\n ANSWERS: 'tiny_cloze_answers',\n ADD: 'tiny_cloze_add',\n CANCEL: 'tiny_cloze_cancel',\n DELETE: 'tiny_cloze_delete',\n FEEDBACK: 'tiny_cloze_feedback',\n FRACTION: 'tiny_cloze_fraction',\n FRAC_CUSTOM: 'tiny_cloze_frac_custom',\n LEFT: 'tiny_cloze_col0',\n LOWER: 'tiny_cloze_down',\n RIGHT: 'tiny_cloze_col1',\n MARKS: 'tiny_cloze_marks',\n DUPLICATE: 'tiny_cloze_duplicate',\n RAISE: 'tiny_cloze_up',\n SUBMIT: 'tiny_cloze_submit',\n SUMMARY: 'tiny_cloze_summary',\n TOLERANCE: 'tiny_cloze_tolerance',\n TYPE: 'tiny_cloze_qtype'\n};\nconst TEMPLATE = {\n FORM: '
' +\n '

{{name}} ({{qtype}})

' +\n '
' +\n '
' +\n '
' +\n '' +\n '' +\n '' +\n '\"{{STR.addmoreanswerblanks}}\"' +\n '
' +\n '
' +\n '
' +\n '
' +\n '
    {{#answerdata}}' +\n '
  1. ' +\n '
    ' +\n '' +\n '' +\n '
    ' +\n '
    ' +\n '' +\n '\"{{STR.addmoreanswerblanks}}\"' +\n '' +\n '\"{{STR.delete}}\"' +\n '' +\n '\"{{STR.up}}\"' +\n '' +\n '\"{{STR.down}}\"' +\n '
    ' +\n '
    ' +\n '{{#numerical}}' +\n '
    ' +\n '
    ' +\n '' +\n '' +\n '
    ' +\n '
    ' +\n '{{/numerical}}' +\n '
    ' +\n '
    ' +\n '' +\n '' +\n '
    ' +\n '
    ' +\n '' +\n '' +\n '
    ' +\n '
    ' +\n '%' +\n '
    ' +\n '
  2. ' +\n '{{/answerdata}}
' +\n '
' +\n '
',\n TYPE: '
' +\n '

{{STR.chooseqtypetoadd}}

' +\n '
' +\n '
' +\n '{{#types}}' +\n '
' +\n '' +\n '
' +\n '{{/types}}
' +\n '
',\n FOOTER: '' +\n '',\n};\nconst FRACTIONS = [\n {value: 100},\n {value: 50},\n {value: 0},\n];\n\n// Language strings used in the modal dialogue.\nconst STR = {};\n\n/**\n * The editor instance that is injected via the onInit() function.\n *\n * @type {tinymce.Editor}\n * @private\n */\nlet _editor = null;\n\n/**\n * A reference to the currently open form.\n *\n * @param _form\n * @type {Node}\n * @private\n */\nlet _form = null;\n\n/**\n * An array containing the current answers options\n *\n * @param _answerdata\n * @type {Array}\n * @private\n */\nlet _answerdata = [];\n\n/**\n * The sub question type to be edited\n *\n * @param _qtype\n * @type {string|null}\n * @private\n */\nlet _qtype = null;\n\n/**\n * Remember the pos of the selected node.\n * @type {number}\n * @private\n */\nlet _selectedOffset = -1;\n\n/**\n * The maximum marks for the sub question\n *\n * @param _marks\n * @type {Integer}\n * @private\n */\nlet _marks = 1;\n\n/**\n * The modal dialogue to be displayed when designing the cloze question types.\n * @type {Modal|null}\n */\nlet _modal = null;\n\n/**\n * If its a normal selection of text, use it for the first answer field.\n * @type {string|null}\n */\nlet _firstAnswer = null;\n\n/**\n * Inject the editor instance and add markers to the cloze question texts.\n * @param {tinymce.Editor} ed\n */\nconst onInit = function(ed) {\n _editor = ed; // The current editor instance.\n // Add the marker spans.\n _addMarkers();\n // And get the language strings.\n _getStr();\n};\n\n/**\n * Regex to recognize the question string in the text e.g. {1:NUMERICAL:...} or {:MULTICHOICE:...}\n * @param {tinymce.Editor} editor\n * @return {RegExp}\n * @private\n */\nconst _getRegexQtype = (editor) => {\n // eslint-disable-next-line max-len\n const baseQtypes = 'MULTICHOICE(_H|_V|_S|_HS|_VS)?|MULTIRESPONSE(_H|_S|_HS)?|NUMERICAL|SHORTANSWER(_C)?|SAC?|NM|MWC?|M[CR](V|H|VS|HS)?';\n const extQtypes = hasQtypeMultianswerrgx(editor) ? '|REGEXP(_C)?|RXC?' : '';\n return new RegExp('\\\\{([0-9]*):(' + baseQtypes + extQtypes + '):(.*?)(? {\n let strToFetch = [\n {key: 'answer', component: 'question'},\n {key: 'chooseqtypetoadd', component: 'question'},\n {key: 'defaultmark', component: 'question'},\n {key: 'feedback', component: 'question'},\n {key: 'correct', component: 'question'},\n {key: 'incorrect', component: 'question'},\n {key: 'addmoreanswerblanks', component: 'qtype_calculated'},\n {key: 'delete', component: 'core'},\n {key: 'up', component: 'core'},\n {key: 'down', component: 'core'},\n {key: 'tolerance', component: 'qtype_calculated'},\n {key: 'grade', component: 'grades'},\n {key: 'caseno', component: 'mod_quiz'},\n {key: 'caseyes', component: 'mod_quiz'},\n {key: 'answersingleno', component: 'qtype_multichoice'},\n {key: 'answersingleyes', component: 'qtype_multichoice'},\n {key: 'layoutselectinline', component: 'qtype_multianswer'},\n {key: 'layouthorizontal', component: 'qtype_multianswer'},\n {key: 'layoutvertical', component: 'qtype_multianswer'},\n {key: 'shufflewithin', component: 'mod_quiz'},\n {key: 'layoutmultiple_horizontal', component: 'qtype_multianswer'},\n {key: 'layoutmultiple_vertical', component: 'qtype_multianswer'},\n {key: 'pluginnamesummary', component: 'qtype_multichoice'},\n {key: 'pluginnamesummary', component: 'qtype_shortanswer'},\n {key: 'pluginnamesummary', component: 'qtype_numerical'},\n {key: 'multichoice', component},\n {key: 'multiresponse', component},\n {key: 'numerical', component: 'mod_quiz'},\n {key: 'shortanswer', component: 'mod_quiz'},\n {key: 'cancel', component: 'core'},\n {key: 'select', component},\n {key: 'insert', component},\n {key: 'pluginname', component},\n {key: 'customgrade', component},\n {key: 'err_custom_rate', component},\n {key: 'err_empty_answer', component},\n {key: 'err_none_correct', component},\n {key: 'err_not_numeric', component},\n ];\n let langKeys = [\n 'answer',\n 'chooseqtypetoadd',\n 'defaultmark',\n 'feedback',\n 'correct',\n 'incorrect',\n 'addmoreanswerblanks',\n 'delete',\n 'up',\n 'down',\n 'tolerance',\n 'grade',\n 'caseno',\n 'caseyes',\n 'singleno',\n 'singleyes',\n 'selectinline',\n 'horizontal',\n 'vertical',\n 'shuffle',\n 'multi_horizontal',\n 'multi_vertical',\n 'summary_multichoice',\n 'summary_shortanswer',\n 'summary_numerical',\n 'multichoice',\n 'multiresponse',\n 'numerical',\n 'shortanswer',\n 'btn_cancel',\n 'btn_select',\n 'btn_insert',\n 'title',\n 'custom_grade',\n 'err_custom_rate',\n 'err_empty_answer',\n 'err_none_correct',\n 'err_not_numeric',\n ];\n if (hasQtypeMultianswerrgx(_editor)) {\n strToFetch.push({key: 'regexp', component: 'qtype_regexp'});\n strToFetch.push({key: 'pluginnamesummary', component: 'qtype_regexp'});\n langKeys.push('regexp');\n langKeys.push('summary_regexp');\n }\n getStrings(strToFetch).then(function() {\n const args = Array.from(arguments);\n langKeys.map((l, i) => {\n STR[l] = args[0][i];\n return ''; // Make the linter happy.\n });\n return ''; // Make the linter happy.\n }).catch(() => {\n return '';\n });\n};\n\n/**\n * Return the question types that are available for the cloze question.\n * @returns {Array}\n * @private\n */\nconst _getQuestionTypes = function() {\n let qtypes = [\n {\n 'type': 'MULTICHOICE',\n 'abbr': ['MC'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.selectinline, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_H',\n 'abbr': ['MCH'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.horizontal, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_V',\n 'abbr': ['MCV'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.vertical, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_S',\n 'abbr': ['MCS'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.selectinline, STR.shuffle, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_HS',\n 'abbr': ['MCHS'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.horizontal, STR.shuffle, STR.singleyes],\n },\n {\n 'type': 'MULTICHOICE_VS',\n 'abbr': ['MCVS'],\n 'name': STR.multichoice,\n 'summary': STR.summary_multichoice,\n 'options': [STR.vertical, STR.shuffle, STR.singleyes],\n },\n {\n 'type': 'MULTIRESPONSE',\n 'abbr': ['MR'],\n 'name': STR.multiresponse,\n 'summary': STR.summary_multichoice,\n 'options': [STR.multi_vertical, STR.singleno],\n },\n {\n 'type': 'MULTIRESPONSE_H',\n 'abbr': ['MRH'],\n 'name': STR.multiresponse,\n 'summary': STR.summary_multichoice,\n 'options': [STR.multi_horizontal, STR.singleno],\n },\n {\n 'type': 'MULTIRESPONSE_S',\n 'abbr': ['MRS'],\n 'name': STR.multiresponse,\n 'summary': STR.summary_multichoice,\n 'options': [STR.multi_vertical, STR.shuffle, STR.singleno],\n },\n {\n 'type': 'MULTIRESPONSE_HS',\n 'abbr': ['MRHS'],\n 'name': STR.multiresponse,\n 'summary': STR.summary_multichoice,\n 'options': [STR.multi_horizontal, STR.shuffle, STR.singleno],\n },\n {\n 'type': 'NUMERICAL',\n 'abbr': ['NM'],\n 'name': STR.numerical,\n 'summary': STR.summary_numerical,\n },\n {\n 'type': 'SHORTANSWER',\n 'abbr': ['SA', 'MW'],\n 'name': STR.shortanswer,\n 'summary': STR.summary_shortanswer,\n 'options': [STR.caseno],\n },\n {\n 'type': 'SHORTANSWER_C',\n 'abbr': ['SAC', 'MWC'],\n 'name': STR.shortanswer,\n 'summary': STR.summary_shortanswer,\n 'options': [STR.caseyes],\n },\n ];\n if (hasQtypeMultianswerrgx(_editor)) {\n qtypes.splice(11, 0, {\n 'type': 'REGEXP',\n 'abbr': ['RX'],\n 'name': STR.regexp,\n 'summary': STR.summary_regexp,\n 'options': [STR.caseno],\n }, {\n 'type': 'REGEXP_C',\n 'abbr': ['RXC'],\n 'name': STR.regexp,\n 'summary': STR.summary_regexp,\n 'options': [STR.caseyes],\n });\n }\n return qtypes;\n};\n\n/**\n * Create the modal.\n * @return {Promise}\n * @private\n */\nconst _createModal = async function() {\n // Create the modal dialogue. Depending on whether we have a selected node or not, the content is different.\n const cfg = {\n title: STR.title,\n templateContext: {\n elementid: _editor.id\n },\n removeOnClose: true,\n large: true,\n };\n if (typeof Modal.create === 'function') {\n _modal = await Modal.create(cfg);\n } else {\n _modal = await ModalFactory.create(cfg);\n }\n};\n\n/**\n * Display modal dialogue to edit a cloze question. Either a form is displayed to edit subquestion or a list\n * of possible questions is show.\n *\n * @method displayDialogue\n * @public\n */\nconst displayDialogue = async function() {\n await _createModal();\n\n // Resolve whether cursor is in a subquestion.\n const subquestion = resolveSubquestion();\n if (subquestion) {\n _firstAnswer = null;\n // Subquestion found, remember which node of the marker nodes is selected.\n _selectedOffset = indexOfNode(_editor.dom.select('.' + markerClass), subquestion);\n _parseSubquestion(subquestion.innerHTML);\n _setDialogueContent(_qtype);\n } else {\n // No subquestion found, no offset to remember.\n _firstAnswer = _editor.selection.getContent();\n _selectedOffset = -1;\n _setDialogueContent();\n }\n};\n\n/**\n * On double click, check that we are on a question and display the dialogue with the question to edit.\n * @method displayDialogueForEdit\n * @param {Node} target\n * @public\n */\nconst displayDialogueForEdit = async function(target) {\n\n const subquestion = resolveSubquestion(target);\n if (!subquestion) {\n return;\n }\n await _createModal();\n _selectedOffset = indexOfNode(_editor.dom.select('.' + markerClass), subquestion);\n _parseSubquestion(subquestion.innerHTML);\n _setDialogueContent(_qtype);\n};\n\n/**\n * Search for cloze questions based on a regular expression. All the matching snippets at least contain the cloze\n * question definition. Although Moodle does not support encapsulated other functions within curly brackets, we\n * still try to find the correct closing bracket. The so extracted cloze question is surrounded by a marker span\n * element, that contains attributes so that the content inside the span cannot be modified by the editor (in the\n * textarea). Also, this makes it a lot easier to select the question, edit it in the dialogue and replace the result\n * in the existing text area.\n *\n * @method _addMarkers\n * @private\n */\nconst _addMarkers = function() {\n\n let content = _editor.getContent();\n let newContent = '';\n\n // Check if there is already a marker span. In this case we do not have to do anything.\n if (content.indexOf(markerClass) !== -1) {\n return;\n }\n\n let m;\n do {\n m = content.match((_getRegexQtype(_editor)));\n if (!m) { // No match of a cloze question, then we are done.\n newContent += content;\n break;\n }\n // Copy the current match to the new string preceded with the .\n const pos = content.indexOf(m[0]);\n newContent += content.substring(0, pos) + markerSpan + content.substring(pos, pos + m[0].length);\n content = content.substring(pos + m[0].length);\n\n // Count the { in the string, should be just one (the very first one at position 0).\n let level = (m[0].match(/\\{/g) || []).length;\n if (level === 1) {\n // If that's the case, we close the span and the cloze question text is the innerHTML of that marker span.\n newContent += '';\n continue; // Look for the next matching cloze question.\n }\n // If there are more { than } in the string, then we did not find the corresponding } that belongs to the cloze string.\n while (level > 1) {\n const a = content.indexOf('{');\n const b = content.indexOf('}');\n if (a > -1 && b > -1 && a < b) { // The { is before another } so remember to find as many } until we back at level 1.\n level++;\n newContent = content.substring(0, a);\n content = content.substring(a + 1);\n } else if (b > -1) { // We found a closing } to a previously {.\n newContent = content.substring(0, b);\n content = content.substring(b + 1);\n level--;\n } else {\n level = 1; // Should not happen, just to stop the endless loop.\n }\n }\n newContent += '
';\n } while (m);\n _editor.setContent(newContent);\n};\n\n/**\n * Look for the marker span elements around a cloze question and remove that span. Also, the marker for a new\n * node to be inserted would be removed here as well.\n */\nconst _removeMarkers = function() {\n for (const span of _editor.dom.select('span.' + markerClass)) {\n _editor.dom.setOuterHTML(span, span.classList.contains('new') ? '' : span.innerHTML);\n }\n};\n\n/**\n * When the source code view dialogue is show, we must remove the spans around the cloze question strings\n * from the editor content and add them again when the dialogue is closed.\n * Since this event is also triggered when the editor data is saved, we use this function to remove the\n * highlighting content at that time.\n *\n * @method onBeforeGetContent\n * @param {object} content\n * @public\n */\nconst onBeforeGetContent = function(content) {\n if (!isNull(content.source_view) && content.source_view === true) {\n // If the user clicks on 'Cancel' or the close button on the html\n // source code dialog view, make sure we re-add the visual styling.\n var onClose = function() {\n _editor.off('close', onClose);\n _addMarkers();\n };\n _editor.on('CloseWindow', () => {\n onClose();\n });\n // Remove markers only if modal is not called, otherwise we will lose our new question marker.\n if (!_modal) {\n _removeMarkers();\n }\n }\n};\n\n/**\n * Fires when the form containing the editor is submitted.\n *\n * @method onSubmit\n * @public\n */\nconst onSubmit = function() {\n _removeMarkers();\n};\n\n/**\n * Set the dialogue content for the tool, attaching any required events. Either the modal dialogue displays\n * a list of the question types for the form for a particular question to edit. The set content is also\n * called when the form has changed (up or down move, deletion and adding a response). We must be aware of that\n * an event to the dialogue buttons must be attached once only. Therefore, when the form content is modified, only\n * the form events for the answers are set again, the general events are nor (nomodalevents is true then).\n *\n * @method _setDialogueContent\n * @param {String} qtype The question type to be used\n * @param {boolean} nomodalevents Optional do not attach events.\n * @private\n */\nconst _setDialogueContent = function(qtype, nomodalevents) {\n const footer = Mustache.render(TEMPLATE.FOOTER, {\n cancel: STR.btn_cancel,\n submit: !qtype ? STR.btn_select : STR.btn_insert,\n });\n let contentText;\n if (!qtype) {\n contentText = Mustache.render(TEMPLATE.TYPE, {\n CSS: CSS,\n STR: STR,\n qtype: _qtype,\n types: _getQuestionTypes()\n });\n } else {\n contentText = Mustache.render(TEMPLATE.FORM, {\n CSS: CSS,\n STR: STR,\n answerdata: _answerdata,\n elementid: getUuid(),\n qtype: _qtype,\n name: _getQuestionTypes().filter(q => _qtype === q.type)[0].name,\n marks: _marks,\n numerical: (_qtype === 'NUMERICAL' || _qtype === 'NM')\n });\n }\n _modal.setBody(contentText);\n _modal.setFooter(footer);\n _modal.show();\n const $root = _modal.getRoot();\n _form = $root.get(0).querySelector('form');\n _toggleDeleteIcon();\n\n if (!nomodalevents) {\n _modal.registerEventListeners();\n _modal.registerCloseOnSave();\n _modal.registerCloseOnCancel();\n $root.on(ModalEvents.cancel, _cancel);\n\n if (!qtype) { // For the question list we need the choice handler only, and we are done.\n $root.on(ModalEvents.save, _choiceHandler);\n return;\n } // Handler to add the question string to the editor content.\n $root.on(ModalEvents.save, _setSubquestion);\n }\n // The form needs events for the icons to move up/down, add or delete a response.\n const getTarget = e => {\n let p = e.target;\n while (!isNull(p) && p.nodeType === 1 && p.tagName !== 'A') {\n p = p.parentNode;\n }\n if (isNull(p.classList)) {\n return null;\n }\n return p;\n };\n\n _form.addEventListener('click', e => {\n const p = getTarget(e);\n if (isNull(p)) {\n return;\n }\n if (p.classList.contains(CSS.DELETE)) {\n e.preventDefault();\n _deleteAnswer(p);\n return;\n }\n if (p.classList.contains(CSS.ADD)) {\n e.preventDefault();\n _addAnswer(p);\n return;\n }\n if (p.classList.contains(CSS.LOWER)) {\n e.preventDefault();\n _lowerAnswer(p);\n return;\n }\n if (p.classList.contains(CSS.RAISE)) {\n e.preventDefault();\n _raiseAnswer(p);\n }\n });\n _form.addEventListener('keyup', e => {\n const p = getTarget(e);\n if (isNull(p)) {\n return;\n }\n if (p.classList.contains(CSS.ANSWER) || p.classList.contains(CSS.FEEDBACK)) {\n e.preventDefault();\n _addAnswer(p);\n }\n });\n _form.querySelectorAll('.' + CSS.FRACTION).forEach((sel) => {\n sel.addEventListener('change', e => {\n const id = e.target.getAttribute('id');\n if (e.target.value === selectCustomPercent) {\n document.getElementById(id + '_custom').parentNode.classList.remove('hidden');\n } else {\n document.getElementById(id + '_custom').parentNode.classList.add('hidden');\n }\n });\n });\n};\n\n/**\n * If there is one answer field, hide the delete icon. Otherwise show them\n * all to allow deletion of any answer.\n *\n * @private\n */\nconst _toggleDeleteIcon = function() {\n const deleteIcons = _form.querySelectorAll('.' + CSS.DELETE);\n if (deleteIcons.length === 1) {\n deleteIcons[0].classList.add('hidden');\n return;\n }\n for (let i = 0; i < deleteIcons.length; i++) {\n deleteIcons[i].classList.remove('hidden');\n }\n};\n\n/**\n * Handle question choice.\n *\n * @method _choiceHandler\n * @private\n * @param {Event} e Event from button click in chooser\n */\nconst _choiceHandler = function(e) {\n e.preventDefault();\n let qtype = _form.querySelector('input[name=qtype]:checked');\n if (qtype) {\n _qtype = qtype.value;\n }\n // For numerical and short answer questions (and when installed regexp) we offer one response field only.\n // All other question types have three empty response fields.\n const max = (_qtype.indexOf('SHORTANSWER') !== -1 || _qtype === 'NUMERICAL' || _qtype.indexOf('REGEXP') !== -1) ? 1 : 3;\n const blankAnswer = {\n id: getUuid(),\n answer: '',\n feedback: '',\n fraction: 100,\n fractionOptions: getFractionOptions(''),\n tolerance: 0,\n isCustomGrade: false,\n };\n _answerdata = [];\n for (let x = 0; x < max; x++) {\n _answerdata.push({...blankAnswer, id: getUuid()});\n }\n // The first response field gets the default grade correct.\n _answerdata[0].fractionOptions = getFractionOptions('=');\n // In case the user seleced some text, this is used as the first answer.\n if (_firstAnswer) {\n _answerdata[0].answer = _firstAnswer;\n }\n _modal.destroy();\n // Our choice is stored in _qtype. We need to create the modal dialogue with the form now.\n _createModal().then(() => {\n _setDialogueContent(_qtype);\n _form.querySelector('.' + CSS.ANSWER).focus();\n return ''; // Make the linter happy.\n }).catch(() => {\n return '';\n });\n};\n\n/**\n * Parse question and set properties found.\n *\n * @method _parseSubquestion\n * @private\n * @param {String} question The question string\n */\nconst _parseSubquestion = function(question) {\n _answerdata = []; // Flush answers to have an empty dialogue if something goes wrong parsing the question string.\n const regexQtype = _getRegexQtype(_editor);\n const parts = regexQtype.exec(question);\n regexQtype.lastIndex = 0; // Reset lastIndex so that the next match starts from the beginning of the question string.\n if (!parts) {\n return;\n }\n _marks = parts[1];\n _qtype = parts[2];\n // Convert the short notation to the long form e.g. SA to SHORTANSWER.\n if (_qtype.length < 5) {\n _getQuestionTypes().forEach(l => {\n for (const a of l.abbr) {\n if (a === _qtype) {\n _qtype = l.type;\n return;\n }\n }\n });\n }\n // Depending on the regex the position of the answers is different.\n const answers = parts[hasQtypeMultianswerrgx(_editor) ? 8 : 7].match(/(\\\\.|[^~])*/g);\n if (!answers) {\n return;\n }\n answers.forEach(function(answer) {\n const options = /^(%(-?[.0-9]+)%|(=?))((\\\\.|[^#])*)#?(.*)/.exec(answer);\n if (options && options[4]) {\n let frac = '';\n if (options[3]) {\n frac = options[3] === '=' ? '=' : 100;\n } else if (options[2]) {\n frac = options[2];\n }\n if (_qtype === 'NUMERICAL' || _qtype === 'NM') {\n const tolerance = /^([^:]*):?(.*)/.exec(options[4])[2] || 0;\n _answerdata.push({\n id: getUuid(),\n answer: strdecode(options[4].replace(/:.*/, '')),\n feedback: strdecode(options[6]),\n tolerance: tolerance,\n fraction: frac,\n fractionOptions: getFractionOptions(frac),\n isCustomGrade: isCustomGrade(frac),\n });\n return;\n }\n _answerdata.push({\n answer: strdecode(options[4]),\n id: getUuid(),\n feedback: strdecode(options[6]),\n fraction: frac,\n fractionOptions: getFractionOptions(frac),\n isCustomGrade: isCustomGrade(frac),\n });\n }\n });\n};\n\n/**\n * Insert a new set of answer blanks below the button.\n *\n * @method _addAnswer\n * @param {Node} a Node that is the referred element\n * @private\n */\nconst _addAnswer = function(a) {\n let index = indexOfNode(_form.querySelectorAll('.' + CSS.ADD), a);\n if (index === -1) {\n index = 0;\n }\n let fraction = '';\n let answer = '';\n let feedback = '';\n let tolerance = 0;\n if (a.closest('li')) {\n fraction = a.closest('li').querySelector('.' + CSS.FRACTION).value;\n if (fraction === selectCustomPercent) {\n fraction = a.closest('li').querySelector('.' + CSS.FRAC_CUSTOM).value;\n }\n answer = a.closest('li').querySelector('.' + CSS.ANSWER).value;\n feedback = a.closest('li').querySelector('.' + CSS.FEEDBACK).value;\n if (a.closest('li').querySelector('.' + CSS.TOLERANCE)) {\n tolerance = a.closest('li').querySelector('.' + CSS.TOLERANCE).value;\n }\n }\n _processFormData();\n _answerdata.splice(index, 0, {\n id: getUuid(),\n answer: answer,\n feedback: feedback,\n fraction: fraction,\n fractionOptions: getFractionOptions(fraction),\n tolerance: tolerance,\n isCustomGrade: isCustomGrade(fraction)\n });\n _setDialogueContent(_qtype, true);\n _toggleDeleteIcon();\n _form.querySelectorAll('.' + CSS.ANSWER).item(index).focus();\n};\n\n/**\n * Delete set of answer next to the button.\n *\n * @method _deleteAnswer\n * @param {Node} a Node that is the referred element\n * @private\n */\nconst _deleteAnswer = function(a) {\n let index = indexOfNode(_form.querySelectorAll('.' + CSS.DELETE), a);\n if (index === -1) {\n index = indexOfNode(_form.querySelectorAll('li'), a.closest('li'));\n }\n _processFormData();\n _answerdata.splice(index, 1);\n _setDialogueContent(_qtype, true);\n const answers = _form.querySelectorAll('.' + CSS.ANSWER);\n index = Math.min(index, answers.length - 1);\n answers.item(index).focus();\n _toggleDeleteIcon();\n};\n\n/**\n * Lower answer option\n *\n * @method _lowerAnswer\n * @param {Node} a Node that is the referred element\n * @private\n */\nconst _lowerAnswer = function(a) {\n const li = a.closest('li');\n li.before(li.nextSibling);\n li.querySelector('.' + CSS.ANSWER).focus();\n};\n\n/**\n * Raise answer option\n *\n * @method _raiseAnswer\n * @param {Node} a Node that is the referred element\n * @private\n */\nconst _raiseAnswer = function(a) {\n const li = a.closest('li');\n li.after(li.previousSibling);\n li.querySelector('.' + CSS.ANSWER).focus();\n};\n\n/**\n * Reset and hide form.\n *\n * @method _cancel\n * @param {Event} e Event from button click\n * @private\n */\nconst _cancel = function(e) {\n e.preventDefault();\n // In case there is a marker where the new question should be inserted in the text it needs to be removed.\n for (const span of _editor.dom.select('.' + markerClass + '.new')) {\n span.remove();\n }\n _modal.destroy();\n _editor.focus();\n _modal = null;\n};\n\n/**\n * Insert question string into editor content and reset and hide form. If the form contains an error\n * nothing happens.\n *\n * @method _setSubquestion\n * @param {Event} e Event from button click\n * @private\n */\nconst _setSubquestion = function(e) {\n e.preventDefault();\n // Check if there are any errors and if so, fill the error container with the\n // messages and return without going any further and closing the dialogue.\n const errMsg = _form.querySelector('.msg-error');\n const formErrors = _processFormData(true);\n if (formErrors.length > 0) {\n errMsg.innerHTML = '
  • ' + formErrors.join('
  • ') + '
';\n errMsg.classList.remove('hidden');\n return;\n } else {\n errMsg.classList.add('hidden');\n }\n // Build the parser function from the data, that is going to be placed into the editor content.\n let question = '{' + _marks + ':' + _qtype + ':';\n\n // Filter all empty responses\n for (let i = 0; i < _answerdata.length; i++) {\n if (_answerdata[i].raw === '') {\n continue;\n }\n question += _answerdata[i].fraction && !isNaN(_answerdata[i].fraction)\n ? '%' + _answerdata[i].fraction + '%' : _answerdata[i].fraction;\n question += strencode(_answerdata[i].answer);\n if (_qtype === 'NM' || _qtype === 'NUMERICAL') {\n question += ':' + _answerdata[i].tolerance;\n }\n if (_answerdata[i].feedback) {\n question += '#' + strencode(_answerdata[i].feedback);\n }\n if (i < _answerdata.length - 1) {\n question += '~';\n }\n }\n if (question.slice(-1) === '~') {\n question = question.substring(0, question.length - 1);\n }\n question += '}';\n\n _modal.destroy();\n _modal = null;\n _editor.focus();\n if (_selectedOffset > -1) { // We have to replace one of the marker spans (the innerHTML contains the question string).\n _editor.dom.select('.' + markerClass)[_selectedOffset].innerHTML = question;\n } else {\n // Just add the question text with markup.\n _editor.insertContent(markerSpan + question + '');\n }\n};\n\n/**\n * Read the form data, process it and store the result in the internal _answerdata array.\n * Also, if validation is enabled, the fields are checked for invalid values e.g.\n * - answer field is empty (if a correct answer is contained, empty fields are eliminated).\n * - custom_grade field whenin use and does not contain a number.\n * - no field is marked as a correct answer.\n * - tolerance field must be in percentage of min -100 and max 100.\n * Any field with an error is maked and the first field containing an error gets the focus.\n *\n * @method _processFormData\n * @param {boolean} validate\n * @return {Array}\n * @private\n */\nconst _processFormData = function(validate) {\n _answerdata = [];\n let globalErrors = [];\n const answers = _form.querySelectorAll('.' + CSS.ANSWER);\n const feedbacks = _form.querySelectorAll('.' + CSS.FEEDBACK);\n const fractions = _form.querySelectorAll('.' + CSS.FRACTION);\n const customGrades = _form.querySelectorAll('.' + CSS.FRAC_CUSTOM);\n const tolerances = _form.querySelectorAll('.' + CSS.TOLERANCE);\n // Remove any error classes.\n for (let i = 0; i < answers.length; i++) {\n answers.item(i).classList.remove('error');\n customGrades.item(i).classList.remove('error');\n const currentAnswer = {\n raw: answers.item(i).value.trim(),\n answer: answers.item(i).value.trim(),\n id: getUuid(),\n feedback: feedbacks.item(i).value,\n fraction: fractions.item(i).value === selectCustomPercent ? customGrades.item(i).value : fractions.item(i).value,\n fractionOptions: getFractionOptions(fractions.item(i).value),\n tolerance: tolerances.length > 0 ? tolerances.item(i).value : 0,\n isCustomGrade: fractions.item(i).value === selectCustomPercent\n };\n if (_qtype === 'NM' || _qtype === 'NUMERICAL') {\n tolerances.item(i).classList.remove('error');\n // In numeric questions convert answer and tolerance to numeric values (this filters non numeric values).\n currentAnswer.answer = Number(currentAnswer.answer);\n currentAnswer.tolerance = Number(currentAnswer.tolerance);\n }\n _answerdata.push(currentAnswer);\n }\n _marks = _form.querySelector('.' + CSS.MARKS).value;\n\n if (validate) {\n const {hasCorrectAnswer, errors} = _validateAnswers();\n for (let i = 0; i < _answerdata.length; i++) {\n for (const err of _answerdata[i].hasErrors) {\n if (hasCorrectAnswer && (err === 'empty_answer' || err === 'correct_but_empty')) {\n break;\n }\n if (err === 'answer_not_numeric' || err === 'empty_answer' || err === 'correct_but_empty') {\n answers.item(i).classList.add('error');\n } else if (err === 'tolerance_not_numeric') {\n tolerances.item(i).classList.add('error');\n } else if (err === 'error_custom_rate') {\n customGrades.item(i).classList.add('error');\n }\n }\n }\n globalErrors = _translateGlobalErrors(hasCorrectAnswer, errors);\n // If we have errors, we focus the first field that contains an error.\n if (globalErrors.length > 0) {\n _form.querySelector('input.error').focus();\n }\n }\n return globalErrors;\n};\n\n/**\n * Validates the answer array. Checks for each question if the data from the form is\n * incomplete or has other errors. These are flagged accordingly in the array element.\n * The retruned object contains the properties:\n * - hasCorrectAnswer {boolean} is true if there is at least one correct answer.\n * - errors {Array} list of strings that contain an error code that is globaly used for error messages.\n *\n * @return {Array}\n * @private\n */\nconst _validateAnswers = function() {\n let errors = [];\n let hasCorrect = false;\n for (let i = 0; i < _answerdata.length; i++) {\n _answerdata[i].hasErrors = [];\n // Check if we have an empty answer string.\n if (_answerdata[i].raw === '') {\n _answerdata[i].hasErrors.push('empty_answer');\n }\n // When there are numeric questions, check that the answer and tolerance is a valid number.\n if (_qtype === 'NM' || _qtype === 'NUMERICAL') {\n if (isNaN(_answerdata[i].answer) && _answerdata[i].raw !== '') {\n _answerdata[i].hasErrors.push('answer_not_numeric');\n }\n if (isNaN(_answerdata[i].tolerance)) {\n _answerdata[i].hasErrors.push('tolerance_not_numeric');\n }\n }\n // Check the custom grade, that must be a percentage number between -100 and 100.\n if (_answerdata[i].isCustomGrade &&\n (isNaN(_answerdata[i].fraction) || _answerdata[i].fraction < -100 || _answerdata[i].fraction > 100\n || _answerdata[i].fraction.trim() === '')\n ) {\n _answerdata[i].hasErrors.push('error_custom_rate');\n }\n // We found a correct answer, when grade is marked as 100 or \"=\" and the answer is not empty.\n if (_answerdata[i].fraction === '100' || _answerdata[i].fraction === '=') {\n if (_answerdata[i].raw !== '') {\n _answerdata[i].isCorrect = true;\n hasCorrect = true;\n } else {\n _answerdata[i].hasErrors.push('correct_but_empty');\n }\n }\n errors = errors.concat(_answerdata[i].hasErrors);\n }\n\n return {\n hasCorrectAnswer: hasCorrect,\n errors: _combineGlobalErrors(hasCorrect, errors),\n };\n};\n\n/**\n * Translate the errors into a readable string for a list that is used on top of the\n * input fields, to indicate what part of the data is incorrect.\n *\n * @param {Boolean} hasCorrectAnswer\n * @param {Array} errors\n * @return {Array}\n * @private\n */\nconst _translateGlobalErrors = function(hasCorrectAnswer, errors) {\n const errTranslated = [];\n // Translate the error strings into a string that can be displayed in the form.\n const trMsg = {\n emptyanswer: STR.err_empty_answer,\n answernotnumeric: STR.err_not_numeric,\n tolerancenotnumeric: STR.err_not_numeric,\n errorcustomrate: STR.err_custom_rate,\n nonecorrect: STR.err_none_correct,\n };\n for (const err of errors) {\n // If there's at least one correct answer, we filter out all empty answers and therefore do not\n // show the error message.\n if (hasCorrectAnswer && err === 'empty_answer' || err === 'correct_but_empty') {\n continue;\n }\n // Remove underscore (we do this only because of the js linter).\n const key = err.replace(/_/g, '');\n errTranslated.push(trMsg[key]);\n }\n return errTranslated;\n};\n\n/**\n * Combine the error list from the answers to a global list.\n *\n * @param {Boolean} hasCorrectAnswer\n * @param {Array} errors\n * @return {Array}\n * @private\n */\nconst _combineGlobalErrors = function(hasCorrectAnswer, errors) {\n // Unique errors for the global error list.\n const errUnique = errors.filter((value, index, array) => array.indexOf(value) === index);\n // If we have a correct answer, do not show the empty answer error, because empty responses are filtered.\n if (hasCorrectAnswer) {\n const i = errUnique.indexOf('empty_answer');\n if (i > -1) {\n errUnique.splice(i, 1);\n }\n } else if (!errUnique.includes('correct_but_empty')) {\n errUnique.push('none_correct');\n }\n return errUnique;\n};\n\n/**\n * Check whether cursor is in a subquestion and return subquestion text if\n * true.\n *\n * @method resolveSubquestion\n * @param {Node|null} element The element to check if it is a subquestion.\n * @return {Mixed} The selected node of with the subquestion if found, false otherwise.\n */\nconst resolveSubquestion = function(element) {\n let span = element || _editor.selection.getStart();\n if (!isNull(span.classList) && span.classList.contains(markerClass)) {\n return span;\n }\n _editor.dom.getParents(span, elm => {\n // Are we in a span that encapsulates the cloze question?\n if (!isNull(elm.classList) && elm.classList.contains(markerClass)) {\n return elm;\n }\n return false;\n });\n return false;\n};\n\nexport {\n displayDialogue,\n displayDialogueForEdit,\n resolveSubquestion,\n onInit,\n onBeforeGetContent,\n onSubmit,\n};\n"],"names":["isNull","a","strdecode","t","String","replace","strencode","indexOfNode","list","node","i","length","getUuid","crypto","randomUUID","Math","floor","random","toString","getFractionOptions","s","attrSel","isSel","html","STR","incorrect","correct","FRACTIONS","forEach","item","value","indexOf","custom_grade","isCustomGrade","found","markerClass","markerSpan","CSS","ANSWER","ANSWERS","ADD","CANCEL","DELETE","FEEDBACK","FRACTION","FRAC_CUSTOM","LEFT","LOWER","RIGHT","MARKS","DUPLICATE","RAISE","SUBMIT","SUMMARY","TOLERANCE","TYPE","TEMPLATE","FORM","M","util","image_url","FOOTER","_editor","_form","_answerdata","_qtype","_selectedOffset","_marks","_modal","_firstAnswer","ed","_addMarkers","_getStr","_getRegexQtype","editor","extQtypes","RegExp","async","strToFetch","key","component","langKeys","push","then","args","Array","from","arguments","map","l","catch","_getQuestionTypes","qtypes","multichoice","summary_multichoice","selectinline","singleyes","horizontal","vertical","shuffle","multiresponse","multi_vertical","singleno","multi_horizontal","numerical","summary_numerical","shortanswer","summary_shortanswer","caseno","caseyes","splice","regexp","summary_regexp","_createModal","cfg","title","templateContext","elementid","id","removeOnClose","large","Modal","create","ModalFactory","subquestion","resolveSubquestion","dom","select","_parseSubquestion","innerHTML","_setDialogueContent","selection","getContent","target","m","content","newContent","match","pos","substring","level","b","setContent","_removeMarkers","span","setOuterHTML","classList","contains","source_view","onClose","off","on","qtype","nomodalevents","footer","Mustache","render","cancel","btn_cancel","submit","btn_insert","btn_select","contentText","answerdata","name","filter","q","type","marks","types","setBody","setFooter","show","$root","getRoot","get","querySelector","_toggleDeleteIcon","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","ModalEvents","_cancel","save","_choiceHandler","_setSubquestion","getTarget","e","p","nodeType","tagName","parentNode","addEventListener","preventDefault","_deleteAnswer","_addAnswer","_lowerAnswer","_raiseAnswer","querySelectorAll","sel","getAttribute","document","getElementById","remove","add","deleteIcons","max","blankAnswer","answer","feedback","fraction","fractionOptions","tolerance","x","destroy","focus","question","regexQtype","parts","exec","lastIndex","abbr","answers","options","frac","index","closest","_processFormData","min","li","before","nextSibling","after","previousSibling","errMsg","formErrors","join","raw","isNaN","slice","insertContent","validate","globalErrors","feedbacks","fractions","customGrades","tolerances","currentAnswer","trim","Number","hasCorrectAnswer","errors","_validateAnswers","err","hasErrors","_translateGlobalErrors","hasCorrect","isCorrect","concat","_combineGlobalErrors","errTranslated","trMsg","emptyanswer","err_empty_answer","answernotnumeric","err_not_numeric","tolerancenotnumeric","errorcustomrate","err_custom_rate","nonecorrect","err_none_correct","errUnique","array","includes","element","getStart","getParents","elm"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/ui.js b/amd/src/ui.js index 86ac690..98f8796 100644 --- a/amd/src/ui.js +++ b/amd/src/ui.js @@ -268,7 +268,7 @@ const onInit = function(ed) { // Add the marker spans. _addMarkers(); // And get the language strings. - _getStr(ed); + _getStr(); }; /** @@ -286,10 +286,9 @@ const _getRegexQtype = (editor) => { /** * Load strings for the modal dialogue from the language packs. - * @param {tinymce.Editor} editor * @private */ -const _getStr = async(editor) => { +const _getStr = async() => { let strToFetch = [ {key: 'answer', component: 'question'}, {key: 'chooseqtypetoadd', component: 'question'}, @@ -370,7 +369,7 @@ const _getStr = async(editor) => { 'err_none_correct', 'err_not_numeric', ]; - if (hasQtypeMultianswerrgx(editor)) { + if (hasQtypeMultianswerrgx(_editor)) { strToFetch.push({key: 'regexp', component: 'qtype_regexp'}); strToFetch.push({key: 'pluginnamesummary', component: 'qtype_regexp'}); langKeys.push('regexp');