From 63a7c58ee345090ef7ec6d7b8939b1c0e9ed39c1 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Thu, 17 Nov 2022 12:02:08 +0100 Subject: [PATCH] MDL-76188 core_courseformat: migrate highlight to reactive component The highlight and unhighlight sections applies only to the format_topics plugins. With this commit all the related code is migrated to reactive component and format_topics extend the current course editor to add all the logic --- course/amd/build/actions.min.js | 2 +- course/amd/build/actions.min.js.map | 2 +- course/amd/src/actions.js | 2 +- .../amd/build/local/content/section.min.js | 2 +- .../build/local/content/section.min.js.map | 2 +- .../format/amd/src/local/content/section.js | 2 + course/format/classes/base.php | 9 + .../format/topics/amd/build/mutations.min.js | 3 + .../topics/amd/build/mutations.min.js.map | 1 + course/format/topics/amd/build/section.min.js | 10 + .../topics/amd/build/section.min.js.map | 1 + course/format/topics/amd/src/mutations.js | 80 +++++++ course/format/topics/amd/src/section.js | 115 +++++++++ .../classes/courseformat/stateactions.php | 117 +++++++++ .../classes/output/courseformat/content.php | 14 ++ .../content/section/controlmenu.php | 114 +++++---- .../tests/behat/highlight_sections.feature | 49 ++++ .../tests/courseformat/stateactions_test.php | 222 ++++++++++++++++++ course/format/upgrade.txt | 3 + 19 files changed, 704 insertions(+), 46 deletions(-) create mode 100644 course/format/topics/amd/build/mutations.min.js create mode 100644 course/format/topics/amd/build/mutations.min.js.map create mode 100644 course/format/topics/amd/build/section.min.js create mode 100644 course/format/topics/amd/build/section.min.js.map create mode 100644 course/format/topics/amd/src/mutations.js create mode 100644 course/format/topics/amd/src/section.js create mode 100644 course/format/topics/classes/courseformat/stateactions.php create mode 100644 course/format/topics/tests/behat/highlight_sections.feature create mode 100644 course/format/topics/tests/courseformat/stateactions_test.php diff --git a/course/amd/build/actions.min.js b/course/amd/build/actions.min.js index 8d542003cae..e0a6cce9ed1 100644 --- a/course/amd/build/actions.min.js +++ b/course/amd/build/actions.min.js @@ -6,6 +6,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_factory","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core_course/events"],(function($,ajax,templates,notification,str,url,Y,ModalFactory,ModalEvents,KeyCodes,log,editor,EventDispatcher,CourseEvents){const componentActions=["moveSection","moveCm","addSection","deleteSection","sectionHide","sectionShow","cmHide","cmShow","cmStealth"],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",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"):0}}],!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"):0;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){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.SECTIONLI+" "+SELECTOR.SECTIONACTIONMENU+"[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.SECTIONACTIONMENU).attr("data-sectionid");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("updated","".concat(SELECTOR.SECTIONLI," [data-inplaceeditable]"),(function(e){if(e.ajaxreturn&&e.ajaxreturn.itemid){void 0!==courseeditor.state.section.get(e.ajaxreturn.itemid)&&courseeditor.dispatch("sectionState",[e.ajaxreturn.itemid])}})),$("body").on("updated","".concat(SELECTOR.ACTIVITYLI," [data-inplaceeditable]"),(function(e){e.ajaxreturn&&e.ajaxreturn.itemid&&courseeditor.dispatch("cmState",[e.ajaxreturn.itemid])})),courseeditor.supportComponents&&componentActions.includes("addSection")||str.get_string("numberweeks").done((function(strNumberSections){var trigger=$(SELECTOR.ADDSECTIONS),modalTitle=trigger.attr("data-add-sections"),newSections=trigger.attr("data-new-sections"),modalBody=$('
');modalBody.find("label").html(strNumberSections),ModalFactory.create({title:modalTitle,type:ModalFactory.types.SAVE_CANCEL,body:modalBody.html()},trigger).done((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()))};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()}))}))}))},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_factory","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core_course/events"],(function($,ajax,templates,notification,str,url,Y,ModalFactory,ModalEvents,KeyCodes,log,editor,EventDispatcher,CourseEvents){const componentActions=["moveSection","moveCm","addSection","deleteSection","sectionHide","sectionShow","cmHide","cmShow","cmStealth","sectionHighlight","sectionUnhighlight"],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",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"):0}}],!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"):0;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){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.SECTIONLI+" "+SELECTOR.SECTIONACTIONMENU+"[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.SECTIONACTIONMENU).attr("data-sectionid");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("updated","".concat(SELECTOR.SECTIONLI," [data-inplaceeditable]"),(function(e){if(e.ajaxreturn&&e.ajaxreturn.itemid){void 0!==courseeditor.state.section.get(e.ajaxreturn.itemid)&&courseeditor.dispatch("sectionState",[e.ajaxreturn.itemid])}})),$("body").on("updated","".concat(SELECTOR.ACTIVITYLI," [data-inplaceeditable]"),(function(e){e.ajaxreturn&&e.ajaxreturn.itemid&&courseeditor.dispatch("cmState",[e.ajaxreturn.itemid])})),courseeditor.supportComponents&&componentActions.includes("addSection")||str.get_string("numberweeks").done((function(strNumberSections){var trigger=$(SELECTOR.ADDSECTIONS),modalTitle=trigger.attr("data-add-sections"),newSections=trigger.attr("data-new-sections"),modalBody=$('
');modalBody.find("label").html(strNumberSections),ModalFactory.create({title:modalTitle,type:ModalFactory.types.SAVE_CANCEL,body:modalBody.html()},trigger).done((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()))};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()}))}))}))},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 9a3d91c70d8..f78ad7ec634 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 * @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_factory',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalFactory,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n CourseEvents\n ) {\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', 'sectionHide', 'sectionShow',\n 'cmHide', 'cmShow', 'cmStealth',\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 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') : 0\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') : 0;\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 show/hide actions.\n $('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +\n SELECTOR.SECTIONACTIONMENU + '[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.SECTIONACTIONMENU).attr('data-sectionid');\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('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.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 str.get_string('numberweeks').done(function(strNumberSections) {\n var trigger = $(SELECTOR.ADDSECTIONS),\n modalTitle = trigger.attr('data-add-sections'),\n newSections = trigger.attr('data-new-sections');\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n ModalFactory.create({\n title: modalTitle,\n type: ModalFactory.types.SAVE_CANCEL,\n body: modalBody.html()\n }, trigger)\n .done(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 });\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","ModalFactory","ModalEvents","KeyCodes","log","editor","EventDispatcher","CourseEvents","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","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","isExecuted","message","itemid","strNumberSections","modalTitle","newSections","modalBody","create","title","types","SAVE_CANCEL","body","modal","numSections","getBody","addSections","parseInt","val","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug","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 * @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_factory',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalFactory,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n CourseEvents\n ) {\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', 'sectionHide', 'sectionShow',\n 'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight',\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 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') : 0\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') : 0;\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 show/hide actions.\n $('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +\n SELECTOR.SECTIONACTIONMENU + '[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.SECTIONACTIONMENU).attr('data-sectionid');\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('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.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 str.get_string('numberweeks').done(function(strNumberSections) {\n var trigger = $(SELECTOR.ADDSECTIONS),\n modalTitle = trigger.attr('data-add-sections'),\n newSections = trigger.attr('data-new-sections');\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n ModalFactory.create({\n title: modalTitle,\n type: ModalFactory.types.SAVE_CANCEL,\n body: modalBody.html()\n }, trigger)\n .done(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 });\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","ModalFactory","ModalEvents","KeyCodes","log","editor","EventDispatcher","CourseEvents","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","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","isExecuted","message","itemid","strNumberSections","modalTitle","newSections","modalBody","create","title","types","SAVE_CANCEL","body","modal","numSections","getBody","addSections","parseInt","val","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug","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 cdb24431cd7..bff9ae5e945 100644 --- a/course/amd/src/actions.js +++ b/course/amd/src/actions.js @@ -60,7 +60,7 @@ define( // Meanwhile, we filter the migrated actions. const componentActions = [ 'moveSection', 'moveCm', 'addSection', 'deleteSection', 'sectionHide', 'sectionShow', - 'cmHide', 'cmShow', 'cmStealth', + 'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight', ]; // The course reactive instance. diff --git a/course/format/amd/build/local/content/section.min.js b/course/format/amd/build/local/content/section.min.js index 352127462a0..ccf772993af 100644 --- a/course/format/amd/build/local/content/section.min.js +++ b/course/format/amd/build/local/content/section.min.js @@ -6,6 +6,6 @@ define("core_courseformat/local/content/section",["exports","core_courseformat/l * @class core_courseformat/local/content/section * @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,_header=_interopRequireDefault(_header),_dndsection=_interopRequireDefault(_dndsection),_templates=_interopRequireDefault(_templates);class _default extends _dndsection.default{create(){this.name="content_section",this.selectors={SECTION_ITEM:"[data-for='section_title']",CM:'[data-for="cmitem"]',SECTIONINFO:'[data-for="sectioninfo"]',SECTIONBADGES:'[data-region="sectionbadges"]',SHOWSECTION:'[data-action="sectionShow"]',HIDESECTION:'[data-action="sectionHide"]',ACTIONTEXT:".menu-action-text",ICON:".icon"},this.classes={LOCKED:"editinprogress",HASDESCRIPTION:"description",HIDE:"d-none",HIDDEN:"hidden"},this.id=this.element.dataset.id}stateReady(state){if(this.configState(state),this.reactive.isEditing&&this.reactive.supportComponents){const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(sectionItem){const headerComponent=new _header.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(headerComponent)}}}getWatchers(){return[{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection}]}validateDropData(dropdata){return("section"!==(null==dropdata?void 0:dropdata.type)||0==this.reactive.sectionReturn)&&super.validateDropData(dropdata)}getLastCm(){const cms=this.getElements(this.selectors.CM);return cms&&0!==cms.length?cms[cms.length-1]:null}_refreshSection(_ref){var _element$dragging,_element$locked,_element$visible;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.HIDDEN,null!==(_element$visible=!element.visible)&&void 0!==_element$visible&&_element$visible),this.locked=element.locked;const sectioninfo=this.getElement(this.selectors.SECTIONINFO);sectioninfo&§ioninfo.classList.toggle(this.classes.HASDESCRIPTION,element.hasrestrictions),this._updateBadges(element),this._updateActionsMenu(element)}_updateBadges(section){const current=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='iscurrent']"));null==current||current.classList.toggle(this.classes.HIDE,!section.current);const hiddenFromStudents=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='hiddenfromstudents']"));null==hiddenFromStudents||hiddenFromStudents.classList.toggle(this.classes.HIDE,section.visible)}async _updateActionsMenu(section){var _affectedAction$datas,_affectedAction$datas2;let selector,newAction;section.visible?(selector=this.selectors.SHOWSECTION,newAction="sectionHide"):(selector=this.selectors.HIDESECTION,newAction="sectionShow");const affectedAction=this.getElement(selector);if(!affectedAction)return;affectedAction.dataset.action=newAction;const actionText=affectedAction.querySelector(this.selectors.ACTIONTEXT);if(null!==(_affectedAction$datas=affectedAction.dataset)&&void 0!==_affectedAction$datas&&_affectedAction$datas.swapname&&actionText){const oldText=null==actionText?void 0:actionText.innerText;actionText.innerText=affectedAction.dataset.swapname,affectedAction.dataset.swapname=oldText}const icon=affectedAction.querySelector(this.selectors.ICON);if(null!==(_affectedAction$datas2=affectedAction.dataset)&&void 0!==_affectedAction$datas2&&_affectedAction$datas2.swapicon&&icon){const newIcon=affectedAction.dataset.swapicon;if(newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_header=_interopRequireDefault(_header),_dndsection=_interopRequireDefault(_dndsection),_templates=_interopRequireDefault(_templates);class _default extends _dndsection.default{create(){this.name="content_section",this.selectors={SECTION_ITEM:"[data-for='section_title']",CM:'[data-for="cmitem"]',SECTIONINFO:'[data-for="sectioninfo"]',SECTIONBADGES:'[data-region="sectionbadges"]',SHOWSECTION:'[data-action="sectionShow"]',HIDESECTION:'[data-action="sectionHide"]',ACTIONTEXT:".menu-action-text",ICON:".icon"},this.classes={LOCKED:"editinprogress",HASDESCRIPTION:"description",HIDE:"d-none",HIDDEN:"hidden",CURRENT:"current"},this.id=this.element.dataset.id}stateReady(state){if(this.configState(state),this.reactive.isEditing&&this.reactive.supportComponents){const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(sectionItem){const headerComponent=new _header.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(headerComponent)}}}getWatchers(){return[{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection}]}validateDropData(dropdata){return("section"!==(null==dropdata?void 0:dropdata.type)||0==this.reactive.sectionReturn)&&super.validateDropData(dropdata)}getLastCm(){const cms=this.getElements(this.selectors.CM);return cms&&0!==cms.length?cms[cms.length-1]:null}_refreshSection(_ref){var _element$dragging,_element$locked,_element$visible,_element$current;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.HIDDEN,null!==(_element$visible=!element.visible)&&void 0!==_element$visible&&_element$visible),this.element.classList.toggle(this.classes.CURRENT,null!==(_element$current=element.current)&&void 0!==_element$current&&_element$current),this.locked=element.locked;const sectioninfo=this.getElement(this.selectors.SECTIONINFO);sectioninfo&§ioninfo.classList.toggle(this.classes.HASDESCRIPTION,element.hasrestrictions),this._updateBadges(element),this._updateActionsMenu(element)}_updateBadges(section){const current=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='iscurrent']"));null==current||current.classList.toggle(this.classes.HIDE,!section.current);const hiddenFromStudents=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='hiddenfromstudents']"));null==hiddenFromStudents||hiddenFromStudents.classList.toggle(this.classes.HIDE,section.visible)}async _updateActionsMenu(section){var _affectedAction$datas,_affectedAction$datas2;let selector,newAction;section.visible?(selector=this.selectors.SHOWSECTION,newAction="sectionHide"):(selector=this.selectors.HIDESECTION,newAction="sectionShow");const affectedAction=this.getElement(selector);if(!affectedAction)return;affectedAction.dataset.action=newAction;const actionText=affectedAction.querySelector(this.selectors.ACTIONTEXT);if(null!==(_affectedAction$datas=affectedAction.dataset)&&void 0!==_affectedAction$datas&&_affectedAction$datas.swapname&&actionText){const oldText=null==actionText?void 0:actionText.innerText;actionText.innerText=affectedAction.dataset.swapname,affectedAction.dataset.swapname=oldText}const icon=affectedAction.querySelector(this.selectors.ICON);if(null!==(_affectedAction$datas2=affectedAction.dataset)&&void 0!==_affectedAction$datas2&&_affectedAction$datas2.swapicon&&icon){const newIcon=affectedAction.dataset.swapicon;if(newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=section.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/section.min.js.map b/course/format/amd/build/local/content/section.min.js.map index acb7a258ed3..99b9d3cd7c0 100644 --- a/course/format/amd/build/local/content/section.min.js.map +++ b/course/format/amd/build/local/content/section.min.js.map @@ -1 +1 @@ -{"version":3,"file":"section.min.js","sources":["../../../src/local/content/section.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 section format component.\n *\n * @module core_courseformat/local/content/section\n * @class core_courseformat/local/content/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'core_courseformat/local/content/section/header';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\nimport Templates from 'core/templates';\n\nexport default class extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_title']`,\n CM: `[data-for=\"cmitem\"]`,\n SECTIONINFO: `[data-for=\"sectioninfo\"]`,\n SECTIONBADGES: `[data-region=\"sectionbadges\"]`,\n SHOWSECTION: `[data-action=\"sectionShow\"]`,\n HIDESECTION: `[data-action=\"sectionHide\"]`,\n ACTIONTEXT: `.menu-action-text`,\n ICON: `.icon`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n HASDESCRIPTION: 'description',\n HIDE: 'd-none',\n HIDDEN: 'hidden',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // If the format uses one section per page sections dropping in the content is ignored.\n if (dropdata?.type === 'section' && this.reactive.sectionReturn != 0) {\n return false;\n }\n return super.validateDropData(dropdata);\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a content section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.HIDDEN, !element.visible ?? false);\n this.locked = element.locked;\n // The description box classes depends on the section state.\n const sectioninfo = this.getElement(this.selectors.SECTIONINFO);\n if (sectioninfo) {\n sectioninfo.classList.toggle(this.classes.HASDESCRIPTION, element.hasrestrictions);\n }\n // Update section badges and menus.\n this._updateBadges(element);\n this._updateActionsMenu(element);\n }\n\n /**\n * Update a section badges using the state information.\n *\n * @param {object} section the section state.\n */\n _updateBadges(section) {\n const current = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='iscurrent']`);\n current?.classList.toggle(this.classes.HIDE, !section.current);\n\n const hiddenFromStudents = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='hiddenfromstudents']`);\n hiddenFromStudents?.classList.toggle(this.classes.HIDE, section.visible);\n }\n\n /**\n * Update a section action menus.\n *\n * @param {object} section the section state.\n */\n async _updateActionsMenu(section) {\n let selector;\n let newAction;\n if (section.visible) {\n selector = this.selectors.SHOWSECTION;\n newAction = 'sectionHide';\n } else {\n selector = this.selectors.HIDESECTION;\n newAction = 'sectionShow';\n }\n // Find the affected action.\n const affectedAction = this.getElement(selector);\n if (!affectedAction) {\n return;\n }\n // Change action.\n affectedAction.dataset.action = newAction;\n // Change text.\n const actionText = affectedAction.querySelector(this.selectors.ACTIONTEXT);\n if (affectedAction.dataset?.swapname && actionText) {\n const oldText = actionText?.innerText;\n actionText.innerText = affectedAction.dataset.swapname;\n affectedAction.dataset.swapname = oldText;\n }\n // Change icon.\n const icon = affectedAction.querySelector(this.selectors.ICON);\n if (affectedAction.dataset?.swapicon && icon) {\n const newIcon = affectedAction.dataset.swapicon;\n if (newIcon) {\n const pixHtml = await Templates.renderPix(newIcon, 'core');\n Templates.replaceNode(icon, pixHtml, '');\n }\n }\n }\n}\n"],"names":["DndSection","create","name","selectors","SECTION_ITEM","CM","SECTIONINFO","SECTIONBADGES","SHOWSECTION","HIDESECTION","ACTIONTEXT","ICON","classes","LOCKED","HASDESCRIPTION","HIDE","HIDDEN","id","this","element","dataset","stateReady","state","configState","reactive","isEditing","supportComponents","sectionItem","getElement","headerComponent","Header","fullregion","configDragDrop","getWatchers","watch","handler","_refreshSection","validateDropData","dropdata","type","sectionReturn","super","getLastCm","cms","getElements","length","classList","toggle","DRAGGING","dragging","locked","visible","sectioninfo","hasrestrictions","_updateBadges","_updateActionsMenu","section","current","hiddenFromStudents","selector","newAction","affectedAction","action","actionText","querySelector","swapname","oldText","innerText","icon","swapicon","newIcon","pixHtml","Templates","renderPix","replaceNode"],"mappings":";;;;;;;;kPA4B6BA,oBAKzBC,cAESC,KAAO,uBAEPC,UAAY,CACbC,0CACAC,yBACAC,uCACAC,8CACAC,0CACAC,0CACAC,+BACAC,mBAGCC,QAAU,CACXC,OAAQ,iBACRC,eAAgB,cAChBC,KAAM,SACNC,OAAQ,eAIPC,GAAKC,KAAKC,QAAQC,QAAQH,GAQnCI,WAAWC,eACFC,YAAYD,OAEbJ,KAAKM,SAASC,WAAaP,KAAKM,SAASE,kBAAmB,OAEtDC,YAAcT,KAAKU,WAAWV,KAAKf,UAAUC,iBAC/CuB,YAAa,OAEPE,gBAAkB,IAAIC,gBAAO,IAC5BZ,KACHC,QAASQ,YACTI,WAAYb,KAAKC,eAEhBa,eAAeH,mBAUhCI,oBACW,CACH,CAACC,wBAAkBhB,KAAKD,gBAAekB,QAASjB,KAAKkB,kBAU7DC,iBAAiBC,iBAES,aAAnBA,MAAAA,gBAAAA,SAAUC,OAAqD,GAA/BrB,KAAKM,SAASgB,gBAG1CC,MAAMJ,iBAAiBC,UAQlCI,kBACUC,IAAMzB,KAAK0B,YAAY1B,KAAKf,UAAUE,WAEvCsC,KAAsB,IAAfA,IAAIE,OAGTF,IAAIA,IAAIE,OAAS,GAFb,KAWfT,iFAAgBjB,QAACA,mBAERA,QAAQ2B,UAAUC,OAAO7B,KAAKN,QAAQoC,mCAAU7B,QAAQ8B,+DACxD9B,QAAQ2B,UAAUC,OAAO7B,KAAKN,QAAQC,+BAAQM,QAAQ+B,yDACtD/B,QAAQ2B,UAAUC,OAAO7B,KAAKN,QAAQI,iCAASG,QAAQgC,4DACvDD,OAAS/B,QAAQ+B,aAEhBE,YAAclC,KAAKU,WAAWV,KAAKf,UAAUG,aAC/C8C,aACAA,YAAYN,UAAUC,OAAO7B,KAAKN,QAAQE,eAAgBK,QAAQkC,sBAGjEC,cAAcnC,cACdoC,mBAAmBpC,SAQ5BmC,cAAcE,eACJC,QAAUvC,KAAKU,qBAAcV,KAAKf,UAAUI,2CAClDkD,MAAAA,SAAAA,QAASX,UAAUC,OAAO7B,KAAKN,QAAQG,MAAOyC,QAAQC,eAEhDC,mBAAqBxC,KAAKU,qBAAcV,KAAKf,UAAUI,oDAC7DmD,MAAAA,oBAAAA,mBAAoBZ,UAAUC,OAAO7B,KAAKN,QAAQG,KAAMyC,QAAQL,kCAQ3CK,8DACjBG,SACAC,UACAJ,QAAQL,SACRQ,SAAWzC,KAAKf,UAAUK,YAC1BoD,UAAY,gBAEZD,SAAWzC,KAAKf,UAAUM,YAC1BmD,UAAY,qBAGVC,eAAiB3C,KAAKU,WAAW+B,cAClCE,sBAILA,eAAezC,QAAQ0C,OAASF,gBAE1BG,WAAaF,eAAeG,cAAc9C,KAAKf,UAAUO,6CAC3DmD,eAAezC,gEAAS6C,UAAYF,WAAY,OAC1CG,QAAUH,MAAAA,kBAAAA,WAAYI,UAC5BJ,WAAWI,UAAYN,eAAezC,QAAQ6C,SAC9CJ,eAAezC,QAAQ6C,SAAWC,cAGhCE,KAAOP,eAAeG,cAAc9C,KAAKf,UAAUQ,wCACrDkD,eAAezC,kEAASiD,UAAYD,KAAM,OACpCE,QAAUT,eAAezC,QAAQiD,YACnCC,QAAS,OACHC,cAAgBC,mBAAUC,UAAUH,QAAS,2BACzCI,YAAYN,KAAMG,QAAS"} \ No newline at end of file +{"version":3,"file":"section.min.js","sources":["../../../src/local/content/section.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 section format component.\n *\n * @module core_courseformat/local/content/section\n * @class core_courseformat/local/content/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Header from 'core_courseformat/local/content/section/header';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\nimport Templates from 'core/templates';\n\nexport default class extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_title']`,\n CM: `[data-for=\"cmitem\"]`,\n SECTIONINFO: `[data-for=\"sectioninfo\"]`,\n SECTIONBADGES: `[data-region=\"sectionbadges\"]`,\n SHOWSECTION: `[data-action=\"sectionShow\"]`,\n HIDESECTION: `[data-action=\"sectionHide\"]`,\n ACTIONTEXT: `.menu-action-text`,\n ICON: `.icon`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n HASDESCRIPTION: 'description',\n HIDE: 'd-none',\n HIDDEN: 'hidden',\n CURRENT: 'current',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Validate if the drop data can be dropped over the component.\n *\n * @param {Object} dropdata the exported drop data.\n * @returns {boolean}\n */\n validateDropData(dropdata) {\n // If the format uses one section per page sections dropping in the content is ignored.\n if (dropdata?.type === 'section' && this.reactive.sectionReturn != 0) {\n return false;\n }\n return super.validateDropData(dropdata);\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a content section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);\n this.element.classList.toggle(this.classes.HIDDEN, !element.visible ?? false);\n this.element.classList.toggle(this.classes.CURRENT, element.current ?? false);\n this.locked = element.locked;\n // The description box classes depends on the section state.\n const sectioninfo = this.getElement(this.selectors.SECTIONINFO);\n if (sectioninfo) {\n sectioninfo.classList.toggle(this.classes.HASDESCRIPTION, element.hasrestrictions);\n }\n // Update section badges and menus.\n this._updateBadges(element);\n this._updateActionsMenu(element);\n }\n\n /**\n * Update a section badges using the state information.\n *\n * @param {object} section the section state.\n */\n _updateBadges(section) {\n const current = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='iscurrent']`);\n current?.classList.toggle(this.classes.HIDE, !section.current);\n\n const hiddenFromStudents = this.getElement(`${this.selectors.SECTIONBADGES} [data-type='hiddenfromstudents']`);\n hiddenFromStudents?.classList.toggle(this.classes.HIDE, section.visible);\n }\n\n /**\n * Update a section action menus.\n *\n * @param {object} section the section state.\n */\n async _updateActionsMenu(section) {\n let selector;\n let newAction;\n if (section.visible) {\n selector = this.selectors.SHOWSECTION;\n newAction = 'sectionHide';\n } else {\n selector = this.selectors.HIDESECTION;\n newAction = 'sectionShow';\n }\n // Find the affected action.\n const affectedAction = this.getElement(selector);\n if (!affectedAction) {\n return;\n }\n // Change action.\n affectedAction.dataset.action = newAction;\n // Change text.\n const actionText = affectedAction.querySelector(this.selectors.ACTIONTEXT);\n if (affectedAction.dataset?.swapname && actionText) {\n const oldText = actionText?.innerText;\n actionText.innerText = affectedAction.dataset.swapname;\n affectedAction.dataset.swapname = oldText;\n }\n // Change icon.\n const icon = affectedAction.querySelector(this.selectors.ICON);\n if (affectedAction.dataset?.swapicon && icon) {\n const newIcon = affectedAction.dataset.swapicon;\n if (newIcon) {\n const pixHtml = await Templates.renderPix(newIcon, 'core');\n Templates.replaceNode(icon, pixHtml, '');\n }\n }\n }\n}\n"],"names":["DndSection","create","name","selectors","SECTION_ITEM","CM","SECTIONINFO","SECTIONBADGES","SHOWSECTION","HIDESECTION","ACTIONTEXT","ICON","classes","LOCKED","HASDESCRIPTION","HIDE","HIDDEN","CURRENT","id","this","element","dataset","stateReady","state","configState","reactive","isEditing","supportComponents","sectionItem","getElement","headerComponent","Header","fullregion","configDragDrop","getWatchers","watch","handler","_refreshSection","validateDropData","dropdata","type","sectionReturn","super","getLastCm","cms","getElements","length","classList","toggle","DRAGGING","dragging","locked","visible","current","sectioninfo","hasrestrictions","_updateBadges","_updateActionsMenu","section","hiddenFromStudents","selector","newAction","affectedAction","action","actionText","querySelector","swapname","oldText","innerText","icon","swapicon","newIcon","pixHtml","Templates","renderPix","replaceNode"],"mappings":";;;;;;;;kPA4B6BA,oBAKzBC,cAESC,KAAO,uBAEPC,UAAY,CACbC,0CACAC,yBACAC,uCACAC,8CACAC,0CACAC,0CACAC,+BACAC,mBAGCC,QAAU,CACXC,OAAQ,iBACRC,eAAgB,cAChBC,KAAM,SACNC,OAAQ,SACRC,QAAS,gBAIRC,GAAKC,KAAKC,QAAQC,QAAQH,GAQnCI,WAAWC,eACFC,YAAYD,OAEbJ,KAAKM,SAASC,WAAaP,KAAKM,SAASE,kBAAmB,OAEtDC,YAAcT,KAAKU,WAAWV,KAAKhB,UAAUC,iBAC/CwB,YAAa,OAEPE,gBAAkB,IAAIC,gBAAO,IAC5BZ,KACHC,QAASQ,YACTI,WAAYb,KAAKC,eAEhBa,eAAeH,mBAUhCI,oBACW,CACH,CAACC,wBAAkBhB,KAAKD,gBAAekB,QAASjB,KAAKkB,kBAU7DC,iBAAiBC,iBAES,aAAnBA,MAAAA,gBAAAA,SAAUC,OAAqD,GAA/BrB,KAAKM,SAASgB,gBAG1CC,MAAMJ,iBAAiBC,UAQlCI,kBACUC,IAAMzB,KAAK0B,YAAY1B,KAAKhB,UAAUE,WAEvCuC,KAAsB,IAAfA,IAAIE,OAGTF,IAAIA,IAAIE,OAAS,GAFb,KAWfT,kGAAgBjB,QAACA,mBAERA,QAAQ2B,UAAUC,OAAO7B,KAAKP,QAAQqC,mCAAU7B,QAAQ8B,+DACxD9B,QAAQ2B,UAAUC,OAAO7B,KAAKP,QAAQC,+BAAQO,QAAQ+B,yDACtD/B,QAAQ2B,UAAUC,OAAO7B,KAAKP,QAAQI,iCAASI,QAAQgC,4DACvDhC,QAAQ2B,UAAUC,OAAO7B,KAAKP,QAAQK,iCAASG,QAAQiC,4DACvDF,OAAS/B,QAAQ+B,aAEhBG,YAAcnC,KAAKU,WAAWV,KAAKhB,UAAUG,aAC/CgD,aACAA,YAAYP,UAAUC,OAAO7B,KAAKP,QAAQE,eAAgBM,QAAQmC,sBAGjEC,cAAcpC,cACdqC,mBAAmBrC,SAQ5BoC,cAAcE,eACJL,QAAUlC,KAAKU,qBAAcV,KAAKhB,UAAUI,2CAClD8C,MAAAA,SAAAA,QAASN,UAAUC,OAAO7B,KAAKP,QAAQG,MAAO2C,QAAQL,eAEhDM,mBAAqBxC,KAAKU,qBAAcV,KAAKhB,UAAUI,oDAC7DoD,MAAAA,oBAAAA,mBAAoBZ,UAAUC,OAAO7B,KAAKP,QAAQG,KAAM2C,QAAQN,kCAQ3CM,8DACjBE,SACAC,UACAH,QAAQN,SACRQ,SAAWzC,KAAKhB,UAAUK,YAC1BqD,UAAY,gBAEZD,SAAWzC,KAAKhB,UAAUM,YAC1BoD,UAAY,qBAGVC,eAAiB3C,KAAKU,WAAW+B,cAClCE,sBAILA,eAAezC,QAAQ0C,OAASF,gBAE1BG,WAAaF,eAAeG,cAAc9C,KAAKhB,UAAUO,6CAC3DoD,eAAezC,gEAAS6C,UAAYF,WAAY,OAC1CG,QAAUH,MAAAA,kBAAAA,WAAYI,UAC5BJ,WAAWI,UAAYN,eAAezC,QAAQ6C,SAC9CJ,eAAezC,QAAQ6C,SAAWC,cAGhCE,KAAOP,eAAeG,cAAc9C,KAAKhB,UAAUQ,wCACrDmD,eAAezC,kEAASiD,UAAYD,KAAM,OACpCE,QAAUT,eAAezC,QAAQiD,YACnCC,QAAS,OACHC,cAAgBC,mBAAUC,UAAUH,QAAS,2BACzCI,YAAYN,KAAMG,QAAS"} \ No newline at end of file diff --git a/course/format/amd/src/local/content/section.js b/course/format/amd/src/local/content/section.js index 272b9e6853f..0dc7d92f42b 100644 --- a/course/format/amd/src/local/content/section.js +++ b/course/format/amd/src/local/content/section.js @@ -51,6 +51,7 @@ export default class extends DndSection { HASDESCRIPTION: 'description', HIDE: 'd-none', HIDDEN: 'hidden', + CURRENT: 'current', }; // We need our id to watch specific events. @@ -130,6 +131,7 @@ export default class extends DndSection { this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false); this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false); this.element.classList.toggle(this.classes.HIDDEN, !element.visible ?? false); + this.element.classList.toggle(this.classes.CURRENT, element.current ?? false); this.locked = element.locked; // The description box classes depends on the section state. const sectioninfo = this.getElement(this.selectors.SECTIONINFO); diff --git a/course/format/classes/base.php b/course/format/classes/base.php index 66b1e5f09f5..0e31a33289d 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -286,6 +286,15 @@ abstract class base { return $this->courseid; } + /** + * Returns the course context. + * + * @return context_course the course context + */ + final public function get_context(): context_course { + return context_course::instance($this->courseid); + } + /** * Returns a record from course database table plus additional fields * that course format defines diff --git a/course/format/topics/amd/build/mutations.min.js b/course/format/topics/amd/build/mutations.min.js new file mode 100644 index 00000000000..046cdebd46a --- /dev/null +++ b/course/format/topics/amd/build/mutations.min.js @@ -0,0 +1,3 @@ +define("format_topics/mutations",["exports","core_courseformat/courseeditor","core_courseformat/local/courseeditor/mutations","core_courseformat/local/content/actions"],(function(_exports,_courseeditor,_mutations,_actions){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_mutations=_interopRequireDefault(_mutations),_actions=_interopRequireDefault(_actions);class TopicsMutations extends _mutations.default{constructor(){super(...arguments),_defineProperty(this,"sectionHighlight",(async function(stateManager,sectionIds){const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_highlight",course.id,sectionIds);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)})),_defineProperty(this,"sectionUnhighlight",(async function(stateManager,sectionIds){const course=stateManager.get("course");this.sectionLock(stateManager,sectionIds,!0);const updates=await this._callEditWebservice("section_unhighlight",course.id,sectionIds);stateManager.processUpdates(updates),this.sectionLock(stateManager,sectionIds,!1)}))}}_exports.init=()=>{(0,_courseeditor.getCurrentCourseEditor)().addMutations(new TopicsMutations),_actions.default.addActions({sectionHighlight:"sectionHighlight",sectionUnhighlight:"sectionUnhighlight"})}})); + +//# sourceMappingURL=mutations.min.js.map \ No newline at end of file diff --git a/course/format/topics/amd/build/mutations.min.js.map b/course/format/topics/amd/build/mutations.min.js.map new file mode 100644 index 00000000000..6864951ec4b --- /dev/null +++ b/course/format/topics/amd/build/mutations.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mutations.min.js","sources":["../src/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\n/**\n * Format topics mutations.\n *\n * An instance of this class will be used to add custom mutations to the course editor.\n * To make sure the addMutations method find the proper functions, all functions must\n * be declared as class attributes, not a simple methods. The reason is because many\n * plugins can add extra mutations to the course editor.\n *\n * @module format_topics/mutations\n * @copyright 2022 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport DefaultMutations from 'core_courseformat/local/courseeditor/mutations';\nimport CourseActions from 'core_courseformat/local/content/actions';\n\nclass TopicsMutations extends DefaultMutations {\n\n /**\n * Highlight sections.\n *\n * It is important to note this mutation method is declared as a class attribute,\n * See the class jsdoc for more details on why.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n sectionHighlight = async function(stateManager, sectionIds) {\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_highlight', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n };\n\n /**\n * Unhighlight sections.\n *\n * It is important to note this mutation method is declared as a class attribute,\n * See the class jsdoc for more details on why.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n */\n sectionUnhighlight = async function(stateManager, sectionIds) {\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_unhighlight', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n };\n}\n\nexport const init = () => {\n const courseEditor = getCurrentCourseEditor();\n // Some plugin (activity or block) may have their own mutations already registered.\n // This is why we use addMutations instead of setMutations here.\n courseEditor.addMutations(new TopicsMutations());\n // Add direct mutation content actions.\n CourseActions.addActions({\n sectionHighlight: 'sectionHighlight',\n sectionUnhighlight: 'sectionUnhighlight',\n });\n};\n"],"names":["TopicsMutations","DefaultMutations","async","stateManager","sectionIds","course","get","sectionLock","updates","this","_callEditWebservice","id","processUpdates","addMutations","addActions","sectionHighlight","sectionUnhighlight"],"mappings":"goBAgCMA,wBAAwBC,8FAWPC,eAAeC,aAAcC,kBACtCC,OAASF,aAAaG,IAAI,eAC3BC,YAAYJ,aAAcC,YAAY,SACrCI,cAAgBC,KAAKC,oBAAoB,oBAAqBL,OAAOM,GAAIP,YAC/ED,aAAaS,eAAeJ,cACvBD,YAAYJ,aAAcC,YAAY,iDAY1BF,eAAeC,aAAcC,kBACxCC,OAASF,aAAaG,IAAI,eAC3BC,YAAYJ,aAAcC,YAAY,SACrCI,cAAgBC,KAAKC,oBAAoB,sBAAuBL,OAAOM,GAAIP,YACjFD,aAAaS,eAAeJ,cACvBD,YAAYJ,aAAcC,YAAY,qBAI/B,MACK,0CAGRS,aAAa,IAAIb,kCAEhBc,WAAW,CACrBC,iBAAkB,mBAClBC,mBAAoB"} \ No newline at end of file diff --git a/course/format/topics/amd/build/section.min.js b/course/format/topics/amd/build/section.min.js new file mode 100644 index 00000000000..b6f3c84a6ba --- /dev/null +++ b/course/format/topics/amd/build/section.min.js @@ -0,0 +1,10 @@ +define("format_topics/section",["exports","core/reactive","core_courseformat/courseeditor","core/templates"],(function(_exports,_reactive,_courseeditor,_templates){var obj; +/** + * Format topics section extra logic component. + * + * @module format_topics/mutations + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};class HighlightSection extends _reactive.BaseComponent{create(){this.name="format_topics_section",this.selectors={SECTION:"[data-for='section']",SETMARKER:'[data-action="sectionHighlight"]',REMOVEMARKER:'[data-action="sectionUnhighlight"]',ACTIONTEXT:".menu-action-text",ICON:".icon"},this.classes={HIDE:"d-none"},this.formatActions={HIGHLIGHT:"sectionHighlight",UNHIGHLIGHT:"sectionUnhighlight"}}getWatchers(){return[{watch:"section.current:updated",handler:this._refreshHighlight}]}async _refreshHighlight(_ref){var _affectedAction$datas,_affectedAction$datas2;let selector,newAction,{element:element}=_ref;element.current?(selector=this.selectors.SETMARKER,newAction=this.formatActions.UNHIGHLIGHT):(selector=this.selectors.REMOVEMARKER,newAction=this.formatActions.HIGHLIGHT);const affectedAction=this.getElement("".concat(this.selectors.SECTION," ").concat(selector),element.id);if(!affectedAction)return;affectedAction.dataset.action=newAction;const actionText=affectedAction.querySelector(this.selectors.ACTIONTEXT);if(null!==(_affectedAction$datas=affectedAction.dataset)&&void 0!==_affectedAction$datas&&_affectedAction$datas.swapname&&actionText){const oldText=null==actionText?void 0:actionText.innerText;actionText.innerText=affectedAction.dataset.swapname,affectedAction.dataset.swapname=oldText}const icon=affectedAction.querySelector(this.selectors.ICON);if(null!==(_affectedAction$datas2=affectedAction.dataset)&&void 0!==_affectedAction$datas2&&_affectedAction$datas2.swapicon&&icon){const newIcon=affectedAction.dataset.swapicon;if(newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}}_exports.init=()=>{const courseEditor=(0,_courseeditor.getCurrentCourseEditor)();courseEditor.supportComponents&&courseEditor.isEditing&&new HighlightSection({element:document.getElementById("region-main"),reactive:courseEditor})}})); + +//# sourceMappingURL=section.min.js.map \ No newline at end of file diff --git a/course/format/topics/amd/build/section.min.js.map b/course/format/topics/amd/build/section.min.js.map new file mode 100644 index 00000000000..d2444516253 --- /dev/null +++ b/course/format/topics/amd/build/section.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"section.min.js","sources":["../src/section.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 * Format topics section extra logic component.\n *\n * @module format_topics/mutations\n * @copyright 2022 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport Templates from 'core/templates';\n\nclass HighlightSection extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'format_topics_section';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SETMARKER: `[data-action=\"sectionHighlight\"]`,\n REMOVEMARKER: `[data-action=\"sectionUnhighlight\"]`,\n ACTIONTEXT: `.menu-action-text`,\n ICON: `.icon`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n HIDE: 'd-none',\n };\n // The topics format section specific actions.\n this.formatActions = {\n HIGHLIGHT: 'sectionHighlight',\n UNHIGHLIGHT: 'sectionUnhighlight',\n };\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section.current:updated`, handler: this._refreshHighlight},\n ];\n }\n\n /**\n * Update a content section using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n async _refreshHighlight({element}) {\n let selector;\n let newAction;\n if (element.current) {\n selector = this.selectors.SETMARKER;\n newAction = this.formatActions.UNHIGHLIGHT;\n } else {\n selector = this.selectors.REMOVEMARKER;\n newAction = this.formatActions.HIGHLIGHT;\n }\n // Find the affected action.\n const affectedAction = this.getElement(`${this.selectors.SECTION} ${selector}`, element.id);\n if (!affectedAction) {\n return;\n }\n // Change action, text and icon.\n affectedAction.dataset.action = newAction;\n const actionText = affectedAction.querySelector(this.selectors.ACTIONTEXT);\n if (affectedAction.dataset?.swapname && actionText) {\n const oldText = actionText?.innerText;\n actionText.innerText = affectedAction.dataset.swapname;\n affectedAction.dataset.swapname = oldText;\n }\n const icon = affectedAction.querySelector(this.selectors.ICON);\n if (affectedAction.dataset?.swapicon && icon) {\n const newIcon = affectedAction.dataset.swapicon;\n if (newIcon) {\n const pixHtml = await Templates.renderPix(newIcon, 'core');\n Templates.replaceNode(icon, pixHtml, '');\n }\n }\n }\n}\n\nexport const init = () => {\n // Add component to the section.\n const courseEditor = getCurrentCourseEditor();\n if (courseEditor.supportComponents && courseEditor.isEditing) {\n new HighlightSection({\n element: document.getElementById('region-main'),\n reactive: courseEditor,\n });\n }\n};\n"],"names":["HighlightSection","BaseComponent","create","name","selectors","SECTION","SETMARKER","REMOVEMARKER","ACTIONTEXT","ICON","classes","HIDE","formatActions","HIGHLIGHT","UNHIGHLIGHT","getWatchers","watch","handler","this","_refreshHighlight","selector","newAction","element","current","affectedAction","getElement","id","dataset","action","actionText","querySelector","swapname","oldText","innerText","icon","swapicon","newIcon","pixHtml","Templates","renderPix","replaceNode","courseEditor","supportComponents","isEditing","document","getElementById","reactive"],"mappings":";;;;;;;sJA2BMA,yBAAyBC,wBAK3BC,cAESC,KAAO,6BAEPC,UAAY,CACbC,+BACAC,6CACAC,kDACAC,+BACAC,mBAGCC,QAAU,CACXC,KAAM,eAGLC,cAAgB,CACjBC,UAAW,mBACXC,YAAa,sBASrBC,oBACW,CACH,CAACC,gCAAkCC,QAASC,KAAKC,uGAWjDC,SACAC,WAFgBC,QAACA,cAGjBA,QAAQC,SACRH,SAAWF,KAAKd,UAAUE,UAC1Be,UAAYH,KAAKN,cAAcE,cAE/BM,SAAWF,KAAKd,UAAUG,aAC1Bc,UAAYH,KAAKN,cAAcC,iBAG7BW,eAAiBN,KAAKO,qBAAcP,KAAKd,UAAUC,oBAAWe,UAAYE,QAAQI,QACnFF,sBAILA,eAAeG,QAAQC,OAASP,gBAC1BQ,WAAaL,eAAeM,cAAcZ,KAAKd,UAAUI,6CAC3DgB,eAAeG,gEAASI,UAAYF,WAAY,OAC1CG,QAAUH,MAAAA,kBAAAA,WAAYI,UAC5BJ,WAAWI,UAAYT,eAAeG,QAAQI,SAC9CP,eAAeG,QAAQI,SAAWC,cAEhCE,KAAOV,eAAeM,cAAcZ,KAAKd,UAAUK,wCACrDe,eAAeG,kEAASQ,UAAYD,KAAM,OACpCE,QAAUZ,eAAeG,QAAQQ,YACnCC,QAAS,OACHC,cAAgBC,mBAAUC,UAAUH,QAAS,2BACzCI,YAAYN,KAAMG,QAAS,qBAMjC,WAEVI,cAAe,0CACjBA,aAAaC,mBAAqBD,aAAaE,eAC3C3C,iBAAiB,CACjBsB,QAASsB,SAASC,eAAe,eACjCC,SAAUL"} \ No newline at end of file diff --git a/course/format/topics/amd/src/mutations.js b/course/format/topics/amd/src/mutations.js new file mode 100644 index 00000000000..9931d099ebc --- /dev/null +++ b/course/format/topics/amd/src/mutations.js @@ -0,0 +1,80 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Format topics mutations. + * + * An instance of this class will be used to add custom mutations to the course editor. + * To make sure the addMutations method find the proper functions, all functions must + * be declared as class attributes, not a simple methods. The reason is because many + * plugins can add extra mutations to the course editor. + * + * @module format_topics/mutations + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {getCurrentCourseEditor} from 'core_courseformat/courseeditor'; +import DefaultMutations from 'core_courseformat/local/courseeditor/mutations'; +import CourseActions from 'core_courseformat/local/content/actions'; + +class TopicsMutations extends DefaultMutations { + + /** + * Highlight sections. + * + * It is important to note this mutation method is declared as a class attribute, + * See the class jsdoc for more details on why. + * + * @param {StateManager} stateManager the current state manager + * @param {array} sectionIds the list of section ids + */ + sectionHighlight = async function(stateManager, sectionIds) { + const course = stateManager.get('course'); + this.sectionLock(stateManager, sectionIds, true); + const updates = await this._callEditWebservice('section_highlight', course.id, sectionIds); + stateManager.processUpdates(updates); + this.sectionLock(stateManager, sectionIds, false); + }; + + /** + * Unhighlight sections. + * + * It is important to note this mutation method is declared as a class attribute, + * See the class jsdoc for more details on why. + * + * @param {StateManager} stateManager the current state manager + * @param {array} sectionIds the list of section ids + */ + sectionUnhighlight = async function(stateManager, sectionIds) { + const course = stateManager.get('course'); + this.sectionLock(stateManager, sectionIds, true); + const updates = await this._callEditWebservice('section_unhighlight', course.id, sectionIds); + stateManager.processUpdates(updates); + this.sectionLock(stateManager, sectionIds, false); + }; +} + +export const init = () => { + const courseEditor = getCurrentCourseEditor(); + // Some plugin (activity or block) may have their own mutations already registered. + // This is why we use addMutations instead of setMutations here. + courseEditor.addMutations(new TopicsMutations()); + // Add direct mutation content actions. + CourseActions.addActions({ + sectionHighlight: 'sectionHighlight', + sectionUnhighlight: 'sectionUnhighlight', + }); +}; diff --git a/course/format/topics/amd/src/section.js b/course/format/topics/amd/src/section.js new file mode 100644 index 00000000000..152cb9a5d48 --- /dev/null +++ b/course/format/topics/amd/src/section.js @@ -0,0 +1,115 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Format topics section extra logic component. + * + * @module format_topics/mutations + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {BaseComponent} from 'core/reactive'; +import {getCurrentCourseEditor} from 'core_courseformat/courseeditor'; +import Templates from 'core/templates'; + +class HighlightSection extends BaseComponent { + + /** + * Constructor hook. + */ + create() { + // Optional component name for debugging. + this.name = 'format_topics_section'; + // Default query selectors. + this.selectors = { + SECTION: `[data-for='section']`, + SETMARKER: `[data-action="sectionHighlight"]`, + REMOVEMARKER: `[data-action="sectionUnhighlight"]`, + ACTIONTEXT: `.menu-action-text`, + ICON: `.icon`, + }; + // Default classes to toggle on refresh. + this.classes = { + HIDE: 'd-none', + }; + // The topics format section specific actions. + this.formatActions = { + HIGHLIGHT: 'sectionHighlight', + UNHIGHLIGHT: 'sectionUnhighlight', + }; + } + + /** + * Component watchers. + * + * @returns {Array} of watchers + */ + getWatchers() { + return [ + {watch: `section.current:updated`, handler: this._refreshHighlight}, + ]; + } + + /** + * Update a content section using the state information. + * + * @param {object} param + * @param {Object} param.element details the update details. + */ + async _refreshHighlight({element}) { + let selector; + let newAction; + if (element.current) { + selector = this.selectors.SETMARKER; + newAction = this.formatActions.UNHIGHLIGHT; + } else { + selector = this.selectors.REMOVEMARKER; + newAction = this.formatActions.HIGHLIGHT; + } + // Find the affected action. + const affectedAction = this.getElement(`${this.selectors.SECTION} ${selector}`, element.id); + if (!affectedAction) { + return; + } + // Change action, text and icon. + affectedAction.dataset.action = newAction; + const actionText = affectedAction.querySelector(this.selectors.ACTIONTEXT); + if (affectedAction.dataset?.swapname && actionText) { + const oldText = actionText?.innerText; + actionText.innerText = affectedAction.dataset.swapname; + affectedAction.dataset.swapname = oldText; + } + const icon = affectedAction.querySelector(this.selectors.ICON); + if (affectedAction.dataset?.swapicon && icon) { + const newIcon = affectedAction.dataset.swapicon; + if (newIcon) { + const pixHtml = await Templates.renderPix(newIcon, 'core'); + Templates.replaceNode(icon, pixHtml, ''); + } + } + } +} + +export const init = () => { + // Add component to the section. + const courseEditor = getCurrentCourseEditor(); + if (courseEditor.supportComponents && courseEditor.isEditing) { + new HighlightSection({ + element: document.getElementById('region-main'), + reactive: courseEditor, + }); + } +}; diff --git a/course/format/topics/classes/courseformat/stateactions.php b/course/format/topics/classes/courseformat/stateactions.php new file mode 100644 index 00000000000..69f310501f8 --- /dev/null +++ b/course/format/topics/classes/courseformat/stateactions.php @@ -0,0 +1,117 @@ +. + +namespace format_topics\courseformat; + +use core_courseformat\stateupdates; +use core_courseformat\stateactions as stateactions_base; +use core\event\course_module_updated; +use cm_info; +use section_info; +use stdClass; +use course_modinfo; +use moodle_exception; +use context_module; +use context_course; + +/** + * Contains the core course state actions specific to topics format. + * + * @package format_topics + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class stateactions extends stateactions_base { + + /** + * Highlight course section. + * + * @param stateupdates $updates the affected course elements track + * @param stdClass $course the course object + * @param int[] $ids section ids (only ther first one will be highlighted) + * @param int $targetsectionid not used + * @param int $targetcmid not used + */ + public function section_highlight( + stateupdates $updates, + stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null + ): void { + global $DB; + + $this->validate_sections($course, $ids, __FUNCTION__); + $coursecontext = context_course::instance($course->id); + require_capability('moodle/course:setcurrentsection', $coursecontext); + + // Get the previous marked section. + $modinfo = get_fast_modinfo($course); + $previousmarker = $DB->get_field("course", "marker", ['id' => $course->id]); + + $section = $modinfo->get_section_info_by_id(reset($ids), MUST_EXIST); + if ($section->section == $previousmarker) { + return; + } + + // Mark the new one. + course_set_marker($course->id, $section->section); + $updates->add_section_put($section->id); + if ($previousmarker) { + $section = $modinfo->get_section_info($previousmarker); + $updates->add_section_put($section->id); + } + } + + /** + * Remove highlight from a course sections. + * + * @param stateupdates $updates the affected course elements track + * @param stdClass $course the course object + * @param int[] $ids optional extra section ids to refresh + * @param int $targetsectionid not used + * @param int $targetcmid not used + */ + public function section_unhighlight( + stateupdates $updates, + stdClass $course, + array $ids = [], + ?int $targetsectionid = null, + ?int $targetcmid = null + ): void { + global $DB; + + $this->validate_sections($course, $ids, __FUNCTION__); + $coursecontext = context_course::instance($course->id); + require_capability('moodle/course:setcurrentsection', $coursecontext); + + $affectedsections = []; + + // Get the previous marked section and unmark it. + $modinfo = get_fast_modinfo($course); + $previousmarker = $DB->get_field("course", "marker", ['id' => $course->id]); + course_set_marker($course->id, 0); + $section = $modinfo->get_section_info($previousmarker, MUST_EXIST); + $updates->add_section_put($section->id); + + foreach ($ids as $sectionid) { + $section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST); + if ($section->section != $previousmarker) { + $updates->add_section_put($section->id); + } + } + } +} diff --git a/course/format/topics/classes/output/courseformat/content.php b/course/format/topics/classes/output/courseformat/content.php index 8d3e6308108..68a12ce8002 100644 --- a/course/format/topics/classes/output/courseformat/content.php +++ b/course/format/topics/classes/output/courseformat/content.php @@ -25,6 +25,7 @@ namespace format_topics\output\courseformat; use core_courseformat\output\local\content as content_base; +use renderer_base; /** * Base class to render a course content. @@ -42,4 +43,17 @@ class content extends content_base { */ protected $hasaddsection = false; + /** + * Export this data so it can be used as the context for a mustache template (core/inplace_editable). + * + * @param renderer_base $output typically, the renderer that's calling this function + * @return stdClass data context for a mustache template + */ + public function export_for_template(renderer_base $output) { + global $PAGE; + $PAGE->requires->js_call_amd('format_topics/mutations', 'init'); + $PAGE->requires->js_call_amd('format_topics/section', 'init'); + return parent::export_for_template($output); + } + } diff --git a/course/format/topics/classes/output/courseformat/content/section/controlmenu.php b/course/format/topics/classes/output/courseformat/content/section/controlmenu.php index e4acb352c81..d554e84a69e 100644 --- a/course/format/topics/classes/output/courseformat/content/section/controlmenu.php +++ b/course/format/topics/classes/output/courseformat/content/section/controlmenu.php @@ -24,8 +24,8 @@ namespace format_topics\output\courseformat\content\section; -use context_course; use core_courseformat\output\local\content\section\controlmenu as controlmenu_base; +use moodle_url; /** * Base class to render a course section menu. @@ -36,10 +36,10 @@ use core_courseformat\output\local\content\section\controlmenu as controlmenu_ba */ class controlmenu extends controlmenu_base { - /** @var course_format the course format class */ + /** @var \core_courseformat\base the course format class */ protected $format; - /** @var section_info the course section class */ + /** @var \section_info the course section class */ protected $section; /** @@ -53,47 +53,11 @@ class controlmenu extends controlmenu_base { $format = $this->format; $section = $this->section; - $course = $format->get_course(); - $sectionreturn = $format->get_section_number(); - - $coursecontext = context_course::instance($course->id); - - if ($sectionreturn) { - $url = course_get_url($course, $section->section); - } else { - $url = course_get_url($course); - } - $url->param('sesskey', sesskey()); + $coursecontext = $format->get_context(); $controls = []; if ($section->section && has_capability('moodle/course:setcurrentsection', $coursecontext)) { - if ($course->marker == $section->section) { // Show the "light globe" on/off. - $url->param('marker', 0); - $highlightoff = get_string('highlightoff'); - $controls['highlight'] = [ - 'url' => $url, - 'icon' => 'i/marked', - 'name' => $highlightoff, - 'pixattr' => ['class' => ''], - 'attr' => [ - 'class' => 'editing_highlight', - 'data-action' => 'removemarker' - ], - ]; - } else { - $url->param('marker', $section->section); - $highlight = get_string('highlight'); - $controls['highlight'] = [ - 'url' => $url, - 'icon' => 'i/marker', - 'name' => $highlight, - 'pixattr' => ['class' => ''], - 'attr' => [ - 'class' => 'editing_highlight', - 'data-action' => 'setmarker' - ], - ]; - } + $controls['highlight'] = $this->get_highlight_control(); } $parentcontrols = parent::section_control_items(); @@ -116,4 +80,72 @@ class controlmenu extends controlmenu_base { return array_merge($controls, $parentcontrols); } } + + /** + * Return the course url. + * + * @return moodle_url + */ + protected function get_course_url(): moodle_url { + $format = $this->format; + $section = $this->section; + $course = $format->get_course(); + $sectionreturn = $format->get_section_number(); + + if ($sectionreturn) { + $url = course_get_url($course, $section->section); + } else { + $url = course_get_url($course); + } + $url->param('sesskey', sesskey()); + return $url; + } + + /** + * Return the specific section highlight action. + * + * @return array the action element. + */ + protected function get_highlight_control(): array { + $format = $this->format; + $section = $this->section; + $course = $format->get_course(); + $url = $this->get_course_url(); + + $highlightoff = get_string('highlightoff'); + $highlighton = get_string('highlight'); + + if ($course->marker == $section->section) { // Show the "light globe" on/off. + $url->param('marker', 0); + $result = [ + 'url' => $url, + 'icon' => 'i/marked', + 'name' => $highlightoff, + 'pixattr' => ['class' => ''], + 'attr' => [ + 'class' => 'editing_highlight', + 'data-action' => 'sectionUnhighlight', + 'data-id' => $section->id, + 'data-swapname' => $highlighton, + 'data-swapicon' => 'i/marker', + ], + ]; + } else { + $url->param('marker', $section->section); + $result = [ + 'url' => $url, + 'icon' => 'i/marker', + 'name' => $highlighton, + 'pixattr' => ['class' => ''], + 'attr' => [ + 'class' => 'editing_highlight', + 'data-action' => 'sectionHighlight', + 'data-id' => $section->id, + 'data-swapname' => $highlightoff, + 'data-swapicon' => 'i/marked', + ], + ]; + } + return $result; + } } diff --git a/course/format/topics/tests/behat/highlight_sections.feature b/course/format/topics/tests/behat/highlight_sections.feature new file mode 100644 index 00000000000..a520be353ef --- /dev/null +++ b/course/format/topics/tests/behat/highlight_sections.feature @@ -0,0 +1,49 @@ +@format @format_topics +Feature: Sections can be highlighted + In order to mark sections + As a teacher + I need to highlight and unhighlight sections + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + And the following "courses" exist: + | fullname | shortname | format | coursedisplay | numsections | + | Course 1 | C1 | topics | 0 | 5 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | assign | Test assignment name | Test assignment description | C1 | assign1 | 0 | + | book | Test book name | Test book description | C1 | book1 | 1 | + | chat | Test chat name | Test chat description | C1 | chat1 | 4 | + | choice | Test choice name | Test choice description | C1 | choice1 | 5 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + + @javascript + Scenario: Highlight a section + When I open section "2" edit menu + And I click on "Highlight" "link" in the "Topic 2" "section" + Then I should see "Highlighted" in the "Topic 2" "section" + + @javascript + Scenario: Highlight a section when another section is already highlighted + Given I open section "3" edit menu + And I click on "Highlight" "link" in the "Topic 3" "section" + And I should see "Highlighted" in the "Topic 3" "section" + When I open section "2" edit menu + And I click on "Highlight" "link" in the "Topic 2" "section" + Then I should see "Highlighted" in the "Topic 2" "section" + And I should not see "Highlighted" in the "Topic 3" "section" + + @javascript + Scenario: Unhighlight a section + Given I open section "3" edit menu + And I click on "Highlight" "link" in the "Topic 3" "section" + And I should see "Highlighted" in the "Topic 3" "section" + When I open section "3" edit menu + And I click on "Remove highlight" "link" in the "Topic 3" "section" + Then I should not see "Highlighted" in the "Topic 3" "section" diff --git a/course/format/topics/tests/courseformat/stateactions_test.php b/course/format/topics/tests/courseformat/stateactions_test.php new file mode 100644 index 00000000000..6c1d936b407 --- /dev/null +++ b/course/format/topics/tests/courseformat/stateactions_test.php @@ -0,0 +1,222 @@ +. + +namespace format_topics\courseformat; + +use core_courseformat\stateupdates; +use moodle_exception; +use stdClass; + +/** + * Topics course format related unit tests. + * + * @package format_topics + * @copyright 2022 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class stateactions_test extends \advanced_testcase { + + /** + * Enrol a user into a course and login as this user. + * + * @param stdClass $course the course object + * @param string $rolename the rolename + */ + private function enrol_user(stdClass $course, string $rolename): void { + // Create and enrol user using given role. + if ($rolename == 'admin') { + $this->setAdminUser(); + } else { + $user = $this->getDataGenerator()->create_user(); + if ($rolename != 'unenroled') { + $this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename); + } + $this->setUser($user); + } + } + + /** + * Tests for section_highlight method. + * + * @dataProvider basic_role_provider + * @covers ::section_highlight + * @param string $rolename The role of the user that will execute the method. + * @param bool $expectedexception If this call will raise an exception. + */ + public function test_section_highlight(string $rolename, bool $expectedexception = false): void { + global $DB; + $this->resetAfterTest(true); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course( + ['numsections' => 4, 'format' => 'topics'], + ['createsections' => true] + ); + + $this->enrol_user($course, $rolename); + + $sectionrecords = $DB->get_records('course_sections', ['course' => $course->id], 'section'); + $sectionids = []; + foreach ($sectionrecords as $section) { + $sectionids[] = $section->id; + } + + // Initialise stateupdates. + $courseformat = course_get_format($course->id); + $updates = new stateupdates($courseformat); + + // All state actions accepts batch editing (an array of sections in this case). However, + // only one course section can be marked as highlighted. This means that if we send more + // than one section id only the first one will be highlighted and the rest will be ignored. + $methodparam = [ + $sectionids[1], + $sectionids[2], + $sectionids[3], + ]; + + // Actions have an array of ids as param but only the first one will be highlighted. + $highlightid = reset($methodparam); + $highlight = $sectionrecords[$highlightid]; + + if ($expectedexception) { + $this->expectException(moodle_exception::class); + } + + // Execute given method. + $actions = new stateactions(); + $actions->section_highlight( + $updates, + $course, + $methodparam + ); + + // Check state returned after executing given action. + $updatelist = $updates->jsonSerialize(); + $this->assertCount(1, $updatelist); + $update = reset($updatelist); + $this->assertEquals('section', $update->name); + $this->assertEquals('put', $update->action); + $this->assertEquals($highlightid, $update->fields->id); + $this->assertEquals(1, $update->fields->current); + + // Check DB sections. + $this->assertEquals($highlight->section, $DB->get_field("course", "marker", ['id' => $course->id])); + } + + /** + * Tests for section_unhighlight method. + * + * @dataProvider basic_role_provider + * @covers ::section_unhighlight + * @param string $rolename The role of the user that will execute the method. + * @param bool $expectedexception If this call will raise an exception. + */ + public function test_section_unhighlight(string $rolename, bool $expectedexception = false): void { + global $DB; + $this->resetAfterTest(true); + + $generator = $this->getDataGenerator(); + $course = $generator->create_course( + ['numsections' => 4, 'format' => 'topics'], + ['createsections' => true] + ); + + // Highlight section 1. + course_set_marker($course->id, 1); + + $this->enrol_user($course, $rolename); + + $sectionrecords = $DB->get_records('course_sections', ['course' => $course->id], 'section'); + $sectionids = []; + foreach ($sectionrecords as $section) { + $sectionids[] = $section->id; + } + + // Initialise stateupdates. + $courseformat = course_get_format($course->id); + $updates = new stateupdates($courseformat); + + // The section_unhighlight accepts extra sections to refresh the state data. + $methodparam = [ + $sectionids[3], + $sectionids[4], + ]; + + if ($expectedexception) { + $this->expectException(moodle_exception::class); + } + + // Execute given method. + $actions = new stateactions(); + $actions->section_unhighlight( + $updates, + $course, + $methodparam + ); + + // The Unhilight mutation always return the previous highlighted + // section (1) and all the extra sections passed (3, and 4) to ensure + // all of them are updated. + $returnedsectionnumbers = [1, 3, 4]; + + // Check state returned after executing given action. + $updatelist = $updates->jsonSerialize(); + $this->assertCount(3, $updatelist); + foreach ($updatelist as $update) { + $this->assertEquals('section', $update->name); + $this->assertEquals('put', $update->action); + $this->assertContains($update->fields->number, $returnedsectionnumbers); + $this->assertEquals(0, $update->fields->current); + } + + // Check DB sections. + $this->assertEquals(0, $DB->get_field("course", "marker", ['id' => $course->id])); + } + + /** + * Data provider for basic role tests. + * + * @return array the testing scenarios + */ + public function basic_role_provider(): array { + return [ + 'admin' => [ + 'role' => 'admin', + 'expectedexception' => false, + ], + 'editingteacher' => [ + 'role' => 'editingteacher', + 'expectedexception' => false, + ], + 'teacher' => [ + 'role' => 'teacher', + 'expectedexception' => true, + ], + 'student' => [ + 'role' => 'student', + 'expectedexception' => true, + ], + 'guest' => [ + 'role' => 'guest', + 'expectedexception' => true, + ], + 'unenroled' => [ + 'role' => 'unenroled', + 'expectedexception' => true, + ], + ]; + } +} diff --git a/course/format/upgrade.txt b/course/format/upgrade.txt index 38220c87823..7cefdb0a0e2 100644 --- a/course/format/upgrade.txt +++ b/course/format/upgrade.txt @@ -2,6 +2,9 @@ This files describes API changes for course formats Overview of this plugin type at http://docs.moodle.org/dev/Course_formats +=== 4.2 === +* New core_courseformat\base::get_context() to get the course context directly from the format instance. + === 4.1 === * New \core_courseformat\stateupdates methods add_section_remove() and add_cm_remove() have been added to replace the deprecated methods add_section_delete() and add_cm_delete().