diff --git a/.upgradenotes/MDL-83469-2025032005165303.yml b/.upgradenotes/MDL-83469-2025032005165303.yml new file mode 100644 index 00000000000..b8c5b6d1ab1 --- /dev/null +++ b/.upgradenotes/MDL-83469-2025032005165303.yml @@ -0,0 +1,14 @@ +issueNumber: MDL-83469 +notes: + core_courseformat: + - message: >- + The core_courseformat_create_module web service has been deprecated. + Please use core_courseformat_new_module as its replacement. + type: deprecated + - message: >- + The state mutation addModule, primarily used for creating mod_subsection + instances, has been deprecated. Replace it with newModule. Additionally, + all course formats using links with data-action="addModule" must be updated + to use data-action="newModule" and include a data-sectionid attribute + specifying the target section ID. + type: deprecated diff --git a/course/amd/build/actions.min.js b/course/amd/build/actions.min.js index 895c3f0b3f8..5a9c8deadab 100644 --- a/course/amd/build/actions.min.js +++ b/course/amd/build/actions.min.js @@ -8,6 +8,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.3 */ -define("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_copy_to_clipboard","core/modal_save_cancel","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core/local/inplace_editable/events","core_course/events"],(function($,ajax,templates,notification,str,url,Y,ModalCopyToClipboard,ModalSaveCancel,ModalEvents,KeyCodes,log,editor,EventDispatcher,InplaceEditableEvents,CourseEvents){log.debug("The course/actions module is deprecated. Please, add support_components to your course format.");const componentActions=["moveSection","moveCm","addSection","deleteSection","cmDelete","cmDuplicate","sectionHide","sectionShow","cmHide","cmShow","cmStealth","sectionHighlight","sectionUnhighlight","cmMoveRight","cmMoveLeft","cmNoGroups","cmVisibleGroups","cmSeparateGroups","addModule"],courseeditor=editor.getCurrentCourseEditor();let formatname;var CSS_EDITINPROGRESS="editinprogress",CSS_EDITINGMOVE="editing_move",SELECTOR={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",SECTIONACTIONMENUTRIGGER:".section-actions",SECTIONITEM:'[data-for="section_title"]',ADDSECTIONS:".changenumsections [data-add-sections]",SECTIONBADGES:'[data-region="sectionbadges"]'};Y.use("moodle-course-coursebase",(function(){var courseformatselector=M.course.format.get_section_selector();courseformatselector&&(SELECTOR.SECTIONLI=courseformatselector)}));const dispatchEvent=function(eventName,detail,container,options){return container instanceof Element||void 0===container.get||(container=container.get(0)),EventDispatcher.dispatchEvent(eventName,detail,container,options)};var getModuleId=function(element){const item=element.get(0);if(item.dataset.id)return item.dataset.id;let id;return Y.use("moodle-course-util",(function(Y){id=Y.Moodle.core_course.util.cm.getId(Y.Node(item))})),id},addActivitySpinner=function(activity){activity.addClass(CSS_EDITINPROGRESS);var actionarea=activity.find(SELECTOR.ACTIONAREA).get(0);if(actionarea){var spinner=M.util.add_spinner(Y,Y.Node(actionarea));return spinner.show(),void 0!==activity.data("id")&&courseeditor.dispatch("cmLock",[activity.data("id")],!0),spinner}return null},addSectionSpinner=function(sectionelement){sectionelement.addClass(CSS_EDITINPROGRESS);var actionarea=sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);if(actionarea){var spinner=M.util.add_spinner(Y,Y.Node(actionarea));return spinner.show(),void 0!==sectionelement.data("id")&&courseeditor.dispatch("sectionLock",[sectionelement.data("id")],!0),spinner}return null},addSectionLightbox=function(sectionelement){const item=sectionelement.get(0);var lightbox=M.util.add_lightbox(Y,Y.Node(item));return"section"==item.dataset.for&&item.dataset.id&&(courseeditor.dispatch("sectionLock",[item.dataset.id],!0),lightbox.setAttribute("data-state","section"),lightbox.setAttribute("data-state-id",item.dataset.id)),lightbox.show(),lightbox},removeSpinner=function(element,spinner,delay){window.setTimeout((function(){if(element.removeClass(CSS_EDITINPROGRESS),spinner&&spinner.hide(),void 0!==element.data("id")){const mutation="section"===element.data("for")?"sectionLock":"cmLock";courseeditor.dispatch(mutation,[element.data("id")],!1)}}),delay)},removeLightbox=function(lightbox,delay){lightbox&&window.setTimeout((function(){lightbox.hide(),lightbox.getAttribute("data-state")&&courseeditor.dispatch("".concat(lightbox.getAttribute("data-state"),"Lock"),[lightbox.getAttribute("data-state-id")],!1)}),delay)},initActionMenu=function(elementid){Y.use("moodle-course-coursebase",(function(){M.course.coursebase.invoke_function("setup_for_resource","#"+elementid)})),M.core.actionmenu&&M.core.actionmenu.newDOMNode&&M.core.actionmenu.newDOMNode(Y.one("#"+elementid))},editModule=function(moduleElement,cmid,target){var lightbox,action=target.attr("data-action"),spinner=addActivitySpinner(moduleElement),promises=ajax.call([{methodname:"core_course_edit_module",args:{id:cmid,action:action,sectionreturn:target.attr("data-sectionreturn")?target.attr("data-sectionreturn"):null}}],!0);"duplicate"===action&&(lightbox=addSectionLightbox(target.closest(SELECTOR.SECTIONLI))),$.when.apply($,promises).done((function(data){var mainElement,tabables,isInside,foundElement,elementToFocus=(mainElement=moduleElement,tabables=$("a:visible"),isInside=!1,foundElement=null,tabables.each((function(){if($.contains(mainElement[0],this))isInside=!0;else if(isInside)return foundElement=this,!1;return!0})),foundElement);moduleElement.replaceWith(data);let affectedids=[];$("
"+data+"
").find(SELECTOR.ACTIVITYLI).each((function(index){initActionMenu($(this).attr("id")),0===index&&(!function(elementId,action){var mainelement=$("#"+elementId),selector="[data-action="+action+"]";"groupsseparate"!==action&&"groupsvisible"!==action&&"groupsnone"!==action||(selector="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"),mainelement.find(selector).is(":visible")?mainelement.find(selector).focus():mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus()}($(this).attr("id"),action),elementToFocus=null),affectedids.push(getModuleId($(this)))})),elementToFocus&&elementToFocus.focus(),removeSpinner(moduleElement,spinner,400),removeLightbox(lightbox,400),moduleElement.trigger($.Event("coursemoduleedited",{ajaxreturn:data,action:action})),courseeditor.dispatch("legacyActivityAction",action,cmid,affectedids)})).fail((function(ex){removeSpinner(moduleElement,spinner),removeLightbox(lightbox);var e=$.Event("coursemoduleeditfailed",{exception:ex,action:action});moduleElement.trigger(e),e.isDefaultPrevented()||notification.exception(ex)}))},refreshModule=function(element,cmid,sectionreturn){void 0===sectionreturn&&(sectionreturn=courseeditor.sectionReturn);const activityElement=$(element);var spinner=addActivitySpinner(activityElement),promises=ajax.call([{methodname:"core_course_get_module",args:{id:cmid,sectionreturn:sectionreturn}}],!0);return new Promise(((resolve,reject)=>{$.when.apply($,promises).done((function(data){removeSpinner(activityElement,spinner,400),replaceActivityHtmlWith(data),resolve(data)})).fail((function(){removeSpinner(activityElement,spinner),reject()}))}))},confirmDeleteModule=function(mainelement,onconfirm){var modtypename=mainelement.attr("class").match(/modtype_([^\s]*)/)[1],modulename=function(element){var name;Y.use("moodle-course-util",(function(Y){name=Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)))}));const state=courseeditor.state,cmid=getModuleId(element);var _state$cm$get;return!name&&state&&cmid&&(name=null===(_state$cm$get=state.cm.get(cmid))||void 0===_state$cm$get?void 0:_state$cm$get.name),name}(mainelement);str.get_string("pluginname",modtypename).done((function(pluginname){var plugindata={type:pluginname,name:modulename};str.get_strings([{key:"confirm",component:"core"},{key:null===modulename?"deletechecktype":"deletechecktypename",param:plugindata},{key:"yes"},{key:"no"}]).done((function(s){notification.confirm(s[0],s[1],s[2],s[3],onconfirm)}))}))},replaceActionItem=function(actionitem,image,stringname,stringcomponent,newaction){var stringRequests=[{key:stringname,component:stringcomponent}];return str.get_strings(stringRequests).then((function(strings){return actionitem.find("span.menu-action-text").html(strings[0]),templates.renderPix(image,"core")})).then((function(pixhtml){actionitem.find(".icon").replaceWith(pixhtml),actionitem.attr("data-action",newaction)})).catch(notification.exception)},defaultEditSectionHandler=function(sectionElement,actionItem,data,courseformat,sectionid){var action=actionItem.attr("data-action");if("hide"===action||"show"===action){if("hide"===action?(sectionElement.addClass("hidden"),setSectionBadge(sectionElement[0],"hiddenfromstudents",!0,!1),replaceActionItem(actionItem,"i/show","showfromothers","format_"+courseformat,"show")):(setSectionBadge(sectionElement[0],"hiddenfromstudents",!1,!1),sectionElement.removeClass("hidden"),replaceActionItem(actionItem,"i/hide","hidefromothers","format_"+courseformat,"hide")),void 0!==data.modules)for(var i in data.modules)replaceActivityHtmlWith(data.modules[i]);void 0!==data.section_availability&§ionElement.find(".section_availability").first().replaceWith(data.section_availability);void 0!==courseeditor.state.section.get(sectionid)&&courseeditor.dispatch("sectionState",[sectionid])}else if("setmarker"===action){var oldmarker=$(SELECTOR.SECTIONLI+".current"),oldActionItem=oldmarker.find(SELECTOR.SECTIONACTIONMENU+" a[data-action=removemarker]");oldmarker.removeClass("current"),replaceActionItem(oldActionItem,"i/marker","highlight","core","setmarker"),sectionElement.addClass("current"),replaceActionItem(actionItem,"i/marked","highlightoff","core","removemarker"),courseeditor.dispatch("legacySectionAction",action,sectionid),setSectionBadge(sectionElement[0],"iscurrent",!0,!0)}else"removemarker"===action&&(sectionElement.removeClass("current"),replaceActionItem(actionItem,"i/marker","highlight","core","setmarker"),courseeditor.dispatch("legacySectionAction",action,sectionid),setSectionBadge(sectionElement[0],"iscurrent",!1,!0))};var replaceActivityHtmlWith=function(activityHTML){$("
"+activityHTML+"
").find(SELECTOR.ACTIVITYLI).each((function(){var id=$(this).attr("id");let focusedPath=function(id){const element=document.getElementById(id);if(element&&element.contains(document.activeElement))return element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)?"".concat(SELECTOR.ACTIONAREA,' [tabindex="0"]'):document.activeElement.id?"#".concat(document.activeElement.id):void 0}(id);if($(SELECTOR.ACTIVITYLI+"#"+id).replaceWith(activityHTML),initActionMenu(id),focusedPath){var _newItem$querySelecto;null===(_newItem$querySelecto=document.getElementById(id).querySelector(focusedPath))||void 0===_newItem$querySelecto||_newItem$querySelecto.focus()}}))},editSection=function(sectionElement,sectionid,target,courseformat){var action=target.attr("data-action"),sectionreturn=target.attr("data-sectionreturn")?target.attr("data-sectionreturn"):null;if(courseeditor.supportComponents&&componentActions.includes(action))return!1;var spinner=addSectionSpinner(sectionElement),promises=ajax.call([{methodname:"core_course_edit_section",args:{id:sectionid,action:action,sectionreturn:sectionreturn}}],!0),lightbox=addSectionLightbox(sectionElement);return $.when.apply($,promises).done((function(dataencoded){var data=$.parseJSON(dataencoded);removeSpinner(sectionElement,spinner),removeLightbox(lightbox),sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();var e=$.Event("coursesectionedited",{ajaxreturn:data,action:action});sectionElement.trigger(e),e.isDefaultPrevented()||defaultEditSectionHandler(sectionElement,target,data,courseformat,sectionid)})).fail((function(ex){removeSpinner(sectionElement,spinner),removeLightbox(lightbox);var e=$.Event("coursesectioneditfailed",{exception:ex,action:action});sectionElement.trigger(e),e.isDefaultPrevented()||notification.exception(ex)})),!0},setSectionBadge=function(sectionElement,badgetype,add,removeOther){const sectionbadges=sectionElement.querySelector(SELECTOR.SECTIONBADGES);if(!sectionbadges)return;const badge=sectionbadges.querySelector('[data-type="'+badgetype+'"]');badge&&(add?(removeOther&&document.querySelectorAll('[data-type="'+badgetype+'"]').forEach((b=>{b.classList.add("d-none")})),badge.classList.remove("d-none")):badge.classList.add("d-none"))};return Y.use("moodle-course-coursebase",(function(){M.course.coursebase.register_module({set_visibility_resource_ui:function(args){var mainelement=$(args.element.getDOMNode()),cmid=getModuleId(mainelement);if(cmid){var sectionreturn=mainelement.find("."+CSS_EDITINGMOVE).attr("data-sectionreturn");refreshModule(mainelement,cmid,sectionreturn)}},updateMovedCmState:params=>{const cm=courseeditor.state.cm.get(params.cmid);void 0!==cm&&courseeditor.dispatch("sectionState",[cm.sectionid]),courseeditor.dispatch("cmState",[params.cmid])},updateMovedSectionState:()=>{courseeditor.dispatch("courseState")}})})),courseeditor.addMutations({legacyActivityAction:function(statemanager,action,cmid,affectedids){const state=statemanager.state,cm=state.cm.get(cmid);if(void 0===cm)return;const section=state.section.get(cm.sectionid);if(void 0!==section){switch(courseeditor.dispatch("cmLock",[cm.id],!0),statemanager.setReadOnly(!1),cm.locked=!1,action){case"delete":section.cmlist=section.cmlist.reduce(((cmlist,current)=>(current!=cmid&&cmlist.push(current),cmlist)),[]),state.cm.delete(cmid);break;case"hide":case"show":case"duplicate":courseeditor.dispatch("cmState",affectedids)}statemanager.setReadOnly(!0)}},legacySectionAction:function(statemanager,action,sectionid){const state=statemanager.state,section=state.section.get(sectionid);if(void 0!==section){switch(statemanager.setReadOnly(!1),section.locked=!0,statemanager.setReadOnly(!0),statemanager.setReadOnly(!1),section.locked=!1,action){case"setmarker":state.section.forEach((current=>{current.id!=sectionid&&(current.current=!1)})),section.current=!0;break;case"removemarker":section.current=!1}statemanager.setReadOnly(!0)}}}),{initCoursePage:function(courseformat){if(formatname=courseformat,$("body").on("click keypress",SELECTOR.ACTIVITYLI+" "+SELECTOR.ACTIVITYACTION+"[data-action]",(function(e){if("keypress"!==e.type||13===e.keyCode){var actionItem=$(this),moduleElement=actionItem.closest(SELECTOR.ACTIVITYLI),action=actionItem.attr("data-action"),moduleId=getModuleId(moduleElement);switch(action){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return}moduleId&&(e.preventDefault(),"delete"===action?confirmDeleteModule(moduleElement,(function(){editModule(moduleElement,moduleId,actionItem)})):editModule(moduleElement,moduleId,actionItem))}})),$("body").on("click keypress",SELECTOR.SECTIONACTIONMENUTRIGGER+"[data-sectionid] a[data-action]",(function(e){if("keypress"===e.type&&13!==e.keyCode)return;var actionItem=$(this),sectionElement=actionItem.closest(SELECTOR.SECTIONLI),sectionId=actionItem.closest(SELECTOR.SECTIONACTIONMENUTRIGGER).attr("data-sectionid");if("permalink"===actionItem.attr("data-action"))return e.preventDefault(),void ModalCopyToClipboard.create({text:actionItem.attr("href")},str.get_string("sectionlink","course"));let isExecuted=!0;var message,onconfirm;actionItem.attr("data-confirm")?(message=actionItem.attr("data-confirm"),onconfirm=function(){isExecuted=editSection(sectionElement,sectionId,actionItem,courseformat)},str.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done((function(s){notification.confirm(s[0],message,s[1],s[2],onconfirm)}))):isExecuted=editSection(sectionElement,sectionId,actionItem,courseformat),isExecuted&&e.preventDefault()})),$("body").on(InplaceEditableEvents.eventTypes.elementUpdated,"".concat(SELECTOR.SECTIONITEM," [data-inplaceeditable]"),(function(e){if(e.detail.ajaxreturn.itemid){void 0!==courseeditor.state.section.get(e.detail.ajaxreturn.itemid)&&courseeditor.dispatch("sectionState",[e.detail.ajaxreturn.itemid])}})),$("body").on(InplaceEditableEvents.eventTypes.elementUpdated,"".concat(SELECTOR.ACTIVITYLI,' [data-itemtype="activityname"][data-inplaceeditable]'),(function(e){e.detail.ajaxreturn.itemid&&courseeditor.dispatch("cmState",[e.detail.ajaxreturn.itemid])})),courseeditor.supportComponents&&componentActions.includes("addSection"))return;const trigger=$(SELECTOR.ADDSECTIONS),modalTitle=trigger.attr("data-add-sections"),newSections=trigger.attr("data-new-sections");str.get_string("numberweeks").then((function(strNumberSections){var modalBody=$('
');return modalBody.find("label").html(strNumberSections),modalBody.html()})).then((body=>ModalSaveCancel.create({body:body,title:modalTitle}))).then((function(modal){var numSections=$(modal.getBody()).find("#add_section_numsections"),addSections=function(){""+parseInt(numSections.val())===numSections.val()&&parseInt(numSections.val())>=1&&(document.location=trigger.attr("href")+"&numsections="+parseInt(numSections.val()))};return modal.setSaveButtonText(modalTitle),modal.getRoot().on(ModalEvents.shown,(function(){numSections.focus().select().on("keydown",(function(e){e.keyCode===KeyCodes.enter&&addSections()}))})),modal.getRoot().on(ModalEvents.save,(function(e){e.preventDefault(),addSections()})),trigger.on("click",(e=>{e.preventDefault(),modal.show()})),modal})).catch(notification.exception)},replaceSectionActionItem:function(sectionelement,selector,image,stringname,stringcomponent,newaction){log.debug("replaceSectionActionItem() is deprecated and will be removed.");var actionitem=sectionelement.find(SELECTOR.SECTIONACTIONMENU+" "+selector);replaceActionItem(actionitem,image,stringname,stringcomponent,newaction)},refreshModule:refreshModule,refreshSection:function(element,sectionid,sectionreturn){void 0===sectionreturn&&(sectionreturn=courseeditor.sectionReturn);const sectionElement=$(element),promises=ajax.call([{methodname:"core_course_edit_section",args:{id:sectionid,action:"refresh",sectionreturn:sectionreturn}}],!0);var spinner=addSectionSpinner(sectionElement);return new Promise(((resolve,reject)=>{$.when.apply($,promises).done((dataencoded=>{removeSpinner(sectionElement,spinner);const data=$.parseJSON(dataencoded),newSectionElement=$(data.content);sectionElement.replaceWith(newSectionElement),$("".concat(SELECTOR.SECTIONLI,"#").concat(sectionid," ").concat(SELECTOR.ACTIVITYLI)).each(((index,activity)=>{initActionMenu(activity.data("id"))}));dispatchEvent(CourseEvents.sectionRefreshed,{ajaxreturn:data,action:"refresh",newSectionElement:newSectionElement.get(0)},newSectionElement).defaultPrevented||defaultEditSectionHandler(newSectionElement,$(SELECTOR.SECTIONLI+"#"+sectionid),data,formatname,sectionid),resolve(data)})).fail((ex=>{dispatchEvent("coursesectionrefreshfailed",{exception:ex,action:"refresh"},sectionElement).defaultPrevented||notification.exception(ex),reject()}))}))}}})); +define("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_copy_to_clipboard","core/modal_save_cancel","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core/local/inplace_editable/events","core_course/events"],(function($,ajax,templates,notification,str,url,Y,ModalCopyToClipboard,ModalSaveCancel,ModalEvents,KeyCodes,log,editor,EventDispatcher,InplaceEditableEvents,CourseEvents){log.debug("The course/actions module is deprecated. Please, add support_components to your course format.");const componentActions=["moveSection","moveCm","addSection","deleteSection","cmDelete","cmDuplicate","sectionHide","sectionShow","cmHide","cmShow","cmStealth","sectionHighlight","sectionUnhighlight","cmMoveRight","cmMoveLeft","cmNoGroups","cmVisibleGroups","cmSeparateGroups","addModule","newModule"],courseeditor=editor.getCurrentCourseEditor();let formatname;var CSS_EDITINPROGRESS="editinprogress",CSS_EDITINGMOVE="editing_move",SELECTOR={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",SECTIONACTIONMENUTRIGGER:".section-actions",SECTIONITEM:'[data-for="section_title"]',ADDSECTIONS:".changenumsections [data-add-sections]",SECTIONBADGES:'[data-region="sectionbadges"]'};Y.use("moodle-course-coursebase",(function(){var courseformatselector=M.course.format.get_section_selector();courseformatselector&&(SELECTOR.SECTIONLI=courseformatselector)}));const dispatchEvent=function(eventName,detail,container,options){return container instanceof Element||void 0===container.get||(container=container.get(0)),EventDispatcher.dispatchEvent(eventName,detail,container,options)};var getModuleId=function(element){const item=element.get(0);if(item.dataset.id)return item.dataset.id;let id;return Y.use("moodle-course-util",(function(Y){id=Y.Moodle.core_course.util.cm.getId(Y.Node(item))})),id},addActivitySpinner=function(activity){activity.addClass(CSS_EDITINPROGRESS);var actionarea=activity.find(SELECTOR.ACTIONAREA).get(0);if(actionarea){var spinner=M.util.add_spinner(Y,Y.Node(actionarea));return spinner.show(),void 0!==activity.data("id")&&courseeditor.dispatch("cmLock",[activity.data("id")],!0),spinner}return null},addSectionSpinner=function(sectionelement){sectionelement.addClass(CSS_EDITINPROGRESS);var actionarea=sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);if(actionarea){var spinner=M.util.add_spinner(Y,Y.Node(actionarea));return spinner.show(),void 0!==sectionelement.data("id")&&courseeditor.dispatch("sectionLock",[sectionelement.data("id")],!0),spinner}return null},addSectionLightbox=function(sectionelement){const item=sectionelement.get(0);var lightbox=M.util.add_lightbox(Y,Y.Node(item));return"section"==item.dataset.for&&item.dataset.id&&(courseeditor.dispatch("sectionLock",[item.dataset.id],!0),lightbox.setAttribute("data-state","section"),lightbox.setAttribute("data-state-id",item.dataset.id)),lightbox.show(),lightbox},removeSpinner=function(element,spinner,delay){window.setTimeout((function(){if(element.removeClass(CSS_EDITINPROGRESS),spinner&&spinner.hide(),void 0!==element.data("id")){const mutation="section"===element.data("for")?"sectionLock":"cmLock";courseeditor.dispatch(mutation,[element.data("id")],!1)}}),delay)},removeLightbox=function(lightbox,delay){lightbox&&window.setTimeout((function(){lightbox.hide(),lightbox.getAttribute("data-state")&&courseeditor.dispatch("".concat(lightbox.getAttribute("data-state"),"Lock"),[lightbox.getAttribute("data-state-id")],!1)}),delay)},initActionMenu=function(elementid){Y.use("moodle-course-coursebase",(function(){M.course.coursebase.invoke_function("setup_for_resource","#"+elementid)})),M.core.actionmenu&&M.core.actionmenu.newDOMNode&&M.core.actionmenu.newDOMNode(Y.one("#"+elementid))},editModule=function(moduleElement,cmid,target){var lightbox,action=target.attr("data-action"),spinner=addActivitySpinner(moduleElement),promises=ajax.call([{methodname:"core_course_edit_module",args:{id:cmid,action:action,sectionreturn:target.attr("data-sectionreturn")?target.attr("data-sectionreturn"):null}}],!0);"duplicate"===action&&(lightbox=addSectionLightbox(target.closest(SELECTOR.SECTIONLI))),$.when.apply($,promises).done((function(data){var mainElement,tabables,isInside,foundElement,elementToFocus=(mainElement=moduleElement,tabables=$("a:visible"),isInside=!1,foundElement=null,tabables.each((function(){if($.contains(mainElement[0],this))isInside=!0;else if(isInside)return foundElement=this,!1;return!0})),foundElement);moduleElement.replaceWith(data);let affectedids=[];$("
"+data+"
").find(SELECTOR.ACTIVITYLI).each((function(index){initActionMenu($(this).attr("id")),0===index&&(!function(elementId,action){var mainelement=$("#"+elementId),selector="[data-action="+action+"]";"groupsseparate"!==action&&"groupsvisible"!==action&&"groupsnone"!==action||(selector="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"),mainelement.find(selector).is(":visible")?mainelement.find(selector).focus():mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus()}($(this).attr("id"),action),elementToFocus=null),affectedids.push(getModuleId($(this)))})),elementToFocus&&elementToFocus.focus(),removeSpinner(moduleElement,spinner,400),removeLightbox(lightbox,400),moduleElement.trigger($.Event("coursemoduleedited",{ajaxreturn:data,action:action})),courseeditor.dispatch("legacyActivityAction",action,cmid,affectedids)})).fail((function(ex){removeSpinner(moduleElement,spinner),removeLightbox(lightbox);var e=$.Event("coursemoduleeditfailed",{exception:ex,action:action});moduleElement.trigger(e),e.isDefaultPrevented()||notification.exception(ex)}))},refreshModule=function(element,cmid,sectionreturn){void 0===sectionreturn&&(sectionreturn=courseeditor.sectionReturn);const activityElement=$(element);var spinner=addActivitySpinner(activityElement),promises=ajax.call([{methodname:"core_course_get_module",args:{id:cmid,sectionreturn:sectionreturn}}],!0);return new Promise(((resolve,reject)=>{$.when.apply($,promises).done((function(data){removeSpinner(activityElement,spinner,400),replaceActivityHtmlWith(data),resolve(data)})).fail((function(){removeSpinner(activityElement,spinner),reject()}))}))},confirmDeleteModule=function(mainelement,onconfirm){var modtypename=mainelement.attr("class").match(/modtype_([^\s]*)/)[1],modulename=function(element){var name;Y.use("moodle-course-util",(function(Y){name=Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)))}));const state=courseeditor.state,cmid=getModuleId(element);var _state$cm$get;return!name&&state&&cmid&&(name=null===(_state$cm$get=state.cm.get(cmid))||void 0===_state$cm$get?void 0:_state$cm$get.name),name}(mainelement);str.get_string("pluginname",modtypename).done((function(pluginname){var plugindata={type:pluginname,name:modulename};str.get_strings([{key:"confirm",component:"core"},{key:null===modulename?"deletechecktype":"deletechecktypename",param:plugindata},{key:"yes"},{key:"no"}]).done((function(s){notification.confirm(s[0],s[1],s[2],s[3],onconfirm)}))}))},replaceActionItem=function(actionitem,image,stringname,stringcomponent,newaction){var stringRequests=[{key:stringname,component:stringcomponent}];return str.get_strings(stringRequests).then((function(strings){return actionitem.find("span.menu-action-text").html(strings[0]),templates.renderPix(image,"core")})).then((function(pixhtml){actionitem.find(".icon").replaceWith(pixhtml),actionitem.attr("data-action",newaction)})).catch(notification.exception)},defaultEditSectionHandler=function(sectionElement,actionItem,data,courseformat,sectionid){var action=actionItem.attr("data-action");if("hide"===action||"show"===action){if("hide"===action?(sectionElement.addClass("hidden"),setSectionBadge(sectionElement[0],"hiddenfromstudents",!0,!1),replaceActionItem(actionItem,"i/show","showfromothers","format_"+courseformat,"show")):(setSectionBadge(sectionElement[0],"hiddenfromstudents",!1,!1),sectionElement.removeClass("hidden"),replaceActionItem(actionItem,"i/hide","hidefromothers","format_"+courseformat,"hide")),void 0!==data.modules)for(var i in data.modules)replaceActivityHtmlWith(data.modules[i]);void 0!==data.section_availability&§ionElement.find(".section_availability").first().replaceWith(data.section_availability);void 0!==courseeditor.state.section.get(sectionid)&&courseeditor.dispatch("sectionState",[sectionid])}else if("setmarker"===action){var oldmarker=$(SELECTOR.SECTIONLI+".current"),oldActionItem=oldmarker.find(SELECTOR.SECTIONACTIONMENU+" a[data-action=removemarker]");oldmarker.removeClass("current"),replaceActionItem(oldActionItem,"i/marker","highlight","core","setmarker"),sectionElement.addClass("current"),replaceActionItem(actionItem,"i/marked","highlightoff","core","removemarker"),courseeditor.dispatch("legacySectionAction",action,sectionid),setSectionBadge(sectionElement[0],"iscurrent",!0,!0)}else"removemarker"===action&&(sectionElement.removeClass("current"),replaceActionItem(actionItem,"i/marker","highlight","core","setmarker"),courseeditor.dispatch("legacySectionAction",action,sectionid),setSectionBadge(sectionElement[0],"iscurrent",!1,!0))};var replaceActivityHtmlWith=function(activityHTML){$("
"+activityHTML+"
").find(SELECTOR.ACTIVITYLI).each((function(){var id=$(this).attr("id");let focusedPath=function(id){const element=document.getElementById(id);if(element&&element.contains(document.activeElement))return element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)?"".concat(SELECTOR.ACTIONAREA,' [tabindex="0"]'):document.activeElement.id?"#".concat(document.activeElement.id):void 0}(id);if($(SELECTOR.ACTIVITYLI+"#"+id).replaceWith(activityHTML),initActionMenu(id),focusedPath){var _newItem$querySelecto;null===(_newItem$querySelecto=document.getElementById(id).querySelector(focusedPath))||void 0===_newItem$querySelecto||_newItem$querySelecto.focus()}}))},editSection=function(sectionElement,sectionid,target,courseformat){var action=target.attr("data-action"),sectionreturn=target.attr("data-sectionreturn")?target.attr("data-sectionreturn"):null;if(courseeditor.supportComponents&&componentActions.includes(action))return!1;var spinner=addSectionSpinner(sectionElement),promises=ajax.call([{methodname:"core_course_edit_section",args:{id:sectionid,action:action,sectionreturn:sectionreturn}}],!0),lightbox=addSectionLightbox(sectionElement);return $.when.apply($,promises).done((function(dataencoded){var data=$.parseJSON(dataencoded);removeSpinner(sectionElement,spinner),removeLightbox(lightbox),sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();var e=$.Event("coursesectionedited",{ajaxreturn:data,action:action});sectionElement.trigger(e),e.isDefaultPrevented()||defaultEditSectionHandler(sectionElement,target,data,courseformat,sectionid)})).fail((function(ex){removeSpinner(sectionElement,spinner),removeLightbox(lightbox);var e=$.Event("coursesectioneditfailed",{exception:ex,action:action});sectionElement.trigger(e),e.isDefaultPrevented()||notification.exception(ex)})),!0},setSectionBadge=function(sectionElement,badgetype,add,removeOther){const sectionbadges=sectionElement.querySelector(SELECTOR.SECTIONBADGES);if(!sectionbadges)return;const badge=sectionbadges.querySelector('[data-type="'+badgetype+'"]');badge&&(add?(removeOther&&document.querySelectorAll('[data-type="'+badgetype+'"]').forEach((b=>{b.classList.add("d-none")})),badge.classList.remove("d-none")):badge.classList.add("d-none"))};return Y.use("moodle-course-coursebase",(function(){M.course.coursebase.register_module({set_visibility_resource_ui:function(args){var mainelement=$(args.element.getDOMNode()),cmid=getModuleId(mainelement);if(cmid){var sectionreturn=mainelement.find("."+CSS_EDITINGMOVE).attr("data-sectionreturn");refreshModule(mainelement,cmid,sectionreturn)}},updateMovedCmState:params=>{const cm=courseeditor.state.cm.get(params.cmid);void 0!==cm&&courseeditor.dispatch("sectionState",[cm.sectionid]),courseeditor.dispatch("cmState",[params.cmid])},updateMovedSectionState:()=>{courseeditor.dispatch("courseState")}})})),courseeditor.addMutations({legacyActivityAction:function(statemanager,action,cmid,affectedids){const state=statemanager.state,cm=state.cm.get(cmid);if(void 0===cm)return;const section=state.section.get(cm.sectionid);if(void 0!==section){switch(courseeditor.dispatch("cmLock",[cm.id],!0),statemanager.setReadOnly(!1),cm.locked=!1,action){case"delete":section.cmlist=section.cmlist.reduce(((cmlist,current)=>(current!=cmid&&cmlist.push(current),cmlist)),[]),state.cm.delete(cmid);break;case"hide":case"show":case"duplicate":courseeditor.dispatch("cmState",affectedids)}statemanager.setReadOnly(!0)}},legacySectionAction:function(statemanager,action,sectionid){const state=statemanager.state,section=state.section.get(sectionid);if(void 0!==section){switch(statemanager.setReadOnly(!1),section.locked=!0,statemanager.setReadOnly(!0),statemanager.setReadOnly(!1),section.locked=!1,action){case"setmarker":state.section.forEach((current=>{current.id!=sectionid&&(current.current=!1)})),section.current=!0;break;case"removemarker":section.current=!1}statemanager.setReadOnly(!0)}}}),{initCoursePage:function(courseformat){if(formatname=courseformat,$("body").on("click keypress",SELECTOR.ACTIVITYLI+" "+SELECTOR.ACTIVITYACTION+"[data-action]",(function(e){if("keypress"!==e.type||13===e.keyCode){var actionItem=$(this),moduleElement=actionItem.closest(SELECTOR.ACTIVITYLI),action=actionItem.attr("data-action"),moduleId=getModuleId(moduleElement);switch(action){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return}moduleId&&(e.preventDefault(),"delete"===action?confirmDeleteModule(moduleElement,(function(){editModule(moduleElement,moduleId,actionItem)})):editModule(moduleElement,moduleId,actionItem))}})),$("body").on("click keypress",SELECTOR.SECTIONACTIONMENUTRIGGER+"[data-sectionid] a[data-action]",(function(e){if("keypress"===e.type&&13!==e.keyCode)return;var actionItem=$(this),sectionElement=actionItem.closest(SELECTOR.SECTIONLI),sectionId=actionItem.closest(SELECTOR.SECTIONACTIONMENUTRIGGER).attr("data-sectionid");if("permalink"===actionItem.attr("data-action"))return e.preventDefault(),void ModalCopyToClipboard.create({text:actionItem.attr("href")},str.get_string("sectionlink","course"));let isExecuted=!0;var message,onconfirm;actionItem.attr("data-confirm")?(message=actionItem.attr("data-confirm"),onconfirm=function(){isExecuted=editSection(sectionElement,sectionId,actionItem,courseformat)},str.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done((function(s){notification.confirm(s[0],message,s[1],s[2],onconfirm)}))):isExecuted=editSection(sectionElement,sectionId,actionItem,courseformat),isExecuted&&e.preventDefault()})),$("body").on(InplaceEditableEvents.eventTypes.elementUpdated,"".concat(SELECTOR.SECTIONITEM," [data-inplaceeditable]"),(function(e){if(e.detail.ajaxreturn.itemid){void 0!==courseeditor.state.section.get(e.detail.ajaxreturn.itemid)&&courseeditor.dispatch("sectionState",[e.detail.ajaxreturn.itemid])}})),$("body").on(InplaceEditableEvents.eventTypes.elementUpdated,"".concat(SELECTOR.ACTIVITYLI,' [data-itemtype="activityname"][data-inplaceeditable]'),(function(e){e.detail.ajaxreturn.itemid&&courseeditor.dispatch("cmState",[e.detail.ajaxreturn.itemid])})),courseeditor.supportComponents&&componentActions.includes("addSection"))return;const trigger=$(SELECTOR.ADDSECTIONS),modalTitle=trigger.attr("data-add-sections"),newSections=trigger.attr("data-new-sections");str.get_string("numberweeks").then((function(strNumberSections){var modalBody=$('
');return modalBody.find("label").html(strNumberSections),modalBody.html()})).then((body=>ModalSaveCancel.create({body:body,title:modalTitle}))).then((function(modal){var numSections=$(modal.getBody()).find("#add_section_numsections"),addSections=function(){""+parseInt(numSections.val())===numSections.val()&&parseInt(numSections.val())>=1&&(document.location=trigger.attr("href")+"&numsections="+parseInt(numSections.val()))};return modal.setSaveButtonText(modalTitle),modal.getRoot().on(ModalEvents.shown,(function(){numSections.focus().select().on("keydown",(function(e){e.keyCode===KeyCodes.enter&&addSections()}))})),modal.getRoot().on(ModalEvents.save,(function(e){e.preventDefault(),addSections()})),trigger.on("click",(e=>{e.preventDefault(),modal.show()})),modal})).catch(notification.exception)},replaceSectionActionItem:function(sectionelement,selector,image,stringname,stringcomponent,newaction){log.debug("replaceSectionActionItem() is deprecated and will be removed.");var actionitem=sectionelement.find(SELECTOR.SECTIONACTIONMENU+" "+selector);replaceActionItem(actionitem,image,stringname,stringcomponent,newaction)},refreshModule:refreshModule,refreshSection:function(element,sectionid,sectionreturn){void 0===sectionreturn&&(sectionreturn=courseeditor.sectionReturn);const sectionElement=$(element),promises=ajax.call([{methodname:"core_course_edit_section",args:{id:sectionid,action:"refresh",sectionreturn:sectionreturn}}],!0);var spinner=addSectionSpinner(sectionElement);return new Promise(((resolve,reject)=>{$.when.apply($,promises).done((dataencoded=>{removeSpinner(sectionElement,spinner);const data=$.parseJSON(dataencoded),newSectionElement=$(data.content);sectionElement.replaceWith(newSectionElement),$("".concat(SELECTOR.SECTIONLI,"#").concat(sectionid," ").concat(SELECTOR.ACTIVITYLI)).each(((index,activity)=>{initActionMenu(activity.data("id"))}));dispatchEvent(CourseEvents.sectionRefreshed,{ajaxreturn:data,action:"refresh",newSectionElement:newSectionElement.get(0)},newSectionElement).defaultPrevented||defaultEditSectionHandler(newSectionElement,$(SELECTOR.SECTIONLI+"#"+sectionid),data,formatname,sectionid),resolve(data)})).fail((ex=>{dispatchEvent("coursesectionrefreshfailed",{exception:ex,action:"refresh"},sectionElement).defaultPrevented||notification.exception(ex),reject()}))}))}}})); //# sourceMappingURL=actions.min.js.map \ No newline at end of file diff --git a/course/amd/build/actions.min.js.map b/course/amd/build/actions.min.js.map index 5e6fc697633..5ab3036aae0 100644 --- a/course/amd/build/actions.min.js.map +++ b/course/amd/build/actions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"actions.min.js","sources":["../src/actions.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 * Various actions on modules and sections in the editing mode - hiding, duplicating, deleting, etc.\n *\n * TODO remove this module as part of MDL-83627.\n *\n * @module core_course/actions\n * @copyright 2016 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.3\n */\ndefine(\n [\n 'jquery',\n 'core/ajax',\n 'core/templates',\n 'core/notification',\n 'core/str',\n 'core/url',\n 'core/yui',\n 'core/modal_copy_to_clipboard',\n 'core/modal_save_cancel',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core/local/inplace_editable/events',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalCopyToClipboard,\n ModalSaveCancel,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n InplaceEditableEvents,\n CourseEvents\n ) {\n log.debug('The course/actions module is deprecated. Please, add support_components to your course format.');\n\n // Eventually, core_courseformat/local/content/actions will handle all actions for\n // component compatible formats and the default actions.js won't be necessary anymore.\n // Meanwhile, we filter the migrated actions.\n const componentActions = [\n 'moveSection', 'moveCm', 'addSection', 'deleteSection', 'cmDelete', 'cmDuplicate', 'sectionHide', 'sectionShow',\n 'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight', 'cmMoveRight', 'cmMoveLeft',\n 'cmNoGroups', 'cmVisibleGroups', 'cmSeparateGroups', 'addModule',\n ];\n\n // The course reactive instance.\n const courseeditor = editor.getCurrentCourseEditor();\n\n // The current course format name (loaded on init).\n let formatname;\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n SECTIONACTIONMENUTRIGGER: '.section-actions',\n SECTIONITEM: '[data-for=\"section_title\"]',\n ADDSECTIONS: '.changenumsections [data-add-sections]',\n SECTIONBADGES: '[data-region=\"sectionbadges\"]',\n };\n\n Y.use('moodle-course-coursebase', function() {\n var courseformatselector = M.course.format.get_section_selector();\n if (courseformatselector) {\n SELECTOR.SECTIONLI = courseformatselector;\n }\n });\n\n /**\n * Dispatch event wrapper.\n *\n * Old jQuery events will be replaced by native events gradually.\n *\n * @method dispatchEvent\n * @param {String} eventName The name of the event\n * @param {Object} detail Any additional details to pass into the eveent\n * @param {Node|HTMLElement} container The point at which to dispatch the event\n * @param {Object} options\n * @param {Boolean} options.bubbles Whether to bubble up the DOM\n * @param {Boolean} options.cancelable Whether preventDefault() can be called\n * @param {Boolean} options.composed Whether the event can bubble across the ShadowDOM boundary\n * @returns {CustomEvent}\n */\n const dispatchEvent = function(eventName, detail, container, options) {\n // Most actions still uses jQuery node instead of regular HTMLElement.\n if (!(container instanceof Element) && container.get !== undefined) {\n container = container.get(0);\n }\n return EventDispatcher.dispatchEvent(eventName, detail, container, options);\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getId\n *\n * @param {JQuery} element\n * @returns {Integer}\n */\n var getModuleId = function(element) {\n // Check if we have a data-id first.\n const item = element.get(0);\n if (item.dataset.id) {\n return item.dataset.id;\n }\n // Use YUI way if data-id is not present.\n let id;\n Y.use('moodle-course-util', function(Y) {\n id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));\n });\n return id;\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getName\n *\n * @param {JQuery} element\n * @returns {String}\n */\n var getModuleName = function(element) {\n var name;\n Y.use('moodle-course-util', function(Y) {\n name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));\n });\n // Check if we have the name in the course state.\n const state = courseeditor.state;\n const cmid = getModuleId(element);\n if (!name && state && cmid) {\n name = state.cm.get(cmid)?.name;\n }\n return name;\n };\n\n /**\n * Wrapper for M.util.add_spinner for an activity\n *\n * @param {JQuery} activity\n * @returns {Node}\n */\n var addActivitySpinner = function(activity) {\n activity.addClass(CSS.EDITINPROGRESS);\n var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the activity state element.\n if (activity.data('id') !== undefined) {\n courseeditor.dispatch('cmLock', [activity.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_spinner for a section\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionSpinner = function(sectionelement) {\n sectionelement.addClass(CSS.EDITINPROGRESS);\n var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the section state element.\n if (sectionelement.data('id') !== undefined) {\n courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_lightbox\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionLightbox = function(sectionelement) {\n const item = sectionelement.get(0);\n var lightbox = M.util.add_lightbox(Y, Y.Node(item));\n if (item.dataset.for == 'section' && item.dataset.id) {\n courseeditor.dispatch('sectionLock', [item.dataset.id], true);\n lightbox.setAttribute('data-state', 'section');\n lightbox.setAttribute('data-state-id', item.dataset.id);\n }\n lightbox.show();\n return lightbox;\n };\n\n /**\n * Removes the spinner element\n *\n * @param {JQuery} element\n * @param {Node} spinner\n * @param {Number} delay\n */\n var removeSpinner = function(element, spinner, delay) {\n window.setTimeout(function() {\n element.removeClass(CSS.EDITINPROGRESS);\n if (spinner) {\n spinner.hide();\n }\n // Unlock the state element.\n if (element.data('id') !== undefined) {\n const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';\n courseeditor.dispatch(mutation, [element.data('id')], false);\n }\n }, delay);\n };\n\n /**\n * Removes the lightbox element\n *\n * @param {Node} lightbox lighbox YUI element returned by addSectionLightbox\n * @param {Number} delay\n */\n var removeLightbox = function(lightbox, delay) {\n if (lightbox) {\n window.setTimeout(function() {\n lightbox.hide();\n // Unlock state if necessary.\n if (lightbox.getAttribute('data-state')) {\n courseeditor.dispatch(\n `${lightbox.getAttribute('data-state')}Lock`,\n [lightbox.getAttribute('data-state-id')],\n false\n );\n }\n }, delay);\n }\n };\n\n /**\n * Initialise action menu for the element (section or module)\n *\n * @param {String} elementid CSS id attribute of the element\n */\n var initActionMenu = function(elementid) {\n // Initialise action menu in the new activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);\n });\n if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {\n M.core.actionmenu.newDOMNode(Y.one('#' + elementid));\n }\n };\n\n /**\n * Returns focus to the element that was clicked or \"Edit\" link if element is no longer visible.\n *\n * @param {String} elementId CSS id attribute of the element\n * @param {String} action data-action property of the element that was clicked\n */\n var focusActionItem = function(elementId, action) {\n var mainelement = $('#' + elementId);\n var selector = '[data-action=' + action + ']';\n if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {\n // New element will have different data-action.\n selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';\n }\n if (mainelement.find(selector).is(':visible')) {\n mainelement.find(selector).focus();\n } else {\n // Element not visible, focus the \"Edit\" link.\n mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();\n }\n };\n\n /**\n * Find next after the element\n *\n * @param {JQuery} mainElement element that is about to be deleted\n * @returns {JQuery}\n */\n var findNextFocusable = function(mainElement) {\n var tabables = $(\"a:visible\");\n var isInside = false;\n var foundElement = null;\n tabables.each(function() {\n if ($.contains(mainElement[0], this)) {\n isInside = true;\n } else if (isInside) {\n foundElement = this;\n return false; // Returning false in .each() is equivalent to \"break;\" inside the loop in php.\n }\n return true;\n });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : null\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('
' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const activityElement = $(element);\n var spinner = addActivitySpinner(activityElement);\n var promises = ajax.call([{\n methodname: 'core_course_get_module',\n args: {id: cmid, sectionreturn: sectionreturn}\n }], true);\n\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(function(data) {\n removeSpinner(activityElement, spinner, 400);\n replaceActivityHtmlWith(data);\n resolve(data);\n }).fail(function() {\n removeSpinner(activityElement, spinner);\n reject();\n });\n });\n };\n\n /**\n * Requests html for the section via WS core_course_edit_section and updates the section on the course page\n *\n * @param {JQuery|Element} element\n * @param {Number} sectionid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshSection = function(element, sectionid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const sectionElement = $(element);\n const action = 'refresh';\n const promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action, sectionreturn},\n }], true);\n\n var spinner = addSectionSpinner(sectionElement);\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(dataencoded => {\n\n removeSpinner(sectionElement, spinner);\n const data = $.parseJSON(dataencoded);\n\n const newSectionElement = $(data.content);\n sectionElement.replaceWith(newSectionElement);\n\n // Init modules menus.\n $(`${SELECTOR.SECTIONLI}#${sectionid} ${SELECTOR.ACTIVITYLI}`).each(\n (index, activity) => {\n initActionMenu(activity.data('id'));\n }\n );\n\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n CourseEvents.sectionRefreshed,\n {\n ajaxreturn: data,\n action: action,\n newSectionElement: newSectionElement.get(0),\n },\n newSectionElement\n );\n\n if (!event.defaultPrevented) {\n defaultEditSectionHandler(\n newSectionElement, $(SELECTOR.SECTIONLI + '#' + sectionid),\n data,\n formatname,\n sectionid\n );\n }\n resolve(data);\n }).fail(ex => {\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n 'coursesectionrefreshfailed',\n {exception: ex, action: action},\n sectionElement\n );\n if (!event.defaultPrevented) {\n notification.exception(ex);\n }\n reject();\n });\n });\n };\n\n /**\n * Displays the delete confirmation to delete a module\n *\n * @param {JQuery} mainelement activity element we perform action on\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmDeleteModule = function(mainelement, onconfirm) {\n var modtypename = mainelement.attr('class').match(/modtype_([^\\s]*)/)[1];\n var modulename = getModuleName(mainelement);\n\n str.get_string('pluginname', modtypename).done(function(pluginname) {\n var plugindata = {\n type: pluginname,\n name: modulename\n };\n str.get_strings([\n {key: 'confirm', component: 'core'},\n {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], s[1], s[2], s[3], onconfirm);\n }\n );\n });\n };\n\n /**\n * Displays the delete confirmation to delete a section\n *\n * @param {String} message confirmation message\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmEditSection = function(message, onconfirm) {\n str.get_strings([\n {key: 'confirm'}, // TODO link text\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], message, s[1], s[2], onconfirm);\n }\n );\n };\n\n /**\n * Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * @param {JQuery} actionitem\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n * @return {Promise} promise which is resolved when the replacement has completed\n */\n var replaceActionItem = function(actionitem, image, stringname,\n stringcomponent, newaction) {\n\n var stringRequests = [{key: stringname, component: stringcomponent}];\n // Do not provide an icon with duplicate, different text to the menu item.\n\n return str.get_strings(stringRequests).then(function(strings) {\n actionitem.find('span.menu-action-text').html(strings[0]);\n\n return templates.renderPix(image, 'core');\n }).then(function(pixhtml) {\n actionitem.find('.icon').replaceWith(pixhtml);\n actionitem.attr('data-action', newaction);\n return;\n }).catch(notification.exception);\n };\n\n /**\n * Default post-processing for section AJAX edit actions.\n *\n * This can be overridden in course formats by listening to event coursesectionedited:\n *\n * $('body').on('coursesectionedited', 'li.section', function(e) {\n * var action = e.action,\n * sectionElement = $(e.target),\n * data = e.ajaxreturn;\n * // ... Do some processing here.\n * e.preventDefault(); // Prevent default handler.\n * });\n *\n * @param {JQuery} sectionElement\n * @param {JQuery} actionItem\n * @param {Object} data\n * @param {String} courseformat\n * @param {Number} sectionid\n */\n var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat, sectionid) {\n var action = actionItem.attr('data-action');\n if (action === 'hide' || action === 'show') {\n if (action === 'hide') {\n sectionElement.addClass('hidden');\n setSectionBadge(sectionElement[0], 'hiddenfromstudents', true, false);\n replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\n setSectionBadge(sectionElement[0], 'hiddenfromstudents', false, false);\n sectionElement.removeClass('hidden');\n replaceActionItem(actionItem, 'i/hide',\n 'hidefromothers', 'format_' + courseformat, 'hide');\n }\n // Replace the modules with new html (that indicates that they are now hidden or not hidden).\n if (data.modules !== undefined) {\n for (var i in data.modules) {\n replaceActivityHtmlWith(data.modules[i]);\n }\n }\n // Replace the section availability information.\n if (data.section_availability !== undefined) {\n sectionElement.find('.section_availability').first().replaceWith(data.section_availability);\n }\n // Modify course state.\n const section = courseeditor.state.section.get(sectionid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [sectionid]);\n }\n } else if (action === 'setmarker') {\n var oldmarker = $(SELECTOR.SECTIONLI + '.current'),\n oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');\n oldmarker.removeClass('current');\n replaceActionItem(oldActionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n sectionElement.addClass('current');\n replaceActionItem(actionItem, 'i/marked',\n 'highlightoff', 'core', 'removemarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n setSectionBadge(sectionElement[0], 'iscurrent', true, true);\n } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n setSectionBadge(sectionElement[0], 'iscurrent', false, true);\n }\n };\n\n /**\n * Get the focused element path in an activity if any.\n *\n * This method is used to restore focus when the activity HTML is refreshed.\n * Only the main course editor elements can be refocused as they are always present\n * even if the activity content changes.\n *\n * @param {String} id the element id the activity element\n * @return {String|undefined} the inner path of the focused element or undefined\n */\n const getActivityFocusedElement = function(id) {\n const element = document.getElementById(id);\n if (!element || !element.contains(document.activeElement)) {\n return undefined;\n }\n // Check if the actions menu toggler is focused.\n if (element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)) {\n return `${SELECTOR.ACTIONAREA} [tabindex=\"0\"]`;\n }\n // Return the current element id if any.\n if (document.activeElement.id) {\n return `#${document.activeElement.id}`;\n }\n return undefined;\n };\n\n /**\n * Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).\n *\n * @param {String} activityHTML\n */\n var replaceActivityHtmlWith = function(activityHTML) {\n $('
' + activityHTML + '
').find(SELECTOR.ACTIVITYLI).each(function() {\n // Extract id from the new activity html.\n var id = $(this).attr('id');\n // Check if the current focused element is inside the activity.\n let focusedPath = getActivityFocusedElement(id);\n // Find the existing element with the same id and replace its contents with new html.\n $(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);\n // Initialise action menu.\n initActionMenu(id);\n // Re-focus the previous elements.\n if (focusedPath) {\n const newItem = document.getElementById(id);\n newItem.querySelector(focusedPath)?.focus();\n }\n\n });\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {Nunmber} sectionid\n * @param {JQuery} target the element (menu item) that was clicked\n * @param {String} courseformat\n * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : null;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n /**\n * Sets the section badge in the section header.\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {String} badgetype the type of badge this is for\n * @param {bool} add true to add, false to remove\n * @param {boolean} removeOther in case of adding a badge, whether to remove all other.\n */\n var setSectionBadge = function(sectionElement, badgetype, add, removeOther) {\n const sectionbadges = sectionElement.querySelector(SELECTOR.SECTIONBADGES);\n if (!sectionbadges) {\n return;\n }\n const badge = sectionbadges.querySelector('[data-type=\"' + badgetype + '\"]');\n if (!badge) {\n return;\n }\n if (add) {\n if (removeOther) {\n document.querySelectorAll('[data-type=\"' + badgetype + '\"]').forEach((b) => {\n b.classList.add('d-none');\n });\n }\n badge.classList.remove('d-none');\n } else {\n badge.classList.add('d-none');\n }\n };\n\n // Register a function to be executed after D&D of an activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.register_module({\n // Ignore camelcase eslint rule for the next line because it is an expected name of the callback.\n // eslint-disable-next-line camelcase\n set_visibility_resource_ui: function(args) {\n var mainelement = $(args.element.getDOMNode());\n var cmid = getModuleId(mainelement);\n if (cmid) {\n var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');\n refreshModule(mainelement, cmid, sectionreturn);\n }\n },\n /**\n * Update the course state when some cm is moved via YUI.\n * @param {*} params\n */\n updateMovedCmState: (params) => {\n const state = courseeditor.state;\n\n // Update old section.\n const cm = state.cm.get(params.cmid);\n if (cm !== undefined) {\n courseeditor.dispatch('sectionState', [cm.sectionid]);\n }\n // Update cm state.\n courseeditor.dispatch('cmState', [params.cmid]);\n },\n /**\n * Update the course state when some section is moved via YUI.\n */\n updateMovedSectionState: () => {\n courseeditor.dispatch('courseState');\n },\n });\n });\n\n // From Moodle 4.0 all edit actions are being re-implemented as state mutation.\n // This means all method from this \"actions\" module will be deprecated when all the course\n // interface is migrated to reactive components.\n // Most legacy actions did not provide enough information to regenarate the course so they\n // use the mutations courseState, sectionState and cmState to get the updated state from\n // the server. However, some activity actions where we can prevent an extra webservice\n // call by implementing an adhoc mutation.\n courseeditor.addMutations({\n /**\n * Compatibility function to update Moodle 4.0 course state using legacy actions.\n *\n * This method only updates some actions which does not require to use cmState mutation\n * to get updated data form the server.\n *\n * @param {Object} statemanager the current state in read write mode\n * @param {String} action the performed action\n * @param {Number} cmid the affected course module id\n * @param {Array} affectedids all affected cm ids (for duplicate action)\n */\n legacyActivityAction: function(statemanager, action, cmid, affectedids) {\n\n const state = statemanager.state;\n const cm = state.cm.get(cmid);\n if (cm === undefined) {\n return;\n }\n const section = state.section.get(cm.sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked.\n courseeditor.dispatch('cmLock', [cm.id], true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This unlocked will take effect when the read only is restored.\n cm.locked = false;\n\n switch (action) {\n case 'delete':\n // Remove from section.\n section.cmlist = section.cmlist.reduce(\n (cmlist, current) => {\n if (current != cmid) {\n cmlist.push(current);\n }\n return cmlist;\n },\n []\n );\n // Delete form list.\n state.cm.delete(cmid);\n break;\n\n case 'hide':\n case 'show':\n case 'duplicate':\n courseeditor.dispatch('cmState', affectedids);\n break;\n }\n statemanager.setReadOnly(true);\n },\n legacySectionAction: function(statemanager, action, sectionid) {\n\n const state = statemanager.state;\n const section = state.section.get(sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n section.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n section.locked = false;\n\n switch (action) {\n case 'setmarker':\n // Remove previous marker.\n state.section.forEach((current) => {\n if (current.id != sectionid) {\n current.current = false;\n }\n });\n section.current = true;\n break;\n\n case 'removemarker':\n section.current = false;\n break;\n }\n statemanager.setReadOnly(true);\n },\n });\n\n return /** @alias module:core_course/actions */ {\n\n /**\n * Initialises course page\n *\n * @method init\n * @param {String} courseformat name of the current course format (for fetching strings)\n */\n initCoursePage: function(courseformat) {\n\n formatname = courseformat;\n\n // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section action menu.\n $('body').on('click keypress',\n SELECTOR.SECTIONACTIONMENUTRIGGER + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENUTRIGGER).attr('data-sectionid');\n\n if (actionItem.attr('data-action') === 'permalink') {\n e.preventDefault();\n ModalCopyToClipboard.create({\n text: actionItem.attr('href'),\n }, str.get_string('sectionlink', 'course')\n );\n return;\n }\n\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on(InplaceEditableEvents.eventTypes.elementUpdated,\n `${SELECTOR.SECTIONITEM} [data-inplaceeditable]`, function(e) {\n if (e.detail.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.detail.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.detail.ajaxreturn.itemid]);\n }\n }\n });\n\n $('body').on(InplaceEditableEvents.eventTypes.elementUpdated,\n `${SELECTOR.ACTIVITYLI} [data-itemtype=\"activityname\"][data-inplaceeditable]`, function(e) {\n if (e.detail.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.detail.ajaxreturn.itemid]);\n }\n });\n\n // Component-based formats don't use modals to create sections.\n if (courseeditor.supportComponents && componentActions.includes('addSection')) {\n return;\n }\n\n // Add a handler for \"Add sections\" link to ask for a number of sections to add.\n const trigger = $(SELECTOR.ADDSECTIONS);\n const modalTitle = trigger.attr('data-add-sections');\n const newSections = trigger.attr('data-new-sections');\n str.get_string('numberweeks')\n .then(function(strNumberSections) {\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n\n return modalBody.html();\n })\n .then((body) => ModalSaveCancel.create({\n body,\n title: modalTitle,\n }))\n .then(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n\n trigger.on('click', (e) => {\n e.preventDefault();\n modal.show();\n });\n\n return modal;\n })\n .catch(notification.exception);\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n refreshSection,\n };\n });\n"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalCopyToClipboard","ModalSaveCancel","ModalEvents","KeyCodes","log","editor","EventDispatcher","InplaceEditableEvents","CourseEvents","debug","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","SECTIONACTIONMENUTRIGGER","SECTIONITEM","ADDSECTIONS","SECTIONBADGES","use","courseformatselector","M","course","format","get_section_selector","dispatchEvent","eventName","detail","container","options","Element","undefined","get","getModuleId","element","item","dataset","id","Moodle","core_course","util","cm","getId","Node","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","for","setAttribute","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","getAttribute","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","editModule","moduleElement","cmid","target","action","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","mainElement","tabables","isInside","foundElement","elementToFocus","each","contains","this","replaceWith","affectedids","index","elementId","mainelement","selector","is","focus","focusActionItem","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","sectionReturn","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","name","getName","state","_state$cm$get","getModuleName","get_string","pluginname","plugindata","type","get_strings","key","component","param","s","confirm","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","stringRequests","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","setSectionBadge","modules","i","section_availability","first","section","oldmarker","oldActionItem","activityHTML","focusedPath","document","getElementById","activeElement","querySelector","getActivityFocusedElement","editSection","supportComponents","includes","dataencoded","parseJSON","badgetype","add","removeOther","sectionbadges","badge","querySelectorAll","forEach","b","classList","remove","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","legacySectionAction","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","create","text","isExecuted","message","eventTypes","elementUpdated","itemid","modalTitle","newSections","strNumberSections","modalBody","body","title","modal","numSections","getBody","addSections","parseInt","val","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","refreshSection","newSectionElement","content","sectionRefreshed","defaultPrevented"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"actions.min.js","sources":["../src/actions.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 * Various actions on modules and sections in the editing mode - hiding, duplicating, deleting, etc.\n *\n * TODO remove this module as part of MDL-83627.\n *\n * @module core_course/actions\n * @copyright 2016 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.3\n */\ndefine(\n [\n 'jquery',\n 'core/ajax',\n 'core/templates',\n 'core/notification',\n 'core/str',\n 'core/url',\n 'core/yui',\n 'core/modal_copy_to_clipboard',\n 'core/modal_save_cancel',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core/local/inplace_editable/events',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalCopyToClipboard,\n ModalSaveCancel,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n InplaceEditableEvents,\n CourseEvents\n ) {\n log.debug('The course/actions module is deprecated. Please, add support_components to your course format.');\n\n // Eventually, core_courseformat/local/content/actions will handle all actions for\n // component compatible formats and the default actions.js won't be necessary anymore.\n // Meanwhile, we filter the migrated actions.\n const componentActions = [\n 'moveSection', 'moveCm', 'addSection', 'deleteSection', 'cmDelete', 'cmDuplicate', 'sectionHide', 'sectionShow',\n 'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight', 'cmMoveRight', 'cmMoveLeft',\n 'cmNoGroups', 'cmVisibleGroups', 'cmSeparateGroups', 'addModule', 'newModule',\n ];\n\n // The course reactive instance.\n const courseeditor = editor.getCurrentCourseEditor();\n\n // The current course format name (loaded on init).\n let formatname;\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n SECTIONACTIONMENUTRIGGER: '.section-actions',\n SECTIONITEM: '[data-for=\"section_title\"]',\n ADDSECTIONS: '.changenumsections [data-add-sections]',\n SECTIONBADGES: '[data-region=\"sectionbadges\"]',\n };\n\n Y.use('moodle-course-coursebase', function() {\n var courseformatselector = M.course.format.get_section_selector();\n if (courseformatselector) {\n SELECTOR.SECTIONLI = courseformatselector;\n }\n });\n\n /**\n * Dispatch event wrapper.\n *\n * Old jQuery events will be replaced by native events gradually.\n *\n * @method dispatchEvent\n * @param {String} eventName The name of the event\n * @param {Object} detail Any additional details to pass into the eveent\n * @param {Node|HTMLElement} container The point at which to dispatch the event\n * @param {Object} options\n * @param {Boolean} options.bubbles Whether to bubble up the DOM\n * @param {Boolean} options.cancelable Whether preventDefault() can be called\n * @param {Boolean} options.composed Whether the event can bubble across the ShadowDOM boundary\n * @returns {CustomEvent}\n */\n const dispatchEvent = function(eventName, detail, container, options) {\n // Most actions still uses jQuery node instead of regular HTMLElement.\n if (!(container instanceof Element) && container.get !== undefined) {\n container = container.get(0);\n }\n return EventDispatcher.dispatchEvent(eventName, detail, container, options);\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getId\n *\n * @param {JQuery} element\n * @returns {Integer}\n */\n var getModuleId = function(element) {\n // Check if we have a data-id first.\n const item = element.get(0);\n if (item.dataset.id) {\n return item.dataset.id;\n }\n // Use YUI way if data-id is not present.\n let id;\n Y.use('moodle-course-util', function(Y) {\n id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));\n });\n return id;\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getName\n *\n * @param {JQuery} element\n * @returns {String}\n */\n var getModuleName = function(element) {\n var name;\n Y.use('moodle-course-util', function(Y) {\n name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));\n });\n // Check if we have the name in the course state.\n const state = courseeditor.state;\n const cmid = getModuleId(element);\n if (!name && state && cmid) {\n name = state.cm.get(cmid)?.name;\n }\n return name;\n };\n\n /**\n * Wrapper for M.util.add_spinner for an activity\n *\n * @param {JQuery} activity\n * @returns {Node}\n */\n var addActivitySpinner = function(activity) {\n activity.addClass(CSS.EDITINPROGRESS);\n var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the activity state element.\n if (activity.data('id') !== undefined) {\n courseeditor.dispatch('cmLock', [activity.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_spinner for a section\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionSpinner = function(sectionelement) {\n sectionelement.addClass(CSS.EDITINPROGRESS);\n var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the section state element.\n if (sectionelement.data('id') !== undefined) {\n courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_lightbox\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionLightbox = function(sectionelement) {\n const item = sectionelement.get(0);\n var lightbox = M.util.add_lightbox(Y, Y.Node(item));\n if (item.dataset.for == 'section' && item.dataset.id) {\n courseeditor.dispatch('sectionLock', [item.dataset.id], true);\n lightbox.setAttribute('data-state', 'section');\n lightbox.setAttribute('data-state-id', item.dataset.id);\n }\n lightbox.show();\n return lightbox;\n };\n\n /**\n * Removes the spinner element\n *\n * @param {JQuery} element\n * @param {Node} spinner\n * @param {Number} delay\n */\n var removeSpinner = function(element, spinner, delay) {\n window.setTimeout(function() {\n element.removeClass(CSS.EDITINPROGRESS);\n if (spinner) {\n spinner.hide();\n }\n // Unlock the state element.\n if (element.data('id') !== undefined) {\n const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';\n courseeditor.dispatch(mutation, [element.data('id')], false);\n }\n }, delay);\n };\n\n /**\n * Removes the lightbox element\n *\n * @param {Node} lightbox lighbox YUI element returned by addSectionLightbox\n * @param {Number} delay\n */\n var removeLightbox = function(lightbox, delay) {\n if (lightbox) {\n window.setTimeout(function() {\n lightbox.hide();\n // Unlock state if necessary.\n if (lightbox.getAttribute('data-state')) {\n courseeditor.dispatch(\n `${lightbox.getAttribute('data-state')}Lock`,\n [lightbox.getAttribute('data-state-id')],\n false\n );\n }\n }, delay);\n }\n };\n\n /**\n * Initialise action menu for the element (section or module)\n *\n * @param {String} elementid CSS id attribute of the element\n */\n var initActionMenu = function(elementid) {\n // Initialise action menu in the new activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);\n });\n if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {\n M.core.actionmenu.newDOMNode(Y.one('#' + elementid));\n }\n };\n\n /**\n * Returns focus to the element that was clicked or \"Edit\" link if element is no longer visible.\n *\n * @param {String} elementId CSS id attribute of the element\n * @param {String} action data-action property of the element that was clicked\n */\n var focusActionItem = function(elementId, action) {\n var mainelement = $('#' + elementId);\n var selector = '[data-action=' + action + ']';\n if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {\n // New element will have different data-action.\n selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';\n }\n if (mainelement.find(selector).is(':visible')) {\n mainelement.find(selector).focus();\n } else {\n // Element not visible, focus the \"Edit\" link.\n mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();\n }\n };\n\n /**\n * Find next
after the element\n *\n * @param {JQuery} mainElement element that is about to be deleted\n * @returns {JQuery}\n */\n var findNextFocusable = function(mainElement) {\n var tabables = $(\"a:visible\");\n var isInside = false;\n var foundElement = null;\n tabables.each(function() {\n if ($.contains(mainElement[0], this)) {\n isInside = true;\n } else if (isInside) {\n foundElement = this;\n return false; // Returning false in .each() is equivalent to \"break;\" inside the loop in php.\n }\n return true;\n });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : null\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('
' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const activityElement = $(element);\n var spinner = addActivitySpinner(activityElement);\n var promises = ajax.call([{\n methodname: 'core_course_get_module',\n args: {id: cmid, sectionreturn: sectionreturn}\n }], true);\n\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(function(data) {\n removeSpinner(activityElement, spinner, 400);\n replaceActivityHtmlWith(data);\n resolve(data);\n }).fail(function() {\n removeSpinner(activityElement, spinner);\n reject();\n });\n });\n };\n\n /**\n * Requests html for the section via WS core_course_edit_section and updates the section on the course page\n *\n * @param {JQuery|Element} element\n * @param {Number} sectionid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshSection = function(element, sectionid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const sectionElement = $(element);\n const action = 'refresh';\n const promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action, sectionreturn},\n }], true);\n\n var spinner = addSectionSpinner(sectionElement);\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(dataencoded => {\n\n removeSpinner(sectionElement, spinner);\n const data = $.parseJSON(dataencoded);\n\n const newSectionElement = $(data.content);\n sectionElement.replaceWith(newSectionElement);\n\n // Init modules menus.\n $(`${SELECTOR.SECTIONLI}#${sectionid} ${SELECTOR.ACTIVITYLI}`).each(\n (index, activity) => {\n initActionMenu(activity.data('id'));\n }\n );\n\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n CourseEvents.sectionRefreshed,\n {\n ajaxreturn: data,\n action: action,\n newSectionElement: newSectionElement.get(0),\n },\n newSectionElement\n );\n\n if (!event.defaultPrevented) {\n defaultEditSectionHandler(\n newSectionElement, $(SELECTOR.SECTIONLI + '#' + sectionid),\n data,\n formatname,\n sectionid\n );\n }\n resolve(data);\n }).fail(ex => {\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n 'coursesectionrefreshfailed',\n {exception: ex, action: action},\n sectionElement\n );\n if (!event.defaultPrevented) {\n notification.exception(ex);\n }\n reject();\n });\n });\n };\n\n /**\n * Displays the delete confirmation to delete a module\n *\n * @param {JQuery} mainelement activity element we perform action on\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmDeleteModule = function(mainelement, onconfirm) {\n var modtypename = mainelement.attr('class').match(/modtype_([^\\s]*)/)[1];\n var modulename = getModuleName(mainelement);\n\n str.get_string('pluginname', modtypename).done(function(pluginname) {\n var plugindata = {\n type: pluginname,\n name: modulename\n };\n str.get_strings([\n {key: 'confirm', component: 'core'},\n {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], s[1], s[2], s[3], onconfirm);\n }\n );\n });\n };\n\n /**\n * Displays the delete confirmation to delete a section\n *\n * @param {String} message confirmation message\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmEditSection = function(message, onconfirm) {\n str.get_strings([\n {key: 'confirm'}, // TODO link text\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], message, s[1], s[2], onconfirm);\n }\n );\n };\n\n /**\n * Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * @param {JQuery} actionitem\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n * @return {Promise} promise which is resolved when the replacement has completed\n */\n var replaceActionItem = function(actionitem, image, stringname,\n stringcomponent, newaction) {\n\n var stringRequests = [{key: stringname, component: stringcomponent}];\n // Do not provide an icon with duplicate, different text to the menu item.\n\n return str.get_strings(stringRequests).then(function(strings) {\n actionitem.find('span.menu-action-text').html(strings[0]);\n\n return templates.renderPix(image, 'core');\n }).then(function(pixhtml) {\n actionitem.find('.icon').replaceWith(pixhtml);\n actionitem.attr('data-action', newaction);\n return;\n }).catch(notification.exception);\n };\n\n /**\n * Default post-processing for section AJAX edit actions.\n *\n * This can be overridden in course formats by listening to event coursesectionedited:\n *\n * $('body').on('coursesectionedited', 'li.section', function(e) {\n * var action = e.action,\n * sectionElement = $(e.target),\n * data = e.ajaxreturn;\n * // ... Do some processing here.\n * e.preventDefault(); // Prevent default handler.\n * });\n *\n * @param {JQuery} sectionElement\n * @param {JQuery} actionItem\n * @param {Object} data\n * @param {String} courseformat\n * @param {Number} sectionid\n */\n var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat, sectionid) {\n var action = actionItem.attr('data-action');\n if (action === 'hide' || action === 'show') {\n if (action === 'hide') {\n sectionElement.addClass('hidden');\n setSectionBadge(sectionElement[0], 'hiddenfromstudents', true, false);\n replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\n setSectionBadge(sectionElement[0], 'hiddenfromstudents', false, false);\n sectionElement.removeClass('hidden');\n replaceActionItem(actionItem, 'i/hide',\n 'hidefromothers', 'format_' + courseformat, 'hide');\n }\n // Replace the modules with new html (that indicates that they are now hidden or not hidden).\n if (data.modules !== undefined) {\n for (var i in data.modules) {\n replaceActivityHtmlWith(data.modules[i]);\n }\n }\n // Replace the section availability information.\n if (data.section_availability !== undefined) {\n sectionElement.find('.section_availability').first().replaceWith(data.section_availability);\n }\n // Modify course state.\n const section = courseeditor.state.section.get(sectionid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [sectionid]);\n }\n } else if (action === 'setmarker') {\n var oldmarker = $(SELECTOR.SECTIONLI + '.current'),\n oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');\n oldmarker.removeClass('current');\n replaceActionItem(oldActionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n sectionElement.addClass('current');\n replaceActionItem(actionItem, 'i/marked',\n 'highlightoff', 'core', 'removemarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n setSectionBadge(sectionElement[0], 'iscurrent', true, true);\n } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n setSectionBadge(sectionElement[0], 'iscurrent', false, true);\n }\n };\n\n /**\n * Get the focused element path in an activity if any.\n *\n * This method is used to restore focus when the activity HTML is refreshed.\n * Only the main course editor elements can be refocused as they are always present\n * even if the activity content changes.\n *\n * @param {String} id the element id the activity element\n * @return {String|undefined} the inner path of the focused element or undefined\n */\n const getActivityFocusedElement = function(id) {\n const element = document.getElementById(id);\n if (!element || !element.contains(document.activeElement)) {\n return undefined;\n }\n // Check if the actions menu toggler is focused.\n if (element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)) {\n return `${SELECTOR.ACTIONAREA} [tabindex=\"0\"]`;\n }\n // Return the current element id if any.\n if (document.activeElement.id) {\n return `#${document.activeElement.id}`;\n }\n return undefined;\n };\n\n /**\n * Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).\n *\n * @param {String} activityHTML\n */\n var replaceActivityHtmlWith = function(activityHTML) {\n $('
' + activityHTML + '
').find(SELECTOR.ACTIVITYLI).each(function() {\n // Extract id from the new activity html.\n var id = $(this).attr('id');\n // Check if the current focused element is inside the activity.\n let focusedPath = getActivityFocusedElement(id);\n // Find the existing element with the same id and replace its contents with new html.\n $(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);\n // Initialise action menu.\n initActionMenu(id);\n // Re-focus the previous elements.\n if (focusedPath) {\n const newItem = document.getElementById(id);\n newItem.querySelector(focusedPath)?.focus();\n }\n\n });\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {Nunmber} sectionid\n * @param {JQuery} target the element (menu item) that was clicked\n * @param {String} courseformat\n * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : null;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n /**\n * Sets the section badge in the section header.\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {String} badgetype the type of badge this is for\n * @param {bool} add true to add, false to remove\n * @param {boolean} removeOther in case of adding a badge, whether to remove all other.\n */\n var setSectionBadge = function(sectionElement, badgetype, add, removeOther) {\n const sectionbadges = sectionElement.querySelector(SELECTOR.SECTIONBADGES);\n if (!sectionbadges) {\n return;\n }\n const badge = sectionbadges.querySelector('[data-type=\"' + badgetype + '\"]');\n if (!badge) {\n return;\n }\n if (add) {\n if (removeOther) {\n document.querySelectorAll('[data-type=\"' + badgetype + '\"]').forEach((b) => {\n b.classList.add('d-none');\n });\n }\n badge.classList.remove('d-none');\n } else {\n badge.classList.add('d-none');\n }\n };\n\n // Register a function to be executed after D&D of an activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.register_module({\n // Ignore camelcase eslint rule for the next line because it is an expected name of the callback.\n // eslint-disable-next-line camelcase\n set_visibility_resource_ui: function(args) {\n var mainelement = $(args.element.getDOMNode());\n var cmid = getModuleId(mainelement);\n if (cmid) {\n var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');\n refreshModule(mainelement, cmid, sectionreturn);\n }\n },\n /**\n * Update the course state when some cm is moved via YUI.\n * @param {*} params\n */\n updateMovedCmState: (params) => {\n const state = courseeditor.state;\n\n // Update old section.\n const cm = state.cm.get(params.cmid);\n if (cm !== undefined) {\n courseeditor.dispatch('sectionState', [cm.sectionid]);\n }\n // Update cm state.\n courseeditor.dispatch('cmState', [params.cmid]);\n },\n /**\n * Update the course state when some section is moved via YUI.\n */\n updateMovedSectionState: () => {\n courseeditor.dispatch('courseState');\n },\n });\n });\n\n // From Moodle 4.0 all edit actions are being re-implemented as state mutation.\n // This means all method from this \"actions\" module will be deprecated when all the course\n // interface is migrated to reactive components.\n // Most legacy actions did not provide enough information to regenarate the course so they\n // use the mutations courseState, sectionState and cmState to get the updated state from\n // the server. However, some activity actions where we can prevent an extra webservice\n // call by implementing an adhoc mutation.\n courseeditor.addMutations({\n /**\n * Compatibility function to update Moodle 4.0 course state using legacy actions.\n *\n * This method only updates some actions which does not require to use cmState mutation\n * to get updated data form the server.\n *\n * @param {Object} statemanager the current state in read write mode\n * @param {String} action the performed action\n * @param {Number} cmid the affected course module id\n * @param {Array} affectedids all affected cm ids (for duplicate action)\n */\n legacyActivityAction: function(statemanager, action, cmid, affectedids) {\n\n const state = statemanager.state;\n const cm = state.cm.get(cmid);\n if (cm === undefined) {\n return;\n }\n const section = state.section.get(cm.sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked.\n courseeditor.dispatch('cmLock', [cm.id], true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This unlocked will take effect when the read only is restored.\n cm.locked = false;\n\n switch (action) {\n case 'delete':\n // Remove from section.\n section.cmlist = section.cmlist.reduce(\n (cmlist, current) => {\n if (current != cmid) {\n cmlist.push(current);\n }\n return cmlist;\n },\n []\n );\n // Delete form list.\n state.cm.delete(cmid);\n break;\n\n case 'hide':\n case 'show':\n case 'duplicate':\n courseeditor.dispatch('cmState', affectedids);\n break;\n }\n statemanager.setReadOnly(true);\n },\n legacySectionAction: function(statemanager, action, sectionid) {\n\n const state = statemanager.state;\n const section = state.section.get(sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n section.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n section.locked = false;\n\n switch (action) {\n case 'setmarker':\n // Remove previous marker.\n state.section.forEach((current) => {\n if (current.id != sectionid) {\n current.current = false;\n }\n });\n section.current = true;\n break;\n\n case 'removemarker':\n section.current = false;\n break;\n }\n statemanager.setReadOnly(true);\n },\n });\n\n return /** @alias module:core_course/actions */ {\n\n /**\n * Initialises course page\n *\n * @method init\n * @param {String} courseformat name of the current course format (for fetching strings)\n */\n initCoursePage: function(courseformat) {\n\n formatname = courseformat;\n\n // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section action menu.\n $('body').on('click keypress',\n SELECTOR.SECTIONACTIONMENUTRIGGER + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENUTRIGGER).attr('data-sectionid');\n\n if (actionItem.attr('data-action') === 'permalink') {\n e.preventDefault();\n ModalCopyToClipboard.create({\n text: actionItem.attr('href'),\n }, str.get_string('sectionlink', 'course')\n );\n return;\n }\n\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on(InplaceEditableEvents.eventTypes.elementUpdated,\n `${SELECTOR.SECTIONITEM} [data-inplaceeditable]`, function(e) {\n if (e.detail.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.detail.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.detail.ajaxreturn.itemid]);\n }\n }\n });\n\n $('body').on(InplaceEditableEvents.eventTypes.elementUpdated,\n `${SELECTOR.ACTIVITYLI} [data-itemtype=\"activityname\"][data-inplaceeditable]`, function(e) {\n if (e.detail.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.detail.ajaxreturn.itemid]);\n }\n });\n\n // Component-based formats don't use modals to create sections.\n if (courseeditor.supportComponents && componentActions.includes('addSection')) {\n return;\n }\n\n // Add a handler for \"Add sections\" link to ask for a number of sections to add.\n const trigger = $(SELECTOR.ADDSECTIONS);\n const modalTitle = trigger.attr('data-add-sections');\n const newSections = trigger.attr('data-new-sections');\n str.get_string('numberweeks')\n .then(function(strNumberSections) {\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n\n return modalBody.html();\n })\n .then((body) => ModalSaveCancel.create({\n body,\n title: modalTitle,\n }))\n .then(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n\n trigger.on('click', (e) => {\n e.preventDefault();\n modal.show();\n });\n\n return modal;\n })\n .catch(notification.exception);\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n refreshSection,\n };\n });\n"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalCopyToClipboard","ModalSaveCancel","ModalEvents","KeyCodes","log","editor","EventDispatcher","InplaceEditableEvents","CourseEvents","debug","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","SECTIONACTIONMENUTRIGGER","SECTIONITEM","ADDSECTIONS","SECTIONBADGES","use","courseformatselector","M","course","format","get_section_selector","dispatchEvent","eventName","detail","container","options","Element","undefined","get","getModuleId","element","item","dataset","id","Moodle","core_course","util","cm","getId","Node","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","for","setAttribute","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","getAttribute","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","editModule","moduleElement","cmid","target","action","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","mainElement","tabables","isInside","foundElement","elementToFocus","each","contains","this","replaceWith","affectedids","index","elementId","mainelement","selector","is","focus","focusActionItem","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","sectionReturn","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","name","getName","state","_state$cm$get","getModuleName","get_string","pluginname","plugindata","type","get_strings","key","component","param","s","confirm","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","stringRequests","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","setSectionBadge","modules","i","section_availability","first","section","oldmarker","oldActionItem","activityHTML","focusedPath","document","getElementById","activeElement","querySelector","getActivityFocusedElement","editSection","supportComponents","includes","dataencoded","parseJSON","badgetype","add","removeOther","sectionbadges","badge","querySelectorAll","forEach","b","classList","remove","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","legacySectionAction","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","create","text","isExecuted","message","eventTypes","elementUpdated","itemid","modalTitle","newSections","strNumberSections","modalBody","body","title","modal","numSections","getBody","addSections","parseInt","val","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","refreshSection","newSectionElement","content","sectionRefreshed","defaultPrevented"],"mappings":"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"} \ No newline at end of file diff --git a/course/amd/src/actions.js b/course/amd/src/actions.js index c74bab967fd..39ca020502d 100644 --- a/course/amd/src/actions.js +++ b/course/amd/src/actions.js @@ -68,7 +68,7 @@ define( const componentActions = [ 'moveSection', 'moveCm', 'addSection', 'deleteSection', 'cmDelete', 'cmDuplicate', 'sectionHide', 'sectionShow', 'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight', 'cmMoveRight', 'cmMoveLeft', - 'cmNoGroups', 'cmVisibleGroups', 'cmSeparateGroups', 'addModule', + 'cmNoGroups', 'cmVisibleGroups', 'cmSeparateGroups', 'addModule', 'newModule', ]; // The course reactive instance. diff --git a/course/format/amd/build/local/content/actions.min.js b/course/format/amd/build/local/content/actions.min.js index 69fac67e974..141a4acc28e 100644 --- a/course/format/amd/build/local/content/actions.min.js +++ b/course/format/amd/build/local/content/actions.min.js @@ -1,4 +1,4 @@ -define("core_courseformat/local/content/actions",["exports","core/reactive","core/local/inplace_editable/events","theme_boost/bootstrap/collapse","core/modal","core/modal_save_cancel","core/modal_delete_cancel","core/modal_copy_to_clipboard","core/modal_events","core/templates","core/prefetch","core/str","core/normalise","core_courseformat/local/content/actions/bulkselection","core_course/events","core/pending","core_courseformat/local/courseeditor/contenttree","core/notification"],(function(_exports,_reactive,_events,_collapse,_modal,_modal_save_cancel,_modal_delete_cancel,_modal_copy_to_clipboard,_modal_events,_templates,_prefetch,_str,_normalise,_bulkselection,CourseEvents,_pending,_contenttree,_notification){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +define("core_courseformat/local/content/actions",["exports","core/reactive","core/local/inplace_editable/events","theme_boost/bootstrap/collapse","core/log","core/modal","core/modal_save_cancel","core/modal_delete_cancel","core/modal_copy_to_clipboard","core/modal_events","core/templates","core/prefetch","core/str","core/normalise","core_courseformat/local/content/actions/bulkselection","core_course/events","core/pending","core_courseformat/local/courseeditor/contenttree","core/notification"],(function(_exports,_reactive,_events,_collapse,_log,_modal,_modal_save_cancel,_modal_delete_cancel,_modal_copy_to_clipboard,_modal_events,_templates,_prefetch,_str,_normalise,_bulkselection,CourseEvents,_pending,_contenttree,_notification){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Course state actions dispatcher. * @@ -9,6 +9,6 @@ define("core_courseformat/local/content/actions",["exports","core/reactive","cor * @class core_courseformat/local/content/actions * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_collapse=_interopRequireDefault(_collapse),_modal=_interopRequireDefault(_modal),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_modal_delete_cancel=_interopRequireDefault(_modal_delete_cancel),_modal_copy_to_clipboard=_interopRequireDefault(_modal_copy_to_clipboard),_modal_events=_interopRequireDefault(_modal_events),_templates=_interopRequireDefault(_templates),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents),_pending=_interopRequireDefault(_pending),_contenttree=_interopRequireDefault(_contenttree),_notification=_interopRequireDefault(_notification),(0,_prefetch.prefetchStrings)("core",["movecoursesection","movecoursemodule","confirm","delete"]);const directMutations={sectionHide:"sectionHide",sectionShow:"sectionShow",cmHide:"cmHide",cmShow:"cmShow",cmStealth:"cmStealth",cmMoveRight:"cmMoveRight",cmMoveLeft:"cmMoveLeft",cmNoGroups:"cmNoGroups",cmSeparateGroups:"cmSeparateGroups",cmVisibleGroups:"cmVisibleGroups"};class _default extends _reactive.BaseComponent{create(){this.name="content_actions",this.selectors={ACTIONLINK:"[data-action]",SECTIONLINK:"[data-for='section']",CMLINK:"[data-for='cm']",SECTIONNODE:"[data-for='sectionnode']",MODALTOGGLER:"[data-bs-toggle='collapse']",ADDSECTION:"[data-action='addSection']",CONTENTTREE:"#destination-selector",ACTIONMENU:".action-menu",ACTIONMENUTOGGLER:'[data-bs-toggle="dropdown"]',OPTIONSRADIO:"[type='radio']",COURSEADDSECTION:"#course-addsection",MAXSECTIONSWARNING:"[data-region='max-sections-warning']",ADDSECTIONREGION:"[data-region='section-addsection']"},this.classes={DISABLED:"disabled",ITALIC:"fst-italic",DISPLAYNONE:"d-none"}}static addActions(actions){for(const[action,mutationReference]of Object.entries(actions)){if("function"!=typeof mutationReference&&"string"!=typeof mutationReference)throw new Error("".concat(action," action must be a mutation name or a function"));directMutations[action]=mutationReference}}stateReady(state){this.addEventListener(this.element,"click",this._dispatchClick),this._checkSectionlist({state:state}),this.addEventListener(this.element,CourseEvents.sectionRefreshed,(()=>this._checkSectionlist({state:state}))),this.addEventListener(this.element,_events.eventTypes.elementUpdated,this._inplaceEditableHandler)}getWatchers(){return[{watch:"course.sectionlist:updated",handler:this._checkSectionlist}]}_dispatchClick(event){const target=event.target.closest(this.selectors.ACTIONLINK);if(!target)return;if(target.classList.contains(this.classes.DISABLED))return void event.preventDefault();const actionName=target.dataset.action,methodName=this._actionMethodName(actionName);if(void 0===this[methodName])return void 0!==directMutations[actionName]?"function"==typeof directMutations[actionName]?void directMutations[actionName](target,event):void this._requestMutationAction(target,event,directMutations[actionName]):void 0;this[methodName](target,event)}_actionMethodName(name){const requestName=name.charAt(0).toUpperCase()+name.slice(1);return"_request".concat(requestName)}_checkSectionlist(_ref){let{state:state}=_ref;this._setAddSectionLocked(state.course.sectionlist.length>state.course.maxsections)}_inplaceEditableHandler(event){var _event$detail,_event$detail$ajaxret,_event$detail2,_event$detail2$ajaxre;const itemtype=null===(_event$detail=event.detail)||void 0===_event$detail||null===(_event$detail$ajaxret=_event$detail.ajaxreturn)||void 0===_event$detail$ajaxret?void 0:_event$detail$ajaxret.itemtype,itemid=parseInt(null===(_event$detail2=event.detail)||void 0===_event$detail2||null===(_event$detail2$ajaxre=_event$detail2.ajaxreturn)||void 0===_event$detail2$ajaxre?void 0:_event$detail2$ajaxre.itemid);Number.isFinite(itemid)&&itemtype&&("activityname"!==itemtype?"sectionname"!==itemtype&&"sectionnamenl"!==itemtype||this.reactive.dispatch("sectionState",[itemid]):this.reactive.dispatch("cmState",[itemid]))}_getTargetIds(target){var _target$dataset,_target$dataset2;let ids=[];null!=target&&null!==(_target$dataset=target.dataset)&&void 0!==_target$dataset&&_target$dataset.id&&ids.push(target.dataset.id);const bulkType=null==target||null===(_target$dataset2=target.dataset)||void 0===_target$dataset2?void 0:_target$dataset2.bulk;if(!bulkType)return ids;const bulk=this.reactive.get("bulk");return bulk.enabled&&bulk.selectedType===bulkType&&(ids=[...ids,...bulk.selection]),ids}async _requestMoveSection(target,event){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;event.preventDefault();const pendingModalReady=new _pending.default("courseformat/actions:prepareMoveSectionModal"),editTools=this._getClosestActionMenuToogler(target),data=this.reactive.getExporter().course(this.reactive.state);let titleText=null,sectionInfo=null;1==sectionIds.length?(sectionInfo=this.reactive.get("section",sectionIds[0]),data.sectionid=sectionInfo.id,data.sectiontitle=sectionInfo.title,data.information=await this.reactive.getFormatString("sectionmove_info",data.sectiontitle),titleText=this.reactive.getFormatString("sectionmove_title")):(data.information=await this.reactive.getFormatString("sectionsmove_info",sectionIds.length),titleText=this.reactive.getFormatString("sectionsmove_title"));const modal=await this._modalBodyRenderedPromise(_modal.default,{title:titleText,body:_templates.default.render("core_courseformat/local/content/movesection",data)}),modalBody=(0,_normalise.getFirst)(modal.getBody());sectionIds.forEach((sectionId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(sectionId,"']"));this._disableLink(currentElement)})),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER},!0),modalBody.addEventListener("click",(event=>{const target=event.target;target.matches("a")&&"section"==target.dataset.for&&void 0!==target.dataset.id&&(target.getAttribute("aria-disabled")||(event.preventDefault(),this.reactive.dispatch("sectionMoveAfter",sectionIds,target.dataset.id),this._destroyModal(modal,editTools)))})),pendingModalReady.resolve()}async _requestMoveCm(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();const pendingModalReady=new _pending.default("courseformat/actions:prepareMoveCmModal"),editTools=this._getClosestActionMenuToogler(target),exporter=this.reactive.getExporter(),data=exporter.course(this.reactive.state);let titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);data.cmid=cmInfo.id,data.cmname=cmInfo.name,data.information=await this.reactive.getFormatString("cmmove_info",data.cmname),titleText=cmInfo.hasdelegatedsection?this.reactive.getFormatString("cmmove_subsectiontitle"):this.reactive.getFormatString("cmmove_title")}else data.information=await this.reactive.getFormatString("cmsmove_info",cmIds.length),titleText=this.reactive.getFormatString("cmsmove_title");const modal=await this._modalBodyRenderedPromise(_modal.default,{title:titleText,body:_templates.default.render("core_courseformat/local/content/movecm",data)}),modalBody=(0,_normalise.getFirst)(modal.getBody());cmIds.forEach((cmId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']"));this._disableLink(currentElement)})),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER,ENTER:this.selectors.SECTIONLINK}),cmIds.forEach((cmId=>{const cmInfo=this.reactive.get("cm",cmId);let selector;selector=cmInfo.hasdelegatedsection?"".concat(this.selectors.SECTIONLINK,"[data-id='").concat(cmInfo.sectionid,"']"):"".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']");const currentElement=modalBody.querySelector(selector);this._expandCmMoveModalParentSections(modalBody,currentElement)})),modalBody.addEventListener("click",(event=>{const target=event.target;if(!target.matches("a")||void 0===target.dataset.for||void 0===target.dataset.id)return;if(target.getAttribute("aria-disabled"))return;let targetSectionId,targetCmId;event.preventDefault();let droppedCmIds=[...cmIds];if("cm"==target.dataset.for){const dropData=exporter.cmDraggableData(this.reactive.state,target.dataset.id);targetSectionId=dropData.sectionid,targetCmId=dropData.nextcmid}else{const section=this.reactive.get("section",target.dataset.id);targetSectionId=target.dataset.id,targetCmId=null==section?void 0:section.cmlist[0]}this.reactive.get("section",targetSectionId).component&&(droppedCmIds=droppedCmIds.filter((cmId=>!this.reactive.get("cm",cmId).hasdelegatedsection))),0!==droppedCmIds.length&&(this.reactive.dispatch("cmMove",droppedCmIds,targetSectionId,targetCmId),this._destroyModal(modal,editTools))})),pendingModalReady.resolve()}_expandCmMoveModalParentSections(modalBody,element){var _toggler$dataset$targ;const sectionnode=element.closest(this.selectors.SECTIONNODE);if(!sectionnode)return;const toggler=sectionnode.querySelector(this.selectors.MODALTOGGLER);let collapsibleId=null!==(_toggler$dataset$targ=toggler.dataset.target)&&void 0!==_toggler$dataset$targ?_toggler$dataset$targ:toggler.getAttribute("href");if(collapsibleId){collapsibleId=collapsibleId.replace("#","");const expandNode=modalBody.querySelector("#".concat(collapsibleId));new _collapse.default(expandNode,{toggle:!1}).show()}this._expandCmMoveModalParentSections(modalBody,sectionnode.parentElement)}async _requestAddSection(target,event){var _target$dataset$id;event.preventDefault(),this.reactive.dispatch("addSection",null!==(_target$dataset$id=target.dataset.id)&&void 0!==_target$dataset$id?_target$dataset$id:0)}async _requestAddModule(target,event){event.preventDefault(),this.reactive.dispatch("addModule",target.dataset.modname,target.dataset.sectionnum,target.dataset.beforemod)}async _requestDeleteSection(target,event){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;if(event.preventDefault(),!sectionIds.some((sectionId=>{var _sectionInfo$cmlist;const sectionInfo=this.reactive.get("section",sectionId);return(null!==(_sectionInfo$cmlist=sectionInfo.cmlist)&&void 0!==_sectionInfo$cmlist?_sectionInfo$cmlist:[]).length||sectionInfo.hassummary||sectionInfo.rawtitle})))return void this._dispatchSectionDelete(sectionIds,target);let bodyText=null,titleText=null;if(1==sectionIds.length){titleText=this.reactive.getFormatString("sectiondelete_title");const sectionInfo=this.reactive.get("section",sectionIds[0]);bodyText=this.reactive.getFormatString("sectiondelete_info",{name:sectionInfo.title})}else titleText=this.reactive.getFormatString("sectionsdelete_title"),bodyText=this.reactive.getFormatString("sectionsdelete_info",{count:sectionIds.length});const modal=await this._modalBodyRenderedPromise(_modal_delete_cancel.default,{title:titleText,body:bodyText});modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this._dispatchSectionDelete(sectionIds,target)}))}async _dispatchSectionDelete(sectionIds,target){await this.reactive.dispatch("sectionDelete",sectionIds),target.baseURI.includes("section.php")&&(window.location.href=this.reactive.get("course").baseurl)}async _requestToggleSelectionCm(target,event){(0,_bulkselection.toggleBulkSelectionAction)(this.reactive,target,event,"cm")}async _requestToggleSelectionSection(target,event){(0,_bulkselection.toggleBulkSelectionAction)(this.reactive,target,event,"section")}async _requestMutationAction(target,event,mutationName){(target.dataset.id||"bulkaction"===target.dataset.for)&&(event.preventDefault(),"bulkaction"===target.dataset.for?this.reactive.dispatch(mutationName,this.reactive.get("bulk").selection):this.reactive.dispatch(mutationName,[target.dataset.id]))}_requestPermalink(target,event){event.preventDefault(),_modal_copy_to_clipboard.default.create({text:target.getAttribute("href")},(0,_str.getString)("sectionlink","course"))}async _requestCmDuplicate(target,event){var _target$dataset$secti;const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const sectionId=null!==(_target$dataset$secti=target.dataset.sectionid)&&void 0!==_target$dataset$secti?_target$dataset$secti:null;event.preventDefault(),this.reactive.dispatch("cmDuplicate",cmIds,sectionId)}async _requestCmDelete(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();let bodyText=null,titleText=null,delegatedsection=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);cmInfo.hasdelegatedsection?(delegatedsection=cmInfo.delegatesectionid,titleText=this.reactive.getFormatString("cmdelete_subsectiontitle"),bodyText=(0,_str.getString)("sectiondelete_info","core_courseformat",{type:cmInfo.modname,name:cmInfo.name})):(titleText=this.reactive.getFormatString("cmdelete_title"),bodyText=(0,_str.getString)("cmdelete_info","core_courseformat",{type:cmInfo.modname,name:cmInfo.name}))}else titleText=(0,_str.getString)("cmsdelete_title","core_courseformat"),bodyText=(0,_str.getString)("cmsdelete_info","core_courseformat",{count:cmIds.length});const modal=await this._modalBodyRenderedPromise(_modal_delete_cancel.default,{title:titleText,body:bodyText});modal.getRoot().on(_modal_events.default.delete,(e=>{if(e.preventDefault(),modal.destroy(),this.reactive.dispatch("cmDelete",cmIds),1==cmIds.length&&delegatedsection&&target.baseURI.includes("section.php")){let parameters=new URLSearchParams(window.location.search);parameters.has("id")&¶meters.get("id")==delegatedsection&&this._dispatchSectionDelete([delegatedsection],target)}}))}async _requestCmAvailability(target){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const data={allowstealth:this.reactive.getExporter().canUseStealth(this.reactive.state,cmIds)},modal=await this._modalBodyRenderedPromise(_modal_save_cancel.default,{title:(0,_str.getString)("availability","core"),body:_templates.default.render("core_courseformat/local/content/cm/availabilitymodal",data),saveButtonText:(0,_str.getString)("apply","core")});this._setupMutationRadioButtonModal(modal,cmIds)}async _requestSectionAvailability(target){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;const title=1==sectionIds.length?"sectionavailability_title":"sectionsavailability_title",modal=await this._modalBodyRenderedPromise(_modal_save_cancel.default,{title:this.reactive.getFormatString(title),body:_templates.default.render("core_courseformat/local/content/section/availabilitymodal",[]),saveButtonText:(0,_str.getString)("apply","core")});this._setupMutationRadioButtonModal(modal,sectionIds)}_setupMutationRadioButtonModal(modal,ids){modal.setButtonDisabled("save",!0);const submitFunction=radio=>{const mutation=null==radio?void 0:radio.value;return!!mutation&&(this.reactive.dispatch(mutation,ids),!0)},modalBody=(0,_normalise.getFirst)(modal.getBody());modalBody.querySelectorAll(this.selectors.OPTIONSRADIO).forEach((radio=>{radio.addEventListener("change",(()=>{modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("click",(()=>{radio.checked=!0,modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("dblclick",(dbClickEvent=>{submitFunction(radio)&&(dbClickEvent.preventDefault(),modal.destroy())}))})),modal.getRoot().on(_modal_events.default.save,(()=>{const radio=modalBody.querySelector("".concat(this.selectors.OPTIONSRADIO,":checked"));submitFunction(radio)}))}_setAddSectionLocked(locked){this.getElements(this.selectors.ADDSECTIONREGION).forEach((element=>{element.classList.toggle(this.classes.DISABLED,locked);const addSectionElement=element.querySelector(this.selectors.ADDSECTION);addSectionElement.classList.toggle(this.classes.DISABLED,locked),this.setElementLocked(addSectionElement,locked),locked?((0,_str.getString)("sectionaddmax","core_courseformat").then((text=>addSectionElement.setAttribute("title",text))).catch(_notification.default.exception),addSectionElement.style.pointerEvents=null,addSectionElement.style.userSelect=null):addSectionElement.setAttribute("title",addSectionElement.dataset.addSections)}));const courseAddSection=this.getElement(this.selectors.COURSEADDSECTION);if(courseAddSection){courseAddSection.querySelector(this.selectors.ADDSECTION).classList.toggle(this.classes.DISPLAYNONE,locked);courseAddSection.querySelector(this.selectors.MAXSECTIONSWARNING).classList.toggle(this.classes.DISPLAYNONE,!locked)}}_disableLink(element){element&&(element.style.pointerEvents="none",element.style.userSelect="none",element.classList.add(this.classes.DISABLED),element.classList.add(this.classes.ITALIC),element.setAttribute("aria-disabled",!0),element.addEventListener("click",(event=>event.preventDefault())))}_modalBodyRenderedPromise(ModalClass,modalParams){return new Promise(((resolve,reject)=>{ModalClass.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),void 0!==modalParams.deleteButtonText&&modal.setDeleteButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}_destroyModal(modal,element){modal.hide();const pendingDestroy=new _pending.default("courseformat/actions:destroyModal");element&&element.focus(),setTimeout((()=>{modal.destroy(),pendingDestroy.resolve()}),500)}_getClosestActionMenuToogler(element){const actionMenu=element.closest(this.selectors.ACTIONMENU);if(actionMenu)return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_collapse=_interopRequireDefault(_collapse),_log=_interopRequireDefault(_log),_modal=_interopRequireDefault(_modal),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_modal_delete_cancel=_interopRequireDefault(_modal_delete_cancel),_modal_copy_to_clipboard=_interopRequireDefault(_modal_copy_to_clipboard),_modal_events=_interopRequireDefault(_modal_events),_templates=_interopRequireDefault(_templates),CourseEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(CourseEvents),_pending=_interopRequireDefault(_pending),_contenttree=_interopRequireDefault(_contenttree),_notification=_interopRequireDefault(_notification),(0,_prefetch.prefetchStrings)("core",["movecoursesection","movecoursemodule","confirm","delete"]);const directMutations={sectionHide:"sectionHide",sectionShow:"sectionShow",cmHide:"cmHide",cmShow:"cmShow",cmStealth:"cmStealth",cmMoveRight:"cmMoveRight",cmMoveLeft:"cmMoveLeft",cmNoGroups:"cmNoGroups",cmSeparateGroups:"cmSeparateGroups",cmVisibleGroups:"cmVisibleGroups"};class _default extends _reactive.BaseComponent{create(){this.name="content_actions",this.selectors={ACTIONLINK:"[data-action]",SECTIONLINK:"[data-for='section']",CMLINK:"[data-for='cm']",SECTIONNODE:"[data-for='sectionnode']",MODALTOGGLER:"[data-bs-toggle='collapse']",ADDSECTION:"[data-action='addSection']",CONTENTTREE:"#destination-selector",ACTIONMENU:".action-menu",ACTIONMENUTOGGLER:'[data-bs-toggle="dropdown"]',OPTIONSRADIO:"[type='radio']",COURSEADDSECTION:"#course-addsection",MAXSECTIONSWARNING:"[data-region='max-sections-warning']",ADDSECTIONREGION:"[data-region='section-addsection']"},this.classes={DISABLED:"disabled",ITALIC:"fst-italic",DISPLAYNONE:"d-none"}}static addActions(actions){for(const[action,mutationReference]of Object.entries(actions)){if("function"!=typeof mutationReference&&"string"!=typeof mutationReference)throw new Error("".concat(action," action must be a mutation name or a function"));directMutations[action]=mutationReference}}stateReady(state){this.addEventListener(this.element,"click",this._dispatchClick),this._checkSectionlist({state:state}),this.addEventListener(this.element,CourseEvents.sectionRefreshed,(()=>this._checkSectionlist({state:state}))),this.addEventListener(this.element,_events.eventTypes.elementUpdated,this._inplaceEditableHandler)}getWatchers(){return[{watch:"course.sectionlist:updated",handler:this._checkSectionlist}]}_dispatchClick(event){const target=event.target.closest(this.selectors.ACTIONLINK);if(!target)return;if(target.classList.contains(this.classes.DISABLED))return void event.preventDefault();const actionName=target.dataset.action,methodName=this._actionMethodName(actionName);if(void 0===this[methodName])return void 0!==directMutations[actionName]?"function"==typeof directMutations[actionName]?void directMutations[actionName](target,event):void this._requestMutationAction(target,event,directMutations[actionName]):void 0;this[methodName](target,event)}_actionMethodName(name){const requestName=name.charAt(0).toUpperCase()+name.slice(1);return"_request".concat(requestName)}_checkSectionlist(_ref){let{state:state}=_ref;this._setAddSectionLocked(state.course.sectionlist.length>state.course.maxsections)}_inplaceEditableHandler(event){var _event$detail,_event$detail$ajaxret,_event$detail2,_event$detail2$ajaxre;const itemtype=null===(_event$detail=event.detail)||void 0===_event$detail||null===(_event$detail$ajaxret=_event$detail.ajaxreturn)||void 0===_event$detail$ajaxret?void 0:_event$detail$ajaxret.itemtype,itemid=parseInt(null===(_event$detail2=event.detail)||void 0===_event$detail2||null===(_event$detail2$ajaxre=_event$detail2.ajaxreturn)||void 0===_event$detail2$ajaxre?void 0:_event$detail2$ajaxre.itemid);Number.isFinite(itemid)&&itemtype&&("activityname"!==itemtype?"sectionname"!==itemtype&&"sectionnamenl"!==itemtype||this.reactive.dispatch("sectionState",[itemid]):this.reactive.dispatch("cmState",[itemid]))}_getTargetIds(target){var _target$dataset,_target$dataset2;let ids=[];null!=target&&null!==(_target$dataset=target.dataset)&&void 0!==_target$dataset&&_target$dataset.id&&ids.push(target.dataset.id);const bulkType=null==target||null===(_target$dataset2=target.dataset)||void 0===_target$dataset2?void 0:_target$dataset2.bulk;if(!bulkType)return ids;const bulk=this.reactive.get("bulk");return bulk.enabled&&bulk.selectedType===bulkType&&(ids=[...ids,...bulk.selection]),ids}async _requestMoveSection(target,event){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;event.preventDefault();const pendingModalReady=new _pending.default("courseformat/actions:prepareMoveSectionModal"),editTools=this._getClosestActionMenuToogler(target),data=this.reactive.getExporter().course(this.reactive.state);let titleText=null,sectionInfo=null;1==sectionIds.length?(sectionInfo=this.reactive.get("section",sectionIds[0]),data.sectionid=sectionInfo.id,data.sectiontitle=sectionInfo.title,data.information=await this.reactive.getFormatString("sectionmove_info",data.sectiontitle),titleText=this.reactive.getFormatString("sectionmove_title")):(data.information=await this.reactive.getFormatString("sectionsmove_info",sectionIds.length),titleText=this.reactive.getFormatString("sectionsmove_title"));const modal=await this._modalBodyRenderedPromise(_modal.default,{title:titleText,body:_templates.default.render("core_courseformat/local/content/movesection",data)}),modalBody=(0,_normalise.getFirst)(modal.getBody());sectionIds.forEach((sectionId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.SECTIONLINK,"[data-id='").concat(sectionId,"']"));this._disableLink(currentElement)})),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER},!0),modalBody.addEventListener("click",(event=>{const target=event.target;target.matches("a")&&"section"==target.dataset.for&&void 0!==target.dataset.id&&(target.getAttribute("aria-disabled")||(event.preventDefault(),this.reactive.dispatch("sectionMoveAfter",sectionIds,target.dataset.id),this._destroyModal(modal,editTools)))})),pendingModalReady.resolve()}async _requestMoveCm(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();const pendingModalReady=new _pending.default("courseformat/actions:prepareMoveCmModal"),editTools=this._getClosestActionMenuToogler(target),exporter=this.reactive.getExporter(),data=exporter.course(this.reactive.state);let titleText=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);data.cmid=cmInfo.id,data.cmname=cmInfo.name,data.information=await this.reactive.getFormatString("cmmove_info",data.cmname),titleText=cmInfo.hasdelegatedsection?this.reactive.getFormatString("cmmove_subsectiontitle"):this.reactive.getFormatString("cmmove_title")}else data.information=await this.reactive.getFormatString("cmsmove_info",cmIds.length),titleText=this.reactive.getFormatString("cmsmove_title");const modal=await this._modalBodyRenderedPromise(_modal.default,{title:titleText,body:_templates.default.render("core_courseformat/local/content/movecm",data)}),modalBody=(0,_normalise.getFirst)(modal.getBody());cmIds.forEach((cmId=>{const currentElement=modalBody.querySelector("".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']"));this._disableLink(currentElement)})),new _contenttree.default(modalBody.querySelector(this.selectors.CONTENTTREE),{SECTION:this.selectors.SECTIONNODE,TOGGLER:this.selectors.MODALTOGGLER,COLLAPSE:this.selectors.MODALTOGGLER,ENTER:this.selectors.SECTIONLINK}),cmIds.forEach((cmId=>{const cmInfo=this.reactive.get("cm",cmId);let selector;selector=cmInfo.hasdelegatedsection?"".concat(this.selectors.SECTIONLINK,"[data-id='").concat(cmInfo.sectionid,"']"):"".concat(this.selectors.CMLINK,"[data-id='").concat(cmId,"']");const currentElement=modalBody.querySelector(selector);this._expandCmMoveModalParentSections(modalBody,currentElement)})),modalBody.addEventListener("click",(event=>{const target=event.target;if(!target.matches("a")||void 0===target.dataset.for||void 0===target.dataset.id)return;if(target.getAttribute("aria-disabled"))return;let targetSectionId,targetCmId;event.preventDefault();let droppedCmIds=[...cmIds];if("cm"==target.dataset.for){const dropData=exporter.cmDraggableData(this.reactive.state,target.dataset.id);targetSectionId=dropData.sectionid,targetCmId=dropData.nextcmid}else{const section=this.reactive.get("section",target.dataset.id);targetSectionId=target.dataset.id,targetCmId=null==section?void 0:section.cmlist[0]}this.reactive.get("section",targetSectionId).component&&(droppedCmIds=droppedCmIds.filter((cmId=>!this.reactive.get("cm",cmId).hasdelegatedsection))),0!==droppedCmIds.length&&(this.reactive.dispatch("cmMove",droppedCmIds,targetSectionId,targetCmId),this._destroyModal(modal,editTools))})),pendingModalReady.resolve()}_expandCmMoveModalParentSections(modalBody,element){var _toggler$dataset$targ;const sectionnode=element.closest(this.selectors.SECTIONNODE);if(!sectionnode)return;const toggler=sectionnode.querySelector(this.selectors.MODALTOGGLER);let collapsibleId=null!==(_toggler$dataset$targ=toggler.dataset.target)&&void 0!==_toggler$dataset$targ?_toggler$dataset$targ:toggler.getAttribute("href");if(collapsibleId){collapsibleId=collapsibleId.replace("#","");const expandNode=modalBody.querySelector("#".concat(collapsibleId));new _collapse.default(expandNode,{toggle:!1}).show()}this._expandCmMoveModalParentSections(modalBody,sectionnode.parentElement)}async _requestAddSection(target,event){var _target$dataset$id;event.preventDefault(),this.reactive.dispatch("addSection",null!==(_target$dataset$id=target.dataset.id)&&void 0!==_target$dataset$id?_target$dataset$id:0)}async _requestAddModule(target,event){_log.default.debug("AddModule action is deprecated. Use newModule instead"),event.preventDefault(),this.reactive.dispatch("addModule",target.dataset.modname,target.dataset.sectionnum,target.dataset.beforemod)}async _requestNewModule(target,event){event.preventDefault(),this.reactive.dispatch("newModule",target.dataset.modname,target.dataset.sectionid,target.dataset.beforemod)}async _requestDeleteSection(target,event){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;if(event.preventDefault(),!sectionIds.some((sectionId=>{var _sectionInfo$cmlist;const sectionInfo=this.reactive.get("section",sectionId);return(null!==(_sectionInfo$cmlist=sectionInfo.cmlist)&&void 0!==_sectionInfo$cmlist?_sectionInfo$cmlist:[]).length||sectionInfo.hassummary||sectionInfo.rawtitle})))return void this._dispatchSectionDelete(sectionIds,target);let bodyText=null,titleText=null;if(1==sectionIds.length){titleText=this.reactive.getFormatString("sectiondelete_title");const sectionInfo=this.reactive.get("section",sectionIds[0]);bodyText=this.reactive.getFormatString("sectiondelete_info",{name:sectionInfo.title})}else titleText=this.reactive.getFormatString("sectionsdelete_title"),bodyText=this.reactive.getFormatString("sectionsdelete_info",{count:sectionIds.length});const modal=await this._modalBodyRenderedPromise(_modal_delete_cancel.default,{title:titleText,body:bodyText});modal.getRoot().on(_modal_events.default.delete,(e=>{e.preventDefault(),modal.destroy(),this._dispatchSectionDelete(sectionIds,target)}))}async _dispatchSectionDelete(sectionIds,target){await this.reactive.dispatch("sectionDelete",sectionIds),target.baseURI.includes("section.php")&&(window.location.href=this.reactive.get("course").baseurl)}async _requestToggleSelectionCm(target,event){(0,_bulkselection.toggleBulkSelectionAction)(this.reactive,target,event,"cm")}async _requestToggleSelectionSection(target,event){(0,_bulkselection.toggleBulkSelectionAction)(this.reactive,target,event,"section")}async _requestMutationAction(target,event,mutationName){(target.dataset.id||"bulkaction"===target.dataset.for)&&(event.preventDefault(),"bulkaction"===target.dataset.for?this.reactive.dispatch(mutationName,this.reactive.get("bulk").selection):this.reactive.dispatch(mutationName,[target.dataset.id]))}_requestPermalink(target,event){event.preventDefault(),_modal_copy_to_clipboard.default.create({text:target.getAttribute("href")},(0,_str.getString)("sectionlink","course"))}async _requestCmDuplicate(target,event){var _target$dataset$secti;const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const sectionId=null!==(_target$dataset$secti=target.dataset.sectionid)&&void 0!==_target$dataset$secti?_target$dataset$secti:null;event.preventDefault(),this.reactive.dispatch("cmDuplicate",cmIds,sectionId)}async _requestCmDelete(target,event){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;event.preventDefault();let bodyText=null,titleText=null,delegatedsection=null;if(1==cmIds.length){const cmInfo=this.reactive.get("cm",cmIds[0]);cmInfo.hasdelegatedsection?(delegatedsection=cmInfo.delegatesectionid,titleText=this.reactive.getFormatString("cmdelete_subsectiontitle"),bodyText=(0,_str.getString)("sectiondelete_info","core_courseformat",{type:cmInfo.modname,name:cmInfo.name})):(titleText=this.reactive.getFormatString("cmdelete_title"),bodyText=(0,_str.getString)("cmdelete_info","core_courseformat",{type:cmInfo.modname,name:cmInfo.name}))}else titleText=(0,_str.getString)("cmsdelete_title","core_courseformat"),bodyText=(0,_str.getString)("cmsdelete_info","core_courseformat",{count:cmIds.length});const modal=await this._modalBodyRenderedPromise(_modal_delete_cancel.default,{title:titleText,body:bodyText});modal.getRoot().on(_modal_events.default.delete,(e=>{if(e.preventDefault(),modal.destroy(),this.reactive.dispatch("cmDelete",cmIds),1==cmIds.length&&delegatedsection&&target.baseURI.includes("section.php")){let parameters=new URLSearchParams(window.location.search);parameters.has("id")&¶meters.get("id")==delegatedsection&&this._dispatchSectionDelete([delegatedsection],target)}}))}async _requestCmAvailability(target){const cmIds=this._getTargetIds(target);if(0==cmIds.length)return;const data={allowstealth:this.reactive.getExporter().canUseStealth(this.reactive.state,cmIds)},modal=await this._modalBodyRenderedPromise(_modal_save_cancel.default,{title:(0,_str.getString)("availability","core"),body:_templates.default.render("core_courseformat/local/content/cm/availabilitymodal",data),saveButtonText:(0,_str.getString)("apply","core")});this._setupMutationRadioButtonModal(modal,cmIds)}async _requestSectionAvailability(target){const sectionIds=this._getTargetIds(target);if(0==sectionIds.length)return;const title=1==sectionIds.length?"sectionavailability_title":"sectionsavailability_title",modal=await this._modalBodyRenderedPromise(_modal_save_cancel.default,{title:this.reactive.getFormatString(title),body:_templates.default.render("core_courseformat/local/content/section/availabilitymodal",[]),saveButtonText:(0,_str.getString)("apply","core")});this._setupMutationRadioButtonModal(modal,sectionIds)}_setupMutationRadioButtonModal(modal,ids){modal.setButtonDisabled("save",!0);const submitFunction=radio=>{const mutation=null==radio?void 0:radio.value;return!!mutation&&(this.reactive.dispatch(mutation,ids),!0)},modalBody=(0,_normalise.getFirst)(modal.getBody());modalBody.querySelectorAll(this.selectors.OPTIONSRADIO).forEach((radio=>{radio.addEventListener("change",(()=>{modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("click",(()=>{radio.checked=!0,modal.setButtonDisabled("save",!1)})),radio.parentNode.addEventListener("dblclick",(dbClickEvent=>{submitFunction(radio)&&(dbClickEvent.preventDefault(),modal.destroy())}))})),modal.getRoot().on(_modal_events.default.save,(()=>{const radio=modalBody.querySelector("".concat(this.selectors.OPTIONSRADIO,":checked"));submitFunction(radio)}))}_setAddSectionLocked(locked){this.getElements(this.selectors.ADDSECTIONREGION).forEach((element=>{element.classList.toggle(this.classes.DISABLED,locked);const addSectionElement=element.querySelector(this.selectors.ADDSECTION);addSectionElement.classList.toggle(this.classes.DISABLED,locked),this.setElementLocked(addSectionElement,locked),locked?((0,_str.getString)("sectionaddmax","core_courseformat").then((text=>addSectionElement.setAttribute("title",text))).catch(_notification.default.exception),addSectionElement.style.pointerEvents=null,addSectionElement.style.userSelect=null):addSectionElement.setAttribute("title",addSectionElement.dataset.addSections)}));const courseAddSection=this.getElement(this.selectors.COURSEADDSECTION);if(courseAddSection){courseAddSection.querySelector(this.selectors.ADDSECTION).classList.toggle(this.classes.DISPLAYNONE,locked);courseAddSection.querySelector(this.selectors.MAXSECTIONSWARNING).classList.toggle(this.classes.DISPLAYNONE,!locked)}}_disableLink(element){element&&(element.style.pointerEvents="none",element.style.userSelect="none",element.classList.add(this.classes.DISABLED),element.classList.add(this.classes.ITALIC),element.setAttribute("aria-disabled",!0),element.addEventListener("click",(event=>event.preventDefault())))}_modalBodyRenderedPromise(ModalClass,modalParams){return new Promise(((resolve,reject)=>{ModalClass.create(modalParams).then((modal=>{modal.setRemoveOnClose(!0),modal.getRoot().on(_modal_events.default.bodyRendered,(()=>{resolve(modal)})),void 0!==modalParams.saveButtonText&&modal.setSaveButtonText(modalParams.saveButtonText),void 0!==modalParams.deleteButtonText&&modal.setDeleteButtonText(modalParams.saveButtonText),modal.show()})).catch((()=>{reject("Cannot load modal content")}))}))}_destroyModal(modal,element){modal.hide();const pendingDestroy=new _pending.default("courseformat/actions:destroyModal");element&&element.focus(),setTimeout((()=>{modal.destroy(),pendingDestroy.resolve()}),500)}_getClosestActionMenuToogler(element){const actionMenu=element.closest(this.selectors.ACTIONMENU);if(actionMenu)return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=actions.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/actions.min.js.map b/course/format/amd/build/local/content/actions.min.js.map index c3035775310..3d1cf7bebda 100644 --- a/course/format/amd/build/local/content/actions.min.js.map +++ b/course/format/amd/build/local/content/actions.min.js.map @@ -1 +1 @@ -{"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.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 * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {eventTypes} from 'core/local/inplace_editable/events';\nimport Collapse from 'theme_boost/bootstrap/collapse';\nimport Modal from 'core/modal';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport ModalDeleteCancel from 'core/modal_delete_cancel';\nimport ModalCopyToClipboard from 'core/modal_copy_to_clipboard';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {getString} from 'core/str';\nimport {getFirst} from 'core/normalise';\nimport {toggleBulkSelectionAction} from 'core_courseformat/local/content/actions/bulkselection';\nimport * as CourseEvents from 'core_course/events';\nimport Pending from 'core/pending';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.\nimport Notification from \"core/notification\";\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\n// Mutations are dispatched by the course content actions.\n// Formats can use this module addActions static method to add custom actions.\n// Direct mutations can be simple strings (mutation) name or functions.\nconst directMutations = {\n sectionHide: 'sectionHide',\n sectionShow: 'sectionShow',\n cmHide: 'cmHide',\n cmShow: 'cmShow',\n cmStealth: 'cmStealth',\n cmMoveRight: 'cmMoveRight',\n cmMoveLeft: 'cmMoveLeft',\n cmNoGroups: 'cmNoGroups',\n cmSeparateGroups: 'cmSeparateGroups',\n cmVisibleGroups: 'cmVisibleGroups',\n};\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n // Move modal selectors.\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n MODALTOGGLER: `[data-bs-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n CONTENTTREE: `#destination-selector`,\n ACTIONMENU: `.action-menu`,\n ACTIONMENUTOGGLER: `[data-bs-toggle=\"dropdown\"]`,\n // Availability modal selectors.\n OPTIONSRADIO: `[type='radio']`,\n COURSEADDSECTION: `#course-addsection`,\n MAXSECTIONSWARNING: `[data-region='max-sections-warning']`,\n ADDSECTIONREGION: `[data-region='section-addsection']`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n ITALIC: `fst-italic`,\n DISPLAYNONE: `d-none`,\n };\n }\n\n /**\n * Add extra actions to the module.\n *\n * @param {array} actions array of methods to execute\n */\n static addActions(actions) {\n for (const [action, mutationReference] of Object.entries(actions)) {\n if (typeof mutationReference !== 'function' && typeof mutationReference !== 'string') {\n throw new Error(`${action} action must be a mutation name or a function`);\n }\n directMutations[action] = mutationReference;\n }\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n // Any inplace editable update needs state refresh.\n this.addEventListener(\n this.element,\n eventTypes.elementUpdated,\n this._inplaceEditableHandler\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const actionName = target.dataset.action;\n const methodName = this._actionMethodName(actionName);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n return;\n }\n\n // Check direct mutations or mutations handlers.\n if (directMutations[actionName] !== undefined) {\n if (typeof directMutations[actionName] === 'function') {\n directMutations[actionName](target, event);\n return;\n }\n this._requestMutationAction(target, event, directMutations[actionName]);\n return;\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Handle inplace editable updates.\n *\n * @param {Event} event the triggered event\n * @private\n */\n _inplaceEditableHandler(event) {\n const itemtype = event.detail?.ajaxreturn?.itemtype;\n const itemid = parseInt(event.detail?.ajaxreturn?.itemid);\n if (!Number.isFinite(itemid) || !itemtype) {\n return;\n }\n\n if (itemtype === 'activityname') {\n this.reactive.dispatch('cmState', [itemid]);\n return;\n }\n // Sections uses sectionname for normal sections and sectionnamenl for the no link sections.\n if (itemtype === 'sectionname' || itemtype === 'sectionnamenl') {\n this.reactive.dispatch('sectionState', [itemid]);\n return;\n }\n }\n\n /**\n * Return the ids represented by this element.\n *\n * Depending on the dataset attributes the action could represent a single id\n * or a bulk actions with all the current selected ids.\n *\n * @param {HTMLElement} target\n * @returns {Number[]} array of Ids\n */\n _getTargetIds(target) {\n let ids = [];\n if (target?.dataset?.id) {\n ids.push(target.dataset.id);\n }\n const bulkType = target?.dataset?.bulk;\n if (!bulkType) {\n return ids;\n }\n const bulk = this.reactive.get('bulk');\n if (bulk.enabled && bulk.selectedType === bulkType) {\n ids = [...ids, ...bulk.selection];\n }\n return ids;\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n const pendingModalReady = new Pending(`courseformat/actions:prepareMoveSectionModal`);\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n let titleText = null;\n\n // Add the target section id and title.\n let sectionInfo = null;\n if (sectionIds.length == 1) {\n sectionInfo = this.reactive.get('section', sectionIds[0]);\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n data.information = await this.reactive.getFormatString('sectionmove_info', data.sectiontitle);\n titleText = this.reactive.getFormatString('sectionmove_title');\n } else {\n data.information = await this.reactive.getFormatString('sectionsmove_info', sectionIds.length);\n titleText = this.reactive.getFormatString('sectionsmove_title');\n }\n\n\n // Create the modal.\n // Build the modal parameters from the event data.\n const modal = await this._modalBodyRenderedPromise(Modal, {\n title: titleText,\n body: Templates.render('core_courseformat/local/content/movesection', data),\n });\n\n const modalBody = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n sectionIds.forEach(sectionId => {\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n });\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n },\n true\n );\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMoveAfter', sectionIds, target.dataset.id);\n this._destroyModal(modal, editTools);\n });\n\n pendingModalReady.resolve();\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n const pendingModalReady = new Pending(`courseformat/actions:prepareMoveCmModal`);\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n data.information = await this.reactive.getFormatString('cmmove_info', data.cmname);\n if (cmInfo.hasdelegatedsection) {\n titleText = this.reactive.getFormatString('cmmove_subsectiontitle');\n } else {\n titleText = this.reactive.getFormatString('cmmove_title');\n }\n } else {\n data.information = await this.reactive.getFormatString('cmsmove_info', cmIds.length);\n titleText = this.reactive.getFormatString('cmsmove_title');\n }\n\n // Create the modal.\n // Build the modal parameters from the event data.\n const modal = await this._modalBodyRenderedPromise(Modal, {\n title: titleText,\n body: Templates.render('core_courseformat/local/content/movecm', data),\n });\n\n const modalBody = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n cmIds.forEach(cmId => {\n const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n });\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n ENTER: this.selectors.SECTIONLINK,\n }\n );\n\n cmIds.forEach(cmId => {\n const cmInfo = this.reactive.get('cm', cmId);\n let selector;\n if (!cmInfo.hasdelegatedsection) {\n selector = `${this.selectors.CMLINK}[data-id='${cmId}']`;\n } else {\n selector = `${this.selectors.SECTIONLINK}[data-id='${cmInfo.sectionid}']`;\n }\n const currentElement = modalBody.querySelector(selector);\n this._expandCmMoveModalParentSections(modalBody, currentElement);\n });\n\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n let targetSectionId;\n let targetCmId;\n let droppedCmIds = [...cmIds];\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n const section = this.reactive.get('section', targetSectionId);\n if (section.component) {\n // Remove cmIds which are not allowed to be moved to this delegated section (mostly\n // all other delegated cm).\n droppedCmIds = droppedCmIds.filter(cmId => {\n const cmInfo = this.reactive.get('cm', cmId);\n return !cmInfo.hasdelegatedsection;\n });\n }\n if (droppedCmIds.length === 0) {\n return; // No cm to move.\n }\n this.reactive.dispatch('cmMove', droppedCmIds, targetSectionId, targetCmId);\n this._destroyModal(modal, editTools);\n });\n\n pendingModalReady.resolve();\n }\n\n /**\n * Expand all the modal tree branches that contains the element.\n *\n * @private\n * @param {HTMLElement} modalBody the modal body element\n * @param {HTMLElement} element the element to display\n */\n _expandCmMoveModalParentSections(modalBody, element) {\n const sectionnode = element.closest(this.selectors.SECTIONNODE);\n if (!sectionnode) {\n return;\n }\n\n const toggler = sectionnode.querySelector(this.selectors.MODALTOGGLER);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute('href');\n if (collapsibleId) {\n // We cannot be sure we have # in the id element name.\n collapsibleId = collapsibleId.replace('#', '');\n const expandNode = modalBody.querySelector(`#${collapsibleId}`);\n new Collapse(expandNode, {toggle: false}).show();\n }\n\n // Section are a tree structure, we need to expand all the parents.\n this._expandCmMoveModalParentSections(modalBody, sectionnode.parentElement);\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a create subsection request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddModule(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addModule', target.dataset.modname, target.dataset.sectionnum, target.dataset.beforemod);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // We don't need confirmation to delete empty sections.\n let needsConfirmation = sectionIds.some(sectionId => {\n const sectionInfo = this.reactive.get('section', sectionId);\n const cmList = sectionInfo.cmlist ?? [];\n return (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle);\n });\n if (!needsConfirmation) {\n this._dispatchSectionDelete(sectionIds, target);\n return;\n }\n\n let bodyText = null;\n let titleText = null;\n if (sectionIds.length == 1) {\n titleText = this.reactive.getFormatString('sectiondelete_title');\n const sectionInfo = this.reactive.get('section', sectionIds[0]);\n bodyText = this.reactive.getFormatString('sectiondelete_info', {name: sectionInfo.title});\n } else {\n titleText = this.reactive.getFormatString('sectionsdelete_title');\n bodyText = this.reactive.getFormatString('sectionsdelete_info', {count: sectionIds.length});\n }\n\n const modal = await this._modalBodyRenderedPromise(ModalDeleteCancel, {\n title: titleText,\n body: bodyText,\n });\n\n modal.getRoot().on(\n ModalEvents.delete,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this._dispatchSectionDelete(sectionIds, target);\n }\n );\n }\n\n /**\n * Dispatch the section delete action and handle the redirection if necessary.\n *\n * @param {Array} sectionIds the IDs of the sections to delete.\n * @param {Element} target the dispatch action element\n */\n async _dispatchSectionDelete(sectionIds, target) {\n await this.reactive.dispatch('sectionDelete', sectionIds);\n if (target.baseURI.includes('section.php')) {\n // Redirect to the course main page if the section is the current page.\n window.location.href = this.reactive.get('course').baseurl;\n }\n }\n\n /**\n * Handle a toggle cm selection.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestToggleSelectionCm(target, event) {\n toggleBulkSelectionAction(this.reactive, target, event, 'cm');\n }\n\n /**\n * Handle a toggle section selection.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestToggleSelectionSection(target, event) {\n toggleBulkSelectionAction(this.reactive, target, event, 'section');\n }\n\n /**\n * Basic mutation action helper.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n * @param {string} mutationName the mutation name\n */\n async _requestMutationAction(target, event, mutationName) {\n if (!target.dataset.id && target.dataset.for !== 'bulkaction') {\n return;\n }\n event.preventDefault();\n if (target.dataset.for === 'bulkaction') {\n // If the mutation is a bulk action we use the current selection.\n this.reactive.dispatch(mutationName, this.reactive.get('bulk').selection);\n } else {\n this.reactive.dispatch(mutationName, [target.dataset.id]);\n }\n }\n\n /**\n * Handle a course permalink modal request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n _requestPermalink(target, event) {\n event.preventDefault();\n ModalCopyToClipboard.create(\n {\n text: target.getAttribute('href'),\n },\n getString('sectionlink', 'course')\n );\n return;\n }\n\n /**\n * Handle a course module duplicate request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDuplicate(target, event) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n const sectionId = target.dataset.sectionid ?? null;\n event.preventDefault();\n this.reactive.dispatch('cmDuplicate', cmIds, sectionId);\n }\n\n /**\n * Handle a delete cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDelete(target, event) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n let bodyText = null;\n let titleText = null;\n let delegatedsection = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n if (cmInfo.hasdelegatedsection) {\n delegatedsection = cmInfo.delegatesectionid;\n titleText = this.reactive.getFormatString('cmdelete_subsectiontitle');\n bodyText = getString(\n 'sectiondelete_info',\n 'core_courseformat',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n );\n } else {\n titleText = this.reactive.getFormatString('cmdelete_title');\n bodyText = getString(\n 'cmdelete_info',\n 'core_courseformat',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n );\n }\n } else {\n titleText = getString('cmsdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmsdelete_info',\n 'core_courseformat',\n {count: cmIds.length}\n );\n }\n\n const modal = await this._modalBodyRenderedPromise(ModalDeleteCancel, {\n title: titleText,\n body: bodyText,\n });\n\n modal.getRoot().on(\n ModalEvents.delete,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('cmDelete', cmIds);\n if (cmIds.length == 1 && delegatedsection && target.baseURI.includes('section.php')) {\n // Redirect to the course main page if the subsection is the current page.\n let parameters = new URLSearchParams(window.location.search);\n if (parameters.has('id') && parameters.get('id') == delegatedsection) {\n this._dispatchSectionDelete([delegatedsection], target);\n }\n }\n }\n );\n }\n\n /**\n * Handle a cm availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestCmAvailability(target) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n // Show the availability modal to decide which action to trigger.\n const exporter = this.reactive.getExporter();\n const data = {\n allowstealth: exporter.canUseStealth(this.reactive.state, cmIds),\n };\n const modal = await this._modalBodyRenderedPromise(ModalSaveCancel, {\n title: getString('availability', 'core'),\n body: Templates.render('core_courseformat/local/content/cm/availabilitymodal', data),\n saveButtonText: getString('apply', 'core'),\n });\n\n this._setupMutationRadioButtonModal(modal, cmIds);\n }\n\n /**\n * Handle a section availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestSectionAvailability(target) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n const title = (sectionIds.length == 1) ? 'sectionavailability_title' : 'sectionsavailability_title';\n // Show the availability modal to decide which action to trigger.\n const modal = await this._modalBodyRenderedPromise(ModalSaveCancel, {\n title: this.reactive.getFormatString(title),\n body: Templates.render('core_courseformat/local/content/section/availabilitymodal', []),\n saveButtonText: getString('apply', 'core'),\n });\n\n this._setupMutationRadioButtonModal(modal, sectionIds);\n }\n\n /**\n * Add events to a mutation selector radio buttons modal.\n * @param {Modal} modal\n * @param {Number[]} ids the section or cm ids to apply the mutation\n */\n _setupMutationRadioButtonModal(modal, ids) {\n // The save button is not enabled until the user selects an option.\n modal.setButtonDisabled('save', true);\n\n const submitFunction = (radio) => {\n const mutation = radio?.value;\n if (!mutation) {\n return false;\n }\n this.reactive.dispatch(mutation, ids);\n return true;\n };\n\n const modalBody = getFirst(modal.getBody());\n const radioOptions = modalBody.querySelectorAll(this.selectors.OPTIONSRADIO);\n radioOptions.forEach(radio => {\n radio.addEventListener('change', () => {\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('click', () => {\n radio.checked = true;\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('dblclick', dbClickEvent => {\n if (submitFunction(radio)) {\n dbClickEvent.preventDefault();\n modal.destroy();\n }\n });\n });\n\n modal.getRoot().on(\n ModalEvents.save,\n () => {\n const radio = modalBody.querySelector(`${this.selectors.OPTIONSRADIO}:checked`);\n submitFunction(radio);\n }\n );\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTIONREGION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n const addSectionElement = element.querySelector(this.selectors.ADDSECTION);\n addSectionElement.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(addSectionElement, locked);\n // We tweak the element to show a tooltip as a title attribute.\n if (locked) {\n getString('sectionaddmax', 'core_courseformat')\n .then((text) => addSectionElement.setAttribute('title', text))\n .catch(Notification.exception);\n addSectionElement.style.pointerEvents = null; // Unlocks the pointer events.\n addSectionElement.style.userSelect = null; // Unlocks the pointer events.\n } else {\n addSectionElement.setAttribute('title', addSectionElement.dataset.addSections);\n }\n });\n const courseAddSection = this.getElement(this.selectors.COURSEADDSECTION);\n if (courseAddSection) {\n const addSection = courseAddSection.querySelector(this.selectors.ADDSECTION);\n addSection.classList.toggle(this.classes.DISPLAYNONE, locked);\n const noMoreSections = courseAddSection.querySelector(this.selectors.MAXSECTIONSWARNING);\n noMoreSections.classList.toggle(this.classes.DISPLAYNONE, !locked);\n }\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.classList.add(this.classes.ITALIC);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {Modal} ModalClass the modal class\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(ModalClass, modalParams) {\n return new Promise((resolve, reject) => {\n ModalClass.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n if (modalParams.deleteButtonText !== undefined) {\n modal.setDeleteButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n\n /**\n * Hide and later destroy a modal.\n *\n * Behat will fail if we remove the modal while some boostrap collapse is executing.\n *\n * @param {Modal} modal\n * @param {HTMLElement} element the dom element to focus on.\n */\n _destroyModal(modal, element) {\n modal.hide();\n const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);\n if (element) {\n element.focus();\n }\n setTimeout(() =>{\n modal.destroy();\n pendingDestroy.resolve();\n }, 500);\n }\n\n /**\n * Get the closest actions menu toggler to an action element.\n *\n * @param {HTMLElement} element the action link element\n * @returns {HTMLElement|undefined}\n */\n _getClosestActionMenuToogler(element) {\n const actionMenu = element.closest(this.selectors.ACTIONMENU);\n if (!actionMenu) {\n return undefined;\n }\n return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);\n }\n}\n"],"names":["directMutations","sectionHide","sectionShow","cmHide","cmShow","cmStealth","cmMoveRight","cmMoveLeft","cmNoGroups","cmSeparateGroups","cmVisibleGroups","BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","OPTIONSRADIO","COURSEADDSECTION","MAXSECTIONSWARNING","ADDSECTIONREGION","classes","DISABLED","ITALIC","DISPLAYNONE","actions","action","mutationReference","Object","entries","Error","stateReady","state","addEventListener","this","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","eventTypes","elementUpdated","_inplaceEditableHandler","getWatchers","watch","handler","event","target","closest","classList","contains","preventDefault","actionName","dataset","methodName","_actionMethodName","undefined","_requestMutationAction","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","itemtype","detail","_event$detail","ajaxreturn","_event$detail$ajaxret","itemid","parseInt","_event$detail2","_event$detail2$ajaxre","Number","isFinite","reactive","dispatch","_getTargetIds","ids","_target$dataset","id","push","bulkType","_target$dataset2","bulk","get","enabled","selectedType","selection","sectionIds","pendingModalReady","Pending","editTools","_getClosestActionMenuToogler","data","getExporter","titleText","sectionInfo","sectionid","sectiontitle","title","information","getFormatString","modal","_modalBodyRenderedPromise","Modal","body","Templates","render","modalBody","getBody","forEach","sectionId","currentElement","querySelector","_disableLink","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","_destroyModal","resolve","cmIds","exporter","cmInfo","cmid","cmname","hasdelegatedsection","cmId","ENTER","selector","_expandCmMoveModalParentSections","targetSectionId","targetCmId","droppedCmIds","dropData","cmDraggableData","nextcmid","section","cmlist","component","filter","sectionnode","toggler","collapsibleId","replace","expandNode","Collapse","toggle","show","parentElement","modname","sectionnum","beforemod","some","hassummary","rawtitle","_dispatchSectionDelete","bodyText","count","ModalDeleteCancel","getRoot","on","ModalEvents","delete","e","destroy","baseURI","includes","window","location","href","baseurl","mutationName","_requestPermalink","text","delegatedsection","delegatesectionid","type","parameters","URLSearchParams","search","has","allowstealth","canUseStealth","ModalSaveCancel","saveButtonText","_setupMutationRadioButtonModal","setButtonDisabled","submitFunction","radio","mutation","value","querySelectorAll","parentNode","checked","dbClickEvent","save","locked","getElements","addSectionElement","setElementLocked","then","setAttribute","catch","Notification","exception","style","pointerEvents","userSelect","addSections","courseAddSection","getElement","add","ModalClass","modalParams","Promise","reject","setRemoveOnClose","bodyRendered","setSaveButtonText","deleteButtonText","setDeleteButtonText","hide","pendingDestroy","focus","setTimeout","actionMenu"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"actions.min.js","sources":["../../../src/local/content/actions.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 * Course state actions dispatcher.\n *\n * This module captures all data-dispatch links in the course content and dispatch the proper\n * state mutation, including any confirmation and modal required.\n *\n * @module core_courseformat/local/content/actions\n * @class core_courseformat/local/content/actions\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {eventTypes} from 'core/local/inplace_editable/events';\nimport Collapse from 'theme_boost/bootstrap/collapse';\nimport log from 'core/log';\nimport Modal from 'core/modal';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport ModalDeleteCancel from 'core/modal_delete_cancel';\nimport ModalCopyToClipboard from 'core/modal_copy_to_clipboard';\nimport ModalEvents from 'core/modal_events';\nimport Templates from 'core/templates';\nimport {prefetchStrings} from 'core/prefetch';\nimport {getString} from 'core/str';\nimport {getFirst} from 'core/normalise';\nimport {toggleBulkSelectionAction} from 'core_courseformat/local/content/actions/bulkselection';\nimport * as CourseEvents from 'core_course/events';\nimport Pending from 'core/pending';\nimport ContentTree from 'core_courseformat/local/courseeditor/contenttree';\n// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.\nimport Notification from \"core/notification\";\n\n// Load global strings.\nprefetchStrings('core', ['movecoursesection', 'movecoursemodule', 'confirm', 'delete']);\n\n// Mutations are dispatched by the course content actions.\n// Formats can use this module addActions static method to add custom actions.\n// Direct mutations can be simple strings (mutation) name or functions.\nconst directMutations = {\n sectionHide: 'sectionHide',\n sectionShow: 'sectionShow',\n cmHide: 'cmHide',\n cmShow: 'cmShow',\n cmStealth: 'cmStealth',\n cmMoveRight: 'cmMoveRight',\n cmMoveLeft: 'cmMoveLeft',\n cmNoGroups: 'cmNoGroups',\n cmSeparateGroups: 'cmSeparateGroups',\n cmVisibleGroups: 'cmVisibleGroups',\n};\n\nexport default class extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_actions';\n // Default query selectors.\n this.selectors = {\n ACTIONLINK: `[data-action]`,\n // Move modal selectors.\n SECTIONLINK: `[data-for='section']`,\n CMLINK: `[data-for='cm']`,\n SECTIONNODE: `[data-for='sectionnode']`,\n MODALTOGGLER: `[data-bs-toggle='collapse']`,\n ADDSECTION: `[data-action='addSection']`,\n CONTENTTREE: `#destination-selector`,\n ACTIONMENU: `.action-menu`,\n ACTIONMENUTOGGLER: `[data-bs-toggle=\"dropdown\"]`,\n // Availability modal selectors.\n OPTIONSRADIO: `[type='radio']`,\n COURSEADDSECTION: `#course-addsection`,\n MAXSECTIONSWARNING: `[data-region='max-sections-warning']`,\n ADDSECTIONREGION: `[data-region='section-addsection']`,\n };\n // Component css classes.\n this.classes = {\n DISABLED: `disabled`,\n ITALIC: `fst-italic`,\n DISPLAYNONE: `d-none`,\n };\n }\n\n /**\n * Add extra actions to the module.\n *\n * @param {array} actions array of methods to execute\n */\n static addActions(actions) {\n for (const [action, mutationReference] of Object.entries(actions)) {\n if (typeof mutationReference !== 'function' && typeof mutationReference !== 'string') {\n throw new Error(`${action} action must be a mutation name or a function`);\n }\n directMutations[action] = mutationReference;\n }\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the state data.\n *\n */\n stateReady(state) {\n // Delegate dispatch clicks.\n this.addEventListener(\n this.element,\n 'click',\n this._dispatchClick\n );\n // Check section limit.\n this._checkSectionlist({state});\n // Add an Event listener to recalculate limits it if a section HTML is altered.\n this.addEventListener(\n this.element,\n CourseEvents.sectionRefreshed,\n () => this._checkSectionlist({state})\n );\n // Any inplace editable update needs state refresh.\n this.addEventListener(\n this.element,\n eventTypes.elementUpdated,\n this._inplaceEditableHandler\n );\n }\n\n /**\n * Return the component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n // Check section limit.\n {watch: `course.sectionlist:updated`, handler: this._checkSectionlist},\n ];\n }\n\n _dispatchClick(event) {\n const target = event.target.closest(this.selectors.ACTIONLINK);\n if (!target) {\n return;\n }\n if (target.classList.contains(this.classes.DISABLED)) {\n event.preventDefault();\n return;\n }\n\n // Invoke proper method.\n const actionName = target.dataset.action;\n const methodName = this._actionMethodName(actionName);\n\n if (this[methodName] !== undefined) {\n this[methodName](target, event);\n return;\n }\n\n // Check direct mutations or mutations handlers.\n if (directMutations[actionName] !== undefined) {\n if (typeof directMutations[actionName] === 'function') {\n directMutations[actionName](target, event);\n return;\n }\n this._requestMutationAction(target, event, directMutations[actionName]);\n return;\n }\n }\n\n _actionMethodName(name) {\n const requestName = name.charAt(0).toUpperCase() + name.slice(1);\n return `_request${requestName}`;\n }\n\n /**\n * Check the section list and disable some options if needed.\n *\n * @param {Object} detail the update details.\n * @param {Object} detail.state the state object.\n */\n _checkSectionlist({state}) {\n // Disable \"add section\" actions if the course max sections has been exceeded.\n this._setAddSectionLocked(state.course.sectionlist.length > state.course.maxsections);\n }\n\n /**\n * Handle inplace editable updates.\n *\n * @param {Event} event the triggered event\n * @private\n */\n _inplaceEditableHandler(event) {\n const itemtype = event.detail?.ajaxreturn?.itemtype;\n const itemid = parseInt(event.detail?.ajaxreturn?.itemid);\n if (!Number.isFinite(itemid) || !itemtype) {\n return;\n }\n\n if (itemtype === 'activityname') {\n this.reactive.dispatch('cmState', [itemid]);\n return;\n }\n // Sections uses sectionname for normal sections and sectionnamenl for the no link sections.\n if (itemtype === 'sectionname' || itemtype === 'sectionnamenl') {\n this.reactive.dispatch('sectionState', [itemid]);\n return;\n }\n }\n\n /**\n * Return the ids represented by this element.\n *\n * Depending on the dataset attributes the action could represent a single id\n * or a bulk actions with all the current selected ids.\n *\n * @param {HTMLElement} target\n * @returns {Number[]} array of Ids\n */\n _getTargetIds(target) {\n let ids = [];\n if (target?.dataset?.id) {\n ids.push(target.dataset.id);\n }\n const bulkType = target?.dataset?.bulk;\n if (!bulkType) {\n return ids;\n }\n const bulk = this.reactive.get('bulk');\n if (bulk.enabled && bulk.selectedType === bulkType) {\n ids = [...ids, ...bulk.selection];\n }\n return ids;\n }\n\n /**\n * Handle a move section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveSection(target, event) {\n // Check we have an id.\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n const pendingModalReady = new Pending(`courseformat/actions:prepareMoveSectionModal`);\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect section information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n let titleText = null;\n\n // Add the target section id and title.\n let sectionInfo = null;\n if (sectionIds.length == 1) {\n sectionInfo = this.reactive.get('section', sectionIds[0]);\n data.sectionid = sectionInfo.id;\n data.sectiontitle = sectionInfo.title;\n data.information = await this.reactive.getFormatString('sectionmove_info', data.sectiontitle);\n titleText = this.reactive.getFormatString('sectionmove_title');\n } else {\n data.information = await this.reactive.getFormatString('sectionsmove_info', sectionIds.length);\n titleText = this.reactive.getFormatString('sectionsmove_title');\n }\n\n\n // Create the modal.\n // Build the modal parameters from the event data.\n const modal = await this._modalBodyRenderedPromise(Modal, {\n title: titleText,\n body: Templates.render('core_courseformat/local/content/movesection', data),\n });\n\n const modalBody = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n sectionIds.forEach(sectionId => {\n const currentElement = modalBody.querySelector(`${this.selectors.SECTIONLINK}[data-id='${sectionId}']`);\n this._disableLink(currentElement);\n });\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n },\n true\n );\n\n // Capture click.\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for != 'section' || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n this.reactive.dispatch('sectionMoveAfter', sectionIds, target.dataset.id);\n this._destroyModal(modal, editTools);\n });\n\n pendingModalReady.resolve();\n }\n\n /**\n * Handle a move cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestMoveCm(target, event) {\n // Check we have an id.\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n const pendingModalReady = new Pending(`courseformat/actions:prepareMoveCmModal`);\n\n // The section edit menu to refocus on end.\n const editTools = this._getClosestActionMenuToogler(target);\n\n // Collect information from the state.\n const exporter = this.reactive.getExporter();\n const data = exporter.course(this.reactive.state);\n\n let titleText = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n data.cmid = cmInfo.id;\n data.cmname = cmInfo.name;\n data.information = await this.reactive.getFormatString('cmmove_info', data.cmname);\n if (cmInfo.hasdelegatedsection) {\n titleText = this.reactive.getFormatString('cmmove_subsectiontitle');\n } else {\n titleText = this.reactive.getFormatString('cmmove_title');\n }\n } else {\n data.information = await this.reactive.getFormatString('cmsmove_info', cmIds.length);\n titleText = this.reactive.getFormatString('cmsmove_title');\n }\n\n // Create the modal.\n // Build the modal parameters from the event data.\n const modal = await this._modalBodyRenderedPromise(Modal, {\n title: titleText,\n body: Templates.render('core_courseformat/local/content/movecm', data),\n });\n\n const modalBody = getFirst(modal.getBody());\n\n // Disable current selected section ids.\n cmIds.forEach(cmId => {\n const currentElement = modalBody.querySelector(`${this.selectors.CMLINK}[data-id='${cmId}']`);\n this._disableLink(currentElement);\n });\n\n // Setup keyboard navigation.\n new ContentTree(\n modalBody.querySelector(this.selectors.CONTENTTREE),\n {\n SECTION: this.selectors.SECTIONNODE,\n TOGGLER: this.selectors.MODALTOGGLER,\n COLLAPSE: this.selectors.MODALTOGGLER,\n ENTER: this.selectors.SECTIONLINK,\n }\n );\n\n cmIds.forEach(cmId => {\n const cmInfo = this.reactive.get('cm', cmId);\n let selector;\n if (!cmInfo.hasdelegatedsection) {\n selector = `${this.selectors.CMLINK}[data-id='${cmId}']`;\n } else {\n selector = `${this.selectors.SECTIONLINK}[data-id='${cmInfo.sectionid}']`;\n }\n const currentElement = modalBody.querySelector(selector);\n this._expandCmMoveModalParentSections(modalBody, currentElement);\n });\n\n modalBody.addEventListener('click', (event) => {\n const target = event.target;\n if (!target.matches('a') || target.dataset.for === undefined || target.dataset.id === undefined) {\n return;\n }\n if (target.getAttribute('aria-disabled')) {\n return;\n }\n event.preventDefault();\n\n let targetSectionId;\n let targetCmId;\n let droppedCmIds = [...cmIds];\n if (target.dataset.for == 'cm') {\n const dropData = exporter.cmDraggableData(this.reactive.state, target.dataset.id);\n targetSectionId = dropData.sectionid;\n targetCmId = dropData.nextcmid;\n } else {\n const section = this.reactive.get('section', target.dataset.id);\n targetSectionId = target.dataset.id;\n targetCmId = section?.cmlist[0];\n }\n const section = this.reactive.get('section', targetSectionId);\n if (section.component) {\n // Remove cmIds which are not allowed to be moved to this delegated section (mostly\n // all other delegated cm).\n droppedCmIds = droppedCmIds.filter(cmId => {\n const cmInfo = this.reactive.get('cm', cmId);\n return !cmInfo.hasdelegatedsection;\n });\n }\n if (droppedCmIds.length === 0) {\n return; // No cm to move.\n }\n this.reactive.dispatch('cmMove', droppedCmIds, targetSectionId, targetCmId);\n this._destroyModal(modal, editTools);\n });\n\n pendingModalReady.resolve();\n }\n\n /**\n * Expand all the modal tree branches that contains the element.\n *\n * @private\n * @param {HTMLElement} modalBody the modal body element\n * @param {HTMLElement} element the element to display\n */\n _expandCmMoveModalParentSections(modalBody, element) {\n const sectionnode = element.closest(this.selectors.SECTIONNODE);\n if (!sectionnode) {\n return;\n }\n\n const toggler = sectionnode.querySelector(this.selectors.MODALTOGGLER);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute('href');\n if (collapsibleId) {\n // We cannot be sure we have # in the id element name.\n collapsibleId = collapsibleId.replace('#', '');\n const expandNode = modalBody.querySelector(`#${collapsibleId}`);\n new Collapse(expandNode, {toggle: false}).show();\n }\n\n // Section are a tree structure, we need to expand all the parents.\n this._expandCmMoveModalParentSections(modalBody, sectionnode.parentElement);\n }\n\n /**\n * Handle a create section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddSection(target, event) {\n event.preventDefault();\n this.reactive.dispatch('addSection', target.dataset.id ?? 0);\n }\n\n /**\n * Handle a create subsection request.\n *\n * @deprecated since Moodle 5.0 MDL-83469.\n * @todo MDL-83851 This will be deleted in Moodle 6.0.\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestAddModule(target, event) {\n log.debug('AddModule action is deprecated. Use newModule instead');\n event.preventDefault();\n this.reactive.dispatch('addModule', target.dataset.modname, target.dataset.sectionnum, target.dataset.beforemod);\n }\n\n /**\n * Handle a new create subsection request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestNewModule(target, event) {\n event.preventDefault();\n this.reactive.dispatch('newModule', target.dataset.modname, target.dataset.sectionid, target.dataset.beforemod);\n }\n\n /**\n * Handle a delete section request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestDeleteSection(target, event) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n // We don't need confirmation to delete empty sections.\n let needsConfirmation = sectionIds.some(sectionId => {\n const sectionInfo = this.reactive.get('section', sectionId);\n const cmList = sectionInfo.cmlist ?? [];\n return (cmList.length || sectionInfo.hassummary || sectionInfo.rawtitle);\n });\n if (!needsConfirmation) {\n this._dispatchSectionDelete(sectionIds, target);\n return;\n }\n\n let bodyText = null;\n let titleText = null;\n if (sectionIds.length == 1) {\n titleText = this.reactive.getFormatString('sectiondelete_title');\n const sectionInfo = this.reactive.get('section', sectionIds[0]);\n bodyText = this.reactive.getFormatString('sectiondelete_info', {name: sectionInfo.title});\n } else {\n titleText = this.reactive.getFormatString('sectionsdelete_title');\n bodyText = this.reactive.getFormatString('sectionsdelete_info', {count: sectionIds.length});\n }\n\n const modal = await this._modalBodyRenderedPromise(ModalDeleteCancel, {\n title: titleText,\n body: bodyText,\n });\n\n modal.getRoot().on(\n ModalEvents.delete,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this._dispatchSectionDelete(sectionIds, target);\n }\n );\n }\n\n /**\n * Dispatch the section delete action and handle the redirection if necessary.\n *\n * @param {Array} sectionIds the IDs of the sections to delete.\n * @param {Element} target the dispatch action element\n */\n async _dispatchSectionDelete(sectionIds, target) {\n await this.reactive.dispatch('sectionDelete', sectionIds);\n if (target.baseURI.includes('section.php')) {\n // Redirect to the course main page if the section is the current page.\n window.location.href = this.reactive.get('course').baseurl;\n }\n }\n\n /**\n * Handle a toggle cm selection.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestToggleSelectionCm(target, event) {\n toggleBulkSelectionAction(this.reactive, target, event, 'cm');\n }\n\n /**\n * Handle a toggle section selection.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestToggleSelectionSection(target, event) {\n toggleBulkSelectionAction(this.reactive, target, event, 'section');\n }\n\n /**\n * Basic mutation action helper.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n * @param {string} mutationName the mutation name\n */\n async _requestMutationAction(target, event, mutationName) {\n if (!target.dataset.id && target.dataset.for !== 'bulkaction') {\n return;\n }\n event.preventDefault();\n if (target.dataset.for === 'bulkaction') {\n // If the mutation is a bulk action we use the current selection.\n this.reactive.dispatch(mutationName, this.reactive.get('bulk').selection);\n } else {\n this.reactive.dispatch(mutationName, [target.dataset.id]);\n }\n }\n\n /**\n * Handle a course permalink modal request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n _requestPermalink(target, event) {\n event.preventDefault();\n ModalCopyToClipboard.create(\n {\n text: target.getAttribute('href'),\n },\n getString('sectionlink', 'course')\n );\n return;\n }\n\n /**\n * Handle a course module duplicate request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDuplicate(target, event) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n const sectionId = target.dataset.sectionid ?? null;\n event.preventDefault();\n this.reactive.dispatch('cmDuplicate', cmIds, sectionId);\n }\n\n /**\n * Handle a delete cm request.\n *\n * @param {Element} target the dispatch action element\n * @param {Event} event the triggered event\n */\n async _requestCmDelete(target, event) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n\n event.preventDefault();\n\n let bodyText = null;\n let titleText = null;\n let delegatedsection = null;\n if (cmIds.length == 1) {\n const cmInfo = this.reactive.get('cm', cmIds[0]);\n if (cmInfo.hasdelegatedsection) {\n delegatedsection = cmInfo.delegatesectionid;\n titleText = this.reactive.getFormatString('cmdelete_subsectiontitle');\n bodyText = getString(\n 'sectiondelete_info',\n 'core_courseformat',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n );\n } else {\n titleText = this.reactive.getFormatString('cmdelete_title');\n bodyText = getString(\n 'cmdelete_info',\n 'core_courseformat',\n {\n type: cmInfo.modname,\n name: cmInfo.name,\n }\n );\n }\n } else {\n titleText = getString('cmsdelete_title', 'core_courseformat');\n bodyText = getString(\n 'cmsdelete_info',\n 'core_courseformat',\n {count: cmIds.length}\n );\n }\n\n const modal = await this._modalBodyRenderedPromise(ModalDeleteCancel, {\n title: titleText,\n body: bodyText,\n });\n\n modal.getRoot().on(\n ModalEvents.delete,\n e => {\n // Stop the default save button behaviour which is to close the modal.\n e.preventDefault();\n modal.destroy();\n this.reactive.dispatch('cmDelete', cmIds);\n if (cmIds.length == 1 && delegatedsection && target.baseURI.includes('section.php')) {\n // Redirect to the course main page if the subsection is the current page.\n let parameters = new URLSearchParams(window.location.search);\n if (parameters.has('id') && parameters.get('id') == delegatedsection) {\n this._dispatchSectionDelete([delegatedsection], target);\n }\n }\n }\n );\n }\n\n /**\n * Handle a cm availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestCmAvailability(target) {\n const cmIds = this._getTargetIds(target);\n if (cmIds.length == 0) {\n return;\n }\n // Show the availability modal to decide which action to trigger.\n const exporter = this.reactive.getExporter();\n const data = {\n allowstealth: exporter.canUseStealth(this.reactive.state, cmIds),\n };\n const modal = await this._modalBodyRenderedPromise(ModalSaveCancel, {\n title: getString('availability', 'core'),\n body: Templates.render('core_courseformat/local/content/cm/availabilitymodal', data),\n saveButtonText: getString('apply', 'core'),\n });\n\n this._setupMutationRadioButtonModal(modal, cmIds);\n }\n\n /**\n * Handle a section availability change request.\n *\n * @param {Element} target the dispatch action element\n */\n async _requestSectionAvailability(target) {\n const sectionIds = this._getTargetIds(target);\n if (sectionIds.length == 0) {\n return;\n }\n const title = (sectionIds.length == 1) ? 'sectionavailability_title' : 'sectionsavailability_title';\n // Show the availability modal to decide which action to trigger.\n const modal = await this._modalBodyRenderedPromise(ModalSaveCancel, {\n title: this.reactive.getFormatString(title),\n body: Templates.render('core_courseformat/local/content/section/availabilitymodal', []),\n saveButtonText: getString('apply', 'core'),\n });\n\n this._setupMutationRadioButtonModal(modal, sectionIds);\n }\n\n /**\n * Add events to a mutation selector radio buttons modal.\n * @param {Modal} modal\n * @param {Number[]} ids the section or cm ids to apply the mutation\n */\n _setupMutationRadioButtonModal(modal, ids) {\n // The save button is not enabled until the user selects an option.\n modal.setButtonDisabled('save', true);\n\n const submitFunction = (radio) => {\n const mutation = radio?.value;\n if (!mutation) {\n return false;\n }\n this.reactive.dispatch(mutation, ids);\n return true;\n };\n\n const modalBody = getFirst(modal.getBody());\n const radioOptions = modalBody.querySelectorAll(this.selectors.OPTIONSRADIO);\n radioOptions.forEach(radio => {\n radio.addEventListener('change', () => {\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('click', () => {\n radio.checked = true;\n modal.setButtonDisabled('save', false);\n });\n radio.parentNode.addEventListener('dblclick', dbClickEvent => {\n if (submitFunction(radio)) {\n dbClickEvent.preventDefault();\n modal.destroy();\n }\n });\n });\n\n modal.getRoot().on(\n ModalEvents.save,\n () => {\n const radio = modalBody.querySelector(`${this.selectors.OPTIONSRADIO}:checked`);\n submitFunction(radio);\n }\n );\n }\n\n /**\n * Disable all add sections actions.\n *\n * @param {boolean} locked the new locked value.\n */\n _setAddSectionLocked(locked) {\n const targets = this.getElements(this.selectors.ADDSECTIONREGION);\n targets.forEach(element => {\n element.classList.toggle(this.classes.DISABLED, locked);\n const addSectionElement = element.querySelector(this.selectors.ADDSECTION);\n addSectionElement.classList.toggle(this.classes.DISABLED, locked);\n this.setElementLocked(addSectionElement, locked);\n // We tweak the element to show a tooltip as a title attribute.\n if (locked) {\n getString('sectionaddmax', 'core_courseformat')\n .then((text) => addSectionElement.setAttribute('title', text))\n .catch(Notification.exception);\n addSectionElement.style.pointerEvents = null; // Unlocks the pointer events.\n addSectionElement.style.userSelect = null; // Unlocks the pointer events.\n } else {\n addSectionElement.setAttribute('title', addSectionElement.dataset.addSections);\n }\n });\n const courseAddSection = this.getElement(this.selectors.COURSEADDSECTION);\n if (courseAddSection) {\n const addSection = courseAddSection.querySelector(this.selectors.ADDSECTION);\n addSection.classList.toggle(this.classes.DISPLAYNONE, locked);\n const noMoreSections = courseAddSection.querySelector(this.selectors.MAXSECTIONSWARNING);\n noMoreSections.classList.toggle(this.classes.DISPLAYNONE, !locked);\n }\n }\n\n /**\n * Replace an element with a copy with a different tag name.\n *\n * @param {Element} element the original element\n */\n _disableLink(element) {\n if (element) {\n element.style.pointerEvents = 'none';\n element.style.userSelect = 'none';\n element.classList.add(this.classes.DISABLED);\n element.classList.add(this.classes.ITALIC);\n element.setAttribute('aria-disabled', true);\n element.addEventListener('click', event => event.preventDefault());\n }\n }\n\n /**\n * Render a modal and return a body ready promise.\n *\n * @param {Modal} ModalClass the modal class\n * @param {object} modalParams the modal params\n * @return {Promise} the modal body ready promise\n */\n _modalBodyRenderedPromise(ModalClass, modalParams) {\n return new Promise((resolve, reject) => {\n ModalClass.create(modalParams).then((modal) => {\n modal.setRemoveOnClose(true);\n // Handle body loading event.\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n resolve(modal);\n });\n // Configure some extra modal params.\n if (modalParams.saveButtonText !== undefined) {\n modal.setSaveButtonText(modalParams.saveButtonText);\n }\n if (modalParams.deleteButtonText !== undefined) {\n modal.setDeleteButtonText(modalParams.saveButtonText);\n }\n modal.show();\n return;\n }).catch(() => {\n reject(`Cannot load modal content`);\n });\n });\n }\n\n /**\n * Hide and later destroy a modal.\n *\n * Behat will fail if we remove the modal while some boostrap collapse is executing.\n *\n * @param {Modal} modal\n * @param {HTMLElement} element the dom element to focus on.\n */\n _destroyModal(modal, element) {\n modal.hide();\n const pendingDestroy = new Pending(`courseformat/actions:destroyModal`);\n if (element) {\n element.focus();\n }\n setTimeout(() =>{\n modal.destroy();\n pendingDestroy.resolve();\n }, 500);\n }\n\n /**\n * Get the closest actions menu toggler to an action element.\n *\n * @param {HTMLElement} element the action link element\n * @returns {HTMLElement|undefined}\n */\n _getClosestActionMenuToogler(element) {\n const actionMenu = element.closest(this.selectors.ACTIONMENU);\n if (!actionMenu) {\n return undefined;\n }\n return actionMenu.querySelector(this.selectors.ACTIONMENUTOGGLER);\n }\n}\n"],"names":["directMutations","sectionHide","sectionShow","cmHide","cmShow","cmStealth","cmMoveRight","cmMoveLeft","cmNoGroups","cmSeparateGroups","cmVisibleGroups","BaseComponent","create","name","selectors","ACTIONLINK","SECTIONLINK","CMLINK","SECTIONNODE","MODALTOGGLER","ADDSECTION","CONTENTTREE","ACTIONMENU","ACTIONMENUTOGGLER","OPTIONSRADIO","COURSEADDSECTION","MAXSECTIONSWARNING","ADDSECTIONREGION","classes","DISABLED","ITALIC","DISPLAYNONE","actions","action","mutationReference","Object","entries","Error","stateReady","state","addEventListener","this","element","_dispatchClick","_checkSectionlist","CourseEvents","sectionRefreshed","eventTypes","elementUpdated","_inplaceEditableHandler","getWatchers","watch","handler","event","target","closest","classList","contains","preventDefault","actionName","dataset","methodName","_actionMethodName","undefined","_requestMutationAction","requestName","charAt","toUpperCase","slice","_setAddSectionLocked","course","sectionlist","length","maxsections","itemtype","detail","_event$detail","ajaxreturn","_event$detail$ajaxret","itemid","parseInt","_event$detail2","_event$detail2$ajaxre","Number","isFinite","reactive","dispatch","_getTargetIds","ids","_target$dataset","id","push","bulkType","_target$dataset2","bulk","get","enabled","selectedType","selection","sectionIds","pendingModalReady","Pending","editTools","_getClosestActionMenuToogler","data","getExporter","titleText","sectionInfo","sectionid","sectiontitle","title","information","getFormatString","modal","_modalBodyRenderedPromise","Modal","body","Templates","render","modalBody","getBody","forEach","sectionId","currentElement","querySelector","_disableLink","ContentTree","SECTION","TOGGLER","COLLAPSE","matches","for","getAttribute","_destroyModal","resolve","cmIds","exporter","cmInfo","cmid","cmname","hasdelegatedsection","cmId","ENTER","selector","_expandCmMoveModalParentSections","targetSectionId","targetCmId","droppedCmIds","dropData","cmDraggableData","nextcmid","section","cmlist","component","filter","sectionnode","toggler","collapsibleId","replace","expandNode","Collapse","toggle","show","parentElement","debug","modname","sectionnum","beforemod","some","hassummary","rawtitle","_dispatchSectionDelete","bodyText","count","ModalDeleteCancel","getRoot","on","ModalEvents","delete","e","destroy","baseURI","includes","window","location","href","baseurl","mutationName","_requestPermalink","text","delegatedsection","delegatesectionid","type","parameters","URLSearchParams","search","has","allowstealth","canUseStealth","ModalSaveCancel","saveButtonText","_setupMutationRadioButtonModal","setButtonDisabled","submitFunction","radio","mutation","value","querySelectorAll","parentNode","checked","dbClickEvent","save","locked","getElements","addSectionElement","setElementLocked","then","setAttribute","catch","Notification","exception","style","pointerEvents","userSelect","addSections","courseAddSection","getElement","add","ModalClass","modalParams","Promise","reject","setRemoveOnClose","bodyRendered","setSaveButtonText","deleteButtonText","setDeleteButtonText","hide","pendingDestroy","focus","setTimeout","actionMenu"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js index 119912e2d2f..46aecfcb121 100644 --- a/course/format/amd/build/local/courseeditor/mutations.min.js +++ b/course/format/amd/build/local/courseeditor/mutations.min.js @@ -1,4 +1,4 @@ -define("core_courseformat/local/courseeditor/mutations",["exports","core/ajax","core/str","core/local/reactive/srlogger"],(function(_exports,_ajax,_str,_srlogger){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_srlogger=_interopRequireDefault(_srlogger);let isLoggerSet=!1; +define("core_courseformat/local/courseeditor/mutations",["exports","core/ajax","core/str","core/log","core/local/reactive/srlogger"],(function(_exports,_ajax,_str,_log,_srlogger){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_log=_interopRequireDefault(_log),_srlogger=_interopRequireDefault(_srlogger);let isLoggerSet=!1; /** * Default mutation manager * @@ -6,6 +6,6 @@ define("core_courseformat/local/courseeditor/mutations",["exports","core/ajax"," * @class core_courseformat/local/courseeditor/mutations * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */return _exports.default=class{async _callEditWebservice(action,courseId,ids,targetSectionId,targetCmId){const args={action:action,courseid:courseId,ids:ids};targetSectionId&&(args.targetsectionid=targetSectionId),targetCmId&&(args.targetcmid=targetCmId);let ajaxresult=await _ajax.default.call([{methodname:"core_courseformat_update_course",args:args}])[0];return JSON.parse(ajaxresult)}async _callAddModuleWebservice(courseId,modName,targetSectionNum,targetCmId){const args={courseid:courseId,modname:modName,targetsectionnum:targetSectionNum};targetCmId&&(args.targetcmid=targetCmId);let ajaxresult=await _ajax.default.call([{methodname:"core_courseformat_create_module",args:args}])[0];return JSON.parse(ajaxresult)}async _sectionBasicAction(stateManager,action,sectionIds,targetSectionId,targetCmId){const logEntry=this._getLoggerEntry(stateManager,action,sectionIds,{targetSectionId:targetSectionId,targetCmId:targetCmId,itemType:"section"}),course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice(action,course.id,sectionIds,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1),stateManager.addLoggerEntry(await logEntry)}async _cmBasicAction(stateManager,action,cmIds,targetSectionId,targetCmId){const logEntry=this._getLoggerEntry(stateManager,action,cmIds,{targetSectionId:targetSectionId,targetCmId:targetCmId,itemType:"cm"}),course=stateManager.get("course");this.cmLock(stateManager,cmIds,!0);const updates=await this._callEditWebservice(action,course.id,cmIds,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.cmLock(stateManager,cmIds,!1),stateManager.addLoggerEntry(await logEntry)}async _getLoggerEntry(stateManager,action,itemIds){var _data$itemType,_data$component;let data=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};isLoggerSet||(stateManager.setLogger(new _srlogger.default),isLoggerSet=!0);const feedbackParams={action:action,itemType:null!==(_data$itemType=data.itemType)&&void 0!==_data$itemType?_data$itemType:action.split("_")[0]};let batch="";if(itemIds.length>1)feedbackParams.count=itemIds.length,batch="_batch";else if(1===itemIds.length){var _itemInfo$title;const itemInfo=stateManager.get(feedbackParams.itemType,itemIds[0]);feedbackParams.name=null!==(_itemInfo$title=itemInfo.title)&&void 0!==_itemInfo$title?_itemInfo$title:itemInfo.name}data.targetSectionId&&(feedbackParams.targetSectionName=stateManager.get("section",data.targetSectionId).title),data.targetCmId&&(feedbackParams.targetCmName=stateManager.get("cm",data.targetCmId).name);return{feedbackMessage:await(0,_str.getString)("".concat(action.toLowerCase(),"_feedback").concat(batch),null!==(_data$component=data.component)&&void 0!==_data$component?_data$component:"core_courseformat",feedbackParams)}}init(stateManager){stateManager.addUpdateTypes({prepareFields:this._prepareFields}),stateManager.setLogger(new _srlogger.default),isLoggerSet=!0}_prepareFields(stateManager,updateName,fields){return fields.locked=!1,fields}async sectionHide(stateManager,sectionIds){await this._sectionBasicAction(stateManager,"section_hide",sectionIds)}async sectionShow(stateManager,sectionIds){await this._sectionBasicAction(stateManager,"section_show",sectionIds)}async cmShow(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_show",cmIds)}async cmHide(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_hide",cmIds)}async cmStealth(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_stealth",cmIds)}async cmDuplicate(stateManager,cmIds,targetSectionId,targetCmId){const logEntry=this._getLoggerEntry(stateManager,"cm_duplicate",cmIds),course=stateManager.get("course"),sectionIds=new Set;targetSectionId?sectionIds.add(targetSectionId):cmIds.forEach((cmId=>{const cm=stateManager.get("cm",cmId);sectionIds.add(cm.sectionid)})),this.sectionLock(stateManager,Array.from(sectionIds),!0);const updates=await this._callEditWebservice("cm_duplicate",course.id,cmIds,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,Array.from(sectionIds),!1),stateManager.addLoggerEntry(await logEntry)}async cmMove(stateManager,cmids,targetSectionId,targetCmId){if(!targetSectionId&&!targetCmId)throw new Error("Mutation cmMove requires targetSectionId or targetCmId");const course=stateManager.get("course");this.cmLock(stateManager,cmids,!0);const updates=await this._callEditWebservice("cm_move",course.id,cmids,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.cmLock(stateManager,cmids,!1)}async sectionMoveAfter(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMoveAfter requires targetSectionId");const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_move_after",course.id,sectionIds,targetSectionId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async addSection(stateManager,targetSectionId){targetSectionId||(targetSectionId=0);const course=stateManager.get("course"),updates=await this._callEditWebservice("section_add",course.id,[],targetSectionId);stateManager.processUpdates(updates)}async sectionDelete(stateManager,sectionIds){const course=stateManager.get("course"),updates=await this._callEditWebservice("section_delete",course.id,sectionIds);this.bulkReset(stateManager),stateManager.processUpdates(updates)}async cmDelete(stateManager,cmIds){const course=stateManager.get("course");this.cmLock(stateManager,cmIds,!0);const updates=await this._callEditWebservice("cm_delete",course.id,cmIds);this.bulkReset(stateManager),this.cmLock(stateManager,cmIds,!1),stateManager.processUpdates(updates)}async addModule(stateManager,modName,targetSectionNum,targetCmId){if(!modName)throw new Error("Mutation addModule requires moduleName");if(!targetSectionNum)throw new Error("Mutation addModule requires targetSectionNum");targetCmId||(targetCmId=0);const course=stateManager.get("course"),updates=await this._callAddModuleWebservice(course.id,modName,targetSectionNum,targetCmId);stateManager.processUpdates(updates)}cmDrag(stateManager,cmIds,dragValue){this.setPageItem(stateManager),this._setElementsValue(stateManager,"cm",cmIds,"dragging",dragValue)}sectionDrag(stateManager,sectionIds,dragValue){this.setPageItem(stateManager),this._setElementsValue(stateManager,"section",sectionIds,"dragging",dragValue)}cmCompletion(stateManager,cmIds,complete){const newState=complete?1:0;stateManager.setReadOnly(!1),cmIds.forEach((id=>{const element=stateManager.get("cm",id);element&&(element.isoverallcomplete=complete,element.completionstate=newState)})),stateManager.setReadOnly(!0)}async cmMoveRight(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_moveright",cmIds)}async cmMoveLeft(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_moveleft",cmIds)}async cmNoGroups(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_nogroups",cmIds)}async cmVisibleGroups(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_visiblegroups",cmIds)}async cmSeparateGroups(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_separategroups",cmIds)}cmLock(stateManager,cmIds,lockValue){this._setElementsValue(stateManager,"cm",cmIds,"locked",lockValue)}sectionLock(stateManager,sectionIds,lockValue){this._setElementsValue(stateManager,"section",sectionIds,"locked",lockValue)}_setElementsValue(stateManager,name,ids,fieldName,newValue){stateManager.setReadOnly(!1),ids.forEach((id=>{const element=stateManager.get(name,id);element&&(element[fieldName]=newValue)})),stateManager.setReadOnly(!0)}setPageItem(stateManager,type,id,isStatic){let newPageItem;if(void 0!==type&&(newPageItem=stateManager.get(type,id),!newPageItem))return;const course=stateManager.get("course");course.pageItem&&course.pageItem.type===type&&course.pageItem.id===id||(stateManager.setReadOnly(!1),course.pageItem=null,newPageItem&&(course.pageItem={id:id,type:type,sectionId:"section"==type?newPageItem.id:newPageItem.sectionid,isStatic:isStatic}),stateManager.setReadOnly(!0))}unlockAll(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.section.forEach((section=>{section.locked=!1})),state.cm.forEach((cm=>{cm.locked=!1})),stateManager.setReadOnly(!0)}async sectionIndexCollapsed(stateManager,sectionIds,collapsed){const affectedSections=this._updateStateSectionPreference(stateManager,"indexcollapsed",sectionIds,collapsed);if(!affectedSections)return;const course=stateManager.get("course");let actionName="section_index_collapsed";collapsed||(actionName="section_index_expanded"),await this._callEditWebservice(actionName,course.id,affectedSections)}async allSectionsIndexCollapsed(stateManager,collapsed){const sectionIds=stateManager.getIds("section");this.sectionIndexCollapsed(stateManager,sectionIds,collapsed)}async sectionContentCollapsed(stateManager,sectionIds,collapsed){const affectedSections=this._updateStateSectionPreference(stateManager,"contentcollapsed",sectionIds,collapsed);if(!affectedSections)return;const course=stateManager.get("course");let actionName="section_content_collapsed";collapsed||(actionName="section_content_expanded"),await this._callEditWebservice(actionName,course.id,affectedSections)}_updateStateSectionPreference(stateManager,preferenceName,sectionIds,preferenceValue){stateManager.setReadOnly(!1);const affectedSections=[];return sectionIds.forEach((sectionId=>{const section=stateManager.get("section",sectionId);if(void 0===section)return stateManager.setReadOnly(!0),null;const newValue=null!=preferenceValue?preferenceValue:section[preferenceName];section[preferenceName]!=newValue&&(section[preferenceName]=newValue,affectedSections.push(section.id))})),stateManager.setReadOnly(!0),affectedSections}bulkEnable(stateManager,enabled){const state=stateManager.state;stateManager.setReadOnly(!1),state.bulk.enabled=enabled,state.bulk.selectedType="",state.bulk.selection=[],stateManager.setReadOnly(!0)}bulkReset(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.bulk.selectedType="",state.bulk.selection=[],stateManager.setReadOnly(!0)}cmSelect(stateManager,cmIds){this._addIdsToSelection(stateManager,"cm",cmIds)}cmUnselect(stateManager,cmIds){this._removeIdsFromSelection(stateManager,"cm",cmIds)}sectionSelect(stateManager,sectionIds){this._addIdsToSelection(stateManager,"section",sectionIds)}sectionUnselect(stateManager,sectionIds){this._removeIdsFromSelection(stateManager,"section",sectionIds)}_addIdsToSelection(stateManager,typeName,ids){const bulk=stateManager.state.bulk;if(null==bulk||!bulk.enabled)throw new Error("Bulk is not enabled");if(""!==(null==bulk?void 0:bulk.selectedType)&&(null==bulk?void 0:bulk.selectedType)!==typeName)throw new Error("Cannot add ".concat(typeName," to the current selection"));ids=ids.map((value=>value.toString())),stateManager.setReadOnly(!1),bulk.selectedType=typeName;const newSelection=new Set([...bulk.selection,...ids]);bulk.selection=[...newSelection],stateManager.setReadOnly(!0)}_removeIdsFromSelection(stateManager,typeName,ids){const bulk=stateManager.state.bulk;if(null==bulk||!bulk.enabled)throw new Error("Bulk is not enabled");if(""!==(null==bulk?void 0:bulk.selectedType)&&(null==bulk?void 0:bulk.selectedType)!==typeName)throw new Error("Cannot remove ".concat(typeName," from the current selection"));ids=ids.map((value=>value.toString())),stateManager.setReadOnly(!1);const IdsToFilter=new Set(ids);bulk.selection=bulk.selection.filter((current=>!IdsToFilter.has(current))),0===bulk.selection.length&&(bulk.selectedType=""),stateManager.setReadOnly(!0)}async cmState(stateManager,cmids){this.cmLock(stateManager,cmids,!0);const course=stateManager.get("course"),updates=await this._callEditWebservice("cm_state",course.id,cmids);stateManager.processUpdates(updates),this.cmLock(stateManager,cmids,!1)}async sectionState(stateManager,sectionIds){this.sectionLock(stateManager,sectionIds,!0);const course=stateManager.get("course"),updates=await this._callEditWebservice("section_state",course.id,sectionIds);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async courseState(stateManager){const course=stateManager.get("course"),updates=await this._callEditWebservice("course_state",course.id);stateManager.processUpdates(updates)}},_exports.default})); + */return _exports.default=class{async _callEditWebservice(action,courseId,ids,targetSectionId,targetCmId){const args={action:action,courseid:courseId,ids:ids};targetSectionId&&(args.targetsectionid=targetSectionId),targetCmId&&(args.targetcmid=targetCmId);let ajaxresult=await _ajax.default.call([{methodname:"core_courseformat_update_course",args:args}])[0];return JSON.parse(ajaxresult)}async _callAddModuleWebservice(courseId,modName,targetSectionNum,targetCmId){_log.default.debug("_callAddModuleWebservice() is deprecated. Use _callNewModuleWebservice() instead");const args={courseid:courseId,modname:modName,targetsectionnum:targetSectionNum};targetCmId&&(args.targetcmid=targetCmId);let ajaxresult=await _ajax.default.call([{methodname:"core_courseformat_create_module",args:args}])[0];return JSON.parse(ajaxresult)}async _callNewModuleWebservice(courseId,modName,targetSectionId,targetCmId){const args={courseid:courseId,modname:modName,targetsectionid:targetSectionId};targetCmId&&(args.targetcmid=targetCmId);let ajaxresult=await _ajax.default.call([{methodname:"core_courseformat_new_module",args:args}])[0];return JSON.parse(ajaxresult)}async _sectionBasicAction(stateManager,action,sectionIds,targetSectionId,targetCmId){const logEntry=this._getLoggerEntry(stateManager,action,sectionIds,{targetSectionId:targetSectionId,targetCmId:targetCmId,itemType:"section"}),course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice(action,course.id,sectionIds,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1),stateManager.addLoggerEntry(await logEntry)}async _cmBasicAction(stateManager,action,cmIds,targetSectionId,targetCmId){const logEntry=this._getLoggerEntry(stateManager,action,cmIds,{targetSectionId:targetSectionId,targetCmId:targetCmId,itemType:"cm"}),course=stateManager.get("course");this.cmLock(stateManager,cmIds,!0);const updates=await this._callEditWebservice(action,course.id,cmIds,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.cmLock(stateManager,cmIds,!1),stateManager.addLoggerEntry(await logEntry)}async _getLoggerEntry(stateManager,action,itemIds){var _data$itemType,_data$component;let data=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};isLoggerSet||(stateManager.setLogger(new _srlogger.default),isLoggerSet=!0);const feedbackParams={action:action,itemType:null!==(_data$itemType=data.itemType)&&void 0!==_data$itemType?_data$itemType:action.split("_")[0]};let batch="";if(itemIds.length>1)feedbackParams.count=itemIds.length,batch="_batch";else if(1===itemIds.length){var _itemInfo$title;const itemInfo=stateManager.get(feedbackParams.itemType,itemIds[0]);feedbackParams.name=null!==(_itemInfo$title=itemInfo.title)&&void 0!==_itemInfo$title?_itemInfo$title:itemInfo.name}data.targetSectionId&&(feedbackParams.targetSectionName=stateManager.get("section",data.targetSectionId).title),data.targetCmId&&(feedbackParams.targetCmName=stateManager.get("cm",data.targetCmId).name);return{feedbackMessage:await(0,_str.getString)("".concat(action.toLowerCase(),"_feedback").concat(batch),null!==(_data$component=data.component)&&void 0!==_data$component?_data$component:"core_courseformat",feedbackParams)}}init(stateManager){stateManager.addUpdateTypes({prepareFields:this._prepareFields}),stateManager.setLogger(new _srlogger.default),isLoggerSet=!0}_prepareFields(stateManager,updateName,fields){return fields.locked=!1,fields}async sectionHide(stateManager,sectionIds){await this._sectionBasicAction(stateManager,"section_hide",sectionIds)}async sectionShow(stateManager,sectionIds){await this._sectionBasicAction(stateManager,"section_show",sectionIds)}async cmShow(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_show",cmIds)}async cmHide(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_hide",cmIds)}async cmStealth(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_stealth",cmIds)}async cmDuplicate(stateManager,cmIds,targetSectionId,targetCmId){const logEntry=this._getLoggerEntry(stateManager,"cm_duplicate",cmIds),course=stateManager.get("course"),sectionIds=new Set;targetSectionId?sectionIds.add(targetSectionId):cmIds.forEach((cmId=>{const cm=stateManager.get("cm",cmId);sectionIds.add(cm.sectionid)})),this.sectionLock(stateManager,Array.from(sectionIds),!0);const updates=await this._callEditWebservice("cm_duplicate",course.id,cmIds,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,Array.from(sectionIds),!1),stateManager.addLoggerEntry(await logEntry)}async cmMove(stateManager,cmids,targetSectionId,targetCmId){if(!targetSectionId&&!targetCmId)throw new Error("Mutation cmMove requires targetSectionId or targetCmId");const course=stateManager.get("course");this.cmLock(stateManager,cmids,!0);const updates=await this._callEditWebservice("cm_move",course.id,cmids,targetSectionId,targetCmId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.cmLock(stateManager,cmids,!1)}async sectionMoveAfter(stateManager,sectionIds,targetSectionId){if(!targetSectionId)throw new Error("Mutation sectionMoveAfter requires targetSectionId");const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_move_after",course.id,sectionIds,targetSectionId);this.bulkReset(stateManager),stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async addSection(stateManager,targetSectionId){targetSectionId||(targetSectionId=0);const course=stateManager.get("course"),updates=await this._callEditWebservice("section_add",course.id,[],targetSectionId);stateManager.processUpdates(updates)}async sectionDelete(stateManager,sectionIds){const course=stateManager.get("course"),updates=await this._callEditWebservice("section_delete",course.id,sectionIds);this.bulkReset(stateManager),stateManager.processUpdates(updates)}async cmDelete(stateManager,cmIds){const course=stateManager.get("course");this.cmLock(stateManager,cmIds,!0);const updates=await this._callEditWebservice("cm_delete",course.id,cmIds);this.bulkReset(stateManager),this.cmLock(stateManager,cmIds,!1),stateManager.processUpdates(updates)}async addModule(stateManager,modName,targetSectionNum,targetCmId){if(_log.default.debug("addModule() is deprecated. Use newModule() instead"),!modName)throw new Error("Mutation addModule requires moduleName");if(!targetSectionNum)throw new Error("Mutation addModule requires targetSectionNum");targetCmId||(targetCmId=0);const course=stateManager.get("course"),updates=await this._callAddModuleWebservice(course.id,modName,targetSectionNum,targetCmId);stateManager.processUpdates(updates)}async newModule(stateManager,modName,targetSectionId,targetCmId){if(!modName)throw new Error("Mutation newModule requires moduleName");if(!targetSectionId)throw new Error("Mutation newModule requires targetSectionId");targetCmId||(targetCmId=0);const course=stateManager.get("course"),updates=await this._callNewModuleWebservice(course.id,modName,targetSectionId,targetCmId);stateManager.processUpdates(updates)}cmDrag(stateManager,cmIds,dragValue){this.setPageItem(stateManager),this._setElementsValue(stateManager,"cm",cmIds,"dragging",dragValue)}sectionDrag(stateManager,sectionIds,dragValue){this.setPageItem(stateManager),this._setElementsValue(stateManager,"section",sectionIds,"dragging",dragValue)}cmCompletion(stateManager,cmIds,complete){const newState=complete?1:0;stateManager.setReadOnly(!1),cmIds.forEach((id=>{const element=stateManager.get("cm",id);element&&(element.isoverallcomplete=complete,element.completionstate=newState)})),stateManager.setReadOnly(!0)}async cmMoveRight(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_moveright",cmIds)}async cmMoveLeft(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_moveleft",cmIds)}async cmNoGroups(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_nogroups",cmIds)}async cmVisibleGroups(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_visiblegroups",cmIds)}async cmSeparateGroups(stateManager,cmIds){await this._cmBasicAction(stateManager,"cm_separategroups",cmIds)}cmLock(stateManager,cmIds,lockValue){this._setElementsValue(stateManager,"cm",cmIds,"locked",lockValue)}sectionLock(stateManager,sectionIds,lockValue){this._setElementsValue(stateManager,"section",sectionIds,"locked",lockValue)}_setElementsValue(stateManager,name,ids,fieldName,newValue){stateManager.setReadOnly(!1),ids.forEach((id=>{const element=stateManager.get(name,id);element&&(element[fieldName]=newValue)})),stateManager.setReadOnly(!0)}setPageItem(stateManager,type,id,isStatic){let newPageItem;if(void 0!==type&&(newPageItem=stateManager.get(type,id),!newPageItem))return;const course=stateManager.get("course");course.pageItem&&course.pageItem.type===type&&course.pageItem.id===id||(stateManager.setReadOnly(!1),course.pageItem=null,newPageItem&&(course.pageItem={id:id,type:type,sectionId:"section"==type?newPageItem.id:newPageItem.sectionid,isStatic:isStatic}),stateManager.setReadOnly(!0))}unlockAll(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.section.forEach((section=>{section.locked=!1})),state.cm.forEach((cm=>{cm.locked=!1})),stateManager.setReadOnly(!0)}async sectionIndexCollapsed(stateManager,sectionIds,collapsed){const affectedSections=this._updateStateSectionPreference(stateManager,"indexcollapsed",sectionIds,collapsed);if(!affectedSections)return;const course=stateManager.get("course");let actionName="section_index_collapsed";collapsed||(actionName="section_index_expanded"),await this._callEditWebservice(actionName,course.id,affectedSections)}async allSectionsIndexCollapsed(stateManager,collapsed){const sectionIds=stateManager.getIds("section");this.sectionIndexCollapsed(stateManager,sectionIds,collapsed)}async sectionContentCollapsed(stateManager,sectionIds,collapsed){const affectedSections=this._updateStateSectionPreference(stateManager,"contentcollapsed",sectionIds,collapsed);if(!affectedSections)return;const course=stateManager.get("course");let actionName="section_content_collapsed";collapsed||(actionName="section_content_expanded"),await this._callEditWebservice(actionName,course.id,affectedSections)}_updateStateSectionPreference(stateManager,preferenceName,sectionIds,preferenceValue){stateManager.setReadOnly(!1);const affectedSections=[];return sectionIds.forEach((sectionId=>{const section=stateManager.get("section",sectionId);if(void 0===section)return stateManager.setReadOnly(!0),null;const newValue=null!=preferenceValue?preferenceValue:section[preferenceName];section[preferenceName]!=newValue&&(section[preferenceName]=newValue,affectedSections.push(section.id))})),stateManager.setReadOnly(!0),affectedSections}bulkEnable(stateManager,enabled){const state=stateManager.state;stateManager.setReadOnly(!1),state.bulk.enabled=enabled,state.bulk.selectedType="",state.bulk.selection=[],stateManager.setReadOnly(!0)}bulkReset(stateManager){const state=stateManager.state;stateManager.setReadOnly(!1),state.bulk.selectedType="",state.bulk.selection=[],stateManager.setReadOnly(!0)}cmSelect(stateManager,cmIds){this._addIdsToSelection(stateManager,"cm",cmIds)}cmUnselect(stateManager,cmIds){this._removeIdsFromSelection(stateManager,"cm",cmIds)}sectionSelect(stateManager,sectionIds){this._addIdsToSelection(stateManager,"section",sectionIds)}sectionUnselect(stateManager,sectionIds){this._removeIdsFromSelection(stateManager,"section",sectionIds)}_addIdsToSelection(stateManager,typeName,ids){const bulk=stateManager.state.bulk;if(null==bulk||!bulk.enabled)throw new Error("Bulk is not enabled");if(""!==(null==bulk?void 0:bulk.selectedType)&&(null==bulk?void 0:bulk.selectedType)!==typeName)throw new Error("Cannot add ".concat(typeName," to the current selection"));ids=ids.map((value=>value.toString())),stateManager.setReadOnly(!1),bulk.selectedType=typeName;const newSelection=new Set([...bulk.selection,...ids]);bulk.selection=[...newSelection],stateManager.setReadOnly(!0)}_removeIdsFromSelection(stateManager,typeName,ids){const bulk=stateManager.state.bulk;if(null==bulk||!bulk.enabled)throw new Error("Bulk is not enabled");if(""!==(null==bulk?void 0:bulk.selectedType)&&(null==bulk?void 0:bulk.selectedType)!==typeName)throw new Error("Cannot remove ".concat(typeName," from the current selection"));ids=ids.map((value=>value.toString())),stateManager.setReadOnly(!1);const IdsToFilter=new Set(ids);bulk.selection=bulk.selection.filter((current=>!IdsToFilter.has(current))),0===bulk.selection.length&&(bulk.selectedType=""),stateManager.setReadOnly(!0)}async cmState(stateManager,cmids){this.cmLock(stateManager,cmids,!0);const course=stateManager.get("course"),updates=await this._callEditWebservice("cm_state",course.id,cmids);stateManager.processUpdates(updates),this.cmLock(stateManager,cmids,!1)}async sectionState(stateManager,sectionIds){this.sectionLock(stateManager,sectionIds,!0);const course=stateManager.get("course"),updates=await this._callEditWebservice("section_state",course.id,sectionIds);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}async courseState(stateManager){const course=stateManager.get("course"),updates=await this._callEditWebservice("course_state",course.id);stateManager.processUpdates(updates)}},_exports.default})); //# sourceMappingURL=mutations.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js.map b/course/format/amd/build/local/courseeditor/mutations.min.js.map index cf9eb29e0f5..a7d52a1b813 100644 --- a/course/format/amd/build/local/courseeditor/mutations.min.js.map +++ b/course/format/amd/build/local/courseeditor/mutations.min.js.map @@ -1 +1 @@ -{"version":3,"file":"mutations.min.js","sources":["../../../src/local/courseeditor/mutations.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\nimport ajax from 'core/ajax';\nimport {getString} from \"core/str\";\nimport SRLogger from \"core/local/reactive/srlogger\";\n\n/**\n * Flag to determine whether the screen reader-only logger has already been set, so we only need to set it once.\n *\n * @type {boolean}\n */\nlet isLoggerSet = false;\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n /**\n * Private method to call core_courseformat_create_module webservice.\n *\n * @method _callEditWebservice\n * @param {number} courseId\n * @param {string} modName module name\n * @param {number} targetSectionNum target section number\n * @param {number} targetCmId optional target cm id\n */\n async _callAddModuleWebservice(courseId, modName, targetSectionNum, targetCmId) {\n const args = {\n courseid: courseId,\n modname: modName,\n targetsectionnum: targetSectionNum,\n };\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_create_module',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n /**\n * Execute a basic section state action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {array} sectionIds the section ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _sectionBasicAction(stateManager, action, sectionIds, targetSectionId, targetCmId) {\n const logEntry = this._getLoggerEntry(stateManager, action, sectionIds, {\n targetSectionId,\n targetCmId,\n itemType: 'section',\n });\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice(\n action,\n course.id,\n sectionIds,\n targetSectionId,\n targetCmId\n );\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n stateManager.addLoggerEntry(await logEntry);\n }\n\n /**\n * Execute a basic course module state action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {array} cmIds the cm ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _cmBasicAction(stateManager, action, cmIds, targetSectionId, targetCmId) {\n const logEntry = this._getLoggerEntry(stateManager, action, cmIds, {\n targetSectionId,\n targetCmId,\n itemType: 'cm',\n });\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmIds, true);\n const updates = await this._callEditWebservice(\n action,\n course.id,\n cmIds,\n targetSectionId,\n targetCmId\n );\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmIds, false);\n stateManager.addLoggerEntry(await logEntry);\n }\n\n /**\n * Get log entry for the current action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {int[]|null} itemIds the element ids\n * @param {Object|undefined} data extra params for the log entry\n * @param {string|undefined} data.itemType the element type (will be taken from action if none)\n * @param {int|null|undefined} data.targetSectionId the target section id\n * @param {int|null|undefined} data.targetCmId the target cm id\n * @param {String|null|undefined} data.component optional component (for format plugins)\n * @return {Object} the log entry\n */\n async _getLoggerEntry(stateManager, action, itemIds, data = {}) {\n if (!isLoggerSet) {\n // In case the logger has not been set from init(), ensure we set the logger.\n stateManager.setLogger(new SRLogger());\n isLoggerSet = true;\n }\n const feedbackParams = {\n action,\n itemType: data.itemType ?? action.split('_')[0],\n };\n let batch = '';\n if (itemIds.length > 1) {\n feedbackParams.count = itemIds.length;\n batch = '_batch';\n } else if (itemIds.length === 1) {\n const itemInfo = stateManager.get(feedbackParams.itemType, itemIds[0]);\n feedbackParams.name = itemInfo.title ?? itemInfo.name;\n // Apply shortener for modules like label.\n }\n if (data.targetSectionId) {\n feedbackParams.targetSectionName = stateManager.get('section', data.targetSectionId).title;\n }\n if (data.targetCmId) {\n feedbackParams.targetCmName = stateManager.get('cm', data.targetCmId).name;\n }\n\n const message = await getString(\n `${action.toLowerCase()}_feedback${batch}`,\n data.component ?? 'core_courseformat',\n feedbackParams\n );\n\n return {\n feedbackMessage: message,\n };\n }\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is coming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n // Use the screen reader-only logger (SRLogger) to handle the feedback messages from the mutations.\n stateManager.setLogger(new SRLogger());\n isLoggerSet = true;\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Hides sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n async sectionHide(stateManager, sectionIds) {\n await this._sectionBasicAction(stateManager, 'section_hide', sectionIds);\n }\n\n /**\n * Show sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n async sectionShow(stateManager, sectionIds) {\n await this._sectionBasicAction(stateManager, 'section_show', sectionIds);\n }\n\n /**\n * Show cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmShow(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_show', cmIds);\n }\n\n /**\n * Hide cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmHide(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_hide', cmIds);\n }\n\n /**\n * Stealth cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmStealth(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_stealth', cmIds);\n }\n\n /**\n * Duplicate course modules\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {number|undefined} targetSectionId the optional target sectionId\n * @param {number|undefined} targetCmId the target course module id\n */\n async cmDuplicate(stateManager, cmIds, targetSectionId, targetCmId) {\n const logEntry = this._getLoggerEntry(stateManager, 'cm_duplicate', cmIds);\n const course = stateManager.get('course');\n // Lock all target sections.\n const sectionIds = new Set();\n if (targetSectionId) {\n sectionIds.add(targetSectionId);\n } else {\n cmIds.forEach((cmId) => {\n const cm = stateManager.get('cm', cmId);\n sectionIds.add(cm.sectionid);\n });\n }\n this.sectionLock(stateManager, Array.from(sectionIds), true);\n\n const updates = await this._callEditWebservice('cm_duplicate', course.id, cmIds, targetSectionId, targetCmId);\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n\n this.sectionLock(stateManager, Array.from(sectionIds), false);\n stateManager.addLoggerEntry(await logEntry);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules after a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMoveAfter(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMoveAfter requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move_after', course.id, sectionIds, targetSectionId);\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of section ids\n */\n async cmDelete(stateManager, cmIds) {\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmIds, true);\n const updates = await this._callEditWebservice('cm_delete', course.id, cmIds);\n this.bulkReset(stateManager);\n this.cmLock(stateManager, cmIds, false);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Add a new module to a specific course section.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {string} modName the modulename to add\n * @param {number} targetSectionNum the target section number\n * @param {number} targetCmId optional the target cm id\n */\n async addModule(stateManager, modName, targetSectionNum, targetCmId) {\n if (!modName) {\n throw new Error(`Mutation addModule requires moduleName`);\n }\n if (!targetSectionNum) {\n throw new Error(`Mutation addModule requires targetSectionNum`);\n }\n if (!targetCmId) {\n targetCmId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callAddModuleWebservice(course.id, modName, targetSectionNum, targetCmId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newState = (complete) ? 1 : 0;\n stateManager.setReadOnly(false);\n cmIds.forEach((id) => {\n const element = stateManager.get('cm', id);\n if (element) {\n element.isoverallcomplete = complete;\n element.completionstate = newState;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Move cms to the right: indent = 1.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmMoveRight(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_moveright', cmIds);\n }\n\n /**\n * Move cms to the left: indent = 0.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmMoveLeft(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_moveleft', cmIds);\n }\n\n /**\n * Set cms group mode to NOGROUPS.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmNoGroups(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_nogroups', cmIds);\n }\n\n /**\n * Set cms group mode to VISIBLEGROUPS.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmVisibleGroups(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_visiblegroups', cmIds);\n }\n\n /**\n * Set cms group mode to SEPARATEGROUPS.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmSeparateGroups(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_separategroups', cmIds);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n const course = stateManager.get('course');\n if (course.pageItem && course.pageItem.type === type && course.pageItem.id === id) {\n return;\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Update the course index collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {\n const affectedSections = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);\n if (!affectedSections) {\n return;\n }\n const course = stateManager.get('course');\n let actionName = 'section_index_collapsed';\n if (!collapsed) {\n actionName = 'section_index_expanded';\n }\n await this._callEditWebservice(actionName, course.id, affectedSections);\n }\n\n /**\n * Update the course index collapsed attribute of all sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {boolean} collapsed the new collapsed value\n */\n async allSectionsIndexCollapsed(stateManager, collapsed) {\n const sectionIds = stateManager.getIds('section');\n this.sectionIndexCollapsed(stateManager, sectionIds, collapsed);\n }\n\n /**\n * Update the course content collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionContentCollapsed(stateManager, sectionIds, collapsed) {\n const affectedSections = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);\n if (!affectedSections) {\n return;\n }\n const course = stateManager.get('course');\n let actionName = 'section_content_collapsed';\n if (!collapsed) {\n actionName = 'section_content_expanded';\n }\n await this._callEditWebservice(actionName, course.id, affectedSections);\n }\n\n /**\n * Private batch update for a section preference attribute.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {string} preferenceName the preference name\n * @param {array} sectionIds the affected section ids\n * @param {boolean} preferenceValue the new preferenceValue value\n * @return {Number[]|null} sections ids with the preference value true or null if no update is required\n */\n _updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {\n stateManager.setReadOnly(false);\n const affectedSections = [];\n // Check if we need to update preferences.\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n stateManager.setReadOnly(true);\n return null;\n }\n const newValue = preferenceValue ?? section[preferenceName];\n if (section[preferenceName] != newValue) {\n section[preferenceName] = newValue;\n affectedSections.push(section.id);\n }\n });\n stateManager.setReadOnly(true);\n return affectedSections;\n }\n\n /**\n * Enable/disable bulk editing.\n *\n * Note: reenabling the bulk will clean the current selection.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {Boolean} enabled the new bulk state.\n */\n bulkEnable(stateManager, enabled) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.bulk.enabled = enabled;\n state.bulk.selectedType = '';\n state.bulk.selection = [];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Reset the current selection.\n * @param {StateManager} stateManager the current state manager\n */\n bulkReset(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.bulk.selectedType = '';\n state.bulk.selection = [];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Select a list of cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n cmSelect(stateManager, cmIds) {\n this._addIdsToSelection(stateManager, 'cm', cmIds);\n }\n\n /**\n * Unselect a list of cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n cmUnselect(stateManager, cmIds) {\n this._removeIdsFromSelection(stateManager, 'cm', cmIds);\n }\n\n /**\n * Select a list of sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of cm ids\n */\n sectionSelect(stateManager, sectionIds) {\n this._addIdsToSelection(stateManager, 'section', sectionIds);\n }\n\n /**\n * Unselect a list of sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of cm ids\n */\n sectionUnselect(stateManager, sectionIds) {\n this._removeIdsFromSelection(stateManager, 'section', sectionIds);\n }\n\n /**\n * Add some ids to the current bulk selection.\n * @param {StateManager} stateManager the current state manager\n * @param {String} typeName the type name (section/cm)\n * @param {array} ids the list of ids\n */\n _addIdsToSelection(stateManager, typeName, ids) {\n const bulk = stateManager.state.bulk;\n if (!bulk?.enabled) {\n throw new Error(`Bulk is not enabled`);\n }\n if (bulk?.selectedType !== \"\" && bulk?.selectedType !== typeName) {\n throw new Error(`Cannot add ${typeName} to the current selection`);\n }\n\n // Stored ids are strings for compatability with HTML data attributes.\n ids = ids.map(value => value.toString());\n\n stateManager.setReadOnly(false);\n bulk.selectedType = typeName;\n const newSelection = new Set([...bulk.selection, ...ids]);\n bulk.selection = [...newSelection];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Remove some ids to the current bulk selection.\n *\n * The method resets the selection type if the current selection is empty.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String} typeName the type name (section/cm)\n * @param {array} ids the list of ids\n */\n _removeIdsFromSelection(stateManager, typeName, ids) {\n const bulk = stateManager.state.bulk;\n if (!bulk?.enabled) {\n throw new Error(`Bulk is not enabled`);\n }\n if (bulk?.selectedType !== \"\" && bulk?.selectedType !== typeName) {\n throw new Error(`Cannot remove ${typeName} from the current selection`);\n }\n\n // Stored ids are strings for compatability with HTML data attributes.\n ids = ids.map(value => value.toString());\n\n stateManager.setReadOnly(false);\n const IdsToFilter = new Set(ids);\n bulk.selection = bulk.selection.filter(current => !IdsToFilter.has(current));\n if (bulk.selection.length === 0) {\n bulk.selectedType = '';\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"names":["isLoggerSet","action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajaxresult","ajax","call","methodname","JSON","parse","modName","targetSectionNum","modname","targetsectionnum","stateManager","sectionIds","logEntry","this","_getLoggerEntry","itemType","course","get","sectionLock","updates","_callEditWebservice","id","bulkReset","processUpdates","addLoggerEntry","cmIds","cmLock","itemIds","data","setLogger","SRLogger","feedbackParams","split","batch","length","count","itemInfo","name","title","targetSectionName","targetCmName","feedbackMessage","toLowerCase","component","init","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","_sectionBasicAction","_cmBasicAction","Set","add","forEach","cmId","cm","sectionid","Array","from","cmids","Error","_callAddModuleWebservice","cmDrag","dragValue","setPageItem","_setElementsValue","sectionDrag","cmCompletion","complete","newState","setReadOnly","element","isoverallcomplete","completionstate","lockValue","fieldName","newValue","type","isStatic","newPageItem","undefined","pageItem","sectionId","unlockAll","state","section","collapsed","affectedSections","_updateStateSectionPreference","actionName","getIds","sectionIndexCollapsed","preferenceName","preferenceValue","push","bulkEnable","enabled","bulk","selectedType","selection","cmSelect","_addIdsToSelection","cmUnselect","_removeIdsFromSelection","sectionSelect","sectionUnselect","typeName","map","value","toString","newSelection","IdsToFilter","filter","current","has"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mutations.min.js","sources":["../../../src/local/courseeditor/mutations.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\nimport ajax from 'core/ajax';\nimport {getString} from \"core/str\";\nimport log from 'core/log';\nimport SRLogger from \"core/local/reactive/srlogger\";\n\n/**\n * Flag to determine whether the screen reader-only logger has already been set, so we only need to set it once.\n *\n * @type {boolean}\n */\nlet isLoggerSet = false;\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n /**\n * Private method to call core_courseformat_create_module webservice.\n *\n * @deprecated since Moodle 5.0 MDL-83469.\n * @todo MDL-83851 This will be deleted in Moodle 6.0.\n * @method _callEditWebservice\n * @param {number} courseId\n * @param {string} modName module name\n * @param {number} targetSectionNum target section number\n * @param {number} targetCmId optional target cm id\n */\n async _callAddModuleWebservice(courseId, modName, targetSectionNum, targetCmId) {\n log.debug('_callAddModuleWebservice() is deprecated. Use _callNewModuleWebservice() instead');\n const args = {\n courseid: courseId,\n modname: modName,\n targetsectionnum: targetSectionNum,\n };\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_create_module',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n /**\n * Private method to call core_courseformat_new_module webservice.\n *\n * @method _callEditWebservice\n * @param {number} courseId\n * @param {string} modName module name\n * @param {number} targetSectionId target section number\n * @param {number} targetCmId optional target cm id\n */\n async _callNewModuleWebservice(courseId, modName, targetSectionId, targetCmId) {\n const args = {\n courseid: courseId,\n modname: modName,\n targetsectionid: targetSectionId,\n };\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_new_module',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n /**\n * Execute a basic section state action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {array} sectionIds the section ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _sectionBasicAction(stateManager, action, sectionIds, targetSectionId, targetCmId) {\n const logEntry = this._getLoggerEntry(stateManager, action, sectionIds, {\n targetSectionId,\n targetCmId,\n itemType: 'section',\n });\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice(\n action,\n course.id,\n sectionIds,\n targetSectionId,\n targetCmId\n );\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n stateManager.addLoggerEntry(await logEntry);\n }\n\n /**\n * Execute a basic course module state action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {array} cmIds the cm ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _cmBasicAction(stateManager, action, cmIds, targetSectionId, targetCmId) {\n const logEntry = this._getLoggerEntry(stateManager, action, cmIds, {\n targetSectionId,\n targetCmId,\n itemType: 'cm',\n });\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmIds, true);\n const updates = await this._callEditWebservice(\n action,\n course.id,\n cmIds,\n targetSectionId,\n targetCmId\n );\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmIds, false);\n stateManager.addLoggerEntry(await logEntry);\n }\n\n /**\n * Get log entry for the current action.\n * @param {StateManager} stateManager the current state manager\n * @param {string} action the action name\n * @param {int[]|null} itemIds the element ids\n * @param {Object|undefined} data extra params for the log entry\n * @param {string|undefined} data.itemType the element type (will be taken from action if none)\n * @param {int|null|undefined} data.targetSectionId the target section id\n * @param {int|null|undefined} data.targetCmId the target cm id\n * @param {String|null|undefined} data.component optional component (for format plugins)\n * @return {Object} the log entry\n */\n async _getLoggerEntry(stateManager, action, itemIds, data = {}) {\n if (!isLoggerSet) {\n // In case the logger has not been set from init(), ensure we set the logger.\n stateManager.setLogger(new SRLogger());\n isLoggerSet = true;\n }\n const feedbackParams = {\n action,\n itemType: data.itemType ?? action.split('_')[0],\n };\n let batch = '';\n if (itemIds.length > 1) {\n feedbackParams.count = itemIds.length;\n batch = '_batch';\n } else if (itemIds.length === 1) {\n const itemInfo = stateManager.get(feedbackParams.itemType, itemIds[0]);\n feedbackParams.name = itemInfo.title ?? itemInfo.name;\n // Apply shortener for modules like label.\n }\n if (data.targetSectionId) {\n feedbackParams.targetSectionName = stateManager.get('section', data.targetSectionId).title;\n }\n if (data.targetCmId) {\n feedbackParams.targetCmName = stateManager.get('cm', data.targetCmId).name;\n }\n\n const message = await getString(\n `${action.toLowerCase()}_feedback${batch}`,\n data.component ?? 'core_courseformat',\n feedbackParams\n );\n\n return {\n feedbackMessage: message,\n };\n }\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is coming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n // Use the screen reader-only logger (SRLogger) to handle the feedback messages from the mutations.\n stateManager.setLogger(new SRLogger());\n isLoggerSet = true;\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Hides sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n async sectionHide(stateManager, sectionIds) {\n await this._sectionBasicAction(stateManager, 'section_hide', sectionIds);\n }\n\n /**\n * Show sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n async sectionShow(stateManager, sectionIds) {\n await this._sectionBasicAction(stateManager, 'section_show', sectionIds);\n }\n\n /**\n * Show cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmShow(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_show', cmIds);\n }\n\n /**\n * Hide cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmHide(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_hide', cmIds);\n }\n\n /**\n * Stealth cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmStealth(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_stealth', cmIds);\n }\n\n /**\n * Duplicate course modules\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {number|undefined} targetSectionId the optional target sectionId\n * @param {number|undefined} targetCmId the target course module id\n */\n async cmDuplicate(stateManager, cmIds, targetSectionId, targetCmId) {\n const logEntry = this._getLoggerEntry(stateManager, 'cm_duplicate', cmIds);\n const course = stateManager.get('course');\n // Lock all target sections.\n const sectionIds = new Set();\n if (targetSectionId) {\n sectionIds.add(targetSectionId);\n } else {\n cmIds.forEach((cmId) => {\n const cm = stateManager.get('cm', cmId);\n sectionIds.add(cm.sectionid);\n });\n }\n this.sectionLock(stateManager, Array.from(sectionIds), true);\n\n const updates = await this._callEditWebservice('cm_duplicate', course.id, cmIds, targetSectionId, targetCmId);\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n\n this.sectionLock(stateManager, Array.from(sectionIds), false);\n stateManager.addLoggerEntry(await logEntry);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules after a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMoveAfter(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMoveAfter requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move_after', course.id, sectionIds, targetSectionId);\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Add a new section to a specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {number} targetSectionId optional the target section id\n */\n async addSection(stateManager, targetSectionId) {\n if (!targetSectionId) {\n targetSectionId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_add', course.id, [], targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of course modules ids\n */\n async sectionDelete(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_delete', course.id, sectionIds);\n this.bulkReset(stateManager);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Delete cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of section ids\n */\n async cmDelete(stateManager, cmIds) {\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmIds, true);\n const updates = await this._callEditWebservice('cm_delete', course.id, cmIds);\n this.bulkReset(stateManager);\n this.cmLock(stateManager, cmIds, false);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Add a new module to a specific course section.\n *\n * @deprecated since Moodle 5.0 MDL-83469.\n * @todo MDL-83851 This will be deleted in Moodle 6.0.\n * @param {StateManager} stateManager the current state manager\n * @param {string} modName the modulename to add\n * @param {number} targetSectionNum the target section number\n * @param {number} targetCmId optional the target cm id\n */\n async addModule(stateManager, modName, targetSectionNum, targetCmId) {\n log.debug('addModule() is deprecated. Use newModule() instead');\n if (!modName) {\n throw new Error(`Mutation addModule requires moduleName`);\n }\n if (!targetSectionNum) {\n throw new Error(`Mutation addModule requires targetSectionNum`);\n }\n if (!targetCmId) {\n targetCmId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callAddModuleWebservice(course.id, modName, targetSectionNum, targetCmId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Add a new module to a specific course section.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {string} modName the modulename to add\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId optional the target cm id\n */\n async newModule(stateManager, modName, targetSectionId, targetCmId) {\n if (!modName) {\n throw new Error(`Mutation newModule requires moduleName`);\n }\n if (!targetSectionId) {\n throw new Error(`Mutation newModule requires targetSectionId`);\n }\n if (!targetCmId) {\n targetCmId = 0;\n }\n const course = stateManager.get('course');\n const updates = await this._callNewModuleWebservice(course.id, modName, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this.setPageItem(stateManager);\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course modules as complete.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} complete the new completion value\n */\n cmCompletion(stateManager, cmIds, complete) {\n const newState = (complete) ? 1 : 0;\n stateManager.setReadOnly(false);\n cmIds.forEach((id) => {\n const element = stateManager.get('cm', id);\n if (element) {\n element.isoverallcomplete = complete;\n element.completionstate = newState;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Move cms to the right: indent = 1.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmMoveRight(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_moveright', cmIds);\n }\n\n /**\n * Move cms to the left: indent = 0.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmMoveLeft(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_moveleft', cmIds);\n }\n\n /**\n * Set cms group mode to NOGROUPS.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmNoGroups(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_nogroups', cmIds);\n }\n\n /**\n * Set cms group mode to VISIBLEGROUPS.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmVisibleGroups(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_visiblegroups', cmIds);\n }\n\n /**\n * Set cms group mode to SEPARATEGROUPS.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n async cmSeparateGroups(stateManager, cmIds) {\n await this._cmBasicAction(stateManager, 'cm_separategroups', cmIds);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Set the page current item.\n *\n * Only one element of the course state can be the page item at a time.\n *\n * There are several actions that can alter the page current item. For example, when the user is in an activity\n * page, the page item is always the activity one. However, in a course page, when the user scrolls to an element,\n * this element get the page item.\n *\n * If the page item is static means that it is not meant to change. This is important because\n * static page items has some special logic. For example, if a cm is the static page item\n * and it is inside a collapsed section, the course index will expand the section to make it visible.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String|undefined} type the element type (section or cm). Undefined will remove the current page item.\n * @param {Number|undefined} id the element id\n * @param {boolean|undefined} isStatic if the page item is static\n */\n setPageItem(stateManager, type, id, isStatic) {\n let newPageItem;\n if (type !== undefined) {\n newPageItem = stateManager.get(type, id);\n if (!newPageItem) {\n return;\n }\n }\n const course = stateManager.get('course');\n if (course.pageItem && course.pageItem.type === type && course.pageItem.id === id) {\n return;\n }\n stateManager.setReadOnly(false);\n // Remove the current page item.\n course.pageItem = null;\n // Save the new page item.\n if (newPageItem) {\n course.pageItem = {\n id,\n type,\n sectionId: (type == 'section') ? newPageItem.id : newPageItem.sectionid,\n isStatic,\n };\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Update the course index collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionIndexCollapsed(stateManager, sectionIds, collapsed) {\n const affectedSections = this._updateStateSectionPreference(stateManager, 'indexcollapsed', sectionIds, collapsed);\n if (!affectedSections) {\n return;\n }\n const course = stateManager.get('course');\n let actionName = 'section_index_collapsed';\n if (!collapsed) {\n actionName = 'section_index_expanded';\n }\n await this._callEditWebservice(actionName, course.id, affectedSections);\n }\n\n /**\n * Update the course index collapsed attribute of all sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {boolean} collapsed the new collapsed value\n */\n async allSectionsIndexCollapsed(stateManager, collapsed) {\n const sectionIds = stateManager.getIds('section');\n this.sectionIndexCollapsed(stateManager, sectionIds, collapsed);\n }\n\n /**\n * Update the course content collapsed attribute of some sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the affected section ids\n * @param {boolean} collapsed the new collapsed value\n */\n async sectionContentCollapsed(stateManager, sectionIds, collapsed) {\n const affectedSections = this._updateStateSectionPreference(stateManager, 'contentcollapsed', sectionIds, collapsed);\n if (!affectedSections) {\n return;\n }\n const course = stateManager.get('course');\n let actionName = 'section_content_collapsed';\n if (!collapsed) {\n actionName = 'section_content_expanded';\n }\n await this._callEditWebservice(actionName, course.id, affectedSections);\n }\n\n /**\n * Private batch update for a section preference attribute.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {string} preferenceName the preference name\n * @param {array} sectionIds the affected section ids\n * @param {boolean} preferenceValue the new preferenceValue value\n * @return {Number[]|null} sections ids with the preference value true or null if no update is required\n */\n _updateStateSectionPreference(stateManager, preferenceName, sectionIds, preferenceValue) {\n stateManager.setReadOnly(false);\n const affectedSections = [];\n // Check if we need to update preferences.\n sectionIds.forEach(sectionId => {\n const section = stateManager.get('section', sectionId);\n if (section === undefined) {\n stateManager.setReadOnly(true);\n return null;\n }\n const newValue = preferenceValue ?? section[preferenceName];\n if (section[preferenceName] != newValue) {\n section[preferenceName] = newValue;\n affectedSections.push(section.id);\n }\n });\n stateManager.setReadOnly(true);\n return affectedSections;\n }\n\n /**\n * Enable/disable bulk editing.\n *\n * Note: reenabling the bulk will clean the current selection.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {Boolean} enabled the new bulk state.\n */\n bulkEnable(stateManager, enabled) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.bulk.enabled = enabled;\n state.bulk.selectedType = '';\n state.bulk.selection = [];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Reset the current selection.\n * @param {StateManager} stateManager the current state manager\n */\n bulkReset(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.bulk.selectedType = '';\n state.bulk.selection = [];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Select a list of cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n cmSelect(stateManager, cmIds) {\n this._addIdsToSelection(stateManager, 'cm', cmIds);\n }\n\n /**\n * Unselect a list of cms.\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of cm ids\n */\n cmUnselect(stateManager, cmIds) {\n this._removeIdsFromSelection(stateManager, 'cm', cmIds);\n }\n\n /**\n * Select a list of sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of cm ids\n */\n sectionSelect(stateManager, sectionIds) {\n this._addIdsToSelection(stateManager, 'section', sectionIds);\n }\n\n /**\n * Unselect a list of sections.\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of cm ids\n */\n sectionUnselect(stateManager, sectionIds) {\n this._removeIdsFromSelection(stateManager, 'section', sectionIds);\n }\n\n /**\n * Add some ids to the current bulk selection.\n * @param {StateManager} stateManager the current state manager\n * @param {String} typeName the type name (section/cm)\n * @param {array} ids the list of ids\n */\n _addIdsToSelection(stateManager, typeName, ids) {\n const bulk = stateManager.state.bulk;\n if (!bulk?.enabled) {\n throw new Error(`Bulk is not enabled`);\n }\n if (bulk?.selectedType !== \"\" && bulk?.selectedType !== typeName) {\n throw new Error(`Cannot add ${typeName} to the current selection`);\n }\n\n // Stored ids are strings for compatability with HTML data attributes.\n ids = ids.map(value => value.toString());\n\n stateManager.setReadOnly(false);\n bulk.selectedType = typeName;\n const newSelection = new Set([...bulk.selection, ...ids]);\n bulk.selection = [...newSelection];\n stateManager.setReadOnly(true);\n }\n\n /**\n * Remove some ids to the current bulk selection.\n *\n * The method resets the selection type if the current selection is empty.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {String} typeName the type name (section/cm)\n * @param {array} ids the list of ids\n */\n _removeIdsFromSelection(stateManager, typeName, ids) {\n const bulk = stateManager.state.bulk;\n if (!bulk?.enabled) {\n throw new Error(`Bulk is not enabled`);\n }\n if (bulk?.selectedType !== \"\" && bulk?.selectedType !== typeName) {\n throw new Error(`Cannot remove ${typeName} from the current selection`);\n }\n\n // Stored ids are strings for compatability with HTML data attributes.\n ids = ids.map(value => value.toString());\n\n stateManager.setReadOnly(false);\n const IdsToFilter = new Set(ids);\n bulk.selection = bulk.selection.filter(current => !IdsToFilter.has(current));\n if (bulk.selection.length === 0) {\n bulk.selectedType = '';\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"names":["isLoggerSet","action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajaxresult","ajax","call","methodname","JSON","parse","modName","targetSectionNum","debug","modname","targetsectionnum","stateManager","sectionIds","logEntry","this","_getLoggerEntry","itemType","course","get","sectionLock","updates","_callEditWebservice","id","bulkReset","processUpdates","addLoggerEntry","cmIds","cmLock","itemIds","data","setLogger","SRLogger","feedbackParams","split","batch","length","count","itemInfo","name","title","targetSectionName","targetCmName","feedbackMessage","toLowerCase","component","init","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","_sectionBasicAction","_cmBasicAction","Set","add","forEach","cmId","cm","sectionid","Array","from","cmids","Error","_callAddModuleWebservice","_callNewModuleWebservice","cmDrag","dragValue","setPageItem","_setElementsValue","sectionDrag","cmCompletion","complete","newState","setReadOnly","element","isoverallcomplete","completionstate","lockValue","fieldName","newValue","type","isStatic","newPageItem","undefined","pageItem","sectionId","unlockAll","state","section","collapsed","affectedSections","_updateStateSectionPreference","actionName","getIds","sectionIndexCollapsed","preferenceName","preferenceValue","push","bulkEnable","enabled","bulk","selectedType","selection","cmSelect","_addIdsToSelection","cmUnselect","_removeIdsFromSelection","sectionSelect","sectionUnselect","typeName","map","value","toString","newSelection","IdsToFilter","filter","current","has"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/src/local/content/actions.js b/course/format/amd/src/local/content/actions.js index b513e39fe02..8fc375ffaec 100644 --- a/course/format/amd/src/local/content/actions.js +++ b/course/format/amd/src/local/content/actions.js @@ -28,6 +28,7 @@ import {BaseComponent} from 'core/reactive'; import {eventTypes} from 'core/local/inplace_editable/events'; import Collapse from 'theme_boost/bootstrap/collapse'; +import log from 'core/log'; import Modal from 'core/modal'; import ModalSaveCancel from 'core/modal_save_cancel'; import ModalDeleteCancel from 'core/modal_delete_cancel'; @@ -488,14 +489,28 @@ export default class extends BaseComponent { /** * Handle a create subsection request. * + * @deprecated since Moodle 5.0 MDL-83469. + * @todo MDL-83851 This will be deleted in Moodle 6.0. * @param {Element} target the dispatch action element * @param {Event} event the triggered event */ async _requestAddModule(target, event) { + log.debug('AddModule action is deprecated. Use newModule instead'); event.preventDefault(); this.reactive.dispatch('addModule', target.dataset.modname, target.dataset.sectionnum, target.dataset.beforemod); } + /** + * Handle a new create subsection request. + * + * @param {Element} target the dispatch action element + * @param {Event} event the triggered event + */ + async _requestNewModule(target, event) { + event.preventDefault(); + this.reactive.dispatch('newModule', target.dataset.modname, target.dataset.sectionid, target.dataset.beforemod); + } + /** * Handle a delete section request. * diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js index e96c7017098..4d6c7da559c 100644 --- a/course/format/amd/src/local/courseeditor/mutations.js +++ b/course/format/amd/src/local/courseeditor/mutations.js @@ -15,6 +15,7 @@ import ajax from 'core/ajax'; import {getString} from "core/str"; +import log from 'core/log'; import SRLogger from "core/local/reactive/srlogger"; /** @@ -68,27 +69,55 @@ export default class { /** * Private method to call core_courseformat_create_module webservice. * + * @deprecated since Moodle 5.0 MDL-83469. + * @todo MDL-83851 This will be deleted in Moodle 6.0. * @method _callEditWebservice * @param {number} courseId * @param {string} modName module name * @param {number} targetSectionNum target section number * @param {number} targetCmId optional target cm id */ - async _callAddModuleWebservice(courseId, modName, targetSectionNum, targetCmId) { - const args = { - courseid: courseId, - modname: modName, - targetsectionnum: targetSectionNum, - }; - if (targetCmId) { - args.targetcmid = targetCmId; - } - let ajaxresult = await ajax.call([{ - methodname: 'core_courseformat_create_module', - args, - }])[0]; - return JSON.parse(ajaxresult); + async _callAddModuleWebservice(courseId, modName, targetSectionNum, targetCmId) { + log.debug('_callAddModuleWebservice() is deprecated. Use _callNewModuleWebservice() instead'); + const args = { + courseid: courseId, + modname: modName, + targetsectionnum: targetSectionNum, + }; + if (targetCmId) { + args.targetcmid = targetCmId; } + let ajaxresult = await ajax.call([{ + methodname: 'core_courseformat_create_module', + args, + }])[0]; + return JSON.parse(ajaxresult); + } + + /** + * Private method to call core_courseformat_new_module webservice. + * + * @method _callEditWebservice + * @param {number} courseId + * @param {string} modName module name + * @param {number} targetSectionId target section number + * @param {number} targetCmId optional target cm id + */ + async _callNewModuleWebservice(courseId, modName, targetSectionId, targetCmId) { + const args = { + courseid: courseId, + modname: modName, + targetsectionid: targetSectionId, + }; + if (targetCmId) { + args.targetcmid = targetCmId; + } + let ajaxresult = await ajax.call([{ + methodname: 'core_courseformat_new_module', + args, + }])[0]; + return JSON.parse(ajaxresult); + } /** * Execute a basic section state action. @@ -396,12 +425,15 @@ export default class { /** * Add a new module to a specific course section. * + * @deprecated since Moodle 5.0 MDL-83469. + * @todo MDL-83851 This will be deleted in Moodle 6.0. * @param {StateManager} stateManager the current state manager * @param {string} modName the modulename to add * @param {number} targetSectionNum the target section number * @param {number} targetCmId optional the target cm id */ async addModule(stateManager, modName, targetSectionNum, targetCmId) { + log.debug('addModule() is deprecated. Use newModule() instead'); if (!modName) { throw new Error(`Mutation addModule requires moduleName`); } @@ -416,6 +448,29 @@ export default class { stateManager.processUpdates(updates); } + /** + * Add a new module to a specific course section. + * + * @param {StateManager} stateManager the current state manager + * @param {string} modName the modulename to add + * @param {number} targetSectionId the target section id + * @param {number} targetCmId optional the target cm id + */ + async newModule(stateManager, modName, targetSectionId, targetCmId) { + if (!modName) { + throw new Error(`Mutation newModule requires moduleName`); + } + if (!targetSectionId) { + throw new Error(`Mutation newModule requires targetSectionId`); + } + if (!targetCmId) { + targetCmId = 0; + } + const course = stateManager.get('course'); + const updates = await this._callNewModuleWebservice(course.id, modName, targetSectionId, targetCmId); + stateManager.processUpdates(updates); + } + /** * Mark or unmark course modules as dragging. * diff --git a/course/format/classes/external/create_module.php b/course/format/classes/external/create_module.php index 46f99c008db..85990d85190 100644 --- a/course/format/classes/external/create_module.php +++ b/course/format/classes/external/create_module.php @@ -30,12 +30,15 @@ use core_courseformat\base as course_format; * @package core_courseformat * @copyright 2024 Mikel Martín * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @deprecated since Moodle 5.0 + * @todo Remove this class in Moodle 6.0 MDL-83851 */ class create_module extends external_api { /** * Webservice parameters. * + * @deprecated since Moodle 5.0 * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { @@ -60,6 +63,7 @@ class create_module extends external_api { * on cms, sections and the general course data. However, if some plugin needs adhoc messages for * its own mutation module, extend this class in format_XXX\course. * + * @deprecated since Moodle 5.0 * @param int $courseid the course id * @param string $modname the module name * @param int $targetsectionnum the target section number @@ -139,9 +143,18 @@ class create_module extends external_api { /** * Webservice returns. * + * @deprecated since Moodle 5.0 * @return external_value */ public static function execute_returns(): external_value { return new external_value(PARAM_RAW, 'Encoded course update JSON'); } + + /** + * Mark the function as deprecated. + * @return bool + */ + public static function execute_is_deprecated() { + return true; + } } diff --git a/course/format/classes/external/new_module.php b/course/format/classes/external/new_module.php new file mode 100644 index 00000000000..f29293cd63d --- /dev/null +++ b/course/format/classes/external/new_module.php @@ -0,0 +1,148 @@ +. + +namespace core_courseformat\external; + +use core_external\external_api; +use core_external\external_function_parameters; +use core_external\external_value; +use moodle_exception; +use coding_exception; +use context_course; +use core_courseformat\base as course_format; + +/** + * External service to create a new module instance in the course. + * + * @package core_courseformat + * @copyright 2024 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class new_module extends external_api { + + /** + * Webservice parameters. + * + * @return external_function_parameters + */ + public static function execute_parameters(): external_function_parameters { + return new external_function_parameters( + [ + 'courseid' => new external_value(PARAM_INT, 'course id', VALUE_REQUIRED), + 'modname' => new external_value(PARAM_ALPHANUMEXT, 'module name', VALUE_REQUIRED), + 'targetsectionid' => new external_value(PARAM_INT, 'target section id', VALUE_REQUIRED, null), + 'targetcmid' => new external_value(PARAM_INT, 'Optional target cm id', VALUE_DEFAULT, null), + ] + ); + } + + /** + * This webservice will execute the create_module action from the course editor. + * + * The action will register in a {@see \core_courseformat\stateupdates} all the affected + * sections, cms and course attribute. This object (in JSON) will be sent back to the + * frontend editor to refresh the updated state elements. + * + * By default, {@see \core_courseformat\stateupdates} will register only create, delete and update events + * on cms, sections and the general course data. However, if some plugin needs adhoc messages for + * its own mutation module, extend this class in format_XXX\course. + * + * @param int $courseid the course id + * @param string $modname the module name + * @param int $targetsectionid the target section id + * @param int|null $targetcmid optional target cm id + * @return string Course state in JSON + */ + public static function execute( + int $courseid, + string $modname, + int $targetsectionid, + ?int $targetcmid = null + ): string { + global $CFG; + + require_once($CFG->dirroot . '/course/lib.php'); + + [ + 'courseid' => $courseid, + 'modname' => $modname, + 'targetsectionid' => $targetsectionid, + 'targetcmid' => $targetcmid, + ] = self::validate_parameters(self::execute_parameters(), [ + 'courseid' => $courseid, + 'modname' => $modname, + 'targetsectionid' => $targetsectionid, + 'targetcmid' => $targetcmid, + ]); + + self::validate_context(context_course::instance($courseid)); + + // Plugin needs to support quick creation and the course format needs to support components. + // Formats using YUI modules should not be able to quick-create because the front end cannot react to the change. + if (!plugin_supports('mod', $modname, FEATURE_QUICKCREATE) || !course_get_format($courseid)->supports_components()) { + throw new moodle_exception("Module $modname does not support quick creation"); + } + + $courseformat = course_get_format($courseid); + + // Create a course changes tracker object. + $defaultupdatesclass = 'core_courseformat\\stateupdates'; + $updatesclass = 'format_' . $courseformat->get_format() . '\\courseformat\\stateupdates'; + if (!class_exists($updatesclass)) { + $updatesclass = $defaultupdatesclass; + } + $updates = new $updatesclass($courseformat); + + if (!is_a($updates, $defaultupdatesclass)) { + throw new coding_exception("The \"$updatesclass\" class must extend \"$defaultupdatesclass\""); + } + + // Get the actions class from the course format. + $actionsclass = 'format_'. $courseformat->get_format().'\\courseformat\\stateactions'; + if (!class_exists($actionsclass)) { + $actionsclass = 'core_courseformat\\stateactions'; + } + /** @var \core_courseformat\stateactions $actions */ + $actions = new $actionsclass(); + + $action = 'new_module'; + if (!is_callable([$actions, $action])) { + throw new moodle_exception("Invalid course state action $action in ".get_class($actions)); + } + + $course = $courseformat->get_course(); + + // Execute the action. + $actions->$action($updates, $course, $modname, $targetsectionid, $targetcmid); + + // Dispatch the hook for post course content update. + $hook = new \core_courseformat\hook\after_course_content_updated( + course: $course + ); + \core\di::get(\core\hook\manager::class)->dispatch($hook); + + return json_encode($updates); + } + + /** + * Webservice returns. + * + * @return external_value + */ + public static function execute_returns(): external_value { + return new external_value(PARAM_RAW, 'Encoded course update JSON'); + } +} diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php index 92e23bdefb0..2c287552575 100644 --- a/course/format/classes/stateactions.php +++ b/course/format/classes/stateactions.php @@ -1135,12 +1135,19 @@ class stateactions { /** * Create a course module. * + * @deprecated since Moodle 5.0, use new_module instead. + * @todo MDL-83851: final deprecation of this method in Moodle 6.0. * @param stateupdates $updates the affected course elements track * @param stdClass $course the course object * @param string $modname the module name * @param int $targetsectionnum target section number * @param int|null $targetcmid optional target cm id */ + #[\core\attribute\deprecated( + replacement: 'new_module', + since: '5.0', + mdl: 'MDL-83469', + )] public function create_module( stateupdates $updates, stdClass $course, @@ -1151,6 +1158,8 @@ class stateactions { global $CFG; require_once($CFG->dirroot . '/course/modlib.php'); + \core\deprecation::emit_deprecation_if_present([self::class, __FUNCTION__]); + $coursecontext = context_course::instance($course->id); require_capability('moodle/course:update', $coursecontext); @@ -1162,4 +1171,38 @@ class stateactions { // Adding module affects section structure, and if the module has a delegated section even the course structure. $this->course_state($updates, $course); } + + /** + * Create a new course module. + * + * @param stateupdates $updates the affected course elements track + * @param stdClass $course the course object + * @param string $modname the module name + * @param int $targetsectionid target section id + * @param int|null $targetcmid optional target cm id + */ + public function new_module( + stateupdates $updates, + stdClass $course, + string $modname, + int $targetsectionid, + ?int $targetcmid = null + ): void { + global $CFG; + require_once($CFG->dirroot . '/course/modlib.php'); + + $coursecontext = context_course::instance($course->id); + require_capability('moodle/course:update', $coursecontext); + + $modinfo = get_fast_modinfo($course); + $section = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST); + + // Method "can_add_moduleinfo" called in "prepare_new_moduleinfo_data" will handle the capability checks. + [, , , , $moduleinfo] = prepare_new_moduleinfo_data($course, $modname, $section->sectionnum); + $moduleinfo->beforemod = $targetcmid; + create_module((object) $moduleinfo); + + // Adding module affects section structure, and if the module has a delegated section even the course structure. + $this->course_state($updates, $course); + } } diff --git a/course/format/tests/external/create_module_test.php b/course/format/tests/external/create_module_test.php index 539efe2dc0d..33b69784ef6 100644 --- a/course/format/tests/external/create_module_test.php +++ b/course/format/tests/external/create_module_test.php @@ -69,6 +69,7 @@ final class create_module_test extends \externallib_advanced_testcase { // Execute course action. $results = json_decode(create_module::execute((int)$course->id, $modname, (int)$targetsection->id, (int)$activity->id)); + $this->assertDebuggingCalled(); // Check result. $cmupdate = $this->find_update_by_fieldname($results, 'put', 'cm', get_string('quickcreatename', 'mod_' . $modname)); @@ -122,6 +123,7 @@ final class create_module_test extends \externallib_advanced_testcase { // Execute course action. $modname = 'book'; create_module::execute((int)$course->id, $modname, (int)$targetsection->id); + $this->assertDebuggingCalled(); } /** diff --git a/course/format/tests/external/new_module_test.php b/course/format/tests/external/new_module_test.php new file mode 100644 index 00000000000..d7adafbf588 --- /dev/null +++ b/course/format/tests/external/new_module_test.php @@ -0,0 +1,164 @@ +. + +declare(strict_types=1); + +namespace core_courseformat\external; + +defined('MOODLE_INTERNAL') || die(); + +use moodle_exception; +use stdClass; + +global $CFG; +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); + +/** + * Tests for the new_module class. + * + * @package core_courseformat + * @category test + * @copyright 2024 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core_courseformat\external\new_module + */ +final class new_module_test extends \externallib_advanced_testcase { + + /** + * Setup to ensure that fixtures are loaded. + */ + public static function setupBeforeClass(): void { // phpcs:ignore + global $CFG; + + require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest.php'); + require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest_output_course_format_state.php'); + require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest_stateactions.php'); + } + + /** + * Test the webservice can execute the new_module action. + * + * @covers ::execute + */ + public function test_execute(): void { + $this->resetAfterTest(); + + $modname = 'subsection'; + $manager = \core_plugin_manager::resolve_plugininfo_class('mod'); + $manager::enable_plugin($modname, 1); + + // Create a course with an activity. + $course = $this->getDataGenerator()->create_course(['numsections' => 1]); + $activity = $this->getDataGenerator()->create_module('book', ['course' => $course->id]); + $targetsection = get_fast_modinfo($course->id)->get_section_info(1); + + $this->setAdminUser(); + + // Execute course action. + $results = json_decode( + new_module::execute((int)$course->id, $modname, (int)$targetsection->id, (int)$activity->id), + ); + + // Check result. + $cmupdate = $this->find_update_by_fieldname($results, 'put', 'cm', get_string('quickcreatename', 'mod_' . $modname)); + $this->assertNotEmpty($cmupdate); + $this->assertEquals($modname, $cmupdate->fields->module); + $this->assertEquals($targetsection->id, $cmupdate->fields->sectionid); + $this->assertEquals(1, $cmupdate->fields->sectionnumber); + } + + /** + * Test the webservice can execute the new_module action with a format override. + * + * @covers ::execute + */ + public function test_execute_with_format_override(): void { + $this->resetAfterTest(); + + $manager = \core_plugin_manager::resolve_plugininfo_class('mod'); + $manager::enable_plugin('subsection', 1); + + // Create a course. + $course = $this->getDataGenerator()->create_course(['format' => 'theunittest', 'numsections' => 1, 'initsections' => 1]); + $targetsection = get_fast_modinfo($course->id)->get_section_info(1); + + $this->setAdminUser(); + + // Execute course action. + $modname = 'subsection'; + $results = json_decode( + new_module::execute((int)$course->id, $modname, (int)$targetsection->id), + ); + + // Some course formats doesn't have the renderer file, so a debugging message will be displayed. + $this->assertDebuggingCalled(); + + // Check result. + $this->assertEmpty($results); + } + + /** + * Test the webservice can execute the new_module action with an invalid module. + * + * @covers ::execute + */ + public function test_execute_with_invalid_module(): void { + $this->resetAfterTest(); + + // Create a course. + $course = $this->getDataGenerator()->create_course(['numsections' => 1]); + $targetsection = get_fast_modinfo($course->id)->get_section_info(1); + + $this->setAdminUser(); + + // Expect exception. Book module doesn't support quickcreate feature. + $this->expectException(moodle_exception::class); + + // Execute course action. + $modname = 'book'; + new_module::execute((int)$course->id, $modname, (int)$targetsection->id); + } + + /** + * Helper methods to find a specific update in the updadelist. + * + * @param array $updatelist the update list + * @param string $action the action to find + * @param string $name the element name to find + * @param string $fieldname the element identifiername + * @return stdClass|null the object found, if any. + */ + private function find_update_by_fieldname( + array $updatelist, + string $action, + string $name, + string $fieldname, + + ): ?stdClass { + foreach ($updatelist as $update) { + if ($update->action != $action || $update->name != $name) { + continue; + } + if (!isset($update->fields->name)) { + continue; + } + if ($update->fields->name == $fieldname) { + return $update; + } + } + return null; + } +} diff --git a/course/format/tests/fixtures/format_theunittest_stateactions.php b/course/format/tests/fixtures/format_theunittest_stateactions.php index bdc76e5ef3a..36517875d0c 100644 --- a/course/format/tests/fixtures/format_theunittest_stateactions.php +++ b/course/format/tests/fixtures/format_theunittest_stateactions.php @@ -54,10 +54,28 @@ class stateactions extends core_actions { * @param stateupdates $updates the affected course elements track * @param stdClass $course the course object * @param string $modname the module name - * @param int $targetsectionid target section id + * @param int $targetsectionnum target section number * @param int|null $targetcmid optional target cm id */ public function create_module( + stateupdates $updates, + stdClass $course, + string $modname, + int $targetsectionnum, + ?int $targetcmid = null + ): void { + } + + /** + * Alternative new_module state action for testing. + * + * @param stateupdates $updates the affected course elements track + * @param stdClass $course the course object + * @param string $modname the module name + * @param int $targetsectionid target section id + * @param int|null $targetcmid optional target cm id + */ + public function new_module( stateupdates $updates, stdClass $course, string $modname, diff --git a/course/format/tests/stateactions_test.php b/course/format/tests/stateactions_test.php index 7c8e25376e6..0db324c6d29 100644 --- a/course/format/tests/stateactions_test.php +++ b/course/format/tests/stateactions_test.php @@ -1740,8 +1740,6 @@ final class stateactions_test extends \advanced_testcase { // Create a course with 1 section and 1 student. $course = $this->getDataGenerator()->create_course(['numsections' => 1]); - $student = $this->getDataGenerator()->create_user(); - $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); $courseformat = course_get_format($course->id); $targetsection = $courseformat->get_modinfo()->get_section_info(1); @@ -1762,13 +1760,47 @@ final class stateactions_test extends \advanced_testcase { $this->assertEquals($modname, $cmupdate->module); $this->assertEquals($targetsection->id, $cmupdate->sectionid); $this->assertEquals(get_string('quickcreatename', 'mod_' . $modname), $cmupdate->name); + $this->assertDebuggingCalled(); + } + + /** + * Test for create_module public method with no capabilities. + * + * @covers ::create_module + */ + public function test_create_module_no_capabilities(): void { + $this->resetAfterTest(); + + $modname = 'subsection'; + + // Create a course with 1 section and 1 student. + $course = $this->getDataGenerator()->create_course(['numsections' => 1]); + $student = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); + $courseformat = course_get_format($course->id); + $targetsection = $courseformat->get_modinfo()->get_section_info(1); + + $this->setAdminUser(); + + // Sanity check. + $this->assertEmpty($courseformat->get_modinfo()->get_cms()); // Change to a user without permission. $this->setUser($student); // Validate that the method throws an exception. - $this->expectException(moodle_exception::class); - $actions->create_module($updates, $course, $modname, $targetsection->sectionnum); + $actions = new stateactions(); + $updates = new stateupdates($courseformat); + + // Capturing exceptions on deprecated methods is tricky because expectException is executed + // before assertDebuggingCalled. We need to use try catch in a creative way. + try { + $actions->create_module($updates, $course, $modname, $targetsection->sectionnum); + } catch (moodle_exception $exception) { + $this->assertDebuggingCalled(); + $this->expectException(moodle_exception::class); + throw $exception; + } } /** @@ -1812,6 +1844,122 @@ final class stateactions_test extends \advanced_testcase { $this->assertEquals($targetsection->id, $cmupdate->sectionid); $this->assertEquals(get_string('quickcreatename', 'mod_' . $modname), $cmupdate->name); + // Validate that the new module was created between both modules. + $this->assertCount(3, $cms); + $this->assertArrayHasKey($cmupdate->id, $cms); + $this->assertEquals( + implode(',', [$forum->cmid, $cmupdate->id, $page->cmid]), + $modinfo->get_section_info(1)->sequence + ); + $this->assertDebuggingCalled(); + } + + /** + * Test for new_module public method. + * + * @covers ::new_module + */ + public function test_new_module(): void { + $this->resetAfterTest(); + + $modname = 'subsection'; + + // Create a course with 1 section and 1 student. + $course = $this->getDataGenerator()->create_course(['numsections' => 1]); + $courseformat = course_get_format($course->id); + $targetsection = $courseformat->get_modinfo()->get_section_info(1); + + $this->setAdminUser(); + + // Sanity check. + $this->assertEmpty($courseformat->get_modinfo()->get_cms()); + + // Execute given method. + $actions = new stateactions(); + $updates = new stateupdates($courseformat); + $actions->new_module($updates, $course, $modname, $targetsection->id); + + // Validate cm was created and updates were generated. + $results = $this->summarize_updates($updates); + $cmupdate = reset($results['put']['cm']); + $this->assertCount(1, $courseformat->get_modinfo()->get_cms()); + $this->assertEquals($modname, $cmupdate->module); + $this->assertEquals($targetsection->id, $cmupdate->sectionid); + $this->assertEquals(get_string('quickcreatename', 'mod_' . $modname), $cmupdate->name); + } + + /** + * Test for new_module public method with no capabilities. + * + * @covers ::new_module + */ + public function test_new_module_no_capabilities(): void { + $this->resetAfterTest(); + + $modname = 'subsection'; + + // Create a course with 1 section and 1 student. + $course = $this->getDataGenerator()->create_course(['numsections' => 1]); + $student = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); + $courseformat = course_get_format($course->id); + $targetsection = $courseformat->get_modinfo()->get_section_info(1); + + $this->setAdminUser(); + + // Sanity check. + $this->assertEmpty($courseformat->get_modinfo()->get_cms()); + + // Change to a user without permission. + $this->setUser($student); + + // Validate that the method throws an exception. + $actions = new stateactions(); + $updates = new stateupdates($courseformat); + + $this->expectException(moodle_exception::class); + $actions->new_module($updates, $course, $modname, $targetsection->id); + } + + /** + * Test for new_module public method with targetcmid parameter. + * + * @covers ::new_module + */ + public function test_new_module_with_targetcmid(): void { + $this->resetAfterTest(); + + $modname = 'subsection'; + + // Create a course with 1 section, 2 modules (forum and page) and 1 student. + $course = $this->getDataGenerator()->create_course(['numsections' => 1]); + $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course], ['section' => 1]); + $page = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 1]); + $student = $this->getDataGenerator()->create_user(); + $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student'); + $courseformat = course_get_format($course->id); + $targetsection = $courseformat->get_modinfo()->get_section_info(1); + + $this->setAdminUser(); + + // Sanity check. + $this->assertCount(2, $courseformat->get_modinfo()->get_cms()); + + // Execute given method. + $actions = new stateactions(); + $updates = new stateupdates($courseformat); + $actions->new_module($updates, $course, $modname, $targetsection->id, $page->cmid); + + $modinfo = $courseformat->get_modinfo(); + $cms = $modinfo->get_cms(); + $results = $this->summarize_updates($updates); + $cmupdate = reset($results['put']['cm']); + + // Validate updates were generated. + $this->assertEquals($modname, $cmupdate->module); + $this->assertEquals($targetsection->id, $cmupdate->sectionid); + $this->assertEquals(get_string('quickcreatename', 'mod_' . $modname), $cmupdate->name); + // Validate that the new module was created between both modules. $this->assertCount(3, $cms); $this->assertArrayHasKey($cmupdate->id, $cms); diff --git a/lib/db/services.php b/lib/db/services.php index ca5b4c601f8..c29cf1098c2 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -593,6 +593,14 @@ $functions = array( 'type' => 'read', 'ajax' => true, ], + 'core_courseformat_new_module' => [ + 'classname' => 'core_courseformat\external\new_module', + 'methodname' => 'execute', + 'description' => 'Create a new module to course.', + 'type' => 'write', + 'ajax' => true, + 'capabilities' => 'moodle/course:manageactivities', + ], 'core_courseformat_update_course' => [ 'classname' => 'core_courseformat\external\update_course', 'methodname' => 'execute', @@ -604,7 +612,9 @@ $functions = array( 'core_courseformat_create_module' => [ 'classname' => 'core_courseformat\external\create_module', 'methodname' => 'execute', - 'description' => 'Add module to course.', + 'description' => '** DEPRECATED ** Please do not call this function any more. ' . + 'Use core_courseformat_new_module instead. ' . + 'Add module to course.', 'type' => 'write', 'ajax' => true, 'capabilities' => 'moodle/course:manageactivities', diff --git a/mod/subsection/classes/local/callbacks/before_activitychooserbutton_exported_handler.php b/mod/subsection/classes/local/callbacks/before_activitychooserbutton_exported_handler.php index a74efe2af8e..8b73e780366 100644 --- a/mod/subsection/classes/local/callbacks/before_activitychooserbutton_exported_handler.php +++ b/mod/subsection/classes/local/callbacks/before_activitychooserbutton_exported_handler.php @@ -46,9 +46,10 @@ class before_activitychooserbutton_exported_handler { $attributes = [ 'class' => 'dropdown-item', - 'data-action' => 'addModule', + 'data-action' => 'newModule', 'data-modname' => 'subsection', 'data-sectionnum' => $section->sectionnum, + 'data-sectionid' => $section->id, ]; if ($hook->get_cm()) { $attributes['data-beforemod'] = $hook->get_cm()->id; diff --git a/mod/subsection/tests/behat/subsection_coursepage.feature b/mod/subsection/tests/behat/subsection_coursepage.feature index 1c2af2c0686..3cc3d9a4a9c 100644 --- a/mod/subsection/tests/behat/subsection_coursepage.feature +++ b/mod/subsection/tests/behat/subsection_coursepage.feature @@ -109,3 +109,15 @@ Feature: Users view subsections on course page Then I should see "New forum" in the "Section 3" "section" And "New database" "text" should appear before "New forum" "text" And "New forum" "text" should appear before "New page" "text" + + @javascript + Scenario: Teacher can subsections after moving the parent section + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I open section "1" edit menu + And I click on "Move" "link" in the "Section 1" "section" + And I click on "Section 3" "link" in the "Move section" "dialogue" + And "Section 1" "section" should appear after "Section 3" "section" + When I click on "Add content" "button" in the "Section 1" "section" + And I click on "Subsection" "link" in the ".dropdown-menu.show" "css_element" + Then I should see "New subsection" in the "Section 1" "section"