diff --git a/course/amd/build/actions.min.js b/course/amd/build/actions.min.js
index 36d59dfd814..2c3fda01b4e 100644
--- a/course/amd/build/actions.min.js
+++ b/course/amd/build/actions.min.js
@@ -1,2 +1,2 @@
-define ("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_factory","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m=l.getCurrentCourseEditor(),n={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},o={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",ADDSECTIONS:"#changenumsections [data-add-sections]"};g.use("moodle-course-coursebase",function(){var a=M.course.format.get_section_selector();if(a){o.SECTIONLI=a}});var p=function(a){var b=a.get(0);if(b.dataset.id){return b.dataset.id}var c;g.use("moodle-course-util",function(a){c=a.Moodle.core_course.util.cm.getId(a.Node(b))});return c},q=function(a){var b;g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getName(c.Node(a.get(0)))});var c=m.state,d=p(a);if(!b&&c&&d){var e;b=null===(e=c.cm.get(d))||void 0===e?void 0:e.name}return b},r=function(a){a.addClass(n.EDITINPROGRESS);var b=a.find(o.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();return c}return null},s=function(a){a.addClass(n.EDITINPROGRESS);var b=a.find(o.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();return c}return null},t=function(a){var b=M.util.add_lightbox(g,g.Node(a.get(0)));b.show();return b},u=function(a,b,c){window.setTimeout(function(){a.removeClass(n.EDITINPROGRESS);if(b){b.hide()}},c)},v=function(a,b){if(a){window.setTimeout(function(){a.hide()},b)}},w=function(a){g.use("moodle-course-coursebase",function(){M.course.coursebase.invoke_function("setup_for_resource","#"+a)});if(M.core.actionmenu&&M.core.actionmenu.newDOMNode){M.core.actionmenu.newDOMNode(g.one("#"+a))}},x=function(b,c){var d=a("#"+b),e="[data-action="+c+"]";if("groupsseparate"===c||"groupsvisible"===c||"groupsnone"===c){e="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"}if(d.find(e).is(":visible")){d.find(e).focus()}else{d.find(o.MENU).find(o.TOGGLE).focus()}},y=function(b){var c=a("a:visible"),d=!1,e=null;c.each(function(){if(a.contains(b[0],this)){d=!0}else if(d){e=this;return!1}});return e},z=function(c,e,f){var g=f.attr("data-action"),h=r(c),i=b.call([{methodname:"core_course_edit_module",args:{id:e,action:g,sectionreturn:f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0}}],!0),j;if("duplicate"===g){j=t(f.closest(o.SECTIONLI))}a.when.apply(a,i).done(function(b){var d=y(c);c.replaceWith(b);var f=[];a("
"+b+"
").find(o.ACTIVITYLI).each(function(b){w(a(this).attr("id"));if(0===b){x(a(this).attr("id"),g);d=null}f.push(p(a(this)))});if(d){d.focus()}u(c,h,400);v(j,400);c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:g}));m.dispatch("legacyActivityAction",g,e,f)}).fail(function(b){u(c,h);v(j);var f=a.Event("coursemoduleeditfailed",{exception:b,action:g});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})},A=function(c,d,e){var f=a(c),g=r(f),h=b.call([{methodname:"core_course_get_module",args:{id:d,sectionreturn:e}}],!0);return new Promise(function(b,c){a.when.apply(a,h).done(function(a){u(f,g,400);F(a);b(a)}).fail(function(){u(f,g);c()})})},B=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=q(a);e.get_string("pluginname",c).done(function(a){e.get_strings([{key:"confirm"},{key:null===f?"deletechecktype":"deletechecktypename",param:{type:a,name:f}},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],b)})})},C=function(a,b){e.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done(function(c){d.confirm(c[0],a,c[1],c[2],b)})},D=function(a,b,f,g,h){return e.get_strings([{key:f,component:g}]).then(function(d){a.find("span.menu-action-text").html(d[0]);return c.renderPix(b,"core")}).then(function(b){a.find(".icon").replaceWith(b);a.attr("data-action",h)}).catch(d.exception)},E=function(b,c,d,e,f){var g=c.attr("data-action");if("hide"===g||"show"===g){if("hide"===g){b.addClass("hidden");D(c,"i/show","showfromothers","format_"+e,"show")}else{b.removeClass("hidden");D(c,"i/hide","hidefromothers","format_"+e,"hide")}if(d.modules!==void 0){for(var h in d.modules){F(d.modules[h])}}if(d.section_availability!==void 0){b.find(".section_availability").first().replaceWith(d.section_availability)}var k=m.state.section.get(f);if(k!==void 0){m.dispatch("sectionState",[f])}}else if("setmarker"===g){var i=a(o.SECTIONLI+".current"),j=i.find(o.SECTIONACTIONMENU+" a[data-action=removemarker]");i.removeClass("current");D(j,"i/marker","highlight","core","setmarker");b.addClass("current");D(c,"i/marked","highlightoff","core","removemarker");m.dispatch("legacySectionAction",g,f)}else if("removemarker"===g){b.removeClass("current");D(c,"i/marker","highlight","core","setmarker");m.dispatch("legacySectionAction",g,f)}},F=function(b){a(""+b+"
").find(o.ACTIVITYLI).each(function(){var c=a(this).attr("id");a(o.ACTIVITYLI+"#"+c).replaceWith(b);w(c)})},G=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0,j=s(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),l=t(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);u(c,j);v(l);c.find(o.SECTIONACTIONMENU).find(o.TOGGLE).focus();var i=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(i);if(!i.isDefaultPrevented()){E(c,f,d,g,e)}}).fail(function(b){u(c,j);v(l);var f=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})};g.use("moodle-course-coursebase",function(){M.course.coursebase.register_module({set_visibility_resource_ui:function set_visibility_resource_ui(b){var c=a(b.element.getDOMNode()),d=p(c);if(d){var e=c.find("."+n.EDITINGMOVE).attr("data-sectionreturn");A(c,d,e)}},updateMovedCmState:function updateMovedCmState(a){var b=m.state,c=b.cm.get(a.cmid);if(c!==void 0){m.dispatch("sectionState",[c.sectionid])}m.dispatch("cmState",[a.cmid])},updateMovedSectionState:function updateMovedSectionState(){m.dispatch("courseState")}})});m.addMutations({legacyActivityAction:function legacyActivityAction(a,b,c,d){var e=a.state,f=e.cm.get(c);if(f===void 0){return}var g=e.section.get(f.sectionid);if(g===void 0){return}a.setReadOnly(!1);switch(b){case"delete":g.cmlist=g.cmlist.reduce(function(a,b){if(b!=c){a.push(b)}return a},[]);e.cm.delete(c);break;case"hide":case"show":f.visible="show"===b?!0:!1;break;case"duplicate":m.dispatch("cmState",d);break;}a.setReadOnly(!0)},legacySectionAction:function legacySectionAction(a,b,c){var d=a.state,e=d.section.get(c);if(e===void 0){return}a.setReadOnly(!1);switch(b){case"setmarker":d.section.forEach(function(a){if(a.id!=c){a.current=!1}});e.current=!0;break;case"removemarker":e.current=!1;break;}a.setReadOnly(!0)}});return{initCoursePage:function initCoursePage(b){a("body").on("click keypress",o.ACTIVITYLI+" "+o.ACTIVITYACTION+"[data-action]",function(b){if("keypress"===b.type&&13!==b.keyCode){return}var c=a(this),d=c.closest(o.ACTIVITYLI),e=c.attr("data-action"),f=p(d);switch(e){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return;}if(!f){return}b.preventDefault();if("delete"===e){B(d,function(){z(d,f,c)})}else{z(d,f,c)}});a("body").on("click keypress",o.SECTIONLI+" "+o.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"===c.type&&13!==c.keyCode){return}var d=a(this),e=d.closest(o.SECTIONLI),f=d.closest(o.SECTIONACTIONMENU).attr("data-sectionid");c.preventDefault();if(d.attr("data-confirm")){C(d.attr("data-confirm"),function(){G(e,f,d,b)})}else{G(e,f,d,b)}});a("body").on("updated","".concat(o.SECTIONLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){var b=m.state,c=b.section.get(a.ajaxreturn.itemid);if(c!==void 0){m.dispatch("sectionState",[a.ajaxreturn.itemid])}}});a("body").on("updated","".concat(o.ACTIVITYLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){m.dispatch("cmState",[a.ajaxreturn.itemid])}});e.get_string("numberweeks").done(function(b){var c=a(o.ADDSECTIONS),d=c.attr("data-add-sections"),e=c.attr("data-new-sections"),f=a("
");f.find("label").html(b);h.create({title:d,type:h.types.SAVE_CANCEL,body:f.html()},c).done(function(b){var e=a(b.getBody()).find("#add_section_numsections"),f=function(){if(""+parseInt(e.val())===e.val()&&1<=parseInt(e.val())){document.location=c.attr("href")+"&numsections="+parseInt(e.val())}};b.setSaveButtonText(d);b.getRoot().on(i.shown,function(){e.focus().select().on("keydown",function(a){if(a.keyCode===j.enter){f()}})});b.getRoot().on(i.save,function(a){a.preventDefault();f()})})})},replaceSectionActionItem:function replaceSectionActionItem(a,b,c,d,e,f){k.debug("replaceSectionActionItem() is deprecated and will be removed.");var g=a.find(o.SECTIONACTIONMENU+" "+b);D(g,c,d,e,f)},refreshModule:A}});
+define ("core_course/actions",["jquery","core/ajax","core/templates","core/notification","core/str","core/url","core/yui","core/modal_factory","core/modal_events","core/key_codes","core/log","core_courseformat/courseeditor"],function(a,b,c,d,e,f,g,h,i,j,k,l){var m=l.getCurrentCourseEditor(),n={EDITINPROGRESS:"editinprogress",SECTIONDRAGGABLE:"sectiondraggable",EDITINGMOVE:"editing_move"},o={ACTIVITYLI:"li.activity",ACTIONAREA:".actions",ACTIVITYACTION:"a.cm-edit-action",MENU:".moodle-actionmenu[data-enhance=moodle-core-actionmenu]",TOGGLE:".toggle-display,.dropdown-toggle",SECTIONLI:"li.section",SECTIONACTIONMENU:".section_action_menu",ADDSECTIONS:"#changenumsections [data-add-sections]"};g.use("moodle-course-coursebase",function(){var a=M.course.format.get_section_selector();if(a){o.SECTIONLI=a}});var p=function(a){var b=a.get(0);if(b.dataset.id){return b.dataset.id}var c;g.use("moodle-course-util",function(a){c=a.Moodle.core_course.util.cm.getId(a.Node(b))});return c},q=function(a){var b;g.use("moodle-course-util",function(c){b=c.Moodle.core_course.util.cm.getName(c.Node(a.get(0)))});var c=m.state,d=p(a);if(!b&&c&&d){var e;b=null===(e=c.cm.get(d))||void 0===e?void 0:e.name}return b},r=function(a){a.addClass(n.EDITINPROGRESS);var b=a.find(o.ACTIONAREA).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){m.dispatch("cmLock",[a.data("id")],!0)}return c}return null},s=function(a){a.addClass(n.EDITINPROGRESS);var b=a.find(o.SECTIONACTIONMENU).get(0);if(b){var c=M.util.add_spinner(g,g.Node(b));c.show();if(a.data("id")!==void 0){m.dispatch("sectionLock",[a.data("id")],!0)}return c}return null},t=function(a){var b=M.util.add_lightbox(g,g.Node(a.get(0)));b.show();return b},u=function(a,b,c){window.setTimeout(function(){a.removeClass(n.EDITINPROGRESS);if(b){b.hide()}if(a.data("id")!==void 0){var c="section"===a.data("for")?"sectionLock":"cmLock";m.dispatch(c,[a.data("id")],!1)}},c)},v=function(a,b){if(a){window.setTimeout(function(){a.hide()},b)}},w=function(a){g.use("moodle-course-coursebase",function(){M.course.coursebase.invoke_function("setup_for_resource","#"+a)});if(M.core.actionmenu&&M.core.actionmenu.newDOMNode){M.core.actionmenu.newDOMNode(g.one("#"+a))}},x=function(b,c){var d=a("#"+b),e="[data-action="+c+"]";if("groupsseparate"===c||"groupsvisible"===c||"groupsnone"===c){e="[data-action=groupsseparate],[data-action=groupsvisible],[data-action=groupsnone]"}if(d.find(e).is(":visible")){d.find(e).focus()}else{d.find(o.MENU).find(o.TOGGLE).focus()}},y=function(b){var c=a("a:visible"),d=!1,e=null;c.each(function(){if(a.contains(b[0],this)){d=!0}else if(d){e=this;return!1}});return e},z=function(c,e,f){var g=f.attr("data-action"),h=r(c),i=b.call([{methodname:"core_course_edit_module",args:{id:e,action:g,sectionreturn:f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0}}],!0),j;if("duplicate"===g){j=t(f.closest(o.SECTIONLI))}a.when.apply(a,i).done(function(b){var d=y(c);c.replaceWith(b);var f=[];a(""+b+"
").find(o.ACTIVITYLI).each(function(b){w(a(this).attr("id"));if(0===b){x(a(this).attr("id"),g);d=null}f.push(p(a(this)))});if(d){d.focus()}u(c,h,400);v(j,400);c.trigger(a.Event("coursemoduleedited",{ajaxreturn:b,action:g}));m.dispatch("legacyActivityAction",g,e,f)}).fail(function(b){u(c,h);v(j);var f=a.Event("coursemoduleeditfailed",{exception:b,action:g});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})},A=function(c,d,e){var f=a(c),g=r(f),h=b.call([{methodname:"core_course_get_module",args:{id:d,sectionreturn:e}}],!0);return new Promise(function(b,c){a.when.apply(a,h).done(function(a){u(f,g,400);F(a);b(a)}).fail(function(){u(f,g);c()})})},B=function(a,b){var c=a.attr("class").match(/modtype_([^\s]*)/)[1],f=q(a);e.get_string("pluginname",c).done(function(a){e.get_strings([{key:"confirm"},{key:null===f?"deletechecktype":"deletechecktypename",param:{type:a,name:f}},{key:"yes"},{key:"no"}]).done(function(a){d.confirm(a[0],a[1],a[2],a[3],b)})})},C=function(a,b){e.get_strings([{key:"confirm"},{key:"yes"},{key:"no"}]).done(function(c){d.confirm(c[0],a,c[1],c[2],b)})},D=function(a,b,f,g,h){return e.get_strings([{key:f,component:g}]).then(function(d){a.find("span.menu-action-text").html(d[0]);return c.renderPix(b,"core")}).then(function(b){a.find(".icon").replaceWith(b);a.attr("data-action",h)}).catch(d.exception)},E=function(b,c,d,e,f){var g=c.attr("data-action");if("hide"===g||"show"===g){if("hide"===g){b.addClass("hidden");D(c,"i/show","showfromothers","format_"+e,"show")}else{b.removeClass("hidden");D(c,"i/hide","hidefromothers","format_"+e,"hide")}if(d.modules!==void 0){for(var h in d.modules){F(d.modules[h])}}if(d.section_availability!==void 0){b.find(".section_availability").first().replaceWith(d.section_availability)}var k=m.state.section.get(f);if(k!==void 0){m.dispatch("sectionState",[f])}}else if("setmarker"===g){var i=a(o.SECTIONLI+".current"),j=i.find(o.SECTIONACTIONMENU+" a[data-action=removemarker]");i.removeClass("current");D(j,"i/marker","highlight","core","setmarker");b.addClass("current");D(c,"i/marked","highlightoff","core","removemarker");m.dispatch("legacySectionAction",g,f)}else if("removemarker"===g){b.removeClass("current");D(c,"i/marker","highlight","core","setmarker");m.dispatch("legacySectionAction",g,f)}},F=function(b){a(""+b+"
").find(o.ACTIVITYLI).each(function(){var c=a(this).attr("id");a(o.ACTIVITYLI+"#"+c).replaceWith(b);w(c)})},G=function(c,e,f,g){var h=f.attr("data-action"),i=f.attr("data-sectionreturn")?f.attr("data-sectionreturn"):0,j=s(c),k=b.call([{methodname:"core_course_edit_section",args:{id:e,action:h,sectionreturn:i}}],!0),l=t(c);a.when.apply(a,k).done(function(b){var d=a.parseJSON(b);u(c,j);v(l);c.find(o.SECTIONACTIONMENU).find(o.TOGGLE).focus();var i=a.Event("coursesectionedited",{ajaxreturn:d,action:h});c.trigger(i);if(!i.isDefaultPrevented()){E(c,f,d,g,e)}}).fail(function(b){u(c,j);v(l);var f=a.Event("coursesectioneditfailed",{exception:b,action:h});c.trigger(f);if(!f.isDefaultPrevented()){d.exception(b)}})};g.use("moodle-course-coursebase",function(){M.course.coursebase.register_module({set_visibility_resource_ui:function set_visibility_resource_ui(b){var c=a(b.element.getDOMNode()),d=p(c);if(d){var e=c.find("."+n.EDITINGMOVE).attr("data-sectionreturn");A(c,d,e)}},updateMovedCmState:function updateMovedCmState(a){var b=m.state,c=b.cm.get(a.cmid);if(c!==void 0){m.dispatch("sectionState",[c.sectionid])}m.dispatch("cmState",[a.cmid])},updateMovedSectionState:function updateMovedSectionState(){m.dispatch("courseState")}})});m.addMutations({legacyActivityAction:function legacyActivityAction(a,b,c,d){var e=a.state,f=e.cm.get(c);if(f===void 0){return}var g=e.section.get(f.sectionid);if(g===void 0){return}a.setReadOnly(!1);f.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);f.locked=!1;switch(b){case"delete":g.cmlist=g.cmlist.reduce(function(a,b){if(b!=c){a.push(b)}return a},[]);e.cm.delete(c);break;case"hide":case"show":f.visible="show"===b?!0:!1;break;case"duplicate":m.dispatch("cmState",d);break;}a.setReadOnly(!0)},legacySectionAction:function legacySectionAction(a,b,c){var d=a.state,e=d.section.get(c);if(e===void 0){return}a.setReadOnly(!1);e.locked=!0;a.setReadOnly(!0);a.setReadOnly(!1);e.locked=!1;switch(b){case"setmarker":d.section.forEach(function(a){if(a.id!=c){a.current=!1}});e.current=!0;break;case"removemarker":e.current=!1;break;}a.setReadOnly(!0)}});return{initCoursePage:function initCoursePage(b){a("body").on("click keypress",o.ACTIVITYLI+" "+o.ACTIVITYACTION+"[data-action]",function(b){if("keypress"===b.type&&13!==b.keyCode){return}var c=a(this),d=c.closest(o.ACTIVITYLI),e=c.attr("data-action"),f=p(d);switch(e){case"moveleft":case"moveright":case"delete":case"duplicate":case"hide":case"stealth":case"show":case"groupsseparate":case"groupsvisible":case"groupsnone":break;default:return;}if(!f){return}b.preventDefault();if("delete"===e){B(d,function(){z(d,f,c)})}else{z(d,f,c)}});a("body").on("click keypress",o.SECTIONLI+" "+o.SECTIONACTIONMENU+"[data-sectionid] a[data-action]",function(c){if("keypress"===c.type&&13!==c.keyCode){return}var d=a(this),e=d.closest(o.SECTIONLI),f=d.closest(o.SECTIONACTIONMENU).attr("data-sectionid");c.preventDefault();if(d.attr("data-confirm")){C(d.attr("data-confirm"),function(){G(e,f,d,b)})}else{G(e,f,d,b)}});a("body").on("updated","".concat(o.SECTIONLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){var b=m.state,c=b.section.get(a.ajaxreturn.itemid);if(c!==void 0){m.dispatch("sectionState",[a.ajaxreturn.itemid])}}});a("body").on("updated","".concat(o.ACTIVITYLI," [data-inplaceeditable]"),function(a){if(a.ajaxreturn&&a.ajaxreturn.itemid){m.dispatch("cmState",[a.ajaxreturn.itemid])}});e.get_string("numberweeks").done(function(b){var c=a(o.ADDSECTIONS),d=c.attr("data-add-sections"),e=c.attr("data-new-sections"),f=a("
");f.find("label").html(b);h.create({title:d,type:h.types.SAVE_CANCEL,body:f.html()},c).done(function(b){var e=a(b.getBody()).find("#add_section_numsections"),f=function(){if(""+parseInt(e.val())===e.val()&&1<=parseInt(e.val())){document.location=c.attr("href")+"&numsections="+parseInt(e.val())}};b.setSaveButtonText(d);b.getRoot().on(i.shown,function(){e.focus().select().on("keydown",function(a){if(a.keyCode===j.enter){f()}})});b.getRoot().on(i.save,function(a){a.preventDefault();f()})})})},replaceSectionActionItem:function replaceSectionActionItem(a,b,c,d,e,f){k.debug("replaceSectionActionItem() is deprecated and will be removed.");var g=a.find(o.SECTIONACTIONMENU+" "+b);D(g,c,d,e,f)},refreshModule:A}});
//# sourceMappingURL=actions.min.js.map
diff --git a/course/amd/build/actions.min.js.map b/course/amd/build/actions.min.js.map
index 66080cb281f..ce14cf77970 100644
--- a/course/amd/build/actions.min.js.map
+++ b/course/amd/build/actions.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/actions.js"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalFactory","ModalEvents","KeyCodes","log","editor","courseeditor","getCurrentCourseEditor","CSS","EDITINPROGRESS","SECTIONDRAGGABLE","EDITINGMOVE","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","ADDSECTIONS","use","courseformatselector","M","course","format","get_section_selector","getModuleId","element","item","get","dataset","id","Moodle","core_course","util","cm","getId","Node","getModuleName","name","getName","state","cmid","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","removeSpinner","delay","window","setTimeout","removeClass","hide","removeLightbox","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","focusActionItem","elementId","action","mainelement","selector","is","focus","findNextFocusable","mainElement","tabables","isInside","foundElement","each","contains","editModule","moduleElement","target","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","data","elementToFocus","replaceWith","affectedids","index","push","trigger","Event","ajaxreturn","dispatch","fail","ex","e","exception","isDefaultPrevented","refreshModule","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","get_string","pluginname","get_strings","key","param","type","s","confirm","confirmEditSection","message","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","component","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","modules","i","section_availability","first","section","oldmarker","oldActionItem","activityHTML","editSection","dataencoded","parseJSON","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","cmlist","reduce","current","delete","visible","legacySectionAction","forEach","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","itemid","strNumberSections","modalTitle","newSections","modalBody","create","title","types","SAVE_CANCEL","body","modal","numSections","getBody","addSections","parseInt","val","document","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug"],"mappings":"AAuBAA,OAAM,uBAAC,CAAC,QAAD,CAAW,WAAX,CAAwB,gBAAxB,CAA0C,mBAA1C,CAA+D,UAA/D,CAA2E,UAA3E,CAAuF,UAAvF,CACC,oBADD,CACuB,mBADvB,CAC4C,gBAD5C,CAC8D,UAD9D,CAC0E,gCAD1E,CAAD,CAEF,SAASC,CAAT,CAAYC,CAAZ,CAAkBC,CAAlB,CAA6BC,CAA7B,CAA2CC,CAA3C,CAAgDC,CAAhD,CAAqDC,CAArD,CAAwDC,CAAxD,CAAsEC,CAAtE,CAAmFC,CAAnF,CAA6FC,CAA7F,CAAkGC,CAAlG,CAA0G,IAEhGC,CAAAA,CAAY,CAAGD,CAAM,CAACE,sBAAP,EAFiF,CAIlGC,CAAG,CAAG,CACNC,cAAc,CAAE,gBADV,CAENC,gBAAgB,CAAE,kBAFZ,CAGNC,WAAW,CAAE,cAHP,CAJ4F,CASlGC,CAAQ,CAAG,CACXC,UAAU,CAAE,aADD,CAEXC,UAAU,CAAE,UAFD,CAGXC,cAAc,CAAE,kBAHL,CAIXC,IAAI,CAAE,yDAJK,CAKXC,MAAM,CAAE,kCALG,CAMXC,SAAS,CAAE,YANA,CAOXC,iBAAiB,CAAE,sBAPR,CAQXC,WAAW,CAAE,wCARF,CATuF,CAoBtGpB,CAAC,CAACqB,GAAF,CAAM,0BAAN,CAAkC,UAAW,CACzC,GAAIC,CAAAA,CAAoB,CAAGC,CAAC,CAACC,MAAF,CAASC,MAAT,CAAgBC,oBAAhB,EAA3B,CACA,GAAIJ,CAAJ,CAA0B,CACtBV,CAAQ,CAACM,SAAT,CAAqBI,CACxB,CACJ,CALD,EApBsG,GAiClGK,CAAAA,CAAW,CAAG,SAASC,CAAT,CAAkB,CAEhC,GAAMC,CAAAA,CAAI,CAAGD,CAAO,CAACE,GAAR,CAAY,CAAZ,CAAb,CACA,GAAID,CAAI,CAACE,OAAL,CAAaC,EAAjB,CAAqB,CACjB,MAAOH,CAAAA,CAAI,CAACE,OAAL,CAAaC,EACvB,CAED,GAAIA,CAAAA,CAAJ,CACAhC,CAAC,CAACqB,GAAF,CAAM,oBAAN,CAA4B,SAASrB,CAAT,CAAY,CACpCgC,CAAE,CAAGhC,CAAC,CAACiC,MAAF,CAASC,WAAT,CAAqBC,IAArB,CAA0BC,EAA1B,CAA6BC,KAA7B,CAAmCrC,CAAC,CAACsC,IAAF,CAAOT,CAAP,CAAnC,CACR,CAFD,EAGA,MAAOG,CAAAA,CACV,CA7CqG,CAqDlGO,CAAa,CAAG,SAASX,CAAT,CAAkB,CAClC,GAAIY,CAAAA,CAAJ,CACAxC,CAAC,CAACqB,GAAF,CAAM,oBAAN,CAA4B,SAASrB,CAAT,CAAY,CACpCwC,CAAI,CAAGxC,CAAC,CAACiC,MAAF,CAASC,WAAT,CAAqBC,IAArB,CAA0BC,EAA1B,CAA6BK,OAA7B,CAAqCzC,CAAC,CAACsC,IAAF,CAAOV,CAAO,CAACE,GAAR,CAAY,CAAZ,CAAP,CAArC,CACV,CAFD,EAFkC,GAM5BY,CAAAA,CAAK,CAAGpC,CAAY,CAACoC,KANO,CAO5BC,CAAI,CAAGhB,CAAW,CAACC,CAAD,CAPU,CAQlC,GAAI,CAACY,CAAD,EAASE,CAAT,EAAkBC,CAAtB,CAA4B,OACxBH,CAAI,WAAGE,CAAK,CAACN,EAAN,CAASN,GAAT,CAAaa,CAAb,CAAH,qBAAG,EAAoBH,IAC9B,CACD,MAAOA,CAAAA,CACV,CAjEqG,CAyElGI,CAAkB,CAAG,SAASC,CAAT,CAAmB,CACxCA,CAAQ,CAACC,QAAT,CAAkBtC,CAAG,CAACC,cAAtB,EACA,GAAIsC,CAAAA,CAAU,CAAGF,CAAQ,CAACG,IAAT,CAAcpC,CAAQ,CAACE,UAAvB,EAAmCgB,GAAnC,CAAuC,CAAvC,CAAjB,CACA,GAAIiB,CAAJ,CAAgB,CACZ,GAAIE,CAAAA,CAAO,CAAG1B,CAAC,CAACY,IAAF,CAAOe,WAAP,CAAmBlD,CAAnB,CAAsBA,CAAC,CAACsC,IAAF,CAAOS,CAAP,CAAtB,CAAd,CACAE,CAAO,CAACE,IAAR,GACA,MAAOF,CAAAA,CACV,CACD,MAAO,KACV,CAlFqG,CA0FlGG,CAAiB,CAAG,SAASC,CAAT,CAAyB,CAC7CA,CAAc,CAACP,QAAf,CAAwBtC,CAAG,CAACC,cAA5B,EACA,GAAIsC,CAAAA,CAAU,CAAGM,CAAc,CAACL,IAAf,CAAoBpC,CAAQ,CAACO,iBAA7B,EAAgDW,GAAhD,CAAoD,CAApD,CAAjB,CACA,GAAIiB,CAAJ,CAAgB,CACZ,GAAIE,CAAAA,CAAO,CAAG1B,CAAC,CAACY,IAAF,CAAOe,WAAP,CAAmBlD,CAAnB,CAAsBA,CAAC,CAACsC,IAAF,CAAOS,CAAP,CAAtB,CAAd,CACAE,CAAO,CAACE,IAAR,GACA,MAAOF,CAAAA,CACV,CACD,MAAO,KACV,CAnGqG,CA2GlGK,CAAkB,CAAG,SAASD,CAAT,CAAyB,CAC9C,GAAIE,CAAAA,CAAQ,CAAGhC,CAAC,CAACY,IAAF,CAAOqB,YAAP,CAAoBxD,CAApB,CAAuBA,CAAC,CAACsC,IAAF,CAAOe,CAAc,CAACvB,GAAf,CAAmB,CAAnB,CAAP,CAAvB,CAAf,CACAyB,CAAQ,CAACJ,IAAT,GACA,MAAOI,CAAAA,CACV,CA/GqG,CAwHlGE,CAAa,CAAG,SAAS7B,CAAT,CAAkBqB,CAAlB,CAA2BS,CAA3B,CAAkC,CAClDC,MAAM,CAACC,UAAP,CAAkB,UAAW,CACzBhC,CAAO,CAACiC,WAAR,CAAoBrD,CAAG,CAACC,cAAxB,EACA,GAAIwC,CAAJ,CAAa,CACTA,CAAO,CAACa,IAAR,EACH,CACJ,CALD,CAKGJ,CALH,CAMH,CA/HqG,CAuIlGK,CAAc,CAAG,SAASR,CAAT,CAAmBG,CAAnB,CAA0B,CAC3C,GAAIH,CAAJ,CAAc,CACVI,MAAM,CAACC,UAAP,CAAkB,UAAW,CACzBL,CAAQ,CAACO,IAAT,EACH,CAFD,CAEGJ,CAFH,CAGH,CACJ,CA7IqG,CAoJlGM,CAAc,CAAG,SAASC,CAAT,CAAoB,CAErCjE,CAAC,CAACqB,GAAF,CAAM,0BAAN,CAAkC,UAAW,CACzCE,CAAC,CAACC,MAAF,CAAS0C,UAAT,CAAoBC,eAApB,CAAoC,oBAApC,CAA0D,IAAMF,CAAhE,CACH,CAFD,EAGA,GAAI1C,CAAC,CAAC6C,IAAF,CAAOC,UAAP,EAAqB9C,CAAC,CAAC6C,IAAF,CAAOC,UAAP,CAAkBC,UAA3C,CAAuD,CACnD/C,CAAC,CAAC6C,IAAF,CAAOC,UAAP,CAAkBC,UAAlB,CAA6BtE,CAAC,CAACuE,GAAF,CAAM,IAAMN,CAAZ,CAA7B,CACH,CACJ,CA5JqG,CAoKlGO,CAAe,CAAG,SAASC,CAAT,CAAoBC,CAApB,CAA4B,IAC1CC,CAAAA,CAAW,CAAGjF,CAAC,CAAC,IAAM+E,CAAP,CAD2B,CAE1CG,CAAQ,CAAG,gBAAkBF,CAAlB,CAA2B,GAFI,CAG9C,GAAe,gBAAX,GAAAA,CAAM,EAAoC,eAAX,GAAAA,CAA/B,EAAwE,YAAX,GAAAA,CAAjE,CAA0F,CAEtFE,CAAQ,CAAG,mFACd,CACD,GAAID,CAAW,CAAC3B,IAAZ,CAAiB4B,CAAjB,EAA2BC,EAA3B,CAA8B,UAA9B,CAAJ,CAA+C,CAC3CF,CAAW,CAAC3B,IAAZ,CAAiB4B,CAAjB,EAA2BE,KAA3B,EACH,CAFD,IAEO,CAEHH,CAAW,CAAC3B,IAAZ,CAAiBpC,CAAQ,CAACI,IAA1B,EAAgCgC,IAAhC,CAAqCpC,CAAQ,CAACK,MAA9C,EAAsD6D,KAAtD,EACH,CACJ,CAjLqG,CAyLlGC,CAAiB,CAAG,SAASC,CAAT,CAAsB,IACtCC,CAAAA,CAAQ,CAAGvF,CAAC,CAAC,WAAD,CAD0B,CAEtCwF,CAAQ,GAF8B,CAGtCC,CAAY,CAAG,IAHuB,CAI1CF,CAAQ,CAACG,IAAT,CAAc,UAAW,CACrB,GAAI1F,CAAC,CAAC2F,QAAF,CAAWL,CAAW,CAAC,CAAD,CAAtB,CAA2B,IAA3B,CAAJ,CAAsC,CAClCE,CAAQ,GACX,CAFD,IAEO,IAAIA,CAAJ,CAAc,CACjBC,CAAY,CAAG,IAAf,CACA,QACH,CACJ,CAPD,EAQA,MAAOA,CAAAA,CACV,CAtMqG,CA+MlGG,CAAU,CAAG,SAASC,CAAT,CAAwB5C,CAAxB,CAA8B6C,CAA9B,CAAsC,IAC/Cd,CAAAA,CAAM,CAAGc,CAAM,CAACC,IAAP,CAAY,aAAZ,CADsC,CAE/CxC,CAAO,CAAGL,CAAkB,CAAC2C,CAAD,CAFmB,CAG/CG,CAAQ,CAAG/F,CAAI,CAACgG,IAAL,CAAU,CAAC,CACtBC,UAAU,CAAE,yBADU,CAEtBC,IAAI,CAAE,CAAC7D,EAAE,CAAEW,CAAL,CACF+B,MAAM,CAAEA,CADN,CAEFoB,aAAa,CAAEN,CAAM,CAACC,IAAP,CAAY,oBAAZ,EAAoCD,CAAM,CAACC,IAAP,CAAY,oBAAZ,CAApC,CAAwE,CAFrF,CAFgB,CAAD,CAAV,IAHoC,CAW/ClC,CAX+C,CAYnD,GAAe,WAAX,GAAAmB,CAAJ,CAA4B,CACxBnB,CAAQ,CAAGD,CAAkB,CAACkC,CAAM,CAACO,OAAP,CAAenF,CAAQ,CAACM,SAAxB,CAAD,CAChC,CACDxB,CAAC,CAACsG,IAAF,CAAOC,KAAP,CAAavG,CAAb,CAAgBgG,CAAhB,EACKQ,IADL,CACU,SAASC,CAAT,CAAe,CACjB,GAAIC,CAAAA,CAAc,CAAGrB,CAAiB,CAACQ,CAAD,CAAtC,CACAA,CAAa,CAACc,WAAd,CAA0BF,CAA1B,EACA,GAAIG,CAAAA,CAAW,CAAG,EAAlB,CAEA5G,CAAC,CAAC,QAAUyG,CAAV,CAAiB,QAAlB,CAAD,CAA6BnD,IAA7B,CAAkCpC,CAAQ,CAACC,UAA3C,EAAuDuE,IAAvD,CAA4D,SAASmB,CAAT,CAAgB,CACxEvC,CAAc,CAACtE,CAAC,CAAC,IAAD,CAAD,CAAQ+F,IAAR,CAAa,IAAb,CAAD,CAAd,CACA,GAAc,CAAV,GAAAc,CAAJ,CAAiB,CACb/B,CAAe,CAAC9E,CAAC,CAAC,IAAD,CAAD,CAAQ+F,IAAR,CAAa,IAAb,CAAD,CAAqBf,CAArB,CAAf,CACA0B,CAAc,CAAG,IACpB,CAEDE,CAAW,CAACE,IAAZ,CAAiB7E,CAAW,CAACjC,CAAC,CAAC,IAAD,CAAF,CAA5B,CACH,CARD,EAUA,GAAI0G,CAAJ,CAAoB,CAChBA,CAAc,CAACtB,KAAf,EACH,CAEDrB,CAAa,CAAC8B,CAAD,CAAgBtC,CAAhB,CAAyB,GAAzB,CAAb,CACAc,CAAc,CAACR,CAAD,CAAW,GAAX,CAAd,CAEAgC,CAAa,CAACkB,OAAd,CAAsB/G,CAAC,CAACgH,KAAF,CAAQ,oBAAR,CAA8B,CAACC,UAAU,CAAER,CAAb,CAAmBzB,MAAM,CAAEA,CAA3B,CAA9B,CAAtB,EAGApE,CAAY,CAACsG,QAAb,CAAsB,sBAAtB,CAA8ClC,CAA9C,CAAsD/B,CAAtD,CAA4D2D,CAA5D,CAEH,CA5BL,EA4BOO,IA5BP,CA4BY,SAASC,CAAT,CAAa,CAEjBrD,CAAa,CAAC8B,CAAD,CAAgBtC,CAAhB,CAAb,CACAc,CAAc,CAACR,CAAD,CAAd,CAEA,GAAIwD,CAAAA,CAAC,CAAGrH,CAAC,CAACgH,KAAF,CAAQ,wBAAR,CAAkC,CAACM,SAAS,CAAEF,CAAZ,CAAgBpC,MAAM,CAAEA,CAAxB,CAAlC,CAAR,CACAa,CAAa,CAACkB,OAAd,CAAsBM,CAAtB,EACA,GAAI,CAACA,CAAC,CAACE,kBAAF,EAAL,CAA6B,CACzBpH,CAAY,CAACmH,SAAb,CAAuBF,CAAvB,CACH,CACJ,CAtCL,CAuCH,CArQqG,CAiRlGI,CAAa,CAAG,SAAStF,CAAT,CAAkBe,CAAlB,CAAwBmD,CAAxB,CAAuC,IACjDqB,CAAAA,CAAe,CAAGzH,CAAC,CAACkC,CAAD,CAD8B,CAEnDqB,CAAO,CAAGL,CAAkB,CAACuE,CAAD,CAFuB,CAGnDzB,CAAQ,CAAG/F,CAAI,CAACgG,IAAL,CAAU,CAAC,CACtBC,UAAU,CAAE,wBADU,CAEtBC,IAAI,CAAE,CAAC7D,EAAE,CAAEW,CAAL,CAAWmD,aAAa,CAAEA,CAA1B,CAFgB,CAAD,CAAV,IAHwC,CAQvD,MAAO,IAAIsB,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAUC,CAAV,CAAqB,CACpC5H,CAAC,CAACsG,IAAF,CAAOC,KAAP,CAAavG,CAAb,CAAgBgG,CAAhB,EACKQ,IADL,CACU,SAASC,CAAT,CAAe,CACjB1C,CAAa,CAAC0D,CAAD,CAAkBlE,CAAlB,CAA2B,GAA3B,CAAb,CACAsE,CAAuB,CAACpB,CAAD,CAAvB,CACAkB,CAAO,CAAClB,CAAD,CACV,CALL,EAKOU,IALP,CAKY,UAAW,CACfpD,CAAa,CAAC0D,CAAD,CAAkBlE,CAAlB,CAAb,CACAqE,CAAM,EACT,CARL,CASH,CAVM,CAWV,CApSqG,CA4SlGE,CAAmB,CAAG,SAAS7C,CAAT,CAAsB8C,CAAtB,CAAiC,IACnDC,CAAAA,CAAW,CAAG/C,CAAW,CAACc,IAAZ,CAAiB,OAAjB,EAA0BkC,KAA1B,CAAgC,kBAAhC,EAAoD,CAApD,CADqC,CAEnDC,CAAU,CAAGrF,CAAa,CAACoC,CAAD,CAFyB,CAIvD7E,CAAG,CAAC+H,UAAJ,CAAe,YAAf,CAA6BH,CAA7B,EAA0CxB,IAA1C,CAA+C,SAAS4B,CAAT,CAAqB,CAKhEhI,CAAG,CAACiI,WAAJ,CAAgB,CACZ,CAACC,GAAG,CAAE,SAAN,CADY,CAEZ,CAACA,GAAG,CAAiB,IAAf,GAAAJ,CAAU,CAAY,iBAAZ,CAAgC,qBAAhD,CAAuEK,KAAK,CAN/D,CACbC,IAAI,CAAEJ,CADO,CAEbtF,IAAI,CAAEoF,CAFO,CAMb,CAFY,CAGZ,CAACI,GAAG,CAAE,KAAN,CAHY,CAIZ,CAACA,GAAG,CAAE,IAAN,CAJY,CAAhB,EAKG9B,IALH,CAKQ,SAASiC,CAAT,CAAY,CACZtI,CAAY,CAACuI,OAAb,CAAqBD,CAAC,CAAC,CAAD,CAAtB,CAA2BA,CAAC,CAAC,CAAD,CAA5B,CAAiCA,CAAC,CAAC,CAAD,CAAlC,CAAuCA,CAAC,CAAC,CAAD,CAAxC,CAA6CV,CAA7C,CACH,CAPL,CASH,CAdD,CAeH,CA/TqG,CAuUlGY,CAAkB,CAAG,SAASC,CAAT,CAAkBb,CAAlB,CAA6B,CAClD3H,CAAG,CAACiI,WAAJ,CAAgB,CACZ,CAACC,GAAG,CAAE,SAAN,CADY,CAEZ,CAACA,GAAG,CAAE,KAAN,CAFY,CAGZ,CAACA,GAAG,CAAE,IAAN,CAHY,CAAhB,EAIG9B,IAJH,CAIQ,SAASiC,CAAT,CAAY,CACZtI,CAAY,CAACuI,OAAb,CAAqBD,CAAC,CAAC,CAAD,CAAtB,CAA2BG,CAA3B,CAAoCH,CAAC,CAAC,CAAD,CAArC,CAA0CA,CAAC,CAAC,CAAD,CAA3C,CAAgDV,CAAhD,CACH,CANL,CAQH,CAhVqG,CA4VlGc,CAAiB,CAAG,SAASC,CAAT,CAAqBC,CAArB,CAA4BC,CAA5B,CACWC,CADX,CAC4BC,CAD5B,CACuC,CAK3D,MAAO9I,CAAAA,CAAG,CAACiI,WAAJ,CAHc,CAAC,CAACC,GAAG,CAAEU,CAAN,CAAkBG,SAAS,CAAEF,CAA7B,CAAD,CAGd,EAAgCG,IAAhC,CAAqC,SAASC,CAAT,CAAkB,CAC1DP,CAAU,CAACxF,IAAX,CAAgB,uBAAhB,EAAyCgG,IAAzC,CAA8CD,CAAO,CAAC,CAAD,CAArD,EAEA,MAAOnJ,CAAAA,CAAS,CAACqJ,SAAV,CAAoBR,CAApB,CAA2B,MAA3B,CACV,CAJM,EAIJK,IAJI,CAIC,SAASI,CAAT,CAAkB,CACtBV,CAAU,CAACxF,IAAX,CAAgB,OAAhB,EAAyBqD,WAAzB,CAAqC6C,CAArC,EACAV,CAAU,CAAC/C,IAAX,CAAgB,aAAhB,CAA+BmD,CAA/B,CAEH,CARM,EAQJO,KARI,CAQEtJ,CAAY,CAACmH,SARf,CASV,CA3WqG,CAgYlGoC,CAAyB,CAAG,SAASC,CAAT,CAAyBC,CAAzB,CAAqCnD,CAArC,CAA2CoD,CAA3C,CAAyDC,CAAzD,CAAoE,CAChG,GAAI9E,CAAAA,CAAM,CAAG4E,CAAU,CAAC7D,IAAX,CAAgB,aAAhB,CAAb,CACA,GAAe,MAAX,GAAAf,CAAM,EAA0B,MAAX,GAAAA,CAAzB,CAA4C,CACxC,GAAe,MAAX,GAAAA,CAAJ,CAAuB,CACnB2E,CAAc,CAACvG,QAAf,CAAwB,QAAxB,EACAyF,CAAiB,CAACe,CAAD,CAAa,QAAb,CACb,gBADa,CACK,UAAYC,CADjB,CAC+B,MAD/B,CAEpB,CAJD,IAIO,CACHF,CAAc,CAACxF,WAAf,CAA2B,QAA3B,EACA0E,CAAiB,CAACe,CAAD,CAAa,QAAb,CACb,gBADa,CACK,UAAYC,CADjB,CAC+B,MAD/B,CAEpB,CAED,GAAIpD,CAAI,CAACsD,OAAL,SAAJ,CAAgC,CAC5B,IAAK,GAAIC,CAAAA,CAAT,GAAcvD,CAAAA,CAAI,CAACsD,OAAnB,CAA4B,CACxBlC,CAAuB,CAACpB,CAAI,CAACsD,OAAL,CAAaC,CAAb,CAAD,CAC1B,CACJ,CAED,GAAIvD,CAAI,CAACwD,oBAAL,SAAJ,CAA6C,CACzCN,CAAc,CAACrG,IAAf,CAAoB,uBAApB,EAA6C4G,KAA7C,GAAqDvD,WAArD,CAAiEF,CAAI,CAACwD,oBAAtE,CACH,CAED,GAAME,CAAAA,CAAO,CAAGvJ,CAAY,CAACoC,KAAb,CAAmBmH,OAAnB,CAA2B/H,GAA3B,CAA+B0H,CAA/B,CAAhB,CACA,GAAIK,CAAO,SAAX,CAA2B,CACvBvJ,CAAY,CAACsG,QAAb,CAAsB,cAAtB,CAAsC,CAAC4C,CAAD,CAAtC,CACH,CACJ,CAzBD,IAyBO,IAAe,WAAX,GAAA9E,CAAJ,CAA4B,CAC/B,GAAIoF,CAAAA,CAAS,CAAGpK,CAAC,CAACkB,CAAQ,CAACM,SAAT,CAAqB,UAAtB,CAAjB,CACI6I,CAAa,CAAGD,CAAS,CAAC9G,IAAV,CAAepC,CAAQ,CAACO,iBAAT,+BAAf,CADpB,CAEA2I,CAAS,CAACjG,WAAV,CAAsB,SAAtB,EACA0E,CAAiB,CAACwB,CAAD,CAAgB,UAAhB,CACb,WADa,CACA,MADA,CACQ,WADR,CAAjB,CAEAV,CAAc,CAACvG,QAAf,CAAwB,SAAxB,EACAyF,CAAiB,CAACe,CAAD,CAAa,UAAb,CACb,cADa,CACG,MADH,CACW,cADX,CAAjB,CAEAhJ,CAAY,CAACsG,QAAb,CAAsB,qBAAtB,CAA6ClC,CAA7C,CAAqD8E,CAArD,CACH,CAVM,IAUA,IAAe,cAAX,GAAA9E,CAAJ,CAA+B,CAClC2E,CAAc,CAACxF,WAAf,CAA2B,SAA3B,EACA0E,CAAiB,CAACe,CAAD,CAAa,UAAb,CACb,WADa,CACA,MADA,CACQ,WADR,CAAjB,CAEAhJ,CAAY,CAACsG,QAAb,CAAsB,qBAAtB,CAA6ClC,CAA7C,CAAqD8E,CAArD,CACH,CACJ,CA3aqG,CAkblGjC,CAAuB,CAAG,SAASyC,CAAT,CAAuB,CACjDtK,CAAC,CAAC,QAAUsK,CAAV,CAAyB,QAA1B,CAAD,CAAqChH,IAArC,CAA0CpC,CAAQ,CAACC,UAAnD,EAA+DuE,IAA/D,CAAoE,UAAW,CAE3E,GAAIpD,CAAAA,CAAE,CAAGtC,CAAC,CAAC,IAAD,CAAD,CAAQ+F,IAAR,CAAa,IAAb,CAAT,CAEA/F,CAAC,CAACkB,CAAQ,CAACC,UAAT,CAAsB,GAAtB,CAA4BmB,CAA7B,CAAD,CAAkCqE,WAAlC,CAA8C2D,CAA9C,EAEAhG,CAAc,CAAChC,CAAD,CACjB,CAPD,CAQH,CA3bqG,CAqclGiI,CAAW,CAAG,SAASZ,CAAT,CAAyBG,CAAzB,CAAoChE,CAApC,CAA4C+D,CAA5C,CAA0D,IACpE7E,CAAAA,CAAM,CAAGc,CAAM,CAACC,IAAP,CAAY,aAAZ,CAD2D,CAEpEK,CAAa,CAAGN,CAAM,CAACC,IAAP,CAAY,oBAAZ,EAAoCD,CAAM,CAACC,IAAP,CAAY,oBAAZ,CAApC,CAAwE,CAFpB,CAGpExC,CAAO,CAAGG,CAAiB,CAACiG,CAAD,CAHyC,CAIpE3D,CAAQ,CAAG/F,CAAI,CAACgG,IAAL,CAAU,CAAC,CACtBC,UAAU,CAAE,0BADU,CAEtBC,IAAI,CAAE,CAAC7D,EAAE,CAAEwH,CAAL,CAAgB9E,MAAM,CAAEA,CAAxB,CAAgCoB,aAAa,CAAEA,CAA/C,CAFgB,CAAD,CAAV,IAJyD,CASpEvC,CAAQ,CAAGD,CAAkB,CAAC+F,CAAD,CATuC,CAUxE3J,CAAC,CAACsG,IAAF,CAAOC,KAAP,CAAavG,CAAb,CAAgBgG,CAAhB,EACKQ,IADL,CACU,SAASgE,CAAT,CAAsB,CACxB,GAAI/D,CAAAA,CAAI,CAAGzG,CAAC,CAACyK,SAAF,CAAYD,CAAZ,CAAX,CACAzG,CAAa,CAAC4F,CAAD,CAAiBpG,CAAjB,CAAb,CACAc,CAAc,CAACR,CAAD,CAAd,CACA8F,CAAc,CAACrG,IAAf,CAAoBpC,CAAQ,CAACO,iBAA7B,EAAgD6B,IAAhD,CAAqDpC,CAAQ,CAACK,MAA9D,EAAsE6D,KAAtE,GAEA,GAAIiC,CAAAA,CAAC,CAAGrH,CAAC,CAACgH,KAAF,CAAQ,qBAAR,CAA+B,CAACC,UAAU,CAAER,CAAb,CAAmBzB,MAAM,CAAEA,CAA3B,CAA/B,CAAR,CACA2E,CAAc,CAAC5C,OAAf,CAAuBM,CAAvB,EACA,GAAI,CAACA,CAAC,CAACE,kBAAF,EAAL,CAA6B,CACzBmC,CAAyB,CAACC,CAAD,CAAiB7D,CAAjB,CAAyBW,CAAzB,CAA+BoD,CAA/B,CAA6CC,CAA7C,CAC5B,CACJ,CAZL,EAYO3C,IAZP,CAYY,SAASC,CAAT,CAAa,CAEjBrD,CAAa,CAAC4F,CAAD,CAAiBpG,CAAjB,CAAb,CACAc,CAAc,CAACR,CAAD,CAAd,CAEA,GAAIwD,CAAAA,CAAC,CAAGrH,CAAC,CAACgH,KAAF,CAAQ,yBAAR,CAAmC,CAACM,SAAS,CAAEF,CAAZ,CAAgBpC,MAAM,CAAEA,CAAxB,CAAnC,CAAR,CACA2E,CAAc,CAAC5C,OAAf,CAAuBM,CAAvB,EACA,GAAI,CAACA,CAAC,CAACE,kBAAF,EAAL,CAA6B,CACzBpH,CAAY,CAACmH,SAAb,CAAuBF,CAAvB,CACH,CACJ,CAtBL,CAuBH,CAteqG,CAyetG9G,CAAC,CAACqB,GAAF,CAAM,0BAAN,CAAkC,UAAW,CACzCE,CAAC,CAACC,MAAF,CAAS0C,UAAT,CAAoBkG,eAApB,CAAoC,CAGhCC,0BAA0B,CAAE,oCAASxE,CAAT,CAAe,IACnClB,CAAAA,CAAW,CAAGjF,CAAC,CAACmG,CAAI,CAACjE,OAAL,CAAa0I,UAAb,EAAD,CADoB,CAEnC3H,CAAI,CAAGhB,CAAW,CAACgD,CAAD,CAFiB,CAGvC,GAAIhC,CAAJ,CAAU,CACN,GAAImD,CAAAA,CAAa,CAAGnB,CAAW,CAAC3B,IAAZ,CAAiB,IAAMxC,CAAG,CAACG,WAA3B,EAAwC8E,IAAxC,CAA6C,oBAA7C,CAApB,CACAyB,CAAa,CAACvC,CAAD,CAAchC,CAAd,CAAoBmD,CAApB,CAChB,CACJ,CAV+B,CAehCyE,kBAAkB,CAAE,4BAACC,CAAD,CAAY,IACtB9H,CAAAA,CAAK,CAAGpC,CAAY,CAACoC,KADC,CAItBN,CAAE,CAAGM,CAAK,CAACN,EAAN,CAASN,GAAT,CAAa0I,CAAM,CAAC7H,IAApB,CAJiB,CAK5B,GAAIP,CAAE,SAAN,CAAsB,CAClB9B,CAAY,CAACsG,QAAb,CAAsB,cAAtB,CAAsC,CAACxE,CAAE,CAACoH,SAAJ,CAAtC,CACH,CAEDlJ,CAAY,CAACsG,QAAb,CAAsB,SAAtB,CAAiC,CAAC4D,CAAM,CAAC7H,IAAR,CAAjC,CACH,CAzB+B,CA6BhC8H,uBAAuB,CAAE,kCAAM,CAC3BnK,CAAY,CAACsG,QAAb,CAAsB,aAAtB,CACH,CA/B+B,CAApC,CAiCH,CAlCD,EA2CAtG,CAAY,CAACoK,YAAb,CAA0B,CAYtBC,oBAAoB,CAAE,8BAASC,CAAT,CAAuBlG,CAAvB,CAA+B/B,CAA/B,CAAqC2D,CAArC,CAAkD,IAE9D5D,CAAAA,CAAK,CAAGkI,CAAY,CAAClI,KAFyC,CAG9DN,CAAE,CAAGM,CAAK,CAACN,EAAN,CAASN,GAAT,CAAaa,CAAb,CAHyD,CAIpE,GAAIP,CAAE,SAAN,CAAsB,CAClB,MACH,CACD,GAAMyH,CAAAA,CAAO,CAAGnH,CAAK,CAACmH,OAAN,CAAc/H,GAAd,CAAkBM,CAAE,CAACoH,SAArB,CAAhB,CACA,GAAIK,CAAO,SAAX,CAA2B,CACvB,MACH,CAEDe,CAAY,CAACC,WAAb,KAEA,OAAQnG,CAAR,EACI,IAAK,QAAL,CAEImF,CAAO,CAACiB,MAAR,CAAiBjB,CAAO,CAACiB,MAAR,CAAeC,MAAf,CACb,SAACD,CAAD,CAASE,CAAT,CAAqB,CACjB,GAAIA,CAAO,EAAIrI,CAAf,CAAqB,CACjBmI,CAAM,CAACtE,IAAP,CAAYwE,CAAZ,CACH,CACD,MAAOF,CAAAA,CACV,CANY,CAOb,EAPa,CAAjB,CAUApI,CAAK,CAACN,EAAN,CAAS6I,MAAT,CAAgBtI,CAAhB,EACA,MAEJ,IAAK,MAAL,CACA,IAAK,MAAL,CACIP,CAAE,CAAC8I,OAAH,CAAyB,MAAX,GAAAxG,CAAD,MAAb,CACA,MAEJ,IAAK,WAAL,CAEIpE,CAAY,CAACsG,QAAb,CAAsB,SAAtB,CAAiCN,CAAjC,EACA,MAxBR,CA0BAsE,CAAY,CAACC,WAAb,IACH,CArDqB,CAsDtBM,mBAAmB,CAAE,6BAASP,CAAT,CAAuBlG,CAAvB,CAA+B8E,CAA/B,CAA0C,IAErD9G,CAAAA,CAAK,CAAGkI,CAAY,CAAClI,KAFgC,CAGrDmH,CAAO,CAAGnH,CAAK,CAACmH,OAAN,CAAc/H,GAAd,CAAkB0H,CAAlB,CAH2C,CAI3D,GAAIK,CAAO,SAAX,CAA2B,CACvB,MACH,CAEDe,CAAY,CAACC,WAAb,KAEA,OAAQnG,CAAR,EACI,IAAK,WAAL,CAEIhC,CAAK,CAACmH,OAAN,CAAcuB,OAAd,CAAsB,SAACJ,CAAD,CAAa,CAC/B,GAAIA,CAAO,CAAChJ,EAAR,EAAcwH,CAAlB,CAA6B,CACzBwB,CAAO,CAACA,OAAR,GACH,CACJ,CAJD,EAKAnB,CAAO,CAACmB,OAAR,IACA,MAEJ,IAAK,cAAL,CACInB,CAAO,CAACmB,OAAR,IACA,MAbR,CAeAJ,CAAY,CAACC,WAAb,IACH,CAhFqB,CAA1B,EAmFA,MAAgD,CAQ5CQ,cAAc,CAAE,wBAAS9B,CAAT,CAAuB,CAGnC7J,CAAC,CAAC,MAAD,CAAD,CAAU4L,EAAV,CAAa,gBAAb,CAA+B1K,CAAQ,CAACC,UAAT,CAAsB,GAAtB,CACvBD,CAAQ,CAACG,cADc,CACG,eADlC,CACmD,SAASgG,CAAT,CAAY,CAC3D,GAAe,UAAX,GAAAA,CAAC,CAACmB,IAAF,EAAuC,EAAd,GAAAnB,CAAC,CAACwE,OAA/B,CAA+C,CAC3C,MACH,CACD,GAAIjC,CAAAA,CAAU,CAAG5J,CAAC,CAAC,IAAD,CAAlB,CACI6F,CAAa,CAAG+D,CAAU,CAACvD,OAAX,CAAmBnF,CAAQ,CAACC,UAA5B,CADpB,CAEI6D,CAAM,CAAG4E,CAAU,CAAC7D,IAAX,CAAgB,aAAhB,CAFb,CAGI+F,CAAQ,CAAG7J,CAAW,CAAC4D,CAAD,CAH1B,CAIA,OAAQb,CAAR,EACI,IAAK,UAAL,CACA,IAAK,WAAL,CACA,IAAK,QAAL,CACA,IAAK,WAAL,CACA,IAAK,MAAL,CACA,IAAK,SAAL,CACA,IAAK,MAAL,CACA,IAAK,gBAAL,CACA,IAAK,eAAL,CACA,IAAK,YAAL,CACI,MACJ,QAEI,OAdR,CAgBA,GAAI,CAAC8G,CAAL,CAAe,CACX,MACH,CACDzE,CAAC,CAAC0E,cAAF,GACA,GAAe,QAAX,GAAA/G,CAAJ,CAAyB,CAErB8C,CAAmB,CAACjC,CAAD,CAAgB,UAAW,CAC1CD,CAAU,CAACC,CAAD,CAAgBiG,CAAhB,CAA0BlC,CAA1B,CACb,CAFkB,CAGtB,CALD,IAKO,CACHhE,CAAU,CAACC,CAAD,CAAgBiG,CAAhB,CAA0BlC,CAA1B,CACb,CACJ,CArCD,EAwCA5J,CAAC,CAAC,MAAD,CAAD,CAAU4L,EAAV,CAAa,gBAAb,CAA+B1K,CAAQ,CAACM,SAAT,CAAqB,GAArB,CACnBN,CAAQ,CAACO,iBADU,kCAA/B,CAE8B,SAAS4F,CAAT,CAAY,CACtC,GAAe,UAAX,GAAAA,CAAC,CAACmB,IAAF,EAAuC,EAAd,GAAAnB,CAAC,CAACwE,OAA/B,CAA+C,CAC3C,MACH,CACD,GAAIjC,CAAAA,CAAU,CAAG5J,CAAC,CAAC,IAAD,CAAlB,CACI2J,CAAc,CAAGC,CAAU,CAACvD,OAAX,CAAmBnF,CAAQ,CAACM,SAA5B,CADrB,CAEIwK,CAAS,CAAGpC,CAAU,CAACvD,OAAX,CAAmBnF,CAAQ,CAACO,iBAA5B,EAA+CsE,IAA/C,CAAoD,gBAApD,CAFhB,CAGAsB,CAAC,CAAC0E,cAAF,GACA,GAAInC,CAAU,CAAC7D,IAAX,CAAgB,cAAhB,CAAJ,CAAqC,CAEjC4C,CAAkB,CAACiB,CAAU,CAAC7D,IAAX,CAAgB,cAAhB,CAAD,CAAkC,UAAW,CAC3DwE,CAAW,CAACZ,CAAD,CAAiBqC,CAAjB,CAA4BpC,CAA5B,CAAwCC,CAAxC,CACd,CAFiB,CAGrB,CALD,IAKO,CACHU,CAAW,CAACZ,CAAD,CAAiBqC,CAAjB,CAA4BpC,CAA5B,CAAwCC,CAAxC,CACd,CACJ,CAlBD,EAsBA7J,CAAC,CAAC,MAAD,CAAD,CAAU4L,EAAV,CAAa,SAAb,WAA2B1K,CAAQ,CAACM,SAApC,4BAAwE,SAAS6F,CAAT,CAAY,CAChF,GAAIA,CAAC,CAACJ,UAAF,EAAgBI,CAAC,CAACJ,UAAF,CAAagF,MAAjC,CAAyC,IAC/BjJ,CAAAA,CAAK,CAAGpC,CAAY,CAACoC,KADU,CAE/BmH,CAAO,CAAGnH,CAAK,CAACmH,OAAN,CAAc/H,GAAd,CAAkBiF,CAAC,CAACJ,UAAF,CAAagF,MAA/B,CAFqB,CAGrC,GAAI9B,CAAO,SAAX,CAA2B,CACvBvJ,CAAY,CAACsG,QAAb,CAAsB,cAAtB,CAAsC,CAACG,CAAC,CAACJ,UAAF,CAAagF,MAAd,CAAtC,CACH,CACJ,CACJ,CARD,EASAjM,CAAC,CAAC,MAAD,CAAD,CAAU4L,EAAV,CAAa,SAAb,WAA2B1K,CAAQ,CAACC,UAApC,4BAAyE,SAASkG,CAAT,CAAY,CACjF,GAAIA,CAAC,CAACJ,UAAF,EAAgBI,CAAC,CAACJ,UAAF,CAAagF,MAAjC,CAAyC,CACrCrL,CAAY,CAACsG,QAAb,CAAsB,SAAtB,CAAiC,CAACG,CAAC,CAACJ,UAAF,CAAagF,MAAd,CAAjC,CACH,CACJ,CAJD,EAOA7L,CAAG,CAAC+H,UAAJ,CAAe,aAAf,EAA8B3B,IAA9B,CAAmC,SAAS0F,CAAT,CAA4B,IACvDnF,CAAAA,CAAO,CAAG/G,CAAC,CAACkB,CAAQ,CAACQ,WAAV,CAD4C,CAEvDyK,CAAU,CAAGpF,CAAO,CAAChB,IAAR,CAAa,mBAAb,CAF0C,CAGvDqG,CAAW,CAAGrF,CAAO,CAAChB,IAAR,CAAa,mBAAb,CAHyC,CAIvDsG,CAAS,CAAGrM,CAAC,CAAC,8HACsDoM,CADtD,CACoE,uBADrE,CAJ0C,CAM3DC,CAAS,CAAC/I,IAAV,CAAe,OAAf,EAAwBgG,IAAxB,CAA6B4C,CAA7B,EACA3L,CAAY,CAAC+L,MAAb,CAAoB,CAChBC,KAAK,CAAEJ,CADS,CAEhB3D,IAAI,CAAEjI,CAAY,CAACiM,KAAb,CAAmBC,WAFT,CAGhBC,IAAI,CAAEL,CAAS,CAAC/C,IAAV,EAHU,CAApB,CAIGvC,CAJH,EAKCP,IALD,CAKM,SAASmG,CAAT,CAAgB,CAClB,GAAIC,CAAAA,CAAW,CAAG5M,CAAC,CAAC2M,CAAK,CAACE,OAAN,EAAD,CAAD,CAAmBvJ,IAAnB,CAAwB,0BAAxB,CAAlB,CACAwJ,CAAW,CAAG,UAAW,CAGrB,GAAI,GAAKC,QAAQ,CAACH,CAAW,CAACI,GAAZ,EAAD,CAAb,GAAqCJ,CAAW,CAACI,GAAZ,EAArC,EAAyF,CAA/B,EAAAD,QAAQ,CAACH,CAAW,CAACI,GAAZ,EAAD,CAAtE,CAAgG,CAC5FC,QAAQ,CAACC,QAAT,CAAoBnG,CAAO,CAAChB,IAAR,CAAa,MAAb,EAAuB,eAAvB,CAAyCgH,QAAQ,CAACH,CAAW,CAACI,GAAZ,EAAD,CACxE,CACJ,CAPD,CAQAL,CAAK,CAACQ,iBAAN,CAAwBhB,CAAxB,EACAQ,CAAK,CAACS,OAAN,GAAgBxB,EAAhB,CAAmBpL,CAAW,CAAC6M,KAA/B,CAAsC,UAAW,CAE7CT,CAAW,CAACxH,KAAZ,GAAoBkI,MAApB,GAA6B1B,EAA7B,CAAgC,SAAhC,CAA2C,SAASvE,CAAT,CAAY,CACnD,GAAIA,CAAC,CAACwE,OAAF,GAAcpL,CAAQ,CAAC8M,KAA3B,CAAkC,CAC9BT,CAAW,EACd,CACJ,CAJD,CAKH,CAPD,EAQAH,CAAK,CAACS,OAAN,GAAgBxB,EAAhB,CAAmBpL,CAAW,CAACgN,IAA/B,CAAqC,SAASnG,CAAT,CAAY,CAE7CA,CAAC,CAAC0E,cAAF,GACAe,CAAW,EACd,CAJD,CAKH,CA5BD,CA6BH,CApCD,CAqCH,CA9H2C,CA6I5CW,wBAAwB,CAAE,kCAAS9J,CAAT,CAAyBuB,CAAzB,CAAmC6D,CAAnC,CAA0CC,CAA1C,CACcC,CADd,CAC+BC,CAD/B,CAC0C,CAChExI,CAAG,CAACgN,KAAJ,CAAU,+DAAV,EACA,GAAI5E,CAAAA,CAAU,CAAGnF,CAAc,CAACL,IAAf,CAAoBpC,CAAQ,CAACO,iBAAT,CAA6B,GAA7B,CAAmCyD,CAAvD,CAAjB,CACA2D,CAAiB,CAACC,CAAD,CAAaC,CAAb,CAAoBC,CAApB,CAAgCC,CAAhC,CAAiDC,CAAjD,CACpB,CAlJ2C,CAoJ5C1B,aAAa,CAAbA,CApJ4C,CAsJnD,CA/vBC,CAAN","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(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/url', 'core/yui',\n 'core/modal_factory', 'core/modal_events', 'core/key_codes', 'core/log', 'core_courseformat/courseeditor'],\n function($, ajax, templates, notification, str, url, Y, ModalFactory, ModalEvents, KeyCodes, log, editor) {\n\n const courseeditor = editor.getCurrentCourseEditor();\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n ADDSECTIONS: '#changenumsections [data-add-sections]'\n };\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 * 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 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 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 var lightbox = M.util.add_lightbox(Y, Y.Node(sectionelement.get(0)));\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 }, 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 }, 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 });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n 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 * 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'},\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 replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\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 } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n }\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 // 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 });\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 */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0;\n 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 };\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 statemanager.setReadOnly(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 cm.visible = (action === 'show') ? true : false;\n break;\n\n case 'duplicate':\n // Duplicate requires to get extra data from the server.\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 statemanager.setReadOnly(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 // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section show/hide actions.\n $('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +\n SELECTOR.SECTIONACTIONMENU + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENU).attr('data-sectionid');\n e.preventDefault();\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]);\n }\n });\n\n // Add a handler for \"Add sections\" link to ask for a number of sections to add.\n str.get_string('numberweeks').done(function(strNumberSections) {\n var trigger = $(SELECTOR.ADDSECTIONS),\n modalTitle = trigger.attr('data-add-sections'),\n newSections = trigger.attr('data-new-sections');\n var modalBody = $(' ' +\n '
');\n modalBody.find('label').html(strNumberSections);\n ModalFactory.create({\n title: modalTitle,\n type: ModalFactory.types.SAVE_CANCEL,\n body: modalBody.html()\n }, trigger)\n .done(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n });\n });\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n };\n });\n"],"file":"actions.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/actions.js"],"names":["define","$","ajax","templates","notification","str","url","Y","ModalFactory","ModalEvents","KeyCodes","log","editor","courseeditor","getCurrentCourseEditor","CSS","EDITINPROGRESS","SECTIONDRAGGABLE","EDITINGMOVE","SELECTOR","ACTIVITYLI","ACTIONAREA","ACTIVITYACTION","MENU","TOGGLE","SECTIONLI","SECTIONACTIONMENU","ADDSECTIONS","use","courseformatselector","M","course","format","get_section_selector","getModuleId","element","item","get","dataset","id","Moodle","core_course","util","cm","getId","Node","getModuleName","name","getName","state","cmid","addActivitySpinner","activity","addClass","actionarea","find","spinner","add_spinner","show","data","dispatch","addSectionSpinner","sectionelement","addSectionLightbox","lightbox","add_lightbox","removeSpinner","delay","window","setTimeout","removeClass","hide","mutation","removeLightbox","initActionMenu","elementid","coursebase","invoke_function","core","actionmenu","newDOMNode","one","focusActionItem","elementId","action","mainelement","selector","is","focus","findNextFocusable","mainElement","tabables","isInside","foundElement","each","contains","editModule","moduleElement","target","attr","promises","call","methodname","args","sectionreturn","closest","when","apply","done","elementToFocus","replaceWith","affectedids","index","push","trigger","Event","ajaxreturn","fail","ex","e","exception","isDefaultPrevented","refreshModule","activityElement","Promise","resolve","reject","replaceActivityHtmlWith","confirmDeleteModule","onconfirm","modtypename","match","modulename","get_string","pluginname","get_strings","key","param","type","s","confirm","confirmEditSection","message","replaceActionItem","actionitem","image","stringname","stringcomponent","newaction","component","then","strings","html","renderPix","pixhtml","catch","defaultEditSectionHandler","sectionElement","actionItem","courseformat","sectionid","modules","i","section_availability","first","section","oldmarker","oldActionItem","activityHTML","editSection","dataencoded","parseJSON","register_module","set_visibility_resource_ui","getDOMNode","updateMovedCmState","params","updateMovedSectionState","addMutations","legacyActivityAction","statemanager","setReadOnly","locked","cmlist","reduce","current","delete","visible","legacySectionAction","forEach","initCoursePage","on","keyCode","moduleId","preventDefault","sectionId","itemid","strNumberSections","modalTitle","newSections","modalBody","create","title","types","SAVE_CANCEL","body","modal","numSections","getBody","addSections","parseInt","val","document","location","setSaveButtonText","getRoot","shown","select","enter","save","replaceSectionActionItem","debug"],"mappings":"AAuBAA,OAAM,uBAAC,CAAC,QAAD,CAAW,WAAX,CAAwB,gBAAxB,CAA0C,mBAA1C,CAA+D,UAA/D,CAA2E,UAA3E,CAAuF,UAAvF,CACC,oBADD,CACuB,mBADvB,CAC4C,gBAD5C,CAC8D,UAD9D,CAC0E,gCAD1E,CAAD,CAEF,SAASC,CAAT,CAAYC,CAAZ,CAAkBC,CAAlB,CAA6BC,CAA7B,CAA2CC,CAA3C,CAAgDC,CAAhD,CAAqDC,CAArD,CAAwDC,CAAxD,CAAsEC,CAAtE,CAAmFC,CAAnF,CAA6FC,CAA7F,CAAkGC,CAAlG,CAA0G,IAEhGC,CAAAA,CAAY,CAAGD,CAAM,CAACE,sBAAP,EAFiF,CAIlGC,CAAG,CAAG,CACNC,cAAc,CAAE,gBADV,CAENC,gBAAgB,CAAE,kBAFZ,CAGNC,WAAW,CAAE,cAHP,CAJ4F,CASlGC,CAAQ,CAAG,CACXC,UAAU,CAAE,aADD,CAEXC,UAAU,CAAE,UAFD,CAGXC,cAAc,CAAE,kBAHL,CAIXC,IAAI,CAAE,yDAJK,CAKXC,MAAM,CAAE,kCALG,CAMXC,SAAS,CAAE,YANA,CAOXC,iBAAiB,CAAE,sBAPR,CAQXC,WAAW,CAAE,wCARF,CATuF,CAoBtGpB,CAAC,CAACqB,GAAF,CAAM,0BAAN,CAAkC,UAAW,CACzC,GAAIC,CAAAA,CAAoB,CAAGC,CAAC,CAACC,MAAF,CAASC,MAAT,CAAgBC,oBAAhB,EAA3B,CACA,GAAIJ,CAAJ,CAA0B,CACtBV,CAAQ,CAACM,SAAT,CAAqBI,CACxB,CACJ,CALD,EApBsG,GAiClGK,CAAAA,CAAW,CAAG,SAASC,CAAT,CAAkB,CAEhC,GAAMC,CAAAA,CAAI,CAAGD,CAAO,CAACE,GAAR,CAAY,CAAZ,CAAb,CACA,GAAID,CAAI,CAACE,OAAL,CAAaC,EAAjB,CAAqB,CACjB,MAAOH,CAAAA,CAAI,CAACE,OAAL,CAAaC,EACvB,CAED,GAAIA,CAAAA,CAAJ,CACAhC,CAAC,CAACqB,GAAF,CAAM,oBAAN,CAA4B,SAASrB,CAAT,CAAY,CACpCgC,CAAE,CAAGhC,CAAC,CAACiC,MAAF,CAASC,WAAT,CAAqBC,IAArB,CAA0BC,EAA1B,CAA6BC,KAA7B,CAAmCrC,CAAC,CAACsC,IAAF,CAAOT,CAAP,CAAnC,CACR,CAFD,EAGA,MAAOG,CAAAA,CACV,CA7CqG,CAqDlGO,CAAa,CAAG,SAASX,CAAT,CAAkB,CAClC,GAAIY,CAAAA,CAAJ,CACAxC,CAAC,CAACqB,GAAF,CAAM,oBAAN,CAA4B,SAASrB,CAAT,CAAY,CACpCwC,CAAI,CAAGxC,CAAC,CAACiC,MAAF,CAASC,WAAT,CAAqBC,IAArB,CAA0BC,EAA1B,CAA6BK,OAA7B,CAAqCzC,CAAC,CAACsC,IAAF,CAAOV,CAAO,CAACE,GAAR,CAAY,CAAZ,CAAP,CAArC,CACV,CAFD,EAFkC,GAM5BY,CAAAA,CAAK,CAAGpC,CAAY,CAACoC,KANO,CAO5BC,CAAI,CAAGhB,CAAW,CAACC,CAAD,CAPU,CAQlC,GAAI,CAACY,CAAD,EAASE,CAAT,EAAkBC,CAAtB,CAA4B,OACxBH,CAAI,WAAGE,CAAK,CAACN,EAAN,CAASN,GAAT,CAAaa,CAAb,CAAH,qBAAG,EAAoBH,IAC9B,CACD,MAAOA,CAAAA,CACV,CAjEqG,CAyElGI,CAAkB,CAAG,SAASC,CAAT,CAAmB,CACxCA,CAAQ,CAACC,QAAT,CAAkBtC,CAAG,CAACC,cAAtB,EACA,GAAIsC,CAAAA,CAAU,CAAGF,CAAQ,CAACG,IAAT,CAAcpC,CAAQ,CAACE,UAAvB,EAAmCgB,GAAnC,CAAuC,CAAvC,CAAjB,CACA,GAAIiB,CAAJ,CAAgB,CACZ,GAAIE,CAAAA,CAAO,CAAG1B,CAAC,CAACY,IAAF,CAAOe,WAAP,CAAmBlD,CAAnB,CAAsBA,CAAC,CAACsC,IAAF,CAAOS,CAAP,CAAtB,CAAd,CACAE,CAAO,CAACE,IAAR,GAEA,GAAIN,CAAQ,CAACO,IAAT,CAAc,IAAd,UAAJ,CAAuC,CACnC9C,CAAY,CAAC+C,QAAb,CAAsB,QAAtB,CAAgC,CAACR,CAAQ,CAACO,IAAT,CAAc,IAAd,CAAD,CAAhC,IACH,CACD,MAAOH,CAAAA,CACV,CACD,MAAO,KACV,CAtFqG,CA8FlGK,CAAiB,CAAG,SAASC,CAAT,CAAyB,CAC7CA,CAAc,CAACT,QAAf,CAAwBtC,CAAG,CAACC,cAA5B,EACA,GAAIsC,CAAAA,CAAU,CAAGQ,CAAc,CAACP,IAAf,CAAoBpC,CAAQ,CAACO,iBAA7B,EAAgDW,GAAhD,CAAoD,CAApD,CAAjB,CACA,GAAIiB,CAAJ,CAAgB,CACZ,GAAIE,CAAAA,CAAO,CAAG1B,CAAC,CAACY,IAAF,CAAOe,WAAP,CAAmBlD,CAAnB,CAAsBA,CAAC,CAACsC,IAAF,CAAOS,CAAP,CAAtB,CAAd,CACAE,CAAO,CAACE,IAAR,GAEA,GAAII,CAAc,CAACH,IAAf,CAAoB,IAApB,UAAJ,CAA6C,CACzC9C,CAAY,CAAC+C,QAAb,CAAsB,aAAtB,CAAqC,CAACE,CAAc,CAACH,IAAf,CAAoB,IAApB,CAAD,CAArC,IACH,CACD,MAAOH,CAAAA,CACV,CACD,MAAO,KACV,CA3GqG,CAmHlGO,CAAkB,CAAG,SAASD,CAAT,CAAyB,CAC9C,GAAIE,CAAAA,CAAQ,CAAGlC,CAAC,CAACY,IAAF,CAAOuB,YAAP,CAAoB1D,CAApB,CAAuBA,CAAC,CAACsC,IAAF,CAAOiB,CAAc,CAACzB,GAAf,CAAmB,CAAnB,CAAP,CAAvB,CAAf,CACA2B,CAAQ,CAACN,IAAT,GACA,MAAOM,CAAAA,CACV,CAvHqG,CAgIlGE,CAAa,CAAG,SAAS/B,CAAT,CAAkBqB,CAAlB,CAA2BW,CAA3B,CAAkC,CAClDC,MAAM,CAACC,UAAP,CAAkB,UAAW,CACzBlC,CAAO,CAACmC,WAAR,CAAoBvD,CAAG,CAACC,cAAxB,EACA,GAAIwC,CAAJ,CAAa,CACTA,CAAO,CAACe,IAAR,EACH,CAED,GAAIpC,CAAO,CAACwB,IAAR,CAAa,IAAb,UAAJ,CAAsC,CAClC,GAAMa,CAAAA,CAAQ,CAA4B,SAAxB,GAAArC,CAAO,CAACwB,IAAR,CAAa,KAAb,CAAD,CAAsC,aAAtC,CAAsD,QAAvE,CACA9C,CAAY,CAAC+C,QAAb,CAAsBY,CAAtB,CAAgC,CAACrC,CAAO,CAACwB,IAAR,CAAa,IAAb,CAAD,CAAhC,IACH,CACJ,CAVD,CAUGQ,CAVH,CAWH,CA5IqG,CAoJlGM,CAAc,CAAG,SAAST,CAAT,CAAmBG,CAAnB,CAA0B,CAC3C,GAAIH,CAAJ,CAAc,CACVI,MAAM,CAACC,UAAP,CAAkB,UAAW,CACzBL,CAAQ,CAACO,IAAT,EACH,CAFD,CAEGJ,CAFH,CAGH,CACJ,CA1JqG,CAiKlGO,CAAc,CAAG,SAASC,CAAT,CAAoB,CAErCpE,CAAC,CAACqB,GAAF,CAAM,0BAAN,CAAkC,UAAW,CACzCE,CAAC,CAACC,MAAF,CAAS6C,UAAT,CAAoBC,eAApB,CAAoC,oBAApC,CAA0D,IAAMF,CAAhE,CACH,CAFD,EAGA,GAAI7C,CAAC,CAACgD,IAAF,CAAOC,UAAP,EAAqBjD,CAAC,CAACgD,IAAF,CAAOC,UAAP,CAAkBC,UAA3C,CAAuD,CACnDlD,CAAC,CAACgD,IAAF,CAAOC,UAAP,CAAkBC,UAAlB,CAA6BzE,CAAC,CAAC0E,GAAF,CAAM,IAAMN,CAAZ,CAA7B,CACH,CACJ,CAzKqG,CAiLlGO,CAAe,CAAG,SAASC,CAAT,CAAoBC,CAApB,CAA4B,IAC1CC,CAAAA,CAAW,CAAGpF,CAAC,CAAC,IAAMkF,CAAP,CAD2B,CAE1CG,CAAQ,CAAG,gBAAkBF,CAAlB,CAA2B,GAFI,CAG9C,GAAe,gBAAX,GAAAA,CAAM,EAAoC,eAAX,GAAAA,CAA/B,EAAwE,YAAX,GAAAA,CAAjE,CAA0F,CAEtFE,CAAQ,CAAG,mFACd,CACD,GAAID,CAAW,CAAC9B,IAAZ,CAAiB+B,CAAjB,EAA2BC,EAA3B,CAA8B,UAA9B,CAAJ,CAA+C,CAC3CF,CAAW,CAAC9B,IAAZ,CAAiB+B,CAAjB,EAA2BE,KAA3B,EACH,CAFD,IAEO,CAEHH,CAAW,CAAC9B,IAAZ,CAAiBpC,CAAQ,CAACI,IAA1B,EAAgCgC,IAAhC,CAAqCpC,CAAQ,CAACK,MAA9C,EAAsDgE,KAAtD,EACH,CACJ,CA9LqG,CAsMlGC,CAAiB,CAAG,SAASC,CAAT,CAAsB,IACtCC,CAAAA,CAAQ,CAAG1F,CAAC,CAAC,WAAD,CAD0B,CAEtC2F,CAAQ,GAF8B,CAGtCC,CAAY,CAAG,IAHuB,CAI1CF,CAAQ,CAACG,IAAT,CAAc,UAAW,CACrB,GAAI7F,CAAC,CAAC8F,QAAF,CAAWL,CAAW,CAAC,CAAD,CAAtB,CAA2B,IAA3B,CAAJ,CAAsC,CAClCE,CAAQ,GACX,CAFD,IAEO,IAAIA,CAAJ,CAAc,CACjBC,CAAY,CAAG,IAAf,CACA,QACH,CACJ,CAPD,EAQA,MAAOA,CAAAA,CACV,CAnNqG,CA4NlGG,CAAU,CAAG,SAASC,CAAT,CAAwB/C,CAAxB,CAA8BgD,CAA9B,CAAsC,IAC/Cd,CAAAA,CAAM,CAAGc,CAAM,CAACC,IAAP,CAAY,aAAZ,CADsC,CAE/C3C,CAAO,CAAGL,CAAkB,CAAC8C,CAAD,CAFmB,CAG/CG,CAAQ,CAAGlG,CAAI,CAACmG,IAAL,CAAU,CAAC,CACtBC,UAAU,CAAE,yBADU,CAEtBC,IAAI,CAAE,CAAChE,EAAE,CAAEW,CAAL,CACFkC,MAAM,CAAEA,CADN,CAEFoB,aAAa,CAAEN,CAAM,CAACC,IAAP,CAAY,oBAAZ,EAAoCD,CAAM,CAACC,IAAP,CAAY,oBAAZ,CAApC,CAAwE,CAFrF,CAFgB,CAAD,CAAV,IAHoC,CAW/CnC,CAX+C,CAYnD,GAAe,WAAX,GAAAoB,CAAJ,CAA4B,CACxBpB,CAAQ,CAAGD,CAAkB,CAACmC,CAAM,CAACO,OAAP,CAAetF,CAAQ,CAACM,SAAxB,CAAD,CAChC,CACDxB,CAAC,CAACyG,IAAF,CAAOC,KAAP,CAAa1G,CAAb,CAAgBmG,CAAhB,EACKQ,IADL,CACU,SAASjD,CAAT,CAAe,CACjB,GAAIkD,CAAAA,CAAc,CAAGpB,CAAiB,CAACQ,CAAD,CAAtC,CACAA,CAAa,CAACa,WAAd,CAA0BnD,CAA1B,EACA,GAAIoD,CAAAA,CAAW,CAAG,EAAlB,CAEA9G,CAAC,CAAC,QAAU0D,CAAV,CAAiB,QAAlB,CAAD,CAA6BJ,IAA7B,CAAkCpC,CAAQ,CAACC,UAA3C,EAAuD0E,IAAvD,CAA4D,SAASkB,CAAT,CAAgB,CACxEtC,CAAc,CAACzE,CAAC,CAAC,IAAD,CAAD,CAAQkG,IAAR,CAAa,IAAb,CAAD,CAAd,CACA,GAAc,CAAV,GAAAa,CAAJ,CAAiB,CACb9B,CAAe,CAACjF,CAAC,CAAC,IAAD,CAAD,CAAQkG,IAAR,CAAa,IAAb,CAAD,CAAqBf,CAArB,CAAf,CACAyB,CAAc,CAAG,IACpB,CAEDE,CAAW,CAACE,IAAZ,CAAiB/E,CAAW,CAACjC,CAAC,CAAC,IAAD,CAAF,CAA5B,CACH,CARD,EAUA,GAAI4G,CAAJ,CAAoB,CAChBA,CAAc,CAACrB,KAAf,EACH,CAEDtB,CAAa,CAAC+B,CAAD,CAAgBzC,CAAhB,CAAyB,GAAzB,CAAb,CACAiB,CAAc,CAACT,CAAD,CAAW,GAAX,CAAd,CAEAiC,CAAa,CAACiB,OAAd,CAAsBjH,CAAC,CAACkH,KAAF,CAAQ,oBAAR,CAA8B,CAACC,UAAU,CAAEzD,CAAb,CAAmByB,MAAM,CAAEA,CAA3B,CAA9B,CAAtB,EAGAvE,CAAY,CAAC+C,QAAb,CAAsB,sBAAtB,CAA8CwB,CAA9C,CAAsDlC,CAAtD,CAA4D6D,CAA5D,CAEH,CA5BL,EA4BOM,IA5BP,CA4BY,SAASC,CAAT,CAAa,CAEjBpD,CAAa,CAAC+B,CAAD,CAAgBzC,CAAhB,CAAb,CACAiB,CAAc,CAACT,CAAD,CAAd,CAEA,GAAIuD,CAAAA,CAAC,CAAGtH,CAAC,CAACkH,KAAF,CAAQ,wBAAR,CAAkC,CAACK,SAAS,CAAEF,CAAZ,CAAgBlC,MAAM,CAAEA,CAAxB,CAAlC,CAAR,CACAa,CAAa,CAACiB,OAAd,CAAsBK,CAAtB,EACA,GAAI,CAACA,CAAC,CAACE,kBAAF,EAAL,CAA6B,CACzBrH,CAAY,CAACoH,SAAb,CAAuBF,CAAvB,CACH,CACJ,CAtCL,CAuCH,CAlRqG,CA8RlGI,CAAa,CAAG,SAASvF,CAAT,CAAkBe,CAAlB,CAAwBsD,CAAxB,CAAuC,IACjDmB,CAAAA,CAAe,CAAG1H,CAAC,CAACkC,CAAD,CAD8B,CAEnDqB,CAAO,CAAGL,CAAkB,CAACwE,CAAD,CAFuB,CAGnDvB,CAAQ,CAAGlG,CAAI,CAACmG,IAAL,CAAU,CAAC,CACtBC,UAAU,CAAE,wBADU,CAEtBC,IAAI,CAAE,CAAChE,EAAE,CAAEW,CAAL,CAAWsD,aAAa,CAAEA,CAA1B,CAFgB,CAAD,CAAV,IAHwC,CAQvD,MAAO,IAAIoB,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAUC,CAAV,CAAqB,CACpC7H,CAAC,CAACyG,IAAF,CAAOC,KAAP,CAAa1G,CAAb,CAAgBmG,CAAhB,EACKQ,IADL,CACU,SAASjD,CAAT,CAAe,CACjBO,CAAa,CAACyD,CAAD,CAAkBnE,CAAlB,CAA2B,GAA3B,CAAb,CACAuE,CAAuB,CAACpE,CAAD,CAAvB,CACAkE,CAAO,CAAClE,CAAD,CACV,CALL,EAKO0D,IALP,CAKY,UAAW,CACfnD,CAAa,CAACyD,CAAD,CAAkBnE,CAAlB,CAAb,CACAsE,CAAM,EACT,CARL,CASH,CAVM,CAWV,CAjTqG,CAyTlGE,CAAmB,CAAG,SAAS3C,CAAT,CAAsB4C,CAAtB,CAAiC,IACnDC,CAAAA,CAAW,CAAG7C,CAAW,CAACc,IAAZ,CAAiB,OAAjB,EAA0BgC,KAA1B,CAAgC,kBAAhC,EAAoD,CAApD,CADqC,CAEnDC,CAAU,CAAGtF,CAAa,CAACuC,CAAD,CAFyB,CAIvDhF,CAAG,CAACgI,UAAJ,CAAe,YAAf,CAA6BH,CAA7B,EAA0CtB,IAA1C,CAA+C,SAAS0B,CAAT,CAAqB,CAKhEjI,CAAG,CAACkI,WAAJ,CAAgB,CACZ,CAACC,GAAG,CAAE,SAAN,CADY,CAEZ,CAACA,GAAG,CAAiB,IAAf,GAAAJ,CAAU,CAAY,iBAAZ,CAAgC,qBAAhD,CAAuEK,KAAK,CAN/D,CACbC,IAAI,CAAEJ,CADO,CAEbvF,IAAI,CAAEqF,CAFO,CAMb,CAFY,CAGZ,CAACI,GAAG,CAAE,KAAN,CAHY,CAIZ,CAACA,GAAG,CAAE,IAAN,CAJY,CAAhB,EAKG5B,IALH,CAKQ,SAAS+B,CAAT,CAAY,CACZvI,CAAY,CAACwI,OAAb,CAAqBD,CAAC,CAAC,CAAD,CAAtB,CAA2BA,CAAC,CAAC,CAAD,CAA5B,CAAiCA,CAAC,CAAC,CAAD,CAAlC,CAAuCA,CAAC,CAAC,CAAD,CAAxC,CAA6CV,CAA7C,CACH,CAPL,CASH,CAdD,CAeH,CA5UqG,CAoVlGY,CAAkB,CAAG,SAASC,CAAT,CAAkBb,CAAlB,CAA6B,CAClD5H,CAAG,CAACkI,WAAJ,CAAgB,CACZ,CAACC,GAAG,CAAE,SAAN,CADY,CAEZ,CAACA,GAAG,CAAE,KAAN,CAFY,CAGZ,CAACA,GAAG,CAAE,IAAN,CAHY,CAAhB,EAIG5B,IAJH,CAIQ,SAAS+B,CAAT,CAAY,CACZvI,CAAY,CAACwI,OAAb,CAAqBD,CAAC,CAAC,CAAD,CAAtB,CAA2BG,CAA3B,CAAoCH,CAAC,CAAC,CAAD,CAArC,CAA0CA,CAAC,CAAC,CAAD,CAA3C,CAAgDV,CAAhD,CACH,CANL,CAQH,CA7VqG,CAyWlGc,CAAiB,CAAG,SAASC,CAAT,CAAqBC,CAArB,CAA4BC,CAA5B,CACWC,CADX,CAC4BC,CAD5B,CACuC,CAK3D,MAAO/I,CAAAA,CAAG,CAACkI,WAAJ,CAHc,CAAC,CAACC,GAAG,CAAEU,CAAN,CAAkBG,SAAS,CAAEF,CAA7B,CAAD,CAGd,EAAgCG,IAAhC,CAAqC,SAASC,CAAT,CAAkB,CAC1DP,CAAU,CAACzF,IAAX,CAAgB,uBAAhB,EAAyCiG,IAAzC,CAA8CD,CAAO,CAAC,CAAD,CAArD,EAEA,MAAOpJ,CAAAA,CAAS,CAACsJ,SAAV,CAAoBR,CAApB,CAA2B,MAA3B,CACV,CAJM,EAIJK,IAJI,CAIC,SAASI,CAAT,CAAkB,CACtBV,CAAU,CAACzF,IAAX,CAAgB,OAAhB,EAAyBuD,WAAzB,CAAqC4C,CAArC,EACAV,CAAU,CAAC7C,IAAX,CAAgB,aAAhB,CAA+BiD,CAA/B,CAEH,CARM,EAQJO,KARI,CAQEvJ,CAAY,CAACoH,SARf,CASV,CAxXqG,CA6YlGoC,CAAyB,CAAG,SAASC,CAAT,CAAyBC,CAAzB,CAAqCnG,CAArC,CAA2CoG,CAA3C,CAAyDC,CAAzD,CAAoE,CAChG,GAAI5E,CAAAA,CAAM,CAAG0E,CAAU,CAAC3D,IAAX,CAAgB,aAAhB,CAAb,CACA,GAAe,MAAX,GAAAf,CAAM,EAA0B,MAAX,GAAAA,CAAzB,CAA4C,CACxC,GAAe,MAAX,GAAAA,CAAJ,CAAuB,CACnByE,CAAc,CAACxG,QAAf,CAAwB,QAAxB,EACA0F,CAAiB,CAACe,CAAD,CAAa,QAAb,CACb,gBADa,CACK,UAAYC,CADjB,CAC+B,MAD/B,CAEpB,CAJD,IAIO,CACHF,CAAc,CAACvF,WAAf,CAA2B,QAA3B,EACAyE,CAAiB,CAACe,CAAD,CAAa,QAAb,CACb,gBADa,CACK,UAAYC,CADjB,CAC+B,MAD/B,CAEpB,CAED,GAAIpG,CAAI,CAACsG,OAAL,SAAJ,CAAgC,CAC5B,IAAK,GAAIC,CAAAA,CAAT,GAAcvG,CAAAA,CAAI,CAACsG,OAAnB,CAA4B,CACxBlC,CAAuB,CAACpE,CAAI,CAACsG,OAAL,CAAaC,CAAb,CAAD,CAC1B,CACJ,CAED,GAAIvG,CAAI,CAACwG,oBAAL,SAAJ,CAA6C,CACzCN,CAAc,CAACtG,IAAf,CAAoB,uBAApB,EAA6C6G,KAA7C,GAAqDtD,WAArD,CAAiEnD,CAAI,CAACwG,oBAAtE,CACH,CAED,GAAME,CAAAA,CAAO,CAAGxJ,CAAY,CAACoC,KAAb,CAAmBoH,OAAnB,CAA2BhI,GAA3B,CAA+B2H,CAA/B,CAAhB,CACA,GAAIK,CAAO,SAAX,CAA2B,CACvBxJ,CAAY,CAAC+C,QAAb,CAAsB,cAAtB,CAAsC,CAACoG,CAAD,CAAtC,CACH,CACJ,CAzBD,IAyBO,IAAe,WAAX,GAAA5E,CAAJ,CAA4B,CAC/B,GAAIkF,CAAAA,CAAS,CAAGrK,CAAC,CAACkB,CAAQ,CAACM,SAAT,CAAqB,UAAtB,CAAjB,CACI8I,CAAa,CAAGD,CAAS,CAAC/G,IAAV,CAAepC,CAAQ,CAACO,iBAAT,+BAAf,CADpB,CAEA4I,CAAS,CAAChG,WAAV,CAAsB,SAAtB,EACAyE,CAAiB,CAACwB,CAAD,CAAgB,UAAhB,CACb,WADa,CACA,MADA,CACQ,WADR,CAAjB,CAEAV,CAAc,CAACxG,QAAf,CAAwB,SAAxB,EACA0F,CAAiB,CAACe,CAAD,CAAa,UAAb,CACb,cADa,CACG,MADH,CACW,cADX,CAAjB,CAEAjJ,CAAY,CAAC+C,QAAb,CAAsB,qBAAtB,CAA6CwB,CAA7C,CAAqD4E,CAArD,CACH,CAVM,IAUA,IAAe,cAAX,GAAA5E,CAAJ,CAA+B,CAClCyE,CAAc,CAACvF,WAAf,CAA2B,SAA3B,EACAyE,CAAiB,CAACe,CAAD,CAAa,UAAb,CACb,WADa,CACA,MADA,CACQ,WADR,CAAjB,CAEAjJ,CAAY,CAAC+C,QAAb,CAAsB,qBAAtB,CAA6CwB,CAA7C,CAAqD4E,CAArD,CACH,CACJ,CAxbqG,CA+blGjC,CAAuB,CAAG,SAASyC,CAAT,CAAuB,CACjDvK,CAAC,CAAC,QAAUuK,CAAV,CAAyB,QAA1B,CAAD,CAAqCjH,IAArC,CAA0CpC,CAAQ,CAACC,UAAnD,EAA+D0E,IAA/D,CAAoE,UAAW,CAE3E,GAAIvD,CAAAA,CAAE,CAAGtC,CAAC,CAAC,IAAD,CAAD,CAAQkG,IAAR,CAAa,IAAb,CAAT,CAEAlG,CAAC,CAACkB,CAAQ,CAACC,UAAT,CAAsB,GAAtB,CAA4BmB,CAA7B,CAAD,CAAkCuE,WAAlC,CAA8C0D,CAA9C,EAEA9F,CAAc,CAACnC,CAAD,CACjB,CAPD,CAQH,CAxcqG,CAkdlGkI,CAAW,CAAG,SAASZ,CAAT,CAAyBG,CAAzB,CAAoC9D,CAApC,CAA4C6D,CAA5C,CAA0D,IACpE3E,CAAAA,CAAM,CAAGc,CAAM,CAACC,IAAP,CAAY,aAAZ,CAD2D,CAEpEK,CAAa,CAAGN,CAAM,CAACC,IAAP,CAAY,oBAAZ,EAAoCD,CAAM,CAACC,IAAP,CAAY,oBAAZ,CAApC,CAAwE,CAFpB,CAGpE3C,CAAO,CAAGK,CAAiB,CAACgG,CAAD,CAHyC,CAIpEzD,CAAQ,CAAGlG,CAAI,CAACmG,IAAL,CAAU,CAAC,CACtBC,UAAU,CAAE,0BADU,CAEtBC,IAAI,CAAE,CAAChE,EAAE,CAAEyH,CAAL,CAAgB5E,MAAM,CAAEA,CAAxB,CAAgCoB,aAAa,CAAEA,CAA/C,CAFgB,CAAD,CAAV,IAJyD,CASpExC,CAAQ,CAAGD,CAAkB,CAAC8F,CAAD,CATuC,CAUxE5J,CAAC,CAACyG,IAAF,CAAOC,KAAP,CAAa1G,CAAb,CAAgBmG,CAAhB,EACKQ,IADL,CACU,SAAS8D,CAAT,CAAsB,CACxB,GAAI/G,CAAAA,CAAI,CAAG1D,CAAC,CAAC0K,SAAF,CAAYD,CAAZ,CAAX,CACAxG,CAAa,CAAC2F,CAAD,CAAiBrG,CAAjB,CAAb,CACAiB,CAAc,CAACT,CAAD,CAAd,CACA6F,CAAc,CAACtG,IAAf,CAAoBpC,CAAQ,CAACO,iBAA7B,EAAgD6B,IAAhD,CAAqDpC,CAAQ,CAACK,MAA9D,EAAsEgE,KAAtE,GAEA,GAAI+B,CAAAA,CAAC,CAAGtH,CAAC,CAACkH,KAAF,CAAQ,qBAAR,CAA+B,CAACC,UAAU,CAAEzD,CAAb,CAAmByB,MAAM,CAAEA,CAA3B,CAA/B,CAAR,CACAyE,CAAc,CAAC3C,OAAf,CAAuBK,CAAvB,EACA,GAAI,CAACA,CAAC,CAACE,kBAAF,EAAL,CAA6B,CACzBmC,CAAyB,CAACC,CAAD,CAAiB3D,CAAjB,CAAyBvC,CAAzB,CAA+BoG,CAA/B,CAA6CC,CAA7C,CAC5B,CACJ,CAZL,EAYO3C,IAZP,CAYY,SAASC,CAAT,CAAa,CAEjBpD,CAAa,CAAC2F,CAAD,CAAiBrG,CAAjB,CAAb,CACAiB,CAAc,CAACT,CAAD,CAAd,CAEA,GAAIuD,CAAAA,CAAC,CAAGtH,CAAC,CAACkH,KAAF,CAAQ,yBAAR,CAAmC,CAACK,SAAS,CAAEF,CAAZ,CAAgBlC,MAAM,CAAEA,CAAxB,CAAnC,CAAR,CACAyE,CAAc,CAAC3C,OAAf,CAAuBK,CAAvB,EACA,GAAI,CAACA,CAAC,CAACE,kBAAF,EAAL,CAA6B,CACzBrH,CAAY,CAACoH,SAAb,CAAuBF,CAAvB,CACH,CACJ,CAtBL,CAuBH,CAnfqG,CAsftG/G,CAAC,CAACqB,GAAF,CAAM,0BAAN,CAAkC,UAAW,CACzCE,CAAC,CAACC,MAAF,CAAS6C,UAAT,CAAoBgG,eAApB,CAAoC,CAGhCC,0BAA0B,CAAE,oCAAStE,CAAT,CAAe,IACnClB,CAAAA,CAAW,CAAGpF,CAAC,CAACsG,CAAI,CAACpE,OAAL,CAAa2I,UAAb,EAAD,CADoB,CAEnC5H,CAAI,CAAGhB,CAAW,CAACmD,CAAD,CAFiB,CAGvC,GAAInC,CAAJ,CAAU,CACN,GAAIsD,CAAAA,CAAa,CAAGnB,CAAW,CAAC9B,IAAZ,CAAiB,IAAMxC,CAAG,CAACG,WAA3B,EAAwCiF,IAAxC,CAA6C,oBAA7C,CAApB,CACAuB,CAAa,CAACrC,CAAD,CAAcnC,CAAd,CAAoBsD,CAApB,CAChB,CACJ,CAV+B,CAehCuE,kBAAkB,CAAE,4BAACC,CAAD,CAAY,IACtB/H,CAAAA,CAAK,CAAGpC,CAAY,CAACoC,KADC,CAItBN,CAAE,CAAGM,CAAK,CAACN,EAAN,CAASN,GAAT,CAAa2I,CAAM,CAAC9H,IAApB,CAJiB,CAK5B,GAAIP,CAAE,SAAN,CAAsB,CAClB9B,CAAY,CAAC+C,QAAb,CAAsB,cAAtB,CAAsC,CAACjB,CAAE,CAACqH,SAAJ,CAAtC,CACH,CAEDnJ,CAAY,CAAC+C,QAAb,CAAsB,SAAtB,CAAiC,CAACoH,CAAM,CAAC9H,IAAR,CAAjC,CACH,CAzB+B,CA6BhC+H,uBAAuB,CAAE,kCAAM,CAC3BpK,CAAY,CAAC+C,QAAb,CAAsB,aAAtB,CACH,CA/B+B,CAApC,CAiCH,CAlCD,EA2CA/C,CAAY,CAACqK,YAAb,CAA0B,CAYtBC,oBAAoB,CAAE,8BAASC,CAAT,CAAuBhG,CAAvB,CAA+BlC,CAA/B,CAAqC6D,CAArC,CAAkD,IAE9D9D,CAAAA,CAAK,CAAGmI,CAAY,CAACnI,KAFyC,CAG9DN,CAAE,CAAGM,CAAK,CAACN,EAAN,CAASN,GAAT,CAAaa,CAAb,CAHyD,CAIpE,GAAIP,CAAE,SAAN,CAAsB,CAClB,MACH,CACD,GAAM0H,CAAAA,CAAO,CAAGpH,CAAK,CAACoH,OAAN,CAAchI,GAAd,CAAkBM,CAAE,CAACqH,SAArB,CAAhB,CACA,GAAIK,CAAO,SAAX,CAA2B,CACvB,MACH,CAMDe,CAAY,CAACC,WAAb,KACA1I,CAAE,CAAC2I,MAAH,IACAF,CAAY,CAACC,WAAb,KAGAD,CAAY,CAACC,WAAb,KAGA1I,CAAE,CAAC2I,MAAH,IAEA,OAAQlG,CAAR,EACI,IAAK,QAAL,CAEIiF,CAAO,CAACkB,MAAR,CAAiBlB,CAAO,CAACkB,MAAR,CAAeC,MAAf,CACb,SAACD,CAAD,CAASE,CAAT,CAAqB,CACjB,GAAIA,CAAO,EAAIvI,CAAf,CAAqB,CACjBqI,CAAM,CAACtE,IAAP,CAAYwE,CAAZ,CACH,CACD,MAAOF,CAAAA,CACV,CANY,CAOb,EAPa,CAAjB,CAUAtI,CAAK,CAACN,EAAN,CAAS+I,MAAT,CAAgBxI,CAAhB,EACA,MAEJ,IAAK,MAAL,CACA,IAAK,MAAL,CACIP,CAAE,CAACgJ,OAAH,CAAyB,MAAX,GAAAvG,CAAD,MAAb,CACA,MAEJ,IAAK,WAAL,CAEIvE,CAAY,CAAC+C,QAAb,CAAsB,SAAtB,CAAiCmD,CAAjC,EACA,MAxBR,CA0BAqE,CAAY,CAACC,WAAb,IACH,CAjEqB,CAkEtBO,mBAAmB,CAAE,6BAASR,CAAT,CAAuBhG,CAAvB,CAA+B4E,CAA/B,CAA0C,IAErD/G,CAAAA,CAAK,CAAGmI,CAAY,CAACnI,KAFgC,CAGrDoH,CAAO,CAAGpH,CAAK,CAACoH,OAAN,CAAchI,GAAd,CAAkB2H,CAAlB,CAH2C,CAI3D,GAAIK,CAAO,SAAX,CAA2B,CACvB,MACH,CAMDe,CAAY,CAACC,WAAb,KACAhB,CAAO,CAACiB,MAAR,IACAF,CAAY,CAACC,WAAb,KAGAD,CAAY,CAACC,WAAb,KAGAhB,CAAO,CAACiB,MAAR,IAEA,OAAQlG,CAAR,EACI,IAAK,WAAL,CAEInC,CAAK,CAACoH,OAAN,CAAcwB,OAAd,CAAsB,SAACJ,CAAD,CAAa,CAC/B,GAAIA,CAAO,CAAClJ,EAAR,EAAcyH,CAAlB,CAA6B,CACzByB,CAAO,CAACA,OAAR,GACH,CACJ,CAJD,EAKApB,CAAO,CAACoB,OAAR,IACA,MAEJ,IAAK,cAAL,CACIpB,CAAO,CAACoB,OAAR,IACA,MAbR,CAeAL,CAAY,CAACC,WAAb,IACH,CAxGqB,CAA1B,EA2GA,MAAgD,CAQ5CS,cAAc,CAAE,wBAAS/B,CAAT,CAAuB,CAGnC9J,CAAC,CAAC,MAAD,CAAD,CAAU8L,EAAV,CAAa,gBAAb,CAA+B5K,CAAQ,CAACC,UAAT,CAAsB,GAAtB,CACvBD,CAAQ,CAACG,cADc,CACG,eADlC,CACmD,SAASiG,CAAT,CAAY,CAC3D,GAAe,UAAX,GAAAA,CAAC,CAACmB,IAAF,EAAuC,EAAd,GAAAnB,CAAC,CAACyE,OAA/B,CAA+C,CAC3C,MACH,CACD,GAAIlC,CAAAA,CAAU,CAAG7J,CAAC,CAAC,IAAD,CAAlB,CACIgG,CAAa,CAAG6D,CAAU,CAACrD,OAAX,CAAmBtF,CAAQ,CAACC,UAA5B,CADpB,CAEIgE,CAAM,CAAG0E,CAAU,CAAC3D,IAAX,CAAgB,aAAhB,CAFb,CAGI8F,CAAQ,CAAG/J,CAAW,CAAC+D,CAAD,CAH1B,CAIA,OAAQb,CAAR,EACI,IAAK,UAAL,CACA,IAAK,WAAL,CACA,IAAK,QAAL,CACA,IAAK,WAAL,CACA,IAAK,MAAL,CACA,IAAK,SAAL,CACA,IAAK,MAAL,CACA,IAAK,gBAAL,CACA,IAAK,eAAL,CACA,IAAK,YAAL,CACI,MACJ,QAEI,OAdR,CAgBA,GAAI,CAAC6G,CAAL,CAAe,CACX,MACH,CACD1E,CAAC,CAAC2E,cAAF,GACA,GAAe,QAAX,GAAA9G,CAAJ,CAAyB,CAErB4C,CAAmB,CAAC/B,CAAD,CAAgB,UAAW,CAC1CD,CAAU,CAACC,CAAD,CAAgBgG,CAAhB,CAA0BnC,CAA1B,CACb,CAFkB,CAGtB,CALD,IAKO,CACH9D,CAAU,CAACC,CAAD,CAAgBgG,CAAhB,CAA0BnC,CAA1B,CACb,CACJ,CArCD,EAwCA7J,CAAC,CAAC,MAAD,CAAD,CAAU8L,EAAV,CAAa,gBAAb,CAA+B5K,CAAQ,CAACM,SAAT,CAAqB,GAArB,CACnBN,CAAQ,CAACO,iBADU,kCAA/B,CAE8B,SAAS6F,CAAT,CAAY,CACtC,GAAe,UAAX,GAAAA,CAAC,CAACmB,IAAF,EAAuC,EAAd,GAAAnB,CAAC,CAACyE,OAA/B,CAA+C,CAC3C,MACH,CACD,GAAIlC,CAAAA,CAAU,CAAG7J,CAAC,CAAC,IAAD,CAAlB,CACI4J,CAAc,CAAGC,CAAU,CAACrD,OAAX,CAAmBtF,CAAQ,CAACM,SAA5B,CADrB,CAEI0K,CAAS,CAAGrC,CAAU,CAACrD,OAAX,CAAmBtF,CAAQ,CAACO,iBAA5B,EAA+CyE,IAA/C,CAAoD,gBAApD,CAFhB,CAGAoB,CAAC,CAAC2E,cAAF,GACA,GAAIpC,CAAU,CAAC3D,IAAX,CAAgB,cAAhB,CAAJ,CAAqC,CAEjC0C,CAAkB,CAACiB,CAAU,CAAC3D,IAAX,CAAgB,cAAhB,CAAD,CAAkC,UAAW,CAC3DsE,CAAW,CAACZ,CAAD,CAAiBsC,CAAjB,CAA4BrC,CAA5B,CAAwCC,CAAxC,CACd,CAFiB,CAGrB,CALD,IAKO,CACHU,CAAW,CAACZ,CAAD,CAAiBsC,CAAjB,CAA4BrC,CAA5B,CAAwCC,CAAxC,CACd,CACJ,CAlBD,EAsBA9J,CAAC,CAAC,MAAD,CAAD,CAAU8L,EAAV,CAAa,SAAb,WAA2B5K,CAAQ,CAACM,SAApC,4BAAwE,SAAS8F,CAAT,CAAY,CAChF,GAAIA,CAAC,CAACH,UAAF,EAAgBG,CAAC,CAACH,UAAF,CAAagF,MAAjC,CAAyC,IAC/BnJ,CAAAA,CAAK,CAAGpC,CAAY,CAACoC,KADU,CAE/BoH,CAAO,CAAGpH,CAAK,CAACoH,OAAN,CAAchI,GAAd,CAAkBkF,CAAC,CAACH,UAAF,CAAagF,MAA/B,CAFqB,CAGrC,GAAI/B,CAAO,SAAX,CAA2B,CACvBxJ,CAAY,CAAC+C,QAAb,CAAsB,cAAtB,CAAsC,CAAC2D,CAAC,CAACH,UAAF,CAAagF,MAAd,CAAtC,CACH,CACJ,CACJ,CARD,EASAnM,CAAC,CAAC,MAAD,CAAD,CAAU8L,EAAV,CAAa,SAAb,WAA2B5K,CAAQ,CAACC,UAApC,4BAAyE,SAASmG,CAAT,CAAY,CACjF,GAAIA,CAAC,CAACH,UAAF,EAAgBG,CAAC,CAACH,UAAF,CAAagF,MAAjC,CAAyC,CACrCvL,CAAY,CAAC+C,QAAb,CAAsB,SAAtB,CAAiC,CAAC2D,CAAC,CAACH,UAAF,CAAagF,MAAd,CAAjC,CACH,CACJ,CAJD,EAOA/L,CAAG,CAACgI,UAAJ,CAAe,aAAf,EAA8BzB,IAA9B,CAAmC,SAASyF,CAAT,CAA4B,IACvDnF,CAAAA,CAAO,CAAGjH,CAAC,CAACkB,CAAQ,CAACQ,WAAV,CAD4C,CAEvD2K,CAAU,CAAGpF,CAAO,CAACf,IAAR,CAAa,mBAAb,CAF0C,CAGvDoG,CAAW,CAAGrF,CAAO,CAACf,IAAR,CAAa,mBAAb,CAHyC,CAIvDqG,CAAS,CAAGvM,CAAC,CAAC,8HACsDsM,CADtD,CACoE,uBADrE,CAJ0C,CAM3DC,CAAS,CAACjJ,IAAV,CAAe,OAAf,EAAwBiG,IAAxB,CAA6B6C,CAA7B,EACA7L,CAAY,CAACiM,MAAb,CAAoB,CAChBC,KAAK,CAAEJ,CADS,CAEhB5D,IAAI,CAAElI,CAAY,CAACmM,KAAb,CAAmBC,WAFT,CAGhBC,IAAI,CAAEL,CAAS,CAAChD,IAAV,EAHU,CAApB,CAIGtC,CAJH,EAKCN,IALD,CAKM,SAASkG,CAAT,CAAgB,CAClB,GAAIC,CAAAA,CAAW,CAAG9M,CAAC,CAAC6M,CAAK,CAACE,OAAN,EAAD,CAAD,CAAmBzJ,IAAnB,CAAwB,0BAAxB,CAAlB,CACA0J,CAAW,CAAG,UAAW,CAGrB,GAAI,GAAKC,QAAQ,CAACH,CAAW,CAACI,GAAZ,EAAD,CAAb,GAAqCJ,CAAW,CAACI,GAAZ,EAArC,EAAyF,CAA/B,EAAAD,QAAQ,CAACH,CAAW,CAACI,GAAZ,EAAD,CAAtE,CAAgG,CAC5FC,QAAQ,CAACC,QAAT,CAAoBnG,CAAO,CAACf,IAAR,CAAa,MAAb,EAAuB,eAAvB,CAAyC+G,QAAQ,CAACH,CAAW,CAACI,GAAZ,EAAD,CACxE,CACJ,CAPD,CAQAL,CAAK,CAACQ,iBAAN,CAAwBhB,CAAxB,EACAQ,CAAK,CAACS,OAAN,GAAgBxB,EAAhB,CAAmBtL,CAAW,CAAC+M,KAA/B,CAAsC,UAAW,CAE7CT,CAAW,CAACvH,KAAZ,GAAoBiI,MAApB,GAA6B1B,EAA7B,CAAgC,SAAhC,CAA2C,SAASxE,CAAT,CAAY,CACnD,GAAIA,CAAC,CAACyE,OAAF,GAActL,CAAQ,CAACgN,KAA3B,CAAkC,CAC9BT,CAAW,EACd,CACJ,CAJD,CAKH,CAPD,EAQAH,CAAK,CAACS,OAAN,GAAgBxB,EAAhB,CAAmBtL,CAAW,CAACkN,IAA/B,CAAqC,SAASpG,CAAT,CAAY,CAE7CA,CAAC,CAAC2E,cAAF,GACAe,CAAW,EACd,CAJD,CAKH,CA5BD,CA6BH,CApCD,CAqCH,CA9H2C,CA6I5CW,wBAAwB,CAAE,kCAAS9J,CAAT,CAAyBwB,CAAzB,CAAmC2D,CAAnC,CAA0CC,CAA1C,CACcC,CADd,CAC+BC,CAD/B,CAC0C,CAChEzI,CAAG,CAACkN,KAAJ,CAAU,+DAAV,EACA,GAAI7E,CAAAA,CAAU,CAAGlF,CAAc,CAACP,IAAf,CAAoBpC,CAAQ,CAACO,iBAAT,CAA6B,GAA7B,CAAmC4D,CAAvD,CAAjB,CACAyD,CAAiB,CAACC,CAAD,CAAaC,CAAb,CAAoBC,CAApB,CAAgCC,CAAhC,CAAiDC,CAAjD,CACpB,CAlJ2C,CAoJ5C1B,aAAa,CAAbA,CApJ4C,CAsJnD,CApyBC,CAAN","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(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/url', 'core/yui',\n 'core/modal_factory', 'core/modal_events', 'core/key_codes', 'core/log', 'core_courseformat/courseeditor'],\n function($, ajax, templates, notification, str, url, Y, ModalFactory, ModalEvents, KeyCodes, log, editor) {\n\n const courseeditor = editor.getCurrentCourseEditor();\n\n var CSS = {\n EDITINPROGRESS: 'editinprogress',\n SECTIONDRAGGABLE: 'sectiondraggable',\n EDITINGMOVE: 'editing_move'\n };\n var SELECTOR = {\n ACTIVITYLI: 'li.activity',\n ACTIONAREA: '.actions',\n ACTIVITYACTION: 'a.cm-edit-action',\n MENU: '.moodle-actionmenu[data-enhance=moodle-core-actionmenu]',\n TOGGLE: '.toggle-display,.dropdown-toggle',\n SECTIONLI: 'li.section',\n SECTIONACTIONMENU: '.section_action_menu',\n ADDSECTIONS: '#changenumsections [data-add-sections]'\n };\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 * 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 var lightbox = M.util.add_lightbox(Y, Y.Node(sectionelement.get(0)));\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 }, 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 });\n return foundElement;\n };\n\n /**\n * Performs an action on a module (moving, deleting, duplicating, hiding, etc.)\n *\n * @param {JQuery} moduleElement activity element we perform action on\n * @param {Number} cmid\n * @param {JQuery} target the element (menu item) that was clicked\n */\n var editModule = function(moduleElement, cmid, target) {\n var action = target.attr('data-action');\n var spinner = addActivitySpinner(moduleElement);\n var promises = ajax.call([{\n methodname: 'core_course_edit_module',\n args: {id: cmid,\n action: action,\n sectionreturn: target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0\n }\n }], true);\n\n var lightbox;\n if (action === 'duplicate') {\n lightbox = addSectionLightbox(target.closest(SELECTOR.SECTIONLI));\n }\n $.when.apply($, promises)\n .done(function(data) {\n var elementToFocus = findNextFocusable(moduleElement);\n moduleElement.replaceWith(data);\n let affectedids = [];\n // Initialise action menu for activity(ies) added as a result of this.\n $('' + data + '
').find(SELECTOR.ACTIVITYLI).each(function(index) {\n initActionMenu($(this).attr('id'));\n if (index === 0) {\n focusActionItem($(this).attr('id'), action);\n elementToFocus = null;\n }\n // Save any activity id in cmids.\n affectedids.push(getModuleId($(this)));\n });\n // In case of activity deletion focus the next focusable element.\n if (elementToFocus) {\n elementToFocus.focus();\n }\n // Remove spinner and lightbox with a delay.\n removeSpinner(moduleElement, spinner, 400);\n removeLightbox(lightbox, 400);\n // Trigger event that can be observed by course formats.\n moduleElement.trigger($.Event('coursemoduleedited', {ajaxreturn: data, action: action}));\n\n // Modify cm state.\n courseeditor.dispatch('legacyActivityAction', action, cmid, affectedids);\n\n }).fail(function(ex) {\n // Remove spinner and lightbox.\n removeSpinner(moduleElement, spinner);\n removeLightbox(lightbox);\n // Trigger event that can be observed by course formats.\n var e = $.Event('coursemoduleeditfailed', {exception: ex, action: action});\n moduleElement.trigger(e);\n if (!e.isDefaultPrevented()) {\n notification.exception(ex);\n }\n });\n };\n\n /**\n * Requests html for the module via WS core_course_get_module and updates the module on the course page\n *\n * Used after d&d of the module to another section\n *\n * @param {JQuery|Element} element\n * @param {Number} cmid\n * @param {Number} sectionreturn\n * @return {Promise} the refresh promise\n */\n var refreshModule = function(element, cmid, sectionreturn) {\n 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 * 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'},\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 replaceActionItem(actionItem, 'i/show',\n 'showfromothers', 'format_' + courseformat, 'show');\n } else {\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 } else if (action === 'removemarker') {\n sectionElement.removeClass('current');\n replaceActionItem(actionItem, 'i/marker',\n 'highlight', 'core', 'setmarker');\n courseeditor.dispatch('legacySectionAction', action, sectionid);\n }\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 // 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 });\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 */\n var editSection = function(sectionElement, sectionid, target, courseformat) {\n var action = target.attr('data-action'),\n sectionreturn = target.attr('data-sectionreturn') ? target.attr('data-sectionreturn') : 0;\n 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 };\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. 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 cm.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 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 cm.visible = (action === 'show') ? true : false;\n break;\n\n case 'duplicate':\n // Duplicate requires to get extra data from the server.\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 // Add a handler for course module actions.\n $('body').on('click keypress', SELECTOR.ACTIVITYLI + ' ' +\n SELECTOR.ACTIVITYACTION + '[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n moduleElement = actionItem.closest(SELECTOR.ACTIVITYLI),\n action = actionItem.attr('data-action'),\n moduleId = getModuleId(moduleElement);\n switch (action) {\n case 'moveleft':\n case 'moveright':\n case 'delete':\n case 'duplicate':\n case 'hide':\n case 'stealth':\n case 'show':\n case 'groupsseparate':\n case 'groupsvisible':\n case 'groupsnone':\n break;\n default:\n // Nothing to do here!\n return;\n }\n if (!moduleId) {\n return;\n }\n e.preventDefault();\n if (action === 'delete') {\n // Deleting requires confirmation.\n confirmDeleteModule(moduleElement, function() {\n editModule(moduleElement, moduleId, actionItem);\n });\n } else {\n editModule(moduleElement, moduleId, actionItem);\n }\n });\n\n // Add a handler for section show/hide actions.\n $('body').on('click keypress', SELECTOR.SECTIONLI + ' ' +\n SELECTOR.SECTIONACTIONMENU + '[data-sectionid] ' +\n 'a[data-action]', function(e) {\n if (e.type === 'keypress' && e.keyCode !== 13) {\n return;\n }\n var actionItem = $(this),\n sectionElement = actionItem.closest(SELECTOR.SECTIONLI),\n sectionId = actionItem.closest(SELECTOR.SECTIONACTIONMENU).attr('data-sectionid');\n e.preventDefault();\n if (actionItem.attr('data-confirm')) {\n // Action requires confirmation.\n confirmEditSection(actionItem.attr('data-confirm'), function() {\n editSection(sectionElement, sectionId, actionItem, courseformat);\n });\n } else {\n editSection(sectionElement, sectionId, actionItem, courseformat);\n }\n });\n\n // The section and activity names are edited using inplace editable.\n // The \"update\" jQuery event must be captured in order to update the course state.\n $('body').on('updated', `${SELECTOR.SECTIONLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n const state = courseeditor.state;\n const section = state.section.get(e.ajaxreturn.itemid);\n if (section !== undefined) {\n courseeditor.dispatch('sectionState', [e.ajaxreturn.itemid]);\n }\n }\n });\n $('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {\n if (e.ajaxreturn && e.ajaxreturn.itemid) {\n courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]);\n }\n });\n\n // Add a handler for \"Add sections\" link to ask for a number of sections to add.\n str.get_string('numberweeks').done(function(strNumberSections) {\n var trigger = $(SELECTOR.ADDSECTIONS),\n modalTitle = trigger.attr('data-add-sections'),\n newSections = trigger.attr('data-new-sections');\n var modalBody = $(' ' +\n '
');\n modalBody.find('label').html(strNumberSections);\n ModalFactory.create({\n title: modalTitle,\n type: ModalFactory.types.SAVE_CANCEL,\n body: modalBody.html()\n }, trigger)\n .done(function(modal) {\n var numSections = $(modal.getBody()).find('#add_section_numsections'),\n addSections = function() {\n // Check if value of the \"Number of sections\" is a valid positive integer and redirect\n // to adding a section script.\n if ('' + parseInt(numSections.val()) === numSections.val() && parseInt(numSections.val()) >= 1) {\n document.location = trigger.attr('href') + '&numsections=' + parseInt(numSections.val());\n }\n };\n modal.setSaveButtonText(modalTitle);\n modal.getRoot().on(ModalEvents.shown, function() {\n // When modal is shown focus and select the input and add a listener to keypress of \"Enter\".\n numSections.focus().select().on('keydown', function(e) {\n if (e.keyCode === KeyCodes.enter) {\n addSections();\n }\n });\n });\n modal.getRoot().on(ModalEvents.save, function(e) {\n // When modal \"Add\" button is pressed.\n e.preventDefault();\n addSections();\n });\n });\n });\n },\n\n /**\n * Replaces a section action menu item with another one (for example Show->Hide, Set marker->Remove marker)\n *\n * This method can be used by course formats in their listener to the coursesectionedited event\n *\n * @deprecated since Moodle 3.9\n * @param {JQuery} sectionelement\n * @param {String} selector CSS selector inside the section element, for example \"a[data-action=show]\"\n * @param {String} image new image name (\"i/show\", \"i/hide\", etc.)\n * @param {String} stringname new string for the action menu item\n * @param {String} stringcomponent\n * @param {String} newaction new value for data-action attribute of the link\n */\n replaceSectionActionItem: function(sectionelement, selector, image, stringname,\n stringcomponent, newaction) {\n log.debug('replaceSectionActionItem() is deprecated and will be removed.');\n var actionitem = sectionelement.find(SELECTOR.SECTIONACTIONMENU + ' ' + selector);\n replaceActionItem(actionitem, image, stringname, stringcomponent, newaction);\n },\n // Method to refresh a module.\n refreshModule,\n };\n });\n"],"file":"actions.min.js"}
\ No newline at end of file
diff --git a/course/amd/src/actions.js b/course/amd/src/actions.js
index 109bac740db..3f2fdb0b6aa 100644
--- a/course/amd/src/actions.js
+++ b/course/amd/src/actions.js
@@ -102,6 +102,10 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
if (actionarea) {
var spinner = M.util.add_spinner(Y, Y.Node(actionarea));
spinner.show();
+ // Lock the activity state element.
+ if (activity.data('id') !== undefined) {
+ courseeditor.dispatch('cmLock', [activity.data('id')], true);
+ }
return spinner;
}
return null;
@@ -119,6 +123,10 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
if (actionarea) {
var spinner = M.util.add_spinner(Y, Y.Node(actionarea));
spinner.show();
+ // Lock the section state element.
+ if (sectionelement.data('id') !== undefined) {
+ courseeditor.dispatch('sectionLock', [sectionelement.data('id')], true);
+ }
return spinner;
}
return null;
@@ -149,6 +157,11 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
if (spinner) {
spinner.hide();
}
+ // Unlock the state element.
+ if (element.data('id') !== undefined) {
+ const mutation = (element.data('for') === 'section') ? 'sectionLock' : 'cmLock';
+ courseeditor.dispatch(mutation, [element.data('id')], false);
+ }
}, delay);
};
@@ -579,7 +592,19 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
return;
}
+ // Send the element is locked. Reactive events are only triggered when the state
+ // read only mode is restored. We want to notify the interface the element is
+ // locked so we need to do a quick lock operation before performing the rest
+ // of the mutation.
statemanager.setReadOnly(false);
+ cm.locked = true;
+ statemanager.setReadOnly(true);
+
+ // Now we do the real mutation.
+ statemanager.setReadOnly(false);
+
+ // This locked will take effect when the read only is restored.
+ cm.locked = false;
switch (action) {
case 'delete':
@@ -617,7 +642,19 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
return;
}
+ // Send the element is locked. Reactive events are only triggered when the state
+ // read only mode is restored. We want to notify the interface the element is
+ // locked so we need to do a quick lock operation before performing the rest
+ // of the mutation.
statemanager.setReadOnly(false);
+ section.locked = true;
+ statemanager.setReadOnly(true);
+
+ // Now we do the real mutation.
+ statemanager.setReadOnly(false);
+
+ // This locked will take effect when the read only is restored.
+ section.locked = false;
switch (action) {
case 'setmarker':
diff --git a/course/format/amd/build/local/content/section.min.js b/course/format/amd/build/local/content/section.min.js
index 175f41892ea..03849de3a35 100644
--- a/course/format/amd/build/local/content/section.min.js
+++ b/course/format/amd/build/local/content/section.min.js
@@ -1,2 +1,2 @@
-define ("core_courseformat/local/content/section",["exports","core_courseformat/local/content/section/header","core_courseformat/local/courseeditor/dndsection"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function g(a){for(var b=1,c;b .\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';\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 };\n // All classes will be loaded later by DndCmItem.\n this.classes = {};\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n }\n}\n"],"file":"section.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/local/content/section.js"],"names":["name","selectors","SECTION_ITEM","CM","classes","LOCKED","id","element","dataset","state","configState","reactive","isEditing","supportComponents","sectionItem","getElement","headerComponent","Header","fullregion","configDragDrop","watch","handler","_refreshSection","cms","getElements","length","classList","toggle","DRAGGING","dragging","locked","DndSection"],"mappings":"gQAwBA,OACA,O,8/EAOa,CAEL,KAAKA,IAAL,CAAY,iBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,YAAY,6BADC,CAEbC,EAAE,wBAFW,CAAjB,CAKA,KAAKC,OAAL,CAAe,CACXC,MAAM,CAAE,gBADG,CAAf,CAKA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,8CAOUG,C,CAAO,CACd,KAAKC,WAAL,CAAiBD,CAAjB,EAEA,GAAI,KAAKE,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,GAAMC,CAAAA,CAAW,CAAG,KAAKC,UAAL,CAAgB,KAAKd,SAAL,CAAeC,YAA/B,CAApB,CACA,GAAIY,CAAJ,CAAiB,CAEb,GAAME,CAAAA,CAAe,CAAG,GAAIC,UAAJ,MACjB,IADiB,EAEpBV,OAAO,CAAEO,CAFW,CAGpBI,UAAU,CAAE,KAAKX,OAHG,GAAxB,CAKA,KAAKY,cAAL,CAAoBH,CAApB,CACH,CACJ,CACJ,C,iDAOa,CACV,MAAO,CACH,CAACI,KAAK,mBAAa,KAAKd,EAAlB,aAAN,CAAuCe,OAAO,CAAE,KAAKC,eAArD,CADG,CAGV,C,6CAOW,CACR,GAAMC,CAAAA,CAAG,CAAG,KAAKC,WAAL,CAAiB,KAAKvB,SAAL,CAAeE,EAAhC,CAAZ,CAEA,GAAI,CAACoB,CAAD,EAAuB,CAAf,GAAAA,CAAG,CAACE,MAAhB,CAA8B,CAC1B,MAAO,KACV,CACD,MAAOF,CAAAA,CAAG,CAACA,CAAG,CAACE,MAAJ,CAAa,CAAd,CACb,C,0DAO0B,SAAVlB,CAAU,GAAVA,OAAU,CAEvB,KAAKA,OAAL,CAAamB,SAAb,CAAuBC,MAAvB,CAA8B,KAAKvB,OAAL,CAAawB,QAA3C,WAAqDrB,CAAO,CAACsB,QAA7D,oBACA,KAAKtB,OAAL,CAAamB,SAAb,CAAuBC,MAAvB,CAA8B,KAAKvB,OAAL,CAAaC,MAA3C,WAAmDE,CAAO,CAACuB,MAA3D,oBACA,KAAKA,MAAL,CAAcvB,CAAO,CAACuB,MACzB,C,cAhFwBC,S","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';\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 };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Initial state ready method.\n *\n * @param {Object} state the initial state\n */\n stateReady(state) {\n this.configState(state);\n // Drag and drop is only available for components compatible course formats.\n if (this.reactive.isEditing && this.reactive.supportComponents) {\n // Section zero and other formats sections may not have a title to drag.\n const sectionItem = this.getElement(this.selectors.SECTION_ITEM);\n if (sectionItem) {\n // Init the inner dragable element.\n const headerComponent = new Header({\n ...this,\n element: sectionItem,\n fullregion: this.element,\n });\n this.configDragDrop(headerComponent);\n }\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n const cms = this.getElements(this.selectors.CM);\n // DndUpload may add extra elements so :last-child selector cannot be used.\n if (!cms || cms.length === 0) {\n return null;\n }\n return cms[cms.length - 1];\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} 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.locked = element.locked;\n }\n}\n"],"file":"section.min.js"}
\ 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 bcf7af37a29..f55ba5be7f1 100644
--- a/course/format/amd/build/local/content/section/cmitem.min.js
+++ b/course/format/amd/build/local/content/section/cmitem.min.js
@@ -1,2 +1,2 @@
-define ("core_courseformat/local/content/section/cmitem",["exports","core_courseformat/local/courseeditor/dndcmitem"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c .\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 DRAGICON: `.editing_move`,\n };\n // All classes will be loaded later by DndCmItem.\n this.classes = {};\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 stateReady() {\n this.configDragDrop(this.id);\n this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON);\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 ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n }\n}"],"file":"cmitem.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../../src/local/content/section/cmitem.js"],"names":["name","selectors","DRAGICON","classes","LOCKED","id","element","dataset","configDragDrop","getElement","classList","add","watch","handler","unregister","_refreshCm","toggle","DRAGGING","dragging","locked","DndCmItem"],"mappings":"mNA0BA,uD,gwDAOa,CAEL,KAAKA,IAAL,CAAY,wBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,QAAQ,gBADK,CAAjB,CAIA,KAAKC,OAAL,CAAe,CACXC,MAAM,CAAE,gBADG,CAAf,CAIA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,+CAKY,OACT,KAAKG,cAAL,CAAoB,KAAKH,EAAzB,EACA,eAAKI,UAAL,CAAgB,KAAKR,SAAL,CAAeC,QAA/B,wBAA0CQ,SAA1C,CAAoDC,GAApD,CAAwD,KAAKR,OAAL,CAAaD,QAArE,CACH,C,iDAOa,CACV,MAAO,CACH,CAACU,KAAK,cAAQ,KAAKP,EAAb,aAAN,CAAkCQ,OAAO,CAAE,KAAKC,UAAhD,CADG,CAEH,CAACF,KAAK,cAAQ,KAAKP,EAAb,aAAN,CAAkCQ,OAAO,CAAE,KAAKE,UAAhD,CAFG,CAIV,C,gDAOqB,SAAVT,CAAU,GAAVA,OAAU,CAElB,KAAKA,OAAL,CAAaI,SAAb,CAAuBM,MAAvB,CAA8B,KAAKb,OAAL,CAAac,QAA3C,WAAqDX,CAAO,CAACY,QAA7D,oBACA,KAAKZ,OAAL,CAAaI,SAAb,CAAuBM,MAAvB,CAA8B,KAAKb,OAAL,CAAaC,MAA3C,WAAmDE,CAAO,CAACa,MAA3D,oBACA,KAAKA,MAAL,CAAcb,CAAO,CAACa,MACzB,C,cAlDwBC,S","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 DRAGICON: `.editing_move`,\n };\n // Most classes will be loaded later by DndCmItem.\n this.classes = {\n LOCKED: 'editinprogress',\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 stateReady() {\n this.configDragDrop(this.id);\n this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON);\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 ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {Object} 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}"],"file":"cmitem.min.js"}
\ No newline at end of file
diff --git a/course/format/amd/build/local/courseeditor/courseeditor.min.js b/course/format/amd/build/local/courseeditor/courseeditor.min.js
index 0d746db1d92..89a03362d34 100644
--- a/course/format/amd/build/local/courseeditor/courseeditor.min.js
+++ b/course/format/amd/build/local/courseeditor/courseeditor.min.js
@@ -1,2 +1,2 @@
-define ("core_courseformat/local/courseeditor/courseeditor",["exports","core/reactive","core/notification","core_courseformat/local/courseeditor/exporter","core/log","core/ajax","core/sessionstorage"],function(a,b,c,d,e,f,g){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;c=j(c);d=j(d);e=j(e);f=j(f);g=i(g);function h(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;h=function(){return a};return a}function i(a){if(a&&a.__esModule){return a}if(null===a||"object"!==k(a)&&"function"!=typeof a){return{default:a}}var b=h();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function j(a){return a&&a.__esModule?a:{default:a}}function k(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){k=function(a){return typeof a}}else{k=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return k(a)}function l(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function m(a){for(var b=1,c;b .\n\nimport {Reactive} from 'core/reactive';\nimport notification from 'core/notification';\nimport Exporter from 'core_courseformat/local/courseeditor/exporter';\nimport log from 'core/log';\nimport ajax from 'core/ajax';\nimport * as Storage from 'core/sessionstorage';\n\n/**\n * Main course editor module.\n *\n * All formats can register new components on this object to create new reactive\n * UI components that watch the current course state.\n *\n * @module core_courseformat/local/courseeditor/courseeditor\n * @class core_courseformat/local/courseeditor/courseeditor\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends Reactive {\n\n /**\n * The current state cache key\n *\n * The state cache is considered dirty if the state changes from the last page or\n * if the page has editing mode on.\n *\n * @attribute stateKey\n * @type number|null\n * @default 1\n * @package\n */\n stateKey = 1;\n\n /**\n * Set up the course editor when the page is ready.\n *\n * The course can only be loaded once per instance. Otherwise an error is thrown.\n *\n * @param {number} courseId course id\n */\n async loadCourse(courseId) {\n\n if (this.courseId) {\n throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`);\n }\n\n // Default view format setup.\n this._editing = false;\n this._supportscomponents = false;\n\n this.courseId = courseId;\n\n let stateData;\n\n try {\n stateData = await this.getServerCourseState();\n } catch (error) {\n log.error(\"EXCEPTION RAISED WHILE INIT COURSE EDITOR\");\n log.error(error);\n return;\n }\n\n this.setInitialState(stateData);\n\n // In editing mode, the session cache is considered dirty always.\n if (this.isEditing) {\n this.stateKey = null;\n } else {\n // Check if the last state is the same as the cached one.\n const newState = JSON.stringify(stateData);\n const previousState = Storage.get(`course/${courseId}/staticState`);\n if (previousState !== newState) {\n Storage.set(`course/${courseId}/staticState`, newState);\n Storage.set(`course/${courseId}/stateKey`, Date.now());\n }\n this.stateKey = Storage.get(`course/${courseId}/stateKey`);\n }\n }\n\n /**\n * Setup the current view settings\n *\n * @param {Object} setup format, page and course settings\n * @property {boolean} setup.editing if the page is in edit mode\n * @property {boolean} setup.supportscomponents if the format supports components for content\n */\n setViewFormat(setup) {\n this._editing = setup.editing ?? false;\n this._supportscomponents = setup.supportscomponents ?? false;\n }\n\n /**\n * Load the current course state from the server.\n *\n * @returns {Object} the current course state\n */\n async getServerCourseState() {\n const courseState = await ajax.call([{\n methodname: 'core_courseformat_get_state',\n args: {\n courseid: this.courseId,\n }\n }])[0];\n\n const stateData = JSON.parse(courseState);\n\n return {\n course: {},\n section: [],\n cm: [],\n ...stateData,\n };\n }\n\n /**\n * Return the current edit mode.\n *\n * Components should use this method to check if edit mode is active.\n *\n * @return {boolean} if edit is enabled\n */\n get isEditing() {\n return this._editing ?? false;\n }\n\n /**\n * Return a data exporter to transform state part into mustache contexts.\n *\n * @return {Exporter} the exporter class\n */\n getExporter() {\n return new Exporter(this);\n }\n\n /**\n * Return if the current course support components to refresh the content.\n *\n * @returns {boolean} if the current content support components\n */\n get supportComponents() {\n return this._supportscomponents ?? false;\n }\n\n /**\n * Get a value from the course editor static storage if any.\n *\n * The course editor static storage uses the sessionStorage to store values from the\n * components. This is used to prevent unnecesary template loadings on every page. However,\n * the storage does not work if no sessionStorage can be used (in debug mode for example),\n * if the page is in editing mode or if the initial state change from the last page.\n *\n * @param {string} key the key to get\n * @return {boolean|string} the storage value or false if cannot be loaded\n */\n getStorageValue(key) {\n if (this.isEditing || !this.stateKey) {\n return false;\n }\n const dataJson = Storage.get(`course/${this.courseId}/${key}`);\n if (!dataJson) {\n return false;\n }\n // Check the stateKey.\n try {\n const data = JSON.parse(dataJson);\n if (data?.stateKey !== this.stateKey) {\n return false;\n }\n return data.value;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Stores a value into the course editor static storage if available\n *\n * @param {String} key the key to store\n * @param {*} value the value to store (must be compatible with JSON,stringify)\n * @returns {boolean} true if the value is stored\n */\n setStorageValue(key, value) {\n // Values cannot be stored on edit mode.\n if (this.isEditing) {\n return false;\n }\n const data = {\n stateKey: this.stateKey,\n value,\n };\n return Storage.set(`course/${this.courseId}/${key}`, JSON.stringify(data));\n }\n\n /**\n * Dispatch a change in the state.\n *\n * Usually reactive modules throw an error directly to the components when something\n * goes wrong. However, course editor can directly display a notification.\n *\n * @method dispatch\n * @param {string} actionName the action name (usually the mutation name)\n * @param {*} param any number of params the mutation needs.\n */\n async dispatch(...args) {\n try {\n await super.dispatch(...args);\n } catch (error) {\n notification.exception(error);\n }\n }\n}\n"],"file":"courseeditor.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/local/courseeditor/courseeditor.js"],"names":["courseId","Error","_editing","_supportscomponents","getServerCourseState","stateData","log","error","setInitialState","isEditing","stateKey","newState","JSON","stringify","previousState","Storage","get","set","Date","now","setup","editing","supportscomponents","ajax","call","methodname","args","courseid","courseState","parse","course","section","cm","Exporter","key","dataJson","data","value","notification","exception","Reactive"],"mappings":"gTAgBA,OACA,OACA,OACA,OACA,O,muHA0Be,C,8FASMA,C,iGAET,KAAKA,Q,sBACC,IAAIC,CAAAA,KAAJ,uBAAyBD,CAAzB,4CAAoE,KAAKA,QAAzE,E,QAIV,KAAKE,QAAL,IACA,KAAKC,mBAAL,IAEA,KAAKH,QAAL,CAAgBA,CAAhB,C,wBAKsB,MAAKI,oBAAL,E,QAAlBC,C,6DAEAC,UAAIC,KAAJ,CAAU,2CAAV,EACAD,UAAIC,KAAJ,O,kCAIJ,KAAKC,eAAL,CAAqBH,CAArB,EAGA,GAAI,KAAKI,SAAT,CAAoB,CAChB,KAAKC,QAAL,CAAgB,IACnB,CAFD,IAEO,CAEGC,CAFH,CAEcC,IAAI,CAACC,SAAL,CAAeR,CAAf,CAFd,CAGGS,CAHH,CAGmBC,CAAO,CAACC,GAAR,kBAAsBhB,CAAtB,iBAHnB,CAIH,GAAIc,CAAa,GAAKH,CAAtB,CAAgC,CAC5BI,CAAO,CAACE,GAAR,kBAAsBjB,CAAtB,iBAA8CW,CAA9C,EACAI,CAAO,CAACE,GAAR,kBAAsBjB,CAAtB,cAA2CkB,IAAI,CAACC,GAAL,EAA3C,CACH,CACD,KAAKT,QAAL,CAAgBK,CAAO,CAACC,GAAR,kBAAsBhB,CAAtB,cACnB,C,6KAUSoB,C,CAAO,SACjB,KAAKlB,QAAL,WAAgBkB,CAAK,CAACC,OAAtB,mBACA,KAAKlB,mBAAL,WAA2BiB,CAAK,CAACE,kBAAjC,kBACH,C,oMAQ6BC,WAAKC,IAAL,CAAU,CAAC,CACjCC,UAAU,CAAE,6BADqB,CAEjCC,IAAI,CAAE,CACFC,QAAQ,CAAE,KAAK3B,QADb,CAF2B,CAAD,CAAV,EAKtB,CALsB,C,QAApB4B,C,QAOAvB,C,CAAYO,IAAI,CAACiB,KAAL,CAAWD,CAAX,C,6BAGdE,MAAM,CAAE,E,CACRC,OAAO,CAAE,E,CACTC,EAAE,CAAE,E,EACD3B,C,6KAoBG,CACV,MAAO,IAAI4B,UAAJ,CAAa,IAAb,CACV,C,wDAsBeC,C,CAAK,CACjB,GAAI,KAAKzB,SAAL,EAAkB,CAAC,KAAKC,QAA5B,CAAsC,CAClC,QACH,CACD,GAAMyB,CAAAA,CAAQ,CAAGpB,CAAO,CAACC,GAAR,kBAAsB,KAAKhB,QAA3B,aAAuCkC,CAAvC,EAAjB,CACA,GAAI,CAACC,CAAL,CAAe,CACX,QACH,CAED,GAAI,CACA,GAAMC,CAAAA,CAAI,CAAGxB,IAAI,CAACiB,KAAL,CAAWM,CAAX,CAAb,CACA,GAAI,QAAAC,CAAI,WAAJA,SAAAA,CAAI,CAAE1B,QAAN,IAAmB,KAAKA,QAA5B,CAAsC,CAClC,QACH,CACD,MAAO0B,CAAAA,CAAI,CAACC,KACf,CAAC,MAAO9B,CAAP,CAAc,CACZ,QACH,CACJ,C,wDASe2B,C,CAAKG,C,CAAO,CAExB,GAAI,KAAK5B,SAAT,CAAoB,CAChB,QACH,CACD,GAAM2B,CAAAA,CAAI,CAAG,CACT1B,QAAQ,CAAE,KAAKA,QADN,CAET2B,KAAK,CAALA,CAFS,CAAb,CAIA,MAAOtB,CAAAA,CAAO,CAACE,GAAR,kBAAsB,KAAKjB,QAA3B,aAAuCkC,CAAvC,EAA8CtB,IAAI,CAACC,SAAL,CAAeuB,CAAf,CAA9C,CACV,C,iNAYiBV,C,uBAAAA,C,yFAEcA,C,6DAGxBY,UAAaC,SAAb,OAEA,4CAAe,WAAf,E,2JAzFQ,OACZ,iBAAO,KAAKrC,QAAZ,kBACH,C,6CAgBuB,OACpB,iBAAO,KAAKC,mBAAZ,kBACH,C,cA3HwBqC,U","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {Reactive} from 'core/reactive';\nimport notification from 'core/notification';\nimport Exporter from 'core_courseformat/local/courseeditor/exporter';\nimport log from 'core/log';\nimport ajax from 'core/ajax';\nimport * as Storage from 'core/sessionstorage';\n\n/**\n * Main course editor module.\n *\n * All formats can register new components on this object to create new reactive\n * UI components that watch the current course state.\n *\n * @module core_courseformat/local/courseeditor/courseeditor\n * @class core_courseformat/local/courseeditor/courseeditor\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends Reactive {\n\n /**\n * The current state cache key\n *\n * The state cache is considered dirty if the state changes from the last page or\n * if the page has editing mode on.\n *\n * @attribute stateKey\n * @type number|null\n * @default 1\n * @package\n */\n stateKey = 1;\n\n /**\n * Set up the course editor when the page is ready.\n *\n * The course can only be loaded once per instance. Otherwise an error is thrown.\n *\n * @param {number} courseId course id\n */\n async loadCourse(courseId) {\n\n if (this.courseId) {\n throw new Error(`Cannot load ${courseId}, course already loaded with id ${this.courseId}`);\n }\n\n // Default view format setup.\n this._editing = false;\n this._supportscomponents = false;\n\n this.courseId = courseId;\n\n let stateData;\n\n try {\n stateData = await this.getServerCourseState();\n } catch (error) {\n log.error(\"EXCEPTION RAISED WHILE INIT COURSE EDITOR\");\n log.error(error);\n return;\n }\n\n this.setInitialState(stateData);\n\n // In editing mode, the session cache is considered dirty always.\n if (this.isEditing) {\n this.stateKey = null;\n } else {\n // Check if the last state is the same as the cached one.\n const newState = JSON.stringify(stateData);\n const previousState = Storage.get(`course/${courseId}/staticState`);\n if (previousState !== newState) {\n Storage.set(`course/${courseId}/staticState`, newState);\n Storage.set(`course/${courseId}/stateKey`, Date.now());\n }\n this.stateKey = Storage.get(`course/${courseId}/stateKey`);\n }\n }\n\n /**\n * Setup the current view settings\n *\n * @param {Object} setup format, page and course settings\n * @property {boolean} setup.editing if the page is in edit mode\n * @property {boolean} setup.supportscomponents if the format supports components for content\n */\n setViewFormat(setup) {\n this._editing = setup.editing ?? false;\n this._supportscomponents = setup.supportscomponents ?? false;\n }\n\n /**\n * Load the current course state from the server.\n *\n * @returns {Object} the current course state\n */\n async getServerCourseState() {\n const courseState = await ajax.call([{\n methodname: 'core_courseformat_get_state',\n args: {\n courseid: this.courseId,\n }\n }])[0];\n\n const stateData = JSON.parse(courseState);\n\n return {\n course: {},\n section: [],\n cm: [],\n ...stateData,\n };\n }\n\n /**\n * Return the current edit mode.\n *\n * Components should use this method to check if edit mode is active.\n *\n * @return {boolean} if edit is enabled\n */\n get isEditing() {\n return this._editing ?? false;\n }\n\n /**\n * Return a data exporter to transform state part into mustache contexts.\n *\n * @return {Exporter} the exporter class\n */\n getExporter() {\n return new Exporter(this);\n }\n\n /**\n * Return if the current course support components to refresh the content.\n *\n * @returns {boolean} if the current content support components\n */\n get supportComponents() {\n return this._supportscomponents ?? false;\n }\n\n /**\n * Get a value from the course editor static storage if any.\n *\n * The course editor static storage uses the sessionStorage to store values from the\n * components. This is used to prevent unnecesary template loadings on every page. However,\n * the storage does not work if no sessionStorage can be used (in debug mode for example),\n * if the page is in editing mode or if the initial state change from the last page.\n *\n * @param {string} key the key to get\n * @return {boolean|string} the storage value or false if cannot be loaded\n */\n getStorageValue(key) {\n if (this.isEditing || !this.stateKey) {\n return false;\n }\n const dataJson = Storage.get(`course/${this.courseId}/${key}`);\n if (!dataJson) {\n return false;\n }\n // Check the stateKey.\n try {\n const data = JSON.parse(dataJson);\n if (data?.stateKey !== this.stateKey) {\n return false;\n }\n return data.value;\n } catch (error) {\n return false;\n }\n }\n\n /**\n * Stores a value into the course editor static storage if available\n *\n * @param {String} key the key to store\n * @param {*} value the value to store (must be compatible with JSON,stringify)\n * @returns {boolean} true if the value is stored\n */\n setStorageValue(key, value) {\n // Values cannot be stored on edit mode.\n if (this.isEditing) {\n return false;\n }\n const data = {\n stateKey: this.stateKey,\n value,\n };\n return Storage.set(`course/${this.courseId}/${key}`, JSON.stringify(data));\n }\n\n /**\n * Dispatch a change in the state.\n *\n * Usually reactive modules throw an error directly to the components when something\n * goes wrong. However, course editor can directly display a notification.\n *\n * @method dispatch\n * @param {string} actionName the action name (usually the mutation name)\n * @param {*} param any number of params the mutation needs.\n */\n async dispatch(...args) {\n try {\n await super.dispatch(...args);\n } catch (error) {\n // Display error modal.\n notification.exception(error);\n // Force unlock all elements.\n super.dispatch('unlockAll');\n }\n }\n}\n"],"file":"courseeditor.min.js"}
\ No newline at end of file
diff --git a/course/format/amd/build/local/courseeditor/mutations.min.js b/course/format/amd/build/local/courseeditor/mutations.min.js
index 972730528a6..203a3e3a7e1 100644
--- a/course/format/amd/build/local/courseeditor/mutations.min.js
+++ b/course/format/amd/build/local/courseeditor/mutations.min.js
@@ -1,2 +1,2 @@
-define ("core_courseformat/local/courseeditor/mutations",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function d(a){return function(){var b=this,d=arguments;return new Promise(function(e,f){var i=a.apply(b,d);function g(a){c(i,e,f,g,h,"next",a)}function h(a){c(i,e,f,g,h,"throw",a)}g(void 0)})}}function e(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function f(a,b){for(var c=0,d;c .\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/local/courseeditor/mutations.js"],"names":["action","courseId","ids","targetSectionId","targetCmId","args","courseid","targetsectionid","targetcmid","ajax","call","methodname","ajaxresult","JSON","parse","stateManager","addUpdateTypes","prepareFields","_prepareFields","updateName","fields","locked","cmids","Error","course","get","cmLock","_callEditWebservice","id","updates","processUpdates","sectionIds","sectionLock","cmIds","dragValue","_setElementsValue","lockValue","name","fieldName","newValue","setReadOnly","forEach","element","state","section","cm"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport ajax from 'core/ajax';\n\n/**\n * Default mutation manager\n *\n * @module core_courseformat/local/courseeditor/mutations\n * @class core_courseformat/local/courseeditor/mutations\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n // All course editor mutations for Moodle 4.0 will be located in this file.\n\n /**\n * Private method to call core_courseformat_update_course webservice.\n *\n * @method _callEditWebservice\n * @param {string} action\n * @param {number} courseId\n * @param {array} ids\n * @param {number} targetSectionId optional target section id (for moving actions)\n * @param {number} targetCmId optional target cm id (for moving actions)\n */\n async _callEditWebservice(action, courseId, ids, targetSectionId, targetCmId) {\n const args = {\n action,\n courseid: courseId,\n ids,\n };\n if (targetSectionId) {\n args.targetsectionid = targetSectionId;\n }\n if (targetCmId) {\n args.targetcmid = targetCmId;\n }\n let ajaxresult = await ajax.call([{\n methodname: 'core_courseformat_update_course',\n args,\n }])[0];\n return JSON.parse(ajaxresult);\n }\n\n\n /**\n * Mutation module initialize.\n *\n * The reactive instance will execute this method when addMutations or setMutation is invoked.\n *\n * @param {StateManager} stateManager the state manager\n */\n init(stateManager) {\n // Add a method to prepare the fields when some update is comming from the server.\n stateManager.addUpdateTypes({\n prepareFields: this._prepareFields,\n });\n }\n\n /**\n * Add default values to state elements.\n *\n * This method is called every time a webservice returns a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n _prepareFields(stateManager, updateName, fields) {\n // Any update should unlock the element.\n fields.locked = false;\n return fields;\n }\n\n /**\n * Move course modules to specific course location.\n *\n * Note that one of targetSectionId or targetCmId should be provided in order to identify the\n * new location:\n * - targetCmId: the activities will be located avobe the target cm. The targetSectionId\n * value will be ignored in this case.\n * - targetSectionId: the activities will be appended to the section. In this case\n * targetSectionId should not be present.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmids the list of cm ids to move\n * @param {number} targetSectionId the target section id\n * @param {number} targetCmId the target course module id\n */\n async cmMove(stateManager, cmids, targetSectionId, targetCmId) {\n if (!targetSectionId && !targetCmId) {\n throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);\n }\n const course = stateManager.get('course');\n this.cmLock(stateManager, cmids, true);\n const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Move course modules to specific course location.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids to move\n * @param {number} targetSectionId the target section id\n */\n async sectionMove(stateManager, sectionIds, targetSectionId) {\n if (!targetSectionId) {\n throw new Error(`Mutation sectionMove requires targetSectionId`);\n }\n const course = stateManager.get('course');\n this.sectionLock(stateManager, sectionIds, true);\n const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Mark or unmark course modules as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} dragValue the new dragging value\n */\n cmDrag(stateManager, cmIds, dragValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'dragging', dragValue);\n }\n\n /**\n * Mark or unmark course sections as dragging.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} dragValue the new dragging value\n */\n sectionDrag(stateManager, sectionIds, dragValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);\n }\n\n /**\n * Lock or unlock course modules.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} cmIds the list of course modules ids\n * @param {bool} lockValue the new locked value\n */\n cmLock(stateManager, cmIds, lockValue) {\n this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);\n }\n\n /**\n * Lock or unlock course sections.\n *\n * @param {StateManager} stateManager the current state manager\n * @param {array} sectionIds the list of section ids\n * @param {bool} lockValue the new locked value\n */\n sectionLock(stateManager, sectionIds, lockValue) {\n this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);\n }\n\n _setElementsValue(stateManager, name, ids, fieldName, newValue) {\n stateManager.setReadOnly(false);\n ids.forEach((id) => {\n const element = stateManager.get(name, id);\n if (element) {\n element[fieldName] = newValue;\n }\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Unlock all course elements.\n *\n * @param {StateManager} stateManager the current state manager\n */\n unlockAll(stateManager) {\n const state = stateManager.state;\n stateManager.setReadOnly(false);\n state.section.forEach((section) => {\n section.locked = false;\n });\n state.cm.forEach((cm) => {\n cm.locked = false;\n });\n stateManager.setReadOnly(true);\n }\n\n /**\n * Get updated state data related to some cm ids.\n *\n * @method cmState\n * @param {StateManager} stateManager the current state\n * @param {array} cmids the list of cm ids to update\n */\n async cmState(stateManager, cmids) {\n this.cmLock(stateManager, cmids, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('cm_state', course.id, cmids);\n stateManager.processUpdates(updates);\n this.cmLock(stateManager, cmids, false);\n }\n\n /**\n * Get updated state data related to some section ids.\n *\n * @method sectionState\n * @param {StateManager} stateManager the current state\n * @param {array} sectionIds the list of section ids to update\n */\n async sectionState(stateManager, sectionIds) {\n this.sectionLock(stateManager, sectionIds, true);\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('section_state', course.id, sectionIds);\n stateManager.processUpdates(updates);\n this.sectionLock(stateManager, sectionIds, false);\n }\n\n /**\n * Get the full updated state data of the course.\n *\n * @param {StateManager} stateManager the current state\n */\n async courseState(stateManager) {\n const course = stateManager.get('course');\n const updates = await this._callEditWebservice('course_state', course.id);\n stateManager.processUpdates(updates);\n }\n\n}\n"],"file":"mutations.min.js"}
\ No newline at end of file
diff --git a/course/format/amd/build/local/courseindex/cm.min.js b/course/format/amd/build/local/courseindex/cm.min.js
index a481627cb34..35376b27afd 100644
--- a/course/format/amd/build/local/courseindex/cm.min.js
+++ b/course/format/amd/build/local/courseindex/cm.min.js
@@ -1,2 +1,2 @@
-define ("core_courseformat/local/courseindex/cm",["exports","core_courseformat/local/courseeditor/dndcmitem"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c .\n\n/**\n * Course index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\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 Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n this.configDragDrop(this.id);\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.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n }\n\n}\n"],"file":"cm.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/local/courseindex/cm.js"],"names":["Component","name","selectors","CM_NAME","classes","CMHIDDEN","LOCKED","id","element","dataset","configDragDrop","watch","handler","remove","_refreshCm","classList","toggle","visible","getElement","innerHTML","DRAGGING","dragging","locked","target","document","getElementById","DndCmItem"],"mappings":"2MA0BA,uD,+nDAEqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,gBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,OAAO,uBADM,CAAjB,CAIA,KAAKC,OAAL,CAAe,CACXC,QAAQ,CAAE,QADC,CAEXC,MAAM,CAAE,gBAFG,CAAf,CAKA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,+CAmBY,CACT,KAAKG,cAAL,CAAoB,KAAKH,EAAzB,CACH,C,iDAOa,CACV,MAAO,CACH,CAACI,KAAK,cAAQ,KAAKJ,EAAb,aAAN,CAAkCK,OAAO,CAAE,KAAKC,MAAhD,CADG,CAEH,CAACF,KAAK,cAAQ,KAAKJ,EAAb,aAAN,CAAkCK,OAAO,CAAE,KAAKE,UAAhD,CAFG,CAIV,C,gDAOqB,SAAVN,CAAU,GAAVA,OAAU,CAElB,KAAKA,OAAL,CAAaO,SAAb,CAAuBC,MAAvB,CAA8B,KAAKZ,OAAL,CAAaC,QAA3C,CAAqD,CAACG,CAAO,CAACS,OAA9D,EACA,KAAKC,UAAL,CAAgB,KAAKhB,SAAL,CAAeC,OAA/B,EAAwCgB,SAAxC,CAAoDX,CAAO,CAACP,IAA5D,CACA,KAAKO,OAAL,CAAaO,SAAb,CAAuBC,MAAvB,CAA8B,KAAKZ,OAAL,CAAagB,QAA3C,WAAqDZ,CAAO,CAACa,QAA7D,oBACA,KAAKb,OAAL,CAAaO,SAAb,CAAuBC,MAAvB,CAA8B,KAAKZ,OAAL,CAAaE,MAA3C,WAAmDE,CAAO,CAACc,MAA3D,oBACA,KAAKA,MAAL,CAAcd,CAAO,CAACc,MACzB,C,oCAtCWC,C,CAAQrB,C,CAAW,CAC3B,MAAO,IAAIF,CAAAA,CAAJ,CAAc,CACjBQ,OAAO,CAAEgB,QAAQ,CAACC,cAAT,CAAwBF,CAAxB,CADQ,CAEjBrB,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cAjCkCwB,S","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 index cm component.\n *\n * This component is used to control specific course modules interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/cm\n * @class core_courseformat/local/courseindex/cm\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 Component extends DndCmItem {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_cm';\n // Default query selectors.\n this.selectors = {\n CM_NAME: `[data-for='cm_name']`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n CMHIDDEN: 'dimmed',\n LOCKED: 'editinprogress',\n };\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n this.configDragDrop(this.id);\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.remove},\n {watch: `cm[${this.id}]:updated`, handler: this._refreshCm},\n ];\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshCm({element}) {\n // Update classes.\n this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n this.getElement(this.selectors.CM_NAME).innerHTML = element.name;\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"],"file":"cm.min.js"}
\ No newline at end of file
diff --git a/course/format/amd/build/local/courseindex/section.min.js b/course/format/amd/build/local/courseindex/section.min.js
index 01e5a76b00c..4a1ce94cb05 100644
--- a/course/format/amd/build/local/courseindex/section.min.js
+++ b/course/format/amd/build/local/courseindex/section.min.js
@@ -1,2 +1,2 @@
-define ("core_courseformat/local/courseindex/section",["exports","core_courseformat/local/courseindex/sectiontitle","core_courseformat/local/courseeditor/dndsection"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function g(a){for(var b=1,c;b .\n\n/**\n * Course index section component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/section\n * @class core_courseformat/local/courseindex/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class Component extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_item']`,\n SECTION_TITLE: `[data-for='section_title']`,\n CM_LAST: `[data-for=\"cm\"]:last-child`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\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 // Init the inner dragable element passing the full section as affected region.\n const titleitem = new SectionTitle({\n ...this,\n element: this.getElement(this.selectors.SECTION_ITEM),\n fullregion: this.element,\n });\n this.configDragDrop(titleitem);\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n return this.getElement(this.selectors.CM_LAST);\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n const sectionitem = this.getElement(this.selectors.SECTION_ITEM);\n sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);\n this.element.classList.toggle(this.classes.SECTIONCURRENT, element.current);\n this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);\n // Update title.\n this.getElement(this.selectors.SECTION_TITLE).innerHTML = element.title;\n }\n}\n"],"file":"section.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/local/courseindex/section.js"],"names":["Component","name","selectors","SECTION_ITEM","SECTION_TITLE","CM_LAST","classes","SECTIONHIDDEN","SECTIONCURRENT","LOCKED","id","element","dataset","state","configState","reactive","isEditing","supportComponents","titleitem","SectionTitle","getElement","fullregion","configDragDrop","watch","handler","_refreshSection","sectionitem","classList","toggle","visible","current","DRAGGING","dragging","locked","innerHTML","title","target","document","getElementById","DndSection"],"mappings":"sQA0BA,OACA,O,63EAEqBA,CAAAA,C,+HAKR,CAEL,KAAKC,IAAL,CAAY,qBAAZ,CAEA,KAAKC,SAAL,CAAiB,CACbC,YAAY,4BADC,CAEbC,aAAa,6BAFA,CAGbC,OAAO,+BAHM,CAAjB,CAMA,KAAKC,OAAL,CAAe,CACXC,aAAa,CAAE,QADJ,CAEXC,cAAc,CAAE,SAFL,CAGXC,MAAM,CAAE,gBAHG,CAAf,CAOA,KAAKC,EAAL,CAAU,KAAKC,OAAL,CAAaC,OAAb,CAAqBF,EAClC,C,8CAqBUG,C,CAAO,CACd,KAAKC,WAAL,CAAiBD,CAAjB,EAEA,GAAI,KAAKE,QAAL,CAAcC,SAAd,EAA2B,KAAKD,QAAL,CAAcE,iBAA7C,CAAgE,CAE5D,GAAMC,CAAAA,CAAS,CAAG,GAAIC,UAAJ,MACX,IADW,EAEdR,OAAO,CAAE,KAAKS,UAAL,CAAgB,KAAKlB,SAAL,CAAeC,YAA/B,CAFK,CAGdkB,UAAU,CAAE,KAAKV,OAHH,GAAlB,CAKA,KAAKW,cAAL,CAAoBJ,CAApB,CACH,CACJ,C,iDAOa,CACV,MAAO,CACH,CAACK,KAAK,mBAAa,KAAKb,EAAlB,aAAN,CAAuCc,OAAO,CAAE,KAAKC,eAArD,CADG,CAGV,C,6CAOW,CACR,MAAO,MAAKL,UAAL,CAAgB,KAAKlB,SAAL,CAAeG,OAA/B,CACV,C,0DAO0B,SAAVM,CAAU,GAAVA,OAAU,CAEjBe,CAAW,CAAG,KAAKN,UAAL,CAAgB,KAAKlB,SAAL,CAAeC,YAA/B,CAFG,CAGvBuB,CAAW,CAACC,SAAZ,CAAsBC,MAAtB,CAA6B,KAAKtB,OAAL,CAAaC,aAA1C,CAAyD,CAACI,CAAO,CAACkB,OAAlE,EACA,KAAKlB,OAAL,CAAagB,SAAb,CAAuBC,MAAvB,CAA8B,KAAKtB,OAAL,CAAaE,cAA3C,CAA2DG,CAAO,CAACmB,OAAnE,EACA,KAAKnB,OAAL,CAAagB,SAAb,CAAuBC,MAAvB,CAA8B,KAAKtB,OAAL,CAAayB,QAA3C,WAAqDpB,CAAO,CAACqB,QAA7D,oBACA,KAAKrB,OAAL,CAAagB,SAAb,CAAuBC,MAAvB,CAA8B,KAAKtB,OAAL,CAAaG,MAA3C,WAAmDE,CAAO,CAACsB,MAA3D,oBACA,KAAKA,MAAL,CAActB,CAAO,CAACsB,MAAtB,CAEA,KAAKb,UAAL,CAAgB,KAAKlB,SAAL,CAAeE,aAA/B,EAA8C8B,SAA9C,CAA0DvB,CAAO,CAACwB,KACrE,C,oCA7DWC,C,CAAQlC,C,CAAW,CAC3B,MAAO,IAAIF,CAAAA,CAAJ,CAAc,CACjBW,OAAO,CAAE0B,QAAQ,CAACC,cAAT,CAAwBF,CAAxB,CADQ,CAEjBlC,SAAS,CAATA,CAFiB,CAAd,CAIV,C,cArCkCqC,S","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 index section component.\n *\n * This component is used to control specific course section interactions like drag and drop.\n *\n * @module core_courseformat/local/courseindex/section\n * @class core_courseformat/local/courseindex/section\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport SectionTitle from 'core_courseformat/local/courseindex/sectiontitle';\nimport DndSection from 'core_courseformat/local/courseeditor/dndsection';\n\nexport default class Component extends DndSection {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex_section';\n // Default query selectors.\n this.selectors = {\n SECTION_ITEM: `[data-for='section_item']`,\n SECTION_TITLE: `[data-for='section_title']`,\n CM_LAST: `[data-for=\"cm\"]:last-child`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n LOCKED: 'editinprogress',\n };\n\n // We need our id to watch specific events.\n this.id = this.element.dataset.id;\n }\n\n /**\n * Static method to create a component instance form the mustahce template.\n *\n * @param {string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n selectors,\n });\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 // Init the inner dragable element passing the full section as affected region.\n const titleitem = new SectionTitle({\n ...this,\n element: this.getElement(this.selectors.SECTION_ITEM),\n fullregion: this.element,\n });\n this.configDragDrop(titleitem);\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `section[${this.id}]:updated`, handler: this._refreshSection},\n ];\n }\n\n /**\n * Get the last CM element of that section.\n *\n * @returns {element|null}\n */\n getLastCm() {\n return this.getElement(this.selectors.CM_LAST);\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshSection({element}) {\n // Update classes.\n const sectionitem = this.getElement(this.selectors.SECTION_ITEM);\n sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);\n this.element.classList.toggle(this.classes.SECTIONCURRENT, element.current);\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 // Update title.\n this.getElement(this.selectors.SECTION_TITLE).innerHTML = element.title;\n }\n}\n"],"file":"section.min.js"}
\ 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 e7b7b242c71..c449da58667 100644
--- a/course/format/amd/src/local/content/section.js
+++ b/course/format/amd/src/local/content/section.js
@@ -38,8 +38,10 @@ export default class extends DndSection {
SECTION_ITEM: `[data-for='section_title']`,
CM: `[data-for="cmitem"]`,
};
- // All classes will be loaded later by DndCmItem.
- this.classes = {};
+ // Most classes will be loaded later by DndCmItem.
+ this.classes = {
+ LOCKED: 'editinprogress',
+ };
// We need our id to watch specific events.
this.id = this.element.dataset.id;
@@ -101,5 +103,7 @@ export default class extends DndSection {
_refreshSection({element}) {
// Update classes.
this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);
+ this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);
+ this.locked = element.locked;
}
}
diff --git a/course/format/amd/src/local/content/section/cmitem.js b/course/format/amd/src/local/content/section/cmitem.js
index f0aa05f73a5..8b729d5b40c 100644
--- a/course/format/amd/src/local/content/section/cmitem.js
+++ b/course/format/amd/src/local/content/section/cmitem.js
@@ -38,8 +38,10 @@ export default class extends DndCmItem {
this.selectors = {
DRAGICON: `.editing_move`,
};
- // All classes will be loaded later by DndCmItem.
- this.classes = {};
+ // Most classes will be loaded later by DndCmItem.
+ this.classes = {
+ LOCKED: 'editinprogress',
+ };
// We need our id to watch specific events.
this.id = this.element.dataset.id;
}
@@ -72,5 +74,7 @@ export default class extends DndCmItem {
_refreshCm({element}) {
// Update classes.
this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);
+ this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);
+ this.locked = element.locked;
}
}
\ No newline at end of file
diff --git a/course/format/amd/src/local/courseeditor/courseeditor.js b/course/format/amd/src/local/courseeditor/courseeditor.js
index ff85f0a845c..a0a4db4c5d3 100644
--- a/course/format/amd/src/local/courseeditor/courseeditor.js
+++ b/course/format/amd/src/local/courseeditor/courseeditor.js
@@ -220,7 +220,10 @@ export default class extends Reactive {
try {
await super.dispatch(...args);
} catch (error) {
+ // Display error modal.
notification.exception(error);
+ // Force unlock all elements.
+ super.dispatch('unlockAll');
}
}
}
diff --git a/course/format/amd/src/local/courseeditor/mutations.js b/course/format/amd/src/local/courseeditor/mutations.js
index feecf413d16..a1ba176bd37 100644
--- a/course/format/amd/src/local/courseeditor/mutations.js
+++ b/course/format/amd/src/local/courseeditor/mutations.js
@@ -56,6 +56,37 @@ export default class {
return JSON.parse(ajaxresult);
}
+
+ /**
+ * Mutation module initialize.
+ *
+ * The reactive instance will execute this method when addMutations or setMutation is invoked.
+ *
+ * @param {StateManager} stateManager the state manager
+ */
+ init(stateManager) {
+ // Add a method to prepare the fields when some update is comming from the server.
+ stateManager.addUpdateTypes({
+ prepareFields: this._prepareFields,
+ });
+ }
+
+ /**
+ * Add default values to state elements.
+ *
+ * This method is called every time a webservice returns a update state message.
+ *
+ * @param {Object} stateManager the state manager
+ * @param {String} updateName the state element to update
+ * @param {Object} fields the new data
+ * @returns {Object} final fields data
+ */
+ _prepareFields(stateManager, updateName, fields) {
+ // Any update should unlock the element.
+ fields.locked = false;
+ return fields;
+ }
+
/**
* Move course modules to specific course location.
*
@@ -76,8 +107,10 @@ export default class {
throw new Error(`Mutation cmMove requires targetSectionId or targetCmId`);
}
const course = stateManager.get('course');
+ this.cmLock(stateManager, cmids, true);
const updates = await this._callEditWebservice('cm_move', course.id, cmids, targetSectionId, targetCmId);
stateManager.processUpdates(updates);
+ this.cmLock(stateManager, cmids, false);
}
/**
@@ -92,8 +125,10 @@ export default class {
throw new Error(`Mutation sectionMove requires targetSectionId`);
}
const course = stateManager.get('course');
+ this.sectionLock(stateManager, sectionIds, true);
const updates = await this._callEditWebservice('section_move', course.id, sectionIds, targetSectionId);
stateManager.processUpdates(updates);
+ this.sectionLock(stateManager, sectionIds, false);
}
/**
@@ -118,6 +153,28 @@ export default class {
this._setElementsValue(stateManager, 'section', sectionIds, 'dragging', dragValue);
}
+ /**
+ * Lock or unlock course modules.
+ *
+ * @param {StateManager} stateManager the current state manager
+ * @param {array} cmIds the list of course modules ids
+ * @param {bool} lockValue the new locked value
+ */
+ cmLock(stateManager, cmIds, lockValue) {
+ this._setElementsValue(stateManager, 'cm', cmIds, 'locked', lockValue);
+ }
+
+ /**
+ * Lock or unlock course sections.
+ *
+ * @param {StateManager} stateManager the current state manager
+ * @param {array} sectionIds the list of section ids
+ * @param {bool} lockValue the new locked value
+ */
+ sectionLock(stateManager, sectionIds, lockValue) {
+ this._setElementsValue(stateManager, 'section', sectionIds, 'locked', lockValue);
+ }
+
_setElementsValue(stateManager, name, ids, fieldName, newValue) {
stateManager.setReadOnly(false);
ids.forEach((id) => {
@@ -129,6 +186,23 @@ export default class {
stateManager.setReadOnly(true);
}
+ /**
+ * Unlock all course elements.
+ *
+ * @param {StateManager} stateManager the current state manager
+ */
+ unlockAll(stateManager) {
+ const state = stateManager.state;
+ stateManager.setReadOnly(false);
+ state.section.forEach((section) => {
+ section.locked = false;
+ });
+ state.cm.forEach((cm) => {
+ cm.locked = false;
+ });
+ stateManager.setReadOnly(true);
+ }
+
/**
* Get updated state data related to some cm ids.
*
@@ -137,9 +211,11 @@ export default class {
* @param {array} cmids the list of cm ids to update
*/
async cmState(stateManager, cmids) {
+ this.cmLock(stateManager, cmids, true);
const course = stateManager.get('course');
const updates = await this._callEditWebservice('cm_state', course.id, cmids);
stateManager.processUpdates(updates);
+ this.cmLock(stateManager, cmids, false);
}
/**
@@ -150,9 +226,11 @@ export default class {
* @param {array} sectionIds the list of section ids to update
*/
async sectionState(stateManager, sectionIds) {
+ this.sectionLock(stateManager, sectionIds, true);
const course = stateManager.get('course');
const updates = await this._callEditWebservice('section_state', course.id, sectionIds);
stateManager.processUpdates(updates);
+ this.sectionLock(stateManager, sectionIds, false);
}
/**
diff --git a/course/format/amd/src/local/courseindex/cm.js b/course/format/amd/src/local/courseindex/cm.js
index a147a7e99f3..07d81c71d97 100644
--- a/course/format/amd/src/local/courseindex/cm.js
+++ b/course/format/amd/src/local/courseindex/cm.js
@@ -41,6 +41,7 @@ export default class Component extends DndCmItem {
// Default classes to toggle on refresh.
this.classes = {
CMHIDDEN: 'dimmed',
+ LOCKED: 'editinprogress',
};
// We need our id to watch specific events.
this.id = this.element.dataset.id;
@@ -89,6 +90,8 @@ export default class Component extends DndCmItem {
this.element.classList.toggle(this.classes.CMHIDDEN, !element.visible);
this.getElement(this.selectors.CM_NAME).innerHTML = element.name;
this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);
+ this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);
+ this.locked = element.locked;
}
}
diff --git a/course/format/amd/src/local/courseindex/section.js b/course/format/amd/src/local/courseindex/section.js
index 354d65d3ddb..81b74bf390a 100644
--- a/course/format/amd/src/local/courseindex/section.js
+++ b/course/format/amd/src/local/courseindex/section.js
@@ -45,6 +45,7 @@ export default class Component extends DndSection {
this.classes = {
SECTIONHIDDEN: 'dimmed',
SECTIONCURRENT: 'current',
+ LOCKED: 'editinprogress',
};
// We need our id to watch specific events.
@@ -115,6 +116,8 @@ export default class Component extends DndSection {
sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);
this.element.classList.toggle(this.classes.SECTIONCURRENT, element.current);
this.element.classList.toggle(this.classes.DRAGGING, element.dragging ?? false);
+ this.element.classList.toggle(this.classes.LOCKED, element.locked ?? false);
+ this.locked = element.locked;
// Update title.
this.getElement(this.selectors.SECTION_TITLE).innerHTML = element.title;
}
diff --git a/lib/amd/build/local/reactive/basecomponent.min.js b/lib/amd/build/local/reactive/basecomponent.min.js
index de4b8f20738..b854a6fb4f1 100644
--- a/lib/amd/build/local/reactive/basecomponent.min.js
+++ b/lib/amd/build/local/reactive/basecomponent.min.js
@@ -1,2 +1,2 @@
-define ("core/local/reactive/basecomponent",["exports","core/templates"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a,b){return h(a)||g(a,b)||e(a,b)||d()}function d(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function e(a,b){if(!a)return;if("string"==typeof a)return f(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return f(a,b)}function f(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c .\n\nimport Templates from 'core/templates';\n\n/**\n * Reactive UI component base class.\n *\n * Each UI reactive component should extend this class to interact with a reactive state.\n *\n * @module core/local/reactive/basecomponent\n * @class core/local/reactive/basecomponent\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * This structure is used by any component and init method to define the way the component will interact\n * with the interface and whith reactive instance operates. The logic behind this object is to avoid\n * unnecessary dependancies between the final interface and the state logic.\n *\n * Any component interacts with a single main DOM element (description.element) but it can use internal\n * selector to select elements within this main element (descriptor.selectors). By default each component\n * will provide it's own default selectors, but those can be overridden by the \"descriptor.selectors\"\n * property in case the mustache wants to reuse the same component logic but with a different interface.\n *\n * @typedef {object} descriptor\n * @property {Reactive} reactive an optional reactive module to register in\n * @property {DOMElement} element all components needs an element to anchor events\n * @property {object} [selectors] an optional object to override query selectors\n */\n\n /**\n * The class constructor.\n *\n * The only param this method gets is a constructor with all the mandatory\n * and optional component data. Component will receive the same descriptor\n * as create method param.\n *\n * This method will call the \"create\" method before registering the component into\n * the reactive module. This way any component can add default selectors and events.\n *\n * @param {descriptor} descriptor data to create the object.\n */\n constructor(descriptor) {\n\n if (descriptor.element === undefined || !(descriptor.element instanceof HTMLElement)) {\n throw Error(`Reactive components needs a main DOM element to dispatch events`);\n }\n\n this.element = descriptor.element;\n\n // Variable to track event listeners.\n this.eventHandlers = new Map([]);\n this.eventListeners = [];\n\n // Empty default component selectors.\n this.selectors = {};\n\n // Empty default event list from the static method.\n this.events = this.constructor.getEvents();\n\n // Call create function to get the component defaults.\n this.create(descriptor);\n\n // Overwrite the components selectors if necessary.\n if (descriptor.selectors !== undefined) {\n this.addSelectors(descriptor.selectors);\n }\n\n // Register into a reactive instance.\n if (descriptor.reactive === undefined) {\n // Ask parent components for registration.\n this.element.dispatchEvent(new CustomEvent(\n 'core/reactive:requestRegistration',\n {\n bubbles: true,\n detail: {component: this},\n }\n ));\n } else {\n this.reactive = descriptor.reactive;\n this.reactive.registerComponent(this);\n // Add a listener to register child components.\n this.addEventListener(\n this.element,\n 'core/reactive:requestRegistration',\n (event) => {\n if (event?.detail?.component) {\n event.stopPropagation();\n this.registerChildComponent(event.detail.component);\n }\n }\n );\n }\n }\n\n /**\n * Return the component custom event names.\n *\n * Components may override this method to provide their own events.\n *\n * Component custom events is an important part of component reusability. This function\n * is static because is part of the component definition and should be accessible from\n * outsite the instances. However, values will be available at instance level in the\n * this.events object.\n *\n * @returns {Object} the component events.\n */\n static getEvents() {\n return {};\n }\n\n /**\n * Component create function.\n *\n * Default init method will call \"create\" when all internal attributes are set\n * but before the component is not yet registered in the reactive module.\n *\n * In this method any component can define its own defaults such as:\n * - this.selectors {object} the default query selectors of this component.\n * - this.events {object} a list of event names this component dispatch\n * - extract any data from the main dom element (this.element)\n * - set any other data the component uses\n *\n * @param {descriptor} descriptor the component descriptor\n */\n // eslint-disable-next-line no-unused-vars\n create(descriptor) {\n // Components may override this method to initialize selects, events or other data.\n }\n\n /**\n * Component destroy hook.\n *\n * BaseComponent call this method when a component is unregistered or removed.\n *\n * Components may override this method to clean the HTML or do some action when the\n * component is unregistered or removed.\n */\n destroy() {\n // Components can override this method.\n }\n\n /**\n * Return the list of watchers that component has.\n *\n * Each watcher is represented by an object with two attributes:\n * - watch (string) the specific state event to watch. Example 'section.visible:updated'\n * - handler (function) the function to call when the watching state change happens\n *\n * Any component shoudl override this method to define their state watchers.\n *\n * @returns {array} array of watchers.\n */\n getWatchers() {\n return [];\n }\n\n /**\n * Reactive module will call this method when the state is ready.\n *\n * Component can override this method to update/load the component HTML or to bind\n * listeners to HTML entities.\n */\n stateReady() {\n // Components can override this method.\n }\n\n /**\n * Get the main DOM element of this component or a subelement.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {element|undefined} the DOM element (if any)\n */\n getElement(query, dataId) {\n if (query === undefined && dataId === undefined) {\n return this.element;\n }\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelector(selector);\n }\n\n /**\n * Get the all subelement that match a query selector.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {NodeList} the DOM elements\n */\n getElements(query, dataId) {\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelectorAll(selector);\n }\n\n /**\n * Add or update the component selectors.\n *\n * @param {Object} newSelectors an object of new selectors.\n */\n addSelectors(newSelectors) {\n for (const [selectorName, selector] of Object.entries(newSelectors)) {\n this.selectors[selectorName] = selector;\n }\n }\n\n /**\n * Return a component selector.\n *\n * @param {string} selectorName the selector name\n * @return {string|undefined} the query selector\n */\n getSelector(selectorName) {\n return this.selectors[selectorName];\n }\n\n /**\n * Dispatch a custom event on this.element.\n *\n * This is just a convenient method to dispatch custom events from within a component.\n * Components are free to use an alternative function to dispatch custom\n * events. The only restriction is that it should be dispatched on this.element\n * and specify \"bubbles:true\" to alert any component listeners.\n *\n * @param {string} eventName the event name\n * @param {*} detail event detail data\n */\n dispatchEvent(eventName, detail) {\n this.element.dispatchEvent(new CustomEvent(eventName, {\n bubbles: true,\n detail: detail,\n }));\n }\n\n /**\n * Render a new Component using a mustache file.\n *\n * It is important to note that this method should NOT be used for loading regular mustache files\n * as it returns a Promise that will only be resolved if the mustache registers a component instance.\n *\n * @param {element} target the DOM element that contains the component\n * @param {string} file the component mustache file to render\n * @param {*} data the mustache data\n * @return {Promise} a promise of the resulting component instance\n */\n renderComponent(target, file, data) {\n return new Promise((resolve, reject) => {\n target.addEventListener('ComponentRegistration:Success', ({detail}) => {\n resolve(detail.component);\n });\n target.addEventListener('ComponentRegistration:Fail', () => {\n reject(`Registration of ${file} fails.`);\n });\n Templates.renderForPromise(\n file,\n data\n ).then(({html, js}) => {\n Templates.replaceNodeContents(target, html, js);\n return true;\n }).catch(error => {\n reject(`Rendering of ${file} throws an error.`);\n throw error;\n });\n });\n }\n\n /**\n * Add and bind an event listener to a target and keep track of all event listeners.\n *\n * The native element.addEventListener method is not object oriented friently as the\n * \"this\" represents the element that triggers the event and not the listener class.\n * As components can be unregister and removed at any time, the BaseComponent provides\n * this method to keep track of all component listeners and do all of the bind stuff.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n addEventListener(target, type, listener) {\n\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n bindListener = listener.bind(this);\n this.eventHandlers.set(listener, bindListener);\n }\n\n target.addEventListener(type, bindListener);\n\n // Keep track of all component event listeners in case we need to remove them.\n this.eventListeners.push({\n target,\n type,\n bindListener,\n });\n\n }\n\n /**\n * Remove an event listener from a component.\n *\n * This method allows components to remove listeners without keeping track of the\n * listeners bind versions of the method. Both addEventListener and removeEventListener\n * keeps internally the relation between the original class method and the bind one.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n removeEventListener(target, type, listener) {\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n // This listener has not been added.\n return;\n }\n\n target.removeEventListener(type, bindListener);\n }\n\n /**\n * Remove all event listeners from this component.\n *\n * This method is called also when the component is unregistered or removed.\n *\n * Note that only listeners registered with the addEventListener method\n * will be removed. Other manual listeners will keep active.\n */\n removeAllEventListeners() {\n this.eventListeners.forEach(({target, type, bindListener}) => {\n target.removeEventListener(type, bindListener);\n });\n this.eventListeners = [];\n }\n\n /**\n * Remove a previously rendered component instance.\n *\n * This method will remove the component HTML and unregister it from the\n * reactive module.\n */\n remove() {\n this.unregister();\n this.element.remove();\n }\n\n /**\n * Unregister the component from the reactive module.\n *\n * This method will disable the component logic, event listeners and watchers\n * but it won't remove any HTML created by the component. However, it will trigger\n * the destroy hook to allow the component to clean parts of the interface.\n */\n unregister() {\n this.reactive.unregisterComponent(this);\n this.removeAllEventListeners();\n this.destroy();\n }\n\n /**\n * Dispatch a component registration event to inform the parent node.\n *\n * The registration event is different from the rest of the component events because\n * is the only way in which components can communicate its existence to a possible parent.\n * Most components will be created by including a mustache file, child components\n * must emit a registration event to the parent DOM element to alert about the registration.\n */\n dispatchRegistrationSuccess() {\n // The registration event does not bubble because we just want to comunicate with the parentNode.\n // Otherwise, any component can get multiple registrations events and could not differentiate\n // between child components and grand child components.\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Success',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Dispatch a component registration fail event to inform the parent node.\n *\n * As dispatchRegistrationSuccess, this method will communicate the registration fail to the\n * parent node to inform the possible parent component.\n */\n dispatchRegistrationFail() {\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured only by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Fail',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Register a child component into the reactive instance.\n *\n * @param {self} component the component to register.\n */\n registerChildComponent(component) {\n component.reactive = this.reactive;\n this.reactive.registerComponent(component);\n }\n}\n"],"file":"basecomponent.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/local/reactive/basecomponent.js"],"names":["descriptor","element","HTMLElement","Error","eventHandlers","Map","eventListeners","selectors","events","constructor","getEvents","create","addSelectors","reactive","dispatchEvent","CustomEvent","bubbles","detail","component","registerComponent","addEventListener","event","stopPropagation","registerChildComponent","query","dataId","dataSelector","selector","querySelector","querySelectorAll","newSelectors","Object","entries","selectorName","eventName","target","file","data","Promise","resolve","reject","Templates","renderForPromise","then","html","js","replaceNodeContents","catch","error","type","listener","bindListener","get","bind","set","push","removeEventListener","forEach","unregister","remove","unregisterComponent","removeAllEventListeners","destroy","parentNode","locked","dataset","style","pointerEvents","userSelect","hasAttribute","setAttribute"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport Templates from 'core/templates';\n\n/**\n * Reactive UI component base class.\n *\n * Each UI reactive component should extend this class to interact with a reactive state.\n *\n * @module core/local/reactive/basecomponent\n * @class core/local/reactive/basecomponent\n * @copyright 2020 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * This structure is used by any component and init method to define the way the component will interact\n * with the interface and whith reactive instance operates. The logic behind this object is to avoid\n * unnecessary dependancies between the final interface and the state logic.\n *\n * Any component interacts with a single main DOM element (description.element) but it can use internal\n * selector to select elements within this main element (descriptor.selectors). By default each component\n * will provide it's own default selectors, but those can be overridden by the \"descriptor.selectors\"\n * property in case the mustache wants to reuse the same component logic but with a different interface.\n *\n * @typedef {object} descriptor\n * @property {Reactive} reactive an optional reactive module to register in\n * @property {DOMElement} element all components needs an element to anchor events\n * @property {object} [selectors] an optional object to override query selectors\n */\n\n /**\n * The class constructor.\n *\n * The only param this method gets is a constructor with all the mandatory\n * and optional component data. Component will receive the same descriptor\n * as create method param.\n *\n * This method will call the \"create\" method before registering the component into\n * the reactive module. This way any component can add default selectors and events.\n *\n * @param {descriptor} descriptor data to create the object.\n */\n constructor(descriptor) {\n\n if (descriptor.element === undefined || !(descriptor.element instanceof HTMLElement)) {\n throw Error(`Reactive components needs a main DOM element to dispatch events`);\n }\n\n this.element = descriptor.element;\n\n // Variable to track event listeners.\n this.eventHandlers = new Map([]);\n this.eventListeners = [];\n\n // Empty default component selectors.\n this.selectors = {};\n\n // Empty default event list from the static method.\n this.events = this.constructor.getEvents();\n\n // Call create function to get the component defaults.\n this.create(descriptor);\n\n // Overwrite the components selectors if necessary.\n if (descriptor.selectors !== undefined) {\n this.addSelectors(descriptor.selectors);\n }\n\n // Register into a reactive instance.\n if (descriptor.reactive === undefined) {\n // Ask parent components for registration.\n this.element.dispatchEvent(new CustomEvent(\n 'core/reactive:requestRegistration',\n {\n bubbles: true,\n detail: {component: this},\n }\n ));\n } else {\n this.reactive = descriptor.reactive;\n this.reactive.registerComponent(this);\n // Add a listener to register child components.\n this.addEventListener(\n this.element,\n 'core/reactive:requestRegistration',\n (event) => {\n if (event?.detail?.component) {\n event.stopPropagation();\n this.registerChildComponent(event.detail.component);\n }\n }\n );\n }\n }\n\n /**\n * Return the component custom event names.\n *\n * Components may override this method to provide their own events.\n *\n * Component custom events is an important part of component reusability. This function\n * is static because is part of the component definition and should be accessible from\n * outsite the instances. However, values will be available at instance level in the\n * this.events object.\n *\n * @returns {Object} the component events.\n */\n static getEvents() {\n return {};\n }\n\n /**\n * Component create function.\n *\n * Default init method will call \"create\" when all internal attributes are set\n * but before the component is not yet registered in the reactive module.\n *\n * In this method any component can define its own defaults such as:\n * - this.selectors {object} the default query selectors of this component.\n * - this.events {object} a list of event names this component dispatch\n * - extract any data from the main dom element (this.element)\n * - set any other data the component uses\n *\n * @param {descriptor} descriptor the component descriptor\n */\n // eslint-disable-next-line no-unused-vars\n create(descriptor) {\n // Components may override this method to initialize selects, events or other data.\n }\n\n /**\n * Component destroy hook.\n *\n * BaseComponent call this method when a component is unregistered or removed.\n *\n * Components may override this method to clean the HTML or do some action when the\n * component is unregistered or removed.\n */\n destroy() {\n // Components can override this method.\n }\n\n /**\n * Return the list of watchers that component has.\n *\n * Each watcher is represented by an object with two attributes:\n * - watch (string) the specific state event to watch. Example 'section.visible:updated'\n * - handler (function) the function to call when the watching state change happens\n *\n * Any component shoudl override this method to define their state watchers.\n *\n * @returns {array} array of watchers.\n */\n getWatchers() {\n return [];\n }\n\n /**\n * Reactive module will call this method when the state is ready.\n *\n * Component can override this method to update/load the component HTML or to bind\n * listeners to HTML entities.\n */\n stateReady() {\n // Components can override this method.\n }\n\n /**\n * Get the main DOM element of this component or a subelement.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {element|undefined} the DOM element (if any)\n */\n getElement(query, dataId) {\n if (query === undefined && dataId === undefined) {\n return this.element;\n }\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelector(selector);\n }\n\n /**\n * Get the all subelement that match a query selector.\n *\n * @param {string|undefined} query optional subelement query\n * @param {string|undefined} dataId optional data-id value\n * @returns {NodeList} the DOM elements\n */\n getElements(query, dataId) {\n const dataSelector = (dataId) ? `[data-id='${dataId}']` : '';\n const selector = `${query ?? ''}${dataSelector}`;\n return this.element.querySelectorAll(selector);\n }\n\n /**\n * Add or update the component selectors.\n *\n * @param {Object} newSelectors an object of new selectors.\n */\n addSelectors(newSelectors) {\n for (const [selectorName, selector] of Object.entries(newSelectors)) {\n this.selectors[selectorName] = selector;\n }\n }\n\n /**\n * Return a component selector.\n *\n * @param {string} selectorName the selector name\n * @return {string|undefined} the query selector\n */\n getSelector(selectorName) {\n return this.selectors[selectorName];\n }\n\n /**\n * Dispatch a custom event on this.element.\n *\n * This is just a convenient method to dispatch custom events from within a component.\n * Components are free to use an alternative function to dispatch custom\n * events. The only restriction is that it should be dispatched on this.element\n * and specify \"bubbles:true\" to alert any component listeners.\n *\n * @param {string} eventName the event name\n * @param {*} detail event detail data\n */\n dispatchEvent(eventName, detail) {\n this.element.dispatchEvent(new CustomEvent(eventName, {\n bubbles: true,\n detail: detail,\n }));\n }\n\n /**\n * Render a new Component using a mustache file.\n *\n * It is important to note that this method should NOT be used for loading regular mustache files\n * as it returns a Promise that will only be resolved if the mustache registers a component instance.\n *\n * @param {element} target the DOM element that contains the component\n * @param {string} file the component mustache file to render\n * @param {*} data the mustache data\n * @return {Promise} a promise of the resulting component instance\n */\n renderComponent(target, file, data) {\n return new Promise((resolve, reject) => {\n target.addEventListener('ComponentRegistration:Success', ({detail}) => {\n resolve(detail.component);\n });\n target.addEventListener('ComponentRegistration:Fail', () => {\n reject(`Registration of ${file} fails.`);\n });\n Templates.renderForPromise(\n file,\n data\n ).then(({html, js}) => {\n Templates.replaceNodeContents(target, html, js);\n return true;\n }).catch(error => {\n reject(`Rendering of ${file} throws an error.`);\n throw error;\n });\n });\n }\n\n /**\n * Add and bind an event listener to a target and keep track of all event listeners.\n *\n * The native element.addEventListener method is not object oriented friently as the\n * \"this\" represents the element that triggers the event and not the listener class.\n * As components can be unregister and removed at any time, the BaseComponent provides\n * this method to keep track of all component listeners and do all of the bind stuff.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n addEventListener(target, type, listener) {\n\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n bindListener = listener.bind(this);\n this.eventHandlers.set(listener, bindListener);\n }\n\n target.addEventListener(type, bindListener);\n\n // Keep track of all component event listeners in case we need to remove them.\n this.eventListeners.push({\n target,\n type,\n bindListener,\n });\n\n }\n\n /**\n * Remove an event listener from a component.\n *\n * This method allows components to remove listeners without keeping track of the\n * listeners bind versions of the method. Both addEventListener and removeEventListener\n * keeps internally the relation between the original class method and the bind one.\n *\n * @param {Element} target the event target\n * @param {string} type the event name\n * @param {function} listener the class method that recieve the event\n */\n removeEventListener(target, type, listener) {\n // Check if we have the bind version of that listener.\n let bindListener = this.eventHandlers.get(listener);\n\n if (bindListener === undefined) {\n // This listener has not been added.\n return;\n }\n\n target.removeEventListener(type, bindListener);\n }\n\n /**\n * Remove all event listeners from this component.\n *\n * This method is called also when the component is unregistered or removed.\n *\n * Note that only listeners registered with the addEventListener method\n * will be removed. Other manual listeners will keep active.\n */\n removeAllEventListeners() {\n this.eventListeners.forEach(({target, type, bindListener}) => {\n target.removeEventListener(type, bindListener);\n });\n this.eventListeners = [];\n }\n\n /**\n * Remove a previously rendered component instance.\n *\n * This method will remove the component HTML and unregister it from the\n * reactive module.\n */\n remove() {\n this.unregister();\n this.element.remove();\n }\n\n /**\n * Unregister the component from the reactive module.\n *\n * This method will disable the component logic, event listeners and watchers\n * but it won't remove any HTML created by the component. However, it will trigger\n * the destroy hook to allow the component to clean parts of the interface.\n */\n unregister() {\n this.reactive.unregisterComponent(this);\n this.removeAllEventListeners();\n this.destroy();\n }\n\n /**\n * Dispatch a component registration event to inform the parent node.\n *\n * The registration event is different from the rest of the component events because\n * is the only way in which components can communicate its existence to a possible parent.\n * Most components will be created by including a mustache file, child components\n * must emit a registration event to the parent DOM element to alert about the registration.\n */\n dispatchRegistrationSuccess() {\n // The registration event does not bubble because we just want to comunicate with the parentNode.\n // Otherwise, any component can get multiple registrations events and could not differentiate\n // between child components and grand child components.\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Success',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Dispatch a component registration fail event to inform the parent node.\n *\n * As dispatchRegistrationSuccess, this method will communicate the registration fail to the\n * parent node to inform the possible parent component.\n */\n dispatchRegistrationFail() {\n if (this.element.parentNode === undefined) {\n return;\n }\n // This custom element is captured only by renderComponent method.\n this.element.parentNode.dispatchEvent(new CustomEvent(\n 'ComponentRegistration:Fail',\n {\n bubbles: false,\n detail: {component: this},\n }\n ));\n }\n\n /**\n * Register a child component into the reactive instance.\n *\n * @param {self} component the component to register.\n */\n registerChildComponent(component) {\n component.reactive = this.reactive;\n this.reactive.registerComponent(component);\n }\n\n /**\n * Set the lock value and locks or unlocks the element.\n *\n * @param {boolean} locked the new locked value\n */\n set locked(locked) {\n this.element.dataset.locked = locked ?? false;\n if (locked) {\n // Disable interactions.\n this.element.style.pointerEvents = 'none';\n this.element.style.userSelect = 'none';\n // Check if it is draggable.\n if (this.element.hasAttribute('draggable')) {\n this.element.setAttribute('draggable', false);\n }\n } else {\n // Reanable interactions.\n this.element.style.pointerEvents = null;\n this.element.style.userSelect = null;\n // Check if it was draggable.\n if (this.element.hasAttribute('draggable')) {\n this.element.setAttribute('draggable', true);\n }\n }\n }\n\n /**\n * Get the current locket value from the element.\n *\n * @return {boolean}\n */\n get locked() {\n return this.element.dataset.locked ?? false;\n }\n}\n"],"file":"basecomponent.min.js"}
\ No newline at end of file
diff --git a/lib/amd/build/local/reactive/reactive.min.js b/lib/amd/build/local/reactive/reactive.min.js
index 071a6da0be9..03886840701 100644
--- a/lib/amd/build/local/reactive/reactive.min.js
+++ b/lib/amd/build/local/reactive/reactive.min.js
@@ -1,2 +1,2 @@
-define ("core/local/reactive/reactive",["exports","core/log","core/local/reactive/statemanager","core/pending"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=e(b);c=e(c);d=e(d);function e(a){return a&&a.__esModule?a:{default:a}}function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}function h(a,b){return n(a)||m(a,b)||k(a,b)||j()}function j(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function k(a,b){if(!a)return;if("string"==typeof a)return l(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return l(a,b)}function l(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c .\n\n/**\n * A generic single state reactive module.\n *\n * @module core/reactive/local/reactive/reactive\n * @class core/reactive/local/reactive/reactive\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport log from 'core/log';\nimport StateManager from 'core/local/reactive/statemanager';\nimport Pending from 'core/pending';\n\n// Count the number of pending operations done to ensure we have a unique id for each one.\nlet pendingCount = 0;\n\n/**\n * Set up general reactive class to create a single state application with components.\n *\n * The reactive class is used for registering new UI components and manage the access to the state values\n * and mutations.\n *\n * When a new reactive instance is created, it will contain an empty state and and empty mutations\n * lists. When the state data is ready, the initial state can be loaded using the \"setInitialState\"\n * method. This will protect the state from writing and will trigger all the components \"stateReady\"\n * methods.\n *\n * State can only be altered by mutations. To replace all the mutations with a specific class,\n * use \"setMutations\" method. If you need to just add some new mutation methods, use \"addMutations\".\n *\n * To register new components into a reactive instance, use \"registerComponent\".\n *\n * Inside a component, use \"dispatch\" to invoke a mutation on the state (components can only access\n * the state in read only mode).\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * @typedef {object} description\n * @property {string} eventName the custom event name used for state changed events\n * @property {Function} eventDispatch the state update event dispatch function\n * @property {Element} [target] the target of the event dispatch. If not passed a fake element will be created\n * @property {Object} [mutations] an object with state mutations functions\n * @property {Object} [state] an object to initialize the state.\n */\n\n /**\n * Create a basic reactive manager.\n *\n * Note that if your state is not async loaded, you can pass directly on creation by using the\n * description.state attribute. However, this will initialize the state, this means\n * setInitialState will throw an exception because the state is already defined.\n *\n * @param {description} description reactive manager description.\n */\n constructor(description) {\n\n if (description.eventName === undefined || description.eventDispatch === undefined) {\n throw new Error(`Reactivity event required`);\n }\n\n if (description.name !== undefined) {\n this.name = description.name;\n }\n\n // Each reactive instance has its own element anchor to propagate state changes internally.\n // By default the module will create a fake DOM element to target custom events but\n // if all reactive components is constrait to a single element, this can be passed as\n // target in the description.\n this.target = description.target ?? document.createTextNode(null);\n\n this.eventName = description.eventName;\n this.eventDispatch = description.eventDispatch;\n\n // State manager is responsible for dispatch state change events when a mutation happens.\n this.stateManager = new StateManager(this.eventDispatch, this.target);\n\n // An internal registry of watchers and components.\n this.watchers = new Map([]);\n this.components = new Set([]);\n\n // Mutations can be overridden later using setMutations method.\n this.mutations = description.mutations ?? {};\n\n // Register the event to alert watchers when specific state change happens.\n this.target.addEventListener(this.eventName, this.callWatchersHandler.bind(this));\n\n // Add a pending operation waiting for the initial state.\n this.pendingState = new Pending(`core/reactive:registerInstance${pendingCount++}`);\n\n // Set initial state if we already have it.\n if (description.state !== undefined) {\n this.setInitialState(description.state);\n }\n }\n\n /**\n * State changed listener.\n *\n * This function take any state change and send it to the proper watchers.\n *\n * To prevent internal state changes from colliding with other reactive instances, only the\n * general \"state changed\" is triggered at document level. All the internal changes are\n * triggered at private target level without bubbling. This way any reactive instance can alert\n * only its own watchers.\n *\n * @param {CustomEvent} event\n */\n callWatchersHandler(event) {\n // Execute any registered component watchers.\n this.target.dispatchEvent(new CustomEvent(event.detail.action, {\n bubbles: false,\n detail: event.detail,\n }));\n }\n\n /**\n * Set the initial state.\n *\n * @param {object} stateData the initial state data.\n */\n setInitialState(stateData) {\n this.pendingState.resolve();\n this.stateManager.setInitialState(stateData);\n }\n\n /**\n * Add individual functions to the mutations.\n *\n * Note new mutations will be added to the existing ones. To replace the full mutation\n * object with a new one, use setMutations method.\n *\n * @method addMutations\n * @param {Object} newFunctions an object with new mutation functions.\n */\n addMutations(newFunctions) {\n for (const [mutation, mutationFunction] of Object.entries(newFunctions)) {\n this.mutations[mutation] = mutationFunction.bind(newFunctions);\n }\n }\n\n /**\n * Replace the current mutations with a new object.\n *\n * This method is designed to override the full mutations class, for example by extending\n * the original one. To add some individual mutations, use addMutations instead.\n *\n * @param {object} manager the new mutations intance\n */\n setMutations(manager) {\n this.mutations = manager;\n }\n\n /**\n * Return the current state.\n *\n * @return {object}\n */\n get state() {\n return this.stateManager.state;\n }\n\n /**\n * Return the initial state promise.\n *\n * Typically, components do not require to use this promise because registerComponent\n * will trigger their stateReady method automatically. But it could be useful for complex\n * components that require to combine state, template and string loadings.\n *\n * @method getState\n * @return {Promise}\n */\n getInitialStatePromise() {\n return this.stateManager.getInitialPromise();\n }\n\n /**\n * Register a new component.\n *\n * Component can provide some optional functions to the reactive module:\n * - getWatchers: returns an array of watchers\n * - stateReady: a method to call when the initial state is loaded\n *\n * It can also provide some optional attributes:\n * - name: the component name (default value: \"Unkown component\") to customize debug messages.\n *\n * The method will also use dispatchRegistrationSuccess and dispatchRegistrationFail. Those\n * are BaseComponent methods to inform parent components of the registration status.\n * Components should not override those methods.\n *\n * @method registerComponent\n * @param {object} component the new component\n * @property {string} [component.name] the component name to display in warnings and errors.\n * @property {Function} [component.dispatchRegistrationSuccess] method to notify registration success\n * @property {Function} [component.dispatchRegistrationFail] method to notify registration fail\n * @property {Function} [component.getWatchers] getter of the component watchers\n * @property {Function} [component.stateReady] method to call when the state is ready\n * @return {object} the registered component\n */\n registerComponent(component) {\n\n // Component name is an optional attribute to customize debug messages.\n const componentName = component.name ?? 'Unkown component';\n\n // Components can provide special methods to communicate registration to parent components.\n let dispatchSuccess = () => {\n return;\n };\n let dispatchFail = dispatchSuccess;\n if (component.dispatchRegistrationSuccess !== undefined) {\n dispatchSuccess = component.dispatchRegistrationSuccess.bind(component);\n }\n if (component.dispatchRegistrationFail !== undefined) {\n dispatchFail = component.dispatchRegistrationFail.bind(component);\n }\n\n // Components can be registered only one time.\n if (this.components.has(component)) {\n dispatchSuccess();\n return component;\n }\n\n // Components are fully registered only when the state ready promise is resolved.\n const pendingPromise = new Pending(`core/reactive:registerComponent${pendingCount++}`);\n\n // Keep track of the event listeners.\n let listeners = [];\n\n // Register watchers.\n let handlers = [];\n if (component.getWatchers !== undefined) {\n handlers = component.getWatchers();\n }\n handlers.forEach(({watch, handler}) => {\n\n if (watch === undefined) {\n dispatchFail();\n throw new Error(`Missing watch attribute in ${componentName} watcher`);\n }\n if (handler === undefined) {\n dispatchFail();\n throw new Error(`Missing handler for watcher ${watch} in ${componentName}`);\n }\n\n const listener = (event) => {\n handler.apply(component, [event.detail]);\n };\n\n // Save the listener information in case the component must be unregistered later.\n listeners.push({target: this.target, watch, listener});\n\n // The state manager triggers a general \"state changed\" event at a document level. However,\n // for the internal watchers, each component can listen to specific state changed custom events\n // in the target element. This way we can use the native event loop without colliding with other\n // reactive instances.\n this.target.addEventListener(watch, listener);\n });\n\n // Register state ready function. There's the possibility a component is registered after the initial state\n // is loaded. For those cases we have a state promise to handle this specific state change.\n if (component.stateReady !== undefined) {\n this.getInitialStatePromise()\n .then(state => {\n component.stateReady(state);\n pendingPromise.resolve();\n return true;\n })\n .catch(reason => {\n pendingPromise.resolve();\n log.error(`Initial state in ${componentName} rejected due to: ${reason}`);\n log.error(reason);\n });\n }\n\n // Save unregister data.\n this.watchers.set(component, listeners);\n this.components.add(component);\n\n dispatchSuccess();\n return component;\n }\n\n /**\n * Unregister a component and its watchers.\n *\n * @param {object} component the object instance to unregister\n * @returns {object} the deleted component\n */\n unregisterComponent(component) {\n if (!this.components.has(component)) {\n return component;\n }\n\n this.components.delete(component);\n\n // Remove event listeners.\n const listeners = this.watchers.get(component);\n if (listeners === undefined) {\n return component;\n }\n\n listeners.forEach(({target, watch, listener}) => {\n target.removeEventListener(watch, listener);\n });\n\n this.watchers.delete(component);\n\n return component;\n }\n\n /**\n * Dispatch a change in the state.\n *\n * This method is the only way for components to alter the state. Watchers will receive a\n * read only state to prevent illegal changes. If some user action require a state change, the\n * component should dispatch a mutation to trigger all the necessary logic to alter the state.\n *\n * @method dispatch\n * @param {string} actionName the action name (usually the mutation name)\n * @param {*} param any number of params the mutation needs.\n */\n async dispatch(actionName, ...params) {\n if (typeof actionName !== 'string') {\n throw new Error(`Dispatch action name must be a string`);\n }\n // JS does not have private methods yet. However, we prevent any component from calling\n // a method starting with \"_\" because the most accepted convention for private methods.\n if (actionName.charAt(0) === '_') {\n throw new Error(`Illegal Private ${actionName} mutation method dispatch`);\n }\n if (this.mutations[actionName] === undefined) {\n throw new Error(`Unkown ${actionName} mutation`);\n }\n\n const pendingPromise = new Pending(`core/reactive:${actionName}${pendingCount++}`);\n\n const mutationFunction = this.mutations[actionName];\n try {\n await mutationFunction.apply(this.mutations, [this.stateManager, ...params]);\n pendingPromise.resolve();\n } catch (error) {\n // Ensure the state is locked.\n this.stateManager.setReadOnly(true);\n pendingPromise.resolve();\n throw error;\n }\n }\n}\n"],"file":"reactive.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/local/reactive/reactive.js"],"names":["pendingCount","description","eventName","eventDispatch","Error","name","target","document","createTextNode","stateManager","StateManager","watchers","Map","components","Set","mutations","addEventListener","callWatchersHandler","bind","pendingState","Pending","state","setInitialState","event","dispatchEvent","CustomEvent","detail","action","bubbles","stateData","resolve","newFunctions","init","Object","entries","mutation","mutationFunction","manager","getInitialPromise","component","componentName","dispatchSuccess","dispatchFail","dispatchRegistrationSuccess","dispatchRegistrationFail","has","pendingPromise","listeners","handlers","getWatchers","forEach","watch","handler","listener","apply","push","stateReady","getInitialStatePromise","then","catch","reason","log","error","set","add","delete","get","removeEventListener","actionName","charAt","params","setReadOnly"],"mappings":"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","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 generic single state reactive module.\n *\n * @module core/reactive/local/reactive/reactive\n * @class core/reactive/local/reactive/reactive\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport log from 'core/log';\nimport StateManager from 'core/local/reactive/statemanager';\nimport Pending from 'core/pending';\n\n// Count the number of pending operations done to ensure we have a unique id for each one.\nlet pendingCount = 0;\n\n/**\n * Set up general reactive class to create a single state application with components.\n *\n * The reactive class is used for registering new UI components and manage the access to the state values\n * and mutations.\n *\n * When a new reactive instance is created, it will contain an empty state and and empty mutations\n * lists. When the state data is ready, the initial state can be loaded using the \"setInitialState\"\n * method. This will protect the state from writing and will trigger all the components \"stateReady\"\n * methods.\n *\n * State can only be altered by mutations. To replace all the mutations with a specific class,\n * use \"setMutations\" method. If you need to just add some new mutation methods, use \"addMutations\".\n *\n * To register new components into a reactive instance, use \"registerComponent\".\n *\n * Inside a component, use \"dispatch\" to invoke a mutation on the state (components can only access\n * the state in read only mode).\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * @typedef {object} description\n * @property {string} eventName the custom event name used for state changed events\n * @property {Function} eventDispatch the state update event dispatch function\n * @property {Element} [target] the target of the event dispatch. If not passed a fake element will be created\n * @property {Object} [mutations] an object with state mutations functions\n * @property {Object} [state] an object to initialize the state.\n */\n\n /**\n * Create a basic reactive manager.\n *\n * Note that if your state is not async loaded, you can pass directly on creation by using the\n * description.state attribute. However, this will initialize the state, this means\n * setInitialState will throw an exception because the state is already defined.\n *\n * @param {description} description reactive manager description.\n */\n constructor(description) {\n\n if (description.eventName === undefined || description.eventDispatch === undefined) {\n throw new Error(`Reactivity event required`);\n }\n\n if (description.name !== undefined) {\n this.name = description.name;\n }\n\n // Each reactive instance has its own element anchor to propagate state changes internally.\n // By default the module will create a fake DOM element to target custom events but\n // if all reactive components is constrait to a single element, this can be passed as\n // target in the description.\n this.target = description.target ?? document.createTextNode(null);\n\n this.eventName = description.eventName;\n this.eventDispatch = description.eventDispatch;\n\n // State manager is responsible for dispatch state change events when a mutation happens.\n this.stateManager = new StateManager(this.eventDispatch, this.target);\n\n // An internal registry of watchers and components.\n this.watchers = new Map([]);\n this.components = new Set([]);\n\n // Mutations can be overridden later using setMutations method.\n this.mutations = description.mutations ?? {};\n\n // Register the event to alert watchers when specific state change happens.\n this.target.addEventListener(this.eventName, this.callWatchersHandler.bind(this));\n\n // Add a pending operation waiting for the initial state.\n this.pendingState = new Pending(`core/reactive:registerInstance${pendingCount++}`);\n\n // Set initial state if we already have it.\n if (description.state !== undefined) {\n this.setInitialState(description.state);\n }\n }\n\n /**\n * State changed listener.\n *\n * This function take any state change and send it to the proper watchers.\n *\n * To prevent internal state changes from colliding with other reactive instances, only the\n * general \"state changed\" is triggered at document level. All the internal changes are\n * triggered at private target level without bubbling. This way any reactive instance can alert\n * only its own watchers.\n *\n * @param {CustomEvent} event\n */\n callWatchersHandler(event) {\n // Execute any registered component watchers.\n this.target.dispatchEvent(new CustomEvent(event.detail.action, {\n bubbles: false,\n detail: event.detail,\n }));\n }\n\n /**\n * Set the initial state.\n *\n * @param {object} stateData the initial state data.\n */\n setInitialState(stateData) {\n this.pendingState.resolve();\n this.stateManager.setInitialState(stateData);\n }\n\n /**\n * Add individual functions to the mutations.\n *\n * Note new mutations will be added to the existing ones. To replace the full mutation\n * object with a new one, use setMutations method.\n *\n * @method addMutations\n * @param {Object} newFunctions an object with new mutation functions.\n */\n addMutations(newFunctions) {\n // Mutations can provide an init method to do some setup in the statemanager.\n if (newFunctions.init !== undefined) {\n newFunctions.init(this.stateManager);\n }\n // Save all mutations.\n for (const [mutation, mutationFunction] of Object.entries(newFunctions)) {\n this.mutations[mutation] = mutationFunction.bind(newFunctions);\n }\n }\n\n /**\n * Replace the current mutations with a new object.\n *\n * This method is designed to override the full mutations class, for example by extending\n * the original one. To add some individual mutations, use addMutations instead.\n *\n * @param {object} manager the new mutations intance\n */\n setMutations(manager) {\n this.mutations = manager;\n // Mutations can provide an init method to do some setup in the statemanager.\n if (manager.init !== undefined) {\n manager.init(this.stateManager);\n }\n }\n\n /**\n * Return the current state.\n *\n * @return {object}\n */\n get state() {\n return this.stateManager.state;\n }\n\n /**\n * Return the initial state promise.\n *\n * Typically, components do not require to use this promise because registerComponent\n * will trigger their stateReady method automatically. But it could be useful for complex\n * components that require to combine state, template and string loadings.\n *\n * @method getState\n * @return {Promise}\n */\n getInitialStatePromise() {\n return this.stateManager.getInitialPromise();\n }\n\n /**\n * Register a new component.\n *\n * Component can provide some optional functions to the reactive module:\n * - getWatchers: returns an array of watchers\n * - stateReady: a method to call when the initial state is loaded\n *\n * It can also provide some optional attributes:\n * - name: the component name (default value: \"Unkown component\") to customize debug messages.\n *\n * The method will also use dispatchRegistrationSuccess and dispatchRegistrationFail. Those\n * are BaseComponent methods to inform parent components of the registration status.\n * Components should not override those methods.\n *\n * @method registerComponent\n * @param {object} component the new component\n * @property {string} [component.name] the component name to display in warnings and errors.\n * @property {Function} [component.dispatchRegistrationSuccess] method to notify registration success\n * @property {Function} [component.dispatchRegistrationFail] method to notify registration fail\n * @property {Function} [component.getWatchers] getter of the component watchers\n * @property {Function} [component.stateReady] method to call when the state is ready\n * @return {object} the registered component\n */\n registerComponent(component) {\n\n // Component name is an optional attribute to customize debug messages.\n const componentName = component.name ?? 'Unkown component';\n\n // Components can provide special methods to communicate registration to parent components.\n let dispatchSuccess = () => {\n return;\n };\n let dispatchFail = dispatchSuccess;\n if (component.dispatchRegistrationSuccess !== undefined) {\n dispatchSuccess = component.dispatchRegistrationSuccess.bind(component);\n }\n if (component.dispatchRegistrationFail !== undefined) {\n dispatchFail = component.dispatchRegistrationFail.bind(component);\n }\n\n // Components can be registered only one time.\n if (this.components.has(component)) {\n dispatchSuccess();\n return component;\n }\n\n // Components are fully registered only when the state ready promise is resolved.\n const pendingPromise = new Pending(`core/reactive:registerComponent${pendingCount++}`);\n\n // Keep track of the event listeners.\n let listeners = [];\n\n // Register watchers.\n let handlers = [];\n if (component.getWatchers !== undefined) {\n handlers = component.getWatchers();\n }\n handlers.forEach(({watch, handler}) => {\n\n if (watch === undefined) {\n dispatchFail();\n throw new Error(`Missing watch attribute in ${componentName} watcher`);\n }\n if (handler === undefined) {\n dispatchFail();\n throw new Error(`Missing handler for watcher ${watch} in ${componentName}`);\n }\n\n const listener = (event) => {\n handler.apply(component, [event.detail]);\n };\n\n // Save the listener information in case the component must be unregistered later.\n listeners.push({target: this.target, watch, listener});\n\n // The state manager triggers a general \"state changed\" event at a document level. However,\n // for the internal watchers, each component can listen to specific state changed custom events\n // in the target element. This way we can use the native event loop without colliding with other\n // reactive instances.\n this.target.addEventListener(watch, listener);\n });\n\n // Register state ready function. There's the possibility a component is registered after the initial state\n // is loaded. For those cases we have a state promise to handle this specific state change.\n if (component.stateReady !== undefined) {\n this.getInitialStatePromise()\n .then(state => {\n component.stateReady(state);\n pendingPromise.resolve();\n return true;\n })\n .catch(reason => {\n pendingPromise.resolve();\n log.error(`Initial state in ${componentName} rejected due to: ${reason}`);\n log.error(reason);\n });\n }\n\n // Save unregister data.\n this.watchers.set(component, listeners);\n this.components.add(component);\n\n dispatchSuccess();\n return component;\n }\n\n /**\n * Unregister a component and its watchers.\n *\n * @param {object} component the object instance to unregister\n * @returns {object} the deleted component\n */\n unregisterComponent(component) {\n if (!this.components.has(component)) {\n return component;\n }\n\n this.components.delete(component);\n\n // Remove event listeners.\n const listeners = this.watchers.get(component);\n if (listeners === undefined) {\n return component;\n }\n\n listeners.forEach(({target, watch, listener}) => {\n target.removeEventListener(watch, listener);\n });\n\n this.watchers.delete(component);\n\n return component;\n }\n\n /**\n * Dispatch a change in the state.\n *\n * This method is the only way for components to alter the state. Watchers will receive a\n * read only state to prevent illegal changes. If some user action require a state change, the\n * component should dispatch a mutation to trigger all the necessary logic to alter the state.\n *\n * @method dispatch\n * @param {string} actionName the action name (usually the mutation name)\n * @param {*} param any number of params the mutation needs.\n */\n async dispatch(actionName, ...params) {\n if (typeof actionName !== 'string') {\n throw new Error(`Dispatch action name must be a string`);\n }\n // JS does not have private methods yet. However, we prevent any component from calling\n // a method starting with \"_\" because the most accepted convention for private methods.\n if (actionName.charAt(0) === '_') {\n throw new Error(`Illegal Private ${actionName} mutation method dispatch`);\n }\n if (this.mutations[actionName] === undefined) {\n throw new Error(`Unkown ${actionName} mutation`);\n }\n\n const pendingPromise = new Pending(`core/reactive:${actionName}${pendingCount++}`);\n\n const mutationFunction = this.mutations[actionName];\n try {\n await mutationFunction.apply(this.mutations, [this.stateManager, ...params]);\n pendingPromise.resolve();\n } catch (error) {\n // Ensure the state is locked.\n this.stateManager.setReadOnly(true);\n pendingPromise.resolve();\n throw error;\n }\n }\n}\n"],"file":"reactive.min.js"}
\ No newline at end of file
diff --git a/lib/amd/build/local/reactive/statemanager.min.js b/lib/amd/build/local/reactive/statemanager.min.js
index ff027e5adea..8b0cd8e54bc 100644
--- a/lib/amd/build/local/reactive/statemanager.min.js
+++ b/lib/amd/build/local/reactive/statemanager.min.js
@@ -1,2 +1,2 @@
-define ("core/local/reactive/statemanager",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){b=function(a){return typeof a}}else{b=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return b(a)}function c(a,b,e){if("undefined"!=typeof Reflect&&Reflect.get){c=Reflect.get}else{c=function(a,b,c){var e=d(a,b);if(!e)return;var f=Object.getOwnPropertyDescriptor(e,b);if(f.get){return f.get.call(c)}return f.value}}return c(a,b,e||a)}function d(a,b){while(!Object.prototype.hasOwnProperty.call(a,b)){a=n(a);if(null===a)break}return a}function e(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)m(a,b)}function f(a){return function(){var b=n(a),c;if(k()){var d=n(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return g(this,c)}}function g(a,c){if(c&&("object"===b(c)||"function"==typeof c)){return c}return h(a)}function h(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function i(a){var b="function"==typeof Map?new Map:void 0;i=function(a){if(null===a||!l(a))return a;if("function"!=typeof a){throw new TypeError("Super expression must either be null or a function")}if("undefined"!=typeof b){if(b.has(a))return b.get(a);b.set(a,c)}function c(){return j(a,arguments,n(this).constructor)}c.prototype=Object.create(a.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});return m(c,a)};return i(a)}function j(){if(k()){j=Reflect.construct}else{j=function(b,c,d){var e=[null];e.push.apply(e,c);var a=Function.bind.apply(b,e),f=new a;if(d)m(f,d.prototype);return f}}return j.apply(null,arguments)}function k(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function l(a){return-1!==Function.toString.call(a).indexOf("[native code]")}function m(a,b){m=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return m(a,b)}function n(a){n=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return n(a)}function o(a,b){return t(a)||s(a,b)||q(a,b)||p()}function p(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function q(a,b){if(!a)return;if("string"==typeof a)return r(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return r(a,b)}function r(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c .\n\n/**\n * Reactive simple state manager.\n *\n * The state manager contains the state data, trigger update events and\n * can lock and unlock the state data.\n *\n * This file contains the three main elements of the state manager:\n * - State manager: the public class to alter the state, dispatch events and process update messages.\n * - Proxy handler: a private class to keep track of the state object changes.\n * - StateMap class: a private class extending Map class that triggers event when a state list is modifed.\n *\n * @module core/local/reactive/stateManager\n * @class core/local/reactive/stateManager\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * State manager class.\n *\n * This class handle the reactive state and ensure only valid mutations can modify the state.\n * It also provide methods to apply batch state update messages (see processUpdates function doc\n * for more details on update messages).\n *\n * Implementing a deep state manager is complex and will require many frontend resources. To keep\n * the state fast and simple, the state can ONLY store two kind of data:\n * - Object with attributes\n * - Sets of objects with id attributes.\n *\n * This is an example of a valid state:\n *\n * {\n * course: {\n * name: 'course name',\n * shortname: 'courseshort',\n * sectionlist: [21, 34]\n * },\n * sections: [\n * {id: 21, name: 'Topic 1', visible: true},\n * {id: 34, name: 'Topic 2', visible: false,\n * ],\n * }\n *\n * The following cases are NOT allowed at a state ROOT level (throws an exception if they are assigned):\n * - Simple values (strings, boolean...).\n * - Arrays of simple values.\n * - Array of objects without ID attribute (all arrays will be converted to maps and requires an ID).\n *\n * Thanks to those limitations it can simplify the state update messages and the event names. If You\n * need to store simple data, just group them in an object.\n *\n * To grant any state change triggers the proper events, the class uses two private structures:\n * - proxy handler: any object stored in the state is proxied using this class.\n * - StateMap class: any object set in the state will be converted to StateMap using the\n * objects id attribute.\n */\nexport default class StateManager {\n\n /**\n * Create a basic reactive state store.\n *\n * The state manager is meant to work with native JS events. To ensure each reactive module can use\n * it in its own way, the parent element must provide a valid event dispatcher function and an optional\n * DOM element to anchor the event.\n *\n * @param {function} dispatchEvent the function to dispatch the custom event when the state changes.\n * @param {element} target the state changed custom event target (document if none provided)\n */\n constructor(dispatchEvent, target) {\n\n // The dispatch event function.\n /** @package */\n this.dispatchEvent = dispatchEvent;\n\n // The DOM container to trigger events.\n /** @package */\n this.target = target ?? document;\n\n // State can be altered freely until initial state is set.\n /** @package */\n this.readonly = false;\n\n // List of state changes pending to be published as events.\n /** @package */\n this.eventsToPublish = [];\n\n // The update state types functions.\n /** @package */\n this.updateTypes = {\n \"create\": this.defaultCreate.bind(this),\n \"update\": this.defaultUpdate.bind(this),\n \"delete\": this.defaultDelete.bind(this),\n \"put\": this.defaultPut.bind(this),\n \"override\": this.defaultOverride.bind(this),\n };\n\n // The state_loaded event is special because it only happens one but all components\n // may react to that state, even if they are registered after the setIinitialState.\n // For these reason we use a promise for that event.\n this.initialPromise = new Promise((resolve) => {\n const initialStateDone = (event) => {\n resolve(event.detail.state);\n };\n this.target.addEventListener('state:loaded', initialStateDone);\n });\n }\n\n /**\n * Loads the initial state.\n *\n * Note this method will trigger a state changed event with \"state:loaded\" actionname.\n *\n * The state mode will be set to read only when the initial state is loaded.\n *\n * @param {object} initialState\n */\n setInitialState(initialState) {\n\n if (this.state !== undefined) {\n throw Error('Initial state can only be initialized ones');\n }\n\n // Create the state object.\n const state = new Proxy({}, new Handler('state', this, true));\n for (const [prop, propValue] of Object.entries(initialState)) {\n state[prop] = propValue;\n }\n this.state = state;\n\n // When the state is loaded we can lock it to prevent illegal changes.\n this.readonly = true;\n\n this.dispatchEvent({\n action: 'state:loaded',\n state: this.state,\n }, this.target);\n }\n\n /**\n * Generate a promise that will be resolved when the initial state is loaded.\n *\n * In most cases the final state will be loaded using an ajax call. This is the reason\n * why states manager are created unlocked and won't be reactive until the initial state is set.\n *\n * @return {Promise} the resulting promise\n */\n getInitialPromise() {\n return this.initialPromise;\n }\n\n /**\n * Locks or unlocks the state to prevent illegal updates.\n *\n * Mutations use this method to modify the state. Once the state is updated, they must\n * block again the state.\n *\n * All changes done while the state is writable will be registered using registerStateAction.\n * When the state is set again to read only the method will trigger _publishEvents to communicate\n * changes to all watchers.\n *\n * @param {bool} readonly if the state is in read only mode enabled\n */\n setReadOnly(readonly) {\n\n this.readonly = readonly;\n\n // When the state is in readonly again is time to publish all pending events.\n if (this.readonly) {\n this._publishEvents();\n }\n }\n\n /**\n * Add methods to process update state messages.\n *\n * The state manager provide a default update, create and delete methods. However,\n * some applications may require to override the default methods or even add new ones\n * like \"refresh\" or \"error\".\n *\n * @param {Object} newFunctions the new update types functions.\n */\n addUpdateTypes(newFunctions) {\n for (const [updateType, updateFunction] of Object.entries(newFunctions)) {\n if (typeof updateFunction === 'function') {\n this.updateTypes[updateType] = updateFunction.bind(newFunctions);\n }\n }\n }\n\n /**\n * Process a state updates array and do all the necessary changes.\n *\n * Note this method unlocks the state while it is executing and relocks it\n * when finishes.\n *\n * @param {array} updates\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdates(updates, updateTypes) {\n if (!Array.isArray(updates)) {\n throw Error('State updates must be an array');\n }\n this.setReadOnly(false);\n updates.forEach((update) => {\n if (update.name === undefined) {\n throw Error('Missing state update name');\n }\n this.processUpdate(\n update.name,\n update.action,\n update.fields,\n updateTypes\n );\n });\n this.setReadOnly(true);\n }\n\n /**\n * Process a single state update.\n *\n * Note this method will not lock or unlock the state by itself.\n *\n * @param {string} updateName the state element to update\n * @param {string} action to action to perform\n * @param {object} fields the new data\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdate(updateName, action, fields, updateTypes) {\n\n if (!fields) {\n throw Error('Missing state update fields');\n }\n\n if (updateTypes === undefined) {\n updateTypes = {};\n }\n\n action = action ?? 'update';\n\n const method = updateTypes[action] ?? this.updateTypes[action];\n\n if (method === undefined) {\n throw Error(`Unkown update action ${action}`);\n }\n\n method(this, updateName, fields);\n }\n\n /**\n * Process a create state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultCreate(stateManager, updateName, fields) {\n\n let state = stateManager.state;\n\n // Create can be applied only to lists, not to objects.\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n\n /**\n * Process a delete state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultDelete(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Process deletion.\n let state = stateManager.state;\n\n if (state[updateName] instanceof StateMap) {\n state[updateName].delete(fields.id);\n return;\n }\n delete state[updateName];\n }\n\n /**\n * Process a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultUpdate(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Execute updates.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n }\n\n /**\n * Process a put state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultPut(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Update attributes.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create new object.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Process an override state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultOverride(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Remove any unnecessary fields.\n for (const [fieldName] of Object.entries(current)) {\n if (fields[fieldName] === undefined) {\n delete current[fieldName];\n }\n }\n // Update field.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create the element if not exists.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Get an element from the state or form an alternative state object.\n *\n * The altstate param is used by external update functions that gets the current\n * state as param.\n *\n * @param {String} name the state object name\n * @param {*} id and object id for state maps.\n * @return {Object|undefined} the state object found\n */\n get(name, id) {\n const state = this.state;\n\n let current = state[name];\n if (current instanceof StateMap) {\n if (id === undefined) {\n throw Error(`Missing id for ${name} state update`);\n }\n current = state[name].get(id);\n }\n\n return current;\n }\n\n /**\n * Register a state modification and generate the necessary events.\n *\n * This method is used mainly by proxy helpers to dispatch state change event.\n * However, mutations can use it to inform components about non reactive changes\n * in the state (only the two first levels of the state are reactive).\n *\n * Each action can produce several events:\n * - The specific attribute updated, created or deleter (example: \"cm.visible:updated\")\n * - The general state object updated, created or deleted (example: \"cm:updated\")\n * - If the element has an ID attribute, the specific event with id (example: \"cm[42].visible:updated\")\n * - If the element has an ID attribute, the general event with id (example: \"cm[42]:updated\")\n * - A generic state update event \"state:update\"\n *\n * @param {string} field the affected state field name\n * @param {string|null} prop the affecter field property (null if affect the full object)\n * @param {string} action the action done (created/updated/deleted)\n * @param {*} data the affected data\n */\n registerStateAction(field, prop, action, data) {\n\n let parentAction = 'updated';\n\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}.${prop}:${action}`,\n eventData: data,\n action,\n });\n } else {\n parentAction = action;\n }\n\n // Trigger extra events if the element has an ID attribute.\n if (data.id !== undefined) {\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}].${prop}:${action}`,\n eventData: data,\n action,\n });\n }\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}]:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n }\n\n // Register the general change.\n this.eventsToPublish.push({\n eventName: `${field}:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n\n // Register state updated event.\n this.eventsToPublish.push({\n eventName: `state:updated`,\n eventData: data,\n action: 'updated',\n });\n }\n\n /**\n * Internal method to publish events.\n *\n * This is a private method, it will be invoked when the state is set back to read only mode.\n */\n _publishEvents() {\n const fieldChanges = this.eventsToPublish;\n this.eventsToPublish = [];\n\n // Dispatch a transaction start event.\n this.dispatchEvent({\n action: 'transaction:start',\n state: this.state,\n element: null,\n }, this.target);\n\n // State changes can be registered in any order. However it will avoid many\n // components errors if they are sorted to have creations-updates-deletes in case\n // some component needs to create or destroy DOM elements before updating them.\n fieldChanges.sort((a, b) => {\n const weights = {\n created: 0,\n updated: 1,\n deleted: 2,\n };\n const aweight = weights[a.action] ?? 0;\n const bweight = weights[b.action] ?? 0;\n // In case both have the same weight, the eventName length decide.\n if (aweight === bweight) {\n return a.eventName.length - b.eventName.length;\n }\n return aweight - bweight;\n });\n\n // List of the published events to prevent redundancies.\n let publishedEvents = new Set();\n\n fieldChanges.forEach((event) => {\n\n const eventkey = `${event.eventName}.${event.eventData.id ?? 0}`;\n\n if (!publishedEvents.has(eventkey)) {\n this.dispatchEvent({\n action: event.eventName,\n state: this.state,\n element: event.eventData\n }, this.target);\n\n publishedEvents.add(eventkey);\n }\n });\n\n // Dispatch a transaction end event.\n this.dispatchEvent({\n action: 'transaction:end',\n state: this.state,\n element: null,\n }, this.target);\n }\n}\n\n// Proxy helpers.\n\n/**\n * The proxy handler.\n *\n * This class will inform any value change directly to the state manager.\n *\n * The proxied variable will throw an error if it is altered when the state manager is\n * in read only mode.\n */\nclass Handler {\n\n /**\n * Class constructor.\n *\n * @param {string} name the variable name used for identify triggered actions\n * @param {StateManager} stateManager the state manager object\n * @param {boolean} proxyValues if new values must be proxied (used only at state root level)\n */\n constructor(name, stateManager, proxyValues) {\n this.name = name;\n this.stateManager = stateManager;\n this.proxyValues = proxyValues ?? false;\n }\n\n /**\n * Set trap to trigger events when the state changes.\n *\n * @param {object} obj the source object (not proxied)\n * @param {string} prop the attribute to set\n * @param {*} value the value to save\n * @param {*} receiver the proxied element to be attached to events\n * @returns {boolean} if the value is set\n */\n set(obj, prop, value, receiver) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${prop} value in ${this.name}.`);\n }\n\n // Check any data change.\n if (JSON.stringify(obj[prop]) === JSON.stringify(value)) {\n return true;\n }\n\n const action = (obj[prop] !== undefined) ? 'updated' : 'created';\n\n // Proxy value if necessary (used at state root level).\n if (this.proxyValues) {\n if (Array.isArray(value)) {\n obj[prop] = new StateMap(prop, this.stateManager).loadValues(value);\n } else {\n obj[prop] = new Proxy(value, new Handler(prop, this.stateManager));\n }\n } else {\n obj[prop] = value;\n }\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return true;\n }\n\n this.stateManager.registerStateAction(this.name, prop, action, receiver);\n\n return true;\n }\n\n /**\n * Delete property trap to trigger state change events.\n *\n * @param {*} obj the affected object (not proxied)\n * @param {*} prop the prop to delete\n * @returns {boolean} if prop is deleted\n */\n deleteProperty(obj, prop) {\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to delete ${prop} in ${this.name}.`);\n }\n if (prop in obj) {\n\n delete obj[prop];\n\n this.stateManager.registerStateAction(this.name, prop, 'deleted', obj);\n }\n return true;\n }\n}\n\n/**\n * Class to add events dispatching to the JS Map class.\n *\n * When the state has a list of objects (with IDs) it will be converted into a StateMap.\n * StateMap is used almost in the same way as a regular JS map. Because all elements have an\n * id attribute, it has some specific methods:\n * - add: a convenient method to add an element without specifying the key (\"id\" attribute will be used as a key).\n * - loadValues: to add many elements at once wihout specifying keys (\"id\" attribute will be used).\n *\n * Apart, the main difference between regular Map and MapState is that this one will inform any change to the\n * state manager.\n */\nclass StateMap extends Map {\n\n /**\n * Create a reactive Map.\n *\n * @param {string} name the property name\n * @param {StateManager} stateManager the state manager\n * @param {iterable} iterable an iterable object to create the Map\n */\n constructor(name, stateManager, iterable) {\n // We don't have any \"this\" until be call super.\n super(iterable);\n this.name = name;\n this.stateManager = stateManager;\n }\n\n /**\n * Set an element into the map.\n *\n * Each value needs it's own id attribute. Objects without id will be rejected.\n * The function will throw an error if the value id and the key are not the same.\n *\n * @param {*} key the key to store\n * @param {*} value the value to store\n * @returns {Map} the resulting Map object\n */\n set(key, value) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n // Normalize keys as string to prevent json decoding errors.\n key = this.normalizeKey(key);\n\n this.checkValue(value);\n\n if (key === undefined || key === null) {\n throw Error('State lists keys cannot be null or undefined');\n }\n\n // ID is mandatory and should be the same as the key.\n if (this.normalizeKey(value.id) !== key) {\n throw new Error(`State error: ${this.name} list element ID (${value.id}) and key (${key}) mismatch`);\n }\n\n const action = (super.has(key)) ? 'updated' : 'created';\n\n // Save proxied data into the list.\n const result = super.set(key, new Proxy(value, new Handler(this.name, this.stateManager)));\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, action, super.get(key));\n\n return result;\n }\n\n /**\n * Check if a value is valid to be stored in a a State List.\n *\n * Only objects with id attribute can be stored in State lists.\n *\n * This method throws an error if the value is not valid.\n *\n * @param {object} value (with ID)\n */\n checkValue(value) {\n if (!typeof value === 'object' && value !== null) {\n throw Error('State lists can contain objects only');\n }\n\n if (value.id === undefined) {\n throw Error('State lists elements must contain at least an id attribute');\n }\n }\n\n /**\n * Return a normalized key value for state map.\n *\n * Regular maps uses strict key comparissons but state maps are indexed by ID.JSON conversions\n * and webservices sometimes do unexpected types conversions so we convert any integer key to string.\n *\n * @param {*} key the provided key\n * @returns {string}\n */\n normalizeKey(key) {\n return String(key).valueOf();\n }\n\n /**\n * Insert a new element int a list.\n *\n * Each value needs it's own id attribute. Objects withouts id will be rejected.\n *\n * @param {object} value the value to add (needs an id attribute)\n * @returns {Map} the resulting Map object\n */\n add(value) {\n this.checkValue(value);\n return this.set(value.id, value);\n }\n\n /**\n * Return a state map element.\n *\n * @param {*} key the element id\n * @return {Object}\n */\n get(key) {\n return super.get(this.normalizeKey(key));\n }\n\n /**\n * Check whether an element with the specified key exists or not.\n *\n * @param {*} key the key to find\n * @return {boolean}\n */\n has(key) {\n return super.has(this.normalizeKey(key));\n }\n\n /**\n * Delete an element from the map.\n *\n * @param {*} key\n * @returns {boolean}\n */\n delete(key) {\n // State maps uses only string keys to avoid strict comparisons.\n key = this.normalizeKey(key);\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n const previous = super.get(key);\n\n const result = super.delete(key);\n if (!result) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, 'deleted', previous);\n\n return result;\n }\n\n /**\n * Return a suitable structure for JSON conversion.\n *\n * This function is needed because new values are compared in JSON. StateMap has Private\n * attributes which cannot be stringified (like this.stateManager which will produce an\n * infinite recursivity).\n *\n * @returns {array}\n */\n toJSON() {\n let result = [];\n this.forEach((value) => {\n result.push(value);\n });\n return result;\n }\n\n /**\n * Insert a full list of values using the id attributes as keys.\n *\n * This method is used mainly to initialize the list. Note each element is indexed by its \"id\" attribute.\n * This is a basic restriction of StateMap. All elements need an id attribute, otherwise it won't be saved.\n *\n * @param {iterable} values the values to load\n * @returns {StateMap} return the this value\n */\n loadValues(values) {\n values.forEach((data) => {\n this.checkValue(data);\n let key = data.id;\n let newvalue = new Proxy(data, new Handler(this.name, this.stateManager));\n this.set(key, newvalue);\n });\n return this;\n }\n}\n"],"file":"statemanager.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../../../src/local/reactive/statemanager.js"],"names":["StateManager","dispatchEvent","target","document","readonly","eventsToPublish","updateTypes","defaultCreate","bind","defaultUpdate","defaultDelete","defaultPut","defaultOverride","defaultPrepareFields","initialPromise","Promise","resolve","addEventListener","initialStateDone","event","detail","state","initialState","Error","Proxy","Handler","Object","entries","prop","propValue","action","_publishEvents","newFunctions","updateType","updateFunction","updates","Array","isArray","setReadOnly","forEach","update","name","processUpdate","fields","updateName","method","prepareFields","stateManager","StateMap","add","current","get","id","delete","fieldName","fieldValue","field","data","parentAction","push","eventName","eventData","fieldChanges","element","sort","a","b","weights","created","updated","deleted","aweight","bweight","length","publishedEvents","Set","eventkey","has","proxyValues","obj","value","receiver","JSON","stringify","loadValues","registerStateAction","iterable","key","normalizeKey","checkValue","result","valueOf","set","previous","values","newvalue","Map"],"mappings":"gxHAuEqBA,CAAAA,C,YAYjB,WAAYC,CAAZ,CAA2BC,CAA3B,CAAmC,sBAI/B,KAAKD,aAAL,CAAqBA,CAArB,CAIA,KAAKC,MAAL,QAAcA,CAAd,WAAcA,CAAd,CAAcA,CAAd,CAAwBC,QAAxB,CAIA,KAAKC,QAAL,IAIA,KAAKC,eAAL,CAAuB,EAAvB,CAIA,KAAKC,WAAL,CAAmB,CACf,OAAU,KAAKC,aAAL,CAAmBC,IAAnB,CAAwB,IAAxB,CADK,CAEf,OAAU,KAAKC,aAAL,CAAmBD,IAAnB,CAAwB,IAAxB,CAFK,CAGf,OAAU,KAAKE,aAAL,CAAmBF,IAAnB,CAAwB,IAAxB,CAHK,CAIf,IAAO,KAAKG,UAAL,CAAgBH,IAAhB,CAAqB,IAArB,CAJQ,CAKf,SAAY,KAAKI,eAAL,CAAqBJ,IAArB,CAA0B,IAA1B,CALG,CAMf,cAAiB,KAAKK,oBAAL,CAA0BL,IAA1B,CAA+B,IAA/B,CANF,CAAnB,CAYA,KAAKM,cAAL,CAAsB,GAAIC,CAAAA,OAAJ,CAAY,SAACC,CAAD,CAAa,CAI3C,CAAI,CAACd,MAAL,CAAYe,gBAAZ,CAA6B,cAA7B,CAHyB,QAAnBC,CAAAA,gBAAmB,CAACC,CAAD,CAAW,CAChCH,CAAO,CAACG,CAAK,CAACC,MAAN,CAAaC,KAAd,CACV,CACD,CACH,CALqB,CAMzB,C,2DAWeC,C,CAAc,CAE1B,GAAI,KAAKD,KAAL,SAAJ,CAA8B,CAC1B,KAAME,CAAAA,KAAK,CAAC,4CAAD,CACd,CAID,OADMF,CAAAA,CAAK,CAAG,GAAIG,CAAAA,KAAJ,CAAU,EAAV,CAAc,GAAIC,CAAAA,CAAJ,CAAY,OAAZ,CAAqB,IAArB,IAAd,CACd,OAAgCC,MAAM,CAACC,OAAP,CAAeL,CAAf,CAAhC,gBAA8D,iBAAlDM,CAAkD,MAA5CC,CAA4C,MAC1DR,CAAK,CAACO,CAAD,CAAL,CAAcC,CACjB,CACD,KAAKR,KAAL,CAAaA,CAAb,CAGA,KAAKjB,QAAL,IAEA,KAAKH,aAAL,CAAmB,CACf6B,MAAM,CAAE,cADO,CAEfT,KAAK,CAAE,KAAKA,KAFG,CAAnB,CAGG,KAAKnB,MAHR,CAIH,C,6DAUmB,CAChB,MAAO,MAAKY,cACf,C,gDAcWV,C,CAAU,CAElB,KAAKA,QAAL,CAAgBA,CAAhB,CAGA,GAAI,KAAKA,QAAT,CAAmB,CACf,KAAK2B,cAAL,EACH,CACJ,C,sDAWcC,C,CAAc,CACzB,cAA2CN,MAAM,CAACC,OAAP,CAAeK,CAAf,CAA3C,gBAAyE,iBAA7DC,CAA6D,MAAjDC,CAAiD,MACrE,GAA8B,UAA1B,QAAOA,CAAAA,CAAX,CAA0C,CACtC,KAAK5B,WAAL,CAAiB2B,CAAjB,EAA+BC,CAAc,CAAC1B,IAAf,CAAoBwB,CAApB,CAClC,CACJ,CACJ,C,sDAWcG,C,CAAS7B,C,CAAa,YACjC,GAAI,CAAC8B,KAAK,CAACC,OAAN,CAAcF,CAAd,CAAL,CAA6B,CACzB,KAAMZ,CAAAA,KAAK,CAAC,gCAAD,CACd,CACD,KAAKe,WAAL,KACAH,CAAO,CAACI,OAAR,CAAgB,SAACC,CAAD,CAAY,CACxB,GAAIA,CAAM,CAACC,IAAP,SAAJ,CAA+B,CAC3B,KAAMlB,CAAAA,KAAK,CAAC,2BAAD,CACd,CACD,CAAI,CAACmB,aAAL,CACIF,CAAM,CAACC,IADX,CAEID,CAAM,CAACV,MAFX,CAGIU,CAAM,CAACG,MAHX,CAIIrC,CAJJ,CAMH,CAVD,EAWA,KAAKgC,WAAL,IACH,C,oDAYaM,C,CAAYd,C,CAAQa,C,CAAQrC,C,CAAa,WAEnD,GAAI,CAACqC,CAAL,CAAa,CACT,KAAMpB,CAAAA,KAAK,CAAC,6BAAD,CACd,CAED,GAAIjB,CAAW,SAAf,CAA+B,CAC3BA,CAAW,CAAG,EACjB,CAEDwB,CAAM,WAAGA,CAAH,gBAAa,QAAnB,CAEA,GAAMe,CAAAA,CAAM,WAAGvC,CAAW,CAACwB,CAAD,CAAd,gBAA0B,KAAKxB,WAAL,CAAiBwB,CAAjB,CAAtC,CAEA,GAAIe,CAAM,SAAV,CAA0B,CACtB,KAAMtB,CAAAA,KAAK,gCAAyBO,CAAzB,EACd,CAKD,GAAMgB,CAAAA,CAAa,WAAGxC,CAAW,CAACwC,aAAf,gBAAgC,KAAKxC,WAAL,CAAiBwC,aAApE,CAEAD,CAAM,CAAC,IAAD,CAAOD,CAAP,CAAmBE,CAAa,CAAC,IAAD,CAAOF,CAAP,CAAmBD,CAAnB,CAAhC,CACT,C,kEAYoBI,C,CAAcH,C,CAAYD,C,CAAQ,CACnD,MAAOA,CAAAA,CACV,C,oDAUaI,C,CAAcH,C,CAAYD,C,CAAQ,CAE5C,GAAItB,CAAAA,CAAK,CAAG0B,CAAY,CAAC1B,KAAzB,CAGA,GAAIA,CAAK,CAACuB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC3B,CAAK,CAACuB,CAAD,CAAL,CAAkBK,GAAlB,CAAsBN,CAAtB,EACA,MACH,CACDtB,CAAK,CAACuB,CAAD,CAAL,CAAoBD,CACvB,C,oDASaI,C,CAAcH,C,CAAYD,C,CAAQ,CAG5C,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAI,CAACF,CAAL,CAAc,CACV,KAAM3B,CAAAA,KAAK,sBAAeqB,CAAf,aAA6BD,CAAM,CAACS,EAApC,EACd,CAGD,GAAI/B,CAAAA,CAAK,CAAG0B,CAAY,CAAC1B,KAAzB,CAEA,GAAIA,CAAK,CAACuB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC3B,CAAK,CAACuB,CAAD,CAAL,CAAkBS,MAAlB,CAAyBV,CAAM,CAACS,EAAhC,EACA,MACH,CACD,MAAO/B,CAAAA,CAAK,CAACuB,CAAD,CACf,C,oDASaG,C,CAAcH,C,CAAYD,C,CAAQ,CAG5C,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAI,CAACF,CAAL,CAAc,CACV,KAAM3B,CAAAA,KAAK,sBAAeqB,CAAf,aAA6BD,CAAM,CAACS,EAApC,EACd,CAGD,cAAsC1B,MAAM,CAACC,OAAP,CAAegB,CAAf,CAAtC,gBAA8D,iBAAlDW,CAAkD,MAAvCC,CAAuC,MAC1DL,CAAO,CAACI,CAAD,CAAP,CAAqBC,CACxB,CACJ,C,8CASUR,C,CAAcH,C,CAAYD,C,CAAQ,CAGzC,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAIF,CAAJ,CAAa,CAET,cAAsCxB,MAAM,CAACC,OAAP,CAAegB,CAAf,CAAtC,gBAA8D,iBAAlDW,CAAkD,MAAvCC,CAAuC,MAC1DL,CAAO,CAACI,CAAD,CAAP,CAAqBC,CACxB,CACJ,CALD,IAKO,CAEH,GAAIlC,CAAAA,CAAK,CAAG0B,CAAY,CAAC1B,KAAzB,CACA,GAAIA,CAAK,CAACuB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC3B,CAAK,CAACuB,CAAD,CAAL,CAAkBK,GAAlB,CAAsBN,CAAtB,EACA,MACH,CACDtB,CAAK,CAACuB,CAAD,CAAL,CAAoBD,CACvB,CACJ,C,wDASeI,C,CAAcH,C,CAAYD,C,CAAQ,CAG9C,GAAIO,CAAAA,CAAO,CAAGH,CAAY,CAACI,GAAb,CAAiBP,CAAjB,CAA6BD,CAAM,CAACS,EAApC,CAAd,CACA,GAAIF,CAAJ,CAAa,CAET,cAA0BxB,MAAM,CAACC,OAAP,CAAeuB,CAAf,CAA1B,gBAAmD,iBAAvCI,CAAuC,MAC/C,GAAIX,CAAM,CAACW,CAAD,CAAN,SAAJ,CAAqC,CACjC,MAAOJ,CAAAA,CAAO,CAACI,CAAD,CACjB,CACJ,CAED,cAAsC5B,MAAM,CAACC,OAAP,CAAegB,CAAf,CAAtC,gBAA8D,iBAAlDW,CAAkD,MAAvCC,CAAuC,MAC1DL,CAAO,CAACI,CAAD,CAAP,CAAqBC,CACxB,CACJ,CAXD,IAWO,CAEH,GAAIlC,CAAAA,CAAK,CAAG0B,CAAY,CAAC1B,KAAzB,CACA,GAAIA,CAAK,CAACuB,CAAD,CAAL,UAA6BI,CAAAA,CAAjC,CAA2C,CACvC3B,CAAK,CAACuB,CAAD,CAAL,CAAkBK,GAAlB,CAAsBN,CAAtB,EACA,MACH,CACDtB,CAAK,CAACuB,CAAD,CAAL,CAAoBD,CACvB,CACJ,C,gCAYGF,C,CAAMW,C,CAAI,IACJ/B,CAAAA,CAAK,CAAG,KAAKA,KADT,CAGN6B,CAAO,CAAG7B,CAAK,CAACoB,CAAD,CAHT,CAIV,GAAIS,CAAO,WAAYF,CAAAA,CAAvB,CAAiC,CAC7B,GAAII,CAAE,SAAN,CAAsB,CAClB,KAAM7B,CAAAA,KAAK,0BAAmBkB,CAAnB,kBACd,CACDS,CAAO,CAAG7B,CAAK,CAACoB,CAAD,CAAL,CAAYU,GAAZ,CAAgBC,CAAhB,CACb,CAED,MAAOF,CAAAA,CACV,C,gEAqBmBM,C,CAAO5B,C,CAAME,C,CAAQ2B,C,CAAM,CAE3C,GAAIC,CAAAA,CAAY,CAAG,SAAnB,CAEA,GAAa,IAAT,GAAA9B,CAAJ,CAAmB,CACf,KAAKvB,eAAL,CAAqBsD,IAArB,CAA0B,CACtBC,SAAS,WAAKJ,CAAL,aAAc5B,CAAd,aAAsBE,CAAtB,CADa,CAEtB+B,SAAS,CAAEJ,CAFW,CAGtB3B,MAAM,CAANA,CAHsB,CAA1B,CAKH,CAND,IAMO,CACH4B,CAAY,CAAG5B,CAClB,CAGD,GAAI2B,CAAI,CAACL,EAAL,SAAJ,CAA2B,CACvB,GAAa,IAAT,GAAAxB,CAAJ,CAAmB,CACf,KAAKvB,eAAL,CAAqBsD,IAArB,CAA0B,CACtBC,SAAS,WAAKJ,CAAL,aAAcC,CAAI,CAACL,EAAnB,cAA0BxB,CAA1B,aAAkCE,CAAlC,CADa,CAEtB+B,SAAS,CAAEJ,CAFW,CAGtB3B,MAAM,CAANA,CAHsB,CAA1B,CAKH,CACD,KAAKzB,eAAL,CAAqBsD,IAArB,CAA0B,CACtBC,SAAS,WAAKJ,CAAL,aAAcC,CAAI,CAACL,EAAnB,cAA0BM,CAA1B,CADa,CAEtBG,SAAS,CAAEJ,CAFW,CAGtB3B,MAAM,CAAE4B,CAHc,CAA1B,CAKH,CAGD,KAAKrD,eAAL,CAAqBsD,IAArB,CAA0B,CACtBC,SAAS,WAAKJ,CAAL,aAAcE,CAAd,CADa,CAEtBG,SAAS,CAAEJ,CAFW,CAGtB3B,MAAM,CAAE4B,CAHc,CAA1B,EAOA,KAAKrD,eAAL,CAAqBsD,IAArB,CAA0B,CACtBC,SAAS,gBADa,CAEtBC,SAAS,CAAEJ,CAFW,CAGtB3B,MAAM,CAAE,SAHc,CAA1B,CAKH,C,uDAOgB,YACPgC,CAAY,CAAG,KAAKzD,eADb,CAEb,KAAKA,eAAL,CAAuB,EAAvB,CAGA,KAAKJ,aAAL,CAAmB,CACf6B,MAAM,CAAE,mBADO,CAEfT,KAAK,CAAE,KAAKA,KAFG,CAGf0C,OAAO,CAAE,IAHM,CAAnB,CAIG,KAAK7D,MAJR,EASA4D,CAAY,CAACE,IAAb,CAAkB,SAACC,CAAD,CAAIC,CAAJ,CAAU,SAClBC,CAAO,CAAG,CACZC,OAAO,CAAE,CADG,CAEZC,OAAO,CAAE,CAFG,CAGZC,OAAO,CAAE,CAHG,CADQ,CAMlBC,CAAO,WAAGJ,CAAO,CAACF,CAAC,CAACnC,MAAH,CAAV,gBAAwB,CANb,CAOlB0C,CAAO,WAAGL,CAAO,CAACD,CAAC,CAACpC,MAAH,CAAV,gBAAwB,CAPb,CASxB,GAAIyC,CAAO,GAAKC,CAAhB,CAAyB,CACrB,MAAOP,CAAAA,CAAC,CAACL,SAAF,CAAYa,MAAZ,CAAqBP,CAAC,CAACN,SAAF,CAAYa,MAC3C,CACD,MAAOF,CAAAA,CAAO,CAAGC,CACpB,CAbD,EAgBA,GAAIE,CAAAA,CAAe,CAAG,GAAIC,CAAAA,GAA1B,CAEAb,CAAY,CAACvB,OAAb,CAAqB,SAACpB,CAAD,CAAW,OAEtByD,CAAQ,WAAMzD,CAAK,CAACyC,SAAZ,uBAAyBzC,CAAK,CAAC0C,SAAN,CAAgBT,EAAzC,gBAA+C,CAA/C,CAFc,CAI5B,GAAI,CAACsB,CAAe,CAACG,GAAhB,CAAoBD,CAApB,CAAL,CAAoC,CAChC,CAAI,CAAC3E,aAAL,CAAmB,CACf6B,MAAM,CAAEX,CAAK,CAACyC,SADC,CAEfvC,KAAK,CAAE,CAAI,CAACA,KAFG,CAGf0C,OAAO,CAAE5C,CAAK,CAAC0C,SAHA,CAAnB,CAIG,CAAI,CAAC3D,MAJR,EAMAwE,CAAe,CAACzB,GAAhB,CAAoB2B,CAApB,CACH,CACJ,CAbD,EAgBA,KAAK3E,aAAL,CAAmB,CACf6B,MAAM,CAAE,iBADO,CAEfT,KAAK,CAAE,KAAKA,KAFG,CAGf0C,OAAO,CAAE,IAHM,CAAnB,CAIG,KAAK7D,MAJR,CAKH,C,+BAaCuB,CAAAA,C,YASF,WAAYgB,CAAZ,CAAkBM,CAAlB,CAAgC+B,CAAhC,CAA6C,WACzC,KAAKrC,IAAL,CAAYA,CAAZ,CACA,KAAKM,YAAL,CAAoBA,CAApB,CACA,KAAK+B,WAAL,QAAmBA,CAAnB,WAAmBA,CAAnB,CAAmBA,CAAnB,GACH,C,mCAWGC,C,CAAKnD,C,CAAMoD,C,CAAOC,C,CAAU,CAG5B,GAAI,KAAKlC,YAAL,CAAkB3C,QAAtB,CAAgC,CAC5B,KAAM,IAAImB,CAAAA,KAAJ,iDAAmDK,CAAnD,sBAAoE,KAAKa,IAAzE,MACT,CAGD,GAAIyC,IAAI,CAACC,SAAL,CAAeJ,CAAG,CAACnD,CAAD,CAAlB,IAA8BsD,IAAI,CAACC,SAAL,CAAeH,CAAf,CAAlC,CAAyD,CACrD,QACH,CAED,GAAMlD,CAAAA,CAAM,CAAIiD,CAAG,CAACnD,CAAD,CAAH,SAAD,CAA4B,SAA5B,CAAwC,SAAvD,CAGA,GAAI,KAAKkD,WAAT,CAAsB,CAClB,GAAI1C,KAAK,CAACC,OAAN,CAAc2C,CAAd,CAAJ,CAA0B,CACtBD,CAAG,CAACnD,CAAD,CAAH,CAAY,GAAIoB,CAAAA,CAAJ,CAAapB,CAAb,CAAmB,KAAKmB,YAAxB,EAAsCqC,UAAtC,CAAiDJ,CAAjD,CACf,CAFD,IAEO,CACHD,CAAG,CAACnD,CAAD,CAAH,CAAY,GAAIJ,CAAAA,KAAJ,CAAUwD,CAAV,CAAiB,GAAIvD,CAAAA,CAAJ,CAAYG,CAAZ,CAAkB,KAAKmB,YAAvB,CAAjB,CACf,CACJ,CAND,IAMO,CACHgC,CAAG,CAACnD,CAAD,CAAH,CAAYoD,CACf,CAGD,GAAI,KAAKjC,YAAL,CAAkB1B,KAAlB,SAAJ,CAA2C,CACvC,QACH,CAED,KAAK0B,YAAL,CAAkBsC,mBAAlB,CAAsC,KAAK5C,IAA3C,CAAiDb,CAAjD,CAAuDE,CAAvD,CAA+DmD,CAA/D,EAEA,QACH,C,sDAScF,C,CAAKnD,C,CAAM,CAEtB,GAAI,KAAKmB,YAAL,CAAkB3C,QAAtB,CAAgC,CAC5B,KAAM,IAAImB,CAAAA,KAAJ,iDAAmDK,CAAnD,gBAA8D,KAAKa,IAAnE,MACT,CACD,GAAIb,CAAI,GAAImD,CAAAA,CAAZ,CAAiB,CAEb,MAAOA,CAAAA,CAAG,CAACnD,CAAD,CAAV,CAEA,KAAKmB,YAAL,CAAkBsC,mBAAlB,CAAsC,KAAK5C,IAA3C,CAAiDb,CAAjD,CAAuD,SAAvD,CAAkEmD,CAAlE,CACH,CACD,QACH,C,gBAeC/B,C,+BASF,WAAYP,CAAZ,CAAkBM,CAAlB,CAAgCuC,CAAhC,CAA0C,iBAEtC,cAAMA,CAAN,EACA,EAAK7C,IAAL,CAAYA,CAAZ,CACA,EAAKM,YAAL,CAAoBA,CAApB,CAJsC,QAKzC,C,mCAYGwC,C,CAAKP,C,CAAO,CAGZ,GAAI,KAAKjC,YAAL,CAAkB3C,QAAtB,CAAgC,CAC5B,KAAM,IAAImB,CAAAA,KAAJ,iDAAmDgE,CAAnD,sBAAmE,KAAK9C,IAAxE,MACT,CAGD8C,CAAG,CAAG,KAAKC,YAAL,CAAkBD,CAAlB,CAAN,CAEA,KAAKE,UAAL,CAAgBT,CAAhB,EAEA,GAAIO,CAAG,SAAH,EAA6B,IAAR,GAAAA,CAAzB,CAAuC,CACnC,KAAMhE,CAAAA,KAAK,CAAC,8CAAD,CACd,CAGD,GAAI,KAAKiE,YAAL,CAAkBR,CAAK,CAAC5B,EAAxB,IAAgCmC,CAApC,CAAyC,CACrC,KAAM,IAAIhE,CAAAA,KAAJ,wBAA0B,KAAKkB,IAA/B,8BAAwDuC,CAAK,CAAC5B,EAA9D,uBAA8EmC,CAA9E,eACT,CAnBW,GAqBNzD,CAAAA,CAAM,CAAG,uCAAWyD,CAAX,EAAmB,SAAnB,CAA+B,SArBlC,CAwBNG,CAAM,wCAAaH,CAAb,CAAkB,GAAI/D,CAAAA,KAAJ,CAAUwD,CAAV,CAAiB,GAAIvD,CAAAA,CAAJ,CAAY,KAAKgB,IAAjB,CAAuB,KAAKM,YAA5B,CAAjB,CAAlB,CAxBA,CA2BZ,GAAI,KAAKA,YAAL,CAAkB1B,KAAlB,SAAJ,CAA2C,CACvC,MAAOqE,CAAAA,CACV,CAED,KAAK3C,YAAL,CAAkBsC,mBAAlB,CAAsC,KAAK5C,IAA3C,CAAiD,IAAjD,CAAuDX,CAAvD,wCAAyEyD,CAAzE,GAEA,MAAOG,CAAAA,CACV,C,8CAWUV,C,CAAO,CACd,GAAsB,QAAlB,MAAQA,CAAR,GAAwC,IAAV,GAAAA,CAAlC,CAAkD,CAC9C,KAAMzD,CAAAA,KAAK,CAAC,sCAAD,CACd,CAED,GAAIyD,CAAK,CAAC5B,EAAN,SAAJ,CAA4B,CACxB,KAAM7B,CAAAA,KAAK,CAAC,4DAAD,CACd,CACJ,C,kDAWYgE,C,CAAK,CACd,MAAO,CAAOA,CAAP,KAAYI,OAAZ,EACV,C,gCAUGX,C,CAAO,CACP,KAAKS,UAAL,CAAgBT,CAAhB,EACA,MAAO,MAAKY,GAAL,CAASZ,CAAK,CAAC5B,EAAf,CAAmB4B,CAAnB,CACV,C,gCAQGO,C,CAAK,CACL,8CAAiB,KAAKC,YAAL,CAAkBD,CAAlB,CAAjB,CACH,C,gCAQGA,C,CAAK,CACL,8CAAiB,KAAKC,YAAL,CAAkBD,CAAlB,CAAjB,CACH,C,uCAQMA,C,CAAK,CAERA,CAAG,CAAG,KAAKC,YAAL,CAAkBD,CAAlB,CAAN,CAGA,GAAI,KAAKxC,YAAL,CAAkB3C,QAAtB,CAAgC,CAC5B,KAAM,IAAImB,CAAAA,KAAJ,iDAAmDgE,CAAnD,sBAAmE,KAAK9C,IAAxE,MACT,CAPO,GASFoD,CAAAA,CAAQ,wCAAaN,CAAb,CATN,CAWFG,CAAM,2CAAgBH,CAAhB,CAXJ,CAYR,GAAI,CAACG,CAAL,CAAa,CACT,MAAOA,CAAAA,CACV,CAED,KAAK3C,YAAL,CAAkBsC,mBAAlB,CAAsC,KAAK5C,IAA3C,CAAiD,IAAjD,CAAuD,SAAvD,CAAkEoD,CAAlE,EAEA,MAAOH,CAAAA,CACV,C,uCAWQ,CACL,GAAIA,CAAAA,CAAM,CAAG,EAAb,CACA,KAAKnD,OAAL,CAAa,SAACyC,CAAD,CAAW,CACpBU,CAAM,CAAC/B,IAAP,CAAYqB,CAAZ,CACH,CAFD,EAGA,MAAOU,CAAAA,CACV,C,8CAWUI,C,CAAQ,YACfA,CAAM,CAACvD,OAAP,CAAe,SAACkB,CAAD,CAAU,CACrB,CAAI,CAACgC,UAAL,CAAgBhC,CAAhB,EADqB,GAEjB8B,CAAAA,CAAG,CAAG9B,CAAI,CAACL,EAFM,CAGjB2C,CAAQ,CAAG,GAAIvE,CAAAA,KAAJ,CAAUiC,CAAV,CAAgB,GAAIhC,CAAAA,CAAJ,CAAY,CAAI,CAACgB,IAAjB,CAAuB,CAAI,CAACM,YAA5B,CAAhB,CAHM,CAIrB,CAAI,CAAC6C,GAAL,CAASL,CAAT,CAAcQ,CAAd,CACH,CALD,EAMA,MAAO,KACV,C,gBA5LkBC,G","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 * Reactive simple state manager.\n *\n * The state manager contains the state data, trigger update events and\n * can lock and unlock the state data.\n *\n * This file contains the three main elements of the state manager:\n * - State manager: the public class to alter the state, dispatch events and process update messages.\n * - Proxy handler: a private class to keep track of the state object changes.\n * - StateMap class: a private class extending Map class that triggers event when a state list is modifed.\n *\n * @module core/local/reactive/stateManager\n * @class core/local/reactive/stateManager\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * State manager class.\n *\n * This class handle the reactive state and ensure only valid mutations can modify the state.\n * It also provide methods to apply batch state update messages (see processUpdates function doc\n * for more details on update messages).\n *\n * Implementing a deep state manager is complex and will require many frontend resources. To keep\n * the state fast and simple, the state can ONLY store two kind of data:\n * - Object with attributes\n * - Sets of objects with id attributes.\n *\n * This is an example of a valid state:\n *\n * {\n * course: {\n * name: 'course name',\n * shortname: 'courseshort',\n * sectionlist: [21, 34]\n * },\n * sections: [\n * {id: 21, name: 'Topic 1', visible: true},\n * {id: 34, name: 'Topic 2', visible: false,\n * ],\n * }\n *\n * The following cases are NOT allowed at a state ROOT level (throws an exception if they are assigned):\n * - Simple values (strings, boolean...).\n * - Arrays of simple values.\n * - Array of objects without ID attribute (all arrays will be converted to maps and requires an ID).\n *\n * Thanks to those limitations it can simplify the state update messages and the event names. If You\n * need to store simple data, just group them in an object.\n *\n * To grant any state change triggers the proper events, the class uses two private structures:\n * - proxy handler: any object stored in the state is proxied using this class.\n * - StateMap class: any object set in the state will be converted to StateMap using the\n * objects id attribute.\n */\nexport default class StateManager {\n\n /**\n * Create a basic reactive state store.\n *\n * The state manager is meant to work with native JS events. To ensure each reactive module can use\n * it in its own way, the parent element must provide a valid event dispatcher function and an optional\n * DOM element to anchor the event.\n *\n * @param {function} dispatchEvent the function to dispatch the custom event when the state changes.\n * @param {element} target the state changed custom event target (document if none provided)\n */\n constructor(dispatchEvent, target) {\n\n // The dispatch event function.\n /** @package */\n this.dispatchEvent = dispatchEvent;\n\n // The DOM container to trigger events.\n /** @package */\n this.target = target ?? document;\n\n // State can be altered freely until initial state is set.\n /** @package */\n this.readonly = false;\n\n // List of state changes pending to be published as events.\n /** @package */\n this.eventsToPublish = [];\n\n // The update state types functions.\n /** @package */\n this.updateTypes = {\n \"create\": this.defaultCreate.bind(this),\n \"update\": this.defaultUpdate.bind(this),\n \"delete\": this.defaultDelete.bind(this),\n \"put\": this.defaultPut.bind(this),\n \"override\": this.defaultOverride.bind(this),\n \"prepareFields\": this.defaultPrepareFields.bind(this),\n };\n\n // The state_loaded event is special because it only happens one but all components\n // may react to that state, even if they are registered after the setIinitialState.\n // For these reason we use a promise for that event.\n this.initialPromise = new Promise((resolve) => {\n const initialStateDone = (event) => {\n resolve(event.detail.state);\n };\n this.target.addEventListener('state:loaded', initialStateDone);\n });\n }\n\n /**\n * Loads the initial state.\n *\n * Note this method will trigger a state changed event with \"state:loaded\" actionname.\n *\n * The state mode will be set to read only when the initial state is loaded.\n *\n * @param {object} initialState\n */\n setInitialState(initialState) {\n\n if (this.state !== undefined) {\n throw Error('Initial state can only be initialized ones');\n }\n\n // Create the state object.\n const state = new Proxy({}, new Handler('state', this, true));\n for (const [prop, propValue] of Object.entries(initialState)) {\n state[prop] = propValue;\n }\n this.state = state;\n\n // When the state is loaded we can lock it to prevent illegal changes.\n this.readonly = true;\n\n this.dispatchEvent({\n action: 'state:loaded',\n state: this.state,\n }, this.target);\n }\n\n /**\n * Generate a promise that will be resolved when the initial state is loaded.\n *\n * In most cases the final state will be loaded using an ajax call. This is the reason\n * why states manager are created unlocked and won't be reactive until the initial state is set.\n *\n * @return {Promise} the resulting promise\n */\n getInitialPromise() {\n return this.initialPromise;\n }\n\n /**\n * Locks or unlocks the state to prevent illegal updates.\n *\n * Mutations use this method to modify the state. Once the state is updated, they must\n * block again the state.\n *\n * All changes done while the state is writable will be registered using registerStateAction.\n * When the state is set again to read only the method will trigger _publishEvents to communicate\n * changes to all watchers.\n *\n * @param {bool} readonly if the state is in read only mode enabled\n */\n setReadOnly(readonly) {\n\n this.readonly = readonly;\n\n // When the state is in readonly again is time to publish all pending events.\n if (this.readonly) {\n this._publishEvents();\n }\n }\n\n /**\n * Add methods to process update state messages.\n *\n * The state manager provide a default update, create and delete methods. However,\n * some applications may require to override the default methods or even add new ones\n * like \"refresh\" or \"error\".\n *\n * @param {Object} newFunctions the new update types functions.\n */\n addUpdateTypes(newFunctions) {\n for (const [updateType, updateFunction] of Object.entries(newFunctions)) {\n if (typeof updateFunction === 'function') {\n this.updateTypes[updateType] = updateFunction.bind(newFunctions);\n }\n }\n }\n\n /**\n * Process a state updates array and do all the necessary changes.\n *\n * Note this method unlocks the state while it is executing and relocks it\n * when finishes.\n *\n * @param {array} updates\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdates(updates, updateTypes) {\n if (!Array.isArray(updates)) {\n throw Error('State updates must be an array');\n }\n this.setReadOnly(false);\n updates.forEach((update) => {\n if (update.name === undefined) {\n throw Error('Missing state update name');\n }\n this.processUpdate(\n update.name,\n update.action,\n update.fields,\n updateTypes\n );\n });\n this.setReadOnly(true);\n }\n\n /**\n * Process a single state update.\n *\n * Note this method will not lock or unlock the state by itself.\n *\n * @param {string} updateName the state element to update\n * @param {string} action to action to perform\n * @param {object} fields the new data\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdate(updateName, action, fields, updateTypes) {\n\n if (!fields) {\n throw Error('Missing state update fields');\n }\n\n if (updateTypes === undefined) {\n updateTypes = {};\n }\n\n action = action ?? 'update';\n\n const method = updateTypes[action] ?? this.updateTypes[action];\n\n if (method === undefined) {\n throw Error(`Unkown update action ${action}`);\n }\n\n // Some state data may require some cooking before sending to the\n // state. Reactive instances can overrdide the default fieldDefaults\n // method to add extra logic to all updates.\n const prepareFields = updateTypes.prepareFields ?? this.updateTypes.prepareFields;\n\n method(this, updateName, prepareFields(this, updateName, fields));\n }\n\n /**\n * Prepare fields for processing.\n *\n * This method is used to add default values or calculations from the frontend side.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n defaultPrepareFields(stateManager, updateName, fields) {\n return fields;\n }\n\n\n /**\n * Process a create state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultCreate(stateManager, updateName, fields) {\n\n let state = stateManager.state;\n\n // Create can be applied only to lists, not to objects.\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n\n /**\n * Process a delete state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultDelete(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Process deletion.\n let state = stateManager.state;\n\n if (state[updateName] instanceof StateMap) {\n state[updateName].delete(fields.id);\n return;\n }\n delete state[updateName];\n }\n\n /**\n * Process a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultUpdate(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Execute updates.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n }\n\n /**\n * Process a put state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultPut(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Update attributes.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create new object.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Process an override state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultOverride(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Remove any unnecessary fields.\n for (const [fieldName] of Object.entries(current)) {\n if (fields[fieldName] === undefined) {\n delete current[fieldName];\n }\n }\n // Update field.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create the element if not exists.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Get an element from the state or form an alternative state object.\n *\n * The altstate param is used by external update functions that gets the current\n * state as param.\n *\n * @param {String} name the state object name\n * @param {*} id and object id for state maps.\n * @return {Object|undefined} the state object found\n */\n get(name, id) {\n const state = this.state;\n\n let current = state[name];\n if (current instanceof StateMap) {\n if (id === undefined) {\n throw Error(`Missing id for ${name} state update`);\n }\n current = state[name].get(id);\n }\n\n return current;\n }\n\n /**\n * Register a state modification and generate the necessary events.\n *\n * This method is used mainly by proxy helpers to dispatch state change event.\n * However, mutations can use it to inform components about non reactive changes\n * in the state (only the two first levels of the state are reactive).\n *\n * Each action can produce several events:\n * - The specific attribute updated, created or deleter (example: \"cm.visible:updated\")\n * - The general state object updated, created or deleted (example: \"cm:updated\")\n * - If the element has an ID attribute, the specific event with id (example: \"cm[42].visible:updated\")\n * - If the element has an ID attribute, the general event with id (example: \"cm[42]:updated\")\n * - A generic state update event \"state:update\"\n *\n * @param {string} field the affected state field name\n * @param {string|null} prop the affecter field property (null if affect the full object)\n * @param {string} action the action done (created/updated/deleted)\n * @param {*} data the affected data\n */\n registerStateAction(field, prop, action, data) {\n\n let parentAction = 'updated';\n\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}.${prop}:${action}`,\n eventData: data,\n action,\n });\n } else {\n parentAction = action;\n }\n\n // Trigger extra events if the element has an ID attribute.\n if (data.id !== undefined) {\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}].${prop}:${action}`,\n eventData: data,\n action,\n });\n }\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}]:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n }\n\n // Register the general change.\n this.eventsToPublish.push({\n eventName: `${field}:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n\n // Register state updated event.\n this.eventsToPublish.push({\n eventName: `state:updated`,\n eventData: data,\n action: 'updated',\n });\n }\n\n /**\n * Internal method to publish events.\n *\n * This is a private method, it will be invoked when the state is set back to read only mode.\n */\n _publishEvents() {\n const fieldChanges = this.eventsToPublish;\n this.eventsToPublish = [];\n\n // Dispatch a transaction start event.\n this.dispatchEvent({\n action: 'transaction:start',\n state: this.state,\n element: null,\n }, this.target);\n\n // State changes can be registered in any order. However it will avoid many\n // components errors if they are sorted to have creations-updates-deletes in case\n // some component needs to create or destroy DOM elements before updating them.\n fieldChanges.sort((a, b) => {\n const weights = {\n created: 0,\n updated: 1,\n deleted: 2,\n };\n const aweight = weights[a.action] ?? 0;\n const bweight = weights[b.action] ?? 0;\n // In case both have the same weight, the eventName length decide.\n if (aweight === bweight) {\n return a.eventName.length - b.eventName.length;\n }\n return aweight - bweight;\n });\n\n // List of the published events to prevent redundancies.\n let publishedEvents = new Set();\n\n fieldChanges.forEach((event) => {\n\n const eventkey = `${event.eventName}.${event.eventData.id ?? 0}`;\n\n if (!publishedEvents.has(eventkey)) {\n this.dispatchEvent({\n action: event.eventName,\n state: this.state,\n element: event.eventData\n }, this.target);\n\n publishedEvents.add(eventkey);\n }\n });\n\n // Dispatch a transaction end event.\n this.dispatchEvent({\n action: 'transaction:end',\n state: this.state,\n element: null,\n }, this.target);\n }\n}\n\n// Proxy helpers.\n\n/**\n * The proxy handler.\n *\n * This class will inform any value change directly to the state manager.\n *\n * The proxied variable will throw an error if it is altered when the state manager is\n * in read only mode.\n */\nclass Handler {\n\n /**\n * Class constructor.\n *\n * @param {string} name the variable name used for identify triggered actions\n * @param {StateManager} stateManager the state manager object\n * @param {boolean} proxyValues if new values must be proxied (used only at state root level)\n */\n constructor(name, stateManager, proxyValues) {\n this.name = name;\n this.stateManager = stateManager;\n this.proxyValues = proxyValues ?? false;\n }\n\n /**\n * Set trap to trigger events when the state changes.\n *\n * @param {object} obj the source object (not proxied)\n * @param {string} prop the attribute to set\n * @param {*} value the value to save\n * @param {*} receiver the proxied element to be attached to events\n * @returns {boolean} if the value is set\n */\n set(obj, prop, value, receiver) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${prop} value in ${this.name}.`);\n }\n\n // Check any data change.\n if (JSON.stringify(obj[prop]) === JSON.stringify(value)) {\n return true;\n }\n\n const action = (obj[prop] !== undefined) ? 'updated' : 'created';\n\n // Proxy value if necessary (used at state root level).\n if (this.proxyValues) {\n if (Array.isArray(value)) {\n obj[prop] = new StateMap(prop, this.stateManager).loadValues(value);\n } else {\n obj[prop] = new Proxy(value, new Handler(prop, this.stateManager));\n }\n } else {\n obj[prop] = value;\n }\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return true;\n }\n\n this.stateManager.registerStateAction(this.name, prop, action, receiver);\n\n return true;\n }\n\n /**\n * Delete property trap to trigger state change events.\n *\n * @param {*} obj the affected object (not proxied)\n * @param {*} prop the prop to delete\n * @returns {boolean} if prop is deleted\n */\n deleteProperty(obj, prop) {\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to delete ${prop} in ${this.name}.`);\n }\n if (prop in obj) {\n\n delete obj[prop];\n\n this.stateManager.registerStateAction(this.name, prop, 'deleted', obj);\n }\n return true;\n }\n}\n\n/**\n * Class to add events dispatching to the JS Map class.\n *\n * When the state has a list of objects (with IDs) it will be converted into a StateMap.\n * StateMap is used almost in the same way as a regular JS map. Because all elements have an\n * id attribute, it has some specific methods:\n * - add: a convenient method to add an element without specifying the key (\"id\" attribute will be used as a key).\n * - loadValues: to add many elements at once wihout specifying keys (\"id\" attribute will be used).\n *\n * Apart, the main difference between regular Map and MapState is that this one will inform any change to the\n * state manager.\n */\nclass StateMap extends Map {\n\n /**\n * Create a reactive Map.\n *\n * @param {string} name the property name\n * @param {StateManager} stateManager the state manager\n * @param {iterable} iterable an iterable object to create the Map\n */\n constructor(name, stateManager, iterable) {\n // We don't have any \"this\" until be call super.\n super(iterable);\n this.name = name;\n this.stateManager = stateManager;\n }\n\n /**\n * Set an element into the map.\n *\n * Each value needs it's own id attribute. Objects without id will be rejected.\n * The function will throw an error if the value id and the key are not the same.\n *\n * @param {*} key the key to store\n * @param {*} value the value to store\n * @returns {Map} the resulting Map object\n */\n set(key, value) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n // Normalize keys as string to prevent json decoding errors.\n key = this.normalizeKey(key);\n\n this.checkValue(value);\n\n if (key === undefined || key === null) {\n throw Error('State lists keys cannot be null or undefined');\n }\n\n // ID is mandatory and should be the same as the key.\n if (this.normalizeKey(value.id) !== key) {\n throw new Error(`State error: ${this.name} list element ID (${value.id}) and key (${key}) mismatch`);\n }\n\n const action = (super.has(key)) ? 'updated' : 'created';\n\n // Save proxied data into the list.\n const result = super.set(key, new Proxy(value, new Handler(this.name, this.stateManager)));\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, action, super.get(key));\n\n return result;\n }\n\n /**\n * Check if a value is valid to be stored in a a State List.\n *\n * Only objects with id attribute can be stored in State lists.\n *\n * This method throws an error if the value is not valid.\n *\n * @param {object} value (with ID)\n */\n checkValue(value) {\n if (!typeof value === 'object' && value !== null) {\n throw Error('State lists can contain objects only');\n }\n\n if (value.id === undefined) {\n throw Error('State lists elements must contain at least an id attribute');\n }\n }\n\n /**\n * Return a normalized key value for state map.\n *\n * Regular maps uses strict key comparissons but state maps are indexed by ID.JSON conversions\n * and webservices sometimes do unexpected types conversions so we convert any integer key to string.\n *\n * @param {*} key the provided key\n * @returns {string}\n */\n normalizeKey(key) {\n return String(key).valueOf();\n }\n\n /**\n * Insert a new element int a list.\n *\n * Each value needs it's own id attribute. Objects withouts id will be rejected.\n *\n * @param {object} value the value to add (needs an id attribute)\n * @returns {Map} the resulting Map object\n */\n add(value) {\n this.checkValue(value);\n return this.set(value.id, value);\n }\n\n /**\n * Return a state map element.\n *\n * @param {*} key the element id\n * @return {Object}\n */\n get(key) {\n return super.get(this.normalizeKey(key));\n }\n\n /**\n * Check whether an element with the specified key exists or not.\n *\n * @param {*} key the key to find\n * @return {boolean}\n */\n has(key) {\n return super.has(this.normalizeKey(key));\n }\n\n /**\n * Delete an element from the map.\n *\n * @param {*} key\n * @returns {boolean}\n */\n delete(key) {\n // State maps uses only string keys to avoid strict comparisons.\n key = this.normalizeKey(key);\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n const previous = super.get(key);\n\n const result = super.delete(key);\n if (!result) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, 'deleted', previous);\n\n return result;\n }\n\n /**\n * Return a suitable structure for JSON conversion.\n *\n * This function is needed because new values are compared in JSON. StateMap has Private\n * attributes which cannot be stringified (like this.stateManager which will produce an\n * infinite recursivity).\n *\n * @returns {array}\n */\n toJSON() {\n let result = [];\n this.forEach((value) => {\n result.push(value);\n });\n return result;\n }\n\n /**\n * Insert a full list of values using the id attributes as keys.\n *\n * This method is used mainly to initialize the list. Note each element is indexed by its \"id\" attribute.\n * This is a basic restriction of StateMap. All elements need an id attribute, otherwise it won't be saved.\n *\n * @param {iterable} values the values to load\n * @returns {StateMap} return the this value\n */\n loadValues(values) {\n values.forEach((data) => {\n this.checkValue(data);\n let key = data.id;\n let newvalue = new Proxy(data, new Handler(this.name, this.stateManager));\n this.set(key, newvalue);\n });\n return this;\n }\n}\n"],"file":"statemanager.min.js"}
\ No newline at end of file
diff --git a/lib/amd/src/local/reactive/basecomponent.js b/lib/amd/src/local/reactive/basecomponent.js
index c56b76237fd..0e665bd64b4 100644
--- a/lib/amd/src/local/reactive/basecomponent.js
+++ b/lib/amd/src/local/reactive/basecomponent.js
@@ -431,4 +431,39 @@ export default class {
component.reactive = this.reactive;
this.reactive.registerComponent(component);
}
+
+ /**
+ * Set the lock value and locks or unlocks the element.
+ *
+ * @param {boolean} locked the new locked value
+ */
+ set locked(locked) {
+ this.element.dataset.locked = locked ?? false;
+ if (locked) {
+ // Disable interactions.
+ this.element.style.pointerEvents = 'none';
+ this.element.style.userSelect = 'none';
+ // Check if it is draggable.
+ if (this.element.hasAttribute('draggable')) {
+ this.element.setAttribute('draggable', false);
+ }
+ } else {
+ // Reanable interactions.
+ this.element.style.pointerEvents = null;
+ this.element.style.userSelect = null;
+ // Check if it was draggable.
+ if (this.element.hasAttribute('draggable')) {
+ this.element.setAttribute('draggable', true);
+ }
+ }
+ }
+
+ /**
+ * Get the current locket value from the element.
+ *
+ * @return {boolean}
+ */
+ get locked() {
+ return this.element.dataset.locked ?? false;
+ }
}
diff --git a/lib/amd/src/local/reactive/reactive.js b/lib/amd/src/local/reactive/reactive.js
index 3460838b26d..f772c724b4f 100644
--- a/lib/amd/src/local/reactive/reactive.js
+++ b/lib/amd/src/local/reactive/reactive.js
@@ -151,6 +151,11 @@ export default class {
* @param {Object} newFunctions an object with new mutation functions.
*/
addMutations(newFunctions) {
+ // Mutations can provide an init method to do some setup in the statemanager.
+ if (newFunctions.init !== undefined) {
+ newFunctions.init(this.stateManager);
+ }
+ // Save all mutations.
for (const [mutation, mutationFunction] of Object.entries(newFunctions)) {
this.mutations[mutation] = mutationFunction.bind(newFunctions);
}
@@ -166,6 +171,10 @@ export default class {
*/
setMutations(manager) {
this.mutations = manager;
+ // Mutations can provide an init method to do some setup in the statemanager.
+ if (manager.init !== undefined) {
+ manager.init(this.stateManager);
+ }
}
/**
diff --git a/lib/amd/src/local/reactive/statemanager.js b/lib/amd/src/local/reactive/statemanager.js
index bdbf15dde96..8f22e0371ef 100644
--- a/lib/amd/src/local/reactive/statemanager.js
+++ b/lib/amd/src/local/reactive/statemanager.js
@@ -107,6 +107,7 @@ export default class StateManager {
"delete": this.defaultDelete.bind(this),
"put": this.defaultPut.bind(this),
"override": this.defaultOverride.bind(this),
+ "prepareFields": this.defaultPrepareFields.bind(this),
};
// The state_loaded event is special because it only happens one but all components
@@ -258,9 +259,29 @@ export default class StateManager {
throw Error(`Unkown update action ${action}`);
}
- method(this, updateName, fields);
+ // Some state data may require some cooking before sending to the
+ // state. Reactive instances can overrdide the default fieldDefaults
+ // method to add extra logic to all updates.
+ const prepareFields = updateTypes.prepareFields ?? this.updateTypes.prepareFields;
+
+ method(this, updateName, prepareFields(this, updateName, fields));
}
+ /**
+ * Prepare fields for processing.
+ *
+ * This method is used to add default values or calculations from the frontend side.
+ *
+ * @param {Object} stateManager the state manager
+ * @param {String} updateName the state element to update
+ * @param {Object} fields the new data
+ * @returns {Object} final fields data
+ */
+ defaultPrepareFields(stateManager, updateName, fields) {
+ return fields;
+ }
+
+
/**
* Process a create state message.
*
diff --git a/theme/boost/scss/moodle/course.scss b/theme/boost/scss/moodle/course.scss
index 88bc99b9717..721b1e1310e 100644
--- a/theme/boost/scss/moodle/course.scss
+++ b/theme/boost/scss/moodle/course.scss
@@ -1,4 +1,7 @@
/* course.less */
+
+$course-editinprogress-bg: $gray-100 !default;
+
/* COURSE CONTENT */
.section_add_menus {
@@ -186,6 +189,25 @@ body:not(.editing) .sitetopic ul.section {
}
}
}
+ .editinprogress {
+ opacity: .7;
+ animation: editinprogress-pulse 2s infinite linear;
+ }
+}
+
+@keyframes editinprogress-pulse {
+ 0% {
+ background-color: $course-editinprogress-bg;
+ filter: grayscale(60%);
+ }
+ 50% {
+ background-color: darken($course-editinprogress-bg, 5%);
+ filter: grayscale(40%);
+ }
+ 100% {
+ background-color: $course-editinprogress-bg;
+ filter: grayscale(60%);
+ }
}
.editing_show + .editing_assign,
diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css
index af11a7dcd5e..06b94d7d54d 100644
--- a/theme/boost/style/moodle.css
+++ b/theme/boost/style/moodle.css
@@ -13656,6 +13656,21 @@ body:not(.editing) .sitetopic ul.section {
*/
padding-left: 2rem; }
+.editing .editinprogress {
+ opacity: .7;
+ animation: editinprogress-pulse 2s infinite linear; }
+
+@keyframes editinprogress-pulse {
+ 0% {
+ background-color: #f8f9fa;
+ filter: grayscale(60%); }
+ 50% {
+ background-color: #e9ecef;
+ filter: grayscale(40%); }
+ 100% {
+ background-color: #f8f9fa;
+ filter: grayscale(60%); } }
+
.editing_show + .editing_assign,
.editing_hide + .editing_assign {
margin-left: 20px; }
diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css
index 145a0291fa6..c93559699bc 100644
--- a/theme/classic/style/moodle.css
+++ b/theme/classic/style/moodle.css
@@ -13656,6 +13656,21 @@ body:not(.editing) .sitetopic ul.section {
*/
padding-left: 2rem; }
+.editing .editinprogress {
+ opacity: .7;
+ animation: editinprogress-pulse 2s infinite linear; }
+
+@keyframes editinprogress-pulse {
+ 0% {
+ background-color: #f8f9fa;
+ filter: grayscale(60%); }
+ 50% {
+ background-color: #e9ecef;
+ filter: grayscale(40%); }
+ 100% {
+ background-color: #f8f9fa;
+ filter: grayscale(60%); } }
+
.editing_show + .editing_assign,
.editing_hide + .editing_assign {
margin-left: 20px; }