diff --git a/course/amd/build/actions.min.js b/course/amd/build/actions.min.js index c25032540a3..b6a2862d863 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_copy_to_clipboard","core/modal_save_cancel","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core_course/events"],(function($,ajax,templates,notification,str,url,Y,ModalCopyToClipboard,ModalSaveCancel,ModalEvents,KeyCodes,log,editor,EventDispatcher,CourseEvents){const componentActions=["moveSection","moveCm","addSection","deleteSection","cmDelete","cmDuplicate","sectionHide","sectionShow","cmHide","cmShow","cmStealth","sectionHighlight","sectionUnhighlight","cmMoveRight","cmMoveLeft","cmNoGroups","cmVisibleGroups","cmSeparateGroups"],courseeditor=editor.getCurrentCourseEditor();let formatname;var CSS_EDITINPROGRESS="editinprogress",CSS_EDITINGMOVE="editing_move",SELECTOR={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",SECTIONACTIONMENUTRIGGER:".section-actions",SECTIONITEM:'[data-for="section_title"]',ADDSECTIONS:".changenumsections [data-add-sections]",SECTIONBADGES:'[data-region="sectionbadges"]'};Y.use("moodle-course-coursebase",(function(){var courseformatselector=M.course.format.get_section_selector();courseformatselector&&(SELECTOR.SECTIONLI=courseformatselector)}));const dispatchEvent=function(eventName,detail,container,options){return container instanceof Element||void 0===container.get||(container=container.get(0)),EventDispatcher.dispatchEvent(eventName,detail,container,options)};var getModuleId=function(element){const item=element.get(0);if(item.dataset.id)return item.dataset.id;let id;return Y.use("moodle-course-util",(function(Y){id=Y.Moodle.core_course.util.cm.getId(Y.Node(item))})),id},addActivitySpinner=function(activity){activity.addClass(CSS_EDITINPROGRESS);var actionarea=activity.find(SELECTOR.ACTIONAREA).get(0);if(actionarea){var spinner=M.util.add_spinner(Y,Y.Node(actionarea));return spinner.show(),void 0!==activity.data("id")&&courseeditor.dispatch("cmLock",[activity.data("id")],!0),spinner}return null},addSectionSpinner=function(sectionelement){sectionelement.addClass(CSS_EDITINPROGRESS);var actionarea=sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);if(actionarea){var spinner=M.util.add_spinner(Y,Y.Node(actionarea));return spinner.show(),void 0!==sectionelement.data("id")&&courseeditor.dispatch("sectionLock",[sectionelement.data("id")],!0),spinner}return null},addSectionLightbox=function(sectionelement){const item=sectionelement.get(0);var lightbox=M.util.add_lightbox(Y,Y.Node(item));return"section"==item.dataset.for&&item.dataset.id&&(courseeditor.dispatch("sectionLock",[item.dataset.id],!0),lightbox.setAttribute("data-state","section"),lightbox.setAttribute("data-state-id",item.dataset.id)),lightbox.show(),lightbox},removeSpinner=function(element,spinner,delay){window.setTimeout((function(){if(element.removeClass(CSS_EDITINPROGRESS),spinner&&spinner.hide(),void 0!==element.data("id")){const mutation="section"===element.data("for")?"sectionLock":"cmLock";courseeditor.dispatch(mutation,[element.data("id")],!1)}}),delay)},removeLightbox=function(lightbox,delay){lightbox&&window.setTimeout((function(){lightbox.hide(),lightbox.getAttribute("data-state")&&courseeditor.dispatch("".concat(lightbox.getAttribute("data-state"),"Lock"),[lightbox.getAttribute("data-state-id")],!1)}),delay)},initActionMenu=function(elementid){Y.use("moodle-course-coursebase",(function(){M.course.coursebase.invoke_function("setup_for_resource","#"+elementid)})),M.core.actionmenu&&M.core.actionmenu.newDOMNode&&M.core.actionmenu.newDOMNode(Y.one("#"+elementid))},editModule=function(moduleElement,cmid,target){var lightbox,action=target.attr("data-action"),spinner=addActivitySpinner(moduleElement),promises=ajax.call([{methodname:"core_course_edit_module",args:{id:cmid,action:action,sectionreturn:target.attr("data-sectionreturn")?target.attr("data-sectionreturn"):null}}],!0);"duplicate"===action&&(lightbox=addSectionLightbox(target.closest(SELECTOR.SECTIONLI))),$.when.apply($,promises).done((function(data){var mainElement,tabables,isInside,foundElement,elementToFocus=(mainElement=moduleElement,tabables=$("a:visible"),isInside=!1,foundElement=null,tabables.each((function(){if($.contains(mainElement[0],this))isInside=!0;else if(isInside)return foundElement=this,!1;return!0})),foundElement);moduleElement.replaceWith(data);let affectedids=[];$("
"+data+"
").find(SELECTOR.ACTIVITYLI).each((function(index){initActionMenu($(this).attr("id")),0===index&&(!function(elementId,action){var mainelement=$("#"+elementId),selector="[data-action="+action+"]";"groupsseparate"!==action&&"groupsvisible"!==action&&"groupsnone"!==action||(selector="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"),mainelement.find(selector).is(":visible")?mainelement.find(selector).focus():mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus()}($(this).attr("id"),action),elementToFocus=null),affectedids.push(getModuleId($(this)))})),elementToFocus&&elementToFocus.focus(),removeSpinner(moduleElement,spinner,400),removeLightbox(lightbox,400),moduleElement.trigger($.Event("coursemoduleedited",{ajaxreturn:data,action:action})),courseeditor.dispatch("legacyActivityAction",action,cmid,affectedids)})).fail((function(ex){removeSpinner(moduleElement,spinner),removeLightbox(lightbox);var e=$.Event("coursemoduleeditfailed",{exception:ex,action:action});moduleElement.trigger(e),e.isDefaultPrevented()||notification.exception(ex)}))},refreshModule=function(element,cmid,sectionreturn){void 0===sectionreturn&&(sectionreturn=courseeditor.sectionReturn);const activityElement=$(element);var spinner=addActivitySpinner(activityElement),promises=ajax.call([{methodname:"core_course_get_module",args:{id:cmid,sectionreturn:sectionreturn}}],!0);return new Promise(((resolve,reject)=>{$.when.apply($,promises).done((function(data){removeSpinner(activityElement,spinner,400),replaceActivityHtmlWith(data),resolve(data)})).fail((function(){removeSpinner(activityElement,spinner),reject()}))}))},confirmDeleteModule=function(mainelement,onconfirm){var modtypename=mainelement.attr("class").match(/modtype_([^\s]*)/)[1],modulename=function(element){var name;Y.use("moodle-course-util",(function(Y){name=Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)))}));const state=courseeditor.state,cmid=getModuleId(element);var _state$cm$get;return!name&&state&&cmid&&(name=null===(_state$cm$get=state.cm.get(cmid))||void 0===_state$cm$get?void 0:_state$cm$get.name),name}(mainelement);str.get_string("pluginname",modtypename).done((function(pluginname){var plugindata={type:pluginname,name:modulename};str.get_strings([{key:"confirm",component:"core"},{key:null===modulename?"deletechecktype":"deletechecktypename",param:plugindata},{key:"yes"},{key:"no"}]).done((function(s){notification.confirm(s[0],s[1],s[2],s[3],onconfirm)}))}))},replaceActionItem=function(actionitem,image,stringname,stringcomponent,newaction){var stringRequests=[{key:stringname,component:stringcomponent}];return str.get_strings(stringRequests).then((function(strings){return actionitem.find("span.menu-action-text").html(strings[0]),templates.renderPix(image,"core")})).then((function(pixhtml){actionitem.find(".icon").replaceWith(pixhtml),actionitem.attr("data-action",newaction)})).catch(notification.exception)},defaultEditSectionHandler=function(sectionElement,actionItem,data,courseformat,sectionid){var action=actionItem.attr("data-action");if("hide"===action||"show"===action){if("hide"===action?(sectionElement.addClass("hidden"),setSectionBadge(sectionElement[0],"hiddenfromstudents",!0,!1),replaceActionItem(actionItem,"i/show","showfromothers","format_"+courseformat,"show")):(setSectionBadge(sectionElement[0],"hiddenfromstudents",!1,!1),sectionElement.removeClass("hidden"),replaceActionItem(actionItem,"i/hide","hidefromothers","format_"+courseformat,"hide")),void 0!==data.modules)for(var i in data.modules)replaceActivityHtmlWith(data.modules[i]);void 0!==data.section_availability&§ionElement.find(".section_availability").first().replaceWith(data.section_availability);void 0!==courseeditor.state.section.get(sectionid)&&courseeditor.dispatch("sectionState",[sectionid])}else if("setmarker"===action){var oldmarker=$(SELECTOR.SECTIONLI+".current"),oldActionItem=oldmarker.find(SELECTOR.SECTIONACTIONMENU+" a[data-action=removemarker]");oldmarker.removeClass("current"),replaceActionItem(oldActionItem,"i/marker","highlight","core","setmarker"),sectionElement.addClass("current"),replaceActionItem(actionItem,"i/marked","highlightoff","core","removemarker"),courseeditor.dispatch("legacySectionAction",action,sectionid),setSectionBadge(sectionElement[0],"iscurrent",!0,!0)}else"removemarker"===action&&(sectionElement.removeClass("current"),replaceActionItem(actionItem,"i/marker","highlight","core","setmarker"),courseeditor.dispatch("legacySectionAction",action,sectionid),setSectionBadge(sectionElement[0],"iscurrent",!1,!0))};var replaceActivityHtmlWith=function(activityHTML){$("
"+activityHTML+"
").find(SELECTOR.ACTIVITYLI).each((function(){var id=$(this).attr("id");let focusedPath=function(id){const element=document.getElementById(id);if(element&&element.contains(document.activeElement))return element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)?"".concat(SELECTOR.ACTIONAREA,' [tabindex="0"]'):document.activeElement.id?"#".concat(document.activeElement.id):void 0}(id);if($(SELECTOR.ACTIVITYLI+"#"+id).replaceWith(activityHTML),initActionMenu(id),focusedPath){var _newItem$querySelecto;null===(_newItem$querySelecto=document.getElementById(id).querySelector(focusedPath))||void 0===_newItem$querySelecto||_newItem$querySelecto.focus()}}))},editSection=function(sectionElement,sectionid,target,courseformat){var action=target.attr("data-action"),sectionreturn=target.attr("data-sectionreturn")?target.attr("data-sectionreturn"):null;if(courseeditor.supportComponents&&componentActions.includes(action))return!1;var spinner=addSectionSpinner(sectionElement),promises=ajax.call([{methodname:"core_course_edit_section",args:{id:sectionid,action:action,sectionreturn:sectionreturn}}],!0),lightbox=addSectionLightbox(sectionElement);return $.when.apply($,promises).done((function(dataencoded){var data=$.parseJSON(dataencoded);removeSpinner(sectionElement,spinner),removeLightbox(lightbox),sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();var e=$.Event("coursesectionedited",{ajaxreturn:data,action:action});sectionElement.trigger(e),e.isDefaultPrevented()||defaultEditSectionHandler(sectionElement,target,data,courseformat,sectionid)})).fail((function(ex){removeSpinner(sectionElement,spinner),removeLightbox(lightbox);var e=$.Event("coursesectioneditfailed",{exception:ex,action:action});sectionElement.trigger(e),e.isDefaultPrevented()||notification.exception(ex)})),!0},setSectionBadge=function(sectionElement,badgetype,add,removeOther){const sectionbadges=sectionElement.querySelector(SELECTOR.SECTIONBADGES);if(!sectionbadges)return;const badge=sectionbadges.querySelector('[data-type="'+badgetype+'"]');badge&&(add?(removeOther&&document.querySelectorAll('[data-type="'+badgetype+'"]').forEach((b=>{b.classList.add("d-none")})),badge.classList.remove("d-none")):badge.classList.add("d-none"))};return Y.use("moodle-course-coursebase",(function(){M.course.coursebase.register_module({set_visibility_resource_ui:function(args){var mainelement=$(args.element.getDOMNode()),cmid=getModuleId(mainelement);if(cmid){var sectionreturn=mainelement.find("."+CSS_EDITINGMOVE).attr("data-sectionreturn");refreshModule(mainelement,cmid,sectionreturn)}},updateMovedCmState:params=>{const cm=courseeditor.state.cm.get(params.cmid);void 0!==cm&&courseeditor.dispatch("sectionState",[cm.sectionid]),courseeditor.dispatch("cmState",[params.cmid])},updateMovedSectionState:()=>{courseeditor.dispatch("courseState")}})})),courseeditor.addMutations({legacyActivityAction:function(statemanager,action,cmid,affectedids){const state=statemanager.state,cm=state.cm.get(cmid);if(void 0===cm)return;const section=state.section.get(cm.sectionid);if(void 0!==section){switch(courseeditor.dispatch("cmLock",[cm.id],!0),statemanager.setReadOnly(!1),cm.locked=!1,action){case"delete":section.cmlist=section.cmlist.reduce(((cmlist,current)=>(current!=cmid&&cmlist.push(current),cmlist)),[]),state.cm.delete(cmid);break;case"hide":case"show":case"duplicate":courseeditor.dispatch("cmState",affectedids)}statemanager.setReadOnly(!0)}},legacySectionAction:function(statemanager,action,sectionid){const state=statemanager.state,section=state.section.get(sectionid);if(void 0!==section){switch(statemanager.setReadOnly(!1),section.locked=!0,statemanager.setReadOnly(!0),statemanager.setReadOnly(!1),section.locked=!1,action){case"setmarker":state.section.forEach((current=>{current.id!=sectionid&&(current.current=!1)})),section.current=!0;break;case"removemarker":section.current=!1}statemanager.setReadOnly(!0)}}}),{initCoursePage:function(courseformat){if(formatname=courseformat,$("body").on("click keypress",SELECTOR.ACTIVITYLI+" "+SELECTOR.ACTIVITYACTION+"[data-action]",(function(e){if("keypress"!==e.type||13===e.keyCode){var actionItem=$(this),moduleElement=actionItem.closest(SELECTOR.ACTIVITYLI),action=actionItem.attr("data-action"),moduleId=getModuleId(moduleElement);switch(action){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return}moduleId&&(e.preventDefault(),"delete"===action?confirmDeleteModule(moduleElement,(function(){editModule(moduleElement,moduleId,actionItem)})):editModule(moduleElement,moduleId,actionItem))}})),$("body").on("click keypress",SELECTOR.SECTIONACTIONMENUTRIGGER+"[data-sectionid] a[data-action]",(function(e){if("keypress"===e.type&&13!==e.keyCode)return;var actionItem=$(this),sectionElement=actionItem.closest(SELECTOR.SECTIONLI),sectionId=actionItem.closest(SELECTOR.SECTIONACTIONMENUTRIGGER).attr("data-sectionid");if("permalink"===actionItem.attr("data-action"))return e.preventDefault(),void ModalCopyToClipboard.create({text:actionItem.attr("href")},str.get_string("sectionlink","course"));let isExecuted=!0;var message,onconfirm;actionItem.attr("data-confirm")?(message=actionItem.attr("data-confirm"),onconfirm=function(){isExecuted=editSection(sectionElement,sectionId,actionItem,courseformat)},str.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done((function(s){notification.confirm(s[0],message,s[1],s[2],onconfirm)}))):isExecuted=editSection(sectionElement,sectionId,actionItem,courseformat),isExecuted&&e.preventDefault()})),$("body").on("updated","".concat(SELECTOR.SECTIONITEM," [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"))return;const trigger=$(SELECTOR.ADDSECTIONS),modalTitle=trigger.attr("data-add-sections"),newSections=trigger.attr("data-new-sections");str.get_string("numberweeks").then((function(strNumberSections){var modalBody=$('
');return modalBody.find("label").html(strNumberSections),modalBody.html()})).then((body=>ModalSaveCancel.create({body:body,title:modalTitle}))).then((function(modal){var numSections=$(modal.getBody()).find("#add_section_numsections"),addSections=function(){""+parseInt(numSections.val())===numSections.val()&&parseInt(numSections.val())>=1&&(document.location=trigger.attr("href")+"&numsections="+parseInt(numSections.val()))};return modal.setSaveButtonText(modalTitle),modal.getRoot().on(ModalEvents.shown,(function(){numSections.focus().select().on("keydown",(function(e){e.keyCode===KeyCodes.enter&&addSections()}))})),modal.getRoot().on(ModalEvents.save,(function(e){e.preventDefault(),addSections()})),trigger.on("click",(e=>{e.preventDefault(),modal.show()})),modal})).catch(notification.exception)},replaceSectionActionItem:function(sectionelement,selector,image,stringname,stringcomponent,newaction){log.debug("replaceSectionActionItem() is deprecated and will be removed.");var actionitem=sectionelement.find(SELECTOR.SECTIONACTIONMENU+" "+selector);replaceActionItem(actionitem,image,stringname,stringcomponent,newaction)},refreshModule:refreshModule,refreshSection:function(element,sectionid,sectionreturn){void 0===sectionreturn&&(sectionreturn=courseeditor.sectionReturn);const sectionElement=$(element),promises=ajax.call([{methodname:"core_course_edit_section",args:{id:sectionid,action:"refresh",sectionreturn:sectionreturn}}],!0);var spinner=addSectionSpinner(sectionElement);return new Promise(((resolve,reject)=>{$.when.apply($,promises).done((dataencoded=>{removeSpinner(sectionElement,spinner);const data=$.parseJSON(dataencoded),newSectionElement=$(data.content);sectionElement.replaceWith(newSectionElement),$("".concat(SELECTOR.SECTIONLI,"#").concat(sectionid," ").concat(SELECTOR.ACTIVITYLI)).each(((index,activity)=>{initActionMenu(activity.data("id"))}));dispatchEvent(CourseEvents.sectionRefreshed,{ajaxreturn:data,action:"refresh",newSectionElement:newSectionElement.get(0)},newSectionElement).defaultPrevented||defaultEditSectionHandler(newSectionElement,$(SELECTOR.SECTIONLI+"#"+sectionid),data,formatname,sectionid),resolve(data)})).fail((ex=>{dispatchEvent("coursesectionrefreshfailed",{exception:ex,action:"refresh"},sectionElement).defaultPrevented||notification.exception(ex),reject()}))}))}}})); +define("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_copy_to_clipboard","core/modal_save_cancel","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor","core/event_dispatcher","core_course/events"],(function($,ajax,templates,notification,str,url,Y,ModalCopyToClipboard,ModalSaveCancel,ModalEvents,KeyCodes,log,editor,EventDispatcher,CourseEvents){const componentActions=["moveSection","moveCm","addSection","deleteSection","cmDelete","cmDuplicate","sectionHide","sectionShow","cmHide","cmShow","cmStealth","sectionHighlight","sectionUnhighlight","cmMoveRight","cmMoveLeft","cmNoGroups","cmVisibleGroups","cmSeparateGroups"],courseeditor=editor.getCurrentCourseEditor();let formatname;var CSS_EDITINPROGRESS="editinprogress",CSS_EDITINGMOVE="editing_move",SELECTOR={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",SECTIONACTIONMENUTRIGGER:".section-actions",SECTIONITEM:'[data-for="section_title"]',ADDSECTIONS:".changenumsections [data-add-sections]",SECTIONBADGES:'[data-region="sectionbadges"]'};Y.use("moodle-course-coursebase",(function(){var courseformatselector=M.course.format.get_section_selector();courseformatselector&&(SELECTOR.SECTIONLI=courseformatselector)}));const dispatchEvent=function(eventName,detail,container,options){return container instanceof Element||void 0===container.get||(container=container.get(0)),EventDispatcher.dispatchEvent(eventName,detail,container,options)};var getModuleId=function(element){const item=element.get(0);if(item.dataset.id)return item.dataset.id;let id;return Y.use("moodle-course-util",(function(Y){id=Y.Moodle.core_course.util.cm.getId(Y.Node(item))})),id},addActivitySpinner=function(activity){activity.addClass(CSS_EDITINPROGRESS);var actionarea=activity.find(SELECTOR.ACTIONAREA).get(0);if(actionarea){var spinner=M.util.add_spinner(Y,Y.Node(actionarea));return spinner.show(),void 0!==activity.data("id")&&courseeditor.dispatch("cmLock",[activity.data("id")],!0),spinner}return null},addSectionSpinner=function(sectionelement){sectionelement.addClass(CSS_EDITINPROGRESS);var actionarea=sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);if(actionarea){var spinner=M.util.add_spinner(Y,Y.Node(actionarea));return spinner.show(),void 0!==sectionelement.data("id")&&courseeditor.dispatch("sectionLock",[sectionelement.data("id")],!0),spinner}return null},addSectionLightbox=function(sectionelement){const item=sectionelement.get(0);var lightbox=M.util.add_lightbox(Y,Y.Node(item));return"section"==item.dataset.for&&item.dataset.id&&(courseeditor.dispatch("sectionLock",[item.dataset.id],!0),lightbox.setAttribute("data-state","section"),lightbox.setAttribute("data-state-id",item.dataset.id)),lightbox.show(),lightbox},removeSpinner=function(element,spinner,delay){window.setTimeout((function(){if(element.removeClass(CSS_EDITINPROGRESS),spinner&&spinner.hide(),void 0!==element.data("id")){const mutation="section"===element.data("for")?"sectionLock":"cmLock";courseeditor.dispatch(mutation,[element.data("id")],!1)}}),delay)},removeLightbox=function(lightbox,delay){lightbox&&window.setTimeout((function(){lightbox.hide(),lightbox.getAttribute("data-state")&&courseeditor.dispatch("".concat(lightbox.getAttribute("data-state"),"Lock"),[lightbox.getAttribute("data-state-id")],!1)}),delay)},initActionMenu=function(elementid){Y.use("moodle-course-coursebase",(function(){M.course.coursebase.invoke_function("setup_for_resource","#"+elementid)})),M.core.actionmenu&&M.core.actionmenu.newDOMNode&&M.core.actionmenu.newDOMNode(Y.one("#"+elementid))},editModule=function(moduleElement,cmid,target){var lightbox,action=target.attr("data-action"),spinner=addActivitySpinner(moduleElement),promises=ajax.call([{methodname:"core_course_edit_module",args:{id:cmid,action:action,sectionreturn:target.attr("data-sectionreturn")?target.attr("data-sectionreturn"):null}}],!0);"duplicate"===action&&(lightbox=addSectionLightbox(target.closest(SELECTOR.SECTIONLI))),$.when.apply($,promises).done((function(data){var mainElement,tabables,isInside,foundElement,elementToFocus=(mainElement=moduleElement,tabables=$("a:visible"),isInside=!1,foundElement=null,tabables.each((function(){if($.contains(mainElement[0],this))isInside=!0;else if(isInside)return foundElement=this,!1;return!0})),foundElement);moduleElement.replaceWith(data);let affectedids=[];$("
"+data+"
").find(SELECTOR.ACTIVITYLI).each((function(index){initActionMenu($(this).attr("id")),0===index&&(!function(elementId,action){var mainelement=$("#"+elementId),selector="[data-action="+action+"]";"groupsseparate"!==action&&"groupsvisible"!==action&&"groupsnone"!==action||(selector="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"),mainelement.find(selector).is(":visible")?mainelement.find(selector).focus():mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus()}($(this).attr("id"),action),elementToFocus=null),affectedids.push(getModuleId($(this)))})),elementToFocus&&elementToFocus.focus(),removeSpinner(moduleElement,spinner,400),removeLightbox(lightbox,400),moduleElement.trigger($.Event("coursemoduleedited",{ajaxreturn:data,action:action})),courseeditor.dispatch("legacyActivityAction",action,cmid,affectedids)})).fail((function(ex){removeSpinner(moduleElement,spinner),removeLightbox(lightbox);var e=$.Event("coursemoduleeditfailed",{exception:ex,action:action});moduleElement.trigger(e),e.isDefaultPrevented()||notification.exception(ex)}))},refreshModule=function(element,cmid,sectionreturn){void 0===sectionreturn&&(sectionreturn=courseeditor.sectionReturn);const activityElement=$(element);var spinner=addActivitySpinner(activityElement),promises=ajax.call([{methodname:"core_course_get_module",args:{id:cmid,sectionreturn:sectionreturn}}],!0);return new Promise(((resolve,reject)=>{$.when.apply($,promises).done((function(data){removeSpinner(activityElement,spinner,400),replaceActivityHtmlWith(data),resolve(data)})).fail((function(){removeSpinner(activityElement,spinner),reject()}))}))},confirmDeleteModule=function(mainelement,onconfirm){var modtypename=mainelement.attr("class").match(/modtype_([^\s]*)/)[1],modulename=function(element){var name;Y.use("moodle-course-util",(function(Y){name=Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)))}));const state=courseeditor.state,cmid=getModuleId(element);var _state$cm$get;return!name&&state&&cmid&&(name=null===(_state$cm$get=state.cm.get(cmid))||void 0===_state$cm$get?void 0:_state$cm$get.name),name}(mainelement);str.get_string("pluginname",modtypename).done((function(pluginname){var plugindata={type:pluginname,name:modulename};str.get_strings([{key:"confirm",component:"core"},{key:null===modulename?"deletechecktype":"deletechecktypename",param:plugindata},{key:"yes"},{key:"no"}]).done((function(s){notification.confirm(s[0],s[1],s[2],s[3],onconfirm)}))}))},replaceActionItem=function(actionitem,image,stringname,stringcomponent,newaction){var stringRequests=[{key:stringname,component:stringcomponent}];return str.get_strings(stringRequests).then((function(strings){return actionitem.find("span.menu-action-text").html(strings[0]),templates.renderPix(image,"core")})).then((function(pixhtml){actionitem.find(".icon").replaceWith(pixhtml),actionitem.attr("data-action",newaction)})).catch(notification.exception)},defaultEditSectionHandler=function(sectionElement,actionItem,data,courseformat,sectionid){var action=actionItem.attr("data-action");if("hide"===action||"show"===action){if("hide"===action?(sectionElement.addClass("hidden"),setSectionBadge(sectionElement[0],"hiddenfromstudents",!0,!1),replaceActionItem(actionItem,"i/show","showfromothers","format_"+courseformat,"show")):(setSectionBadge(sectionElement[0],"hiddenfromstudents",!1,!1),sectionElement.removeClass("hidden"),replaceActionItem(actionItem,"i/hide","hidefromothers","format_"+courseformat,"hide")),void 0!==data.modules)for(var i in data.modules)replaceActivityHtmlWith(data.modules[i]);void 0!==data.section_availability&§ionElement.find(".section_availability").first().replaceWith(data.section_availability);void 0!==courseeditor.state.section.get(sectionid)&&courseeditor.dispatch("sectionState",[sectionid])}else if("setmarker"===action){var oldmarker=$(SELECTOR.SECTIONLI+".current"),oldActionItem=oldmarker.find(SELECTOR.SECTIONACTIONMENU+" a[data-action=removemarker]");oldmarker.removeClass("current"),replaceActionItem(oldActionItem,"i/marker","highlight","core","setmarker"),sectionElement.addClass("current"),replaceActionItem(actionItem,"i/marked","highlightoff","core","removemarker"),courseeditor.dispatch("legacySectionAction",action,sectionid),setSectionBadge(sectionElement[0],"iscurrent",!0,!0)}else"removemarker"===action&&(sectionElement.removeClass("current"),replaceActionItem(actionItem,"i/marker","highlight","core","setmarker"),courseeditor.dispatch("legacySectionAction",action,sectionid),setSectionBadge(sectionElement[0],"iscurrent",!1,!0))};var replaceActivityHtmlWith=function(activityHTML){$("
"+activityHTML+"
").find(SELECTOR.ACTIVITYLI).each((function(){var id=$(this).attr("id");let focusedPath=function(id){const element=document.getElementById(id);if(element&&element.contains(document.activeElement))return element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)?"".concat(SELECTOR.ACTIONAREA,' [tabindex="0"]'):document.activeElement.id?"#".concat(document.activeElement.id):void 0}(id);if($(SELECTOR.ACTIVITYLI+"#"+id).replaceWith(activityHTML),initActionMenu(id),focusedPath){var _newItem$querySelecto;null===(_newItem$querySelecto=document.getElementById(id).querySelector(focusedPath))||void 0===_newItem$querySelecto||_newItem$querySelecto.focus()}}))},editSection=function(sectionElement,sectionid,target,courseformat){var action=target.attr("data-action"),sectionreturn=target.attr("data-sectionreturn")?target.attr("data-sectionreturn"):null;if(courseeditor.supportComponents&&componentActions.includes(action))return!1;var spinner=addSectionSpinner(sectionElement),promises=ajax.call([{methodname:"core_course_edit_section",args:{id:sectionid,action:action,sectionreturn:sectionreturn}}],!0),lightbox=addSectionLightbox(sectionElement);return $.when.apply($,promises).done((function(dataencoded){var data=$.parseJSON(dataencoded);removeSpinner(sectionElement,spinner),removeLightbox(lightbox),sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();var e=$.Event("coursesectionedited",{ajaxreturn:data,action:action});sectionElement.trigger(e),e.isDefaultPrevented()||defaultEditSectionHandler(sectionElement,target,data,courseformat,sectionid)})).fail((function(ex){removeSpinner(sectionElement,spinner),removeLightbox(lightbox);var e=$.Event("coursesectioneditfailed",{exception:ex,action:action});sectionElement.trigger(e),e.isDefaultPrevented()||notification.exception(ex)})),!0},setSectionBadge=function(sectionElement,badgetype,add,removeOther){const sectionbadges=sectionElement.querySelector(SELECTOR.SECTIONBADGES);if(!sectionbadges)return;const badge=sectionbadges.querySelector('[data-type="'+badgetype+'"]');badge&&(add?(removeOther&&document.querySelectorAll('[data-type="'+badgetype+'"]').forEach((b=>{b.classList.add("d-none")})),badge.classList.remove("d-none")):badge.classList.add("d-none"))};return Y.use("moodle-course-coursebase",(function(){M.course.coursebase.register_module({set_visibility_resource_ui:function(args){var mainelement=$(args.element.getDOMNode()),cmid=getModuleId(mainelement);if(cmid){var sectionreturn=mainelement.find("."+CSS_EDITINGMOVE).attr("data-sectionreturn");refreshModule(mainelement,cmid,sectionreturn)}},updateMovedCmState:params=>{const cm=courseeditor.state.cm.get(params.cmid);void 0!==cm&&courseeditor.dispatch("sectionState",[cm.sectionid]),courseeditor.dispatch("cmState",[params.cmid])},updateMovedSectionState:()=>{courseeditor.dispatch("courseState")}})})),courseeditor.addMutations({legacyActivityAction:function(statemanager,action,cmid,affectedids){const state=statemanager.state,cm=state.cm.get(cmid);if(void 0===cm)return;const section=state.section.get(cm.sectionid);if(void 0!==section){switch(courseeditor.dispatch("cmLock",[cm.id],!0),statemanager.setReadOnly(!1),cm.locked=!1,action){case"delete":section.cmlist=section.cmlist.reduce(((cmlist,current)=>(current!=cmid&&cmlist.push(current),cmlist)),[]),state.cm.delete(cmid);break;case"hide":case"show":case"duplicate":courseeditor.dispatch("cmState",affectedids)}statemanager.setReadOnly(!0)}},legacySectionAction:function(statemanager,action,sectionid){const state=statemanager.state,section=state.section.get(sectionid);if(void 0!==section){switch(statemanager.setReadOnly(!1),section.locked=!0,statemanager.setReadOnly(!0),statemanager.setReadOnly(!1),section.locked=!1,action){case"setmarker":state.section.forEach((current=>{current.id!=sectionid&&(current.current=!1)})),section.current=!0;break;case"removemarker":section.current=!1}statemanager.setReadOnly(!0)}}}),{initCoursePage:function(courseformat){if(formatname=courseformat,$("body").on("click keypress",SELECTOR.ACTIVITYLI+" "+SELECTOR.ACTIVITYACTION+"[data-action]",(function(e){if("keypress"!==e.type||13===e.keyCode){var actionItem=$(this),moduleElement=actionItem.closest(SELECTOR.ACTIVITYLI),action=actionItem.attr("data-action"),moduleId=getModuleId(moduleElement);switch(action){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return}moduleId&&(e.preventDefault(),"delete"===action?confirmDeleteModule(moduleElement,(function(){editModule(moduleElement,moduleId,actionItem)})):editModule(moduleElement,moduleId,actionItem))}})),$("body").on("click keypress",SELECTOR.SECTIONACTIONMENUTRIGGER+"[data-sectionid] a[data-action]",(function(e){if("keypress"===e.type&&13!==e.keyCode)return;var actionItem=$(this),sectionElement=actionItem.closest(SELECTOR.SECTIONLI),sectionId=actionItem.closest(SELECTOR.SECTIONACTIONMENUTRIGGER).attr("data-sectionid");if("permalink"===actionItem.attr("data-action"))return e.preventDefault(),void ModalCopyToClipboard.create({text:actionItem.attr("href")},str.get_string("sectionlink","course"));let isExecuted=!0;var message,onconfirm;actionItem.attr("data-confirm")?(message=actionItem.attr("data-confirm"),onconfirm=function(){isExecuted=editSection(sectionElement,sectionId,actionItem,courseformat)},str.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done((function(s){notification.confirm(s[0],message,s[1],s[2],onconfirm)}))):isExecuted=editSection(sectionElement,sectionId,actionItem,courseformat),isExecuted&&e.preventDefault()})),$("body").on("updated","".concat(SELECTOR.SECTIONITEM," [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-itemtype="activityname"][data-inplaceeditable]'),(function(e){e.ajaxreturn&&e.ajaxreturn.itemid&&courseeditor.dispatch("cmState",[e.ajaxreturn.itemid])})),courseeditor.supportComponents&&componentActions.includes("addSection"))return;const trigger=$(SELECTOR.ADDSECTIONS),modalTitle=trigger.attr("data-add-sections"),newSections=trigger.attr("data-new-sections");str.get_string("numberweeks").then((function(strNumberSections){var modalBody=$('
');return modalBody.find("label").html(strNumberSections),modalBody.html()})).then((body=>ModalSaveCancel.create({body:body,title:modalTitle}))).then((function(modal){var numSections=$(modal.getBody()).find("#add_section_numsections"),addSections=function(){""+parseInt(numSections.val())===numSections.val()&&parseInt(numSections.val())>=1&&(document.location=trigger.attr("href")+"&numsections="+parseInt(numSections.val()))};return modal.setSaveButtonText(modalTitle),modal.getRoot().on(ModalEvents.shown,(function(){numSections.focus().select().on("keydown",(function(e){e.keyCode===KeyCodes.enter&&addSections()}))})),modal.getRoot().on(ModalEvents.save,(function(e){e.preventDefault(),addSections()})),trigger.on("click",(e=>{e.preventDefault(),modal.show()})),modal})).catch(notification.exception)},replaceSectionActionItem:function(sectionelement,selector,image,stringname,stringcomponent,newaction){log.debug("replaceSectionActionItem() is deprecated and will be removed.");var actionitem=sectionelement.find(SELECTOR.SECTIONACTIONMENU+" "+selector);replaceActionItem(actionitem,image,stringname,stringcomponent,newaction)},refreshModule:refreshModule,refreshSection:function(element,sectionid,sectionreturn){void 0===sectionreturn&&(sectionreturn=courseeditor.sectionReturn);const sectionElement=$(element),promises=ajax.call([{methodname:"core_course_edit_section",args:{id:sectionid,action:"refresh",sectionreturn:sectionreturn}}],!0);var spinner=addSectionSpinner(sectionElement);return new Promise(((resolve,reject)=>{$.when.apply($,promises).done((dataencoded=>{removeSpinner(sectionElement,spinner);const data=$.parseJSON(dataencoded),newSectionElement=$(data.content);sectionElement.replaceWith(newSectionElement),$("".concat(SELECTOR.SECTIONLI,"#").concat(sectionid," ").concat(SELECTOR.ACTIVITYLI)).each(((index,activity)=>{initActionMenu(activity.data("id"))}));dispatchEvent(CourseEvents.sectionRefreshed,{ajaxreturn:data,action:"refresh",newSectionElement:newSectionElement.get(0)},newSectionElement).defaultPrevented||defaultEditSectionHandler(newSectionElement,$(SELECTOR.SECTIONLI+"#"+sectionid),data,formatname,sectionid),resolve(data)})).fail((ex=>{dispatchEvent("coursesectionrefreshfailed",{exception:ex,action:"refresh"},sectionElement).defaultPrevented||notification.exception(ex),reject()}))}))}}})); //# sourceMappingURL=actions.min.js.map \ No newline at end of file diff --git a/course/amd/build/actions.min.js.map b/course/amd/build/actions.min.js.map index a7cf51b9e70..9a7d8152d76 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_copy_to_clipboard',\n 'core/modal_save_cancel',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalCopyToClipboard,\n ModalSaveCancel,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n 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', 'cmDelete', 'cmDuplicate', 'sectionHide', 'sectionShow',\n 'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight', 'cmMoveRight', 'cmMoveLeft',\n 'cmNoGroups', 'cmVisibleGroups', 'cmSeparateGroups',\n ];\n\n // The course reactive instance.\n const courseeditor = editor.getCurrentCourseEditor();\n\n // The current course format name (loaded on init).\n let formatname;\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n SECTIONACTIONMENUTRIGGER: '.section-actions',\n SECTIONITEM: '[data-for=\"section_title\"]',\n ADDSECTIONS: '.changenumsections [data-add-sections]',\n SECTIONBADGES: '[data-region=\"sectionbadges\"]',\n };\n\n Y.use('moodle-course-coursebase', function() {\n var courseformatselector = M.course.format.get_section_selector();\n if (courseformatselector) {\n SELECTOR.SECTIONLI = courseformatselector;\n }\n });\n\n /**\n * Dispatch event wrapper.\n *\n * Old jQuery events will be replaced by native events gradually.\n *\n * @method dispatchEvent\n * @param {String} eventName The name of the event\n * @param {Object} detail Any additional details to pass into the eveent\n * @param {Node|HTMLElement} container The point at which to dispatch the event\n * @param {Object} options\n * @param {Boolean} options.bubbles Whether to bubble up the DOM\n * @param {Boolean} options.cancelable Whether preventDefault() can be called\n * @param {Boolean} options.composed Whether the event can bubble across the ShadowDOM boundary\n * @returns {CustomEvent}\n */\n const dispatchEvent = function(eventName, detail, container, options) {\n // Most actions still uses jQuery node instead of regular HTMLElement.\n if (!(container instanceof Element) && container.get !== undefined) {\n container = container.get(0);\n }\n return EventDispatcher.dispatchEvent(eventName, detail, container, options);\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getId\n *\n * @param {JQuery} element\n * @returns {Integer}\n */\n var getModuleId = function(element) {\n // Check if we have a data-id first.\n const item = element.get(0);\n if (item.dataset.id) {\n return item.dataset.id;\n }\n // Use YUI way if data-id is not present.\n let id;\n Y.use('moodle-course-util', function(Y) {\n id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));\n });\n return id;\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getName\n *\n * @param {JQuery} element\n * @returns {String}\n */\n var getModuleName = function(element) {\n var name;\n Y.use('moodle-course-util', function(Y) {\n name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));\n });\n // Check if we have the name in the course state.\n const state = courseeditor.state;\n const cmid = getModuleId(element);\n if (!name && state && cmid) {\n name = state.cm.get(cmid)?.name;\n }\n return name;\n };\n\n /**\n * Wrapper for M.util.add_spinner for an activity\n *\n * @param {JQuery} activity\n * @returns {Node}\n */\n var addActivitySpinner = function(activity) {\n activity.addClass(CSS.EDITINPROGRESS);\n var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the activity state element.\n if (activity.data('id') !== undefined) {\n courseeditor.dispatch('cmLock', [activity.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_spinner for a section\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionSpinner = function(sectionelement) {\n sectionelement.addClass(CSS.EDITINPROGRESS);\n var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the section state element.\n if (sectionelement.data('id') !== undefined) {\n courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_lightbox\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionLightbox = function(sectionelement) {\n const item = sectionelement.get(0);\n var lightbox = M.util.add_lightbox(Y, Y.Node(item));\n if (item.dataset.for == 'section' && item.dataset.id) {\n courseeditor.dispatch('sectionLock', [item.dataset.id], true);\n lightbox.setAttribute('data-state', 'section');\n lightbox.setAttribute('data-state-id', item.dataset.id);\n }\n lightbox.show();\n return lightbox;\n };\n\n /**\n * Removes the spinner element\n *\n * @param {JQuery} element\n * @param {Node} spinner\n * @param {Number} delay\n */\n var removeSpinner = function(element, spinner, delay) {\n window.setTimeout(function() {\n element.removeClass(CSS.EDITINPROGRESS);\n if (spinner) {\n spinner.hide();\n }\n // Unlock the state element.\n if (element.data('id') !== undefined) {\n const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';\n courseeditor.dispatch(mutation, [element.data('id')], false);\n }\n }, delay);\n };\n\n /**\n * Removes the lightbox element\n *\n * @param {Node} lightbox lighbox YUI element returned by addSectionLightbox\n * @param {Number} delay\n */\n var removeLightbox = function(lightbox, delay) {\n if (lightbox) {\n window.setTimeout(function() {\n lightbox.hide();\n // Unlock state if necessary.\n if (lightbox.getAttribute('data-state')) {\n courseeditor.dispatch(\n `${lightbox.getAttribute('data-state')}Lock`,\n [lightbox.getAttribute('data-state-id')],\n false\n );\n }\n }, delay);\n }\n };\n\n /**\n * Initialise action menu for the element (section or module)\n *\n * @param {String} elementid CSS id attribute of the element\n */\n var initActionMenu = function(elementid) {\n // Initialise action menu in the new activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);\n });\n if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {\n M.core.actionmenu.newDOMNode(Y.one('#' + elementid));\n }\n };\n\n /**\n * Returns focus to the element that was clicked or \"Edit\" link if element is no longer visible.\n *\n * @param {String} elementId CSS id attribute of the element\n * @param {String} action data-action property of the element that was clicked\n */\n var focusActionItem = function(elementId, action) {\n var mainelement = $('#' + elementId);\n var selector = '[data-action=' + action + ']';\n if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {\n // New element will have different data-action.\n selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';\n }\n if (mainelement.find(selector).is(':visible')) {\n mainelement.find(selector).focus();\n } else {\n // Element not visible, focus the \"Edit\" link.\n mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();\n }\n };\n\n /**\n * Find next after the element\n *\n * @param {JQuery} mainElement element that is about to be deleted\n * @returns {JQuery}\n */\n var findNextFocusable = function(mainElement) {\n var tabables = $(\"a:visible\");\n var isInside = false;\n var foundElement = null;\n tabables.each(function() {\n if ($.contains(mainElement[0], this)) {\n isInside = true;\n } else if (isInside) {\n foundElement = this;\n return false; // Returning false in .each() is equivalent to \"break;\" inside the loop in php.\n }\n return true;\n });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : null\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('
' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const activityElement = $(element);\n var spinner = addActivitySpinner(activityElement);\n var promises = ajax.call([{\n methodname: 'core_course_get_module',\n args: {id: cmid, sectionreturn: sectionreturn}\n }], true);\n\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(function(data) {\n removeSpinner(activityElement, spinner, 400);\n replaceActivityHtmlWith(data);\n resolve(data);\n }).fail(function() {\n removeSpinner(activityElement, spinner);\n reject();\n });\n });\n };\n\n /**\n * Requests html for the section via WS core_course_edit_section and updates the section on the course page\n *\n * @param {JQuery|Element} element\n * @param {Number} sectionid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshSection = function(element, sectionid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const sectionElement = $(element);\n const action = 'refresh';\n const promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action, sectionreturn},\n }], true);\n\n var spinner = addSectionSpinner(sectionElement);\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(dataencoded => {\n\n removeSpinner(sectionElement, spinner);\n const data = $.parseJSON(dataencoded);\n\n const newSectionElement = $(data.content);\n sectionElement.replaceWith(newSectionElement);\n\n // Init modules menus.\n $(`${SELECTOR.SECTIONLI}#${sectionid} ${SELECTOR.ACTIVITYLI}`).each(\n (index, activity) => {\n initActionMenu(activity.data('id'));\n }\n );\n\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n CourseEvents.sectionRefreshed,\n {\n ajaxreturn: data,\n action: action,\n newSectionElement: newSectionElement.get(0),\n },\n newSectionElement\n );\n\n if (!event.defaultPrevented) {\n defaultEditSectionHandler(\n newSectionElement, $(SELECTOR.SECTIONLI + '#' + sectionid),\n data,\n formatname,\n sectionid\n );\n }\n resolve(data);\n }).fail(ex => {\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n 'coursesectionrefreshfailed',\n {exception: ex, action: action},\n sectionElement\n );\n if (!event.defaultPrevented) {\n notification.exception(ex);\n }\n reject();\n });\n });\n };\n\n /**\n * Displays the delete confirmation to delete a module\n *\n * @param {JQuery} mainelement activity element we perform action on\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmDeleteModule = function(mainelement, onconfirm) {\n var modtypename = mainelement.attr('class').match(/modtype_([^\\s]*)/)[1];\n var modulename = getModuleName(mainelement);\n\n str.get_string('pluginname', modtypename).done(function(pluginname) {\n var plugindata = {\n type: pluginname,\n name: modulename\n };\n str.get_strings([\n {key: 'confirm', component: 'core'},\n {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], s[1], s[2], s[3], onconfirm);\n }\n );\n });\n };\n\n /**\n * Displays the delete confirmation to delete a section\n *\n * @param {String} message confirmation message\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmEditSection = function(message, onconfirm) {\n str.get_strings([\n {key: 'confirm'}, // TODO link text\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], message, s[1], s[2], onconfirm);\n }\n );\n };\n\n /**\n * Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * @param {JQuery} actionitem\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n * @return {Promise} promise which is resolved when the replacement has completed\n */\n var replaceActionItem = function(actionitem, image, stringname,\n stringcomponent, newaction) {\n\n var stringRequests = [{key: stringname, component: stringcomponent}];\n // Do not provide an icon with duplicate, different text to the menu item.\n\n return str.get_strings(stringRequests).then(function(strings) {\n actionitem.find('span.menu-action-text').html(strings[0]);\n\n return templates.renderPix(image, 'core');\n }).then(function(pixhtml) {\n actionitem.find('.icon').replaceWith(pixhtml);\n actionitem.attr('data-action', newaction);\n return;\n }).catch(notification.exception);\n };\n\n /**\n * Default post-processing for section AJAX edit actions.\n *\n * This can be overridden in course formats by listening to event coursesectionedited:\n *\n * $('body').on('coursesectionedited', 'li.section', function(e) {\n * var action = e.action,\n * sectionElement = $(e.target),\n * data = e.ajaxreturn;\n * // ... Do some processing here.\n * e.preventDefault(); // Prevent default handler.\n * });\n *\n * @param {JQuery} sectionElement\n * @param {JQuery} actionItem\n * @param {Object} data\n * @param {String} courseformat\n * @param {Number} sectionid\n */\n var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat, sectionid) {\n var action = actionItem.attr('data-action');\n if (action === 'hide' || action === 'show') {\n if (action === 'hide') {\n sectionElement.addClass('hidden');\n setSectionBadge(sectionElement[0], 'hiddenfromstudents', true, false);\n replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\n setSectionBadge(sectionElement[0], 'hiddenfromstudents', false, false);\n sectionElement.removeClass('hidden');\n replaceActionItem(actionItem, 'i/hide',\n 'hidefromothers', 'format_' + courseformat, 'hide');\n }\n // Replace the modules with new html (that indicates that they are now hidden or not hidden).\n if (data.modules !== undefined) {\n for (var i in data.modules) {\n replaceActivityHtmlWith(data.modules[i]);\n }\n }\n // Replace the section availability information.\n if (data.section_availability !== undefined) {\n sectionElement.find('.section_availability').first().replaceWith(data.section_availability);\n }\n // Modify course state.\n const section = courseeditor.state.section.get(sectionid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [sectionid]);\n }\n } else if (action === 'setmarker') {\n var oldmarker = $(SELECTOR.SECTIONLI + '.current'),\n oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');\n oldmarker.removeClass('current');\n replaceActionItem(oldActionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n sectionElement.addClass('current');\n replaceActionItem(actionItem, 'i/marked',\n 'highlightoff', 'core', 'removemarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n setSectionBadge(sectionElement[0], 'iscurrent', true, true);\n } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n setSectionBadge(sectionElement[0], 'iscurrent', false, true);\n }\n };\n\n /**\n * Get the focused element path in an activity if any.\n *\n * This method is used to restore focus when the activity HTML is refreshed.\n * Only the main course editor elements can be refocused as they are always present\n * even if the activity content changes.\n *\n * @param {String} id the element id the activity element\n * @return {String|undefined} the inner path of the focused element or undefined\n */\n const getActivityFocusedElement = function(id) {\n const element = document.getElementById(id);\n if (!element || !element.contains(document.activeElement)) {\n return undefined;\n }\n // Check if the actions menu toggler is focused.\n if (element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)) {\n return `${SELECTOR.ACTIONAREA} [tabindex=\"0\"]`;\n }\n // Return the current element id if any.\n if (document.activeElement.id) {\n return `#${document.activeElement.id}`;\n }\n return undefined;\n };\n\n /**\n * Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).\n *\n * @param {String} activityHTML\n */\n var replaceActivityHtmlWith = function(activityHTML) {\n $('
' + activityHTML + '
').find(SELECTOR.ACTIVITYLI).each(function() {\n // Extract id from the new activity html.\n var id = $(this).attr('id');\n // Check if the current focused element is inside the activity.\n let focusedPath = getActivityFocusedElement(id);\n // Find the existing element with the same id and replace its contents with new html.\n $(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);\n // Initialise action menu.\n initActionMenu(id);\n // Re-focus the previous elements.\n if (focusedPath) {\n const newItem = document.getElementById(id);\n newItem.querySelector(focusedPath)?.focus();\n }\n\n });\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {Nunmber} sectionid\n * @param {JQuery} target the element (menu item) that was clicked\n * @param {String} courseformat\n * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : null;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n /**\n * Sets the section badge in the section header.\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {String} badgetype the type of badge this is for\n * @param {bool} add true to add, false to remove\n * @param {boolean} removeOther in case of adding a badge, whether to remove all other.\n */\n var setSectionBadge = function(sectionElement, badgetype, add, removeOther) {\n const sectionbadges = sectionElement.querySelector(SELECTOR.SECTIONBADGES);\n if (!sectionbadges) {\n return;\n }\n const badge = sectionbadges.querySelector('[data-type=\"' + badgetype + '\"]');\n if (!badge) {\n return;\n }\n if (add) {\n if (removeOther) {\n document.querySelectorAll('[data-type=\"' + badgetype + '\"]').forEach((b) => {\n b.classList.add('d-none');\n });\n }\n badge.classList.remove('d-none');\n } else {\n badge.classList.add('d-none');\n }\n };\n\n // Register a function to be executed after D&D of an activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.register_module({\n // Ignore camelcase eslint rule for the next line because it is an expected name of the callback.\n // eslint-disable-next-line camelcase\n set_visibility_resource_ui: function(args) {\n var mainelement = $(args.element.getDOMNode());\n var cmid = getModuleId(mainelement);\n if (cmid) {\n var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');\n refreshModule(mainelement, cmid, sectionreturn);\n }\n },\n /**\n * Update the course state when some cm is moved via YUI.\n * @param {*} params\n */\n updateMovedCmState: (params) => {\n const state = courseeditor.state;\n\n // Update old section.\n const cm = state.cm.get(params.cmid);\n if (cm !== undefined) {\n courseeditor.dispatch('sectionState', [cm.sectionid]);\n }\n // Update cm state.\n courseeditor.dispatch('cmState', [params.cmid]);\n },\n /**\n * Update the course state when some section is moved via YUI.\n */\n updateMovedSectionState: () => {\n courseeditor.dispatch('courseState');\n },\n });\n });\n\n // From Moodle 4.0 all edit actions are being re-implemented as state mutation.\n // This means all method from this \"actions\" module will be deprecated when all the course\n // interface is migrated to reactive components.\n // Most legacy actions did not provide enough information to regenarate the course so they\n // use the mutations courseState, sectionState and cmState to get the updated state from\n // the server. However, some activity actions where we can prevent an extra webservice\n // call by implementing an adhoc mutation.\n courseeditor.addMutations({\n /**\n * Compatibility function to update Moodle 4.0 course state using legacy actions.\n *\n * This method only updates some actions which does not require to use cmState mutation\n * to get updated data form the server.\n *\n * @param {Object} statemanager the current state in read write mode\n * @param {String} action the performed action\n * @param {Number} cmid the affected course module id\n * @param {Array} affectedids all affected cm ids (for duplicate action)\n */\n legacyActivityAction: function(statemanager, action, cmid, affectedids) {\n\n const state = statemanager.state;\n const cm = state.cm.get(cmid);\n if (cm === undefined) {\n return;\n }\n const section = state.section.get(cm.sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked.\n courseeditor.dispatch('cmLock', [cm.id], true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This unlocked will take effect when the read only is restored.\n cm.locked = false;\n\n switch (action) {\n case 'delete':\n // Remove from section.\n section.cmlist = section.cmlist.reduce(\n (cmlist, current) => {\n if (current != cmid) {\n cmlist.push(current);\n }\n return cmlist;\n },\n []\n );\n // Delete form list.\n state.cm.delete(cmid);\n break;\n\n case 'hide':\n case 'show':\n case 'duplicate':\n courseeditor.dispatch('cmState', affectedids);\n break;\n }\n statemanager.setReadOnly(true);\n },\n legacySectionAction: function(statemanager, action, sectionid) {\n\n const state = statemanager.state;\n const section = state.section.get(sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n section.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n section.locked = false;\n\n switch (action) {\n case 'setmarker':\n // Remove previous marker.\n state.section.forEach((current) => {\n if (current.id != sectionid) {\n current.current = false;\n }\n });\n section.current = true;\n break;\n\n case 'removemarker':\n section.current = false;\n break;\n }\n statemanager.setReadOnly(true);\n },\n });\n\n return /** @alias module:core_course/actions */ {\n\n /**\n * Initialises course page\n *\n * @method init\n * @param {String} courseformat name of the current course format (for fetching strings)\n */\n initCoursePage: function(courseformat) {\n\n formatname = courseformat;\n\n // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section action menu.\n $('body').on('click keypress',\n SELECTOR.SECTIONACTIONMENUTRIGGER + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENUTRIGGER).attr('data-sectionid');\n\n if (actionItem.attr('data-action') === 'permalink') {\n e.preventDefault();\n ModalCopyToClipboard.create({\n text: actionItem.attr('href'),\n }, str.get_string('sectionlink', 'course')\n );\n return;\n }\n\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONITEM} [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 const trigger = $(SELECTOR.ADDSECTIONS);\n const modalTitle = trigger.attr('data-add-sections');\n const newSections = trigger.attr('data-new-sections');\n str.get_string('numberweeks')\n .then(function(strNumberSections) {\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n\n return modalBody.html();\n })\n .then((body) => ModalSaveCancel.create({\n body,\n title: modalTitle,\n }))\n .then(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n\n trigger.on('click', (e) => {\n e.preventDefault();\n modal.show();\n });\n\n return modal;\n })\n .catch(notification.exception);\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n refreshSection,\n };\n });\n"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalCopyToClipboard","ModalSaveCancel","ModalEvents","KeyCodes","log","editor","EventDispatcher","CourseEvents","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","SECTIONACTIONMENUTRIGGER","SECTIONITEM","ADDSECTIONS","SECTIONBADGES","use","courseformatselector","M","course","format","get_section_selector","dispatchEvent","eventName","detail","container","options","Element","undefined","get","getModuleId","element","item","dataset","id","Moodle","core_course","util","cm","getId","Node","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","for","setAttribute","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","getAttribute","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","editModule","moduleElement","cmid","target","action","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","mainElement","tabables","isInside","foundElement","elementToFocus","each","contains","this","replaceWith","affectedids","index","elementId","mainelement","selector","is","focus","focusActionItem","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","sectionReturn","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","name","getName","state","_state$cm$get","getModuleName","get_string","pluginname","plugindata","type","get_strings","key","component","param","s","confirm","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","stringRequests","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","setSectionBadge","modules","i","section_availability","first","section","oldmarker","oldActionItem","activityHTML","focusedPath","document","getElementById","activeElement","querySelector","getActivityFocusedElement","editSection","supportComponents","includes","dataencoded","parseJSON","badgetype","add","removeOther","sectionbadges","badge","querySelectorAll","forEach","b","classList","remove","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","legacySectionAction","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","create","text","isExecuted","message","itemid","modalTitle","newSections","strNumberSections","modalBody","body","title","modal","numSections","getBody","addSections","parseInt","val","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug","refreshSection","newSectionElement","content","sectionRefreshed","defaultPrevented"],"mappings":";;;;;;;;AAuBAA,6BACI,CACI,SACA,YACA,iBACA,oBACA,WACA,WACA,WACA,+BACA,yBACA,oBACA,iBACA,WACA,iCACA,wBACA,uBAEJ,SACIC,EACAC,KACAC,UACAC,aACAC,IACAC,IACAC,EACAC,qBACAC,gBACAC,YACAC,SACAC,IACAC,OACAC,gBACAC,oBAMMC,iBAAmB,CACrB,cAAe,SAAU,aAAc,gBAAiB,WAAY,cAAe,cAAe,cAClG,SAAU,SAAU,YAAa,mBAAoB,qBAAsB,cAAe,aAC1F,aAAc,kBAAmB,oBAI/BC,aAAeJ,OAAOK,6BAGxBC,eAEAC,mBACgB,iBADhBA,gBAGa,eAEbC,SAAW,CACXC,WAAY,cACZC,WAAY,WACZC,eAAgB,mBAChBC,KAAM,0DACNC,OAAQ,mCACRC,UAAW,aACXC,kBAAmB,uBACnBC,yBAA0B,mBAC1BC,YAAa,6BACbC,YAAa,yCACbC,cAAe,iCAGnBzB,EAAE0B,IAAI,4BAA4B,eAC1BC,qBAAuBC,EAAEC,OAAOC,OAAOC,uBACvCJ,uBACAb,SAASM,UAAYO,+BAmBvBK,cAAgB,SAASC,UAAWC,OAAQC,UAAWC,gBAEnDD,qBAAqBE,cAA8BC,IAAlBH,UAAUI,MAC7CJ,UAAYA,UAAUI,IAAI,IAEvBhC,gBAAgByB,cAAcC,UAAWC,OAAQC,UAAWC,cASnEI,YAAc,SAASC,eAEjBC,KAAOD,QAAQF,IAAI,MACrBG,KAAKC,QAAQC,UACNF,KAAKC,QAAQC,OAGpBA,UACJ5C,EAAE0B,IAAI,sBAAsB,SAAS1B,GACjC4C,GAAK5C,EAAE6C,OAAOC,YAAYC,KAAKC,GAAGC,MAAMjD,EAAEkD,KAAKR,UAE5CE,IA6BPO,mBAAqB,SAASC,UAC9BA,SAASC,SAASxC,wBACdyC,WAAaF,SAASG,KAAKzC,SAASE,YAAYuB,IAAI,MACpDe,WAAY,KACRE,QAAU5B,EAAEmB,KAAKU,YAAYzD,EAAGA,EAAEkD,KAAKI,oBAC3CE,QAAQE,YAEoBpB,IAAxBc,SAASO,KAAK,OACdjD,aAAakD,SAAS,SAAU,CAACR,SAASO,KAAK,QAAQ,GAEpDH,eAEJ,MASPK,kBAAoB,SAASC,gBAC7BA,eAAeT,SAASxC,wBACpByC,WAAaQ,eAAeP,KAAKzC,SAASO,mBAAmBkB,IAAI,MACjEe,WAAY,KACRE,QAAU5B,EAAEmB,KAAKU,YAAYzD,EAAGA,EAAEkD,KAAKI,oBAC3CE,QAAQE,YAE0BpB,IAA9BwB,eAAeH,KAAK,OACpBjD,aAAakD,SAAS,cAAe,CAACE,eAAeH,KAAK,QAAQ,GAE/DH,eAEJ,MASPO,mBAAqB,SAASD,sBACxBpB,KAAOoB,eAAevB,IAAI,OAC5ByB,SAAWpC,EAAEmB,KAAKkB,aAAajE,EAAGA,EAAEkD,KAAKR,aACrB,WAApBA,KAAKC,QAAQuB,KAAoBxB,KAAKC,QAAQC,KAC9ClC,aAAakD,SAAS,cAAe,CAAClB,KAAKC,QAAQC,KAAK,GACxDoB,SAASG,aAAa,aAAc,WACpCH,SAASG,aAAa,gBAAiBzB,KAAKC,QAAQC,KAExDoB,SAASN,OACFM,UAUPI,cAAgB,SAAS3B,QAASe,QAASa,OAC3CC,OAAOC,YAAW,cACd9B,QAAQ+B,YAAY3D,oBAChB2C,SACAA,QAAQiB,YAGenC,IAAvBG,QAAQkB,KAAK,MAAqB,OAC5Be,SAAoC,YAAxBjC,QAAQkB,KAAK,OAAwB,cAAgB,SACvEjD,aAAakD,SAASc,SAAU,CAACjC,QAAQkB,KAAK,QAAQ,MAE3DU,QASHM,eAAiB,SAASX,SAAUK,OAChCL,UACAM,OAAOC,YAAW,WACdP,SAASS,OAELT,SAASY,aAAa,eACtBlE,aAAakD,mBACNI,SAASY,aAAa,sBACzB,CAACZ,SAASY,aAAa,mBACvB,KAGTP,QASPQ,eAAiB,SAASC,WAE1B9E,EAAE0B,IAAI,4BAA4B,WAC9BE,EAAEC,OAAOkD,WAAWC,gBAAgB,qBAAsB,IAAMF,cAEhElD,EAAEqD,KAAKC,YAActD,EAAEqD,KAAKC,WAAWC,YACvCvD,EAAEqD,KAAKC,WAAWC,WAAWnF,EAAEoF,IAAI,IAAMN,aAsD7CO,WAAa,SAASC,cAAeC,KAAMC,YAWvCxB,SAVAyB,OAASD,OAAOE,KAAK,eACrBlC,QAAUL,mBAAmBmC,eAC7BK,SAAWhG,KAAKiG,KAAK,CAAC,CACtBC,WAAY,0BACZC,KAAM,CAAClD,GAAI2C,KACPE,OAAQA,OACRM,cAAeP,OAAOE,KAAK,sBAAwBF,OAAOE,KAAK,sBAAwB,SAE3F,GAGW,cAAXD,SACAzB,SAAWD,mBAAmByB,OAAOQ,QAAQlF,SAASM,aAE1D1B,EAAEuG,KAAKC,MAAMxG,EAAGiG,UACXQ,MAAK,SAASxC,UAvCUyC,YACzBC,SACAC,SACAC,aAqCQC,gBAxCiBJ,YAwCkBd,cAvC3Ce,SAAW3G,EAAE,aACb4G,UAAW,EACXC,aAAe,KACnBF,SAASI,MAAK,cACN/G,EAAEgH,SAASN,YAAY,GAAIO,MAC3BL,UAAW,OACR,GAAIA,gBACPC,aAAeI,MACR,SAEJ,KAEJJ,cA4BCjB,cAAcsB,YAAYjD,UACtBkD,YAAc,GAElBnH,EAAE,QAAUiE,KAAO,UAAUJ,KAAKzC,SAASC,YAAY0F,MAAK,SAASK,OACjEjC,eAAenF,EAAEiH,MAAMjB,KAAK,OACd,IAAVoB,SAnEE,SAASC,UAAWtB,YAClCuB,YAActH,EAAE,IAAMqH,WACtBE,SAAW,gBAAkBxB,OAAS,IAC3B,mBAAXA,QAA0C,kBAAXA,QAAyC,eAAXA,SAE7DwB,SAAW,qFAEXD,YAAYzD,KAAK0D,UAAUC,GAAG,YAC9BF,YAAYzD,KAAK0D,UAAUE,QAG3BH,YAAYzD,KAAKzC,SAASI,MAAMqC,KAAKzC,SAASK,QAAQgG,QAyD1CC,CAAgB1H,EAAEiH,MAAMjB,KAAK,MAAOD,QACpCe,eAAiB,MAGrBK,YAAYQ,KAAK7E,YAAY9C,EAAEiH,WAG/BH,gBACAA,eAAeW,QAGnB/C,cAAckB,cAAe9B,QAAS,KACtCmB,eAAeX,SAAU,KAEzBsB,cAAcgC,QAAQ5H,EAAE6H,MAAM,qBAAsB,CAACC,WAAY7D,KAAM8B,OAAQA,UAG/E/E,aAAakD,SAAS,uBAAwB6B,OAAQF,KAAMsB,gBAE7DY,MAAK,SAASC,IAEbtD,cAAckB,cAAe9B,SAC7BmB,eAAeX,cAEX2D,EAAIjI,EAAE6H,MAAM,yBAA0B,CAACK,UAAWF,GAAIjC,OAAQA,SAClEH,cAAcgC,QAAQK,GACjBA,EAAEE,sBACHhI,aAAa+H,UAAUF,QAenCI,cAAgB,SAASrF,QAAS8C,KAAMQ,oBAElBzD,IAAlByD,gBACAA,cAAgBrF,aAAaqH,qBAG3BC,gBAAkBtI,EAAE+C,aACtBe,QAAUL,mBAAmB6E,iBAC7BrC,SAAWhG,KAAKiG,KAAK,CAAC,CACtBC,WAAY,yBACZC,KAAM,CAAClD,GAAI2C,KAAMQ,cAAeA,kBAChC,UAEG,IAAIkC,SAAQ,CAACC,QAASC,UACzBzI,EAAEuG,KAAKC,MAAMxG,EAAGiG,UACXQ,MAAK,SAASxC,MACXS,cAAc4D,gBAAiBxE,QAAS,KACxC4E,wBAAwBzE,MACxBuE,QAAQvE,SACT8D,MAAK,WACJrD,cAAc4D,gBAAiBxE,SAC/B2E,gBAqFZE,oBAAsB,SAASrB,YAAasB,eACxCC,YAAcvB,YAAYtB,KAAK,SAAS8C,MAAM,oBAAoB,GAClEC,WApWY,SAAShG,aACrBiG,KACJ1I,EAAE0B,IAAI,sBAAsB,SAAS1B,GACjC0I,KAAO1I,EAAE6C,OAAOC,YAAYC,KAAKC,GAAG2F,QAAQ3I,EAAEkD,KAAKT,QAAQF,IAAI,cAG7DqG,MAAQlI,aAAakI,MACrBrD,KAAO/C,YAAYC,kCACpBiG,MAAQE,OAASrD,OAClBmD,2BAAOE,MAAM5F,GAAGT,IAAIgD,sCAAbsD,cAAoBH,MAExBA,KAyVUI,CAAc9B,aAE/BlH,IAAIiJ,WAAW,aAAcR,aAAapC,MAAK,SAAS6C,gBAChDC,WAAa,CACbC,KAAMF,WACNN,KAAMD,YAEV3I,IAAIqJ,YAAY,CACZ,CAACC,IAAK,UAAWC,UAAW,QAC5B,CAACD,IAAoB,OAAfX,WAAsB,kBAAoB,sBAAuBa,MAAOL,YAC9E,CAACG,IAAK,OACN,CAACA,IAAK,QACPjD,MAAK,SAASoD,GACT1J,aAAa2J,QAAQD,EAAE,GAAIA,EAAE,GAAIA,EAAE,GAAIA,EAAE,GAAIjB,kBAiCzDmB,kBAAoB,SAASC,WAAYC,MAAOC,WACjBC,gBAAiBC,eAE5CC,eAAiB,CAAC,CAACX,IAAKQ,WAAYP,UAAWQ,yBAG5C/J,IAAIqJ,YAAYY,gBAAgBC,MAAK,SAASC,gBACjDP,WAAWnG,KAAK,yBAAyB2G,KAAKD,QAAQ,IAE/CrK,UAAUuK,UAAUR,MAAO,WACnCK,MAAK,SAASI,SACbV,WAAWnG,KAAK,SAASqD,YAAYwD,SACrCV,WAAWhE,KAAK,cAAeoE,cAEhCO,MAAMxK,aAAa+H,YAsBtB0C,0BAA4B,SAASC,eAAgBC,WAAY7G,KAAM8G,aAAcC,eACjFjF,OAAS+E,WAAW9E,KAAK,kBACd,SAAXD,QAAgC,SAAXA,OAAmB,IACzB,SAAXA,QACA8E,eAAelH,SAAS,UACxBsH,gBAAgBJ,eAAe,GAAI,sBAAsB,GAAM,GAC/Dd,kBAAkBe,WAAY,SAC1B,iBAAkB,UAAYC,aAAc,UAEhDE,gBAAgBJ,eAAe,GAAI,sBAAsB,GAAO,GAChEA,eAAe/F,YAAY,UAC3BiF,kBAAkBe,WAAY,SAC1B,iBAAkB,UAAYC,aAAc,cAG/BnI,IAAjBqB,KAAKiH,YACA,IAAIC,KAAKlH,KAAKiH,QACfxC,wBAAwBzE,KAAKiH,QAAQC,SAIXvI,IAA9BqB,KAAKmH,sBACLP,eAAehH,KAAK,yBAAyBwH,QAAQnE,YAAYjD,KAAKmH,2BAI1DxI,IADA5B,aAAakI,MAAMoC,QAAQzI,IAAImI,YAE3ChK,aAAakD,SAAS,eAAgB,CAAC8G,iBAExC,GAAe,cAAXjF,OAAwB,KAC3BwF,UAAYvL,EAAEoB,SAASM,UAAY,YACnC8J,cAAgBD,UAAU1H,KAAKzC,SAASO,kBAATP,gCACnCmK,UAAUzG,YAAY,WACtBiF,kBAAkByB,cAAe,WAC7B,YAAa,OAAQ,aACzBX,eAAelH,SAAS,WACxBoG,kBAAkBe,WAAY,WAC1B,eAAgB,OAAQ,gBAC5B9J,aAAakD,SAAS,sBAAuB6B,OAAQiF,WACrDC,gBAAgBJ,eAAe,GAAI,aAAa,GAAM,OACpC,iBAAX9E,SACP8E,eAAe/F,YAAY,WAC3BiF,kBAAkBe,WAAY,WAC1B,YAAa,OAAQ,aACzB9J,aAAakD,SAAS,sBAAuB6B,OAAQiF,WACrDC,gBAAgBJ,eAAe,GAAI,aAAa,GAAO,SAmC3DnC,wBAA0B,SAAS+C,cACnCzL,EAAE,QAAUyL,aAAe,UAAU5H,KAAKzC,SAASC,YAAY0F,MAAK,eAE5D7D,GAAKlD,EAAEiH,MAAMjB,KAAK,UAElB0F,YA1BsB,SAASxI,UACjCH,QAAU4I,SAASC,eAAe1I,OACnCH,SAAYA,QAAQiE,SAAS2E,SAASE,sBAIvC9I,QAAQ+I,cAAc1K,SAASE,YAAY0F,SAAS2E,SAASE,yBACnDzK,SAASE,8BAGnBqK,SAASE,cAAc3I,cACZyI,SAASE,cAAc3I,WAehB6I,CAA0B7I,OAE5ClD,EAAEoB,SAASC,WAAa,IAAM6B,IAAIgE,YAAYuE,cAE9CtG,eAAejC,IAEXwI,YAAa,yDACGC,SAASC,eAAe1I,IAChC4I,cAAcJ,qEAAcjE,aAe5CuE,YAAc,SAASnB,eAAgBG,UAAWlF,OAAQiF,kBACtDhF,OAASD,OAAOE,KAAK,eACrBK,cAAgBP,OAAOE,KAAK,sBAAwBF,OAAOE,KAAK,sBAAwB,QAGxFhF,aAAaiL,mBAAqBlL,iBAAiBmL,SAASnG,eACrD,MAGPjC,QAAUK,kBAAkB0G,gBAC5B5E,SAAWhG,KAAKiG,KAAK,CAAC,CACtBC,WAAY,2BACZC,KAAM,CAAClD,GAAI8H,UAAWjF,OAAQA,OAAQM,cAAeA,kBACrD,GAEA/B,SAAWD,mBAAmBwG,uBAClC7K,EAAEuG,KAAKC,MAAMxG,EAAGiG,UACXQ,MAAK,SAAS0F,iBACPlI,KAAOjE,EAAEoM,UAAUD,aACvBzH,cAAcmG,eAAgB/G,SAC9BmB,eAAeX,UACfuG,eAAehH,KAAKzC,SAASO,mBAAmBkC,KAAKzC,SAASK,QAAQgG,YAElEQ,EAAIjI,EAAE6H,MAAM,sBAAuB,CAACC,WAAY7D,KAAM8B,OAAQA,SAClE8E,eAAejD,QAAQK,GAClBA,EAAEE,sBACHyC,0BAA0BC,eAAgB/E,OAAQ7B,KAAM8G,aAAcC,cAE3EjD,MAAK,SAASC,IAEbtD,cAAcmG,eAAgB/G,SAC9BmB,eAAeX,cAEX2D,EAAIjI,EAAE6H,MAAM,0BAA2B,CAACK,UAAWF,GAAIjC,OAAQA,SACnE8E,eAAejD,QAAQK,GAClBA,EAAEE,sBACHhI,aAAa+H,UAAUF,QAG5B,GAWPiD,gBAAkB,SAASJ,eAAgBwB,UAAWC,IAAKC,mBACrDC,cAAgB3B,eAAeiB,cAAc1K,SAASW,mBACvDyK,2BAGCC,MAAQD,cAAcV,cAAc,eAAiBO,UAAY,MAClEI,QAGDH,KACIC,aACAZ,SAASe,iBAAiB,eAAiBL,UAAY,MAAMM,SAASC,IAClEA,EAAEC,UAAUP,IAAI,aAGxBG,MAAMI,UAAUC,OAAO,WAEvBL,MAAMI,UAAUP,IAAI,mBAK5BhM,EAAE0B,IAAI,4BAA4B,WAC9BE,EAAEC,OAAOkD,WAAW0H,gBAAgB,CAGhCC,2BAA4B,SAAS5G,UAC7BkB,YAActH,EAAEoG,KAAKrD,QAAQkK,cAC7BpH,KAAO/C,YAAYwE,gBACnBzB,KAAM,KACFQ,cAAgBiB,YAAYzD,KAAK,IAAM1C,iBAAiB6E,KAAK,sBACjEoC,cAAcd,YAAazB,KAAMQ,iBAOzC6G,mBAAqBC,eAIX7J,GAHQtC,aAAakI,MAGV5F,GAAGT,IAAIsK,OAAOtH,WACpBjD,IAAPU,IACAtC,aAAakD,SAAS,eAAgB,CAACZ,GAAG0H,YAG9ChK,aAAakD,SAAS,UAAW,CAACiJ,OAAOtH,QAK7CuH,wBAAyB,KACrBpM,aAAakD,SAAS,qBAYlClD,aAAaqM,aAAa,CAYtBC,qBAAsB,SAASC,aAAcxH,OAAQF,KAAMsB,mBAEjD+B,MAAQqE,aAAarE,MACrB5F,GAAK4F,MAAM5F,GAAGT,IAAIgD,cACbjD,IAAPU,gBAGEgI,QAAUpC,MAAMoC,QAAQzI,IAAIS,GAAG0H,mBACrBpI,IAAZ0I,gBAKJtK,aAAakD,SAAS,SAAU,CAACZ,GAAGJ,KAAK,GAGzCqK,aAAaC,aAAY,GAGzBlK,GAAGmK,QAAS,EAEJ1H,YACC,SAEDuF,QAAQoC,OAASpC,QAAQoC,OAAOC,QAC5B,CAACD,OAAQE,WACDA,SAAW/H,MACX6H,OAAO/F,KAAKiG,SAETF,SAEX,IAGJxE,MAAM5F,GAAGuK,OAAOhI,gBAGf,WACA,WACA,YACD7E,aAAakD,SAAS,UAAWiD,aAGzCoG,aAAaC,aAAY,KAE7BM,oBAAqB,SAASP,aAAcxH,OAAQiF,iBAE1C9B,MAAQqE,aAAarE,MACrBoC,QAAUpC,MAAMoC,QAAQzI,IAAImI,mBAClBpI,IAAZ0I,gBAQJiC,aAAaC,aAAY,GACzBlC,QAAQmC,QAAS,EACjBF,aAAaC,aAAY,GAGzBD,aAAaC,aAAY,GAGzBlC,QAAQmC,QAAS,EAET1H,YACC,YAEDmD,MAAMoC,QAAQqB,SAASiB,UACfA,QAAQ1K,IAAM8H,YACd4C,QAAQA,SAAU,MAG1BtC,QAAQsC,SAAU,YAGjB,eACDtC,QAAQsC,SAAU,EAG1BL,aAAaC,aAAY,OAIe,CAQ5CO,eAAgB,SAAShD,iBAErB7J,WAAa6J,aAGb/K,EAAE,QAAQgO,GAAG,iBAAkB5M,SAASC,WAAa,IAC7CD,SAASG,eAAiB,iBAAiB,SAAS0G,MACzC,aAAXA,EAAEuB,MAAqC,KAAdvB,EAAEgG,aAG3BnD,WAAa9K,EAAEiH,MACfrB,cAAgBkF,WAAWxE,QAAQlF,SAASC,YAC5C0E,OAAS+E,WAAW9E,KAAK,eACzBkI,SAAWpL,YAAY8C,sBACnBG,YACC,eACA,gBACA,aACA,gBACA,WACA,cACA,WACA,qBACA,oBACA,kCAMJmI,WAGLjG,EAAEkG,iBACa,WAAXpI,OAEA4C,oBAAoB/C,eAAe,WAC/BD,WAAWC,cAAesI,SAAUpD,eAGxCnF,WAAWC,cAAesI,SAAUpD,iBAK5C9K,EAAE,QAAQgO,GAAG,iBACD5M,SAASQ,yBAATR,mCACkB,SAAS6G,MACpB,aAAXA,EAAEuB,MAAqC,KAAdvB,EAAEgG,mBAG3BnD,WAAa9K,EAAEiH,MACf4D,eAAiBC,WAAWxE,QAAQlF,SAASM,WAC7C0M,UAAYtD,WAAWxE,QAAQlF,SAASQ,0BAA0BoE,KAAK,qBAEpC,cAAnC8E,WAAW9E,KAAK,sBAChBiC,EAAEkG,sBACF5N,qBAAqB8N,OAAO,CACxBC,KAAMxD,WAAW9E,KAAK,SACvB5F,IAAIiJ,WAAW,cAAe,eAKjCkF,YAAa,EAlcJ,IAASC,QAAS5F,UAmc3BkC,WAAW9E,KAAK,iBAncEwI,QAqcC1D,WAAW9E,KAAK,gBArcR4C,UAqcyB,WAChD2F,WAAavC,YAAYnB,eAAgBuD,UAAWtD,WAAYC,eArchF3K,IAAIqJ,YAAY,CACZ,CAACC,IAAK,WACN,CAACA,IAAK,OACN,CAACA,IAAK,QACPjD,MAAK,SAASoD,GACT1J,aAAa2J,QAAQD,EAAE,GAAI2E,QAAS3E,EAAE,GAAIA,EAAE,GAAIjB,eAmc5C2F,WAAavC,YAAYnB,eAAgBuD,UAAWtD,WAAYC,cAGhEwD,YACAtG,EAAEkG,oBAMVnO,EAAE,QAAQgO,GAAG,oBAAc5M,SAASS,wCAAsC,SAASoG,MAC3EA,EAAEH,YAAcG,EAAEH,WAAW2G,OAAQ,MAGrB7L,IAFF5B,aAAakI,MACLoC,QAAQzI,IAAIoF,EAAEH,WAAW2G,SAE3CzN,aAAakD,SAAS,eAAgB,CAAC+D,EAAEH,WAAW2G,aAIhEzO,EAAE,QAAQgO,GAAG,oBAAc5M,SAASC,uCAAqC,SAAS4G,GAC1EA,EAAEH,YAAcG,EAAEH,WAAW2G,QAC7BzN,aAAakD,SAAS,UAAW,CAAC+D,EAAEH,WAAW2G,YAKnDzN,aAAaiL,mBAAqBlL,iBAAiBmL,SAAS,2BAK1DtE,QAAU5H,EAAEoB,SAASU,aACrB4M,WAAa9G,QAAQ5B,KAAK,qBAC1B2I,YAAc/G,QAAQ5B,KAAK,qBACjC5F,IAAIiJ,WAAW,eACdiB,MAAK,SAASsE,uBACPC,UAAY7O,EAAE,qHACsD2O,YAAc,6BACtFE,UAAUhL,KAAK,SAAS2G,KAAKoE,mBAEtBC,UAAUrE,UAEpBF,MAAMwE,MAAStO,gBAAgB6N,OAAO,CACnCS,KAAAA,KACAC,MAAOL,eAEVpE,MAAK,SAAS0E,WACPC,YAAcjP,EAAEgP,MAAME,WAAWrL,KAAK,4BAC1CsL,YAAc,WAGN,GAAKC,SAASH,YAAYI,SAAWJ,YAAYI,OAASD,SAASH,YAAYI,QAAU,IACzF1D,SAAS2D,SAAW1H,QAAQ5B,KAAK,QAAU,gBAAkBoJ,SAASH,YAAYI,gBAG1FL,MAAMO,kBAAkBb,YACxBM,MAAMQ,UAAUxB,GAAGvN,YAAYgP,OAAO,WAElCR,YAAYxH,QAAQiI,SAAS1B,GAAG,WAAW,SAAS/F,GAC5CA,EAAEgG,UAAYvN,SAASiP,OACvBR,oBAIZH,MAAMQ,UAAUxB,GAAGvN,YAAYmP,MAAM,SAAS3H,GAE1CA,EAAEkG,iBACFgB,iBAGJvH,QAAQoG,GAAG,SAAU/F,IACjBA,EAAEkG,iBACFa,MAAMhL,UAGHgL,SAEVrE,MAAMxK,aAAa+H,YAgBxB2H,yBAA0B,SAASzL,eAAgBmD,SAAU0C,MAAOC,WAC5BC,gBAAiBC,WACrDzJ,IAAImP,MAAM,qEACN9F,WAAa5F,eAAeP,KAAKzC,SAASO,kBAAoB,IAAM4F,UACxEwC,kBAAkBC,WAAYC,MAAOC,WAAYC,gBAAiBC,YAGtEhC,cAAAA,cACA2H,eAjpBiB,SAAShN,QAASiI,UAAW3E,oBAExBzD,IAAlByD,gBACAA,cAAgBrF,aAAaqH,qBAG3BwC,eAAiB7K,EAAE+C,SAEnBkD,SAAWhG,KAAKiG,KAAK,CAAC,CACxBC,WAAY,2BACZC,KAAM,CAAClD,GAAI8H,UAAWjF,OAHX,UAGmBM,cAAAA,kBAC9B,OAEAvC,QAAUK,kBAAkB0G,uBACzB,IAAItC,SAAQ,CAACC,QAASC,UACzBzI,EAAEuG,KAAKC,MAAMxG,EAAGiG,UACXQ,MAAK0F,cAEFzH,cAAcmG,eAAgB/G,eACxBG,KAAOjE,EAAEoM,UAAUD,aAEnB6D,kBAAoBhQ,EAAEiE,KAAKgM,SACjCpF,eAAe3D,YAAY8I,mBAG3BhQ,YAAKoB,SAASM,sBAAasJ,sBAAa5J,SAASC,aAAc0F,MAC3D,CAACK,MAAO1D,YACJyB,eAAezB,SAASO,KAAK,UAKvB3B,cACVxB,aAAaoP,iBACb,CACIpI,WAAY7D,KACZ8B,OA7BL,UA8BKiK,kBAAmBA,kBAAkBnN,IAAI,IAE7CmN,mBAGOG,kBACPvF,0BACIoF,kBAAmBhQ,EAAEoB,SAASM,UAAY,IAAMsJ,WAChD/G,KACA/C,WACA8J,WAGRxC,QAAQvE,SACT8D,MAAKC,KAEU1F,cACV,6BACA,CAAC4F,UAAWF,GAAIjC,OAhDjB,WAiDC8E,gBAEOsF,kBACPhQ,aAAa+H,UAAUF,IAE3BS"} \ 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_copy_to_clipboard',\n 'core/modal_save_cancel',\n 'core/modal_events',\n 'core/key_codes',\n 'core/log',\n 'core_courseformat/courseeditor',\n 'core/event_dispatcher',\n 'core_course/events'\n ],\n function(\n $,\n ajax,\n templates,\n notification,\n str,\n url,\n Y,\n ModalCopyToClipboard,\n ModalSaveCancel,\n ModalEvents,\n KeyCodes,\n log,\n editor,\n EventDispatcher,\n 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', 'cmDelete', 'cmDuplicate', 'sectionHide', 'sectionShow',\n 'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight', 'cmMoveRight', 'cmMoveLeft',\n 'cmNoGroups', 'cmVisibleGroups', 'cmSeparateGroups',\n ];\n\n // The course reactive instance.\n const courseeditor = editor.getCurrentCourseEditor();\n\n // The current course format name (loaded on init).\n let formatname;\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n SECTIONACTIONMENUTRIGGER: '.section-actions',\n SECTIONITEM: '[data-for=\"section_title\"]',\n ADDSECTIONS: '.changenumsections [data-add-sections]',\n SECTIONBADGES: '[data-region=\"sectionbadges\"]',\n };\n\n Y.use('moodle-course-coursebase', function() {\n var courseformatselector = M.course.format.get_section_selector();\n if (courseformatselector) {\n SELECTOR.SECTIONLI = courseformatselector;\n }\n });\n\n /**\n * Dispatch event wrapper.\n *\n * Old jQuery events will be replaced by native events gradually.\n *\n * @method dispatchEvent\n * @param {String} eventName The name of the event\n * @param {Object} detail Any additional details to pass into the eveent\n * @param {Node|HTMLElement} container The point at which to dispatch the event\n * @param {Object} options\n * @param {Boolean} options.bubbles Whether to bubble up the DOM\n * @param {Boolean} options.cancelable Whether preventDefault() can be called\n * @param {Boolean} options.composed Whether the event can bubble across the ShadowDOM boundary\n * @returns {CustomEvent}\n */\n const dispatchEvent = function(eventName, detail, container, options) {\n // Most actions still uses jQuery node instead of regular HTMLElement.\n if (!(container instanceof Element) && container.get !== undefined) {\n container = container.get(0);\n }\n return EventDispatcher.dispatchEvent(eventName, detail, container, options);\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getId\n *\n * @param {JQuery} element\n * @returns {Integer}\n */\n var getModuleId = function(element) {\n // Check if we have a data-id first.\n const item = element.get(0);\n if (item.dataset.id) {\n return item.dataset.id;\n }\n // Use YUI way if data-id is not present.\n let id;\n Y.use('moodle-course-util', function(Y) {\n id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));\n });\n return id;\n };\n\n /**\n * Wrapper for Y.Moodle.core_course.util.cm.getName\n *\n * @param {JQuery} element\n * @returns {String}\n */\n var getModuleName = function(element) {\n var name;\n Y.use('moodle-course-util', function(Y) {\n name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));\n });\n // Check if we have the name in the course state.\n const state = courseeditor.state;\n const cmid = getModuleId(element);\n if (!name && state && cmid) {\n name = state.cm.get(cmid)?.name;\n }\n return name;\n };\n\n /**\n * Wrapper for M.util.add_spinner for an activity\n *\n * @param {JQuery} activity\n * @returns {Node}\n */\n var addActivitySpinner = function(activity) {\n activity.addClass(CSS.EDITINPROGRESS);\n var actionarea = activity.find(SELECTOR.ACTIONAREA).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the activity state element.\n if (activity.data('id') !== undefined) {\n courseeditor.dispatch('cmLock', [activity.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_spinner for a section\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionSpinner = function(sectionelement) {\n sectionelement.addClass(CSS.EDITINPROGRESS);\n var actionarea = sectionelement.find(SELECTOR.SECTIONACTIONMENU).get(0);\n if (actionarea) {\n var spinner = M.util.add_spinner(Y, Y.Node(actionarea));\n spinner.show();\n // Lock the section state element.\n if (sectionelement.data('id') !== undefined) {\n courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);\n }\n return spinner;\n }\n return null;\n };\n\n /**\n * Wrapper for M.util.add_lightbox\n *\n * @param {JQuery} sectionelement\n * @returns {Node}\n */\n var addSectionLightbox = function(sectionelement) {\n const item = sectionelement.get(0);\n var lightbox = M.util.add_lightbox(Y, Y.Node(item));\n if (item.dataset.for == 'section' && item.dataset.id) {\n courseeditor.dispatch('sectionLock', [item.dataset.id], true);\n lightbox.setAttribute('data-state', 'section');\n lightbox.setAttribute('data-state-id', item.dataset.id);\n }\n lightbox.show();\n return lightbox;\n };\n\n /**\n * Removes the spinner element\n *\n * @param {JQuery} element\n * @param {Node} spinner\n * @param {Number} delay\n */\n var removeSpinner = function(element, spinner, delay) {\n window.setTimeout(function() {\n element.removeClass(CSS.EDITINPROGRESS);\n if (spinner) {\n spinner.hide();\n }\n // Unlock the state element.\n if (element.data('id') !== undefined) {\n const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';\n courseeditor.dispatch(mutation, [element.data('id')], false);\n }\n }, delay);\n };\n\n /**\n * Removes the lightbox element\n *\n * @param {Node} lightbox lighbox YUI element returned by addSectionLightbox\n * @param {Number} delay\n */\n var removeLightbox = function(lightbox, delay) {\n if (lightbox) {\n window.setTimeout(function() {\n lightbox.hide();\n // Unlock state if necessary.\n if (lightbox.getAttribute('data-state')) {\n courseeditor.dispatch(\n `${lightbox.getAttribute('data-state')}Lock`,\n [lightbox.getAttribute('data-state-id')],\n false\n );\n }\n }, delay);\n }\n };\n\n /**\n * Initialise action menu for the element (section or module)\n *\n * @param {String} elementid CSS id attribute of the element\n */\n var initActionMenu = function(elementid) {\n // Initialise action menu in the new activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);\n });\n if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {\n M.core.actionmenu.newDOMNode(Y.one('#' + elementid));\n }\n };\n\n /**\n * Returns focus to the element that was clicked or \"Edit\" link if element is no longer visible.\n *\n * @param {String} elementId CSS id attribute of the element\n * @param {String} action data-action property of the element that was clicked\n */\n var focusActionItem = function(elementId, action) {\n var mainelement = $('#' + elementId);\n var selector = '[data-action=' + action + ']';\n if (action === 'groupsseparate' || action === 'groupsvisible' || action === 'groupsnone') {\n // New element will have different data-action.\n selector = '[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]';\n }\n if (mainelement.find(selector).is(':visible')) {\n mainelement.find(selector).focus();\n } else {\n // Element not visible, focus the \"Edit\" link.\n mainelement.find(SELECTOR.MENU).find(SELECTOR.TOGGLE).focus();\n }\n };\n\n /**\n * Find next
after the element\n *\n * @param {JQuery} mainElement element that is about to be deleted\n * @returns {JQuery}\n */\n var findNextFocusable = function(mainElement) {\n var tabables = $(\"a:visible\");\n var isInside = false;\n var foundElement = null;\n tabables.each(function() {\n if ($.contains(mainElement[0], this)) {\n isInside = true;\n } else if (isInside) {\n foundElement = this;\n return false; // Returning false in .each() is equivalent to \"break;\" inside the loop in php.\n }\n return true;\n });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : null\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('
' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const activityElement = $(element);\n var spinner = addActivitySpinner(activityElement);\n var promises = ajax.call([{\n methodname: 'core_course_get_module',\n args: {id: cmid, sectionreturn: sectionreturn}\n }], true);\n\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(function(data) {\n removeSpinner(activityElement, spinner, 400);\n replaceActivityHtmlWith(data);\n resolve(data);\n }).fail(function() {\n removeSpinner(activityElement, spinner);\n reject();\n });\n });\n };\n\n /**\n * Requests html for the section via WS core_course_edit_section and updates the section on the course page\n *\n * @param {JQuery|Element} element\n * @param {Number} sectionid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshSection = function(element, sectionid, sectionreturn) {\n\n if (sectionreturn === undefined) {\n sectionreturn = courseeditor.sectionReturn;\n }\n\n const sectionElement = $(element);\n const action = 'refresh';\n const promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action, sectionreturn},\n }], true);\n\n var spinner = addSectionSpinner(sectionElement);\n return new Promise((resolve, reject) => {\n $.when.apply($, promises)\n .done(dataencoded => {\n\n removeSpinner(sectionElement, spinner);\n const data = $.parseJSON(dataencoded);\n\n const newSectionElement = $(data.content);\n sectionElement.replaceWith(newSectionElement);\n\n // Init modules menus.\n $(`${SELECTOR.SECTIONLI}#${sectionid} ${SELECTOR.ACTIVITYLI}`).each(\n (index, activity) => {\n initActionMenu(activity.data('id'));\n }\n );\n\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n CourseEvents.sectionRefreshed,\n {\n ajaxreturn: data,\n action: action,\n newSectionElement: newSectionElement.get(0),\n },\n newSectionElement\n );\n\n if (!event.defaultPrevented) {\n defaultEditSectionHandler(\n newSectionElement, $(SELECTOR.SECTIONLI + '#' + sectionid),\n data,\n formatname,\n sectionid\n );\n }\n resolve(data);\n }).fail(ex => {\n // Trigger event that can be observed by course formats.\n const event = dispatchEvent(\n 'coursesectionrefreshfailed',\n {exception: ex, action: action},\n sectionElement\n );\n if (!event.defaultPrevented) {\n notification.exception(ex);\n }\n reject();\n });\n });\n };\n\n /**\n * Displays the delete confirmation to delete a module\n *\n * @param {JQuery} mainelement activity element we perform action on\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmDeleteModule = function(mainelement, onconfirm) {\n var modtypename = mainelement.attr('class').match(/modtype_([^\\s]*)/)[1];\n var modulename = getModuleName(mainelement);\n\n str.get_string('pluginname', modtypename).done(function(pluginname) {\n var plugindata = {\n type: pluginname,\n name: modulename\n };\n str.get_strings([\n {key: 'confirm', component: 'core'},\n {key: modulename === null ? 'deletechecktype' : 'deletechecktypename', param: plugindata},\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], s[1], s[2], s[3], onconfirm);\n }\n );\n });\n };\n\n /**\n * Displays the delete confirmation to delete a section\n *\n * @param {String} message confirmation message\n * @param {function} onconfirm function to execute on confirm\n */\n var confirmEditSection = function(message, onconfirm) {\n str.get_strings([\n {key: 'confirm'}, // TODO link text\n {key: 'yes'},\n {key: 'no'}\n ]).done(function(s) {\n notification.confirm(s[0], message, s[1], s[2], onconfirm);\n }\n );\n };\n\n /**\n * Replaces an action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * @param {JQuery} actionitem\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n * @return {Promise} promise which is resolved when the replacement has completed\n */\n var replaceActionItem = function(actionitem, image, stringname,\n stringcomponent, newaction) {\n\n var stringRequests = [{key: stringname, component: stringcomponent}];\n // Do not provide an icon with duplicate, different text to the menu item.\n\n return str.get_strings(stringRequests).then(function(strings) {\n actionitem.find('span.menu-action-text').html(strings[0]);\n\n return templates.renderPix(image, 'core');\n }).then(function(pixhtml) {\n actionitem.find('.icon').replaceWith(pixhtml);\n actionitem.attr('data-action', newaction);\n return;\n }).catch(notification.exception);\n };\n\n /**\n * Default post-processing for section AJAX edit actions.\n *\n * This can be overridden in course formats by listening to event coursesectionedited:\n *\n * $('body').on('coursesectionedited', 'li.section', function(e) {\n * var action = e.action,\n * sectionElement = $(e.target),\n * data = e.ajaxreturn;\n * // ... Do some processing here.\n * e.preventDefault(); // Prevent default handler.\n * });\n *\n * @param {JQuery} sectionElement\n * @param {JQuery} actionItem\n * @param {Object} data\n * @param {String} courseformat\n * @param {Number} sectionid\n */\n var defaultEditSectionHandler = function(sectionElement, actionItem, data, courseformat, sectionid) {\n var action = actionItem.attr('data-action');\n if (action === 'hide' || action === 'show') {\n if (action === 'hide') {\n sectionElement.addClass('hidden');\n setSectionBadge(sectionElement[0], 'hiddenfromstudents', true, false);\n replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\n setSectionBadge(sectionElement[0], 'hiddenfromstudents', false, false);\n sectionElement.removeClass('hidden');\n replaceActionItem(actionItem, 'i/hide',\n 'hidefromothers', 'format_' + courseformat, 'hide');\n }\n // Replace the modules with new html (that indicates that they are now hidden or not hidden).\n if (data.modules !== undefined) {\n for (var i in data.modules) {\n replaceActivityHtmlWith(data.modules[i]);\n }\n }\n // Replace the section availability information.\n if (data.section_availability !== undefined) {\n sectionElement.find('.section_availability').first().replaceWith(data.section_availability);\n }\n // Modify course state.\n const section = courseeditor.state.section.get(sectionid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [sectionid]);\n }\n } else if (action === 'setmarker') {\n var oldmarker = $(SELECTOR.SECTIONLI + '.current'),\n oldActionItem = oldmarker.find(SELECTOR.SECTIONACTIONMENU + ' ' + 'a[data-action=removemarker]');\n oldmarker.removeClass('current');\n replaceActionItem(oldActionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n sectionElement.addClass('current');\n replaceActionItem(actionItem, 'i/marked',\n 'highlightoff', 'core', 'removemarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n setSectionBadge(sectionElement[0], 'iscurrent', true, true);\n } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n setSectionBadge(sectionElement[0], 'iscurrent', false, true);\n }\n };\n\n /**\n * Get the focused element path in an activity if any.\n *\n * This method is used to restore focus when the activity HTML is refreshed.\n * Only the main course editor elements can be refocused as they are always present\n * even if the activity content changes.\n *\n * @param {String} id the element id the activity element\n * @return {String|undefined} the inner path of the focused element or undefined\n */\n const getActivityFocusedElement = function(id) {\n const element = document.getElementById(id);\n if (!element || !element.contains(document.activeElement)) {\n return undefined;\n }\n // Check if the actions menu toggler is focused.\n if (element.querySelector(SELECTOR.ACTIONAREA).contains(document.activeElement)) {\n return `${SELECTOR.ACTIONAREA} [tabindex=\"0\"]`;\n }\n // Return the current element id if any.\n if (document.activeElement.id) {\n return `#${document.activeElement.id}`;\n }\n return undefined;\n };\n\n /**\n * Replaces the course module with the new html (used to update module after it was edited or its visibility was changed).\n *\n * @param {String} activityHTML\n */\n var replaceActivityHtmlWith = function(activityHTML) {\n $('
' + activityHTML + '
').find(SELECTOR.ACTIVITYLI).each(function() {\n // Extract id from the new activity html.\n var id = $(this).attr('id');\n // Check if the current focused element is inside the activity.\n let focusedPath = getActivityFocusedElement(id);\n // Find the existing element with the same id and replace its contents with new html.\n $(SELECTOR.ACTIVITYLI + '#' + id).replaceWith(activityHTML);\n // Initialise action menu.\n initActionMenu(id);\n // Re-focus the previous elements.\n if (focusedPath) {\n const newItem = document.getElementById(id);\n newItem.querySelector(focusedPath)?.focus();\n }\n\n });\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {Nunmber} sectionid\n * @param {JQuery} target the element (menu item) that was clicked\n * @param {String} courseformat\n * @return {boolean} true the action call is sent to the server or false if it is ignored.\n */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : null;\n\n // Filter direct component handled actions.\n if (courseeditor.supportComponents && componentActions.includes(action)) {\n return false;\n }\n\n var spinner = addSectionSpinner(sectionElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_section',\n args: {id: sectionid, action: action, sectionreturn: sectionreturn}\n }], true);\n\n var lightbox = addSectionLightbox(sectionElement);\n $.when.apply($, promises)\n .done(function(dataencoded) {\n var data = $.parseJSON(dataencoded);\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n sectionElement.find(SELECTOR.SECTIONACTIONMENU).find(SELECTOR.TOGGLE).focus();\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectionedited', {ajaxreturn: data, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n defaultEditSectionHandler(sectionElement, target, data, courseformat, sectionid);\n }\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(sectionElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursesectioneditfailed', {exception: ex, action: action});\n sectionElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n return true;\n };\n\n /**\n * Sets the section badge in the section header.\n *\n * @param {JQuery} sectionElement section element we perform action on\n * @param {String} badgetype the type of badge this is for\n * @param {bool} add true to add, false to remove\n * @param {boolean} removeOther in case of adding a badge, whether to remove all other.\n */\n var setSectionBadge = function(sectionElement, badgetype, add, removeOther) {\n const sectionbadges = sectionElement.querySelector(SELECTOR.SECTIONBADGES);\n if (!sectionbadges) {\n return;\n }\n const badge = sectionbadges.querySelector('[data-type=\"' + badgetype + '\"]');\n if (!badge) {\n return;\n }\n if (add) {\n if (removeOther) {\n document.querySelectorAll('[data-type=\"' + badgetype + '\"]').forEach((b) => {\n b.classList.add('d-none');\n });\n }\n badge.classList.remove('d-none');\n } else {\n badge.classList.add('d-none');\n }\n };\n\n // Register a function to be executed after D&D of an activity.\n Y.use('moodle-course-coursebase', function() {\n M.course.coursebase.register_module({\n // Ignore camelcase eslint rule for the next line because it is an expected name of the callback.\n // eslint-disable-next-line camelcase\n set_visibility_resource_ui: function(args) {\n var mainelement = $(args.element.getDOMNode());\n var cmid = getModuleId(mainelement);\n if (cmid) {\n var sectionreturn = mainelement.find('.' + CSS.EDITINGMOVE).attr('data-sectionreturn');\n refreshModule(mainelement, cmid, sectionreturn);\n }\n },\n /**\n * Update the course state when some cm is moved via YUI.\n * @param {*} params\n */\n updateMovedCmState: (params) => {\n const state = courseeditor.state;\n\n // Update old section.\n const cm = state.cm.get(params.cmid);\n if (cm !== undefined) {\n courseeditor.dispatch('sectionState', [cm.sectionid]);\n }\n // Update cm state.\n courseeditor.dispatch('cmState', [params.cmid]);\n },\n /**\n * Update the course state when some section is moved via YUI.\n */\n updateMovedSectionState: () => {\n courseeditor.dispatch('courseState');\n },\n });\n });\n\n // From Moodle 4.0 all edit actions are being re-implemented as state mutation.\n // This means all method from this \"actions\" module will be deprecated when all the course\n // interface is migrated to reactive components.\n // Most legacy actions did not provide enough information to regenarate the course so they\n // use the mutations courseState, sectionState and cmState to get the updated state from\n // the server. However, some activity actions where we can prevent an extra webservice\n // call by implementing an adhoc mutation.\n courseeditor.addMutations({\n /**\n * Compatibility function to update Moodle 4.0 course state using legacy actions.\n *\n * This method only updates some actions which does not require to use cmState mutation\n * to get updated data form the server.\n *\n * @param {Object} statemanager the current state in read write mode\n * @param {String} action the performed action\n * @param {Number} cmid the affected course module id\n * @param {Array} affectedids all affected cm ids (for duplicate action)\n */\n legacyActivityAction: function(statemanager, action, cmid, affectedids) {\n\n const state = statemanager.state;\n const cm = state.cm.get(cmid);\n if (cm === undefined) {\n return;\n }\n const section = state.section.get(cm.sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked.\n courseeditor.dispatch('cmLock', [cm.id], true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This unlocked will take effect when the read only is restored.\n cm.locked = false;\n\n switch (action) {\n case 'delete':\n // Remove from section.\n section.cmlist = section.cmlist.reduce(\n (cmlist, current) => {\n if (current != cmid) {\n cmlist.push(current);\n }\n return cmlist;\n },\n []\n );\n // Delete form list.\n state.cm.delete(cmid);\n break;\n\n case 'hide':\n case 'show':\n case 'duplicate':\n courseeditor.dispatch('cmState', affectedids);\n break;\n }\n statemanager.setReadOnly(true);\n },\n legacySectionAction: function(statemanager, action, sectionid) {\n\n const state = statemanager.state;\n const section = state.section.get(sectionid);\n if (section === undefined) {\n return;\n }\n\n // Send the element is locked. Reactive events are only triggered when the state\n // read only mode is restored. We want to notify the interface the element is\n // locked so we need to do a quick lock operation before performing the rest\n // of the mutation.\n statemanager.setReadOnly(false);\n section.locked = true;\n statemanager.setReadOnly(true);\n\n // Now we do the real mutation.\n statemanager.setReadOnly(false);\n\n // This locked will take effect when the read only is restored.\n section.locked = false;\n\n switch (action) {\n case 'setmarker':\n // Remove previous marker.\n state.section.forEach((current) => {\n if (current.id != sectionid) {\n current.current = false;\n }\n });\n section.current = true;\n break;\n\n case 'removemarker':\n section.current = false;\n break;\n }\n statemanager.setReadOnly(true);\n },\n });\n\n return /** @alias module:core_course/actions */ {\n\n /**\n * Initialises course page\n *\n * @method init\n * @param {String} courseformat name of the current course format (for fetching strings)\n */\n initCoursePage: function(courseformat) {\n\n formatname = courseformat;\n\n // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section action menu.\n $('body').on('click keypress',\n SELECTOR.SECTIONACTIONMENUTRIGGER + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENUTRIGGER).attr('data-sectionid');\n\n if (actionItem.attr('data-action') === 'permalink') {\n e.preventDefault();\n ModalCopyToClipboard.create({\n text: actionItem.attr('href'),\n }, str.get_string('sectionlink', 'course')\n );\n return;\n }\n\n let isExecuted = true;\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n isExecuted = editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n // Prevent any other module from capturing the action if it is already in execution.\n if (isExecuted) {\n e.preventDefault();\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONITEM} [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-itemtype=\"activityname\"][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 const trigger = $(SELECTOR.ADDSECTIONS);\n const modalTitle = trigger.attr('data-add-sections');\n const newSections = trigger.attr('data-new-sections');\n str.get_string('numberweeks')\n .then(function(strNumberSections) {\n var modalBody = $('
' +\n '
');\n modalBody.find('label').html(strNumberSections);\n\n return modalBody.html();\n })\n .then((body) => ModalSaveCancel.create({\n body,\n title: modalTitle,\n }))\n .then(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n\n trigger.on('click', (e) => {\n e.preventDefault();\n modal.show();\n });\n\n return modal;\n })\n .catch(notification.exception);\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n refreshSection,\n };\n });\n"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalCopyToClipboard","ModalSaveCancel","ModalEvents","KeyCodes","log","editor","EventDispatcher","CourseEvents","componentActions","courseeditor","getCurrentCourseEditor","formatname","CSS","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","SECTIONACTIONMENUTRIGGER","SECTIONITEM","ADDSECTIONS","SECTIONBADGES","use","courseformatselector","M","course","format","get_section_selector","dispatchEvent","eventName","detail","container","options","Element","undefined","get","getModuleId","element","item","dataset","id","Moodle","core_course","util","cm","getId","Node","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","for","setAttribute","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","getAttribute","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","editModule","moduleElement","cmid","target","action","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","mainElement","tabables","isInside","foundElement","elementToFocus","each","contains","this","replaceWith","affectedids","index","elementId","mainelement","selector","is","focus","focusActionItem","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","sectionReturn","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","name","getName","state","_state$cm$get","getModuleName","get_string","pluginname","plugindata","type","get_strings","key","component","param","s","confirm","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","stringRequests","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","setSectionBadge","modules","i","section_availability","first","section","oldmarker","oldActionItem","activityHTML","focusedPath","document","getElementById","activeElement","querySelector","getActivityFocusedElement","editSection","supportComponents","includes","dataencoded","parseJSON","badgetype","add","removeOther","sectionbadges","badge","querySelectorAll","forEach","b","classList","remove","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","legacySectionAction","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","create","text","isExecuted","message","itemid","modalTitle","newSections","strNumberSections","modalBody","body","title","modal","numSections","getBody","addSections","parseInt","val","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug","refreshSection","newSectionElement","content","sectionRefreshed","defaultPrevented"],"mappings":";;;;;;;;AAuBAA,6BACI,CACI,SACA,YACA,iBACA,oBACA,WACA,WACA,WACA,+BACA,yBACA,oBACA,iBACA,WACA,iCACA,wBACA,uBAEJ,SACIC,EACAC,KACAC,UACAC,aACAC,IACAC,IACAC,EACAC,qBACAC,gBACAC,YACAC,SACAC,IACAC,OACAC,gBACAC,oBAMMC,iBAAmB,CACrB,cAAe,SAAU,aAAc,gBAAiB,WAAY,cAAe,cAAe,cAClG,SAAU,SAAU,YAAa,mBAAoB,qBAAsB,cAAe,aAC1F,aAAc,kBAAmB,oBAI/BC,aAAeJ,OAAOK,6BAGxBC,eAEAC,mBACgB,iBADhBA,gBAGa,eAEbC,SAAW,CACXC,WAAY,cACZC,WAAY,WACZC,eAAgB,mBAChBC,KAAM,0DACNC,OAAQ,mCACRC,UAAW,aACXC,kBAAmB,uBACnBC,yBAA0B,mBAC1BC,YAAa,6BACbC,YAAa,yCACbC,cAAe,iCAGnBzB,EAAE0B,IAAI,4BAA4B,eAC1BC,qBAAuBC,EAAEC,OAAOC,OAAOC,uBACvCJ,uBACAb,SAASM,UAAYO,+BAmBvBK,cAAgB,SAASC,UAAWC,OAAQC,UAAWC,gBAEnDD,qBAAqBE,cAA8BC,IAAlBH,UAAUI,MAC7CJ,UAAYA,UAAUI,IAAI,IAEvBhC,gBAAgByB,cAAcC,UAAWC,OAAQC,UAAWC,cASnEI,YAAc,SAASC,eAEjBC,KAAOD,QAAQF,IAAI,MACrBG,KAAKC,QAAQC,UACNF,KAAKC,QAAQC,OAGpBA,UACJ5C,EAAE0B,IAAI,sBAAsB,SAAS1B,GACjC4C,GAAK5C,EAAE6C,OAAOC,YAAYC,KAAKC,GAAGC,MAAMjD,EAAEkD,KAAKR,UAE5CE,IA6BPO,mBAAqB,SAASC,UAC9BA,SAASC,SAASxC,wBACdyC,WAAaF,SAASG,KAAKzC,SAASE,YAAYuB,IAAI,MACpDe,WAAY,KACRE,QAAU5B,EAAEmB,KAAKU,YAAYzD,EAAGA,EAAEkD,KAAKI,oBAC3CE,QAAQE,YAEoBpB,IAAxBc,SAASO,KAAK,OACdjD,aAAakD,SAAS,SAAU,CAACR,SAASO,KAAK,QAAQ,GAEpDH,eAEJ,MASPK,kBAAoB,SAASC,gBAC7BA,eAAeT,SAASxC,wBACpByC,WAAaQ,eAAeP,KAAKzC,SAASO,mBAAmBkB,IAAI,MACjEe,WAAY,KACRE,QAAU5B,EAAEmB,KAAKU,YAAYzD,EAAGA,EAAEkD,KAAKI,oBAC3CE,QAAQE,YAE0BpB,IAA9BwB,eAAeH,KAAK,OACpBjD,aAAakD,SAAS,cAAe,CAACE,eAAeH,KAAK,QAAQ,GAE/DH,eAEJ,MASPO,mBAAqB,SAASD,sBACxBpB,KAAOoB,eAAevB,IAAI,OAC5ByB,SAAWpC,EAAEmB,KAAKkB,aAAajE,EAAGA,EAAEkD,KAAKR,aACrB,WAApBA,KAAKC,QAAQuB,KAAoBxB,KAAKC,QAAQC,KAC9ClC,aAAakD,SAAS,cAAe,CAAClB,KAAKC,QAAQC,KAAK,GACxDoB,SAASG,aAAa,aAAc,WACpCH,SAASG,aAAa,gBAAiBzB,KAAKC,QAAQC,KAExDoB,SAASN,OACFM,UAUPI,cAAgB,SAAS3B,QAASe,QAASa,OAC3CC,OAAOC,YAAW,cACd9B,QAAQ+B,YAAY3D,oBAChB2C,SACAA,QAAQiB,YAGenC,IAAvBG,QAAQkB,KAAK,MAAqB,OAC5Be,SAAoC,YAAxBjC,QAAQkB,KAAK,OAAwB,cAAgB,SACvEjD,aAAakD,SAASc,SAAU,CAACjC,QAAQkB,KAAK,QAAQ,MAE3DU,QASHM,eAAiB,SAASX,SAAUK,OAChCL,UACAM,OAAOC,YAAW,WACdP,SAASS,OAELT,SAASY,aAAa,eACtBlE,aAAakD,mBACNI,SAASY,aAAa,sBACzB,CAACZ,SAASY,aAAa,mBACvB,KAGTP,QASPQ,eAAiB,SAASC,WAE1B9E,EAAE0B,IAAI,4BAA4B,WAC9BE,EAAEC,OAAOkD,WAAWC,gBAAgB,qBAAsB,IAAMF,cAEhElD,EAAEqD,KAAKC,YAActD,EAAEqD,KAAKC,WAAWC,YACvCvD,EAAEqD,KAAKC,WAAWC,WAAWnF,EAAEoF,IAAI,IAAMN,aAsD7CO,WAAa,SAASC,cAAeC,KAAMC,YAWvCxB,SAVAyB,OAASD,OAAOE,KAAK,eACrBlC,QAAUL,mBAAmBmC,eAC7BK,SAAWhG,KAAKiG,KAAK,CAAC,CACtBC,WAAY,0BACZC,KAAM,CAAClD,GAAI2C,KACPE,OAAQA,OACRM,cAAeP,OAAOE,KAAK,sBAAwBF,OAAOE,KAAK,sBAAwB,SAE3F,GAGW,cAAXD,SACAzB,SAAWD,mBAAmByB,OAAOQ,QAAQlF,SAASM,aAE1D1B,EAAEuG,KAAKC,MAAMxG,EAAGiG,UACXQ,MAAK,SAASxC,UAvCUyC,YACzBC,SACAC,SACAC,aAqCQC,gBAxCiBJ,YAwCkBd,cAvC3Ce,SAAW3G,EAAE,aACb4G,UAAW,EACXC,aAAe,KACnBF,SAASI,MAAK,cACN/G,EAAEgH,SAASN,YAAY,GAAIO,MAC3BL,UAAW,OACR,GAAIA,gBACPC,aAAeI,MACR,SAEJ,KAEJJ,cA4BCjB,cAAcsB,YAAYjD,UACtBkD,YAAc,GAElBnH,EAAE,QAAUiE,KAAO,UAAUJ,KAAKzC,SAASC,YAAY0F,MAAK,SAASK,OACjEjC,eAAenF,EAAEiH,MAAMjB,KAAK,OACd,IAAVoB,SAnEE,SAASC,UAAWtB,YAClCuB,YAActH,EAAE,IAAMqH,WACtBE,SAAW,gBAAkBxB,OAAS,IAC3B,mBAAXA,QAA0C,kBAAXA,QAAyC,eAAXA,SAE7DwB,SAAW,qFAEXD,YAAYzD,KAAK0D,UAAUC,GAAG,YAC9BF,YAAYzD,KAAK0D,UAAUE,QAG3BH,YAAYzD,KAAKzC,SAASI,MAAMqC,KAAKzC,SAASK,QAAQgG,QAyD1CC,CAAgB1H,EAAEiH,MAAMjB,KAAK,MAAOD,QACpCe,eAAiB,MAGrBK,YAAYQ,KAAK7E,YAAY9C,EAAEiH,WAG/BH,gBACAA,eAAeW,QAGnB/C,cAAckB,cAAe9B,QAAS,KACtCmB,eAAeX,SAAU,KAEzBsB,cAAcgC,QAAQ5H,EAAE6H,MAAM,qBAAsB,CAACC,WAAY7D,KAAM8B,OAAQA,UAG/E/E,aAAakD,SAAS,uBAAwB6B,OAAQF,KAAMsB,gBAE7DY,MAAK,SAASC,IAEbtD,cAAckB,cAAe9B,SAC7BmB,eAAeX,cAEX2D,EAAIjI,EAAE6H,MAAM,yBAA0B,CAACK,UAAWF,GAAIjC,OAAQA,SAClEH,cAAcgC,QAAQK,GACjBA,EAAEE,sBACHhI,aAAa+H,UAAUF,QAenCI,cAAgB,SAASrF,QAAS8C,KAAMQ,oBAElBzD,IAAlByD,gBACAA,cAAgBrF,aAAaqH,qBAG3BC,gBAAkBtI,EAAE+C,aACtBe,QAAUL,mBAAmB6E,iBAC7BrC,SAAWhG,KAAKiG,KAAK,CAAC,CACtBC,WAAY,yBACZC,KAAM,CAAClD,GAAI2C,KAAMQ,cAAeA,kBAChC,UAEG,IAAIkC,SAAQ,CAACC,QAASC,UACzBzI,EAAEuG,KAAKC,MAAMxG,EAAGiG,UACXQ,MAAK,SAASxC,MACXS,cAAc4D,gBAAiBxE,QAAS,KACxC4E,wBAAwBzE,MACxBuE,QAAQvE,SACT8D,MAAK,WACJrD,cAAc4D,gBAAiBxE,SAC/B2E,gBAqFZE,oBAAsB,SAASrB,YAAasB,eACxCC,YAAcvB,YAAYtB,KAAK,SAAS8C,MAAM,oBAAoB,GAClEC,WApWY,SAAShG,aACrBiG,KACJ1I,EAAE0B,IAAI,sBAAsB,SAAS1B,GACjC0I,KAAO1I,EAAE6C,OAAOC,YAAYC,KAAKC,GAAG2F,QAAQ3I,EAAEkD,KAAKT,QAAQF,IAAI,cAG7DqG,MAAQlI,aAAakI,MACrBrD,KAAO/C,YAAYC,kCACpBiG,MAAQE,OAASrD,OAClBmD,2BAAOE,MAAM5F,GAAGT,IAAIgD,sCAAbsD,cAAoBH,MAExBA,KAyVUI,CAAc9B,aAE/BlH,IAAIiJ,WAAW,aAAcR,aAAapC,MAAK,SAAS6C,gBAChDC,WAAa,CACbC,KAAMF,WACNN,KAAMD,YAEV3I,IAAIqJ,YAAY,CACZ,CAACC,IAAK,UAAWC,UAAW,QAC5B,CAACD,IAAoB,OAAfX,WAAsB,kBAAoB,sBAAuBa,MAAOL,YAC9E,CAACG,IAAK,OACN,CAACA,IAAK,QACPjD,MAAK,SAASoD,GACT1J,aAAa2J,QAAQD,EAAE,GAAIA,EAAE,GAAIA,EAAE,GAAIA,EAAE,GAAIjB,kBAiCzDmB,kBAAoB,SAASC,WAAYC,MAAOC,WACjBC,gBAAiBC,eAE5CC,eAAiB,CAAC,CAACX,IAAKQ,WAAYP,UAAWQ,yBAG5C/J,IAAIqJ,YAAYY,gBAAgBC,MAAK,SAASC,gBACjDP,WAAWnG,KAAK,yBAAyB2G,KAAKD,QAAQ,IAE/CrK,UAAUuK,UAAUR,MAAO,WACnCK,MAAK,SAASI,SACbV,WAAWnG,KAAK,SAASqD,YAAYwD,SACrCV,WAAWhE,KAAK,cAAeoE,cAEhCO,MAAMxK,aAAa+H,YAsBtB0C,0BAA4B,SAASC,eAAgBC,WAAY7G,KAAM8G,aAAcC,eACjFjF,OAAS+E,WAAW9E,KAAK,kBACd,SAAXD,QAAgC,SAAXA,OAAmB,IACzB,SAAXA,QACA8E,eAAelH,SAAS,UACxBsH,gBAAgBJ,eAAe,GAAI,sBAAsB,GAAM,GAC/Dd,kBAAkBe,WAAY,SAC1B,iBAAkB,UAAYC,aAAc,UAEhDE,gBAAgBJ,eAAe,GAAI,sBAAsB,GAAO,GAChEA,eAAe/F,YAAY,UAC3BiF,kBAAkBe,WAAY,SAC1B,iBAAkB,UAAYC,aAAc,cAG/BnI,IAAjBqB,KAAKiH,YACA,IAAIC,KAAKlH,KAAKiH,QACfxC,wBAAwBzE,KAAKiH,QAAQC,SAIXvI,IAA9BqB,KAAKmH,sBACLP,eAAehH,KAAK,yBAAyBwH,QAAQnE,YAAYjD,KAAKmH,2BAI1DxI,IADA5B,aAAakI,MAAMoC,QAAQzI,IAAImI,YAE3ChK,aAAakD,SAAS,eAAgB,CAAC8G,iBAExC,GAAe,cAAXjF,OAAwB,KAC3BwF,UAAYvL,EAAEoB,SAASM,UAAY,YACnC8J,cAAgBD,UAAU1H,KAAKzC,SAASO,kBAATP,gCACnCmK,UAAUzG,YAAY,WACtBiF,kBAAkByB,cAAe,WAC7B,YAAa,OAAQ,aACzBX,eAAelH,SAAS,WACxBoG,kBAAkBe,WAAY,WAC1B,eAAgB,OAAQ,gBAC5B9J,aAAakD,SAAS,sBAAuB6B,OAAQiF,WACrDC,gBAAgBJ,eAAe,GAAI,aAAa,GAAM,OACpC,iBAAX9E,SACP8E,eAAe/F,YAAY,WAC3BiF,kBAAkBe,WAAY,WAC1B,YAAa,OAAQ,aACzB9J,aAAakD,SAAS,sBAAuB6B,OAAQiF,WACrDC,gBAAgBJ,eAAe,GAAI,aAAa,GAAO,SAmC3DnC,wBAA0B,SAAS+C,cACnCzL,EAAE,QAAUyL,aAAe,UAAU5H,KAAKzC,SAASC,YAAY0F,MAAK,eAE5D7D,GAAKlD,EAAEiH,MAAMjB,KAAK,UAElB0F,YA1BsB,SAASxI,UACjCH,QAAU4I,SAASC,eAAe1I,OACnCH,SAAYA,QAAQiE,SAAS2E,SAASE,sBAIvC9I,QAAQ+I,cAAc1K,SAASE,YAAY0F,SAAS2E,SAASE,yBACnDzK,SAASE,8BAGnBqK,SAASE,cAAc3I,cACZyI,SAASE,cAAc3I,WAehB6I,CAA0B7I,OAE5ClD,EAAEoB,SAASC,WAAa,IAAM6B,IAAIgE,YAAYuE,cAE9CtG,eAAejC,IAEXwI,YAAa,yDACGC,SAASC,eAAe1I,IAChC4I,cAAcJ,qEAAcjE,aAe5CuE,YAAc,SAASnB,eAAgBG,UAAWlF,OAAQiF,kBACtDhF,OAASD,OAAOE,KAAK,eACrBK,cAAgBP,OAAOE,KAAK,sBAAwBF,OAAOE,KAAK,sBAAwB,QAGxFhF,aAAaiL,mBAAqBlL,iBAAiBmL,SAASnG,eACrD,MAGPjC,QAAUK,kBAAkB0G,gBAC5B5E,SAAWhG,KAAKiG,KAAK,CAAC,CACtBC,WAAY,2BACZC,KAAM,CAAClD,GAAI8H,UAAWjF,OAAQA,OAAQM,cAAeA,kBACrD,GAEA/B,SAAWD,mBAAmBwG,uBAClC7K,EAAEuG,KAAKC,MAAMxG,EAAGiG,UACXQ,MAAK,SAAS0F,iBACPlI,KAAOjE,EAAEoM,UAAUD,aACvBzH,cAAcmG,eAAgB/G,SAC9BmB,eAAeX,UACfuG,eAAehH,KAAKzC,SAASO,mBAAmBkC,KAAKzC,SAASK,QAAQgG,YAElEQ,EAAIjI,EAAE6H,MAAM,sBAAuB,CAACC,WAAY7D,KAAM8B,OAAQA,SAClE8E,eAAejD,QAAQK,GAClBA,EAAEE,sBACHyC,0BAA0BC,eAAgB/E,OAAQ7B,KAAM8G,aAAcC,cAE3EjD,MAAK,SAASC,IAEbtD,cAAcmG,eAAgB/G,SAC9BmB,eAAeX,cAEX2D,EAAIjI,EAAE6H,MAAM,0BAA2B,CAACK,UAAWF,GAAIjC,OAAQA,SACnE8E,eAAejD,QAAQK,GAClBA,EAAEE,sBACHhI,aAAa+H,UAAUF,QAG5B,GAWPiD,gBAAkB,SAASJ,eAAgBwB,UAAWC,IAAKC,mBACrDC,cAAgB3B,eAAeiB,cAAc1K,SAASW,mBACvDyK,2BAGCC,MAAQD,cAAcV,cAAc,eAAiBO,UAAY,MAClEI,QAGDH,KACIC,aACAZ,SAASe,iBAAiB,eAAiBL,UAAY,MAAMM,SAASC,IAClEA,EAAEC,UAAUP,IAAI,aAGxBG,MAAMI,UAAUC,OAAO,WAEvBL,MAAMI,UAAUP,IAAI,mBAK5BhM,EAAE0B,IAAI,4BAA4B,WAC9BE,EAAEC,OAAOkD,WAAW0H,gBAAgB,CAGhCC,2BAA4B,SAAS5G,UAC7BkB,YAActH,EAAEoG,KAAKrD,QAAQkK,cAC7BpH,KAAO/C,YAAYwE,gBACnBzB,KAAM,KACFQ,cAAgBiB,YAAYzD,KAAK,IAAM1C,iBAAiB6E,KAAK,sBACjEoC,cAAcd,YAAazB,KAAMQ,iBAOzC6G,mBAAqBC,eAIX7J,GAHQtC,aAAakI,MAGV5F,GAAGT,IAAIsK,OAAOtH,WACpBjD,IAAPU,IACAtC,aAAakD,SAAS,eAAgB,CAACZ,GAAG0H,YAG9ChK,aAAakD,SAAS,UAAW,CAACiJ,OAAOtH,QAK7CuH,wBAAyB,KACrBpM,aAAakD,SAAS,qBAYlClD,aAAaqM,aAAa,CAYtBC,qBAAsB,SAASC,aAAcxH,OAAQF,KAAMsB,mBAEjD+B,MAAQqE,aAAarE,MACrB5F,GAAK4F,MAAM5F,GAAGT,IAAIgD,cACbjD,IAAPU,gBAGEgI,QAAUpC,MAAMoC,QAAQzI,IAAIS,GAAG0H,mBACrBpI,IAAZ0I,gBAKJtK,aAAakD,SAAS,SAAU,CAACZ,GAAGJ,KAAK,GAGzCqK,aAAaC,aAAY,GAGzBlK,GAAGmK,QAAS,EAEJ1H,YACC,SAEDuF,QAAQoC,OAASpC,QAAQoC,OAAOC,QAC5B,CAACD,OAAQE,WACDA,SAAW/H,MACX6H,OAAO/F,KAAKiG,SAETF,SAEX,IAGJxE,MAAM5F,GAAGuK,OAAOhI,gBAGf,WACA,WACA,YACD7E,aAAakD,SAAS,UAAWiD,aAGzCoG,aAAaC,aAAY,KAE7BM,oBAAqB,SAASP,aAAcxH,OAAQiF,iBAE1C9B,MAAQqE,aAAarE,MACrBoC,QAAUpC,MAAMoC,QAAQzI,IAAImI,mBAClBpI,IAAZ0I,gBAQJiC,aAAaC,aAAY,GACzBlC,QAAQmC,QAAS,EACjBF,aAAaC,aAAY,GAGzBD,aAAaC,aAAY,GAGzBlC,QAAQmC,QAAS,EAET1H,YACC,YAEDmD,MAAMoC,QAAQqB,SAASiB,UACfA,QAAQ1K,IAAM8H,YACd4C,QAAQA,SAAU,MAG1BtC,QAAQsC,SAAU,YAGjB,eACDtC,QAAQsC,SAAU,EAG1BL,aAAaC,aAAY,OAIe,CAQ5CO,eAAgB,SAAShD,iBAErB7J,WAAa6J,aAGb/K,EAAE,QAAQgO,GAAG,iBAAkB5M,SAASC,WAAa,IAC7CD,SAASG,eAAiB,iBAAiB,SAAS0G,MACzC,aAAXA,EAAEuB,MAAqC,KAAdvB,EAAEgG,aAG3BnD,WAAa9K,EAAEiH,MACfrB,cAAgBkF,WAAWxE,QAAQlF,SAASC,YAC5C0E,OAAS+E,WAAW9E,KAAK,eACzBkI,SAAWpL,YAAY8C,sBACnBG,YACC,eACA,gBACA,aACA,gBACA,WACA,cACA,WACA,qBACA,oBACA,kCAMJmI,WAGLjG,EAAEkG,iBACa,WAAXpI,OAEA4C,oBAAoB/C,eAAe,WAC/BD,WAAWC,cAAesI,SAAUpD,eAGxCnF,WAAWC,cAAesI,SAAUpD,iBAK5C9K,EAAE,QAAQgO,GAAG,iBACD5M,SAASQ,yBAATR,mCACkB,SAAS6G,MACpB,aAAXA,EAAEuB,MAAqC,KAAdvB,EAAEgG,mBAG3BnD,WAAa9K,EAAEiH,MACf4D,eAAiBC,WAAWxE,QAAQlF,SAASM,WAC7C0M,UAAYtD,WAAWxE,QAAQlF,SAASQ,0BAA0BoE,KAAK,qBAEpC,cAAnC8E,WAAW9E,KAAK,sBAChBiC,EAAEkG,sBACF5N,qBAAqB8N,OAAO,CACxBC,KAAMxD,WAAW9E,KAAK,SACvB5F,IAAIiJ,WAAW,cAAe,eAKjCkF,YAAa,EAlcJ,IAASC,QAAS5F,UAmc3BkC,WAAW9E,KAAK,iBAncEwI,QAqcC1D,WAAW9E,KAAK,gBArcR4C,UAqcyB,WAChD2F,WAAavC,YAAYnB,eAAgBuD,UAAWtD,WAAYC,eArchF3K,IAAIqJ,YAAY,CACZ,CAACC,IAAK,WACN,CAACA,IAAK,OACN,CAACA,IAAK,QACPjD,MAAK,SAASoD,GACT1J,aAAa2J,QAAQD,EAAE,GAAI2E,QAAS3E,EAAE,GAAIA,EAAE,GAAIjB,eAmc5C2F,WAAavC,YAAYnB,eAAgBuD,UAAWtD,WAAYC,cAGhEwD,YACAtG,EAAEkG,oBAMVnO,EAAE,QAAQgO,GAAG,oBAAc5M,SAASS,wCAAsC,SAASoG,MAC3EA,EAAEH,YAAcG,EAAEH,WAAW2G,OAAQ,MAGrB7L,IAFF5B,aAAakI,MACLoC,QAAQzI,IAAIoF,EAAEH,WAAW2G,SAE3CzN,aAAakD,SAAS,eAAgB,CAAC+D,EAAEH,WAAW2G,aAIhEzO,EAAE,QAAQgO,GAAG,oBAAc5M,SAASC,qEAAmE,SAAS4G,GACxGA,EAAEH,YAAcG,EAAEH,WAAW2G,QAC7BzN,aAAakD,SAAS,UAAW,CAAC+D,EAAEH,WAAW2G,YAKnDzN,aAAaiL,mBAAqBlL,iBAAiBmL,SAAS,2BAK1DtE,QAAU5H,EAAEoB,SAASU,aACrB4M,WAAa9G,QAAQ5B,KAAK,qBAC1B2I,YAAc/G,QAAQ5B,KAAK,qBACjC5F,IAAIiJ,WAAW,eACdiB,MAAK,SAASsE,uBACPC,UAAY7O,EAAE,qHACsD2O,YAAc,6BACtFE,UAAUhL,KAAK,SAAS2G,KAAKoE,mBAEtBC,UAAUrE,UAEpBF,MAAMwE,MAAStO,gBAAgB6N,OAAO,CACnCS,KAAAA,KACAC,MAAOL,eAEVpE,MAAK,SAAS0E,WACPC,YAAcjP,EAAEgP,MAAME,WAAWrL,KAAK,4BAC1CsL,YAAc,WAGN,GAAKC,SAASH,YAAYI,SAAWJ,YAAYI,OAASD,SAASH,YAAYI,QAAU,IACzF1D,SAAS2D,SAAW1H,QAAQ5B,KAAK,QAAU,gBAAkBoJ,SAASH,YAAYI,gBAG1FL,MAAMO,kBAAkBb,YACxBM,MAAMQ,UAAUxB,GAAGvN,YAAYgP,OAAO,WAElCR,YAAYxH,QAAQiI,SAAS1B,GAAG,WAAW,SAAS/F,GAC5CA,EAAEgG,UAAYvN,SAASiP,OACvBR,oBAIZH,MAAMQ,UAAUxB,GAAGvN,YAAYmP,MAAM,SAAS3H,GAE1CA,EAAEkG,iBACFgB,iBAGJvH,QAAQoG,GAAG,SAAU/F,IACjBA,EAAEkG,iBACFa,MAAMhL,UAGHgL,SAEVrE,MAAMxK,aAAa+H,YAgBxB2H,yBAA0B,SAASzL,eAAgBmD,SAAU0C,MAAOC,WAC5BC,gBAAiBC,WACrDzJ,IAAImP,MAAM,qEACN9F,WAAa5F,eAAeP,KAAKzC,SAASO,kBAAoB,IAAM4F,UACxEwC,kBAAkBC,WAAYC,MAAOC,WAAYC,gBAAiBC,YAGtEhC,cAAAA,cACA2H,eAjpBiB,SAAShN,QAASiI,UAAW3E,oBAExBzD,IAAlByD,gBACAA,cAAgBrF,aAAaqH,qBAG3BwC,eAAiB7K,EAAE+C,SAEnBkD,SAAWhG,KAAKiG,KAAK,CAAC,CACxBC,WAAY,2BACZC,KAAM,CAAClD,GAAI8H,UAAWjF,OAHX,UAGmBM,cAAAA,kBAC9B,OAEAvC,QAAUK,kBAAkB0G,uBACzB,IAAItC,SAAQ,CAACC,QAASC,UACzBzI,EAAEuG,KAAKC,MAAMxG,EAAGiG,UACXQ,MAAK0F,cAEFzH,cAAcmG,eAAgB/G,eACxBG,KAAOjE,EAAEoM,UAAUD,aAEnB6D,kBAAoBhQ,EAAEiE,KAAKgM,SACjCpF,eAAe3D,YAAY8I,mBAG3BhQ,YAAKoB,SAASM,sBAAasJ,sBAAa5J,SAASC,aAAc0F,MAC3D,CAACK,MAAO1D,YACJyB,eAAezB,SAASO,KAAK,UAKvB3B,cACVxB,aAAaoP,iBACb,CACIpI,WAAY7D,KACZ8B,OA7BL,UA8BKiK,kBAAmBA,kBAAkBnN,IAAI,IAE7CmN,mBAGOG,kBACPvF,0BACIoF,kBAAmBhQ,EAAEoB,SAASM,UAAY,IAAMsJ,WAChD/G,KACA/C,WACA8J,WAGRxC,QAAQvE,SACT8D,MAAKC,KAEU1F,cACV,6BACA,CAAC4F,UAAWF,GAAIjC,OAhDjB,WAiDC8E,gBAEOsF,kBACPhQ,aAAa+H,UAAUF,IAE3BS"} \ No newline at end of file diff --git a/course/amd/build/activitychooser.min.js b/course/amd/build/activitychooser.min.js index ee36e31e62d..c341ef88217 100644 --- a/course/amd/build/activitychooser.min.js +++ b/course/amd/build/activitychooser.min.js @@ -5,6 +5,6 @@ define("core_course/activitychooser",["exports","core_course/local/activitychoos * @module core_course/activitychooser * @copyright 2020 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,ChooserDialogue=_interopRequireWildcard(ChooserDialogue),Repository=_interopRequireWildcard(Repository),_selectors=_interopRequireDefault(_selectors),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),Templates=_interopRequireWildcard(Templates),_modal=_interopRequireDefault(_modal),_pending=_interopRequireDefault(_pending);let initialized=!1;_exports.init=(courseId,chooserConfig,sectionNum)=>{const pendingPromise=new _pending.default;registerListenerEvents(courseId,chooserConfig,sectionNum),pendingPromise.resolve()};const registerListenerEvents=(courseId,chooserConfig,sectionNum)=>{if(initialized)return;const events=["click",_custom_interaction_events.default.events.activate,_custom_interaction_events.default.events.keyboardActivate],fetchModuleData=(()=>{let innerPromise=null;return()=>(innerPromise||(innerPromise=new Promise((resolve=>{resolve(Repository.activityModules(courseId,sectionNum))}))),innerPromise)})(),fetchFooterData=(()=>{let footerInnerPromise=null;return sectionnum=>(footerInnerPromise||(footerInnerPromise=new Promise((resolve=>{resolve(Repository.fetchFooterData(courseId,sectionnum))}))),footerInnerPromise)})();_custom_interaction_events.default.define(document,events),events.forEach((event=>{document.addEventListener(event,(async e=>{if(e.target.closest(_selectors.default.elements.sectionmodchooser)){let caller,sectionnum;const sectionDiv=e.target.closest(_selectors.default.elements.section),button=e.target.closest(_selectors.default.elements.sectionmodchooser);let bodyPromiseResolver;null!==sectionDiv&§ionDiv.hasAttribute("data-number")?(caller=sectionDiv,sectionnum=sectionDiv.getAttribute("data-number")):(caller=button,caller.hasAttribute("data-sectionid")&&(window.console.warn("The data-sectionid attribute has been deprecated. Please update your code to use data-sectionnum instead."),caller.setAttribute("data-sectionnum",caller.dataset.sectionid)),sectionnum=caller.dataset.sectionnum);const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve})),footerData=await fetchFooterData(sectionnum),sectionModal=buildModal(bodyPromise,footerData),data=await fetchModuleData().catch((async e=>{const errorTemplateData={errormessage:e.message};bodyPromiseResolver(await Templates.render("core_course/local/activitychooser/error",errorTemplateData))}));if(!data)return;const builtModuleData=sectionMapper(data,sectionnum,caller.dataset.sectionreturnnum,caller.dataset.beforemod);ChooserDialogue.displayChooser(sectionModal,builtModuleData,partiallyAppliedFavouriteManager(data,sectionnum),footerData),bodyPromiseResolver(await Templates.render("core_course/activitychooser",templateDataBuilder(builtModuleData,chooserConfig)))}}))})),initialized=!0},sectionMapper=(webServiceData,num,sectionreturnnum,beforemod)=>{const newData=JSON.parse(JSON.stringify(webServiceData));return newData.content_items.forEach((module=>{module.link+="§ion="+num+"&beforemod="+(null!=beforemod?beforemod:0),sectionreturnnum&&(module.link+="&sr="+sectionreturnnum)})),newData.content_items},templateDataBuilder=(data,chooserConfig)=>{let activities=[],resources=[],showAll=!0,showActivities=!1,showResources=!1;const tabMode=parseInt(chooserConfig.tabmode),favourites=data.filter((mod=>!0===mod.favourite)),recommended=data.filter((mod=>!0===mod.recommended));(tabMode=>-1!==[0,3,2,5].indexOf(tabMode))(tabMode)&&(activities=data.filter((mod=>0===mod.archetype)),resources=data.filter((mod=>1===mod.archetype)),showActivities=!0,showResources=!0,2!==tabMode&&5!==tabMode||(showAll=!1));const recommendedBeginning=-1!==[3,4,5].indexOf(tabMode),favouritesFirst=!!favourites.length,recommendedFirst=!1===favouritesFirst&&!0===recommendedBeginning&&!!recommended.length;return{default:data,showAll:showAll,activities:activities,showActivities:showActivities,activitiesFirst:!1===showAll&&!1===favouritesFirst&&!1===recommendedFirst,resources:resources,showResources:showResources,favourites:favourites,recommended:recommended,recommendedFirst:recommendedFirst,recommendedBeginning:recommendedBeginning,favouritesFirst:favouritesFirst,fallback:!0===showAll&&!1===favouritesFirst&&!1===recommendedFirst}},buildModal=(body,footer)=>_modal.default.create({body:body,title:(0,_str.getString)("addresourceoractivity"),footer:footer.customfootertemplate,large:!0,scrollable:!1,templateContext:{classes:"modchooser"},show:!0}),partiallyAppliedFavouriteManager=(moduleData,sectionnum)=>async(internal,favourite,modalBody)=>{const favouriteArea=modalBody.querySelector(_selectors.default.render.favourites),favouriteButtons=modalBody.querySelectorAll('[data-internal="'.concat(internal,'"] ').concat(_selectors.default.actions.optionActions.manageFavourite)),favouriteTabNav=modalBody.querySelector(_selectors.default.regions.favouriteTabNav),result=moduleData.content_items.find((_ref=>{let{name:name}=_ref;return name===internal})),newFaves={};if(result)if(favourite){result.favourite=!0,newFaves.content_items=moduleData.content_items.filter((mod=>!0===mod.favourite));const builtFaves=sectionMapper(newFaves,sectionnum),{html:html,js:js}=await Templates.renderForPromise("core_course/local/activitychooser/favourites",{favourites:builtFaves});await Templates.replaceNodeContents(favouriteArea,html,js),Array.from(favouriteButtons).forEach((element=>{element.classList.remove("text-muted"),element.classList.add("text-primary"),element.dataset.favourited="true",element.setAttribute("aria-pressed",!0),element.firstElementChild.classList.remove("fa-star-o"),element.firstElementChild.classList.add("fa-star")})),favouriteTabNav.classList.remove("d-none")}else{result.favourite=!1;const nodeToRemove=favouriteArea.querySelector('[data-internal="'.concat(internal,'"]'));nodeToRemove.parentNode.removeChild(nodeToRemove),Array.from(favouriteButtons).forEach((element=>{element.classList.add("text-muted"),element.classList.remove("text-primary"),element.dataset.favourited="false",element.setAttribute("aria-pressed",!1),element.firstElementChild.classList.remove("fa-star"),element.firstElementChild.classList.add("fa-star-o")}));0===moduleData.content_items.filter((mod=>!0===mod.favourite)).length&&((favouriteTabNav,modalBody)=>{if(favouriteTabNav.tabIndex=-1,favouriteTabNav.classList.add("d-none"),favouriteTabNav.classList.contains("active")){favouriteTabNav.classList.remove("active"),favouriteTabNav.setAttribute("aria-selected","false"),modalBody.querySelector(_selectors.default.regions.favouriteTab).classList.remove("active");const defaultTabNav=modalBody.querySelector(_selectors.default.regions.defaultTabNav),activitiesTabNav=modalBody.querySelector(_selectors.default.regions.activityTabNav);!1===defaultTabNav.classList.contains("d-none")?(defaultTabNav.classList.add("active"),defaultTabNav.setAttribute("aria-selected","true"),defaultTabNav.tabIndex=0,defaultTabNav.focus(),modalBody.querySelector(_selectors.default.regions.defaultTab).classList.add("active")):(activitiesTabNav.classList.add("active"),activitiesTabNav.setAttribute("aria-selected","true"),activitiesTabNav.tabIndex=0,activitiesTabNav.focus(),modalBody.querySelector(_selectors.default.regions.activityTab).classList.add("active"))}})(favouriteTabNav,modalBody)}}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,ChooserDialogue=_interopRequireWildcard(ChooserDialogue),Repository=_interopRequireWildcard(Repository),_selectors=_interopRequireDefault(_selectors),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),Templates=_interopRequireWildcard(Templates),_modal=_interopRequireDefault(_modal),_pending=_interopRequireDefault(_pending);let initialized=!1;_exports.init=(courseId,chooserConfig)=>{const pendingPromise=new _pending.default;registerListenerEvents(courseId,chooserConfig),pendingPromise.resolve()};const registerListenerEvents=(courseId,chooserConfig)=>{if(initialized)return;const events=["click",_custom_interaction_events.default.events.activate,_custom_interaction_events.default.events.keyboardActivate],fetchModuleData=(()=>{let innerPromises=new Map;return sectionNum=>(innerPromises.has(sectionNum)||innerPromises.set(sectionNum,new Promise((resolve=>{resolve(Repository.activityModules(courseId,sectionNum))}))),innerPromises.get(sectionNum))})(),fetchFooterData=(()=>{let footerInnerPromise=null;return sectionNum=>(footerInnerPromise||(footerInnerPromise=new Promise((resolve=>{resolve(Repository.fetchFooterData(courseId,sectionNum))}))),footerInnerPromise)})();_custom_interaction_events.default.define(document,events),events.forEach((event=>{document.addEventListener(event,(async e=>{if(e.target.closest(_selectors.default.elements.sectionmodchooser)){let caller,sectionnum;const sectionDiv=e.target.closest(_selectors.default.elements.section),button=e.target.closest(_selectors.default.elements.sectionmodchooser);let bodyPromiseResolver;null!==sectionDiv&§ionDiv.hasAttribute("data-number")?(caller=sectionDiv,sectionnum=sectionDiv.getAttribute("data-number")):(caller=button,caller.hasAttribute("data-sectionid")&&(window.console.warn("The data-sectionid attribute has been deprecated. Please update your code to use data-sectionnum instead."),caller.setAttribute("data-sectionnum",caller.dataset.sectionid)),sectionnum=caller.dataset.sectionnum);const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve})),footerData=await fetchFooterData(sectionnum),sectionModal=buildModal(bodyPromise,footerData),data=await fetchModuleData(sectionnum).catch((async e=>{const errorTemplateData={errormessage:e.message};bodyPromiseResolver(await Templates.render("core_course/local/activitychooser/error",errorTemplateData))}));if(!data)return;const builtModuleData=sectionMapper(data,sectionnum,caller.dataset.sectionreturnnum,caller.dataset.beforemod);ChooserDialogue.displayChooser(sectionModal,builtModuleData,partiallyAppliedFavouriteManager(data,sectionnum),footerData),bodyPromiseResolver(await Templates.render("core_course/activitychooser",templateDataBuilder(builtModuleData,chooserConfig)))}}))})),initialized=!0},sectionMapper=(webServiceData,num,sectionreturnnum,beforemod)=>{const newData=JSON.parse(JSON.stringify(webServiceData));return newData.content_items.forEach((module=>{module.link+="§ion="+num+"&beforemod="+(null!=beforemod?beforemod:0),sectionreturnnum&&(module.link+="&sr="+sectionreturnnum)})),newData.content_items},templateDataBuilder=(data,chooserConfig)=>{let activities=[],resources=[],showAll=!0,showActivities=!1,showResources=!1;const tabMode=parseInt(chooserConfig.tabmode),favourites=data.filter((mod=>!0===mod.favourite)),recommended=data.filter((mod=>!0===mod.recommended));(tabMode=>-1!==[0,3,2,5].indexOf(tabMode))(tabMode)&&(activities=data.filter((mod=>0===mod.archetype)),resources=data.filter((mod=>1===mod.archetype)),showActivities=!0,showResources=!0,2!==tabMode&&5!==tabMode||(showAll=!1));const recommendedBeginning=-1!==[3,4,5].indexOf(tabMode),favouritesFirst=!!favourites.length,recommendedFirst=!1===favouritesFirst&&!0===recommendedBeginning&&!!recommended.length;return{default:data,showAll:showAll,activities:activities,showActivities:showActivities,activitiesFirst:!1===showAll&&!1===favouritesFirst&&!1===recommendedFirst,resources:resources,showResources:showResources,favourites:favourites,recommended:recommended,recommendedFirst:recommendedFirst,recommendedBeginning:recommendedBeginning,favouritesFirst:favouritesFirst,fallback:!0===showAll&&!1===favouritesFirst&&!1===recommendedFirst}},buildModal=(body,footer)=>_modal.default.create({body:body,title:(0,_str.getString)("addresourceoractivity"),footer:footer.customfootertemplate,large:!0,scrollable:!1,templateContext:{classes:"modchooser"},show:!0}),partiallyAppliedFavouriteManager=(moduleData,sectionnum)=>async(internal,favourite,modalBody)=>{const favouriteArea=modalBody.querySelector(_selectors.default.render.favourites),favouriteButtons=modalBody.querySelectorAll('[data-internal="'.concat(internal,'"] ').concat(_selectors.default.actions.optionActions.manageFavourite)),favouriteTabNav=modalBody.querySelector(_selectors.default.regions.favouriteTabNav),result=moduleData.content_items.find((_ref=>{let{name:name}=_ref;return name===internal})),newFaves={};if(result)if(favourite){result.favourite=!0,newFaves.content_items=moduleData.content_items.filter((mod=>!0===mod.favourite));const builtFaves=sectionMapper(newFaves,sectionnum),{html:html,js:js}=await Templates.renderForPromise("core_course/local/activitychooser/favourites",{favourites:builtFaves});await Templates.replaceNodeContents(favouriteArea,html,js),Array.from(favouriteButtons).forEach((element=>{element.classList.remove("text-muted"),element.classList.add("text-primary"),element.dataset.favourited="true",element.setAttribute("aria-pressed",!0),element.firstElementChild.classList.remove("fa-star-o"),element.firstElementChild.classList.add("fa-star")})),favouriteTabNav.classList.remove("d-none")}else{result.favourite=!1;const nodeToRemove=favouriteArea.querySelector('[data-internal="'.concat(internal,'"]'));nodeToRemove.parentNode.removeChild(nodeToRemove),Array.from(favouriteButtons).forEach((element=>{element.classList.add("text-muted"),element.classList.remove("text-primary"),element.dataset.favourited="false",element.setAttribute("aria-pressed",!1),element.firstElementChild.classList.remove("fa-star"),element.firstElementChild.classList.add("fa-star-o")}));0===moduleData.content_items.filter((mod=>!0===mod.favourite)).length&&((favouriteTabNav,modalBody)=>{if(favouriteTabNav.tabIndex=-1,favouriteTabNav.classList.add("d-none"),favouriteTabNav.classList.contains("active")){favouriteTabNav.classList.remove("active"),favouriteTabNav.setAttribute("aria-selected","false"),modalBody.querySelector(_selectors.default.regions.favouriteTab).classList.remove("active");const defaultTabNav=modalBody.querySelector(_selectors.default.regions.defaultTabNav),activitiesTabNav=modalBody.querySelector(_selectors.default.regions.activityTabNav);!1===defaultTabNav.classList.contains("d-none")?(defaultTabNav.classList.add("active"),defaultTabNav.setAttribute("aria-selected","true"),defaultTabNav.tabIndex=0,defaultTabNav.focus(),modalBody.querySelector(_selectors.default.regions.defaultTab).classList.add("active")):(activitiesTabNav.classList.add("active"),activitiesTabNav.setAttribute("aria-selected","true"),activitiesTabNav.tabIndex=0,activitiesTabNav.focus(),modalBody.querySelector(_selectors.default.regions.activityTab).classList.add("active"))}})(favouriteTabNav,modalBody)}}})); //# sourceMappingURL=activitychooser.min.js.map \ No newline at end of file diff --git a/course/amd/build/activitychooser.min.js.map b/course/amd/build/activitychooser.min.js.map index 64a897c0c80..8bafb5e0463 100644 --- a/course/amd/build/activitychooser.min.js.map +++ b/course/amd/build/activitychooser.min.js.map @@ -1 +1 @@ -{"version":3,"file":"activitychooser.min.js","sources":["../src/activitychooser.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 * A type of dialogue used as for choosing modules in a course.\n *\n * @module core_course/activitychooser\n * @copyright 2020 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as ChooserDialogue from 'core_course/local/activitychooser/dialogue';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport CustomEvents from 'core/custom_interaction_events';\nimport * as Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport Modal from 'core/modal';\nimport Pending from 'core/pending';\n\n// Set up some JS module wide constants that can be added to in the future.\n\n// Tab config options.\nconst ALLACTIVITIESRESOURCES = 0;\nconst ACTIVITIESRESOURCES = 2;\nconst ALLACTIVITIESRESOURCESREC = 3;\nconst ONLYALLREC = 4;\nconst ACTIVITIESRESOURCESREC = 5;\n\n\n// Module types.\nconst ACTIVITY = 0;\nconst RESOURCE = 1;\n\nlet initialized = false;\n\n/**\n * Set up the activity chooser.\n *\n * @method init\n * @param {Number} courseId Course ID to use later on in fetchModules()\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n * @param {Number} sectionNum Section number to use later on in fetchModules()\n */\nexport const init = (courseId, chooserConfig, sectionNum) => {\n const pendingPromise = new Pending();\n\n registerListenerEvents(courseId, chooserConfig, sectionNum);\n\n pendingPromise.resolve();\n};\n\n/**\n * Once a selection has been made make the modal & module information and pass it along\n *\n * @method registerListenerEvents\n * @param {Number} courseId\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n * @param {Number} sectionNum Section number to use later on in fetchModules()\n */\nconst registerListenerEvents = (courseId, chooserConfig, sectionNum) => {\n\n // Ensure we only add our listeners once.\n if (initialized) {\n return;\n }\n\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n\n const fetchModuleData = (() => {\n let innerPromise = null;\n\n return () => {\n if (!innerPromise) {\n innerPromise = new Promise((resolve) => {\n resolve(Repository.activityModules(courseId, sectionNum));\n });\n }\n\n return innerPromise;\n };\n })();\n\n const fetchFooterData = (() => {\n let footerInnerPromise = null;\n\n return (sectionnum) => {\n if (!footerInnerPromise) {\n footerInnerPromise = new Promise((resolve) => {\n resolve(Repository.fetchFooterData(courseId, sectionnum));\n });\n }\n\n return footerInnerPromise;\n };\n })();\n\n CustomEvents.define(document, events);\n\n // Display module chooser event listeners.\n events.forEach((event) => {\n document.addEventListener(event, async(e) => {\n if (e.target.closest(selectors.elements.sectionmodchooser)) {\n let caller;\n let sectionnum;\n // We need to know who called this.\n // Standard courses use the ID in the main section info.\n const sectionDiv = e.target.closest(selectors.elements.section);\n // Front page courses need some special handling.\n const button = e.target.closest(selectors.elements.sectionmodchooser);\n\n // If we don't have a section number use the fallback ID.\n // We always want the sectionDiv caller first as it keeps track of section number's after DnD changes.\n // The button attribute is always just a fallback for us as the section div is not always available.\n // A YUI change could be done maybe to only update the button attribute but we are going for minimal change here.\n if (sectionDiv !== null && sectionDiv.hasAttribute('data-number')) {\n // We check for attributes just in case of outdated contrib course formats.\n caller = sectionDiv;\n sectionnum = sectionDiv.getAttribute('data-number');\n } else {\n caller = button;\n\n if (caller.hasAttribute('data-sectionid')) {\n window.console.warn(\n 'The data-sectionid attribute has been deprecated. ' +\n 'Please update your code to use data-sectionnum instead.'\n );\n caller.setAttribute('data-sectionnum', caller.dataset.sectionid);\n }\n sectionnum = caller.dataset.sectionnum;\n }\n\n // We want to show the modal instantly but loading whilst waiting for our data.\n let bodyPromiseResolver;\n const bodyPromise = new Promise(resolve => {\n bodyPromiseResolver = resolve;\n });\n\n const footerData = await fetchFooterData(sectionnum);\n const sectionModal = buildModal(bodyPromise, footerData);\n\n // Now we have a modal we should start fetching data.\n // If an error occurs while fetching the data, display the error within the modal.\n const data = await fetchModuleData().catch(async(e) => {\n const errorTemplateData = {\n 'errormessage': e.message\n };\n bodyPromiseResolver(await Templates.render('core_course/local/activitychooser/error', errorTemplateData));\n });\n\n // Early return if there is no module data.\n if (!data) {\n return;\n }\n\n // Apply the section num to all the module instance links.\n const builtModuleData = sectionMapper(\n data,\n sectionnum,\n caller.dataset.sectionreturnnum,\n caller.dataset.beforemod\n );\n\n ChooserDialogue.displayChooser(\n sectionModal,\n builtModuleData,\n partiallyAppliedFavouriteManager(data, sectionnum),\n footerData,\n );\n\n bodyPromiseResolver(await Templates.render(\n 'core_course/activitychooser',\n templateDataBuilder(builtModuleData, chooserConfig)\n ));\n }\n });\n });\n\n initialized = true;\n};\n\n/**\n * Given the web service data and an ID we want to make a deep copy\n * of the WS data then add on the section num to the addoption URL\n *\n * @method sectionMapper\n * @param {Object} webServiceData Our original data from the Web service call\n * @param {Number} num The number of the section we need to append to the links\n * @param {Number|null} sectionreturnnum The number of the section return we need to append to the links\n * @param {Number|null} beforemod The ID of the cm we need to append to the links\n * @return {Array} [modules] with URL's built\n */\nconst sectionMapper = (webServiceData, num, sectionreturnnum, beforemod) => {\n // We need to take a fresh deep copy of the original data as an object is a reference type.\n const newData = JSON.parse(JSON.stringify(webServiceData));\n newData.content_items.forEach((module) => {\n module.link += '§ion=' + num + '&beforemod=' + (beforemod ?? 0);\n if (sectionreturnnum) {\n module.link += '&sr=' + sectionreturnnum;\n }\n });\n return newData.content_items;\n};\n\n/**\n * Given an array of modules we want to figure out where & how to place them into our template object\n *\n * @method templateDataBuilder\n * @param {Array} data our modules to manipulate into a Templatable object\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n * @return {Object} Our built object ready to render out\n */\nconst templateDataBuilder = (data, chooserConfig) => {\n // Setup of various bits and pieces we need to mutate before throwing it to the wolves.\n let activities = [];\n let resources = [];\n let showAll = true;\n let showActivities = false;\n let showResources = false;\n\n // Tab mode can be the following [All, Resources & Activities, All & Activities & Resources].\n const tabMode = parseInt(chooserConfig.tabmode);\n\n // Filter the incoming data to find favourite & recommended modules.\n const favourites = data.filter(mod => mod.favourite === true);\n const recommended = data.filter(mod => mod.recommended === true);\n\n // Whether the activities and resources tabs should be displayed or not.\n const showActivitiesAndResources = (tabMode) => {\n const acceptableModes = [\n ALLACTIVITIESRESOURCES,\n ALLACTIVITIESRESOURCESREC,\n ACTIVITIESRESOURCES,\n ACTIVITIESRESOURCESREC,\n ];\n\n return acceptableModes.indexOf(tabMode) !== -1;\n };\n\n // These modes need Activity & Resource tabs.\n if (showActivitiesAndResources(tabMode)) {\n // Filter the incoming data to find activities then resources.\n activities = data.filter(mod => mod.archetype === ACTIVITY);\n resources = data.filter(mod => mod.archetype === RESOURCE);\n showActivities = true;\n showResources = true;\n\n // We want all of the previous information but no 'All' tab.\n if (tabMode === ACTIVITIESRESOURCES || tabMode === ACTIVITIESRESOURCESREC) {\n showAll = false;\n }\n }\n\n const recommendedBeforeTabs = [\n ALLACTIVITIESRESOURCESREC,\n ONLYALLREC,\n ACTIVITIESRESOURCESREC,\n ];\n // Whether the recommended tab should be displayed before the All/Activities/Resources tabs.\n const recommendedBeginning = recommendedBeforeTabs.indexOf(tabMode) !== -1;\n\n // Given the results of the above filters lets figure out what tab to set active.\n // We have some favourites.\n const favouritesFirst = !!favourites.length;\n const recommendedFirst = favouritesFirst === false && recommendedBeginning === true && !!recommended.length;\n // We are in tabMode 2 without any favourites.\n const activitiesFirst = showAll === false && favouritesFirst === false && recommendedFirst === false;\n // We have nothing fallback to show all modules.\n const fallback = showAll === true && favouritesFirst === false && recommendedFirst === false;\n\n return {\n 'default': data,\n showAll: showAll,\n activities: activities,\n showActivities: showActivities,\n activitiesFirst: activitiesFirst,\n resources: resources,\n showResources: showResources,\n favourites: favourites,\n recommended: recommended,\n recommendedFirst: recommendedFirst,\n recommendedBeginning: recommendedBeginning,\n favouritesFirst: favouritesFirst,\n fallback: fallback,\n };\n};\n\n/**\n * Given an object we want to build a modal ready to show\n *\n * @method buildModal\n * @param {Promise} body\n * @param {String|Boolean} footer Either a footer to add or nothing\n * @return {Object} The modal ready to display immediately and render body in later.\n */\nconst buildModal = (body, footer) => Modal.create({\n body,\n title: getString('addresourceoractivity'),\n footer: footer.customfootertemplate,\n large: true,\n scrollable: false,\n templateContext: {\n classes: 'modchooser'\n },\n show: true,\n});\n\n/**\n * A small helper function to handle the case where there are no more favourites\n * and we need to mess a bit with the available tabs in the chooser\n *\n * @method nullFavouriteDomManager\n * @param {HTMLElement} favouriteTabNav Dom node of the favourite tab nav\n * @param {HTMLElement} modalBody Our current modals' body\n */\nconst nullFavouriteDomManager = (favouriteTabNav, modalBody) => {\n favouriteTabNav.tabIndex = -1;\n favouriteTabNav.classList.add('d-none');\n // Need to set active to an available tab.\n if (favouriteTabNav.classList.contains('active')) {\n favouriteTabNav.classList.remove('active');\n favouriteTabNav.setAttribute('aria-selected', 'false');\n const favouriteTab = modalBody.querySelector(selectors.regions.favouriteTab);\n favouriteTab.classList.remove('active');\n const defaultTabNav = modalBody.querySelector(selectors.regions.defaultTabNav);\n const activitiesTabNav = modalBody.querySelector(selectors.regions.activityTabNav);\n if (defaultTabNav.classList.contains('d-none') === false) {\n defaultTabNav.classList.add('active');\n defaultTabNav.setAttribute('aria-selected', 'true');\n defaultTabNav.tabIndex = 0;\n defaultTabNav.focus();\n const defaultTab = modalBody.querySelector(selectors.regions.defaultTab);\n defaultTab.classList.add('active');\n } else {\n activitiesTabNav.classList.add('active');\n activitiesTabNav.setAttribute('aria-selected', 'true');\n activitiesTabNav.tabIndex = 0;\n activitiesTabNav.focus();\n const activitiesTab = modalBody.querySelector(selectors.regions.activityTab);\n activitiesTab.classList.add('active');\n }\n\n }\n};\n\n/**\n * Export a curried function where the builtModules has been applied.\n * We have our array of modules so we can rerender the favourites area and have all of the items sorted.\n *\n * @method partiallyAppliedFavouriteManager\n * @param {Array} moduleData This is our raw WS data that we need to manipulate\n * @param {Number} sectionnum We need this to add the sectionnum to the URL's in the faves area after rerender\n * @return {Function} partially applied function so we can manipulate DOM nodes easily & update our internal array\n */\nconst partiallyAppliedFavouriteManager = (moduleData, sectionnum) => {\n /**\n * Curried function that is being returned.\n *\n * @param {String} internal Internal name of the module to manage\n * @param {Boolean} favourite Is the caller adding a favourite or removing one?\n * @param {HTMLElement} modalBody What we need to update whilst we are here\n */\n return async(internal, favourite, modalBody) => {\n const favouriteArea = modalBody.querySelector(selectors.render.favourites);\n\n // eslint-disable-next-line max-len\n const favouriteButtons = modalBody.querySelectorAll(`[data-internal=\"${internal}\"] ${selectors.actions.optionActions.manageFavourite}`);\n const favouriteTabNav = modalBody.querySelector(selectors.regions.favouriteTabNav);\n const result = moduleData.content_items.find(({name}) => name === internal);\n const newFaves = {};\n if (result) {\n if (favourite) {\n result.favourite = true;\n\n // eslint-disable-next-line camelcase\n newFaves.content_items = moduleData.content_items.filter(mod => mod.favourite === true);\n\n const builtFaves = sectionMapper(newFaves, sectionnum);\n\n const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/favourites',\n {favourites: builtFaves});\n\n await Templates.replaceNodeContents(favouriteArea, html, js);\n\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.remove('text-muted');\n element.classList.add('text-primary');\n element.dataset.favourited = 'true';\n element.setAttribute('aria-pressed', true);\n element.firstElementChild.classList.remove('fa-star-o');\n element.firstElementChild.classList.add('fa-star');\n });\n\n favouriteTabNav.classList.remove('d-none');\n } else {\n result.favourite = false;\n\n const nodeToRemove = favouriteArea.querySelector(`[data-internal=\"${internal}\"]`);\n\n nodeToRemove.parentNode.removeChild(nodeToRemove);\n\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.add('text-muted');\n element.classList.remove('text-primary');\n element.dataset.favourited = 'false';\n element.setAttribute('aria-pressed', false);\n element.firstElementChild.classList.remove('fa-star');\n element.firstElementChild.classList.add('fa-star-o');\n });\n const newFaves = moduleData.content_items.filter(mod => mod.favourite === true);\n\n if (newFaves.length === 0) {\n nullFavouriteDomManager(favouriteTabNav, modalBody);\n }\n }\n }\n };\n};\n"],"names":["initialized","courseId","chooserConfig","sectionNum","pendingPromise","Pending","registerListenerEvents","resolve","events","CustomEvents","activate","keyboardActivate","fetchModuleData","innerPromise","Promise","Repository","activityModules","fetchFooterData","footerInnerPromise","sectionnum","define","document","forEach","event","addEventListener","async","e","target","closest","selectors","elements","sectionmodchooser","caller","sectionDiv","section","button","bodyPromiseResolver","hasAttribute","getAttribute","window","console","warn","setAttribute","dataset","sectionid","bodyPromise","footerData","sectionModal","buildModal","data","catch","errorTemplateData","message","Templates","render","builtModuleData","sectionMapper","sectionreturnnum","beforemod","ChooserDialogue","displayChooser","partiallyAppliedFavouriteManager","templateDataBuilder","webServiceData","num","newData","JSON","parse","stringify","content_items","module","link","activities","resources","showAll","showActivities","showResources","tabMode","parseInt","tabmode","favourites","filter","mod","favourite","recommended","indexOf","showActivitiesAndResources","archetype","recommendedBeginning","favouritesFirst","length","recommendedFirst","activitiesFirst","fallback","body","footer","Modal","create","title","customfootertemplate","large","scrollable","templateContext","classes","show","moduleData","internal","modalBody","favouriteArea","querySelector","favouriteButtons","querySelectorAll","actions","optionActions","manageFavourite","favouriteTabNav","regions","result","find","_ref","name","newFaves","builtFaves","html","js","renderForPromise","replaceNodeContents","Array","from","element","classList","remove","add","favourited","firstElementChild","nodeToRemove","parentNode","removeChild","tabIndex","contains","favouriteTab","defaultTabNav","activitiesTabNav","activityTabNav","focus","defaultTab","activityTab","nullFavouriteDomManager"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"activitychooser.min.js","sources":["../src/activitychooser.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 * A type of dialogue used as for choosing modules in a course.\n *\n * @module core_course/activitychooser\n * @copyright 2020 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as ChooserDialogue from 'core_course/local/activitychooser/dialogue';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport CustomEvents from 'core/custom_interaction_events';\nimport * as Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport Modal from 'core/modal';\nimport Pending from 'core/pending';\n\n// Set up some JS module wide constants that can be added to in the future.\n\n// Tab config options.\nconst ALLACTIVITIESRESOURCES = 0;\nconst ACTIVITIESRESOURCES = 2;\nconst ALLACTIVITIESRESOURCESREC = 3;\nconst ONLYALLREC = 4;\nconst ACTIVITIESRESOURCESREC = 5;\n\n\n// Module types.\nconst ACTIVITY = 0;\nconst RESOURCE = 1;\n\nlet initialized = false;\n\n/**\n * Set up the activity chooser.\n *\n * @method init\n * @param {Number} courseId Course ID to use later on in fetchModules()\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n */\nexport const init = (courseId, chooserConfig) => {\n const pendingPromise = new Pending();\n\n registerListenerEvents(courseId, chooserConfig);\n\n pendingPromise.resolve();\n};\n\n/**\n * Once a selection has been made make the modal & module information and pass it along\n *\n * @method registerListenerEvents\n * @param {Number} courseId\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n */\nconst registerListenerEvents = (courseId, chooserConfig) => {\n\n // Ensure we only add our listeners once.\n if (initialized) {\n return;\n }\n\n const events = [\n 'click',\n CustomEvents.events.activate,\n CustomEvents.events.keyboardActivate\n ];\n\n const fetchModuleData = (() => {\n let innerPromises = new Map();\n\n return (sectionNum) => {\n if (innerPromises.has(sectionNum)) {\n return innerPromises.get(sectionNum);\n }\n\n innerPromises.set(\n sectionNum,\n new Promise((resolve) => {\n resolve(Repository.activityModules(courseId, sectionNum));\n })\n );\n return innerPromises.get(sectionNum);\n };\n })();\n\n const fetchFooterData = (() => {\n let footerInnerPromise = null;\n\n return (sectionNum) => {\n if (!footerInnerPromise) {\n footerInnerPromise = new Promise((resolve) => {\n resolve(Repository.fetchFooterData(courseId, sectionNum));\n });\n }\n\n return footerInnerPromise;\n };\n })();\n\n CustomEvents.define(document, events);\n\n // Display module chooser event listeners.\n events.forEach((event) => {\n document.addEventListener(event, async(e) => {\n if (e.target.closest(selectors.elements.sectionmodchooser)) {\n let caller;\n let sectionnum;\n // We need to know who called this.\n // Standard courses use the ID in the main section info.\n const sectionDiv = e.target.closest(selectors.elements.section);\n // Front page courses need some special handling.\n const button = e.target.closest(selectors.elements.sectionmodchooser);\n\n // If we don't have a section number use the fallback ID.\n // We always want the sectionDiv caller first as it keeps track of section number's after DnD changes.\n // The button attribute is always just a fallback for us as the section div is not always available.\n // A YUI change could be done maybe to only update the button attribute but we are going for minimal change here.\n if (sectionDiv !== null && sectionDiv.hasAttribute('data-number')) {\n // We check for attributes just in case of outdated contrib course formats.\n caller = sectionDiv;\n sectionnum = sectionDiv.getAttribute('data-number');\n } else {\n caller = button;\n\n if (caller.hasAttribute('data-sectionid')) {\n window.console.warn(\n 'The data-sectionid attribute has been deprecated. ' +\n 'Please update your code to use data-sectionnum instead.'\n );\n caller.setAttribute('data-sectionnum', caller.dataset.sectionid);\n }\n sectionnum = caller.dataset.sectionnum;\n }\n\n // We want to show the modal instantly but loading whilst waiting for our data.\n let bodyPromiseResolver;\n const bodyPromise = new Promise(resolve => {\n bodyPromiseResolver = resolve;\n });\n\n const footerData = await fetchFooterData(sectionnum);\n const sectionModal = buildModal(bodyPromise, footerData);\n\n // Now we have a modal we should start fetching data.\n // If an error occurs while fetching the data, display the error within the modal.\n const data = await fetchModuleData(sectionnum).catch(async(e) => {\n const errorTemplateData = {\n 'errormessage': e.message\n };\n bodyPromiseResolver(await Templates.render('core_course/local/activitychooser/error', errorTemplateData));\n });\n\n // Early return if there is no module data.\n if (!data) {\n return;\n }\n\n // Apply the section num to all the module instance links.\n const builtModuleData = sectionMapper(\n data,\n sectionnum,\n caller.dataset.sectionreturnnum,\n caller.dataset.beforemod\n );\n\n ChooserDialogue.displayChooser(\n sectionModal,\n builtModuleData,\n partiallyAppliedFavouriteManager(data, sectionnum),\n footerData,\n );\n\n bodyPromiseResolver(await Templates.render(\n 'core_course/activitychooser',\n templateDataBuilder(builtModuleData, chooserConfig)\n ));\n }\n });\n });\n\n initialized = true;\n};\n\n/**\n * Given the web service data and an ID we want to make a deep copy\n * of the WS data then add on the section num to the addoption URL\n *\n * @method sectionMapper\n * @param {Object} webServiceData Our original data from the Web service call\n * @param {Number} num The number of the section we need to append to the links\n * @param {Number|null} sectionreturnnum The number of the section return we need to append to the links\n * @param {Number|null} beforemod The ID of the cm we need to append to the links\n * @return {Array} [modules] with URL's built\n */\nconst sectionMapper = (webServiceData, num, sectionreturnnum, beforemod) => {\n // We need to take a fresh deep copy of the original data as an object is a reference type.\n const newData = JSON.parse(JSON.stringify(webServiceData));\n newData.content_items.forEach((module) => {\n module.link += '§ion=' + num + '&beforemod=' + (beforemod ?? 0);\n if (sectionreturnnum) {\n module.link += '&sr=' + sectionreturnnum;\n }\n });\n return newData.content_items;\n};\n\n/**\n * Given an array of modules we want to figure out where & how to place them into our template object\n *\n * @method templateDataBuilder\n * @param {Array} data our modules to manipulate into a Templatable object\n * @param {Object} chooserConfig Any PHP config settings that we may need to reference\n * @return {Object} Our built object ready to render out\n */\nconst templateDataBuilder = (data, chooserConfig) => {\n // Setup of various bits and pieces we need to mutate before throwing it to the wolves.\n let activities = [];\n let resources = [];\n let showAll = true;\n let showActivities = false;\n let showResources = false;\n\n // Tab mode can be the following [All, Resources & Activities, All & Activities & Resources].\n const tabMode = parseInt(chooserConfig.tabmode);\n\n // Filter the incoming data to find favourite & recommended modules.\n const favourites = data.filter(mod => mod.favourite === true);\n const recommended = data.filter(mod => mod.recommended === true);\n\n // Whether the activities and resources tabs should be displayed or not.\n const showActivitiesAndResources = (tabMode) => {\n const acceptableModes = [\n ALLACTIVITIESRESOURCES,\n ALLACTIVITIESRESOURCESREC,\n ACTIVITIESRESOURCES,\n ACTIVITIESRESOURCESREC,\n ];\n\n return acceptableModes.indexOf(tabMode) !== -1;\n };\n\n // These modes need Activity & Resource tabs.\n if (showActivitiesAndResources(tabMode)) {\n // Filter the incoming data to find activities then resources.\n activities = data.filter(mod => mod.archetype === ACTIVITY);\n resources = data.filter(mod => mod.archetype === RESOURCE);\n showActivities = true;\n showResources = true;\n\n // We want all of the previous information but no 'All' tab.\n if (tabMode === ACTIVITIESRESOURCES || tabMode === ACTIVITIESRESOURCESREC) {\n showAll = false;\n }\n }\n\n const recommendedBeforeTabs = [\n ALLACTIVITIESRESOURCESREC,\n ONLYALLREC,\n ACTIVITIESRESOURCESREC,\n ];\n // Whether the recommended tab should be displayed before the All/Activities/Resources tabs.\n const recommendedBeginning = recommendedBeforeTabs.indexOf(tabMode) !== -1;\n\n // Given the results of the above filters lets figure out what tab to set active.\n // We have some favourites.\n const favouritesFirst = !!favourites.length;\n const recommendedFirst = favouritesFirst === false && recommendedBeginning === true && !!recommended.length;\n // We are in tabMode 2 without any favourites.\n const activitiesFirst = showAll === false && favouritesFirst === false && recommendedFirst === false;\n // We have nothing fallback to show all modules.\n const fallback = showAll === true && favouritesFirst === false && recommendedFirst === false;\n\n return {\n 'default': data,\n showAll: showAll,\n activities: activities,\n showActivities: showActivities,\n activitiesFirst: activitiesFirst,\n resources: resources,\n showResources: showResources,\n favourites: favourites,\n recommended: recommended,\n recommendedFirst: recommendedFirst,\n recommendedBeginning: recommendedBeginning,\n favouritesFirst: favouritesFirst,\n fallback: fallback,\n };\n};\n\n/**\n * Given an object we want to build a modal ready to show\n *\n * @method buildModal\n * @param {Promise} body\n * @param {String|Boolean} footer Either a footer to add or nothing\n * @return {Object} The modal ready to display immediately and render body in later.\n */\nconst buildModal = (body, footer) => Modal.create({\n body,\n title: getString('addresourceoractivity'),\n footer: footer.customfootertemplate,\n large: true,\n scrollable: false,\n templateContext: {\n classes: 'modchooser'\n },\n show: true,\n});\n\n/**\n * A small helper function to handle the case where there are no more favourites\n * and we need to mess a bit with the available tabs in the chooser\n *\n * @method nullFavouriteDomManager\n * @param {HTMLElement} favouriteTabNav Dom node of the favourite tab nav\n * @param {HTMLElement} modalBody Our current modals' body\n */\nconst nullFavouriteDomManager = (favouriteTabNav, modalBody) => {\n favouriteTabNav.tabIndex = -1;\n favouriteTabNav.classList.add('d-none');\n // Need to set active to an available tab.\n if (favouriteTabNav.classList.contains('active')) {\n favouriteTabNav.classList.remove('active');\n favouriteTabNav.setAttribute('aria-selected', 'false');\n const favouriteTab = modalBody.querySelector(selectors.regions.favouriteTab);\n favouriteTab.classList.remove('active');\n const defaultTabNav = modalBody.querySelector(selectors.regions.defaultTabNav);\n const activitiesTabNav = modalBody.querySelector(selectors.regions.activityTabNav);\n if (defaultTabNav.classList.contains('d-none') === false) {\n defaultTabNav.classList.add('active');\n defaultTabNav.setAttribute('aria-selected', 'true');\n defaultTabNav.tabIndex = 0;\n defaultTabNav.focus();\n const defaultTab = modalBody.querySelector(selectors.regions.defaultTab);\n defaultTab.classList.add('active');\n } else {\n activitiesTabNav.classList.add('active');\n activitiesTabNav.setAttribute('aria-selected', 'true');\n activitiesTabNav.tabIndex = 0;\n activitiesTabNav.focus();\n const activitiesTab = modalBody.querySelector(selectors.regions.activityTab);\n activitiesTab.classList.add('active');\n }\n\n }\n};\n\n/**\n * Export a curried function where the builtModules has been applied.\n * We have our array of modules so we can rerender the favourites area and have all of the items sorted.\n *\n * @method partiallyAppliedFavouriteManager\n * @param {Array} moduleData This is our raw WS data that we need to manipulate\n * @param {Number} sectionnum We need this to add the sectionnum to the URL's in the faves area after rerender\n * @return {Function} partially applied function so we can manipulate DOM nodes easily & update our internal array\n */\nconst partiallyAppliedFavouriteManager = (moduleData, sectionnum) => {\n /**\n * Curried function that is being returned.\n *\n * @param {String} internal Internal name of the module to manage\n * @param {Boolean} favourite Is the caller adding a favourite or removing one?\n * @param {HTMLElement} modalBody What we need to update whilst we are here\n */\n return async(internal, favourite, modalBody) => {\n const favouriteArea = modalBody.querySelector(selectors.render.favourites);\n\n // eslint-disable-next-line max-len\n const favouriteButtons = modalBody.querySelectorAll(`[data-internal=\"${internal}\"] ${selectors.actions.optionActions.manageFavourite}`);\n const favouriteTabNav = modalBody.querySelector(selectors.regions.favouriteTabNav);\n const result = moduleData.content_items.find(({name}) => name === internal);\n const newFaves = {};\n if (result) {\n if (favourite) {\n result.favourite = true;\n\n // eslint-disable-next-line camelcase\n newFaves.content_items = moduleData.content_items.filter(mod => mod.favourite === true);\n\n const builtFaves = sectionMapper(newFaves, sectionnum);\n\n const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/favourites',\n {favourites: builtFaves});\n\n await Templates.replaceNodeContents(favouriteArea, html, js);\n\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.remove('text-muted');\n element.classList.add('text-primary');\n element.dataset.favourited = 'true';\n element.setAttribute('aria-pressed', true);\n element.firstElementChild.classList.remove('fa-star-o');\n element.firstElementChild.classList.add('fa-star');\n });\n\n favouriteTabNav.classList.remove('d-none');\n } else {\n result.favourite = false;\n\n const nodeToRemove = favouriteArea.querySelector(`[data-internal=\"${internal}\"]`);\n\n nodeToRemove.parentNode.removeChild(nodeToRemove);\n\n Array.from(favouriteButtons).forEach((element) => {\n element.classList.add('text-muted');\n element.classList.remove('text-primary');\n element.dataset.favourited = 'false';\n element.setAttribute('aria-pressed', false);\n element.firstElementChild.classList.remove('fa-star');\n element.firstElementChild.classList.add('fa-star-o');\n });\n const newFaves = moduleData.content_items.filter(mod => mod.favourite === true);\n\n if (newFaves.length === 0) {\n nullFavouriteDomManager(favouriteTabNav, modalBody);\n }\n }\n }\n };\n};\n"],"names":["initialized","courseId","chooserConfig","pendingPromise","Pending","registerListenerEvents","resolve","events","CustomEvents","activate","keyboardActivate","fetchModuleData","innerPromises","Map","sectionNum","has","set","Promise","Repository","activityModules","get","fetchFooterData","footerInnerPromise","define","document","forEach","event","addEventListener","async","e","target","closest","selectors","elements","sectionmodchooser","caller","sectionnum","sectionDiv","section","button","bodyPromiseResolver","hasAttribute","getAttribute","window","console","warn","setAttribute","dataset","sectionid","bodyPromise","footerData","sectionModal","buildModal","data","catch","errorTemplateData","message","Templates","render","builtModuleData","sectionMapper","sectionreturnnum","beforemod","ChooserDialogue","displayChooser","partiallyAppliedFavouriteManager","templateDataBuilder","webServiceData","num","newData","JSON","parse","stringify","content_items","module","link","activities","resources","showAll","showActivities","showResources","tabMode","parseInt","tabmode","favourites","filter","mod","favourite","recommended","indexOf","showActivitiesAndResources","archetype","recommendedBeginning","favouritesFirst","length","recommendedFirst","activitiesFirst","fallback","body","footer","Modal","create","title","customfootertemplate","large","scrollable","templateContext","classes","show","moduleData","internal","modalBody","favouriteArea","querySelector","favouriteButtons","querySelectorAll","actions","optionActions","manageFavourite","favouriteTabNav","regions","result","find","_ref","name","newFaves","builtFaves","html","js","renderForPromise","replaceNodeContents","Array","from","element","classList","remove","add","favourited","firstElementChild","nodeToRemove","parentNode","removeChild","tabIndex","contains","favouriteTab","defaultTabNav","activitiesTabNav","activityTabNav","focus","defaultTab","activityTab","nullFavouriteDomManager"],"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 a00bf23daf1..7a6b77bd13d 100644 --- a/course/amd/src/actions.js +++ b/course/amd/src/actions.js @@ -1004,7 +1004,7 @@ define( } } }); - $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) { + $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-itemtype="activityname"][data-inplaceeditable]`, function(e) { if (e.ajaxreturn && e.ajaxreturn.itemid) { courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]); } diff --git a/course/amd/src/activitychooser.js b/course/amd/src/activitychooser.js index 449831c4fd4..1bc047ef9f0 100644 --- a/course/amd/src/activitychooser.js +++ b/course/amd/src/activitychooser.js @@ -52,12 +52,11 @@ let initialized = false; * @method init * @param {Number} courseId Course ID to use later on in fetchModules() * @param {Object} chooserConfig Any PHP config settings that we may need to reference - * @param {Number} sectionNum Section number to use later on in fetchModules() */ -export const init = (courseId, chooserConfig, sectionNum) => { +export const init = (courseId, chooserConfig) => { const pendingPromise = new Pending(); - registerListenerEvents(courseId, chooserConfig, sectionNum); + registerListenerEvents(courseId, chooserConfig); pendingPromise.resolve(); }; @@ -68,9 +67,8 @@ export const init = (courseId, chooserConfig, sectionNum) => { * @method registerListenerEvents * @param {Number} courseId * @param {Object} chooserConfig Any PHP config settings that we may need to reference - * @param {Number} sectionNum Section number to use later on in fetchModules() */ -const registerListenerEvents = (courseId, chooserConfig, sectionNum) => { +const registerListenerEvents = (courseId, chooserConfig) => { // Ensure we only add our listeners once. if (initialized) { @@ -84,26 +82,30 @@ const registerListenerEvents = (courseId, chooserConfig, sectionNum) => { ]; const fetchModuleData = (() => { - let innerPromise = null; + let innerPromises = new Map(); - return () => { - if (!innerPromise) { - innerPromise = new Promise((resolve) => { - resolve(Repository.activityModules(courseId, sectionNum)); - }); + return (sectionNum) => { + if (innerPromises.has(sectionNum)) { + return innerPromises.get(sectionNum); } - return innerPromise; + innerPromises.set( + sectionNum, + new Promise((resolve) => { + resolve(Repository.activityModules(courseId, sectionNum)); + }) + ); + return innerPromises.get(sectionNum); }; })(); const fetchFooterData = (() => { let footerInnerPromise = null; - return (sectionnum) => { + return (sectionNum) => { if (!footerInnerPromise) { footerInnerPromise = new Promise((resolve) => { - resolve(Repository.fetchFooterData(courseId, sectionnum)); + resolve(Repository.fetchFooterData(courseId, sectionNum)); }); } @@ -157,7 +159,7 @@ const registerListenerEvents = (courseId, chooserConfig, sectionNum) => { // Now we have a modal we should start fetching data. // If an error occurs while fetching the data, display the error within the modal. - const data = await fetchModuleData().catch(async(e) => { + const data = await fetchModuleData(sectionnum).catch(async(e) => { const errorTemplateData = { 'errormessage': e.message }; diff --git a/course/format/amd/build/local/content/section.min.js b/course/format/amd/build/local/content/section.min.js index 13812e5152c..f1a80fad77c 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),_pending=_interopRequireDefault(_pending);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)}}this._openSectionIfNecessary()}async _openSectionIfNecessary(){const pageCmInfo=this.reactive.getPageAnchorCmInfo();if(!pageCmInfo||pageCmInfo.sectionid!==this.id)return;await this.reactive.dispatch("sectionContentCollapsed",[this.id],!1);const pendingOpen=new _pending.default("courseformat/section:openSectionIfNecessary");this.element.scrollIntoView({block:"center"}),setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",pageCmInfo.id),pendingOpen.resolve()}),250)}getWatchers(){return[{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection}]}validateDropData(dropdata){return("section"!==(null==dropdata?void 0:dropdata.type)||null===this.reactive.sectionReturn)&&super.validateDropData(dropdata)}getLastCm(){const cms=this.getElements(this.selectors.CM);return cms&&0!==cms.length?cms[cms.length-1]:null}getLastCmFallback(){return this.getElement(this.selectors.SECTIONINFO)}_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._getActionMenu(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(affectedAction.dataset.swapicon=affectedAction.dataset.icon,affectedAction.dataset.icon=newIcon,newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}_getActionMenu(selector){return this.getElement(".section_action_menu")?this.getElement(selector):document.querySelector(selector)}}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),_pending=_interopRequireDefault(_pending);class _default extends _dndsection.default{create(){this.name="content_section",this.selectors={ACTIONMENU:".section-actions",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)}}this._openSectionIfNecessary()}async _openSectionIfNecessary(){const pageCmInfo=this.reactive.getPageAnchorCmInfo();if(!pageCmInfo||pageCmInfo.sectionid!==this.id)return;await this.reactive.dispatch("sectionContentCollapsed",[this.id],!1);const pendingOpen=new _pending.default("courseformat/section:openSectionIfNecessary");this.element.scrollIntoView({block:"center"}),setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",pageCmInfo.id),pendingOpen.resolve()}),250)}getWatchers(){return[{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection}]}validateDropData(dropdata){return("section"!==(null==dropdata?void 0:dropdata.type)||null===this.reactive.sectionReturn)&&super.validateDropData(dropdata)}getLastCm(){const cms=this.getElements(this.selectors.CM);if(!cms||0===cms.length)return null;const lastCm=cms[cms.length-1];if(null!==this.section.component)return lastCm;const parentSection=lastCm.parentNode.closest(this.selectors.CM);return null!=parentSection?parentSection:lastCm}getLastCmFallback(){return this.getElement(this.selectors.SECTIONINFO)}_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._getActionMenu(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(affectedAction.dataset.swapicon=affectedAction.dataset.icon,affectedAction.dataset.icon=newIcon,newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}_getActionMenu(selector){return document.querySelector("".concat(this.selectors.ACTIONMENU,"[data-sectionid='").concat(this.id,"'] ").concat(selector))}}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 013f95978c3..5b7699f96e7 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';\nimport Pending from \"core/pending\";\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 this._openSectionIfNecessary();\n }\n\n /**\n * Open the section if the anchored activity is inside.\n */\n async _openSectionIfNecessary() {\n const pageCmInfo = this.reactive.getPageAnchorCmInfo();\n if (!pageCmInfo || pageCmInfo.sectionid !== this.id) {\n return;\n }\n await this.reactive.dispatch('sectionContentCollapsed', [this.id], false);\n const pendingOpen = new Pending(`courseformat/section:openSectionIfNecessary`);\n this.element.scrollIntoView({block: \"center\"});\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', pageCmInfo.id);\n pendingOpen.resolve();\n }, 250);\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 !== null) {\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 * Get a fallback element when there is no CM in the section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCmFallback() {\n // The sectioninfo is always present, even when the section is empty.\n return this.getElement(this.selectors.SECTIONINFO);\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._getActionMenu(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 affectedAction.dataset.swapicon = affectedAction.dataset.icon;\n affectedAction.dataset.icon = newIcon;\n if (newIcon) {\n const pixHtml = await Templates.renderPix(newIcon, 'core');\n Templates.replaceNode(icon, pixHtml, '');\n }\n }\n }\n\n /**\n * Get the action menu element from the selector.\n *\n * @param {string} selector The selector to find the action menu.\n * @returns The action menu element.\n */\n _getActionMenu(selector) {\n if (this.getElement('.section_action_menu')) {\n return this.getElement(selector);\n }\n\n return document.querySelector(selector);\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","_openSectionIfNecessary","pageCmInfo","getPageAnchorCmInfo","sectionid","dispatch","pendingOpen","Pending","scrollIntoView","block","setTimeout","resolve","getWatchers","watch","handler","_refreshSection","validateDropData","dropdata","type","sectionReturn","super","getLastCm","cms","getElements","length","getLastCmFallback","classList","toggle","DRAGGING","dragging","locked","visible","current","sectioninfo","hasrestrictions","_updateBadges","_updateActionsMenu","section","hiddenFromStudents","selector","newAction","affectedAction","_getActionMenu","action","actionText","querySelector","swapname","oldText","innerText","icon","swapicon","newIcon","pixHtml","Templates","renderPix","replaceNode","document"],"mappings":"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"} \ 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';\nimport Pending from \"core/pending\";\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 ACTIONMENU: '.section-actions',\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 this._openSectionIfNecessary();\n }\n\n /**\n * Open the section if the anchored activity is inside.\n */\n async _openSectionIfNecessary() {\n const pageCmInfo = this.reactive.getPageAnchorCmInfo();\n if (!pageCmInfo || pageCmInfo.sectionid !== this.id) {\n return;\n }\n await this.reactive.dispatch('sectionContentCollapsed', [this.id], false);\n const pendingOpen = new Pending(`courseformat/section:openSectionIfNecessary`);\n this.element.scrollIntoView({block: \"center\"});\n setTimeout(() => {\n this.reactive.dispatch('setPageItem', 'cm', pageCmInfo.id);\n pendingOpen.resolve();\n }, 250);\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 !== null) {\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 const lastCm = cms[cms.length - 1];\n // If it is a delegated section return the last item overall.\n if (this.section.component !== null) {\n return lastCm;\n }\n // If it is a regular section and the last item overall has a parent cm, return the parent instead.\n const parentSection = lastCm.parentNode.closest(this.selectors.CM);\n return parentSection ?? lastCm;\n }\n\n /**\n * Get a fallback element when there is no CM in the section.\n *\n * @returns {element|null} the las course module element of the section.\n */\n getLastCmFallback() {\n // The sectioninfo is always present, even when the section is empty.\n return this.getElement(this.selectors.SECTIONINFO);\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._getActionMenu(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 affectedAction.dataset.swapicon = affectedAction.dataset.icon;\n affectedAction.dataset.icon = newIcon;\n if (newIcon) {\n const pixHtml = await Templates.renderPix(newIcon, 'core');\n Templates.replaceNode(icon, pixHtml, '');\n }\n }\n }\n\n /**\n * Get the action menu element from the selector.\n *\n * @param {string} selector The selector to find the action menu.\n * @returns The action menu element.\n */\n _getActionMenu(selector) {\n return document.querySelector(`${this.selectors.ACTIONMENU}[data-sectionid='${this.id}'] ${selector}`);\n }\n}\n"],"names":["DndSection","create","name","selectors","ACTIONMENU","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","_openSectionIfNecessary","pageCmInfo","getPageAnchorCmInfo","sectionid","dispatch","pendingOpen","Pending","scrollIntoView","block","setTimeout","resolve","getWatchers","watch","handler","_refreshSection","validateDropData","dropdata","type","sectionReturn","super","getLastCm","cms","getElements","length","lastCm","section","component","parentSection","parentNode","closest","getLastCmFallback","classList","toggle","DRAGGING","dragging","locked","visible","current","sectioninfo","hasrestrictions","_updateBadges","_updateActionsMenu","hiddenFromStudents","selector","newAction","affectedAction","_getActionMenu","action","actionText","querySelector","swapname","oldText","innerText","icon","swapicon","newIcon","pixHtml","Templates","renderPix","replaceNode","document"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/build/local/content/section/cmitem.min.js b/course/format/amd/build/local/content/section/cmitem.min.js index 781e257eb14..ee132e4e62a 100644 --- a/course/format/amd/build/local/content/section/cmitem.min.js +++ b/course/format/amd/build/local/content/section/cmitem.min.js @@ -8,6 +8,6 @@ define("core_courseformat/local/content/section/cmitem",["exports","core_coursef * @class core_courseformat/local/content/section/cmitem * @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,_dndcmitem=(obj=_dndcmitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndcmitem.default{create(){this.name="content_section_cmitem",this.selectors={BULKSELECT:"[data-for='cmBulkSelect']",BULKCHECKBOX:"[data-bulkcheckbox]",CARD:"[data-region='activity-card']",DRAGICON:".editing_move",INPLACEEDITABLE:"[data-inplaceeditablelink]"},this.classes={LOCKED:"editinprogress",HIDE:"d-none",SELECTED:"selected"},this.id=this.element.dataset.id}stateReady(state){var _this$getElement;this.configDragDrop(this.id),null===(_this$getElement=this.getElement(this.selectors.DRAGICON))||void 0===_this$getElement||_this$getElement.classList.add(this.classes.DRAGICON),this._refreshBulk({state:state})}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.unregister},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"bulk:updated",handler:this._refreshBulk}]}setDragImage(){return this.getElement(this.selectors.CARD)}_refreshCm(_ref){var _element$dragging,_element$locked;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.locked=element.locked}_refreshBulk(_ref2){var _this$getElement2;let{state:state}=_ref2;const bulk=state.bulk;this.setDraggable(!bulk.enabled),bulk.enabled?(this.element.dataset.action="toggleSelectionCm",this.element.dataset.preventDefault=1):(this.element.removeAttribute("data-action"),this.element.removeAttribute("data-preventDefault")),null===(_this$getElement2=this.getElement(this.selectors.BULKSELECT))||void 0===_this$getElement2||_this$getElement2.classList.toggle(this.classes.HIDE,!bulk.enabled);const disabled=!this._isCmBulkEnabled(bulk),selected=this._isSelected(bulk);this._refreshActivityCard(bulk,selected),this._setCheckboxValue(selected,disabled)}_refreshActivityCard(bulk,selected){var _this$getElement3,_this$getElement4;null===(_this$getElement3=this.getElement(this.selectors.INPLACEEDITABLE))||void 0===_this$getElement3||_this$getElement3.classList.toggle(this.classes.HIDE,bulk.enabled),null===(_this$getElement4=this.getElement(this.selectors.CARD))||void 0===_this$getElement4||_this$getElement4.classList.toggle(this.classes.SELECTED,selected),this.element.classList.toggle(this.classes.SELECTED,selected)}_setCheckboxValue(checked,disabled){const checkbox=this.getElement(this.selectors.BULKCHECKBOX);checkbox&&(checkbox.checked=checked,checkbox.disabled=disabled,disabled?checkbox.removeAttribute("data-is-selectable"):checkbox.dataset.isSelectable=1)}_isCmBulkEnabled(bulk){return!!bulk.enabled&&(""===bulk.selectedType||"cm"===bulk.selectedType)}_isSelected(bulk){return"cm"===bulk.selectedType&&bulk.selection.includes(this.id)}}return _exports.default=_default,_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=(obj=_dndcmitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndcmitem.default{create(){this.name="content_section_cmitem",this.selectors={BULKSELECT:"[data-for='cmBulkSelect']",BULKCHECKBOX:"[data-bulkcheckbox]",CARD:"[data-region='activity-card']",DRAGICON:".editing_move",INPLACEEDITABLE:'[data-itemtype="activityname"] > [data-inplaceeditablelink]'},this.classes={LOCKED:"editinprogress",HIDE:"d-none",SELECTED:"selected"},this.id=this.element.dataset.id}stateReady(state){var _this$getElement;this.configDragDrop(this.id),null===(_this$getElement=this.getElement(this.selectors.DRAGICON))||void 0===_this$getElement||_this$getElement.classList.add(this.classes.DRAGICON),this._refreshBulk({state:state})}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.unregister},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"bulk:updated",handler:this._refreshBulk}]}setDragImage(){return this.getElement(this.selectors.CARD)}_refreshCm(_ref){var _element$dragging,_element$locked;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.locked=element.locked}_refreshBulk(_ref2){var _this$getElement2;let{state:state}=_ref2;const bulk=state.bulk;this.setDraggable(!bulk.enabled),bulk.enabled?(this.element.dataset.action="toggleSelectionCm",this.element.dataset.preventDefault=1):(this.element.removeAttribute("data-action"),this.element.removeAttribute("data-preventDefault")),null===(_this$getElement2=this.getElement(this.selectors.BULKSELECT))||void 0===_this$getElement2||_this$getElement2.classList.toggle(this.classes.HIDE,!bulk.enabled);const disabled=!this._isCmBulkEnabled(bulk),selected=this._isSelected(bulk);this._refreshActivityCard(bulk,selected),this._setCheckboxValue(selected,disabled)}_refreshActivityCard(bulk,selected){var _this$getElement3,_this$getElement4;null===(_this$getElement3=this.getElement(this.selectors.INPLACEEDITABLE))||void 0===_this$getElement3||_this$getElement3.classList.toggle(this.classes.HIDE,bulk.enabled),null===(_this$getElement4=this.getElement(this.selectors.CARD))||void 0===_this$getElement4||_this$getElement4.classList.toggle(this.classes.SELECTED,selected),this.element.classList.toggle(this.classes.SELECTED,selected)}_setCheckboxValue(checked,disabled){const checkbox=this.getElement(this.selectors.BULKCHECKBOX);checkbox&&(checkbox.checked=checked,checkbox.disabled=disabled,disabled?checkbox.removeAttribute("data-is-selectable"):checkbox.dataset.isSelectable=1)}_isCmBulkEnabled(bulk){return!!bulk.enabled&&(""===bulk.selectedType||"cm"===bulk.selectedType)}_isSelected(bulk){return"cm"===bulk.selectedType&&bulk.selection.includes(this.id)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=cmitem.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/content/section/cmitem.min.js.map b/course/format/amd/build/local/content/section/cmitem.min.js.map index e699782d61c..557d83af8e6 100644 --- a/course/format/amd/build/local/content/section/cmitem.min.js.map +++ b/course/format/amd/build/local/content/section/cmitem.min.js.map @@ -1 +1 @@ -{"version":3,"file":"cmitem.min.js","sources":["../../../../src/local/content/section/cmitem.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 course module item component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/content/section/cmitem\n * @class core_courseformat/local/content/section/cmitem\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\n\nexport default class extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section_cmitem';\n // Default query selectors.\n this.selectors = {\n BULKSELECT: `[data-for='cmBulkSelect']`,\n BULKCHECKBOX: `[data-bulkcheckbox]`,\n CARD: `[data-region='activity-card']`,\n DRAGICON: `.editing_move`,\n INPLACEEDITABLE: `[data-inplaceeditablelink]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n HIDE: 'd-none',\n SELECTED: 'selected',\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 * @param {Object} state the state data\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON);\n this._refreshBulk({state});\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.unregister},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `bulk:updated`, handler: this._refreshBulk},\n ];\n }\n\n /**\n * Return the custom activity card drag shadow image.\n *\n * The element returned will be used when the user drags the card.\n *\n * @returns {HTMLElement}\n */\n setDragImage() {\n return this.getElement(this.selectors.CARD);\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({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.locked = element.locked;\n }\n\n /**\n * Update the bulk editing interface.\n *\n * @param {object} param\n * @param {Object} param.state the state data\n */\n _refreshBulk({state}) {\n const bulk = state.bulk;\n // For now, dragging elements in bulk is not possible.\n this.setDraggable(!bulk.enabled);\n // Convert the card into an active element in bulk mode.\n if (bulk.enabled) {\n this.element.dataset.action = 'toggleSelectionCm';\n this.element.dataset.preventDefault = 1;\n } else {\n this.element.removeAttribute('data-action');\n this.element.removeAttribute('data-preventDefault');\n }\n\n this.getElement(this.selectors.BULKSELECT)?.classList.toggle(this.classes.HIDE, !bulk.enabled);\n\n const disabled = !this._isCmBulkEnabled(bulk);\n const selected = this._isSelected(bulk);\n this._refreshActivityCard(bulk, selected);\n this._setCheckboxValue(selected, disabled);\n }\n\n /**\n * Update the activity card depending on the bulk selection.\n *\n * @param {Object} bulk the current bulk state data\n * @param {Boolean} selected if the activity is selected.\n */\n _refreshActivityCard(bulk, selected) {\n this.getElement(this.selectors.INPLACEEDITABLE)?.classList.toggle(this.classes.HIDE, bulk.enabled);\n this.getElement(this.selectors.CARD)?.classList.toggle(this.classes.SELECTED, selected);\n this.element.classList.toggle(this.classes.SELECTED, selected);\n }\n\n /**\n * Modify the checkbox element.\n * @param {Boolean} checked the new checked value\n * @param {Boolean} disabled the new disabled value\n */\n _setCheckboxValue(checked, disabled) {\n const checkbox = this.getElement(this.selectors.BULKCHECKBOX);\n if (!checkbox) {\n return;\n }\n checkbox.checked = checked;\n checkbox.disabled = disabled;\n // Is selectable is used to easily scan the page for bulk checkboxes.\n if (disabled) {\n checkbox.removeAttribute('data-is-selectable');\n } else {\n checkbox.dataset.isSelectable = 1;\n }\n }\n\n /**\n * Check if cm bulk selection is available.\n * @param {Object} bulk the current state bulk attribute\n * @returns {Boolean}\n */\n _isCmBulkEnabled(bulk) {\n if (!bulk.enabled) {\n return false;\n }\n return (bulk.selectedType === '' || bulk.selectedType === 'cm');\n }\n\n /**\n * Check if the cm id is part of the current bulk selection.\n * @param {Object} bulk the current state bulk attribute\n * @returns {Boolean}\n */\n _isSelected(bulk) {\n if (bulk.selectedType !== 'cm') {\n return false;\n }\n return bulk.selection.includes(this.id);\n }\n}\n"],"names":["DndCmItem","create","name","selectors","BULKSELECT","BULKCHECKBOX","CARD","DRAGICON","INPLACEEDITABLE","classes","LOCKED","HIDE","SELECTED","id","this","element","dataset","stateReady","state","configDragDrop","getElement","classList","add","_refreshBulk","getWatchers","watch","handler","unregister","_refreshCm","setDragImage","toggle","DRAGGING","dragging","locked","bulk","setDraggable","enabled","action","preventDefault","removeAttribute","disabled","_isCmBulkEnabled","selected","_isSelected","_refreshActivityCard","_setCheckboxValue","checked","checkbox","isSelectable","selectedType","selection","includes"],"mappings":";;;;;;;;;;0KA4B6BA,mBAKzBC,cAESC,KAAO,8BAEPC,UAAY,CACbC,uCACAC,mCACAC,qCACAC,yBACAC,mDAGCC,QAAU,CACXC,OAAQ,iBACRC,KAAM,SACNC,SAAU,iBAGTC,GAAKC,KAAKC,QAAQC,QAAQH,GAOnCI,WAAWC,iCACFC,eAAeL,KAAKD,kCACpBO,WAAWN,KAAKX,UAAUI,wDAAWc,UAAUC,IAAIR,KAAKL,QAAQF,eAChEgB,aAAa,CAACL,MAAAA,QAQvBM,oBACW,CACH,CAACC,mBAAaX,KAAKD,gBAAea,QAASZ,KAAKa,YAChD,CAACF,mBAAaX,KAAKD,gBAAea,QAASZ,KAAKc,YAChD,CAACH,qBAAuBC,QAASZ,KAAKS,eAW9CM,sBACWf,KAAKM,WAAWN,KAAKX,UAAUG,MAS1CsB,2DAAWb,QAACA,mBAEHA,QAAQM,UAAUS,OAAOhB,KAAKL,QAAQsB,mCAAUhB,QAAQiB,+DACxDjB,QAAQM,UAAUS,OAAOhB,KAAKL,QAAQC,+BAAQK,QAAQkB,yDACtDA,OAASlB,QAAQkB,OAS1BV,8CAAaL,MAACA,mBACJgB,KAAOhB,MAAMgB,UAEdC,cAAcD,KAAKE,SAEpBF,KAAKE,cACArB,QAAQC,QAAQqB,OAAS,yBACzBtB,QAAQC,QAAQsB,eAAiB,SAEjCvB,QAAQwB,gBAAgB,oBACxBxB,QAAQwB,gBAAgB,uDAG5BnB,WAAWN,KAAKX,UAAUC,4DAAaiB,UAAUS,OAAOhB,KAAKL,QAAQE,MAAOuB,KAAKE,eAEhFI,UAAY1B,KAAK2B,iBAAiBP,MAClCQ,SAAW5B,KAAK6B,YAAYT,WAC7BU,qBAAqBV,KAAMQ,eAC3BG,kBAAkBH,SAAUF,UASrCI,qBAAqBV,KAAMQ,iFAClBtB,WAAWN,KAAKX,UAAUK,iEAAkBa,UAAUS,OAAOhB,KAAKL,QAAQE,KAAMuB,KAAKE,wCACrFhB,WAAWN,KAAKX,UAAUG,sDAAOe,UAAUS,OAAOhB,KAAKL,QAAQG,SAAU8B,eACzE3B,QAAQM,UAAUS,OAAOhB,KAAKL,QAAQG,SAAU8B,UAQzDG,kBAAkBC,QAASN,gBACjBO,SAAWjC,KAAKM,WAAWN,KAAKX,UAAUE,cAC3C0C,WAGLA,SAASD,QAAUA,QACnBC,SAASP,SAAWA,SAEhBA,SACAO,SAASR,gBAAgB,sBAEzBQ,SAAS/B,QAAQgC,aAAe,GASxCP,iBAAiBP,cACRA,KAAKE,UAGoB,KAAtBF,KAAKe,cAA6C,OAAtBf,KAAKe,cAQ7CN,YAAYT,YACkB,OAAtBA,KAAKe,cAGFf,KAAKgB,UAAUC,SAASrC,KAAKD"} \ No newline at end of file +{"version":3,"file":"cmitem.min.js","sources":["../../../../src/local/content/section/cmitem.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 course module item component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/content/section/cmitem\n * @class core_courseformat/local/content/section/cmitem\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';\n\nexport default class extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'content_section_cmitem';\n // Default query selectors.\n this.selectors = {\n BULKSELECT: `[data-for='cmBulkSelect']`,\n BULKCHECKBOX: `[data-bulkcheckbox]`,\n CARD: `[data-region='activity-card']`,\n DRAGICON: `.editing_move`,\n INPLACEEDITABLE: `[data-itemtype=\"activityname\"] > [data-inplaceeditablelink]`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n HIDE: 'd-none',\n SELECTED: 'selected',\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 * @param {Object} state the state data\n */\n stateReady(state) {\n this.configDragDrop(this.id);\n this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON);\n this._refreshBulk({state});\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `cm[${this.id}]:deleted`, handler: this.unregister},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n {watch: `bulk:updated`, handler: this._refreshBulk},\n ];\n }\n\n /**\n * Return the custom activity card drag shadow image.\n *\n * The element returned will be used when the user drags the card.\n *\n * @returns {HTMLElement}\n */\n setDragImage() {\n return this.getElement(this.selectors.CARD);\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {object} param\n * @param {Object} param.element details the update details.\n */\n _refreshCm({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.locked = element.locked;\n }\n\n /**\n * Update the bulk editing interface.\n *\n * @param {object} param\n * @param {Object} param.state the state data\n */\n _refreshBulk({state}) {\n const bulk = state.bulk;\n // For now, dragging elements in bulk is not possible.\n this.setDraggable(!bulk.enabled);\n // Convert the card into an active element in bulk mode.\n if (bulk.enabled) {\n this.element.dataset.action = 'toggleSelectionCm';\n this.element.dataset.preventDefault = 1;\n } else {\n this.element.removeAttribute('data-action');\n this.element.removeAttribute('data-preventDefault');\n }\n\n this.getElement(this.selectors.BULKSELECT)?.classList.toggle(this.classes.HIDE, !bulk.enabled);\n\n const disabled = !this._isCmBulkEnabled(bulk);\n const selected = this._isSelected(bulk);\n this._refreshActivityCard(bulk, selected);\n this._setCheckboxValue(selected, disabled);\n }\n\n /**\n * Update the activity card depending on the bulk selection.\n *\n * @param {Object} bulk the current bulk state data\n * @param {Boolean} selected if the activity is selected.\n */\n _refreshActivityCard(bulk, selected) {\n this.getElement(this.selectors.INPLACEEDITABLE)?.classList.toggle(this.classes.HIDE, bulk.enabled);\n this.getElement(this.selectors.CARD)?.classList.toggle(this.classes.SELECTED, selected);\n this.element.classList.toggle(this.classes.SELECTED, selected);\n }\n\n /**\n * Modify the checkbox element.\n * @param {Boolean} checked the new checked value\n * @param {Boolean} disabled the new disabled value\n */\n _setCheckboxValue(checked, disabled) {\n const checkbox = this.getElement(this.selectors.BULKCHECKBOX);\n if (!checkbox) {\n return;\n }\n checkbox.checked = checked;\n checkbox.disabled = disabled;\n // Is selectable is used to easily scan the page for bulk checkboxes.\n if (disabled) {\n checkbox.removeAttribute('data-is-selectable');\n } else {\n checkbox.dataset.isSelectable = 1;\n }\n }\n\n /**\n * Check if cm bulk selection is available.\n * @param {Object} bulk the current state bulk attribute\n * @returns {Boolean}\n */\n _isCmBulkEnabled(bulk) {\n if (!bulk.enabled) {\n return false;\n }\n return (bulk.selectedType === '' || bulk.selectedType === 'cm');\n }\n\n /**\n * Check if the cm id is part of the current bulk selection.\n * @param {Object} bulk the current state bulk attribute\n * @returns {Boolean}\n */\n _isSelected(bulk) {\n if (bulk.selectedType !== 'cm') {\n return false;\n }\n return bulk.selection.includes(this.id);\n }\n}\n"],"names":["DndCmItem","create","name","selectors","BULKSELECT","BULKCHECKBOX","CARD","DRAGICON","INPLACEEDITABLE","classes","LOCKED","HIDE","SELECTED","id","this","element","dataset","stateReady","state","configDragDrop","getElement","classList","add","_refreshBulk","getWatchers","watch","handler","unregister","_refreshCm","setDragImage","toggle","DRAGGING","dragging","locked","bulk","setDraggable","enabled","action","preventDefault","removeAttribute","disabled","_isCmBulkEnabled","selected","_isSelected","_refreshActivityCard","_setCheckboxValue","checked","checkbox","isSelectable","selectedType","selection","includes"],"mappings":";;;;;;;;;;0KA4B6BA,mBAKzBC,cAESC,KAAO,8BAEPC,UAAY,CACbC,uCACAC,mCACAC,qCACAC,yBACAC,oFAGCC,QAAU,CACXC,OAAQ,iBACRC,KAAM,SACNC,SAAU,iBAGTC,GAAKC,KAAKC,QAAQC,QAAQH,GAOnCI,WAAWC,iCACFC,eAAeL,KAAKD,kCACpBO,WAAWN,KAAKX,UAAUI,wDAAWc,UAAUC,IAAIR,KAAKL,QAAQF,eAChEgB,aAAa,CAACL,MAAAA,QAQvBM,oBACW,CACH,CAACC,mBAAaX,KAAKD,gBAAea,QAASZ,KAAKa,YAChD,CAACF,mBAAaX,KAAKD,gBAAea,QAASZ,KAAKc,YAChD,CAACH,qBAAuBC,QAASZ,KAAKS,eAW9CM,sBACWf,KAAKM,WAAWN,KAAKX,UAAUG,MAS1CsB,2DAAWb,QAACA,mBAEHA,QAAQM,UAAUS,OAAOhB,KAAKL,QAAQsB,mCAAUhB,QAAQiB,+DACxDjB,QAAQM,UAAUS,OAAOhB,KAAKL,QAAQC,+BAAQK,QAAQkB,yDACtDA,OAASlB,QAAQkB,OAS1BV,8CAAaL,MAACA,mBACJgB,KAAOhB,MAAMgB,UAEdC,cAAcD,KAAKE,SAEpBF,KAAKE,cACArB,QAAQC,QAAQqB,OAAS,yBACzBtB,QAAQC,QAAQsB,eAAiB,SAEjCvB,QAAQwB,gBAAgB,oBACxBxB,QAAQwB,gBAAgB,uDAG5BnB,WAAWN,KAAKX,UAAUC,4DAAaiB,UAAUS,OAAOhB,KAAKL,QAAQE,MAAOuB,KAAKE,eAEhFI,UAAY1B,KAAK2B,iBAAiBP,MAClCQ,SAAW5B,KAAK6B,YAAYT,WAC7BU,qBAAqBV,KAAMQ,eAC3BG,kBAAkBH,SAAUF,UASrCI,qBAAqBV,KAAMQ,iFAClBtB,WAAWN,KAAKX,UAAUK,iEAAkBa,UAAUS,OAAOhB,KAAKL,QAAQE,KAAMuB,KAAKE,wCACrFhB,WAAWN,KAAKX,UAAUG,sDAAOe,UAAUS,OAAOhB,KAAKL,QAAQG,SAAU8B,eACzE3B,QAAQM,UAAUS,OAAOhB,KAAKL,QAAQG,SAAU8B,UAQzDG,kBAAkBC,QAASN,gBACjBO,SAAWjC,KAAKM,WAAWN,KAAKX,UAAUE,cAC3C0C,WAGLA,SAASD,QAAUA,QACnBC,SAASP,SAAWA,SAEhBA,SACAO,SAASR,gBAAgB,sBAEzBQ,SAAS/B,QAAQgC,aAAe,GASxCP,iBAAiBP,cACRA,KAAKE,UAGoB,KAAtBF,KAAKe,cAA6C,OAAtBf,KAAKe,cAQ7CN,YAAYT,YACkB,OAAtBA,KAAKe,cAGFf,KAAKgB,UAAUC,SAASrC,KAAKD"} \ 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 5627987eeef..4f0d9c894ee 100644 --- a/course/format/amd/src/local/content/section.js +++ b/course/format/amd/src/local/content/section.js @@ -37,6 +37,7 @@ export default class extends DndSection { this.name = 'content_section'; // Default query selectors. this.selectors = { + ACTIONMENU: '.section-actions', SECTION_ITEM: `[data-for='section_title']`, CM: `[data-for="cmitem"]`, SECTIONINFO: `[data-for="sectioninfo"]`, @@ -136,7 +137,14 @@ export default class extends DndSection { if (!cms || cms.length === 0) { return null; } - return cms[cms.length - 1]; + const lastCm = cms[cms.length - 1]; + // If it is a delegated section return the last item overall. + if (this.section.component !== null) { + return lastCm; + } + // If it is a regular section and the last item overall has a parent cm, return the parent instead. + const parentSection = lastCm.parentNode.closest(this.selectors.CM); + return parentSection ?? lastCm; } /** @@ -234,10 +242,6 @@ export default class extends DndSection { * @returns The action menu element. */ _getActionMenu(selector) { - if (this.getElement('.section_action_menu')) { - return this.getElement(selector); - } - - return document.querySelector(selector); + return document.querySelector(`${this.selectors.ACTIONMENU}[data-sectionid='${this.id}'] ${selector}`); } } diff --git a/course/format/amd/src/local/content/section/cmitem.js b/course/format/amd/src/local/content/section/cmitem.js index 4670612b58e..7cb7c970054 100644 --- a/course/format/amd/src/local/content/section/cmitem.js +++ b/course/format/amd/src/local/content/section/cmitem.js @@ -40,7 +40,7 @@ export default class extends DndCmItem { BULKCHECKBOX: `[data-bulkcheckbox]`, CARD: `[data-region='activity-card']`, DRAGICON: `.editing_move`, - INPLACEEDITABLE: `[data-inplaceeditablelink]`, + INPLACEEDITABLE: `[data-itemtype="activityname"] > [data-inplaceeditablelink]`, }; // Most classes will be loaded later by DndCmItem. this.classes = { diff --git a/course/format/classes/base.php b/course/format/classes/base.php index b0f8bc35ce9..e6e90134966 100644 --- a/course/format/classes/base.php +++ b/course/format/classes/base.php @@ -2121,4 +2121,17 @@ abstract class base { public function can_sections_be_removed_from_navigation(): bool { return false; } + + /** + * Determines whether the course module should display the activity editor options. + * + * @param cm_info $cm The activity module. + * @return bool True if the activity editor options are displayed, false otherwise. + */ + public function show_activity_editor_options(cm_info $cm): bool { + if ($cm->get_delegated_section_info() && component_callback_exists('mod_' . $cm->modname, 'cm_info_view')) { + return false; + } + return true; + } } diff --git a/course/format/classes/output/local/content/basecontrolmenu.php b/course/format/classes/output/local/content/basecontrolmenu.php index 877fbecde70..062edd12be0 100644 --- a/course/format/classes/output/local/content/basecontrolmenu.php +++ b/course/format/classes/output/local/content/basecontrolmenu.php @@ -73,9 +73,9 @@ abstract class basecontrolmenu implements named_templatable, renderable { * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output typically, the renderer that's calling this function - * @return array data context for a mustache template + * @return null|array data context for a mustache template */ - public function export_for_template(\renderer_base $output): stdClass { + public function export_for_template(\renderer_base $output): ?stdClass { $menu = $this->get_action_menu($output); if (empty($menu)) { return new stdClass(); diff --git a/course/format/classes/output/local/content/cm.php b/course/format/classes/output/local/content/cm.php index 76173d173b8..a8034235537 100644 --- a/course/format/classes/output/local/content/cm.php +++ b/course/format/classes/output/local/content/cm.php @@ -123,6 +123,7 @@ class cm implements named_templatable, renderable { 'cmid' => $mod->id, 'editing' => $PAGE->user_is_editing(), 'sectionnum' => $this->section->section, + 'cmbulk' => !$mod->get_delegated_section_info(), ]; // Add partial data segments. @@ -185,7 +186,7 @@ class cm implements named_templatable, renderable { $this->displayoptions ); $modavailability = $availability->export_for_template($output); - $data->modavailability = $modavailability; + $data->modavailability = $modavailability ?? false; return $availability->has_availability($output); } @@ -319,7 +320,8 @@ class cm implements named_templatable, renderable { $this->mod, $this->displayoptions ); - $data->controlmenu = $controlmenu->export_for_template($output); + $data->controlmenu = $controlmenu->export_for_template($output) ?? false; + if (!$this->format->supports_components()) { // Add the legacy YUI move link. $data->moveicon = course_get_cm_move($this->mod, $returnsection); @@ -350,8 +352,8 @@ class cm implements named_templatable, renderable { protected function add_visibility_data(stdClass &$data, renderer_base $output): bool { $visibility = new $this->visibilityclass($this->format, $this->section, $this->mod); $templatedata = $visibility->export_for_template($output); + $data->visibility = $templatedata ?? false; if ($templatedata) { - $data->visibility = $templatedata; return true; } return false; diff --git a/course/format/classes/output/local/content/cm/availability.php b/course/format/classes/output/local/content/cm/availability.php index 1b409d14d93..02ce915cdbd 100644 --- a/course/format/classes/output/local/content/cm/availability.php +++ b/course/format/classes/output/local/content/cm/availability.php @@ -75,6 +75,19 @@ class availability extends section_avalability { $this->hasavailabilityname = 'hasmodavailability'; } + /** + * Export this data so it can be used as the context for a mustache template. + * + * @param \renderer_base $output typically, the renderer that's calling this function + * @return stdClass|null data context for a mustache template + */ + public function export_for_template(\renderer_base $output): ?stdClass { + if (!$this->format->show_activity_editor_options($this->mod)) { + return null; + } + return parent::export_for_template($output); + } + /** * Get the availability data to be used as the context for a mustache template. * diff --git a/course/format/classes/output/local/content/cm/completion.php b/course/format/classes/output/local/content/cm/completion.php index 9c69dde4091..fa462ca71fd 100644 --- a/course/format/classes/output/local/content/cm/completion.php +++ b/course/format/classes/output/local/content/cm/completion.php @@ -56,11 +56,15 @@ class completion implements named_templatable, renderable { * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output typically, the renderer that's calling this function - * @return stdClass data context for a mustache template + * @return stdClass|null data context for a mustache template */ public function export_for_template(\renderer_base $output): ?stdClass { global $USER; + if (!$this->format->show_activity_editor_options($this->mod)) { + return null; + } + $course = $this->mod->get_course(); $showcompletionconditions = $course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS; diff --git a/course/format/classes/output/local/content/cm/controlmenu.php b/course/format/classes/output/local/content/cm/controlmenu.php index 5b326d5edc1..7e4a8e47f84 100644 --- a/course/format/classes/output/local/content/cm/controlmenu.php +++ b/course/format/classes/output/local/content/cm/controlmenu.php @@ -62,12 +62,16 @@ class controlmenu extends basecontrolmenu { * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output typically, the renderer that's calling this function - * @return stdClass data context for a mustache template + * @return stdClass|null data context for a mustache template */ - public function export_for_template(\renderer_base $output): stdClass { + public function export_for_template(\renderer_base $output): ?stdClass { $mod = $this->mod; + if (!$this->format->show_activity_editor_options($this->mod)) { + return null; + } + $menu = $this->get_action_menu($output); if (empty($menu)) { diff --git a/course/format/classes/output/local/content/cm/visibility.php b/course/format/classes/output/local/content/cm/visibility.php index 8a6cbc330f6..2af03a27bd4 100644 --- a/course/format/classes/output/local/content/cm/visibility.php +++ b/course/format/classes/output/local/content/cm/visibility.php @@ -78,6 +78,9 @@ class visibility implements named_templatable, renderable { if (!$this->show_visibility()) { return null; } + if (!$this->format->show_activity_editor_options($this->mod)) { + return null; + } $format = $this->format; // In rare legacy cases, the section could be stealth (orphaned) but they are not editable. if (!$format->show_editor() diff --git a/course/format/classes/output/local/content/delegatedsection.php b/course/format/classes/output/local/content/delegatedsection.php new file mode 100644 index 00000000000..027104a78ac --- /dev/null +++ b/course/format/classes/output/local/content/delegatedsection.php @@ -0,0 +1,35 @@ +. + +/** + * Contains the default delegated section course format output class. + * + * @package core_courseformat + * @copyright 2024 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_courseformat\output\local\content; + +/** + * Base class to render a delegated section. + * + * @package core_courseformat + * @copyright 2024 Mikel Martín + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class delegatedsection extends section { +} diff --git a/course/format/classes/output/local/content/section.php b/course/format/classes/output/local/content/section.php index 6e5136a83ad..2c0f615968c 100644 --- a/course/format/classes/output/local/content/section.php +++ b/course/format/classes/output/local/content/section.php @@ -152,7 +152,7 @@ class section implements named_templatable, renderable { 'highlightedlabel' => $format->get_section_highlighted_name(), 'sitehome' => $course->id == SITEID, 'editing' => $PAGE->user_is_editing(), - 'displayonesection' => ($course->id != SITEID && !is_null($format->get_sectionid())), + 'displayonesection' => ($course->id != SITEID && $format->get_sectionid() == $section->id), ]; $haspartials = []; diff --git a/course/format/classes/output/local/content/section/availability.php b/course/format/classes/output/local/content/section/availability.php index eb64d159662..91ebe64c5c3 100644 --- a/course/format/classes/output/local/content/section/availability.php +++ b/course/format/classes/output/local/content/section/availability.php @@ -77,9 +77,9 @@ class availability implements named_templatable, renderable { * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output typically, the renderer that's calling this function - * @return stdClass data context for a mustache template + * @return stdClass|null data context for a mustache template */ - public function export_for_template(\renderer_base $output): stdClass { + public function export_for_template(\renderer_base $output): ?stdClass { $this->build_export_data($output); return $this->data; } diff --git a/course/format/classes/output/local/content/section/header.php b/course/format/classes/output/local/content/section/header.php index 7c938342936..d325efe9a27 100644 --- a/course/format/classes/output/local/content/section/header.php +++ b/course/format/classes/output/local/content/section/header.php @@ -82,7 +82,7 @@ class header implements named_templatable, renderable { $data->title = $output->section_title_without_link($section, $course); $data->sitehome = true; } else { - if (is_null($format->get_sectionid())) { + if (is_null($format->get_sectionid()) || $format->get_sectionid() != $section->id) { // All sections are displayed. if (!$data->editing) { $data->title = $output->section_title($section, $course); @@ -114,10 +114,13 @@ class header implements named_templatable, renderable { $data->name = get_section_name($course, $section); $data->selecttext = $format->get_format_string('selectsection', $data->name); - if (!$format->get_sectionnum()) { + if (!$format->get_sectionnum() && !$section->is_delegated()) { $data->sectionbulk = true; } + // Delegated sections in main course page need to have h4 tag, h3 otherwise. + $data->headinglevel = ($section->is_delegated() && is_null($format->get_sectionid())) ? 4 : 3; + return $data; } } diff --git a/course/format/templates/local/content/cm.mustache b/course/format/templates/local/content/cm.mustache index 8e2102b6ac6..c07b78cd212 100644 --- a/course/format/templates/local/content/cm.mustache +++ b/course/format/templates/local/content/cm.mustache @@ -65,9 +65,11 @@
- {{$ core_courseformat/local/content/cm/bulkselect }} - {{> core_courseformat/local/content/cm/bulkselect }} - {{/ core_courseformat/local/content/cm/bulkselect }} + {{#cmbulk}} + {{$ core_courseformat/local/content/cm/bulkselect }} + {{> core_courseformat/local/content/cm/bulkselect }} + {{/ core_courseformat/local/content/cm/bulkselect }} + {{/cmbulk}} {{! Place the actual content of the activity-item in a separate template to make it easier for other formats to add additional content to the activity wrapper. diff --git a/course/format/templates/local/content/cm/activity.mustache b/course/format/templates/local/content/cm/activity.mustache index 8bd20c4b92d..8ca5b939706 100644 --- a/course/format/templates/local/content/cm/activity.mustache +++ b/course/format/templates/local/content/cm/activity.mustache @@ -139,7 +139,7 @@ {{! Description }} {{#altcontent}} -
+
{{{altcontent}}}
{{/altcontent}} diff --git a/course/format/templates/local/content/delegatedsection.mustache b/course/format/templates/local/content/delegatedsection.mustache new file mode 100644 index 00000000000..3cf4b264a4a --- /dev/null +++ b/course/format/templates/local/content/delegatedsection.mustache @@ -0,0 +1,93 @@ +{{! + 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 . +}} +{{! + @template core_courseformat/local/content/delegatedsection + + Displays a course section. + + Note: This template is a wrapper around the section/content template to allow course formats and theme designers to + modify parts of the wrapper without having to copy/paste the entire template. + + Example context (json): + { + "num": 3, + "id": 35, + "controlmenu": "[tools menu]", + "header": { + "name": "Section title", + "title": "
Section title", + "url": "#", + "ishidden": true, + "headinglevel": 3 + }, + "cmlist": { + "cms": [ + { + "cmitem": { + "cmformat": { + "cmname": "Assign example", + "hasname": "true" + }, + "id": 4, + "anchor": "activity-4", + "module": "assign", + "extraclasses": "" + } + } + ], + "hascms": true + }, + "ishidden": false, + "iscurrent": true, + "currentlink": "This topic", + "availability": { + "info": "Hidden from students", + "hasavailability": true + }, + "summary": { + "summarytext": "Summary text!" + }, + "controlmenu": { + "menu": "Edit", + "hasmenu": true + }, + "cmcontrols": "[Add an activity or resource]", + "iscoursedisplaymultipage": true, + "sectionreturnnum": 0, + "contentcollapsed": false, + "sitehome": false, + "highlightedlabel" : "Highlighted" + } +}} +
    + +
diff --git a/course/format/templates/local/content/section/content.mustache b/course/format/templates/local/content/section/content.mustache index d4db117e78e..7a61ca87e36 100644 --- a/course/format/templates/local/content/section/content.mustache +++ b/course/format/templates/local/content/section/content.mustache @@ -28,7 +28,8 @@ "name": "Section title", "title": "Section title", "url": "#", - "ishidden": true + "ishidden": true, + "headinglevel": 3 }, "cmlist": { "cms": [ diff --git a/course/format/templates/local/content/section/header.mustache b/course/format/templates/local/content/section/header.mustache index 8cac7191cf4..e4046428cba 100644 --- a/course/format/templates/local/content/section/header.mustache +++ b/course/format/templates/local/content/section/header.mustache @@ -27,7 +27,8 @@ "url": "#", "headerdisplaymultipage": true, "sectionbulk": true, - "editing": 0 + "editing": 0, + "headinglevel": 3 } }} {{#sectionbulk}} @@ -50,7 +51,7 @@ {{/sitehome}} {{^sitehome}} {{^displayonesection}} -
+ {{/displayonesection}} {{/sitehome}} diff --git a/course/format/topics/classes/output/courseformat/content/section.php b/course/format/topics/classes/output/courseformat/content/section.php index ca41d215527..a7c50d734d7 100644 --- a/course/format/topics/classes/output/courseformat/content/section.php +++ b/course/format/topics/classes/output/courseformat/content/section.php @@ -45,7 +45,7 @@ class section extends section_base { $data = parent::export_for_template($output); - if (!$this->format->get_sectionnum()) { + if (!$this->format->get_sectionnum() && !$this->section->is_delegated()) { $addsectionclass = $format->get_output_classname('content\\addsection'); $addsection = new $addsectionclass($format); $data->numsections = $addsection->export_for_template($output); diff --git a/course/renderer.php b/course/renderer.php index f667bfd11bf..8de46e710e2 100644 --- a/course/renderer.php +++ b/course/renderer.php @@ -141,10 +141,9 @@ class core_course_renderer extends plugin_renderer_base { * Build the HTML for the module chooser javascript popup. * * @param int $courseid The course id to fetch modules for. - * @param int|null $sectionnum The section number to fetch modules for. * @return string */ - public function course_activitychooser($courseid, ?int $sectionnum = null) { + public function course_activitychooser($courseid) { if (!$this->page->requires->should_create_one_time_item_now('core_course_modchooser')) { return ''; @@ -154,7 +153,7 @@ class core_course_renderer extends plugin_renderer_base { $chooserconfig = (object) [ 'tabmode' => get_config('core', 'activitychoosertabmode'), ]; - $this->page->requires->js_call_amd('core_course/activitychooser', 'init', [$courseid, $chooserconfig, $sectionnum]); + $this->page->requires->js_call_amd('core_course/activitychooser', 'init', [$courseid, $chooserconfig]); return ''; } @@ -232,7 +231,7 @@ class core_course_renderer extends plugin_renderer_base { $ajaxcontrol = $this->render_from_template('course/activitychooserbutton', $data); // Load the JS for the modal. - $this->course_activitychooser($course->id, $section); + $this->course_activitychooser($course->id); return $ajaxcontrol; } diff --git a/mod/subsection/classes/manager.php b/mod/subsection/classes/manager.php index 3e6271e00ec..612d6e53e7a 100644 --- a/mod/subsection/classes/manager.php +++ b/mod/subsection/classes/manager.php @@ -19,8 +19,10 @@ namespace mod_subsection; use cm_info; use context_module; use completion_info; +use core_courseformat\formatactions; use mod_subsection\event\course_module_viewed; use moodle_page; +use section_info; use stdClass; /** @@ -180,4 +182,23 @@ class manager { $completion = new completion_info($course); $completion->set_module_viewed($this->cm); } + + /** + * Get the delegated section info. + * + * @return section_info the delegated section info + */ + public function get_delegated_section_info(): section_info { + $delegatedsection = $this->cm->get_delegated_section_info(); + if (!$delegatedsection) { + // Some restorations can produce a situation where the section is not found. + // In that case, we create a new one. + $delegatedsection = formatactions::section($this->cm->course)->create_delegated( + self::PLUGINNAME, + $this->cm->id, + (object) ['name' => $this->instance->name], + ); + } + return $delegatedsection; + } } diff --git a/mod/subsection/lib.php b/mod/subsection/lib.php index 9a8ed89e8df..b45271cea3b 100644 --- a/mod/subsection/lib.php +++ b/mod/subsection/lib.php @@ -207,3 +207,39 @@ function subsection_extend_navigation($subsectionnode, $course, $module, $cm) { */ function subsection_extend_settings_navigation($settingsnav, $subsectionnode = null) { } + +/** + * Sets dynamic information about a course module + * + * This function is called from cm_info when displaying the module + * mod_subsection can be displayed inline on course page and therefore have no course link + * + * @param cm_info $cm + */ +function subsection_cm_info_dynamic(cm_info $cm) { + $cm->set_no_view_link(); +} + +/** + * Sets the special subsection display on course page. + * + * @param cm_info $cm Course-module object + */ +function subsection_cm_info_view(cm_info $cm) { + global $DB, $PAGE; + + $cm->set_custom_cmlist_item(true); + $course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST); + + // Get the section info. + $delegatedsection = manager::create_from_coursemodule($cm)->get_delegated_section_info(); + + // Render the delegated section. + $format = course_get_format($course); + $renderer = $PAGE->get_renderer('format_' . $course->format); + $outputclass = $format->get_output_classname('content\\delegatedsection'); + /** @var \core_courseformat\output\local\content\delegatedsection $delegatedsectionoutput */ + $delegatedsectionoutput = new $outputclass($format, $delegatedsection); + + $cm->set_content($renderer->render($delegatedsectionoutput), true); +} diff --git a/mod/subsection/tests/behat/subsection_actionmenu.feature b/mod/subsection/tests/behat/subsection_actionmenu.feature index ed1aa6dae47..7c180323305 100644 --- a/mod/subsection/tests/behat/subsection_actionmenu.feature +++ b/mod/subsection/tests/behat/subsection_actionmenu.feature @@ -101,7 +101,7 @@ Feature: The module menu replaces the delegated section menu When I choose "Edit settings" in the open action menu And the field "Section name" matches value "Subsection1" And I click on "Cancel" "button" - And I am on the "Subsection1" "subsection activity" page + And I am on the "C1 > Subsection1" "course > section" page # Subsection page. Open the section header action menu. And I click on "Edit" "icon" in the "[data-region='header-actions-container']" "css_element" And I choose "Edit settings" in the open action menu @@ -120,7 +120,7 @@ Feature: The module menu replaces the delegated section menu When I choose "Permalink" in the open action menu Then I click on "Copy to clipboard" "link" And I should see "Text copied to clipboard" - And I am on the "Subsection1" "subsection activity" page + And I am on the "C1 > Subsection1" "course > section" page # Subsection page. Open the section header action menu. And I click on "Edit" "icon" in the "[data-region='header-actions-container']" "css_element" And I choose "Permalink" in the open action menu @@ -153,7 +153,7 @@ Feature: The module menu replaces the delegated section menu And I choose "Delete" in the open action menu And I click on "Delete" "button" in the "Delete activity?" "dialogue" And "Subsection2" "link" should not exist in the "#region-main-box" "css_element" - And I am on the "Subsection3" "subsection activity" page + And I am on the "C1 > Subsection3" "course > section" page # Subsection page. Open the section header action menu. And I click on "Edit" "icon" in the "[data-region='header-actions-container']" "css_element" And "Delete" "link" should not exist in the "[data-region='header-actions-container']" "css_element" diff --git a/mod/subsection/tests/behat/subsection_coursepage.feature b/mod/subsection/tests/behat/subsection_coursepage.feature new file mode 100644 index 00000000000..2caa5b96b58 --- /dev/null +++ b/mod/subsection/tests/behat/subsection_coursepage.feature @@ -0,0 +1,66 @@ +@mod @mod_subsection +Feature: Users view subsections on course page + In order to use subsections + As an user + I need to view subsections on course page + + Background: + Given I enable "subsection" "mod" plugin + And the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | + And the following "courses" exist: + | fullname | shortname | category | numsections | + | Course 1 | C1 | 0 | 2 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + And the following "activities" exist: + | activity | name | course | idnumber | section | + | subsection | Subsection1 | C1 | sub1 | 1 | + | page | Page1 in Subsection1 | C1 | page11 | 3 | + | subsection | Subsection2 | C1 | sub2 | 1 | + + @javascript + Scenario: Student can view, expand and collapse subsections on course page + When I log in as "student1" + And I am on "Course 1" course homepage + Then I should see "Subsection1" in the "region-main" "region" + And I should see "Page1 in Subsection1" in the "Subsection1" "activity" + And I click on "Collapse" "link" in the "Subsection1" "activity" + And I should not see "Page1 in Subsection1" in the "Subsection1" "activity" + And I click on "Expand" "link" in the "Subsection1" "activity" + And I click on "Page1 in Subsection1" "link" in the "Subsection1" "activity" + + @javascript + Scenario: Teacher can create activities inside subsections on course page + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + # Add an assignment to the top of Subsection1. + And I hover "Insert an activity or resource before 'Page1 in Subsection1'" "button" + And I press "Insert an activity or resource before 'Page1 in Subsection1'" + And I click on "Add a new Assignment" "link" in the "Add an activity or resource" "dialogue" + And I set the following fields to these values: + | Assignment name | Assignment1 in Subsection1 | + And I press "Save and return to course" + Then I should see "Assignment1 in Subsection1" in the "Subsection1" "activity" + # Add an assignment to the empty Subsection2. + And I add an "assign" activity to course "Course 1" section "4" and I fill the form with: + | Assignment name | Assignment1 in Subsection2 | + And I should see "Assignment1 in Subsection2" in the "Subsection2" "activity" + + @javascript + Scenario: Teacher can create activities between subsections on course page + When I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I hover "Insert an activity or resource before 'Subsection2'" "button" + And I press "Insert an activity or resource before 'Subsection2'" + And I click on "Add a new Assignment" "link" in the "Add an activity or resource" "dialogue" + And I set the following fields to these values: + | Assignment name | Assignment between subsections | + And I press "Save and return to course" + And I wait "5" seconds + And "Assignment between subsections" "link" should appear after "Page1 in Subsection1" "text" + And "Assignment between subsections" "link" should appear before "Subsection2" "text" diff --git a/mod/subsection/tests/behat/subsection_rename.feature b/mod/subsection/tests/behat/subsection_rename.feature index e1d758a709c..ec310da2a98 100644 --- a/mod/subsection/tests/behat/subsection_rename.feature +++ b/mod/subsection/tests/behat/subsection_rename.feature @@ -25,10 +25,11 @@ Feature: Teachers can rename subsections @javascript Scenario: Renaming the subsection activity changes the subsection name Given I should see "Subsection activity" in the "page-content" "region" - When I set the field "Edit title" in the "Subsection activity" "activity" to "New name" + When I set the field "Edit section name" in the "Subsection activity" "activity" to "New name" And I should not see "Subsection activity" in the "region-main" "region" And I should see "New name" in the "page-content" "region" - Then I click on "New name" "link" in the "page-content" "region" + Then I open "New name" actions menu + And I choose "View" in the open action menu And I should see "New name" in the "page" "region" And I should see "Subactivity" in the "region-main" "region" diff --git a/mod/subsection/tests/behat/subsection_handling.feature b/mod/subsection/tests/behat/subsection_sectionpage.feature similarity index 67% rename from mod/subsection/tests/behat/subsection_handling.feature rename to mod/subsection/tests/behat/subsection_sectionpage.feature index 0224555f3b8..3e99c1bccc5 100644 --- a/mod/subsection/tests/behat/subsection_handling.feature +++ b/mod/subsection/tests/behat/subsection_sectionpage.feature @@ -1,6 +1,6 @@ @mod @mod_subsection -Feature: Teachers create and destroy subsections - In order to use subsections +Feature: Teachers create and destroy subsections on section page + In order to use subsections on section page As an teacher I need to create and destroy subsections @@ -17,18 +17,6 @@ Feature: Teachers create and destroy subsections | teacher1 | C1 | editingteacher | And I log in as "teacher1" - Scenario: Subsections are not listed as regular sections - Given the following "activities" exist: - | activity | name | course | idnumber | section | - | subsection | Subsection1 | C1 | forum1 | 1 | - | data | Subactivity1 | C1 | data1 | 3 | - When I am on "Course 1" course homepage - Then "Subsection1" "section" should not exist - And I should not see "Subactivity1" in the "region-main" "region" - And I click on "Subsection1" "link" in the "region-main" "region" - And I should see "Subsection1" in the "page" "region" - And I should see "Subactivity1" in the "region-main" "region" - Scenario: Activities can be created in a subsection Given the following "activities" exist: | activity | name | course | idnumber | section | @@ -40,8 +28,6 @@ Feature: Teachers create and destroy subsections And I am on "Course 1" course homepage And I click on "Subsection1" "link" in the "region-main" "region" Then I should see "Test assignment name" in the "region-main" "region" - And I am on "Course 1" course homepage - And I should not see "Test assignment name" in the "region-main" "region" @javascript Scenario: Teacher can create activities in a subsection page with the activity chooser @@ -57,5 +43,3 @@ Feature: Teachers create and destroy subsections | Description | Test assignment description | And I press "Save and return to course" Then I should see "Test assignment name" in the "region-main" "region" - And I am on "Course 1" course homepage - And I should not see "Test assignment name" in the "region-main" "region" diff --git a/mod/subsection/view.php b/mod/subsection/view.php index 82f97a7bede..aa1fa788b57 100644 --- a/mod/subsection/view.php +++ b/mod/subsection/view.php @@ -39,19 +39,6 @@ require_login($course, true, $cm); $modulecontext = $manager->get_context(); $manager->set_module_viewed($course); +$delegatesection = $manager->get_delegated_section_info(); -$modinfo = get_fast_modinfo($course); - -$delegatesection = $modinfo->get_section_info_by_component(manager::PLUGINNAME, $moduleinstance->id); -if (!$delegatesection) { - // Some restorations can produce a situation where the section is not found. - // In that case, we create a new one. - formatactions::section($course)->create_delegated( - manager::PLUGINNAME, - $id, - (object) [ - 'name' => $moduleinstance->name, - ] - ); -} redirect(new moodle_url('/course/section.php', ['id' => $delegatesection->id])); diff --git a/theme/boost/scss/moodle/course.scss b/theme/boost/scss/moodle/course.scss index f36eb2b0a5f..035e6f55024 100644 --- a/theme/boost/scss/moodle/course.scss +++ b/theme/boost/scss/moodle/course.scss @@ -1078,6 +1078,7 @@ span.editinstructions { /* Variables definition*/ +$activity-item-padding: 0.75rem !default; $activity-border-radius: 1rem !default; $activity-border-width: 2px !default; $activity-hover-border-color: $primary !default; @@ -1211,10 +1212,15 @@ $divider-hover-color: $primary !default; } // Override '.btn.btn-icon' styles from buttons.scss to make action menu buttons smaller. - .action-menu .btn.btn-icon { - height: map-get($iconsizes, 5); - width: map-get($iconsizes, 5); - @include border-radius(); + .action-menu { + .btn.btn-icon { + height: map-get($iconsizes, 5); + width: map-get($iconsizes, 5); + @include border-radius(); + } + &.section-actions .btn.btn-icon { + font-size: $font-size-lg; + } } .section-summary-activities { @@ -1299,8 +1305,9 @@ $divider-hover-color: $primary !default; padding-top: map-get($spacers, 1); margin-top: map-get($spacers, 1); border-top: $border-width solid $border-color; - // Custom styles for activity while editing. - .editing & { + // Custom styles for activity while editing and for the first activity card in the section. + .editing &, + .section &:first-child { // Remove activity top border and spacing, while editing a separator is displayed. padding-top: 0; margin-top: 0; @@ -1310,10 +1317,10 @@ $divider-hover-color: $primary !default; .activity-item { position: relative; - padding: .75rem; - background-color: $white; + padding: $activity-item-padding; + @include border-radius($activity-border-radius); &.activityinline { - padding: .75rem 0; + padding: $activity-item-padding 0; } &.hiddenactivity { background-color: $gray-100; @@ -1325,14 +1332,13 @@ $divider-hover-color: $primary !default; // Custom styles for activity cards while editing. .editing & { cursor: move; - border: $activity-border-width solid transparent; - @include border-radius($activity-border-radius); .a { cursor: pointer; } - &:hover, + // Add a border on hover to the activity card (avoiding hover effect in inner activities). + &:hover:not(:has(.activity:hover)), &.selected { - border: $activity-border-width solid $activity-hover-border-color; + outline: $activity-border-width solid $activity-hover-border-color; box-shadow: $box-shadow-sm; .activityiconcontainer, .badge { @@ -1677,6 +1683,11 @@ $divider-hover-color: $primary !default; position: absolute; left: -2rem; } + // Delegated sections are not available for bulk editing. + &:has(.delegated-section):hover { + outline: none !important; // stylelint-disable-line declaration-no-important + box-shadow: none !important; // stylelint-disable-line declaration-no-important + } } .course-section-header .bulkselect { left: -2.75rem; @@ -1737,6 +1748,7 @@ $divider-hover-color: $primary !default; } /* Styles for the Move activity and Move section modals */ + .move-activity-tree { .collapse-list-item { @include border-radius(); @@ -1776,3 +1788,50 @@ $divider-hover-color: $primary !default; font-weight: bold; } } + +/* Subsection specific styles */ + +.activity.subsection { + border-top: none; + > .activity-item { + border: $border-width solid $border-color; + padding: 0; + .activity-altcontent { + margin-top: 0; + } + } + .section { + margin-top: 0; + .section-item { + border: none; + padding: $activity-item-padding; + } + .icons-collapse-expand:has(+ h4) { + height: map-get($iconsizes, 4); + width: map-get($iconsizes, 4); + font-size: $font-size-sm; + } + h4 { + font-size: $h5-font-size; + } + .section-actions .btn.btn-icon { + font-size: inherit; + } + } + + .activity { + border-top: none; + } + // Allow v-parent-focus only for closest acvitity (avoiding subsection). + .focus-control:not(:has(.focus-control)) { + &:focus-within .v-parent-focus, + &:hover .v-parent-focus { + opacity: 1 !important; // stylelint-disable-line declaration-no-important + visibility: visible !important; // stylelint-disable-line declaration-no-important + } + } + .focus-control:focus-within .focus-control .v-parent-focus, + .focus-control:hover .focus-control .v-parent-focus { + opacity: 0; + visibility: hidden; + } +} diff --git a/theme/boost/scss/moodle/undo.scss b/theme/boost/scss/moodle/undo.scss index 0cb23fbb6ba..e384d7804da 100644 --- a/theme/boost/scss/moodle/undo.scss +++ b/theme/boost/scss/moodle/undo.scss @@ -11,7 +11,6 @@ li.activity.label, .file-picker td.label { background: inherit; color: inherit; - border: inherit; text-shadow: none; white-space: normal; display: block; @@ -21,6 +20,7 @@ li.activity.label, } .file-picker td.label { + border: inherit; display: table-cell; text-align: right; padding: 8px; @@ -55,7 +55,7 @@ li.activity.label, * it to white? */ -li.section.hidden, +.section.hidden, .block.hidden, .block.invisible { visibility: visible; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 045580a0e08..ae6f748b20b 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -29068,6 +29068,9 @@ span.editinstructions .alert-link { width: 32px; border-radius: 0.5rem; } +.course-section .action-menu.section-actions .btn.btn-icon { + font-size: 1.171875rem; +} .course-section .section-summary-activities .icon { width: inherit; color: #0f6cbf; @@ -29133,7 +29136,7 @@ span.editinstructions .alert-link { margin-top: 0.25rem; border-top: 1px solid #dee2e6; } -.editing .activity { +.editing .activity, .section .activity:first-child { padding-top: 0; margin-top: 0; border-top: none; @@ -29142,7 +29145,7 @@ span.editinstructions .alert-link { .activity-item { position: relative; padding: 0.75rem; - background-color: #fff; + border-radius: 1rem; } .activity-item.activityinline { padding: 0.75rem 0; @@ -29156,18 +29159,16 @@ span.editinstructions .alert-link { } .editing .activity-item { cursor: move; - border: 2px solid transparent; - border-radius: 1rem; } .editing .activity-item .a { cursor: pointer; } -.editing .activity-item:hover, .editing .activity-item.selected { - border: 2px solid #0f6cbf; +.editing .activity-item:hover:not(:has(.activity:hover)), .editing .activity-item.selected { + outline: 2px solid #0f6cbf; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); } -.editing .activity-item:hover .activityiconcontainer, -.editing .activity-item:hover .badge, .editing .activity-item.selected .activityiconcontainer, +.editing .activity-item:hover:not(:has(.activity:hover)) .activityiconcontainer, +.editing .activity-item:hover:not(:has(.activity:hover)) .badge, .editing .activity-item.selected .activityiconcontainer, .editing .activity-item.selected .badge { mix-blend-mode: multiply; } @@ -29863,6 +29864,10 @@ span.editinstructions .alert-link { position: absolute; left: -2rem; } +.bulkenabled .activity-item:has(.delegated-section):hover { + outline: none !important; + box-shadow: none !important; +} .bulkenabled .course-section-header .bulkselect { left: -2.75rem; position: relative; @@ -29941,6 +29946,48 @@ span.editinstructions .alert-link { font-weight: bold; } +/* Subsection specific styles */ +.activity.subsection { + border-top: none; +} +.activity.subsection > .activity-item { + border: 1px solid #dee2e6; + padding: 0; +} +.activity.subsection > .activity-item .activity-altcontent { + margin-top: 0; +} +.activity.subsection .section { + margin-top: 0; +} +.activity.subsection .section .section-item { + border: none; + padding: 0.75rem; +} +.activity.subsection .section .icons-collapse-expand:has(+ h4) { + height: 24px; + width: 24px; + font-size: 0.8203125rem; +} +.activity.subsection .section h4 { + font-size: 1.171875rem; +} +.activity.subsection .section .section-actions .btn.btn-icon { + font-size: inherit; +} +.activity.subsection + .activity { + border-top: none; +} +.activity.subsection .focus-control:not(:has(.focus-control)):focus-within .v-parent-focus, .activity.subsection .focus-control:not(:has(.focus-control)):hover .v-parent-focus { + opacity: 1 !important; + visibility: visible !important; +} +.activity.subsection .focus-control:focus-within .focus-control .v-parent-focus, +.activity.subsection .focus-control:hover .focus-control .v-parent-focus { + opacity: 0; + visibility: hidden; +} + /* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */ :target, :focus { @@ -36146,7 +36193,6 @@ li.activity.label, .file-picker td.label { background: inherit; color: inherit; - border: inherit; text-shadow: none; white-space: normal; display: block; @@ -36156,6 +36202,7 @@ li.activity.label, } .file-picker td.label { + border: inherit; display: table-cell; text-align: right; padding: 8px; @@ -36187,7 +36234,7 @@ li.activity.label, * nice effect, though they'd still be active. Maybe reverse * it to white? */ -li.section.hidden, +.section.hidden, .block.hidden, .block.invisible { visibility: visible; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 96e0804f59d..f02544c7eee 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -29068,6 +29068,9 @@ span.editinstructions .alert-link { width: 32px; border-radius: 0.25rem; } +.course-section .action-menu.section-actions .btn.btn-icon { + font-size: 1.171875rem; +} .course-section .section-summary-activities .icon { width: inherit; color: #0f6cbf; @@ -29133,7 +29136,7 @@ span.editinstructions .alert-link { margin-top: 0.25rem; border-top: 1px solid #dee2e6; } -.editing .activity { +.editing .activity, .section .activity:first-child { padding-top: 0; margin-top: 0; border-top: none; @@ -29142,7 +29145,7 @@ span.editinstructions .alert-link { .activity-item { position: relative; padding: 0.75rem; - background-color: #fff; + border-radius: 1rem; } .activity-item.activityinline { padding: 0.75rem 0; @@ -29156,18 +29159,16 @@ span.editinstructions .alert-link { } .editing .activity-item { cursor: move; - border: 2px solid transparent; - border-radius: 1rem; } .editing .activity-item .a { cursor: pointer; } -.editing .activity-item:hover, .editing .activity-item.selected { - border: 2px solid #0f6cbf; +.editing .activity-item:hover:not(:has(.activity:hover)), .editing .activity-item.selected { + outline: 2px solid #0f6cbf; box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); } -.editing .activity-item:hover .activityiconcontainer, -.editing .activity-item:hover .badge, .editing .activity-item.selected .activityiconcontainer, +.editing .activity-item:hover:not(:has(.activity:hover)) .activityiconcontainer, +.editing .activity-item:hover:not(:has(.activity:hover)) .badge, .editing .activity-item.selected .activityiconcontainer, .editing .activity-item.selected .badge { mix-blend-mode: multiply; } @@ -29863,6 +29864,10 @@ span.editinstructions .alert-link { position: absolute; left: -2rem; } +.bulkenabled .activity-item:has(.delegated-section):hover { + outline: none !important; + box-shadow: none !important; +} .bulkenabled .course-section-header .bulkselect { left: -2.75rem; position: relative; @@ -29941,6 +29946,48 @@ span.editinstructions .alert-link { font-weight: bold; } +/* Subsection specific styles */ +.activity.subsection { + border-top: none; +} +.activity.subsection > .activity-item { + border: 1px solid #dee2e6; + padding: 0; +} +.activity.subsection > .activity-item .activity-altcontent { + margin-top: 0; +} +.activity.subsection .section { + margin-top: 0; +} +.activity.subsection .section .section-item { + border: none; + padding: 0.75rem; +} +.activity.subsection .section .icons-collapse-expand:has(+ h4) { + height: 24px; + width: 24px; + font-size: 0.8203125rem; +} +.activity.subsection .section h4 { + font-size: 1.171875rem; +} +.activity.subsection .section .section-actions .btn.btn-icon { + font-size: inherit; +} +.activity.subsection + .activity { + border-top: none; +} +.activity.subsection .focus-control:not(:has(.focus-control)):focus-within .v-parent-focus, .activity.subsection .focus-control:not(:has(.focus-control)):hover .v-parent-focus { + opacity: 1 !important; + visibility: visible !important; +} +.activity.subsection .focus-control:focus-within .focus-control .v-parent-focus, +.activity.subsection .focus-control:hover .focus-control .v-parent-focus { + opacity: 0; + visibility: hidden; +} + /* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */ :target, :focus { @@ -36146,7 +36193,6 @@ li.activity.label, .file-picker td.label { background: inherit; color: inherit; - border: inherit; text-shadow: none; white-space: normal; display: block; @@ -36156,6 +36202,7 @@ li.activity.label, } .file-picker td.label { + border: inherit; display: table-cell; text-align: right; padding: 8px; @@ -36187,7 +36234,7 @@ li.activity.label, * nice effect, though they'd still be active. Maybe reverse * it to white? */ -li.section.hidden, +.section.hidden, .block.hidden, .block.invisible { visibility: visible;