diff --git a/amd/build/comment_area.min.js b/amd/build/comment_area.min.js index bc55644e..d1261aa8 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"},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})); +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.isusingtinymce=params.isusingtinymce,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(){var _window,_window$tinyMCE,_self$formSelector$fi,_self$formSelector,_self$formSelector$fi2,editor=self.isusingtinymce?null===(_window=window)||void 0===_window||null===(_window$tinyMCE=_window.tinyMCE)||void 0===_window$tinyMCE?void 0:_window$tinyMCE.get(null!==(_self$formSelector$fi=null===(_self$formSelector=self.formSelector)||void 0===_self$formSelector||null===(_self$formSelector$fi2=_self$formSelector.find(t.SELECTOR.TEXTAREA))||void 0===_self$formSelector$fi2?void 0:_self$formSelector$fi2.attr("id"))&&void 0!==_self$formSelector$fi?_self$formSelector$fi:""):null;(!self.isusingtinymce||editor&&null!=editor&&editor.getBody())&&(self.bindEditorEvent(self.formSelector),isEditorLoaded=!0,clearInterval(interval),M.util.js_complete(t.ACTION_EDITOR_INIT))}),500),editorWaiting=setInterval((function(){if(isEditorLoaded){if(self.isusingtinymce){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(){self.isusingtinymce?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("SetContent",(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){this.isusingtinymce?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 37d42f61..c6336976 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 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 +{"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 self.isusingtinymce = params.isusingtinymce;\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 // Check whether TinyMCE is being used and wait for the editor to fully load.\n var editor = self.isusingtinymce ?\n window?.tinyMCE?.get(\n self.formSelector?.find(t.SELECTOR.TEXTAREA)?.attr('id') ?? '') : null;\n if (!self.isusingtinymce || (editor && editor?.getBody())) {\n // Binding events to the editor.\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 if (self.isusingtinymce) {\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.isusingtinymce) {\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 // Disable the reply button by default because the reply content is always empty at the beginning.\n self.triggerAttoNoContent(fragmentForm);\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 // It is necessary to check the SetContent event from the start because:\n // When the editor content is empty, the tiny_autosave_update_session service is called.\n // If there is existing restore data, it will be set in the editor, but this action won't trigger\n // the \"input\" event. Therefore, we need to use the SetContent event in this case.\n // The SetContent event is also triggered during undo and redo actions.\n editor.on(\"SetContent\", 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.isusingtinymce) {\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","isusingtinymce","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","editor","_window","tinyMCE","_window$tinyMCE","_self$formSelector","_self$formSelector$fi2","attr","getBody","bindEditorEvent","clearInterval","editorWaiting","tinyEditorId","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","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","triggerAttoNoContent","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","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/src/comment_area.js b/amd/src/comment_area.js index d20c7752..a07853ce 100644 --- a/amd/src/comment_area.js +++ b/amd/src/comment_area.js @@ -194,6 +194,7 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates self.canViewDeleted = params.canviewdeleted; self.isNoComment = params.isnocomment; self.allowSelfCommentRating = params.allowselfcommentrating; + self.isusingtinymce = params.isusingtinymce; self.initServerRender(); if (params.allowselfcommentrating) { @@ -267,17 +268,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() { + // Check whether TinyMCE is being used and wait for the editor to fully load. + var editor = self.isusingtinymce ? + window?.tinyMCE?.get( + self.formSelector?.find(t.SELECTOR.TEXTAREA)?.attr('id') ?? '') : null; + if (!self.isusingtinymce || (editor && editor?.getBody())) { + // Binding events to the editor. 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) { - if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) { + if (self.isusingtinymce) { const textareaSelector = self.formSelector.find(t.SELECTOR.TEXTAREA); const tinyEditorId = textareaSelector.attr('id'); const editor = window.tinyMCE.get(tinyEditorId); @@ -393,7 +401,7 @@ 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. - if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) { + if (self.isusingtinymce) { self.resetContent(formSelector, t.EDITOR.TINYMCE.TYPE); } else if (self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length !== 0) { self.formSelector.trigger('reset'); @@ -1087,6 +1095,8 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates var textFragmentFormId = '#id_editor_question_' + self.studentQuizQuestionId + '_' + self.type + '_' + item.id + 'editable'; fragmentForm.find(textFragmentFormId).focus(); + // Disable the reply button by default because the reply content is always empty at the beginning. + self.triggerAttoNoContent(fragmentForm); self.bindFragmentFormEvent(fragmentForm, item); M.util.js_complete(t.ACTION_LOAD_FRAGMENT_FORM); }); @@ -1371,6 +1381,14 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates editor.on("input", function() { self.checkEditorContent(formSelector, editor.getBody(), t.EDITOR.TINYMCE.TYPE); }); + // It is necessary to check the SetContent event from the start because: + // When the editor content is empty, the tiny_autosave_update_session service is called. + // If there is existing restore data, it will be set in the editor, but this action won't trigger + // the "input" event. Therefore, we need to use the SetContent event in this case. + // The SetContent event is also triggered during undo and redo actions. + editor.on("SetContent", 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); @@ -1453,7 +1471,7 @@ define(['jquery', 'core/str', 'core/ajax', 'core/modal_factory', 'core/templates */ bindEditorEvent: function(formSelector) { var self = this; - if (self.formSelector.find(t.SELECTOR.TINYMCE.CONTENT).length !== 0) { + if (self.isusingtinymce) { self.bindHandleTinyEditor(formSelector); } else if (self.formSelector.find(t.SELECTOR.ATTO.CONTENT).length !== 0) { self.bindHandleAttoEditor(formSelector); diff --git a/renderer.php b/renderer.php index 1e4569db..91a3bad3 100644 --- a/renderer.php +++ b/renderer.php @@ -2383,7 +2383,8 @@ public function render_comment_area($studentquizquestion, $userid, $highlight = $jsdata = array_merge($jsdata, [ 'count' => $count, 'total' => $total, - 'allowselfcommentrating' => $allowselfcommentrating + 'allowselfcommentrating' => $allowselfcommentrating, + 'isusingtinymce' => editors_get_preferred_editor() instanceof \editor_tiny\editor, ]); $this->page->requires->js_call_amd(self::MODNAME . '/comment_area', 'generate', [$jsdata]);