From 3c2ae9b016cc2529374a4f717d276545709beb1c Mon Sep 17 00:00:00 2001 From: Hieu Nguyen Dang Date: Mon, 20 May 2024 16:46:20 +0700 Subject: [PATCH] StudentQuiz: The marker bullets are overlapping #787208 with the placeholder text in the comment textbox --- amd/build/comment_area.min.js | 2 +- amd/build/comment_area.min.js.map | 2 +- amd/src/comment_area.js | 33 ++++++++++++++++++++++++++----- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/amd/build/comment_area.min.js b/amd/build/comment_area.min.js index 4c5627a9..ba865699 100644 --- a/amd/build/comment_area.min.js +++ b/amd/build/comment_area.min.js @@ -5,6 +5,6 @@ * @copyright 2020 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("mod_studentquiz/comment_area",["jquery","core/str","core/ajax","core/modal_factory","core/templates","core/fragment","core/modal_events"],(function($,str,ajax,ModalFactory,Templates,fragment,ModalEvents){var t={EMPTY_CONTENT:["


","


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


', '


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


', '


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


.\n // 2)


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

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


. + // 2)


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

or . + // If it matches the regex meaning the textarea is empty. + if (match || (t.EMPTY_CONTENT.indexOf(attoEditableEle.html()) > -1)) { + this.setPlaceholder(formSelector, formSelector.attr('data-textarea-placeholder')); + } else { + this.setPlaceholder(formSelector, ''); + } this.triggerAttoNoContent(formSelector); } else { + this.setPlaceholder(formSelector, ''); this.triggerAttoHasContent(formSelector); } M.util.js_complete(key);