diff --git a/amd/build/comment_area.min.js b/amd/build/comment_area.min.js index ba865699..bc55644e 100644 --- a/amd/build/comment_area.min.js +++ b/amd/build/comment_area.min.js @@ -5,6 +5,6 @@ * @copyright 2020 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("mod_studentquiz/comment_area",["jquery","core/str","core/ajax","core/modal_factory","core/templates","core/fragment","core/modal_events"],(function($,str,ajax,ModalFactory,Templates,fragment,ModalEvents){var t={EMPTY_CONTENT:["


","


","
",""],ROOT_COMMENT_VALUE:0,GET_ALL_VALUE:0,TEMPLATE_COMMENTS:"mod_studentquiz/comments",TEMPLATE_COMMENT:"mod_studentquiz/comment",ACTION_CREATE:"mod_studentquiz_create_comment",ACTION_CREATE_REPLY:"mod_studentquiz_create_reply",ACTION_GET_ALL:"mod_studentquiz_get_comments",ACTION_EXPAND:"mod_studentquiz_expand_comment",ACTION_DELETE:"mod_studentquiz_delete_comment",ACTION_EDIT:"mod_studentquiz_edit_comment",ACTION_LOAD_FRAGMENT_FORM:"mod_studentquiz_load_fragment_form",ACTION_LOAD_FRAGMENT_EDIT_FORM:"mod_studentquiz_load_fragment_edit_form",ACTION_EXPAND_ALL:"action_expand_all",ACTION_COLLAPSE_ALL:"action_collapse_all",ACTION_RENDER_COMMENT:"action_render_comment",ACTION_APPEND_COMMENT:"action_append_comment",ACTION_EDITOR_INIT:"action_editor_init",ACTION_INIT:"action_init",ACTION_UPDATE_COMMENT_COUNT:"action_update_comment_count",ACTION_CLEAR_FORM:"action_clear_form",ACTION_SHOW_ERROR:"action_show_error",FRAGMENT_FORM_CALLBACK:"commentform",FRAGMENT_EDIT_FORM_CALLBACK:"commenteditform",HAS_COMMENT_CLASS:"has-comment",ATTO_CONTENT_TYPE:{HAS_CONTENT:"has-content",NO_CONTENT:"no-content"},SELECTOR:{CONTAINER:".studentquiz-comment-container",EXPAND_ALL:".studentquiz-comment-expand",COLLAPSE_ALL:".studentquiz-comment-collapse",SUBMIT_BUTTON:"#id_submitbutton",CONTAINER_REPLIES:".studentquiz-container-replies",COMMENT_REPLIES_CONTAINER:".studentquiz-comment-replies",COMMENT_COUNT:".studentquiz-comment-postcount",COMMENT_TEXT_CONTAINER:".studentquiz-comment-text",COMMENT_TEXT:".studentquiz-comment-text-inside",COMMENT_HISTORY:".studentquiz-comment-history",COMMENT_REPLIES_TEXT:".studentquiz-comment-replies .studentquiz-comment-text .studentquiz-comment-text-inside",LOADING_ICON:".studentquiz-comment-loading",COMMENT_AREA_FORM:"div.comment-area-form",FORM_SELECTOR:".studentquiz-comment-postform > div.comment-area-form",NO_COMMENT:".no-comment",COLLAPSE_LINK:".studentquiz-comment-collapselink",EXPAND_LINK:".studentquiz-comment-expandlink",COMMENT_ITEM:".studentquiz-comment-item",COMMENT_REPLIES_CONTAINER_TO_ITEM:".studentquiz-comment-replies .studentquiz-comment-item",FRAGMENT_FORM:".studentquiz-comment-postfragmentform",BTN_DELETE:".studentquiz-comment-btndelete",BTN_REPLY:".studentquiz-comment-btnreply",BTN_DELETE_REPLY:".studentquiz-comment-btndeletereply",ATTO_EDITOR_WRAP:".editor_atto_wrap",TEXTAREA:'textarea[id^="id_editor_question_"]',COMMENT_COUNT_NUMBER:".studentquiz-comment-count-number",COMMENT_COUNT_TEXT:".studentquiz-comment-count-text",ATTO:{CONTENT_WRAP:".editor_atto_content_wrap",CONTENT:".editor_atto_content",TOOLBAR:".editor_atto_toolbar"},COMMENT_ID:"#comment_",SPAN_COMMENT_ID:"#c",TOTAL_REPLY:".studentquiz-comment-totalreply",COMMENT_FILTER:".studentquiz-comment-filter",COMMENT_FILTER_HIDE:".hide-comment-filter",COMMENT_ERROR:".studentquiz-comment-container .comment-error",BTN_REPORT:".studentquiz-comment-btnreport",COMMENT_FILTER_ITEM:".studentquiz-comment-filter-item",COMMENT_FILTER_NAME:".studentquiz-comment-filter-name",COMMENT_FILTER_TYPE:".studentquiz-comment-filter-type",BTN_EDIT:".studentquiz-comment-btnedit",BTN_EDIT_REPLY:".studentquiz-comment-btneditreply",ATTO_HTML_BUTTON:"button.atto_html_button",POST_FOOTER:".studentquiz-comment-postfooter"},get:function(){return{elementSelector:null,btnExpandAll:null,btnCollapseAll:null,addComment:null,containerSelector:null,studentQuizQuestionId:null,dialogue:null,loadingIcon:null,lastFocusElement:null,formSelector:null,contextId:null,userId:null,string:{},deleteDialog:null,deleteTarget:null,numberToShow:5,cmId:null,countServerData:[],lastCurrentCount:0,lastTotal:0,expand:!1,forceCommenting:!1,canViewDeleted:!1,hasComment:!1,referer:null,highlight:0,sortFeature:null,sortable:[],workingState:!1,isNoComment:!1,type:0,init:function(params){M.util.js_pending(t.ACTION_INIT);this.elementSelector=$("#"+$.escapeSelector(params.id));var el=this.elementSelector;this.btnExpandAll=el.find(t.SELECTOR.EXPAND_ALL),this.btnCollapseAll=el.find(t.SELECTOR.COLLAPSE_ALL),this.addComment=el.find(t.SELECTOR.SUBMIT_BUTTON),this.containerSelector=el.find(t.SELECTOR.CONTAINER_REPLIES),this.loadingIcon=el.find(t.SELECTOR.LOADING_ICON),this.formSelector=el.find(t.SELECTOR.FORM_SELECTOR),this.studentQuizQuestionId=parseInt(el.data("studentquizquestionid")),this.contextId=parseInt(el.data("contextid")),this.userId=parseInt(el.data("userid")),this.numberToShow=parseInt(el.data("numbertoshow")),this.cmId=parseInt(el.data("cmid")),this.countServerData={count:params.count,total:params.total},this.expand=params.expand||!1,this.referer=el.data("referer"),this.sortFeature=params.sortfeature,this.sortable=el.data("sortable"),this.type=params.type,this.string=el.data("strings"),this.forceCommenting=params.forcecommenting,this.canViewDeleted=params.canviewdeleted,this.isNoComment=params.isnocomment,this.allowSelfCommentRating=params.allowselfcommentrating,this.initServerRender(),params.allowselfcommentrating&&this.initBindEditor(),this.bindEvents(),M.util.js_complete(t.ACTION_INIT)},initServerRender:function(){var self=this;self.changeWorkingState(!0),self.elementSelector.find(t.SELECTOR.COMMENT_ITEM).each((function(){var id=$(this).data("id"),attrs=$(this).find(t.SELECTOR.SPAN_COMMENT_ID+id),replies=[];self.expand&&(replies=attrs.data("replies")||[]);var comment={id:$(this).data("id"),deleted:attrs.data("deleted"),numberofreply:attrs.data("numberofreply"),expanded:self.expand,replies:replies,root:!0,type:self.type};self.bindCommentEvent(comment)}));var commentcount=self.expand?self.countServerData.total:self.countServerData.count.commentcount;self.updateCommentCount(commentcount,self.countServerData.total),self.expand?(self.btnExpandAll.hide(),self.btnCollapseAll.show()):(self.btnExpandAll.show(),self.btnCollapseAll.hide());var query=window.location.search.substring(1),getParams=self.parseQueryString(query);if(self.highlight=parseInt(getParams.highlight)||0,0!==self.highlight){var target=$(t.SELECTOR.COMMENT_ID+self.highlight);target.length&&self.scrollToElement(target)}self.changeWorkingState(!1)},initBindEditor:function(){var self=this,isEditorLoaded=!1;M.util.js_pending(t.ACTION_EDITOR_INIT);var interval=setInterval((function(){0!==self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length&&(self.bindEditorEvent(self.formSelector),isEditorLoaded=!0,clearInterval(interval),M.util.js_complete(t.ACTION_EDITOR_INIT))}),500),editorWaiting=setInterval((function(){isEditorLoaded&&(self.checkEditorContent(self.formSelector),clearInterval(editorWaiting))}),1e3)},bindEvents:function(){var self=this;self.btnExpandAll.click((function(e){e.preventDefault(),M.util.js_pending(t.ACTION_EXPAND_ALL),self.changeWorkingState(!0),self.containerSelector.empty(),self.btnExpandAll.hide(),self.btnCollapseAll.show(),self.loadingIcon.show(),self.getComments(t.GET_ALL_VALUE).then((function(response){var total=self.countCommentAndReplies(response.data).total;return self.updateCommentCount(total,response.total),self.renderComment(response.data,!0),M.util.js_complete(t.ACTION_EXPAND_ALL),!0})).fail((function(err){return M.util.js_complete(t.ACTION_EXPAND_ALL),self.showError(err.message),!1}))})),self.btnCollapseAll.click((function(e){e.preventDefault(),M.util.js_pending(t.ACTION_COLLAPSE_ALL),self.changeWorkingState(!0),self.loadingIcon.show(),self.btnCollapseAll.hide(),self.btnExpandAll.show(),self.containerSelector[0].innerHTML="",self.getComments(self.numberToShow).then((function(response){var count=self.countCommentAndReplies(response.data),commentCount=count.commentCount,deletedComments=count.totalDelete;return 0!==commentCount||0!==deletedComments?(self.btnExpandAll.show(),self.updateCommentCount(commentCount,response.total),self.renderComment(response.data,!1)):(self.loadingIcon.hide(),self.changeWorkingState(!1),self.updateCommentCount(0,0)),M.util.js_complete(t.ACTION_COLLAPSE_ALL),!0})).fail((function(err){return M.util.js_complete(t.ACTION_COLLAPSE_ALL),self.showError(err.message),!1}))})),self.addComment.click((function(e){e.preventDefault(),M.util.js_pending(t.ACTION_CREATE),self.changeWorkingState(!0),self.loadingIcon.show(),$(t.SELECTOR.COMMENT_ERROR).addClass("hide"),$(t.SELECTOR.NO_COMMENT).hide();var rootId=t.ROOT_COMMENT_VALUE,unique=self.studentQuizQuestionId+"_"+self.type+"_"+rootId,formSelector=self.formSelector,formData=self.convertFormToJson(formSelector);if(0===formData["message[text]"].length){var attoWrap=formSelector.find(t.SELECTOR.ATTO_EDITOR_WRAP);return 0===attoWrap.length||attoWrap.hasClass("error")||(attoWrap.addClass("error"),attoWrap.prepend(''+self.string.required+"")),M.util.js_complete(t.ACTION_CREATE),!1}var params={replyto:rootId,message:{text:formData["message[text]"],format:formData["message[format]"]}};return self.createComment(params).then((function(response){M.util.js_pending(t.ACTION_CLEAR_FORM),setTimeout((function(){formSelector.trigger("reset"),formSelector.find("#id_editor_question_"+unique+"editable").is(":visible")||self.elementSelector.find(t.SELECTOR.ATTO_HTML_BUTTON).trigger("click"),formSelector.find("#id_editor_question_"+unique+"editable").empty(),formSelector.find(t.SELECTOR.TEXTAREA).trigger("change"),M.util.js_complete(t.ACTION_CLEAR_FORM)}));var data=self.convertForTemplate(response,!0);return formSelector.find(t.SELECTOR.SUBMIT_BUTTON).addClass("disabled"),self.appendComment(data,self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES),!1),M.util.js_complete(t.ACTION_CREATE),!0})).fail((function(e){self.handleFailWhenCreateComment(e,params),M.util.js_complete(t.ACTION_CREATE)})),!0})),self.elementSelector.find(t.SELECTOR.COMMENT_FILTER_ITEM).on("click",(function(e){if(e.preventDefault(),!self.workingState){var asc=self.string.sort.asc,desc=self.string.sort.desc,nameSelector=$(this).find(t.SELECTOR.COMMENT_FILTER_NAME),iconSelector=$(this).find(t.SELECTOR.COMMENT_FILTER_TYPE),type=$(this).data("type"),orderBy=$(this).attr("data-order"),isCurrent=$(this).hasClass("current"),ascString=$(this).attr("data-asc-string"),descString=$(this).attr("data-desc-string");orderBy="desc"===orderBy?"asc":"desc",$(this).attr("data-order",orderBy),isCurrent||$(this).addClass("current"),"desc"===orderBy?(nameSelector.attr("title",ascString),nameSelector.attr("alt",ascString),iconSelector.attr("title",desc),iconSelector.attr("alt",desc)):(nameSelector.attr("title",descString),nameSelector.attr("alt",descString),iconSelector.attr("title",asc),iconSelector.attr("alt",asc)),self.elementSelector.find(t.SELECTOR.COMMENT_FILTER_ITEM).not(this).each((function(){var each=$(this),eachName=$(this).find(t.SELECTOR.COMMENT_FILTER_NAME),eachType=$(this).find(t.SELECTOR.COMMENT_FILTER_TYPE),defaultString=$(this).attr("data-asc-string");each.attr("data-order","desc"),each.removeClass("filter-asc"),each.removeClass("filter-desc"),each.removeClass("current"),eachName.attr("title",defaultString),eachName.attr("alt",defaultString),eachType.attr("title",asc),eachType.attr("alt",asc)})),"desc"===orderBy?($(this).removeClass("filter-asc"),$(this).addClass("filter-desc")):($(this).removeClass("filter-desc"),$(this).addClass("filter-asc"));var sortType=type+"_"+orderBy;self.setSort(sortType),self.expand?self.btnExpandAll.trigger("click"):self.btnCollapseAll.trigger("click")}}))},getComments:function(numberToShow){var params=this.getParamsBeforeCallApi({numbertoshow:numberToShow,sort:this.sortFeature,type:this.type});return ajax.call([{methodname:t.ACTION_GET_ALL,args:params}])[0]},getParamsBeforeCallApi:function(params){return params.studentquizquestionid=this.studentQuizQuestionId,params.cmid=this.cmId,params.type=this.type,params},showError:function(message){var self=this;M.util.js_pending(t.ACTION_SHOW_ERROR),$.when(self.string.error).done((function(string){self.showDialog(string,message),self.changeWorkingState(!1),M.util.js_complete(t.ACTION_SHOW_ERROR)}))},showDialog:function(title,body){var dialogue=this.dialogue;if(dialogue)return dialogue.title.html(title),dialogue.body.html(body),void dialogue.show();ModalFactory.create({type:ModalFactory.types.CANCEL,title:title,body:body}).done((function(modal){(dialogue=modal).show(),dialogue.getRoot().on(ModalEvents.hidden,{},(function(){location.reload()}))}))},updateCommentCount:function(current,total){M.util.js_pending(t.ACTION_UPDATE_COMMENT_COUNT);var self=this;-1===total?total=self.lastTotal:self.lastTotal=total,-1===current?current=self.lastCurrentCount:self.lastCurrentCount=current;var s=str.get_string("current_of_total","studentquiz",{current:current,total:total}),noCommentSelector=self.elementSelector.find(t.SELECTOR.NO_COMMENT),filter=self.elementSelector.find(t.SELECTOR.COMMENT_FILTER),emptyReplies=self.checkEmptyElement(self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES));0===self.lastCurrentCount&&emptyReplies&&self.isNoComment?(self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES).hide(),filter.hide(),noCommentSelector.show()):(self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES).show(),noCommentSelector.hide(),filter.show()),$.when(s).done((function(text){self.elementSelector.find(t.SELECTOR.COMMENT_COUNT).text(text),M.util.js_complete(t.ACTION_UPDATE_COMMENT_COUNT)}))},renderComment:function(comments,expanded){var self=this;M.util.js_pending(t.ACTION_RENDER_COMMENT),comments=self.convertForTemplate(comments,expanded),Templates.render(t.TEMPLATE_COMMENTS,{comments:comments}).done((function(html){self.containerSelector[0].innerHTML=html,self.loadingIcon.hide();for(var i=0;i1&&void 0!==arguments[1]?arguments[1]:null;var visibility=boolean?"hidden":"visible",self=this;self.workingState=boolean,self.btnExpandAll.prop("disabled",boolean),self.btnCollapseAll.prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_REPLY).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_DELETE).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_DELETE_REPLY).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_REPORT).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.EXPAND_LINK).css("visibility",visibility),self.elementSelector.find(t.SELECTOR.COLLAPSE_LINK).css("visibility",visibility),self.elementSelector.find(t.SELECTOR.BTN_EDIT).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_EDIT_REPLY).prop("disabled",boolean),self.deleteDialog&&self.deleteDialog.getFooter().find('button[data-action="yes"]').prop("disabled",boolean),boolean?(self.addComment.prop("disabled",boolean),null!==elementToHide&&elementToHide instanceof $&&elementToHide.hide()):(self.lastFocusElement&&(self.lastFocusElement.focus(),self.lastFocusElement=null),self.elementSelector.find(t.SELECTOR.POST_FOOTER).show())},countCommentAndReplies:function(data){var commentCount=0,deleteCommentCount=0,replyCount=0,deleteReplyCount=0;data.constructor!==Array&&(data=[data]);for(var i=0;i'+self.string.deletetext+'"}).done((function(modal){self.deleteDialog=modal,modal.getFooter().find('button[data-action="no"]').click((function(e){e.preventDefault(),modal.hide()})),modal.getFooter().find('button[data-action="yes"]').click((function(e){e.preventDefault(),M.util.js_pending(t.ACTION_DELETE),self.changeWorkingState(!0),self.deleteComment(self.deleteTarget.id).then((function(response){if(!response.success)return self.showError(response.message),!0;var convertedCommentData=self.convertForTemplate(response.data,self.deleteTarget.expanded),commentSelector=self.elementSelector.find(t.SELECTOR.COMMENT_ID+convertedCommentData.id);return self.updateCommentCount(self.lastCurrentCount-1,self.lastTotal-1),convertedCommentData.root||(convertedCommentData.expanded=!0),Templates.render(t.TEMPLATE_COMMENT,convertedCommentData).done((function(html){var el=$(html);if(!convertedCommentData.root){var parentCountSelector=commentSelector.parent().closest(t.SELECTOR.COMMENT_ITEM).find(t.SELECTOR.TOTAL_REPLY),countSelector=parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_NUMBER),newCount=parseInt(countSelector.text())-1;parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_NUMBER).text(newCount),parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_TEXT).html(1===newCount?self.string.reply:self.string.replies)}var oldReplies=commentSelector.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).clone(!0);commentSelector.replaceWith(el),el.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).replaceWith(oldReplies),self.deleteTarget.root?self.bindCommentEvent(response.data):self.bindReplyEvent(response.data,el.parent()),self.changeWorkingState(!1),M.util.js_complete(t.ACTION_DELETE)})),modal.hide(),!0})).fail((function(err){return self.showError(err.message),!1}))})),modal.getRoot().on(ModalEvents.hidden,(function(){var el=self.elementSelector.find(t.SELECTOR.COMMENT_ID+self.deleteTarget.id);self.deleteTarget.root?el.find(t.SELECTOR.BTN_DELETE).first().focus():el.find(t.SELECTOR.BTN_DELETE_REPLY).first().focus()})),modal.getRoot().on(ModalEvents.shown,(function(){self.changeWorkingState(!1)})),modal.show(),self.changeWorkingState(!1)})))},deleteComment:function(id){var params=this.getParamsBeforeCallApi({commentid:id});return ajax.call([{methodname:t.ACTION_DELETE,args:params}])[0]},bindEditorEvent:function(formSelector){var self=this;M.util.js_pending("init_editor"),self.triggerAttoNoContent(formSelector),self.setPlaceholder(formSelector,formSelector.attr("data-textarea-placeholder")),formSelector.find(t.SELECTOR.ATTO.TOOLBAR).fadeIn();var textareaSelector=formSelector.find(t.SELECTOR.TEXTAREA),attoEditableId=textareaSelector.attr("id")+"editable",attoEditable=document.getElementById(attoEditableId);new MutationObserver((function(mutationsList){mutationsList.forEach((function(mutation){"childList"!==mutation.type&&("attributes"!==mutation.type||"style"!==mutation.attributeName&&"hidden"!==mutation.attributeName)||self.checkEditorContent(formSelector)}))})).observe(attoEditable,{attributes:!0,childList:!0,subtree:!0}),textareaSelector.change((function(){self.checkEditorContent(formSelector)})),M.util.js_complete("init_editor");var interval=setInterval((function(){formSelector.find('textarea[id^="id_message"]').trigger("change")}),350);setTimeout((function(){clearInterval(interval)}),5e3)},checkEmptyElement:function(el){return 0===el.children().length},setHasComment:function(value){var container=this.elementSelector,hasCommentClass=t.HAS_COMMENT_CLASS;this.forceCommenting?(this.hasComment=value,this.hasComment?container.addClass(hasCommentClass):container.removeClass(hasCommentClass)):(this.hasComment=!0,container.addClass(hasCommentClass))},parseQueryString:function(query){for(var vars=query.split("&"),queryString={},i=0;i]*>)+(
)?(<\/p>)+$/.exec(attoEditableEle.html());t.EMPTY_CONTENT.indexOf(attoEditableEle.html())>-1||attoEditableEle.text().trim().length<1?(match||t.EMPTY_CONTENT.indexOf(attoEditableEle.html())>-1?this.setPlaceholder(formSelector,formSelector.attr("data-textarea-placeholder")):this.setPlaceholder(formSelector,""),this.triggerAttoNoContent(formSelector)):(this.setPlaceholder(formSelector,""),this.triggerAttoHasContent(formSelector)),M.util.js_complete(key)}}},generate:function(params){t.get().init(params)}};return t})); +define("mod_studentquiz/comment_area",["jquery","core/str","core/ajax","core/modal_factory","core/templates","core/fragment","core/modal_events"],(function($,str,ajax,ModalFactory,Templates,fragment,ModalEvents){var t={EMPTY_CONTENT:["


","


","
",""],ROOT_COMMENT_VALUE:0,GET_ALL_VALUE:0,TEMPLATE_COMMENTS:"mod_studentquiz/comments",TEMPLATE_COMMENT:"mod_studentquiz/comment",ACTION_CREATE:"mod_studentquiz_create_comment",ACTION_CREATE_REPLY:"mod_studentquiz_create_reply",ACTION_GET_ALL:"mod_studentquiz_get_comments",ACTION_EXPAND:"mod_studentquiz_expand_comment",ACTION_DELETE:"mod_studentquiz_delete_comment",ACTION_EDIT:"mod_studentquiz_edit_comment",ACTION_LOAD_FRAGMENT_FORM:"mod_studentquiz_load_fragment_form",ACTION_LOAD_FRAGMENT_EDIT_FORM:"mod_studentquiz_load_fragment_edit_form",ACTION_EXPAND_ALL:"action_expand_all",ACTION_COLLAPSE_ALL:"action_collapse_all",ACTION_RENDER_COMMENT:"action_render_comment",ACTION_APPEND_COMMENT:"action_append_comment",ACTION_EDITOR_INIT:"action_editor_init",ACTION_INIT:"action_init",ACTION_UPDATE_COMMENT_COUNT:"action_update_comment_count",ACTION_CLEAR_FORM:"action_clear_form",ACTION_SHOW_ERROR:"action_show_error",FRAGMENT_FORM_CALLBACK:"commentform",FRAGMENT_EDIT_FORM_CALLBACK:"commenteditform",HAS_COMMENT_CLASS:"has-comment",ATTO_CONTENT_TYPE:{HAS_CONTENT:"has-content",NO_CONTENT:"no-content"},SELECTOR:{CONTAINER:".studentquiz-comment-container",EXPAND_ALL:".studentquiz-comment-expand",COLLAPSE_ALL:".studentquiz-comment-collapse",SUBMIT_BUTTON:"#id_submitbutton",CONTAINER_REPLIES:".studentquiz-container-replies",COMMENT_REPLIES_CONTAINER:".studentquiz-comment-replies",COMMENT_COUNT:".studentquiz-comment-postcount",COMMENT_TEXT_CONTAINER:".studentquiz-comment-text",COMMENT_TEXT:".studentquiz-comment-text-inside",COMMENT_HISTORY:".studentquiz-comment-history",COMMENT_REPLIES_TEXT:".studentquiz-comment-replies .studentquiz-comment-text .studentquiz-comment-text-inside",LOADING_ICON:".studentquiz-comment-loading",COMMENT_AREA_FORM:"div.comment-area-form",FORM_SELECTOR:".studentquiz-comment-postform > div.comment-area-form",NO_COMMENT:".no-comment",COLLAPSE_LINK:".studentquiz-comment-collapselink",EXPAND_LINK:".studentquiz-comment-expandlink",COMMENT_ITEM:".studentquiz-comment-item",COMMENT_REPLIES_CONTAINER_TO_ITEM:".studentquiz-comment-replies .studentquiz-comment-item",FRAGMENT_FORM:".studentquiz-comment-postfragmentform",BTN_DELETE:".studentquiz-comment-btndelete",BTN_REPLY:".studentquiz-comment-btnreply",BTN_DELETE_REPLY:".studentquiz-comment-btndeletereply",ATTO_EDITOR_WRAP:".editor_atto_wrap",TEXTAREA:'textarea[id^="id_editor_question_"]',COMMENT_COUNT_NUMBER:".studentquiz-comment-count-number",COMMENT_COUNT_TEXT:".studentquiz-comment-count-text",ATTO:{CONTENT_WRAP:".editor_atto_content_wrap",CONTENT:".editor_atto_content",TOOLBAR:".editor_atto_toolbar"},TINYMCE:{CONTENT:".tox-edit-area"},COMMENT_ID:"#comment_",SPAN_COMMENT_ID:"#c",TOTAL_REPLY:".studentquiz-comment-totalreply",COMMENT_FILTER:".studentquiz-comment-filter",COMMENT_FILTER_HIDE:".hide-comment-filter",COMMENT_ERROR:".studentquiz-comment-container .comment-error",BTN_REPORT:".studentquiz-comment-btnreport",COMMENT_FILTER_ITEM:".studentquiz-comment-filter-item",COMMENT_FILTER_NAME:".studentquiz-comment-filter-name",COMMENT_FILTER_TYPE:".studentquiz-comment-filter-type",BTN_EDIT:".studentquiz-comment-btnedit",BTN_EDIT_REPLY:".studentquiz-comment-btneditreply",ATTO_HTML_BUTTON:"button.atto_html_button",POST_FOOTER:".studentquiz-comment-postfooter"},EDITOR:{ATTO:{TYPE:"atto"},TINYMCE:{TYPE:"tiny"},TEXTAREA:{TYPE:"textarea"}},get:function(){return{elementSelector:null,btnExpandAll:null,btnCollapseAll:null,addComment:null,containerSelector:null,studentQuizQuestionId:null,dialogue:null,loadingIcon:null,lastFocusElement:null,formSelector:null,contextId:null,userId:null,string:{},deleteDialog:null,deleteTarget:null,numberToShow:5,cmId:null,countServerData:[],lastCurrentCount:0,lastTotal:0,expand:!1,forceCommenting:!1,canViewDeleted:!1,hasComment:!1,referer:null,highlight:0,sortFeature:null,sortable:[],workingState:!1,isNoComment:!1,type:0,init:function(params){M.util.js_pending(t.ACTION_INIT);this.elementSelector=$("#"+$.escapeSelector(params.id));var el=this.elementSelector;this.btnExpandAll=el.find(t.SELECTOR.EXPAND_ALL),this.btnCollapseAll=el.find(t.SELECTOR.COLLAPSE_ALL),this.addComment=el.find(t.SELECTOR.SUBMIT_BUTTON),this.containerSelector=el.find(t.SELECTOR.CONTAINER_REPLIES),this.loadingIcon=el.find(t.SELECTOR.LOADING_ICON),this.formSelector=el.find(t.SELECTOR.FORM_SELECTOR),this.studentQuizQuestionId=parseInt(el.data("studentquizquestionid")),this.contextId=parseInt(el.data("contextid")),this.userId=parseInt(el.data("userid")),this.numberToShow=parseInt(el.data("numbertoshow")),this.cmId=parseInt(el.data("cmid")),this.countServerData={count:params.count,total:params.total},this.expand=params.expand||!1,this.referer=el.data("referer"),this.sortFeature=params.sortfeature,this.sortable=el.data("sortable"),this.type=params.type,this.string=el.data("strings"),this.forceCommenting=params.forcecommenting,this.canViewDeleted=params.canviewdeleted,this.isNoComment=params.isnocomment,this.allowSelfCommentRating=params.allowselfcommentrating,this.initServerRender(),params.allowselfcommentrating&&this.initBindEditor(),this.bindEvents(),M.util.js_complete(t.ACTION_INIT)},initServerRender:function(){var self=this;self.changeWorkingState(!0),self.elementSelector.find(t.SELECTOR.COMMENT_ITEM).each((function(){var id=$(this).data("id"),attrs=$(this).find(t.SELECTOR.SPAN_COMMENT_ID+id),replies=[];self.expand&&(replies=attrs.data("replies")||[]);var comment={id:$(this).data("id"),deleted:attrs.data("deleted"),numberofreply:attrs.data("numberofreply"),expanded:self.expand,replies:replies,root:!0,type:self.type};self.bindCommentEvent(comment)}));var commentcount=self.expand?self.countServerData.total:self.countServerData.count.commentcount;self.updateCommentCount(commentcount,self.countServerData.total),self.expand?(self.btnExpandAll.hide(),self.btnCollapseAll.show()):(self.btnExpandAll.show(),self.btnCollapseAll.hide());var query=window.location.search.substring(1),getParams=self.parseQueryString(query);if(self.highlight=parseInt(getParams.highlight)||0,0!==self.highlight){var target=$(t.SELECTOR.COMMENT_ID+self.highlight);target.length&&self.scrollToElement(target)}self.changeWorkingState(!1)},initBindEditor:function(){var self=this,isEditorLoaded=!1;M.util.js_pending(t.ACTION_EDITOR_INIT);var interval=setInterval((function(){self.bindEditorEvent(self.formSelector),isEditorLoaded=!0,clearInterval(interval),M.util.js_complete(t.ACTION_EDITOR_INIT)}),500),editorWaiting=setInterval((function(){if(isEditorLoaded){if(0!==self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length){const tinyEditorId=self.formSelector.find(t.SELECTOR.TEXTAREA).attr("id"),editor=window.tinyMCE.get(tinyEditorId);self.checkEditorContent(self.formSelector,editor.getBody(),t.EDITOR.TINYMCE.TYPE)}else self.checkEditorContent(self.formSelector);clearInterval(editorWaiting)}}),1e3)},bindEvents:function(){var self=this;self.btnExpandAll.click((function(e){e.preventDefault(),M.util.js_pending(t.ACTION_EXPAND_ALL),self.changeWorkingState(!0),self.containerSelector.empty(),self.btnExpandAll.hide(),self.btnCollapseAll.show(),self.loadingIcon.show(),self.getComments(t.GET_ALL_VALUE).then((function(response){var total=self.countCommentAndReplies(response.data).total;return self.updateCommentCount(total,response.total),self.renderComment(response.data,!0),M.util.js_complete(t.ACTION_EXPAND_ALL),!0})).fail((function(err){return M.util.js_complete(t.ACTION_EXPAND_ALL),self.showError(err.message),!1}))})),self.btnCollapseAll.click((function(e){e.preventDefault(),M.util.js_pending(t.ACTION_COLLAPSE_ALL),self.changeWorkingState(!0),self.loadingIcon.show(),self.btnCollapseAll.hide(),self.btnExpandAll.show(),self.containerSelector[0].innerHTML="",self.getComments(self.numberToShow).then((function(response){var count=self.countCommentAndReplies(response.data),commentCount=count.commentCount,deletedComments=count.totalDelete;return 0!==commentCount||0!==deletedComments?(self.btnExpandAll.show(),self.updateCommentCount(commentCount,response.total),self.renderComment(response.data,!1)):(self.loadingIcon.hide(),self.changeWorkingState(!1),self.updateCommentCount(0,0)),M.util.js_complete(t.ACTION_COLLAPSE_ALL),!0})).fail((function(err){return M.util.js_complete(t.ACTION_COLLAPSE_ALL),self.showError(err.message),!1}))})),self.addComment.click((function(e){e.preventDefault(),M.util.js_pending(t.ACTION_CREATE),self.changeWorkingState(!0),self.loadingIcon.show(),$(t.SELECTOR.COMMENT_ERROR).addClass("hide"),$(t.SELECTOR.NO_COMMENT).hide();var rootId=t.ROOT_COMMENT_VALUE,unique=self.studentQuizQuestionId+"_"+self.type+"_"+rootId,formSelector=self.formSelector,formData=self.convertFormToJson(formSelector);if(0===formData["message[text]"].length){var attoWrap=formSelector.find(t.SELECTOR.ATTO_EDITOR_WRAP);return 0===attoWrap.length||attoWrap.hasClass("error")||(attoWrap.addClass("error"),attoWrap.prepend(''+self.string.required+"")),M.util.js_complete(t.ACTION_CREATE),!1}var params={replyto:rootId,message:{text:formData["message[text]"],format:formData["message[format]"]}};return self.createComment(params).then((function(response){M.util.js_pending(t.ACTION_CLEAR_FORM),setTimeout((function(){0!==self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length?self.resetContent(formSelector,t.EDITOR.TINYMCE.TYPE):0!==self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length?(self.formSelector.trigger("reset"),formSelector.find("#id_editor_question_"+unique+"editable").is(":visible")||self.elementSelector.find(t.SELECTOR.ATTO_HTML_BUTTON).trigger("click"),formSelector.find("#id_editor_question_"+unique+"editable").empty(),formSelector.find(t.SELECTOR.TEXTAREA).trigger("change")):self.resetContent(formSelector,t.EDITOR.TEXTAREA.TYPE),M.util.js_complete(t.ACTION_CLEAR_FORM)}));var data=self.convertForTemplate(response,!0);return formSelector.find(t.SELECTOR.SUBMIT_BUTTON).addClass("disabled"),self.appendComment(data,self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES),!1),M.util.js_complete(t.ACTION_CREATE),!0})).fail((function(e){self.handleFailWhenCreateComment(e,params),M.util.js_complete(t.ACTION_CREATE)})),!0})),self.elementSelector.find(t.SELECTOR.COMMENT_FILTER_ITEM).on("click",(function(e){if(e.preventDefault(),!self.workingState){var asc=self.string.sort.asc,desc=self.string.sort.desc,nameSelector=$(this).find(t.SELECTOR.COMMENT_FILTER_NAME),iconSelector=$(this).find(t.SELECTOR.COMMENT_FILTER_TYPE),type=$(this).data("type"),orderBy=$(this).attr("data-order"),isCurrent=$(this).hasClass("current"),ascString=$(this).attr("data-asc-string"),descString=$(this).attr("data-desc-string");orderBy="desc"===orderBy?"asc":"desc",$(this).attr("data-order",orderBy),isCurrent||$(this).addClass("current"),"desc"===orderBy?(nameSelector.attr("title",ascString),nameSelector.attr("alt",ascString),iconSelector.attr("title",desc),iconSelector.attr("alt",desc)):(nameSelector.attr("title",descString),nameSelector.attr("alt",descString),iconSelector.attr("title",asc),iconSelector.attr("alt",asc)),self.elementSelector.find(t.SELECTOR.COMMENT_FILTER_ITEM).not(this).each((function(){var each=$(this),eachName=$(this).find(t.SELECTOR.COMMENT_FILTER_NAME),eachType=$(this).find(t.SELECTOR.COMMENT_FILTER_TYPE),defaultString=$(this).attr("data-asc-string");each.attr("data-order","desc"),each.removeClass("filter-asc"),each.removeClass("filter-desc"),each.removeClass("current"),eachName.attr("title",defaultString),eachName.attr("alt",defaultString),eachType.attr("title",asc),eachType.attr("alt",asc)})),"desc"===orderBy?($(this).removeClass("filter-asc"),$(this).addClass("filter-desc")):($(this).removeClass("filter-desc"),$(this).addClass("filter-asc"));var sortType=type+"_"+orderBy;self.setSort(sortType),self.expand?self.btnExpandAll.trigger("click"):self.btnCollapseAll.trigger("click")}}))},getComments:function(numberToShow){var params=this.getParamsBeforeCallApi({numbertoshow:numberToShow,sort:this.sortFeature,type:this.type});return ajax.call([{methodname:t.ACTION_GET_ALL,args:params}])[0]},getParamsBeforeCallApi:function(params){return params.studentquizquestionid=this.studentQuizQuestionId,params.cmid=this.cmId,params.type=this.type,params},showError:function(message){var self=this;M.util.js_pending(t.ACTION_SHOW_ERROR),$.when(self.string.error).done((function(string){self.showDialog(string,message),self.changeWorkingState(!1),M.util.js_complete(t.ACTION_SHOW_ERROR)}))},showDialog:function(title,body){var dialogue=this.dialogue;if(dialogue)return dialogue.title.html(title),dialogue.body.html(body),void dialogue.show();ModalFactory.create({type:ModalFactory.types.CANCEL,title:title,body:body}).done((function(modal){(dialogue=modal).show(),dialogue.getRoot().on(ModalEvents.hidden,{},(function(){location.reload()}))}))},updateCommentCount:function(current,total){M.util.js_pending(t.ACTION_UPDATE_COMMENT_COUNT);var self=this;-1===total?total=self.lastTotal:self.lastTotal=total,-1===current?current=self.lastCurrentCount:self.lastCurrentCount=current;var s=str.get_string("current_of_total","studentquiz",{current:current,total:total}),noCommentSelector=self.elementSelector.find(t.SELECTOR.NO_COMMENT),filter=self.elementSelector.find(t.SELECTOR.COMMENT_FILTER),emptyReplies=self.checkEmptyElement(self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES));0===self.lastCurrentCount&&emptyReplies&&self.isNoComment?(self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES).hide(),filter.hide(),noCommentSelector.show()):(self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES).show(),noCommentSelector.hide(),filter.show()),$.when(s).done((function(text){self.elementSelector.find(t.SELECTOR.COMMENT_COUNT).text(text),M.util.js_complete(t.ACTION_UPDATE_COMMENT_COUNT)}))},renderComment:function(comments,expanded){var self=this;M.util.js_pending(t.ACTION_RENDER_COMMENT),comments=self.convertForTemplate(comments,expanded),Templates.render(t.TEMPLATE_COMMENTS,{comments:comments}).done((function(html){self.containerSelector[0].innerHTML=html,self.loadingIcon.hide();for(var i=0;i1&&void 0!==arguments[1]?arguments[1]:null;var visibility=boolean?"hidden":"visible",self=this;self.workingState=boolean,self.btnExpandAll.prop("disabled",boolean),self.btnCollapseAll.prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_REPLY).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_DELETE).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_DELETE_REPLY).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_REPORT).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.EXPAND_LINK).css("visibility",visibility),self.elementSelector.find(t.SELECTOR.COLLAPSE_LINK).css("visibility",visibility),self.elementSelector.find(t.SELECTOR.BTN_EDIT).prop("disabled",boolean),self.elementSelector.find(t.SELECTOR.BTN_EDIT_REPLY).prop("disabled",boolean),self.deleteDialog&&self.deleteDialog.getFooter().find('button[data-action="yes"]').prop("disabled",boolean),boolean?(self.addComment.prop("disabled",boolean),null!==elementToHide&&elementToHide instanceof $&&elementToHide.hide()):(self.lastFocusElement&&(self.lastFocusElement.focus(),self.lastFocusElement=null),self.elementSelector.find(t.SELECTOR.POST_FOOTER).show())},countCommentAndReplies:function(data){var commentCount=0,deleteCommentCount=0,replyCount=0,deleteReplyCount=0;data.constructor!==Array&&(data=[data]);for(var i=0;i'+self.string.deletetext+'"}).done((function(modal){self.deleteDialog=modal,modal.getFooter().find('button[data-action="no"]').click((function(e){e.preventDefault(),modal.hide()})),modal.getFooter().find('button[data-action="yes"]').click((function(e){e.preventDefault(),M.util.js_pending(t.ACTION_DELETE),self.changeWorkingState(!0),self.deleteComment(self.deleteTarget.id).then((function(response){if(!response.success)return self.showError(response.message),!0;var convertedCommentData=self.convertForTemplate(response.data,self.deleteTarget.expanded),commentSelector=self.elementSelector.find(t.SELECTOR.COMMENT_ID+convertedCommentData.id);return self.updateCommentCount(self.lastCurrentCount-1,self.lastTotal-1),convertedCommentData.root||(convertedCommentData.expanded=!0),Templates.render(t.TEMPLATE_COMMENT,convertedCommentData).done((function(html){var el=$(html);if(!convertedCommentData.root){var parentCountSelector=commentSelector.parent().closest(t.SELECTOR.COMMENT_ITEM).find(t.SELECTOR.TOTAL_REPLY),countSelector=parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_NUMBER),newCount=parseInt(countSelector.text())-1;parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_NUMBER).text(newCount),parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_TEXT).html(1===newCount?self.string.reply:self.string.replies)}var oldReplies=commentSelector.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).clone(!0);commentSelector.replaceWith(el),el.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).replaceWith(oldReplies),self.deleteTarget.root?self.bindCommentEvent(response.data):self.bindReplyEvent(response.data,el.parent()),self.changeWorkingState(!1),M.util.js_complete(t.ACTION_DELETE)})),modal.hide(),!0})).fail((function(err){return self.showError(err.message),!1}))})),modal.getRoot().on(ModalEvents.hidden,(function(){var el=self.elementSelector.find(t.SELECTOR.COMMENT_ID+self.deleteTarget.id);self.deleteTarget.root?el.find(t.SELECTOR.BTN_DELETE).first().focus():el.find(t.SELECTOR.BTN_DELETE_REPLY).first().focus()})),modal.getRoot().on(ModalEvents.shown,(function(){self.changeWorkingState(!1)})),modal.show(),self.changeWorkingState(!1)})))},deleteComment:function(id){var params=this.getParamsBeforeCallApi({commentid:id});return ajax.call([{methodname:t.ACTION_DELETE,args:params}])[0]},bindHandleTinyEditor:function(formSelector){var self=this;const tinyEditorId=formSelector.find(t.SELECTOR.TEXTAREA).attr("id"),intervalID=setInterval((function(){if(window.tinyMCE){const editor=window.tinyMCE.get(tinyEditorId);editor.on("input",(function(){self.checkEditorContent(formSelector,editor.getBody(),t.EDITOR.TINYMCE.TYPE)})),editor.on("change",(function(){self.checkEditorContent(formSelector,editor.getBody(),t.EDITOR.TINYMCE.TYPE)})),clearInterval(intervalID)}}),100)},bindHandleAttoEditor:function(formSelector){var self=this;M.util.js_pending("init_editor"),self.triggerAttoNoContent(formSelector),self.setPlaceholder(formSelector,formSelector.attr("data-textarea-placeholder")),formSelector.find(t.SELECTOR.ATTO.TOOLBAR).fadeIn();var textareaSelector=formSelector.find(t.SELECTOR.TEXTAREA),attoEditableId=textareaSelector.attr("id")+"editable",attoEditable=document.getElementById(attoEditableId);new MutationObserver((function(mutationsList){mutationsList.forEach((function(mutation){"childList"!==mutation.type&&("attributes"!==mutation.type||"style"!==mutation.attributeName&&"hidden"!==mutation.attributeName)||self.checkEditorContent(formSelector)}))})).observe(attoEditable,{attributes:!0,childList:!0,subtree:!0}),textareaSelector.change((function(){self.checkEditorContent(formSelector)})),M.util.js_complete("init_editor");var interval=setInterval((function(){formSelector.find('textarea[id^="id_message"]').trigger("change")}),350);setTimeout((function(){clearInterval(interval)}),5e3)},bindHandleTextareaEditor:function(formSelector){var self=this;const textareaSelector=formSelector.find(t.SELECTOR.TEXTAREA);textareaSelector&&textareaSelector.on("input",(function(){self.checkEditorContent(formSelector,textareaSelector[0],t.EDITOR.TEXTAREA.TYPE)}))},bindEditorEvent:function(formSelector){0!==this.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length?this.bindHandleTinyEditor(formSelector):0!==this.formSelector.find(t.SELECTOR.ATTO.CONTENT).length?this.bindHandleAttoEditor(formSelector):this.bindHandleTextareaEditor(formSelector)},resetContent:function(formSelector,type){const textareaSelector=formSelector.find(t.SELECTOR.TEXTAREA);if(type===t.EDITOR.TINYMCE.TYPE){const tinyEditorId=textareaSelector.attr("id");window.tinyMCE.get(tinyEditorId).setContent("")}else textareaSelector[0].value=""},checkEmptyElement:function(el){return 0===el.children().length},setHasComment:function(value){var container=this.elementSelector,hasCommentClass=t.HAS_COMMENT_CLASS;this.forceCommenting?(this.hasComment=value,this.hasComment?container.addClass(hasCommentClass):container.removeClass(hasCommentClass)):(this.hasComment=!0,container.addClass(hasCommentClass))},parseQueryString:function(query){for(var vars=query.split("&"),queryString={},i=0;i1&&void 0!==arguments[1]?arguments[1]:null,type=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";const key="text_change_"+Date.now();M.util.js_pending(key);const textareaSelector=formSelector.find(t.SELECTOR.TEXTAREA);let editorBodyContent,contenthtml,condition;switch(type){case t.EDITOR.TINYMCE.TYPE:editorBodyContent=$(bodyContent),contenthtml=editorBodyContent.html(),condition=t.EMPTY_CONTENT.indexOf(contenthtml)>-1||editorBodyContent.text().trim().length<1;break;case t.EDITOR.ATTO.TYPE:case"":editorBodyContent=$("#"+textareaSelector.attr("id")+"editable"),contenthtml=editorBodyContent.html(),condition=t.EMPTY_CONTENT.indexOf(contenthtml)>-1||editorBodyContent.text().trim().length<1;break;case t.EDITOR.TEXTAREA.TYPE:editorBodyContent=$(bodyContent),contenthtml=editorBodyContent[0].value,condition=contenthtml.trim().length<1}const regex=/^(<(?:p)[^>]*>)+(
)?(<\/p>)+$/,match=regex.exec(contenthtml);if(condition){const isEmptyContent=match||t.EMPTY_CONTENT.indexOf(contenthtml)>-1;this.setPlaceholder(formSelector,isEmptyContent?formSelector.attr("data-textarea-placeholder"):""),this.triggerAttoNoContent(formSelector)}else this.setPlaceholder(formSelector,""),this.triggerAttoHasContent(formSelector);M.util.js_complete(key)}}},generate:function(params){t.get().init(params)}};return t})); //# sourceMappingURL=comment_area.min.js.map \ No newline at end of file diff --git a/amd/build/comment_area.min.js.map b/amd/build/comment_area.min.js.map index 2a98d21b..37d42f61 100644 --- a/amd/build/comment_area.min.js.map +++ b/amd/build/comment_area.min.js.map @@ -1 +1 @@ -{"version":3,"file":"comment_area.min.js","sources":["../src/comment_area.js"],"sourcesContent":["// This file is part of Moodle - http://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 * Control the element in comment area.\n *\n * @module mod_studentquiz/comment_area\n * @copyright 2020 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * @module mod_studentquiz/comment_element\n */\ndefine(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates', 'core/fragment', 'core/modal_events'],\n function($, str, ajax, ModalFactory, Templates, fragment, ModalEvents) {\n var t = {\n EMPTY_CONTENT: ['


', '


', '
', ''],\n ROOT_COMMENT_VALUE: 0,\n GET_ALL_VALUE: 0,\n TEMPLATE_COMMENTS: 'mod_studentquiz/comments',\n TEMPLATE_COMMENT: 'mod_studentquiz/comment',\n ACTION_CREATE: 'mod_studentquiz_create_comment',\n ACTION_CREATE_REPLY: 'mod_studentquiz_create_reply',\n ACTION_GET_ALL: 'mod_studentquiz_get_comments',\n ACTION_EXPAND: 'mod_studentquiz_expand_comment',\n ACTION_DELETE: 'mod_studentquiz_delete_comment',\n ACTION_EDIT: 'mod_studentquiz_edit_comment',\n ACTION_LOAD_FRAGMENT_FORM: 'mod_studentquiz_load_fragment_form',\n ACTION_LOAD_FRAGMENT_EDIT_FORM: 'mod_studentquiz_load_fragment_edit_form',\n ACTION_EXPAND_ALL: 'action_expand_all',\n ACTION_COLLAPSE_ALL: 'action_collapse_all',\n ACTION_RENDER_COMMENT: 'action_render_comment',\n ACTION_APPEND_COMMENT: 'action_append_comment',\n ACTION_EDITOR_INIT: 'action_editor_init',\n ACTION_INIT: 'action_init',\n ACTION_UPDATE_COMMENT_COUNT: 'action_update_comment_count',\n ACTION_CLEAR_FORM: 'action_clear_form',\n ACTION_SHOW_ERROR: 'action_show_error',\n FRAGMENT_FORM_CALLBACK: 'commentform',\n FRAGMENT_EDIT_FORM_CALLBACK: 'commenteditform',\n HAS_COMMENT_CLASS: 'has-comment',\n ATTO_CONTENT_TYPE: {\n HAS_CONTENT: 'has-content',\n NO_CONTENT: 'no-content'\n },\n SELECTOR: {\n CONTAINER: '.studentquiz-comment-container',\n EXPAND_ALL: '.studentquiz-comment-expand',\n COLLAPSE_ALL: '.studentquiz-comment-collapse',\n SUBMIT_BUTTON: '#id_submitbutton',\n CONTAINER_REPLIES: '.studentquiz-container-replies',\n COMMENT_REPLIES_CONTAINER: '.studentquiz-comment-replies',\n COMMENT_COUNT: '.studentquiz-comment-postcount',\n COMMENT_TEXT_CONTAINER: '.studentquiz-comment-text',\n COMMENT_TEXT: '.studentquiz-comment-text-inside',\n COMMENT_HISTORY: '.studentquiz-comment-history',\n COMMENT_REPLIES_TEXT: '.studentquiz-comment-replies .studentquiz-comment-text .studentquiz-comment-text-inside',\n LOADING_ICON: '.studentquiz-comment-loading',\n COMMENT_AREA_FORM: 'div.comment-area-form',\n FORM_SELECTOR: '.studentquiz-comment-postform > div.comment-area-form',\n NO_COMMENT: '.no-comment',\n COLLAPSE_LINK: '.studentquiz-comment-collapselink',\n EXPAND_LINK: '.studentquiz-comment-expandlink',\n COMMENT_ITEM: '.studentquiz-comment-item',\n COMMENT_REPLIES_CONTAINER_TO_ITEM: '.studentquiz-comment-replies .studentquiz-comment-item',\n FRAGMENT_FORM: '.studentquiz-comment-postfragmentform',\n BTN_DELETE: '.studentquiz-comment-btndelete',\n BTN_REPLY: '.studentquiz-comment-btnreply',\n BTN_DELETE_REPLY: '.studentquiz-comment-btndeletereply',\n ATTO_EDITOR_WRAP: '.editor_atto_wrap',\n TEXTAREA: 'textarea[id^=\"id_editor_question_\"]',\n COMMENT_COUNT_NUMBER: '.studentquiz-comment-count-number',\n COMMENT_COUNT_TEXT: '.studentquiz-comment-count-text',\n ATTO: {\n CONTENT_WRAP: '.editor_atto_content_wrap',\n CONTENT: '.editor_atto_content',\n TOOLBAR: '.editor_atto_toolbar'\n },\n COMMENT_ID: '#comment_',\n // Is used when server render. We need to collect some stored data attributes to load events.\n SPAN_COMMENT_ID: '#c',\n TOTAL_REPLY: '.studentquiz-comment-totalreply',\n COMMENT_FILTER: '.studentquiz-comment-filter',\n COMMENT_FILTER_HIDE: '.hide-comment-filter',\n COMMENT_ERROR: '.studentquiz-comment-container .comment-error',\n BTN_REPORT: '.studentquiz-comment-btnreport',\n COMMENT_FILTER_ITEM: '.studentquiz-comment-filter-item',\n COMMENT_FILTER_NAME: '.studentquiz-comment-filter-name',\n COMMENT_FILTER_TYPE: '.studentquiz-comment-filter-type',\n BTN_EDIT: '.studentquiz-comment-btnedit',\n BTN_EDIT_REPLY: '.studentquiz-comment-btneditreply',\n ATTO_HTML_BUTTON: 'button.atto_html_button',\n POST_FOOTER: '.studentquiz-comment-postfooter'\n },\n get: function() {\n return {\n elementSelector: null,\n btnExpandAll: null,\n btnCollapseAll: null,\n addComment: null,\n containerSelector: null,\n studentQuizQuestionId: null,\n dialogue: null,\n loadingIcon: null,\n lastFocusElement: null,\n formSelector: null,\n contextId: null,\n userId: null,\n string: {},\n deleteDialog: null,\n deleteTarget: null,\n numberToShow: 5,\n cmId: null,\n countServerData: [],\n lastCurrentCount: 0,\n lastTotal: 0,\n expand: false,\n forceCommenting: false,\n canViewDeleted: false,\n hasComment: false,\n referer: null,\n highlight: 0,\n sortFeature: null,\n sortable: [],\n workingState: false,\n isNoComment: false,\n type: 0,\n\n /**\n * Init function.\n *\n * @param {Object} params\n */\n init: function(params) {\n M.util.js_pending(t.ACTION_INIT);\n var self = this;\n // Assign attribute.\n self.elementSelector = $('#' + $.escapeSelector(params.id));\n var el = self.elementSelector;\n\n self.btnExpandAll = el.find(t.SELECTOR.EXPAND_ALL);\n self.btnCollapseAll = el.find(t.SELECTOR.COLLAPSE_ALL);\n self.addComment = el.find(t.SELECTOR.SUBMIT_BUTTON);\n self.containerSelector = el.find(t.SELECTOR.CONTAINER_REPLIES);\n self.loadingIcon = el.find(t.SELECTOR.LOADING_ICON);\n self.formSelector = el.find(t.SELECTOR.FORM_SELECTOR);\n self.studentQuizQuestionId = parseInt(el.data('studentquizquestionid'));\n self.contextId = parseInt(el.data('contextid'));\n self.userId = parseInt(el.data('userid'));\n self.numberToShow = parseInt(el.data('numbertoshow'));\n self.cmId = parseInt(el.data('cmid'));\n\n self.countServerData = {\n count: params.count,\n total: params.total\n };\n\n self.expand = params.expand || false;\n self.referer = el.data('referer');\n self.sortFeature = params.sortfeature;\n self.sortable = el.data('sortable');\n self.type = params.type;\n\n // Get all language strings.\n self.string = el.data('strings');\n self.forceCommenting = params.forcecommenting;\n self.canViewDeleted = params.canviewdeleted;\n self.isNoComment = params.isnocomment;\n self.allowSelfCommentRating = params.allowselfcommentrating;\n\n self.initServerRender();\n if (params.allowselfcommentrating) {\n self.initBindEditor();\n }\n self.bindEvents();\n M.util.js_complete(t.ACTION_INIT);\n },\n\n /**\n * Init for server rendering.\n */\n initServerRender: function() {\n var self = this;\n self.changeWorkingState(true);\n self.elementSelector.find(t.SELECTOR.COMMENT_ITEM).each(function() {\n var id = $(this).data('id');\n var attrs = $(this).find(t.SELECTOR.SPAN_COMMENT_ID + id);\n var replies = [];\n if (self.expand) {\n replies = attrs.data('replies') || [];\n }\n var comment = {\n id: $(this).data('id'),\n deleted: attrs.data('deleted'),\n numberofreply: attrs.data('numberofreply'),\n expanded: self.expand,\n replies: replies,\n root: true,\n type: self.type\n };\n self.bindCommentEvent(comment);\n });\n\n // If expanded, current comment count is total comments + replies.\n var commentcount = self.expand ? self.countServerData.total : self.countServerData.count.commentcount;\n self.updateCommentCount(commentcount, self.countServerData.total);\n\n if (self.expand) {\n self.btnExpandAll.hide();\n self.btnCollapseAll.show();\n } else {\n self.btnExpandAll.show();\n self.btnCollapseAll.hide();\n }\n\n // Highlight.\n var query = window.location.search.substring(1);\n var getParams = self.parseQueryString(query);\n self.highlight = parseInt(getParams.highlight) || 0;\n // End set highlight.\n\n // Scroll to.\n if (self.highlight !== 0) {\n var target = $(t.SELECTOR.COMMENT_ID + self.highlight);\n if (target.length) {\n self.scrollToElement(target);\n }\n }\n\n self.changeWorkingState(false);\n },\n\n /**\n * Init comment editor.\n */\n initBindEditor: function() {\n var self = this;\n var isEditorLoaded = false;\n M.util.js_pending(t.ACTION_EDITOR_INIT);\n // Interval to init atto editor, there are time when Atto's Javascript slow to init the editor, so we\n // check interval here to make sure the Atto is init before calling our script.\n var interval = setInterval(function() {\n if (self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length !== 0) {\n self.bindEditorEvent(self.formSelector);\n isEditorLoaded = true;\n clearInterval(interval);\n M.util.js_complete(t.ACTION_EDITOR_INIT);\n }\n }, 500);\n\n // If the editor has some content that has been restored\n // then check the editor content.\n var editorWaiting = setInterval(function() {\n if (isEditorLoaded) {\n self.checkEditorContent(self.formSelector);\n clearInterval(editorWaiting);\n }\n }, 1000);\n },\n\n /**\n * Bind events: \"Expand all comments\", \"Collapse all comments\", \"Add Reply\".\n */\n bindEvents: function() {\n var self = this;\n // Bind event to \"Expand all comments\" button.\n self.btnExpandAll.click(function(e) {\n e.preventDefault();\n M.util.js_pending(t.ACTION_EXPAND_ALL);\n self.changeWorkingState(true);\n // Empty the replies section to append new response.\n self.containerSelector.empty();\n // Change button from expand to collapse collapse and disabled button since we don't want user to\n // press the button when javascript is appending item or ajax is working.\n self.btnExpandAll.hide();\n self.btnCollapseAll.show();\n self.loadingIcon.show();\n self.getComments(t.GET_ALL_VALUE).then(function(response) {\n // Calculate length to display count.\n var count = self.countCommentAndReplies(response.data);\n var total = count.total;\n self.updateCommentCount(total, response.total);\n self.renderComment(response.data, true);\n M.util.js_complete(t.ACTION_EXPAND_ALL);\n return true;\n }).fail(function(err) {\n M.util.js_complete(t.ACTION_EXPAND_ALL);\n self.showError(err.message);\n return false;\n });\n });\n\n // Bind event to \"Collapse all comments\" button.\n self.btnCollapseAll.click(function(e) {\n e.preventDefault();\n M.util.js_pending(t.ACTION_COLLAPSE_ALL);\n self.changeWorkingState(true);\n self.loadingIcon.show();\n self.btnCollapseAll.hide();\n self.btnExpandAll.show();\n self.containerSelector[0].innerHTML = '';\n self.getComments(self.numberToShow).then(function(response) {\n // Calculate length to display the post count.\n var count = self.countCommentAndReplies(response.data);\n var commentCount = count.commentCount;\n var deletedComments = count.totalDelete;\n // Only show expand button and count if comment existed.\n if (commentCount !== 0 || deletedComments !== 0) {\n self.btnExpandAll.show();\n self.updateCommentCount(commentCount, response.total);\n self.renderComment(response.data, false);\n } else {\n // No comment found hide loading icon.\n self.loadingIcon.hide();\n self.changeWorkingState(false);\n self.updateCommentCount(0, 0);\n }\n M.util.js_complete(t.ACTION_COLLAPSE_ALL);\n return true;\n }).fail(function(err) {\n M.util.js_complete(t.ACTION_COLLAPSE_ALL);\n self.showError(err.message);\n return false;\n });\n });\n\n // Bind event to \"Add Reply\" button (Root comment).\n self.addComment.click(function(e) {\n e.preventDefault();\n M.util.js_pending(t.ACTION_CREATE);\n self.changeWorkingState(true);\n self.loadingIcon.show();\n // Hide error if exists.\n $(t.SELECTOR.COMMENT_ERROR).addClass('hide');\n // Hide no comment.\n $(t.SELECTOR.NO_COMMENT).hide();\n var rootId = t.ROOT_COMMENT_VALUE;\n var unique = self.studentQuizQuestionId + '_' + self.type + '_' + rootId;\n var formSelector = self.formSelector;\n var formData = self.convertFormToJson(formSelector);\n // Check message field.\n if (formData['message[text]'].length === 0) {\n // Show message, atto won't auto show after second form is appended.\n var attoWrap = formSelector.find(t.SELECTOR.ATTO_EDITOR_WRAP);\n if (attoWrap.length !== 0 && !attoWrap.hasClass('error')) {\n attoWrap.addClass('error');\n attoWrap.prepend('' + self.string.required + '');\n }\n M.util.js_complete(t.ACTION_CREATE);\n return false;\n }\n var params = {\n replyto: rootId,\n message: {\n text: formData['message[text]'],\n format: formData['message[format]'],\n },\n };\n self.createComment(params).then(function(response) {\n M.util.js_pending(t.ACTION_CLEAR_FORM);\n // Clear form in setTimeout to prevent require message still shown when reset on Firefox.\n setTimeout(function() {\n // Clear form data.\n formSelector.trigger('reset');\n // Clear atto editor data.\n if (!formSelector.find('#id_editor_question_' + unique + 'editable').is(':visible')) {\n // HTML mode. Switch back to normal mode.\n self.elementSelector.find(t.SELECTOR.ATTO_HTML_BUTTON).trigger('click');\n }\n formSelector.find('#id_editor_question_' + unique + 'editable').empty();\n formSelector.find(t.SELECTOR.TEXTAREA).trigger('change');\n M.util.js_complete(t.ACTION_CLEAR_FORM);\n });\n var data = self.convertForTemplate(response, true);\n // Disable reply button since content is now empty.\n formSelector.find(t.SELECTOR.SUBMIT_BUTTON).addClass('disabled');\n self.appendComment(data, self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES), false);\n M.util.js_complete(t.ACTION_CREATE);\n return true;\n }).fail(function(e) {\n self.handleFailWhenCreateComment(e, params);\n M.util.js_complete(t.ACTION_CREATE);\n });\n return true;\n });\n\n // Bind events filter sort.\n self.elementSelector.find(t.SELECTOR.COMMENT_FILTER_ITEM).on('click', function(e) {\n e.preventDefault();\n // Check if current state is working, return.\n if (self.workingState) {\n return;\n }\n\n var asc = self.string.sort.asc;\n var desc = self.string.sort.desc;\n\n var nameSelector = $(this).find(t.SELECTOR.COMMENT_FILTER_NAME);\n var iconSelector = $(this).find(t.SELECTOR.COMMENT_FILTER_TYPE);\n\n // Get sort type from data-type.\n var type = $(this).data('type');\n var orderBy = $(this).attr('data-order');\n var isCurrent = $(this).hasClass('current');\n var ascString = $(this).attr('data-asc-string');\n var descString = $(this).attr('data-desc-string');\n\n // Get current orderBy from data-order. If not current sort, don't change.\n // Then reverse it to opposite orderBy and call to API.\n // Example: current is desc, then we should call order by = asc to api.\n\n orderBy = orderBy === 'desc' ? 'asc' : 'desc';\n // Ok we attach that orderBy to current order by.\n $(this).attr('data-order', orderBy);\n\n if (!isCurrent) {\n $(this).addClass('current');\n }\n\n if (orderBy === 'desc') {\n nameSelector.attr('title', ascString);\n nameSelector.attr('alt', ascString);\n iconSelector.attr('title', desc);\n iconSelector.attr('alt', desc);\n } else {\n nameSelector.attr('title', descString);\n nameSelector.attr('alt', descString);\n iconSelector.attr('title', asc);\n iconSelector.attr('alt', asc);\n }\n\n // Note: new text is the opposite of current sort type (old type).\n\n // Reset all filter elements to its default.\n self.elementSelector.find(t.SELECTOR.COMMENT_FILTER_ITEM).not(this).each(function() {\n var each = $(this);\n var eachName = $(this).find(t.SELECTOR.COMMENT_FILTER_NAME);\n var eachType = $(this).find(t.SELECTOR.COMMENT_FILTER_TYPE);\n var defaultString = $(this).attr('data-asc-string');\n each.attr('data-order', 'desc');\n each.removeClass('filter-asc');\n each.removeClass('filter-desc');\n each.removeClass('current');\n eachName.attr('title', defaultString);\n eachName.attr('alt', defaultString);\n eachType.attr('title', asc);\n eachType.attr('alt', asc);\n });\n\n if (orderBy === 'desc') {\n $(this).removeClass('filter-asc');\n $(this).addClass('filter-desc');\n } else {\n $(this).removeClass('filter-desc');\n $(this).addClass('filter-asc');\n }\n\n // Build to sort type. Example: date_asc, date_desc.\n var sortType = type + '_' + orderBy;\n self.setSort(sortType);\n\n if (self.expand) {\n self.btnExpandAll.trigger('click');\n } else {\n self.btnCollapseAll.trigger('click');\n }\n });\n },\n\n /**\n * Get comments, numbertoshow = 0 will get all comment + replies.\n *\n * @param {Integer} numberToShow\n * @returns {Promise}\n */\n getComments: function(numberToShow) {\n var self = this;\n var params = self.getParamsBeforeCallApi({\n numbertoshow: numberToShow,\n sort: self.sortFeature,\n type: self.type\n });\n var promise = ajax.call([{\n methodname: t.ACTION_GET_ALL,\n args: params\n }]);\n return promise[0];\n },\n\n /**\n * Always map studentquizquestionid and cmId to request before send.\n *\n * @param {Object} params\n * @returns {Object}\n */\n getParamsBeforeCallApi: function(params) {\n var self = this;\n params.studentquizquestionid = self.studentQuizQuestionId;\n params.cmid = self.cmId;\n params.type = self.type;\n return params;\n },\n\n /**\n * Show error which call showDialog().\n *\n * @param {String} message\n */\n showError: function(message) {\n var self = this;\n M.util.js_pending(t.ACTION_SHOW_ERROR);\n // Get error string for title.\n $.when(self.string.error).done(function(string) {\n self.showDialog(string, message);\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_SHOW_ERROR);\n });\n },\n\n /**\n * Show the dialog with custom title and body.\n *\n * @param {String} title\n * @param {String} body\n */\n showDialog: function(title, body) {\n var self = this;\n var dialogue = self.dialogue;\n if (dialogue) {\n // This dialog is existed, only change title and body and then display.\n dialogue.title.html(title);\n dialogue.body.html(body);\n dialogue.show();\n return;\n }\n ModalFactory.create({\n type: ModalFactory.types.CANCEL,\n title: title,\n body: body\n }).done(function(modal) {\n dialogue = modal;\n // Display the dialogue.\n dialogue.show();\n dialogue.getRoot().on(ModalEvents.hidden, {}, function() {\n location.reload();\n });\n });\n },\n\n /**\n * Update the comments count on UI, of second parameter is not set then use the last value.\n *\n * @param {Integer|NULL} current\n * @param {Integer|NULL} total\n */\n updateCommentCount: function(current, total) {\n M.util.js_pending(t.ACTION_UPDATE_COMMENT_COUNT);\n var self = this;\n\n // If total parameter is not set, use the old value.\n if (total === -1) {\n total = self.lastTotal;\n } else {\n self.lastTotal = total;\n }\n\n // If current parameter is not set, use the old value.\n if (current === -1) {\n current = self.lastCurrentCount;\n } else {\n self.lastCurrentCount = current;\n }\n\n // Get the postof local string and display.\n var s = str.get_string('current_of_total', 'studentquiz', {\n current: current,\n total: total\n });\n\n var noCommentSelector = self.elementSelector.find(t.SELECTOR.NO_COMMENT);\n var filter = self.elementSelector.find(t.SELECTOR.COMMENT_FILTER);\n var emptyReplies = self.checkEmptyElement(self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES));\n // Note: Admin will see deleted comments. Make sure replies container is empty.\n if (self.lastCurrentCount === 0 && emptyReplies && self.isNoComment) {\n self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES).hide();\n filter.hide();\n noCommentSelector.show();\n } else {\n self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES).show();\n noCommentSelector.hide();\n filter.show();\n }\n\n $.when(s).done(function(text) {\n self.elementSelector.find(t.SELECTOR.COMMENT_COUNT).text(text);\n M.util.js_complete(t.ACTION_UPDATE_COMMENT_COUNT);\n });\n },\n\n /**\n * Request template then append it into the page.\n *\n * @param {Array} comments\n * @param {Boolean} expanded\n */\n renderComment: function(comments, expanded) {\n var self = this;\n M.util.js_pending(t.ACTION_RENDER_COMMENT);\n comments = self.convertForTemplate(comments, expanded);\n Templates.render(t.TEMPLATE_COMMENTS, {\n comments: comments\n }).done(function(html) {\n // We render a lot of data, pure js here.\n self.containerSelector[0].innerHTML = html;\n // Turn off loading to show raw html first, then we bind events.\n self.loadingIcon.hide();\n // Loop to bind event.\n for (var i = 0; i < comments.length; i++) {\n self.bindCommentEvent(comments[i]);\n }\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_RENDER_COMMENT);\n });\n },\n\n /**\n * Bind event to comment: report, reply, expand, collapse button.\n *\n * @param {Object} data\n */\n bindCommentEvent: function(data) {\n var self = this;\n // Loop comments and replies to get id and bind event for button inside it.\n var el = self.containerSelector.find(t.SELECTOR.COMMENT_ID + data.id);\n var i = 0;\n if (data.root && data.hasOwnProperty('replies')) {\n for (i; i < data.replies.length; i++) {\n var reply = data.replies[i];\n if (!reply.hasOwnProperty('expand')) {\n reply.expand = true;\n }\n if (!reply.hasOwnProperty('root')) {\n reply.root = false;\n }\n self.bindReplyEvent(reply, el);\n }\n }\n el.find(t.SELECTOR.BTN_DELETE).click(function(e) {\n self.bindDeleteEvent(data);\n e.preventDefault();\n });\n el.find(t.SELECTOR.BTN_REPLY).click(function(e) {\n e.preventDefault();\n self.getFragmentFormReplyEvent(data);\n });\n el.find(t.SELECTOR.EXPAND_LINK).click(function(e) {\n e.preventDefault();\n self.bindExpandEvent(data);\n });\n el.find(t.SELECTOR.COLLAPSE_LINK).click(function(e) {\n e.preventDefault();\n self.bindCollapseEvent(data);\n });\n el.find(t.SELECTOR.BTN_REPORT).click(function(e) {\n e.preventDefault();\n window.location = $(this).data('href');\n });\n el.find(t.SELECTOR.BTN_EDIT).click(function(e) {\n e.preventDefault();\n self.getFragmentEditFormEvent(data);\n });\n },\n\n /**\n * Bind event to reply's report and edit button.\n *\n * @param {Object} reply\n * @param {jQuery} el\n */\n bindReplyEvent: function(reply, el) {\n var self = this;\n var replySelector = el.find(t.SELECTOR.COMMENT_ID + reply.id);\n replySelector.find(t.SELECTOR.BTN_DELETE_REPLY).click(function(e) {\n self.bindDeleteEvent(reply);\n e.preventDefault();\n });\n replySelector.find(t.SELECTOR.BTN_REPORT).click(function(e) {\n e.preventDefault();\n window.location = $(this).data('href');\n });\n replySelector.find(t.SELECTOR.BTN_EDIT_REPLY).click(function(e) {\n e.preventDefault();\n self.getFragmentEditFormEvent(reply);\n });\n },\n\n /**\n * This function will disable/hide or enable/show when called depending on the working parameter.\n * Should call this function when we are going to perform the heavy operation like calling web service,\n * get render template, its will disabled button to prevent user from perform another action when page\n * is loading.\n * \"working\" is boolean parameter \"true\" will disable/hide \"false\" will enable/show.\n *\n * @param {Boolean} boolean\n * @param {null|jQuery} elementToHide\n */\n changeWorkingState: function(boolean, elementToHide = null) {\n var visibility = boolean ? 'hidden' : 'visible';\n var self = this;\n self.workingState = boolean;\n self.btnExpandAll.prop('disabled', boolean);\n self.btnCollapseAll.prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_REPLY).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_DELETE).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_DELETE_REPLY).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_REPORT).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.EXPAND_LINK).css('visibility', visibility);\n self.elementSelector.find(t.SELECTOR.COLLAPSE_LINK).css('visibility', visibility);\n self.elementSelector.find(t.SELECTOR.BTN_EDIT).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_EDIT_REPLY).prop('disabled', boolean);\n if (self.deleteDialog) {\n self.deleteDialog.getFooter().find('button[data-action=\"yes\"]').prop('disabled', boolean);\n }\n if (boolean) {\n self.addComment.prop('disabled', boolean);\n if (elementToHide !== null && elementToHide instanceof $) {\n elementToHide.hide();\n }\n } else {\n if (self.lastFocusElement) {\n self.lastFocusElement.focus();\n self.lastFocusElement = null;\n }\n self.elementSelector.find(t.SELECTOR.POST_FOOTER).show();\n }\n },\n\n /**\n * Count comments, deleted comments and replies.\n *\n * @param {*} data\n * @returns {{\n * deleteReplyCount: number,\n * total: number,\n * replyCount: number,\n * totalDelete: number,\n * deleteCommentCount: number,\n * commentCount: number\n * }}\n */\n countCommentAndReplies: function(data) {\n var commentCount = 0;\n var deleteCommentCount = 0;\n var replyCount = 0;\n var deleteReplyCount = 0;\n\n if (data.constructor !== Array) {\n data = [data];\n }\n\n for (var i = 0; i < data.length; i++) {\n var item = data[i];\n if (item.deletedtime == 0) {\n commentCount++;\n } else {\n deleteCommentCount++;\n }\n for (var j = 0; j < item.replies.length; j++) {\n var reply = item.replies[j];\n if (reply.deletedtime == 0) {\n replyCount++;\n } else {\n deleteReplyCount++;\n }\n }\n }\n return {\n total: commentCount + replyCount,\n totalDelete: deleteCommentCount + deleteReplyCount,\n commentCount: commentCount,\n deleteCommentCount: deleteCommentCount,\n replyCount: replyCount,\n deleteReplyCount: deleteReplyCount\n };\n },\n\n /**\n * Call web service to info of comment and its replies.\n *\n * @param {Integer} id\n * @returns {Promise}\n */\n expandComment: function(id) {\n var self = this;\n var params = self.getParamsBeforeCallApi({\n commentid: id,\n type: self.type\n });\n var promise = ajax.call([{\n methodname: t.ACTION_EXPAND,\n args: params\n }]);\n return promise[0];\n },\n\n /**\n * Expand event handler.\n *\n * @param {Object} item\n */\n bindExpandEvent: function(item) {\n var self = this;\n var itemSelector = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n var key = t.ACTION_EXPAND;\n M.util.js_pending(key);\n self.changeWorkingState(true);\n // Clone loading icon selector then append into replies section.\n var loadingIcon = self.loadingIcon.clone().show();\n itemSelector.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).append(loadingIcon);\n $(self).hide();\n // Call expand post web service to get replies.\n self.expandComment(item.id).then(function(response) {\n var convertedItem = self.convertForTemplate(response, true);\n\n // Count current reply displayed, because user can reply to this comment then press expanded.\n var currentDisplayComment = itemSelector.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER_TO_ITEM).length;\n\n // Update count, handle the case when another user add post then current user expand.\n var total = self.countCommentAndReplies(convertedItem).replyCount;\n var newCount = self.lastCurrentCount + total - currentDisplayComment;\n var newTotalCount = self.lastTotal + (convertedItem.numberofreply - item.numberofreply);\n\n if (item.deleted && !convertedItem.deleted) {\n newCount++;\n newTotalCount++;\n }\n\n // Normal comment, then deleted by someone else.\n if (!item.deleted && convertedItem.deleted) {\n newCount--;\n newTotalCount--;\n }\n\n // If current show == total mean that all items is shown.\n if (newCount === newTotalCount) {\n self.btnExpandAll.hide();\n self.btnCollapseAll.show();\n }\n\n self.updateCommentCount(newCount, newTotalCount);\n\n return Templates.render(t.TEMPLATE_COMMENT, convertedItem).done(function(html) {\n var el = $(html);\n itemSelector.replaceWith(el);\n self.lastFocusElement = el.find(t.SELECTOR.COLLAPSE_LINK);\n self.bindCommentEvent(response);\n self.changeWorkingState(false);\n M.util.js_complete(key);\n return true;\n });\n }).fail(function(e) {\n M.util.js_complete(key);\n self.showError(e.message);\n });\n },\n\n /**\n * Collapse event handler.\n *\n * @param {Object} item\n */\n bindCollapseEvent: function(item) {\n var self = this;\n\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n\n // Minus the comment currently show, exclude the deleted comment, update main count.\n // Using DOM to count the reply exclude the deleted, when user delete the reply belong to this comment,\n // current comment object don't know that, so we using DOM in this case.\n var commentCount = el.find(t.SELECTOR.COMMENT_REPLIES_TEXT).length;\n self.updateCommentCount(self.lastCurrentCount - commentCount, -1);\n // Assign back to comment object in case user then collapse the comment.\n item.numberofreply = commentCount;\n\n // Remove reply for this comment.\n el.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).empty();\n\n // Replace comment content with short content.\n if (item.deleted) {\n el.find('.studentquiz-comment-delete-content').html(item.shortcontent);\n } else {\n el.find(t.SELECTOR.COMMENT_TEXT).html(item.shortcontent);\n }\n\n // Hide collapse and show expand icon.\n el.find(t.SELECTOR.COLLAPSE_LINK).hide();\n el.find(t.SELECTOR.EXPAND_LINK).show().focus();\n\n // Update state.\n item.expanded = false;\n },\n\n /**\n * Convert for template render.\n *\n * @param {*} data\n * @param {Boolean} expanded\n * @returns {*}\n */\n convertForTemplate: function(data, expanded) {\n var self = this;\n var single = false;\n if (data.constructor !== Array) {\n data = [data];\n single = true;\n }\n for (var i = 0; i < data.length; i++) {\n var item = data[i];\n item.expanded = expanded;\n item.canviewdeleted = self.canViewDeleted;\n if (!item.hasOwnProperty('replies')) {\n item.replies = [];\n }\n self.setHasComment(item.hascomment);\n item.highlight = item.id === self.highlight;\n if (self.referer && item.reportlink) {\n item.reportlink = self.buildRefererReportLink(item.reportlink, item.id);\n }\n // Only root comment has replies.\n if (item.root) {\n for (var j = 0; j < item.replies.length; j++) {\n var reply = item.replies[j];\n reply.expanded = true;\n reply.canviewdeleted = self.canViewDeleted;\n if (!reply.hasOwnProperty('replies')) {\n reply.replies = [];\n }\n reply.highlight = reply.id === self.highlight;\n if (self.referer && reply.reportlink) {\n reply.reportlink = self.buildRefererReportLink(reply.reportlink, reply.id);\n }\n }\n }\n item.allowselfcommentrating = self.allowSelfCommentRating;\n }\n return single ? data[0] : data;\n },\n\n /**\n * Convert form data to Json require for web service.\n * Note: attempt.php had form already, we cannot have a form inside a form.\n *\n * @param {jQuery} form\n * @returns {Object}\n */\n convertFormToJson: function(form) {\n var data = {};\n form.find(\":input\").each(function() {\n var type = $(this).prop(\"type\");\n var name = $(this).attr('name');\n // Checked radios/checkboxes.\n if ((type === \"checkbox\" || type === \"radio\") && this.checked\n || (type !== \"button\" && type !== \"submit\")) {\n data[name] = $(this).val();\n }\n });\n return data;\n },\n\n /**\n * Call web services to create comment.\n *\n * @param {Object} data\n * @returns {Promise}\n */\n createComment: function(data) {\n var self = this;\n data = self.getParamsBeforeCallApi(data);\n var promise = ajax.call([{\n methodname: t.ACTION_CREATE,\n args: data\n }]);\n return promise[0];\n },\n\n /**\n * Append comment to the DOM, and call another function to bind the event into it.\n *\n * @param {Object} item\n * @param {jQuery} target\n * @param {Boolean} isReply\n */\n appendComment: function(item, target, isReply) {\n var self = this;\n M.util.js_pending(t.ACTION_APPEND_COMMENT);\n Templates.render(t.TEMPLATE_COMMENT, item).done(function(html) {\n var el = $(html);\n target.append(el);\n if (!self.lastCurrentCount) {\n // This is the first reply.\n self.elementSelector.find(t.SELECTOR.COMMENT_FILTER).removeClass(t.SELECTOR.COMMENT_FILTER_HIDE);\n self.updateCommentCount(1, 1);\n self.btnExpandAll.prop('disabled', true);\n self.btnExpandAll.hide();\n self.btnCollapseAll.prop('disabled', false);\n self.btnCollapseAll.show();\n self.expand = true;\n self.isNoComment = false;\n } else {\n self.updateCommentCount(self.lastCurrentCount + 1, self.lastTotal + 1);\n }\n if (isReply) {\n self.bindReplyEvent(item, el.parent());\n } else {\n self.bindCommentEvent(item);\n }\n self.loadingIcon.hide();\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_APPEND_COMMENT);\n });\n },\n\n /*\n * Call web services to get the fragment form, append to the DOM then bind event.\n * */\n loadFragmentForm: function(fragmentForm, item) {\n var self = this;\n M.util.js_pending(t.ACTION_LOAD_FRAGMENT_FORM);\n var params = self.getParamsBeforeCallApi({\n replyto: item.id,\n cancelbutton: true,\n forcecommenting: self.forceCommenting,\n type: self.type\n });\n // Clear error message on the main form to prevent Atto editor from focusing to old message.\n var attoWrap = self.formSelector.find(t.SELECTOR.ATTO_EDITOR_WRAP);\n if (attoWrap.length !== 0 && attoWrap.hasClass('error')) {\n attoWrap.removeClass('error');\n attoWrap.find('#id_error_message_5btext_5d').remove();\n }\n fragment.loadFragment(\n 'mod_studentquiz',\n t.FRAGMENT_FORM_CALLBACK,\n self.contextId,\n params\n ).done(function(html, js) {\n Templates.replaceNodeContents(fragmentForm, html, js);\n // Focus form reply.\n var textFragmentFormId = '#id_editor_question_' + self.studentQuizQuestionId + '_' +\n self.type + '_' + item.id + 'editable';\n fragmentForm.find(textFragmentFormId).focus();\n self.bindFragmentFormEvent(fragmentForm, item);\n M.util.js_complete(t.ACTION_LOAD_FRAGMENT_FORM);\n });\n },\n\n /*\n * Bind fragment form action button event like \"Reply\" or \"Save changes\".\n * */\n bindFragmentFormEvent: function(fragmentForm, item) {\n var self = this;\n var formFragmentSelector = fragmentForm.find(t.SELECTOR.COMMENT_AREA_FORM);\n fragmentForm.find(t.SELECTOR.SUBMIT_BUTTON).click(function(e) {\n e.preventDefault();\n self.changeWorkingState(true);\n var data = self.convertFormToJson(formFragmentSelector);\n // Check message field.\n if (data['message[text]'].length === 0) {\n return true; // Return true to trigger form validation and show error messages.\n }\n var clone = self.loadingIcon.clone().show();\n clone.appendTo(fragmentForm);\n formFragmentSelector.hide();\n self.createReplyComment(fragmentForm, item, formFragmentSelector, data);\n return true;\n });\n self.fragmentFormCancelEvent(formFragmentSelector, false);\n self.bindEditorEvent(fragmentForm);\n },\n\n /*\n * Call web services to create reply, update parent comment count, remove the fragment form.\n * */\n createReplyComment: function(replyContainer, item, formSelector, formData) {\n var self = this;\n var params = {\n replyto: item.id,\n message: {\n text: formData['message[text]'],\n format: formData['message[format]'],\n }\n };\n M.util.js_pending(t.ACTION_CREATE_REPLY);\n self.createComment(params).then(function(response) {\n // Hide error if exists.\n $(t.SELECTOR.COMMENT_ERROR).addClass('hide');\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n var repliesEl = el.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER);\n\n // There are case when user delete the reply then add reply then the numberofreply property is\n // not correct because this comment object does not know the child object is deleted, so we update\n // comment count using DOM.\n item.numberofreply++;\n\n var numReply = parseInt(el.find(t.SELECTOR.COMMENT_COUNT_NUMBER).text()) + 1;\n\n // Update total count.\n el.find(t.SELECTOR.COMMENT_COUNT_NUMBER).text(numReply);\n el.find(t.SELECTOR.COMMENT_COUNT_TEXT).html(\n numReply === 1 ? self.string.reply : self.string.replies\n );\n\n replyContainer.empty();\n var data = self.convertForTemplate(response, true);\n self.appendComment(data, repliesEl, true);\n M.util.js_complete(t.ACTION_CREATE_REPLY);\n return true;\n }).fail(function(e) {\n self.handleFailWhenCreateComment(e, params);\n M.util.js_complete(t.ACTION_CREATE_REPLY);\n });\n },\n\n handleFailWhenCreateComment: function(e, params) {\n var self = this;\n self.showError(e.message);\n // Remove the fragment form container.\n var fragmentFormSelector = t.SELECTOR.COMMENT_ID + params.replyto + ' ' + t.SELECTOR.FRAGMENT_FORM;\n self.elementSelector.find(fragmentFormSelector).empty();\n },\n\n /*\n * Begin to load the fragment form for reply.\n * */\n getFragmentFormReplyEvent: function(item) {\n var self = this;\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n var fragmentForm = el.find(t.SELECTOR.FRAGMENT_FORM).first();\n var postFooter = el.find(t.SELECTOR.POST_FOOTER).first();\n var clone = self.loadingIcon.clone().show();\n fragmentForm.append(clone);\n fragmentForm.removeClass('edit');\n fragmentForm.addClass('reply');\n self.loadFragmentForm(fragmentForm, item);\n self.changeWorkingState(true, postFooter);\n },\n\n /**\n * Bind fragment form cancel button event.\n *\n * @param {jQuery} formSelector\n * @param {Boolean} isEdit\n */\n fragmentFormCancelEvent: function(formSelector, isEdit) {\n var self = this;\n formSelector.find('#id_cancel').click(function(e) {\n e.preventDefault();\n var commentSelector = formSelector.closest(t.SELECTOR.COMMENT_ITEM);\n if (isEdit) {\n self.lastFocusElement = commentSelector.find(t.SELECTOR.BTN_EDIT);\n } else {\n self.lastFocusElement = commentSelector.find(t.SELECTOR.BTN_REPLY);\n }\n self.changeWorkingState(false);\n formSelector.parent().empty();\n });\n },\n\n /**\n * Bind comment delete event.\n *\n * @param {Object} data\n */\n bindDeleteEvent: function(data) {\n var self = this;\n self.deleteTarget = data;\n if (self.deleteDialog) {\n // Use the rendered modal.\n self.deleteDialog.show();\n } else {\n // Disabled button to prevent user from double click on button while loading for template\n // for the first time.\n self.changeWorkingState(true);\n ModalFactory.create({\n type: ModalFactory.types.DEFAULT,\n title: self.string.deletecomment,\n body: self.string.confirmdeletecomment,\n footer: '' +\n ''\n }).done(function(modal) {\n // Save modal for later.\n self.deleteDialog = modal;\n\n // Bind event for cancel button.\n modal.getFooter().find('button[data-action=\"no\"]').click(function(e) {\n e.preventDefault();\n modal.hide();\n });\n\n // Bind event for delete button.\n modal.getFooter().find('button[data-action=\"yes\"]').click(function(e) {\n e.preventDefault();\n M.util.js_pending(t.ACTION_DELETE);\n self.changeWorkingState(true);\n // Call web service to delete post.\n self.deleteComment(self.deleteTarget.id).then(function(response) {\n if (!response.success) {\n self.showError(response.message);\n return true;\n }\n\n var convertedCommentData = self.convertForTemplate(response.data,\n self.deleteTarget.expanded);\n\n // Delete success, begin to call template and render the page again.\n var commentSelector = self.elementSelector.find(t.SELECTOR.COMMENT_ID +\n convertedCommentData.id);\n\n var deletedComments = 1;\n\n // Update global comment count.\n self.updateCommentCount(\n self.lastCurrentCount - deletedComments,\n self.lastTotal - deletedComments\n );\n\n // Reply will always be expanded.\n // Root comment deleted all replies => collapsed.\n if (!convertedCommentData.root) {\n convertedCommentData.expanded = true;\n }\n\n // Call template to render.\n Templates.render(t.TEMPLATE_COMMENT, convertedCommentData).done(function(html) {\n var el = $(html);\n\n // Update the parent comment count if we delete reply before replace.\n if (!convertedCommentData.root) {\n var parentSelector = commentSelector.parent();\n var parentCountSelector = parentSelector.closest(t.SELECTOR.COMMENT_ITEM)\n .find(t.SELECTOR.TOTAL_REPLY);\n var countSelector = parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_NUMBER);\n var newCount = parseInt(countSelector.text()) - 1;\n parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_NUMBER).text(newCount);\n parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_TEXT).html(\n newCount === 1 ? self.string.reply : self.string.replies\n );\n }\n\n // Clone replies and append because the replies will be replaced by template.\n var oldReplies = commentSelector.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER)\n .clone(true);\n commentSelector.replaceWith(el);\n el.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).replaceWith(oldReplies);\n if (self.deleteTarget.root) {\n self.bindCommentEvent(response.data);\n } else {\n self.bindReplyEvent(response.data, el.parent());\n }\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_DELETE);\n });\n modal.hide();\n return true;\n }).fail(function(err) {\n self.showError(err.message);\n return false;\n });\n });\n\n // Focus back to delete button when user hide modal.\n modal.getRoot().on(ModalEvents.hidden, function() {\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + self.deleteTarget.id);\n // Focus on different element base on comment or reply.\n if (self.deleteTarget.root) {\n el.find(t.SELECTOR.BTN_DELETE).first().focus();\n } else {\n el.find(t.SELECTOR.BTN_DELETE_REPLY).first().focus();\n }\n });\n\n // Enable button when modal is shown.\n modal.getRoot().on(ModalEvents.shown, function() {\n self.changeWorkingState(false);\n });\n\n // Display the dialogue.\n modal.show();\n\n self.changeWorkingState(false);\n });\n }\n },\n\n /**\n * Delete comment API.\n *\n * @param {Integer} id\n * @returns {Promise}\n */\n deleteComment: function(id) {\n var self = this;\n var params = self.getParamsBeforeCallApi({\n commentid: id\n });\n var promise = ajax.call([{\n methodname: t.ACTION_DELETE,\n args: params\n }]);\n return promise[0];\n },\n\n /**\n * Bind Atto event.\n *\n * @param {jQuery} formSelector\n */\n bindEditorEvent: function(formSelector) {\n var self = this;\n M.util.js_pending('init_editor');\n\n self.triggerAttoNoContent(formSelector);\n self.setPlaceholder(formSelector, formSelector.attr('data-textarea-placeholder'));\n\n formSelector.find(t.SELECTOR.ATTO.TOOLBAR).fadeIn();\n var textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA);\n var attoEditableId = textareaSelector.attr('id') + 'editable';\n var attoEditable = document.getElementById(attoEditableId);\n var observation = new MutationObserver(function(mutationsList) {\n mutationsList.forEach(function(mutation) {\n if (mutation.type === 'childList' || (mutation.type === 'attributes' &&\n (mutation.attributeName === 'style' || mutation.attributeName === 'hidden'))) {\n self.checkEditorContent(formSelector);\n }\n });\n });\n observation.observe(attoEditable, {attributes: true, childList: true, subtree: true});\n textareaSelector.change(function() {\n self.checkEditorContent(formSelector);\n });\n M.util.js_complete('init_editor');\n\n // Check interval for 5s in case draft content show up.\n var interval = setInterval(function() {\n formSelector.find('textarea[id^=\"id_message\"]').trigger('change');\n }, 350);\n\n setTimeout(function() {\n clearInterval(interval);\n }, 5000);\n },\n\n /**\n * Check if element is empty.\n *\n * @param {jQuery} el - Element.\n * @returns {boolean}\n */\n checkEmptyElement: function(el) {\n return el.children().length === 0;\n },\n\n /**\n * Set user has commented.\n *\n * @param {integer} value\n */\n setHasComment: function(value) {\n var self = this;\n var container = self.elementSelector;\n var hasCommentClass = t.HAS_COMMENT_CLASS;\n if (!self.forceCommenting) {\n self.hasComment = true;\n container.addClass(hasCommentClass);\n } else {\n self.hasComment = value;\n if (self.hasComment) {\n container.addClass(hasCommentClass);\n } else {\n container.removeClass(hasCommentClass);\n }\n }\n },\n\n /**\n * Parse query string.\n *\n * @param {string} query\n * @return {string}\n */\n parseQueryString: function(query) {\n var vars = query.split(\"&\");\n var queryString = {};\n for (var i = 0; i < vars.length; i++) {\n var pair = vars[i].split(\"=\");\n var key = decodeURIComponent(pair[0]);\n var value = decodeURIComponent(pair[1]);\n // If first entry with this name.\n if (typeof queryString[key] === \"undefined\") {\n queryString[key] = decodeURIComponent(value);\n // If second entry with this name.\n } else if (typeof queryString[key] === \"string\") {\n queryString[key] = [queryString[key], decodeURIComponent(value)];\n // If third or later entry with this name.\n } else {\n queryString[key].push(decodeURIComponent(value));\n }\n }\n return queryString;\n },\n\n /**\n * Scroll to element.\n *\n * @param {jQuery} target\n * @param {Integer} speed\n */\n scrollToElement: function(target, speed) {\n if (!target.length) {\n return;\n }\n if (typeof speed === 'undefined') {\n speed = 1000;\n }\n var top = target.offset().top;\n $('html,body').animate({scrollTop: top}, speed);\n },\n\n /**\n * Build referer report link.\n *\n * @param {string} link\n * @param {Integer} id\n * @returns {string}\n */\n buildRefererReportLink: function(link, id) {\n var self = this;\n var referer = decodeURIComponent(self.referer);\n // Add highlight.\n link += '&referer=' + encodeURIComponent(referer + '&highlight=' + id);\n return link;\n },\n\n /**\n * Handle when Atto has content.\n *\n * @param {jQuery} formSelector\n */\n triggerAttoHasContent: function(formSelector) {\n var editorContentWrap = formSelector.find(t.SELECTOR.ATTO.CONTENT_WRAP);\n var submitBtn = formSelector.find(t.SELECTOR.SUBMIT_BUTTON);\n submitBtn.removeClass('disabled');\n submitBtn.prop('disabled', false);\n editorContentWrap.addClass(t.ATTO_CONTENT_TYPE.HAS_CONTENT);\n editorContentWrap.removeClass(t.ATTO_CONTENT_TYPE.NO_CONTENT);\n },\n\n /**\n * Handle when Atto has no content.\n *\n * @param {jQuery} formSelector\n */\n triggerAttoNoContent: function(formSelector) {\n var editorContentWrap = formSelector.find(t.SELECTOR.ATTO.CONTENT_WRAP);\n var submitBtn = formSelector.find(t.SELECTOR.SUBMIT_BUTTON);\n submitBtn.addClass('disabled');\n submitBtn.prop('disabled', true);\n editorContentWrap.addClass(t.ATTO_CONTENT_TYPE.NO_CONTENT);\n editorContentWrap.removeClass(t.ATTO_CONTENT_TYPE.HAS_CONTENT);\n },\n\n /**\n * Set placeholder in the textarea.\n *\n * @param {jQuery} formSelector The form selector.\n * @param {string} placeholder The placeholder of the textarea.\n */\n setPlaceholder: function(formSelector, placeholder) {\n formSelector.find(t.SELECTOR.ATTO.CONTENT_WRAP).attr('data-placeholder', placeholder);\n },\n\n /**\n * Set sort depend on sortable array.\n *\n * @param {string} string\n */\n setSort: function(string) {\n var self = this;\n if ($.inArray(string, self.sortable) !== -1) {\n self.sortFeature = string;\n }\n },\n\n /**\n * Begin to load the fragment form for editing.\n *\n * @param {Object} item\n */\n getFragmentEditFormEvent: function(item) {\n var self = this;\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n var fragmentForm = el.find(t.SELECTOR.FRAGMENT_FORM).first();\n var postFooter = el.find(t.SELECTOR.POST_FOOTER).first();\n var clone = self.loadingIcon.clone().show();\n fragmentForm.append(clone);\n fragmentForm.removeClass('reply');\n fragmentForm.addClass('edit');\n self.loadFragmentEditForm(fragmentForm, item);\n self.changeWorkingState(true, postFooter);\n },\n\n /**\n * Call web services to get the fragment edit form, append to the DOM then bind event.\n *\n * @param {jQuery} fragmentForm\n * @param {Object} item\n */\n loadFragmentEditForm: function(fragmentForm, item) {\n var self = this;\n M.util.js_pending(t.ACTION_LOAD_FRAGMENT_EDIT_FORM);\n var params = self.getParamsBeforeCallApi({\n cancelbutton: true,\n forcecommenting: self.forceCommenting,\n commentid: item.id\n });\n // Clear error message on the main form to prevent Atto editor from focusing to old message.\n var attoWrap = self.formSelector.find(t.SELECTOR.ATTO_EDITOR_WRAP);\n if (attoWrap.length !== 0 && attoWrap.hasClass('error')) {\n attoWrap.removeClass('error');\n attoWrap.find('#id_error_message_5btext_5d').remove();\n }\n fragment.loadFragment(\n 'mod_studentquiz',\n t.FRAGMENT_EDIT_FORM_CALLBACK,\n self.contextId,\n params\n ).done(function(html, js) {\n Templates.replaceNodeContents(fragmentForm, html, js);\n // Focus form.\n var textFragmentFormId = '#id_editor_question_' + self.studentQuizQuestionId +\n '_' + self.type + '_' + item.id + 'editable';\n fragmentForm.find(textFragmentFormId).focus();\n self.bindFragmentEditFormEvent(fragmentForm, item);\n M.util.js_complete(t.ACTION_LOAD_FRAGMENT_EDIT_FORM);\n });\n },\n\n /**\n * Bind fragment edit form action button event.\n *\n * @param {jQuery} fragmentForm\n * @param {Object} item\n */\n bindFragmentEditFormEvent: function(fragmentForm, item) {\n var self = this;\n var formFragmentSelector = fragmentForm.find(t.SELECTOR.COMMENT_AREA_FORM);\n fragmentForm.find(t.SELECTOR.SUBMIT_BUTTON).click(function(e) {\n e.preventDefault();\n self.changeWorkingState(true);\n var data = self.convertFormToJson(formFragmentSelector);\n // Check message field.\n if (data['message[text]'].length === 0) {\n return true; // Return true to trigger form validation and show error messages.\n }\n var clone = self.loadingIcon.clone().show();\n clone.appendTo(fragmentForm);\n formFragmentSelector.hide();\n self.editCommentEvent(fragmentForm, item, formFragmentSelector, data);\n return true;\n });\n self.fragmentFormCancelEvent(formFragmentSelector, true);\n self.bindEditorEvent(fragmentForm);\n },\n\n /**\n * Edit comment event.\n *\n * @param {jQuery} container\n * @param {Object} item\n * @param {jQuery} formSelector\n * @param {Object} formData\n */\n editCommentEvent: function(container, item, formSelector, formData) {\n var self = this;\n M.util.js_pending(t.ACTION_EDIT);\n var params = {\n commentid: item.id,\n message: {\n text: formData['message[text]'],\n format: formData['message[format]'],\n }\n };\n self.editComment(params).then(function(response) {\n // Hide error if exists.\n self.elementSelector.find(t.SELECTOR.COMMENT_ERROR).addClass('hide');\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n self.lastFocusElement = el.find(t.SELECTOR.BTN_EDIT);\n if (self.lastFocusElement.length === 0) {\n self.lastFocusElement = el.find(t.SELECTOR.BTN_EDIT_REPLY);\n }\n // Assign new content.\n item.shortcontent = response.shortcontent;\n response.expanded = item.expanded;\n Templates.render(t.TEMPLATE_COMMENT, response).done(function(html) {\n var el = $(html);\n var commentTextSelector = t.SELECTOR.COMMENT_ID + response.id + ' ' +\n t.SELECTOR.COMMENT_TEXT_CONTAINER;\n self.elementSelector.find(commentTextSelector).first().html(el.find(\n t.SELECTOR.COMMENT_TEXT_CONTAINER).html());\n });\n container.empty();\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_EDIT);\n return true;\n }).fail(function(e) {\n self.handleFailWhenCreateComment(e, params);\n M.util.js_complete(t.ACTION_EDIT);\n });\n },\n\n /**\n * Call web services to edit comment.\n *\n * @param {Object} data\n * @returns {Promise}\n */\n editComment: function(data) {\n var self = this;\n data = self.getParamsBeforeCallApi(data);\n var promise = ajax.call([{\n methodname: t.ACTION_EDIT,\n args: data\n }]);\n return promise[0];\n },\n\n /**\n * Check editor content.\n *\n * @param {jQuery} formSelector\n */\n checkEditorContent: function(formSelector) {\n var key = 'text_change_' + Date.now();\n M.util.js_pending(key);\n var textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA);\n var attoEditableId = textareaSelector.attr('id') + 'editable';\n var attoEditableEle = $('#' + attoEditableId);\n\n // This regex will match if the editor have some special cases.\n // 1)


.\n // 2)


.\n // The cases are consider empty in the editor.\n const regex = /^(<(?:p)[^>]*>)+(
)?(<\\/p>)+$/;\n const match = regex.exec(attoEditableEle.html());\n if (t.EMPTY_CONTENT.indexOf(attoEditableEle.html()) > -1 ||\n attoEditableEle.text().trim().length < 1) {\n // On initial load, attoEditableEle.html() contains

or .\n // If it matches the regex meaning the textarea is empty.\n if (match || (t.EMPTY_CONTENT.indexOf(attoEditableEle.html()) > -1)) {\n this.setPlaceholder(formSelector, formSelector.attr('data-textarea-placeholder'));\n } else {\n this.setPlaceholder(formSelector, '');\n }\n this.triggerAttoNoContent(formSelector);\n } else {\n this.setPlaceholder(formSelector, '');\n this.triggerAttoHasContent(formSelector);\n }\n M.util.js_complete(key);\n }\n };\n },\n generate: function(params) {\n t.get().init(params);\n }\n };\n return t;\n });\n"],"names":["define","$","str","ajax","ModalFactory","Templates","fragment","ModalEvents","t","EMPTY_CONTENT","ROOT_COMMENT_VALUE","GET_ALL_VALUE","TEMPLATE_COMMENTS","TEMPLATE_COMMENT","ACTION_CREATE","ACTION_CREATE_REPLY","ACTION_GET_ALL","ACTION_EXPAND","ACTION_DELETE","ACTION_EDIT","ACTION_LOAD_FRAGMENT_FORM","ACTION_LOAD_FRAGMENT_EDIT_FORM","ACTION_EXPAND_ALL","ACTION_COLLAPSE_ALL","ACTION_RENDER_COMMENT","ACTION_APPEND_COMMENT","ACTION_EDITOR_INIT","ACTION_INIT","ACTION_UPDATE_COMMENT_COUNT","ACTION_CLEAR_FORM","ACTION_SHOW_ERROR","FRAGMENT_FORM_CALLBACK","FRAGMENT_EDIT_FORM_CALLBACK","HAS_COMMENT_CLASS","ATTO_CONTENT_TYPE","HAS_CONTENT","NO_CONTENT","SELECTOR","CONTAINER","EXPAND_ALL","COLLAPSE_ALL","SUBMIT_BUTTON","CONTAINER_REPLIES","COMMENT_REPLIES_CONTAINER","COMMENT_COUNT","COMMENT_TEXT_CONTAINER","COMMENT_TEXT","COMMENT_HISTORY","COMMENT_REPLIES_TEXT","LOADING_ICON","COMMENT_AREA_FORM","FORM_SELECTOR","NO_COMMENT","COLLAPSE_LINK","EXPAND_LINK","COMMENT_ITEM","COMMENT_REPLIES_CONTAINER_TO_ITEM","FRAGMENT_FORM","BTN_DELETE","BTN_REPLY","BTN_DELETE_REPLY","ATTO_EDITOR_WRAP","TEXTAREA","COMMENT_COUNT_NUMBER","COMMENT_COUNT_TEXT","ATTO","CONTENT_WRAP","CONTENT","TOOLBAR","COMMENT_ID","SPAN_COMMENT_ID","TOTAL_REPLY","COMMENT_FILTER","COMMENT_FILTER_HIDE","COMMENT_ERROR","BTN_REPORT","COMMENT_FILTER_ITEM","COMMENT_FILTER_NAME","COMMENT_FILTER_TYPE","BTN_EDIT","BTN_EDIT_REPLY","ATTO_HTML_BUTTON","POST_FOOTER","get","elementSelector","btnExpandAll","btnCollapseAll","addComment","containerSelector","studentQuizQuestionId","dialogue","loadingIcon","lastFocusElement","formSelector","contextId","userId","string","deleteDialog","deleteTarget","numberToShow","cmId","countServerData","lastCurrentCount","lastTotal","expand","forceCommenting","canViewDeleted","hasComment","referer","highlight","sortFeature","sortable","workingState","isNoComment","type","init","params","M","util","js_pending","this","escapeSelector","id","el","find","parseInt","data","count","total","sortfeature","forcecommenting","canviewdeleted","isnocomment","allowSelfCommentRating","allowselfcommentrating","initServerRender","initBindEditor","bindEvents","js_complete","self","changeWorkingState","each","attrs","replies","comment","deleted","numberofreply","expanded","root","bindCommentEvent","commentcount","updateCommentCount","hide","show","query","window","location","search","substring","getParams","parseQueryString","target","length","scrollToElement","isEditorLoaded","interval","setInterval","bindEditorEvent","clearInterval","editorWaiting","checkEditorContent","click","e","preventDefault","empty","getComments","then","response","countCommentAndReplies","renderComment","fail","err","showError","message","innerHTML","commentCount","deletedComments","totalDelete","addClass","rootId","unique","formData","convertFormToJson","attoWrap","hasClass","prepend","required","replyto","text","format","createComment","setTimeout","trigger","is","convertForTemplate","appendComment","handleFailWhenCreateComment","on","asc","sort","desc","nameSelector","iconSelector","orderBy","attr","isCurrent","ascString","descString","not","eachName","eachType","defaultString","removeClass","sortType","setSort","getParamsBeforeCallApi","numbertoshow","call","methodname","args","studentquizquestionid","cmid","when","error","done","showDialog","title","body","html","create","types","CANCEL","modal","getRoot","hidden","reload","current","s","get_string","noCommentSelector","filter","emptyReplies","checkEmptyElement","comments","render","i","hasOwnProperty","reply","bindReplyEvent","bindDeleteEvent","getFragmentFormReplyEvent","bindExpandEvent","bindCollapseEvent","getFragmentEditFormEvent","replySelector","boolean","elementToHide","visibility","prop","css","getFooter","focus","deleteCommentCount","replyCount","deleteReplyCount","constructor","Array","item","deletedtime","j","expandComment","commentid","itemSelector","key","clone","append","convertedItem","currentDisplayComment","newCount","newTotalCount","replaceWith","shortcontent","single","setHasComment","hascomment","reportlink","buildRefererReportLink","form","name","checked","val","isReply","parent","loadFragmentForm","fragmentForm","cancelbutton","remove","loadFragment","js","replaceNodeContents","textFragmentFormId","bindFragmentFormEvent","formFragmentSelector","appendTo","createReplyComment","fragmentFormCancelEvent","replyContainer","repliesEl","numReply","fragmentFormSelector","first","postFooter","isEdit","commentSelector","closest","DEFAULT","deletecomment","confirmdeletecomment","footer","deletetext","cancel","deleteComment","success","convertedCommentData","parentCountSelector","countSelector","oldReplies","shown","triggerAttoNoContent","setPlaceholder","fadeIn","textareaSelector","attoEditableId","attoEditable","document","getElementById","MutationObserver","mutationsList","forEach","mutation","attributeName","observe","attributes","childList","subtree","change","children","value","container","hasCommentClass","vars","split","queryString","pair","decodeURIComponent","push","speed","top","offset","animate","scrollTop","link","encodeURIComponent","triggerAttoHasContent","editorContentWrap","submitBtn","placeholder","inArray","loadFragmentEditForm","bindFragmentEditFormEvent","editCommentEvent","editComment","commentTextSelector","Date","now","attoEditableEle","match","exec","indexOf","trim","generate"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"comment_area.min.js","sources":["../src/comment_area.js"],"sourcesContent":["// This file is part of Moodle - http://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 * Control the element in comment area.\n *\n * @module mod_studentquiz/comment_area\n * @copyright 2020 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * @module mod_studentquiz/comment_element\n */\ndefine(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates', 'core/fragment', 'core/modal_events'],\n function($, str, ajax, ModalFactory, Templates, fragment, ModalEvents) {\n var t = {\n EMPTY_CONTENT: ['


', '


', '
', ''],\n ROOT_COMMENT_VALUE: 0,\n GET_ALL_VALUE: 0,\n TEMPLATE_COMMENTS: 'mod_studentquiz/comments',\n TEMPLATE_COMMENT: 'mod_studentquiz/comment',\n ACTION_CREATE: 'mod_studentquiz_create_comment',\n ACTION_CREATE_REPLY: 'mod_studentquiz_create_reply',\n ACTION_GET_ALL: 'mod_studentquiz_get_comments',\n ACTION_EXPAND: 'mod_studentquiz_expand_comment',\n ACTION_DELETE: 'mod_studentquiz_delete_comment',\n ACTION_EDIT: 'mod_studentquiz_edit_comment',\n ACTION_LOAD_FRAGMENT_FORM: 'mod_studentquiz_load_fragment_form',\n ACTION_LOAD_FRAGMENT_EDIT_FORM: 'mod_studentquiz_load_fragment_edit_form',\n ACTION_EXPAND_ALL: 'action_expand_all',\n ACTION_COLLAPSE_ALL: 'action_collapse_all',\n ACTION_RENDER_COMMENT: 'action_render_comment',\n ACTION_APPEND_COMMENT: 'action_append_comment',\n ACTION_EDITOR_INIT: 'action_editor_init',\n ACTION_INIT: 'action_init',\n ACTION_UPDATE_COMMENT_COUNT: 'action_update_comment_count',\n ACTION_CLEAR_FORM: 'action_clear_form',\n ACTION_SHOW_ERROR: 'action_show_error',\n FRAGMENT_FORM_CALLBACK: 'commentform',\n FRAGMENT_EDIT_FORM_CALLBACK: 'commenteditform',\n HAS_COMMENT_CLASS: 'has-comment',\n ATTO_CONTENT_TYPE: {\n HAS_CONTENT: 'has-content',\n NO_CONTENT: 'no-content'\n },\n SELECTOR: {\n CONTAINER: '.studentquiz-comment-container',\n EXPAND_ALL: '.studentquiz-comment-expand',\n COLLAPSE_ALL: '.studentquiz-comment-collapse',\n SUBMIT_BUTTON: '#id_submitbutton',\n CONTAINER_REPLIES: '.studentquiz-container-replies',\n COMMENT_REPLIES_CONTAINER: '.studentquiz-comment-replies',\n COMMENT_COUNT: '.studentquiz-comment-postcount',\n COMMENT_TEXT_CONTAINER: '.studentquiz-comment-text',\n COMMENT_TEXT: '.studentquiz-comment-text-inside',\n COMMENT_HISTORY: '.studentquiz-comment-history',\n COMMENT_REPLIES_TEXT: '.studentquiz-comment-replies .studentquiz-comment-text .studentquiz-comment-text-inside',\n LOADING_ICON: '.studentquiz-comment-loading',\n COMMENT_AREA_FORM: 'div.comment-area-form',\n FORM_SELECTOR: '.studentquiz-comment-postform > div.comment-area-form',\n NO_COMMENT: '.no-comment',\n COLLAPSE_LINK: '.studentquiz-comment-collapselink',\n EXPAND_LINK: '.studentquiz-comment-expandlink',\n COMMENT_ITEM: '.studentquiz-comment-item',\n COMMENT_REPLIES_CONTAINER_TO_ITEM: '.studentquiz-comment-replies .studentquiz-comment-item',\n FRAGMENT_FORM: '.studentquiz-comment-postfragmentform',\n BTN_DELETE: '.studentquiz-comment-btndelete',\n BTN_REPLY: '.studentquiz-comment-btnreply',\n BTN_DELETE_REPLY: '.studentquiz-comment-btndeletereply',\n ATTO_EDITOR_WRAP: '.editor_atto_wrap',\n TEXTAREA: 'textarea[id^=\"id_editor_question_\"]',\n COMMENT_COUNT_NUMBER: '.studentquiz-comment-count-number',\n COMMENT_COUNT_TEXT: '.studentquiz-comment-count-text',\n ATTO: {\n CONTENT_WRAP: '.editor_atto_content_wrap',\n CONTENT: '.editor_atto_content',\n TOOLBAR: '.editor_atto_toolbar',\n },\n TINYMCE: {\n CONTENT: '.tox-edit-area',\n },\n COMMENT_ID: '#comment_',\n // Is used when server render. We need to collect some stored data attributes to load events.\n SPAN_COMMENT_ID: '#c',\n TOTAL_REPLY: '.studentquiz-comment-totalreply',\n COMMENT_FILTER: '.studentquiz-comment-filter',\n COMMENT_FILTER_HIDE: '.hide-comment-filter',\n COMMENT_ERROR: '.studentquiz-comment-container .comment-error',\n BTN_REPORT: '.studentquiz-comment-btnreport',\n COMMENT_FILTER_ITEM: '.studentquiz-comment-filter-item',\n COMMENT_FILTER_NAME: '.studentquiz-comment-filter-name',\n COMMENT_FILTER_TYPE: '.studentquiz-comment-filter-type',\n BTN_EDIT: '.studentquiz-comment-btnedit',\n BTN_EDIT_REPLY: '.studentquiz-comment-btneditreply',\n ATTO_HTML_BUTTON: 'button.atto_html_button',\n POST_FOOTER: '.studentquiz-comment-postfooter'\n },\n EDITOR: {\n ATTO: {\n TYPE: 'atto',\n },\n TINYMCE: {\n TYPE: 'tiny',\n },\n TEXTAREA: {\n TYPE: 'textarea',\n },\n },\n get: function() {\n return {\n elementSelector: null,\n btnExpandAll: null,\n btnCollapseAll: null,\n addComment: null,\n containerSelector: null,\n studentQuizQuestionId: null,\n dialogue: null,\n loadingIcon: null,\n lastFocusElement: null,\n formSelector: null,\n contextId: null,\n userId: null,\n string: {},\n deleteDialog: null,\n deleteTarget: null,\n numberToShow: 5,\n cmId: null,\n countServerData: [],\n lastCurrentCount: 0,\n lastTotal: 0,\n expand: false,\n forceCommenting: false,\n canViewDeleted: false,\n hasComment: false,\n referer: null,\n highlight: 0,\n sortFeature: null,\n sortable: [],\n workingState: false,\n isNoComment: false,\n type: 0,\n\n /**\n * Init function.\n *\n * @param {Object} params\n */\n init: function(params) {\n M.util.js_pending(t.ACTION_INIT);\n var self = this;\n // Assign attribute.\n self.elementSelector = $('#' + $.escapeSelector(params.id));\n var el = self.elementSelector;\n\n self.btnExpandAll = el.find(t.SELECTOR.EXPAND_ALL);\n self.btnCollapseAll = el.find(t.SELECTOR.COLLAPSE_ALL);\n self.addComment = el.find(t.SELECTOR.SUBMIT_BUTTON);\n self.containerSelector = el.find(t.SELECTOR.CONTAINER_REPLIES);\n self.loadingIcon = el.find(t.SELECTOR.LOADING_ICON);\n self.formSelector = el.find(t.SELECTOR.FORM_SELECTOR);\n self.studentQuizQuestionId = parseInt(el.data('studentquizquestionid'));\n self.contextId = parseInt(el.data('contextid'));\n self.userId = parseInt(el.data('userid'));\n self.numberToShow = parseInt(el.data('numbertoshow'));\n self.cmId = parseInt(el.data('cmid'));\n\n self.countServerData = {\n count: params.count,\n total: params.total\n };\n\n self.expand = params.expand || false;\n self.referer = el.data('referer');\n self.sortFeature = params.sortfeature;\n self.sortable = el.data('sortable');\n self.type = params.type;\n\n // Get all language strings.\n self.string = el.data('strings');\n self.forceCommenting = params.forcecommenting;\n self.canViewDeleted = params.canviewdeleted;\n self.isNoComment = params.isnocomment;\n self.allowSelfCommentRating = params.allowselfcommentrating;\n\n self.initServerRender();\n if (params.allowselfcommentrating) {\n self.initBindEditor();\n }\n self.bindEvents();\n M.util.js_complete(t.ACTION_INIT);\n },\n\n /**\n * Init for server rendering.\n */\n initServerRender: function() {\n var self = this;\n self.changeWorkingState(true);\n self.elementSelector.find(t.SELECTOR.COMMENT_ITEM).each(function() {\n var id = $(this).data('id');\n var attrs = $(this).find(t.SELECTOR.SPAN_COMMENT_ID + id);\n var replies = [];\n if (self.expand) {\n replies = attrs.data('replies') || [];\n }\n var comment = {\n id: $(this).data('id'),\n deleted: attrs.data('deleted'),\n numberofreply: attrs.data('numberofreply'),\n expanded: self.expand,\n replies: replies,\n root: true,\n type: self.type\n };\n self.bindCommentEvent(comment);\n });\n\n // If expanded, current comment count is total comments + replies.\n var commentcount = self.expand ? self.countServerData.total : self.countServerData.count.commentcount;\n self.updateCommentCount(commentcount, self.countServerData.total);\n\n if (self.expand) {\n self.btnExpandAll.hide();\n self.btnCollapseAll.show();\n } else {\n self.btnExpandAll.show();\n self.btnCollapseAll.hide();\n }\n\n // Highlight.\n var query = window.location.search.substring(1);\n var getParams = self.parseQueryString(query);\n self.highlight = parseInt(getParams.highlight) || 0;\n // End set highlight.\n\n // Scroll to.\n if (self.highlight !== 0) {\n var target = $(t.SELECTOR.COMMENT_ID + self.highlight);\n if (target.length) {\n self.scrollToElement(target);\n }\n }\n\n self.changeWorkingState(false);\n },\n\n /**\n * Init comment editor.\n */\n initBindEditor: function() {\n var self = this;\n var isEditorLoaded = false;\n M.util.js_pending(t.ACTION_EDITOR_INIT);\n // Interval to init atto editor, there are time when Atto's Javascript slow to init the editor, so we\n // check interval here to make sure the Atto is init before calling our script.\n var interval = setInterval(function() {\n self.bindEditorEvent(self.formSelector);\n isEditorLoaded = true;\n clearInterval(interval);\n M.util.js_complete(t.ACTION_EDITOR_INIT);\n }, 500);\n\n // If the editor has some content that has been restored\n // then check the editor content.\n var editorWaiting = setInterval(function() {\n if (isEditorLoaded) {\n if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) {\n const textareaSelector = self.formSelector.find(t.SELECTOR.TEXTAREA);\n const tinyEditorId = textareaSelector.attr('id');\n const editor = window.tinyMCE.get(tinyEditorId);\n self.checkEditorContent(self.formSelector, editor.getBody(), t.EDITOR.TINYMCE.TYPE);\n } else {\n self.checkEditorContent(self.formSelector);\n }\n clearInterval(editorWaiting);\n }\n }, 1000);\n },\n\n /**\n * Bind events: \"Expand all comments\", \"Collapse all comments\", \"Add Reply\".\n */\n bindEvents: function() {\n var self = this;\n // Bind event to \"Expand all comments\" button.\n self.btnExpandAll.click(function(e) {\n e.preventDefault();\n M.util.js_pending(t.ACTION_EXPAND_ALL);\n self.changeWorkingState(true);\n // Empty the replies section to append new response.\n self.containerSelector.empty();\n // Change button from expand to collapse collapse and disabled button since we don't want user to\n // press the button when javascript is appending item or ajax is working.\n self.btnExpandAll.hide();\n self.btnCollapseAll.show();\n self.loadingIcon.show();\n self.getComments(t.GET_ALL_VALUE).then(function(response) {\n // Calculate length to display count.\n var count = self.countCommentAndReplies(response.data);\n var total = count.total;\n self.updateCommentCount(total, response.total);\n self.renderComment(response.data, true);\n M.util.js_complete(t.ACTION_EXPAND_ALL);\n return true;\n }).fail(function(err) {\n M.util.js_complete(t.ACTION_EXPAND_ALL);\n self.showError(err.message);\n return false;\n });\n });\n\n // Bind event to \"Collapse all comments\" button.\n self.btnCollapseAll.click(function(e) {\n e.preventDefault();\n M.util.js_pending(t.ACTION_COLLAPSE_ALL);\n self.changeWorkingState(true);\n self.loadingIcon.show();\n self.btnCollapseAll.hide();\n self.btnExpandAll.show();\n self.containerSelector[0].innerHTML = '';\n self.getComments(self.numberToShow).then(function(response) {\n // Calculate length to display the post count.\n var count = self.countCommentAndReplies(response.data);\n var commentCount = count.commentCount;\n var deletedComments = count.totalDelete;\n // Only show expand button and count if comment existed.\n if (commentCount !== 0 || deletedComments !== 0) {\n self.btnExpandAll.show();\n self.updateCommentCount(commentCount, response.total);\n self.renderComment(response.data, false);\n } else {\n // No comment found hide loading icon.\n self.loadingIcon.hide();\n self.changeWorkingState(false);\n self.updateCommentCount(0, 0);\n }\n M.util.js_complete(t.ACTION_COLLAPSE_ALL);\n return true;\n }).fail(function(err) {\n M.util.js_complete(t.ACTION_COLLAPSE_ALL);\n self.showError(err.message);\n return false;\n });\n });\n\n // Bind event to \"Add Reply\" button (Root comment).\n self.addComment.click(function(e) {\n e.preventDefault();\n M.util.js_pending(t.ACTION_CREATE);\n self.changeWorkingState(true);\n self.loadingIcon.show();\n // Hide error if exists.\n $(t.SELECTOR.COMMENT_ERROR).addClass('hide');\n // Hide no comment.\n $(t.SELECTOR.NO_COMMENT).hide();\n var rootId = t.ROOT_COMMENT_VALUE;\n var unique = self.studentQuizQuestionId + '_' + self.type + '_' + rootId;\n var formSelector = self.formSelector;\n var formData = self.convertFormToJson(formSelector);\n // Check message field.\n if (formData['message[text]'].length === 0) {\n // Show message, atto won't auto show after second form is appended.\n var attoWrap = formSelector.find(t.SELECTOR.ATTO_EDITOR_WRAP);\n if (attoWrap.length !== 0 && !attoWrap.hasClass('error')) {\n attoWrap.addClass('error');\n attoWrap.prepend('' + self.string.required + '');\n }\n M.util.js_complete(t.ACTION_CREATE);\n return false;\n }\n var params = {\n replyto: rootId,\n message: {\n text: formData['message[text]'],\n format: formData['message[format]'],\n },\n };\n self.createComment(params).then(function(response) {\n M.util.js_pending(t.ACTION_CLEAR_FORM);\n // Clear form in setTimeout to prevent require message still shown when reset on Firefox.\n setTimeout(function() {\n // Clear form data.\n if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) {\n self.resetContent(formSelector, t.EDITOR.TINYMCE.TYPE);\n } else if (self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length !== 0) {\n self.formSelector.trigger('reset');\n // Clear atto editor data.\n if (!formSelector.find('#id_editor_question_' + unique + 'editable').is(':visible')) {\n // HTML mode. Switch back to normal mode.\n self.elementSelector.find(t.SELECTOR.ATTO_HTML_BUTTON).trigger('click');\n }\n formSelector.find('#id_editor_question_' + unique + 'editable').empty();\n formSelector.find(t.SELECTOR.TEXTAREA).trigger('change');\n } else {\n self.resetContent(formSelector, t.EDITOR.TEXTAREA.TYPE);\n }\n M.util.js_complete(t.ACTION_CLEAR_FORM);\n });\n var data = self.convertForTemplate(response, true);\n // Disable reply button since content is now empty.\n formSelector.find(t.SELECTOR.SUBMIT_BUTTON).addClass('disabled');\n self.appendComment(data, self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES), false);\n M.util.js_complete(t.ACTION_CREATE);\n return true;\n }).fail(function(e) {\n self.handleFailWhenCreateComment(e, params);\n M.util.js_complete(t.ACTION_CREATE);\n });\n return true;\n });\n\n // Bind events filter sort.\n self.elementSelector.find(t.SELECTOR.COMMENT_FILTER_ITEM).on('click', function(e) {\n e.preventDefault();\n // Check if current state is working, return.\n if (self.workingState) {\n return;\n }\n\n var asc = self.string.sort.asc;\n var desc = self.string.sort.desc;\n\n var nameSelector = $(this).find(t.SELECTOR.COMMENT_FILTER_NAME);\n var iconSelector = $(this).find(t.SELECTOR.COMMENT_FILTER_TYPE);\n\n // Get sort type from data-type.\n var type = $(this).data('type');\n var orderBy = $(this).attr('data-order');\n var isCurrent = $(this).hasClass('current');\n var ascString = $(this).attr('data-asc-string');\n var descString = $(this).attr('data-desc-string');\n\n // Get current orderBy from data-order. If not current sort, don't change.\n // Then reverse it to opposite orderBy and call to API.\n // Example: current is desc, then we should call order by = asc to api.\n\n orderBy = orderBy === 'desc' ? 'asc' : 'desc';\n // Ok we attach that orderBy to current order by.\n $(this).attr('data-order', orderBy);\n\n if (!isCurrent) {\n $(this).addClass('current');\n }\n\n if (orderBy === 'desc') {\n nameSelector.attr('title', ascString);\n nameSelector.attr('alt', ascString);\n iconSelector.attr('title', desc);\n iconSelector.attr('alt', desc);\n } else {\n nameSelector.attr('title', descString);\n nameSelector.attr('alt', descString);\n iconSelector.attr('title', asc);\n iconSelector.attr('alt', asc);\n }\n\n // Note: new text is the opposite of current sort type (old type).\n\n // Reset all filter elements to its default.\n self.elementSelector.find(t.SELECTOR.COMMENT_FILTER_ITEM).not(this).each(function() {\n var each = $(this);\n var eachName = $(this).find(t.SELECTOR.COMMENT_FILTER_NAME);\n var eachType = $(this).find(t.SELECTOR.COMMENT_FILTER_TYPE);\n var defaultString = $(this).attr('data-asc-string');\n each.attr('data-order', 'desc');\n each.removeClass('filter-asc');\n each.removeClass('filter-desc');\n each.removeClass('current');\n eachName.attr('title', defaultString);\n eachName.attr('alt', defaultString);\n eachType.attr('title', asc);\n eachType.attr('alt', asc);\n });\n\n if (orderBy === 'desc') {\n $(this).removeClass('filter-asc');\n $(this).addClass('filter-desc');\n } else {\n $(this).removeClass('filter-desc');\n $(this).addClass('filter-asc');\n }\n\n // Build to sort type. Example: date_asc, date_desc.\n var sortType = type + '_' + orderBy;\n self.setSort(sortType);\n\n if (self.expand) {\n self.btnExpandAll.trigger('click');\n } else {\n self.btnCollapseAll.trigger('click');\n }\n });\n },\n\n /**\n * Get comments, numbertoshow = 0 will get all comment + replies.\n *\n * @param {Integer} numberToShow\n * @returns {Promise}\n */\n getComments: function(numberToShow) {\n var self = this;\n var params = self.getParamsBeforeCallApi({\n numbertoshow: numberToShow,\n sort: self.sortFeature,\n type: self.type\n });\n var promise = ajax.call([{\n methodname: t.ACTION_GET_ALL,\n args: params\n }]);\n return promise[0];\n },\n\n /**\n * Always map studentquizquestionid and cmId to request before send.\n *\n * @param {Object} params\n * @returns {Object}\n */\n getParamsBeforeCallApi: function(params) {\n var self = this;\n params.studentquizquestionid = self.studentQuizQuestionId;\n params.cmid = self.cmId;\n params.type = self.type;\n return params;\n },\n\n /**\n * Show error which call showDialog().\n *\n * @param {String} message\n */\n showError: function(message) {\n var self = this;\n M.util.js_pending(t.ACTION_SHOW_ERROR);\n // Get error string for title.\n $.when(self.string.error).done(function(string) {\n self.showDialog(string, message);\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_SHOW_ERROR);\n });\n },\n\n /**\n * Show the dialog with custom title and body.\n *\n * @param {String} title\n * @param {String} body\n */\n showDialog: function(title, body) {\n var self = this;\n var dialogue = self.dialogue;\n if (dialogue) {\n // This dialog is existed, only change title and body and then display.\n dialogue.title.html(title);\n dialogue.body.html(body);\n dialogue.show();\n return;\n }\n ModalFactory.create({\n type: ModalFactory.types.CANCEL,\n title: title,\n body: body\n }).done(function(modal) {\n dialogue = modal;\n // Display the dialogue.\n dialogue.show();\n dialogue.getRoot().on(ModalEvents.hidden, {}, function() {\n location.reload();\n });\n });\n },\n\n /**\n * Update the comments count on UI, of second parameter is not set then use the last value.\n *\n * @param {Integer|NULL} current\n * @param {Integer|NULL} total\n */\n updateCommentCount: function(current, total) {\n M.util.js_pending(t.ACTION_UPDATE_COMMENT_COUNT);\n var self = this;\n\n // If total parameter is not set, use the old value.\n if (total === -1) {\n total = self.lastTotal;\n } else {\n self.lastTotal = total;\n }\n\n // If current parameter is not set, use the old value.\n if (current === -1) {\n current = self.lastCurrentCount;\n } else {\n self.lastCurrentCount = current;\n }\n\n // Get the postof local string and display.\n var s = str.get_string('current_of_total', 'studentquiz', {\n current: current,\n total: total\n });\n\n var noCommentSelector = self.elementSelector.find(t.SELECTOR.NO_COMMENT);\n var filter = self.elementSelector.find(t.SELECTOR.COMMENT_FILTER);\n var emptyReplies = self.checkEmptyElement(self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES));\n // Note: Admin will see deleted comments. Make sure replies container is empty.\n if (self.lastCurrentCount === 0 && emptyReplies && self.isNoComment) {\n self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES).hide();\n filter.hide();\n noCommentSelector.show();\n } else {\n self.elementSelector.find(t.SELECTOR.CONTAINER_REPLIES).show();\n noCommentSelector.hide();\n filter.show();\n }\n\n $.when(s).done(function(text) {\n self.elementSelector.find(t.SELECTOR.COMMENT_COUNT).text(text);\n M.util.js_complete(t.ACTION_UPDATE_COMMENT_COUNT);\n });\n },\n\n /**\n * Request template then append it into the page.\n *\n * @param {Array} comments\n * @param {Boolean} expanded\n */\n renderComment: function(comments, expanded) {\n var self = this;\n M.util.js_pending(t.ACTION_RENDER_COMMENT);\n comments = self.convertForTemplate(comments, expanded);\n Templates.render(t.TEMPLATE_COMMENTS, {\n comments: comments\n }).done(function(html) {\n // We render a lot of data, pure js here.\n self.containerSelector[0].innerHTML = html;\n // Turn off loading to show raw html first, then we bind events.\n self.loadingIcon.hide();\n // Loop to bind event.\n for (var i = 0; i < comments.length; i++) {\n self.bindCommentEvent(comments[i]);\n }\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_RENDER_COMMENT);\n });\n },\n\n /**\n * Bind event to comment: report, reply, expand, collapse button.\n *\n * @param {Object} data\n */\n bindCommentEvent: function(data) {\n var self = this;\n // Loop comments and replies to get id and bind event for button inside it.\n var el = self.containerSelector.find(t.SELECTOR.COMMENT_ID + data.id);\n var i = 0;\n if (data.root && data.hasOwnProperty('replies')) {\n for (i; i < data.replies.length; i++) {\n var reply = data.replies[i];\n if (!reply.hasOwnProperty('expand')) {\n reply.expand = true;\n }\n if (!reply.hasOwnProperty('root')) {\n reply.root = false;\n }\n self.bindReplyEvent(reply, el);\n }\n }\n el.find(t.SELECTOR.BTN_DELETE).click(function(e) {\n self.bindDeleteEvent(data);\n e.preventDefault();\n });\n el.find(t.SELECTOR.BTN_REPLY).click(function(e) {\n e.preventDefault();\n self.getFragmentFormReplyEvent(data);\n });\n el.find(t.SELECTOR.EXPAND_LINK).click(function(e) {\n e.preventDefault();\n self.bindExpandEvent(data);\n });\n el.find(t.SELECTOR.COLLAPSE_LINK).click(function(e) {\n e.preventDefault();\n self.bindCollapseEvent(data);\n });\n el.find(t.SELECTOR.BTN_REPORT).click(function(e) {\n e.preventDefault();\n window.location = $(this).data('href');\n });\n el.find(t.SELECTOR.BTN_EDIT).click(function(e) {\n e.preventDefault();\n self.getFragmentEditFormEvent(data);\n });\n },\n\n /**\n * Bind event to reply's report and edit button.\n *\n * @param {Object} reply\n * @param {jQuery} el\n */\n bindReplyEvent: function(reply, el) {\n var self = this;\n var replySelector = el.find(t.SELECTOR.COMMENT_ID + reply.id);\n replySelector.find(t.SELECTOR.BTN_DELETE_REPLY).click(function(e) {\n self.bindDeleteEvent(reply);\n e.preventDefault();\n });\n replySelector.find(t.SELECTOR.BTN_REPORT).click(function(e) {\n e.preventDefault();\n window.location = $(this).data('href');\n });\n replySelector.find(t.SELECTOR.BTN_EDIT_REPLY).click(function(e) {\n e.preventDefault();\n self.getFragmentEditFormEvent(reply);\n });\n },\n\n /**\n * This function will disable/hide or enable/show when called depending on the working parameter.\n * Should call this function when we are going to perform the heavy operation like calling web service,\n * get render template, its will disabled button to prevent user from perform another action when page\n * is loading.\n * \"working\" is boolean parameter \"true\" will disable/hide \"false\" will enable/show.\n *\n * @param {Boolean} boolean\n * @param {null|jQuery} elementToHide\n */\n changeWorkingState: function(boolean, elementToHide = null) {\n var visibility = boolean ? 'hidden' : 'visible';\n var self = this;\n self.workingState = boolean;\n self.btnExpandAll.prop('disabled', boolean);\n self.btnCollapseAll.prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_REPLY).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_DELETE).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_DELETE_REPLY).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_REPORT).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.EXPAND_LINK).css('visibility', visibility);\n self.elementSelector.find(t.SELECTOR.COLLAPSE_LINK).css('visibility', visibility);\n self.elementSelector.find(t.SELECTOR.BTN_EDIT).prop('disabled', boolean);\n self.elementSelector.find(t.SELECTOR.BTN_EDIT_REPLY).prop('disabled', boolean);\n if (self.deleteDialog) {\n self.deleteDialog.getFooter().find('button[data-action=\"yes\"]').prop('disabled', boolean);\n }\n if (boolean) {\n self.addComment.prop('disabled', boolean);\n if (elementToHide !== null && elementToHide instanceof $) {\n elementToHide.hide();\n }\n } else {\n if (self.lastFocusElement) {\n self.lastFocusElement.focus();\n self.lastFocusElement = null;\n }\n self.elementSelector.find(t.SELECTOR.POST_FOOTER).show();\n }\n },\n\n /**\n * Count comments, deleted comments and replies.\n *\n * @param {*} data\n * @returns {{\n * deleteReplyCount: number,\n * total: number,\n * replyCount: number,\n * totalDelete: number,\n * deleteCommentCount: number,\n * commentCount: number\n * }}\n */\n countCommentAndReplies: function(data) {\n var commentCount = 0;\n var deleteCommentCount = 0;\n var replyCount = 0;\n var deleteReplyCount = 0;\n\n if (data.constructor !== Array) {\n data = [data];\n }\n\n for (var i = 0; i < data.length; i++) {\n var item = data[i];\n if (item.deletedtime == 0) {\n commentCount++;\n } else {\n deleteCommentCount++;\n }\n for (var j = 0; j < item.replies.length; j++) {\n var reply = item.replies[j];\n if (reply.deletedtime == 0) {\n replyCount++;\n } else {\n deleteReplyCount++;\n }\n }\n }\n return {\n total: commentCount + replyCount,\n totalDelete: deleteCommentCount + deleteReplyCount,\n commentCount: commentCount,\n deleteCommentCount: deleteCommentCount,\n replyCount: replyCount,\n deleteReplyCount: deleteReplyCount\n };\n },\n\n /**\n * Call web service to info of comment and its replies.\n *\n * @param {Integer} id\n * @returns {Promise}\n */\n expandComment: function(id) {\n var self = this;\n var params = self.getParamsBeforeCallApi({\n commentid: id,\n type: self.type\n });\n var promise = ajax.call([{\n methodname: t.ACTION_EXPAND,\n args: params\n }]);\n return promise[0];\n },\n\n /**\n * Expand event handler.\n *\n * @param {Object} item\n */\n bindExpandEvent: function(item) {\n var self = this;\n var itemSelector = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n var key = t.ACTION_EXPAND;\n M.util.js_pending(key);\n self.changeWorkingState(true);\n // Clone loading icon selector then append into replies section.\n var loadingIcon = self.loadingIcon.clone().show();\n itemSelector.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).append(loadingIcon);\n $(self).hide();\n // Call expand post web service to get replies.\n self.expandComment(item.id).then(function(response) {\n var convertedItem = self.convertForTemplate(response, true);\n\n // Count current reply displayed, because user can reply to this comment then press expanded.\n var currentDisplayComment = itemSelector.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER_TO_ITEM).length;\n\n // Update count, handle the case when another user add post then current user expand.\n var total = self.countCommentAndReplies(convertedItem).replyCount;\n var newCount = self.lastCurrentCount + total - currentDisplayComment;\n var newTotalCount = self.lastTotal + (convertedItem.numberofreply - item.numberofreply);\n\n if (item.deleted && !convertedItem.deleted) {\n newCount++;\n newTotalCount++;\n }\n\n // Normal comment, then deleted by someone else.\n if (!item.deleted && convertedItem.deleted) {\n newCount--;\n newTotalCount--;\n }\n\n // If current show == total mean that all items is shown.\n if (newCount === newTotalCount) {\n self.btnExpandAll.hide();\n self.btnCollapseAll.show();\n }\n\n self.updateCommentCount(newCount, newTotalCount);\n\n return Templates.render(t.TEMPLATE_COMMENT, convertedItem).done(function(html) {\n var el = $(html);\n itemSelector.replaceWith(el);\n self.lastFocusElement = el.find(t.SELECTOR.COLLAPSE_LINK);\n self.bindCommentEvent(response);\n self.changeWorkingState(false);\n M.util.js_complete(key);\n return true;\n });\n }).fail(function(e) {\n M.util.js_complete(key);\n self.showError(e.message);\n });\n },\n\n /**\n * Collapse event handler.\n *\n * @param {Object} item\n */\n bindCollapseEvent: function(item) {\n var self = this;\n\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n\n // Minus the comment currently show, exclude the deleted comment, update main count.\n // Using DOM to count the reply exclude the deleted, when user delete the reply belong to this comment,\n // current comment object don't know that, so we using DOM in this case.\n var commentCount = el.find(t.SELECTOR.COMMENT_REPLIES_TEXT).length;\n self.updateCommentCount(self.lastCurrentCount - commentCount, -1);\n // Assign back to comment object in case user then collapse the comment.\n item.numberofreply = commentCount;\n\n // Remove reply for this comment.\n el.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).empty();\n\n // Replace comment content with short content.\n if (item.deleted) {\n el.find('.studentquiz-comment-delete-content').html(item.shortcontent);\n } else {\n el.find(t.SELECTOR.COMMENT_TEXT).html(item.shortcontent);\n }\n\n // Hide collapse and show expand icon.\n el.find(t.SELECTOR.COLLAPSE_LINK).hide();\n el.find(t.SELECTOR.EXPAND_LINK).show().focus();\n\n // Update state.\n item.expanded = false;\n },\n\n /**\n * Convert for template render.\n *\n * @param {*} data\n * @param {Boolean} expanded\n * @returns {*}\n */\n convertForTemplate: function(data, expanded) {\n var self = this;\n var single = false;\n if (data.constructor !== Array) {\n data = [data];\n single = true;\n }\n for (var i = 0; i < data.length; i++) {\n var item = data[i];\n item.expanded = expanded;\n item.canviewdeleted = self.canViewDeleted;\n if (!item.hasOwnProperty('replies')) {\n item.replies = [];\n }\n self.setHasComment(item.hascomment);\n item.highlight = item.id === self.highlight;\n if (self.referer && item.reportlink) {\n item.reportlink = self.buildRefererReportLink(item.reportlink, item.id);\n }\n // Only root comment has replies.\n if (item.root) {\n for (var j = 0; j < item.replies.length; j++) {\n var reply = item.replies[j];\n reply.expanded = true;\n reply.canviewdeleted = self.canViewDeleted;\n if (!reply.hasOwnProperty('replies')) {\n reply.replies = [];\n }\n reply.highlight = reply.id === self.highlight;\n if (self.referer && reply.reportlink) {\n reply.reportlink = self.buildRefererReportLink(reply.reportlink, reply.id);\n }\n }\n }\n item.allowselfcommentrating = self.allowSelfCommentRating;\n }\n return single ? data[0] : data;\n },\n\n /**\n * Convert form data to Json require for web service.\n * Note: attempt.php had form already, we cannot have a form inside a form.\n *\n * @param {jQuery} form\n * @returns {Object}\n */\n convertFormToJson: function(form) {\n var data = {};\n form.find(\":input\").each(function() {\n var type = $(this).prop(\"type\");\n var name = $(this).attr('name');\n // Checked radios/checkboxes.\n if ((type === \"checkbox\" || type === \"radio\") && this.checked\n || (type !== \"button\" && type !== \"submit\")) {\n data[name] = $(this).val();\n }\n });\n return data;\n },\n\n /**\n * Call web services to create comment.\n *\n * @param {Object} data\n * @returns {Promise}\n */\n createComment: function(data) {\n var self = this;\n data = self.getParamsBeforeCallApi(data);\n var promise = ajax.call([{\n methodname: t.ACTION_CREATE,\n args: data\n }]);\n return promise[0];\n },\n\n /**\n * Append comment to the DOM, and call another function to bind the event into it.\n *\n * @param {Object} item\n * @param {jQuery} target\n * @param {Boolean} isReply\n */\n appendComment: function(item, target, isReply) {\n var self = this;\n M.util.js_pending(t.ACTION_APPEND_COMMENT);\n Templates.render(t.TEMPLATE_COMMENT, item).done(function(html) {\n var el = $(html);\n target.append(el);\n if (!self.lastCurrentCount) {\n // This is the first reply.\n self.elementSelector.find(t.SELECTOR.COMMENT_FILTER).removeClass(t.SELECTOR.COMMENT_FILTER_HIDE);\n self.updateCommentCount(1, 1);\n self.btnExpandAll.prop('disabled', true);\n self.btnExpandAll.hide();\n self.btnCollapseAll.prop('disabled', false);\n self.btnCollapseAll.show();\n self.expand = true;\n self.isNoComment = false;\n } else {\n self.updateCommentCount(self.lastCurrentCount + 1, self.lastTotal + 1);\n }\n if (isReply) {\n self.bindReplyEvent(item, el.parent());\n } else {\n self.bindCommentEvent(item);\n }\n self.loadingIcon.hide();\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_APPEND_COMMENT);\n });\n },\n\n /*\n * Call web services to get the fragment form, append to the DOM then bind event.\n * */\n loadFragmentForm: function(fragmentForm, item) {\n var self = this;\n M.util.js_pending(t.ACTION_LOAD_FRAGMENT_FORM);\n var params = self.getParamsBeforeCallApi({\n replyto: item.id,\n cancelbutton: true,\n forcecommenting: self.forceCommenting,\n type: self.type\n });\n // Clear error message on the main form to prevent Atto editor from focusing to old message.\n var attoWrap = self.formSelector.find(t.SELECTOR.ATTO_EDITOR_WRAP);\n if (attoWrap.length !== 0 && attoWrap.hasClass('error')) {\n attoWrap.removeClass('error');\n attoWrap.find('#id_error_message_5btext_5d').remove();\n }\n fragment.loadFragment(\n 'mod_studentquiz',\n t.FRAGMENT_FORM_CALLBACK,\n self.contextId,\n params\n ).done(function(html, js) {\n Templates.replaceNodeContents(fragmentForm, html, js);\n // Focus form reply.\n var textFragmentFormId = '#id_editor_question_' + self.studentQuizQuestionId + '_' +\n self.type + '_' + item.id + 'editable';\n fragmentForm.find(textFragmentFormId).focus();\n self.bindFragmentFormEvent(fragmentForm, item);\n M.util.js_complete(t.ACTION_LOAD_FRAGMENT_FORM);\n });\n },\n\n /*\n * Bind fragment form action button event like \"Reply\" or \"Save changes\".\n * */\n bindFragmentFormEvent: function(fragmentForm, item) {\n var self = this;\n var formFragmentSelector = fragmentForm.find(t.SELECTOR.COMMENT_AREA_FORM);\n fragmentForm.find(t.SELECTOR.SUBMIT_BUTTON).click(function(e) {\n e.preventDefault();\n self.changeWorkingState(true);\n var data = self.convertFormToJson(formFragmentSelector);\n // Check message field.\n if (data['message[text]'].length === 0) {\n return true; // Return true to trigger form validation and show error messages.\n }\n var clone = self.loadingIcon.clone().show();\n clone.appendTo(fragmentForm);\n formFragmentSelector.hide();\n self.createReplyComment(fragmentForm, item, formFragmentSelector, data);\n return true;\n });\n self.fragmentFormCancelEvent(formFragmentSelector, false);\n self.bindEditorEvent(fragmentForm);\n },\n\n /*\n * Call web services to create reply, update parent comment count, remove the fragment form.\n * */\n createReplyComment: function(replyContainer, item, formSelector, formData) {\n var self = this;\n var params = {\n replyto: item.id,\n message: {\n text: formData['message[text]'],\n format: formData['message[format]'],\n }\n };\n M.util.js_pending(t.ACTION_CREATE_REPLY);\n self.createComment(params).then(function(response) {\n // Hide error if exists.\n $(t.SELECTOR.COMMENT_ERROR).addClass('hide');\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n var repliesEl = el.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER);\n\n // There are case when user delete the reply then add reply then the numberofreply property is\n // not correct because this comment object does not know the child object is deleted, so we update\n // comment count using DOM.\n item.numberofreply++;\n\n var numReply = parseInt(el.find(t.SELECTOR.COMMENT_COUNT_NUMBER).text()) + 1;\n\n // Update total count.\n el.find(t.SELECTOR.COMMENT_COUNT_NUMBER).text(numReply);\n el.find(t.SELECTOR.COMMENT_COUNT_TEXT).html(\n numReply === 1 ? self.string.reply : self.string.replies\n );\n\n replyContainer.empty();\n var data = self.convertForTemplate(response, true);\n self.appendComment(data, repliesEl, true);\n M.util.js_complete(t.ACTION_CREATE_REPLY);\n return true;\n }).fail(function(e) {\n self.handleFailWhenCreateComment(e, params);\n M.util.js_complete(t.ACTION_CREATE_REPLY);\n });\n },\n\n handleFailWhenCreateComment: function(e, params) {\n var self = this;\n self.showError(e.message);\n // Remove the fragment form container.\n var fragmentFormSelector = t.SELECTOR.COMMENT_ID + params.replyto + ' ' + t.SELECTOR.FRAGMENT_FORM;\n self.elementSelector.find(fragmentFormSelector).empty();\n },\n\n /*\n * Begin to load the fragment form for reply.\n * */\n getFragmentFormReplyEvent: function(item) {\n var self = this;\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n var fragmentForm = el.find(t.SELECTOR.FRAGMENT_FORM).first();\n var postFooter = el.find(t.SELECTOR.POST_FOOTER).first();\n var clone = self.loadingIcon.clone().show();\n fragmentForm.append(clone);\n fragmentForm.removeClass('edit');\n fragmentForm.addClass('reply');\n self.loadFragmentForm(fragmentForm, item);\n self.changeWorkingState(true, postFooter);\n },\n\n /**\n * Bind fragment form cancel button event.\n *\n * @param {jQuery} formSelector\n * @param {Boolean} isEdit\n */\n fragmentFormCancelEvent: function(formSelector, isEdit) {\n var self = this;\n formSelector.find('#id_cancel').click(function(e) {\n e.preventDefault();\n var commentSelector = formSelector.closest(t.SELECTOR.COMMENT_ITEM);\n if (isEdit) {\n self.lastFocusElement = commentSelector.find(t.SELECTOR.BTN_EDIT);\n } else {\n self.lastFocusElement = commentSelector.find(t.SELECTOR.BTN_REPLY);\n }\n self.changeWorkingState(false);\n formSelector.parent().empty();\n });\n },\n\n /**\n * Bind comment delete event.\n *\n * @param {Object} data\n */\n bindDeleteEvent: function(data) {\n var self = this;\n self.deleteTarget = data;\n if (self.deleteDialog) {\n // Use the rendered modal.\n self.deleteDialog.show();\n } else {\n // Disabled button to prevent user from double click on button while loading for template\n // for the first time.\n self.changeWorkingState(true);\n ModalFactory.create({\n type: ModalFactory.types.DEFAULT,\n title: self.string.deletecomment,\n body: self.string.confirmdeletecomment,\n footer: '' +\n ''\n }).done(function(modal) {\n // Save modal for later.\n self.deleteDialog = modal;\n\n // Bind event for cancel button.\n modal.getFooter().find('button[data-action=\"no\"]').click(function(e) {\n e.preventDefault();\n modal.hide();\n });\n\n // Bind event for delete button.\n modal.getFooter().find('button[data-action=\"yes\"]').click(function(e) {\n e.preventDefault();\n M.util.js_pending(t.ACTION_DELETE);\n self.changeWorkingState(true);\n // Call web service to delete post.\n self.deleteComment(self.deleteTarget.id).then(function(response) {\n if (!response.success) {\n self.showError(response.message);\n return true;\n }\n\n var convertedCommentData = self.convertForTemplate(response.data,\n self.deleteTarget.expanded);\n\n // Delete success, begin to call template and render the page again.\n var commentSelector = self.elementSelector.find(t.SELECTOR.COMMENT_ID +\n convertedCommentData.id);\n\n var deletedComments = 1;\n\n // Update global comment count.\n self.updateCommentCount(\n self.lastCurrentCount - deletedComments,\n self.lastTotal - deletedComments\n );\n\n // Reply will always be expanded.\n // Root comment deleted all replies => collapsed.\n if (!convertedCommentData.root) {\n convertedCommentData.expanded = true;\n }\n\n // Call template to render.\n Templates.render(t.TEMPLATE_COMMENT, convertedCommentData).done(function(html) {\n var el = $(html);\n\n // Update the parent comment count if we delete reply before replace.\n if (!convertedCommentData.root) {\n var parentSelector = commentSelector.parent();\n var parentCountSelector = parentSelector.closest(t.SELECTOR.COMMENT_ITEM)\n .find(t.SELECTOR.TOTAL_REPLY);\n var countSelector = parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_NUMBER);\n var newCount = parseInt(countSelector.text()) - 1;\n parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_NUMBER).text(newCount);\n parentCountSelector.find(t.SELECTOR.COMMENT_COUNT_TEXT).html(\n newCount === 1 ? self.string.reply : self.string.replies\n );\n }\n\n // Clone replies and append because the replies will be replaced by template.\n var oldReplies = commentSelector.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER)\n .clone(true);\n commentSelector.replaceWith(el);\n el.find(t.SELECTOR.COMMENT_REPLIES_CONTAINER).replaceWith(oldReplies);\n if (self.deleteTarget.root) {\n self.bindCommentEvent(response.data);\n } else {\n self.bindReplyEvent(response.data, el.parent());\n }\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_DELETE);\n });\n modal.hide();\n return true;\n }).fail(function(err) {\n self.showError(err.message);\n return false;\n });\n });\n\n // Focus back to delete button when user hide modal.\n modal.getRoot().on(ModalEvents.hidden, function() {\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + self.deleteTarget.id);\n // Focus on different element base on comment or reply.\n if (self.deleteTarget.root) {\n el.find(t.SELECTOR.BTN_DELETE).first().focus();\n } else {\n el.find(t.SELECTOR.BTN_DELETE_REPLY).first().focus();\n }\n });\n\n // Enable button when modal is shown.\n modal.getRoot().on(ModalEvents.shown, function() {\n self.changeWorkingState(false);\n });\n\n // Display the dialogue.\n modal.show();\n\n self.changeWorkingState(false);\n });\n }\n },\n\n /**\n * Delete comment API.\n *\n * @param {Integer} id\n * @returns {Promise}\n */\n deleteComment: function(id) {\n var self = this;\n var params = self.getParamsBeforeCallApi({\n commentid: id\n });\n var promise = ajax.call([{\n methodname: t.ACTION_DELETE,\n args: params\n }]);\n return promise[0];\n },\n\n /**\n * Binds event handlers to the TinyMCE editor within the specified form.\n *\n * This function sets up a periodic check to ensure that the TinyMCE editor\n * is initialized. Once initialized, it binds a keydown event to the editor\n * that triggers the `checkEditorContent` function, allowing content validation\n * to occur each time the user types within the editor.\n *\n * @param {jQuery} formSelector - The jQuery object representing the form containing the TinyMCE editor.\n */\n bindHandleTinyEditor: function(formSelector) {\n var self = this;\n const textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA);\n const tinyEditorId = textareaSelector.attr('id');\n const intervalID = setInterval(function() {\n if (window.tinyMCE) {\n const editor = window.tinyMCE.get(tinyEditorId);\n editor.on(\"input\", function() {\n self.checkEditorContent(formSelector, editor.getBody(), t.EDITOR.TINYMCE.TYPE);\n });\n // This event will be triggered when user paste content.\n editor.on(\"change\", function() {\n self.checkEditorContent(formSelector, editor.getBody(), t.EDITOR.TINYMCE.TYPE);\n });\n clearInterval(intervalID);\n }\n }, 100);\n },\n\n /**\n * Binds event handlers and initializes the Atto editor within the specified form.\n *\n * This function sets up the Atto editor by triggering initial placeholder settings,\n * displaying the toolbar, and setting up event listeners for content changes. It uses\n * a MutationObserver to monitor changes within the Atto editor, allowing dynamic\n * validation of the editor content. It also sets up a periodic check to handle draft content.\n *\n * @param {jQuery} formSelector - The jQuery object representing the form containing the Atto editor.\n */\n bindHandleAttoEditor: function(formSelector) {\n var self = this;\n M.util.js_pending('init_editor');\n self.triggerAttoNoContent(formSelector);\n self.setPlaceholder(formSelector, formSelector.attr('data-textarea-placeholder'));\n formSelector.find(t.SELECTOR.ATTO.TOOLBAR).fadeIn();\n var textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA);\n var attoEditableId = textareaSelector.attr('id') + 'editable';\n var attoEditable = document.getElementById(attoEditableId);\n var observation = new MutationObserver(function(mutationsList) {\n mutationsList.forEach(function(mutation) {\n if (mutation.type === 'childList' || (mutation.type === 'attributes' &&\n (mutation.attributeName === 'style' || mutation.attributeName === 'hidden'))) {\n self.checkEditorContent(formSelector);\n }\n });\n });\n observation.observe(attoEditable, {attributes: true, childList: true, subtree: true});\n textareaSelector.change(function() {\n self.checkEditorContent(formSelector);\n });\n M.util.js_complete('init_editor');\n\n // Check interval for 5s in case draft content show up.\n var interval = setInterval(function() {\n formSelector.find('textarea[id^=\"id_message\"]').trigger('change');\n }, 350);\n\n setTimeout(function() {\n clearInterval(interval);\n }, 5000);\n },\n\n /**\n * Binds event handlers to a standard textarea editor within the specified form.\n *\n * This function sets up an input event listener on the textarea to monitor changes\n * in its content. Whenever the user types or modifies the content, the `checkEditorContent`\n * function is triggered to validate the editor's content based on the textarea type.\n *\n * @param {jQuery} formSelector - The jQuery object representing the form containing the textarea editor.\n */\n bindHandleTextareaEditor: function(formSelector) {\n var self = this;\n const textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA);\n if (textareaSelector) {\n textareaSelector.on('input', function() {\n self.checkEditorContent(formSelector, textareaSelector[0],\n t.EDITOR.TEXTAREA.TYPE);\n });\n }\n },\n\n /**\n * Bind event handlers for the text editor in the form.\n * This function checks the form for the presence of different types of text editors.\n * It supports TinyMCE, Atto, or a basic textarea.\n * Based on the editor found, it calls the appropriate handler function to bind the necessary events.\n *\n * @param {jQuery} formSelector - A jQuery selector representing the form containing the editor.\n */\n bindEditorEvent: function(formSelector) {\n var self = this;\n if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) {\n self.bindHandleTinyEditor(formSelector);\n } else if (self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length !== 0) {\n self.bindHandleAttoEditor(formSelector);\n } else {\n self.bindHandleTextareaEditor(formSelector);\n }\n },\n\n /**\n * Resets the content of the editor within the specified form to an empty state.\n *\n * This function checks the type of the editor (TinyMCE or standard textarea) and clears\n * its content accordingly. For TinyMCE editors, it uses the `setContent` method to clear\n * the content. For standard textareas, it directly sets the value to an empty string.\n *\n * @param {jQuery} formSelector - The jQuery object representing the form containing the editor.\n * @param {string} type - The type of the editor, which can be TinyMCE or textarea.\n */\n resetContent: function(formSelector, type) {\n const textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA);\n if (type === t.EDITOR.TINYMCE.TYPE) {\n const tinyEditorId = textareaSelector.attr('id');\n const editor = window.tinyMCE.get(tinyEditorId);\n editor.setContent('');\n } else {\n textareaSelector[0].value = '';\n }\n },\n\n /**\n * Check if element is empty.\n *\n * @param {jQuery} el - Element.\n * @returns {boolean}\n */\n checkEmptyElement: function(el) {\n return el.children().length === 0;\n },\n\n /**\n * Set user has commented.\n *\n * @param {integer} value\n */\n setHasComment: function(value) {\n var self = this;\n var container = self.elementSelector;\n var hasCommentClass = t.HAS_COMMENT_CLASS;\n if (!self.forceCommenting) {\n self.hasComment = true;\n container.addClass(hasCommentClass);\n } else {\n self.hasComment = value;\n if (self.hasComment) {\n container.addClass(hasCommentClass);\n } else {\n container.removeClass(hasCommentClass);\n }\n }\n },\n\n /**\n * Parse query string.\n *\n * @param {string} query\n * @return {string}\n */\n parseQueryString: function(query) {\n var vars = query.split(\"&\");\n var queryString = {};\n for (var i = 0; i < vars.length; i++) {\n var pair = vars[i].split(\"=\");\n var key = decodeURIComponent(pair[0]);\n var value = decodeURIComponent(pair[1]);\n // If first entry with this name.\n if (typeof queryString[key] === \"undefined\") {\n queryString[key] = decodeURIComponent(value);\n // If second entry with this name.\n } else if (typeof queryString[key] === \"string\") {\n queryString[key] = [queryString[key], decodeURIComponent(value)];\n // If third or later entry with this name.\n } else {\n queryString[key].push(decodeURIComponent(value));\n }\n }\n return queryString;\n },\n\n /**\n * Scroll to element.\n *\n * @param {jQuery} target\n * @param {Integer} speed\n */\n scrollToElement: function(target, speed) {\n if (!target.length) {\n return;\n }\n if (typeof speed === 'undefined') {\n speed = 1000;\n }\n var top = target.offset().top;\n $('html,body').animate({scrollTop: top}, speed);\n },\n\n /**\n * Build referer report link.\n *\n * @param {string} link\n * @param {Integer} id\n * @returns {string}\n */\n buildRefererReportLink: function(link, id) {\n var self = this;\n var referer = decodeURIComponent(self.referer);\n // Add highlight.\n link += '&referer=' + encodeURIComponent(referer + '&highlight=' + id);\n return link;\n },\n\n /**\n * Handle when Atto has content.\n *\n * @param {jQuery} formSelector\n */\n triggerAttoHasContent: function(formSelector) {\n var editorContentWrap = formSelector.find(t.SELECTOR.ATTO.CONTENT_WRAP);\n var submitBtn = formSelector.find(t.SELECTOR.SUBMIT_BUTTON);\n submitBtn.removeClass('disabled');\n submitBtn.prop('disabled', false);\n editorContentWrap.addClass(t.ATTO_CONTENT_TYPE.HAS_CONTENT);\n editorContentWrap.removeClass(t.ATTO_CONTENT_TYPE.NO_CONTENT);\n },\n\n /**\n * Handle when Atto has no content.\n *\n * @param {jQuery} formSelector\n */\n triggerAttoNoContent: function(formSelector) {\n var editorContentWrap = formSelector.find(t.SELECTOR.ATTO.CONTENT_WRAP);\n var submitBtn = formSelector.find(t.SELECTOR.SUBMIT_BUTTON);\n submitBtn.addClass('disabled');\n submitBtn.prop('disabled', true);\n editorContentWrap.addClass(t.ATTO_CONTENT_TYPE.NO_CONTENT);\n editorContentWrap.removeClass(t.ATTO_CONTENT_TYPE.HAS_CONTENT);\n },\n\n /**\n * Set placeholder in the textarea.\n *\n * @param {jQuery} formSelector The form selector.\n * @param {string} placeholder The placeholder of the textarea.\n */\n setPlaceholder: function(formSelector, placeholder) {\n formSelector.find(t.SELECTOR.ATTO.CONTENT_WRAP).attr('data-placeholder', placeholder);\n },\n\n /**\n * Set sort depend on sortable array.\n *\n * @param {string} string\n */\n setSort: function(string) {\n var self = this;\n if ($.inArray(string, self.sortable) !== -1) {\n self.sortFeature = string;\n }\n },\n\n /**\n * Begin to load the fragment form for editing.\n *\n * @param {Object} item\n */\n getFragmentEditFormEvent: function(item) {\n var self = this;\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n var fragmentForm = el.find(t.SELECTOR.FRAGMENT_FORM).first();\n var postFooter = el.find(t.SELECTOR.POST_FOOTER).first();\n var clone = self.loadingIcon.clone().show();\n fragmentForm.append(clone);\n fragmentForm.removeClass('reply');\n fragmentForm.addClass('edit');\n self.loadFragmentEditForm(fragmentForm, item);\n self.changeWorkingState(true, postFooter);\n },\n\n /**\n * Call web services to get the fragment edit form, append to the DOM then bind event.\n *\n * @param {jQuery} fragmentForm\n * @param {Object} item\n */\n loadFragmentEditForm: function(fragmentForm, item) {\n var self = this;\n M.util.js_pending(t.ACTION_LOAD_FRAGMENT_EDIT_FORM);\n var params = self.getParamsBeforeCallApi({\n cancelbutton: true,\n forcecommenting: self.forceCommenting,\n commentid: item.id\n });\n // Clear error message on the main form to prevent Atto editor from focusing to old message.\n var attoWrap = self.formSelector.find(t.SELECTOR.ATTO_EDITOR_WRAP);\n if (attoWrap.length !== 0 && attoWrap.hasClass('error')) {\n attoWrap.removeClass('error');\n attoWrap.find('#id_error_message_5btext_5d').remove();\n }\n fragment.loadFragment(\n 'mod_studentquiz',\n t.FRAGMENT_EDIT_FORM_CALLBACK,\n self.contextId,\n params\n ).done(function(html, js) {\n Templates.replaceNodeContents(fragmentForm, html, js);\n // Focus form.\n var textFragmentFormId = '#id_editor_question_' + self.studentQuizQuestionId +\n '_' + self.type + '_' + item.id + 'editable';\n fragmentForm.find(textFragmentFormId).focus();\n self.bindFragmentEditFormEvent(fragmentForm, item);\n M.util.js_complete(t.ACTION_LOAD_FRAGMENT_EDIT_FORM);\n });\n },\n\n /**\n * Bind fragment edit form action button event.\n *\n * @param {jQuery} fragmentForm\n * @param {Object} item\n */\n bindFragmentEditFormEvent: function(fragmentForm, item) {\n var self = this;\n var formFragmentSelector = fragmentForm.find(t.SELECTOR.COMMENT_AREA_FORM);\n fragmentForm.find(t.SELECTOR.SUBMIT_BUTTON).click(function(e) {\n e.preventDefault();\n self.changeWorkingState(true);\n var data = self.convertFormToJson(formFragmentSelector);\n // Check message field.\n if (data['message[text]'].length === 0) {\n return true; // Return true to trigger form validation and show error messages.\n }\n var clone = self.loadingIcon.clone().show();\n clone.appendTo(fragmentForm);\n formFragmentSelector.hide();\n self.editCommentEvent(fragmentForm, item, formFragmentSelector, data);\n return true;\n });\n self.fragmentFormCancelEvent(formFragmentSelector, true);\n self.bindEditorEvent(fragmentForm);\n },\n\n /**\n * Edit comment event.\n *\n * @param {jQuery} container\n * @param {Object} item\n * @param {jQuery} formSelector\n * @param {Object} formData\n */\n editCommentEvent: function(container, item, formSelector, formData) {\n var self = this;\n M.util.js_pending(t.ACTION_EDIT);\n var params = {\n commentid: item.id,\n message: {\n text: formData['message[text]'],\n format: formData['message[format]'],\n }\n };\n self.editComment(params).then(function(response) {\n // Hide error if exists.\n self.elementSelector.find(t.SELECTOR.COMMENT_ERROR).addClass('hide');\n var el = self.elementSelector.find(t.SELECTOR.COMMENT_ID + item.id);\n self.lastFocusElement = el.find(t.SELECTOR.BTN_EDIT);\n if (self.lastFocusElement.length === 0) {\n self.lastFocusElement = el.find(t.SELECTOR.BTN_EDIT_REPLY);\n }\n // Assign new content.\n item.shortcontent = response.shortcontent;\n response.expanded = item.expanded;\n Templates.render(t.TEMPLATE_COMMENT, response).done(function(html) {\n var el = $(html);\n var commentTextSelector = t.SELECTOR.COMMENT_ID + response.id + ' ' +\n t.SELECTOR.COMMENT_TEXT_CONTAINER;\n self.elementSelector.find(commentTextSelector).first().html(el.find(\n t.SELECTOR.COMMENT_TEXT_CONTAINER).html());\n });\n container.empty();\n self.changeWorkingState(false);\n M.util.js_complete(t.ACTION_EDIT);\n return true;\n }).fail(function(e) {\n self.handleFailWhenCreateComment(e, params);\n M.util.js_complete(t.ACTION_EDIT);\n });\n },\n\n /**\n * Call web services to edit comment.\n *\n * @param {Object} data\n * @returns {Promise}\n */\n editComment: function(data) {\n var self = this;\n data = self.getParamsBeforeCallApi(data);\n var promise = ajax.call([{\n methodname: t.ACTION_EDIT,\n args: data\n }]);\n return promise[0];\n },\n\n /**\n * Check editor content.\n *\n * @param {jQuery} formSelector\n * @param {jQuery} bodyContent The body content of the editor.\n * @param {string} type The type of editor. ex: 'tiny', 'atto', 'textarea'.\n */\n checkEditorContent: function(formSelector, bodyContent = null, type = '') {\n const key = 'text_change_' + Date.now();\n M.util.js_pending(key);\n\n const textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA);\n let editorBodyContent;\n let contenthtml;\n let condition;\n\n // Handle different editor types.\n switch (type) {\n case t.EDITOR.TINYMCE.TYPE:\n editorBodyContent = $(bodyContent);\n contenthtml = editorBodyContent.html();\n condition = t.EMPTY_CONTENT.indexOf(contenthtml) > -1 ||\n editorBodyContent.text().trim().length < 1;\n break;\n case t.EDITOR.ATTO.TYPE:\n case '':\n editorBodyContent = $('#' + textareaSelector.attr('id') + 'editable');\n contenthtml = editorBodyContent.html();\n condition = t.EMPTY_CONTENT.indexOf(contenthtml) > -1 ||\n editorBodyContent.text().trim().length < 1;\n break;\n case t.EDITOR.TEXTAREA.TYPE:\n editorBodyContent = $(bodyContent);\n contenthtml = editorBodyContent[0].value;\n condition = contenthtml.trim().length < 1;\n break;\n }\n\n // This regex will match if the editor has some special cases.\n // 1)


.\n // 2)


.\n // The cases are considered empty in the editor.\n const regex = /^(<(?:p)[^>]*>)+(
)?(<\\/p>)+$/;\n const match = regex.exec(contenthtml);\n\n // Check the condition and set the placeholder or trigger appropriate events\n if (condition) {\n // On initial load, contenthtml contains

or .\n // If it matches the regex meaning the textarea is empty.\n const isEmptyContent = match ||\n t.EMPTY_CONTENT.indexOf(contenthtml) > -1;\n this.setPlaceholder(formSelector, isEmptyContent ?\n formSelector.attr('data-textarea-placeholder') : '');\n this.triggerAttoNoContent(formSelector);\n } else {\n this.setPlaceholder(formSelector, '');\n this.triggerAttoHasContent(formSelector);\n }\n M.util.js_complete(key);\n }\n };\n },\n generate: function(params) {\n t.get().init(params);\n }\n };\n return t;\n });\n"],"names":["define","$","str","ajax","ModalFactory","Templates","fragment","ModalEvents","t","EMPTY_CONTENT","ROOT_COMMENT_VALUE","GET_ALL_VALUE","TEMPLATE_COMMENTS","TEMPLATE_COMMENT","ACTION_CREATE","ACTION_CREATE_REPLY","ACTION_GET_ALL","ACTION_EXPAND","ACTION_DELETE","ACTION_EDIT","ACTION_LOAD_FRAGMENT_FORM","ACTION_LOAD_FRAGMENT_EDIT_FORM","ACTION_EXPAND_ALL","ACTION_COLLAPSE_ALL","ACTION_RENDER_COMMENT","ACTION_APPEND_COMMENT","ACTION_EDITOR_INIT","ACTION_INIT","ACTION_UPDATE_COMMENT_COUNT","ACTION_CLEAR_FORM","ACTION_SHOW_ERROR","FRAGMENT_FORM_CALLBACK","FRAGMENT_EDIT_FORM_CALLBACK","HAS_COMMENT_CLASS","ATTO_CONTENT_TYPE","HAS_CONTENT","NO_CONTENT","SELECTOR","CONTAINER","EXPAND_ALL","COLLAPSE_ALL","SUBMIT_BUTTON","CONTAINER_REPLIES","COMMENT_REPLIES_CONTAINER","COMMENT_COUNT","COMMENT_TEXT_CONTAINER","COMMENT_TEXT","COMMENT_HISTORY","COMMENT_REPLIES_TEXT","LOADING_ICON","COMMENT_AREA_FORM","FORM_SELECTOR","NO_COMMENT","COLLAPSE_LINK","EXPAND_LINK","COMMENT_ITEM","COMMENT_REPLIES_CONTAINER_TO_ITEM","FRAGMENT_FORM","BTN_DELETE","BTN_REPLY","BTN_DELETE_REPLY","ATTO_EDITOR_WRAP","TEXTAREA","COMMENT_COUNT_NUMBER","COMMENT_COUNT_TEXT","ATTO","CONTENT_WRAP","CONTENT","TOOLBAR","TINYMCE","COMMENT_ID","SPAN_COMMENT_ID","TOTAL_REPLY","COMMENT_FILTER","COMMENT_FILTER_HIDE","COMMENT_ERROR","BTN_REPORT","COMMENT_FILTER_ITEM","COMMENT_FILTER_NAME","COMMENT_FILTER_TYPE","BTN_EDIT","BTN_EDIT_REPLY","ATTO_HTML_BUTTON","POST_FOOTER","EDITOR","TYPE","get","elementSelector","btnExpandAll","btnCollapseAll","addComment","containerSelector","studentQuizQuestionId","dialogue","loadingIcon","lastFocusElement","formSelector","contextId","userId","string","deleteDialog","deleteTarget","numberToShow","cmId","countServerData","lastCurrentCount","lastTotal","expand","forceCommenting","canViewDeleted","hasComment","referer","highlight","sortFeature","sortable","workingState","isNoComment","type","init","params","M","util","js_pending","this","escapeSelector","id","el","find","parseInt","data","count","total","sortfeature","forcecommenting","canviewdeleted","isnocomment","allowSelfCommentRating","allowselfcommentrating","initServerRender","initBindEditor","bindEvents","js_complete","self","changeWorkingState","each","attrs","replies","comment","deleted","numberofreply","expanded","root","bindCommentEvent","commentcount","updateCommentCount","hide","show","query","window","location","search","substring","getParams","parseQueryString","target","length","scrollToElement","isEditorLoaded","interval","setInterval","bindEditorEvent","clearInterval","editorWaiting","tinyEditorId","attr","editor","tinyMCE","checkEditorContent","getBody","click","e","preventDefault","empty","getComments","then","response","countCommentAndReplies","renderComment","fail","err","showError","message","innerHTML","commentCount","deletedComments","totalDelete","addClass","rootId","unique","formData","convertFormToJson","attoWrap","hasClass","prepend","required","replyto","text","format","createComment","setTimeout","resetContent","trigger","is","convertForTemplate","appendComment","handleFailWhenCreateComment","on","asc","sort","desc","nameSelector","iconSelector","orderBy","isCurrent","ascString","descString","not","eachName","eachType","defaultString","removeClass","sortType","setSort","getParamsBeforeCallApi","numbertoshow","call","methodname","args","studentquizquestionid","cmid","when","error","done","showDialog","title","body","html","create","types","CANCEL","modal","getRoot","hidden","reload","current","s","get_string","noCommentSelector","filter","emptyReplies","checkEmptyElement","comments","render","i","hasOwnProperty","reply","bindReplyEvent","bindDeleteEvent","getFragmentFormReplyEvent","bindExpandEvent","bindCollapseEvent","getFragmentEditFormEvent","replySelector","boolean","elementToHide","visibility","prop","css","getFooter","focus","deleteCommentCount","replyCount","deleteReplyCount","constructor","Array","item","deletedtime","j","expandComment","commentid","itemSelector","key","clone","append","convertedItem","currentDisplayComment","newCount","newTotalCount","replaceWith","shortcontent","single","setHasComment","hascomment","reportlink","buildRefererReportLink","form","name","checked","val","isReply","parent","loadFragmentForm","fragmentForm","cancelbutton","remove","loadFragment","js","replaceNodeContents","textFragmentFormId","bindFragmentFormEvent","formFragmentSelector","appendTo","createReplyComment","fragmentFormCancelEvent","replyContainer","repliesEl","numReply","fragmentFormSelector","first","postFooter","isEdit","commentSelector","closest","DEFAULT","deletecomment","confirmdeletecomment","footer","deletetext","cancel","deleteComment","success","convertedCommentData","parentCountSelector","countSelector","oldReplies","shown","bindHandleTinyEditor","intervalID","bindHandleAttoEditor","triggerAttoNoContent","setPlaceholder","fadeIn","textareaSelector","attoEditableId","attoEditable","document","getElementById","MutationObserver","mutationsList","forEach","mutation","attributeName","observe","attributes","childList","subtree","change","bindHandleTextareaEditor","setContent","value","children","container","hasCommentClass","vars","split","queryString","pair","decodeURIComponent","push","speed","top","offset","animate","scrollTop","link","encodeURIComponent","triggerAttoHasContent","editorContentWrap","submitBtn","placeholder","inArray","loadFragmentEditForm","bindFragmentEditFormEvent","editCommentEvent","editComment","commentTextSelector","bodyContent","Date","now","editorBodyContent","contenthtml","condition","indexOf","trim","regex","match","exec","isEmptyContent","generate"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/studentquiz.min.js b/amd/build/studentquiz.min.js index b3a97994..68f36b05 100644 --- a/amd/build/studentquiz.min.js +++ b/amd/build/studentquiz.min.js @@ -5,6 +5,6 @@ * @copyright 2017 HSR (http://www.hsr.ch) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("mod_studentquiz/studentquiz",["jquery"],(function($){return{initialise:function(forcerating,forcecommenting,isanswered){var ratingElements=$(".studentquiz_behaviour .rate .rating .rateable");ratingElements.off("click").on("click",(function(){addRating(this)})),ratingElements.on("keypress",(function(e){13!==e.keyCode&&32!==e.keyCode||(e.preventDefault(),addRating(this))})),$('input[name="next"], input[name="previous"], input[name="finish"]').off("click").on("click",(function(){var $that=$(this);if(isanswered){var hasrated=$(".rating span").hasClass("star"),hascommented=$(".studentquiz-comment-container").hasClass("has-comment");return forcerating&&!hasrated&&$(".studentquiz_behaviour > .rate > .rate_error").removeClass("hide"),forcecommenting&&!hascommented&&$(".studentquiz_behaviour > .comments .comment-error").removeClass("hide"),forcerating&&!hasrated?$(".studentquiz_behaviour .rate .rating .rateable:first-child").focus():forcecommenting&&!hascommented&&$(".studentquiz_behaviour .comments .studentquiz-comment-container .editor_atto_content").focus(),!(forcerating&&!hasrated||forcecommenting&&!hascommented)&&($that.submit(),!0)}return $that.submit(),!0}))},setFocus:function(){$(document).ready((function(){var sortIcon=$("#categoryquestions .iconsort");sortIcon&&sortIcon.parent().focus()}))},selectAllQuestions:function(){let headerCheckbox=document.getElementById("qbheadercheckbox");require(["core/checkbox-toggleall"],(()=>{headerCheckbox.checked||headerCheckbox.click()}))}};function addRating(element){var $element=$(element),rate=$element.attr("data-rate"),$that=$element,cmid=$element.closest("form").find(".cmid_field").attr("value");$.post(M.cfg.wwwroot+"/mod/studentquiz/save.php",{save:"rate",cmid:cmid,studentquizquestionid:$element.attr("data-studentquizquestionid"),sesskey:M.cfg.sesskey,rate:rate},(function(){var $ratingStars=$that.closest(".rating").children("span");$ratingStars.removeClass("star"),$ratingStars.addClass("star-empty"),$ratingStars.each((function(){$(this).attr("data-rate")<=rate&&($(this).removeClass("star-empty"),$(this).addClass("star"))})),$(".studentquiz_behaviour > .rate > .rate_error").addClass("hide")}))}})); +define("mod_studentquiz/studentquiz",["jquery"],(function($){return{initialise:function(forcerating,forcecommenting,isanswered){var ratingElements=$(".studentquiz_behaviour .rate .rating .rateable");ratingElements.off("click").on("click",(function(){addRating(this)})),ratingElements.on("keypress",(function(e){13!==e.keyCode&&32!==e.keyCode||(e.preventDefault(),addRating(this))})),$('input[name="next"], input[name="previous"], input[name="finish"]').off("click").on("click",(function(){var $that=$(this);if(isanswered){var hasrated=$(".rating span").hasClass("star"),hascommented=$(".studentquiz-comment-container").hasClass("has-comment");if(forcerating&&!hasrated&&$(".studentquiz_behaviour > .rate > .rate_error").removeClass("hide"),forcecommenting&&!hascommented&&$(".studentquiz_behaviour > .comments .comment-error").removeClass("hide"),forcerating&&!hasrated)$(".studentquiz_behaviour .rate .rating .rateable:first-child").focus();else if(forcecommenting&&!hascommented){const attoEditorSelector=$(".studentquiz_behaviour .comments .studentquiz-comment-container .editor_atto_content"),tinyMceEditorSelector=$(".studentquiz_behaviour .comments .studentquiz-comment-container .tox-edit-area"),textareaSelector=$('textarea[id^="id_editor_question_"]');if(attoEditorSelector.length>0)attoEditorSelector.focus();else if(tinyMceEditorSelector.length>0){const tinyEditorId=textareaSelector.attr("id");window.tinymce.get(tinyEditorId).focus()}else textareaSelector.focus()}return!(forcerating&&!hasrated||forcecommenting&&!hascommented)&&($that.submit(),!0)}return $that.submit(),!0}))},setFocus:function(){$(document).ready((function(){var sortIcon=$("#categoryquestions .iconsort");sortIcon&&sortIcon.parent().focus()}))},selectAllQuestions:function(){let headerCheckbox=document.getElementById("qbheadercheckbox");require(["core/checkbox-toggleall"],(()=>{headerCheckbox.checked||headerCheckbox.click()}))}};function addRating(element){var $element=$(element),rate=$element.attr("data-rate"),$that=$element,cmid=$element.closest("form").find(".cmid_field").attr("value");$.post(M.cfg.wwwroot+"/mod/studentquiz/save.php",{save:"rate",cmid:cmid,studentquizquestionid:$element.attr("data-studentquizquestionid"),sesskey:M.cfg.sesskey,rate:rate},(function(){var $ratingStars=$that.closest(".rating").children("span");$ratingStars.removeClass("star"),$ratingStars.addClass("star-empty"),$ratingStars.each((function(){$(this).attr("data-rate")<=rate&&($(this).removeClass("star-empty"),$(this).addClass("star"))})),$(".studentquiz_behaviour > .rate > .rate_error").addClass("hide")}))}})); //# sourceMappingURL=studentquiz.min.js.map \ No newline at end of file diff --git a/amd/build/studentquiz.min.js.map b/amd/build/studentquiz.min.js.map index 32566a04..fc26b48c 100644 --- a/amd/build/studentquiz.min.js.map +++ b/amd/build/studentquiz.min.js.map @@ -1 +1 @@ -{"version":3,"file":"studentquiz.min.js","sources":["../src/studentquiz.js"],"sourcesContent":["// This file is part of Moodle - http://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 * Javascript for save rating and save, remove and listing comments\n *\n * @module mod_studentquiz/studentquiz\n * @copyright 2017 HSR (http://www.hsr.ch)\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/* jshint latedef:nofunc */\n\ndefine(['jquery'], function($) {\n return {\n initialise: function(forcerating, forcecommenting, isanswered) {\n\n var ratingElements = $(\".studentquiz_behaviour .rate .rating .rateable\");\n // Ajax request POST on CLICK for add rating.\n ratingElements.off(\"click\").on(\"click\", function() {\n addRating(this);\n });\n // Ajax request POST for add rating when \"Enter\" or \"Space\" is pressed.\n ratingElements.on(\"keypress\", function(e) {\n if (e.keyCode === 13 || e.keyCode === 32) {\n e.preventDefault();\n addRating(this);\n }\n });\n\n // On CLICK check if student submitted result and has rated if not abort and show error for rating.\n $('input[name=\"next\"], input[name=\"previous\"], input[name=\"finish\"]').off('click').on('click', function() {\n var $that = $(this);\n\n if (isanswered) {\n var hasrated = $('.rating span').hasClass('star');\n var hascommented = $('.studentquiz-comment-container').hasClass('has-comment');\n\n if (forcerating && !hasrated) {\n $('.studentquiz_behaviour > .rate > .rate_error').removeClass('hide');\n }\n if (forcecommenting && !hascommented) {\n $('.studentquiz_behaviour > .comments .comment-error').removeClass('hide');\n }\n\n // Set focus.\n if (forcerating && !hasrated) {\n // Set focus to the first star.\n $('.studentquiz_behaviour .rate .rating .rateable:first-child').focus();\n } else if (forcecommenting && !hascommented) {\n // Set focus to atto editor.\n $('.studentquiz_behaviour .comments .studentquiz-comment-container .editor_atto_content').focus();\n }\n\n if ((!forcerating || hasrated) && (!forcecommenting || hascommented)) {\n $that.submit();\n return true;\n }\n return false;\n } else {\n $that.submit();\n return true;\n }\n });\n },\n\n // Set focus to sorted head of question table.\n setFocus: function() {\n $(document).ready(function() {\n var sortIcon = $('#categoryquestions .iconsort');\n if (sortIcon) {\n sortIcon.parent().focus();\n }\n });\n },\n\n // Select all questions.\n selectAllQuestions: function() {\n let headerCheckbox = document.getElementById('qbheadercheckbox');\n\n require(['core/checkbox-toggleall'], () => {\n if (!headerCheckbox.checked) {\n headerCheckbox.click();\n }\n });\n }\n };\n\n /**\n * Add rating to question.\n *\n * @param {DOM} element\n */\n function addRating(element) {\n var $element = $(element);\n var rate = $element.attr('data-rate');\n var $that = $element;\n var $cmIdField = $element.closest('form').find('.cmid_field');\n var cmid = $cmIdField.attr('value');\n $.post(M.cfg.wwwroot + '/mod/studentquiz/save.php',\n {\n save: 'rate',\n cmid: cmid,\n studentquizquestionid: $element.attr('data-studentquizquestionid'),\n sesskey: M.cfg.sesskey,\n rate: rate\n },\n function() {\n var $ratingStars = $that.closest('.rating').children('span');\n $ratingStars.removeClass('star');\n $ratingStars.addClass('star-empty');\n $ratingStars.each(function() {\n if ($(this).attr('data-rate') <= rate) {\n $(this).removeClass('star-empty');\n $(this).addClass('star');\n }\n });\n $('.studentquiz_behaviour > .rate > .rate_error').addClass('hide');\n }\n );\n }\n});\n"],"names":["define","$","initialise","forcerating","forcecommenting","isanswered","ratingElements","off","on","addRating","this","e","keyCode","preventDefault","$that","hasrated","hasClass","hascommented","removeClass","focus","submit","setFocus","document","ready","sortIcon","parent","selectAllQuestions","headerCheckbox","getElementById","require","checked","click","element","$element","rate","attr","cmid","closest","find","post","M","cfg","wwwroot","save","studentquizquestionid","sesskey","$ratingStars","children","addClass","each"],"mappings":";;;;;;;AAyBAA,qCAAO,CAAC,WAAW,SAASC,SACjB,CACHC,WAAY,SAASC,YAAaC,gBAAiBC,gBAE3CC,eAAiBL,EAAE,kDAEvBK,eAAeC,IAAI,SAASC,GAAG,SAAS,WACpCC,UAAUC,SAGdJ,eAAeE,GAAG,YAAY,SAASG,GACjB,KAAdA,EAAEC,SAAgC,KAAdD,EAAEC,UACtBD,EAAEE,iBACFJ,UAAUC,UAKlBT,EAAE,oEAAoEM,IAAI,SAASC,GAAG,SAAS,eACvFM,MAAQb,EAAES,SAEVL,WAAY,KACRU,SAAWd,EAAE,gBAAgBe,SAAS,QACtCC,aAAehB,EAAE,kCAAkCe,SAAS,sBAE5Db,cAAgBY,UAChBd,EAAE,gDAAgDiB,YAAY,QAE9Dd,kBAAoBa,cACpBhB,EAAE,qDAAqDiB,YAAY,QAInEf,cAAgBY,SAEhBd,EAAE,8DAA8DkB,QACzDf,kBAAoBa,cAE3BhB,EAAE,wFAAwFkB,UAGxFhB,cAAeY,UAAeX,kBAAmBa,gBACnDH,MAAMM,UACC,UAIXN,MAAMM,UACC,MAMnBC,SAAU,WACNpB,EAAEqB,UAAUC,OAAM,eACVC,SAAWvB,EAAE,gCACbuB,UACAA,SAASC,SAASN,YAM9BO,mBAAoB,eACZC,eAAiBL,SAASM,eAAe,oBAE7CC,QAAQ,CAAC,4BAA4B,KAC5BF,eAAeG,SAChBH,eAAeI,sBAWtBtB,UAAUuB,aACXC,SAAWhC,EAAE+B,SACbE,KAAOD,SAASE,KAAK,aACrBrB,MAAQmB,SAERG,KADaH,SAASI,QAAQ,QAAQC,KAAK,eACzBH,KAAK,SAC3BlC,EAAEsC,KAAKC,EAAEC,IAAIC,QAAU,4BACnB,CACIC,KAAM,OACNP,KAAMA,KACNQ,sBAAuBX,SAASE,KAAK,8BACrCU,QAASL,EAAEC,IAAII,QACfX,KAAMA,OAEV,eACQY,aAAehC,MAAMuB,QAAQ,WAAWU,SAAS,QACrDD,aAAa5B,YAAY,QACzB4B,aAAaE,SAAS,cACtBF,aAAaG,MAAK,WACVhD,EAAES,MAAMyB,KAAK,cAAgBD,OAC7BjC,EAAES,MAAMQ,YAAY,cACpBjB,EAAES,MAAMsC,SAAS,YAGzB/C,EAAE,gDAAgD+C,SAAS"} \ No newline at end of file +{"version":3,"file":"studentquiz.min.js","sources":["../src/studentquiz.js"],"sourcesContent":["// This file is part of Moodle - http://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 * Javascript for save rating and save, remove and listing comments\n *\n * @module mod_studentquiz/studentquiz\n * @copyright 2017 HSR (http://www.hsr.ch)\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/* jshint latedef:nofunc */\n\ndefine(['jquery'], function($) {\n return {\n initialise: function(forcerating, forcecommenting, isanswered) {\n\n var ratingElements = $(\".studentquiz_behaviour .rate .rating .rateable\");\n // Ajax request POST on CLICK for add rating.\n ratingElements.off(\"click\").on(\"click\", function() {\n addRating(this);\n });\n // Ajax request POST for add rating when \"Enter\" or \"Space\" is pressed.\n ratingElements.on(\"keypress\", function(e) {\n if (e.keyCode === 13 || e.keyCode === 32) {\n e.preventDefault();\n addRating(this);\n }\n });\n\n // On CLICK check if student submitted result and has rated if not abort and show error for rating.\n $('input[name=\"next\"], input[name=\"previous\"], input[name=\"finish\"]').off('click').on('click', function() {\n var $that = $(this);\n\n if (isanswered) {\n var hasrated = $('.rating span').hasClass('star');\n var hascommented = $('.studentquiz-comment-container').hasClass('has-comment');\n\n if (forcerating && !hasrated) {\n $('.studentquiz_behaviour > .rate > .rate_error').removeClass('hide');\n }\n if (forcecommenting && !hascommented) {\n $('.studentquiz_behaviour > .comments .comment-error').removeClass('hide');\n }\n\n // Set focus.\n if (forcerating && !hasrated) {\n // Set focus to the first star.\n $('.studentquiz_behaviour .rate .rating .rateable:first-child').focus();\n } else if (forcecommenting && !hascommented) {\n const attoEditorSelector = $(\n '.studentquiz_behaviour .comments .studentquiz-comment-container .editor_atto_content'\n );\n const tinyMceEditorSelector = $(\n '.studentquiz_behaviour .comments .studentquiz-comment-container .tox-edit-area'\n );\n const textareaSelector = $('textarea[id^=\"id_editor_question_\"]');\n if (attoEditorSelector.length > 0) {\n // Set focus to atto editor.\n attoEditorSelector.focus();\n } else if (tinyMceEditorSelector.length > 0) {\n const tinyEditorId = textareaSelector.attr('id');\n // Set focus to TinyMCE editor.\n window.tinymce.get(tinyEditorId).focus();\n } else {\n // Set focus to textarea.\n textareaSelector.focus();\n }\n }\n\n if ((!forcerating || hasrated) && (!forcecommenting || hascommented)) {\n $that.submit();\n return true;\n }\n return false;\n } else {\n $that.submit();\n return true;\n }\n });\n },\n\n // Set focus to sorted head of question table.\n setFocus: function() {\n $(document).ready(function() {\n var sortIcon = $('#categoryquestions .iconsort');\n if (sortIcon) {\n sortIcon.parent().focus();\n }\n });\n },\n\n // Select all questions.\n selectAllQuestions: function() {\n let headerCheckbox = document.getElementById('qbheadercheckbox');\n\n require(['core/checkbox-toggleall'], () => {\n if (!headerCheckbox.checked) {\n headerCheckbox.click();\n }\n });\n }\n };\n\n /**\n * Add rating to question.\n *\n * @param {DOM} element\n */\n function addRating(element) {\n var $element = $(element);\n var rate = $element.attr('data-rate');\n var $that = $element;\n var $cmIdField = $element.closest('form').find('.cmid_field');\n var cmid = $cmIdField.attr('value');\n $.post(M.cfg.wwwroot + '/mod/studentquiz/save.php',\n {\n save: 'rate',\n cmid: cmid,\n studentquizquestionid: $element.attr('data-studentquizquestionid'),\n sesskey: M.cfg.sesskey,\n rate: rate\n },\n function() {\n var $ratingStars = $that.closest('.rating').children('span');\n $ratingStars.removeClass('star');\n $ratingStars.addClass('star-empty');\n $ratingStars.each(function() {\n if ($(this).attr('data-rate') <= rate) {\n $(this).removeClass('star-empty');\n $(this).addClass('star');\n }\n });\n $('.studentquiz_behaviour > .rate > .rate_error').addClass('hide');\n }\n );\n }\n});\n"],"names":["define","$","initialise","forcerating","forcecommenting","isanswered","ratingElements","off","on","addRating","this","e","keyCode","preventDefault","$that","hasrated","hasClass","hascommented","removeClass","focus","attoEditorSelector","tinyMceEditorSelector","textareaSelector","length","tinyEditorId","attr","window","tinymce","get","submit","setFocus","document","ready","sortIcon","parent","selectAllQuestions","headerCheckbox","getElementById","require","checked","click","element","$element","rate","cmid","closest","find","post","M","cfg","wwwroot","save","studentquizquestionid","sesskey","$ratingStars","children","addClass","each"],"mappings":";;;;;;;AAyBAA,qCAAO,CAAC,WAAW,SAASC,SACjB,CACHC,WAAY,SAASC,YAAaC,gBAAiBC,gBAE3CC,eAAiBL,EAAE,kDAEvBK,eAAeC,IAAI,SAASC,GAAG,SAAS,WACpCC,UAAUC,SAGdJ,eAAeE,GAAG,YAAY,SAASG,GACjB,KAAdA,EAAEC,SAAgC,KAAdD,EAAEC,UACtBD,EAAEE,iBACFJ,UAAUC,UAKlBT,EAAE,oEAAoEM,IAAI,SAASC,GAAG,SAAS,eACvFM,MAAQb,EAAES,SAEVL,WAAY,KACRU,SAAWd,EAAE,gBAAgBe,SAAS,QACtCC,aAAehB,EAAE,kCAAkCe,SAAS,kBAE5Db,cAAgBY,UAChBd,EAAE,gDAAgDiB,YAAY,QAE9Dd,kBAAoBa,cACpBhB,EAAE,qDAAqDiB,YAAY,QAInEf,cAAgBY,SAEhBd,EAAE,8DAA8DkB,aAC7D,GAAIf,kBAAoBa,aAAc,OACnCG,mBAAqBnB,EACvB,wFAEEoB,sBAAwBpB,EAC1B,kFAEEqB,iBAAmBrB,EAAE,0CACvBmB,mBAAmBG,OAAS,EAE5BH,mBAAmBD,aAChB,GAAIE,sBAAsBE,OAAS,EAAG,OACnCC,aAAeF,iBAAiBG,KAAK,MAE3CC,OAAOC,QAAQC,IAAIJ,cAAcL,aAGjCG,iBAAiBH,gBAInBhB,cAAeY,UAAeX,kBAAmBa,gBACnDH,MAAMe,UACC,UAIXf,MAAMe,UACC,MAMnBC,SAAU,WACN7B,EAAE8B,UAAUC,OAAM,eACVC,SAAWhC,EAAE,gCACbgC,UACAA,SAASC,SAASf,YAM9BgB,mBAAoB,eACZC,eAAiBL,SAASM,eAAe,oBAE7CC,QAAQ,CAAC,4BAA4B,KAC5BF,eAAeG,SAChBH,eAAeI,sBAWtB/B,UAAUgC,aACXC,SAAWzC,EAAEwC,SACbE,KAAOD,SAASjB,KAAK,aACrBX,MAAQ4B,SAERE,KADaF,SAASG,QAAQ,QAAQC,KAAK,eACzBrB,KAAK,SAC3BxB,EAAE8C,KAAKC,EAAEC,IAAIC,QAAU,4BACnB,CACIC,KAAM,OACNP,KAAMA,KACNQ,sBAAuBV,SAASjB,KAAK,8BACrC4B,QAASL,EAAEC,IAAII,QACfV,KAAMA,OAEV,eACQW,aAAexC,MAAM+B,QAAQ,WAAWU,SAAS,QACrDD,aAAapC,YAAY,QACzBoC,aAAaE,SAAS,cACtBF,aAAaG,MAAK,WACVxD,EAAES,MAAMe,KAAK,cAAgBkB,OAC7B1C,EAAES,MAAMQ,YAAY,cACpBjB,EAAES,MAAM8C,SAAS,YAGzBvD,EAAE,gDAAgDuD,SAAS"} \ No newline at end of file diff --git a/amd/src/comment_area.js b/amd/src/comment_area.js index 8dc67b02..d20c7752 100644 --- a/amd/src/comment_area.js +++ b/amd/src/comment_area.js @@ -87,7 +87,10 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates ATTO: { CONTENT_WRAP: '.editor_atto_content_wrap', CONTENT: '.editor_atto_content', - TOOLBAR: '.editor_atto_toolbar' + TOOLBAR: '.editor_atto_toolbar', + }, + TINYMCE: { + CONTENT: '.tox-edit-area', }, COMMENT_ID: '#comment_', // Is used when server render. We need to collect some stored data attributes to load events. @@ -105,6 +108,17 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates ATTO_HTML_BUTTON: 'button.atto_html_button', POST_FOOTER: '.studentquiz-comment-postfooter' }, + EDITOR: { + ATTO: { + TYPE: 'atto', + }, + TINYMCE: { + TYPE: 'tiny', + }, + TEXTAREA: { + TYPE: 'textarea', + }, + }, get: function() { return { elementSelector: null, @@ -253,19 +267,24 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates // Interval to init atto editor, there are time when Atto's Javascript slow to init the editor, so we // check interval here to make sure the Atto is init before calling our script. var interval = setInterval(function() { - if (self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length !== 0) { self.bindEditorEvent(self.formSelector); isEditorLoaded = true; clearInterval(interval); M.util.js_complete(t.ACTION_EDITOR_INIT); - } }, 500); // If the editor has some content that has been restored // then check the editor content. var editorWaiting = setInterval(function() { if (isEditorLoaded) { - self.checkEditorContent(self.formSelector); + if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) { + const textareaSelector = self.formSelector.find(t.SELECTOR.TEXTAREA); + const tinyEditorId = textareaSelector.attr('id'); + const editor = window.tinyMCE.get(tinyEditorId); + self.checkEditorContent(self.formSelector, editor.getBody(), t.EDITOR.TINYMCE.TYPE); + } else { + self.checkEditorContent(self.formSelector); + } clearInterval(editorWaiting); } }, 1000); @@ -374,14 +393,20 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates // Clear form in setTimeout to prevent require message still shown when reset on Firefox. setTimeout(function() { // Clear form data. - formSelector.trigger('reset'); - // Clear atto editor data. - if (!formSelector.find('#id_editor_question_' + unique + 'editable').is(':visible')) { - // HTML mode. Switch back to normal mode. - self.elementSelector.find(t.SELECTOR.ATTO_HTML_BUTTON).trigger('click'); + if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) { + self.resetContent(formSelector, t.EDITOR.TINYMCE.TYPE); + } else if (self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length !== 0) { + self.formSelector.trigger('reset'); + // Clear atto editor data. + if (!formSelector.find('#id_editor_question_' + unique + 'editable').is(':visible')) { + // HTML mode. Switch back to normal mode. + self.elementSelector.find(t.SELECTOR.ATTO_HTML_BUTTON).trigger('click'); + } + formSelector.find('#id_editor_question_' + unique + 'editable').empty(); + formSelector.find(t.SELECTOR.TEXTAREA).trigger('change'); + } else { + self.resetContent(formSelector, t.EDITOR.TEXTAREA.TYPE); } - formSelector.find('#id_editor_question_' + unique + 'editable').empty(); - formSelector.find(t.SELECTOR.TEXTAREA).trigger('change'); M.util.js_complete(t.ACTION_CLEAR_FORM); }); var data = self.convertForTemplate(response, true); @@ -1327,17 +1352,49 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates }, /** - * Bind Atto event. + * Binds event handlers to the TinyMCE editor within the specified form. * - * @param {jQuery} formSelector + * This function sets up a periodic check to ensure that the TinyMCE editor + * is initialized. Once initialized, it binds a keydown event to the editor + * that triggers the `checkEditorContent` function, allowing content validation + * to occur each time the user types within the editor. + * + * @param {jQuery} formSelector - The jQuery object representing the form containing the TinyMCE editor. */ - bindEditorEvent: function(formSelector) { + bindHandleTinyEditor: function(formSelector) { var self = this; - M.util.js_pending('init_editor'); + const textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA); + const tinyEditorId = textareaSelector.attr('id'); + const intervalID = setInterval(function() { + if (window.tinyMCE) { + const editor = window.tinyMCE.get(tinyEditorId); + editor.on("input", function() { + self.checkEditorContent(formSelector, editor.getBody(), t.EDITOR.TINYMCE.TYPE); + }); + // This event will be triggered when user paste content. + editor.on("change", function() { + self.checkEditorContent(formSelector, editor.getBody(), t.EDITOR.TINYMCE.TYPE); + }); + clearInterval(intervalID); + } + }, 100); + }, + /** + * Binds event handlers and initializes the Atto editor within the specified form. + * + * This function sets up the Atto editor by triggering initial placeholder settings, + * displaying the toolbar, and setting up event listeners for content changes. It uses + * a MutationObserver to monitor changes within the Atto editor, allowing dynamic + * validation of the editor content. It also sets up a periodic check to handle draft content. + * + * @param {jQuery} formSelector - The jQuery object representing the form containing the Atto editor. + */ + bindHandleAttoEditor: function(formSelector) { + var self = this; + M.util.js_pending('init_editor'); self.triggerAttoNoContent(formSelector); self.setPlaceholder(formSelector, formSelector.attr('data-textarea-placeholder')); - formSelector.find(t.SELECTOR.ATTO.TOOLBAR).fadeIn(); var textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA); var attoEditableId = textareaSelector.attr('id') + 'editable'; @@ -1366,6 +1423,66 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates }, 5000); }, + /** + * Binds event handlers to a standard textarea editor within the specified form. + * + * This function sets up an input event listener on the textarea to monitor changes + * in its content. Whenever the user types or modifies the content, the `checkEditorContent` + * function is triggered to validate the editor's content based on the textarea type. + * + * @param {jQuery} formSelector - The jQuery object representing the form containing the textarea editor. + */ + bindHandleTextareaEditor: function(formSelector) { + var self = this; + const textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA); + if (textareaSelector) { + textareaSelector.on('input', function() { + self.checkEditorContent(formSelector, textareaSelector[0], + t.EDITOR.TEXTAREA.TYPE); + }); + } + }, + + /** + * Bind event handlers for the text editor in the form. + * This function checks the form for the presence of different types of text editors. + * It supports TinyMCE, Atto, or a basic textarea. + * Based on the editor found, it calls the appropriate handler function to bind the necessary events. + * + * @param {jQuery} formSelector - A jQuery selector representing the form containing the editor. + */ + bindEditorEvent: function(formSelector) { + var self = this; + if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) { + self.bindHandleTinyEditor(formSelector); + } else if (self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length !== 0) { + self.bindHandleAttoEditor(formSelector); + } else { + self.bindHandleTextareaEditor(formSelector); + } + }, + + /** + * Resets the content of the editor within the specified form to an empty state. + * + * This function checks the type of the editor (TinyMCE or standard textarea) and clears + * its content accordingly. For TinyMCE editors, it uses the `setContent` method to clear + * the content. For standard textareas, it directly sets the value to an empty string. + * + * @param {jQuery} formSelector - The jQuery object representing the form containing the editor. + * @param {string} type - The type of the editor, which can be TinyMCE or textarea. + */ + resetContent: function(formSelector, type) { + const textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA); + if (type === t.EDITOR.TINYMCE.TYPE) { + const tinyEditorId = textareaSelector.attr('id'); + const editor = window.tinyMCE.get(tinyEditorId); + editor.setContent(''); + } else { + textareaSelector[0].value = ''; + } + }, + /** * Check if element is empty. * @@ -1654,29 +1771,55 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates * Check editor content. * * @param {jQuery} formSelector + * @param {jQuery} bodyContent The body content of the editor. + * @param {string} type The type of editor. ex: 'tiny', 'atto', 'textarea'. */ - checkEditorContent: function(formSelector) { - var key = 'text_change_' + Date.now(); + checkEditorContent: function(formSelector, bodyContent = null, type = '') { + const key = 'text_change_' + Date.now(); M.util.js_pending(key); - var textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA); - var attoEditableId = textareaSelector.attr('id') + 'editable'; - var attoEditableEle = $('#' + attoEditableId); - // This regex will match if the editor have some special cases. + const textareaSelector = formSelector.find(t.SELECTOR.TEXTAREA); + let editorBodyContent; + let contenthtml; + let condition; + + // Handle different editor types. + switch (type) { + case t.EDITOR.TINYMCE.TYPE: + editorBodyContent = $(bodyContent); + contenthtml = editorBodyContent.html(); + condition = t.EMPTY_CONTENT.indexOf(contenthtml) > -1 || + editorBodyContent.text().trim().length < 1; + break; + case t.EDITOR.ATTO.TYPE: + case '': + editorBodyContent = $('#' + textareaSelector.attr('id') + 'editable'); + contenthtml = editorBodyContent.html(); + condition = t.EMPTY_CONTENT.indexOf(contenthtml) > -1 || + editorBodyContent.text().trim().length < 1; + break; + case t.EDITOR.TEXTAREA.TYPE: + editorBodyContent = $(bodyContent); + contenthtml = editorBodyContent[0].value; + condition = contenthtml.trim().length < 1; + break; + } + + // This regex will match if the editor has some special cases. // 1)


. // 2)


. - // The cases are consider empty in the editor. + // The cases are considered empty in the editor. const regex = /^(<(?:p)[^>]*>)+(
)?(<\/p>)+$/; - const match = regex.exec(attoEditableEle.html()); - if (t.EMPTY_CONTENT.indexOf(attoEditableEle.html()) > -1 || - attoEditableEle.text().trim().length < 1) { - // On initial load, attoEditableEle.html() contains

or . + const match = regex.exec(contenthtml); + + // Check the condition and set the placeholder or trigger appropriate events + if (condition) { + // On initial load, contenthtml contains

or . // If it matches the regex meaning the textarea is empty. - if (match || (t.EMPTY_CONTENT.indexOf(attoEditableEle.html()) > -1)) { - this.setPlaceholder(formSelector, formSelector.attr('data-textarea-placeholder')); - } else { - this.setPlaceholder(formSelector, ''); - } + const isEmptyContent = match || + t.EMPTY_CONTENT.indexOf(contenthtml) > -1; + this.setPlaceholder(formSelector, isEmptyContent ? + formSelector.attr('data-textarea-placeholder') : ''); this.triggerAttoNoContent(formSelector); } else { this.setPlaceholder(formSelector, ''); diff --git a/amd/src/studentquiz.js b/amd/src/studentquiz.js index 3f93582a..4d04a5a4 100644 --- a/amd/src/studentquiz.js +++ b/amd/src/studentquiz.js @@ -60,8 +60,24 @@ define(['jquery'], function($) { // Set focus to the first star. $('.studentquiz_behaviour .rate .rating .rateable:first-child').focus(); } else if (forcecommenting && !hascommented) { - // Set focus to atto editor. - $('.studentquiz_behaviour .comments .studentquiz-comment-container .editor_atto_content').focus(); + const attoEditorSelector = $( + '.studentquiz_behaviour .comments .studentquiz-comment-container .editor_atto_content' + ); + const tinyMceEditorSelector = $( + '.studentquiz_behaviour .comments .studentquiz-comment-container .tox-edit-area' + ); + const textareaSelector = $('textarea[id^="id_editor_question_"]'); + if (attoEditorSelector.length > 0) { + // Set focus to atto editor. + attoEditorSelector.focus(); + } else if (tinyMceEditorSelector.length > 0) { + const tinyEditorId = textareaSelector.attr('id'); + // Set focus to TinyMCE editor. + window.tinymce.get(tinyEditorId).focus(); + } else { + // Set focus to textarea. + textareaSelector.focus(); + } } if ((!forcerating || hasrated) && (!forcecommenting || hascommented)) { diff --git a/classes/commentarea/form/comment_simple_editor.php b/classes/commentarea/form/comment_simple_editor.php index f59054b1..372c6305 100644 --- a/classes/commentarea/form/comment_simple_editor.php +++ b/classes/commentarea/form/comment_simple_editor.php @@ -49,13 +49,6 @@ class comment_simple_editor extends MoodleQuickForm_editor { * @param array $options - Options of element. */ public function __construct($elementname = null, $elementlabel = null, $attributes = [], $options = []) { - global $CFG; - - // HACK: There is no way to correctly force a specific editor. Unfortunately the comment area is highly - // specific to the atto editor, both here in php and javascript code. We temporarely fake the atto to be the - // only available editor. ref: https://github.com/frankkoch/moodle-mod_studentquiz/issues/283. - $CFG->texteditors = 'atto'; - $attributes = array_merge($attributes, self::ATTRIBUTES); $options = array_merge($options, self::OPTIONS); $this->_options['atto:toolbar'] = get_config('studentquiz', 'comment_editor_toolbar'); diff --git a/tests/behat/comment_area_create.feature b/tests/behat/comment_area_create.feature index f566d666..0ae5828c 100644 --- a/tests/behat/comment_area_create.feature +++ b/tests/behat/comment_area_create.feature @@ -47,7 +47,7 @@ Feature: Create comment as an user # Wait for comment area init. And I wait until the page is ready # Enter "Comment 1". - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(1)" "css_element" exists @@ -55,35 +55,35 @@ Feature: Create comment as an user # Wait for different created time. And I wait "1" seconds # Enter "Comment 2" - And I enter the text "Comment 2" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 2" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(2)" "css_element" exists And I should see "Comment 2" in the ".studentquiz-comment-item:nth-child(2) .studentquiz-comment-text" "css_element" And I wait "1" seconds # Enter "Comment 3" - And I enter the text "Comment 3" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 3" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(3)" "css_element" exists And I should see "Comment 3" in the ".studentquiz-comment-item:nth-child(3) .studentquiz-comment-text" "css_element" And I wait "1" seconds # Enter "Comment 4" - And I enter the text "Comment 4" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 4" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(4)" "css_element" exists And I should see "Comment 4" in the ".studentquiz-comment-item:nth-child(4) .studentquiz-comment-text" "css_element" And I wait "1" seconds # Enter "Comment 5" - And I enter the text "Comment 5" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 5" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(5)" "css_element" exists And I should see "Comment 5" in the ".studentquiz-comment-item:nth-child(5) .studentquiz-comment-text" "css_element" And I wait "1" seconds # Enter "Comment 6" - And I enter the text "Comment 6" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 6" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(6)" "css_element" exists @@ -144,7 +144,7 @@ Feature: Create comment as an user And I press "Check" # Wait for comment area init. And I wait until the page is ready - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(1)" "css_element" exists @@ -153,7 +153,7 @@ Feature: Create comment as an user And I click on "Reply" "button" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-commands-buttons" "css_element" # Wait for reply init. And I wait until the page is ready - And I enter the text "Reply comment 1" into the "Add reply" editor + And I set the field "Add reply" to "Reply comment 1" And I press "Add reply" And I wait until the page is ready And I should see "1" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-totalreply" "css_element" @@ -165,7 +165,7 @@ Feature: Create comment as an user And I click on "Start Quiz" "button" And I set the field "True" to "1" And I press "Check" - And I enter the text "Comment 1 with long content: simply dummy text of the printing and typesetting industry." into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1 with long content: simply dummy text of the printing and typesetting industry." And I press "Add comment" And I press "Collapse all comments" Then I should see "Comment 1 with long content: simply dummy text of the printing ..." @@ -179,7 +179,7 @@ Feature: Create comment as an user And I press "Check" # Wait for comment area init. And I wait until the page is ready - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(1)" "css_element" exists @@ -206,7 +206,7 @@ Feature: Create comment as an user And I wait until the page is ready And I press "Finish" And I should see "Please comment" - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until the page is ready And I press "Finish" @@ -221,7 +221,7 @@ Feature: Create comment as an user And I press "Check" # Wait for comment area init. And I wait until the page is ready - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(1)" "css_element" exists @@ -252,7 +252,7 @@ Feature: Create comment as an user And I set the field "True" to "1" And I press "Check" And I wait until the page is ready - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(1)" "css_element" exists @@ -298,7 +298,7 @@ Feature: Create comment as an user And I press "id_submitbutton" And I choose "Preview" action for "Question of Student 1" in the question bank And I switch to "questionpreview" window - And I enter the text "Approved the question" into the "Add private comment (these are between the student and tutor only)" editor + And I set the field "Add private comment (these are between the student and tutor only)" to "Approved the question" And I press "Add comment" And I am on the "StudentQuiz 3" "mod_studentquiz > View" page logged in as "teacher" And I choose "Preview" action for "Question of Student 1" in the question bank @@ -319,7 +319,7 @@ Feature: Create comment as an user And I set the field "True" to "1" And I press "Check" And I wait until the page is ready - And I enter the text "Comment 2" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 2" And I press "Add comment" And I wait until the page is ready And I log out @@ -329,7 +329,7 @@ Feature: Create comment as an user And I set the field "True" to "1" And I press "Check" And I wait until the page is ready - And I enter the text "Comment 3" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 3" And I press "Add comment" And I wait until the page is ready And I log out @@ -339,7 +339,7 @@ Feature: Create comment as an user And I set the field "True" to "1" And I press "Check" And I wait until the page is ready - And I enter the text "Comment 4" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 4" And I press "Add comment" And I wait until the page is ready And I log out @@ -349,7 +349,7 @@ Feature: Create comment as an user And I set the field "True" to "1" And I press "Check" And I wait until the page is ready - And I enter the text "Comment 5" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 5" And I press "Add comment" And I wait until the page is ready And I log out @@ -361,10 +361,10 @@ Feature: Create comment as an user And I set the field "True" to "1" And I press "Check" And I wait until the page is ready - And I enter the text "Comment 6" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 6" And I press "Add comment" And I wait "1" seconds - And I enter the text "Comment 7" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 7" And I press "Add comment" And I wait until the page is ready And I log out @@ -442,7 +442,7 @@ Feature: Create comment as an user And I set the field "True" to "1" And I press "Check" And I wait until the page is ready - And I enter the text "Comment test user 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment test user 1" And I press "Add comment" And I wait until the page is ready And I should see "Date" in the ".studentquiz-comment-filters" "css_element" @@ -451,7 +451,12 @@ Feature: Create comment as an user @javascript Scenario: Test placeholder display after click Add comment. - When I am on the "StudentQuiz 1" "mod_studentquiz > View" page logged in as "admin" + Given I log in as "admin" + And I follow "Preferences" in the user menu + And I follow "Editor preferences" + And I set the field "Text editor" to "Atto HTML editor" + And I press "Save changes" + When I am on the "StudentQuiz 1" "mod_studentquiz > View" page And I click on "Start Quiz" "button" And I set the field "True" to "1" And I press "Check" @@ -478,7 +483,7 @@ Feature: Create comment as an user And I press "Check" # Wait for comment area init. And I wait until the page is ready - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(1)" "css_element" exists @@ -489,7 +494,7 @@ Feature: Create comment as an user # Try to edit. And I click on "Edit" "button" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-commands-box" "css_element" And I wait until the page is ready - And I enter the text "Comment 1 edited" into the "Edit comment" editor + And I set the field "Edit comment" to "Comment 1 edited" And I press "Save changes" And I wait until the page is ready And I should see "Comment 1 edited" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-text" "css_element" @@ -503,7 +508,7 @@ Feature: Create comment as an user And I click on "Reply" "button" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-commands-buttons" "css_element" # Wait for reply init. And I wait until the page is ready - And I enter the text "Reply comment 1" into the "Add reply" editor + And I set the field "Add reply" to "Reply comment 1" And I press "Add reply" And I wait until the page is ready And I should see "Reply comment 1" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-replies .studentquiz-comment-item:nth-child(1) .studentquiz-comment-text" "css_element" @@ -513,7 +518,7 @@ Feature: Create comment as an user # Try to edit reply. And I click on "Edit" "button" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-replies .studentquiz-comment-item:nth-child(1) .studentquiz-comment-commands-box" "css_element" And I wait until the page is ready - And I enter the text "Reply comment 1 edited" into the "Edit comment" editor + And I set the field "Edit comment" to "Reply comment 1 edited" And I press "Save changes" And I wait until the page is ready And I should see "Reply comment 1 edited" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-replies .studentquiz-comment-item:nth-child(1) .studentquiz-comment-text" "css_element" @@ -559,7 +564,7 @@ Feature: Create comment as an user # Wait for comment area init. And I wait until the page is ready # Try to comment. - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until the page is ready And I wait until ".studentquiz-comment-item:nth-child(1)" "css_element" exists diff --git a/tests/behat/comment_area_private_comment.feature b/tests/behat/comment_area_private_comment.feature index 56cea0ff..2b2c2380 100644 --- a/tests/behat/comment_area_private_comment.feature +++ b/tests/behat/comment_area_private_comment.feature @@ -33,7 +33,7 @@ Feature: As a user I can add private comment and view private comment in my own And I press "id_submitbutton" And I choose "Preview" action for "Question 1" in the question bank And I switch to "questionpreview" window - And I enter the text "Submitted for approval" into the "Add private comment (these are between the student and tutor only)" editor + And I set the field "Add private comment (these are between the student and tutor only)" to "Submitted for approval" And I press "Add comment" Then I should see "Submitted for approval" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-text" "css_element" And I click on "Public comments" "link" @@ -47,7 +47,7 @@ Feature: As a user I can add private comment and view private comment in my own And I choose "Preview" action for "Question 1" in the question bank And I switch to "questionpreview" window And I should see "Submitted for approval" - And I enter the text "A private comment from teacher" into the "Add private comment (these are between the student and tutor only)" editor + And I set the field "Add private comment (these are between the student and tutor only)" to "A private comment from teacher" And I press "Add comment" And I switch to the main window And I log out @@ -60,7 +60,7 @@ Feature: As a user I can add private comment and view private comment in my own And I should see "Rating and public commenting are not available for your own question in Preview mode." And I click on "Private comments" "link" And I should see "A private comment from teacher" - And I enter the text "Updated for approval again" into the "Add private comment (these are between the student and tutor only)" editor + And I set the field "Add private comment (these are between the student and tutor only)" to "Updated for approval again" And I press "Add comment" And I switch to the main window And I reload the page @@ -72,7 +72,7 @@ Feature: As a user I can add private comment and view private comment in my own And I choose "Preview" action for "Question 1" in the question bank And I switch to "questionpreview" window And I should see "Updated for approval again" - And I enter the text "Approved the question" into the "Add private comment (these are between the student and tutor only)" editor + And I set the field "Add private comment (these are between the student and tutor only)" to "Approved the question" And I press "Add comment" And I set the field "statetype" to "Approved" And I click on "Change state" "button" @@ -94,7 +94,7 @@ Feature: As a user I can add private comment and view private comment in my own And I click on "Start Quiz" "button" And I set the field "True" to "1" And I press "Check" - And I enter the text "Public comment of student 2" into the "Add public comment" editor + And I set the field "Add public comment" to "Public comment of student 2" And I press "Add comment" And I log out And I am on the "StudentQuiz 1" "mod_studentquiz > View" page logged in as "student1" diff --git a/tests/behat/group_separate_groups.feature b/tests/behat/group_separate_groups.feature index e2599d48..2d8d1fe2 100644 --- a/tests/behat/group_separate_groups.feature +++ b/tests/behat/group_separate_groups.feature @@ -153,7 +153,7 @@ Feature: Students can create questions and practice in separate groups. And I click on "Start Quiz" "button" And I set the field "True" to "1" And I press "Check" - And I enter the text "Comment 1" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment 1" And I press "Add comment" And I wait until ".studentquiz-comment-item:nth-child(1)" "css_element" exists Then I should see "Comment 1" in the ".studentquiz-comment-item:nth-child(1) .studentquiz-comment-text" "css_element" diff --git a/tests/behat/preview_question.feature b/tests/behat/preview_question.feature index 054f820f..bdb7a5f0 100644 --- a/tests/behat/preview_question.feature +++ b/tests/behat/preview_question.feature @@ -34,7 +34,7 @@ Feature: Preview a question as a student And I press "Check" And I wait until the page is ready And the state of "What is pi to two d.p.?" question is shown as "Correct" - And I enter the text "Very good question" into the "Add public comment" editor + And I set the field "Add public comment" to "Very good question" And I press "Add comment" And I wait until the page is ready And I should see "Very good question" @@ -83,7 +83,7 @@ Feature: Preview a question as a student When I choose "Preview" action for "Example question 2" in the question bank And I switch to "questionpreview" window And "Add public comment" "field" should exist - And I enter the text "Comment test" into the "Add public comment" editor + And I set the field "Add public comment" to "Comment test" And I press "Add comment" And I wait until the page is ready And I should see "Comment test"